feat(web): conversation turn-rail minimap - #2285
Conversation
|
❌ UI Snapshot doesn't match the committed baseline. If this UI change is intentional, update the baseline — each path renders in the same pinned image, so the result matches this gate:
Diff PNGs ( |
|
Addresses the Polly review's blocking bug and non-blocking notes plus the CodeQL warning on PR #2285: - Blocking: loadHistoryUntilUserMessages now clears hasMoreHistory on fetch failure (matching loadMoreHistory), so the rail's auto-firing eager-load effect can't re-arm into an unbounded retry loop that also left the rail permanently hidden. - Over-fetch overshoot: count users already in state toward the target so we only top up to minUserMessages instead of overshooting by the existing count. - Blank preview: the preview scan now stops only at a real (non-system) user turn, so a system-marker bubble before the reply no longer strands a turn with an empty preview. - CodeQL useless assignment: drop the always-overwritten `next` initializer. - FADE magic-number coupling: drive the CSS fade mask from --turn-rail-fade so the mask width and thumb-tracking math share one constant. - previewTop drift: reposition the hover preview when the rail auto-scrolls under a stationary pointer. Co-authored-by: Isaac
b0695ae to
dc5674b
Compare
|
✅ Regenerated the visual baseline(s) in the pinned Playwright image and pushed to this PR. CI will re-run on the new commit. |
Addresses the Polly review's blocking bug and non-blocking notes plus the CodeQL warning on PR #2285: - Blocking: loadHistoryUntilUserMessages now clears hasMoreHistory on fetch failure (matching loadMoreHistory), so the rail's auto-firing eager-load effect can't re-arm into an unbounded retry loop that also left the rail permanently hidden. - Over-fetch overshoot: count users already in state toward the target so we only top up to minUserMessages instead of overshooting by the existing count. - Blank preview: the preview scan now stops only at a real (non-system) user turn, so a system-marker bubble before the reply no longer strands a turn with an empty preview. - CodeQL useless assignment: drop the always-overwritten `next` initializer. - FADE magic-number coupling: drive the CSS fade mask from --turn-rail-fade so the mask width and thumb-tracking math share one constant. - previewTop drift: reposition the hover preview when the rail auto-scrolls under a stationary pointer. Co-authored-by: Isaac
c31b109 to
b69c550
Compare
|
✅ Regenerated the visual baseline(s) in the pinned Playwright image and pushed to this PR. CI will re-run on the new commit. |
|
/review |
|
A left-edge vertical minimap: one tick per user turn, with a hover preview and click-to-scroll. The rail tracks your position like a scrollbar thumb and eagerly pages older history so it shows a useful run of ticks on load. Fixes found while building it: - History pages now load in chronological order. The eager loader used to prepend fetched blocks one-by-one, reversing each page and scrambling the transcript (a mid-conversation prompt could surface at the top with a hard scroll stop above it). - Rail tracking scrolls the active run into view instead of always re-centering, so clicking a tick you scrolled to leaves the rail parked while the transcript navigates. - Tracking re-runs when the tick count changes, so a fresh load lands at the bottom with the last turn active. - Rail fades in once the eager back-fill settles (no 2→N tick flash). - Wider hover preview; full-pitch clickable tick band (hover == click hit area). Responsive: desktop shows the rail and drops the floating up/down nav buttons; mobile hides the rail and keeps the buttons (no hover on touch). Keyboard nav is unchanged. Tests: chronological-order regression + eager-load coverage in chatStore, TurnRail render/interaction contract, and nav className forwarding. Co-authored-by: Isaac
Addresses the Polly review's blocking bug and non-blocking notes plus the CodeQL warning on PR #2285: - Blocking: loadHistoryUntilUserMessages now clears hasMoreHistory on fetch failure (matching loadMoreHistory), so the rail's auto-firing eager-load effect can't re-arm into an unbounded retry loop that also left the rail permanently hidden. - Over-fetch overshoot: count users already in state toward the target so we only top up to minUserMessages instead of overshooting by the existing count. - Blank preview: the preview scan now stops only at a real (non-system) user turn, so a system-marker bubble before the reply no longer strands a turn with an empty preview. - CodeQL useless assignment: drop the always-overwritten `next` initializer. - FADE magic-number coupling: drive the CSS fade mask from --turn-rail-fade so the mask width and thumb-tracking math share one constant. - previewTop drift: reposition the hover preview when the rail auto-scrolls under a stationary pointer. Co-authored-by: Isaac
Scrolling the rail up near its top triggers loadMoreHistory, which grows `turns` and re-runs the thumb-tracking effect. That effect would smooth-scroll the rail back to the transcript's visible run, yanking the user away from the older ticks they were browsing. Track pointer-over-rail state and skip the auto-scroll while the user is interacting, so a history fetch can't fight the scroll. Co-authored-by: Isaac
Scrolling the rail drags ticks under a stationary cursor, firing onMouseEnter on each and flickering the preview through every turn. Suppress hover updates while the rail is mid-scroll and settle onto the tick under the cursor once scrolling comes to rest, so the preview only changes when the user stops. Co-authored-by: Isaac
Scrolling the rail drags ticks under a stationary cursor, firing onMouseEnter on each and flickering the preview through every turn. A real hover moves the cursor; a scroll-induced enter does not — so ignore enter events whose cursor position matches the last accepted hover, and settle onto the tick under the cursor once scrolling comes to rest. The preview now only changes when the user actually moves the pointer. Adds tests for both the moved-cursor hover and the ignored same-position enter. Co-authored-by: Isaac
78d8691 to
be2fc5b
Compare
|
✅ Regenerated the visual baseline(s) in the pinned Playwright image and pushed to this PR. CI will re-run on the new commit. |
|
/review |
|
Addresses the second Polly review on the turn-rail PR: - B1: the rail derives ticks from non-system user turns, but the eager history loader counted every user-role block — including [System: …] markers. In agent/sub-agent sessions the loader could hit its target on marker blocks and early-return while the rail had too few ticks, leaving hasMoreHistory set and the rail stuck at opacity-0 forever. Share one isSystemUserContent predicate (new in systemMessage.ts) between ChatPage's turn derivation and the loader's count so both agree on what a real turn is. - B2: TurnRail was only CSS-hidden on mobile, so its eager backfill (up to 2000 items/open) still ran on the smallest-bandwidth clients for a rail they can't see. Gate the mount on useIsMobileViewport so mobile skips it entirely. - Gate the inner rail's pointer-events on `revealed` so the invisible rail is not a silent click target before it fades in. - Skip the scroll-settle re-hover once the pointer has left the rail; start pointerRef off-screen so a pre-move settle resolves to no element. - Use a stable tick ref callback to avoid per-render Map churn. Tests: isSystemUserContent unit tests; a chatStore regression proving markers don't count toward the target; a genuine multi-page (>200 item) cross-page assembly/order test; and TurnRail pointer-events reveal-gating tests. Co-authored-by: Isaac
|
/review |
|
|
🏷️ Doc impact: This adds an internal web UI TurnRail minimap component plus supporting store/hook refactors and tests — a frontend enhancement that doesn't change install, integrations, built-in agents/policies, or any documented config surface. Auto-classified on merge. Set the label manually before merging to override. · run |
Non-blocking follow-ups from the #2285 review, all scoped to TurnRail.tsx: - rAF-throttle the visible-tracking recompute. `turns` is a fresh array on every stream token, and the effect-triggered recompute ran synchronously (only the scroll handler was throttled), forcing a querySelector + getBoundingClientRect per turn per token on a long scrolled-back rail. Schedule the initial recompute through the same rAF gate so a burst of token-level changes coalesces to at most one layout read per frame. - Prune tickRefs to the live turn id-set on every `turns` change. setTickRef never deletes on unmount (to avoid churn), so a session switch — where every itemId changes — would otherwise leak references to detached buttons for the component's lifetime. - Clear the hover preview on tick blur so tabbing away doesn't strand it, with a guard so a stale blur can't wipe a preview a newer focus just opened. Adds vitest coverage for the focus-shows / blur-clears preview behavior and the stale-blur guard. Co-authored-by: Isaac
* feat(web): add conversation turn-rail minimap with fixes A left-edge vertical minimap: one tick per user turn, with a hover preview and click-to-scroll. The rail tracks your position like a scrollbar thumb and eagerly pages older history so it shows a useful run of ticks on load. Fixes found while building it: - History pages now load in chronological order. The eager loader used to prepend fetched blocks one-by-one, reversing each page and scrambling the transcript (a mid-conversation prompt could surface at the top with a hard scroll stop above it). - Rail tracking scrolls the active run into view instead of always re-centering, so clicking a tick you scrolled to leaves the rail parked while the transcript navigates. - Tracking re-runs when the tick count changes, so a fresh load lands at the bottom with the last turn active. - Rail fades in once the eager back-fill settles (no 2→N tick flash). - Wider hover preview; full-pitch clickable tick band (hover == click hit area). Responsive: desktop shows the rail and drops the floating up/down nav buttons; mobile hides the rail and keeps the buttons (no hover on touch). Keyboard nav is unchanged. Tests: chronological-order regression + eager-load coverage in chatStore, TurnRail render/interaction contract, and nav className forwarding. Co-authored-by: Isaac * fix(web): address turn-rail PR review comments Addresses the Polly review's blocking bug and non-blocking notes plus the CodeQL warning on PR omnigent-ai#2285: - Blocking: loadHistoryUntilUserMessages now clears hasMoreHistory on fetch failure (matching loadMoreHistory), so the rail's auto-firing eager-load effect can't re-arm into an unbounded retry loop that also left the rail permanently hidden. - Over-fetch overshoot: count users already in state toward the target so we only top up to minUserMessages instead of overshooting by the existing count. - Blank preview: the preview scan now stops only at a real (non-system) user turn, so a system-marker bubble before the reply no longer strands a turn with an empty preview. - CodeQL useless assignment: drop the always-overwritten `next` initializer. - FADE magic-number coupling: drive the CSS fade mask from --turn-rail-fade so the mask width and thumb-tracking math share one constant. - previewTop drift: reposition the hover preview when the rail auto-scrolls under a stationary pointer. Co-authored-by: Isaac * fix(web): stop turn-rail snapping back while user scrolls it Scrolling the rail up near its top triggers loadMoreHistory, which grows `turns` and re-runs the thumb-tracking effect. That effect would smooth-scroll the rail back to the transcript's visible run, yanking the user away from the older ticks they were browsing. Track pointer-over-rail state and skip the auto-scroll while the user is interacting, so a history fetch can't fight the scroll. Co-authored-by: Isaac * test(e2e-ui): regenerate visual baselines * fix(web): freeze turn-rail preview while scrolling the rail Scrolling the rail drags ticks under a stationary cursor, firing onMouseEnter on each and flickering the preview through every turn. Suppress hover updates while the rail is mid-scroll and settle onto the tick under the cursor once scrolling comes to rest, so the preview only changes when the user stops. Co-authored-by: Isaac * fix(web): freeze turn-rail preview while scrolling the rail Scrolling the rail drags ticks under a stationary cursor, firing onMouseEnter on each and flickering the preview through every turn. A real hover moves the cursor; a scroll-induced enter does not — so ignore enter events whose cursor position matches the last accepted hover, and settle onto the tick under the cursor once scrolling comes to rest. The preview now only changes when the user actually moves the pointer. Adds tests for both the moved-cursor hover and the ignored same-position enter. Co-authored-by: Isaac * test(e2e-ui): regenerate visual baselines * fix(web): count real turns for turn-rail, gate mount on viewport Addresses the second Polly review on the turn-rail PR: - B1: the rail derives ticks from non-system user turns, but the eager history loader counted every user-role block — including [System: …] markers. In agent/sub-agent sessions the loader could hit its target on marker blocks and early-return while the rail had too few ticks, leaving hasMoreHistory set and the rail stuck at opacity-0 forever. Share one isSystemUserContent predicate (new in systemMessage.ts) between ChatPage's turn derivation and the loader's count so both agree on what a real turn is. - B2: TurnRail was only CSS-hidden on mobile, so its eager backfill (up to 2000 items/open) still ran on the smallest-bandwidth clients for a rail they can't see. Gate the mount on useIsMobileViewport so mobile skips it entirely. - Gate the inner rail's pointer-events on `revealed` so the invisible rail is not a silent click target before it fades in. - Skip the scroll-settle re-hover once the pointer has left the rail; start pointerRef off-screen so a pre-move settle resolves to no element. - Use a stable tick ref callback to avoid per-render Map churn. Tests: isSystemUserContent unit tests; a chatStore regression proving markers don't count toward the target; a genuine multi-page (>200 item) cross-page assembly/order test; and TurnRail pointer-events reveal-gating tests. Co-authored-by: Isaac --------- Co-authored-by: omnigent-ci[bot] <294685417+omnigent-ci[bot]@users.noreply.github.com> Signed-off-by: Aditya Devarapalli <adityareddyd2@gmail.com>
…2476) Non-blocking follow-ups from the omnigent-ai#2285 review, all scoped to TurnRail.tsx: - rAF-throttle the visible-tracking recompute. `turns` is a fresh array on every stream token, and the effect-triggered recompute ran synchronously (only the scroll handler was throttled), forcing a querySelector + getBoundingClientRect per turn per token on a long scrolled-back rail. Schedule the initial recompute through the same rAF gate so a burst of token-level changes coalesces to at most one layout read per frame. - Prune tickRefs to the live turn id-set on every `turns` change. setTickRef never deletes on unmount (to avoid churn), so a session switch — where every itemId changes — would otherwise leak references to detached buttons for the component's lifetime. - Clear the hover preview on tick blur so tabbing away doesn't strand it, with a guard so a stale blur can't wipe a preview a newer focus just opened. Adds vitest coverage for the focus-shows / blur-clears preview behavior and the stale-blur guard. Co-authored-by: Isaac Signed-off-by: Aditya Devarapalli <adityareddyd2@gmail.com>
Related issue
N/A
Summary
Adds a left-edge turn-rail minimap to the conversation view: one tick per user turn, with a hover preview and click-to-scroll. The rail tracks your position like a scrollbar thumb and eagerly pages older history so it shows a useful run of ticks on load.
While building it I found and fixed several issues:
Responsive: desktop shows the rail and drops the floating ↑/↓ nav buttons; mobile hides the rail and keeps the buttons (no hover on touch). Keyboard ⌘⌥↑↓ nav is unchanged on all sizes.
Test Plan
cd web && npm test— full suite green (3804 pass); new tests below.cd web && npm run build—tsc -b && vite buildclean.Demo
Verified via headless-browser (CDP) measurements and screenshots during development. Behaviour summary — desktop: rail visible, nav buttons removed, gap between ticks and text; mobile: rail hidden, ↑/↓ nav retained.
Type of change
Test coverage
Coverage notes
Unit tests added: chronological-order regression + eager-load coverage in
chatStore.test.ts(verified they fail if the reversal bug is reintroduced),TurnRailrender/interaction contract inTurnRail.test.tsx, and navclassNameforwarding inUserMessageNav.test.tsx.The rail's scroll-positioning behaviours (thumb tracking, park-on-click, load-at-bottom, fade-in reveal, desktop gap) depend on real layout —
offsetTop/clientHeightare0in jsdom — so those were verified live via CDP rather than unit-tested, hence the manual-verification box.Changelog
Conversation view gains a turn-rail minimap for jumping between messages
This pull request and its description were written by Isaac.