@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,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,34 +66,64 @@ 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
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
121
|
+
* The class comes from the caller: a card's title and a lane's head are buttons drawn
|
|
122
|
+
* by the page's own rule, not by the kit's chip-with-border look — and a card draws no
|
|
123
|
+
* action button at all, since claiming, moving, and editing are the tools' work.
|
|
74
124
|
*/
|
|
75
|
-
function actionButton(label, onClick
|
|
76
|
-
var
|
|
77
|
-
var node = el("button", "bf-btn" + kind, label);
|
|
125
|
+
function actionButton(className, label, onClick) {
|
|
126
|
+
var node = el("button", className, label);
|
|
78
127
|
node.type = "button";
|
|
79
128
|
node.addEventListener("click", onClick);
|
|
80
129
|
return node;
|
|
@@ -115,99 +164,6 @@ __RUNTIME__
|
|
|
115
164
|
return undefined;
|
|
116
165
|
}
|
|
117
166
|
|
|
118
|
-
/** The one line under a card's meta: why it sits where it does. */
|
|
119
|
-
function reasonOf(card, view) {
|
|
120
|
-
var last = card.notes.length > 0 ? card.notes[card.notes.length - 1] : undefined;
|
|
121
|
-
// An order is the strongest reason a card is not being worked on, and the one a
|
|
122
|
-
// reader cannot see from the lane: it comes before the note that says the same.
|
|
123
|
-
// The sentence is the snapshot's, so this line and the tool's refusal name one list.
|
|
124
|
-
if (card.waits !== undefined) return { text: "waits on " + card.waits, kind: "waiting" };
|
|
125
|
-
if (card.status === "blocked") {
|
|
126
|
-
// The lane already says blocked and the tone already says danger; the line says why.
|
|
127
|
-
// The recorded reason comes first: the note is what somebody happened to write last,
|
|
128
|
-
// and it is the reason a move to blocked asks for. The slot carries the reason alone —
|
|
129
|
-
// the lane head already says blocked, and a status prefix in it would be a second voice.
|
|
130
|
-
if (card.blockedBy) return { text: card.blockedBy, kind: "blocked" };
|
|
131
|
-
return { text: last ? last.text : "blocked, with nothing recorded", kind: "blocked" };
|
|
132
|
-
}
|
|
133
|
-
// An offer is a decision the lead already made once: the lane says nobody holds it, and
|
|
134
|
-
// this says how long the broadcast has gone unanswered, which is the difference between
|
|
135
|
-
// nobody having looked and nobody taking it.
|
|
136
|
-
if (card.status === "todo" && card.offeredAt !== undefined) {
|
|
137
|
-
return { text: "offered " + ago(card.offeredAt) + " ago · nobody took it", kind: "waiting" };
|
|
138
|
-
}
|
|
139
|
-
if (card.status === "doing" && !card.plan) {
|
|
140
|
-
return { text: "no plan recorded", kind: "unplanned" };
|
|
141
|
-
}
|
|
142
|
-
if (card.status === "doing") return { text: "planned", kind: "plan" };
|
|
143
|
-
if (card.status === "done") {
|
|
144
|
-
var landed = card.landed ? " · landed " + card.landed.commit : "";
|
|
145
|
-
return { text: "✓ done" + (card.owner ? " by " + card.owner.label : "") + landed, kind: "done" };
|
|
146
|
-
}
|
|
147
|
-
return last ? { text: last.text, kind: "note" } : undefined;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/** A path hint, or the +N chip that points at the rest. */
|
|
151
|
-
function paths(card, limit) {
|
|
152
|
-
var list = el("ul", N.cardPaths);
|
|
153
|
-
var shown = card.paths.slice(0, limit);
|
|
154
|
-
for (var i = 0; i < shown.length; i++) {
|
|
155
|
-
var item = el("li", "");
|
|
156
|
-
item.appendChild(chip(shown[i]));
|
|
157
|
-
list.appendChild(item);
|
|
158
|
-
}
|
|
159
|
-
if (card.paths.length > limit) {
|
|
160
|
-
var rest = el("li", "");
|
|
161
|
-
rest.appendChild(chip("+" + (card.paths.length - limit)));
|
|
162
|
-
list.appendChild(rest);
|
|
163
|
-
}
|
|
164
|
-
return list;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/** One card in its lane. */
|
|
168
|
-
function cardNode(card, view) {
|
|
169
|
-
var item = el("li", N.card);
|
|
170
|
-
item.setAttribute("data-card", card.id);
|
|
171
|
-
item.setAttribute("data-status", card.status);
|
|
172
|
-
item.setAttribute("data-rev", String(card.rev));
|
|
173
|
-
|
|
174
|
-
var head = el("p", N.cardHead);
|
|
175
|
-
head.appendChild(el("span", N.cardId, "#" + card.id));
|
|
176
|
-
var title = actionButton(card.title, function () {
|
|
177
|
-
board.openCard(card.id);
|
|
178
|
-
});
|
|
179
|
-
title.className = N.cardTitle;
|
|
180
|
-
head.appendChild(title);
|
|
181
|
-
item.appendChild(head);
|
|
182
|
-
|
|
183
|
-
var meta = el("p", N.cardMeta);
|
|
184
|
-
meta.appendChild(card.owner ? chip(card.owner.label, "accent") : el("span", "", "unassigned"));
|
|
185
|
-
// The order inside a lane is the priority, so an urgent card says so: an order
|
|
186
|
-
// nobody can see reads as an accident. The lane's colour stays the card's own.
|
|
187
|
-
if (card.priority !== "normal") meta.appendChild(chip(card.priority));
|
|
188
|
-
// The stamp, so the clock can keep the age honest between frames: a board can be
|
|
189
|
-
// quiet for ten minutes while the page still says "live".
|
|
190
|
-
var age = el("span", "", ago(card.updatedAt));
|
|
191
|
-
age.setAttribute("data-at", String(card.updatedAt));
|
|
192
|
-
meta.appendChild(age);
|
|
193
|
-
item.appendChild(meta);
|
|
194
|
-
|
|
195
|
-
if (card.paths.length > 0) item.appendChild(paths(card, PATH_CHIPS));
|
|
196
|
-
|
|
197
|
-
var reason = reasonOf(card, view);
|
|
198
|
-
if (reason) {
|
|
199
|
-
// The tone is a lookup, not a suffix: a name assembled from a prefix is a name no
|
|
200
|
-
// test can compare with the stylesheet.
|
|
201
|
-
var tone = REASON_TONE[reason.kind];
|
|
202
|
-
var line = el("p", tone === undefined ? N.cardReason : N.cardReason + " " + tone, reason.text);
|
|
203
|
-
// It clamps to one line on the card, so the full sentence is the tooltip.
|
|
204
|
-
line.title = reason.text;
|
|
205
|
-
item.appendChild(line);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return item;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
167
|
/**
|
|
212
168
|
* The cards as the page draws them.
|
|
213
169
|
*
|
|
@@ -226,40 +182,55 @@ __RUNTIME__
|
|
|
226
182
|
return seen;
|
|
227
183
|
}
|
|
228
184
|
|
|
229
|
-
/** The toolbar's
|
|
185
|
+
/** The toolbar's subject, its progress, the footer, and the tab's own name. */
|
|
230
186
|
function renderChrome(view) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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;
|
|
235
195
|
}
|
|
236
196
|
var progress = document.getElementById("board-progress");
|
|
197
|
+
var done = laneOf(view, "done");
|
|
198
|
+
var finished = done === undefined ? 0 : done.cards.length;
|
|
237
199
|
if (progress) {
|
|
238
|
-
var total = view.
|
|
200
|
+
var total = view.total;
|
|
239
201
|
// One or two cards is not a ratio: an empty bar beside "0/1 done" is noise.
|
|
240
202
|
progress.hidden = total < 3;
|
|
241
203
|
var fill = document.getElementById("board-progress-fill");
|
|
242
|
-
if (fill) fill.style.width = total === 0 ? "0%" : Math.round((
|
|
204
|
+
if (fill) fill.style.width = total === 0 ? "0%" : Math.round((finished / total) * 100) + "%";
|
|
243
205
|
var text = document.getElementById("board-progress-text");
|
|
244
|
-
if (text) text.textContent =
|
|
206
|
+
if (text) text.textContent = finished + "/" + total + " done";
|
|
245
207
|
}
|
|
246
208
|
var where = document.getElementById("board-where");
|
|
247
209
|
if (where) {
|
|
248
|
-
//
|
|
249
|
-
//
|
|
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.
|
|
250
215
|
where.textContent =
|
|
251
|
-
"store " + view.where + " · " + plural(view.
|
|
252
|
-
(view.skipped > 0 ? " · " + plural(view.skipped, "
|
|
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");
|
|
253
220
|
}
|
|
254
|
-
// The tab has to tell two boards apart, and a board's
|
|
255
|
-
//
|
|
256
|
-
//
|
|
257
|
-
|
|
258
|
-
|
|
221
|
+
// The tab has to tell two boards apart, and a board's subject is its repository: one
|
|
222
|
+
// board per repository, named the way the directory holding it is. The store key is a
|
|
223
|
+
// hash, which no reader can say out loud, so it is not what a tab leads with. The count
|
|
224
|
+
// is what a browser full of tabs is read for, and an empty board says nothing rather
|
|
225
|
+
// than 0.
|
|
226
|
+
var open = view.total - finished;
|
|
227
|
+
document.title = tabTitle("board", view.repo, open > 0 ? open + " open" : undefined);
|
|
259
228
|
}
|
|
260
229
|
|
|
261
230
|
/** 1 card, 2 cards — the one body, handed in by the bundle's assembler. */
|
|
262
231
|
var plural = __PLURAL__;
|
|
232
|
+
/** The refused files, named with their reasons: the tool's own sentence, handed in. */
|
|
233
|
+
var skippedText = __SKIPPED__;
|
|
263
234
|
/** board · my-pi-agent · 3 open, the tab's one shape, handed in the same way. */
|
|
264
235
|
var tabTitle = __TAB_TITLE__;
|
|
265
236
|
|
|
@@ -270,44 +241,90 @@ __RUNTIME__
|
|
|
270
241
|
* writing to the cards would slowly lie about how long they have sat there. The
|
|
271
242
|
* ticker calls this; it touches nothing else, so a filter, a folded lane, or an
|
|
272
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.
|
|
273
247
|
*/
|
|
274
248
|
function refreshAges() {
|
|
275
249
|
var nodes = document.querySelectorAll("[data-at]");
|
|
276
250
|
for (var i = 0; i < nodes.length; i++) {
|
|
277
251
|
var next = ago(Number(nodes[i].getAttribute("data-at")));
|
|
278
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);
|
|
279
255
|
}
|
|
280
256
|
}
|
|
281
257
|
|
|
282
258
|
/** Whether the filter keeps a card: the module's one predicate, spliced in as source. */
|
|
283
259
|
var matches = __MATCHES__;
|
|
284
260
|
|
|
285
|
-
/**
|
|
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. */
|
|
286
289
|
function laneNode(lane) {
|
|
287
|
-
var section = document.querySelector('.board-lane[data-
|
|
290
|
+
var section = document.querySelector('.board-lane[data-lane="' + lane.key + '"]');
|
|
288
291
|
if (section) return section;
|
|
289
292
|
section = el("section", N.lane);
|
|
290
|
-
section.setAttribute("data-
|
|
293
|
+
section.setAttribute("data-lane", lane.key);
|
|
291
294
|
var head = el("h2", N.laneHead);
|
|
292
|
-
var toggle = actionButton(
|
|
293
|
-
board.
|
|
295
|
+
var toggle = actionButton(N.laneToggle, lane.label, function () {
|
|
296
|
+
board.toggleFold(lane.key);
|
|
294
297
|
});
|
|
295
|
-
toggle.
|
|
298
|
+
toggle.setAttribute("data-fold", lane.key);
|
|
296
299
|
toggle.setAttribute("aria-expanded", "true");
|
|
297
|
-
toggle.setAttribute("aria-controls", "lane-" + lane.
|
|
298
|
-
toggle.id = "lane-head-" + lane.
|
|
300
|
+
toggle.setAttribute("aria-controls", "lane-" + lane.key);
|
|
301
|
+
toggle.id = "lane-head-" + lane.key;
|
|
299
302
|
head.appendChild(el("span", N.laneDot));
|
|
300
303
|
head.appendChild(toggle);
|
|
301
304
|
head.appendChild(el("span", N.laneCount));
|
|
302
305
|
section.appendChild(head);
|
|
303
306
|
var list = el("ol", N.laneList);
|
|
304
|
-
list.id = "lane-" + lane.
|
|
307
|
+
list.id = "lane-" + lane.key;
|
|
305
308
|
section.appendChild(list);
|
|
306
|
-
section.appendChild(el("p", N.laneEmpty,
|
|
309
|
+
section.appendChild(el("p", N.laneEmpty, lane.empty));
|
|
307
310
|
return section;
|
|
308
311
|
}
|
|
309
312
|
|
|
310
|
-
/**
|
|
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. */
|
|
311
328
|
function renderLanes(view) {
|
|
312
329
|
var lanes = document.getElementById("board-lanes");
|
|
313
330
|
if (!lanes) return;
|
|
@@ -317,9 +334,7 @@ __RUNTIME__
|
|
|
317
334
|
lanes.removeAttribute("data-skeleton");
|
|
318
335
|
}
|
|
319
336
|
var query = board.query || "";
|
|
320
|
-
var
|
|
321
|
-
if (empty) empty.hidden = view.counts.total !== 0;
|
|
322
|
-
lanes.hidden = view.counts.total === 0;
|
|
337
|
+
var shownTotal = 0;
|
|
323
338
|
for (var i = 0; i < view.lanes.length; i++) {
|
|
324
339
|
var lane = view.lanes[i];
|
|
325
340
|
var section = laneNode(lane);
|
|
@@ -327,9 +342,9 @@ __RUNTIME__
|
|
|
327
342
|
// Finished work starts folded, once: the default is what the page opens with, and
|
|
328
343
|
// toggleLane writes a boolean from then on, so a reader who unfolds Done keeps it
|
|
329
344
|
// unfolded through every frame a peer's write causes.
|
|
330
|
-
if (lane.
|
|
345
|
+
if (lane.key === "done" && lane.cards.length >= FOLD_AT.done) {
|
|
331
346
|
board.collapsed = board.collapsed || {};
|
|
332
|
-
if (board.collapsed[lane.
|
|
347
|
+
if (board.collapsed[lane.key] === undefined) board.collapsed[lane.key] = true;
|
|
333
348
|
}
|
|
334
349
|
// A lane holding nothing is a rail: its dot, its word, and its count, in the width
|
|
335
350
|
// the fact deserves. The filter never does this — filtering is a view of the cards,
|
|
@@ -340,25 +355,29 @@ __RUNTIME__
|
|
|
340
355
|
// for want of a column the lane is not using. A count, never text.
|
|
341
356
|
section.style.setProperty("--lane-count", String(lane.cards.length));
|
|
342
357
|
var shown = lane.cards.filter(function (card) { return matches(card, query); });
|
|
343
|
-
|
|
358
|
+
shownTotal += shown.length;
|
|
359
|
+
var sig = lane.cards.map(faceSig).join(",") + "|" + query;
|
|
344
360
|
if (section.getAttribute("data-sig") !== sig) {
|
|
345
361
|
section.setAttribute("data-sig", sig);
|
|
346
362
|
var list = section.querySelector(".board-lane__list");
|
|
347
363
|
list.textContent = "";
|
|
348
|
-
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]));
|
|
349
365
|
}
|
|
350
366
|
var count = section.querySelector(".board-lane__count");
|
|
367
|
+
var head = section.querySelector(".board-lane__head");
|
|
351
368
|
if (count) {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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));
|
|
355
374
|
}
|
|
356
375
|
var none = section.querySelector(".board-lane__empty");
|
|
357
376
|
if (none) {
|
|
358
377
|
none.hidden = shown.length !== 0;
|
|
359
|
-
none.textContent = query !== "" && lane.cards.length > 0 ? "no match" :
|
|
378
|
+
none.textContent = query !== "" && lane.cards.length > 0 ? "no match" : lane.empty;
|
|
360
379
|
}
|
|
361
|
-
var collapsed = board.collapsed && board.collapsed[lane.
|
|
380
|
+
var collapsed = board.collapsed && board.collapsed[lane.key] === true;
|
|
362
381
|
var list = section.querySelector(".board-lane__list");
|
|
363
382
|
if (list) list.hidden = collapsed;
|
|
364
383
|
// A folded lane shows nothing, so it takes the rail's width the way an empty one
|
|
@@ -368,6 +387,7 @@ __RUNTIME__
|
|
|
368
387
|
var toggle = section.querySelector(".board-lane__toggle");
|
|
369
388
|
if (toggle) toggle.setAttribute("aria-expanded", String(!collapsed));
|
|
370
389
|
}
|
|
390
|
+
board.renderEmpty(view, query, shownTotal);
|
|
371
391
|
renderPicker(view);
|
|
372
392
|
}
|
|
373
393
|
|
|
@@ -378,118 +398,33 @@ __RUNTIME__
|
|
|
378
398
|
picker.textContent = "";
|
|
379
399
|
for (var i = 0; i < view.lanes.length; i++) {
|
|
380
400
|
(function (lane) {
|
|
381
|
-
|
|
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));
|
|
382
405
|
item.type = "button";
|
|
383
|
-
item.setAttribute("
|
|
384
|
-
item.
|
|
385
|
-
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); });
|
|
386
408
|
picker.appendChild(item);
|
|
387
409
|
})(view.lanes[i]);
|
|
388
410
|
}
|
|
389
411
|
board.applyLane();
|
|
390
412
|
}
|
|
391
413
|
|
|
392
|
-
/**
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
var dt = document.getElementById("card-dialog-" + name + "-label");
|
|
407
|
-
var dd = document.getElementById("card-dialog-" + name);
|
|
408
|
-
if (dt) dt.hidden = !shown;
|
|
409
|
-
if (dd) dd.hidden = !shown;
|
|
410
|
-
return dd;
|
|
411
|
-
};
|
|
412
|
-
var setRow = function (name, shown, value, full) {
|
|
413
|
-
var dd = pair(name, shown);
|
|
414
|
-
dd.textContent = shown ? value : "";
|
|
415
|
-
if (full === undefined) dd.removeAttribute("title");
|
|
416
|
-
else dd.setAttribute("title", full);
|
|
417
|
-
};
|
|
418
|
-
|
|
419
|
-
var owner = document.getElementById("card-dialog-owner");
|
|
420
|
-
owner.textContent = "";
|
|
421
|
-
owner.appendChild(card.owner ? chip(card.owner.label, "accent") : document.createTextNode("unassigned"));
|
|
422
|
-
pair("owner", true);
|
|
423
|
-
|
|
424
|
-
var priority = pair("priority", card.priority !== "normal");
|
|
425
|
-
priority.textContent = "";
|
|
426
|
-
if (card.priority !== "normal") priority.appendChild(chip(card.priority));
|
|
427
|
-
|
|
428
|
-
setRow("updated", true, ago(card.updatedAt));
|
|
429
|
-
// How long the card has been in the lane it is in now, which updatedAt cannot say:
|
|
430
|
-
// a note filed against a blocked card moves that stamp without moving the card. Absent
|
|
431
|
-
// for a card written before the log existed, so the row takes its label with it.
|
|
432
|
-
setRow("lane", card.since !== undefined, card.status + " · " + ago(card.since));
|
|
433
|
-
setRow("by", true, card.by);
|
|
434
|
-
setRow("landed", card.landed !== undefined, card.landed ? card.landed.commit : "", card.landed ? card.landed.commit : undefined);
|
|
435
|
-
|
|
436
|
-
// The dialog and the card face say one thing, from one field: the ids it was linked
|
|
437
|
-
// to are the storage, and a reader is owed the lanes that make them a decision.
|
|
438
|
-
setRow("depends", card.waits !== undefined, card.waits || "", card.waits);
|
|
439
|
-
// The other side of the same link: what leaving this card undone is holding up, which
|
|
440
|
-
// is the one thing a card's own row cannot say.
|
|
441
|
-
setRow("dependents", card.dependents !== undefined, card.dependents || "", card.dependents);
|
|
442
|
-
|
|
443
|
-
// A long plan path elides on one line, and the whole of it is on the hover title and
|
|
444
|
-
// in the text, so it stays copyable: the reason to open a card is often its plan.
|
|
445
|
-
setRow("plan", Boolean(card.plan), card.plan || "", card.plan || undefined);
|
|
446
|
-
|
|
447
|
-
pair("paths", card.paths.length > 0);
|
|
448
|
-
var pathList = document.getElementById("card-dialog-path-list");
|
|
449
|
-
pathList.textContent = "";
|
|
450
|
-
for (var i = 0; i < card.paths.length; i++) {
|
|
451
|
-
var item = el("li", "");
|
|
452
|
-
var hint = chip(card.paths[i]);
|
|
453
|
-
hint.className += " " + N.dialogPath;
|
|
454
|
-
hint.title = card.paths[i];
|
|
455
|
-
item.appendChild(hint);
|
|
456
|
-
pathList.appendChild(item);
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
var notes = document.getElementById("card-dialog-notes");
|
|
460
|
-
notes.textContent = "";
|
|
461
|
-
for (var j = 0; j < card.notes.length; j++) {
|
|
462
|
-
var note = card.notes[j];
|
|
463
|
-
// The prose is the record and reads at body size; who wrote it and when is its
|
|
464
|
-
// footnote, so it follows the sentence instead of standing over it.
|
|
465
|
-
var row = el("li", N.dialogNote);
|
|
466
|
-
row.appendChild(el("p", N.dialogNoteText, note.text));
|
|
467
|
-
var foot = el("p", N.dialogNoteFoot);
|
|
468
|
-
foot.appendChild(el("span", N.dialogNoteBy, note.by));
|
|
469
|
-
foot.appendChild(el("span", N.dialogNoteAt, ago(note.at)));
|
|
470
|
-
row.appendChild(foot);
|
|
471
|
-
notes.appendChild(row);
|
|
472
|
-
}
|
|
473
|
-
var count = document.getElementById("card-dialog-note-count");
|
|
474
|
-
if (count) count.textContent = card.notes.length === 0 ? " · none" : " · " + card.notes.length;
|
|
475
|
-
var earlier = document.getElementById("card-dialog-earlier");
|
|
476
|
-
earlier.hidden = card.notesDropped === 0;
|
|
477
|
-
earlier.textContent = card.notesDropped === 0 ? "" : card.notesDropped + " earlier notes dropped";
|
|
478
|
-
|
|
479
|
-
// A modal whose title changes is not announced, so walking with j/k says where it
|
|
480
|
-
// landed: the position is counted over the cards as they are drawn, not as stored, so
|
|
481
|
-
// a filter or a folded lane cannot make the number disagree with the screen.
|
|
482
|
-
var where = document.getElementById("card-dialog-where");
|
|
483
|
-
if (where) {
|
|
484
|
-
var drawn = drawnCards();
|
|
485
|
-
var at = 0;
|
|
486
|
-
for (var k = 0; k < drawn.length; k++) {
|
|
487
|
-
if (drawn[k].getAttribute("data-card") === String(card.id)) at = k + 1;
|
|
488
|
-
}
|
|
489
|
-
// Opened from the Team tab there is no drawn board to count along, so the line
|
|
490
|
-
// names the card instead of claiming a position it cannot know.
|
|
491
|
-
where.textContent = drawn.length === 0 ? card.title : "card " + at + " of " + drawn.length + ": " + card.title;
|
|
492
|
-
}
|
|
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();
|
|
493
428
|
}
|
|
494
429
|
|
|
495
430
|
/** Paint a whole snapshot. */
|
|
@@ -498,22 +433,29 @@ __RUNTIME__
|
|
|
498
433
|
renderChrome(view);
|
|
499
434
|
renderLanes(view);
|
|
500
435
|
board.renderTeam(view);
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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();
|
|
506
444
|
};
|
|
507
445
|
|
|
508
446
|
board.el = el;
|
|
509
447
|
board.typing = typing;
|
|
510
448
|
board.chip = chip;
|
|
511
|
-
board.actionButton = actionButton;
|
|
512
449
|
board.ago = ago;
|
|
513
450
|
board.findCard = findCard;
|
|
514
451
|
board.drawnCards = drawnCards;
|
|
515
452
|
board.matches = matches;
|
|
516
|
-
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;
|
|
517
459
|
board.refreshAges = refreshAges;
|
|
518
460
|
board.plural = plural;
|
|
519
461
|
})();`;
|