cyberine
Design notes

TUI Config Interaction Standard — T418 Tier-1 Design Doc

Status: design artifact only — no code built. Tiers 2-3 (shared primitives + migration + settings screen) are TTY+decision-gated and not included here. Source cross-checked: src/tui/, src/core/config.ts, USAGE.md.


1. The North Star Pattern (Claude Code /config as reference)

Claude Code's /config command is the interaction model that every Cyberine TUI interactive surface should converge on. It is not being mimicked for mimicry's sake; it is the right model because it matches the terminal UX genre Cyberine already inhabits (Ink TUI, no mouse by default, keyboard-first) and because users who run Cyberine alongside Claude Code already know the pattern.

1.1 Structural elements

The pattern has five elements that always appear together:

  1. Tab navigation across categories. The top of the view shows a horizontal tab bar: category labels separated by spaces, the active one highlighted with the accent color. Tab/Shift+Tab or h/l cycles categories; the rest of the view is scoped to the active category.

  2. Type-to-filter search box. A single-line input at the top of the list area (below the tab bar). Typing narrows the list to rows whose label or value contains the query as a subsequence. The search box always has focus when the overlay is open; navigation keys (Up/Down/j/k) move the cursor without leaving the search box.

  3. Two-column aligned list. Each row has a fixed-width left column (label, left-aligned, muted color) and a right column (current value, left-aligned, accent color for non-default values). The gutter symbol (existing SYM.gutter) marks the selected row. Both columns share the same monospace grid so values across all rows line up vertically.

  4. Keyboard-driven row editors. Pressing Enter on a selected row opens an inline editor appropriate for that row's type:

    • Toggle (bool): Enter flips the value immediately; the right column updates in place; no confirmation.
    • Enum picker (fixed set): the right column opens a compact inline list of valid options; Up/Down selects; Enter confirms; Esc cancels.
    • Value editor (free text or number): the row switches to an inline text input pre-filled with the current value; Enter saves; Esc cancels. Editing is always modal within the row — no separate screen.
  5. Footer key-hint bar. The existing KeyHints component (src/tui/components/KeyHints.tsx) renders the contextual hints at the bottom of the screen. While the config/settings overlay is active, the hints reflect only the active interaction state: idle list shows Tab categories /jk navigate Enter edit Esc back; while editing a toggle/enum shows Enter confirm Esc cancel; while in a text editor shows Enter save Esc cancel Ctrl+U clear.

1.2 Layout sketch (character-cell, 80-col example)

 [Fleet] [Settings] [Providers]          <- tab bar (ACCENT = active)
  filter: _                              <- search box
  ------------------------------------------
  > fleet       compete                  <- selected row (SYM.gutter + accent value)
    permission  default
    model       deepseek/deepseek-v4-pro
    thinking    on
    theme       dark
  ------------------------------------------
  Tab categories  /jk navigate  Enter edit  Esc back   <- KeyHints footer

1.3 Rendering stack constraints

Every component in the Cyberine TUI runs inside Ink 5 + React 18 (type: module, tsx). All sizes are in character cells. The pattern must respect:

  • No emoji anywhere (global code-style rule).
  • BMP-only glyphs — same contract as the mascot.
  • The KeyHints component already knows how to render a Hint[] list and truncate on narrow terminals.
  • The FuzzyPicker component (src/tui/components/FuzzyPicker.tsx) already implements the type-to-filter search box and the edge-scroll windowed list. The settings view is NOT a FuzzyPicker (items are not searched for selection, they are browsed for editing), but the search box and windowing logic can be reused or cloned.

1.4 Primitive shapes needed for Tier 2

A builder of shared TUI primitives should produce:

  • TabbedView: renders the tab bar; accepts tabs: { id: string; label: string }[] + activeTab: string + onSwitch: (id) => void. Uses useInput for Tab/Shift+Tab and h/l. Wraps whatever content the caller renders below it.
  • SearchableList<T>: wraps the FuzzyPicker search-box + windowed-list pattern but with edit semantics: items are not selected-to-dismiss, they are selected-to-edit. Accepts items: T[], getLabel: (T) => string, getValue: (T) => string, renderRow: (T, selected) => JSX, onEdit: (T) => void, onClose: () => void. Maintains internal query state and filter. No onSelect for dismissal — Enter calls onEdit.
  • KeyHintFooter: a thin wrapper around KeyHints that accepts a context: 'idle' | 'editing-toggle' | 'editing-enum' | 'editing-value' prop and renders the right hint set for each state. Eliminates the per-surface hint array duplication.
  • Row-level editors (rendered inline, not as overlays):
    • ToggleEditor: renders on | off with the current value highlighted; Enter flips and auto-confirms.
    • EnumEditor: renders a compact vertical list of valid values; Up/Down selects; Enter confirms.
    • ValueEditor: a single-line text input (wraps useTextInput); pre-filled with current value; Enter saves; Ctrl+U clears; Esc cancels.

2. Interactive Surface Inventory

Every interactive surface in the current Cyberine TUI, with its current interaction style and its target mapping.

2.1 Fleet Hub (src/tui/app.tsx) — cyberine agents / npm run tui

Surface 1: Runs list (fleet list, main view)

  • Current: arrow keys / j/k navigate a flat or grouped list of runs; Enter opens detail; no search by default.
  • Currently has: text filter overlay (/ key opens FuzzyPicker in filter mode), folder picker overlay (n opens FuzzyPicker), view-mode cycle (Ctrl+S), grouping toggle.
  • Target mapping: the runs list itself does not need the config pattern (it is a task monitor, not a settings UI). The existing FuzzyPicker approach for the run filter is already correct. The folder picker is also correct. No migration needed.

Surface 2: Folder picker (FuzzyPicker overlay, n key)

  • Current: FuzzyPicker overlay with search box, windowed list, footer hints. Pattern already matches the type-to-filter + footer-hint target.
  • Target mapping: compliant. No change needed.

Surface 3: Run filter (FuzzyPicker overlay, / key)

  • Current: FuzzyPicker overlay, same as folder picker.
  • Target mapping: compliant. No change needed.

Surface 4: Quick reply bar (Space on a run, QuickReply component)

  • Current: single-line text input (useTextInput), footer hint bar (QUICKREPLY_HINTS). Simple text-entry, no navigation.
  • Target mapping: not a settings surface; stays as a direct text input. No migration.

Surface 5: Steer input (SteerInput component)

  • Current: single-line text input with Ctrl+V paste; footer hint bar (STEER_HINTS).
  • Target mapping: same as Quick reply — direct text input, not a settings surface.

Surface 6: Compose panel (new session, composeActive state)

  • Current: text input for the initial task; footer hint bar (COMPOSE_HINTS).
  • Target mapping: direct task input, not a settings surface.

Surface 7: Chats view (chat session list, listSource === 'chats')

  • Current: j/k navigation over chat sessions; Enter resumes; Ctrl+X deletes; hint bar (CHATS_HINTS).
  • Target mapping: this is a browsable list like the runs list; not a settings surface. The two-column pattern (session title left, age right) is already in use in ChatSessionList. No migration needed for the list itself, but the column-alignment convention from the config pattern should be adopted as a style norm.

Surface 8: Session browser (global, sessionBrowserOpen, s key from hub)

  • Current: j/k navigation, folder headers, session rows, persistent bottom bar, footer hint bar (SESSION_BROWSER_HINTS).
  • Target mapping: browsable list + action bar; not a settings surface. The folder-header / session-row two-column layout should adopt the label-left / value-right alignment norm from the config pattern (folder name left, session count right).

Surface 9: Hermes hub welcome (hubWelcomeOpen, WelcomeBanner)

  • Current: read-only display of available adapters, judges, workflows. No keyboard-driven editing.
  • Target mapping: informational surface, not interactive. No migration.

Surface 10: Help overlay (HelpOverlay, ? key)

  • Current: full-screen keybinding reference; any key closes. Two-column layout (key left, label right) — this layout already follows the target norm.
  • Target mapping: read-only; compliant already. No migration.

Surface 11: Worker peek (WorkerPeek, Enter on a worker row in detail)

  • Current: scrollable log output; Ctrl+U/Ctrl+D scroll; footer hint bar (HINTS.peek).
  • Target mapping: log viewer, not a settings surface. No migration.

Surface 12: Diff viewer (DiffViewer, d key in run detail)

  • Current: scrollable diff output; j/k, Ctrl+U/Ctrl+D, g/G scroll; footer hints (HINTS.diff).
  • Target mapping: read-only viewer. No migration.

Surface 13: Workflow monitor (WorkflowMonitor)

  • Current: read-only workflow-step graph; Esc back; hint bar.
  • Target mapping: read-only viewer. No migration.

2.2 Chat REPL (src/tui/ChatApp.tsx) — cyberine chat

Surface 14: Main chat input (InputBox component)

  • Current: full-width text input with cursor, history (Up/Down), paste collapse, @-mention expansion, double-Esc clear, Shift+Enter newline. Footer: hint bar from ChatStatusLine.
  • Target mapping: primary text-entry surface, not a settings surface. Already has good UX. No migration.

Surface 15: /config command (inline text command, no TUI overlay)

  • Current: typed as /config [<key> <value>] in the chat input. Bare form prints current values as a system message. With key+value sets one setting (fleet, permission, model, thinking, theme) and prints a confirmation. This is a text-only interaction — no keyboard navigation, no visual list.
  • Target mapping: this surface is the PRIMARY CANDIDATE for migration to the north-star pattern. A future settings screen (tier 2-3) should replace or augment this with the tab nav + two-column list + inline editor pattern. The /config text command should remain as a power-user shortcut even after the visual settings screen exists, because it is scriptable and muscle-memory-friendly.
  • Current managed keys: fleet, permission, model, thinking, theme. These map directly to the config-knob inventory in section 3.

Surface 16: /model picker (ModelPicker component, FuzzyPicker-backed)

  • Current: fuzzy picker overlay listing all models from the model catalog; current model marked with (current); arrows+Enter pick; Esc cancels.
  • Target mapping: already matches the type-to-filter + footer-hint pattern. This is the canonical implementation of the enum-picker element for a large open-ended set. The ModelPicker renders 2-col rows (model id left, context-window + notes right) — this is the right layout. No migration needed; use as a reference for how to build the provider/model picker in the settings screen.

Surface 17: Session picker (SessionPicker component, FuzzyPicker-backed)

  • Current: fuzzy picker overlay for cyberine chat --resume; session title left, id+age right.
  • Target mapping: already correct two-column alignment. No migration.

Surface 18: Question prompt (QuestionPrompt component)

  • Current: multiple-choice question rendered inline in the transcript; Up/Down or number key selects; Enter confirms.
  • Target mapping: this is the inline enum-picker element applied to planner-generated questions. The pattern is correct. The keyboard nav (Up/Down + Enter) should be the same as the EnumEditor primitive in the settings pattern.

Surface 19: /mode and /xmode commands (inline text)

  • Current: /mode with no arg shows current mode + cycle instructions; with an arg sets the permission mode. Shift+Tab cycles through modes.
  • Target mapping: mirrors /config permission. Will be subsumed by the settings screen's permission-mode row in tier 2. The Shift+Tab cycle is the toggle/enum-cycle shorthand that should be preserved.

3. Config-Knob Inventory (Settings Screen Spec for Tier 2)

All knobs that a future persistent settings screen would expose, cross-checked against src/core/config.ts (the Config interface and the config object initialization block) and the env-var table in USAGE.md.

3.1 TUI appearance

Display labelEnv varTypeDefaultRead site
ThemeCYBERINE_THEMEenum picker (dark | cyan | claude-dark | light | ansi)darksrc/tui/theme.ts line 27 (resolveVariant()); THEME_NAMES exported
Mascot variantCYBERINE_MASCOTenum picker (dna-infinity | infinity-wave | infinity-3d)dna-infinitysrc/tui/brand/mascot.ts
Mascot animateCYBERINE_MASCOT_ANIMATEtoggle (on/off)auto (on locally, off over SSH)src/tui/brand/mascot.ts
Alt-screen (fleet)CYBERINE_ALTSCREENtoggle (on/off)on for fleet, off for chatsrc/tui/app.tsx line 344; src/tui/ChatApp.tsx
Terminal bellCYBERINE_BELLtoggle (on/off)onsrc/tui/app.tsx line 336
OS notificationsCYBERINE_NOTIFYtoggle (on/off)onsrc/tui/app.tsx line 338
Mouse mode (chat)CYBERINE_MOUSEtoggle (on/off)on (disable with CYBERINE_MOUSE=0)src/tui/ChatApp.tsx
Wheel linesCYBERINE_WHEEL_LINESvalue (integer 1-10)3src/tui/ChatApp.tsx
Kitty keyboardCYBERINE_KITTY_KEYBOARDtoggle (on/off)offsrc/tui/ChatApp.tsx

3.2 Chat REPL settings

Display labelEnv var / config keyTypeDefaultRead site
Fleet mode (execution mode)CYBERINE_MODE (runtime: executionModeRef)enum picker (compete | swarm | research | deep | smart | rush)competesrc/tui/ChatApp.tsx ExecutionMode type; /config fleet handler
Permission modeCYBERINE_CHAT_PERMISSION_MODE (runtime: permissionMode)enum picker (default | acceptEdits | plan | auto | bypassPermissions | dontAsk)defaultsrc/core/config.ts config.defaultPermissionMode; ChatApp line 806
Planner modelCYBERINE_CHAT_MODEL (runtime: activeModel)value (free text, validated against catalog)first resolved providersrc/core/config.ts config.chatModel; ChatApp /config model handler
Planner providerCYBERINE_CHAT_PROVIDERvalue (provider id from registry)first resolved providersrc/core/config.ts config.chatProvider
Show thinkingCYBERINE_CHAT_THINKING (runtime: showThinking)toggle (on/off)onsrc/tui/ChatApp.tsx line 696 (setShowThinkingGlobal); /config thinking handler
Chat max tokensCYBERINE_CHAT_MAX_TOKENSvalue (integer)8192src/core/config.ts config.chatMaxTokens
Context window hintCYBERINE_CHAT_CONTEXT_WINDOWvalue (integer)65536src/core/config.ts config.chatContextWindow
Model cycle listCYBERINE_CHAT_MODELSvalue (comma-separated model ids)(unset)src/tui/ChatApp.tsx Ctrl+T handler
User memoryCYBERINE_USER_MEMORYtoggle (on/off)onsrc/store/userMemory.ts; USAGE.md line 4699
Status line commandCYBERINE_STATUSLINE_CMDvalue (shell command string)(unset)src/tui/hooks/useStatuslineCmd.ts
PersonaCYBERINE_PERSONAenum picker (cyberine | none)cyberinesrc/core/config.ts config.persona

3.3 Dispatch settings

Display labelEnv varTypeDefaultRead site
Build fleetCYBERINE_BUILD_FLEETvalue (comma-separated worker ids)(unset)src/core/config.ts config.buildFleet
Route modeCYBERINE_ROUTEenum picker (auto | off)autosrc/core/config.ts config.routeMode
Default dispatch modeCYBERINE_MODEenum picker (same as fleet mode above)(unset = no preset)src/core/config.ts config.mode
Loop max tokensLOOP_MAX_TOKENSvalue (integer)16000src/core/config.ts config.loopMaxTokens
Loop max tool roundsLOOP_MAX_TOOL_ROUNDSvalue (integer)25src/core/config.ts config.loopMaxToolRounds
Task trustedCYBERINE_TASK_TRUSTEDtoggle (on/off)onsrc/core/config.ts config.taskTrusted
Host sandbox modeHOST_SANDBOX_MODEenum picker (auto | seatbelt | bubblewrap | docker | gvisor | podman | none)autosrc/core/config.ts config.hostSandboxMode
Daily spend capCYBERINE_DAILY_USD_CAPvalue (number, USD)10src/core/config.ts config.dailySpendCapUsd
Smart routerCYBERINE_SMART_ROUTERtoggle (on/off)offsrc/core/config.ts config.smartRouter

3.4 Worker mode

Display labelEnv varTypeDefaultRead site
Worker modeCYBERINE_WORKER_MODEenum picker (code | architect | ask | debug | custom ids)(unset = no override)src/core/config.ts config.workerMode; src/core/personas/registry.ts
OpenCode modelOPENCODE_MODELvalue (model id)deepseek/deepseek-v4-prosrc/core/config.ts config.openCodeModel
OpenCode agentOPENCODE_AGENTenum picker (plan | build)plansrc/core/config.ts config.openCodeAgent

3.5 Advanced / CYBERINE_HOME

Display labelEnv varTypeDefaultRead site
CYBERINE_HOMECYBERINE_HOMEvalue (directory path)~/.cyberinesrc/core/config.ts line 21
Plans dirCYBERINE_PLANS_DIRvalue (directory path)~/.cyberine/plans/<project>src/core/config.ts config.plansDir
Exec backendCYBERINE_EXEC_BACKENDenum picker (local | docker | ssh)localsrc/core/config.ts config.execBackend
Trust gateCYBERINE_TRUST_GATEtoggle (on/off)onsrc/core/config.ts config.trustGateEnabled
RulesCYBERINE_RULEStoggle (on/off)onsrc/core/config.ts config.rulesEnabled
Repo mapCYBERINE_REPO_MAPtoggle (on/off)onsrc/core/config.ts config.repoMapEnabled
Instruction filesCYBERINE_INSTRUCTION_FILEStoggle (on/off)onsrc/core/config.ts config.instructionFilesEnabled
Context providersCYBERINE_CONTEXT_PROVIDERS_ENABLEDtoggle (on/off)onsrc/core/config.ts config.contextProvidersEnabled

4. Tab Category Proposal for the Tier-2 Settings Screen

Based on the knob inventory above, a four-tab layout covers all knobs without overflow:

  1. Chat — fleet mode, permission mode, planner model/provider, thinking, max tokens, context window, persona, model cycle list.
  2. Appearance — theme, mascot variant, mascot animate, alt-screen, bell, OS notifications, mouse, wheel lines, kitty keyboard, status line command.
  3. Dispatch — build fleet, route mode, loop max tokens/rounds, sandbox mode, daily spend cap, task trusted, smart router, exec backend.
  4. Advanced — CYBERINE_HOME, plans dir, trust gate, rules, repo map, instruction files, context providers, worker mode, OpenCode model/agent, user memory.

The /config command in ChatApp manages a sub-set of "Chat" tab knobs today (fleet, permission, model, thinking, theme). The visual settings screen should present all four tabs; /config remains as a shorthand for the Chat tab only.


5. Cross-Check Statement

Every knob name, env var, default, and read site in section 3 was verified directly against:

  1. The Config interface type definitions in src/core/config.ts (lines 26-612).
  2. The config object initialization block in src/core/config.ts (lines 627-882).
  3. The /config command handler in src/tui/ChatApp.tsx (lines 2068-2201) for the five currently managed keys.
  4. The env-var table in USAGE.md (lines 4699-4763 and surrounding sections).
  5. src/tui/theme.ts for CYBERINE_THEME and THEME_NAMES.
  6. src/tui/app.tsx for CYBERINE_BELL, CYBERINE_NOTIFY, CYBERINE_ALTSCREEN.

No knob name was invented from memory. Any knob that appears only in USAGE.md without a corresponding config.ts entry is documented with its USAGE.md location.


6. Tier 2-3 Scope (NOT in this run)

The following work is gated on a real TTY session and a scope decision from the user. It is documented here so the next agent picks up from a clear spec, not from the ground.

  • Build the shared primitives: TabbedView, SearchableList, KeyHintFooter, ToggleEditor, EnumEditor, ValueEditor.
  • Wire a /settings slash command (or s key from a future settings entry point) that renders the four-tab settings overlay on top of the chat REPL.
  • Migrate the five existing /config keys (fleet, permission, model, thinking, theme) into the Chat tab of the new settings screen, keeping /config as an alias.
  • Add persistence: the settings screen writes changed values via upsertEnvKeys (the same mechanism /config theme and /config model already use) to ~/.cyberine/.env.
  • Migrate the fleet hub's folder-picker and run-filter overlays to use SearchableList once it exists (backward-compatible; same behavior, shared implementation).
  • Visual verification on a real TTY after each primitive is built (no snapshot tests — project rule).