fix: keep long sessions pinned after submit#498
Conversation
|
Warning Rate limit exceeded
To continue reviewing without waiting, purchase usage credits in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThis PR introduces a bottom-follow lock mechanism to prevent session timeline scroll jumps during content updates. The scroll dock gains lock-state management, timeline interaction exports new helpers that coordinate with the lock, hash-scroll callbacks wire lock cancellation, and session components route through the new lock-aware system. Tests verify lock restoration across reset scenarios and E2E validation confirms scroll-anchor preservation. ChangesSession Scroll-Jump Fix via Bottom-Follow Lock
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces a 'bottom follow lock' mechanism to the session scroll dock to ensure consistent scroll behavior during message submission and history loading, accompanied by new unit and E2E tests. Feedback focuses on improving code clarity by simplifying the timeout callback in armBottomFollowLock and reducing duplication by extracting shared state-clearing and scrolling logic into a helper function.
There was a problem hiding this comment.
🧹 Nitpick comments (2)
packages/app/src/pages/session/use-session-scroll-dock.ts (2)
109-127: 💤 Low valueExtract the 3s lock window into a named constant.
The lock lifetime is the central tuning knob for this feature (it bounds how long a browser-induced reset can be repaired without user input), but it's a bare
3_000literal in the middle ofarmBottomFollowLock. Lifting it to a named constant at module scope (e.g.BOTTOM_FOLLOW_LOCK_MS) makes the intent obvious, gives tests a single place to override timing if needed, and documents the decision.♻️ Proposed change
+const BOTTOM_FOLLOW_LOCK_MS = 3_000 + ... const armBottomFollowLock = (owner?: string) => { if (bottomFollowLockTimer !== undefined) clearTimeout(bottomFollowLockTimer) bottomFollowLockOwner = owner setBottomFollowLocked(true) - bottomFollowLockTimer = setTimeout(cancelBottomFollowLock, 3_000) + bottomFollowLockTimer = setTimeout(cancelBottomFollowLock, BOTTOM_FOLLOW_LOCK_MS) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/app/src/pages/session/use-session-scroll-dock.ts` around lines 109 - 127, Extract the hard-coded 3_000 timeout into a module-level named constant (e.g., BOTTOM_FOLLOW_LOCK_MS) and use it inside armBottomFollowLock when calling setTimeout; update any related references (bottomFollowLockTimer, armBottomFollowLock, cancelBottomFollowLock, bottomFollowLocked, bottomFollowLockOwner) to rely on that constant so the lock lifetime is configurable and clearly documented for tests and future tuning.
175-183: 💤 Low valueDocument the cancel-on-mismatch semantics of
restoreBottomIfLocked.When a caller passes an owner that does not match the active lock, this helper not only returns
falsebut also cancels the lock outright. This is exercised by the "lock belongs to another session" unit test, but the contract is non-obvious from the function shape — most readers will assume a query helper is side-effect-free. A short comment explaining the intent (a different session interacting with the dock invalidates a stale lock) will save future maintainers a code-archeology trip.📝 Proposed comment
+ // Querying with a non-matching owner cancels the lock: any interaction + // identifying itself as a different session means the previously-armed + // lock is stale and must not survive into the new session's scroll loop. const restoreBottomIfLocked = (owner?: string) => { if (!bottomFollowLockedFor(owner)) { if (bottomFollowLocked() && owner !== undefined) cancelBottomFollowLock() return false }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/app/src/pages/session/use-session-scroll-dock.ts` around lines 175 - 183, Add a brief doc comment above restoreBottomIfLocked explaining its cancel-on-mismatch semantics: when an owner argument is provided that does not match the current lock (checked via bottomFollowLockedFor), the function not only returns false but also clears the active lock by calling cancelBottomFollowLock; this behavior intentionally invalidates stale locks from other sessions. Mention the related functions bottomFollowLocked, cancelBottomFollowLock, followBottom, and scheduleScrollState (and scroller) so readers understand the side effect and when followBottom/scheduleScrollState will be invoked.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/app/src/pages/session/use-session-scroll-dock.ts`:
- Around line 109-127: Extract the hard-coded 3_000 timeout into a module-level
named constant (e.g., BOTTOM_FOLLOW_LOCK_MS) and use it inside
armBottomFollowLock when calling setTimeout; update any related references
(bottomFollowLockTimer, armBottomFollowLock, cancelBottomFollowLock,
bottomFollowLocked, bottomFollowLockOwner) to rely on that constant so the lock
lifetime is configurable and clearly documented for tests and future tuning.
- Around line 175-183: Add a brief doc comment above restoreBottomIfLocked
explaining its cancel-on-mismatch semantics: when an owner argument is provided
that does not match the current lock (checked via bottomFollowLockedFor), the
function not only returns false but also clears the active lock by calling
cancelBottomFollowLock; this behavior intentionally invalidates stale locks from
other sessions. Mention the related functions bottomFollowLocked,
cancelBottomFollowLock, followBottom, and scheduleScrollState (and scroller) so
readers understand the side effect and when followBottom/scheduleScrollState
will be invoked.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 2f089686-1308-48a7-a933-a9b11b193e3f
📒 Files selected for processing (7)
packages/app/e2e/session/session-renderer-diagnostics.spec.tspackages/app/src/pages/session.tsxpackages/app/src/pages/session/use-session-hash-scroll.test.tspackages/app/src/pages/session/use-session-hash-scroll.tspackages/app/src/pages/session/use-session-scroll-dock.test.tspackages/app/src/pages/session/use-session-scroll-dock.tspackages/app/src/pages/session/use-session-timeline-interaction.ts
Summary
Keep long session timelines pinned to the latest turn after submit by adding a session-local bottom-follow lock around the submit/resume path. The lock is now scoped to the triggering session key and is cancelled by user navigation paths that intentionally leave the latest turn.
Why
Closes #496. In long sessions, the viewport could reset near the top shortly after a user submitted from the bottom. The issue evidence showed this happened within the same session, without a remount, history load, or scroll-height change, so the fix belongs in the session timeline scroll coordination layer.
Related Issue
Closes #496
Human Review Status
Pending. A human should make the final merge decision after reviewing the final diff and verification evidence.
Review Focus
Please focus on the bottom-follow lock lifetime, session-key scoping, real scroll gesture cancellation, and whether keyboard/hash/message navigation correctly cancels the lock without weakening the #496 reset recovery.
Risk Notes
Main risk is over-following if a user intentionally leaves the latest turn immediately after submit. The PR cancels the lock on real timeline scroll gestures, keyboard scroll keys, message navigation, and hash navigation, and refuses owner-mismatched restores.
How To Verify
Screenshots or Recordings
No screenshot attached. Electron smoke verified the dev desktop app opens; the long-session reset behavior is covered by the deterministic E2E that forces the timeline viewport to top after submit.
Checklist
dev, and my PR title and commit messages use Conventional Commits in EnglishSummary by CodeRabbit
Tests
Refactor