VibeVM
Contents
On this page
en
Publisher
org.vibevm.core
Version
1.0.0latest
Audiences
user
Reading time
23 min
Rendered
Read aloud
never

PROP-037: vibe tree — the interactive TUI application

01Status: IMPLEMENTED (requirements authored 2026-07-15, owner-commissioned; TREE-TUI-PLAN v0.2 executed against them, verified against the tree 2026-07-25 by the spec-actualization campaign — the visual language matches byte for byte and the shipped F-key map is the one this Spec 2 text defines); revised 2026-07-15 (Spec 2) onto the action system: the TUI is a Surface on vibe-actions (PROP-039), every command is an addressed action (§13), Search Everywhere (§7.3) is promoted from a stub to a shipped feature, and the i18n mechanism (§1.6) is now real. Extends PROP-036 §2.11 (the analyzer's TUI sketch) into a full application contract.

02Related: PROP-036 (the vibe tree analyzer + the PackageTree model this app renders); PROP-039 (the vibe-actions action system this TUI is built on — §13) + its design-doc vibevm/vibespecs/design/action-system.xml; core-ai-native discipline (§11). Plan (archived): legacy-spec/terraforms/TREE-TUI-PLAN-v0.1.md.

03Language: the shipped UI is English (§1.6); the i18n mechanism ships (PROP-039 §8, §13.4) with English the only mandatory-complete locale — no non-English catalogues are shipped now (§12).

04This contract is deliberately granular and addressable (owner directive): every feature is its own {#anchor} REQ, cited by the code via specmark, so a large feature set stays a map rather than a tangle. A REQ is the unit of work.

0. What "finished" means for this subsystem

05Owner ruling, 2026-08-06: this subsystem is finished when it is a THIN SURFACE — when every capability it presents lives in a library and the TUI only renders. Not when a list of screens is complete, not when a suppression count reaches zero.

06The test: delete the TUI on paper. If anything but presentation and input handling is lost, the split is wrong and the work is not done. This is flow:omnichannel's own test (spec://org.vibevm.world/omnichannel/flows/omnichannel/OMNICHANNEL-PROTOCOL#thinness), applied to the surface that most invites logic to accumulate in it.

07Why this boundary and not the obvious ones, recorded because three alternatives were measured and offered on the same day. A screen list has a visible end but requires deciding which screens should exist — it answers "how much" rather than "what for". A suppression count (27 carry no reason today, of 53 at the perimeter below) is hygiene: all 27 could close without the subsystem moving a step. Manual tests — and the TUI is the one surface automated tests cannot really exercise — are worth having and their number grows with the screens, so they define no end. Thinness is the only one of the four that a new screen does not move.

08Correction, 2026-08-06. The line above said there were none. There are three, and two are this subsystem's: vibevm/vibespecs/manual-tests/MT-02-vibe-tree-tui.xml (the tree TUI's visual sign-off) and MT-03-vibe-prefs-tui.xml, beside MT-01-vibe-tree.xml for the plain renderer. The rejection of manual-tests-as-boundary never depended on the count and still holds; the parenthetical was simply false about a file three directories away — the exact shape this campaign exists to find.

09The measurement that framed the question: 63 files, 18 426 lines, 258 tests inside the subsystem. It is not a sketch, and its problem was never incompleteness — it was an undefined edge.

10The perimeter, so the number is reproducible instead of merely quoted (added 2026-08-06): the two tui/ trees — crates/vibe-cli/src/commands/tree/tui/** and crates/vibe-cli/src/commands/prefs/tui/**. It deliberately excludes commands/tree/'s own model and build code, which is the library half the deletion test is about, and that exclusion is why the figure is the right one to audit against. Both numbers this section quotes reproduce at exactly this perimeter and at no neighbouring one: commands/tree/ alone gives 52 files and 13 117 lines, and adding all of prefs/tui to it gives 72 and 20 372. A number whose perimeter is unstated is a number the next reader re-derives wrongly — this one was recovered by measuring five candidate perimeters.

11This is the §1 layering law seen from outside: vibevm logic never leaks into the app is thinness stated inward, and the deletion test is how it is checked from without. They are one rule with two readings, not two rules.

12The audit ran on 2026-08-06, over the perimeter this section fixes, and its result is a split verdict — because this section's definition has two clauses and the subsystem satisfies one of them. The deletion test passes: no domain capability lives only inside the perimeter, so deleting the TUI on paper loses presentation, input handling, the surface's own persisted UI state (vibe.tree.*) and TUI-specific features, and nothing else. Version comparison, dependency resolution, manifest validation and package-state computation are absent from the perimeter entirely — the app consumes them through PackageTree, which is §1.2's one seam working as written.

13What fails is the other clause: «the TUI only renders». The settings persist path is written three times inside the perimeter — prefs/tui/form/lifecycle.rs:78-89 and prefs/tui/form/provenance_edit.rs:52 re-implement the scope gate, and tree/tui/settings.rs:547 re-implements the dotted-path mutation — while vibe-settings already ships both and the CLI already uses them (crates/vibe-settings/src/cli/mod.rs:231-244 and its set_dotted at :247). Nothing is lost if the TUI is deleted, so the deletion test is silent about it; the work is nonetheless done twice, which is what «only renders» forbids.

14The contrast that makes this a defect rather than a matter of taste sits in the same subsystem. The lint modal calls the library's validate (crates/vibe-settings/src/schema/validate.rs:130) instead of copying it, and diff_from_default is likewise called rather than re-written. The surface therefore already knows how to be thin in two places and is thick in a third — so the fix is a known shape, not a new design.

15The one bounded piece of work that stood between this subsystem and finished is done (2026-08-20): the three persist copies collapsed onto the library seam — B-077's landing gave vibe-settings the public check_writable + set_in_layer pair and every TUI write goes through them; the thinness audit's sole surviving violation is closed. What remains around the perimeter is not contract work but hygiene: the #[allow(dead_code)] shadow (the 2026-08-03-04 audit row), drained by the owner's «построить до конца» directive — each site wired, deleted, or carrying a recorded reason.

16Two candidates were weighed and deliberately cleared, recorded so they are not re-opened as new findings. The filter/shape/order pipeline (flatten.rs, shape.rs, sort.rs) is display-shaping over a graph that stays in the library, and §1.3 assigns filtering, ordering and flattening to the app's own Model layer — legitimate today, though a second tree-shaped consumer would turn it into a lift candidate. The vibe.tree.* parse functions are the wire format of the surface's own keys, so they are own-state rather than leaked domain.

17A correction to this section's own numbers, produced by the audit that reproduced them. All four reproduce exactly — 63 files, 18 426 lines, 258 tests, 53 suppressions of which 27 carry no reason — but «carries no reason» means «no // comment on the same line». Several of the 27 carry their reason on the line above (theme/mod.rs:41-47, ui/mod.rs:80-91, ui/button.rs:50,57`), so under a softer reading the figure is smaller. The strict reading is the one that reproduces, and it is the one this section means.

1. Architecture — layers

  • 18The application separates, as fixed layers, (a) the vibevm data it renders, (b) its own model + UI state, (c) the reusable view components + their styling, and (d) the event/control logic.
  • The one-line law: styling never leaks into control or app logic, and vibevm logic never leaks into the app.

1.1 The four layers

19REQ. The code is organized as four layers with one-directional dependencies (each may depend only on those above it):

  1. 20vibevm backendvibe-core / vibe-workspace / vibe-spec (the PackageTree builder, PROP-036). Unowned by this app.
  2. Model — the app's data: the borrowed/owned PackageTree plus all UI state (§1.3). Pure data + logic-on-data; no rendering, no event types.
  3. View — the component library (§2) and the theme (§2.2). Renders Model + component state into a ratatui buffer; no app control flow.
  4. Controller — event routing, the keymap registry (§5.1), the modal stack (§6), and the actions that mutate the Model (§1.5).

21The MVC cycle: event → Controller (routes to the focused component / top modal, produces an Action) → Action mutates Model → View renders Model + component state through the Theme.

1.2 The vibevm boundary

22REQ. The app consumes vibevm through exactly one seam — the PackageTree model (PROP-036) and a small read interface over it. No view or controller code reaches into vibe-core / vibe-workspace / vibe-spec types directly; if the app needs a vibevm fact, it is exposed as a method/field on the app Model's projection of PackageTree. This keeps the analyzer free to evolve without dragging the UI with it.

1.3 Model

23REQ. The Model owns: the analyzed PackageTree (read-only vibevm data); and the UI state — the active display mode (§4), the sort/shape configuration per context (§3.2, §7.2), the fold set + selection + horizontal pan, the active tab, the sub-table block order, and the loaded settings (§9). The Model exposes queries and mutating methods; it never renders and never names a key or a crossterm event. Model logic (filtering, ordering, flattening) is unit-tested without a terminal.

1.4 View — components + theme

24REQ. All on-screen output is produced by the component library (§2). A component renders itself from its own small state plus the Model and the Theme; a component never reads keys or mutates the Model. The Theme (§2.2) is the sole source of colors, styles, spacing, and glyphs — the "CSS" — so a restyle touches only the theme, never component logic.

1.5 Controller — events, keymap, modals, actions

25REQ. The Controller receives each terminal event and routes it: to the top modal if the modal stack (§6) is non-empty, else to the focused base-screen component, else to the global keymap. Routing is driven by the mode-aware keymap registry (§5.1), never by an ad-hoc match scattered across the app. An event resolves to an Action (a typed intent — e.g. OpenModeMenu, ToggleFold, Quit); Actions mutate the Model. A terminal resize always requests a repaint (already fixed; the repaint law: rat-salsa repaints only on Control::Changed).

1.6 English-only, i18n-ready

26REQ. All user-facing strings are English and live behind a single string accessor (a strings module / a label(key) indirection), so a future localization layer swaps the source without touching call sites. No localization is shipped now (§12); the indirection is the only present obligation.

2. The component library

27A reusable, vibevm-owned widget library — the abstraction that stops "a million implementations of the same control" from accumulating.

2.1 Component strategy — wrap, extend, or invent

28REQ. Components are built in this order of preference: (1) wrap a rat-widget widget behind our ui:: API + Theme (Menu, Button, TextInput, scrolling, Tabbed, popup, MsgDialog — reuse, do not reinvent); (2) extend in rat-widget's idiom when a needed widget is not in rat-widget — author it as though continuing the rat-widget family, but on the vibevm side; (3) invent on bare ratatui-core only as a last resort, when the need is unthinkable within rat-widget's ideology. Every component has exactly one implementation behind one ui:: facade; call sites never touch rat_widget:: directly.

2.2 The theme

29REQ. A single Theme value carries every color, text style, border style, spacing, and glyph the components use (selection highlight, window chrome, group frames, the [x] close glyph, subheaders, disabled/enabled button, etc.). Components take &Theme; no component hard-codes a Color/Style. The theme is terminal light/dark aware where it matters. The theme is the TUI's "CSS" (§1.4): a restyle touches only the theme.

30The visual language is a first-class part of this contract, not an afterthought. The lore (aesthetics, rationale, the full tables the eye reads) lives in the design doc vibevm/vibespecs/design/tui-visual-language.xml; the normative REQs the code is traceable to are the four anchors below.

2.2.1 Palette tokens

31REQ. Colour reaches a component only through a Palette — a data-driven mapping from semantic role tokens to Color. The role set is exactly: base, surface0, surface1, muted, subtext, text, accent, love, gold, foam, rose, selection, border, paper, button_on, button_off. No component names a Color literal; it names a role.

32REQ. Five palettes ship, each a complete role→Color mapping. Rosé Pine (the cosmic-violet look already in theme.rs) is canonical-locked: its eleven Color::Rgb values are preserved exactly (a snapshot test pins them). Catppuccin Mocha, Macchiato, Frappé (dark) and Catppuccin Latte (light) are the canonical Catppuccin values (accent←mauve, love←red, gold←yellow, foam←teal, rose←pink, muted←overlay0, subtext←subtext0). The canonical hex values for all five are normative in the design doc §3.

33REQ. A palette carries an is_light flag; selection is composed (accent ground + base text, bold), border = muted, paper = surface0, and the paper/selection rendering inverts against is_light so a light theme's detail card reads correctly. The active palette is a Model field, persisted through the settings system (§9) and overridable at the CLI/env.

2.2.2 Glyph vocabulary

34REQ. Every glyph is a constant on the Theme, never a hardcoded string at a call site. The fold indicator is / (not +/-); the DAG re-occurrence marker is (not (*)); the on/off flags are / (not x/.). Tree connectors stay │├└─; the frame stays rounded ╭╮╰╯; the close affordance is ; the bar indicator uses block elements ▁▂▃▄▅▆▇█ (or braille). The full replacement table is normative in the design doc §2.

35REQ. In the primary UI (Tier ≥ 1) there are no +/-/*/#/x/. used as semantic glyphs. Those ASCII characters appear only behind the Tier 0 fallback (§2.2.3).

2.2.3 Rendering tiers

36REQ. Rendering degrades through four tiers — 3 (truecolor: full RGB, rounded frames, braille/blocks), 2 (256-colour: palette quantised to the 6×6×6 cube, rounded, blocks), 1 (16 ANSI: role→ANSI mapping, rounded-or-square frames, blocks), 0 (explicitly dumb — TERM=linux (the Linux VT) or TERM=dumb: ANSI mono, ASCII +-| frames, # indicators).

37REQ. Tier detection is a pure function over the environment — detect_tier(colorterm: Option<&str>, term: Option<&str>) -> Tier ($COLORTERM first, then $TERM; crossterm exposes no colour-count API). The TUI reads the env once at launch in a sanctioned spot and feeds the values in; the detected tier is overridable through the settings system (§9). The default is Tier 3: anything not explicitly dumb (an unset or empty TERM/COLORTERM, or a generic TERM=xterm) is assumed truecolor, because every incumbent terminal renders truecolor and several (notably on Windows) do not advertise the capability via env at all — defaulting to Tier 3 keeps a modern terminal colourful instead of degrading it to mono. The lower tiers are the fallback (the degradation path), reached only when the environment explicitly advertises a lower capability (a 256-colour TERM, or an explicitly dumb TERM=linux/dumb); Tier 0 is never reached from an unset env. A genuinely limited terminal is otherwise overridden via vibe.tree.tier.

38REQ. Degradation is a projection: one Theme is built for Tier 3 and projected onto the detected tier (roles quantised / ANSI-mapped / ASCII-fallback). One source of truth, many projections — never bespoke per-tier rendering in a component.

2.2.4 Window aesthetics

39REQ. A window (§2.3) is not a fallback: it composes a solid base panel, a rounded frame stroked in border, a title rendered as an accent-coloured chip, interior padding, an optional shadow (so the panel reads as raised), and a top-right [✕] close affordance. Even at Tier 1 the frame + filled panel + title chip carry the "floating panel" reading without truecolor.

2.2.5 Spacing & rhythm

40REQ. The theme's spacing (§2.2) is normative, not decorative: a component never jams its content against a stroke, and a row of controls is centre- aligned in its area, never left-jammed. The ui:: library owns the spacing vocabulary — three constants that define the rhythm, so a re-space touches one place:

  • 41PAD_X = 2, PAD_Y = 1 — interior padding. Inside every window/dialog frame, content is inset from the border: a horizontal margin (PAD_X) each side and a vertical margin (PAD_Y) top and bottom — a blank row under the title and a blank row above the base. This is the "interior padding" §2.2.4 requires; ui::inner_pad applies it. A window whose body sits on the frame is a violation.
  • GUTTER = 1 — group gutter. A control inside a Group frame (§2.6) is inset from that frame's stroke by the gutter, and its selection highlight bar is inset with it, so neither the label nor the bar touches the border.
  • Rhythm. Sibling controls are separated — framed groups by a blank row between them, inline hint/footer runs by a separator with surrounding space — so the eye reads structure, not a wall of glyphs.

42The values are lore-explained in the design doc §6 #spacing-rhythm; the footer (§5.2) is the canonical inline application.

2.3 Window / Panel

43REQ. A Window component: a bordered, titled region drawn over a cleared rect, with an optional title and an optional top-right esc [x] close affordance (§8). It is the base of every modal and the card. It composes child components and lays them out; it owns no app logic.

2.5 Button

45REQ. A Button component: a labelled, focusable control; the focused button is highlighted; Enter activates it. Buttons participate in a window's focus order (arrow / Tab navigation). Used by dialogs (OK, Save, Cancel).

2.6 Group / Fieldset

46REQ. A Group component: a visually delimited cluster of child components (color / padding / a border frame), with an optional group name rendered at the frame's top-right corner. Groups give a multi-setting dialog its visual structure (§7.2).

2.7 RadioGroup

47REQ. A RadioGroup component: a Group whose children are mutually-exclusive options; exactly one is selected; / move, Enter/Space selects. Backs single-choice settings (the sub-table block order, §7.2; the copy format/dest, §10.2).

2.8 TextField

48REQ. A TextField component: a single-line editable text input (the file-path entry, §10.5). Simple for now (a later REQ enriches it); wraps rat-widget's text input where possible (§2.1).

2.9 Card / Form

49REQ. A Card component: a Window laid out as a labelled vertical form — see §8 for its full design contract (background/foreground, bold headers, line spacing, wrapping, per-line copy).

2.10 The standard "Coming Soon" modal

50REQ. A single reusable ComingSoon modal — a Window titled with the feature name, a "coming soon" body, and an OK button (Enter/Esc closes). It is the standard placeholder for every not-yet-implemented feature (F1 Search Everywhere §7.3; PNG export §10.4; any future stub). Wiring a feature's entry point to ComingSoon is how a feature is "reserved" before it is built.

3. The Tree widget + filter pipeline

51The core reusable abstraction: one tree widget, fed by a configurable pipeline.

3.1 The Tree widget

52REQ. A single Tree component renders a forest of PackageTree nodes — the │├└ glyphs, the +/- expand indicator, the load/T/C/S columns, the (*) DAG dedup + cycle-guard (PROP-036 §2.12), selection highlight, vertical scroll, and horizontal pan. Every mode (§4) renders through this one widget — there are no bespoke flat-list renderers. Fold state and selection are the widget's (per-instance) state.

3.2 The filter / shape pipeline

53REQ. Data reaches the Tree widget only after passing a configurable pipeline: PackageTree → (filter: which packages) → (shape: how they form a forest) → (order: sibling ordering) → the widget's row list. The pipeline is data-driven and testable in isolation; a mode (§4) is a pipeline configuration, not new rendering code. The three tree shapes (§3.3) and the orderings (§7.2) are pipeline settings, selectable by the user and persisted (§9).

3.3 The three tree shapes

54REQ. The pipeline offers three shapes, selectable per context on the F2 sort menu (§7.2), default = (a):

  • 55(a) members-as-roots + full subtrees — each package in the filter set is a forest root shown with its entire dependency subtree (cross-type deps included); DAG dedup via (*).
  • (b) load-type forest — a package is a root only if no other member of the same filter set depends on it; children are its same-set dependencies (cross-set deps omitted).
  • (c) pruned tree — the tree from the declared roots, keeping only branches that reach a member of the filter set.

56All three are pipeline configurations over §3.2 — implementing them validates that the filter/shape abstraction is right.

4. Modes

57REQ. vibe tree has three display modes, each a Tree-widget (§3.1) configuration (§3.2) — none is a flat list:

4.1 Tree mode

58REQ. One tree over the whole package set (the current default). Filter = all; shape + order per §3.2/§7.2.

4.2 Sub-tables mode

59REQ. Several trees stacked vertically — one Tree instance per effective-load partition (static / dynamic / no-boot), each under a subheader, in the user-chosen block order (§7.2). Each block is a full tree (per §3.3), not a flat list.

4.3 Tabs mode

60REQ. One tree per tab — a tab bar (Static / Dynamic / No-boot); the active tab shows that partition's tree (per §3.3). Shift+/ switches tabs (§5.3); plain arrows navigate the active tab's tree.

4.4 Mode selection

61REQ. The active mode is chosen from the F3 menu (§7.1) — not a bare cycle key. The active mode is shown in the status line and persisted (§9).

5. The keymap

5.1 The mode-aware registry

62REQ. A single keymap registry maps each context (base mode, and each modal) to the set of actions available there, each with its key(s) and its footer label. The footer (§5.2) renders only the actions valid in the current context — an action unavailable in a mode (e.g. sub-table block reorder in tree mode) is absent from both the keymap and the footer. Routing (§1.5) consults the registry; no context re-implements dispatch.

5.2 The F-key scheme + footer

63REQ. Primary actions are on function keys: F1 Search Everywhere (§7.3), F2 sort menu (§7.2), F3 mode menu (§7.1), F4 settings (opens the vibe prefs settings UI — PROP-041), F6 copy / Shift+F6 copy-settings (§10.2). The footer lists the active keys with short labels; Shift is written as (e.g. Shift+F6↑F6) to keep hints short.

64REQ. The footer renders as two centred rows (§2.2.5 #spacing): the F-key command row (F1F6) above, the navigation + Enter/Esc row below, each a run of key desc pairs separated by a dim and centred under the screen. Only the keys valid in the current context appear. A single left-aligned run jamming every hint onto one line is a violation — the footer carries visual rhythm like the rest of the surface (§2.2.5).

5.3 Tree interaction keys

65REQ. Within a tree: / move selection (+ scroll), / pan horizontally, Space folds/unfolds the selected node, Enter opens the detail card (§8). Shift+/ switches tabs app-wide — the tree's display tabs (in tabs mode) and the Search Everywhere category tabs (§7.3); plain / stay tree-pan (which is why tab-switch takes Shift). These direct keys are exempt from the F-key scheme (they are navigation, not commands).

5.4 Focus groups + Tab Order

66REQ. A screen's controls are partitioned into focus groups — cohesive clusters the user steps between with Tab (and Shift+Tab backwards). Within a group the group's own keys act (arrows move a radio selection, typing edits a field, Enter activates a button). A screen with more than one focus group declares a Tab Order — the explicit sequence Tab cycles — per screen; a single-group screen has no Tab Order (Tab is inert there). Tab never switches display tabs (that is Shift+←/Shift+→, §5.3) and never activates a control (that is Enter).

67REQ. The focused group is visually marked (the theme's selection/accent), so the user sees where Tab will land next. Example Tab Orders: the F2 sort menu (§7.2) cycles its radio groups — "Sort by" → "Shape" (→ "Block order" in sub-tables mode); a settings form (PROP-041 §4) cycles its field groups; the quit-confirm dialog (§7.4) cycles OK → Cancel.

7. Menus & dialogs

7.1 F3 — the mode menu

69REQ. F3 opens a Menu (§2.4) of the display modes (§4). Selecting one switches the mode. This replaces any bare mode-cycle key.

7.2 F2 — the sort menu

70REQ. F2 opens a dialog whose content depends on the active mode:

  • 71tree & tabs modes — one RadioGroup (§2.7): alphabetical / topological (the sibling order, §3.2). One group; no group chrome needed.
  • sub-tables mode — two Groups (§2.6): a "sort" group (alphabetical / topological) and a "block order" group (a RadioGroup over the static / dynamic / no-boot block orderings). Group names sit at each frame's top-right.

72The tree shape (§3.3) is also chosen here (a further group), per context. Choices persist (§9).

7.4 Escape — quit with confirmation

76REQ. At the base screen, Esc opens a confirm dialog ("Really quit?") with Yes/No buttons; Yes/Enter-on-Yes quits, No/Esc returns. The confirmation exists because loading may be slow and Esc is used pervasively in modals — an accidental single Esc must not discard the session.

8. The detail card

77REQ. Enter on a package opens the detail Card (§2.9) — a real form, not a glued text blob:

  • 78a light panel background with dark text (a "paper" card), distinct from the tree beneath;
  • bold field headers; blank-line / padded spacing between fields;
  • an esc [x] close affordance drawn as a pseudographic cross in the top-right corner;
  • long values wrap (never truncate) and each wrapped value line can be copied to the clipboard (a per-line copy affordance);
  • Esc / the [x] closes it.

79The card's content is the package detail from PROP-036 §2.11 (name, group, version, kind, load, transitive + why, condition, STATIC.md, source, hash, dependencies, boot path).

9. Settings persistence

80REQ. UI state persists under ~/.vibe/tree/ as one or more JSON files. Saved: the active mode (§4.4), the per-context sort + shape + block-order choices (§7.2, §3.3). On TUI launch the settings are loaded and the UI restored to them; a missing/corrupt file falls back to defaults (never a hard error). Writing is atomic; the directory is created if absent. (This is the machine-global ~/.vibe/ config root, distinct from the project's .vibe/.)

10. The copy system

10.1 Per-screen copy providers

81REQ. Each screen (tree/sub-tables/tabs; the card) supplies a copy provider — an object that knows how to serialize what is currently shown into a copy format. A tree provider serializes the tree with its current display options; the card provider serializes the card's fields. Copy is "what I see is what I copy": in tabs mode only the active tab is serialized.

10.2 F6 copy / Shift+F6 copy-settings

82REQ. F6 copies the current screen straight to the clipboard as text (the provider's default format). Shift+F6 opens the copy-settings modal: a RadioGroup for format (Markdown / PNG — and later PlantUML / Mermaid) and a RadioGroup for destination (clipboard / file). Confirming with destination = file pushes the file-path modal (§10.5) over it (§6 stack).

10.3 Markdown export

83REQ. Markdown is a real serialization, not a screen-buffer scrape: the tree renders as a pseudographic tree inside a fenced block (or a nested list); the card renders as well-formatted Markdown (headers + fields), not a raw text dump.

10.4 PNG export

84REQ. PNG export (a rasterized tree image; a designed package info-card image) is reserved — selecting PNG opens the ComingSoon modal (§2.10) until the rasterization (font + image crates) is built. Named non-goal-for-now (§12).

10.5 Destination — clipboard vs file

85REQ. Clipboard writes via the platform clipboard. File destination pushes a modal with a TextField (§2.8) path entry plus Save and Cancel Buttons (§2.5): arrow/Tab focus, Enter on a button acts, Esc cancels back to copy-settings. The path field is plain text for now (a later REQ enriches it).

11. AI-Native Rust discipline

86REQ. All code implementing this contract follows the AI-Native Rust discipline (spec://org.vibevm.ai-native/core-ai-native): cells with single registration points and no sibling-cell coupling; specmark::scope! on every file citing the governing PROP-037 anchor; per-fn #[spec(implements = "spec://…#…")] where a fn implements a specific REQ; anyhow at the command edge, typed errors citing REQs in library layers, no unwrap/expect in domain logic; the ≤600-line file budget; and green conform + specmap at every commit. Each REQ anchor here is an addressable target the code traces to — that traceability is what keeps the feature set navigable.

12. Non-goals

  • 87Non-English localization content — the i18n mechanism ships (PROP-039 §8, §13.4); no non-English catalogues are shipped now, and English is the only mandatory-complete locale.
  • PNG export — reserved behind ComingSoon (§10.4) until the rasterization spike lands.
  • The AIUI surface itself — not built now; this TUI is its prototype (§13.1, PROP-039 §11.3). The Search-Everywhere StructureProvider (AI-Native specmap nodes) is likewise reserved (§7.3) — the same engine, added later.
  • PlantUML / Mermaid copy formats — later additions to §10.2.
  • A settings UI — settings are edited via the menus (§7) and persisted (§9); no dedicated settings editor screen.
  • Non-tty operation--json / --plain (PROP-036) remain the machine and fallback surfaces; this contract governs the interactive TUI only.

13. Built on the action system (vibe-actions, PROP-039)

88This TUI is the first consumer and the prototype surface of the action system (PROP-039). This section adapts the architecture above onto it and is authoritative where it upgrades an earlier section.

13.1 The TUI is a Surface; the Model is the serialisable view

89REQ. The TUI is a Surface (PROP-039 §11.1) over vibe-actions; it owns rendering and event capture and nothing of the action core. The Model (§1.3) is the source of the serialisable ModelView (PROP-039 §11.2) — focus, the modal stack, the visible rows, the active tab/mode, the selection, and the set of enabled actions with their reasons. No rendering type leaks into the Model. This makes the TUI the prototype that proves the AIUI (PROP-039 §11.3): the same tree can later be driven headless with no change to this crate's model/controller.

13.2 Commands are addressed actions

90REQ. Every TUI command is a vibe-actions Action (PROP-039 §3) in the group vibe.tree, addressed action://vibe.tree/<name>, carrying a mandatory human-readable name + description (§13.4), a typed param schema, and a typed enablement over a TreeCtx snapshot (the mode, the selection, the active tab). This upgrades §1.5's "typed intent enum": the Controller resolves an event to an ActionAddr and calls invoke (PROP-039 §7.1); it no longer switch-matches a local enum.

13.3 The keymap binds keys to addresses

91REQ. The mode-aware keymap (§5.1) binds each key/chord, per context, to an (action://vibe.tree/<name>, params) (PROP-039 §9). The footer label (§5.2) is the action's name; the footer lists exactly the actions enabled in the current context (their enablement predicate, PROP-039 §6.2). Tree navigation keys (§5.3) remain direct (navigation, not commands).

13.4 i18n is real

92REQ. This upgrades §1.6 from "i18n-ready" to the shipped vibe-actions i18n (PROP-039 §8): every action and UI string is an address-keyed catalogue entry (action.vibe.tree.<name>.name / .description) with an inline English default; the resolved label keeps {value, original_en} so Search Everywhere (§7.3) matches the English text under any locale. English is the only mandatory-complete locale and is checked by the legibility gate (PROP-039 §8.4).

13.5 The action catalogue

93REQ. The vibe.tree actions at ship — each an addressed Action with a name + description; the key is its default binding (the map, not the identity):

94
Address (action://vibe.tree/…) Key Name Description
search.everywhere F1 Search Everywhere Search packages, cards, and actions; run a found action.
sort F2 Sort & shape… Choose the ordering and tree shape for the current view.
mode.set F3 Switch mode… Switch between tree, sub-tables, and tabs display.
copy F6 Copy Copy the current screen (Markdown) to the clipboard or a file.
copy.settings ↑F6 Copy settings… Choose the copy format and destination.
fold.toggle Space Fold / unfold Fold or unfold the selected node.
card.open Enter Open details Open the detail card for the selected package.
tab.next / tab.prev ↑→ / ↑← Next / previous tab Move between tabs in tabs mode.
quit Esc (base) Quit Leave vibe tree (with confirmation).

95REQ. This catalogue is the enumerable source for both the footer and the Search Everywhere ActionProvider (§7.3): adding a command means registering an Action (address + name + description + enablement), which then appears in the footer, the keymap, and Search Everywhere with no further wiring (PROP-039 §4.3, §12.2).

13.6 What this leaves to PROP-039

96REQ. The action core — address, registry, params, context, invoke, i18n, the keymap resolver, the Search Everywhere engine + provider trait, and the Surface seam — is owned by vibe-actions (PROP-039); this contract owns only the vibe.tree specifics: the concrete actions (§13.5), the three providers' key/resolve/navigate (§7.3), the TreeCtx shape, the F-key map, the theme, and the TUI Surface. The layering law of §1 is extended: the action core never leaks into the TUI, and TUI rendering never leaks into the action core.

For an agent

This page has a machine mirror. The citation carries the version rather than latest, so what an agent quotes does not move under it.

spec://org.vibevm.core/vibevm@1.0.0/modules/vibe-cli/PROP-037-tree-tui

.md.xmlllms.txt