@gavin-hu/my-pi-agent 0.20.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 -30
- package/README.zh-CN.md +40 -25
- 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 -23
- package/docs/extensions/board.md +397 -168
- package/docs/extensions/bytefly.md +234 -197
- package/docs/extensions/check.md +15 -17
- package/docs/extensions/context.md +35 -15
- package/docs/extensions/file-browse.md +57 -63
- 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 +197 -140
- package/docs/extensions/question.md +16 -18
- package/docs/extensions/rewind.md +17 -19
- package/docs/extensions/serve.md +145 -0
- package/docs/extensions/session-bus.md +38 -16
- package/docs/extensions/subagent.md +23 -25
- package/docs/extensions/team.md +83 -44
- 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 +79 -25
- package/extensions/board/format.ts +43 -24
- package/extensions/board/index.ts +17 -4
- package/extensions/board/input.ts +4 -9
- package/extensions/board/join.ts +5 -20
- package/extensions/board/lazy-page.ts +40 -0
- package/extensions/board/page.ts +65 -93
- package/extensions/board/result.ts +30 -39
- package/extensions/board/rows.ts +62 -25
- package/extensions/board/schema.ts +49 -11
- package/extensions/board/snapshot.ts +8 -6
- package/extensions/board/tone.ts +22 -0
- package/extensions/board/tools.ts +154 -55
- package/extensions/board/transport/router.ts +3 -5
- package/extensions/board/transport/server.ts +59 -28
- package/extensions/board/types.ts +36 -42
- package/extensions/board/view/card.ts +229 -0
- package/extensions/board/view/client.ts +35 -16
- 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 +90 -28
- package/extensions/board/view/names.ts +13 -4
- package/extensions/board/view/page.ts +38 -13
- package/extensions/board/view/render.ts +219 -277
- package/extensions/board/view/styles.ts +130 -32
- 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 +7 -8
- package/extensions/bytefly/footer/register.ts +33 -4
- package/extensions/bytefly/footer/render/footer.ts +18 -4
- package/extensions/bytefly/footer/render/key.ts +46 -0
- package/extensions/bytefly/footer/render/layout.ts +13 -3
- package/extensions/bytefly/footer/render/lines.ts +10 -7
- package/extensions/bytefly/footer/render/segments.ts +41 -38
- package/extensions/bytefly/footer/render/status.ts +0 -11
- package/extensions/bytefly/footer/types.ts +7 -0
- package/extensions/bytefly/header/component.ts +4 -20
- 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/render.ts +10 -1
- 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/config/config.ts +4 -19
- package/extensions/file-browse/index.ts +89 -79
- package/extensions/file-browse/live/live.ts +1 -1
- package/extensions/file-browse/transport/router.ts +12 -4
- package/extensions/file-browse/transport/server.ts +61 -28
- package/extensions/file-browse/view/chrome.ts +4 -4
- package/extensions/file-browse/view/client.ts +3 -1
- package/extensions/file-browse/view/html.ts +73 -40
- package/extensions/file-browse/view/live-client.ts +2 -2
- package/extensions/file-browse/view/markdown.ts +13 -14
- package/extensions/file-browse/view/palette.ts +2 -2
- package/extensions/file-browse/view/raw-html.ts +9 -9
- package/extensions/file-browse/view/tree.ts +1 -1
- package/extensions/file-browse/view/urls.ts +20 -6
- package/extensions/file-read/render.ts +4 -4
- package/extensions/goal/plan-route.ts +2 -2
- package/extensions/goal/tui.ts +12 -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 +21 -9
- package/extensions/job/types.ts +47 -0
- package/extensions/job/{tui.ts → view/jobs-screen.ts} +19 -99
- 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/feedback.ts +62 -16
- package/extensions/plan/format.ts +8 -2
- package/extensions/plan/git-bash.ts +115 -16
- package/extensions/plan/index.ts +58 -51
- package/extensions/plan/plan-index.ts +4 -2
- package/extensions/plan/plan-text.ts +114 -0
- 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 +61 -41
- 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-blocks.ts +76 -5
- package/extensions/plan/web-chrome.ts +4 -0
- package/extensions/plan/web-client.ts +102 -2
- package/extensions/plan/web-collab.ts +4 -4
- package/extensions/plan/web-comments.ts +39 -8
- package/extensions/plan/web-composer.ts +14 -3
- package/extensions/plan/web-keys.ts +149 -25
- package/extensions/plan/web-lazy.ts +53 -0
- package/extensions/plan/web-markdown.ts +10 -68
- package/extensions/plan/web-outline.ts +22 -2
- package/extensions/plan/web-page.ts +56 -86
- package/extensions/plan/web-payload.ts +188 -0
- package/extensions/plan/web-plan.ts +23 -13
- package/extensions/plan/web-router.ts +18 -9
- package/extensions/plan/web-settle.ts +14 -7
- package/extensions/plan/web-share-session.ts +24 -55
- package/extensions/plan/web-share.ts +19 -37
- package/extensions/plan/web-write.ts +112 -0
- package/extensions/plan/web.ts +67 -131
- 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 +39 -0
- package/extensions/serve/index.ts +85 -0
- package/extensions/serve/registry.ts +260 -0
- package/extensions/session-bus/commands.ts +11 -6
- package/extensions/session-bus/join-request.ts +17 -9
- 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/result.ts +1 -27
- package/extensions/session-bus/runtime.ts +40 -12
- package/extensions/session-bus/store.ts +101 -19
- package/extensions/session-bus/tools.ts +5 -4
- 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 -5
- 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/{tui.ts → view/todo-screen.ts} +18 -90
- 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 +28 -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 +30 -5
- 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 +199 -51
- package/lib/board/skipped.ts +22 -0
- package/lib/board/steps.ts +487 -0
- package/lib/board/store.ts +119 -36
- package/lib/board/tone.ts +92 -0
- package/lib/board/view.ts +81 -63
- package/lib/bus-request.ts +83 -0
- 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 +17 -11
- package/lib/http/mount.ts +142 -0
- package/lib/http/port.ts +30 -0
- package/lib/http/serve.ts +170 -46
- package/lib/http/server.ts +3 -3
- package/lib/lazy.ts +45 -0
- package/lib/plan-entry.ts +46 -21
- package/lib/plans/paths.ts +82 -0
- package/lib/policy.ts +9 -6
- package/lib/report.ts +3 -2
- package/lib/requirements.ts +10 -10
- package/lib/session-label.ts +129 -7
- package/lib/session-roster.ts +43 -1
- package/lib/session-tool.ts +39 -22
- package/lib/stray-fields.ts +7 -2
- package/lib/tool-names.ts +5 -10
- package/lib/tui/README.md +29 -11
- package/lib/tui/chrome.ts +16 -15
- package/lib/tui/cursor.ts +18 -3
- package/lib/tui/glyphs.ts +11 -11
- package/lib/tui/render-cache.ts +29 -0
- package/lib/tui/tokens.ts +33 -2
- package/lib/tui/tool.ts +33 -4
- package/lib/web/README.md +21 -0
- package/lib/web/client-runtime.ts +27 -1
- 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 +20 -2
- package/lib/web/runtime.ts +9 -10
- package/package.json +8 -2
- package/skills/init/SKILL.md +5 -5
- package/skills/next/SKILL.md +3 -5
- package/extensions/board/config.ts +0 -36
- package/extensions/board/seat.ts +0 -28
- 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
|
@@ -1,19 +1,177 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The card dialog: one card, whole, as a read view.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Filling it, opening it, and forgetting which card is open is the whole of it. `render.ts`
|
|
5
|
+
* paints the board and hands each frame to {@link syncDialog}, which keeps an open record
|
|
6
|
+
* current and closes it when its card has left the board. The page cannot change a card, so
|
|
7
|
+
* this module has no request, no confirmation, no banner and no footer actions — the dialogs
|
|
8
|
+
* it used to fill are gone from the document, and the only way a card changes is a tool.
|
|
9
|
+
*
|
|
10
|
+
* A note is the one field that is not a `textContent` write: its prose is Markdown, read by
|
|
11
|
+
* the kit's one inline renderer (`lib/web/markdown.ts`), which appends text nodes and the
|
|
12
|
+
* four inline atoms and never parses markup. That renderer is spliced into this fragment
|
|
13
|
+
* rather than the bundle's top level — it is only ever read here, and inside this closure it
|
|
14
|
+
* resolves `el` to `board.el` instead of making the page's builder a `window` global.
|
|
15
|
+
* Everything else here is set as text, like the rest of the page.
|
|
8
16
|
*
|
|
9
17
|
* The one thing it must not do is leave a stale panel behind: a dialog the kit closed —
|
|
10
18
|
* `Esc`, a backdrop click, its own close button — forgets `openId`, so the next frame
|
|
11
19
|
* stops filling something nobody can see.
|
|
12
20
|
*/
|
|
13
21
|
|
|
14
|
-
|
|
22
|
+
/**
|
|
23
|
+
* The dialog fragment, with the inline Markdown renderer a note is read through spliced in.
|
|
24
|
+
*
|
|
25
|
+
* The source crosses the boundary as text, because `lib/web/markdown.ts` is JavaScript and a
|
|
26
|
+
* page's bundle has no import graph; `__INLINE_MARKDOWN__` is a rule a served script must
|
|
27
|
+
* never carry, and `pages.test` fails an unfilled one.
|
|
28
|
+
*/
|
|
29
|
+
export function dialogJs(rules: { inlineMarkdown: string }): string {
|
|
30
|
+
return dialogTemplate.replace("__INLINE_MARKDOWN__", rules.inlineMarkdown);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const dialogTemplate = String.raw`(function () {
|
|
15
34
|
"use strict";
|
|
16
35
|
var board = window.bfBoard || (window.bfBoard = {});
|
|
36
|
+
var el = board.el;
|
|
37
|
+
var chip = board.chip;
|
|
38
|
+
var ago = board.ago;
|
|
39
|
+
|
|
40
|
+
__INLINE_MARKDOWN__
|
|
41
|
+
|
|
42
|
+
/** Fill the dialog's contents from a card, without opening or closing it. */
|
|
43
|
+
function fillDialog(card, view) {
|
|
44
|
+
document.getElementById("card-dialog-title").textContent = card.title;
|
|
45
|
+
document.getElementById("card-dialog-id").textContent = "#" + card.id;
|
|
46
|
+
// The lane is a status of its own, not a word inside a sentence: the same dot and
|
|
47
|
+
// label a lane head wears, so the dialog and the board speak the same vocabulary.
|
|
48
|
+
var state = document.getElementById("card-dialog-state");
|
|
49
|
+
var lane = board.laneOf(view, card.lane);
|
|
50
|
+
state.setAttribute("data-lane", card.lane);
|
|
51
|
+
state.textContent = lane === undefined ? card.lane : lane.label;
|
|
52
|
+
|
|
53
|
+
// One fact per row of the ladder, so a reader can pick out a value instead of
|
|
54
|
+
// hunting for it in a sentence. A row with nothing to say takes its label with it:
|
|
55
|
+
// an empty cell under a heading reads as a missing value, not as an absence.
|
|
56
|
+
var pair = function (name, shown) {
|
|
57
|
+
var dt = document.getElementById("card-dialog-" + name + "-label");
|
|
58
|
+
var dd = document.getElementById("card-dialog-" + name);
|
|
59
|
+
if (dt) dt.hidden = !shown;
|
|
60
|
+
if (dd) dd.hidden = !shown;
|
|
61
|
+
return dd;
|
|
62
|
+
};
|
|
63
|
+
var setRow = function (name, shown, value, full) {
|
|
64
|
+
var dd = pair(name, shown);
|
|
65
|
+
dd.textContent = shown ? value : "";
|
|
66
|
+
if (full === undefined) dd.removeAttribute("title");
|
|
67
|
+
else dd.setAttribute("title", full);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
var owner = document.getElementById("card-dialog-owner");
|
|
71
|
+
owner.textContent = "";
|
|
72
|
+
owner.appendChild(card.owner ? chip(card.owner.label, "accent") : document.createTextNode("unassigned"));
|
|
73
|
+
pair("owner", true);
|
|
74
|
+
|
|
75
|
+
var priority = pair("priority", card.priority !== "normal");
|
|
76
|
+
priority.textContent = "";
|
|
77
|
+
if (card.priority !== "normal") priority.appendChild(chip(card.priority, "outline"));
|
|
78
|
+
|
|
79
|
+
setRow("updated", true, ago(card.updatedAt));
|
|
80
|
+
// How long the card has been in the lane it is in now, which updatedAt cannot say:
|
|
81
|
+
// a note filed against a blocked card moves that stamp without moving the card. Absent
|
|
82
|
+
// for a card written before the log existed, so the row takes its label with it.
|
|
83
|
+
setRow("lane", card.since !== undefined, card.lane + " \u00b7 " + ago(card.since));
|
|
84
|
+
setRow("by", true, card.by);
|
|
85
|
+
setRow("landed", card.landed !== undefined, card.landed ? card.landed.commit : "", card.landed ? card.landed.commit : undefined);
|
|
86
|
+
|
|
87
|
+
// The dialog and the card face say one thing, from one field: the ids it was linked
|
|
88
|
+
// to are the storage, and a reader is owed the lanes that make them a decision.
|
|
89
|
+
setRow("depends", card.waits !== undefined, card.waits || "", card.waits);
|
|
90
|
+
// The other side of the same link: what leaving this card undone is holding up, which
|
|
91
|
+
// is the one thing a card's own row cannot say.
|
|
92
|
+
setRow("dependents", card.dependents !== undefined, card.dependents || "", card.dependents);
|
|
93
|
+
|
|
94
|
+
// A long plan path elides on one line, and the whole of it is on the hover title and
|
|
95
|
+
// in the text, so it stays copyable: the reason to open a card is often its plan.
|
|
96
|
+
setRow("plan", Boolean(card.planPath), card.planPath || "", card.planPath || undefined);
|
|
97
|
+
|
|
98
|
+
// The contract is three lines and a signature, and each is its own rung: a reader
|
|
99
|
+
// comparing what was promised with what was done reads them one at a time, and the
|
|
100
|
+
// approval's age is the fact that says whether anybody has looked.
|
|
101
|
+
var contract = card.contract;
|
|
102
|
+
setRow("goal", Boolean(contract), contract ? contract.goal : "", contract ? contract.goal : undefined);
|
|
103
|
+
setRow("acceptance", Boolean(contract), contract ? contract.acceptance : "", contract ? contract.acceptance : undefined);
|
|
104
|
+
setRow("scope-out", Boolean(contract), contract ? contract.scopeOut : "", contract ? contract.scopeOut : undefined);
|
|
105
|
+
var approved = contract && contract.approved;
|
|
106
|
+
setRow("approved", Boolean(approved), approved ? approved.by + " \u00b7 " + ago(approved.at) : "");
|
|
107
|
+
setRow("reviewed", card.review !== undefined, card.review ? card.review.by + " \u00b7 " + ago(card.review.at) : "");
|
|
108
|
+
|
|
109
|
+
pair("paths", card.paths.length > 0);
|
|
110
|
+
var pathList = document.getElementById("card-dialog-path-list");
|
|
111
|
+
pathList.textContent = "";
|
|
112
|
+
for (var i = 0; i < card.paths.length; i++) {
|
|
113
|
+
var item = el("li", "");
|
|
114
|
+
var hint = chip(card.paths[i]);
|
|
115
|
+
hint.className += " " + N.dialogPath;
|
|
116
|
+
hint.title = card.paths[i];
|
|
117
|
+
item.appendChild(hint);
|
|
118
|
+
pathList.appendChild(item);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
var notes = document.getElementById("card-dialog-notes");
|
|
122
|
+
notes.textContent = "";
|
|
123
|
+
for (var j = 0; j < card.notes.length; j++) {
|
|
124
|
+
var note = card.notes[j];
|
|
125
|
+
// The prose is the record and reads at body size; who wrote it and when is its
|
|
126
|
+
// footnote, so it follows the sentence instead of standing over it.
|
|
127
|
+
var row = el("li", N.dialogNote);
|
|
128
|
+
// A note is Markdown, read by the kit's one inline renderer: a backtick, a bold word,
|
|
129
|
+
// or an ordinary URL reads as what it says, a target the policy refuses stays text,
|
|
130
|
+
// and nothing a person typed can become markup.
|
|
131
|
+
var text = el("p", N.dialogNoteText);
|
|
132
|
+
renderInline(text, note.text);
|
|
133
|
+
row.appendChild(text);
|
|
134
|
+
var foot = el("p", N.dialogNoteFoot);
|
|
135
|
+
foot.appendChild(el("span", N.dialogNoteBy, note.by));
|
|
136
|
+
foot.appendChild(el("span", N.dialogNoteAt, ago(note.at)));
|
|
137
|
+
row.appendChild(foot);
|
|
138
|
+
notes.appendChild(row);
|
|
139
|
+
}
|
|
140
|
+
var count = document.getElementById("card-dialog-note-count");
|
|
141
|
+
if (count) count.textContent = card.notes.length === 0 ? " \u00b7 none" : " \u00b7 " + card.notes.length;
|
|
142
|
+
var earlier = document.getElementById("card-dialog-earlier");
|
|
143
|
+
earlier.hidden = card.notesDropped === 0;
|
|
144
|
+
earlier.textContent = card.notesDropped === 0 ? "" : card.notesDropped + " earlier notes dropped";
|
|
145
|
+
|
|
146
|
+
// A modal whose title changes is not announced, so walking with j/k says where it
|
|
147
|
+
// landed: the position is counted over the cards as they are drawn, not as stored, so
|
|
148
|
+
// a filter or a folded lane cannot make the number disagree with the screen.
|
|
149
|
+
var where = document.getElementById("card-dialog-where");
|
|
150
|
+
if (where) {
|
|
151
|
+
var drawn = board.drawnCards();
|
|
152
|
+
var at = 0;
|
|
153
|
+
for (var k = 0; k < drawn.length; k++) {
|
|
154
|
+
if (drawn[k].getAttribute("data-card") === String(card.id)) at = k + 1;
|
|
155
|
+
}
|
|
156
|
+
// Opened from the Team tab there is no drawn board to count along, so the line
|
|
157
|
+
// names the card instead of claiming a position it cannot know.
|
|
158
|
+
where.textContent = drawn.length === 0 ? card.title : "card " + at + " of " + drawn.length + ": " + card.title;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Keep an open record current: fill it from the frame, or close it when its card left.
|
|
164
|
+
*
|
|
165
|
+
* The frame owns this rather than the open handler: a peer's note moves the card's
|
|
166
|
+
* revision, and a panel left showing the record it was opened with is a page that lies
|
|
167
|
+
* for as long as the tab stays open.
|
|
168
|
+
*/
|
|
169
|
+
board.syncDialog = function (view) {
|
|
170
|
+
if (board.openId === undefined) return;
|
|
171
|
+
var card = board.findCard(view, board.openId);
|
|
172
|
+
if (card) fillDialog(card, view);
|
|
173
|
+
else board.closeCard();
|
|
174
|
+
};
|
|
17
175
|
|
|
18
176
|
/** Fill and open the card dialog. */
|
|
19
177
|
board.openCard = function (id) {
|
|
@@ -22,7 +180,12 @@ export const BOARD_DIALOG_JS = String.raw`(function () {
|
|
|
22
180
|
var card = board.findCard(view, id);
|
|
23
181
|
if (!card || !window.bfCard) return;
|
|
24
182
|
board.openId = id;
|
|
25
|
-
|
|
183
|
+
// A card a reader opened is one they have read, and it is where they are: the board
|
|
184
|
+
// paints both marks from these two fields, so closing the record leaves the place.
|
|
185
|
+
board.visited[id] = true;
|
|
186
|
+
board.current = id;
|
|
187
|
+
fillDialog(card, view);
|
|
188
|
+
board.markCards();
|
|
26
189
|
window.bfCard.open();
|
|
27
190
|
};
|
|
28
191
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The board's two empty states: no cards at all, and a filter that matches none.
|
|
3
|
+
*
|
|
4
|
+
* Both reuse the one block the document already carries (`#board-no-cards`), because the
|
|
5
|
+
* difference between them is a sentence and one button, not a second layout. They live in
|
|
6
|
+
* their own fragment so the renderer stays the painter of cards and lanes: an empty board is
|
|
7
|
+
* a statement about the whole board, and it is the one part of the page that says a thing
|
|
8
|
+
* instead of drawing one.
|
|
9
|
+
*
|
|
10
|
+
* Same rule as the rest of the client: every value arrives as `textContent`, and the query
|
|
11
|
+
* the reader typed is one of them.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export const BOARD_EMPTY_JS = String.raw`(function () {
|
|
15
|
+
"use strict";
|
|
16
|
+
var board = window.bfBoard || (window.bfBoard = {});
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* What the board says when there is nothing to read.
|
|
20
|
+
*
|
|
21
|
+
* Two different emptinesses. No cards at all is the board's own state, and it teaches the
|
|
22
|
+
* verb that fills it. A filter that matches none of them is the reader's own query: the
|
|
23
|
+
* lanes go, because five trays saying "no match" read as a board with no work, and the way
|
|
24
|
+
* back is one button rather than a key a reader has to remember.
|
|
25
|
+
*/
|
|
26
|
+
board.renderEmpty = function (view, query, shownTotal) {
|
|
27
|
+
var block = document.getElementById("board-no-cards");
|
|
28
|
+
if (!block) return;
|
|
29
|
+
var lanes = document.getElementById("board-lanes");
|
|
30
|
+
var noCards = view.total === 0;
|
|
31
|
+
var noMatch = !noCards && query !== "" && shownTotal === 0;
|
|
32
|
+
block.hidden = !noCards && !noMatch;
|
|
33
|
+
if (lanes) lanes.hidden = noCards || noMatch;
|
|
34
|
+
var title = document.getElementById("board-no-cards-title");
|
|
35
|
+
var sub = document.getElementById("board-no-cards-sub");
|
|
36
|
+
var hint = document.getElementById("board-no-cards-hint");
|
|
37
|
+
var clear = document.getElementById("board-no-match-clear");
|
|
38
|
+
if (title) title.textContent = noMatch ? "No card matches \u201c" + query + "\u201d" : "No cards yet";
|
|
39
|
+
if (sub) {
|
|
40
|
+
sub.textContent = noMatch
|
|
41
|
+
? "The filter reads a title, its owner, a path, a plan, and the notes."
|
|
42
|
+
: "One card per feature: what it delivers, who holds it, and where to start.";
|
|
43
|
+
}
|
|
44
|
+
if (hint) hint.hidden = noMatch;
|
|
45
|
+
if (clear) clear.hidden = !noMatch;
|
|
46
|
+
};
|
|
47
|
+
})();`;
|
|
@@ -65,7 +65,12 @@ export const BOARD_KEYS_JS = String.raw`(function () {
|
|
|
65
65
|
if (walk(delta)) return;
|
|
66
66
|
var drawn = cards();
|
|
67
67
|
if (drawn.length === 0) return;
|
|
68
|
-
|
|
68
|
+
// Focus sits on a card's title button, not on the card, so the step reads the card that
|
|
69
|
+
// holds it: an index of -1 for every card made j from one land on the first card in the
|
|
70
|
+
// lane instead of the next — which is the place a reader lost when they came back.
|
|
71
|
+
var here = document.activeElement;
|
|
72
|
+
var card = here && here.closest ? here.closest(".board-card") : null;
|
|
73
|
+
var at = card === null ? -1 : drawn.indexOf(card);
|
|
69
74
|
var next = at < 0 ? (delta > 0 ? 0 : drawn.length - 1) : (at + delta + drawn.length) % drawn.length;
|
|
70
75
|
var button = drawn[next].querySelector(".board-card__title");
|
|
71
76
|
if (button) button.focus();
|
|
@@ -75,7 +80,11 @@ export const BOARD_KEYS_JS = String.raw`(function () {
|
|
|
75
80
|
doc.addEventListener("keydown", function (event) {
|
|
76
81
|
if (event.metaKey || event.ctrlKey || event.altKey) return;
|
|
77
82
|
var target = event.target;
|
|
78
|
-
|
|
83
|
+
// A dialog is modal for the pointer and for the keyboard alike: "/", "t", and "?"
|
|
84
|
+
// reach the page behind the scrim from nowhere else, so a modal keeps them and the
|
|
85
|
+
// keys that move inside it -- j, k, Enter, Esc -- are the ones still read.
|
|
86
|
+
var modal = (board.dialogOpen && board.dialogOpen()) || (board.keysOpen && board.keysOpen());
|
|
87
|
+
if (event.key === "?" && !typing(target) && !modal) {
|
|
79
88
|
event.preventDefault();
|
|
80
89
|
board.openKeys();
|
|
81
90
|
return;
|
|
@@ -108,6 +117,7 @@ export const BOARD_KEYS_JS = String.raw`(function () {
|
|
|
108
117
|
return;
|
|
109
118
|
}
|
|
110
119
|
if (event.key === "/") {
|
|
120
|
+
if (modal) return;
|
|
111
121
|
event.preventDefault();
|
|
112
122
|
board.focusFilter();
|
|
113
123
|
} else if (event.key === "j") {
|
|
@@ -117,6 +127,7 @@ export const BOARD_KEYS_JS = String.raw`(function () {
|
|
|
117
127
|
event.preventDefault();
|
|
118
128
|
step(-1);
|
|
119
129
|
} else if (event.key === "t") {
|
|
130
|
+
if (modal) return;
|
|
120
131
|
event.preventDefault();
|
|
121
132
|
board.showView(board.viewName() === "team" ? "board" : "team");
|
|
122
133
|
}
|
|
@@ -3,20 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A frame is a whole snapshot, so every message repaints from it rather than patching
|
|
5
5
|
* it — the renderer decides what actually changed. The only client state is which tab
|
|
6
|
-
* is showing, the filter text, which lanes are collapsed,
|
|
7
|
-
*
|
|
6
|
+
* is showing, the filter text, which lanes are collapsed, which card's dialog is open,
|
|
7
|
+
* and where the reader is on the board — the card they are on and the ones they have
|
|
8
|
+
* read; none of it is authoritative, and none of it is sent anywhere.
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
11
|
export const BOARD_LIVE_JS = String.raw`(function () {
|
|
11
12
|
"use strict";
|
|
12
13
|
var board = window.bfBoard || (window.bfBoard = {});
|
|
13
14
|
var token = new URLSearchParams(window.location.search).get("token") || "";
|
|
14
|
-
var LANE_LABEL = { todo: "To do", doing: "Doing", blocked: "Blocked", done: "Done" };
|
|
15
|
-
|
|
16
|
-
/** The lane's word, for the dialog's move buttons. */
|
|
17
|
-
board.laneLabel = function (lane) {
|
|
18
|
-
return LANE_LABEL[lane] || lane;
|
|
19
|
-
};
|
|
20
15
|
|
|
21
16
|
/** The badge's text and its kit status role: connecting, live, reconnecting. */
|
|
22
17
|
function badge(state, at) {
|
|
@@ -34,7 +29,7 @@ export const BOARD_LIVE_JS = String.raw`(function () {
|
|
|
34
29
|
else node.textContent = "connecting…";
|
|
35
30
|
}
|
|
36
31
|
|
|
37
|
-
/** Show one tab: the panel,
|
|
32
|
+
/** Show one tab: the panel, the selected tab, and the address so a link is shareable. */
|
|
38
33
|
function showView(view) {
|
|
39
34
|
var team = view === "team";
|
|
40
35
|
document.documentElement.setAttribute("data-view", team ? "team" : "board");
|
|
@@ -43,14 +38,40 @@ export const BOARD_LIVE_JS = String.raw`(function () {
|
|
|
43
38
|
var tabs = document.querySelectorAll("[data-view][role='tab']");
|
|
44
39
|
for (var i = 0; i < tabs.length; i++) {
|
|
45
40
|
var current = tabs[i].getAttribute("data-view") === (team ? "team" : "board");
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
// A tablist is read through aria-selected, and a roving tabindex is what makes one
|
|
42
|
+
// Tab land on the strip rather than on each tab in turn. Both are the markup's own
|
|
43
|
+
// attributes: a state painted only in a class is a state a screen reader cannot read.
|
|
44
|
+
tabs[i].setAttribute("aria-selected", current ? "true" : "false");
|
|
45
|
+
tabs[i].setAttribute("tabindex", current ? "0" : "-1");
|
|
48
46
|
}
|
|
49
47
|
var url = new URL(window.location.href);
|
|
50
48
|
url.searchParams.set("view", team ? "team" : "board");
|
|
51
49
|
window.history.replaceState(null, "", url.toString());
|
|
52
50
|
}
|
|
53
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Arrow keys move along the tab strip, the way a tablist is expected to behave.
|
|
54
|
+
*
|
|
55
|
+
* Automatic activation: the panels are both in the document already, so showing one is a
|
|
56
|
+
* hidden attribute rather than a request, and a reader who arrows across the strip sees
|
|
57
|
+
* each tab he lands on. Home and End are the ends of it.
|
|
58
|
+
*/
|
|
59
|
+
function bindTabKeys(tabs) {
|
|
60
|
+
for (var i = 0; i < tabs.length; i++) {
|
|
61
|
+
tabs[i].addEventListener("keydown", function (event) {
|
|
62
|
+
var names = ["ArrowLeft", "ArrowRight", "Home", "End"];
|
|
63
|
+
if (names.indexOf(event.key) === -1) return;
|
|
64
|
+
event.preventDefault();
|
|
65
|
+
var at = 0;
|
|
66
|
+
for (var j = 0; j < tabs.length; j++) if (tabs[j] === event.currentTarget) at = j;
|
|
67
|
+
var step = event.key === "ArrowLeft" ? -1 : 1;
|
|
68
|
+
var next = event.key === "Home" ? 0 : event.key === "End" ? tabs.length - 1 : (at + step + tabs.length) % tabs.length;
|
|
69
|
+
showView(tabs[next].getAttribute("data-view"));
|
|
70
|
+
tabs[next].focus();
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
54
75
|
board.viewName = function () {
|
|
55
76
|
return document.documentElement.getAttribute("data-view") === "team" ? "team" : "board";
|
|
56
77
|
};
|
|
@@ -60,27 +81,40 @@ export const BOARD_LIVE_JS = String.raw`(function () {
|
|
|
60
81
|
var narrow = window.matchMedia && window.matchMedia("(max-width: 860px)").matches;
|
|
61
82
|
var lanes = document.querySelectorAll(".board-lane");
|
|
62
83
|
for (var i = 0; i < lanes.length; i++) {
|
|
63
|
-
var selected = lanes[i].getAttribute("data-
|
|
84
|
+
var selected = lanes[i].getAttribute("data-lane") === board.shown;
|
|
64
85
|
lanes[i].hidden = narrow && !selected;
|
|
65
86
|
}
|
|
66
|
-
var items = document.querySelectorAll("#board-picker [data-lane]");
|
|
87
|
+
var items = document.querySelectorAll("#board-picker [data-picker-lane]");
|
|
67
88
|
for (var j = 0; j < items.length; j++) {
|
|
68
|
-
|
|
69
|
-
|
|
89
|
+
// A pressed button, because the picker shows one lane rather than joining a tablist:
|
|
90
|
+
// "true" is the lane a reader is looking at, and the kit paints it from this state.
|
|
91
|
+
items[j].setAttribute("aria-pressed", items[j].getAttribute("data-picker-lane") === board.shown ? "true" : "false");
|
|
70
92
|
}
|
|
71
93
|
}
|
|
72
94
|
|
|
73
95
|
board.applyLane = applyLane;
|
|
74
96
|
|
|
75
97
|
board.showLane = function (lane) {
|
|
76
|
-
board.
|
|
98
|
+
board.shown = lane;
|
|
77
99
|
applyLane();
|
|
78
100
|
};
|
|
79
101
|
|
|
80
|
-
/**
|
|
81
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Fold one list away and leave its control standing.
|
|
104
|
+
*
|
|
105
|
+
* Three things on the page fold: a lane, the roster's tail of owners nothing answers to, and
|
|
106
|
+
* the Done strip. They share one map, and a key says which kind it is: a lane keeps its own
|
|
107
|
+
* name, and the two the Team tab owns are team-roster and team-done, because done is
|
|
108
|
+
* already a lane.
|
|
109
|
+
*
|
|
110
|
+
* The key is also recorded, because the panel a fold lives in is repainted: the element the
|
|
111
|
+
* reader clicked no longer exists when the frame lands, and the renderer focuses
|
|
112
|
+
* data-fold="<key>" to put the keyboard back where it was.
|
|
113
|
+
*/
|
|
114
|
+
board.toggleFold = function (key) {
|
|
82
115
|
board.collapsed = board.collapsed || {};
|
|
83
|
-
board.collapsed[
|
|
116
|
+
board.collapsed[key] = board.collapsed[key] !== true;
|
|
117
|
+
board.foldFocus = key;
|
|
84
118
|
if (board.view) board.render(board.view);
|
|
85
119
|
};
|
|
86
120
|
|
|
@@ -127,14 +161,14 @@ export const BOARD_LIVE_JS = String.raw`(function () {
|
|
|
127
161
|
* The grey cards a page shows while it waits for its first frame.
|
|
128
162
|
*
|
|
129
163
|
* They are marked as placeholders rather than given a lane status: the renderer
|
|
130
|
-
* looks lanes up by data-
|
|
164
|
+
* looks lanes up by data-lane, so a skeleton that carried one would be adopted
|
|
131
165
|
* as the real lane and the cards would never be drawn into it.
|
|
132
166
|
*/
|
|
133
167
|
function skeletons() {
|
|
134
168
|
var lanes = document.getElementById("board-lanes");
|
|
135
169
|
if (!lanes || lanes.childElementCount > 0) return;
|
|
136
170
|
lanes.setAttribute("data-skeleton", "1");
|
|
137
|
-
for (var i = 0; i <
|
|
171
|
+
for (var i = 0; i < board.laneCount; i++) {
|
|
138
172
|
var lane = board.el("section", N.lane);
|
|
139
173
|
for (var j = 0; j < 2; j++) lane.appendChild(board.el("div", N.skeleton));
|
|
140
174
|
lanes.appendChild(lane);
|
|
@@ -143,25 +177,25 @@ export const BOARD_LIVE_JS = String.raw`(function () {
|
|
|
143
177
|
|
|
144
178
|
/**
|
|
145
179
|
* The lane a narrow reader lands on: the first with a card in it, else the first
|
|
146
|
-
* lane. Opening on an empty
|
|
180
|
+
* lane. Opening on an empty Backlog while the only card sits in Blocked is a blank
|
|
147
181
|
* page with a picker nobody has a reason to press.
|
|
148
182
|
*/
|
|
149
183
|
function laneWithWork(view) {
|
|
150
|
-
for (var i = 0; i < view.lanes.length; i++) if (view.lanes[i].cards.length > 0) return view.lanes[i].
|
|
151
|
-
return view.lanes[0] ? view.lanes[0].
|
|
184
|
+
for (var i = 0; i < view.lanes.length; i++) if (view.lanes[i].cards.length > 0) return view.lanes[i].key;
|
|
185
|
+
return view.lanes[0] ? view.lanes[0].key : "backlog";
|
|
152
186
|
}
|
|
153
187
|
|
|
154
188
|
/** One frame: paint and record when it arrived. */
|
|
155
189
|
function paint(view) {
|
|
156
190
|
board.lastFrame = Date.now();
|
|
157
|
-
board.
|
|
191
|
+
board.shown = board.shown || laneWithWork(view);
|
|
158
192
|
badge("live", board.lastFrame);
|
|
159
193
|
board.render(view);
|
|
160
194
|
}
|
|
161
195
|
|
|
162
196
|
/** The live channel: a reconnect is the browser's own retry, not a second request. */
|
|
163
197
|
function stream() {
|
|
164
|
-
var source = new EventSource("/events?token=" + encodeURIComponent(token));
|
|
198
|
+
var source = new EventSource(pageBase() + "/events?token=" + encodeURIComponent(token));
|
|
165
199
|
source.onopen = function () {
|
|
166
200
|
badge("live", board.lastFrame);
|
|
167
201
|
};
|
|
@@ -187,16 +221,36 @@ export const BOARD_LIVE_JS = String.raw`(function () {
|
|
|
187
221
|
board.applyFilter();
|
|
188
222
|
});
|
|
189
223
|
}
|
|
224
|
+
// The no-match state's one control: a view action, like the tab switch, and it puts the
|
|
225
|
+
// reader back in the field they were typing in.
|
|
226
|
+
var clear = document.getElementById("board-no-match-clear");
|
|
227
|
+
if (clear) {
|
|
228
|
+
clear.addEventListener("click", function () {
|
|
229
|
+
board.clearFilter();
|
|
230
|
+
board.focusFilter();
|
|
231
|
+
});
|
|
232
|
+
}
|
|
190
233
|
var tabs = document.querySelectorAll("[data-view][role='tab']");
|
|
191
234
|
for (var i = 0; i < tabs.length; i++) {
|
|
192
235
|
tabs[i].addEventListener("click", function (event) {
|
|
193
236
|
showView(event.currentTarget.getAttribute("data-view"));
|
|
194
237
|
});
|
|
195
238
|
}
|
|
239
|
+
bindTabKeys(tabs);
|
|
240
|
+
// A card's title button takes the keyboard from j/k, a Tab, or a click, and the mark
|
|
241
|
+
// follows focus rather than the key that moved it. A focus outside the cards leaves the
|
|
242
|
+
// last card marked, which is what a reader coming back from the filter expects.
|
|
243
|
+
document.addEventListener("focusin", function (event) {
|
|
244
|
+
var node = event.target;
|
|
245
|
+
var card = node && node.closest ? node.closest(".board-card") : null;
|
|
246
|
+
if (!card) return;
|
|
247
|
+
board.current = card.getAttribute("data-card");
|
|
248
|
+
board.markCards();
|
|
249
|
+
});
|
|
196
250
|
var picker = document.getElementById("board-picker");
|
|
197
251
|
if (picker) {
|
|
198
252
|
picker.addEventListener("click", function (event) {
|
|
199
|
-
var lane = event.target && event.target.getAttribute ? event.target.getAttribute("data-lane") : null;
|
|
253
|
+
var lane = event.target && event.target.getAttribute ? event.target.getAttribute("data-picker-lane") : null;
|
|
200
254
|
if (lane) board.showLane(lane);
|
|
201
255
|
});
|
|
202
256
|
}
|
|
@@ -218,10 +272,18 @@ export const BOARD_LIVE_JS = String.raw`(function () {
|
|
|
218
272
|
}
|
|
219
273
|
|
|
220
274
|
board.start = function () {
|
|
221
|
-
board.
|
|
275
|
+
board.shown = undefined;
|
|
222
276
|
board.openId = undefined;
|
|
223
277
|
board.query = "";
|
|
224
278
|
board.collapsed = {};
|
|
279
|
+
// The fold a reader just moved, and the panels' remembered signatures: both belong to the
|
|
280
|
+
// tab, and a fresh tab is a fresh read of what the store holds.
|
|
281
|
+
board.foldFocus = undefined;
|
|
282
|
+
board.teamPanels = undefined;
|
|
283
|
+
// Where the reader is and has been: the two marks a card face can wear, which live as
|
|
284
|
+
// long as the tab does and never reach the store.
|
|
285
|
+
board.current = undefined;
|
|
286
|
+
board.visited = {};
|
|
225
287
|
showView(new URLSearchParams(window.location.search).get("view") === "team" ? "team" : "board");
|
|
226
288
|
wireChrome();
|
|
227
289
|
skeletons();
|
|
@@ -35,16 +35,23 @@ export const BOARD_CLASS = {
|
|
|
35
35
|
laneList: "board-lane__list",
|
|
36
36
|
laneEmpty: "board-lane__empty",
|
|
37
37
|
|
|
38
|
-
// One card, and the two tones its reason line can take.
|
|
38
|
+
// One card, the facts its meta line carries, and the two tones its reason line can take.
|
|
39
39
|
card: "board-card",
|
|
40
40
|
cardHead: "board-card__head",
|
|
41
41
|
cardId: "board-card__id",
|
|
42
42
|
cardTitle: "board-card__title",
|
|
43
43
|
cardMeta: "board-card__meta",
|
|
44
|
+
cardNotes: "board-card__notes",
|
|
44
45
|
cardPaths: "board-card__paths",
|
|
45
46
|
cardReason: "board-card__reason",
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
// One class per tone, never per reason kind: the same tone is what the terminal paints.
|
|
48
|
+
cardReasonMuted: "board-card__reason--muted",
|
|
49
|
+
cardReasonDim: "board-card__reason--dim",
|
|
50
|
+
cardReasonWarn: "board-card__reason--warn",
|
|
51
|
+
cardReasonAccent: "board-card__reason--accent",
|
|
52
|
+
cardReasonDanger: "board-card__reason--danger",
|
|
53
|
+
cardReasonSuccess: "board-card__reason--success",
|
|
54
|
+
cardHolds: "board-card__holds",
|
|
48
55
|
|
|
49
56
|
// The card dialog: the record of one card, as a ladder of facts and its notes.
|
|
50
57
|
dialogPanel: "board-dialog__panel",
|
|
@@ -79,7 +86,6 @@ export const BOARD_CLASS = {
|
|
|
79
86
|
teamTitle: "board-team__title",
|
|
80
87
|
teamFoot: "board-team__foot",
|
|
81
88
|
teamWhy: "board-team__why",
|
|
82
|
-
teamWhyBlocked: "board-team__why--blocked",
|
|
83
89
|
teamRoster: "board-team__roster",
|
|
84
90
|
teamMate: "board-team__mate",
|
|
85
91
|
teamDot: "board-team__dot",
|
|
@@ -88,4 +94,7 @@ export const BOARD_CLASS = {
|
|
|
88
94
|
teamEmpty: "board-team__empty",
|
|
89
95
|
teamDone: "board-team__done",
|
|
90
96
|
teamId: "board-team__id",
|
|
97
|
+
teamDoneIds: "board-team__ids",
|
|
98
|
+
teamTail: "board-team__tail",
|
|
99
|
+
teamFold: "board-team__fold",
|
|
91
100
|
} as const satisfies Readonly<Record<string, string>>;
|