Skip to content

A scrollbar you can put anywhere, in every language - #75

Merged
ralyodio merged 1 commit into
mainfrom
feat/scrollbar
Sep 8, 2026
Merged

A scrollbar you can put anywhere, in every language#75
ralyodio merged 1 commit into
mainfrom
feat/scrollbar

Conversation

@ralyodio

@ralyodio ralyodio commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Closes #62.

The renderer already existed. You could only get one by being a table, list, tree or log, each of which owns its own offset — so a wrapped paragraph, a canvas or a draw() of your own could not show a scrollbar at all.

This lifts drawScrollbar out of table.ts into its own module, gives it the four edges and state the caller owns, and adds ui.scrollbar(...). The dense widgets keep scrollbar: true and route through the same code, so there is one implementation and one appearance. Their fixtures are untouched: the 1046 lines added to widgets.json are all new cases, none moved.

Two things are not just a move

The thumb is sized from the viewport, not the track. For a table those are the same number — the bar is exactly as tall as the rows it describes — which is why the old five-argument form never needed to ask. A bar you place yourself has no such guarantee: twenty cells of track can describe an eight-line window, and sizing from the track then reports the wrong fraction. thumb takes the viewport and the legacy entry point passes the track, so every existing caller renders identically. Two fixtures pin the case where they differ.

Clicking the track moves you. offsetForPosition returns the offset that centres the thumb on the click, and the container reports it as an onScroll delta — so the handler already driving the content drives the bar too, with no second callback to wire up. A bar with no handlers registers no hit region, so it cannot eat a click meant for what is underneath it.

A horizontal bar also uses the half-height glyphs rather than the full block: a run of solid blocks across a row reads as a rule, not a thumb.

Reach

Six ports carry all of it, plus a container method on each builder. C has no widget layer, so it has nothing here. The Ruby, PHP and Perl bridges gained a scrollbar node and COBOL a SCROLLBAR verb — that was the price of the site's promise that every language has a runnable example for every widget. The widget gallery now has 29.

Verified

  • 275 TypeScript tests under both bun test and node --test, typecheck clean
  • 61 widget scenes matching the reference in Rust, Go, Python, Zig and C++ (was 54)
  • 11/11 ctest, the bindings ABI test, and ports/bindings/tests/check.py
  • all eleven widget galleries run, including Ruby, PHP and Perl against a locally built bridge
  • both COBOL bridges render the new verb identically (diff clean)
  • next build for the site

🤖 Generated with Claude Code

https://claude.ai/code/session_017Df2FNu5DhinMV2soRz3cy

The renderer already existed. You could only get one by being a table, list,
tree or log, each of which owns its own offset -- so a wrapped paragraph, a
canvas or a draw() of your own could not show a scrollbar at all.

This lifts drawScrollbar out of table.ts into its own module, gives it the
four edges and state the caller owns, and adds ui.scrollbar(...). The dense
widgets keep `scrollbar: true` and route through the same code, so there is
one implementation and one appearance. Their fixtures are untouched: the
1046 lines added to widgets.json are all new cases, none moved.

Two things are not just a move.

The thumb is sized from the viewport, not the track. For a table those are
the same number -- the bar is exactly as tall as the rows it describes --
which is why the old five-argument form never needed to ask. A bar you place
yourself has no such guarantee: twenty cells of track can describe an
eight-line window, and sizing from the track then reports the wrong fraction.
`thumb` takes the viewport and the legacy entry point passes the track, so
every existing caller renders identically. Two fixtures pin the case where
they differ.

A horizontal bar uses the half-height glyphs rather than the full block. A
run of solid blocks across a row reads as a rule, not a thumb.

Clicking the track moves you. offsetForPosition returns the offset that
centres the thumb on the click, and the container reports it as an onScroll
delta, so the handler already driving the content drives the bar too -- no
second callback to wire up.

Six ports carry all of it, plus container methods on each builder. C has no
widget layer, so it has nothing here. The Ruby, PHP and Perl bridges gained a
scrollbar node and COBOL a SCROLLBAR verb, which both COBOL bridges render
identically. That was the price of the site's promise that every language has
a runnable example for every widget; the widget gallery now has 29.

Verified: 275 TS tests under bun and node; 61 widget scenes matching the
reference in Rust, Go, Python, Zig and C++; 11/11 ctest; the bindings ABI
test and check.py; all eleven galleries run; both COBOL bridges diff clean;
the site builds.

Closes #62

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Df2FNu5DhinMV2soRz3cy
@ralyodio
ralyodio merged commit 6c58bfe into main Sep 8, 2026
18 checks passed
@ralyodio
ralyodio deleted the feat/scrollbar branch September 8, 2026 22:26
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.

Standalone scrollbar widget with its own state

1 participant