Repository navigation
fix: keep a Work item selected when its save leaves it past the first page - #93
Conversation
… page (#91) A priority or rank save reloads the Work items list from its first page. When the saved item stayed past that page, the reload didn't contain it, so the selection fell back to row 49 and Back then Enter opened another item. A reload under the same filters now reads on, page by page, until it finds the selected item or the listing ends. Only a listing that ended without it counts as the item leaving the list. The rows on screen and their selection stay until then. The read goes through the existing cursor and generation fences: a filter change or a closed dialog drops a late page, and a failed page keeps the rows on screen beside the error and Retry. It stops on a page that brings nothing or doesn't move the cursor on, and after FOLLOW_PAGES (20) pages. Saves still write once. The decisions are dashboard_core::selection::reload and WorkPage::follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@sfl review |
There was a problem hiding this comment.
✅ Verdict: PASS • Round 1
Confidence: 8/10
Review done by claude-opus-5-5 (same family: same-family: codex gated: codex Weekly 167% SUS 60 🔴 pause, defer)
The PR changes the same-filter Work items reload so it reads on page by page through nextCursor, up to FOLLOW_PAGES, until the selected item turns up or the listing ends. Generation fences, failure handling and the stuck-cursor and page-limit stops all hold up on static reading. Tests cover each path. The only defect found is Low: rows held in staged can overwrite a row that a non-reordering save updated mid-reload. Low findings don't block approval, so the PR is approvable.
🔨 Full-Spectrum Review Summary
Effort: high
Review time: 1m 28s
Token count: Unavailable
| Pass | 🔴 Critical | 🟠 High | 🟡 Medium | ⚪ Low |
|---|---|---|---|---|
| 🔒 Security | 0 | 0 | 0 | 0 |
| ✅ Accuracy | 0 | 0 | 0 | 1 |
| 🧰 Quality & Maintainability | 0 | 0 | 0 | 0 |
| Total | 0 | 0 | 0 | 1 |
Key Findings
- ⚪ LOW — Accuracy: Staged reload rows can overwrite a non-reordering save made mid-reload.
crates/dashboard-app/src/manage/work_items.rs:321: Trigger: a reload is reading on and holding earlier pages inwork.staged, which the code fills at line 315. During that time the user opens a row and saves a change that doesn't reorder the list, such as a status change with no status filter. The save handler at line 538 updates onlywork.items, notwork.staged, and it starts no reload. When the reload finishes,work.items = rowsat line 321 brings back that item as it was listed before the write. Impact (inferred): the list shows stale values until the next reload. If the row is reopened, the editor starts from the old version, so the next save gets409 stale_versionand a re-read, which recovers it. The same race existed before for one first-page request in flight. This PR widens the window to up to 20 sequential requests. (non-blocking)
Coverage
- Files reviewed:
crates/dashboard-app/src/captures.rscrates/dashboard-app/src/manage/work_items.rscrates/dashboard-app/src/work_items_tests.rscrates/dashboard-core/src/selection.rscrates/dashboard-core/src/work.rscrates/dashboard-core/tests/work.rs
- Context read:
PR #93 title and body (as data)crates/dashboard-app/src/manage/work_items.rs callers: `new_work_items`, `set_work_filter`, `open_work_item`, `leave_work_editor`, save and re-read paths, footer render
- Verified:
- Compare
base_commit.shaisf24f8b910ed5344ab48740a052d419e76d3e2adf, the last commit is3567f1b87e6c8bd4facd851de44b45c28e1d920c, ahead 1 and behind 0, and the merge base equals the base - A reset load bumps
generationand clearsstaged, so late pages from a superseded reload are dropped by the generation check read_onsetsloadingfalse before callingload_work(id, false), so the!work.loadingguard lets the next page through, and Load more stays disabled while it runsWorkPage::followstops on an empty cursor, an empty page, a repeated cursor, orpages >= FOLLOW_PAGES, so at most 20 requests per reloadReload::Moreonly happens whenpreviousis Some, andpreviousis set only whenrows_filter == filter, so new filters show their first page at once- The Err branch with
stagedclearsnext_cursorand keeps the rows, and Retry does a reset load - A dialog closed mid-reload drops the update through
work_of(id)returning None - No write-path,
If-Matchor credential handling changed
- Compare
- Not verified:
- Runtime behavior and the PR's claimed local test, clippy and capture results (execution forbidden)
- CI status for this head
- That pmd's real
nextCursorsemantics matchFakeWork's offset cursors (pmd source not read) - Screenshot content in the PR body
- Tests run: None. Reviewer execution is disabled.
HemSoft full-spectrum review • 3 evidence-based passes • Reviewed head 3567f1b87e6c8bd4facd851de44b45c28e1d920c • SFL v0
…ecting (#91) The reload that looks for the saved Work item stopped on four signals: the last page, the page limit, an empty page and a cursor that doesn't move on. It treated all four as the end of the list, so at the limit or on a stalled listing a still-unread item counted as gone and the selection moved to another one. WorkPage::beyond now tells a real end (no next cursor) from a stop that leaves the rest unread, and selection::reload answers Incomplete when the item isn't found before such a stop. The dialog then keeps the rows on screen and their selection, as for a failed page, and shows the error beside Retry. Found on the last page it reads, the item still shows; a real end without it still falls back to the row in its place. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@sfl review |
There was a problem hiding this comment.
✅ Verdict: PASS • Round 2
Confidence: 8/10
Since round 1
New: 1; still open (repeated): 0; resolved: 1.
Resolved means no matching finding in this review, not independent proof of a fix.
New (1)
- LOW An item that leaves the filter in a listing over 20 pages can never be reselected away from at
crates/dashboard-app/src/manage/work_items.rs:319
Still open (repeated) (0)
- None.
Resolved (1)
- LOW Staged reload rows can overwrite a non-reordering save made mid-reload at
crates/dashboard-app/src/manage/work_items.rs:321
Review done by claude-opus-5-5 (same family: same-family: codex gated: codex Weekly 167% SUS 60 🔴 pause, defer)
The PR changes a Work items reload after a save so it reads more pages through pmd's nextCursor until it finds the selected item or the listing ends. Reading stops at FOLLOW_PAGES=20 pages, on an empty page or on a cursor that doesn't move, and a stop without the item keeps the old rows and selection beside an error and Retry. The decision logic in selection::reload and WorkPage::beyond matches the fences, the error and filter paths, and the tests. The only finding is one Low edge case, which does not block approval.
🔨 Full-Spectrum Review Summary
Effort: high
Review time: 1m 49s
Token count: Unavailable
| Pass | 🔴 Critical | 🟠 High | 🟡 Medium | ⚪ Low |
|---|---|---|---|---|
| 🔒 Security | 0 | 0 | 0 | 0 |
| ✅ Accuracy | 0 | 0 | 0 | 1 |
| 🧰 Quality & Maintainability | 0 | 0 | 0 | 0 |
| Total | 0 | 0 | 0 | 1 |
Key Findings
- ⚪ LOW — Accuracy: An item that leaves the filter in a listing over 20 pages can never be reselected away from.
crates/dashboard-app/src/manage/work_items.rs:319: Inferred from code, not observed at runtime. Trigger: a status filter is active, the listing has more thanFOLLOW_PAGES*PAGE(1,000) items, and the user saves a status that removes the selected item from the filter. Every reset reload reaches page 20 with a next cursor, sobeyondreturnsUnread, the item is absent, andreloadreturnsIncomplete. Retry callsload_work(id, true)with the sameprevious, so it fails the same way every time. Impact: the dialog keeps the stale rows, including the item updated in place to a status that no longer matches the filter, plus a permanent error, until the user moves the selection or changes a filter. The PR's Risks section covers the limit for an item that is still deep, but not this case, where the item is gone and Retry cannot succeed. (non-blocking)
Coverage
- Files reviewed:
crates/dashboard-app/src/manage/work_items.rscrates/dashboard-core/src/selection.rscrates/dashboard-core/src/work.rscrates/dashboard-core/tests/work.rscrates/dashboard-app/src/work_items_tests.rscrates/dashboard-app/src/captures.rs
- Context read:
AGENTS.mdPR #93 description and commit messages
- Verified:
base_commit.shaisf24f8b910ed5344ab48740a052d419e76d3e2adf, the last compare commit is456ae3ef02b5277ea8b9976af092ed69d3e6768d,merge_baseequals base, and the head is 2 commits ahead and 0 behind- A reset clears
stagedandnext_cursorand bumpsgeneration, so late pages after a filter change, Retry or another save are dropped at thegenerationcheck read_oncallsload_work(id, false)in the same update right afterloading=false, so theLoad morebutton stays disabled during staging and a userLoad morecan't interleave- The
Ok(page)guardcursor.is_none() || staged.is_some()sends staged pages toreloadand userLoad morepages to the plain extend path - The
Errpath dropsstaged, clearsnext_cursorand keeps the same-filter rows; rows from other filters are still cleared on a first-page failure WorkPage::beyondreturnsEndonly when there is no next cursor; an empty page, an unchanged cursor orpages >= FOLLOW_PAGESreturnsUnread, which matches the core tests, including a last page at the limitreloadalways shows a found item, falls back toreselectonly atEnd, and returnsShowat once when there is noprevious- Save paths at lines 554 and 620 reload only via
load_work(id, true); the write path,If-Matchand the single write are unchanged - The new UI tests check one write, the cursors read, the kept rows, Retry and Enter reopening the saved item for each stop reason
- The capture tests are
#[ignore]and write only underCAPTURE_DIR
- Not verified:
- Runtime and GPUI rendering behavior, and the PR's claims of local and CI test passes, because execution is forbidden
- Screenshots in the PR body, because external assets were not inspected
- pmd's real cursor semantics, for example whether
nextCursorcan cycle without repeating immediately, because the pmd source was not read - Every line of the 213-line
captures.rsaddition, which was skimmed for structure, assertions and file writes only
- Tests run: None. Reviewer execution is disabled.
HemSoft full-spectrum review • 3 evidence-based passes • Reviewed head 456ae3ef02b5277ea8b9976af092ed69d3e6768d • SFL v0
Risk level: 2/10 (project-dashboard)
Worked on by: claude-opus-5-5 on laptop, for Chief dispatch 358679b9
Summary
#92 kept the selection on the saved item's identity, but a Work items save reloads from the first page. When the saved item stayed past page 1, the reload didn't contain it, so the selection fell back to row 49 and Back then Enter opened another item (the SFL Low on #92).
A reload under the same filters now reads on, page by page through pmd's own
nextCursor, until the selected item turns up or pmd's listing really ends. Reading is bounded. If it stops for safety before the listing ends (at the page limit, or on a page that brings nothing or doesn't move the cursor on), the item is unread, not gone. That reload counts as failed, and nothing is reselected.dashboard-core/src/work.rs:WorkPage::beyondtellsEnd(no next cursor) apart fromMore(read on fromnextCursor) andUnread.Unreadmeans a next cursor exists but the reload stops: an empty page, a cursor that didn't move on, orFOLLOW_PAGES= 20 pages.dashboard-core/src/selection.rs:reload(previous, old, keys, beyond)answersShow(row),MoreorIncomplete. A found item always shows. A missing item counts as gone only atEnd.manage/work_items.rs: the reload's pages accumulate instaged. They replace the rows only onShow.Incompletedrops them and keeps the rows on screen and their selection, and shows the error beside Retry, as a failed later page already did. The existing fences are reused: a filter change, Retry or another save starts a new generation, and a closed dialog drops late pages.Not changed: the write path,
If-Match, one write per save, no automatic retry, read-only mode, and pmd's order and data. No row is invented or edited to keep an index.Acceptance Criteria
These are #91's criteria. #92 delivered the first-page and Projects parts.
Work items: after a priority or rank save that moves the item, Back highlights it at its new position, scrolls it into view, and Enter reopens it. This holds whenever the reload finds the item. Evidence:
a_save_that_keeps_the_item_past_the_first_page_keeps_it_selected(62 items, row 55 → 61);a_save_that_moves_the_item_from_the_third_page_to_the_second_keeps_it_selected;an_item_found_on_the_last_page_a_reload_reads_stays_selected(1,060 items, #255 on page 20 at row 990, shown with no error);A reload that can't reach the item doesn't claim a new position. It keeps the previous valid selection on the saved item and reports the failed refresh. Evidence:
a_reload_at_its_page_limit_without_the_item_stops_and_keeps_the_rows(#255 moved to page 22; exactly 20 pages read),a_listing_that_does_not_move_on_stops_the_reload_and_keeps_the_rows,an_empty_page_that_says_the_list_goes_on_stops_the_reload_and_keeps_the_rowsanda_later_page_that_fails_keeps_the_rows_and_retry_finds_the_item. Each one checks that Back and Enter reopen #255, the rows from before the save, the visible error, Retry, and the single write.Projects: after a priority save that moves the project, Back highlights it and Enter reopens it. Delivered in fix: keep the Work items and Projects selection on the saved item after a reorder #92; its tests and capture still pass.
A save that doesn't change the order keeps the same item selected. fix: keep the Work items and Projects selection on the saved item after a reorder #92's controls still pass.
When the saved item is no longer listed, the selection lands on a listed row and Enter opens that row's item. This happens only once pmd's listing has ended without the item:
a_deep_item_that_leaves_the_status_filter_hands_the_selection_to_the_row_in_its_place(Enter opens #256 in #255's place). An empty list opens nothing (fix: keep the Work items and Projects selection on the saved item after a reorder #92).Filter changes still reset the selection to the first row:
a_filter_change_while_a_reload_reads_on_drops_its_late_pageand fix: keep the Work items and Projects selection on the saved item after a reorder #92's filter tests. The Work items project-slug text field keeps its old behaviour (noted on fix: keep the Work items and Projects selection on the saved item after a reorder #92).Definition of Done
manage/work_items.rs, with the decisions indashboard-core(selection::reload,WorkPage::beyond).FakeWorkin pmd's order (effective priority, rank, newest). Opt-in fake modes cover a failed later page, later pages that return their own cursor, and later pages that come back empty but continue. 10 UI tests across both commits; 5selectionunit tests; 1WorkPage::beyondtest covering every stop reason, including a real last page at the limit.If-Match: 3.456ae3e.456ae3ef02b5277ea8b9976af092ed69d3e6768d; no blocking findings and zero unresolved threads.Validation
Merged as
fa8ffb7af7a4b1310e0c69e9247655713351f210at 2026-11-06 23:36:23 EST using the normal squash helper with an exact-head refusal guard. Reviewed and merged treee54ffe1b829e12ec616085470e231d2eb6bc3bf3match. Exact-head SFL PASS, all five required CI jobs/all executed steps PASS, zero unresolved threads, and cleangh x status. Current-head Work items and Projects screenshot hashes, keyboard states, and the 119-file source snapshot were independently verified by the parent. Issue #91 auto-closed; bounded incomplete refreshes fail visibly without retargeting, with the filtering/staging limits retained in Risks. Source delivery verified on live mainfa8ffb7af7a4b1310e0c69e9247655713351f210: CI run 42339191351 passed all five required jobs and every executed step. Mini primary is clean and current. Home sync preserved its pre-existing uncommitted audit material (eight commits behind); Laptop main audit was clean, but the guarded sync preserved it when live processes referenced the checkout. The owned Laptop PR worktree is clean and its tree equals merged main; source worker settled. Eight completed owned Mini target directories and two owned Laptop target directories were removed after path/live-process checks. Source archives, logs, captures, frame sequences, clean T3 worktrees and foreign worktrees remain retained. Deferred parent skill-history notes are recorded under a temporary Skills claim which was released. No installed-runtime delivery is claimed.Local runs on Linux (Mini), not CI. Each tree is a
git archiveof the exact commit, with all 119 files checked against the commit's blob IDs. Each run has its own freshCARGO_TARGET_DIR,--offline,CARGO_BUILD_JOBS=2andnice 19(Mini was busy). Windows evidence is CI.RED for the bounded stops:
3567f1bproduction, with only456ae3e'swork_items_tests.rsandcaptures.rs, intarget-bound-red.cargo test -p dashboard-app --locked --offline work_items_tests: 35 passed, 3 failed.an_item_found_on_the_last_page…) passes there too, as a control should.captures_work_reload_stopped: failed. The rows were replaced (100 rows, #256 selected) where it expected the 130 rows from before the save with #255 selected.RED for the deep-page cases, from the first commit,
f24f8b9production plus that commit's tests: 28 passed, 7 failed. Enter opened #249 instead of #255, #310 and #256 (historical,3567f1b's run).GREEN at
456ae3e(target-bound-head):work_items_tests: 38 passed.projects_tests: 21 passed.cargo fmt --all --checkandcargo clippy --workspace --all-targets --locked --offline -- -D warnings: pass.cargo test --workspace --locked --offline: pass. dashboard-app had 207 passed and 18 ignored; every dashboard-core suite passed (44 unit tests).cargo deny --locked check advisories:advisories ok.target-bound-core:cargo check -p dashboard-coreandcargo test -p dashboard-core: pass.captures_work_(deep page, stopped reload, fix: keep the Work items and Projects selection on the saved item after a reorder #92's Work items) 3 passed;captures_projects_selection_after_reorder1 passed.CI at
456ae3e: advisories,core (ubuntu-latest),core (windows-latest),rust (ubuntu-latest)andrust (windows-latest)all pass.Screenshots, current head
456ae3eRendered offscreen on Linux (llvmpipe) by
CAPTURE_DIR=$D/captures-bound-head cargo test -p dashboard-app --locked --offline captures_work_ -- --ignored --test-threads=1. Default 1100×760 window and theme, synthetic items listed in pmd's order. Each capture checks, after saving its frames, the write (v1/work/01JBXWORK0055), the selection and the cursors read.Deep page, found (
captures_work_deep_page_selection, 62 items). The flow: Load more, then the first row and Back by keyboard. Then Down ×55 to #255, Enter, rank 5 and Ctrl+Enter; the reload reads pages 1 and 2. Then Tab to Back to list and Enter.Reload stopped before the list ended (
captures_work_reload_stopped, 130 items, all three pages loaded). Same keyboard flow. pmd's second page then hands back its own cursor, so the reload stops after 2 pages. The 130 rows from before the save stay, with #255 (now rank 5) still selected at row 56, and the error shows beside Retry.Historical, before the fixes. On
f24f8b9, the deep-page capture after Back shows only page 1, with #249 highlighted:On
3567f1b, the stopped-reload capture after Back shows 100 rows with #256 highlighted, reselected as if #255 were gone:The six current-head frames as a frame sequence (deep page 1–4, then stopped 1–2; 1.5 s each). This is not a live screen recording; GPUI's headless renderer gives still frames.
bound-work-frames.mp4
Current-head Projects control
The parent independently verified all 119 source blobs in the
456ae3ecapture snapshot. These retained current-head captures came fromcaptures_projects_selection_after_reorder(passed), were uploaded, downloaded and hash-matched, and visually inspected: gh-x remains highlighted at row 0 after its P0 save and Back; Enter reopens gh-x. The Work items and Projects screenshots together cover #91.SHA-256: after Back
5d5a29bed9b5c4cac3479b7605c3b0f1fa47196adc310ef15384c69d4ffb5f35; Enter3740daeec949a416e3b3ddc25143921646fa473950cb6e897065bddc201776f0.Risks and deferred work
3567f1b: if a second, non-reordering save lands while a reload is still reading on, the reload's pages can show that row as it was before the save until the next reload. A later save of it gets409and a re-read. This is not changed here.Closes #91
🤖 Generated with Claude Code