<?xml version="1.0" encoding="UTF-8"?>
<spec xmlns="https://vibevm.org/spec/1">
  <title id="root">The vibevm Action System — design &amp; architecture</title>
  <p p="1"><fact id="genre-line" status="doc/done">**Genre:** design (lore) — non-binding rationale and architecture. The normative contract is
**Spec 1 = PROP-039** (`spec://org.vibevm.core/vibevm/modules/vibe-actions/PROP-039`, written and implemented — the
`vibe-actions` crate ships); this document
explains *why* the system is shaped as it is and *how* the pieces fit. It is derived, behind the
clean-room firewall, from the study
[`action-systems-vscode-idea.md`](../../legacy-spec/research/action-systems-vscode-idea.md) (the design
obligations DO1–DO18 and roadmap deltas Δ1–Δ16 cited throughout) and governed by the mandate in
[`ACTION-SYSTEM-RESEARCH-PLAN`](../../legacy-spec/research/ACTION-SYSTEM-RESEARCH-PLAN-v0.1.md#mandate). When this
lore and the contract disagree, **the contract wins** and this file is corrected (spec-genres).</fact></p>
  <section id="thesis" title="0. Thesis — in one paragraph">
    <p p="2"><fact id="THESIS" status="doc/done">The **vibevm action system** is a **frontend-agnostic, addressable, programmatically-drivable
behaviour layer** — the behaviour-layer twin of `spec://`. Every thing a UI can *do* is an
**Action** with a stable **address** (`action://&lt;group&gt;/&lt;name&gt;`), a **typed parameter schema**, a
**typed context-enablement** predicate, a **mandatory human-readable name + description**, and a
pure **`invoke`**. Actions live in a collision-erroring **registry**, are bound to keys by a pure
**keymap resolver**, are localized through an **address-keyed message catalogue**, and are
discovered through a **provider-model Search Everywhere** that searches actions, packages, and every
package-card field today and any structured universe (AI-Native specmap nodes) tomorrow. The core is
**pure Rust with zero rendering dependencies**; a visual **Surface** (the TUI now; web/IDE later) is
one optional projection, and the **headless AIUI surface is the reference** — an AI drives and
observes the interface by address and by serialisable state, never by pixels.</fact></p>
  </section>
  <section id="principles" title="1. Founding principles">
    <p p="3"><fact id="principles-lead" status="doc/done">Six principles, each carrying its study derivation (see the findings doc for the full argument):</fact></p>
    <list ordered="true" p="4">
      <item><fact id="P-ADDRESSABILITY" status="doc/done">**Addressability of behaviour** (Δ1). Actions are addressed by URI, never by paraphrase — the
   same move `addressable-specs` makes for facts, applied to behaviour.</fact></item>
      <item><fact id="P-PROGRAMMATIC-PRIMARY" status="doc/done">**Programmatic invocation is primary; the AIUI is the reference surface** (Δ8, Δ15, DO18).
   `invoke(address, args, ctx)` is *the* interface; key presses and menu clicks are thin callers.
   Because of this, an AI can operate the UI headless.</fact></item>
      <item><fact id="P-FRONTEND-AGNOSTIC" status="doc/done">**Frontend-agnostic core** (Δ10, DO12). Zero rendering deps; visual surfaces are adapters.</fact></item>
      <item><fact id="P-HUMAN-LEGIBILITY" status="doc/done">**Human-legibility is a discipline, not decoration** (Δ6, DO5). Mandatory name + description,
   searchable, enforced by a floor gate.</fact></item>
      <item><fact id="P-DISCOVERY-UNIVERSE" status="doc/done">**Discovery over any structured universe** (Δ7, Δ14, DO16). One provider seam — actions,
   packages, card-fields now; specmap/AI-Native structure later.</fact></item>
      <item><fact id="P-TYPED-EVERYTHING" status="doc/done">**Typed everything** (Δ4, Δ5, DO3, DO4). Typed addresses, params, context, results — the gap
   both incumbents leave stringly.</fact></item>
    </list>
  </section>
  <section id="crate" title="2. Crate &amp; module architecture">
    <p p="5"><fact id="CRATE-INVARIANT" status="doc/done">A new crate **`vibe-actions`** (home `vibevm/vibespecs/modules/vibe-actions/`, contract PROP-039). Pure Rust,
**no `ratatui`/`crossterm`/DOM/any rendering dependency** — this is the invariant that makes the
AIUI and every other surface possible (DO12). Modules:</fact></p>
    <table p="6">
      <tr>
        <td>Module</td>
        <td>Owns</td>
        <td>Key deltas</td>
      </tr>
      <tr>
        <td><fact id="ROW-MOD-ADDRESS" status="doc/done">`address`</fact></td>
        <td><fact id="ROW-MOD-ADDRESS-OWNS" status="doc/done">The `action://&lt;group&gt;/&lt;name&gt;[?params]` address type, its grammar, parse/format, uniqueness, tombstone/alias</fact></td>
        <td><fact id="ROW-MOD-ADDRESS-KEY-DELTAS" status="doc/done">Δ1</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-MOD-ACTION" status="doc/done">`action`</fact></td>
        <td><fact id="ROW-MOD-ACTION-OWNS" status="doc/done">The **Action** value (address · presentation · param-schema · enablement · invoke) and its immutable resolved snapshot</fact></td>
        <td><fact id="ROW-MOD-ACTION-KEY-DELTAS" status="doc/done">Δ2</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-MOD-REGISTRY" status="doc/done">`registry`</fact></td>
        <td><fact id="ROW-MOD-REGISTRY-OWNS" status="doc/done">The collision-erroring registry: register/lookup, referential-integrity checks, full enumeration</fact></td>
        <td><fact id="ROW-MOD-REGISTRY-KEY-DELTAS" status="doc/done">Δ3, Δ12</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-MOD-PARAMS" status="doc/done">`params`</fact></td>
        <td><fact id="ROW-MOD-PARAMS-OWNS" status="doc/done">The typed, serialisable named-parameter **schema** + **values** + validation</fact></td>
        <td><fact id="ROW-MOD-PARAMS-KEY-DELTAS" status="doc/done">Δ4</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-MOD-CONTEXT" status="doc/done">`context`</fact></td>
        <td><fact id="ROW-MOD-CONTEXT-OWNS" status="doc/done">The typed **context snapshot** (a `TypeId`-keyed typemap), context keys, and the pure **enablement** predicate → `{visible, enabled, reason}`</fact></td>
        <td><fact id="ROW-MOD-CONTEXT-KEY-DELTAS" status="doc/done">Δ5</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-MOD-INVOKE" status="doc/done">`invoke`</fact></td>
        <td><fact id="ROW-MOD-INVOKE-OWNS" status="doc/done">Invocation: sync/async, typed **result/error**, cancellation, the **capability** check</fact></td>
        <td><fact id="ROW-MOD-INVOKE-KEY-DELTAS" status="doc/done">Δ8, Δ11</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-MOD-I18N" status="doc/done">`i18n`</fact></td>
        <td><fact id="ROW-MOD-I18N-OWNS" status="doc/done">The address-keyed message **catalogue** (Fluent-backed), `MessageKey`, `ResolvedLabel {value, original_en}`, locale swap</fact></td>
        <td><fact id="ROW-MOD-I18N-KEY-DELTAS" status="doc/done">Δ13</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-MOD-KEYMAP" status="doc/done">`keymap`</fact></td>
        <td><fact id="ROW-MOD-KEYMAP-OWNS" status="doc/done">Key → (address, args) binding, the pure **3-state resolver**, chord model (timers live in the adapter)</fact></td>
        <td><fact id="ROW-MOD-KEYMAP-KEY-DELTAS" status="doc/done">Δ9</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-MOD-SEARCH" status="doc/done">`search`</fact></td>
        <td><fact id="ROW-MOD-SEARCH-OWNS" status="doc/done">The **Search Everywhere** engine: the provider trait (two-phase), the match/rank pipeline, tabs, dedup, recency, freeze-on-more</fact></td>
        <td><fact id="ROW-MOD-SEARCH-KEY-DELTAS" status="doc/done">Δ7, Δ14, Δ16</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-MOD-SURFACE" status="doc/done">`surface`</fact></td>
        <td><fact id="ROW-MOD-SURFACE-OWNS" status="doc/done">The **Surface** adapter trait (the seam) + the headless **AIUI** surface + the serialisable model-state view</fact></td>
        <td><fact id="ROW-MOD-SURFACE-KEY-DELTAS" status="doc/done">Δ10, Δ15</fact></td>
      </tr>
    </table>
    <p p="7"><fact id="crate-consumers" status="doc/done">Consumers: **`vibe-cli`** hosts the TUI Surface (Spec 2 revises PROP-037 to sit on this crate);
future web / VSCode / JetBrains / Zed surfaces are additional adapters. Nothing in `vibe-actions`
depends on any consumer.</fact></p>
  </section>
  <section id="types" title="3. The core types">
    <p p="8"><fact id="types-lead" status="doc/done">Illustrative Rust shapes (the contract PROP-039 fixes the normative form; these convey intent):</fact></p>
    <fence lang="rust" p="9">// address — Δ1
struct ActionAddr { group: Group, name: Name }          // (group, name) globally unique; Display = "action://&lt;group&gt;/&lt;name&gt;"
// e.g. action://vibe.tree/copy.markdown , action://core/search.everywhere

// action — Δ2
struct Action {
    addr: ActionAddr,
    presentation: Presentation,          // Δ6 — name + description MANDATORY, localizable
    params: ParamSchema,                 // Δ4 — may be empty
    enablement: Box&lt;dyn Fn(&amp;Ctx) -&gt; Enablement&gt;,  // Δ5 — pure, fast, no rendering, no UI thread
    invoke: Box&lt;dyn Fn(&amp;Ctx, ParamValues) -&gt; BoxFuture&lt;InvokeResult&gt;&gt;,  // Δ8 — primary interface
    capability: Capability,              // Δ11
    search_meta: SearchMeta,             // Δ16 — synonyms/aliases/abbreviations, keywords
}
struct Presentation { name: Msg, description: Msg, icon: Option&lt;Glyph&gt;, category: Option&lt;Msg&gt; }
struct Msg { key: MessageKey, default_en: &amp;'static str }   // Δ13 — key = "action.&lt;addr&gt;.name"
struct Enablement { visible: bool, enabled: bool, reason: Option&lt;Localized&gt; }  // "why disabled"

// search provider seam — Δ7, Δ14 — the two-phase "searchable structured universe" contract
trait SearchProvider {
    fn id(&amp;self) -&gt; ProviderId;
    fn group_name(&amp;self) -&gt; Localized;       // tab label + group separator
    fn sort_weight(&amp;self) -&gt; i32;            // orders TABS/groups, NOT elements
    fn separate_tab(&amp;self) -&gt; bool;
    fn enumerate(&amp;self, q: &amp;Query, sink: &amp;mut dyn KeySink);          // cheap keys, streamed, scope-aware
    fn resolve(&amp;self, key: &amp;ItemKey) -&gt; Vec&lt;Hit&gt;;                    // heavy items, only for matched keys
    fn accessor(&amp;self) -&gt; &amp;dyn ItemAccessor;                        // {label, description, key} → one ranker
    fn on_selected(&amp;self, hit: &amp;Hit, mods: Modifiers) -&gt; Selected;  // Selected::Close | Selected::Stay
    fn render_row(&amp;self, hit: &amp;Hit) -&gt; RowDescriptor;              // normalized: {icon, primary, secondary, group, enabled, kind}
}

// surface seam — Δ10, Δ15
trait Surface {                        // a visual adapter (TUI) OR the headless AIUI
    fn present(&amp;mut self, view: &amp;ModelView);   // no-op for a headless surface
    fn next_event(&amp;mut self) -&gt; Event;         // key event, or a programmatic Invoke/Query for AIUI
}
struct ModelView { /* serialisable snapshot: focus, modals, visible rows, enabled actions … */ }  // Δ15</fence>
  </section>
  <section id="flow" title="4. Data flow — MVC, with the model as the real interface">
    <fence p="10">        ┌──────────── Surface (adapter) ───────────┐
event → │ TUI: key/mouse   |   AIUI: invoke/query  │
        └───────────────┬──────────────────────────┘
                        ▼
              Controller (keymap resolve → address + args)     Δ9
                        ▼
              invoke(address, args, ctx)  ──►  Action           Δ8
                        ▼
              Action mutates Model (typed, SERIALISABLE)
                        ▼
        ┌───────────────┴───────────────┐
        ▼                               ▼
  View renders ModelView          AIUI reads ModelView + enumerates enabled actions   Δ15
  (TUI, optional)                 (headless, the reference)</fence>
    <p p="11"><fact id="MODEL-IS-THE-INTERFACE" status="doc/done">The load-bearing property: **the Model + the action registry are the interface**; the View is one
optional projection. An AIUI needs only three capabilities the core already provides — *enumerate
enabled actions with their addresses/params/reasons*, *invoke by address with typed args*, and *read
the serialisable `ModelView`* — none of which touch rendering. This is why AIUI is "not built now"
yet costs nothing later: it is a `Surface` whose `present` is a no-op and whose events are
programmatic (DO18).</fact></p>
  </section>
  <section id="decisions" title="5. Key design decisions">
    <p p="12"><fact id="decisions-lead" status="doc/done">Recorded in the four-field form (Decision · Why · Considered-and-rejected · Revisit-when); these
become decision records at their governing PROP-039 anchors.</fact></p>
    <list ordered="false" p="13">
      <item><fact id="D1-URI-ADDRESS" status="doc/done">**D1 — Address = `action://&lt;group&gt;/&lt;name&gt;[?params]` (URI).** *Why:* the behaviour-layer twin of
  `spec://&lt;module&gt;/&lt;doc&gt;#&lt;anchor&gt;`; owner-ratified; typed params ride the query; `(group, name)`
  globally unique (ties to `qualified-naming`). *Rejected:* IntelliJ-style dotted FQDN
  (`org.vibevm.tree.copy.markdown`) — parameters cannot live in the address and it reads less like
  the project brand; a bare opaque string (both incumbents) — no structure, no enforced uniqueness.
  *Revisit:* if URI parse cost ever shows on a profile (it will not at these volumes).</fact></item>
      <item><fact id="D2-COLLISION-ERROR" status="doc/done">**D2 — The registry errors on collision.** *Why:* both incumbents are inconsistent or silent
  (VSCode: three policies; IntelliJ: log-and-drop) → surprise + lost actions; `qualified-naming`
  says a collision is a hard, distinct failure. *Rejected:* the permissive override-stack
  (VSCode `CommandsRegistry`) as the *default* — override must be an explicit, uniform semantics,
  not an accident of which door you use. *Revisit:* if a real layered-override use case appears →
  add an explicit `override_of(addr)` op, still collision-checked.</fact></item>
      <item><fact id="D3-TYPED-CONTEXT" status="doc/done">**D3 — Typed context + pure enablement.** *Why:* IntelliJ's `update()` EDT/BGT threading is its
  single biggest documented pain; VSCode's stringly `when` evaluates false forever on a typo. A pure
  function over a `TypeId`-keyed snapshot has neither failure mode and is introspectable ("why
  disabled") and enumerable ("what keys does this context carry"). *Rejected:* a stringly `when`
  DSL; a nullable `DataContext`-style map. *Revisit:* never — this is the core differentiator.</fact></item>
      <item><fact id="D4-PROGRAMMATIC-PRIMARY" status="doc/done">**D4 — Programmatic invocation is primary; AIUI is the reference surface.** *Why:* the owner's
  AIUI mandate + vibevm's two-process model; both incumbents retrofitted programmatic invocation and
  it shows (VSCode `unknown[]`; IntelliJ result recovered out-of-band). *Rejected:* UI-event-primary
  with a bolted-on programmatic path. *Revisit:* never.</fact></item>
      <item><fact id="D5-PROVIDER-MODEL" status="doc/done">**D5 — Search Everywhere is a provider model with the two-phase enumerate→resolve contract.**
  *Why:* IntelliJ's proven design; it generalises to any structured universe (packages now, specmap
  later) through one seam and keeps per-keystroke latency by resolving only matched keys. *Rejected:*
  a hardwired god-provider (VSCode's `anythingQuickAccess` — the study's explicit cautionary tale).
  *Revisit:* if a provider needs a fundamentally different fetch shape → it drops to the raw
  `enumerate`+`resolve` escape hatch (already supported).</fact></item>
      <item><fact id="D6-I18N-KEYS" status="doc/done">**D6 — i18n: address-derived keys + inline English default + `{value, original_en}` + Fluent.**
  *Why:* IntelliJ's key-from-id (no second namespace) + VSCode's inline-English (self-documenting,
  always-present fallback) + `localize2`'s original-kept-beside-value (so Search Everywhere matches
  the English label under any locale); Fluent is the Rust-idiomatic catalogue with named args +
  plurals. *Rejected:* VSCode's build-time numeric-index indirection (opaque at runtime, needs a
  build step); IntelliJ's JDK-`ResourceBundle` reflection (accidental complexity). *Revisit:* if a
  non-Fluent format is mandated downstream.</fact></item>
      <item><fact id="D7-ENGLISH-GATE" status="doc/done">**D7 — The human-legibility gate targets English only.** *Why:* English is the source of truth and
  the always-present fallback in both incumbents; other locales may lag. *Rejected:* gating every
  locale (blocks shipping on incomplete translations). *Revisit:* if a locale is declared
  ship-blocking.</fact></item>
      <item><fact id="D8-ONE-RENDERER" status="doc/done">**D8 — One normalized row renderer per surface.** *Why:* the study's ADAPT note — IntelliJ's
  per-provider Swing renderers give heterogeneous rows and inconsistent look; a TUI wants one
  renderer over a `RowDescriptor {icon, primary, secondary, group, enabled, kind}` so every category
  looks uniform. *Rejected:* per-provider renderers. *Revisit:* if a provider needs a bespoke row
  the descriptor cannot express → extend the descriptor, not the renderer count.</fact></item>
      <item><fact id="D9-PURE-RESOLVER" status="doc/done">**D9 — The keymap resolver is pure and returns a 3-state result** (`NoMatch | NeedMoreChords |
  Found`); chord timers, IME, focus walking live in the **adapter**. *Why:* VSCode's clean
  `ResultKind` + IntelliJ's ambiguity-as-list-resolved-by-enablement, minus the UI coupling.
  *Rejected:* a resolver that owns timers/focus (both incumbents entangle these). *Revisit:* n/a.</fact></item>
      <item><fact id="D10-ONE-SCORER" status="doc/done">**D10 — Ranking: one commensurable scorer emitting score + highlight ranges, recency-weighted,
  with an exact-match floor.** *Why:* VSCode's two-engine highlight/rank mismatch (DO7) + "recency
  beats score" (DO8) + IntelliJ's exact-match floor. *Rejected:* separate match/highlight engines.
  *Revisit:* if an ML reranker is added → it slots above the floor, like IntelliJ's.</fact></item>
    </list>
  </section>
  <section id="search" title="6. Search Everywhere — the architecture (the acceptance)">
    <p p="14"><fact id="SEARCH-ACCEPTANCE" status="doc/done">The feature the whole system must deliver: **F1 opens a window that searches packages by name, every
field of the package detail cards, and all actions — with a hybrid "All" tab and per-category tabs —
and invokes a found action in place.**</fact></p>
    <p p="15"><fact id="providers-lead" status="doc/done">**Providers at ship** (all against the one `SearchProvider` seam, §3):</fact></p>
    <list ordered="false" p="16">
      <item><fact id="PROVIDER-PACKAGE" status="doc/done">`PackageProvider` — keys = package FQNs streamed from the `PackageTree`; resolve = the
  `PackageNode`; navigate = reveal/select it in the tree.</fact></item>
      <item><fact id="PROVIDER-PACKAGE-FIELD" status="doc/done">`PackageFieldProvider` — keys = every field of every package detail card (name, version, kind,
  license, load-type, origin, path, deps, diagnostics…); resolve = the field's `(package, field)`;
  navigate = open the card focused on that field. *(This is the owner's "search inside all card
  fields.")*</fact></item>
      <item><fact id="PROVIDER-ACTION" status="doc/done">`ActionProvider` — keys = action addresses + names + descriptions + synonyms/aliases; resolve =
  the `Action`; `on_selected` = **invoke it** (perform → close; a toggle stays open). Disabled
  actions render greyed with their "why disabled" reason; shortcuts render right-aligned.</fact></item>
      <item><fact id="PROVIDER-STRUCTURE-RESERVED" status="doc/done">**Reserved (same trait, no engine change):** `StructureProvider` — AI-Native specmap spec/code
  nodes, added when the AI-Native language structure lands.</fact></item>
    </list>
    <p p="17"><fact id="engine-lead" status="doc/done">**The engine** (single-threaded TUI-friendly, the study's ADAPT of IntelliJ's threaded design):</fact></p>
    <list ordered="false" p="18">
      <item><fact id="ENG-DEBOUNCE" status="doc/done">Per keystroke: **debounce** (~90–120 ms) + **cancel** the prior run.</fact></item>
      <item><fact id="ENG-PROVIDER-SET" status="doc/done">Active provider set = one (a category tab) or all filter-enabled (the "All" tab); each gets a cap
  (single 30 / All 15).</fact></item>
      <item><fact id="ENG-MATCH-LADDER" status="doc/done">Each provider **enumerates cheap keys** (streamed, cancellable), the keys are **matched** by one
  scorer (the match-tier ladder: exact → prefix → CamelCase/subsequence → substring → **name/
  description word — the fallback lane**, Δ6/DO5), survivors are **resolved** to hits.</fact></item>
      <item><fact id="ENG-COMMENSURABLE-SCALE" status="doc/done">Hits are wrapped `{hit, score, provider}` on **one commensurable scale**, **recency-weighted** with
  an **exact-match floor**, **deduped keeping the higher score** across providers, drained
  **round-robin** from per-provider bounded queues into a single flat list.</fact></item>
      <item><fact id="ENG-ORDERING" status="doc/done">Ordering: score DESC, tie → provider `sort_weight` DESC. In "All", a **group header** precedes each
  provider's rows; single tabs have none. The "All" tab carries a **category checkbox filter**.</fact></item>
      <item><fact id="ENG-FREEZE-ON-MORE" status="doc/done">**Freeze-on-"more":** a per-provider "more…" row re-queries that provider and freezes the rows
  above so async results don't reshuffle under the cursor.</fact></item>
      <item><fact id="ENG-NORMALIZED-RENDERER" status="doc/done">One **normalized renderer** (`RowDescriptor`) draws every category uniformly.</fact></item>
    </list>
    <p p="19"><fact id="TABS-MODEL" status="doc/done">**Tabs** are built from the providers: sort by `sort_weight`; prepend "All" when &gt;1; one tab per
`separate_tab` provider. `Tab`/`Shift-Tab` cycle. Selecting a row calls `provider.on_selected` →
`Close` dismisses, `Stay` keeps the window (in-place toggles).</fact></p>
  </section>
  <section id="aiui" title="7. The AIUI surface — the reference">
    <p p="20"><fact id="aiui-lead" status="doc/done">Built in its in-process form. The headless surface exposes, over the same core, a small programmatic
protocol (the in-process API ships in `vibe-actions/src/aiui.rs` — `list_actions` and `invoke` — with
`vibe aiui state` projecting the model; a JSON-RPC / MCP binding is the remaining adapter, aligning with
vibevm's MCP surface):</fact></p>
    <list ordered="false" p="21">
      <item><fact id="AIUI-LIST-ACTIONS" status="doc/done">**`list_actions(filter?) -&gt; [{address, name, description, params, enabled, reason}]`** — enumerate
  the registry with live enablement (the enumeration + pure enablement the core already provides).</fact></item>
      <item><fact id="AIUI-INVOKE" status="doc/done">**`invoke(address, args) -&gt; Result`** — the same `invoke` the keymap calls.</fact></item>
      <item><fact id="AIUI-STATE" status="doc/done">**`state() -&gt; ModelView`** — the serialisable model snapshot (focus, modals, visible rows, current
  tree/selection) so the AI observes structured state, not pixels.</fact></item>
      <item><fact id="AIUI-SEARCH" status="doc/done">**`search(query, tab?) -&gt; [Hit]`** — drive Search Everywhere programmatically.</fact></item>
    </list>
    <p p="22"><fact id="AIUI-THIN-ADAPTER" status="doc/done">Because enablement is pure + introspectable, the model is serialisable, and invocation is
address-based, this surface is a thin adapter with a no-op `present`. Prototyping on the TUI proves
the core; the AIUI then "just works" because the core owes rendering nothing (DO18).</fact></p>
  </section>
  <section id="i18n" title="8. i18n — the architecture">
    <p p="23"><fact id="I18N-ARCHITECTURE" status="doc/done">Per D6 / §3.8 of the findings doc: presentation is two `Msg { key = "action.&lt;addr&gt;.name" |
".description", default_en }`. A `Catalogue { locale, entries, parent }` chain resolves a key,
terminating in an `en` catalogue **seeded from the inline defaults** (release lookups never miss).
Every resolved label is `ResolvedLabel { value, original_en }` so Search Everywhere indexes both.
Locale swap is `ArcSwap&lt;Catalogue&gt;`. Packages ship `locales/&lt;lang&gt;.ftl`; a language-pack package may
override. The legibility gate asserts the **English** surface is complete; a `pseudo` locale QA build
surfaces un-externalised strings. `MessageKey`/`Localized` are newtypes; a CI check asserts every
registry key resolves in `en`.</fact></p>
  </section>
  <section id="vibe-tree" title="9. Mapping to `vibe tree` (Spec 2 preview)">
    <p p="24"><fact id="VIBE-TREE-MAPPING" status="doc/done">Spec 2 revised [PROP-037](../modules/vibe-cli/PROP-037-tree-tui.xml) so the TUI sits on this crate,
and that revision **landed** — `keymap_bridge.rs`, action dispatch and the F1 Search Everywhere all ship:
every TUI command is an **Action** with an address in group `vibe.tree` (e.g.
`action://vibe.tree/copy.markdown`, `action://vibe.tree/sort`, `action://vibe.tree/mode.set`), a
name + description (feeding both the footer/menus and Search Everywhere), a typed param schema, and a
typed enablement over a `TreeCtx` snapshot. The F-key map (PROP-037 §5) becomes a `keymap` binding
key → address. F1 opens Search Everywhere with the three providers (§6). The four-layer MVC of
PROP-037 is preserved and *sharpened*: the Model becomes the serialisable `ModelView` source (AIUI-
ready), the Controller becomes the keymap + `invoke`, the View becomes the one normalized renderer +
Theme. `ComingSoon` stays for genuinely-unbuilt features; Search Everywhere is **promoted** from its
reserved stub to a shipped feature.</fact></p>
  </section>
  <section id="contract-pointer" title="10. What Spec 1 ratifies">
    <p p="25"><fact id="CONTRACT-POINTER" status="doc/done">PROP-039 carries one granular addressable REQ per Δ (Δ1–Δ16), organised by the §2 modules, each
cited by the code via `specmark`. The human-legibility gate (D7) and the enumerable-registry golden
(Δ12) are floor gates. This design-doc is the lore that explains those REQs; the two-way links are
kept per spec-genres. No open owner-decisions remain (RP1–RP5 resolved,
`legacy-spec/research/ACTION-SYSTEM-RESEARCH-PLAN-v0.1.md#review-points`); the address form is `action://`
and the crate is `vibe-actions`.</fact></p>
  </section>
</spec>
