Dashboard: the pull request is true, the card names the act, and the work that wants Michal comes first #415
No reviewers
Labels
No labels
bug
contract
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
iterative-works/iw-cli!415
Loading…
Reference in a new issue
No description provided.
Delete branch "dashboard-control-plane"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Three slices of the
dashboard-control-planeeffort, shipped as one batch from oneeffort worktree. Each was closed on Michal's own read against its frozen card.
The thread through them: the dashboard tells the truth about what a worktree wants,
says it in the worktree's own words, and puts the work that wants him first.
Slice 02 — PR state stays current
Goal. I glance at the dashboard to decide what to pick up next, and the worktrees
showing an open pull request are exactly the ones that really have one.
Fit test. Merge a pull request from the forge web interface — not through
iw—and leave the dashboard alone. Within about two minutes the card stops saying open.
Passed 2026-08-07.
gladys-curation#672 merged in the Forgejo web interface at13:51:36Z; the poller read it at 13:52:25Z. 49 seconds, against a two-minute target.
What it took:
ForgeType.resolve— the path./iw phase-pruses.detectPRToolpickedghwheneverghwas installed, so every GitLab worktreereported no pull request and Forgejo had no branch at all. 0 of 61 worktrees carried
a cached pull request; after the repair, 61 of 61 agree with their forge.
PullRequestPollerruns two lanes on a daemon thread: an open pull request every2 minutes, a worktree with none known every 15. Merged and closed are terminal.
state has gone unconfirmed rather than presenting a guess as current.
RefreshThrottle.claimresolves check-and-record in one step, so two racing callerscannot both pass.
Slice 03 — The card names the pending act
Goal. A card names the one act pending on me — review this pull request, launch the
next phase, finish the workflow, read this analysis — and when I go and do it, it is the
right act.
Fit test. Walk the dashboard after a break and work through every flagged worktree.
For each, the act named is the act performed.
Passed 2026-08-09, on the third read, against the live server: 17 acts named, overview
count 17.
What it took:
PendingAct.resolvederives the act from what the writers publish —status,pr_url,activity, phase position — plus the pull request the forge confirmed.Where the published facts do not decide, it says so instead of inventing an answer.
activity = workingnames no act: an agent at work holds the ball.phase, or finish the workflow. Six worktrees had been saying "final PR created"
months after their pull request merged.
no act rather than guessing.
nothing —
glab mr viewlists open merge requests only, so a closed one looked likenone at all.
pr_urlis presence-gated, never matched by equality: a worktree may publish a mirrorURL, or an older phase's number. Exact matching would have silenced 4 of 6 acts.
Slice 04 — Pending acts come first
Goal. I open the dashboard after a break and the work that wants me is already in
front of me: the projects that want me at the top of the overview, the worktrees that
want me at the top of their project page, the act directly under the worktree's name.
Fit test. Walk the dashboard cold and work down until nothing wants me, never
reading past a card that wants nothing.
Passed 2026-08-11.
What it took:
card variants and the detail page.
ProjectSummary.mostWaitingFirstorders the overview;WorktreeOrder.pendingFirstorders a project page. Inside the group that wants him, most recently published first
— the worktree an agent just handed back is the one still in his head — and each act
says how long it has waited. The worktrees that want nothing keep issue-id order;
they are not a queue.
pending-first while the sync endpoint still called issue-id order current, so every
poll read the page's own order as a change, deleted each moved card and re-inserted it
at the top — reversing the group, then flipping again. Both now read one order.
generateReorderOobis gone: itmoved one card to a fixed position, which cannot express a correct multi-card move.
A client whose order is wholly stale converges in one refresh — checked on all 13
projects with more than one card.
currentPhaseis the first phase not completed, and the arithmetic added one to it.
announcedPullRequestsreadpr_urlfrom acache only a watched card writes, so the reaction to an agent's claim fired only for a
worktree the human was already looking at.
Review
Seven reviewers, one skill each: style, testing, security, scala3, composition,
architecture, api. Full report:
project-management/efforts/dashboard-control-plane/slices/04-pending-acts-come-first/review-slice-2026-08-11.mdFour criticals, all fixed in-loop:
DashboardService.pendingActForis now the one composition the count and the orderboth ask.
promoted by a state its own markup did not show. One fresh read now feeds both.
last_updatedhad no test at the wire. The recency order rests on a hand-writtencodec no test read; a key-name typo would have compiled, passed, and silently ordered
every worktree as if none had published. Verified by breaking the key and watching the
new tests fail.
statushad no test at the wire — the same gap for slice 03's field.Also fixed: a direct test for
WorktreeOrder.pendingFirst, theannouncedPullRequestsmissing-worktree guard, a phase number past the end of a workflow, and seven unused
imports.
Gates: unit 366/366 · integration 240/240 · E2E 34/34.
Follow-ups (parked, in the effort map)
Two need a decision rather than a patch:
review-state.jsonreads as "nothing pending" everywhere. What the ordershould do with a worktree whose state cannot be read is a card question.
and "just dropped" are the same value, so it needs an ownership rule.
Carried: CSS-selector escaping for a worktree key, a traversal guard on the effort
review-state path, forge read timeouts and 429 backoff, the poller's lifecycle never
closed, the unordered
/api/worktrees/changessibling, and theMap.emptycache bypassin
PullRequestReader.🤖 Generated with Claude Code