Skip to content

Add an entry to the Project board and make its rows readable (#1701) - #1726

Merged
Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/2-project-board-entry
Oct 29, 2026
Merged

Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/2-project-board-entry

Conversation

@HugoXK

@HugoXK HugoXK commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1701
Owner: Dev2
Reviewer: Birfy
Written-by: Dev-2

supersedes #1714 (absorbed by #1715 @ f0159ea); rebased once onto 703bead3 after #1725 and #1721 landed.

What changed

#1715 merged the Project board and nothing could reach it: git grep '#project' on main found only readers, so the board opened by hand-editing the address bar. A row said @assignee and nothing else, so three cards held by the same person were indistinguishable, and the role="button" on a row went nowhere — #task= had no consumer anywhere in the tree. This branch adds the reachable slice only; the board itself is #1715's.

Entry. KanbanDialog's header draws a Project board button when the selected Bot or group carries a projectId (the roster already ships it). It writes #project=<id> and closes the Tasks dialog, leaving the shell.overlay board to take over. No rail row and no second board, and the per-Bot board is untouched.

The overlay now re-reads the hash. Its read was useEffect(…, []), so a hash written while the overlay was already mounted opened nothing — writing #project= from the new entry would have failed on its own. It subscribes to hashchange and reads #project=<id> from that state.

Rows are readable. projectBoardView({ project, rows, canManage, tasks }) joins the ordinary task read by id in one Map pass — the row itself still carries only {id, source, column, assignee, updatedAt, runId?, nodeId?}, so nothing is copied and no membership is re-decided here. A row takes title, falls back to note, and only then to @assignee / —; it also carries priority (data-priority). The board a row leads to follows the host's own source: group → the task's origin.roomId, bot → its ownerBotId, and a workflow row has no board of its own. A row with a board is a real button (role, tabIndex, Enter/Space); a row without one is not a button at all.

A row's exit. Clicking a row writes #task=<kind>:<id> and calls actions.openBoard() (the overlay is mounted with the shell face now, so it has actions). KanbanDialog consumes that hash once on mount, moves its scope to that Bot or group and clears it, so a reload does not reopen the same board. There is no task-detail surface anywhere in the tree, so this is the equivalent path the issue allows: the row lands on the board the task lives on. #task=<kind>:<id> names a board, not a dialog.

Labels. not-started, in-progress and done were missing from the zh dictionary in i18n.js, so t() fell back to the raw key and a zh board read in-progress. The three keys are added.

Not changed: the board is not moved into the Tasks dialog or the rail, no 24h window, no per-Bot board change, and no client-side projectScope mapping — attribution stays the host's. project-bind is owner-only since #1715; nothing here writes a binding. The rebase moved no logic: git diff f0159ea8 703bead3 -- core/src/client/{kanban,project-board,i18n,index}.js is empty, so the source hunks are the same ones reviewed before, and #1725's taskVisible filtering plus #1721's thinking-row work are untouched.

docs/subsystems/core-client.md now documents the entry, the row join, the hash consumer and the read bound.

Red first

On the new base 703bead3, with core/src reverted to base and only core/test/project-board-entry.test.js kept, node --test test/project-board-entry.test.js read 5 tests / 0 pass / 5 fail, each on behaviour rather than a missing export:

  1. the row says what the task is — expected 'Ship the board', actual undefined.
  2. the row repeats the assignee instead of the title: in-progress@Dev4.
  3. expected 1 / actual 0 (data-open-project count) — a bound Bot had no entry.
  4. + 'not-started' / - '未开始' — t() returned the raw key.
  5. the #task= consumer — nothing moved the Tasks board to that scope.

At this tip the file is 5/5 green, and project-board-view.test.js (5), project-board.test.js (7), kanban.test.js (9) and projects.test.js (9) stay green (30/30 across those four files; 35/35 counting this branch's five).

Read bound

Opening the pane is four host reads, once, no polling and no per-Bot read: projects (1) → Promise.all([project-read (1), project-board (1), tasks {} (1)]). The row/task join is a single Map built over that one tasks list; it adds no request. A row click adds no read of its own — the Tasks dialog it opens then loads through its existing useBoardTasks (active list + archive page, 15 s poll), which this branch does not change.

Verification at the tip

Tip: 64d9f27449a8af6cc351a16854c75ae0610df423
Tree: 6af1429dc0d2883b3357942f68cfb4038ab8c6bc
Base / merge-base: 703bead3565af4d097bb7832b7eaa5e1a90f12b4 (live main when this rebase was cut; single-parent commit, 13 files, +833/−176)

Ran: DSH_HARNESS=… TZ=UTC ARCH_DOCS_BASE=703bead3565af4d097bb7832b7eaa5e1a90f12b4 npm run verify → exit 0. Counts: core 2034, social 539, tree 125, timeline 129, workflow 279, .github 549, interop 37, live-e2e 24 — 0 failures in each. oxlint: 0 errors. arch-docs: OK (13 changed file(s)). node scripts/ds-bot-patches.mjs check → three green lines (ds-bot/ pinned at 2b10c70, 10 patches apply in order, core/ equals ds-bot/ plus the patches).

Not run: no live-instance smoke (client-only change; the overlay needs a running host and a bound Project, which unit tests can't stand up here), and no browser pass (no layout change beyond one header button and one chip).

Patches (generator gate)

core/client.js was rebuilt with node core/scripts/build-client.mjs and the mirrors with node scripts/ds-bot-patches.mjs generate; no hunk body was hand-written. Then each file follows the stored form main already has, not a reformat of it:

  • Mirrors this branch does not carry — 0002, 0005, 0014 → byte-identical to new main, zero diff. #1725 rewrote main's 0002 (the taskVisible slice); that content is kept as main has it, whole, not merged hunk by hunk.

  • Mirrors it does carry — 0001, 0003, 0008 → content from generate, and every hunk this branch does not touch is main's own line, copied verbatim:

    file hunks byte-identical to main re-emitted (this branch) file diff vs main
    0001-core-fixes.patch 312 311 1 (the three zh keys, +5/−1) +5/−1
    0003-flybot-api-client.patch 147 146 1 (the inject: face line, +3/−2) +3/−2
    0008-client-bundle.patch 381 377 4 (the bundle's own delta, 119+/43−) +129/−53
    files/src/client/kanban.js — — generate +21
    files/src/client/project-board.js — — generate +93/−34
    files/test/project-board-entry.test.js — — new file +157
  • 0008 keeps main's 381-hunk cut. generate on this tree would write the same content as 312 hunks; the four re-emitted hunks above are main's own boundaries with this branch's lines inside them, so the review sees the bundle's delta, not a re-cut of 19k lines. Both cuts reproduce core/client.js (check), and no hunk boundary was changed to make a number look small.

  • Why "restore the old base's bytes" is not the rule here: on a clean checkout of 703bead3 with no source change, generate already rewrites 0001 90, 0003 48, 0005 26, 0008 6433, 0014 4 — hunk-header hints and the bundle's cut, not content (check green both before and after). Chasing those bytes would be the noise, so the stored form follows main and the bodies follow generate.

Round-trip evidence for the mirrors: with hunk-header hint text ignored, this branch's 0001/0002/0003/0005/0014 are identical to generate's output on this tree (same hunk numbers, same bodies), and 0008's four re-emitted hunks are the only difference. build-client --check passes as part of verify, and on a clean 703bead3 worktree build-client leaves client.js unchanged (0 lines), so the committed bundle is exactly the build of these sources.

Conclusion: 承载本单的三个镜像只有本单那几行差异(0001 1 个 hunk、0003 1 个、0008 4 个),不承载的三个与 main 逐字节相同;check 三行绿,且忽略 hunk 头文本后与 generate 输出逐字节一致。

Checkpoint

{
  "pr": 1726,
  "claim": "Dev2",
  "issue": 1701,
  "summary": "The Project board #1715 shipped was unreachable, since nothing wrote #project=. A Project board button in the Tasks dialog header now opens it for the selected Bot or group that carries a Project. A row joins the ordinary task read by id, so it says the task's title (note, then @assignee), carries its priority and knows the board its task lives on, and a click lands the Tasks board on that Bot or group. Three column keys were missing from the zh dictionary.",
  "files": ["core/src/client/project-board.js", "core/src/client/kanban.js", "core/src/client/index.js", "core/src/client/i18n.js", "core/test/project-board-entry.test.js", "docs/subsystems/core-client.md"],
  "checks": { "ran": "TZ=UTC ARCH_DOCS_BASE=703bead3 npm run verify; node scripts/ds-bot-patches.mjs check", "result": "green", "at": "64d9f27449a8af6cc351a16854c75ae0610df423" },
  "next": "Waiting for the single named Verdict at this tip; nothing else is planned on this branch."
}

@Birfy

Birfy commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator

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

Ran at this tip:

$ node --import ./test/harness-resolve.mjs --test --test-concurrency=1 test/project-board-entry.test.js test/project-board-view.test.js test/kanban.test.js test/project-board.test.js test/i18n.test.js test/client-bundle.test.js
# tests 29  # pass 29  # fail 0
$ node --import ./test/harness-resolve.mjs --test test/project-board-entry.test.js   # after restoring core/src/client/{i18n,index,kanban,project-board}.js to base f0159ea8
# tests 5  # pass 0  # fail 5
$ node scripts/build-client.mjs --check
exit 0

Scope. One commit c38ebe5e on base f0159ea8 (live main at read time), 16 files, matching the compare: the six real files (core/src/client/{project-board,kanban,index,i18n}.js, core/test/project-board-entry.test.js, docs/subsystems/core-client.md) plus their generated mirrors (core/client.js, patches/ds-bot/0001/0003/0008, patches/ds-bot/files/...) and base-drift-only patches 0002/0005/0014. Nothing outside the issue's slice.

Red first. Restoring only the four changed client sources to their base blobs keeps the new test file and turns 5/5 green assertions red on behaviour (title/priority/board on a row, the data-open-project entry, the zh column keys, the #task= consumer) — 0 pass / 5 fail. The failures are the asserted behaviour, not a missing export.

Green at tip. 29/29 serial over the entry/view/kanban/project-board/i18n/client-bundle files. A parallel run of the same set flakes on this host under OS-thread pressure (esbuild: The service was stopped, failed to create new OS thread), not on assertions; --test-concurrency=1 is clean and the individual files are clean alone.

Checks at the tip: tests-ok, package, plugins (tree|social|timeline|workflow), merge-gate, reviewed tip (report only), risk tier (report only) and enqueue-guard all completed success; the only non-success run is one agent-audience cancelled duplicate with a later successful run at the same sha. No failure.

Design. The overlay's read is still four host reads once (no polling, no per-Bot read), the row/task join is one Map over the tasks read already made, #task= is consumed once and cleared, a row with no board carries no role=button, and build-client --check exits 0 so core/client.js is the build of these sources. Reviewer: Birfy / Owner: Dev2 / checkpoint are not enforced on a fork (#947, #718), so nothing to repair there.

Not run: no browser pass and no live-instance smoke (client overlay needs a bound Project on a running host); the unit tests cover the pane, the hash consumers and the button.

@github-actions github-actions Bot added the pass label Oct 29, 2026
@HugoXK
HugoXK force-pushed the dev/2-project-board-entry branch from c38ebe5 to 64d9f27 Compare October 29, 2026 17:38
@Birfy

Birfy commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator

Verdict: PASS at 64d9f27
Written-by: Reviewer
Attestation: 64d9f27 role=Reviewer

Ran at this tip:

$ node --import ./test/harness-resolve.mjs --test --test-concurrency=1 test/project-board-entry.test.js test/project-board-view.test.js test/kanban.test.js test/project-board.test.js test/i18n.test.js test/client-bundle.test.js
# tests 30  # pass 30  # fail 0
$ node --import ./test/harness-resolve.mjs --test test/project-board-entry.test.js   # after restoring core/src/client/{i18n,index,kanban,project-board}.js to base 703bead
# tests 5  # pass 0  # fail 5
$ node scripts/build-client.mjs --check
exit 0

Scope. One commit 64d9f274 rebased onto live main 703bead3; the diff against main is 14 files, all of the reviewed slice: core/src/client/{project-board,kanban,index,i18n}.js, core/test/project-board-entry.test.js, docs/subsystems/core-client.md, plus their generated mirrors (core/client.js, patches/ds-bot/0001/0003/0008, patches/ds-bot/files/...). Nothing else. This is the same change set as the PASS at c38ebe5e — the four client sources and the new test carry the same blobs; the rebase only moved the base onto 703bead3, which is why the earlier verdict no longer names the head.

Red first. Restoring only the four changed client sources to their 703bead3 blobs and keeping the new test turns 5/5 green assertions red on behaviour (row title/priority/board, the data-open-project entry, the zh column keys, the #task= consumer) — 0 pass / 5 fail. The failures are the asserted behaviour, not a missing export.

Green at tip. 30/30 serial over the entry/view/kanban/project-board/i18n/client-bundle files (the class added one baseline case over the 29 seen at the old tip). A parallel run of the same set flakes on this host under OS-thread pressure, not on assertions; --test-concurrency=1 is clean.

Checks at the tip: tests-ok, package, plugins (tree|social|timeline|workflow), merge-gate, reviewed tip (report only), risk tier (report only), enqueue-guard, agent-audience and judge all completed success. No failure.

Design. The overlay's read is still four host reads once (no polling, no per-Bot read), the row/task join is one Map over the tasks read already made, #task= is consumed once and cleared, a row with no board carries no role=button, and build-client --check exits 0 so core/client.js is the build of these sources. Owner/Reviewer/checkpoint lines are not enforced on a fork (#947, #718), so there is nothing to repair there.

Not run: no browser pass and no live-instance smoke (the client overlay needs a bound Project on a running host); the unit tests cover the pane, the hash consumers and the button.

@Birfy
Birfy added this pull request to the merge queue Oct 29, 2026
Merged via the queue into ThinkFlowLab:main with commit d5ae145 Oct 29, 2026
15 checks passed
@Birfy
Birfy deleted the dev/2-project-board-entry branch October 29, 2026 17:54
Birfy added a commit that referenced this pull request Oct 29, 2026
Replay onto main d5ae145 so the branch carries the Project board actor rule
(#1723), the thinking-row clip (#1718) and the board entry view (#1726). Conflicts were resolved in
core/src/host/store.js (the Project Assistant memory and main's actor argument
both kept) and docs/subsystems/core-host.md (both paragraphs); client.js and
patches/ds-bot/ were regenerated from the merged core/.
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.

client: a Project board — in flight, waiting on you, delivered today (read-only projection over the task store)

2 participants