Skip to content

Add SCROLL - #26

Merged
brynary merged 3 commits into
dragfrom
scroll
Oct 5, 2026
Merged

brynary merged 3 commits into
dragfrom
scroll

Conversation

@brynary

@brynary brynary commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #25. The last of three pointer-action PRs: scrolling, as a flow command and as ACT methods. It also closes the SPEC §18 item for more ACT methods.

Change

Form Meaning
SCROLL locator Bring the element into view.
SCROLL [locator] down / up / left / right Scroll one visible height or width.
SCROLL [locator] to 50% Scroll to a vertical position. A new percent literal, 0% to 100%.
  • Which box scrolls. The element when it can scroll in that direction, else the largest box inside it that can, else its nearest ancestor that can, else its document. An iframe element scrolls the page inside it, across origins too. html and body stand for the page, and no locator means the page. So SCROLL text:"Filter 3" down and SCROLL role:dialog Filters down both scroll the dialog's list.
  • How. The shim sets the position at once, even when the page asks for smooth scrolling, and the step ends when the position holds for two frames. A box already there stays put, and the step passes. scroll events and intersection observers fire; wheel events do not.
  • Syntax. A trailing bare down, up, left, or right and a bare to are keywords. whirl fmt keeps the quotes on text that spells them, such as SCROLL "down".
  • ACT. scrollIntoView, scrollTo, nextChunk, prevChunk, scrollLeft, and scrollRight. In a whole-page snapshot the first element is <body> in all three engines, so a scroll of it runs and reports as the page form, SCROLL down. scrollTo needs a percent; anything else fails with act-invalid-decision. With --jev, scrolls go to the model.
  • Limit. A row that a virtualized list has not rendered cannot be scrolled into view. The SPEC says to write enough SCROLL locator down lines first.

Evals

Five models, the six new tasks (this PR and #24, #25), three runs each. Summary: evals/act/results/2026-09-27-local.md.

task terra luna flash-lite jev + flash-lite jev + luna
right-click-menu 3/3 3/3 3/3 3/3 3/3
kanban-move 3/3 3/3 3/3 3/3 3/3
scroll-halfway 3/3 3/3 3/3 3/3 3/3
scroll-back-up 3/3 3/3 3/3 3/3 3/3
scroll-panel-chunk 3/3 3/3 3/3 3/3 3/3
scroll-in-frame 3/3 3/3 3/3 3/3 3/3

The first run, before commit 0a9f2d2, passed 0.67 to 0.94. Models answer "scroll down one chunk in the filters panel" with the dialog, and "scroll inside the incident history" with the iframe element; neither can scroll, so the page behind the dialog scrolled and the frame did not. 0a9f2d2 looks inside the named element and enters an iframe. The rerun still names the dialog in 12 of 15 runs and the iframe in 10 of 15, and all pass. With --jev, Jev plans every right click; drags and scrolls go to the model. Both sessions cost about $0.09.

Tests

  • Browser, in all three engines: a dialog's list scrolled through the dialog; a same-origin and a cross-origin frame scrolled through their iframe elements; one chunk equals the viewport height; to 100% triggers a lazy load and shows a back-to-top button; to 0%; a trigger scrolled into view loads more; a chunk inside a dialog panel found by its text; to 100% on a terms box enables "I agree", and one more chunk passes without moving; a sideways board; SCROLL "down" as text; and a scrollable iframe that scrolls while the page stays put.
  • ACT: the page's <body> renders as SCROLL to 100%; a position that is not a percent fails; a --jev scroll goes to the model.
  • Unit: parsing, percent literals, fmt quoting, the six methods, and the prompt.
  • Eval: scroll-halfway, scroll-panel-chunk, scroll-back-up, and scroll-in-frame, each checked first with plain SCROLL lines.
  • mise run check passes. SPEC §3.1, §6.1, §7, §7.4, §17, and §18, the shim protocol doc, and the CHANGELOG describe the change.

🤖 Generated with Claude Code

brynary and others added 3 commits September 26, 2026 23:50
Actions already scroll their elements into view, so SCROLL is for what
scrolling itself does: content that loads on scroll, controls that
react to the position, and boxes that scroll on their own.

- SCROLL locator brings the element into view.
- SCROLL [locator] down, up, left, or right scrolls one visible height
  or width.
- SCROLL [locator] to N% scrolls to a vertical position, with a new
  percent literal from 0% to 100%.

The box that scrolls is the element when it can scroll in that
direction, else its nearest ancestor that can, else its document; html
and body stand for the page, and no locator means the page. The shim
sets the position at once, even on a page with smooth scrolling, and
ends when it holds for two frames; a box already there stays put. A
trailing bare direction word and a bare `to` are keywords, and `whirl
fmt` keeps the quotes on text that spells them.

ACT runs scrollIntoView, scrollTo, nextChunk, prevChunk, scrollLeft, and
scrollRight. In a whole-page snapshot the first element is <body>, so a
scroll of it runs as the page form, such as SCROLL down. scrollTo needs
a percent; anything else fails with act-invalid-decision. With --jev,
scrolls go to the model.

Tests cover each form on a long page, a lazy-load trigger, a panel in a
dialog, a terms box, a sideways board, and a scrollable iframe in all
three engines, plus the ACT paths. Four eval tasks scroll halfway, by a
chunk in a panel, back up, and inside a frame.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first eval run of the scroll tasks showed models naming the
container they mean, not the box that scrolls. Four of five models
answered "scroll down one chunk in the filters panel" with the dialog,
which cannot scroll, so the page behind it scrolled. Failing runs of
"scroll inside the incident history" named the iframe element, which
cannot scroll either, so nothing moved.

The scroll box is now the element when it can scroll, else the largest
box inside it that can, else its nearest scrollable ancestor, else its
document. An iframe element scrolls the page inside it: the shim enters
the frame with contentFrame(), which works across origins.

The browser test now scrolls a dialog's list through the dialog, and a
same-origin and a cross-origin frame through their iframe elements, in
all three engines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Five models ran the six new tasks three times each: right-click-menu,
kanban-move, and four scroll tasks. All 90 runs pass. Models still name
the filters dialog and the iframe element when asked to scroll inside
them, and the scroll box rule now reaches the list and the frame's page.

Before that rule, the same session passed 0.67 to 0.94: the dialog
scrolled the page behind it, and the iframe element scrolled nothing.

With --jev, Jev plans every right click without the model; drags and
scrolls go to the model by design. The two sessions cost about $0.09.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@brynary
brynary added this pull request to stack #55 October 5, 2026 13:05
@brynary
brynary merged commit 6064828 into pointer-buttons Oct 5, 2026
3 checks passed
@brynary
brynary deleted the scroll branch October 5, 2026 18:29
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