Apple: honour Prop.Axis, and fix the circular width it exposed #11

Merged
rleibman merged 1 commit from issue/4-apple-horizontal-scroll into main 2026-09-29 13:17:00 -07:00
Owner

A horizontal Scroll is the framework's answer to Row overflow. GTK and
Android honoured Prop.Axis; the Apple renderer had an explicit no-op, so on
macOS and iOS such a row still clipped.

The axis is read at create and create reaches Swift as one int, so it
travels as kind code 9. 6 stays the vertical one, and no function is added —
phase 1's ConsistencySuite is untouched (6/6 still green).

The interesting part was not the new kind. The outer vertical Scroll had been
left on AppKit's defaults, with a comment saying it laid out correctly without
constraints. It did, until something inside it asked the container for its
width. A horizontal scroller does exactly that, which closed a loop — the
Column asks its children, a child asks the Column — and Auto Layout resolves
a circular width as zero. The button row collapsed to 0px rather than clipping.
So pinning the document view on the cross axis is not an optimisation for the
horizontal case; it is what keeps the vertical one composable.

That first showed up as a passing test printing "viewport asks 0px":
viewport * 4 < content is trivially true at zero. GTK's 4x ratio is a GTK
constant, not a cross-toolkit invariant — a GtkScrolledWindow reports a minimum
near zero, an NSScrollView has no intrinsic size and is pinned to fill its
parent, and a UIScrollView reports its laid-out frame. The three numbers are not
comparable. The portable invariant is that a horizontal scroller leaves its
content's width free, so content != viewport; on the wrong axis they are equal
to the pixel. Both platforms were falsified by forcing kind 6 and confirming the
check goes red.

Measured:
macOS content 357px, viewport 206px 27/27 checks
iOS content 267px, viewport 370px 26/26 checks

On every simulator available here the phone is wider than the five buttons, so
the overflow path is exercised on macOS only. The iOS self-test says so out
loud rather than passing silently.

Closes #4

Co-Authored-By: Claude Opus 5 noreply@anthropic.com

A horizontal `Scroll` is the framework's answer to `Row` overflow. GTK and Android honoured `Prop.Axis`; the Apple renderer had an explicit no-op, so on macOS and iOS such a row still clipped. The axis is read at `create` and `create` reaches Swift as one int, so it travels as kind code 9. 6 stays the vertical one, and no *function* is added — phase 1's ConsistencySuite is untouched (6/6 still green). The interesting part was not the new kind. The outer vertical `Scroll` had been left on AppKit's defaults, with a comment saying it laid out correctly without constraints. It did, until something inside it asked the container for its width. A horizontal scroller does exactly that, which closed a loop — the `Column` asks its children, a child asks the `Column` — and Auto Layout resolves a circular width as zero. The button row collapsed to 0px rather than clipping. So pinning the document view on the cross axis is not an optimisation for the horizontal case; it is what keeps the vertical one composable. That first showed up as a *passing* test printing "viewport asks 0px": `viewport * 4 < content` is trivially true at zero. GTK's 4x ratio is a GTK constant, not a cross-toolkit invariant — a GtkScrolledWindow reports a minimum near zero, an NSScrollView has no intrinsic size and is pinned to fill its parent, and a UIScrollView reports its laid-out frame. The three numbers are not comparable. The portable invariant is that a horizontal scroller leaves its content's width free, so content != viewport; on the wrong axis they are equal to the pixel. Both platforms were falsified by forcing kind 6 and confirming the check goes red. Measured: macOS content 357px, viewport 206px 27/27 checks iOS content 267px, viewport 370px 26/26 checks On every simulator available here the phone is wider than the five buttons, so the *overflow* path is exercised on macOS only. The iOS self-test says so out loud rather than passing silently. Closes #4 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A horizontal `Scroll` is the framework's answer to `Row` overflow. GTK and
Android honoured `Prop.Axis`; the Apple renderer had an explicit no-op, so on
macOS and iOS such a row still clipped.

The axis is read at `create` and `create` reaches Swift as one int, so it
travels as kind code 9. 6 stays the vertical one, and no *function* is added —
phase 1's ConsistencySuite is untouched (6/6 still green).

The interesting part was not the new kind. The outer vertical `Scroll` had been
left on AppKit's defaults, with a comment saying it laid out correctly without
constraints. It did, until something inside it asked the container for its
width. A horizontal scroller does exactly that, which closed a loop — the
`Column` asks its children, a child asks the `Column` — and Auto Layout resolves
a circular width as zero. The button row collapsed to 0px rather than clipping.
So pinning the document view on the cross axis is not an optimisation for the
horizontal case; it is what keeps the vertical one composable.

That first showed up as a *passing* test printing "viewport asks 0px":
`viewport * 4 < content` is trivially true at zero. GTK's 4x ratio is a GTK
constant, not a cross-toolkit invariant — a GtkScrolledWindow reports a minimum
near zero, an NSScrollView has no intrinsic size and is pinned to fill its
parent, and a UIScrollView reports its laid-out frame. The three numbers are not
comparable. The portable invariant is that a horizontal scroller leaves its
content's width free, so content != viewport; on the wrong axis they are equal
to the pixel. Both platforms were falsified by forcing kind 6 and confirming the
check goes red.

Measured:
  macOS  content 357px, viewport 206px   27/27 checks
  iOS    content 267px, viewport 370px   26/26 checks

On every simulator available here the phone is wider than the five buttons, so
the *overflow* path is exercised on macOS only. The iOS self-test says so out
loud rather than passing silently.

Closes #4

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
rleibman force-pushed issue/4-apple-horizontal-scroll from e4c5b7d445 to 4c49bd582f 2026-09-29 13:16:10 -07:00 Compare
rleibman referenced this pull request from a commit 2026-09-29 14:43:37 -07:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
rleibman/scala-ui!11
No description provided.