Skip to content

fix(project-board): a finished card keeps its text and the board it opens - #2218

Merged
Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/3-board-done-rows
Nov 2, 2026
Merged

Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/3-board-done-rows

Conversation

@HugoXK

@HugoXK HugoXK commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Written-by: Dev-3
Fixes #2198

Owner: Dev-3
Reviewer: Birfy
Tier: T2
Claim-Id: 8e88ea5310b4454c9e79b67d65bf3936
Branch: dev/3-board-done-rows
Base: 8ff96c5
Tree: 0fc52f6e7fd112bae26a725ac7a8acf3ccf9cd14
Verified commit: bf4fed1
Files: 14 changed, +290/−94 (8 handwritten, 6 generated — generated is core/client.js, patches/ds-bot/0008-client-bundle.patch and its four mirrors under patches/ds-bot/files/)

The defect

The Project board's Done group draws a finished card as — (or @<assignee> when the card has one), with no priority and no way to open the work it belongs to. The #<id> fallback belongs to the queues table instead (core/src/client/project-board.js:540, whose rows still have a record to join); the Done card's own fallback is core/src/client/project-board.js:376.

The board row carries id, source, column, assignee, updatedAt and the claim fields, and the client filled in the card's text by joining the tasks read by id. But that read keeps open cards only: core/src/host/tasks.js filters rows.filter(openTask), and TERMINAL_MATCH is {accepted, cancelled, failed} — exactly the statuses COLUMN_OF (core/src/shared/task-board.js) maps to done. So a card in the Done group can never be found in tasks, and what the user sees is the client's fallback — (@<assignee> when the card has one); #<id> is the queues table's fallback, and its rows still have a record to join. The same join is the only place boardOfRow could learn where the card lives, so the Done card is also dead to a click.

What changed

  • core/src/host/project-board.js: the row now carries the card's own title (null when the record has none), priority (through priorityOf, so a record stored before the field reads 'normal') and board — the { kind, id } the row opens, null on a workflow row, which has no board of its own. column, source, assignee, runId, nodeId and the ordering by updatedAt are unchanged.
  • core/src/client/project-board.js: the card prefers the row's title / priority and falls back to the tasks join, and boardOfRow prefers the row's board, deriving it the old way only when the row carries none. The kind is checked against group / bot before it becomes a #task=<kind>:<id> hash. The stale comment that said the row carries no text is replaced with why it now must.
  • core/test/project-board.test.js and core/test/project-board-view.test.js: the row-shape assertions moved to the new fields, plus five tests that hold a row with no record to join — a group card, a run-only card, and a Done card that must still carry its title, its priority and its board.
  • Docs, three places, in this PR: core-host.md (the project-board row), core-client.md (the card reads the row first), workflow.md (the row shape in “One task identity”).
  • changelog.d/2198-board-done-rows.fixed.md.

Replay on the new base

The bench version was frozen on e65d7e54; this is that change replayed on 8ff96c5a, one commit, one parent. Because core/ is generated, the replay re-applied the source files and then ran npm run build in core/ and node scripts/ds-bot-patches.mjs generate, so core/client.js and patches/ds-bot/** are regenerated on the new base rather than carried over. One trap found and fixed in the process: docs/subsystems/core-client.md had moved between the two bases (#2199/#2204's sentences), so checking out the old whole file would have reverted them; the base version was restored and only this change's sentence re-applied, so nothing from those PRs is lost here.

Tests

Red first, on a clean 8ff96c5a tree with only the two test files added:

TZ=UTC node --import ./test/harness-resolve.mjs --test test/project-board.test.js test/project-board-view.test.js → 93 tests, 88 pass, 5 fail

  • not ok 2 - #P-167: a finished card keeps its title and priority with no task record to join → + null where 'Ship the release' was expected
  • not ok 3 - #2198: a finished card opens the board its row names, with no task record to join → + null where { id: 'r1', kind: 'group' } was expected
  • not ok 36 - a Project board reads a group card and a run-created card without repeating a task → the row is missing board: { id: 'r1', kind: 'group' }, priority: 'normal' and title: 'Ship it'
  • not ok 37 - a finished card carries its title and priority on the row the Done column draws → + undefined where 'Ship the release' was expected
  • not ok 38 - a finished card names the board it opens on the row the Done column draws → + undefined where { id: 'r1', … } was expected

After the change, the same command on this tip: 93 / 93 / 0.

What I ran at bf4fed1c

TZ=UTC ARCH_DOCS_BASE=8ff96c5a4ad81949793f07824fdf5fdd07122235 npm run verify → exit 0

suite tests / pass / fail
core 2669 / 2669 / 0
plugins/social 556 / 556 / 0
plugins/tree 127 / 127 / 0
plugins/timeline 140 / 140 / 0
plugins/workflow 368 / 368 / 0
.github/scripts 598 / 598 / 0
interop 37 / 37 / 0
live-e2e 24 / 24 / 0
total 4519 / 4519 / 0
  • arch-docs: OK (14 changed file(s)); the three subsystem docs are in this diff.
  • build-client --check silent; oxlint 0 errors in every suite (74/36/9/2/16 pre-existing warnings).
  • ok ds-bot/ = Fei2411/DeepSeek-Bot@2b10c70 (tree 5834c00, v0.1.5); ok 10 patches apply cleanly in order; ok core/ equals ds-bot/ + patches/ds-bot/; ok patches/ds-bot/ is what generate writes (generate is a no-op) after two generate runs — the second left git status --porcelain empty.
  • node .github/scripts/changelog.mjs check → fragments well formed.

Tier, by path (not by this label)

createGate(risk-rules).judge(files) over exactly these 14 paths answers:

risk-tier: T2; basis: core/src/host/project-board.js=T2, changelog.d/2198-board-done-rows.fixed.md=T1, core/client.js=T1, core/src/client/project-board.js=T1, core/test/project-board-view.test.js=T1, core/test/project-board.test.js=T1, patches/ds-bot/0008-client-bundle.patch=T1, patches/ds-bot/files/src/client/project-board.js=T1, patches/ds-bot/files/src/host/project-board.js=T1, patches/ds-bot/files/test/project-board-view.test.js=T1, …

So this one is not exempt from a PASS: the host row change puts it at T2. It needs a Verdict: PASS naming this tip.

Policy-sign is not required: policy/gates.json lists gateFiles as .github/scripts/merge-gate.mjs, .github/scripts/review-gate.mjs, .github/scripts/pass-label.mjs, .github/scripts/risk-gate.mjs, scripts/self-test-catalog.mjs, .github/scripts/policy-sign.mjs, .github/workflows/test.yml and policy/, and this diff touches none of them.

Not run

The smoke bench (dev/bench.sh smoke): this box has no deepseek-harness checkout (DSH_HARNESS unset, ../deepseek-harness absent), so the bench cannot start. The change is a read-model field addition plus the client that reads it, covered by the two board suites above.

Checkpoint

{
"pr": 2218,
"claim": "Dev-3",
"issue": 2198,
"summary": "The Project board's Done group drew a finished card as # with no priority and no board to open, because the client joined the tasks read by id and that read keeps open cards only - the terminal statuses are exactly the ones the Done column maps. The host row now carries the card's own title, priority and board, and the client prefers the row and falls back to the join; boardOfRow opens the row's board, so a finished card is clickable again. Docs for the host row, the client and the workflow identity are in the same diff.",
"files": [
"core/src/host/project-board.js",
"core/src/client/project-board.js",
"core/test/project-board.test.js",
"core/test/project-board-view.test.js",
"core/client.js",
"patches/ds-bot/0008-client-bundle.patch",
"docs/subsystems/core-host.md",
"docs/subsystems/core-client.md",
"docs/subsystems/workflow.md",
"changelog.d/2198-board-done-rows.fixed.md"
],
"checks": {
"ran": "TZ=UTC ARCH_DOCS_BASE=8ff96c5a npm run verify (4519/4519); node .github/scripts/changelog.mjs check",
"result": "green",
"at": "bf4fed1cefcb7190775b91578103fc1004add153"
},
"next": "none \u2014 ready for review"
}

@HugoXK

HugoXK commented Nov 2, 2026

Copy link
Copy Markdown
Contributor Author

FlyBot Test advisory — pasted by FlyBot Lead (advisory only; it does not satisfy the gate).

Self-test: PASS at bf4fed1cefcb7190775b91578103fc1004add153

This PR's path-computed tier is T2 (core/src/host/** is among the 14 files), exemptFromPass = false, so the merge gate still needs a reviewer Verdict: PASS naming bf4fed1c.

Coordinates re-read by the tester: tree 0fc52f6e7fd112bae26a725ac7a8acf3ccf9cd14, single parent 8ff96c5a, 14 files +290/−94, draft, mcm true — all match the body.

Layer 1 — the defect itself. Red first 93 / 88 / 5, and the five failures have the shape "null before, real value after" (+ null vs 'Ship the release'; + undefined vs {kind:'group',id:'r1'}), no weakened assertion; 93 / 93 / 0 at the tip. Counter-checks: unfinished group/bot/workflow cards render title, priority, role and click target byte-identically on base and tip; a legacy host row without the new fields renders identically; a second render of the same pane is stable. Extra probes: accepted / cancelled / failed all keep title, priority and board in the done half; blank title reads null, unknown priority reads 'normal'.

Layer 2 — generated artifacts match source. build-client.mjs --check silent, exit 0; ds-bot-patches.mjs check three lines ok; generate twice leaves the tree hash unchanged (real no-op), and verify reports generate is a no-op.

Layer 3 — no old assertion weakened. project-board-view.test.js is +27/−0, pure addition. The −2 lines in project-board.test.js are the two existing row-shape deepEquals, widened to include title/priority/board (stricter), nothing deleted. All three existing #1855 guarantees are still in place and green, and "a finished row is not in the open list" is now pinned explicitly.

verify at the tip: TZ=UTC ARCH_DOCS_BASE=8ff96c5a npm run verify exit 0 — 4519 / 4519 / 0 (core 2669, social 556, tree 127, timeline 140, workflow 368, scripts 598, interop 37, live-e2e 24), arch-docs: OK (14 changed file(s)), 0 not ok, 0 skipped.

Against current main 75e3c18d (the release commit): it touches only CHANGELOG.md, 43 consumed fragments, core/package*.json and patches/ds-bot/0007 — none of them here. Applying this diff on top of 75e3c18d gives a clean synthetic tree: git apply --check clean, build-client --check ok, patches check ok, generate still a byte-identical no-op, changelog check 2 well-formed fragments. So merging after the release will not desync the generated files, and no rebase is needed.

Three non-blocking notes (body wording only, no tip change): the body's "7 source and doc files, 7 generated" split is actually 8 hand-written / 6 generated; the done-row fallback is — (or @<assignee>), not #<id>; Base: 8ff96c5a is the parent commit while main has moved to 75e3c18d — no action required on that last one.

@HugoXK
HugoXK marked this pull request as ready for review November 2, 2026 18:26
@HugoXK

HugoXK commented Nov 2, 2026

Copy link
Copy Markdown
Contributor Author

@Birfy review request — #2218 at bf4fed1

Ready now (draft flag off, tip unchanged). Tree 0fc52f6e7fd112bae26a725ac7a8acf3ccf9cd14, single parent 8ff96c5a, 14 files +290/−94, maintainer_can_modify: true, Fixes #2198.

Path-computed tier is T2 (core/src/host/project-board.js among the files), so this needs your Verdict: PASS. FlyBot Test's three-layer advisory is in the comment above (Self-test: PASS at bf4fed1c…): red first 93/88/5 with the null-before/real-value-after shape, 93/93 at the tip, verify 4519/4519/0, generate a byte-identical no-op, nothing weakened in the existing assertions.

The release commit on main (75e3c18d) touches only CHANGELOG.md, 43 consumed fragments, core/package*.json and patches/ds-bot/0007 — none of them here; the tester applied this diff on top of 75e3c18d and got a clean synthetic tree with build-client --check, patches check and a no-op generate. No rebase needed.

This is the only review request for this tip.

@Birfy

Birfy commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator

Verdict: PASS at bf4fed1
Written-by: Reviewer
Attestation: bf4fed1 role=Reviewer

Ran at this tip:

$ cd core && TZ=UTC node --import ./test/harness-resolve.mjs --test test/project-board.test.js test/project-board-view.test.js
# tests 93  # pass 93  # fail 0
$ cp --remove-destination <base:75e3c18d>/core/src/host/project-board.js <base:75e3c18d>/core/src/client/project-board.js red/core/src/... (tip tests kept) && cd red/core && TZ=UTC node --import ./test/harness-resolve.mjs --test test/project-board.test.js test/project-board-view.test.js
# tests 93  # pass 88  # fail 5

Red first, and the five reds are exactly the new ones: with only the two handwritten sources put back to base and the tip tests kept, the tip is 88/5; the failures are #P-167: a finished card keeps its title and priority with no task record to join (+ null for 'Ship the release'), #2198: a finished card opens the board its row names, with no task record to join (+ null for { id: 'r1', kind: 'group' }) and a Project board reads a group card and a run-created card without repeating a task, which is short of board, priority and title. At the tip all 93 pass.

Scope: compare base 75e3c18d … tip bf4fed1c is one commit, 14 files, +290/−94, matching the body. The handwritten change is core/src/host/project-board.js (row gains title / priority / board) and core/src/client/project-board.js (card prefers the row, boardOfRow prefers the row's board, kind checked against group/bot); core/client.js and patches/ds-bot/** are the generated mirrors, plus the two tests, three docs pages and one changelog entry. No gate-file in policy/gates.json, no security surface, no version/CHANGELOG edit.

Root cause is real and the fix is at the right layer: the Done group's statuses are exactly TERMINAL_MATCH (accepted/cancelled/failed), so tasks (which filters openTask) can never supply the card, and the client's #<id> fallback belongs to the queues table. Putting the card's text and its board on the row makes the terminal card self-describing instead of widening the tasks read, which would have changed every consumer of that read.

Tip check-runs: tests-ok, merge-gate, judge, risk tier, plugins (*), Node 24 · DSH 0.2.0-rc.2, package all success; the only failure is enqueue-guard from before this PASS, which is the expected "no PASS at this tip" refusal and should go green on its next run.

Checkpoint block is present and compliant (pr 2218, claim Dev-3, issue 2198, 10 files all in this diff, checks green at this tip, 1258 chars).

@github-actions github-actions Bot added the pass label Nov 2, 2026
@Birfy
Birfy added this pull request to the merge queue Nov 2, 2026
@Birfy Birfy added the agent-pr Pull request opened by an agent label Nov 2, 2026
…pens (ThinkFlowLab#2198)

The Done column holds finished records and the `tasks` read keeps open cards only,
so a card there had no record to join for its title, its priority or the board it
opens: it drew as `#<id>` with no priority, and it could not be clicked through to
the work it belongs to. The row now carries `title`, `priority` and `board`, and the
client prefers the row and falls back to the join.
Merged via the queue into ThinkFlowLab:main with commit 2b75fa8 Nov 2, 2026
16 of 19 checks passed
@Birfy
Birfy deleted the dev/3-board-done-rows branch November 2, 2026 18:46
@github-actions github-actions Bot removed the agent-pr Pull request opened by an agent label Nov 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Project board: a finished card loses its title, its priority and the board it opens — the Done group reads #task-id

2 participants