@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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The client's renderer: snapshot in, DOM out — the toolbar, the lanes, the cards,
|
|
3
|
-
*
|
|
2
|
+
* The client's renderer: snapshot in, DOM out — the toolbar, the lanes, the cards, and the
|
|
3
|
+
* card dialog's contents.
|
|
4
4
|
*
|
|
5
5
|
* This file runs in the browser, where no type checker sees it, so it holds to one
|
|
6
6
|
* rule: **every value from the snapshot is set as `textContent`** — never as
|
|
@@ -9,36 +9,55 @@
|
|
|
9
9
|
* them.
|
|
10
10
|
*
|
|
11
11
|
* A frame replaces what changed and leaves the rest alone: a lane is rebuilt only
|
|
12
|
-
* when its cards
|
|
13
|
-
*
|
|
12
|
+
* when what its cards draw has moved — a revision of their own, or a blocker or a
|
|
13
|
+
* dependent the rest of the board decides — so the filter input, a collapsed lane, the
|
|
14
|
+
* scroll offset, and an open dialog all survive a peer's write.
|
|
14
15
|
*
|
|
15
|
-
* The client modules share one namespace, `window.bfBoard`: this one paints
|
|
16
|
-
* `
|
|
17
|
-
* owns the frame stream and the wiring.
|
|
16
|
+
* The client modules share one namespace, `window.bfBoard`: this one paints the whole
|
|
17
|
+
* frame, `card.ts` draws one card's face, `team.ts` draws the round, `dialog.ts` fills and
|
|
18
|
+
* opens a card, `keys.ts` binds, and `live.ts` owns the frame stream and the wiring. The
|
|
19
|
+
* browser runtime (`el`, `typing`, …) is spliced in here, because a page's one global body
|
|
20
|
+
* is the runtime every fragment reads through `board.`; the note renderer a dialog reads
|
|
21
|
+
* with is spliced into that fragment instead, so nothing reaches `window` that this page
|
|
22
|
+
* does not place on `board.`.
|
|
18
23
|
*
|
|
19
|
-
* `plural`, `tabTitle`, `matchesQuery`, `el`, `typing`, and `paintStatus` are
|
|
20
|
-
* here. Their one bodies live in `lib/` beside the rules they express, and the
|
|
21
|
-
* hands the source in — a second copy in the bundle is a second wording for
|
|
22
|
-
* second shape for the tab, a filter that finds a different card than the tool
|
|
23
|
-
* element builder and a typing test that
|
|
24
|
+
* `plural`, `tabTitle`, `matchesQuery`, `reasonFor`, `el`, `typing`, and `paintStatus` are
|
|
25
|
+
* not written here. Their one bodies live in `lib/` beside the rules they express, and the
|
|
26
|
+
* assembler hands the source in — a second copy in the bundle is a second wording for
|
|
27
|
+
* `1 card`, a second shape for the tab, a filter that finds a different card than the tool
|
|
28
|
+
* does, a second reason for the same lane, or an element builder and a typing test that
|
|
29
|
+
* drift from the review page's.
|
|
24
30
|
*/
|
|
25
31
|
|
|
26
32
|
/**
|
|
27
|
-
* The renderer, with what it needs spliced in: the counts' wording, the tab title's
|
|
28
|
-
*
|
|
29
|
-
*
|
|
33
|
+
* The renderer, with what it needs spliced in: the counts' wording, the tab title's shape,
|
|
34
|
+
* what a query matches, why a card sits where it does, how many placeholders the skeleton
|
|
35
|
+
* paints, and the browser runtime's element builder and typing test — each written once, in
|
|
36
|
+
* `lib/`, and handed in as source.
|
|
30
37
|
*/
|
|
31
38
|
export function renderJs(rules: {
|
|
32
39
|
plural: string;
|
|
33
40
|
tabTitle: string;
|
|
34
41
|
matches: string;
|
|
42
|
+
reason: string;
|
|
35
43
|
age: string;
|
|
44
|
+
lanes: string;
|
|
45
|
+
skipped: string;
|
|
46
|
+
decisions: string;
|
|
47
|
+
decisionTones: string;
|
|
48
|
+
reasonTones: string;
|
|
36
49
|
runtime: string;
|
|
37
50
|
}): string {
|
|
38
51
|
return renderTemplate
|
|
39
52
|
.replace("__PLURAL__", rules.plural)
|
|
53
|
+
.replace("__SKIPPED__", rules.skipped)
|
|
40
54
|
.replace("__TAB_TITLE__", rules.tabTitle)
|
|
41
55
|
.replace("__MATCHES__", rules.matches)
|
|
56
|
+
.replace("__REASON__", rules.reason)
|
|
57
|
+
.replace("__LANE_COUNT__", rules.lanes)
|
|
58
|
+
.replace("__DECISION_RANK__", rules.decisions)
|
|
59
|
+
.replace("__DECISION_TONES__", rules.decisionTones)
|
|
60
|
+
.replace("__REASON_TONES__", rules.reasonTones)
|
|
42
61
|
.replace("__AGE__", rules.age)
|
|
43
62
|
.replace("__RUNTIME__", rules.runtime);
|
|
44
63
|
}
|
|
@@ -47,24 +66,55 @@ const renderTemplate = String.raw`(function () {
|
|
|
47
66
|
"use strict";
|
|
48
67
|
var board = window.bfBoard || (window.bfBoard = {});
|
|
49
68
|
|
|
50
|
-
/** Lanes in the order the server sends them, with the words a reader expects. */
|
|
51
|
-
var LANE_LABEL = { todo: "To do", doing: "Doing", blocked: "Blocked", done: "Done" };
|
|
52
|
-
var LANE_EMPTY = { todo: "nothing waiting", doing: "nothing in flight", blocked: "nothing blocked", done: "nothing finished yet" };
|
|
53
|
-
/** Path chips a lane card shows before it counts the rest. */
|
|
54
|
-
var PATH_CHIPS = 3;
|
|
55
69
|
/**
|
|
56
|
-
*
|
|
70
|
+
* The page holds no lane table: a lane's words, colour, limit, and count arrive with it
|
|
71
|
+
* from the snapshot, so a head, a picker button, and the transcript cannot name one lane
|
|
72
|
+
* two ways — the words are decided once, in lib/board/lanes.ts.
|
|
73
|
+
*/
|
|
74
|
+
/** How many placeholder trays the skeleton paints: the lane table's own length, spliced in. */
|
|
75
|
+
var LANE_COUNT = __LANE_COUNT__;
|
|
76
|
+
/**
|
|
77
|
+
* How many rows a list may show before it starts folded, by list — one table, because two
|
|
78
|
+
* lists are the same pile and a second threshold is a second answer to "how much is history".
|
|
57
79
|
*
|
|
58
80
|
* A board of 26 cards is 19 of them finished, and an open Done lane is four times the
|
|
59
|
-
* height of the three that carry work
|
|
60
|
-
*
|
|
81
|
+
* height of the three that carry work: three or four is work a reader still checks, past
|
|
82
|
+
* six it is history, and the count says how much. The roster has the same problem for the
|
|
83
|
+
* same reason — a label that ever claimed a card stays an owner for good, so a long
|
|
84
|
+
* history is a wall of no-record rows — and three is where it stops being a team.
|
|
61
85
|
*/
|
|
62
|
-
var
|
|
63
|
-
|
|
64
|
-
|
|
86
|
+
var FOLD_AT = { done: 6, roster: 3 };
|
|
87
|
+
|
|
88
|
+
/** The same table, for the fragments painted after this one: team folds two lists. */
|
|
89
|
+
board.foldAt = FOLD_AT;
|
|
65
90
|
|
|
66
91
|
__RUNTIME__
|
|
67
92
|
|
|
93
|
+
/**
|
|
94
|
+
* The queue's order, its colours, and the tone a reason line wears — spliced in from
|
|
95
|
+
* lib/board/tone.ts, which the terminal reads through extensions/board/tone.ts. One
|
|
96
|
+
* table, so a row cannot be the second-strongest reason and the quietest colour.
|
|
97
|
+
*/
|
|
98
|
+
var DECISION_RANK = __DECISION_RANK__;
|
|
99
|
+
var DECISION_TONE = __DECISION_TONES__;
|
|
100
|
+
var REASON_TONE = __REASON_TONES__;
|
|
101
|
+
|
|
102
|
+
/** Where a kind sits in the queue: the order the table states, not a copy. */
|
|
103
|
+
function rankOf(kind) {
|
|
104
|
+
return DECISION_RANK.indexOf(kind);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** The colour a decision row is drawn in — its edge and its reason line. */
|
|
108
|
+
function decisionTone(kind) {
|
|
109
|
+
return DECISION_TONE[kind] || "dim";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** The colour a card's reason line is drawn in, by the kind the rule reported. */
|
|
113
|
+
function reasonTone(kind) {
|
|
114
|
+
return REASON_TONE[kind] || "muted";
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
68
118
|
/**
|
|
69
119
|
* One button that calls back, so nothing on the page is a click handler in markup.
|
|
70
120
|
*
|
|
@@ -114,102 +164,6 @@ __RUNTIME__
|
|
|
114
164
|
return undefined;
|
|
115
165
|
}
|
|
116
166
|
|
|
117
|
-
/** The one line under a card's meta: why it sits where it does. */
|
|
118
|
-
function reasonOf(card, view) {
|
|
119
|
-
var last = card.notes.length > 0 ? card.notes[card.notes.length - 1] : undefined;
|
|
120
|
-
// An order is the strongest reason a card is not being worked on, and the one a
|
|
121
|
-
// reader cannot see from the lane: it comes before the note that says the same.
|
|
122
|
-
// The sentence is the snapshot's, so this line and the tool's refusal name one list.
|
|
123
|
-
if (card.waits !== undefined) return { text: "waits on " + card.waits, kind: "waiting" };
|
|
124
|
-
if (card.status === "blocked") {
|
|
125
|
-
// The lane already says blocked and the tone already says danger; the line says why.
|
|
126
|
-
// The recorded reason comes first: the note is what somebody happened to write last,
|
|
127
|
-
// and it is the reason a move to blocked asks for. The slot carries the reason alone —
|
|
128
|
-
// the lane head already says blocked, and a status prefix in it would be a second voice.
|
|
129
|
-
if (card.blockedBy) return { text: card.blockedBy, kind: "blocked" };
|
|
130
|
-
return { text: last ? last.text : "blocked, with nothing recorded", kind: "blocked" };
|
|
131
|
-
}
|
|
132
|
-
// An offer is a decision the lead already made once: the lane says nobody holds it, and
|
|
133
|
-
// this says how long the broadcast has gone unanswered, which is the difference between
|
|
134
|
-
// nobody having looked and nobody taking it.
|
|
135
|
-
if (card.status === "todo" && card.offeredAt !== undefined) {
|
|
136
|
-
return { text: "offered " + ago(card.offeredAt) + " ago · nobody took it", kind: "waiting" };
|
|
137
|
-
}
|
|
138
|
-
if (card.status === "doing" && !card.plan) {
|
|
139
|
-
return { text: "no plan recorded", kind: "unplanned" };
|
|
140
|
-
}
|
|
141
|
-
if (card.status === "doing") return { text: "planned", kind: "plan" };
|
|
142
|
-
if (card.status === "done") {
|
|
143
|
-
var landed = card.landed ? " · landed " + card.landed.commit : "";
|
|
144
|
-
return { text: "✓ done" + (card.owner ? " by " + card.owner.label : "") + landed, kind: "done" };
|
|
145
|
-
}
|
|
146
|
-
return last ? { text: last.text, kind: "note" } : undefined;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/** A path hint, or the +N chip that points at the rest. */
|
|
150
|
-
function paths(card, limit) {
|
|
151
|
-
var list = el("ul", N.cardPaths);
|
|
152
|
-
var shown = card.paths.slice(0, limit);
|
|
153
|
-
for (var i = 0; i < shown.length; i++) {
|
|
154
|
-
var item = el("li", "");
|
|
155
|
-
var hint = chip(shown[i]);
|
|
156
|
-
// A path longer than the card elides, and the whole of it is on the hover title: the
|
|
157
|
-
// card is often the only place its hints are read before opening the record.
|
|
158
|
-
hint.title = shown[i];
|
|
159
|
-
item.appendChild(hint);
|
|
160
|
-
list.appendChild(item);
|
|
161
|
-
}
|
|
162
|
-
if (card.paths.length > limit) {
|
|
163
|
-
var rest = el("li", "");
|
|
164
|
-
rest.appendChild(chip("+" + (card.paths.length - limit)));
|
|
165
|
-
list.appendChild(rest);
|
|
166
|
-
}
|
|
167
|
-
return list;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/** One card in its lane. */
|
|
171
|
-
function cardNode(card, view) {
|
|
172
|
-
var item = el("li", N.card);
|
|
173
|
-
item.setAttribute("data-card", card.id);
|
|
174
|
-
item.setAttribute("data-status", card.status);
|
|
175
|
-
item.setAttribute("data-rev", String(card.rev));
|
|
176
|
-
|
|
177
|
-
var head = el("p", N.cardHead);
|
|
178
|
-
head.appendChild(el("span", N.cardId, "#" + card.id));
|
|
179
|
-
var title = actionButton(N.cardTitle, card.title, function () {
|
|
180
|
-
board.openCard(card.id);
|
|
181
|
-
});
|
|
182
|
-
head.appendChild(title);
|
|
183
|
-
item.appendChild(head);
|
|
184
|
-
|
|
185
|
-
var meta = el("p", N.cardMeta);
|
|
186
|
-
meta.appendChild(card.owner ? chip(card.owner.label, "accent") : el("span", "", "unassigned"));
|
|
187
|
-
// The order inside a lane is the priority, so an urgent card says so: an order
|
|
188
|
-
// nobody can see reads as an accident. The lane's colour stays the card's own.
|
|
189
|
-
if (card.priority !== "normal") meta.appendChild(chip(card.priority));
|
|
190
|
-
// The stamp, so the clock can keep the age honest between frames: a board can be
|
|
191
|
-
// quiet for ten minutes while the page still says "live".
|
|
192
|
-
var age = el("span", "", ago(card.updatedAt));
|
|
193
|
-
age.setAttribute("data-at", String(card.updatedAt));
|
|
194
|
-
meta.appendChild(age);
|
|
195
|
-
item.appendChild(meta);
|
|
196
|
-
|
|
197
|
-
if (card.paths.length > 0) item.appendChild(paths(card, PATH_CHIPS));
|
|
198
|
-
|
|
199
|
-
var reason = reasonOf(card, view);
|
|
200
|
-
if (reason) {
|
|
201
|
-
// The tone is a lookup, not a suffix: a name assembled from a prefix is a name no
|
|
202
|
-
// test can compare with the stylesheet.
|
|
203
|
-
var tone = REASON_TONE[reason.kind];
|
|
204
|
-
var line = el("p", tone === undefined ? N.cardReason : N.cardReason + " " + tone, reason.text);
|
|
205
|
-
// It clamps to one line on the card, so the full sentence is the tooltip.
|
|
206
|
-
line.title = reason.text;
|
|
207
|
-
item.appendChild(line);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
return item;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
167
|
/**
|
|
214
168
|
* The cards as the page draws them.
|
|
215
169
|
*
|
|
@@ -228,45 +182,55 @@ __RUNTIME__
|
|
|
228
182
|
return seen;
|
|
229
183
|
}
|
|
230
184
|
|
|
231
|
-
/** The toolbar's
|
|
185
|
+
/** The toolbar's subject, its progress, the footer, and the tab's own name. */
|
|
232
186
|
function renderChrome(view) {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
187
|
+
// The board's subject, which the header carries: the repository's name, with the root's
|
|
188
|
+
// whole path on the hover, because a name alone cannot tell two clones of one repository
|
|
189
|
+
// apart. It arrives in the document too, so the toolbar is complete before the first
|
|
190
|
+
// frame; this keeps it true if a frame ever names a different store.
|
|
191
|
+
var repo = document.getElementById("board-repo");
|
|
192
|
+
if (repo) {
|
|
193
|
+
repo.textContent = view.repo;
|
|
194
|
+
repo.title = view.root;
|
|
237
195
|
}
|
|
238
196
|
var progress = document.getElementById("board-progress");
|
|
197
|
+
var done = laneOf(view, "done");
|
|
198
|
+
var finished = done === undefined ? 0 : done.cards.length;
|
|
239
199
|
if (progress) {
|
|
240
|
-
var total = view.
|
|
200
|
+
var total = view.total;
|
|
241
201
|
// One or two cards is not a ratio: an empty bar beside "0/1 done" is noise.
|
|
242
202
|
progress.hidden = total < 3;
|
|
243
203
|
var fill = document.getElementById("board-progress-fill");
|
|
244
|
-
if (fill) fill.style.width = total === 0 ? "0%" : Math.round((
|
|
204
|
+
if (fill) fill.style.width = total === 0 ? "0%" : Math.round((finished / total) * 100) + "%";
|
|
245
205
|
var text = document.getElementById("board-progress-text");
|
|
246
|
-
if (text) text.textContent =
|
|
206
|
+
if (text) text.textContent = finished + "/" + total + " done";
|
|
247
207
|
}
|
|
248
208
|
var where = document.getElementById("board-where");
|
|
249
209
|
if (where) {
|
|
250
|
-
//
|
|
251
|
-
//
|
|
210
|
+
// The footer adds only what the header does not: which store these cards are filed
|
|
211
|
+
// under, how many, and what they weigh. A footer that only counted read cards would
|
|
212
|
+
// say "2 cards" over a directory holding three files, so the refused ones are counted
|
|
213
|
+
// beside the size and named with the store's own reason on the hover — the line has no
|
|
214
|
+
// room for a file and a reason each, and a count alone is a fact a reader cannot act on.
|
|
252
215
|
where.textContent =
|
|
253
|
-
|
|
254
|
-
(view.skipped > 0 ? " · " + plural(view.skipped, "
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
where.title = view.root;
|
|
216
|
+
"store " + view.where + " · " + plural(view.total, "card") + " · " + size(view.bytes) +
|
|
217
|
+
(view.skipped.length > 0 ? " · " + plural(view.skipped.length, "card") + " could not be read" : "");
|
|
218
|
+
if (view.skipped.length > 0) where.title = skippedText(view.skipped);
|
|
219
|
+
else where.removeAttribute("title");
|
|
258
220
|
}
|
|
259
221
|
// The tab has to tell two boards apart, and a board's subject is its repository: one
|
|
260
222
|
// board per repository, named the way the directory holding it is. The store key is a
|
|
261
223
|
// hash, which no reader can say out loud, so it is not what a tab leads with. The count
|
|
262
224
|
// is what a browser full of tabs is read for, and an empty board says nothing rather
|
|
263
225
|
// than 0.
|
|
264
|
-
var open = view.
|
|
226
|
+
var open = view.total - finished;
|
|
265
227
|
document.title = tabTitle("board", view.repo, open > 0 ? open + " open" : undefined);
|
|
266
228
|
}
|
|
267
229
|
|
|
268
230
|
/** 1 card, 2 cards — the one body, handed in by the bundle's assembler. */
|
|
269
231
|
var plural = __PLURAL__;
|
|
232
|
+
/** The refused files, named with their reasons: the tool's own sentence, handed in. */
|
|
233
|
+
var skippedText = __SKIPPED__;
|
|
270
234
|
/** board · my-pi-agent · 3 open, the tab's one shape, handed in the same way. */
|
|
271
235
|
var tabTitle = __TAB_TITLE__;
|
|
272
236
|
|
|
@@ -277,43 +241,90 @@ __RUNTIME__
|
|
|
277
241
|
* writing to the cards would slowly lie about how long they have sat there. The
|
|
278
242
|
* ticker calls this; it touches nothing else, so a filter, a folded lane, or an
|
|
279
243
|
* open dialog survives it.
|
|
244
|
+
*
|
|
245
|
+
* A stamp carries its own wording ("updated {age}", "last note {age} by …"), because a
|
|
246
|
+
* card now shows two ages and a ticker that assumed one of them would relabel the other.
|
|
280
247
|
*/
|
|
281
248
|
function refreshAges() {
|
|
282
249
|
var nodes = document.querySelectorAll("[data-at]");
|
|
283
250
|
for (var i = 0; i < nodes.length; i++) {
|
|
284
251
|
var next = ago(Number(nodes[i].getAttribute("data-at")));
|
|
285
252
|
if (nodes[i].textContent !== next) nodes[i].textContent = next;
|
|
253
|
+
var wording = nodes[i].getAttribute("data-at-title");
|
|
254
|
+
if (wording !== null) nodes[i].title = board.sentence(wording, next);
|
|
286
255
|
}
|
|
287
256
|
}
|
|
288
257
|
|
|
289
258
|
/** Whether the filter keeps a card: the module's one predicate, spliced in as source. */
|
|
290
259
|
var matches = __MATCHES__;
|
|
291
260
|
|
|
292
|
-
/**
|
|
261
|
+
/** Why a card sits where it does: one body, spliced in from lib/board/reason.ts. */
|
|
262
|
+
var reasonFor = __REASON__;
|
|
263
|
+
|
|
264
|
+
/** One lane from the snapshot, by its name; absent when the snapshot no longer has it. */
|
|
265
|
+
function laneOf(view, key) {
|
|
266
|
+
for (var i = 0; i < view.lanes.length; i++) if (view.lanes[i].key === key) return view.lanes[i];
|
|
267
|
+
return undefined;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** A lane head's count: n/limit while the lane is pulled, and n otherwise. */
|
|
271
|
+
function headCount(lane) {
|
|
272
|
+
return lane.limit === undefined ? String(lane.count) : lane.count + "/" + lane.limit;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* The same count, in words a screen reader can read.
|
|
277
|
+
*
|
|
278
|
+
* The pill prints "1/3" because an eye takes the slash in at a glance; spoken, it is
|
|
279
|
+
* punctuation between two numbers. The heading therefore names itself from the same
|
|
280
|
+
* facts -- "Plan, 1 of 3" -- and the pill keeps the shape.
|
|
281
|
+
*/
|
|
282
|
+
function headLabel(lane, shown) {
|
|
283
|
+
if (shown !== undefined) return lane.label + ", " + shown + " of " + lane.cards.length;
|
|
284
|
+
if (lane.limit !== undefined) return lane.label + ", " + lane.count + " of " + lane.limit;
|
|
285
|
+
return lane.label + ", " + plural(lane.count, "card");
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** The lane section for one lane, created once and then reused. */
|
|
293
289
|
function laneNode(lane) {
|
|
294
|
-
var section = document.querySelector('.board-lane[data-
|
|
290
|
+
var section = document.querySelector('.board-lane[data-lane="' + lane.key + '"]');
|
|
295
291
|
if (section) return section;
|
|
296
292
|
section = el("section", N.lane);
|
|
297
|
-
section.setAttribute("data-
|
|
293
|
+
section.setAttribute("data-lane", lane.key);
|
|
298
294
|
var head = el("h2", N.laneHead);
|
|
299
|
-
var toggle = actionButton(N.laneToggle,
|
|
300
|
-
board.
|
|
295
|
+
var toggle = actionButton(N.laneToggle, lane.label, function () {
|
|
296
|
+
board.toggleFold(lane.key);
|
|
301
297
|
});
|
|
298
|
+
toggle.setAttribute("data-fold", lane.key);
|
|
302
299
|
toggle.setAttribute("aria-expanded", "true");
|
|
303
|
-
toggle.setAttribute("aria-controls", "lane-" + lane.
|
|
304
|
-
toggle.id = "lane-head-" + lane.
|
|
300
|
+
toggle.setAttribute("aria-controls", "lane-" + lane.key);
|
|
301
|
+
toggle.id = "lane-head-" + lane.key;
|
|
305
302
|
head.appendChild(el("span", N.laneDot));
|
|
306
303
|
head.appendChild(toggle);
|
|
307
304
|
head.appendChild(el("span", N.laneCount));
|
|
308
305
|
section.appendChild(head);
|
|
309
306
|
var list = el("ol", N.laneList);
|
|
310
|
-
list.id = "lane-" + lane.
|
|
307
|
+
list.id = "lane-" + lane.key;
|
|
311
308
|
section.appendChild(list);
|
|
312
|
-
section.appendChild(el("p", N.laneEmpty,
|
|
309
|
+
section.appendChild(el("p", N.laneEmpty, lane.empty));
|
|
313
310
|
return section;
|
|
314
311
|
}
|
|
315
312
|
|
|
316
|
-
/**
|
|
313
|
+
/**
|
|
314
|
+
* What one face is drawn from: the card's own revision, and the two facts about it that
|
|
315
|
+
* the rest of the board decides.
|
|
316
|
+
*
|
|
317
|
+
* A face prints what the card waits for and what it holds up, both derived from other
|
|
318
|
+
* cards, so a blocker finished in another lane moves nothing in this card's record — and
|
|
319
|
+
* a lane keyed on revisions alone kept a sentence the board had already left behind. The
|
|
320
|
+
* Team tab's queue carries its rows' reasons the same way: a signature lists the facts a
|
|
321
|
+
* row prints, not just the row's own file.
|
|
322
|
+
*/
|
|
323
|
+
function faceSig(card) {
|
|
324
|
+
return card.id + ":" + card.rev + "|" + (card.waits || "") + "|" + (card.dependents || "");
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** Draw the lanes, rebuilding only one whose faces actually moved. */
|
|
317
328
|
function renderLanes(view) {
|
|
318
329
|
var lanes = document.getElementById("board-lanes");
|
|
319
330
|
if (!lanes) return;
|
|
@@ -323,9 +334,7 @@ __RUNTIME__
|
|
|
323
334
|
lanes.removeAttribute("data-skeleton");
|
|
324
335
|
}
|
|
325
336
|
var query = board.query || "";
|
|
326
|
-
var
|
|
327
|
-
if (empty) empty.hidden = view.counts.total !== 0;
|
|
328
|
-
lanes.hidden = view.counts.total === 0;
|
|
337
|
+
var shownTotal = 0;
|
|
329
338
|
for (var i = 0; i < view.lanes.length; i++) {
|
|
330
339
|
var lane = view.lanes[i];
|
|
331
340
|
var section = laneNode(lane);
|
|
@@ -333,9 +342,9 @@ __RUNTIME__
|
|
|
333
342
|
// Finished work starts folded, once: the default is what the page opens with, and
|
|
334
343
|
// toggleLane writes a boolean from then on, so a reader who unfolds Done keeps it
|
|
335
344
|
// unfolded through every frame a peer's write causes.
|
|
336
|
-
if (lane.
|
|
345
|
+
if (lane.key === "done" && lane.cards.length >= FOLD_AT.done) {
|
|
337
346
|
board.collapsed = board.collapsed || {};
|
|
338
|
-
if (board.collapsed[lane.
|
|
347
|
+
if (board.collapsed[lane.key] === undefined) board.collapsed[lane.key] = true;
|
|
339
348
|
}
|
|
340
349
|
// A lane holding nothing is a rail: its dot, its word, and its count, in the width
|
|
341
350
|
// the fact deserves. The filter never does this — filtering is a view of the cards,
|
|
@@ -346,25 +355,29 @@ __RUNTIME__
|
|
|
346
355
|
// for want of a column the lane is not using. A count, never text.
|
|
347
356
|
section.style.setProperty("--lane-count", String(lane.cards.length));
|
|
348
357
|
var shown = lane.cards.filter(function (card) { return matches(card, query); });
|
|
349
|
-
|
|
358
|
+
shownTotal += shown.length;
|
|
359
|
+
var sig = lane.cards.map(faceSig).join(",") + "|" + query;
|
|
350
360
|
if (section.getAttribute("data-sig") !== sig) {
|
|
351
361
|
section.setAttribute("data-sig", sig);
|
|
352
362
|
var list = section.querySelector(".board-lane__list");
|
|
353
363
|
list.textContent = "";
|
|
354
|
-
for (var j = 0; j < shown.length; j++) list.appendChild(cardNode(shown[j]
|
|
364
|
+
for (var j = 0; j < shown.length; j++) list.appendChild(board.cardNode(shown[j]));
|
|
355
365
|
}
|
|
356
366
|
var count = section.querySelector(".board-lane__count");
|
|
367
|
+
var head = section.querySelector(".board-lane__head");
|
|
357
368
|
if (count) {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
369
|
+
var filtered = query !== "" && shown.length !== lane.cards.length;
|
|
370
|
+
count.textContent = filtered ? shown.length + "/" + lane.cards.length : headCount(lane);
|
|
371
|
+
// The visible pill and the spoken name come from the same numbers, so a filtered lane
|
|
372
|
+
// says "1 of 2" rather than reading as a board of one card.
|
|
373
|
+
if (head) head.setAttribute("aria-label", headLabel(lane, filtered ? shown.length : undefined));
|
|
361
374
|
}
|
|
362
375
|
var none = section.querySelector(".board-lane__empty");
|
|
363
376
|
if (none) {
|
|
364
377
|
none.hidden = shown.length !== 0;
|
|
365
|
-
none.textContent = query !== "" && lane.cards.length > 0 ? "no match" :
|
|
378
|
+
none.textContent = query !== "" && lane.cards.length > 0 ? "no match" : lane.empty;
|
|
366
379
|
}
|
|
367
|
-
var collapsed = board.collapsed && board.collapsed[lane.
|
|
380
|
+
var collapsed = board.collapsed && board.collapsed[lane.key] === true;
|
|
368
381
|
var list = section.querySelector(".board-lane__list");
|
|
369
382
|
if (list) list.hidden = collapsed;
|
|
370
383
|
// A folded lane shows nothing, so it takes the rail's width the way an empty one
|
|
@@ -374,6 +387,7 @@ __RUNTIME__
|
|
|
374
387
|
var toggle = section.querySelector(".board-lane__toggle");
|
|
375
388
|
if (toggle) toggle.setAttribute("aria-expanded", String(!collapsed));
|
|
376
389
|
}
|
|
390
|
+
board.renderEmpty(view, query, shownTotal);
|
|
377
391
|
renderPicker(view);
|
|
378
392
|
}
|
|
379
393
|
|
|
@@ -384,118 +398,33 @@ __RUNTIME__
|
|
|
384
398
|
picker.textContent = "";
|
|
385
399
|
for (var i = 0; i < view.lanes.length; i++) {
|
|
386
400
|
(function (lane) {
|
|
387
|
-
|
|
401
|
+
// A lane switch, not a tablist: it shows one lane and there is no tabpanel behind
|
|
402
|
+
// each button, so the pressed state is the honest one rather than a tab without its
|
|
403
|
+
// panel. The counts stay on the face, where a reader chooses by them.
|
|
404
|
+
var item = el("button", "bf-segmented__item", lane.label + " " + headCount(lane));
|
|
388
405
|
item.type = "button";
|
|
389
|
-
item.setAttribute("
|
|
390
|
-
item.
|
|
391
|
-
item.addEventListener("click", function () { board.showLane(lane.status); });
|
|
406
|
+
item.setAttribute("data-picker-lane", lane.key);
|
|
407
|
+
item.addEventListener("click", function () { board.showLane(lane.key); });
|
|
392
408
|
picker.appendChild(item);
|
|
393
409
|
})(view.lanes[i]);
|
|
394
410
|
}
|
|
395
411
|
board.applyLane();
|
|
396
412
|
}
|
|
397
413
|
|
|
398
|
-
/**
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
var dt = document.getElementById("card-dialog-" + name + "-label");
|
|
413
|
-
var dd = document.getElementById("card-dialog-" + name);
|
|
414
|
-
if (dt) dt.hidden = !shown;
|
|
415
|
-
if (dd) dd.hidden = !shown;
|
|
416
|
-
return dd;
|
|
417
|
-
};
|
|
418
|
-
var setRow = function (name, shown, value, full) {
|
|
419
|
-
var dd = pair(name, shown);
|
|
420
|
-
dd.textContent = shown ? value : "";
|
|
421
|
-
if (full === undefined) dd.removeAttribute("title");
|
|
422
|
-
else dd.setAttribute("title", full);
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
var owner = document.getElementById("card-dialog-owner");
|
|
426
|
-
owner.textContent = "";
|
|
427
|
-
owner.appendChild(card.owner ? chip(card.owner.label, "accent") : document.createTextNode("unassigned"));
|
|
428
|
-
pair("owner", true);
|
|
429
|
-
|
|
430
|
-
var priority = pair("priority", card.priority !== "normal");
|
|
431
|
-
priority.textContent = "";
|
|
432
|
-
if (card.priority !== "normal") priority.appendChild(chip(card.priority));
|
|
433
|
-
|
|
434
|
-
setRow("updated", true, ago(card.updatedAt));
|
|
435
|
-
// How long the card has been in the lane it is in now, which updatedAt cannot say:
|
|
436
|
-
// a note filed against a blocked card moves that stamp without moving the card. Absent
|
|
437
|
-
// for a card written before the log existed, so the row takes its label with it.
|
|
438
|
-
setRow("lane", card.since !== undefined, card.status + " · " + ago(card.since));
|
|
439
|
-
setRow("by", true, card.by);
|
|
440
|
-
setRow("landed", card.landed !== undefined, card.landed ? card.landed.commit : "", card.landed ? card.landed.commit : undefined);
|
|
441
|
-
|
|
442
|
-
// The dialog and the card face say one thing, from one field: the ids it was linked
|
|
443
|
-
// to are the storage, and a reader is owed the lanes that make them a decision.
|
|
444
|
-
setRow("depends", card.waits !== undefined, card.waits || "", card.waits);
|
|
445
|
-
// The other side of the same link: what leaving this card undone is holding up, which
|
|
446
|
-
// is the one thing a card's own row cannot say.
|
|
447
|
-
setRow("dependents", card.dependents !== undefined, card.dependents || "", card.dependents);
|
|
448
|
-
|
|
449
|
-
// A long plan path elides on one line, and the whole of it is on the hover title and
|
|
450
|
-
// in the text, so it stays copyable: the reason to open a card is often its plan.
|
|
451
|
-
setRow("plan", Boolean(card.plan), card.plan || "", card.plan || undefined);
|
|
452
|
-
|
|
453
|
-
pair("paths", card.paths.length > 0);
|
|
454
|
-
var pathList = document.getElementById("card-dialog-path-list");
|
|
455
|
-
pathList.textContent = "";
|
|
456
|
-
for (var i = 0; i < card.paths.length; i++) {
|
|
457
|
-
var item = el("li", "");
|
|
458
|
-
var hint = chip(card.paths[i]);
|
|
459
|
-
hint.className += " " + N.dialogPath;
|
|
460
|
-
hint.title = card.paths[i];
|
|
461
|
-
item.appendChild(hint);
|
|
462
|
-
pathList.appendChild(item);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
var notes = document.getElementById("card-dialog-notes");
|
|
466
|
-
notes.textContent = "";
|
|
467
|
-
for (var j = 0; j < card.notes.length; j++) {
|
|
468
|
-
var note = card.notes[j];
|
|
469
|
-
// The prose is the record and reads at body size; who wrote it and when is its
|
|
470
|
-
// footnote, so it follows the sentence instead of standing over it.
|
|
471
|
-
var row = el("li", N.dialogNote);
|
|
472
|
-
row.appendChild(el("p", N.dialogNoteText, note.text));
|
|
473
|
-
var foot = el("p", N.dialogNoteFoot);
|
|
474
|
-
foot.appendChild(el("span", N.dialogNoteBy, note.by));
|
|
475
|
-
foot.appendChild(el("span", N.dialogNoteAt, ago(note.at)));
|
|
476
|
-
row.appendChild(foot);
|
|
477
|
-
notes.appendChild(row);
|
|
478
|
-
}
|
|
479
|
-
var count = document.getElementById("card-dialog-note-count");
|
|
480
|
-
if (count) count.textContent = card.notes.length === 0 ? " · none" : " · " + card.notes.length;
|
|
481
|
-
var earlier = document.getElementById("card-dialog-earlier");
|
|
482
|
-
earlier.hidden = card.notesDropped === 0;
|
|
483
|
-
earlier.textContent = card.notesDropped === 0 ? "" : card.notesDropped + " earlier notes dropped";
|
|
484
|
-
|
|
485
|
-
// A modal whose title changes is not announced, so walking with j/k says where it
|
|
486
|
-
// landed: the position is counted over the cards as they are drawn, not as stored, so
|
|
487
|
-
// a filter or a folded lane cannot make the number disagree with the screen.
|
|
488
|
-
var where = document.getElementById("card-dialog-where");
|
|
489
|
-
if (where) {
|
|
490
|
-
var drawn = drawnCards();
|
|
491
|
-
var at = 0;
|
|
492
|
-
for (var k = 0; k < drawn.length; k++) {
|
|
493
|
-
if (drawn[k].getAttribute("data-card") === String(card.id)) at = k + 1;
|
|
494
|
-
}
|
|
495
|
-
// Opened from the Team tab there is no drawn board to count along, so the line
|
|
496
|
-
// names the card instead of claiming a position it cannot know.
|
|
497
|
-
where.textContent = drawn.length === 0 ? card.title : "card " + at + " of " + drawn.length + ": " + card.title;
|
|
498
|
-
}
|
|
414
|
+
/**
|
|
415
|
+
* Put focus back on the control a reader just moved.
|
|
416
|
+
*
|
|
417
|
+
* A fold repaints the panel it lives in, so the element that was clicked is gone by the
|
|
418
|
+
* time the frame lands and focus would fall to the document — a reader who folds with the
|
|
419
|
+
* keyboard would then start again from the top of the page. toggleFold records the key
|
|
420
|
+
* and each control names itself with it, so this is the whole lookup.
|
|
421
|
+
*/
|
|
422
|
+
function restoreFoldFocus() {
|
|
423
|
+
var key = board.foldFocus;
|
|
424
|
+
if (key === undefined) return;
|
|
425
|
+
board.foldFocus = undefined;
|
|
426
|
+
var control = document.querySelector('[data-fold="' + key + '"]');
|
|
427
|
+
if (control) control.focus();
|
|
499
428
|
}
|
|
500
429
|
|
|
501
430
|
/** Paint a whole snapshot. */
|
|
@@ -504,11 +433,14 @@ __RUNTIME__
|
|
|
504
433
|
renderChrome(view);
|
|
505
434
|
renderLanes(view);
|
|
506
435
|
board.renderTeam(view);
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
436
|
+
// The dialog module keeps an open record current — or closes it when its card left the
|
|
437
|
+
// frame — so the panel and the board cannot disagree about one card.
|
|
438
|
+
board.syncDialog(view);
|
|
439
|
+
// The marks are painted after the frame, not with each card: a lane is rebuilt whole
|
|
440
|
+
// when its cards move, and a reader's place must survive that.
|
|
441
|
+
board.markCards();
|
|
442
|
+
// Last, because the control it looks for is one this frame has just built.
|
|
443
|
+
restoreFoldFocus();
|
|
512
444
|
};
|
|
513
445
|
|
|
514
446
|
board.el = el;
|
|
@@ -518,7 +450,12 @@ __RUNTIME__
|
|
|
518
450
|
board.findCard = findCard;
|
|
519
451
|
board.drawnCards = drawnCards;
|
|
520
452
|
board.matches = matches;
|
|
521
|
-
board.
|
|
453
|
+
board.reasonFor = reasonFor;
|
|
454
|
+
board.rankOf = rankOf;
|
|
455
|
+
board.decisionTone = decisionTone;
|
|
456
|
+
board.reasonTone = reasonTone;
|
|
457
|
+
board.laneCount = LANE_COUNT;
|
|
458
|
+
board.laneOf = laneOf;
|
|
522
459
|
board.refreshAges = refreshAges;
|
|
523
460
|
board.plural = plural;
|
|
524
461
|
})();`;
|