Skip to content

fix(ui): stabilize virtual follow autoscroll#406

Open
pascalandr wants to merge 4 commits into
NeuralNomadsAI:devfrom
Pagecran:autoscroll-state-machine
Open

fix(ui): stabilize virtual follow autoscroll#406
pascalandr wants to merge 4 commits into
NeuralNomadsAI:devfrom
Pagecran:autoscroll-state-machine

Conversation

@pascalandr
Copy link
Copy Markdown
Contributor

@pascalandr pascalandr commented May 8, 2026

Summary

  • Stabilize virtualized message autoscroll by keeping DOM/Virtua orchestration in VirtualFollowList and moving pure follow/hold behavior into a colocated tested module.
  • Restore per-session scroll positions using Virtua metrics and anchor snapshots while preventing hidden/inactive panes from overwriting good snapshots.
  • Gate streaming magnetization on active streaming, hold state, and the bottom-of-viewport item so long assistant replies can still rejoin near their tail without using stale top-of-viewport position.

Supersedes / fixes

Validation

  • node --test packages/ui/src/components/virtual-follow-behavior.test.ts
  • npm run typecheck --workspace @codenomad/ui
  • npm run build --workspace @codenomad/tauri-app
  • Many manual release executable testing.

Keep DOM and Virtua timing in VirtualFollowList while moving pure follow and hold transitions into a colocated behavior module with focused tests. The integration restores hidden session scroll snapshots safely, gates streaming magnetization on active streaming and hold state, and preserves explicit latest/submit bottom jumps without reintroducing fast-down heuristics.

Use Virtua offsets for scroll snapshots and decide streaming rejoin from the item at the bottom of the viewport so long assistant messages can still magnetize near their tail without using stale top-of-viewport position. Remove the unused generic scroll cache hook now that message scroll restore is handled by the virtual follow integration.

Validation: node --test packages/ui/src/components/virtual-follow-behavior.test.ts, npm run typecheck --workspace @codenomad/ui, npm run build --workspace @codenomad/tauri-app, and manual release-exe testing.
@pascalandr
Copy link
Copy Markdown
Contributor Author

@CodeNomadBot

I want you to perform the gatekeeper checks for latest PR state

check for regressions
check for better possible implementations
Be the PR gatekeeper
Be ruthless about code quality

@codenomadbot
Copy link
Copy Markdown
Contributor

codenomadbot Bot commented May 8, 2026

Understood request: gatekeeper review of the latest PR #406 state, focused on regressions, implementation quality, and whether this should be accepted as-is. Assumption: review only; I did not make code changes.

Gatekeeper result: not ready as-is.

Findings:

  1. High: session scroll snapshots can be written to the wrong session during session switches.

packages/ui/src/components/message-section.tsx:660-666 updates the module-local lastGoodScrollSnapshot when props.sessionId changes. A later effect cleanup persists the previous session id at packages/ui/src/components/message-section.tsx:729-733, and persistMessageScrollSnapshot() falls back to lastGoodScrollSnapshot at packages/ui/src/components/message-section.tsx:711-713 when it cannot safely capture. Because these effects are registered in this order, a session change can refresh lastGoodScrollSnapshot for the new session before the old-session cleanup persists, causing the new session’s snapshot to be stored under the old session id whenever capture is blocked or unavailable. That directly undermines the PR’s stated goal of preserving per-session scroll positions and avoiding hidden/inactive panes overwriting good snapshots.

A safer implementation would pass the old session’s last-good snapshot into the cleanup, or store lastGoodScrollSnapshot keyed by session id instead of as a single mutable variable.

  1. Medium: the new streaming rejoin logic is effectively disabled for the default hold preference.

packages/ui/src/components/virtual-follow-list.tsx:393-405 has the new “near tail” rejoin gate, but it immediately returns false when autoPinHoldEnabled() is true at line 395. In MessageSection, that prop is wired to holdLongAssistantRepliesEnabled at packages/ui/src/components/message-section.tsx:1313-1314, which defaults to true. That means the PR’s advertised behavior, “long assistant replies can still rejoin near their tail,” does not happen in the default configuration unless the user reaches exact bottom. The more precise gate should be the active hold state/suspension state, not the global preference being enabled.

This needs an integration-level test because the pure controller tests do not cover the canRejoinFollowFromDownScroll() wrapper that currently blocks the feature.

  1. Low: changed file exceeds the project’s stated refactor threshold.

packages/ui/src/components/virtual-follow-list.tsx is now about 923 lines. Per AGENTS.md, this should be highlighted when touched. I would not block solely on this, but this file now mixes controller wiring, snapshot capture/restore, DOM scroll orchestration, intent listeners, hold alignment, and controls. If this area keeps changing, the next step should be extracting snapshot restore/capture or DOM intent handling into focused helpers.

Validation run:

  • node --test packages/ui/src/components/virtual-follow-behavior.test.ts passed: 26/26.
  • npm run typecheck --workspace @codenomad/ui could not validate this checkout because dependencies are missing in the local environment (solid-js, Tauri plugins, SDK packages, etc.), producing broad module-resolution failures unrelated to the reviewed diff.

I would request changes before merging, specifically for findings 1 and 2.

--
Yours,
CodeNomadBot

Keep last-good scroll snapshots keyed by session so cleanup during session switches cannot write another session's snapshot under the previous session id. Also allow streaming rejoin while the hold preference is enabled when no hold target is actively suspending bottom pinning.

Validation: node --test packages/ui/src/components/virtual-follow-behavior.test.ts and npm run typecheck --workspace @codenomad/ui.
@pascalandr
Copy link
Copy Markdown
Contributor Author

Addressed in ba05b8e0:

  • Fixed the session-switch snapshot risk by storing lastGoodScrollSnapshot per session id instead of using one mutable variable shared across sessions. Cleanup for an old session can no longer fall back to a snapshot loaded for the new session.
  • Removed the global autoPinHoldEnabled rejoin block. Streaming rejoin is now blocked by the actual active/suspended hold state, not merely by the Hold preference being enabled.

Validation rerun:

  • node --test packages/ui/src/components/virtual-follow-behavior.test.ts
  • npm run typecheck --workspace @codenomad/ui

The file-size note is acknowledged; VirtualFollowList is oversized and should be split in a later refactor, but I kept this PR focused on the behavior fix.

@pascalandr
Copy link
Copy Markdown
Contributor Author

@CodeNomadBot

same review again

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 8, 2026

PR builds are available as GitHub Actions artifacts:

https://github.com/NeuralNomadsAI/CodeNomad/actions/runs/25557064452

Artifacts expire in 7 days.
Artifacts: (none found on this run)

@codenomadbot
Copy link
Copy Markdown
Contributor

codenomadbot Bot commented May 8, 2026

Understood request: repeat the same gatekeeper review on the latest PR state after ba05b8e0. Assumption: review only; I did not make code changes.

Gatekeeper result: no blocking findings in this pass.

Findings:

None found that I would block merge on after the ba05b8e0 fixes.

Prior findings check:

  • The session snapshot fallback issue is addressed. lastGoodScrollSnapshots is now keyed by session id in packages/ui/src/components/message-section.tsx:657-667, and persistence now falls back via getLastGoodScrollSnapshot(sessionId) at packages/ui/src/components/message-section.tsx:721-724. This removes the single mutable cross-session fallback that could write a new session snapshot under an old session id.
  • The default hold preference no longer disables streaming rejoin. autoPinHoldEnabled was removed from VirtualFollowList, and canRejoinFollowFromDownScroll() now gates on actual active/suspended hold state at packages/ui/src/components/virtual-follow-list.tsx:391-402.

Residual risks / non-blocking notes:

  • The integration boundary is still under-tested. The pure controller test suite is good, but the wrapper logic around canRejoinFollowFromDownScroll(), session snapshot capture/restore, hidden panes, and Virtua metrics remains mostly untested at component level. Given the complexity of this behavior, I would strongly prefer follow-up component/integration coverage, but I am not blocking this fix solely on that.
  • Oversized touched files remain: packages/ui/src/components/message-section.tsx is about 1,726 lines and packages/ui/src/components/virtual-follow-list.tsx is about 920 lines. Per AGENTS.md, these should be highlighted. I agree with keeping this PR focused, but this area is now large enough that future behavior changes should extract snapshot persistence/restore and DOM scroll orchestration into smaller units.

Validation run:

  • node --test packages/ui/src/components/virtual-follow-behavior.test.ts passed: 26/26.
  • npm run typecheck --workspace @codenomad/ui still could not validate in this local checkout because dependencies/modules are missing (solid-js, Tauri plugins, SDK packages, etc.), causing broad module-resolution failures unrelated to the specific diff.

Recommendation: acceptable to proceed if CI typecheck/build is green in the proper dependency environment and the manual scroll scenarios from the PR description were rerun after ba05b8e0.

--
Yours,
CodeNomadBot

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 8, 2026

PR builds are available as GitHub Actions artifacts:

https://github.com/NeuralNomadsAI/CodeNomad/actions/runs/25557064452

Artifacts expire in 7 days.
Artifacts: (none found on this run)

1 similar comment
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 8, 2026

PR builds are available as GitHub Actions artifacts:

https://github.com/NeuralNomadsAI/CodeNomad/actions/runs/25557064452

Artifacts expire in 7 days.
Artifacts: (none found on this run)

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 9, 2026

PR builds are available as GitHub Actions artifacts:

https://github.com/NeuralNomadsAI/CodeNomad/actions/runs/25597432431

Artifacts expire in 7 days.
Artifacts: (none found on this run)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: can't scroll up when agent still thinking

1 participant