@mu-crew/mu 3.13.3 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +73 -35
- package/README.md +71 -57
- package/dist/chunk-5HECDBK3.js +4482 -0
- package/dist/chunk-5HECDBK3.js.map +1 -0
- package/dist/chunk-AW2NONYU.js +301 -0
- package/dist/chunk-AW2NONYU.js.map +1 -0
- package/dist/chunk-GQNHIPJN.js +4060 -0
- package/dist/chunk-GQNHIPJN.js.map +1 -0
- package/dist/{chunk-3BARPJLY.js → chunk-PZ5GOCWH.js} +16 -2
- package/dist/chunk-PZ5GOCWH.js.map +1 -0
- package/dist/chunk-S424D5EP.js +13766 -0
- package/dist/chunk-S424D5EP.js.map +1 -0
- package/dist/{cli-CKOMHBXV.js → cli-GB2CAVVW.js} +6776 -4350
- package/dist/cli-GB2CAVVW.js.map +1 -0
- package/dist/cli.js +2 -3
- package/dist/cli.js.map +1 -1
- package/dist/errors-KHOYKD7D.js +65 -0
- package/dist/extension/mu-pi.js +1617 -836
- package/dist/extension/mu-pi.js.map +1 -1
- package/dist/index.d.ts +4015 -2561
- package/dist/index.js +614 -297
- package/dist/index.js.map +1 -1
- package/dist/tui-ALPHYTA2.js +1773 -0
- package/dist/tui-ALPHYTA2.js.map +1 -0
- package/docs/ARCHITECTURE.md +117 -66
- package/docs/VOCABULARY.md +112 -49
- package/docs/guide/README.md +3 -3
- package/docs/guide/backends.md +8 -4
- package/docs/guide/cleanup.md +61 -33
- package/docs/guide/delegate.md +141 -66
- package/docs/guide/dispatch.md +148 -53
- package/docs/guide/getting-started.md +37 -44
- package/docs/guide/other-agent-clis.md +102 -0
- package/docs/guide/recovery.md +31 -50
- package/docs/guide/remote.md +115 -45
- package/docs/guide/sql.md +104 -24
- package/docs/guide/stop-a-worker.md +30 -37
- package/docs/guide/sync.md +11 -3
- package/docs/guide/upgrade.md +93 -43
- package/docs/reference/env.md +23 -32
- package/docs/reference/naming.md +12 -7
- package/docs/reference/surface-contract.md +153 -0
- package/package.json +33 -11
- package/prompts/mu-debug.md +3 -1
- package/prompts/mu-refute.md +2 -1
- package/prompts/mu-research.md +3 -1
- package/prompts/mu-review.md +2 -1
- package/prompts/mu-rules-audit.md +5 -4
- package/prompts/mu-sweep.md +2 -1
- package/prompts/mu-tournament.md +4 -2
- package/prompts/mu-until.md +3 -1
- package/prompts/ultrathink.md +1 -1
- package/skills/mu/SKILL.md +60 -291
- package/skills/mu/recipes/adversarial-review.md +12 -103
- package/skills/mu/recipes/brief.md +14 -61
- package/skills/mu/recipes/course-correct.md +23 -0
- package/skills/mu/recipes/findings.md +17 -111
- package/skills/mu/recipes/models.md +17 -92
- package/skills/mu/recipes/move.md +14 -0
- package/skills/mu/recipes/orchestrator-loop.md +20 -119
- package/skills/mu/recipes/recovery.md +8 -29
- package/skills/mu/recipes/remote-workers.md +16 -318
- package/skills/mu/recipes/tasks-or-calls.md +15 -78
- package/skills/mu/recipes/test-health.md +19 -77
- package/skills/mu/recipes/worker.md +17 -24
- package/dist/chunk-45KQLMEL.js +0 -2908
- package/dist/chunk-45KQLMEL.js.map +0 -1
- package/dist/chunk-G434JKV2.js +0 -641
- package/dist/chunk-G434JKV2.js.map +0 -1
- package/dist/chunk-KUGUFBWH.js +0 -12527
- package/dist/chunk-KUGUFBWH.js.map +0 -1
- package/dist/chunk-QITLNZEM.js +0 -87
- package/dist/chunk-QITLNZEM.js.map +0 -1
- package/dist/chunk-VIEYQMQA.js +0 -393
- package/dist/chunk-VIEYQMQA.js.map +0 -1
- package/dist/chunk-WI6AWCMK.js +0 -19
- package/dist/chunk-WI6AWCMK.js.map +0 -1
- package/dist/cli-CKOMHBXV.js.map +0 -1
- package/dist/devtools-LS4ZEPWM.js +0 -3772
- package/dist/devtools-LS4ZEPWM.js.map +0 -1
- package/dist/tui-LFJ5P4Y2.js +0 -40956
- package/dist/tui-LFJ5P4Y2.js.map +0 -1
- package/dist/tui-sync-worker.js +0 -17
- package/dist/tui-sync-worker.js.map +0 -1
- package/dist/yank-XDOONQSL.js +0 -13
- package/dist/yank-XDOONQSL.js.map +0 -1
- package/docs/ROADMAP.md +0 -123
- package/docs/VISION.md +0 -298
- package/docs/architecture/control-socket.md +0 -142
- package/docs/architecture/dag.md +0 -117
- package/docs/architecture/mux.md +0 -167
- package/docs/architecture/ops-log.md +0 -160
- package/docs/architecture/sdk.md +0 -187
- package/docs/architecture/sync.md +0 -159
- package/docs/architecture/tui.md +0 -165
- package/docs/guide/tui.md +0 -98
- package/docs/history/CHANGELOG-pre-3.md +0 -4866
- package/docs/img/tui-dashboard.png +0 -0
- package/docs/specs/2026-09-28-agent-state-from-murmur.md +0 -333
- package/docs/specs/2026-09-29-task-substates.md +0 -247
- package/docs/specs/2026-10-03-keep-driving-nudge.md +0 -163
- package/docs/specs/2026-10-21-fast-and-slow-gates.md +0 -229
- package/skills/mu/recipes/backlog-triage.md +0 -45
- package/skills/mu/recipes/codemode-driver.md +0 -118
- package/skills/mu/recipes/deep-research.md +0 -69
- package/skills/mu/recipes/drift-audit.md +0 -75
- package/skills/mu/recipes/fan-out.md +0 -42
- package/skills/mu/recipes/hypothesis-panel.md +0 -48
- package/skills/mu/recipes/long-run.md +0 -32
- package/skills/mu/recipes/loop-until-done.md +0 -45
- package/skills/mu/recipes/plan.md +0 -67
- package/skills/mu/recipes/refute.md +0 -55
- package/skills/mu/recipes/review-panel.md +0 -65
- package/skills/mu/recipes/rules-audit.md +0 -89
- package/skills/mu/recipes/tournament.md +0 -44
- package/skills/mu/recipes/ultrathink.md +0 -86
- package/skills/mu/recipes/watcher.md +0 -28
- package/skills/mu/recipes/waves.md +0 -21
- /package/dist/{chunk-3BARPJLY.js.map → errors-KHOYKD7D.js.map} +0 -0
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
# Sync
|
|
2
|
-
|
|
3
|
-
Machines share a workstream by exchanging ops through a shared folder
|
|
4
|
-
(`MU_SYNC_DIR`) kept in step by any folder syncer, such as Syncthing. The ops
|
|
5
|
-
themselves are described in [ops-log.md](ops-log.md). Overview:
|
|
6
|
-
[ARCHITECTURE.md](../ARCHITECTURE.md). Why segments and not
|
|
7
|
-
Litestream, cr-sqlite or a peer list, and why the DB must never sit in
|
|
8
|
-
`MU_SYNC_DIR`:
|
|
9
|
-
[ROADMAP § Rejected sync substrates](../ROADMAP.md#rejected-sync-substrates).
|
|
10
|
-
|
|
11
|
-
## Segments
|
|
12
|
-
|
|
13
|
-
`src/segments.ts` is the transport.
|
|
14
|
-
|
|
15
|
-
- `flushSegment` appends this machine's unflushed ops to
|
|
16
|
-
`<MU_SYNC_DIR>/<machine_id>.jsonl`.
|
|
17
|
-
- `ingestSegment` reads a peer segment from its watermark into
|
|
18
|
-
`applyOp`. Ambient ingest skips a peer that is caught up: the manifest
|
|
19
|
-
counts the watermark, and the segment's size, exact mtime and
|
|
20
|
-
manifest sha256 equal what the last clean read to the end recorded
|
|
21
|
-
(in `sync_fingerprints`). Any change, including a rewrite with a
|
|
22
|
-
backdated mtime, fails that check, so the full read hashes the file
|
|
23
|
-
and reports the mismatch. `mu sync` always reads every line.
|
|
24
|
-
- When a peer segment has grown, ambient ingest resumes: a clean read
|
|
25
|
-
saved the byte offset and running sha256 state with the fingerprint
|
|
26
|
-
(`src/segment-resume.ts`), so only the appended bytes are read, and
|
|
27
|
-
they count only if the resumed hash equals the manifest's sha256. A
|
|
28
|
-
shrunk file, a stale manifest, a defect, or `mu sync` takes the full
|
|
29
|
-
read and whole-file verify. The resumed hash trusts the bytes it
|
|
30
|
-
already read, so a local change to that prefix (with the file still
|
|
31
|
-
growing) is caught only by `mu sync`.
|
|
32
|
-
- Peer discovery is implicit: every non-self `*.jsonl` is a peer. A
|
|
33
|
-
peer disappears only when its segment is deleted.
|
|
34
|
-
- A Syncthing conflict copy (`<machine>.sync-conflict-….jsonl`) is
|
|
35
|
-
ingested too, with its own watermark keyed by the file stem: it can
|
|
36
|
-
diverge from the original, so line counts are not comparable.
|
|
37
|
-
|
|
38
|
-
**Single writer per file.** A machine appends only to its own segment,
|
|
39
|
-
so nothing is contended and any folder syncer is adequate. Segments
|
|
40
|
-
are regenerable, so there is no fsync. `src/file-lock.ts` stops two
|
|
41
|
-
local `mu` processes from interleaving partial lines. A flush never
|
|
42
|
-
appends without that lock: if another process still holds it after
|
|
43
|
-
2 s (15 s for `mu sync`), the flush writes nothing, the ops stay
|
|
44
|
-
pending in `ops`, and the next invocation appends them. A stale lock
|
|
45
|
-
(holder crashed, older than 30 s) is broken as before.
|
|
46
|
-
|
|
47
|
-
**Only `SYNCED_ENTITIES` and only this machine's ops are flushed.**
|
|
48
|
-
Pane ids and absolute paths never leave the machine, and peers never
|
|
49
|
-
echo each other's history.
|
|
50
|
-
|
|
51
|
-
**Payload encoding.** `ops.payload` is TEXT that is usually JSON, but
|
|
52
|
-
`mu log "text"` writes prose. The encoder passes valid JSON through
|
|
53
|
-
verbatim, so the crc covers those exact bytes, and JSON-encodes
|
|
54
|
-
anything else, unwrapping on ingest.
|
|
55
|
-
|
|
56
|
-
## Robustness layers
|
|
57
|
-
|
|
58
|
-
The first three checks stop ingest at the first bad record and advance
|
|
59
|
-
the watermark only that far. The fourth is whole-file: it reports the
|
|
60
|
-
defect without halting, because every line still present is valid, and
|
|
61
|
-
only a fresh copy of the file clears it:
|
|
62
|
-
|
|
63
|
-
| Layer | Catches |
|
|
64
|
-
| --- | --- |
|
|
65
|
-
| `JSON.parse` | a torn write (line lacks its trailing `,"crc"…}` framing) or a malformed shape (framing present). Same failure, different remediation: a cut transfer vs a writer bug |
|
|
66
|
-
| crc32 per line | bit rot |
|
|
67
|
-
| monotonic HLC | reorder, duplicate, truncation |
|
|
68
|
-
| `.manifest` sidecar | truncation exactly on a line boundary |
|
|
69
|
-
|
|
70
|
-
**Damage halts; refusal skips.** Halting is right for damage, where a
|
|
71
|
-
gap looks the same as reordering. It is wrong for a well-formed line
|
|
72
|
-
mu declines to project: a refusal leaves no hole, and halting would
|
|
73
|
-
freeze the peer's watermark for good, since `mu sync --repair` only
|
|
74
|
-
resets the watermark and re-reads into the same line. Ingest
|
|
75
|
-
distinguishes three cases:
|
|
76
|
-
|
|
77
|
-
| Line | Result |
|
|
78
|
-
| --- | --- |
|
|
79
|
-
| damaged (including a blank line) | halt |
|
|
80
|
-
| a known machine-local entity | reported defect, skipped |
|
|
81
|
-
| a historical log-only intent (`workstream.export` prose from mu < 1.1) | skipped silently, as flush and `--from` skip it |
|
|
82
|
-
| an unrecognised entity | applied as a no-op, no defect (a reader behind the writer is normal in a mixed fleet) |
|
|
83
|
-
|
|
84
|
-
`mu sync` suggests `--repair` only for defects a re-read can clear.
|
|
85
|
-
|
|
86
|
-
**Sync never fails a command.** A truncated segment, a garbage segment,
|
|
87
|
-
a sync dir that is a file, or a vanished directory warns on stderr and
|
|
88
|
-
returns.
|
|
89
|
-
|
|
90
|
-
## The ambient hook
|
|
91
|
-
|
|
92
|
-
Sync is ambient, not a daemon: no watcher, no background process, no
|
|
93
|
-
poll outliving a command. It happens because you already run `mu`
|
|
94
|
-
constantly.
|
|
95
|
-
|
|
96
|
-
The seam is `handle()` (`src/cli/handle.ts`), which every verb passes
|
|
97
|
-
through and which is already async. It awaits `ambientIngest` and
|
|
98
|
-
`ambientFlush`; the flush is async because it takes the file lock,
|
|
99
|
-
while most verb bodies are synchronous
|
|
100
|
-
better-sqlite3 code. One `await` before `fn(db)` and one after cover
|
|
101
|
-
every verb.
|
|
102
|
-
|
|
103
|
-
- **Ingest before the body**, so the verb reads the freshest state.
|
|
104
|
-
- **Flush after the body**, so this invocation's ops reach the segment
|
|
105
|
-
now.
|
|
106
|
-
- **Flush on the error path too.** A verb may commit ops before
|
|
107
|
-
throwing. Those ops are canonical, so withholding them would make
|
|
108
|
-
this machine's history depend on the exit code.
|
|
109
|
-
|
|
110
|
-
`src/sync.ts` holds `ambientIngest`, `ambientFlush`, `ambientSyncPass`,
|
|
111
|
-
`peerStatuses`, `ingestFromDb` (`mu sync --from <path>`) and
|
|
112
|
-
`repairPeer` (`mu sync --repair <peer>`, a unique prefix; ambiguity is
|
|
113
|
-
exit 4). A prefix names a machine, so it resets the watermark of the
|
|
114
|
-
peer's segment and of every conflict copy of it.
|
|
115
|
-
|
|
116
|
-
### Carve-outs
|
|
117
|
-
|
|
118
|
-
| Surface | Behaviour | Why |
|
|
119
|
-
| --- | --- | --- |
|
|
120
|
-
| `MU_SYNC_DIR` unset | one `if` on one env var; no filesystem touch | the single-machine case pays nothing (measured: baseline; about 3ms with one peer) |
|
|
121
|
-
| `mu sql` | opts out (`handle(..., { ambientSync: false })`) | an ingest changing a row count mid-inspection reads as a mu bug |
|
|
122
|
-
| `mu sync` | opts out and runs the pass itself | otherwise the report prints "ingested 0" right after ingesting |
|
|
123
|
-
| the TUI | slow tick only (10s), one pass per beat across tabs, `quiet: true` | the fast tick can go to 100ms; the TUI owns the screen, so problems show on the Doctor card |
|
|
124
|
-
|
|
125
|
-
## Out-of-order arrival
|
|
126
|
-
|
|
127
|
-
An `edge` or `note` put whose task has not arrived yet skips as
|
|
128
|
-
`absent`, correctly. Ingest still advances the watermark past it, so
|
|
129
|
-
without a second pass it would never land. Peers are discovered by
|
|
130
|
-
`localeCompare` over random-UUID filenames, so the arrival order is a
|
|
131
|
-
coin flip per fleet.
|
|
132
|
-
|
|
133
|
-
`reprojectDeferredOps` (`src/apply.ts`) re-queries the log for note and
|
|
134
|
-
edge puts that are resolvable now but unprojected. It skips ops whose
|
|
135
|
-
parent task is gone and keys with a newer `del`, so a deleted edge is
|
|
136
|
-
never resurrected and an orphan is not retried forever. It runs once
|
|
137
|
-
per ingest pass, not per peer, because an edge in one segment may name
|
|
138
|
-
a task in another. Ambient ingest runs it only while SQLite's
|
|
139
|
-
`user_version` is 1. `applyIncomingOp` sets that marker in the same
|
|
140
|
-
transaction whenever an op changes a row or defers (`absent`), whether
|
|
141
|
-
it came from a segment, `--from`, or an SDK caller; the repair clears
|
|
142
|
-
it. A process that dies between
|
|
143
|
-
the two leaves the marker set, so the next invocation runs the repair.
|
|
144
|
-
`mu sync` and `--from` always run it.
|
|
145
|
-
|
|
146
|
-
There is no retry queue. A queue would be a second source of truth and
|
|
147
|
-
would not survive a short-lived `mu` process when the parent arrives
|
|
148
|
-
days later.
|
|
149
|
-
|
|
150
|
-
## Mixed-fleet hazards
|
|
151
|
-
|
|
152
|
-
`src/fleet-hazards.ts` adds three checks to the default `mu doctor`,
|
|
153
|
-
all no-ops when `MU_SYNC_DIR` is unset:
|
|
154
|
-
|
|
155
|
-
| Check | Severity |
|
|
156
|
-
| --- | --- |
|
|
157
|
-
| `MU_DB_PATH` inside `MU_SYNC_DIR` | fail |
|
|
158
|
-
| DB on a network mount | warn |
|
|
159
|
-
| two workstream names differing only by case | warn |
|
package/docs/architecture/tui.md
DELETED
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
# TUI architecture
|
|
2
|
-
|
|
3
|
-
The TUI is a read-only, live-updating dashboard of 10 cards on `ink`.
|
|
4
|
-
It lives entirely under `src/cli/tui/`, the only place ink and react
|
|
5
|
-
are imported (a [ROADMAP](../ROADMAP.md#anti-feature-pledges) pledge).
|
|
6
|
-
CLI verbs stay the mutation API: the TUI yanks `mu` commands and the
|
|
7
|
-
operator runs them. Overview: [ARCHITECTURE.md](../ARCHITECTURE.md).
|
|
8
|
-
Press `?` in the TUI for the keymap.
|
|
9
|
-
|
|
10
|
-
## Entry points
|
|
11
|
-
|
|
12
|
-
- Bare `mu` launches the TUI when `process.stdout.isTTY` is true. It
|
|
13
|
-
loads every workstream and picks the initial tab with the focus
|
|
14
|
-
ladder in `src/cli/tui-launch-focus.ts`: `$MU_SESSION` → tmux session
|
|
15
|
-
name → cwd inside a workspace → cwd equal to a workspace's project
|
|
16
|
-
root (`src/project-root.ts`; latest activity breaks ties) → tab 0.
|
|
17
|
-
With no workstreams it prints `mu --help` plus
|
|
18
|
-
`Get started: mu workstream init <name>` and exits 0.
|
|
19
|
-
- On non-TTY stdout (pipes, CI, most agent calls) bare `mu` prints help.
|
|
20
|
-
`MU_NO_TUI=1` forces that path inside a terminal.
|
|
21
|
-
- `mu state` is the static card; `mu state --tui` selects the TUI.
|
|
22
|
-
- The import stays dynamic (`await import("./cli/tui/index.js")`). A
|
|
23
|
-
static import of ink anywhere else would pull the TUI graph into the
|
|
24
|
-
help, version and `--json` paths.
|
|
25
|
-
|
|
26
|
-
## Cluster shape
|
|
27
|
-
|
|
28
|
-
| Files | Role |
|
|
29
|
-
| --- | --- |
|
|
30
|
-
| `index.ts`, `escapes.ts` | `runTui` entry; alt-screen and SGR mouse-mode lifecycle; pure escape bytes |
|
|
31
|
-
| `app.tsx` | `<App>` root: popup state machine, global keymap, tabs, footer |
|
|
32
|
-
| `state.ts` | `useDashboardSnapshot` poll loop (fast and slow tiers) |
|
|
33
|
-
| `keys.ts`, `keymap-spec.ts`, `help.tsx` | pure key dispatch; the keymap source of truth that drives both dispatch and the `?` overlay |
|
|
34
|
-
| `mouse.ts`, `use-popup-action-queue.ts` | vendored SGR mouse parser with double-click; one queued popup action per render |
|
|
35
|
-
| `layout.ts`, `columns.ts`, `wrap-ansi.ts`, `use-terminal-size.ts` | responsive columns and row budgets; aligned clipping; ANSI-aware wrapping; resize hook |
|
|
36
|
-
| `popup-input.ts` | `usePopupInput`: popup keyboard, off while the help overlay covers the popup |
|
|
37
|
-
| `titled-box.tsx`, `popup-shell.tsx`, `list-row.tsx`, `padded-rows.tsx`, `status-bar.tsx`, `tab-strip*.ts(x)` | chrome primitives |
|
|
38
|
-
| `format-helpers.ts`, `agent-display.ts` | shared formatters and agent-row display |
|
|
39
|
-
| `use-popup-filter.tsx`, `use-status-filter.tsx`, `use-notes-drill.ts` | `/` filter; status (`o`/`i`/`c`) and substate (`p`/`w`) toggles; shared notes drill |
|
|
40
|
-
| `yank.ts`, `tuicr.ts`, `lazygit.ts`, `tmux-attach.ts` | clipboard; the alt-screen handoffs |
|
|
41
|
-
| `cards/*.tsx` | the 10 dashboard cards, plus `_placeholder.tsx` |
|
|
42
|
-
| `popups/*.tsx` | one fullscreen popup per card, plus `dag.tsx` (`g`), `all-tasks.tsx` (`t`), the `task-list-popup.tsx` scaffold, `drill.tsx`, `task-detail.tsx`, `cursor-row.tsx`, `scroll.ts`, `viewport.ts`, `show-loader.ts` |
|
|
43
|
-
|
|
44
|
-
## State machine
|
|
45
|
-
|
|
46
|
-
`<App>` owns:
|
|
47
|
-
|
|
48
|
-
- **Popup state:** `null` (dashboard) or one popup id. One popup at a
|
|
49
|
-
time; `Esc` or `q` returns to the dashboard. `?` over a popup hides
|
|
50
|
-
it (`display="none"`) and turns its keyboard off rather than
|
|
51
|
-
unmounting it, so its cursor, filter and drill survive the overlay.
|
|
52
|
-
- **Card visibility:** toggled by `0`-`9`.
|
|
53
|
-
- **Tick rate:** fast tick, 1s default, adjusted with `+` / `=` and
|
|
54
|
-
`-`. There is no reset key: `0` toggles the Commits card.
|
|
55
|
-
- **Active workstream tab:** `Tab` / `Shift-Tab` cycles when there are
|
|
56
|
-
two or more. A torn-down workstream keeps its tab position and
|
|
57
|
-
renders dimmed with strikethrough until the name is recreated. A
|
|
58
|
-
torn-down single workstream shows its normally hidden tab.
|
|
59
|
-
- **Footer flash:** a transient status-bar message.
|
|
60
|
-
|
|
61
|
-
Popups own their local state (cursor, filter, drill mode, local modes
|
|
62
|
-
such as Workspaces' `list` / `commits` / `show`). They never mutate App
|
|
63
|
-
state. They receive a read-only props bag: `snapshot`, `db`,
|
|
64
|
-
`workstream`, `fastTickNonce`, `slowTickNonce`, `yank`, `onClose`,
|
|
65
|
-
`onModeChange`, `onFilterEditingChange`, `onFooter`.
|
|
66
|
-
|
|
67
|
-
## Polling tiers
|
|
68
|
-
|
|
69
|
-
| Tier | Interval | Loader | Reads | Cost |
|
|
70
|
-
| --- | --- | --- | --- | --- |
|
|
71
|
-
| fast | 1s, adjustable | `loadWorkstreamSnapshotFast` | SQL only: tasks, tracks, workspace rows, recent events, workspace orphans | p50 < 1ms |
|
|
72
|
-
| slow | 10s (`SLOW_TICK_MS`) | `loadWorkstreamSnapshotSlow` | subprocesses: agent liveness and state, workspace dirty status, recent commits, doctor summary | p50 hundreds of ms |
|
|
73
|
-
|
|
74
|
-
`mergeSnapshotFastSlow` merges the last slow result into every fast
|
|
75
|
-
render, so cards never flicker through loading. `r` / `F5` runs both
|
|
76
|
-
tiers now. A tab switch clears the slow cache and fetches eagerly.
|
|
77
|
-
`snapshotKey` returns the same `data` reference across no-op ticks, so
|
|
78
|
-
React's diffing short-circuits. The slow tick also runs ambient sync
|
|
79
|
-
([sync.md](sync.md#carve-outs)).
|
|
80
|
-
|
|
81
|
-
## Render geometry
|
|
82
|
-
|
|
83
|
-
`layout.ts`:
|
|
84
|
-
|
|
85
|
-
- **Columns by width:** stacked below 120 columns, then 2 at 120, 3 at
|
|
86
|
-
180, 4 at 240. Stream cards (Commits, Activity log) trail; Commits
|
|
87
|
-
trails last.
|
|
88
|
-
- **Row budgets:** each card has `min` / `max` / `chrome`. The allocator
|
|
89
|
-
distributes rows up to `max` first so a noisy list cannot crowd its
|
|
90
|
-
siblings, then gives any spare rows to the cards still hiding data,
|
|
91
|
-
in proportion to what each hides. A column with hidden data always
|
|
92
|
-
fills its height; short lists are not padded. Overflow shows as
|
|
93
|
-
`+N more · Shift+N` in the card's bottom border.
|
|
94
|
-
- **Card placement:** `arrangeColumns` starts from the group packing
|
|
95
|
-
and moves or swaps cards between columns while that lowers the total
|
|
96
|
-
hidden rows (a local optimum, well under 1ms). It keeps the previous
|
|
97
|
-
assignment unless the new one hides at least max(3, 10%) fewer rows,
|
|
98
|
-
so cards do not jump as counts change. A resize, toggle or tab
|
|
99
|
-
switch reflows freely.
|
|
100
|
-
- **Culling:** when even minimum budgets do not fit, cards drop by
|
|
101
|
-
priority (Doctor → Recent → Workspaces → …) and the screen shows
|
|
102
|
-
`+N cards hidden · resize taller`.
|
|
103
|
-
|
|
104
|
-
`wrap-ansi.ts` wraps by visual width (`string-width`) and closes open
|
|
105
|
-
SGR state on every exit path, so a fragment without a trailing
|
|
106
|
-
`\x1b[0m` cannot bleed into ink chrome. Drill bodies are space-padded
|
|
107
|
-
to the exact box width, or ink's `wrap="truncate"` ANSI miscount eats
|
|
108
|
-
the right border.
|
|
109
|
-
|
|
110
|
-
## Read-only, with handoffs
|
|
111
|
-
|
|
112
|
-
Every popup row exposes one canonical `mu` command through `y`.
|
|
113
|
-
`yank.ts` probes pbcopy, wl-copy, xclip, xsel and clip.exe, then falls
|
|
114
|
-
back to OSC-52 written to `/dev/tty`, since ink owns stdout.
|
|
115
|
-
|
|
116
|
-
The handoffs leave the alt screen, disable mouse mode, run a
|
|
117
|
-
foreground subprocess, and restore both on exit:
|
|
118
|
-
|
|
119
|
-
- `t` in any `git show` drill runs `tuicr -r <sha>` in the project root
|
|
120
|
-
or workspace.
|
|
121
|
-
- `l` in the Commits popup runs lazygit.
|
|
122
|
-
- `a` in the Agents popup attaches to the agent's mux pane.
|
|
123
|
-
|
|
124
|
-
A TUI gesture that mutates state needs a ROADMAP entry first.
|
|
125
|
-
|
|
126
|
-
## Input
|
|
127
|
-
|
|
128
|
-
Mouse support uses SGR mouse mode. `mouse.ts` parses
|
|
129
|
-
`ESC[<button;x;y;M/m`, detects double-clicks, and exposes `useMouse()`.
|
|
130
|
-
A double-click on a card opens its popup. A double-click on a popup row
|
|
131
|
-
emits one `clickRow` action (the 0-based body row) through the action
|
|
132
|
-
queue. The popup maps that row through its own scroll window and
|
|
133
|
-
header strips (`clickedItem` in `popups/scroll.ts`), then focuses and
|
|
134
|
-
drills the item. App cannot do the mapping: only the popup knows its
|
|
135
|
-
window start and strip rows.
|
|
136
|
-
|
|
137
|
-
`keys.ts` holds `dispatchGlobalKey` (dashboard), `dispatchPopupKey`
|
|
138
|
-
(popup), and `shouldSwallowGlobalKey` (keys a popup consumes).
|
|
139
|
-
|
|
140
|
-
## Drill recursion
|
|
141
|
-
|
|
142
|
-
List popups drill with `Enter`. Each popup pins the drilled row's
|
|
143
|
-
identity at `Enter` (task, agent, check, commit sha, log seq, track
|
|
144
|
-
goals), so a refresh that reorders or extends the list cannot move an
|
|
145
|
-
open drill to another row. `DrillScrollView`
|
|
146
|
-
(`popups/drill.tsx`) is the shared scrollable text leaf: Workspaces'
|
|
147
|
-
git show, Agents' scrollback, Activity log payloads, and Doctor
|
|
148
|
-
remediation. Task popups drill into `TaskDetailDrill`
|
|
149
|
-
(`popups/task-detail.tsx`, the notes timeline). Tracks chains track →
|
|
150
|
-
task list → `TaskDetailDrill`.
|
|
151
|
-
|
|
152
|
-
`useDrillKeymap` owns scroll state. An optional `resetKey` resets
|
|
153
|
-
scroll on identity change while a tick refresh keeps it, and
|
|
154
|
-
`onScrollChange` serves the DAG popup's focused-root tracking. Body
|
|
155
|
-
wrap metadata is shared so the scroll clamp and the painter cannot
|
|
156
|
-
disagree. Subprocess-backed drills use `popups/show-loader.ts`, which
|
|
157
|
-
keeps the prior body during a refetch.
|
|
158
|
-
|
|
159
|
-
## Test seam
|
|
160
|
-
|
|
161
|
-
See `test/README.md`. `test/_ink-render.ts` provides
|
|
162
|
-
`createInkInputStream`, `createInkCaptureStream`, `simulateInput` and
|
|
163
|
-
`latestRenderedFrame`: mount a popup or `<App>`, drive keys, assert on
|
|
164
|
-
the visible frame and spy callbacks. Source greps are for narrow
|
|
165
|
-
structural guards only, never behaviour.
|
package/docs/guide/tui.md
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
# How to use the TUI dashboard
|
|
2
|
-
|
|
3
|
-
The dashboard is read-only. To change anything, press `y` on a row to
|
|
4
|
-
copy its `mu` command, then run it in your shell.
|
|
5
|
-
|
|
6
|
-
## Open it
|
|
7
|
-
|
|
8
|
-
```bash
|
|
9
|
-
mu # every workstream, as tabs
|
|
10
|
-
mu state --tui -w a,b # only these workstreams
|
|
11
|
-
MU_NO_TUI=1 mu # print help instead
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
Bare `mu` opens the dashboard only when stdout is a terminal. Scripts
|
|
15
|
-
get help text. For a static card or JSON, use `mu state` and
|
|
16
|
-
`mu state --json`. Quit with `q` or `Ctrl-C`.
|
|
17
|
-
|
|
18
|
-
The first active tab is the first match of: `$MU_SESSION`, the current
|
|
19
|
-
`mu-<ws>` tmux session, a workspace that contains the current
|
|
20
|
-
directory, a project root that contains it, then the first tab.
|
|
21
|
-
|
|
22
|
-
## Read the cards
|
|
23
|
-
|
|
24
|
-
| Key | Card | Shows |
|
|
25
|
-
| --- | ---- | ----- |
|
|
26
|
-
| `0` | Commits | recent commits in the project root |
|
|
27
|
-
| `1` | Agents | agents with runtime state, CLI, and role |
|
|
28
|
-
| `2` | Tracks | parallel tracks of tasks |
|
|
29
|
-
| `3` | Ready | tasks with no open blockers |
|
|
30
|
-
| `4` | Activity log | recent ops as prose |
|
|
31
|
-
| `5` | Workspaces | per-agent workspaces, commits behind, dirty flag |
|
|
32
|
-
| `6` | In-progress | `IN_PROGRESS` tasks |
|
|
33
|
-
| `7` | Blocked | tasks with an open blocker |
|
|
34
|
-
| `8` | Recent | recently closed tasks |
|
|
35
|
-
| `9` | Doctor | quick health checks |
|
|
36
|
-
| `g` | DAG | the whole task graph (popup only) |
|
|
37
|
-
| `t` | All tasks | every task, sortable (popup only) |
|
|
38
|
-
|
|
39
|
-
A digit hides or shows its card. `Shift` plus the digit opens the
|
|
40
|
-
card as a popup. Only one popup is open at a time.
|
|
41
|
-
|
|
42
|
-
Cards reflow into 2, 3, or 4 columns at 120, 180, and 240 columns
|
|
43
|
-
wide. On a short terminal, low-priority cards drop out and the footer
|
|
44
|
-
says how many are hidden.
|
|
45
|
-
|
|
46
|
-
With two or more workstreams, a tab strip appears. `Tab` and
|
|
47
|
-
`Shift-Tab` switch tabs. A `*` marks `scratch`. A torn-down
|
|
48
|
-
workstream's tab stays, dimmed and struck through.
|
|
49
|
-
|
|
50
|
-
## Drill into rows
|
|
51
|
-
|
|
52
|
-
`Enter` opens the focused row. On a task it opens the task's notes.
|
|
53
|
-
`Esc` or `q` goes back one level.
|
|
54
|
-
|
|
55
|
-
- Tracks: track, then its tasks, then a task's notes.
|
|
56
|
-
- Workspaces: workspace, then its commits, then `git show`.
|
|
57
|
-
- Commits: commit, then the backend's show view.
|
|
58
|
-
- Activity log: the full text of the entry.
|
|
59
|
-
- Doctor: the remediation text for the check.
|
|
60
|
-
|
|
61
|
-
In a `git show` view, `t` opens `tuicr -r <sha>`. In the Commits popup,
|
|
62
|
-
`l` opens lazygit. In the Agents popup, `a` attaches to the agent's
|
|
63
|
-
pane. The dashboard comes back when the tool exits.
|
|
64
|
-
|
|
65
|
-
## Filter and sort
|
|
66
|
-
|
|
67
|
-
`/` filters the popup's rows by substring as you type. `Enter` keeps
|
|
68
|
-
the filter. `Esc` clears it.
|
|
69
|
-
|
|
70
|
-
In the DAG and All-tasks popups, `o`, `i`, and `c` toggle `OPEN`,
|
|
71
|
-
`IN_PROGRESS`, and `CLOSED`, `p` toggles parked tasks, and `w` toggles
|
|
72
|
-
tasks closed as anything but `done`. In All tasks, `b`
|
|
73
|
-
cycles the blocked filter and `s` cycles the sort: ROI, recency, age,
|
|
74
|
-
id.
|
|
75
|
-
|
|
76
|
-
## Keys
|
|
77
|
-
|
|
78
|
-
| Where | Keys | Action |
|
|
79
|
-
| ----- | ---- | ------ |
|
|
80
|
-
| dashboard | `+` or `=`, `-` | refresh faster (100 ms floor), slower (10 s ceiling) |
|
|
81
|
-
| dashboard | `r` or `F5` | refresh now |
|
|
82
|
-
| dashboard | `c` | clear the footer |
|
|
83
|
-
| any | `?` | help overlay |
|
|
84
|
-
| popup | `j` `k` | move |
|
|
85
|
-
| popup | `g` `G` | top, bottom |
|
|
86
|
-
| popup | `Ctrl-D` `Ctrl-U`, `PgDn` `PgUp` | half page, full page |
|
|
87
|
-
| popup | `y` | copy the row's `mu` command |
|
|
88
|
-
|
|
89
|
-
The mouse works for navigation only. Double-click a card to open it,
|
|
90
|
-
double-click a row to drill in, and scroll inside lists. There is no
|
|
91
|
-
mouse binding for back.
|
|
92
|
-
|
|
93
|
-
## Refresh
|
|
94
|
-
|
|
95
|
-
Task, track, and log data refreshes every second from SQLite. Agent
|
|
96
|
-
state, workspace dirty flags, commits, and doctor checks come from
|
|
97
|
-
subprocesses and refresh every 10 seconds. Switching tabs refreshes
|
|
98
|
-
the new tab at once.
|