Repository navigation
OVFL-b: native scrolling (UIScrollView and an Android scroll view sized by the engine) - #231
Open
thejackshelton wants to merge 29 commits into
Open
thejackshelton wants to merge 29 commits into
thejackshelton wants to merge 29 commits into
Conversation
…ontentInset changes
…ound (Chrome paints it in the scrolling contents at the scroll origin's whole device px); iOS scroll views rest at offset 0 after UIKit's layout moves them to minus the inset
…m -s run parity:devices, then pnpm regen through /tmp/job.sh; fixed point): dump lanes 0 failures on both platforms; device-pixels per case equals master's (ios 57, android 86) apart from the iPad capture-trust points the MQ lane is fixing
…ected-dump.ts imports both INL1a's InlineChild and OVFL-B's ScrollRangesResult
…ds no viewport The regen after merging master failed at inline-mixed-sizes: scrollRanges went through scrollMetrics, whose viewport pass refuses any inline box in the document (R16, INL1a), so every expected dump and every native layout with an inline box anywhere refused, scroll container or not. scrollRanges now computes each element scroll container alone and lists the undecided ones as refused; the expected dump and the native hooks stop only for a scroll view whose range was refused, naming the reason. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WSkSiXMPXTAsNpS9BD9LDR
Commands: pnpm regen --skip lanes-host --skip tw-sweep (macos-26) and pnpm regen --only lanes-host (xcode-27), alternated to a fixed point, then pnpm regen --only tw-sweep (macos-26). Base: dd6ebd5 Run: https://github.com/compiled-run/dragoncss/actions/runs/37842533126
Commands: pnpm regen --skip lanes-host --skip tw-sweep (macos-26) and pnpm regen --only lanes-host (xcode-27), alternated to a fixed point, then pnpm regen --only tw-sweep (macos-26). Base: 05ea6bf Run: https://github.com/compiled-run/dragoncss/actions/runs/37849453312
5 of 20 tasks
…croll backgrounds, and fix the Android drag arithmetic 1. lower/scroll-decidable.ts mirrors the engine's scroll-range refusals (line-level boxes in an inline formatting context, a percentage height whose end margins may collapse through it) on the lowered tree; the native lowering refuses each auto or scroll container it finds (DRAGON_UNPROVEN_CONTEXT, naming the engine's reason), so no native scroll view meets an engine refusal on the device. scroll-decidable.test.ts checks it against scrollRanges on 1176 generated containers. 2. The scrolling-contents background refusal covers every scroll container whose overflow may extend past its start: rtl as well as reversed flex. The unproven rtl clean-compile pin is replaced by refusals. 3. The Android scroll view drags with DragonDragAxis (ScrollView's arithmetic): whole-px positions are differenced, so sub-pixel moves add up, and the move that starts the drag gives up the touch slop. native-backends.test.ts runs it on the JVM. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WSkSiXMPXTAsNpS9BD9LDR
…ansparent; drop a stray helpers.d.ts The regen stopped at hit-capture: overflow-nested-rtl had no native programs, because the scroll container background refusal now covers rtl (a fraction of a device px of scroll origin). As overflow-flex-reverse already does for its reversed auto containers, the auto and scroll containers of overflow-nested, overflow-auto-fits, overflow-hidden-x-auto-y and overflow-replaced paint no background; their children keep theirs. Every layout case lowers on native again (checked over all 319 layout fixtures). helpers.d.ts was emitted by a failed tsc build into packages/layout/test and committed by mistake. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WSkSiXMPXTAsNpS9BD9LDR
Commands: pnpm regen --skip lanes-host --skip tw-sweep (macos-26) and pnpm regen --only lanes-host (xcode-27), alternated to a fixed point, then pnpm regen --only tw-sweep (macos-26). Base: 7012175 Run: https://github.com/compiled-run/dragoncss/actions/runs/37859483362
… with a painted background 7012175 took the scroll-container backgrounds out of four both() fixtures, which left the ltr claim (an auto or scroll container with a background compiles on native) with no Chrome comparison. The new ltr-only fixture overflow-background restores those shapes with backgrounds: auto and scroll overflowing both axes, auto that fits, a flex row, overflow-x hidden beside auto, and a nested inner auto container. ovfl-metrics.test.ts counts each overflow fixture's own directions (35 cases) and fails if these six scroll views with an opaque background disappear from the native lowering of the overflow cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WSkSiXMPXTAsNpS9BD9LDR
Commands: pnpm regen --skip lanes-host --skip tw-sweep (macos-26) and pnpm regen --only lanes-host (xcode-27), alternated to a fixed point, then pnpm regen --only tw-sweep (macos-26). Base: fbe732a Run: https://github.com/compiled-run/dragoncss/actions/runs/37864618916
…into ovfl-b
Conflicts, each resolved keeping both sides:
- emit/expected-dump.ts: ExpectedEngine has both scrollRanges (OVFL-B) and paint (PNT1); NodeGeometry has both size and
scroll; each node's geometry carries both, and the scroll-range refusal check stays.
- emit/native-support.ts (Swift and Kotlin): DragonBoxShape takes master's size argument, and the scroll range and refusal
are still set on every box before the after-layout hooks.
- test/expected-dump.test.ts and parity/src/native-host.ts: the engine passes both scrollRanges and paint: { roundedShape }.
- test/paint-radius.test.ts (master's, no conflict): its NodeGeometry literal gains scroll: null, the field OVFL-B adds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WSkSiXMPXTAsNpS9BD9LDR
#198's rounded overflow clip masks the clip view in its own coordinates (iOS layer mask, Android outline or overlay mask), and a DragonScrollView moves those with its scroll offset; no fixture compares a rounded scroll container with Chrome. checkRoundedScrollContainer refuses a rounded corner on an auto or scroll container on ios and android (DRAGON_UNPROVEN_CONTEXT at the radius). overflow.test.ts pins it, that it fires beside the start-overflow background refusal, and that rounded hidden boxes and square scroll containers still compile. Every layout fixture still lowers on native (649 cases). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WSkSiXMPXTAsNpS9BD9LDR
Commands: pnpm regen --skip lanes-host --skip tw-sweep (macos-26) and pnpm regen --only lanes-host (xcode-27), alternated to a fixed point, then pnpm regen --only tw-sweep (macos-26). Base: 9f9c033 Run: https://github.com/compiled-run/dragoncss/actions/runs/37873155483
Commands: pnpm regen --skip lanes-host --skip tw-sweep (macos-26) and pnpm regen --only lanes-host (xcode-27), alternated to a fixed point, then pnpm regen --only tw-sweep (macos-26). Base: 09932ff Run: https://github.com/compiled-run/dragoncss/actions/runs/37877146377
Commands: pnpm regen --skip lanes-host --skip tw-sweep (macos-26) and pnpm regen --only lanes-host (xcode-27), alternated to a fixed point, then pnpm regen --only tw-sweep (macos-26). Base: c1c53ba Run: https://github.com/compiled-run/dragoncss/actions/runs/37881619635
…vfl-b project.ts conflict: master passes #218's GEN-b faults to checkComputed (kept) beside the T078 R14 checkNativeScroll call, which OVFL-B removes (native scroll views replace that refusal; checkNativeScroll no longer exists). checkComputed keeps OVFL-B's start-overflow background and rounded scroll container refusals; the scroll-decidable refusal stays in the native lowering. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WSkSiXMPXTAsNpS9BD9LDR
Commands: pnpm regen --skip lanes-host --skip tw-sweep (macos-26) and pnpm regen --only lanes-host (xcode-27), alternated to a fixed point, then pnpm regen --only tw-sweep (macos-26). Base: 9f86a44 Run: https://github.com/compiled-run/dragoncss/actions/runs/37889702905
This branch has not been deployed
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.
OVFL Phase B (T078, notes/T078-ovfl-spec.md R9, R10, R14): native scroll views for
overflow: autoandscroll. Prepared on top of ovfl-a (#194, now on master); master merged in twice (a1b65ee, then 05ea6bf after #200 made GitHub report the PR conflicting); both merges were conflict-free in git (generated outputs kept by the merge driver, then regenerated on CI). Basemaster, not stacked.What changed
overflow.tsgainsscrollRanges(andscrollRangesWithFaults): every element scroll container's scroll offset range[minX, maxX, minY, maxY]in whole device px, in CSS scroll-offset terms (0 is the layout position; the start side is negative where the overflow extends past it, as in rtl or a reversed flex container). The scroll origin is floored to whole device px; contents and client sizes snap at the padding box origin.ScrollMetricscarries the padding box origin for it.scrollRangesis a translator root (generate.ts), so the device runs the same code.lower/paint/scroll.tsfills the scroll seam: a box whose usedoverflow-x/overflow-yisautoorscrollgets ascroll-containerwrite{x, y}(a hidden axis is locked).emit/paint/scroll.tsfills the scroll emitter:DragonScrollView(UIScrollView) replaces the clip view over the padding box.contentInsetgives the start side,contentSizethe end; safe areas never adjust it; it rests at offset 0 (UIKit moves a resting view to minus its inset on layout, so it is set back until the user pans). Bounce, deceleration and indicators are native.DragonScrollViewextendsDragonClipView(nowopen), scrolled withView.scrollToclamped to the range on both axes,OverScrollerflings, platform touch slop and fling speeds, overlay scrollbars enabled at the first touch so a view at rest draws none. The drag usesDragonDragAxis(ScrollView's arithmetic, review fix): whole-px positions are differenced, so sub-pixel moves add up, and the move that starts the drag gives up the touch slop instead of jumping by it.native-support.ts:DragonTreecalls the translatedoverflow_scrollRangesafter layout and hands each box its range before the after-layout hooks (a refused or missing result is fatal). The readback keydragonScroll.rangeis projected inexpected-dump.tsfrom the TS engine (ExpectedEngine.scrollRanges).scrollRangesdecides each scroll container on its own and reads no viewport: the first CI regen failed atinline-mixed-sizesbecause the viewport pass refuses any inline box in the document (R16, INL1a). An undecided container is listed inrefused; the expected dump and the native hooks stop only for a scroll view whose range was refused, naming the node and the reason (dd6ebd5).runtime/state.ts: a scroll container inside a state program is refused by name (StateEmitError), like transforms.checkNativeScroll,nativeScrollPendingand the lane-only marking for auto/scroll are gone, so auto and scroll compile on iOS and Android and the lanes prove native rows for them. NewcheckStartOverflowBackground: an auto/scroll container whose overflow may extend past its start (any rtl one, and a reversed flex one: row-reverse, column-reverse, wrap-reverse) with a paintedbackground-colorisDRAGON_UNPROVEN_CONTEXTon ios and android. Chrome paints that background in the scrolling contents at the scroll origin's whole device px, so it stops up to one device px short of the padding box end (measured: overflow-flex-reverse a2 at DPR 2.625); the native views paint the whole padding box. rtl is refused too (review fix) because no fixture proves an overflowing rtl scroll container with a background.lower/scroll-decidable.tsmirrors, on the lowered tree, the engine's scroll-range refusals (overflow.tsrefuseLineLevelBoxesthroughoverflowOf/propagated, andreadChild's percentage height whose end margins may collapse through it, taken whenever it may apply). The native lowering refuses each auto or scroll container it finds withDRAGON_UNPROVEN_CONTEXTon ios and android, naming the engine's reason, so a native scroll view never meets an engine refusal on the device. Hidden containers are no scroll views and are not refused.checkRoundedScrollContainer: a rounded corner on an auto/scroll container isDRAGON_UNPROVEN_CONTEXTon ios and android. PNT1-radius: border-radius, its corner longhands and -webkit-border-radius drawn natively on iOS and Android (supersedes #80, #81) #198's rounded overflow clip masks the clip view in its own coordinates (iOS layer mask, Android outline or overlay mask), a DragonScrollView moves those with its scroll offset, and no fixture compares a rounded scroll container with Chrome.expected-dump.ts:ExpectedEnginehas bothscrollRangesandpaint;NodeGeometryhas bothsizeandscroll.native-support.ts:DragonBoxShapetakes PNT1-radius: border-radius, its corner longhands and -webkit-border-radius drawn natively on iOS and Android (supersedes #80, #81) #198's size, and the scroll range and refusal are still set before the hooks.expected-dump.test.tsandnative-host.ts: the engine passes bothscrollRangesandpaint: { roundedShape }.OVERFLOW_AT_REST: they are native scroll views whose offsets rt-hit does not read yet (OVFL-B2), so an interaction rule beside them is refused on native as an unmodelled hit fact. The element the viewport took its overflow from uses visible and is no fact (propagatedFrom, now exported).scroll-metrics.ts) also recordsextents: each element scroll container'sscrollLeft/scrollTopclamps afterscrollTofar past each end, at DPR 1, 2, 3, 2.625;parseScrollCapturechecks they are finite and are exactly the element scroll containers.ovfl-metrics.test.tscompares the engine'sscrollRangeswith them for every overflow case. The device lanes compare the native readbackdragonScroll.rangewith the expected dump.pnpm regenthen CI): profiles (iOS and Android gaincaveatrows foroverflow-x/overflow-yautoandscrollin the contexts the fixtures prove, under the Android support profile follows the iOS rule (music player Android 20 -> 165 declarations); UA bold/italic refusal on every native target #49/T075J paint-aspect cap), expected dumps, emitted CSS headers, captures, the tailwind snapshot, the WPT web expectations and the music-player north-star check (errors 194 -> 186: nativeDRAGON_UNSUPPORTED_VALUE17 -> 13 on each of iOS and Android).Changed tests, with reasons
dragon/test/scroll-decidable.test.ts(new):undecidedScrollContainersagainst the engine'sscrollRangeson 1176 generated containers (14 child shapes in pairs, auto/scroll/hidden, static/relative). Every container the engine refuses is refused, and exactly those apart from the value-dependent percentage-height rule. Each reason is pinned, and so is the compiler diagnostic for an inline box, a<br>and a collapsing percentage height on ios and android. An atomic inline is refused on native earlier, by the lowering (DRAGON_LOWERING_FAILED, INL2 not landed). A decidable container and a hidden one compile.dragon/test/native-backends.test.ts: new JVM test ofDragonDragAxis. 100 moves of 0.3 px sum to 30 px less the slop, and no move (the start included) scrolls more than one move's worth. The old per-event truncation scrolled 0, and its start jumped by the slop.dragon/test/overflow.test.ts: the "T078 R14: native refuses auto and scroll until OVFL-B" block is replaced, because that refusal is what this PR removes. The new block pins the lowering per axis (auto both axes, hidden+computed auto, scroll+hidden, and hidden/clip get no scroll write) and that native auto/scroll rows exist; a second block pins the reversed-flex background refusal (each reverse shape with auto and scroll, ios and android, located at the background) and the cases that must still compile (no, transparent or zero-alpha background, hidden, forward ltr flex, ltr block, rtl without a background, rtl hidden). The earlier pin that an rtl auto block with a background compiles was replaced by refusals (declared and inherited rtl, block and flex), because no Chrome comparison proves it.dragon/test/s4a.test.ts:overflow-x: hiddenalone (computingoverflow-y: auto) now compiles with no diagnostics on ios; it was refused naming OVFL-B.dragon/test/interaction.test.ts: auto, scroll andoverflow-x: hiddenalone with a:hoverrule are now refused by the hit model on native (2 "needs Dragon hit testing" errors, nothing else), since the hit test does not read scroll offsets yet; html's propagatedoverflow-x: hidden, clip and hidden stay modelled. The old "outside the lanes, every error names OVFL-B" assertion went with the R14 refusal.dragon/test/expected-dump.test.ts: the test engine passesscrollRanges(new requiredExpectedEnginemember).parity/test/ovfl-metrics.test.ts: new "scroll offset range against Chrome" block (every container, every DPR, whole device px, offset 0 inside the range, >400 checked, >80 scrolling past the start side), the 2.625 floor case, and twoparseScrollCapturerefusals for badextents. The capture fixture object gainsextents: [].layout/test/overflow.test.ts: new case, scrollRanges with an inline box outside every scroll container gives every range (scrollMetrics refuses), and an inline box inside one container refuses only that one.dragon/test/expected-dump.test.ts(also): the scroll range readback, and an engine refusal stops the dump only for a scroll view, with its reason.parity/test/ovfl-metrics.test.ts(also): a case with any refused container fails, so no container leaves the Chrome comparison.overflow-nested,overflow-auto-fits,overflow-hidden-x-auto-yandoverflow-replaced: their auto/scroll containers no longer paint abackground-color(their children keep theirs). Their rtl twins overflow past the start, and native now refuses a background there (review fix 2), but every layout case must lower on native (native-hostnativeCases; the regen stopped at hit-capture on overflow-nested-rtl). This follows the precedent ofoverflow-flex-reverse's transparent.autocontainers. All layout fixtures lower on native.overflow-background, ltr only (layout(id, ['ltr'])): puts the scroll-container backgrounds back in ltr, so an auto/scroll container with a painted background is still proven against Chrome at DPR 1, 2, 3 and 2.625 (re-review finding). It holds auto and scroll overflowing both axes, auto that fits, a flex row, overflow-x hidden beside auto, and a nested inner auto container.ovfl-metrics.test.tscounts each overflow fixture's own directions (now 35 cases), and a new test fails if these six scroll views with an opaque background (b1–b5, b6a) drop out of the native lowering.dragon/test/paint-radius.test.ts(from PNT1-radius: border-radius, its corner longhands and -webkit-border-radius drawn natively on iOS and Android (supersedes #80, #81) #198): itsNodeGeometryliteral gainsscroll: null, the field this PR adds.dragon/test/overflow.test.ts(also): the rounded scroll container refusal on ios and android at the radius, for three shapes. It fires beside the start-overflow background refusal on one element. A rounded hidden box, square corners and a corner with a zero component still compile.Outside the spec
Reviewed size
.macroscope/ignore.mdpaths).What passed
Head 059685f (the CI regen commit; 0 commits behind master 981c068):
pnpm ci:regen ovfl-b --head c1c53ba6d3db4a49b8e11b981f9b6d97e143830e: pass, regen commit 059685f, https://github.com/compiled-run/dragoncss/actions/runs/37881619635.pnpm typecheck: pass (local).expected-dump,interaction,overflow,s4a,scroll-decidable,registry-claims,profile-indexandpaint-radius, plus layoutoverflow.pnpm ci:test-fileson 059685f, https://github.com/compiled-run/dragoncss/actions/runs/37884749014: pass, 25 files, all passed, none skipped.native-backends12.ovfl-metrics17,native-host336,parity534,p6a-dash-oracle329,pnt1-radius13,pnt1-radius-edges1,hit-capture-identity7,pixel-reference12,chrome-ports13,registry-claims8,android-profile5,dist6,env-engine8,fixture-reader7,fonts-fixtures26,inline13,interaction-forced3,media-runtime8,media-sweep19,phrasing-blockified3,platform12,replaced-identity3,tree-faults3,values72.pnpm pr:review 231 --once: exit 0. UNREVIEWED (Macroscope spending limit); 0 findings.🤖 Generated with Claude Code
Generated by Claude Code