feat(browser): scroll nested containers in browser_scroll - #92
Open
happinessisreal wants to merge 1 commit into
Open
happinessisreal wants to merge 1 commit into
happinessisreal wants to merge 1 commit into
Conversation
browser_scroll only ever called window.scrollTo/scrollBy, so it could not move an app pane whose own overflow container holds the content. Fixed-height app shells (chat transcripts, side panes, modal bodies) keep every message inside a nested scroller, so the window has no overflow to scroll and the container never receives the scroll event its lazy loading waits on. browser_scroll now accepts an optional element index or CSS selector. The action resolves the nearest scrollable ancestor of that anchor and moves that container's scrollTop, then replays a wheel gesture because a synthetic wheel event does not scroll by itself while apps commonly gate lazy loading on it. Without a target the previous page-level behavior is unchanged; a target with no scrollable container falls back to the page and says so in the status line.
Comment on lines
+450
to
+453
| const selector = typeof args.selector === 'string' && args.selector !== '' ? args.selector : undefined | ||
| const hasIndex = args.index !== undefined | ||
| if (hasIndex && selector !== undefined) { | ||
| throw new ActionError('bad-args', 'Provide either index or selector, not both.') |
There was a problem hiding this comment.
Empty selectors bypass validation
An explicitly supplied empty selector is converted to undefined before validation. As a result, { selector: "" } silently scrolls the page instead of returning bad-args, while { index: 7, selector: "" } bypasses the check that rejects both target fields and silently uses the index. The bridge schema permits empty strings and forwards them, so an invalid targeted request can unexpectedly scroll the page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
browser_scrollonly ever calledwindow.scrollTo/window.scrollBy, so it could not move content held in a nested scroll container. Fixed-height app shells - chat transcripts, side panes, modal bodies - keep their content in an inneroverflow: autoelement, so the window itself has no overflow: the page-level scroll is a no-op, and that container never receives the scroll event its lazy loading waits on.Concretely, on Instagram web DMs the conversation pane unloads everything older than the newest few messages, and the pane only moves on a real wheel gesture. Text-only browsing therefore stops at whatever the app happens to have mounted, with no way to reach the history above it.
Change
browser_scrollaccepts an optionalindex(element index frombrowser_snapshot) orselector(CSS selector). The action resolves the nearest scrollable ancestor of that anchor - the anchor itself included - moves that container'sscrollTop, and then replays awheelgesture. A synthetic wheel event does not scroll anything by itself, but apps commonly gate lazy loading on it, so the gesture is replayed after the position moves. The existingwaitForPageSettled/withPageDeltapath then returns the newly mounted content automatically.Behaviour:
indexandselector: rejected asbad-args.bad-args; selector matching nothing:action-failed.Tests
extensions/dsh-browser/tests/actions-scroll.spec.ts(new, 6 cases): container scroll via element index,top/bottomedges, selector anchor, index+selector rejection, page fallback, and unchanged no-target behaviour.packages/browser/bridge-browser/tests/tools.spec.ts: passthrough assertions forindexandselector.The new spec stubs scroll geometry and resolved overflow for the container only, because jsdom has no layout engine.
Verification
extensions/dsh-browser:tsc --noEmitclean;vitest run381/381 passing across 49 files.packages/browser/bridge-browser:tsc -p tsconfig.json --noEmitclean;vitest run139/140, the single failure beingtoken.spec.ts's POSIX mode assertion on Windows (0o600vs0o666), unrelated to this change.The nested-scrolling implementation is otherwise coherent, but the invalid empty-selector path should be fixed before merging because it can silently perform an unintended page scroll.
Findings
Summary
This PR extends
browser_scrollso callers can anchor scrolling by snapshot index or CSS selector, resolve the nearest vertically scrollable container, update its position, and emit a synthetic wheel event for application listeners.Diagram
%%{init: {'theme': 'neutral'}}%% flowchart TD A[browser_scroll request] --> B{Index or selector supplied?} B -- No --> C[Scroll document] B -- Yes --> D[Resolve anchor element] D --> E[Find nearest vertically scrollable ancestor] E --> F{Container found?} F -- No --> C F -- Yes --> G[Update container scrollTop] G --> H[Dispatch synthetic wheel event] C --> I[Wait for page to settle] H --> I I --> J[Return page delta and status]Reviews (1) · Last reviewed commit: "feat(browser): scroll nested containers ..."