@gavin-hu/my-pi-agent 0.21.0 → 0.22.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/README.md +46 -31
- package/README.zh-CN.md +40 -26
- package/assets/screens/board.png +0 -0
- package/assets/screens/chrome.png +0 -0
- package/assets/screens/file-browse.png +0 -0
- package/assets/screens/plan-review.png +0 -0
- package/assets/screens/plan.png +0 -0
- package/docs/extensions/README.md +22 -24
- package/docs/extensions/board.md +361 -147
- package/docs/extensions/bytefly.md +152 -111
- package/docs/extensions/check.md +15 -17
- package/docs/extensions/context.md +35 -15
- package/docs/extensions/file-browse.md +30 -31
- package/docs/extensions/file-open.md +9 -11
- package/docs/extensions/file-read.md +12 -14
- package/docs/extensions/goal.md +15 -17
- package/docs/extensions/job.md +54 -42
- package/docs/extensions/memory.md +17 -19
- package/docs/extensions/plan.md +166 -126
- package/docs/extensions/question.md +16 -18
- package/docs/extensions/rewind.md +17 -19
- package/docs/extensions/serve.md +34 -18
- package/docs/extensions/session-bus.md +38 -16
- package/docs/extensions/subagent.md +23 -25
- package/docs/extensions/team.md +72 -33
- package/docs/extensions/todo.md +18 -18
- package/docs/extensions/watch.md +31 -31
- package/docs/extensions/web-access.md +2 -4
- package/docs/extensions/wechat.md +34 -35
- package/docs/extensions/worktree.md +56 -50
- package/extensions/board/commands.ts +56 -15
- package/extensions/board/format.ts +43 -24
- package/extensions/board/index.ts +5 -4
- package/extensions/board/input.ts +4 -9
- package/extensions/board/join.ts +4 -40
- package/extensions/board/lazy-page.ts +40 -0
- package/extensions/board/page.ts +13 -11
- package/extensions/board/result.ts +30 -16
- package/extensions/board/rows.ts +62 -25
- package/extensions/board/schema.ts +42 -10
- package/extensions/board/snapshot.ts +2 -1
- package/extensions/board/tone.ts +22 -0
- package/extensions/board/tools.ts +131 -44
- package/extensions/board/types.ts +36 -32
- package/extensions/board/view/card.ts +229 -0
- package/extensions/board/view/client.ts +33 -15
- package/extensions/board/view/dialog.ts +169 -6
- package/extensions/board/view/empty.ts +47 -0
- package/extensions/board/view/keys.ts +13 -2
- package/extensions/board/view/live.ts +89 -21
- package/extensions/board/view/names.ts +13 -4
- package/extensions/board/view/page.ts +23 -9
- package/extensions/board/view/render.ts +208 -271
- package/extensions/board/view/styles.ts +123 -31
- package/extensions/board/view/team.ts +297 -76
- package/extensions/bytefly/block/fallback.ts +1 -1
- package/extensions/bytefly/doctor/runtime.ts +5 -2
- package/extensions/bytefly/footer/collect/label.ts +60 -0
- package/extensions/bytefly/footer/collect/snapshot.ts +8 -1
- package/extensions/bytefly/footer/config.ts +4 -2
- package/extensions/bytefly/footer/register.ts +33 -4
- package/extensions/bytefly/footer/render/lines.ts +7 -4
- package/extensions/bytefly/footer/render/segments.ts +37 -10
- package/extensions/bytefly/footer/render/status.ts +0 -11
- package/extensions/bytefly/footer/types.ts +7 -0
- package/extensions/bytefly/header/config.ts +1 -1
- package/extensions/bytefly/index.ts +32 -6
- package/extensions/bytefly/mode/editor.ts +115 -0
- package/extensions/bytefly/panel/commands.ts +210 -120
- package/extensions/bytefly/panel/format.ts +4 -4
- package/extensions/bytefly/panel/settings.ts +73 -6
- package/extensions/bytefly/panel/tui/keys.ts +2 -6
- package/extensions/bytefly/update/cache.ts +1 -1
- package/extensions/bytefly/update/register.ts +4 -4
- package/extensions/bytefly/working/config.ts +5 -7
- package/extensions/bytefly/working/format.ts +10 -13
- package/extensions/bytefly/working/register.ts +22 -27
- package/extensions/check/commands.ts +3 -2
- package/extensions/check/{tui.ts → view/check-screens.ts} +14 -19
- package/extensions/context/commands.ts +2 -2
- package/extensions/context/{tui.ts → view/context-report.ts} +51 -19
- package/extensions/file-browse/index.ts +67 -36
- package/extensions/file-read/render.ts +4 -4
- package/extensions/goal/plan-route.ts +2 -2
- package/extensions/job/child.ts +527 -0
- package/extensions/job/clock.ts +142 -0
- package/extensions/job/commands.ts +4 -2
- package/extensions/job/config.ts +8 -1
- package/extensions/job/format.ts +22 -1
- package/extensions/job/log-writer.ts +13 -2
- package/extensions/job/registry.ts +63 -7
- package/extensions/job/render.ts +34 -0
- package/extensions/job/runtime.ts +296 -655
- package/extensions/job/session.ts +132 -19
- package/extensions/job/store.ts +7 -7
- package/extensions/job/tools.ts +3 -3
- package/extensions/job/types.ts +47 -0
- package/extensions/job/{tui.ts → view/jobs-screen.ts} +19 -73
- package/extensions/job/waiters.ts +44 -1
- package/extensions/memory/commands.ts +4 -2
- package/extensions/memory/{tui.ts → view/memory-picker.ts} +12 -12
- package/extensions/plan/commands.ts +12 -9
- package/extensions/plan/format.ts +8 -2
- package/extensions/plan/git-bash.ts +115 -16
- package/extensions/plan/index.ts +53 -52
- package/extensions/plan/plan-index.ts +4 -2
- package/extensions/plan/plans.ts +5 -54
- package/extensions/plan/policy.ts +8 -10
- package/extensions/plan/review-lazy.ts +85 -0
- package/extensions/plan/review.ts +2 -2
- package/extensions/plan/runtime.ts +49 -39
- package/extensions/plan/sections.ts +1 -1
- package/extensions/plan/steps.ts +3 -3
- package/extensions/plan/todos.ts +1 -1
- package/extensions/plan/tools/cancel.ts +27 -62
- package/extensions/plan/tools/enter.ts +55 -81
- package/extensions/plan/tools/exit.ts +221 -238
- package/extensions/plan/tools/refusal.ts +18 -0
- package/extensions/plan/tools/steps.ts +75 -92
- package/extensions/plan/tools/write.ts +77 -120
- package/extensions/plan/tools.ts +188 -30
- package/extensions/plan/tui.ts +5 -5
- package/extensions/plan/types.ts +48 -5
- package/extensions/plan/web-client.ts +3 -0
- package/extensions/plan/web-lazy.ts +53 -0
- package/extensions/plan/web-markdown.ts +9 -67
- package/extensions/plan/web-payload.ts +3 -3
- package/extensions/plan/web-router.ts +18 -9
- package/extensions/plan/web.ts +1 -12
- package/extensions/question/tools.ts +6 -2
- package/extensions/question/{tui.ts → view/question-screen.ts} +11 -11
- package/extensions/rewind/commands/commands.ts +6 -3
- package/extensions/rewind/state/runtime.ts +1 -1
- package/extensions/rewind/{view/status.ts → status.ts} +4 -4
- package/extensions/rewind/view/review-tui.ts +4 -4
- package/extensions/serve/config.ts +10 -8
- package/extensions/serve/index.ts +17 -3
- package/extensions/serve/registry.ts +26 -17
- package/extensions/session-bus/commands.ts +11 -6
- package/extensions/session-bus/join-request.ts +2 -1
- package/extensions/session-bus/mail-watch.ts +26 -17
- package/extensions/session-bus/protocol.ts +15 -3
- package/extensions/session-bus/resolve.ts +5 -7
- package/extensions/session-bus/runtime.ts +40 -12
- package/extensions/session-bus/store.ts +101 -19
- package/extensions/session-bus/types.ts +19 -0
- package/extensions/session-bus/{tui.ts → view/session-list.ts} +16 -16
- package/extensions/subagent/agents.ts +1 -1
- package/extensions/subagent/commands.ts +4 -2
- package/extensions/subagent/format.ts +15 -15
- package/extensions/subagent/index.ts +1 -1
- package/extensions/subagent/render.ts +3 -3
- package/extensions/subagent/{background-tui.ts → view/agents-board.ts} +11 -11
- package/extensions/team/commands.ts +24 -7
- package/extensions/team/dispatch.ts +18 -4
- package/extensions/team/format.ts +17 -2
- package/extensions/team/index.ts +9 -8
- package/extensions/team/schema.ts +6 -5
- package/extensions/team/tools.ts +107 -45
- package/extensions/team/types.ts +3 -2
- package/extensions/todo/commands.ts +4 -2
- package/extensions/todo/format.ts +11 -0
- package/extensions/todo/render.ts +57 -0
- package/extensions/todo/runtime.ts +1 -1
- package/extensions/todo/tools.ts +1 -1
- package/extensions/todo/view/todo-screen.ts +122 -0
- package/extensions/todo/widget.ts +47 -0
- package/extensions/watch/commands.ts +4 -2
- package/extensions/watch/format.ts +36 -7
- package/extensions/watch/render.ts +57 -0
- package/extensions/watch/runtime.ts +27 -22
- package/extensions/watch/tools.ts +4 -4
- package/extensions/watch/{tui.ts → view/watch-screen.ts} +18 -80
- package/extensions/web-access/fetch/tool.ts +12 -4
- package/extensions/wechat/commands/commands.ts +18 -14
- package/extensions/wechat/index.ts +72 -37
- package/extensions/wechat/state/credentials.ts +1 -1
- package/extensions/wechat/state/state.ts +1 -1
- package/extensions/wechat/text/status.ts +112 -0
- package/extensions/wechat/types.ts +2 -2
- package/extensions/wechat/view/status-screen.ts +4 -103
- package/extensions/worktree/commands.ts +11 -9
- package/extensions/worktree/enter.ts +2 -2
- package/extensions/worktree/exit.ts +2 -2
- package/extensions/worktree/index.ts +5 -5
- package/extensions/worktree/screen.ts +3 -3
- package/extensions/worktree/status.ts +2 -2
- package/extensions/worktree/tools.ts +113 -116
- package/lib/README.md +27 -13
- package/lib/board/actions.ts +111 -0
- package/lib/board/artifact.ts +149 -0
- package/lib/board/card.ts +163 -52
- package/lib/board/config.ts +58 -0
- package/lib/board/contract.ts +113 -0
- package/lib/board/join.ts +38 -0
- package/lib/board/lanes.ts +182 -0
- package/lib/board/lead.ts +130 -0
- package/lib/board/limits.ts +78 -0
- package/lib/board/links.ts +31 -10
- package/lib/board/page-pointer.ts +1 -1
- package/lib/board/read.ts +83 -0
- package/lib/board/reason.ts +102 -0
- package/lib/board/round-text.ts +68 -6
- package/lib/board/round.ts +31 -15
- package/lib/board/rules.ts +151 -307
- package/lib/board/seat.ts +35 -0
- package/lib/board/session.ts +136 -54
- package/lib/board/skipped.ts +22 -0
- package/lib/board/steps.ts +487 -0
- package/lib/board/store.ts +87 -30
- package/lib/board/tone.ts +92 -0
- package/lib/board/view.ts +70 -58
- package/{extensions/watch → lib}/child.ts +40 -21
- package/lib/env.ts +65 -7
- package/lib/format.ts +13 -0
- package/lib/http/index.ts +14 -12
- package/lib/http/port.ts +15 -10
- package/lib/lazy.ts +45 -0
- package/lib/plan-entry.ts +39 -10
- package/lib/plans/paths.ts +82 -0
- package/lib/policy.ts +9 -6
- package/lib/report.ts +3 -2
- package/lib/requirements.ts +8 -8
- package/lib/session-label.ts +111 -5
- package/lib/session-roster.ts +43 -1
- package/lib/session-tool.ts +5 -4
- package/lib/stray-fields.ts +7 -2
- package/lib/tool-names.ts +5 -10
- package/lib/tui/README.md +23 -8
- package/lib/tui/chrome.ts +16 -15
- package/lib/tui/cursor.ts +18 -3
- package/lib/tui/glyphs.ts +9 -7
- package/lib/tui/tokens.ts +33 -2
- package/lib/tui/tool.ts +3 -3
- package/lib/web/README.md +21 -0
- package/lib/web/components.ts +5 -1
- package/lib/web/index.ts +1 -0
- package/lib/web/markdown.ts +83 -0
- package/lib/web/markup.ts +2 -1
- package/lib/web/runtime.ts +9 -10
- package/package.json +6 -1
- package/skills/init/SKILL.md +5 -5
- package/skills/next/SKILL.md +3 -5
- package/extensions/board/seat.ts +0 -28
- package/extensions/todo/tui.ts +0 -212
- package/lib/tui/index.ts +0 -34
- package/skills/project-docs/SKILL.md +0 -133
- package/skills/project-docs/references/audit.md +0 -131
- package/skills/project-docs/references/changelog.md +0 -105
- package/skills/project-docs/references/discovery.md +0 -127
- package/skills/project-docs/references/readme.md +0 -80
- package/skills/project-docs/references/release.md +0 -84
- package/skills/project-docs/references/roadmap.md +0 -93
- package/skills/project-docs/scripts/state.ts +0 -316
package/docs/extensions/board.md
CHANGED
|
@@ -1,22 +1,69 @@
|
|
|
1
1
|
# board — one shared feature board per repository
|
|
2
2
|
|
|
3
3
|
Every Pi session on a repository works the same cards through the tools, and a repository
|
|
4
|
-
has **one** page that shows them: the session that starts it serves it under the seat
|
|
5
|
-
`
|
|
4
|
+
has **one** page that shows them: the session that starts it serves it under the seat —
|
|
5
|
+
the board's lead, `board.lead`, `lead` by default — and every other session opens that same
|
|
6
|
+
page instead of a second one. The
|
|
6
7
|
page is a **view** — it cannot change a card — so what a browser shows and what the
|
|
7
8
|
terminal holds cannot disagree. A card is one **feature**: its outcome, who holds it,
|
|
8
|
-
where the work happens, the
|
|
9
|
-
and the notes that explain how it got there.
|
|
9
|
+
where the work happens, the contract it promises, the plan it is executed against, how
|
|
10
|
+
urgent it is, what it waits for, and the notes that explain how it got there.
|
|
11
|
+
|
|
12
|
+
A card moves through **five lanes**, and the flow is a pull one: `backlog` is the pool
|
|
13
|
+
nobody holds, `plan` is where the plan is designed and the contract written and signed,
|
|
14
|
+
`execute` is where the work happens, `blocked` is a card waiting on a person, and `done` is
|
|
15
|
+
closed. The order is the board's, and each call can only follow the one before it:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
board claim 4 the card is yours, in plan
|
|
19
|
+
/plan (plan mode on) design it, plan write saves the artifact
|
|
20
|
+
plan exit the user approves the plan
|
|
21
|
+
board contract 4 path … goal, acceptance, scopeOut, and that file — it must exist
|
|
22
|
+
board approve 4 or /board approve 4 as the person; the holder may never sign
|
|
23
|
+
board move 4 execute the plan is re-checked, then the work starts
|
|
24
|
+
board review 4 or /board review 4, before it can be called done
|
|
25
|
+
board move 4 done
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Two gates are humans'. A card cannot enter `execute` until **the lead** — `board.lead` in
|
|
29
|
+
`bytefly.json`, `lead` by default — has approved the contract, and cannot be
|
|
30
|
+
finished until the lead has spot-checked the work. The lead is a team name, so it signs as a session that
|
|
31
|
+
joined under it, and never while holding the card: a lead releases a card before signing it.
|
|
32
|
+
The person at the keyboard is the other way in — `/board approve` and `/board review` write
|
|
33
|
+
as `you` — which is what makes a one-session flow possible, and the reason a repository
|
|
34
|
+
nobody configured can still finish work. A
|
|
35
|
+
card cannot skip the artifact either: `contract` requires the plan file `plan` action `write`
|
|
36
|
+
returned, inside this repository's plan store, and a move to `execute` or `done` re-checks
|
|
37
|
+
that the file is still there. `plan` and `execute` are held to a WIP limit, so the board
|
|
38
|
+
says "make room" instead of quietly accumulating work nobody is reviewing.
|
|
10
39
|
|
|
11
40
|
The steps of the work are deliberately *not* on the board. They live in the
|
|
12
|
-
owner's plan and in `todo`; the board answers who is
|
|
41
|
+
owner's plan and in `todo`; the board answers who is holding what, and what needs a
|
|
13
42
|
decision.
|
|
14
43
|
|
|
15
|
-
|
|
44
|
+
The board is **opt-in**. Its tool, its command, and its page belong to a workflow a
|
|
45
|
+
session either runs or does not, so nothing is registered until the session asks for
|
|
46
|
+
it:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
PI_ENABLED_EXTENSIONS=board pi
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`team` — the lead's half of the same workflow, and the only extension that can hand a
|
|
53
|
+
card to a peer — is opt-in beside it, so a lead names both:
|
|
54
|
+
`PI_ENABLED_EXTENSIONS=board,team`. They stay independent: one name never loads the
|
|
55
|
+
other.
|
|
56
|
+
|
|
57
|
+
The extension is still declared in the package manifest, so Pi discovers it and
|
|
58
|
+
documents its surface with the rest; the gate is the variable, in `lib/env.ts` beside
|
|
59
|
+
`PI_DISABLED_EXTENSIONS`, which still wins when it names `board`. `doctor` reports the
|
|
60
|
+
row as an opt-in extension and names the switch.
|
|
16
61
|
|
|
17
|
-
|
|
62
|
+
<img src="../../assets/screens/board.png" alt="The board page: a toolbar with the repository's name and a live badge, Board and Team tabs, done-of-total progress, a filter, the keys button and a theme toggle; five lane trays, each with a dot, a count and a fold control; and cards carrying an owner chip, a note count and its age, path hints, a line naming what the card waits for, a line naming the cards it holds up, and a reason line, with no button on any of them" width="100%">
|
|
18
63
|
|
|
19
|
-
|
|
64
|
+
*`/board`: the five lanes, with the round one tab away and every card opened by its title.*
|
|
65
|
+
|
|
66
|
+
<img src="../../assets/screens/card-dialog.png" alt="The card dialog on one executing card: the id and title with the lane as a dot and a word, a head/value ladder reading owner, updated, in lane, last edited, plan, goal, acceptance, scope out, approved, reviewed and paths, each value on one line, then a Notes section whose sentences are attributed under them" width="100%">
|
|
20
67
|
|
|
21
68
|
*The card dialog: the record of one card — a ladder of facts, then its notes at body size
|
|
22
69
|
(`screenshot:board --dialog card`).*
|
|
@@ -25,30 +72,32 @@ decision.
|
|
|
25
72
|
|
|
26
73
|
| Capability | How | What you get |
|
|
27
74
|
|---|---|---|
|
|
28
|
-
| A shared feature list for one repository | `board` action `list` | Every open card in lane order — `
|
|
29
|
-
| The lead's rollup | `board` action `list` with `round: true` | Every owner with its live cards and, beside the count, what it has finished — an owner whose last card is done stays listed — then unassigned, unplanned, blocked, idle, and done |
|
|
30
|
-
| Publish a feature | `board` action `add` | A `
|
|
31
|
-
| Take a feature | `board` action `claim` | The card is `
|
|
32
|
-
| Hand it back | `board` action `release` | The card is `
|
|
33
|
-
|
|
|
34
|
-
|
|
|
35
|
-
|
|
|
75
|
+
| A shared feature list for one repository | `board` action `list` | Every open card in lane order — `backlog`, `plan`, `execute`, `blocked` — or one card, or one lane; the `done` cards are left out with a count of how many, and `lane: "done"` reads them |
|
|
76
|
+
| The lead's rollup | `board` action `list` with `round: true` | Every owner with its live cards and, beside the count, what it has finished — an owner whose last card is done stays listed, and the owners a bus has no record of and that hold nothing are one line, `18 owners with no record · 76 done` — then unassigned (the pool), unplanned (a contract still missing a line, or a plan never recorded — either one refuses `execute`), awaiting approval, blocked, idle, and done |
|
|
77
|
+
| Publish a feature | `board` action `add` | A `backlog` card with path hints and a first note (one line, at most 1000 characters) |
|
|
78
|
+
| Take a feature | `board` action `claim` | The card is pulled into `plan` and owned by this session — or refused, naming the count, when `plan` is full. The result names the next step, because the claim is what commits the session to a plan |
|
|
79
|
+
| Hand it back | `board` action `release` | The card is back in `backlog` with no owner; the contract and its approval stay, because the plan did not change |
|
|
80
|
+
| Write the contract | `board` action `contract` | The plan file and the three lines the card promises — `goal`, `acceptance`, `scopeOut` — written in one call; `path` is required, and it must be the plan `plan` action `write` saved in this repository's plan store, so a contract cannot name a file that does not exist. Any change to a line or to the file drops the approval, because the sign named both |
|
|
81
|
+
| Approve the contract | `board` action `approve`, or `/board approve <id>` | The lead records that the wording is right — a session that joined under `board.lead`, and never the holder — or the person at the keyboard signs; only then may the card enter `execute`. `text` adds a note, recorded with the sign, because a signer is exactly the session `note` refuses on a held card |
|
|
82
|
+
| Spot-check the work | `board` action `review`, or `/board review <id>` | The lead records that they looked at the finished work in `execute` — never the holder — or the person does; only then may the card be finished. `text` adds a note, recorded with the sign, so "checked, and this is still wrong" is one call |
|
|
83
|
+
| Change lane | `board` action `move` | Wherever the board allows from where the card is: `plan` to `execute` (no unfinished dependency, a complete contract, a plan that is still in the store, an approval by somebody other than the holder) or `blocked`, `execute` to `plan` (re-plan), `blocked`, or `done` (no unfinished dependency, spot-checked, the same plan check), `blocked` back to `plan` when that lane has room, or to `execute` once its contract is approved and its plan is there |
|
|
84
|
+
| Record what happened | `board` action `note` | A note appended to the card's history, attributed and stamped — one line, at most 1000 characters; its holder writes it, or anyone while nobody holds the card, the way `edit` works |
|
|
36
85
|
| Fix what a card says | `board` action `edit` | A new title, path hints, priority, or the cards it waits for — by its holder, or by anyone while nobody holds it |
|
|
37
86
|
| Say how urgent it is | `board` action `add` or `edit` with `priority` | `high` sorts first inside its lane, and the card says so |
|
|
38
|
-
| Record what a card waits for | `board` action `edit` with `dependsOn` | An unfinished dependency refuses a claim and a move to `
|
|
39
|
-
| Hand a card to a teammate | `team` action `assign` | The owner is recorded **and** a handoff is sent to that session's inbox. The page cannot do this: a browser has no way to message a peer, so a page assignment that recorded an owner and told nobody is gone with the write path |
|
|
40
|
-
| Take a card back from the tool | `board` action `reclaim` | The owner clears and the card returns to
|
|
87
|
+
| Record what a card waits for | `board` action `edit` with `dependsOn` | An unfinished dependency refuses a claim and a move to `execute` or `done`, and the reason line names it; the card it waits on answers the other half — `list` on it names the cards waiting on it, and its dialog row `waited on by` says the same |
|
|
88
|
+
| Hand a card to a teammate | `team` action `assign` | The owner is recorded **and** a handoff is sent to that session's inbox; a pool card comes with `plan`, and a card somebody already holds is refused — `reclaim` it, then assign. The page cannot do this: a browser has no way to message a peer, so a page assignment that recorded an owner and told nobody is gone with the write path |
|
|
89
|
+
| Take a card back from the tool | `board` action `reclaim` | The owner clears and the card returns to `backlog`, without the page and without `team` |
|
|
41
90
|
| Find a card without reading every lane | `board` action `list` with `query` | The cards whose title, holder, path hint, plan, or note text matches — the same rule the page's filter uses, from one body (`MATCHES_QUERY_JS`) |
|
|
42
91
|
| See the work you hold | `board` action `list` with `mine` | The cards this session holds, by the same rule a claim is checked against |
|
|
43
|
-
| Set a finished card aside | `board` action `archive` | A `done` card's file moves into an `archive/` pile beside the board with its notes, plan, owner, and landed commit still readable, and the board stops reading it: the card named by `card`, or every finished card when none is named. It is a decision a person makes, never a threshold in the background, and nothing un-archives |
|
|
44
|
-
| Delete a card | `board` action `drop` | The card and its notes are gone, and its id is spent: a record of the title, the lane, who ended it, and when stays in `ended/`, so a note or a handoff that names that id cannot come to mean a different feature. A `
|
|
45
|
-
| One page per repository | `/board` | Serves a loopback page as
|
|
92
|
+
| Set a finished card aside | `board` action `archive` | A `done` card's file moves into an `archive/` pile beside the board with its notes, plan, contract, owner, and landed commit still readable, and the board stops reading it: the card named by `card`, or every finished card when none is named. A card that already names it keeps that link — it is inert, because a finished dependency stops blocking — though a new link has to name a card on the board. It is a decision a person makes, never a threshold in the background, and nothing un-archives |
|
|
93
|
+
| Delete a card | `board` action `drop` | The card and its notes are gone, and its id is spent: a record of the title, the lane, who ended it, and when stays in `ended/`, so a note or a handoff that names that id cannot come to mean a different feature. A `backlog` or `done` card is anyone's to drop, an archived one included, one in `plan`, `execute`, or `blocked` only its holder's, and one that a card waits on cannot leave at all |
|
|
94
|
+
| One page per repository | `/board` | Serves a loopback page as the board's lead with a **Board** and a **Team** tab, following the store live; a page already served is opened instead |
|
|
46
95
|
| Watch somebody else's page | `/board` on a session that is not serving | The same page, because there is one: every page is a read view, so a second session gets exactly what the first one sees instead of a writable rival. It joins the team under its own name, so a handoff can still reach it |
|
|
47
|
-
| Read a board at its real size | the `Done` fold, an empty lane's rail | `Done` starts folded at six cards or more, its count in the head, and a reader's toggle decides from then on. A lane holding no cards narrows to its head, word, and count, so the lanes carrying work keep the width |
|
|
48
|
-
| Open a card | the card
|
|
96
|
+
| Read a board at its real size | the `Done` fold, an empty lane's rail, the Team tab's own folds | `Done` starts folded at six cards or more, its count in the head, and a reader's toggle decides from then on. A lane holding no cards narrows to its head, word, and count, and a lane of finished cards stops widening at three, so the lanes carrying work keep the width. On the Team tab the queue and the roster share one cap, so both stay on one screen, and the ids past the strip's sixth fold behind its count |
|
|
97
|
+
| Open a card | the card, or `Enter` on a focused one | The whole card is the door — its title stays the focusable button — and it opens the dialog holding the card whole: its lane, its owner, its paths, what it waits for, its plan, and every note. A drag across a card's prose selects it instead of opening it |
|
|
49
98
|
| See the lead's round at a glance | `/board team`, or `/team` with no browser | A queue of cards that need a decision, each with the reason and its way out, then who is on the team and what they hold |
|
|
50
|
-
| Join the team | `/board`, or the first card you claim | Opening the page joins this session to the team — as
|
|
51
|
-
| See who is around | `/board team` | The Teammates list: every session this repository's bus has recorded, plus every owner a card names, each stating `live`, `quiet`, `no record`, or `—` when no bus is loaded |
|
|
99
|
+
| Join the team | `/board`, or the first card you claim | Opening the page joins this session to the team — as the lead's name when it serves the page, under the name it already holds when it opens one somebody else serves — and so does the first card it claims, so nothing has to be armed by hand. Joining never renames Pi's session: the footer keeps the name you gave it, and a `/team join` name is kept rather than replaced by the id-derived one. `/board status` reports the team this session is on, whichever door it used |
|
|
100
|
+
| See who is around | `/board team` | The Teammates list: every session this repository's bus has recorded, plus every owner a card names, each stating `live`, `quiet`, `no record`, or `—` when no bus is loaded. An owner the bus has no record of and that holds nothing is one row stating how many there are and what they finished, unfolded by a click — the list only grows, because a label that ever claimed a card stays an owner — while one that still holds a card keeps its own row |
|
|
52
101
|
|
|
53
102
|
## Quickstart
|
|
54
103
|
|
|
@@ -57,20 +106,30 @@ you: publish two features for the billing work
|
|
|
57
106
|
→ board add "Billing webhook accepts orders" paths ["src/billing"]
|
|
58
107
|
→ board add "Retry branches merge cleanly"
|
|
59
108
|
lead: board edit 2 dependsOn ["1"] priority "high"
|
|
60
|
-
→ #2 waits on #1 (
|
|
61
|
-
peer:
|
|
62
|
-
→ board claim 1
|
|
109
|
+
→ #2 waits on #1 (backlog), and sorts first in Backlog
|
|
110
|
+
peer: pull the first, design it, then write down what it promises
|
|
111
|
+
→ board claim 1
|
|
112
|
+
→ next: design it in plan mode, `plan` action `write`, then `contract` with the path it returned
|
|
113
|
+
→ /plan … plan write … plan exit (the user reads the plan and approves it)
|
|
114
|
+
→ board contract 1 path <the file `plan` action `write` returned> goal … acceptance … scopeOut …
|
|
115
|
+
→ board move 1 execute — refused: the contract is not approved yet
|
|
116
|
+
lead: board approve 1 (/team join lead first; else /board approve 1)
|
|
117
|
+
peer: board move 1 execute → board note 1 …
|
|
118
|
+
lead: board review 1 (or the person: /board review 1)
|
|
119
|
+
peer: board move 1 done
|
|
63
120
|
→ board claim 2 — refused while #1 is unfinished, allowed once it is done
|
|
64
121
|
lead: board list round: true
|
|
65
|
-
→ 1 owner · 2 features · 0 unassigned ·
|
|
122
|
+
→ 1 owner · 2 features · 0 unassigned · 0 unplanned · 0 awaiting approval
|
|
66
123
|
```
|
|
67
124
|
|
|
68
125
|
## Commands
|
|
69
126
|
|
|
70
127
|
| Command | Who | Effect |
|
|
71
128
|
|---|---|---|
|
|
72
|
-
| `/board` | lead | Serves the page as `
|
|
129
|
+
| `/board` | the lead | Serves the page as the board's lead (`board.lead`, `lead` by default), or opens the one another session already serves — and opens it again when it is already up, so a closed tab is one command away. The line it prints names the repository and the address |
|
|
73
130
|
| `/board team` | lead | The same, opened on the Team tab |
|
|
131
|
+
| `/board approve <id>` | the person | Signs the card's contract as `you`, the other actor the gate takes: a session must be the lead (`board.lead`), and the holder may sign nothing at all. Only a card in `plan` with a complete contract can be signed, and the refusal is the tool's own sentence |
|
|
132
|
+
| `/board review <id>` | the person | Records the spot-check of finished work as `you`; only a card in `execute` can be signed |
|
|
74
133
|
| `/board status` | both | One line: the seat, the repository, the port, how many tabs are watching, the store's path, and the team the last `/board` joined — or the page being watched, and whether the note behind it still answers |
|
|
75
134
|
| `/board stop` | lead | Stops the page this session serves; a watched page that has stopped answering is dropped instead, and a watched page that is alive is its owner's to stop |
|
|
76
135
|
| `/team` | both | The round in the terminal: the rollup, the work a dead owner left behind, then who is live, who has gone quiet, and who is gone |
|
|
@@ -87,26 +146,50 @@ one a crash left. `/board status` probes the note it is watching, and names one
|
|
|
87
146
|
answers nobody; `/board stop` then drops it (a token-safe clear, so a page that came back
|
|
88
147
|
keeps its own note) instead of waiting for the next `/board` to trip over it.
|
|
89
148
|
|
|
149
|
+
**Who signs.** The two gates take the lead's signature and nothing else: `approve` and
|
|
150
|
+
`review` accept a session only while its team name is `board.lead`, and never while that
|
|
151
|
+
session holds the card — a lead releases a card before signing it, so the gate is never its
|
|
152
|
+
own. **The page serves as `board.lead`** (`lead` by default), so the session that opens
|
|
153
|
+
`/board` takes that name and can sign — the shipped board has a signer without being
|
|
154
|
+
configured. A repository that wants its own name sets `{"board": {"lead": "gavin"}}`, and the
|
|
155
|
+
page, the roster, and the gate follow it. It is a label check and nothing more, which is the
|
|
156
|
+
half of "somebody read this" a file can hold.
|
|
157
|
+
|
|
158
|
+
**The person's door.** A repository worked by one session would otherwise never get a card
|
|
159
|
+
past `plan` or `execute`, so `/board approve` and `/board review` write the same two signs as
|
|
160
|
+
`you` — a label, with no session id — whatever the lead is called and whether or not the
|
|
161
|
+
session typing it holds the card. The card says who signed, and the two read differently
|
|
162
|
+
because they are different: `you` is the person who read the plan, the lead is a second pair
|
|
163
|
+
of eyes.
|
|
164
|
+
|
|
90
165
|
**Joining.** One thing, done for you: `/board` puts this session on the team — as
|
|
91
|
-
|
|
166
|
+
the lead's name when it serves the page, under its own name when it watches one — and so does
|
|
92
167
|
the first card it claims. A read, a new card, a note, a plan, or an edit enlists nobody: a
|
|
93
168
|
teammate is a session that holds work. The join is also the listening: mail is reported at
|
|
94
169
|
that session's next turn, and no turn is raised, so being on the team never spends a turn.
|
|
95
170
|
A session that has neither opened the board nor claimed a card is not on it.
|
|
96
171
|
|
|
97
|
-
**The two names.** A session's team name is the seat —
|
|
98
|
-
page,
|
|
99
|
-
|
|
100
|
-
the
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
172
|
+
**The two names.** A session's team name is the seat — the board's lead — while it serves the
|
|
173
|
+
page, which is also what makes it the lead unless `board.lead` names someone else; otherwise
|
|
174
|
+
it is the name the session already holds: `/team join billing-core` names
|
|
175
|
+
it, and the cards it claims, the notes it writes, and the join a claim makes all say
|
|
176
|
+
`billing-core` rather than a name derived past it. A session the bus has no record of
|
|
177
|
+
takes a preset teammate name — `tom`, `lily`, `ben` — drawn from its id and walked past the
|
|
178
|
+
names live teammates hold, so `billing-core` before it names itself is `lily` to the team;
|
|
179
|
+
once every preset name is held it is `team-agent-` plus the last six characters of its id
|
|
180
|
+
(`team-agent-9f21ac`). Until it joins, that name is only a draw: a live peer that takes it
|
|
181
|
+
moves the session on to the next free one, and a card written before then keeps the name it
|
|
182
|
+
was written with. The name is never Pi's session name, which means a rename cannot strand a
|
|
183
|
+
card and the bus will not let two live sessions settle on one name, while nothing here ever
|
|
184
|
+
renames your session: `/team join <label>` is the one door that does. Ownership is the id,
|
|
185
|
+
so a name that changes leaves the card where it was. A card records the name as its owner,
|
|
186
|
+
so the chip, the roster, and the bus record are one word — and `/board status` says it
|
|
187
|
+
whichever door put this session on the team, including a claim made with no page open.
|
|
188
|
+
|
|
189
|
+
The page still serves as a seat: `/board status` names the lead, the Team tab marks that
|
|
190
|
+
row `you`, and a card a browser claimed **before** the page stopped writing still names
|
|
191
|
+
`board:<lead>` as its owner — `reclaim` is how a lead takes that back from the terminal. The toolbar does not print the seat: a board's subject is its repository, so the
|
|
192
|
+
name there is the project's, and the seat is read off the roster where it belongs.
|
|
110
193
|
|
|
111
194
|
## Tool
|
|
112
195
|
|
|
@@ -115,20 +198,25 @@ terminal.
|
|
|
115
198
|
|
|
116
199
|
| Action | Parameters | Result |
|
|
117
200
|
|---|---|---|
|
|
118
|
-
| `list` | `
|
|
119
|
-
| `add` | `title`, `paths?`, `priority?`, `note?` | the new `
|
|
120
|
-
| `claim` | `card` | the card,
|
|
121
|
-
| `release` | `card` | the card, back in `
|
|
122
|
-
| `reclaim` | `card` | the card, back in `
|
|
123
|
-
| `move` | `card`, `
|
|
201
|
+
| `list` | `lane?`, `mine?`, `card?`, `query?`, `round?`, `archived?` | the cards in lane order, one card, or what a query or `mine` matches — a query reads a card's title, holder, path hints, plan, and the text of its notes; a listing that names no lane leaves out the `done` cards and says how many, since a finished card is history rather than work; `round: true` adds the rollup, which always covers the whole board; `archived: true` reads the archive instead of the board, with `query` and `mine` narrowing it and `lane`/`round` refused |
|
|
202
|
+
| `add` | `title`, `paths?`, `priority?`, `note?` | the new `backlog` card |
|
|
203
|
+
| `claim` | `card` | the card, pulled into `plan` and owned by this session — refused, naming the count, when `plan` is at its limit (the limit counts the whole board, not one session's cards); the result ends with the next step, which is plan mode. `team`'s `claim` writes the same operation for a lead taking work from its round |
|
|
204
|
+
| `release` | `card` | the card, back in `backlog` with no owner; the contract and its approval stay |
|
|
205
|
+
| `reclaim` | `card` | the card, back in `backlog`, whoever held it |
|
|
206
|
+
| `move` | `card`, `lane`, `blockedBy?`, `blocker?` | the card in its new lane, through the one transition the board allows from where it is; `blocker` (`contract`, `dependency`, or `decision`) is required for `blocked` and is recorded with `blockedBy` (one line, ≤200 characters) as the card's one `blocked` record — sent with any other lane it is refused, and it cannot change while the card is parked, because a reason is what the card was parked on; a re-plan from `execute` or a resume from `blocked` is an entry into `plan` and so is held to that lane's limit. A move to `execute` or `done` re-checks the plan the card recorded — a deleted file, or one outside the store, is refused with `contract` as the way out — and a move to `done` records the commit `HEAD` resolved to, when the directory is a repository |
|
|
124
207
|
| `note` | `card`, `text` | the card with the note appended |
|
|
125
|
-
| `
|
|
208
|
+
| `contract` | `card`, `path`, `goal?`, `acceptance?`, `scopeOut?` | the card with its plan file and its contract recorded, and any approval dropped: a changed line is an unapproved one. `path` is required and must resolve inside this repository's plan store (a bare name is read there, `~/…` is accepted) and exist; a path outside it is refused with the store named |
|
|
209
|
+
| `approve` | `card`, `text?` | the card with its contract approved, by the lead and not by its holder — the gate in front of `execute` (see **Who signs**); `text` is a note recorded in the same write. The person signs the same gate with `/board approve <id>` |
|
|
210
|
+
| `review` | `card`, `text?` | the card with a spot-check recorded, by the lead and not by its holder — the gate in front of `done`; `text` is a note recorded in the same write. The person signs with `/board review <id>` |
|
|
126
211
|
| `edit` | `card`, `title?`, `paths?`, `priority?`, `dependsOn?` | the card with what it says corrected; an omitted field is left alone, and `dependsOn: []` removes every link |
|
|
127
212
|
| `archive` | `card?` | the ids set aside — one finished card, or every finished card when no card is named; a card still in flight is refused |
|
|
128
213
|
| `drop` | `card` | the id that was dropped, whose number is now spent |
|
|
129
214
|
|
|
130
215
|
`card` takes `4` or `#4`. A refused call is an error result that still carries the
|
|
131
|
-
card, so the next attempt starts from the truth.
|
|
216
|
+
card, so the next attempt starts from the truth. A `list` that names a card refuses the
|
|
217
|
+
fields that only narrow a listing — `lane`, `query`, `mine`, and `round` — because its
|
|
218
|
+
answer is that one card; `archived` is kept, because it decides which pile the card is
|
|
219
|
+
read from.
|
|
132
220
|
|
|
133
221
|
The parameters are one flat object, so a field is published to every action at once.
|
|
134
222
|
An action reads only the fields in its row above, and a call carrying one that its
|
|
@@ -178,41 +266,53 @@ answering its browser would otherwise be declared dead and taken over.
|
|
|
178
266
|
|
|
179
267
|
One page, two tabs, one lifecycle. The kit shell keeps the toolbar and the footer
|
|
180
268
|
pinned and makes the main pane the only scroll box; the page adds no second one. The
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
reason: a store key is a hash, and no reader can say it out loud.
|
|
269
|
+
toolbar leads with the repository the page is a board of, as a quiet chip named the way the
|
|
270
|
+
directory holding it is, with the root's whole path on its hover — the name fits the line,
|
|
271
|
+
and the path is what tells two clones of one repository apart. That name arrives in the
|
|
272
|
+
document, so a slow frame does not leave the toolbar unnamed. The tab carries the same name
|
|
273
|
+
for the same reason: a store key is a hash, and no reader can say it out loud. The footer
|
|
274
|
+
is the board's own account: the store's path, the count of cards, and the size they occupy
|
|
275
|
+
— plus how many card files this build could not read, named with the store's own reason on
|
|
276
|
+
the hover (`0099.json (not a card record)`), so a board that is not readable never reads as
|
|
277
|
+
a board that is empty.
|
|
186
278
|
|
|
187
279
|
| Band | What it holds |
|
|
188
280
|
|---|---|
|
|
189
|
-
| Toolbar | the mark, the
|
|
190
|
-
| Lanes | trays, not a bare list: one column per lane, sized by the work it holds — a lane with two cards is wider than one with a single card, and
|
|
191
|
-
| Cards | the id and a two-line title (the page's loudest text and its
|
|
192
|
-
| Team | two panels and a strip. **Needs a decision** is one row per card — a card that is both idle and unplanned arrives once, keeping its strongest reason — laid out as two lines: the id, title and owner chip on the first, the reason on the second — a blocked card's recorded `blockedBy`, else its last note, and `offered 2h ago — nobody took it` for a card whose offer went unanswered — with the whole sentence as its tooltip, and its kind as a left edge (`--danger` blocked, `--warn`
|
|
193
|
-
| Card dialog | the record of one card, in two named sections. The head carries the id, the title, and the lane as a dot and a word. **The card** is a ladder — one fact per row, labels sized to the longest of them so the values line up: owner, priority when it is not `normal`, updated, in lane, last edited, landed, waits on, waited on by, plan, paths. `in lane` is the lane the card is in and how long it has held it, read from the card's transitions rather than from `updatedAt`, which a note or an edit moves; a card whose history does not say omits the row. The plan and each path are one line that elides, with the whole value on the hover title; what it waits for reads `#2 (
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
`blocked` and destructive choices, `--success` for `done`. The Team tab's decision
|
|
281
|
+
| Toolbar | the mark, the repository's name as a quiet chip with the root's whole path on hover, a live badge (`connecting…`, `live` or `live · 3m`, `reconnecting…`), then one group: the Board\|Team switch, done-of-total progress (from three cards up, and only above 1180px), a filter, the keys button, and the theme toggle |
|
|
282
|
+
| Lanes | trays, not a bare list: one column per lane, sized by the work it holds — a lane with two cards is wider than one with a single card, and one holding no cards, or folded, narrows to a rail of dot, word, and count — except `done`, which draws as if it held three cards however much history it has, because the lanes still being worked are the ones a reader is working in. One row of five above 1180px, two per row at 1180px or narrower, one lane plus the lane switch at 860px or narrower — where it opens on the first lane that holds a card, and the switch is a group of pressed buttons rather than a second set of tabs, because there is no tabpanel behind it. A filter that matches nothing replaces the lanes with one sentence — `No card matches “x”` — and a `Clear filter` button, so five trays of `no match` never read as a board with no work. Each head sticks to the pane's own top edge while the pane scrolls, carries its count as a pill — `2/3` on `Plan` and `1/5` on `Execute`, the limit and the count in one fact — names itself from the same numbers for a screen reader (`Plan, 1 of 3`, `Backlog, 2 cards`, `Backlog, 1 of 2` under a filter), and folds its cards away: `Done` starts folded at six cards or more, since finished work is history, and a reader's toggle decides from then on |
|
|
283
|
+
| Cards | the id and a two-line title (the page's loudest text, and the focusable half of its door: the whole card opens it), an owner chip and an age whose hover says which fact it is (`updated 2d ago`), a quiet outlined chip for a priority that is not `normal`, `2 notes` when it carries any history — the count, with the last note's age beside it only when that age reads differently from the card's own updated stamp, since a note is itself a write and the same moment printed twice is two bare numbers nobody can tell apart, and a card with ten notes and one with none used to draw the same face — path hints in the mono face, and one reason line in one voice, from `lib/board/reason.ts`: what a card waits for, named by title as well as id (`waits on #31 (execute) The page's roster key…`), a blocked card's recorded reason or else its last note (or `blocked, with nothing recorded`), `no contract yet — missing …`, `contract ready — needs approval`, `contract approved by <who>`, `executing — no spot-check yet`, `spot-checked by <who>`, `offered 2h ago — nobody took it`, and `✓ done by <owner>` with the commit it landed as — up to two lines at the Team tab's size, with the whole sentence as its tooltip, because the reason is the reason to act on the card. A card holding others up says so below it — `holds up #79 (backlog) The page's roster key…` — the one fact its own row cannot carry. A title and a path hint elide **inside** the card: a chip is one line by design, so a path or a label longer than the card is clipped rather than crossing into the lane beside it, and its whole value is the hover title. The hints past the third are counted by a `+N` chip whose hover lists them one per line, and which are in the list again as text a screen reader reads. What a card cannot show — the reason, the holds-up line, the hints behind a `+N` chip — is named by the title button's `aria-describedby`, so the keyboard that reaches the card by `j`/`k` reads the same sentences a pointer gets on hover. A card draws **no button**: claiming, moving, editing, and annotating are the tools' work, and a face that offered them would be offering a refusal. A hovered card lifts a pixel; a finished one recedes, title and owner chip muted; the card the reader is on wears an accent edge and the ones they have opened keep a quiet one, so walking a lane and coming back does not look like arriving for the first time |
|
|
284
|
+
| Team | two panels and a strip. **Needs a decision** is one row per card — a card that is both idle and unplanned arrives once, keeping its strongest reason — laid out as two lines: the id, title and owner chip on the first, the reason on the second — a blocked card's recorded `blockedBy`, else its last note, and `offered 2h ago — nobody took it` for a card whose offer went unanswered — with the whole sentence as its tooltip, and its kind as a left edge, in the tone `lib/board/tone.ts` gives that kind (`--danger` blocked, and `gone` — an owner whose process is not running, ranked above a card merely sitting still; `--warn` approval, and idle; `--dim` unplanned or waiting, the same `--dim` dashed for nobody's). The tone is read from one table, so the row's rank and its colour cannot disagree, and a danger row is the only one whose reason sentence is coloured with its edge. The row is a reason to pick the card up in the terminal, and it draws no button: claiming and reclaiming are the tools' work. An unassigned card that waits on unfinished work is a row of its own kind, ranked just under a blocker. Above 1180px the queue stops at one screen's height and scrolls inside its panel, so the roster and the Done strip stay on the same page as its top. **Teammates** is one row per person — a state dot, the label, a `you` chip, a `not listening` chip on a live peer whose inbox watch is off, a mail badge, then `live · 3 held · 1 done · oldest 21m` (a quiet session reads `quiet for 2h`, because a word without an age is no reason to reclaim) — sorted you, live, quiet, no record, and an owner whose label nothing answers to keeps its row. Done is one quiet strip whose ids open their cards. Both panels honour the toolbar's filter, and their headings read `shown/total` the way a lane head does. Below 1180px the queue comes first and the roster follows |
|
|
285
|
+
| Card dialog | the record of one card, in two named sections. The head carries the id, the title, and the lane as a dot and a word. **The card** is a ladder — one fact per row, labels sized to the longest of them so the values line up: owner, priority when it is not `normal`, updated, in lane, last edited, landed, waits on, waited on by, plan, goal, acceptance, scope out, approved, reviewed, paths. `in lane` is the lane the card is in and how long it has held it, read from the card's transitions rather than from `updatedAt`, which a note or an edit moves; a card whose history does not say omits the row. The plan and each path are one line that elides, with the whole value on the hover title; what it waits for reads `#2 (execute) <title>`, the lane that makes the id a decision and the card it names, and the contract's three lines are rungs of their own so a reader compares what was promised with what was done. Empty rows take their labels with them. **Notes · N** is the cargo at body size on a 68ch measure, each sentence with its attribution under it and a rule between notes, and a note's prose is inline Markdown read by the kit's one renderer — `code`, **bold**, *italic*, and a link to an `http`, `https`, or `mailto` target become atoms, and any other target stays the text it was, so a note can never become markup — and `M earlier notes dropped` when the store capped them. The body is the only scroll, so the head and `Close` stay put. Nothing in it writes, every field is a set as `textContent` — a note's prose is the page's one exception, handed to the kit's inline renderer, which appends text and four atoms and never parses markup — and the card underneath every title is the way in. `j`/`k` walk to the next or previous card without closing it, and an `sr-only` live region announces `card 2 of 3: <title>` — a title that changes inside an open modal is not announced on its own |
|
|
286
|
+
Colour is a role, one per lane, read from `LANE_RULES`: `--muted` for `backlog`,
|
|
287
|
+
`--warn` for `plan`, `--accent` for `execute`, `--danger` for `blocked` and destructive
|
|
288
|
+
choices, `--success` for `done`. The Team tab's decision
|
|
198
289
|
rows sit on the kit's `--raised`, the one step above `--surface` that lets a block read
|
|
199
290
|
as a plane instead of an outline; every lane, `Done` included, keeps that `--surface`
|
|
200
291
|
tray, so a finished card still sits on something and history recedes through its muted
|
|
201
|
-
title and chip instead of through a hole in the row. A card takes its colour
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
292
|
+
title and chip instead of through a hole in the row. A card takes its lane's colour on the head's
|
|
293
|
+
dot and the dialog's state, and its reason line takes the tone of the reason's kind — `--danger`
|
|
294
|
+
for a block, `--warn` for a contract, an approval, or a spot-check somebody still owes, `--muted`
|
|
295
|
+
for what is merely true of the card. The rule behind those words: **warn is a person who must act
|
|
296
|
+
next, danger is a refusal or a loss, and anything else is quiet**. One table in
|
|
297
|
+
`lib/board/tone.ts` answers for all of it, and the terminal reads the same table through
|
|
298
|
+
`extensions/board/tone.ts`, so a lane is one colour on both surfaces. The page states a lane with
|
|
299
|
+
`data-lane` and a decision row's tone with `data-tone`, so a theme change repaints either without
|
|
300
|
+
a rule of its own.
|
|
205
301
|
|
|
206
302
|
The board as a posting card — the same rules, drawn to be read at half width:
|
|
207
303
|
|
|
208
|
-
<img src="../../assets/cards/board.png" alt="One board, every session: the page's
|
|
304
|
+
<img src="../../assets/cards/board.png" alt="One board, every session: the page's repository name and live badge; five lane trays — Backlog, Plan, Execute, Blocked, Done — each with a dot, a count pill and a fold; one card carrying an id, a title, an owner chip, a priority chip, its age, a path chip and one reason line, with no action on it; and the lead's round with two owner lines — each dot in the colour of the state it reports, success for live and warn for quiet — and the counts unassigned, unplanned, awaiting approval, blocked, idle and done" width="100%">
|
|
209
305
|
|
|
210
306
|
*`board`: the lanes, one card's atoms, and the lead's round — three readings of one board, drawn by `screenshot:cards board`.*
|
|
211
307
|
|
|
212
|
-
<img src="../../assets/cards/board-team.png" alt="The three team verbs as three bands: assign sends a handoff to a named peer and writes the card
|
|
308
|
+
<img src="../../assets/cards/board-team.png" alt="The three team verbs as three bands: assign sends a handoff to a named peer and writes the card into plan, owned by that peer, before the peer reads the mail; offer broadcasts an event to every other live session without recording an owner, so the first claim wins; reclaim returns the card to Backlog with its notes kept and no handoff sent" width="100%">
|
|
213
309
|
|
|
214
310
|
*`board-team`: `assign`, `offer`, and `reclaim` — the two that reach the bus, and the one that writes the card alone (`screenshot:cards team`).*
|
|
215
311
|
|
|
312
|
+
<img src="../../assets/cards/board-parallel.png" alt="Two lanes of three rows each. In the plan lane, #4 billing-core, #5 api-core, and #6 web-core each carry a contract node reading contract, 契约, and plan file, side by side, with goal, acceptance, and scope out named under them; every row crosses one vertical approve line, a signature only the lead may give and never while holding the card. In the execute lane the same three run at once under their owners, each row crossing a vertical review spot-check line into done. Each lane's floor states its count — 3/3 contracts, 3/5 owners executing — and the round beneath counts 0 unplanned, 1 awaiting approval, and 2 done; the foot says execute opens only on the lead's approval and that assign and offer hand a card over without starting a turn" width="100%">
|
|
313
|
+
|
|
314
|
+
*`board-parallel`: plan three at once, then execute three at once — `approve` is the lead's signature on the contract, `review` the lead's look before `done`, and both are lines every row crosses (`screenshot:cards parallel`).*
|
|
315
|
+
|
|
216
316
|
## Keys
|
|
217
317
|
|
|
218
318
|
| Key | What it does |
|
|
@@ -224,9 +324,13 @@ The board as a posting card — the same rules, drawn to be read at half width:
|
|
|
224
324
|
| `Esc` | Close a dialog, or clear the filter |
|
|
225
325
|
| `?` | The keyboard sheet |
|
|
226
326
|
|
|
227
|
-
A dialog traps `Tab
|
|
228
|
-
whatever opened it — the kit's `dialogScript`, shared with the review and browse
|
|
229
|
-
pages. A field keeps its keys: `/` inside the filter box types a slash.
|
|
327
|
+
A dialog traps `Tab` in both directions, closes on `Esc` or a backdrop click, and returns
|
|
328
|
+
focus to whatever opened it — the kit's `dialogScript`, shared with the review and browse
|
|
329
|
+
pages. A field keeps its keys: `/` inside the filter box types a slash. A card dialog keeps
|
|
330
|
+
the page's keys too: `/`, `t`, and `?` do nothing while it is open, and `j`, `k`, `Enter`,
|
|
331
|
+
and `Esc` are the ones still read at it — the sheet says so in a line under its table. A
|
|
332
|
+
kit dialog whose focusable elements are two or more cycles between them; one with a single
|
|
333
|
+
button stays on that button.
|
|
230
334
|
|
|
231
335
|
## Security
|
|
232
336
|
|
|
@@ -248,26 +352,35 @@ pages. A field keeps its keys: `/` inside the filter box types a slash.
|
|
|
248
352
|
the cards accumulate — most of this repository's own board was finished cards — and
|
|
249
353
|
reading them on every call is a cost that grows with the history rather than with the
|
|
250
354
|
work. A `list` that names no lane therefore leaves `done` out and prints the count, which
|
|
251
|
-
is what keeps the omission from reading as a smaller board; `
|
|
355
|
+
is what keeps the omission from reading as a smaller board; `lane: "done"` reads them,
|
|
252
356
|
and `round` counts every card, because a rollup that hid half the board would not be one.
|
|
253
357
|
The page bounds its own View the same way, folding a long `done` lane. `archive` is how a
|
|
254
358
|
board stops paying for its history at all: the card leaves the board and its read.
|
|
255
|
-
- **A
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
359
|
+
- **A card planned before the plan was required must record one.** `contract` now demands
|
|
360
|
+
the file `plan` action `write` saved, and a move to `execute` or `done` re-checks it, so a card
|
|
361
|
+
whose contract names a path outside this repository's plan store — or no plan at all —
|
|
362
|
+
cannot move until its owner calls `contract` again with a real one. That is the fix, not a
|
|
363
|
+
migration: the refusal names `contract`, the plan tool is where the file comes from, and
|
|
364
|
+
nothing is rewritten behind a read. A card already in `execute` is held to the same check
|
|
365
|
+
on its way to `done`.
|
|
366
|
+
- **A finished card stays finished.** `done` is terminal — claim, assign, release,
|
|
367
|
+
reclaim, move, contract, approve, review, and offer all refuse it — so a card a
|
|
368
|
+
dependency was recorded against cannot un-finish and reopen the order after the fact,
|
|
369
|
+
and `archive` does not change that: it moves a `done` card's file, and never its lane.
|
|
370
|
+
The one exception is `edit`: a title, path hints, priority, or `dependsOn` can still be
|
|
371
|
+
corrected by whoever holds it, or by anyone while nobody does, because a title is what a
|
|
372
|
+
reader reads and a finished feature whose title is wrong would stay wrong for as long as
|
|
373
|
+
the board keeps history. A card finished by mistake is dropped and added again: the new
|
|
374
|
+
card takes a new number, and the dropped one's title and lane stay in `ended/` under the
|
|
375
|
+
id nothing will use again. Its notes do not come with it.
|
|
266
376
|
- **A card file this build cannot read is reported, not skipped.** A file written by a
|
|
267
377
|
newer build, a half-written one, or a record moved under another name is named by
|
|
268
378
|
`list` with the reason and counted in the page's footer beside the store's size, so a
|
|
269
|
-
board that is not readable never reads as a board that is empty.
|
|
270
|
-
|
|
379
|
+
board that is not readable never reads as a board that is empty. The record is version
|
|
380
|
+
`2`; a version `1` file — the shape before the lane and the block were named — is read by
|
|
381
|
+
mapping it and is rewritten as `2` on its next write, so nothing is lost and nothing is
|
|
382
|
+
migrated behind a read. A version above this build's is named by number rather than
|
|
383
|
+
guessed at. `done` stays terminal until a `reopen` is designed.
|
|
271
384
|
- **Links are recorded by the tool.** A link names an id, so the page shows what a
|
|
272
385
|
card waits for but does not edit it; `board edit` records it while the card is being
|
|
273
386
|
planned, and `board drop` refuses to strand one.
|
|
@@ -288,7 +401,7 @@ pages. A field keeps its keys: `/` inside the filter box types a slash.
|
|
|
288
401
|
with no tool-side twin is assigning **without** messaging the peer: `team`'s `assign`
|
|
289
402
|
sends a handoff, and nothing else records an owner silently.
|
|
290
403
|
- **A seat can outlive its writes.** A card claimed from the page before this change still
|
|
291
|
-
names `board
|
|
404
|
+
names `board:<lead>` as its owner, and `reclaim` is how a lead takes it back.
|
|
292
405
|
- **A lane history is short, and it starts when the field did.** A card keeps the last
|
|
293
406
|
eight lane transitions, dropping the oldest and counting what it dropped, so the record
|
|
294
407
|
is bounded at write time rather than by a janitor — nothing prunes a board. A card
|
|
@@ -309,8 +422,9 @@ it always meant: when the card was last touched, which is not when it last chang
|
|
|
309
422
|
prefix held for the session. Either way `/board` starts it again, on the session's port,
|
|
310
423
|
and prints the address it reopened.
|
|
311
424
|
- **The prefix is `/board` on the session's one server.** The port belongs to `serve`, so
|
|
312
|
-
`/board`, `/browse`, and a browser plan review answer on one origin;
|
|
313
|
-
`bytefly.json` pins
|
|
425
|
+
`/board`, `/browse`, and a browser plan review answer on one origin; that port is the
|
|
426
|
+
project's own number, and `serve.port` in `bytefly.json` pins another, so a bookmarked
|
|
427
|
+
URL survives a restart. A live note still
|
|
314
428
|
wins: one repository has one page, so this session attaches to the page that note names
|
|
315
429
|
rather than serving a second one — and a session that attaches binds nothing at all.
|
|
316
430
|
- **The note is believed only while it answers.** A page that crashed leaves its note
|
|
@@ -336,11 +450,24 @@ it always meant: when the card was last touched, which is not when it last chang
|
|
|
336
450
|
and never renames the session, so the footer stays the user's. A failed join is a clause
|
|
337
451
|
on the `/board` line, not a failed page. The seat is the name the page is served and
|
|
338
452
|
reported under, and `/board status` says which session is on the team through it.
|
|
339
|
-
- **`plan`**: `board
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
453
|
+
- **`plan`**: `board contract` records the file `plan` action `write` saved; the plan extension owns
|
|
454
|
+
it, and `lib/plans/paths.ts` is the one statement of where it lives — the board reads the
|
|
455
|
+
same module, so the two cannot disagree about what a plan path is. The board stays
|
|
456
|
+
available while planning, and only the reads in `lib/board/actions.ts` work: the same tool
|
|
457
|
+
writes, so its read-only hint cannot say that, and plan mode reads the action in the call
|
|
458
|
+
instead (`lib/policy.ts`'s `isBoardRead`). Every other verb is refused with the board's own
|
|
459
|
+
sentence, which names the order — claim before you plan, record the plan once it is
|
|
460
|
+
approved — so a model that tries to claim mid-plan is told what to do instead. The contract
|
|
461
|
+
is recorded **after** the plan is approved, when the writing verbs work again.
|
|
462
|
+
- **`bytefly`**: the board's own section of the shared document, in one place:
|
|
463
|
+
`{"board": {"wip": {"plan": 3, "execute": 5}, "lead": "gavin"}}`. Each number is clamped to
|
|
464
|
+
`1..20` and the page's heads show the numbers the store read once; `lead` is the team name the
|
|
465
|
+
two signatures must carry **and** the name the page serves as, so setting it renames the lead
|
|
466
|
+
everywhere at once. The project file overrides the global one. Every key falls
|
|
467
|
+
back on its own, so setting one leaves the others alone. `/bytefly settings` carries all
|
|
468
|
+
three as rows — `Plan limit`, `Execute limit`, and `Lead` — and a change lands in the file at
|
|
469
|
+
once and in the board at the next session, because the section is read when the store
|
|
470
|
+
opens.
|
|
344
471
|
- **`todo`**: the approved plan's steps are seeded into `todo`. Steps never become
|
|
345
472
|
cards. A `list` call is a read for the lag reminder too, so reading the board never
|
|
346
473
|
forces a todo update, while `add` or `claim` counts as work like any other write.
|
|
@@ -352,9 +479,18 @@ it always meant: when the card was last touched, which is not when it last chang
|
|
|
352
479
|
|
|
353
480
|
## Design notes
|
|
354
481
|
|
|
355
|
-
- **One JSON file per card**, in `<
|
|
482
|
+
- **One JSON file per card**, in `<agent-dir>/board/<repo-key>/cards/`, one per ended
|
|
356
483
|
card in `ended/`, and one per archived card in `archive/`. The listing is the index, so
|
|
357
484
|
a reader needs no cache and no daemon.
|
|
485
|
+
- **The plan is proved, not asserted.** `contract` requires the path `plan` action `write` returned,
|
|
486
|
+
`lib/board/artifact.ts` resolves it and checks that it is a markdown file inside this
|
|
487
|
+
repository's plan store, and a move to `execute` or `done` re-checks that the file is still
|
|
488
|
+
there. The board still never reads a plan — a `stat` is the whole check — but a card can no
|
|
489
|
+
longer be executed against a path to nothing, which is what "the plan it is executed
|
|
490
|
+
against" had been claiming while `execute` asked no question at all. The proof is a fact
|
|
491
|
+
the tool hands the state machine, so `rules.ts` stays pure and a surface added later cannot
|
|
492
|
+
pass the gate by forgetting it; the stored path is the resolved one, so a card reads the
|
|
493
|
+
same absolute file from every session.
|
|
358
494
|
- **An id is never reused.** The next id is the highest in `cards/`, `archive/` *or*
|
|
359
495
|
`ended/` plus one, so a number cannot be handed to a second feature — which is the only
|
|
360
496
|
thing that lets a note, a handoff, or a link name a card and still mean it. A record
|
|
@@ -367,7 +503,7 @@ it always meant: when the card was last touched, which is not when it last chang
|
|
|
367
503
|
drop still leaves a record because the card is gone, while an archived card is not gone
|
|
368
504
|
at all — and why a read by id looks in both piles: a card is where it lies. The
|
|
369
505
|
decision is explicit because nothing in this board prunes by itself, and because an
|
|
370
|
-
automatic sweep would make `
|
|
506
|
+
automatic sweep would make `lane: "done"` return something different past a threshold
|
|
371
507
|
nobody could see. The board, the rollup, and the page read `cards/` alone — the only
|
|
372
508
|
look at the archive is the directory listing a lane-less `list` counts, so the omission
|
|
373
509
|
cannot pass for a deleted history — and an archive therefore lowers the board's size
|
|
@@ -379,7 +515,11 @@ it always meant: when the card was last touched, which is not when it last chang
|
|
|
379
515
|
- **The page is a dumb renderer** over one snapshot built on the server, pushed
|
|
380
516
|
only when a card's revision, or a teammate's state, mail, or listening flag, moves: a
|
|
381
517
|
heartbeat every ten seconds is not news, and a frame nobody needs costs the page a
|
|
382
|
-
parse.
|
|
518
|
+
parse. The snapshot carries the lanes rather than a count beside them — each with its
|
|
519
|
+
key, label, empty word, colour role, limit (where it has one), and count — plus `total`,
|
|
520
|
+
the rollup, the store's size and path, the files it could not read, and the repository's
|
|
521
|
+
name and root. There is no `counts` object to disagree with the trays, and no lane table
|
|
522
|
+
in the browser.
|
|
383
523
|
- **A query reads what a card says, not just what it is called.** A card's reasoning lives
|
|
384
524
|
in its notes and in the plan it is executing, so leaving both out of the search made the
|
|
385
525
|
two fields that hold the argument the two a reader could not find. The order is the
|
|
@@ -404,20 +544,25 @@ it always meant: when the card was last touched, which is not when it last chang
|
|
|
404
544
|
the prefix and could not see the whole name, so a rule with nothing behind it (or a
|
|
405
545
|
name with no rule) survived until a capture showed it.
|
|
406
546
|
- **The page is given what it prints.** A card arrives with the priority it acts on and
|
|
407
|
-
the unfinished dependencies that block it (`waits`, as `#2 (
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
547
|
+
the unfinished dependencies that block it (`waits`, as `#2 (execute) Patch the gateway`),
|
|
548
|
+
and a lane arrives
|
|
549
|
+
with its words, colour, limit, and count, so the client holds no copy of a rule
|
|
550
|
+
`lib/board` owns — the copies it did hold had already drifted from the tool's. What cannot
|
|
551
|
+
cross as data crosses as source, because either it needs the reader's clock or it must be
|
|
552
|
+
the same sentence on both surfaces: `plural` (one wording for `1 card`), `matchesQuery`
|
|
553
|
+
(a filter that finds the card the tool would), `reasonFor` (one reason line, whose
|
|
554
|
+
`offered … ago` has to tick rather than freeze at snapshot time), `formatAge`, the tab
|
|
555
|
+
title, and the placeholder lane count. Each is a `*_JS` string spliced into the bundle by
|
|
556
|
+
`view/client.ts`, and `extensions/board/view/pages.test.ts` holds the splices in place.
|
|
412
557
|
- **An offer is a record, not a reservation.** `team offer` broadcasts a card and then
|
|
413
558
|
writes `offeredAt` — the moment, and nothing else — because an event leaves nothing
|
|
414
559
|
behind and a card three sessions ignored read exactly like one nobody had looked at.
|
|
415
560
|
Every answer clears it: a claim, an assignment, and a release each delete the field, so
|
|
416
|
-
its presence means the offer is still open. The page reads it in two places
|
|
417
|
-
|
|
418
|
-
nobody took it`
|
|
419
|
-
|
|
420
|
-
|
|
561
|
+
its presence means the offer is still open. The page reads it in two places — the card
|
|
562
|
+
face and the Team tab's row — and both print the one sentence `lib/board/reason.ts`
|
|
563
|
+
builds, `offered 1m ago — nobody took it`; `list <card>` says `offered: 1m ago, and
|
|
564
|
+
nobody took it`, because the transcript has no reason slot to share. The age itself is
|
|
565
|
+
`lib/format.ts`'s one rule, handed to the page's bundle as source.
|
|
421
566
|
- **`waits on` answers two questions, on purpose.** The page names the *unfinished*
|
|
422
567
|
dependencies in one line — "why can I not start" — and `board`'s result names every
|
|
423
568
|
dependency with its lane, including finished ones — "what is this card tied to". Both
|
|
@@ -425,15 +570,18 @@ it always meant: when the card was last touched, which is not when it last chang
|
|
|
425
570
|
a drift.
|
|
426
571
|
- **A link is stored once, and read both ways.** `dependsOn` lives on the waiting card
|
|
427
572
|
alone, so nothing on disk says what a card is holding up; `openDependentsOf` answers
|
|
428
|
-
it over the board, `list <card>` prints `waited on by N cards: #2 (
|
|
573
|
+
it over the board, `list <card>` prints `waited on by N cards: #2 (backlog)`, the page's
|
|
574
|
+
card face says `holds up #2 (backlog) <title>`, and the
|
|
429
575
|
dialog has the rung the card's own row cannot. Both skip a finished card, unlike the
|
|
430
576
|
drop refusal, which keeps every edge: "what is held up" is about work, and "would this
|
|
431
577
|
drop strand a link" is about the file.
|
|
432
578
|
- **One page is found, not configured.** A note beside the cards carries the port, the
|
|
433
579
|
seat, and the token; it is created exclusively, so two sessions starting at
|
|
434
580
|
once cannot both serve, and the loser attaches to the winner. The alternative —
|
|
435
|
-
a setting that
|
|
436
|
-
not
|
|
581
|
+
a setting that named the page's address — was rejected: the role moves, the seat's name
|
|
582
|
+
does not, and a second session has to find the page rather than pick one. (The lead is a
|
|
583
|
+
different question, and it *is* a setting: see **Who signs**.) The port in the note is the
|
|
584
|
+
session's, never a number this page chose.
|
|
437
585
|
- **The page is a view because a browser is not an identity.** Every page write was made
|
|
438
586
|
as the seat, so a page `assign` had to write down that nobody had been told, and the
|
|
439
587
|
client's copy of the card rules drifted from `lib/board/rules.ts` — it offered a drop
|
|
@@ -444,79 +592,145 @@ it always meant: when the card was last touched, which is not when it last chang
|
|
|
444
592
|
reverse edge is computed over the board, because a copy of the same edge on the card
|
|
445
593
|
being waited for is a copy that drifts; the cycle check and the drop rule are what
|
|
446
594
|
keep the graph whole instead.
|
|
447
|
-
- **A priority is a field, not a lane.**
|
|
448
|
-
a
|
|
449
|
-
|
|
595
|
+
- **A priority is a field, not a lane.** Urgency is not a stage of the work, so it stays
|
|
596
|
+
a field: inside a lane, priority then the id the card was given is the order — never the
|
|
597
|
+
moment it was last touched, so a note or a claim cannot move a card out from under a reader
|
|
598
|
+
working down the lane — and a card says its priority only when it is not the ordinary one.
|
|
599
|
+
The lanes are the flow's stages, which is why a fifth one had to be argued for in the note
|
|
600
|
+
below rather than added as "urgent".
|
|
450
601
|
- **`edit` leaves an omitted field alone.** It is the one action whose job is "make it
|
|
451
602
|
say this", so an absent field must mean "not this field" — a caller that sends only a
|
|
452
603
|
title must not clear the path hints and links it never mentioned.
|
|
453
|
-
- **A lane change is recorded where every transition ends.** `
|
|
604
|
+
- **A lane change is recorded where every transition ends.** `steps.ts` decides each
|
|
454
605
|
transition, but `write()` in `rules.ts` is the one place all of them pass through, so
|
|
455
|
-
the log is appended there rather than from
|
|
456
|
-
remember it. A write that leaves the lane alone — a note, a
|
|
457
|
-
the holder — appends nothing, because the question the log answers is "how long has
|
|
458
|
-
|
|
459
|
-
|
|
606
|
+
the log is appended there rather than from thirteen operations that would each have to
|
|
607
|
+
remember it. A write that leaves the lane alone — a note, a contract, an edit, a claim by
|
|
608
|
+
the holder — appends nothing, because the question the log answers is "how long has this
|
|
609
|
+
been blocked", and `updatedAt` cannot answer it: filing a note against a blocked card
|
|
610
|
+
moves that stamp without moving the card.
|
|
611
|
+
- **Five lanes, and the gates are why.** This board used to say the four lanes were its
|
|
612
|
+
whole vocabulary and a fifth would double the counts and the heads. The flow changed
|
|
613
|
+
that: a card now moves `backlog → plan → execute → done`, and `blocked` is the pool a
|
|
614
|
+
decision parks in. Two of those lanes exist because an agent is a poor judge of itself —
|
|
615
|
+
`plan` holds a contract until the lead, who is not its holder, approves it, and `done` is
|
|
616
|
+
refused until the lead has spot-checked the work — and a WIP limit on `plan` and
|
|
617
|
+
`execute`, counted over the whole board because a repository has one, is what keeps a
|
|
618
|
+
review queue from turning into a shelf. The cost is real (one more tray, one more head)
|
|
619
|
+
and is paid once, in `lib/board/lanes.ts`: the label, the empty word, the colour, the
|
|
620
|
+
ownership rule, and the limit are that table's, and the page receives them in its
|
|
621
|
+
snapshot instead of keeping a copy.
|
|
622
|
+
- **The gates are a label check, and say so.** `approve` and `review` take the lead's name —
|
|
623
|
+
`board.lead`, the page's seat by default — and refuse the card's own holder even when that holder is
|
|
624
|
+
the lead, which is the half of "somebody read this" a file can hold; the board cannot tell a
|
|
625
|
+
person from a session, and does not pretend it can. The name is a team name rather than a
|
|
626
|
+
session, because that is what a pure rule can be handed and what a card file can record: a
|
|
627
|
+
session signs by joining under it, and `board.lead` moving is the board saying who is
|
|
628
|
+
accountable now. The person gets a door of their own — `/board approve` and `/board review`
|
|
629
|
+
write as `you`, a label with no session id — because one session alone would otherwise hold
|
|
630
|
+
a card it could never sign, and because a repository should not need a second process to
|
|
631
|
+
finish its work. The two read differently on the card, which is the honest way to say it:
|
|
632
|
+
`you` is the person who read the plan, the lead is a second pair of eyes. Either sign takes an
|
|
633
|
+
optional `text` note, written with the sign: the signer is the session `note` refuses on a
|
|
634
|
+
held card, so without it a caveat has nowhere to go on the card. The contract is
|
|
635
|
+
three one-line fields written by `contract`, and a changed line or a different plan file
|
|
636
|
+
drops the sign, because the sign named both the wording and the artifact — so a re-worded
|
|
637
|
+
card is signed again, by the lead that signed the last one. A card too small to state an
|
|
638
|
+
acceptance does not belong on the board — it belongs in `todo` — which is the honest answer
|
|
639
|
+
to "this is ceremony for a typo fix".
|
|
640
|
+
- **`status` became `lane` because a card now has two states.** The field, the parameter, and
|
|
641
|
+
the page's attribute were renamed in one step with no behaviour change: once a card can be
|
|
642
|
+
approved and reviewed, "status" answers more than one question, and the docs had always
|
|
643
|
+
said *lane*. `readCard` maps an older file's `status` and `plan` onto `lane` and
|
|
644
|
+
`planPath`, so a published board keeps reading until each card is written again. The log
|
|
645
|
+
needed the same map twice over: a file from before the rename spells a lane `status`, and
|
|
646
|
+
a file from before the *lanes* says `todo` or `doing` — the four-lane board's pool and its
|
|
647
|
+
work in hand — which read as `backlog` and `execute`. A word that is neither a lane nor one
|
|
648
|
+
of those two refuses the record, because a lane guessed from it would put the card
|
|
649
|
+
somewhere its own history disagrees with.
|
|
460
650
|
- **Rejected:** steps as cards (they flood the board), a shared database (a file per
|
|
461
651
|
card needs no dependency and no lock), an index file (every write maintains it,
|
|
462
652
|
every read can find it stale), drag-and-drop (a lane change is a named `move`), a
|
|
463
|
-
|
|
464
|
-
|
|
653
|
+
reverse link field (two copies of one edge), a separate review lane (a review is a fact
|
|
654
|
+
about a card, not a place), deriving the contract from the plan file (the board never
|
|
655
|
+
reads a plan), and an automatic archive past some number of finished cards (a threshold
|
|
465
656
|
nobody stated would decide what a read returns, and nothing else here prunes).
|
|
466
657
|
- **A block says why, and a finished card says with what.** `blocked` exists to ask a
|
|
467
|
-
person for a decision, so the move into it records
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
658
|
+
person for a decision, so the move into it records one record — `blocked: { kind, note }`
|
|
659
|
+
— rather than two sibling keys: the kind (`contract`, `dependency`, or `decision`, and
|
|
660
|
+
required by the move) and the sentence a person reads, which is optional. The kind is the
|
|
661
|
+
errand, not the route: where a blocked card may go is decided by what it has, so `blocked`
|
|
662
|
+
offers `plan` whenever that lane has room and `execute` only with the same contract and
|
|
663
|
+
approval the other door asks for. A park frees a slot rather than reserving one, which is
|
|
664
|
+
why a resume can be refused: the lane may have refilled while the card waited, and the
|
|
665
|
+
refusal says so — `wait for a slot, or release it back to the pool` — rather than naming
|
|
666
|
+
the `finish` and `block` exits a parked card does not have. A card parked while it was still being planned comes back to `plan`; one parked
|
|
667
|
+
mid-flight carries both and resumes. A block record is a fact about the `blocked` lane and
|
|
668
|
+
nothing else, so the board's one write deletes it the moment the card is somewhere else — a
|
|
669
|
+
release, a reclaim, and a claim cannot carry a sentence into a lane it was not written for
|
|
670
|
+
— **but the lane log keeps it**: the transition that parked the card carries the same
|
|
671
|
+
`block`, so "why did this sit for two days" outlives the resume. A spot-check is cleared
|
|
672
|
+
only when the card leaves `execute` for `blocked` or `plan` — a park is somebody deciding
|
|
673
|
+
the work is not finished, and re-planning changes what was agreed — so a card that was
|
|
674
|
+
blocked and resumed needs another look before `done`, while a card that lands keeps the
|
|
675
|
+
sign that let it through.
|
|
471
676
|
`landed` is the commit `HEAD` resolved to at the move to `done`, recorded because
|
|
472
|
-
`done` is terminal and nothing later can say which change closed the card.
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
677
|
+
`done` is terminal and nothing later can say which change closed the card. Of the three
|
|
678
|
+
the move writes, only the blocker is required — a card cannot be parked without saying
|
|
679
|
+
what kind of block it is, because a block is what a person is asked to answer. The
|
|
680
|
+
sentence is optional (the page says `blocked, with nothing recorded`), and a card
|
|
681
|
+
finished before the field, or outside a repository, has no commit to record — finishing
|
|
682
|
+
never depends on a git read.
|
|
476
683
|
|
|
477
684
|
## Files
|
|
478
685
|
|
|
479
686
|
| File | Responsibility |
|
|
480
687
|
|---|---|
|
|
481
|
-
| `index.ts` | The factory: gate on `PI_DISABLED_EXTENSIONS`, build the runtime and the page, register both, close on shutdown |
|
|
688
|
+
| `index.ts` | The factory: gate on the opt-in switch (`PI_ENABLED_EXTENSIONS`) and `PI_DISABLED_EXTENSIONS`, build the runtime and the lazy page, register both, close on shutdown |
|
|
482
689
|
| `schema.ts` | The tool's parameters |
|
|
483
690
|
| `types.ts` | The shapes the tool registers and reports — the actions and the fields each acts on, the card an action produced, the cards a list read, the rollup when it was asked for, and the card a refusal carries |
|
|
484
691
|
| `tools.ts` | Action dispatch and the refusal shape |
|
|
485
692
|
| `input.ts` | A card body as the tool reads it: the `add` fields normalized in one place |
|
|
486
693
|
| `format.ts` | The model-facing text |
|
|
487
|
-
| `rows.ts` | The transcript row grammar |
|
|
488
|
-
| `result.ts` | The transcript block |
|
|
694
|
+
| `rows.ts` | The transcript row grammar: one card as a line, its lane and its facts painted in the tones `lib/board/tone.ts` gives them, and the accent on the holder's own rows in a `mine` listing |
|
|
695
|
+
| `result.ts` | The transcript block: the collapsed line, and the rows a `list` expands to |
|
|
696
|
+
| `tone.ts` | The terminal's word for a board tone: `TONE_ROLE`, because the page wears a tone as a CSS property and the terminal's roles are its own five words |
|
|
697
|
+
| `lazy-page.ts` | The page behind `lib/lazy`: the command paths load `page.ts` once, and `closeAll` closes only a page that was already opened, so a session that never touches the board imports none of the server |
|
|
489
698
|
| `page.ts` | The page's lifecycle: the `/board` prefix it holds on the session's server, the three closes (`/board stop`, the idle grace, and shutdown — each taking the note with it), and the probe behind `/board status` |
|
|
490
699
|
| `snapshot.ts` | The page's snapshot: the cards, the bus's records, the store's size and path, and the repository's own name, handed to `buildView` |
|
|
491
700
|
| `commands.ts` | `/board`, its three words and their refusals, and the one line `/board status` prints |
|
|
492
|
-
| `seat.ts` | The seat the page is served as: one name, and an id derived from it. The page cannot write, so it is a label now — but the id is still what a card claimed from an older page names |
|
|
493
701
|
| `join.ts` | The board's side of the team: when it asks (the page, or the first claimed card), the name it asks for, and how the outcome reads on a `/board` line |
|
|
494
702
|
| `transport/router.ts` | The five reads — the document, the client script, the theme script, the snapshot, and the frame stream — with the token gate and the guards in front of them |
|
|
495
703
|
| `transport/server.ts` | The loopback listener, the frame push — one read of the board per frame, and a failed read reported rather than thrown — and the close |
|
|
496
|
-
| `view/page.ts` | The document: an inert shell the client fills, with no form on it |
|
|
704
|
+
| `view/page.ts` | The document: an inert shell the client fills, named after the repository it is a board of so the toolbar is not blank before the first frame, with no form on it |
|
|
497
705
|
| `view/styles.ts` | The page's `board-*` layout rules |
|
|
498
|
-
| `view/render.ts` | The client's snapshot → DOM renderer: toolbar, lanes,
|
|
706
|
+
| `view/render.ts` | The client's snapshot → DOM renderer: the toolbar, the lanes, and the frame that keeps the dialog current |
|
|
707
|
+
| `view/card.ts` | The client's card face: one card's title and facts, the path hints behind `+N`, the note count, what it holds up, and the read/current marks |
|
|
499
708
|
| `view/names.ts` | The page's class vocabulary: one table the document and the bundle both read, so what the page wears and what its stylesheet draws can be compared |
|
|
500
709
|
| `view/team.ts` | The client's round: the two tables and the Done line |
|
|
501
|
-
| `view/
|
|
710
|
+
| `view/empty.ts` | The client's two empty states: no cards at all, and a filter that matches none — one sentence, and the way back |
|
|
711
|
+
| `view/dialog.ts` | The client's card dialog: fill one card's record (a note through the kit's inline Markdown renderer), keep an open one current across frames, record it as read, close it, and forget it when the kit closes it |
|
|
502
712
|
| `view/keys.ts` | The key table, and the bindings that make it true — including `j`/`k` walking the open dialog through the drawn cards |
|
|
503
713
|
| `view/live.ts` | The client's tabs, filter, and frame stream |
|
|
504
714
|
| `view/client.ts` | The one script served at `/app.js` |
|
|
505
715
|
|
|
506
|
-
The
|
|
716
|
+
The lanes, the contract, the limits, the reason rule, the steps, the store, the rollup, and
|
|
717
|
+
the snapshot, and the rule that a plan path is a plan file are `lib/board/` — see
|
|
507
718
|
[`lib/README.md`](../../lib/README.md).
|
|
508
719
|
|
|
509
720
|
## Testing
|
|
510
721
|
|
|
511
722
|
`test/suite-manifest.ts` declares the suites:
|
|
512
|
-
`lib/board/{card,links,page-pointer,round,round-text,rules,session,store,view}`
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
723
|
+
`lib/board/{artifact,card,contract,lanes,limits,links,page-pointer,read,reason,round,round-text,rules,session,skipped,store,view}`
|
|
724
|
+
for the plan a card is executed against, the record, the reader that maps a v1 file onto it, the contract and its boundary, the
|
|
725
|
+
lane table and its transition matrix, the WIP limits and their config, the dependency graph,
|
|
726
|
+
the note a page leaves, the one reason rule, the rollup and its text, the sentence for a
|
|
727
|
+
refused file, the card's transitions, the store a call opens and the identity it writes as,
|
|
728
|
+
the files that store keeps, and the snapshot, and
|
|
516
729
|
`extensions/board/{input,seat,tools,page,commands,extension,transport/router,view/pages,view/behaviour}`
|
|
517
730
|
for the card body, the seat, every action, the page's lifecycle over a real socket (a page
|
|
518
731
|
served, attached to, and taken over, a board that stops being readable, and a write refused
|
|
519
732
|
on the wire), the command, the
|
|
520
733
|
factory, the routes, the document and client bundle, and the
|
|
521
|
-
page driven in a browser — a card opened from its title
|
|
522
|
-
|
|
734
|
+
page driven in a browser — a card opened from its title or anywhere on it, the filter and
|
|
735
|
+
the no-match state, the fold, the lane switch, a live frame, the dialog's ladder, and the
|
|
736
|
+
keyboard a dialog keeps.
|