Repository navigation
feat(*): pin a details tab in the trajectory view - #989
Conversation
…ctory store The details pane remembers one pinned tab per conversation. Each time the pane arrives at an entry (a new selection with the pane open, or the pane reopened), the pin picks that entry's tab when the descriptor offers it; otherwise the entry keeps the tab it would show without a pin. A reread of the entry on screen, a tab click or a click on the open row is not an arrival, so the reader is never pulled off a tab mid-read. Pins live in memory per conversation and survive a conversation switch, so returning to a conversation never depends on storage. They are mirrored to the tab's sessionStorage through the persist slot with no cap, so a reload brings them back and no conversation's pin is evicted. visibleTabs is now the one definition of the strip's tabs, shared by the strip and the pin. No visible control yet. Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
The details pane's head gets a pin button just left of the close button (or "Back to list" on a narrow area). It pins the tab on screen for the conversation and reads as pressed while that tab is the pinned one; on any other tab it offers to pin that tab instead. The pin glyph is hollow and faint until pressed, then solid in the colour the selected tab is underlined in. The pinned tab carries a small solid pin in the strip, named "Pinned" so the tab's accessible name says so, which tells the reader which tab is pinned while they are on another one. Adds the three catalogue entries (regenerating the TUI copy) and defines the pinned tab in ui-web/CONTEXT.md. Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
…trip's arrows appear A strip remounts on every arrival at an entry, without arrows. The selected tab was scrolled to the strip's edge first; the overflow measured next brought in the two 22 px arrows, which take their room from the strip, and the tab at the right edge ended up 44 px under one of them. A pinned tab far right made this the common case: "Raw record" read "Raw reco". The keep-in-view effect now runs again when the arrows come or go. A nudge with the arrows does not change whether they show, so it still scrolls freely and is not pulled back to the selected tab. Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
…egression test The "not on a nudge" half clicked the left arrow, which is disabled while the strip sits at its start, so no nudge happened and the test could not tell a keep-in-view step that follows each arrow's state from one that follows only whether the arrows show. It now clicks the enabled right arrow, dispatches the scroll the browser would, and checks that the strip moved by one step and both arrows turned enabled while the selected tab was not scrolled back into view. Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
Reviewed the full github/main...HEAD diff at the current GitHub base and covered the repository rules in AGENTS.md and CONTEXT-MAP.md/ui-web/CONTEXT.md, the pin state and persistence flow, React callers and accessibility behavior, selection/session/revision compatibility, commit history, and the tests for signs of weakened assertions. I found no plain error or merge blocker.
Verification:
npm test: 249 files, 3968 tests passed- focused trajectory tests: 3 files, 72 tests passed
npm run type-check: passednpm run lint -- --quiet: passeduv run pytest tests/test_resume_remote.py tests/test_subagent_acp_remote.py tests/test_subagent_cli_remote.py -x: 134 passed on rerun (an initial run had one unrelated machine-process cleanup timeout; the exact test passed alone before the clean rerun)uv run python scripts/check_source_language.py github/main...HEAD: passedgit diff --check github/main...HEAD: passed
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
This revision merges current main into the previously reviewed feature head. git range-diff shows the four feature commits are patch-identical, and I found no integration issue with the updated shared UI or localization baseline. I rechecked the current github/main...HEAD diff, affected callers and context, compatibility, repository rules, commit history, and the tests for weakened coverage.
Verification:
- focused trajectory tests: 3 files, 72 tests passed
npm run type-check: passednpm run lint -- --quiet: passeduv run python scripts/check_source_language.py github/main...HEAD: passedgit diff --check github/main...HEAD: passed
|
Not a blocker. One case where the pin breaks this PR's own Severity of this one finding, not a verdict on the pull request. Any blocking findings from this pass are review threads on the changed files; GitHub renders those collapsed, as a file name with no text. The entry says the pinned tab is opened "never while the reader is already on the entry". Reopening a row whose descriptor went stale while the pane was closed sets Suggested fix: clear |
|
No blockers; suggestions only, and they are marked inline. Confirmed. While a stale descriptor is being reread after reopen, the old tab strip remains interactive; I am carrying this as the existing nonblocking follow-up, so the change can still merge as it stands. The focused trajectory suite remains green (3 files, 72 tests), and |
Summary
The trajectory view's details pane opens an entry on the overview, or on the tab last chosen for that entry, so a reader checking the same aspect of many steps (relations, usage, parameters) has to pick that tab again for every step. This adds a pin for the tab:
lib/persist.tsslot, so they survive a reload. A tab opened fresh starts with no pin; a duplicated tab, or a page opened from this one with an opener, starts with a copy, the same as the page's other per-tab view state.Key decisions:
visibleTabs()indetailStore.tsis now the one definition of the strip's tabs, shared by the strip and the pin rule, so "the entry has the pinned tab" and "the strip shows it" cannot drift apart.ui-web/CONTEXT.mddefines the pinned tab. The three new catalogue entries regenerate the TUI copy.docs-site/docs/webui.md) is not changed: its trajectory section is a three-line overview that does not go into the details pane's tabs, and the button carries its own tooltip. Walk screenshots were checked locally and are not committed, per the repository asset rule.Four commits: the store semantics; the control, the tab mark, the catalogue entries and the term; the fix that keeps the selected tab whole once the strip's arrows appear; and a test follow-up that makes that fix's regression test nudge the strip for real.
Type
Verification
cd ui-web && npx vitest run --maxWorkers=8: 249 files, 3968 tests passed. 18 tests are new (12 indetailStore.test.ts, 3 inDetails.test.tsx, 3 inTabs.test.tsx); the slowest new one takes 27.5 ms.In
ui-web:npm run gen:check(240 methods match),npm run lint(0 errors; the 4 warnings are already on main),npm run type-check: pass.In
ui-web:npm run build && uv run --no-sync python build.py && node scripts/check-page.mjs && node scripts/check-css.mjs && node scripts/check-class-namespace.mjs: pass.In
ui-tui:npm run lint,npm run lint:rpc,npm run lint:i18n,npm run type-check,npm test -- --maxWorkers=8(2106 passed, 13 skipped),npm run build: pass.UV_NO_SYNC=1 make check-source-language check-large-files check-commits: pass.Browser walk of the offline page (
index.html?stub=1, Playwright with Chromium): 33 of 33 checks pass. It covers pin and unpin, another entry opening on the pinned tab, an entry without that tab keeping the tab the reader chose, a reopened pane, a pinned muted tab, two conversations each restoring its own pin, a reload, a page opened fresh (no pin), a page opened withwindow.open(a copy, independent afterwards), and the narrow overlay, in light and dark themes. A second walk pinsraw,relationsandoverviewin turn and opens all 8 entries for each, measuring the selected tab against the strip: 11 of 24 arrivals were clipped by up to 44 px before the fix, none after.Mutation checks: restoring the default cap of 20 fails the 25-conversation test; dropping the in-memory pins on a conversation switch fails the storage-unavailable test; running the keep-in-view step only on a tab change fails the new arrows test.
Relevant tests pass locally
Relevant lint / type checks pass locally
User-facing docs or screenshots are updated when needed
Risk
Security impact considered
Backward compatibility considered
Rollback path is clear for risky changes
User-visible changes: a pin button in the details head, a pin mark on the pinned tab, and the tab an entry opens on once something is pinned. With nothing pinned, the pane behaves exactly as before.
sessionStorage gains one key,
raven.gui.view.trajectory.pin, holding one tab id per conversation that has a pin; unpinning deletes that conversation's entry. Nothing leaves the page.When sessionStorage is unavailable (private mode, quota), pins still work for the page's lifetime, including conversation switches, but do not survive a reload.
Rollback: revert this PR. No data migration; the leftover sessionStorage key is never read and goes away when the browser tab closes.
Related Issues
N/A