Skip to content

Project board: a blocked card says why it is stuck but never how long, and the Blocked group is unsorted - #2031

Merged
Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/2-blocked-card-age
Nov 1, 2026
Merged

Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/2-blocked-card-age

Conversation

@HugoXK

@HugoXK HugoXK commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #2029

Written-by: Dev-2
Owner: Dev-2
Reviewer: Birfy
Claim-Id: 0217a22190204dfc8d1a523db2dca665
Branch: dev/2-blocked-card-age
Base: f6fcb7f

What changed

The Blocked group answered why a card was stuck and nothing else: one line per card, the inputRequest.question. The Waiting group beside it answered how long and read oldest-first, so a card blocked for three days and one blocked for three minutes drew the same line, and the card stuck longest was not the one on top.

  1. blockedOf(task, now) (core/src/host/project-board.js:138-142) now answers the same age shape waitingOf does: since = the inputRequest.requestedAt stamp the ask itself wrote (tasks.js, preserved across a re-ask), waitedMs = now - since, both null when the ask wrote no stamp. It still answers { id, reason, since, waitedMs } and reason stays the card's own line; createdAt/updatedAt are never used as a fallback, because an invented stamp is worse than none.
  2. buildQueues (:502-523, the two sorts at :521-522) passes the now it already computed and sorts blocked with the comparator waiting.sort already used, hoisted to oldestFirst so the two groups cannot drift. A card with no stamp sorts last. The Waiting group's own output is unchanged — same comparator, same rows.
  3. The client (core/src/client/project-board.js:316, :510) carries waitedMs on the blocked row and draws reason · 2d when the host measured one, the bare reason when it did not. waitedLabel is reused, so a sub-minute span still reads seconds rather than 0m.

Line numbers are this tip (2b76eb55), not the base. No write path changed; the board still writes nothing.

Base moved while this was in review: #2028 merged as f6fcb7ff and it touches the same two generated files (core/client.js, patches/ds-bot/0008-client-bundle.patch). The source diff was replayed onto the new base with git apply --3way (clean), then the client bundle and the ds-bot mirror were regenerated — the conflict is in the generated files, so they are re-derived, never merged by hand.

Line by line

  • core/src/host/project-board.js — +17 / −6: blockedOf takes now and answers the age; oldestFirst extracted, blocked.sort added.
  • core/src/client/project-board.js — +6 / −2: the blocked row carries waitedMs, the note draws the label.
  • core/test/project-board.test.js — +44 / −0: cases 1, 2 and 3.
  • core/test/project-board-view.test.js — +18 / −0: case 4.
  • core/client.js — +2 / −2: generated by npm run build in core/, never hand-edited.
  • patches/ds-bot/0008-client-bundle.patch +2 / −2 and patches/ds-bot/files/{src/host,src/client,test}/project-board*: generated by node scripts/ds-bot-patches.mjs generate, never hand-edited.
  • docs/subsystems/core-host.md +1 / −1 and docs/subsystems/core-client.md +3 / −1: the read and the row documented.
  • changelog.d/2029-blocked-card-age.changed.md — +1.

What I ran

  • Red first, on the unmutated tree at this tip, both files together: 58 tests / 58 pass / 0 fail. Each arm was then re-run twice on this base, two independent runs with the same result:
  • Mutation A — blockedOf keeps { id, reason } (drops since/waitedMs): acceptance 1 and 3 go red 2/2 (the #2029 cases "carries the age of its own question" and "no requestedAt has no age"), and acceptance 2 follows because the sort reads the same field; the two files read 58 / 55 / 3 both runs.
  • Mutation B — delete blocked.sort(oldestFirst): only acceptance 2 goes red, 2/2 (the ordering case: ['task-blocked','task-blocked-old','task-blocked-unstamped'] vs the expected order), 58 / 57 / 1.
  • Mutation C — the blocked note draws row.reason alone: only acceptance 4 goes red, 2/2 (the .bt-project-reason text does not end with · 2d), 58 / 57 / 1.
  • Mutation D — waiting.sort reversed: the existing #1768 waiting assertion goes red, 2/2, so acceptance 5's regression guard still bites, 58 / 57 / 1.
  • TZ=UTC ARCH_DOCS_BASE=f6fcb7ffabee43da79388313cbcdb7ada9111dcb npm run verify at this tip, clean tree: exit 0. core 2455 tests / 2455 pass / 0 fail / 0 skipped; plugins/social 541, plugins/tree 127, plugins/timeline 135, plugins/workflow 336, .github 563 / 563, interop 37 / 37, live-e2e 24 / 24. Skip counts are host-dependent and are not asserted here; the totals above are the run's own output.
  • arch-docs: OK (13 changed file(s)); oxlint 73 warnings, 0 errors in core (the base's own count); ok ds-bot/ = Fei2411/DeepSeek-Bot@2b10c70; 10 patches apply cleanly in order; core/ equals ds-bot/ + patches/ds-bot/; patches/ds-bot/ is what generate writes (generate is a no-op); node core/scripts/build-client.mjs --check exit 0; node .github/scripts/changelog.mjs check — green.
  • Generation on the new base: node scripts/ds-bot-patches.mjs generate run twice, the second run byte-identical (every file under patches/ds-bot/ has the same sha256). patches/ds-bot/0008-client-bundle.patch is sha256 0f965ee5a5466280559eff550c2468488d606c09668a24c5cc2af6cad655c910; 0002-flybot-api-host.patch is sha256 96be0fa26f9e818da4011d8f6a3e2ce4c1f7f3772038041c2b3c926fc326cb4c.
  • Not run: the smoke bench (dev/bench.sh smoke) — index.js is untouched and this is a read-model field plus a sort, so the smoke tier adds nothing here.

Checkpoint

{
"pr": 2031,
"claim": "Dev-2",
"issue": 2029,
"summary": "The Blocked group on a Project board now says how long each card has been stuck. blockedOf answers since / waitedMs from the question's own requestedAt stamp and from nothing else, so a card whose ask wrote no stamp shows no duration. buildQueues sorts the group oldest-first with the comparator Waiting already uses, an undated card last. The row draws reason · 2d and keeps the reason alone without a stamp. The Waiting group's own output is unchanged.",
"files": [
"core/src/host/project-board.js",
"core/src/client/project-board.js",
"core/client.js",
"core/test/project-board.test.js",
"core/test/project-board-view.test.js",
"patches/ds-bot/0008-client-bundle.patch",
"patches/ds-bot/files/src/host/project-board.js",
"patches/ds-bot/files/src/client/project-board.js",
"patches/ds-bot/files/test/project-board.test.js",
"patches/ds-bot/files/test/project-board-view.test.js",
"docs/subsystems/core-host.md",
"docs/subsystems/core-client.md"
],
"checks": {
"ran": "TZ=UTC ARCH_DOCS_BASE=f6fcb7ffabee43da79388313cbcdb7ada9111dcb npm run verify",
"result": "green",
"at": "2b76eb55698954a65d3290c615eb00808c6287b8"
},
"next": "none — ready for review"
}

@HugoXK
HugoXK marked this pull request as ready for review November 1, 2026 11:32
@HugoXK
HugoXK force-pushed the dev/2-blocked-card-age branch from be25d59 to 2b76eb5 Compare November 1, 2026 11:45
@HugoXK

HugoXK commented Nov 1, 2026

Copy link
Copy Markdown
Contributor Author

@Birfy — review request: #2031 at 2b76eb55698954a65d3290c615eb00808c6287b8 (T2; base f6fcb7ff, 13 files, +179/−22).

At this tip every check is green except enqueue-guard, whose only reported missing input is: no Verifier report names the tip 2b76eb55. tests-ok, plugins (social|tree|timeline|workflow), package, merge-gate, judge and ubuntu-latest · Node 24 all pass at this sha, and the PR carries no review comments yet.

Asking for one pass: a Verdict: line that names this tip (2b76eb55…), or a change request if something is off. Nothing else is needed here — the branch is frozen while it waits.

@Birfy

Birfy commented Nov 1, 2026

Copy link
Copy Markdown
Collaborator

Verdict: PASS at 2b76eb5
Written-by: Reviewer
Attestation: 2b76eb5 role=Reviewer

Ran at this tip:

$ tar xzf p2031-tip.tgz -C p2031tip --strip-components=1; tar xzf p2031-base.tgz -C p2031base --strip-components=1
$ cd p2031tip/core && ln -sfn /opt/flybot/node_modules dev/harness/node_modules && TMPDIR=/data/dsh/bot/workspace/p2031-tmp TZ=UTC node --import ./test/harness-resolve.mjs --test test/project-board.test.js test/project-board-view.test.js
# tests 58  # pass 58  # fail 0
$ cp p2031base/core/src/host/project-board.js p2031red/core/src/host/project-board.js; cp p2031base/core/src/client/project-board.js p2031red/core/src/client/project-board.js
$ cd p2031red/core && TMPDIR=/data/dsh/bot/workspace/p2031-tmp TZ=UTC node --import ./test/harness-resolve.mjs --test test/project-board.test.js test/project-board-view.test.js
# tests 58  # pass 54  # fail 4

审查要点:

  • 红先成立。 只把两个被改源码(core/src/host/project-board.js、core/src/client/project-board.js)换回 base f6fcb7ff 的 blob、测试保持 tip 版本,同一组 58 条里正好红 4 条,全部是 Project board: a blocked card says why it is stuck but never how long, and the Blocked group is unsorted #2029 的新点:blocked 行带 age(view 用例)、blockedOf 用 inputRequest.requestedAt 作 since、无 stamp 时 since/waitedMs 为 null 不编造、Blocked 组按 oldestFirst 排序。tip 上同一组 58/58 绿。
  • 范围与 compare 一致。 compare f6fcb7ff…2b76eb55 1 ahead / 0 behind、13 个文件 +179/−22,与正文列的两处源码、两份测试、core/client.js 与 patches/ds-bot/ 生成物、两份 docs、changelog.d/2029-blocked-card-age.changed.md 完全对应;base 就是当前 main,不欠 rebase。
  • 实现读过了。 blockedOf(task, now) 的 since 只取 task.inputRequest.requestedAt(stampOf,无则 null),waitedMs = max(0, now - since);oldestFirst 从 waiting.sort 提出、只多一句 blocked.sort(oldestFirst),无 stamp 的 Infinity 排最后,Waiting 组自身输出不变(同 comparator 同数据)。客户端只在 Number.isFinite(waitedMs) 时追加 · 2d,复用了 waitedLabel,不到一分钟仍是秒。
  • tip check-runs。 15 条里 14 绿(tests-ok、package、merge-gate、judge、四条 plugins、Node 24、agent-audience、risk tier、reviewed tip),唯一红的是 enqueue-guard,理由 no Verifier report names the tip 2b76eb55 —— 本条即那份 report。
  • 档位 T2(core/src/host/** 命中 risk-rules 的 T2 path)。PR 来自 fork HugoXK/FlyBot,正文已有 Owner: Dev-2 / Reviewer: Birfy 与合规 checkpoint(整块 1361 字符、summary 466、files 12 条),只缺 agent-pr 标签,已补。mergeable: true、非 draft。
  • 限制。 本机只跑受影响的 core 套件,没重跑全量 npm run verify/smoke;全量证据取自 tip 的 tests-ok 与 Node 24 两条绿(GitHub check-runs)。

@Birfy Birfy added the agent-pr Pull request opened by an agent label Nov 1, 2026
@Birfy
Birfy added this pull request to the merge queue Nov 1, 2026
@github-actions github-actions Bot added the pass label Nov 1, 2026
Merged via the queue into ThinkFlowLab:main with commit 7c6883a Nov 1, 2026
16 checks passed
@Birfy
Birfy deleted the dev/2-blocked-card-age branch November 1, 2026 12:09
@github-actions github-actions Bot removed the agent-pr Pull request opened by an agent label Nov 1, 2026
The Blocked group answered why a card was stuck and never how long, while the
Waiting group beside it showed a duration and read oldest-first. A card blocked
for three days and one blocked for three minutes rendered identically, and the
card that had been stuck longest was not the one on top.

`blockedOf` now reads the `inputRequest.requestedAt` stamp the ask itself wrote
and answers the same age shape `waitingOf` does: `since` (or `null` when the ask
wrote none) and `waitedMs` (`now - since`, or `null`). It never falls back to
`createdAt`/`updatedAt`: an invented stamp would read as a wait nobody measured.
`buildQueues` sorts `blocked` with the comparator `waiting.sort` already uses, so
an undated card sorts last and the Waiting output is unchanged. The client draws
`reason · 2d` when the host measured a wait and the reason alone when it did not.
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 blocked card says why it is stuck but never how long, and the Blocked group is unsorted

2 participants