@ucsandman/legcli 0.7.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/CHANGELOG.md +560 -0
- package/LICENSE +58 -0
- package/NOTICE +4 -0
- package/README.md +558 -0
- package/bin/fake-agent.mjs +143 -0
- package/bin/leg.mjs +449 -0
- package/docs/DECISIONS.md +119 -0
- package/docs/DEMO.md +214 -0
- package/docs/DEVIATIONS.md +182 -0
- package/docs/ERRORS.md +326 -0
- package/docs/README.md +40 -0
- package/docs/REUSE.md +289 -0
- package/docs/ROADMAP-v2.md +55 -0
- package/docs/VOCABULARY.md +243 -0
- package/docs/adapters.md +300 -0
- package/docs/board-guide.md +579 -0
- package/docs/cli-contracts.md +401 -0
- package/docs/concepts.md +426 -0
- package/docs/configuration.md +220 -0
- package/docs/faq.md +206 -0
- package/docs/getting-started.md +251 -0
- package/docs/real-run.md +80 -0
- package/docs/review-2026-09-14.md +72 -0
- package/docs/screenshots/board-400px.png +0 -0
- package/docs/screenshots/board-details-open.png +0 -0
- package/docs/screenshots/board-done.png +0 -0
- package/docs/screenshots/board-drawer.png +0 -0
- package/docs/screenshots/board-empty.png +0 -0
- package/docs/screenshots/board-handoff.png +0 -0
- package/docs/screenshots/board-running.png +0 -0
- package/docs/screenshots/demo-1-claude-running.png +0 -0
- package/docs/screenshots/demo-2-limit-hit.png +0 -0
- package/docs/screenshots/demo-3-handoff-bundle.png +0 -0
- package/docs/screenshots/demo-4-codex-running.png +0 -0
- package/docs/screenshots/demo-5-done.png +0 -0
- package/docs/screenshots/floor-final-1280.png +0 -0
- package/docs/screenshots/floor-final-400.png +0 -0
- package/docs/screenshots/floor-landing.png +0 -0
- package/docs/screenshots/floor.png +0 -0
- package/docs/screenshots/share-guest-1280.png +0 -0
- package/docs/screenshots/share-owner-1280.png +0 -0
- package/docs/screenshots/terminals-1280.png +0 -0
- package/docs/screenshots/thumb-done.jpg +0 -0
- package/docs/screenshots/thumb-handoff-details.jpg +0 -0
- package/docs/screenshots/thumb-limit-hit.jpg +0 -0
- package/docs/screenshots/youtube-thumbnail.jpg +0 -0
- package/fixtures/limits/agy/agy-resource-exhausted.json +11 -0
- package/fixtures/limits/claude/claude-budget-limit.json +11 -0
- package/fixtures/limits/claude/claude-max-turns.json +11 -0
- package/fixtures/limits/claude/claude-model-limit.json +11 -0
- package/fixtures/limits/claude/claude-session-limit.json +11 -0
- package/fixtures/limits/claude/claude-weekly-limit.json +11 -0
- package/fixtures/limits/codex/codex-quota-exceeded.json +11 -0
- package/fixtures/limits/codex/codex-rate-limit-exceeded.json +11 -0
- package/fixtures/limits/codex/codex-skills-notice.json +11 -0
- package/fixtures/limits/codex/codex-usage-limit-reached.json +11 -0
- package/fixtures/limits/codex/codex-usage-limit.json +11 -0
- package/fixtures/limits/generic/generic-429.json +11 -0
- package/fixtures/limits/generic/generic-overloaded.json +11 -0
- package/fixtures/limits/generic/generic-quota.json +11 -0
- package/fixtures/limits/generic/generic-rate-limit.json +11 -0
- package/fixtures/limits/generic/generic-resource-exhausted.json +11 -0
- package/fixtures/limits/generic/generic-usage-limit.json +11 -0
- package/fixtures/limits/grok/grok-not-logged-in.json +11 -0
- package/fixtures/limits/negative/auth-source-set.json +11 -0
- package/fixtures/limits/negative/compile-error.json +11 -0
- package/fixtures/limits/negative/empty-stdout-exit-0.json +11 -0
- package/fixtures/live/agy/attempt-1-scratch-workspace.out.log +1 -0
- package/fixtures/live/agy/cmd.txt +1 -0
- package/fixtures/live/agy/err.log +0 -0
- package/fixtures/live/agy/out.log +1 -0
- package/fixtures/live/agy/parsed.json +5 -0
- package/fixtures/live/agy/run.json +18 -0
- package/fixtures/live/agy/supervisor.log +2 -0
- package/fixtures/live/claude/cmd.txt +1 -0
- package/fixtures/live/claude/err.log +0 -0
- package/fixtures/live/claude/limit-rate_limit.json +21 -0
- package/fixtures/live/claude/out.log +1 -0
- package/fixtures/live/claude/parsed.json +11 -0
- package/fixtures/live/claude/run.json +18 -0
- package/fixtures/live/claude/supervisor.log +2 -0
- package/fixtures/live/codex/cmd.txt +1 -0
- package/fixtures/live/codex/err.log +1 -0
- package/fixtures/live/codex/last.md +2 -0
- package/fixtures/live/codex/limit-usage_limit_exceeded.json +25 -0
- package/fixtures/live/codex/out.log +8 -0
- package/fixtures/live/codex/parsed.json +15 -0
- package/fixtures/live/codex/run.json +18 -0
- package/fixtures/live/codex/supervisor.log +2 -0
- package/fixtures/live/grok/cmd.txt +1 -0
- package/fixtures/live/grok/err.log +32 -0
- package/fixtures/live/grok/out.log +7 -0
- package/fixtures/live/grok/parsed.json +5 -0
- package/fixtures/live/grok/run.json +18 -0
- package/fixtures/live/grok/supervisor.log +2 -0
- package/fixtures/verified.json +34 -0
- package/package.json +68 -0
- package/scripts/board-shots.mjs +69 -0
- package/scripts/build-docs-site.mjs +308 -0
- package/scripts/check-claims.mjs +150 -0
- package/scripts/clean-clone-check.sh +42 -0
- package/scripts/license-sign.mjs +23 -0
- package/scripts/limits-table.mjs +38 -0
- package/scripts/live-limits.mjs +36 -0
- package/scripts/privacy-check.mjs +63 -0
- package/scripts/probe.mjs +92 -0
- package/scripts/seed-fake-cards.mjs +34 -0
- package/scripts/seed-floor-board.mjs +69 -0
- package/scripts/seed-wes-board.mjs +91 -0
- package/scripts/stripe-setup.mjs +99 -0
- package/scripts/vercel-env.mjs +40 -0
- package/src/accounts.mjs +119 -0
- package/src/adapters/agy.mjs +56 -0
- package/src/adapters/claude.mjs +54 -0
- package/src/adapters/codex.mjs +73 -0
- package/src/adapters/common.mjs +32 -0
- package/src/adapters/fake.mjs +48 -0
- package/src/adapters/grok.mjs +51 -0
- package/src/adapters/index.mjs +27 -0
- package/src/adapters/resolve.mjs +40 -0
- package/src/attach.mjs +592 -0
- package/src/auth.mjs +85 -0
- package/src/board/board.css +543 -0
- package/src/board/board.js +1307 -0
- package/src/board/favicon.svg +1 -0
- package/src/board/floor.html +127 -0
- package/src/board/floor.js +670 -0
- package/src/board/fonts/OFL-atkinson-hyperlegible-next.txt +93 -0
- package/src/board/fonts/OFL-azeret-mono.txt +94 -0
- package/src/board/fonts/atkinson-board.woff2 +0 -0
- package/src/board/fonts/azeret-board.woff2 +0 -0
- package/src/board/index.html +251 -0
- package/src/board/sessions.js +1451 -0
- package/src/bundle.mjs +103 -0
- package/src/cards.mjs +114 -0
- package/src/chain.mjs +264 -0
- package/src/commands.mjs +79 -0
- package/src/contract.mjs +73 -0
- package/src/env.mjs +19 -0
- package/src/fsx.mjs +72 -0
- package/src/git-snapshot.mjs +152 -0
- package/src/handoff.mjs +171 -0
- package/src/hook.mjs +49 -0
- package/src/land.mjs +106 -0
- package/src/launcher.mjs +272 -0
- package/src/leases.mjs +86 -0
- package/src/ledger.mjs +369 -0
- package/src/license.mjs +168 -0
- package/src/limits.mjs +97 -0
- package/src/live-capture.mjs +69 -0
- package/src/mergequeue.mjs +225 -0
- package/src/orchestrator.mjs +365 -0
- package/src/pipeline.mjs +100 -0
- package/src/preferences.mjs +47 -0
- package/src/presets.mjs +23 -0
- package/src/ratelimit.mjs +49 -0
- package/src/redact.mjs +47 -0
- package/src/resume.mjs +354 -0
- package/src/runner.mjs +438 -0
- package/src/scheduler.mjs +118 -0
- package/src/server.mjs +845 -0
- package/src/session-detail.mjs +129 -0
- package/src/sessions.mjs +202 -0
- package/src/share.mjs +163 -0
- package/src/stations/agent.mjs +42 -0
- package/src/stations/build.mjs +9 -0
- package/src/stations/human.mjs +5 -0
- package/src/stations/land.mjs +6 -0
- package/src/stations/plan.mjs +8 -0
- package/src/stations/pr.mjs +31 -0
- package/src/stations/review.mjs +8 -0
- package/src/stations/test.mjs +27 -0
- package/src/store.mjs +90 -0
- package/src/sync/dashclaw.mjs +70 -0
- package/src/sync/index.mjs +54 -0
- package/src/sync/workboard.mjs +54 -0
- package/src/taps/agy.mjs +64 -0
- package/src/taps/claude-usage.mjs +67 -0
- package/src/taps/claude.mjs +170 -0
- package/src/taps/codex.mjs +286 -0
- package/src/trust.mjs +323 -0
- package/src/usage.mjs +179 -0
- package/src/wait.mjs +30 -0
- package/src/worktree.mjs +207 -0
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
# Board guide
|
|
2
|
+
|
|
3
|
+
For anyone using the Leg board day to day: what every element means and when
|
|
4
|
+
it shows up. The board is one page, read top to bottom: **the verdict** (one
|
|
5
|
+
sentence saying what to do next), **the logins** (one panel each), **Terminals**
|
|
6
|
+
(one row each), **the ledger** (finished terminals, what landed, background
|
|
7
|
+
tasks, as three counts that open), then **Settings**. Start a session
|
|
8
|
+
(`leg claude`) or the background-task board (`npm start`), see
|
|
9
|
+
[getting-started.md](getting-started.md), then use this as a reference.
|
|
10
|
+
|
|
11
|
+
The visual system, and why it is what it is, is `DESIGN.md` at the repo root.
|
|
12
|
+
|
|
13
|
+
## Screenshots
|
|
14
|
+
|
|
15
|
+
`docs/screenshots/` (listed here so you know what exists before you look for
|
|
16
|
+
one). All nineteen were retaken on 2026-09-15 against the current build, after
|
|
17
|
+
the dark-product-surface redesign and the card-row port that followed it:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
terminals-1280.png the board at 1280 px, four terminals on one login
|
|
21
|
+
board-400px.png the same board at 400 px
|
|
22
|
+
board-details-open.png a terminal with its expansion open
|
|
23
|
+
board-empty.png no background tasks at all
|
|
24
|
+
board-running.png one card running its first agent
|
|
25
|
+
board-handoff.png the limit hit, the card waiting on you
|
|
26
|
+
board-drawer.png that card expanded: bundle, runs, timeline
|
|
27
|
+
board-done.png the card finished, on the agent that finished it
|
|
28
|
+
floor.png /floor with nothing queued
|
|
29
|
+
floor-landing.png /floor with every lane full
|
|
30
|
+
floor-final-1280.png /floor after a card finished
|
|
31
|
+
floor-final-400.png the same at 400 px
|
|
32
|
+
share-owner-1280.png a shared board as its owner
|
|
33
|
+
share-guest-1280.png the same board as a guest: prompts and repos redacted
|
|
34
|
+
demo-1-claude-running.png leg 1 running on fake-claude
|
|
35
|
+
demo-2-limit-hit.png limit hit, bundle written, the row waiting on you
|
|
36
|
+
demo-3-handoff-bundle.png the row expanded: bundle path, run signal, timeline
|
|
37
|
+
demo-4-codex-running.png leg 2 running on fake-codex
|
|
38
|
+
demo-5-done.png done, both legs on the chain
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The five `demo-*.png` are one run of the sequence in [DEMO.md](DEMO.md), all at
|
|
42
|
+
1280 px.
|
|
43
|
+
|
|
44
|
+
To retake one, seed a board with the shape a real one has and drive it to the
|
|
45
|
+
state the shot needs:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
node scripts/seed-wes-board.mjs # 4 live terminals, 5 finished, long paths
|
|
49
|
+
node scripts/seed-floor-board.mjs # cards in every floor lane, plus a landing
|
|
50
|
+
node scripts/board-shots.mjs <port> <tag> # shoot 1280 and 400, print the numbers
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Each seeder writes a throwaway `LEG_HOME` under the OS temp dir; serve it on a
|
|
54
|
+
spare port with `LEG_TRUST=never`. **Never use port 4747**, that is the live
|
|
55
|
+
board, with real sessions on it.
|
|
56
|
+
|
|
57
|
+
Measure against those seeds and never against a board of healthy terminals:
|
|
58
|
+
styling scoped to live rows and measured on a clean board reported 40px terminal
|
|
59
|
+
rows while the real screen showed 400px.
|
|
60
|
+
|
|
61
|
+
## The verdict and the logins
|
|
62
|
+
|
|
63
|
+
`src/board/sessions.js` draws both, from the `accounts` array of
|
|
64
|
+
`GET /api/sessions` and the sessions beside it.
|
|
65
|
+
|
|
66
|
+
**The verdict** is the largest thing on the page and it is a sentence, not a
|
|
67
|
+
number. It names the one fact that decides what happens next:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
All 4 terminals are on claude, and claude has 5% left.
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
One login carrying every terminal is one point of failure, so that is what the
|
|
74
|
+
sentence says. With the terminals spread across logins it names the one closest
|
|
75
|
+
to a wall instead; with nothing running it says so, and names any login that is
|
|
76
|
+
walled. The number it prints is what is **left**, because that is the quantity
|
|
77
|
+
you are deciding against.
|
|
78
|
+
|
|
79
|
+
Under it, one line carrying the age of the reading and which direction it is
|
|
80
|
+
wrong in:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
Measured 2h 13m ago. 4 terminals have been running since, so the real figure is
|
|
84
|
+
higher than 95 percent, never lower.
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
A reading taken two hours ago is a floor, not a measurement, and saying so is
|
|
88
|
+
the entire reason to print its age. Any login at a wall other than the one in
|
|
89
|
+
the headline is named on the same line.
|
|
90
|
+
|
|
91
|
+
**The logins** sit under the verdict, one panel each, and how much surface a
|
|
92
|
+
panel gets is the design saying how much it matters:
|
|
93
|
+
|
|
94
|
+
- The login carrying the terminals gets a wide panel, lit one step brighter than
|
|
95
|
+
the rest, with both of its windows drawn. The window closest to a wall is the
|
|
96
|
+
full-size instrument; the other is the same instrument at half height with a
|
|
97
|
+
smaller numeral, so which one to read is not a question.
|
|
98
|
+
- A login with a single fact to report gets a half panel: its gauge if it has a
|
|
99
|
+
reading, then the fact. A walled login reads `At the wall` and
|
|
100
|
+
`Back Saturday 10:11 PM. Nothing runs on codex until then.`
|
|
101
|
+
- A login that publishes no figure draws no instrument at all. agy's panel says
|
|
102
|
+
`agy publishes no usage figure, ever. Leg shows its terminals and their
|
|
103
|
+
elapsed time instead.` An empty track reads as a measurement of zero to anyone
|
|
104
|
+
glancing at it, so none is drawn.
|
|
105
|
+
|
|
106
|
+
**The gauge** is a track, a fill and a numeral. The fill runs in the login's own
|
|
107
|
+
identity colour up to 85 percent and in the over colour past it, and a 2px notch
|
|
108
|
+
is cut through the bar at 85 at all times, including at zero fill: you can see
|
|
109
|
+
the reserve before you reach it. A window that has never been read draws no fill
|
|
110
|
+
and prints `no reading`, because a zero is a reading and Leg does not print
|
|
111
|
+
one it does not have.
|
|
112
|
+
|
|
113
|
+
Nothing here is on hover. A screen reader gets the whole answer from each
|
|
114
|
+
track's `aria-valuetext`, including the reset, the source and the staleness; a
|
|
115
|
+
window with no value is not a `meter` at all and carries the same sentence as
|
|
116
|
+
its label. The caption under the logins reads `Times are local.`
|
|
117
|
+
|
|
118
|
+
For Codex, the board reads the app-server's read-only
|
|
119
|
+
`account/rateLimits/read` response every 60 seconds and maps its 300- and
|
|
120
|
+
10080-minute windows to 5h and 7d. The active `leg codex` attach also polls
|
|
121
|
+
that response. A percentage alone does not clear a limit wall: Leg waits for
|
|
122
|
+
an explicit available answer from the backend.
|
|
123
|
+
|
|
124
|
+
## Terminals
|
|
125
|
+
|
|
126
|
+
`src/board/sessions.js` renders the region from `GET /api/sessions`, refreshed
|
|
127
|
+
by the server-sent `sessions` event, re-sorted every 15 s, with the elapsed
|
|
128
|
+
clocks ticking every second. With no sessions it prints `No terminal is running.
|
|
129
|
+
Start one in any repo: leg claude, leg codex or leg agy. It appears here
|
|
130
|
+
within a second of the agent's first turn.`
|
|
131
|
+
|
|
132
|
+
The region head carries one verdict with its volume: `3 running, 2 waiting on
|
|
133
|
+
you`, or `3 running, nothing is waiting on you`, or `nothing is running`, with
|
|
134
|
+
`, last landed 11:02 PM` appended when a terminal on the page has landed.
|
|
135
|
+
|
|
136
|
+
### Terminal row
|
|
137
|
+
|
|
138
|
+
One row per live session, inside a single panel, separated from its siblings by
|
|
139
|
+
a hairline. Rows that need an answer come first; the rest follow in start order.
|
|
140
|
+
A row that needs you says `waiting on you` where its status word would be, so
|
|
141
|
+
the state is never carried by colour or position alone.
|
|
142
|
+
|
|
143
|
+
Reading across the row: what it is doing, what it is working on, how long it has
|
|
144
|
+
been at it, and what you can do about it.
|
|
145
|
+
|
|
146
|
+
- **The register**, one line of the smallest type on the board: the status word
|
|
147
|
+
with its dot, then where the work is (`leg on main`, or the folder when it
|
|
148
|
+
is not a repo), then anything unusual about this terminal as plain words, the
|
|
149
|
+
account when it is not `default`, the owner on a shared board, `from <agent>`
|
|
150
|
+
when it was handed off, `own worktree, from main` when it cut its own.
|
|
151
|
+
- **The prompt**, as a button: the first prompt of the session, carried in full
|
|
152
|
+
in its tooltip and in the expansion. A pasted screenshot arrives as an
|
|
153
|
+
`<image name=... path=...>` tag with an absolute temp path in it; the tag is
|
|
154
|
+
replaced by `(image)` and the text, so the operator's home directory is not on
|
|
155
|
+
screen and the sentence starts where the sentence starts.
|
|
156
|
+
- **Under the prompt**, exactly one sentence, the highest-ranked thing true
|
|
157
|
+
about this terminal, then `also: <names>` naming every sentence it is holding
|
|
158
|
+
back, then the files this session is touching as comma-separated **basenames**
|
|
159
|
+
up to six, then `, and N more`. The whole path stays on the title and in the
|
|
160
|
+
expansion; printed in full it was ninety characters of temp path per file. A
|
|
161
|
+
file another live session is also touching is printed in the warning colour.
|
|
162
|
+
A terminal that is merely running prints no sentence at all: its own row
|
|
163
|
+
already says so, and four rows each saying `activity` is four lines of noise.
|
|
164
|
+
- **The clock**: elapsed since the session started (`4h 24m`), and the session's
|
|
165
|
+
short id. The id used to print as `claude-7f3a` immediately after the word
|
|
166
|
+
`claude`; the prefix is the agent name twice and it is gone.
|
|
167
|
+
- **The buttons**, in a fixed 2x2 grid so every row's controls sit in the same
|
|
168
|
+
place: Land, Hand off now, Details, End. Land is the primary action only when
|
|
169
|
+
it can actually run, when it is blocked the accent moves to Hand off now,
|
|
170
|
+
because a disabled control should not wear the one accent colour in the
|
|
171
|
+
design. When Land is disabled its reason is printed, never left in a tooltip.
|
|
172
|
+
|
|
173
|
+
**A fact true of every row is said once, at the region.** Three rows all reading
|
|
174
|
+
`this terminal works in the checkout itself: there is no branch of its own to
|
|
175
|
+
land` is one fact and two lines of noise, so it is hoisted to the region head as
|
|
176
|
+
`every terminal here works in the checkout itself...`. The per-row copy stays in
|
|
177
|
+
the DOM, visually hidden, so each Land button's `aria-describedby` still resolves
|
|
178
|
+
to its own reason. The same applies to any sentence two or more terminals would
|
|
179
|
+
print identically, except muted ones: `turn 12, last activity 1:04 AM` is shared
|
|
180
|
+
by coincidence, not a fact about the board.
|
|
181
|
+
|
|
182
|
+
**Finished terminals leave this panel.** A terminal that has ended or been lost
|
|
183
|
+
is history, and after a day's work it is most of the list. It moves to the
|
|
184
|
+
ledger (below) as part of a count that opens. A finished terminal that still
|
|
185
|
+
needs you, or whose expansion you have open, stays in place.
|
|
186
|
+
|
|
187
|
+
### The one sentence
|
|
188
|
+
|
|
189
|
+
`rankedNotes` in `src/board/sessions.js` is the single source of every sentence
|
|
190
|
+
a panel prints. It builds the list, sorts it, prints the first, and names the
|
|
191
|
+
rest behind `also:`. Rank 1 is worst.
|
|
192
|
+
|
|
193
|
+
| rank | what it says |
|
|
194
|
+
|------|--------------|
|
|
195
|
+
| 1 | `every option is out, first back: codex 10:11 PM`, or `limit: <reason>, back <time>` |
|
|
196
|
+
| 2 | a bounced Land, or `the landing was cut off (the board restarted); press Land again` |
|
|
197
|
+
| 3 | an overlap with another live session |
|
|
198
|
+
| 4 | `sam asked to take this terminal at 11:04 PM` |
|
|
199
|
+
| 5 | `waiting for codex at 10:11 PM` |
|
|
200
|
+
| 6 | `handing off to codex, <reason>, 2m` |
|
|
201
|
+
| 7 | `landing <branch> onto main: commit, rebase, test, fast-forward` |
|
|
202
|
+
| 8 | `near the 5h wall, next: codex` |
|
|
203
|
+
| 9 | a finished Land, `nothing to land`, or the button you last pressed |
|
|
204
|
+
| 10 | `turn 14, last activity 11:04 PM`, the fallback that is always true |
|
|
205
|
+
|
|
206
|
+
A sentence at rank 4 or better is what raises the panel, sorts it to the top and
|
|
207
|
+
counts it in the region head, so those three cannot disagree.
|
|
208
|
+
|
|
209
|
+
Two of them in full, because they are what the board exists to say:
|
|
210
|
+
|
|
211
|
+
- **Overlap**, two live sessions in one repo whose `files_touched` or
|
|
212
|
+
`files_dirty` sets intersect (`sessions.overlaps`), so it catches a file one
|
|
213
|
+
agent has edited and another has only made dirty. In separate checkouts:
|
|
214
|
+
`codex (codex-99ab) is changing src/server.mjs in another checkout; whoever
|
|
215
|
+
lands second rebases`. In the same checkout: `codex (codex-99ab) is editing
|
|
216
|
+
src/server.mjs too`. Three file names, then `, and N more`.
|
|
217
|
+
- **A Land**. Bounced: `Land was attempted at 6:40 PM onto main and bounced:
|
|
218
|
+
CONFLICT (content): Merge conflict in src/server.mjs. The branch still holds
|
|
219
|
+
every commit; nothing was lost.` Finished: `landed on <base>, <7-char sha>,
|
|
220
|
+
<n> files, +<added>/-<removed>`, with `, untested` when the land recorded no
|
|
221
|
+
test run. A finished Land stops being printed ten minutes after it happened.
|
|
222
|
+
Nothing to land: `nothing to land: <branch> has no changes beyond main`.
|
|
223
|
+
|
|
224
|
+
### Status words
|
|
225
|
+
|
|
226
|
+
| status | word |
|
|
227
|
+
|--------|------|
|
|
228
|
+
| `starting` | starting |
|
|
229
|
+
| `running` | running |
|
|
230
|
+
| `warning` | near limit |
|
|
231
|
+
| `limit` | limit hit |
|
|
232
|
+
| `handing_off` | handing off |
|
|
233
|
+
| `waiting` | waiting for reset |
|
|
234
|
+
| `handed_off` | handed off |
|
|
235
|
+
| `ended` | ended |
|
|
236
|
+
| `lost` | lost |
|
|
237
|
+
|
|
238
|
+
The word carries the meaning. The 7 px square beside it is `aria-hidden` and
|
|
239
|
+
only repeats the tone. `waiting` means every option is walled and the terminal
|
|
240
|
+
is counting down to the first reset; its sentence reads `waiting for <agent> at
|
|
241
|
+
<time>`, and End quits that terminal with exit 3. `lost` means the runner
|
|
242
|
+
process that owned that terminal is gone. It is never counted as live.
|
|
243
|
+
|
|
244
|
+
### Terminal buttons
|
|
245
|
+
|
|
246
|
+
The order is fixed, Land, Hand off now, Details, End, and it never reflows by
|
|
247
|
+
availability: a button that does not apply is omitted, never moved.
|
|
248
|
+
|
|
249
|
+
| button | shown when | what it does |
|
|
250
|
+
|--------|------------|--------------|
|
|
251
|
+
| Land | always, disabled with the reason printed under it when the session has no worktree of its own, while it is landing, or when the worktree is gone | `POST /api/sessions/:id/land` (202): commits what the agent left on `leg/<id>`, rebases it onto its base, runs the tests, fast-forwards the base or bounces; the panel's sentence shows the result |
|
|
252
|
+
| Request handoff | the board is shared and this terminal is someone else's | `POST /api/sessions/:id/request-handoff` (202): asks the owner; nothing happens until they approve |
|
|
253
|
+
| Approve `<name>` / Dismiss `<name>` | the board is shared and someone asked for a hand-off on your terminal | `POST /api/sessions/:id/requests/<name>/approve` (or `/dismiss`): approving hands the terminal off, and the event says who it was for |
|
|
254
|
+
| Details | any terminal of yours | `GET /api/sessions/:id/detail`: the transcript, the files changed with their line counts, the timeline and the current bundle; `GET /api/sessions/:id/diff?file=<path>` for one file, capped at 400 lines, refused for any path outside that terminal's own tree |
|
|
255
|
+
| Hand off now | the session is active | `POST /api/sessions/:id/handoff`: saves the bundle, stops this agent, starts the next option in the same terminal |
|
|
256
|
+
| Change order | inside the expansion, while the status is `starting`, `running`, `warning`, `limit` or `waiting` | saves its validated claude/codex/agy priority; an older wrapper instead saves the machine default and says to restart the terminal |
|
|
257
|
+
| End | the session is active | `POST /api/sessions/:id/end`: stops the agent, ends the session |
|
|
258
|
+
| Remove | the session is not active | `DELETE /api/sessions/:id`: safely prunes the session record, worktree, and merged branch only when the worktree is clean and the branch is already on its base; otherwise it leaves them and explains why |
|
|
259
|
+
| Remove record | the session is not active and has an own worktree | `DELETE /api/sessions/:id?force=1&keep_worktree=1`: removes only Leg's record and keeps the worktree, branch, unmerged commits, and dirty files |
|
|
260
|
+
|
|
261
|
+
End, Remove and Remove record confirm first: the button row is replaced in place
|
|
262
|
+
by one sentence and two buttons, focus moves to Cancel, and Escape cancels.
|
|
263
|
+
There is no modal and no browser `confirm()`. Settings has the same order editor
|
|
264
|
+
for the default copied by new terminals.
|
|
265
|
+
|
|
266
|
+
### The expansion
|
|
267
|
+
|
|
268
|
+
**Details**, or a click on the prompt, expands a region in flow directly under
|
|
269
|
+
that panel; the page grows and the panel stays where it is. Nothing slides in
|
|
270
|
+
from the right, and the page keeps one scroll container
|
|
271
|
+
(`docs/screenshots/board-details-open.png`). Sections, top to bottom:
|
|
272
|
+
|
|
273
|
+
1. A header with the agent, the id tail, **Pause updates** and **Close**.
|
|
274
|
+
2. **Now**: the status word, the turn count and the last activity time, the last
|
|
275
|
+
thing the agent said, then `path`, `branch`, `cut from`, `transcript` and
|
|
276
|
+
`head` in full, never shortened. `usage unknown (<why>); the limit still
|
|
277
|
+
hands off` sits here when the endpoint had no numbers for this login: no
|
|
278
|
+
login in that config directory, a 404, a 429, a body that is not JSON, or a
|
|
279
|
+
shape Leg does not recognise.
|
|
280
|
+
3. **Task**: the prompt in full.
|
|
281
|
+
4. **Conversation**: the last 8 turns, newest first, headed `showing 8 of 34
|
|
282
|
+
turns`; **show 40 more** raises the cap.
|
|
283
|
+
5. **Files**: every file changed, with `uncommitted`, `+<adds>`, `-<dels>`, or
|
|
284
|
+
`new` or `committed` when there are no counts against `HEAD`. A row expands
|
|
285
|
+
to that file's diff, capped at 400 lines.
|
|
286
|
+
6. **Timeline**: the last 40 events, newest first.
|
|
287
|
+
7. **What happens next**: `now: claude, then codex, then agy`, which fallback is
|
|
288
|
+
preferred and which is eligible now, the line `Used after a usage limit or
|
|
289
|
+
Hand off now. A normal exit ends this terminal.`, the **Change order**
|
|
290
|
+
editor, the current bundle id, and whether `.leg/RESUME.md` still describes
|
|
291
|
+
the repository (recomputed from git on every poll).
|
|
292
|
+
|
|
293
|
+
It refetches every 3 seconds while it is open, and stops on **Pause updates**,
|
|
294
|
+
when the tab is in the background, or when it is closed. The rest of the page
|
|
295
|
+
stays live, so the panel's own buttons keep working. Messages and diffs are
|
|
296
|
+
scrubbed for secrets on the way out, and the whole region is refused for a
|
|
297
|
+
terminal that belongs to someone else. **Close** or the Escape key closes it.
|
|
298
|
+
|
|
299
|
+
**Change order** moves the three agents without removing one; same-agent
|
|
300
|
+
secondary accounts still come before the other agents. The list is an absolute
|
|
301
|
+
priority: an agent moved to the bottom is tried last from every starting agent.
|
|
302
|
+
The editor previews the resulting priority, with the agent running now skipped,
|
|
303
|
+
before saving.
|
|
304
|
+
|
|
305
|
+
## The ledger
|
|
306
|
+
|
|
307
|
+
Below the terminals, three counts sitting on the ground with no panel, because a
|
|
308
|
+
raised surface here would compete with the terminals that are live. Each is a
|
|
309
|
+
heading, a line of detail and a button that opens the detail below the row.
|
|
310
|
+
|
|
311
|
+
- **N finished**, the terminals that have ended or been lost, `4 lost, 2 ended,
|
|
312
|
+
in leg, costclaw, declick`. **View all N** opens them as full rows. On a real
|
|
313
|
+
board after a day's work this is most of the list, which is exactly why it is a
|
|
314
|
+
count and not the list.
|
|
315
|
+
- **N landed**, what has landed on trunk across every repo the board can see,
|
|
316
|
+
`newest 38 minutes ago, on leg@main, recruiting-tool@main`. **View N
|
|
317
|
+
commits** opens them grouped by repo, newest first: the subject and, for a
|
|
318
|
+
commit a Land put there, `3 hours ago, by claude`, read from
|
|
319
|
+
`~/.leg/landings.jsonl`, which outlives the session; any other commit shows
|
|
320
|
+
its git author. Shown in full it was eighteen rows of `git log` at the same
|
|
321
|
+
visual weight as the live terminals, so the loudest thing on the page was a
|
|
322
|
+
commit from eleven days ago. With nothing landed it reads `Nothing landed yet`
|
|
323
|
+
and explains what Land does.
|
|
324
|
+
- **N background tasks**, the card runtime below, as a count with **View N
|
|
325
|
+
cards**, plus **New card**. With none it reads `Nothing is queued. Leg starts
|
|
326
|
+
the next login only when a terminal hands off.`
|
|
327
|
+
|
|
328
|
+
## A shared board (more than one human)
|
|
329
|
+
|
|
330
|
+
With `leg share` on (off by default), the Terminals region head carries a chip
|
|
331
|
+
reading `you are wes, 2 on this board`, with `, a guest` after the name for
|
|
332
|
+
anyone who is not the owner, and every row carries an owner word in its
|
|
333
|
+
register: `wes, you` on yours, `wes` on someone else's. There is no colour
|
|
334
|
+
difference between the two.
|
|
335
|
+
|
|
336
|
+
A row that belongs to someone else carries exactly one sentence, `read-only: wes
|
|
337
|
+
owns this terminal`. Its prompt reads `prompt hidden`; it has no file names, no
|
|
338
|
+
path beyond the repo and branch, no bundle and no event log; and its only button
|
|
339
|
+
is **Request handoff**. A guest's login panels say `Usage for this login is not
|
|
340
|
+
shared with guests` and draw no instrument. The owner sees `sam asked to take
|
|
341
|
+
this terminal at 11:04 PM` on their own row, with **Approve sam** and **Dismiss
|
|
342
|
+
sam**.
|
|
343
|
+
|
|
344
|
+
A guest's board has no background tasks cell, no New card button and no Floor
|
|
345
|
+
link: that side belongs to the owner of the machine, and the API answers 403.
|
|
346
|
+
|
|
347
|
+
## Background tasks: page layout
|
|
348
|
+
|
|
349
|
+
Everything from here down is the v0.1 card runtime. Each card runs separately
|
|
350
|
+
from the interactive terminal conversations, in its own git worktree. It is the
|
|
351
|
+
third ledger cell, and its rows open in a drawer under the ledger.
|
|
352
|
+
|
|
353
|
+
The masthead (`src/board/index.html`) has the Leg wordmark on the left and, on
|
|
354
|
+
the right, the connection word (`connecting` / `live` / `reconnecting…`) with its
|
|
355
|
+
dot, the scheduler status (`scheduler running, 2 max`), and the **Floor** link.
|
|
356
|
+
**New card** is in the ledger cell, not the masthead, and **Settings** is the
|
|
357
|
+
last region of the page, in flow. Nothing on the board is sticky: the verdict is
|
|
358
|
+
what you came for and it is at the top, so there is nothing to pin.
|
|
359
|
+
|
|
360
|
+
With no cards the region prints `No background task. New card queues one. They
|
|
361
|
+
run headless in their own worktree and report on the floor.` With cards the
|
|
362
|
+
region head prints the counts instead: `2 running, 1 queued, 3 finished`.
|
|
363
|
+
|
|
364
|
+
**Settings** holds the **API token** field (only needed when the server is bound
|
|
365
|
+
off loopback; see [configuration.md](configuration.md#network-exposure)), the
|
|
366
|
+
**New terminal handoff order** editor used by terminals started after you save,
|
|
367
|
+
and the board's own facts: version and bind address, who you are signed in as,
|
|
368
|
+
whether share is on, the scheduler, and the board home. Its region head says
|
|
369
|
+
`No API token set, requests reach 127.0.0.1:4747 unauthenticated`, or `API token
|
|
370
|
+
set, sent to 127.0.0.1:4747 as a bearer token` once a token is stored.
|
|
371
|
+
|
|
372
|
+
## Order
|
|
373
|
+
|
|
374
|
+
There are no columns. Cards are rows in one list, ordered by status (`ROW_RANK`
|
|
375
|
+
in `src/board/board.js`), then by start time within a rank:
|
|
376
|
+
|
|
377
|
+
| rank | statuses |
|
|
378
|
+
|------|----------|
|
|
379
|
+
| 0 | `needs_approval`, `waiting_human` |
|
|
380
|
+
| 1 | `failed`, `killed` |
|
|
381
|
+
| 2 | `running`, `handing_off` |
|
|
382
|
+
| 3 | `paused` |
|
|
383
|
+
| 4 | `queued` |
|
|
384
|
+
| 5 | `backlog` |
|
|
385
|
+
| 6 | `done` |
|
|
386
|
+
|
|
387
|
+
Rows are re-appended only when that order actually changes, so a row does not
|
|
388
|
+
move out from under the cursor.
|
|
389
|
+
|
|
390
|
+
## Card row
|
|
391
|
+
|
|
392
|
+
Each card row reads in the same four registers as a terminal panel:
|
|
393
|
+
|
|
394
|
+
- **Who**: an adapter chip in that agent's colour (or `no agent`), the station
|
|
395
|
+
name when it is not `-`, and the status word.
|
|
396
|
+
- **What**: the title (the card's `title`, or the first 60 characters of the
|
|
397
|
+
task) as a button that opens the [expansion](#card-expansion), and one
|
|
398
|
+
sentence under it.
|
|
399
|
+
- **Where**: `repo@trunk`, the worktree path shortened to its tail with the full
|
|
400
|
+
path in its tooltip, the chain rail, and the lease chips, one per claimed
|
|
401
|
+
lease or `**` (the whole repo) when none were set, plus `blocked by lease`
|
|
402
|
+
when the scheduler says so.
|
|
403
|
+
- **When**: the elapsed clock, `mm:ss` while a leg is running and `--:--`
|
|
404
|
+
otherwise, then the buttons.
|
|
405
|
+
|
|
406
|
+
The log is not on the row. It is in the expansion.
|
|
407
|
+
|
|
408
|
+
### The one sentence
|
|
409
|
+
|
|
410
|
+
`cardSentence` in `src/board/board.js`, one sentence, the highest-ranked thing
|
|
411
|
+
true about the card:
|
|
412
|
+
|
|
413
|
+
- A bounced land, at `queued`, `running`, `handing_off` or `needs_approval`:
|
|
414
|
+
`Land bounced on attempt 2: rebase-conflict. The worktree still holds every
|
|
415
|
+
commit; nothing was lost.` The reason is printed in full.
|
|
416
|
+
- Queued and blocked: the scheduler's own `blocked_by` message.
|
|
417
|
+
- `failed` or `killed`: `failed at station build after 2 runs: <last event>`.
|
|
418
|
+
- `waiting_human` or `needs_approval`: `waiting on you at station review since
|
|
419
|
+
11:04 PM`.
|
|
420
|
+
- `done`: `done after 3 runs, <last event>`.
|
|
421
|
+
- Otherwise the last ledger event, `<type>: <summary>`, truncated to 90
|
|
422
|
+
characters, with its time.
|
|
423
|
+
|
|
424
|
+
### Status words
|
|
425
|
+
|
|
426
|
+
| status | word | notes |
|
|
427
|
+
|--------|------|-------|
|
|
428
|
+
| `backlog` | backlog | |
|
|
429
|
+
| `queued` | queued | |
|
|
430
|
+
| `running` | running, or **landing** | "landing" when the current station is a `land` station |
|
|
431
|
+
| `handing_off` | handing off | |
|
|
432
|
+
| `waiting_human` | waiting human, or **PR open** | "PR open" when the card carries a `pr_url` (land mode `pr`) |
|
|
433
|
+
| `needs_approval` | needs approval | |
|
|
434
|
+
| `paused` | paused | |
|
|
435
|
+
| `done` | done | |
|
|
436
|
+
| `failed` | failed | |
|
|
437
|
+
| `killed` | killed | |
|
|
438
|
+
|
|
439
|
+
### Chain rail
|
|
440
|
+
|
|
441
|
+
One pair of tokens per chain entry at the current station: the adapter name in
|
|
442
|
+
its agent colour, then that leg's state as a word, `pending`, `running`, `done`,
|
|
443
|
+
`handed off` or `failed` (`stateGlyph` in `src/board/board.js`). There are no
|
|
444
|
+
glyphs: the subset fonts carry no check, cross or arrow. A gated `approve: true`
|
|
445
|
+
entry is not marked on the rail; **Approve** appears in the row's buttons when
|
|
446
|
+
the card reaches `needs_approval`.
|
|
447
|
+
|
|
448
|
+
## Card expansion
|
|
449
|
+
|
|
450
|
+
Click a card's title to expand a region in flow under that row. Sections, top to
|
|
451
|
+
bottom:
|
|
452
|
+
|
|
453
|
+
1. **Title**.
|
|
454
|
+
2. **Task**: the full task text, headed `card <id>`.
|
|
455
|
+
3. **Where**: `repo`, `trunk`, `worktree`, `leases` and the bundle path, full
|
|
456
|
+
paths never shortened, with a **Copy worktree path** button.
|
|
457
|
+
4. **Pipeline**: one row per station, its name and its kind, the current one
|
|
458
|
+
marked `, this station`; the heading says `station 2 of 5`.
|
|
459
|
+
5. **Runs**: one row per run: run number, adapter, outcome or status, signal,
|
|
460
|
+
exit code.
|
|
461
|
+
6. **Timeline**: the ledger, newest first.
|
|
462
|
+
7. **Log**: the tail of the active or most recent run's combined log, up to 2000
|
|
463
|
+
lines.
|
|
464
|
+
|
|
465
|
+
**Close** or the Escape key collapses it, and focus returns to the title.
|
|
466
|
+
|
|
467
|
+
## New card form
|
|
468
|
+
|
|
469
|
+
The dialog opened by **New card** (`src/board/index.html`):
|
|
470
|
+
|
|
471
|
+
| field | notes |
|
|
472
|
+
|-------|-------|
|
|
473
|
+
| Repo path | required; an absolute path to a git repository |
|
|
474
|
+
| Task | required; the prompt every leg gets |
|
|
475
|
+
| First agent | required; real agents appear here and Claude is preferred when installed |
|
|
476
|
+
| Run now | checked by default; unchecking saves the card as a draft in `backlog` |
|
|
477
|
+
| Workflow (Advanced) | Build only stops unmerged in the worktree; Build-land runs test then land; Factory runs plan, build, review, test, and land; custom JSON reveals a station-array textarea |
|
|
478
|
+
| First-agent controls (Advanced) | allowed permission mode, approval gate, max turns, and scripted behavior when the test/demo override is selected |
|
|
479
|
+
| Fallback agents (Advanced) | one understandable row per later adapter, tried in displayed order only if the previous agent cannot continue |
|
|
480
|
+
| Scripted first agent (Advanced) | explicitly test/demo only; keeps fake adapters out of the normal first-agent default |
|
|
481
|
+
| File leases (Advanced) | comma-separated path globs to reserve so overlapping cards wait |
|
|
482
|
+
| Trunk branch | default `main` |
|
|
483
|
+
| Merge method | `ff` or `pr`; relevant to a workflow with a land station |
|
|
484
|
+
| Test command | overrides the land station's auto-detected command |
|
|
485
|
+
| Title | optional; defaults to the task's first 80 characters |
|
|
486
|
+
|
|
487
|
+
**Cancel** closes without creating a card; **Create card** posts it and closes on
|
|
488
|
+
success (errors show inline above the form).
|
|
489
|
+
|
|
490
|
+
## Buttons
|
|
491
|
+
|
|
492
|
+
Which buttons a card shows depends on its status (`src/chain.mjs`
|
|
493
|
+
`availableActions`, labels from `src/board/board.js` `ACTION_LABELS`). The order
|
|
494
|
+
is fixed: Approve, Run, Resume, Pause, Hand off now, Rerun, Reassign, Kill.
|
|
495
|
+
|
|
496
|
+
| button | shown when | what it does |
|
|
497
|
+
|--------|------------|--------------|
|
|
498
|
+
| Run | `backlog` | queues the card (`enqueue`) |
|
|
499
|
+
| Pause | `running` | stop after the current leg, child killed, bundle written |
|
|
500
|
+
| Hand off now | `running`, at an `agent` station that has a later chain entry | end the current leg as if it were incomplete, write the bundle, start the next adapter |
|
|
501
|
+
| Resume | `paused` | pick up where it stopped |
|
|
502
|
+
| Approve | `needs_approval` or `waiting_human` | release a gated leg, or clear a human station |
|
|
503
|
+
| Kill | any non-terminal status | stop the running agent; card ends `killed` |
|
|
504
|
+
| Reassign | any non-terminal status, and the current station is an `agent` station | pick a different adapter/mode for the current leg from a picker |
|
|
505
|
+
| Rerun | `done`, `failed`, `killed` | start the station over from the last bundle |
|
|
506
|
+
| Remove | `done`, `failed`, `killed` | delete the card record, its events and its runs; the worktree is kept (confirms first) |
|
|
507
|
+
|
|
508
|
+
## What a state looks like
|
|
509
|
+
|
|
510
|
+
| state | row rank | status word | typical buttons |
|
|
511
|
+
|-------|----------|-------------|------------------|
|
|
512
|
+
| backlog | 5 | backlog | Run |
|
|
513
|
+
| queued | 4 | queued | Kill, Reassign* |
|
|
514
|
+
| blocked by lease | 4 | queued, with a `blocked by lease` chip | Kill, Reassign* |
|
|
515
|
+
| running | 2 | running (or landing) | Pause, Hand off now†, Kill, Reassign* |
|
|
516
|
+
| handing off | 2 | handing off | Kill, Reassign* |
|
|
517
|
+
| needs approval | 0 | needs approval | Approve, Kill, Reassign* |
|
|
518
|
+
| waiting human | 0 | waiting human (or PR open) | Approve, Kill |
|
|
519
|
+
| paused | 3 | paused | Resume, Kill, Reassign* |
|
|
520
|
+
| done | 6 | done | Rerun, Remove |
|
|
521
|
+
| failed | 1 | failed | Rerun, Remove |
|
|
522
|
+
| killed | 1 | killed | Rerun, Remove |
|
|
523
|
+
|
|
524
|
+
\* Reassign only shows when the current station is an `agent` station.
|
|
525
|
+
† Hand off now only shows when that station has a later chain entry.
|
|
526
|
+
|
|
527
|
+
## Floor view
|
|
528
|
+
|
|
529
|
+
`/floor` (`docs/screenshots/floor.png`) is the scheduler-eye view across every
|
|
530
|
+
card. It carries the same instrument head as the board, then polls `/api/floor`
|
|
531
|
+
and `/api/trunk?since=1h` every 2 seconds and refreshes on the same server-sent
|
|
532
|
+
events as the board. Its top bar has the Leg floor brand, the same connection
|
|
533
|
+
word and 24 px rule, a repos list, the scheduler status, running/queued/waiting/
|
|
534
|
+
done counts, then a spacer and the **Board** link. Five tables:
|
|
535
|
+
|
|
536
|
+
| section | columns |
|
|
537
|
+
|---------|---------|
|
|
538
|
+
| Running | Card, Station, Agent / leg, Leases, Last event, Elapsed, Actions |
|
|
539
|
+
| Waiting on humans | Card, Station, Status, Since, Actions |
|
|
540
|
+
| Queued | Card, Station, Leases, Blocked by |
|
|
541
|
+
| Leases | Lease, Card, Station, Since |
|
|
542
|
+
| Trunk lane | Time, Card, Summary |
|
|
543
|
+
|
|
544
|
+
Each region head names its own count, `0 running`, `0 landed in the last hour`,
|
|
545
|
+
and an empty table is replaced by a sentence saying what would put a row there.
|
|
546
|
+
The Trunk lane lists commits landed in the last hour, newest first. The Leases
|
|
547
|
+
table is `src/leases.mjs` `held()`: one row per lease currently claimed by a
|
|
548
|
+
running or handing-off card.
|
|
549
|
+
|
|
550
|
+
## Keyboard and accessibility
|
|
551
|
+
|
|
552
|
+
- Every interactive element (links, buttons, inputs, selects, textareas,
|
|
553
|
+
summaries) gets a visible focus ring: `outline: 2px solid var(--focus);
|
|
554
|
+
outline-offset: 2px` on `:focus-visible` only (`src/board/board.css`). There
|
|
555
|
+
is no bare `:focus` rule, so a mouse click leaves no ring behind it.
|
|
556
|
+
- Escape closes the open expansion, the terminal's and the card's. If a confirm
|
|
557
|
+
row is open, Escape cancels that first. The floor has neither, so Escape does
|
|
558
|
+
nothing there.
|
|
559
|
+
- Buttons carry descriptive `aria-label`s (for example "Pause `<card title>`",
|
|
560
|
+
"Reassign adapter for `<card title>`") so a screen reader announces which card
|
|
561
|
+
an action applies to, not just the button label.
|
|
562
|
+
- A terminal's expansion is a `role="region"` labelled "What this terminal is
|
|
563
|
+
doing", and both expansions set `aria-hidden` and `hidden` on open and close.
|
|
564
|
+
- The 7 px status mark is an `aria-hidden` decoration; the status word beside it
|
|
565
|
+
carries the meaning. The chain rail prints each leg's state as a word, so
|
|
566
|
+
nothing on it is hidden from a screen reader either.
|
|
567
|
+
- No state is carried by colour alone: a panel that needs you also says `waiting
|
|
568
|
+
on you` and gains a 2 px bar, and every usage tier prints its word beside the
|
|
569
|
+
rail.
|
|
570
|
+
- `@media (prefers-reduced-motion: reduce)` is respected in
|
|
571
|
+
`src/board/board.css`: the status mark's annunciation animation and every
|
|
572
|
+
transition on the page are disabled for readers who ask for it.
|
|
573
|
+
|
|
574
|
+
## See also
|
|
575
|
+
|
|
576
|
+
- [concepts.md](concepts.md): what each status and outcome means underneath
|
|
577
|
+
the board.
|
|
578
|
+
- [configuration.md](configuration.md): `LEG_BIND` / `LEG_TOKEN` (the
|
|
579
|
+
Settings token field), `LEG_PORT`.
|