feat(cli): full-screen terminal app (#1544, row 55)
Bare `mosaic` on a terminal opens the app. It has Inbox, Tasks, Agents
and Trail screens, help, and a decide overlay, and it uses the alternate
screen, raw mode, resize and SIGTERM, and restores the terminal on exit.
draw(state) in screens.mjs is pure. cli.mjs exports resolveDecision,
which `mosaic decide` and the app share, so both write the same lines
and ask the same question. A decision resolves on `y` only while
decideShown() says the whole decide box is on screen. The app reads that
at the key. A box that doesn't fit is replaced by "Too small to show this
decision", and any key answers No. scripts/mosaic sends no arguments to
packages/cli (a decided scope addition), so the queue dispatch test
follows. The cli README and docs/TOOLS.md describe the app.
Darkwing built it (agents/darkwing/work/queue-55), in three rounds.
Round 3 was approved by Dewey (27308) and Filbert (27309) on manifest
59f1b9b4, 9 paths. Their non-blocking notes go to row 69 (#1562): a
narrow-terminal test for the decideShown MIN_COLS guard, a way out from
the hidden box, a U+18D08 width test, a cut box keeping Esc back, and
the README row count stated as an example.
Sage's gate on 3634d06e, sequential and teed: node suites cli 110 and
queue 150, 0 failed; test-auth 15, test-conductor 17, test-config 24,
test-discord 66, test-extension-package 18, test-foundation 44,
test-queue 27, test-release 14, test-task 98, all 0 failed (Docker on).
122b1223..3634d06e touches none of the 9 paths.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
+74
-2
@@ -4,12 +4,16 @@ Jason's front door to the bus (slice 1 row S4, #1521): `mosaic inbox`,
|
||||
`decide`, `tasks`, `agents` and `trail`, and the bus host `mosaic bus
|
||||
start|stop|status` with its Discord notifier (row S4, #1521); the session
|
||||
launcher in the host and `mosaic talk`, `stop` and `launches` (row S6,
|
||||
#1523). Brief: `docs/plans/2026-10-04_slice-1.md`, rows S4 and S6. Rulings:
|
||||
lead decisions 70 and 77 in `docs/plans/2026-09-26_lead-decisions.md`.
|
||||
#1523); and `mosaic` with no argument, the full-screen app (queue row 55,
|
||||
#1544). Briefs: `docs/plans/2026-10-04_slice-1.md`, rows S4 and S6, and
|
||||
`docs/plans/2026-10-10_design-implementation.md`, "Full-screen terminal
|
||||
app". Rulings: lead decisions 70 and 77 in
|
||||
`docs/plans/2026-09-26_lead-decisions.md`.
|
||||
|
||||
Every command reads through the broker. None opens the SQLite file.
|
||||
|
||||
```sh
|
||||
scripts/mosaic # the app, on a terminal
|
||||
scripts/mosaic inbox
|
||||
scripts/mosaic decide 3f2a9c1e yes --note "ship it"
|
||||
scripts/mosaic trail 3f2a9c1e-… # then: scripts/mosaic trail vikunja:1/7
|
||||
@@ -70,6 +74,74 @@ mosaic launches list [--json]
|
||||
`launches list` does not, because the file is readable to the same user
|
||||
anyway.
|
||||
|
||||
## The app
|
||||
|
||||
```
|
||||
mosaic
|
||||
```
|
||||
|
||||
With no argument, on a terminal, `mosaic` opens a full-screen app over the
|
||||
same broker reads as the verbs: Inbox, Tasks, Agents, Trail and Help.
|
||||
`src/app.mjs` handles keys and reads; `src/screens.mjs` draws the frames
|
||||
and nothing else.
|
||||
|
||||
- In an agent run it exits 3 with the same refusal as the verbs, before
|
||||
anything else. Without a terminal on both stdin and stdout it exits 4
|
||||
and prints the seat usage (`mosaic launch`, `mosaic seat task`) and the
|
||||
verbs above. A script never gets the app.
|
||||
- The business is the one of the bus host on this data root. With no
|
||||
host the app says so and `:business <id>` picks one.
|
||||
- Keys: `c i t a r` switch views (Talk, Inbox, Tasks, Agents, Trail). `↑↓`
|
||||
or `j k` move, PgUp and PgDn page, Enter opens the selection whole, `v`
|
||||
opens its trail, `:` the command line, `?` help, Esc closes a box, `q`
|
||||
or Ctrl-C quits. On the command line, Esc or Ctrl-C closes it and the
|
||||
arrows type nothing. Commands: `:decide <decision> <option>`, `:trail
|
||||
<task|decision>`, `:business <id>`, `:q`.
|
||||
- Deciding: in the Inbox, digit `n` picks the selected decision's `n`th
|
||||
option and runs `resolveDecision` in `src/cli.mjs`, the code `mosaic
|
||||
decide` runs. The box shows the same lines and the same `[y/N]`
|
||||
question. `y` or `Y` resolves; any other key answers No, and so does
|
||||
quitting. Keys that arrive within 200 ms of the question being drawn
|
||||
are type-ahead and are dropped: a paste such as `1 yes`, or keys typed
|
||||
while a read held the screen, never answer it. After `outcome unknown` the app shows the CLI's sentence and
|
||||
offers the trail (`v`) or a re-read (`r`). No key sends the choice
|
||||
again.
|
||||
- Reads happen on entering a view, after a decision, and every 30 s while
|
||||
no box is open. A failed read keeps the last list on screen and marks
|
||||
it stale. A refused read (exit 3 codes) says refused. Broker text goes
|
||||
through `visible()` from `packages/conversation/src/terminal.mjs`, so
|
||||
control characters and bidi overrides show as `^[` and `<U+202E>`.
|
||||
- Talk (`c`) points to `mosaic talk` for now. The conversation pane and
|
||||
`stop` come in a later row.
|
||||
- 16 ANSI colours. Under `NO_COLOR` the only escape codes in a frame are
|
||||
reverse video on the selected row (the design's tokens keep selection
|
||||
visible without colour). Frames fit 80x24; the layout is designed at
|
||||
120x36, and under 100 columns the nav shows keys only and the Inbox
|
||||
drops RAISED BY. Below 40x10 the frame only says the terminal is too
|
||||
small. If the decide question is open then, the frame says so, and any
|
||||
key answers it No, `y` included. Widths count terminal cells: East
|
||||
Asian Width W and F characters and emoji 2, a one-cell emoji base with
|
||||
U+FE0F 2. ZWJ sequences and flags count more than they draw, so such a
|
||||
row can come out short, never too wide. Wrapping breaks between
|
||||
graphemes, so U+FE0F and combining marks stay with their base.
|
||||
- A decide box taller than the screen cuts the end of the question and
|
||||
nothing else: the decision line, the options, the recommendation, the
|
||||
action and target, what the choice authorizes, the task, your choice
|
||||
and the `[y/N]` question stay. The cut line points to `mosaic inbox
|
||||
--json`. When even those lines don't fit, the box says only "Too small
|
||||
to show this decision. Any key answers No." `y` resolves only while the
|
||||
frame shows the whole box apart from the cut, and the app checks the
|
||||
size at each key, so a terminal shrunk after the question appeared
|
||||
answers No. A gated decision with a task needs 20 rows at 80 columns
|
||||
(21 with a cut question), and 29 rows at 40 columns.
|
||||
- The transport is the verbs' `spawnSync` child, so the screen does not
|
||||
redraw while a read is in flight (up to its 30 s timeout).
|
||||
- On quit, end of input, SIGTERM, SIGINT, SIGHUP or an error (one in a
|
||||
resize redraw included) the app restores the screen, the cursor and
|
||||
cooked mode. After anything but an error it then prints
|
||||
`(app closed with q; N decisions resolved this session)`; an error
|
||||
goes to stderr instead.
|
||||
|
||||
## Bus host
|
||||
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user