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:
@@ -1,8 +1,9 @@
|
||||
// `scripts/mosaic queue` (A2). The dispatch must leave every other call to
|
||||
// scripts/mosaic exactly as it was: the seat CLI sees the same argv, working
|
||||
// directory, environment and stdin under the new script as under the pre-A2
|
||||
// copy in fixtures/mosaic-pre-a2.sh. Both CLIs are fakes that record what
|
||||
// they got; nothing here runs a seat or touches a queue.
|
||||
// copy in fixtures/mosaic-pre-a2.sh. Row 55 (Sage, 2026-10-11) sends the call
|
||||
// with no argument to the app in packages/cli instead. The CLIs are fakes that
|
||||
// record what they got; nothing here runs a seat or touches a queue.
|
||||
import assert from "node:assert/strict";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
||||
@@ -23,13 +24,13 @@ process.stderr.write("fake err\\n");
|
||||
process.exitCode = Number(process.env.EXIT);
|
||||
`;
|
||||
|
||||
// A root holding `script` as scripts/mosaic and both fake CLIs.
|
||||
// A root holding `script` as scripts/mosaic and the fake CLIs.
|
||||
function root(t, script) {
|
||||
const r = realpathSync(mkdtempSync(join(tmpdir(), "mosaic-queue-dispatch-")));
|
||||
t.after(() => rmSync(r, { recursive: true, force: true }));
|
||||
mkdirSync(join(r, "scripts"));
|
||||
cpSync(script, join(r, "scripts/mosaic"));
|
||||
for (const pkg of ["seat", "queue"]) {
|
||||
for (const pkg of ["seat", "queue", "cli"]) {
|
||||
mkdirSync(join(r, `packages/${pkg}/src`), { recursive: true });
|
||||
writeFileSync(join(r, `packages/${pkg}/src/cli.mjs`), FAKE);
|
||||
}
|
||||
@@ -52,7 +53,6 @@ function call(r, args, exit = 7) {
|
||||
const SEAT_CALLS = [
|
||||
["launch", "--repo", "/srv/repo", "--harness", "pi", "darkwing"],
|
||||
["seat", "task", "darkwing", "text with two spaces", ""],
|
||||
[],
|
||||
[""],
|
||||
["launch", "queue"],
|
||||
["Queue", "list"],
|
||||
@@ -73,6 +73,19 @@ test("every call but `queue` reaches the seat CLI exactly as before A2", (t) =>
|
||||
}
|
||||
});
|
||||
|
||||
test("no argument reaches the app entry in packages/cli, and only that call", (t) => {
|
||||
const r = root(t, join(SCRIPTS, "mosaic"));
|
||||
const got = call(r, []);
|
||||
assert.deepEqual([got.status, got.out, got.err], [7, "fake out\n", "fake err\n"]);
|
||||
const c = JSON.parse(got.capture);
|
||||
assert.deepEqual([c.cli, c.argv, c.cwd, c.stdin], ["/packages/cli/src/cli.mjs", [], "<ROOT>/work", "stdin line\nsecond\n"]);
|
||||
// Before row 55 the same call went to the seat CLI, with the same
|
||||
// environment the app now gets.
|
||||
const before = call(root(t, join(HERE, "fixtures", "mosaic-pre-a2.sh")), []);
|
||||
assert.equal(JSON.parse(before.capture).cli, "/packages/seat/src/cli.mjs");
|
||||
assert.deepEqual(c.env, JSON.parse(before.capture).env);
|
||||
});
|
||||
|
||||
test("`queue` reaches the queue CLI with the rest of the arguments", (t) => {
|
||||
const r = root(t, join(SCRIPTS, "mosaic"));
|
||||
for (const rest of [["list"], [], ["note", "9", "text with spaces", "--op", "note-9-000001"], ["queue"], [""]]) {
|
||||
|
||||
Reference in New Issue
Block a user