Skip to content

fix(web): arrow and page keys scroll the pane under the pointer - #695

Merged
lastobelus merged 8 commits into
lastcode/mainfrom
lastcode/keyboard-scrolling-under-pointer
Nov 14, 2026
Merged

lastobelus merged 8 commits into
lastcode/mainfrom
lastcode/keyboard-scrolling-under-pointer

Conversation

@lastobelus

@lastobelus lastobelus commented Nov 13, 2026 •

Copy link
Copy Markdown
Owner

Upstream sends arrow and page keys to whichever element has focus. Since upstream made the timeline scroller focusable (#27209), ArrowUp/ArrowDown with the pointer over the LastCode sidebar scrolled the conversation. LastCode's earlier fixes were four separate patches that handled page keys only, and they kept regressing as upstream changed.

Fix

One router, apps/web/src/lastcodeScrollKeys.ts, owns one rule: the pointer picks the pane; focus decides what happens inside it.

  • Arrow and page keys scroll the pane under the pointer: sidebar (including its resize rail and header), conversation, settings, details and hover cards. Focus does not move.
  • Text fields and keyboard widgets keep their keys while the pointer is over their own pane. The composer counts as part of its conversation, so caret moves, upstream ArrowUp prompt recall and upstream's composer page handoff are unchanged.
  • Any other key parks the pointer for arrow keys until the mouse moves, so typing then pressing arrows edits text. Page keys always follow the pointer.
  • The router leaves alone: the terminal, shortcut recorders, open suggestion lists and menus, configured keybindings, modified keys, and contained scrollers.
  • The timeline registers as a scroll-key owner, so routed scrolls release live follow and page the area visible above the composer.

It replaces the old sidebarPageScroll, threadHoverScrollKeys, the workspaceFocus pane paging and the dispatcher fallback scope, and the timeline's page fallback. workspaceFocus.ts and dispatcher.ts go back to upstream.

docs/lastcode/keyboard-scrolling.md is the behaviour contract for checkpoint rebases. It records the desired behaviour table, the upstream seams the router depends on, and when to stop and report a clash with upstream. Checkpoint recovery in release.md links to it.

All eight source commits belong to the incubator carry group.

Proof

  • Real app, head 691d5226dd5: with the router disabled, ArrowUp with the timeline focused and the pointer over the sidebar scrolled the conversation (420→340). Enabled, the same steps scroll the sidebar (300→220) and the conversation stays at 420.
  • Also checked in the real app:
    • page keys over the sidebar;
    • the sidebar search header;
    • composer focused over the conversation (caret moves, upstream PageDown handoff);
    • composer focused over the sidebar (sidebar scrolls);
    • typing parks arrows;
    • settings dialog search field over the content (content scrolls) and over its own nav (keeps keys);
    • wheel scrolling unchanged.
  • apps/web/src/lastcodeScrollKeys.browser.test.ts has one case per row of the behaviour table. Each review fix's assertion was mutation-checked to fail without the fix.
  • Neighbouring browser tests pass (workspaceFocus, SidebarThreadHoverContent, LastCodeThreadHoverDismissal, MessagesTimeline, ComposerPromptEditorTiptap.input, sidebarRailScroll), as do the timelineViewport and pageScrollController unit tests.
  • After rebasing onto the repaired base, Quick local CI passes on 6b005e0c486 against c5dd72b8a02, with a matching validation receipt. check:changed origin/lastcode/main passes format, lint, both knip checks, affected typechecks, all 90 tests across 9 files, and all 7 React compiler checks. git range-diff confirms all eight scrolling patches are unchanged by the rebase.

Local review: 3 rounds with gpt-6.1-terra (xhigh), not clean; churn-gate MEDIUM (converging, no cuts). Every finding from the final round is fixed.

Done by Claude Opus 5.5 in Claude Code (T3 Code).

🤖 Generated with Claude Code

@lastobelus
lastobelus marked this pull request as ready for review November 13, 2026 22:17
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-14T00:57:23.127485Z 6b005e0 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@lastobelus

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 691d5226dd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/web/src/lastcodeScrollKeys.ts Resolved
@lastobelus
lastobelus force-pushed the lastcode/keyboard-scrolling-under-pointer branch from 691d522 to 38a6720 Compare November 13, 2026 22:31
lastobelus added a commit that referenced this pull request Nov 13, 2026
The source-file editor focuses inside a shadow root, where `closest` cannot
reach the surrounding workspace area. The router then missed that the editor
and the pointer's scroller share a pane, and took the editor's arrow and page
keys. Pane, layer and key-owner lookups now continue through shadow hosts.

Carry-Group: incubator
Carry-Observation: Follows up the router commit in the same PR; same mechanism, same group. Reported by Codex review on PR #695.

Co-Authored-By: Claude <[email protected]>
@lastobelus

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 38a6720975

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/web/src/lastcodeScrollKeys.ts Resolved
Comment thread apps/web/src/lastcodeScrollKeys.ts Resolved
lastobelus added a commit that referenced this pull request Nov 13, 2026
…ting panes' headers

- A configured binding on a scroll key wins even when its handler listens
  outside the shortcut dispatcher, as thread navigation does.
- Over a pane's header, the pane's scroll container counts even while its
  content fits, so the key stops there instead of scrolling the focused pane.

Carry-Group: incubator
Carry-Observation: Follows up the router commit in the same PR; same mechanism, same group. Reported by Codex review on PR #695.

Co-Authored-By: Claude <[email protected]>
@lastobelus

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Another round soon, please!

Reviewed commit: a4370f0835

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@lastobelus lastobelus closed this Nov 14, 2026
@lastobelus lastobelus reopened this Nov 14, 2026
lastobelus and others added 8 commits November 13, 2026 17:45
Workspace regions that hold focus no longer page their scroller from
workspaceFocus, and the dispatcher loses the fork-only `fallback` scope that
served it. The LastCode scroll-key router (docs/lastcode/keyboard-scrolling.md)
now covers a focused region along with pointer-targeted scrolling, so
workspaceFocus.ts and dispatcher.ts return to upstream.

Carry-Group: incubator
Carry-Observation: Owned by incubator because incubator already edits these workspaceFocus lines (same-base replay conflicted under upstream-bugfixes). Removes the pageFocusedArea part of lastCode #530; its page-distance and controller-reattach changes in pageScrollController/useTimelineFollow stay.
Carry-Supersedes: lastCode #530 (pageFocusedArea, dispatcher fallback scope)

Co-Authored-By: Claude <[email protected]>
The sidebar no longer binds its own PageUp/PageDown listener. The LastCode
scroll-key router pages whatever pane is under the pointer, the sidebar and its
resize rail included, and adds arrow keys.

Carry-Group: incubator
Carry-Observation: Owned by incubator because incubator already edits sidebar.tsx and this test (same-base replay conflicted under legacy-sidebar). sidebarPageScroll handled only page keys, so arrows over the sidebar went to the focused timeline once upstream made it focusable (pingdotgg#27209).
Carry-Supersedes: lastCode #301

Co-Authored-By: Claude <[email protected]>
Upstream sends arrow and page keys to the focused element. Since the timeline
scroller became focusable (upstream pingdotgg#27209), ArrowUp/ArrowDown with the
pointer over the sidebar scrolled the conversation. LastCode's per-surface
patches covered page keys only and kept regressing as upstream moved.

One router, lastcodeScrollKeys.ts, now owns the rule: the pointer picks the
pane, focus decides inside it. Text fields and keyboard widgets keep their
keys over their own pane (the composer over its conversation keeps caret
moves, prompt recall and its page handoff); any other key parks the pointer
for arrows until the mouse moves; suggestion lists, the terminal, open
menus, keybindings and modified keys are left alone. The timeline registers
as an owner so routed scrolls release follow and page above the composer.
It replaces the hover-card key handler and the timeline page fallback.

docs/lastcode/keyboard-scrolling.md records the desired behaviour, the
upstream seams it rests on, and when a checkpoint is fighting upstream.

Carry-Group: incubator
Carry-Observation: Inseparable from its tests and docs; the timeline owner registration and hover-card removal replace incubator code in the same mechanism.
Carry-Supersedes: lastCode #654 timeline page fallback; thread hover scroll keys
Carry-Applies-To: upstream 7e1a058 (focus-driven native scrolling, pingdotgg#27209)

Co-Authored-By: Claude <[email protected]>
Review follow-ups for the scroll-key router:

- A pane under the pointer stops the key even while its content fits, so
  a short sidebar no longer lets the key fall through to the focused pane.
- Workspace areas inside one dialog are separate panes, so a dialog's
  search field no longer keeps keys aimed at its content area.
- A touch or pen tap forgets the remembered mouse position.
- The no-pointer fallback leaves the terminal region alone.

The behaviour table now states the sidebar-row and modified-arrow rules
and that keys inside an embedded frame cannot be routed.

Carry-Group: incubator
Carry-Observation: Follows up the router commit in the same PR; same mechanism, same group.

Co-Authored-By: Claude <[email protected]>
…ane focus

Second round of review follow-ups for the scroll-key router:

- The scroller chain stops at overscroll-behavior contain/none, like the
  wheel, so a contained list no longer moves its enclosing pane.
- Shortcut recorders ([data-keybinding-capture]) keep every key, so a
  PageUp/PageDown binding can be recorded with the pointer elsewhere.
- When the pane under the pointer has nothing to scroll and focus is
  inside it, the focused element gets the key, so settings-section and
  sidebar-row arrow navigation work in panes whose content fits.

Carry-Group: incubator
Carry-Observation: Follows up the router commit in the same PR; same mechanism, same group.

Co-Authored-By: Claude <[email protected]>
With the pointer over non-scrolling chrome inside a workspace pane, such as
the sidebar's search header beside its list, scroll keys now scroll that
pane's list instead of falling through to the focused pane.

Carry-Group: incubator
Carry-Observation: Follows up the router commit in the same PR; same mechanism, same group.

Co-Authored-By: Claude <[email protected]>
The source-file editor focuses inside a shadow root, where `closest` cannot
reach the surrounding workspace area. The router then missed that the editor
and the pointer's scroller share a pane, and took the editor's arrow and page
keys. Pane, layer and key-owner lookups now continue through shadow hosts.

Carry-Group: incubator
Carry-Observation: Follows up the router commit in the same PR; same mechanism, same group. Reported by Codex review on PR #695.

Co-Authored-By: Claude <[email protected]>
…ting panes' headers

- A configured binding on a scroll key wins even when its handler listens
  outside the shortcut dispatcher, as thread navigation does.
- Over a pane's header, the pane's scroll container counts even while its
  content fits, so the key stops there instead of scrolling the focused pane.

Carry-Group: incubator
Carry-Observation: Follows up the router commit in the same PR; same mechanism, same group. Reported by Codex review on PR #695.

Co-Authored-By: Claude <[email protected]>
@lastobelus
lastobelus force-pushed the lastcode/keyboard-scrolling-under-pointer branch from a4370f0 to 6b005e0 Compare November 14, 2026 00:54
@lastobelus

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: 6b005e0c48

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@lastobelus
lastobelus merged commit 6ce03b9 into lastcode/main Nov 14, 2026
21 checks passed
@lastobelus
lastobelus deleted the lastcode/keyboard-scrolling-under-pointer branch November 14, 2026 01:05
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.

1 participant