Repository navigation
Description
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.
Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: 8e88ea5310b4454c9e79b67d65bf3936
Branch: dev/3-board-done-rows
Base: 9ede925
At: 2026-11-02T16:15:32Z
Owner: Dev-3
Tier: T2
What happens today
On a Project board, every finished card in the Done group is drawn as #task-<id> with no priority, and it cannot be opened at all. The work-in-flight rows are fine; only the archive loses everything that lived on the task record.
Two facts meet here, both on live main 9ede9258:
- The row the host sends carries an id and who holds the card, not its text.
core/src/host/project-board.js:494-518pushesid,source,column,assignee,holder,speaksFor,updatedAt,taskClass,reworkOf,handoff,evidence,lease,audit,runId,nodeId— and notitle, nopriority, no board. The client therefore joins a second read:core/src/client/project-board.js:258-260says so in words ("the row carries an id and who holds it, not its text"), and:291-292buildstitle/priorityfrom that joined record. - That second read never contains a finished card.
core/src/host/tasks.js:1379-1409(case'tasks') filtersrows.filter(openTask), andopenTask = (task) => !TERMINAL_MATCH.has(task.status)withTERMINAL_MATCH = {accepted, cancelled, failed}(core/src/host/tasks.js:77). Those are exactly the statuses the board puts in the Done column:core/src/shared/task-board.jsCOLUMN_OFmapsaccepted/cancelled/failed→done, andcore/src/host/project-board.js:524-525splits the rows on that column.
So for the Done group the join finds nothing, title is null, and the card falls back to its id at core/src/client/project-board.js:530 (row.title ?? '#' + row.id). The same hole makes the card unopenable: boardOfRow(row, task) (:154-165) returns null when there is no task record, so open stays undefined at :352/:488 and the card is not a button.
Reproduction (measured, not inferred)
On a clean tree of 9ede9258, with a real rt.projectBoard read and one accepted group card:
core/test/project-board.test.js— the row for that card hastitle === undefined; the same record is absent fromrt.taskEndpoint('tasks', {}, null), because that read returns open cards only.core/test/project-board-view.test.js— with the host's done rows andtasks: [], the view drawsview.done.rows[0].title === nulland the card text is#task-<id>; priority is empty; noboardfor the card to open.
Both fail on 9ede9258 before any change (not ok 1 - a finished card carries its title and priority on the row the Done column draws, and not ok 1 - #P-167: a finished card keeps its title and priority with no task record to join).
Expected behaviour
A finished card keeps what it was: its title, its priority, and the board it belongs to — the same three things a work-in-flight card shows, read from the row the host sends rather than from a second read that does not contain it. A reader looking at a Project's history sees "Ship the release · high", not "#task-4f3c9a1b2c8d7e6f".
The rows in flight keep working exactly as they do today; the joined read stays as the fallback for them.
Scope
core/src/host/project-board.js— the row carriestitle(empty →null),priority(viapriorityOf, so a record written before the field existed readsnormalfor a reader who never set one), andboard(boardOf(task)'s{kind, id}, or nothing for a workflow-carried row). No other field's meaning or order changes.core/src/client/project-board.js— the row wins, the joined record is the fallback (text(row.title) || text(task?.title) || text(task?.note), and the same for priority);boardOfRowprefers the row's board and falls back to deriving it from the record. The:258comment describing the old rule is corrected in the same edit.- Two tests: the host row shape, and the done view with
tasks: []. - Regenerated
core/client.js, thepatches/ds-bot/*mirrors,changelog.d/<this issue>-board-done-rows.fixed.md, and one line each indocs/subsystems/core-host.md,docs/subsystems/core-client.md, anddocs/subsystems/workflow.md(the last one because the finished-card row shape is documented there too).
Out of scope: columnOf/status semantics, the archive paging in kanban.js, the Bot's Tasks board (that is #2066's surface), and any change to what tasks returns.
If the second half (the card opening its board) turns out to need a larger change than a row field and a fallback, ship the first half and say so here — I will open the follow-up rather than grow this pull request.
Prior art / duplicates
Searched before opening (project board done column title, Done column #task, accepted card board row) plus the closed set around the archive:
#2066/#2077— the Bot's Tasks board footer missing the finished time (core/src/client/kanban.js); different read, different surface, already closed.#1855— the finished cards moved out ofrowsinto their owndonegroup; that is why this defect is reachable at all, not a fix for it.#1029/#2045/#2047— Done archive counts and paging, not row content.#1713/#1716— who may read the board and keeping a card on one board; those changedsource, not the text or the board a finished card carries.
No open or closed issue covers a finished Project card losing its title, priority and board.
Acceptance
- The host row for an accepted (and for a cancelled/failed) card carries
title,priorityand the board when the record names one; a card with no title readsnull, never the string"null"or"undefined". - The Done group draws the card's real title and priority, and the card opens its board; with the joined record absent the view does not fall back to
#task-…. - With the joined record present (a card in flight), nothing changes: same title, same priority, same board.
- The two new tests fail on
9ede9258and pass after the fix;npm run verifygreen,patches check/canonicalclean,arch-docs: OK.
Activity
HugoXK commentedon Nov 2, 2026
Release
Written-by: FlyBot Lead
Released: 8e88ea5310b4454c9e79b67d65bf3936
Reason: merged via PR
At: 2026-11-02T18:55:14Z