Skip to content

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

Closed

Description

@HugoXK

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: 0217a22190204dfc8d1a523db2dca665
Branch: dev/2-blocked-card-age
Base: 5f11035
At: 2026-11-01T11:19:39Z

What

The board's Blocked group answers why a card is stuck and nothing else: one line per card, the inputRequest.question. The Waiting group right next to it answers how long (Dev · 2h) and is sorted oldest-first. So the two groups are not comparable — a card blocked for three days and one blocked for three minutes render identically, and the card that has been stuck longest is not the one on top.

The host already has the stamp: inputRequest.requestedAt is written by core/src/host/tasks.js:841 and :1498, and preserved across a re-ask at :833 ({ ...task.inputRequest, question }). blockedOf simply drops it.

Expected

  • blockedOf returns the same age shape waitingOf does: since = the requestedAt stamp or null, waitedMs = now - since or null. It keeps returning { id, reason, … } — reason stays the card's own line.
  • buildQueues sorts blocked oldest-first with the comparator waiting.sort already uses ((a.since ?? Infinity) - (b.since ?? Infinity) || a.id.localeCompare(b.id)); a card with no stamp sorts last.
  • The client's blocked row renders reason · 2d when waitedMs is a finite number and just reason when it is null.
  • A card with no requestedAt shows no duration. Do not fall back to updatedAt or createdAt: an invented stamp is worse than none.
  • The Waiting group's output stays byte-for-byte what it is today (existing assertions unchanged).

Reproduction at 5f110356

  • core/src/host/project-board.js:119 — waitingOf returns { id, role, since, waitedMs } (since = stampOf(task.createdAt) at :118).
  • core/src/host/project-board.js:135-138 — blockedOf returns { id, reason }; no stamp is read.
  • core/src/host/project-board.js:497 (const blocked = []), :507 (blockedOf call), :508 (blocked.push), :511 — the only sort in buildQueues is waiting.sort; blocked is returned unsorted.
  • core/src/client/project-board.js:314 — the blocked mapping keeps reason only, so the view never sees an age.
  • core/src/client/project-board.js:506 — the blocked note is h('span', { className: 'bt-project-reason' }, row.reason).
  • core/src/client/project-board.js:389-397 — waitedLabel already exists and is exported; nothing new is needed to format.

Acceptance (red first, one assertion per case, each able to go red on its own)

  1. blockedOf({ id: 't1', status: 'blocked', inputRequest: { id: 'q', question: 'why', requestedAt: NOW - 48 * 3600e3 } }, NOW) returns { id: 't1', reason: 'why', since: NOW - 48 * 3600e3, waitedMs: 172800000 }.
  2. Host: rt.projectBoardQueues('project-p1') with two blocked cards whose requestedAt are 1 h and 48 h ago → blocked.map(i => i.id) has the 48 h card first.
  3. Host: a blocked card whose inputRequest is { id, question } only → since === null && waitedMs === null (and no duration reaches the view).
  4. View: projectBoardView({ …, queues: { blocked: [{ id, reason, since, waitedMs: 172800000 }] } }) renders a .bt-project-reason whose text ends with · 2d; with waitedMs: null the text carries no duration at all.
  5. Regression: the existing waiting assertions stay green unchanged — core/test/project-board.test.js compares [id, reason] / [id, role] pairs, and core/test/project-board-view.test.js:259 compares [id, role], so added fields must not break them.

Scope

  • core/src/host/project-board.js — blockedOf (:135-138) and buildQueues (:497-512).
  • core/src/client/project-board.js — the blocked mapping (:314) and the blocked note (:506).
  • core/test/project-board.test.js — cases 1–3.
  • core/test/project-board-view.test.js — case 4.
  • core/client.js and the patch mirrors are generator output: the host mirror patches/ds-bot/files/src/host/project-board.js exists and is carried by patches/ds-bot/0002-flybot-api-host.patch (only 0002 matches that path); the client mirror patches/ds-bot/files/src/client/project-board.js exists and is carried by patches/ds-bot/0008-client-bundle.patch. Run generate; do not hand-edit either.

Not in scope

  • Changing what a blocked card's reason says, or reading the question text from anywhere else — the reason line stays exactly as it is.
  • Any change to the Waiting or In flight groups, or to how waitedLabel formats.
  • The 3-row cap on the details-panel scheduled-task list (separate).

Dedupe at 5f110356

  • Tree grep waitedMs → only the waiting side: host/project-board.js:119, client/project-board.js:312, :503, :940; blockedOf has none.
  • Tree grep blocked.sort → 0 hits (github code search on main, 0 results).
  • Issue search project board blocked card duration how long stuck → 13 results, all closed and none this gap: #1768 (introduced queues/leases/blocked cards — the parent feature), #1896 (a card that waited under a minute read 0m, waiting side), #1814, #1848, #1855, #1701, #1332, #1749, #1767, #1884.
  • Issue search blocked queue reason waitedMs sort oldest first → 1 result, #602 (merge queue hold, unrelated).

Notes for whoever picks this up

  • The existing fixture at core/test/project-board.test.js:454 has blocked cards without requestedAt; the new cases have to add the stamp themselves rather than rely on it.
  • #2026 and #2028 are in flight and both regenerate core/client.js / patches/ds-bot/0008-client-bundle.patch. Branch from live main right before starting, and rebase + rerun generate/check/canonical/build-client --check before opening the pull request, so the generated artifacts are computed on the base that will actually merge.

Activity

  1. added a commit that references this issue on Nov 1, 2026
    7c6883a
  2. HugoXK commented on Nov 1, 2026

    @HugoXK
    ContributorAuthor

    Release

    Written-by: FlyBot Lead
    Released: 0217a22190204dfc8d1a523db2dca665
    Reason: merged into main as 7c6883a
    At: 2026-11-01T12:21:52Z

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions