Repository navigation
fix(dispatch): give every Grid Dispatch lane its own selector strip - #859
Merged
Merged
Conversation
…r strip Refs #850 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J8PfoJxJ1cvJ9akXsWSVZB
Since #687 a row's index fills whichever lane of the row is focused, but the first lane of each row still skipped its compact index strip on the old grounds that the index was its selector. That left the leftmost lane as the only one that could not be swapped in one click: the user had to click into it to move focus, then pick from the index. Every lane, single-lane rows included, now carries a strip addressed to that lane, and the index keeps following focus. A strip that appeared only once a row gained a second lane would shift the existing lane's content sideways mid-rearrangement, so the rule has no exceptions. The empty-lane hint loses its first-lane branch and a dead branch keyed on the legacy projectTabId, which normalizeGridShape folds away on read. That branch already fell through to the correct copy, because ⌥↓ has walked the focused row's own list since 29ecd82, so "the top of the index" holds in project-bound rows too. DispatchMiniList's prop type now names projectTabIds, the field it actually filters on. Refs #850 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J8PfoJxJ1cvJ9akXsWSVZB
The first draft planned to fix the hint's legacy projectTabId read so bound rows would stop promising "the top of the index". That premise predates 29ecd82, which made ⌥↓ row-scoped and the promise true, so the plan now records deleting the dead branch instead and drops the hint test. Refs #850 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J8PfoJxJ1cvJ9akXsWSVZB
…expand target Review of #859 showed the strip tests could not tell right wiring from wrong. The only strip clicked sat in flat lane 0, where laneIndex, column and start are all 0. Nothing checked which row's binding a strip was handed, and the expand test passed for any handler, a no-op included. The strip-click test is now a table over lanes 0, 2 and 3 with focus elsewhere. New tests check that each strip gets its own row's binding and that expand addresses its own row. Another pins that a row's index fills the focused lane of that row, which is the premise that makes every lane need a strip. Each was checked by mutating the layout: column, start, grid.rows[0], a row-0 expand and a bare start in selectIntoRow all fail. Strip lookup now requires exactly one match. Refs #850 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J8PfoJxJ1cvJ9akXsWSVZB
The comment, plan and PR said #687 kept the first lane strip-less. It did the opposite. 524671b (#687) made the index follow focus and gave every lane a strip for that reason. 1eb9a47 (#691, a lane-wake fix) then removed the first lane's strip as a side change, on the grounds that the index was its selector, which had already stopped being true. Both PR reviewers caught it with git show. The record now points at the change that actually regressed, and the plan records the review and its resolutions. Refs #850 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J8PfoJxJ1cvJ9akXsWSVZB
…ents A focused empty lane advertised "pick from the strip, or press ⌥↓" even in a row bound to projects that currently have no agents. There the strip is empty and ⌥↓ returns without doing anything, so both halves of the hint promised a pick that could not happen. The hint is now gated on the row offering at least one agent. That is checked through rowScopedRows, the same filter the strip and ⌥↓ use, so the hint and the gestures it names cannot drift apart. The test was RED before the change, and it has an unbound contrast case so "never show a hint" cannot pass it. Pre-existing, raised in review of #859 because the PR rewrote this hint. Refs #850 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J8PfoJxJ1cvJ9akXsWSVZB
Owner
Author
|
Review round complete. Two orchestrated Codex reviewers ran: correctness gave Yes, tests and conventions gave With fixes, and neither found anything Critical. All valid findings are resolved in |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
In Grid Dispatch, the first (leftmost) lane of every row had no compact index strip. Every other lane had one. Changing the agent in the first lane took two gestures (click into the lane to move focus, then pick from the row's index), while every other lane took one click on its strip. The first lane is also the widest by default, so it's where agents get swapped most.
How it got that way, corrected after review:
524671b2) made the index per-row and made it fill whichever lane of its row is focused. For exactly that reason it gave every lane a strip.1eb9a472, a lane-wake fix) then removed the first lane's strip as a side change, arguing that the row's index "IS its selector". That was already false, because the index followed focus.Change
DispatchMiniListstrip, including the first lane of each row. A strip always writes its own lane and focuses it, through the wakingselectTiledLaneSessionpath. The index is unchanged and still follows focus.rowScopedRows, the same filter the strip and ⌥↓ use.DispatchMiniList'sgridRowprop type now namesprojectTabIds, the fieldrowScopedRowsactually filters on, instead of the legacyprojectTabId.Design notes
524671b2withheld "for the top of the index" from project-bound rows because ⌥↓ then walked the global list.29ecd829later made ⌥↓ walk the focused row's own filtered list (tiledRowScopedRows), which makes the promise true in bound rows. The branch read the legacyprojectTabId, whichnormalizeGridShapefolds away, so it was dead and already fell through to the correct copy.Review
Two orchestrated reviewers ran through the Agent Code orchestration MCP, both on Codex: one on correctness and behavior, one on tests, comments and conventions. A Claude reviewer was attempted twice but never received its prompt (#854), so the second seat moved to Codex. Verdicts were Yes and With fixes. There were no Critical findings. Everything valid is resolved on this branch:
318ad66f)laneIndex,columnandstartcoincide19dbccd1)gridRowwiring untestedtoggleDispatchRowExpandedParent(rowIndex, parentId)per stripstripSelectingrequires exactly one match6ee33cc0); test was RED firstMutation-checked: I temporarily applied each realistic miswiring to the layout and confirmed the matching test fails:
columnorstartinstead of its own lane;grid.rows[0];selectIntoRowalways targetsstart.Not changed (known limitation, follow-up): the fixed 46px strip plus the percentage-only
LANE_MIN_FRACTION(0.08 of the lane region) can leave a lane that was dragged to its minimum with no visible content. This already applied to every other lane; this PR extends it to the first lane. The fix is a minimum-content-width rule for lanes and belongs in its own change.Tests
gridDispatchLayout.renderer.test.tsxrenders the realTiledDispatchLayoutover the recordeddispatch-global-d23fixture.[3, 1]→ 4. This replaces the two tests that pinned "first lane has no strip". It failed onmainwith 2.main, where lane 0 had no strip.6ee33cc0.Verification
Node 24 (
.nvmrc):tsc -b: exit 0 (node and web projects).0ef2b17e):quality-gate(full suite, coverage, package verification) andminimum-node-fixture-gateboth passed.npm testlocally: could not complete. macOS killed it for low memory (load 99–169, swap about 12 of 13 GB, alongside other agent sessions and the running app).vitest relatedon the changed components passed: 5 files, 30 tests.store.test.tsandlazy-prosestill time out, but only under this machine's load; both pass in CI.imageAttachment.test.tsfails a machine-local provenance check because a cited~/.claudesession file was deleted. CI skips that check.I didn't launch the app, per the repo rule.
Risk
Low. The change is purely presentational: the first lane of each row is 46px narrower, like every other lane already was, and a hint is hidden in rows that offer nothing. There are no state shape, persistence, keybinding or command changes.
Unrelated CI note:
mainat48ea6912went red in "Enforce coverage baseline" onsrc/remote-client/src/WebSocketSessionFeed.integration.test.ts. The same test passed in that job's "Test system" step, and it passed on this PR's first run, so it looks timing-sensitive under coverage instrumentation.Fixes #850
🤖 Generated with Claude Code
https://claude.ai/code/session_01J8PfoJxJ1cvJ9akXsWSVZB