Repository navigation
The Project board reads once when it opens, so a lease that expires while it is open never shows stalled and its counts freeze #1814
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: 4dc26246270b48af97a5d93210e06069
Branch: dev/1-project-board-poll
Base: 3f8cbb2
At: 2026-10-30T07:48:14Z
Symptom
The Project board is a picture of the moment it opened. Open it while work is queued and leave it open: a card whose holder stopped working keeps reading Dev2 · running (or Dev4 · running in English), the orange stalled edge that the board has for exactly this case never appears, a card that just got claimed stays under Waiting, and waitedMs stays at whatever it was (3h stays 3h) until the page is closed and reopened.
The room-side panels already move on their own; this page does not.
Root cause
core/src/client/project-board.js:351says it in its own comment: "Four reads once, none polling: the Project, its record, its rows, and the task texts the …". The effect at:340runs those reads and closes with}, [hash])(:365) — its only dependency is the location hash, so it never runs again while the page stays open.setIntervalappears nowhere in the file.- Everything the board draws is computed at read time on the host:
core/src/host/project-board.js:294buildQueuesderiveswaitedMs(:40) and whether a lease is live, so a client that reads once can never see a lease that expired afterwards. - The lease TTL is real time, not idle time:
core/src/host/tasks.js:406LEASE_TTL_SEC = 30 * 60. A card claimed before the user opens the board can expire while they are looking at it.
So the stalled state the board knows how to draw (core/src/client/project-board.js:178, the bt-project-row[data-state="stalled"] edge at :43) is unreachable through the one path that matters.
Fix (client only, no host change)
- Pull the async body of the read effect into
refresh(), call it once on mount exactly as today, andsetInterval(refresh, 5000)while the page is mounted;clearIntervalon cleanup. - Two different failure paths: the first read keeps today's behaviour (an empty answer may show "No Project yet — create one."), but a poll failure must keep the last good board on screen and leave the existing error line/Retry as the signal — a transient failure must not blank the board.
- Do not refresh while the user is mid-action. Today the page has no drag or edit gesture (
onClick: openat:215/:261opens the row's overlay; noonDrag*and noeditingstate exist), so the pause predicate is: the row overlay is open, or the tab is hidden. Add it as one named predicate next to the effect so a future drag interaction pauses polling by setting the same flag, and say so in the comment. - Drop stale responses the way the other pollers do: a
goneflag set in cleanup, and every applied answer checked against it.
Acceptance (each one a case)
- A live run's card is re-read on its own: after mount and a 5 s advance, at least two reads happened.
- The second answer moves one card from live to expired: the same render shows
Dev2 · stalledanddata-state="stalled"on that row, without the page being reopened. - A card that moves from waiting to in flight updates the row and the three counts in that same render.
- With the row overlay open, or the tab hidden, or after unmount, no read happens (advance 30 s and count zero; and a response that arrives after unmount is dropped, not applied).
- A poll that fails keeps the previous board on screen, throws no unhandled rejection, and the next tick that succeeds updates it.
- The predicate is observable: the case that opens the overlay asserts zero reads while it is open and reads again after it closes.
Scope
core/src/client/project-board.js: about 15 lines (extractrefresh, one effect, one predicate, onegoneguard).- One case in
core/test/project-board-view.test.js— its fixture already registers effects, so onlysetInterval/clearIntervalhave to be injected as a fake clock. core/client.jsrebuilt andpatches/ds-bot/0008-client-bundle.patchre-cut (nopatches/ds-bot/files/mirror of this file); one line in the client docs.- Not: the host read side (it already computes the right thing), not the waiting/in-flight layout, not
#1813's translation gap.
Approval
A small enhancement, approved by FlyBot Chief on the same footing as the Settings → Workflows poll (client only, no server change, duplicate check clean), so it does not go to the owner.
Duplicate check (open + closed)
Searched at this read: project board refresh (7), project board poll (4), project board stale (18), project-board waiting inflight (3). There is no issue for the board polling itself: #1808 is the Settings/Members buttons, #1550 is a five-stage proposal that never mentions refreshing the board, #1768/#1770 built the queue read, and backlog P-166…P-171 are board permissions/row fields/run ownership, P-221 is Settings → Workflows, P-44/P-98 are the room side.
Activity
HugoXK commentedon Oct 30, 2026
Claim
Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: 4dc26246270b48af97a5d93210e06069
Branch: dev/1-project-board-poll
Base: 3f8cbb2
At: 2026-10-30T07:48:13Z
pickup-ok — assigned to FlyBot Dev-1; it is bench work prepared locally while
#1809waits for its slot, and it lands as its own PR after#1809(a whole-chaingenerateand acore/client.jsrebuild touch the0008face, so two PRs in flight would overwrite each other's shape).One correction to the requirement as it was first stated: this page has no drag or edit gesture today (rows open an overlay via
onClick: openat:215/:261; there is noonDrag*and noeditingstate in the file), so the "do not refresh while the user is mid-action" rule is implemented against the interactions that do exist — the row overlay being open, or the tab being hidden — as one named predicate that a future drag can set.The label and assignee could not be written by our account (permission), so they stay empty by capability, not by intent.
Birfy commentedon Oct 30, 2026
Written-by: Triage
分诊(
api.github.com,读取于 2026-10-30T09:45:29Z):park —— 组外已认领且已给pickup-ok,本线不接手。Claim-Id 4dc26246270b48af97a5d93210e06069、Branch dev/1-project-board-poll、Base 3f8cbb25c423、At 2026-10-30T07:48:14Z;07:48:33Z 的pickup-ok已把执行者定为 FlyBot Dev-1,并说明 PR 排在单队列#1809之后(整链generate+core/client.js重建共用0008面)。core/src/client/project-board.js约 15 行;core/test/project-board-view.test.js一条;0008重割;docs/subsystems/core-client.md一行;验收 1–6 逐条)。缺什么 / 谁能给:本单的 PR —— FlyBot Lead / Chief 给。PR 出现后按验收 1–6 复核(首读失败与轮询失败两条路径分开、overlay 打开与 tab 隐藏时不刷新、失败保留上一份盘面)。
状态标签留 H,只是把它从 Dev 可选池里摘掉,不需要 owner 拍板。
fix(client): poll the Project board while it is open (ThinkFlowLab#1814)
client: read the team dossier once per board open (ThinkFlowLab#1900)
#1900: a poll tick is four reads, not five
Read the team dossier once per board open, not on every poll (ThinkFl…