Interactive TUI¶
Stage 13–18 Textual UI for browsing and editing music-rig planning/CURRENT state.
Rules¶
- Presentation only — mutations go through shared services (
question_service,todo_service,patchbay_state.propose_*+current_service.commit_*, etc.). - Never parse CLI output; never write YAML from Textual widgets.
- OBS / Ableton / MIDI / Stream Deck / macOS automation remain
NOT_IMPLEMENTED. - Apply lifecycle: Edit → dirty → Ctrl+S / :w Review → Apply → validate → commit → render → refresh. Enter does not apply.
- If the TUI says saved, canonical YAML has the value; reopen shows it; failed
save retains edits (
SaveOutcome: SUCCESS | FAILED | CANCELLED | CONCURRENT_MODIFICATION | VALIDATION_ERROR). - Answering a question does not invent
verification_result(Stage 17).
Launch¶
Prefer the project environment (avoids stale global rig installs):
uv run rig tui
# or, with the project venv activated:
# source .venv/bin/activate && rig tui
uv run rig tui question Q-008
uv run rig tui patchbay PB-B
uv run rig tui todo
uv run rig tui --debug # or RIG_DEBUG=1 — logs python/textual paths + screen push/pop
Screens use RigHeader (not Textual's stock Header) so rapid Questions ↔
Reconcile navigation cannot hit Textual 8.2.8's HeaderTitle lifecycle race.
Modal / screen result ownership¶
Hard rules for result-bearing Screen / ModalScreen:
- Exactly one completion per push. Prefer one semantic handler (
complete()/SingleShotMixin) shared by keyboard bindings and buttonaction=paths. - Do not mix
Binding("enter") → dismisswith a focusedButtonthat also postsButton.Pressed → dismissfor the same physical Enter unless you have proven one action cannot trigger both (preferaction=on buttons). - Child screens return intent; requesters navigate. Example:
AnswerScreendismisses withAnswerResult(next_action=RECONCILE|NONE);QuestionsScreenreloads, then optionallyopen_domain("reconcile", …). - Never
pusha sibling screen from a child and thendismissthe child (Textualdismisspops the top screen — the sibling — leaving the child with an already-finished result Future →InvalidStateErroron a later Esc).
| Screen | Completes result | Opens sibling itself? | Notes |
|---|---|---|---|
| AnswerScreen | yes (AnswerResult) |
no | Parent opens Reconcile |
| ConfirmModal | yes | no | Enter/y/n + button action= |
| SelectModeModal | yes | no | Enter / 1–4 / button action= |
| InputModal | yes | no | Submit / OK / Esc |
| QuestionsScreen | no (list) | yes (Answer, etc.) | Owns post-answer nav |
| ReconcileScreen | no (list) | yes (modals) | — |
Vim-like modes¶
Footer / banner shows -- NORMAL -- / -- INSERT -- / -- COMMAND --.
| Mode | Behavior |
|---|---|
| NORMAL | j/k, gg/G, / n N, i edit, Enter inspect, u undo, U redo, : command |
| INSERT | Normal typing (j inserts j); Esc → NORMAL (keeps field value) |
| COMMAND | :w apply, :q quit (refuses if dirty), :wq apply+quit, :q! discard |
Redo is U (uppercase). Ctrl+r remains Refresh (not redo).
Keyboard (global)¶
| Key | Action |
|---|---|
| j/k or arrows | Navigate |
| gg / G | Top / bottom |
| Enter | Open / inspect (never apply) |
| i / e | Edit fields (INSERT) |
| Ctrl+S / :w | Review changes → Apply |
| A | Add (where supported) |
| / n N | Search / next / previous |
| u / U | Undo / redo staged working-copy edits |
| : | COMMAND mode |
| Ctrl+r | Refresh |
| Esc | NORMAL / back / cancel |
| ? | Context-sensitive help |
| q | Quit on home; back on nested (discard prompt if dirty) |
Questions¶
| Key | Action |
|---|---|
| a | Answer → Save Draft / Answer & Resolve |
| A | Add question |
| r / R | Resolve draft → FINAL (existing answer or Answer screen) |
| V | Verify (observation / verification_result flow) |
| C | Reconcile handoff |
| d | Defer |
| o | Reopen |
| t | Open typed target |
| f | Cycle ACTIVE / OPEN / RESOLVED / UNRECONCILED / DEFERRED / ALL |
| Ctrl+r | Refresh |
Save Draft keeps status OPEN (answer_state=DRAFT). Answer & Resolve
sets FINAL (RESOLVED + resolved_at); reconciled_at stays null. Neither
invents verification_result.
List labels: OPEN/UNANSWERED, OPEN/DRAFT, RESOLVED/UNRECONCILED,
RESOLVED/RECONCILED. Default filter ACTIVE = OPEN + RESOLVED-unreconciled
(excludes RECONCILED/DEFERRED).
After Answer & Resolve under filter=OPEN the row leaves the OPEN view; notification explains reconciliation remains pending.
Patchbay editor¶
| Key | Action |
|---|---|
| e / i / Space | Stage MODE (Space cycles UNKNOWN→NORMAL→HALF-NORMAL→THRU) |
| c | Edit upper/lower connections |
| m | Hardware model (not mode) |
| n / N | Next / prev UNKNOWN mode |
| Ctrl+S / s / :w | Apply staged (optional snapshot) |
| u / U | Undo / redo staged |
| o | Related OPEN question (Q-xxx: <question text>) |
| :q / :q! | Quit / discard quit |
MODE is staged until Apply. SelectModeModal focuses primary; Enter confirms; 1–4 pick.
Coverage matrix¶
| Domain | View | Edit | Add | Round-trip | Vim-modal | Notes |
|---|---|---|---|---|---|---|
| Questions | yes | yes | yes | yes | yes | Answer screen keeps question visible |
| Patchbays | yes | yes | no | yes | yes | Do not Add Pair |
| TODO | yes | yes | yes | yes | yes | |
| Wishlist | yes | yes | yes | yes | yes | |
| Inbox | yes | yes | yes | yes | yes | |
| Changes | yes | yes | yes | yes | yes | |
| Gear | yes | yes | yes | yes | yes | |
| Channel map | yes | yes | no | yes | yes | |
| Routing | yes | yes | no | yes | yes | Structural via CLI |
| MIDI | yes | yes | partial | yes | yes | No TX |
| Controllers | yes | yes | partial | yes | yes | |
| Ableton | yes | yes | no | yes | yes | Metadata |
| Performance | yes | partial | partial | yes | yes | Evidence; no execution |
| Backups | yes | yes | partial | yes | yes | Plan fields; no secrets |
| Sessions | yes | partial | — | — | — | ACTIVE notes |
| Snapshots | yes | no | — | — | — | create/browse only |
| Doctor/Status/Reconcile/Automation | yes | no | — | — | — | Derived / read-only |
Architecture¶
src/music_rig/tui/
modes.py debug.py save_outcome.py
fields.py editors.py editable.py forms.py pickers.py working.py
editable_domains/ # FieldSpec adapters + coverage markers
screens/ # home, questions, answer, create, patchbays, editable, …
rig inspect schema <domain> exposes the same FieldSpecs as the TUI.
Agent inspection¶
See repository-root SKILLS.md (CLI-first; agents prefer CLI over TUI automation):
uv run rig inspect domains
uv run rig inspect cleanup --json