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):
- 20vibevm backend —
vibe-core/vibe-workspace/vibe-spec(thePackageTreebuilder, PROP-036). Unowned by this app. - Model — the app's data: the borrowed/owned
PackageTreeplus all UI state (§1.3). Pure data + logic-on-data; no rendering, no event types. - View — the component library (§2) and the theme (§2.2). Renders Model + component state into a ratatui buffer; no app control flow.
- 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:
- 41
PAD_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_padapplies it. A window whose body sits on the frame is a violation. GUTTER= 1 — group gutter. A control inside aGroupframe (§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.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 (F1…F6) 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.
6. The modal stack
68REQ. Modals form a stack: opening a modal pushes it; it draws over
everything below (each lower layer still visible as backdrop); input goes only to
the top modal; Esc pops the top modal (returning to the one beneath). A feature
may push a modal over a modal — e.g. copy-settings (§10.2) → file-path (§10.5) is
a depth-2 stack. The base screen is the bottom of the stack; Esc at the bottom
triggers quit-confirm (§7.4), it does not pop.
7. Menus & dialogs
7.3 F1 — Search Everywhere
73REQ. F1 opens the Search Everywhere window — the vibe-actions Search
Everywhere engine (PROP-039 §10), in the IntelliJ IDEA idiom: a hybrid "All"
tab that searches everything, plus per-category tabs that narrow it
(Tab/Shift+Tab cycle; the "All" tab carries a category checkbox filter). Three
providers ship (PROP-039 §10.4):
- 74Packages — by name (
PackageProviderover thePackageTree); selecting reveals the package in the tree. - Cards — inside every field of the package detail cards (§8)
(
PackageFieldProvider: name, version, kind, license, load-type, origin, path, deps, diagnostics…); selecting opens the card focused on that field. - Actions — all
vibe.treeactions (§13.5) by address, name, and description (ActionProvider); selecting invokes the action in place (a command performs and closes; a toggle stays open). Disabled actions render greyed with their "why disabled" reason and their keybinding.
75Matching falls back to the name/description lane when nothing matches by id or
another field (PROP-039 §10.3). Results render through one normalized row (icon ·
primary · a right-aligned keybinding · group). This supersedes the reserved
ComingSoon stub. A future StructureProvider (AI-Native specmap nodes) plugs into
the same engine with no TUI change.
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):
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.