<?xml version="1.0" encoding="UTF-8"?>
<spec xmlns="https://vibevm.org/spec/1">
  <title id="root">PROP-041: the vibevm settings UI — the `vibe prefs` TUI surface (`vibe-settings-ui`)</title>
  <status stage="impl" state="done" comment="C 2026-07-25: SETTINGS-UI-PLAN executed; the prefs TUI ships under commands/prefs/tui/ and PROP-037&apos;s F4 opens it by name; fact grain 2026-07-24"/>
  <p p="1"><fact id="status-line" status="impl/done">**Status: IMPLEMENTED** (requirements authored 2026-07-16, owner-commissioned; the
SETTINGS-UI-PLAN executed against them, verified against the tree 2026-07-25 by the spec-actualization
campaign — `commands/prefs/tui/**` ships the form lifecycle, the provenance edit and the controls, and
PROP-037's F4 opens it by name). The **contract** for the TUI surface
that lets a user **view and edit** the application/user preferences of [PROP-040](PROP-040-settings.xml):
a settings tree, per-type edit forms, a provenance ("where does this value come from?") view, validation
feedback, and search — built on the `vibe tree` TUI (PROP-037) and drivable headless (AIUI-ready).</fact></p>
  <p p="2"><fact id="related" status="spec/done">**Related:** [PROP-040](PROP-040-settings.xml) (the settings system — the data layer this renders);
[PROP-037](../vibe-cli/PROP-037-tree-tui.xml) (the `vibe tree` TUI — component library + theme this is
built on); the visual-language design-doc
[`vibevm/vibespecs/design/tui-visual-language.xml`](../../design/tui-visual-language.xml); the clean-room study
[`legacy-spec/research/settings-system-vscode-idea.md`](../../../legacy-spec/research/settings-system-vscode-idea.md) (§3.7
Configurable EP, §3.9 introspection); the `addressable-specs` and `managed-blocks` flows.</fact></p>
  <list ordered="false" p="3">
    <item><fact id="granular-directive" status="spec/done">This contract is deliberately **granular and addressable** (owner directive): every feature is its own
  `{#anchor}` REQ.</fact></item>
    <item><fact id="surface-ownership" status="spec/done">It owns the **surface**; the data layer, resolver, and inspect-API are PROP-040's.</fact></item>
  </list>
  <section id="overview" title="1. Overview — a settings surface, not a settings engine">
    <p p="4"><fact id="surface-not-engine" status="spec/done">REQ {#surface-not-engine}. The settings UI is a **Surface** over `vibe-settings` (PROP-040): it renders
`ResolvedPrefs` and captures edits, but owns **no** preference logic, schema, or merge — those are
PROP-040's. The UI reads through `inspect`/`get`/`list` and writes through `set` (PROP-040 §5). This is
the data/surface split that lets the same prefs be driven headless by the AIUI unchanged.</fact></p>
    <p p="5"><fact id="built-on-tree-tui" status="spec/done">REQ {#built-on-tree-tui}. The UI is built from the PROP-037 component library (`ui::` facade — Window,
Menu, Form fields, Card) and the visual-language `Theme` (PROP-037 §2.2 /
[`vibevm/vibespecs/design/tui-visual-language.xml`](../../design/tui-visual-language.xml)). It does **not** re-invent
widgets; it composes the TUI's components. It is built in **Step 4** of the meta-plan, after the TUI
itself (Step 3) lands.</fact></p>
    <p p="6"><fact id="english-default-i18n-ready" status="spec/done">REQ {#english-default-i18n-ready}. UI strings are English and live behind the `vibe-actions` i18n
indirection (PROP-039 §8), English the only mandatory-complete locale.</fact></p>
  </section>
  <section id="registry" title="2. The page registry — Configurable-EP-style">
    <p p="7"><fact id="declarative-pages" status="spec/done">REQ {#declarative-pages} (Δ-15; `vibevm/vibespecs/research` §3.7). Settings are organised into **pages** declared
in a registry (the IntelliJ `Configurable` EP, clean-room — *not* its code). A page declaration carries:</fact></p>
    <list ordered="false" p="8">
      <item><fact id="page-id" status="spec/done">`id` — a stable, non-localised identifier (the join-key);</fact></item>
      <item><fact id="page-parent" status="spec/done">`parent_id` / `group_id` — for the tree hierarchy (unresolved groups land in "Other");</fact></item>
      <item><fact id="page-display" status="spec/done">`display_name` (localisable) + `description`;</fact></item>
      <item><fact id="page-weight" status="spec/done">`group_weight` — ordering within a parent;</fact></item>
      <item><fact id="page-scope-flag" status="spec/done">`scope_flag` — application-level vs project-level page (the IntelliJ `nonDefaultProject` analogue);</fact></item>
      <item><fact id="page-lazy-body" status="spec/done">a **lazy** page body (created on first open) — the registry metadata is cheap so the whole tree
  renders without constructing every form (IntelliJ §3.7).</fact></item>
    </list>
    <p p="9"><fact id="registry-is-introspectable" status="spec/done">REQ {#registry-is-introspectable}. The page registry is the **enumerable** source for both the settings
tree (§3) and the search index (§7): adding a page means registering a declaration, which then appears
in the tree, search, and the AIUI with no further wiring (the IntelliJ `searchableOptions.xml`
build-time index is the model; vibevm builds it from the registry at runtime).</fact></p>
    <p p="10"><fact id="stable-id-law" status="spec/done">REQ {#stable-id-law}. A page `id` is immutable once published (the `qualified-naming` rule): a rename
is a new id with the old one retired as a tombstone/alias, so a bookmark/link/search-result never
silently retargets.</fact></p>
  </section>
  <section id="tree-widget" title="3. The settings tree widget">
    <p p="11"><fact id="tree-widget-req" status="spec/done" action="continue" actionstage="doc" audience="user">REQ {#tree-widget}. The left pane is a **tree of pages** (groups → pages) rendered through the PROP-037
`Tree` widget (so it inherits its glyphs, theme, fold, keyboard model — no bespoke renderer). `↑`/`↓`
move, `←`/`→` fold/expand, `Enter` opens the focused page's form (§4) in the right pane.</fact></p>
    <p p="12"><fact id="tree-shows-origin-hint" status="spec/done">REQ {#tree-shows-origin-hint}. Each page row carries a compact **origin hint** when one of its keys is
shadowed — a glyph + the winning layer (e.g. `▾ appearance  [L3]`) — so the user sees at a glance where
a value is coming from (the §4.1.1 precedence pain, surfaced visually).</fact></p>
    <p p="13"><fact id="tree-context" status="spec/done" action="continue" actionstage="doc" audience="user">REQ {#tree-context}. The tree respects the active project context (which repo's `.vibe/` is L2); a
no-project session shows only L1 (user-machine) pages.</fact></p>
  </section>
  <section id="edit-form" title="4. The edit form — per-type fields">
    <p p="14"><fact id="form-per-type" status="spec/done">REQ {#form-per-type} (Δ-16; `vibevm/vibespecs/research` §3.7). The right pane renders the focused page as a **form**
of typed fields, one per preference key, by `KeyMeta.type` (PROP-040 §6):</fact></p>
    <list ordered="false" p="15">
      <item><fact id="field-bool" status="spec/done">**bool** → a toggle/checkbox;</fact></item>
      <item><fact id="field-enum" status="spec/done">**enum** → a `RadioGroup` (PROP-037 §2.7) or dropdown `Menu` (§2.4);</fact></item>
      <item><fact id="field-int-string" status="spec/done">**int/string** → a `TextField` (§2.8), with the schema's constraints surfaced;</fact></item>
      <item><fact id="field-array" status="spec/done">**array** → an editable list (add/remove/reorder), honouring the key's `merge` strategy (PROP-040 §4);</fact></item>
      <item><fact id="field-table" status="spec/done">**table** → a nested group (`Group`, §2.6) of sub-fields.</fact></item>
    </list>
    <p p="16"><fact id="configurable-lifecycle" status="spec/done">REQ {#configurable-lifecycle} (`vibevm/vibespecs/research` §3.7). Each form follows the IntelliJ `Configurable`
contract, clean-room: a cheap `is_modified()` (form vs current `ResolvedPrefs`), `apply()` (writes via
PROP-040 `set`, throws a typed error on validation failure), `reset()` (model → form). `apply` is gated
on `is_modified()`; the form never writes a no-op.</fact></p>
    <p p="17"><fact id="write-layer-choice" status="spec/done" action="continue" actionstage="doc" audience="user">REQ {#write-layer-choice}. Editing a field writes to a **chosen layer** (default L3 for a project
session, L1 for a no-project session), selected in the form — never silently to the wrong layer (the
VSCode `.vscode-overwrites-contributors` pain, §4.1.2). Writing to a layer the key's `scope` forbids is
refused with the reason (PROP-040 §7).</fact></p>
    <p p="18"><fact id="apply-indicator" status="spec/done">REQ {#apply-indicator}. The `applies` metadata (PROP-040 §10) is shown per field — a "needs restart" /
"needs reload" badge — so the user knows when a change takes effect (the §4.3.2 hot-reload pain).</fact></p>
  </section>
  <section id="provenance" title="5. The provenance view — &quot;where does this value come from?&quot;">
    <p p="19"><fact id="provenance-view" status="spec/done" action="continue" actionstage="doc" audience="user">REQ {#provenance-view} (PROP-040 §5, §8). A field shows its **provenance** on demand: the resolved
value plus each layer's contribution (`default / L1 / L2 / L3 / CLI / env`), the winning `origin`
(file:line where known), and which layers are shadowed. This is the visual form of `vibe prefs
show-origins` (PROP-040 §8) — the first-class answer to "which layer is winning?".</fact></p>
    <p p="20"><fact id="provenance-edit" status="spec/done" action="continue" actionstage="doc" audience="user">REQ {#provenance-edit}. From the provenance view the user can **override at a specific layer** (set L3
without touching L2, or clear L3 to fall back to L2) — direct, layer-aware editing, not a single
mystery write.</fact></p>
  </section>
  <section id="validation" title="6. Validation feedback">
    <p p="21"><fact id="validation-feedback" status="spec/done">REQ {#validation-feedback} (PROP-040 §6). Schema violations (unknown key, wrong type, out-of-range,
deprecated) render inline next to the offending field, in the warning style, with the rule cited — the
VSCode "silent typo" pain (§4.1.5) made visible. A field in error blocks `apply` for that field and
reports why.</fact></p>
    <p p="22"><fact id="lint-all" status="spec/done">REQ {#lint-all}. `vibe prefs` UI offers a "check all layers" action (the `vibe prefs check` command,
PROP-040 §6) — a flat list of every warning across L1/L2/L3, jump-to-field.</fact></p>
  </section>
  <section id="search" title="7. Search">
    <p p="23"><fact id="settings-search" status="spec/done" action="continue" actionstage="doc" audience="user">REQ {#settings-search} (Δ-15; the archived settings study §3.7). A search (the `vibe-actions` Search Everywhere
engine, PROP-039 §10 — the same engine the `vibe tree` TUI uses) finds settings by **key, display name,
description, and synonyms**. Selecting a result opens the owning page with that field focused. The
search index is built from the page registry (§2) so a new page is searchable with no extra wiring.</fact></p>
    <p p="24"><fact id="deprecated-discoverable" status="spec/done">REQ {#deprecated-discoverable}. Deprecated keys remain searchable (they surface the `replaced_by`
migration path, PROP-040 §6), so a user looking for an old name is guided to the new one.</fact></p>
  </section>
  <section id="built-on" title="8. Built on the TUI + the action system">
    <p p="25"><fact id="commands-are-actions" status="spec/done">REQ {#commands-are-actions}. Every settings-UI command (open a page, apply, reset, search, jump-to-next
warning, switch write-layer) is a `vibe-actions` **Action** in group `vibe.prefs`, addressed
`action://vibe.prefs/&lt;name&gt;` (PROP-039 §3), bound through the PROP-037 keymap (§5). The footer lists the
enabled actions for the current context (PROP-037 §5.2). This makes the settings UI consistent with the
`vibe tree` TUI and AIUI-drivable like it.</fact></p>
    <p p="26"><fact id="modal-stack" status="spec/done">REQ {#modal-stack}. Settings forms use the PROP-037 modal stack (§6): an enum-edit dropdown or the
provenance detail opens as a modal over the form; `Esc` pops back; depth-N is supported.</fact></p>
  </section>
  <section id="aiui-ready" title="9. AIUI-ready">
    <p p="27"><fact id="aiui-ready-req" status="spec/done">REQ {#aiui-ready}. Because the UI is a Surface over PROP-040's inspect/get/set and its commands are
addressed actions, the **same settings** can later be driven headless (the AIUI, PROP-039 §11.3): an
agent lists pages (§2), reads/writes keys (PROP-040 §5), and observes change events (PROP-040 §10)
without this UI. The AIUI surface itself is **not built** here (PROP-040 §14); this contract only keeps
the design AIUI-clean (no logic leaks into the surface).</fact></p>
  </section>
  <section id="non-goals" title="10. Non-goals">
    <list ordered="false" p="28">
      <item><fact id="ng-engine" status="spec/done">**A settings engine** — PROP-040 owns the data layer; this contract owns only the surface.</fact></item>
      <item><fact id="ng-component-library" status="spec/done">**A separate component library** — uses PROP-037's `ui::` components + theme.</fact></item>
      <item><fact id="ng-aiui-surface" status="spec/done">**The AIUI surface** — AIUI-ready (§9), built with AIUI.</fact></item>
      <item><fact id="ng-cloud-sync" status="spec/done">**Cloud-sync UI** — deferred with PROP-040's cloud sync (DEF-1).</fact></item>
      <item><fact id="ng-gui" status="spec/done">**A GUI** — this is a TUI surface; a future GUI is a separate surface over the same PROP-040 API.</fact></item>
    </list>
  </section>
  <section id="discipline" title="11. AI-Native Rust discipline">
    <p p="29"><fact id="discipline-req" status="spec/done">REQ. The settings-UI code follows the AI-Native Rust discipline
(`spec://org.vibevm.ai-native/core-ai-native`): cells; `specmark::scope!` citing the PROP-041 anchor on
every file; per-fn `#[spec(implements = "spec://…#…")]` where a fn implements a REQ; `anyhow` at the
command edge, typed errors citing REQs; no `unwrap`/`expect` in domain logic; ≤600-line file budget;
green `conform` + `specmap` at every commit. Each REQ anchor here is an addressable target.</fact></p>
  </section>
</spec>
