Skip to content

FORM-a 3/3: input[type=range] as Chrome's UA shadow tree, CSS-painted, with the slider pseudo-elements cascaded per part - #79

Open
thejackshelton wants to merge 482 commits into
review/form-a3-v2from
form-a4-v2
Open

thejackshelton wants to merge 482 commits into
review/form-a3-v2from
form-a4-v2

Conversation

@thejackshelton

@thejackshelton thejackshelton commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

FORM-a part 3 of 3 (T052, A4 in notes/T052J-form-a.md): input[type=range] as Chrome's UA shadow tree, CSS-painted. It is stacked on FORM-a part 2 (#78), an existing stack draining under the no-stacks rule. For review, its base is review/form-a3-v2, part 2's head c201360; it retargets to master after #78 lands.

What changed

  • The range is laid out as Chrome's shadow tree. The input is a flex container holding a container, a track (a block flex item, align-self center) and a thumb. The parts resolve as synthetic elements at <input>::container, ::track and ::thumb, styled by FORM-0's captured UA shadow rules (forms/ua-shadow.generated.ts). An unexpected rule in that data throws.
  • Lowering. The input lowers to A1's range control. Its default track length is FORM-0's captured width (129 px). The thumb lowers to a slider-thumb control, whose ratio comes from FORM-0's Decimal model of min, max, value and step.
  • Selectors and cascade.
    • ::-webkit-slider-thumb and ::-webkit-slider-runnable-track are handled at the end of a top-level selector, counted as a type selector.
    • They stay refused inside a selector argument, before another compound, and as ::-webkit-slider-container.
    • The element cascade never takes them: each part cascades its own rules on the input's chain, with the input's direction.
  • Default width. In block flow, a range's auto width fits its content (129 px plus its edges). This is the A3/A4-1 block.ts hunk; the new fixtures confirm it, and turning it off fails them.
  • Web output. The track and thumb get ::-webkit-slider-runnable-track and ::-webkit-slider-thumb rules on the input's class. Each part has its own band diffs under @media.
  • Attributes. type, min, max, value and step are handled on input. The type check is ASCII case-insensitive, so type="RANGE" is a range.
  • Capture (parity/src/capture.ts). Each part's box and computed values are recorded through Chrome's DevTools protocol (DOM pierce and getBoxModel), as FORM-0 does.
    • Only CDP's "Could not compute box model" error (a part with display: none, probed) counts as no box. Any other error throws.
    • The capture checks its range count, and that every property came back.
    • Pages without a range never open the session, so earlier captures are unchanged.
  • Refused:
    • other input types (FORM-b);
    • a range whose input or thumb is not appearance: none (FORM-b);
    • an inline-level range (RF-INL);
    • a column or reversed flex direction on the range or a part;
    • a state pseudo-class on a range part;
    • an absolutely positioned thumb.
  • Port registry. ios-layout.ts lowerBox is listed as a reference of range_input_type.cc: a range lowers to a flex container, as CreateLayoutObject makes it.
  • Fixtures:
    • layout: controls-range-block (the min/max/value/step matrix and solid thumb and track colours) and controls-range-flex (the demo's time control with display: block), each in ltr and rtl;
    • rejects: input-text, range-theme, range-inline, range-flex-direction, range-part-direction, range-pseudo-state and range-thumb-abspos.

What passed (host, at the head of this PR)

  • pnpm regen is at a fixed point: "fixed point after 1 passes, 19 steps run", no files changed. Generated outputs are in their own commits, each naming its command.
    • No earlier capture, vector, break vector or media capture is modified or deleted against part 2.
    • Emitted CSS changes only by the compilation digest header.
    • Profile rows only gain rows and proof cases: ios and android 1992 → 1994, web 1998 → 2000. No row is lost, no status changes and no proof case is dropped.
  • pnpm typecheck passes.
  • pnpm test (full suite): 161 of 166 files pass, and 3736 tests pass with 1 skipped.
    • The 12 failing tests and 1 failing suite are the device-record checks only: lanes.test, lanes-records.test, lanes-concurrent.test, device-failures.test and p6a-promotion.test. They fail only because the committed packages/parity/out/lanes.json is host-only; the train's device run for this position commits it.
  • Range cases. Both pass the host lanes in ltr and rtl, and every part box matches Chrome. Forcing the ratio to 0, or turning off the fit-content hunk, makes them fail. All 7 rejects pass.
  • controls-identity.test. Every case that predates this part, the button cases included, resolves no range, lowers no range or thumb box, and keeps its vector input and web body.
  • North star (errors 323 → 320):
    • the range's type, min, max and value attribute errors (4 → 0), its <input> element error (1 → 0) and its ::-webkit-slider-thumb selector error (1 → 0) all clear;
    • supported elements reach 62 of 62;
    • the demo's range is inline-level inside .track, so it is refused once per target, naming RF-INL (T053).
  • wpt web.json (regenerated): only grid-align-baseline-nested-intrinsic-sizing-001's not-runnable reason changes. Its input is now resolved, so it is refused at display: grid. Its status is unchanged.

Changed tests and checks, with reasons

  • selectors.test: the thumb and track pseudo-elements are now handled, so their refusal pin is replaced. New pins cover what stays refused: inside an argument, before another compound, and ::-webkit-slider-container.
  • seams.test: the 4c1331c byte pin strips the new Selector.pseudoElement field, which every selector there carries at its default, null. This is the same treatment as the earlier added fields.
  • ua.test: the web emitter's import list gains rangePartOf from analysis/resolve.ts. It is a fact of the resolved tree; the emitter still matches no selector and reads no lowering.
  • block-elements.test: the control tag check reads the input's element key by type.
  • attributes.test: type, min, max, value and step are handled on input.
  • controls-identity.test: 5 → 7 control layout fixtures, plus the A4 identity block above.

Catch-up onto master (2026-10-05)

Merged form-a3-v2 after its catch-up (#72's last review head, then origin/master f50b6ad with HOTSPOT-SPLIT #137/#138). Regenerated in fd19b55 (pnpm regen). The "What passed" section above was measured at the earlier head.

  • packages/dragon/src/emit/web-css.ts: master's per-element animation lines and its inherited colour under an animating element (T065 R9, R15) now apply to the range's pseudo-element rules. A part's declarations and band diffs take its input's under-a-source flag, and the walk still skips range parts.
  • computed-checks.ts: both sides' imports. block.ts: the range joins the button in the fit-content auto width, against master's stretched size.
  • The p1 corpus counts are raised in p1-floor.json (DRAGON_FLOOR_WRITE=1). No glyph-clearance pin changed. No hot-spot edits under HOTSPOT-SPLIT: the range fixtures are in the controls group's own file.

Passed at 255456a (no local full test, per the lane contract): pnpm typecheck; targeted tests (this stack's tests, the layout package, parity, values, replaced-identity, controls-identity, pixel-reference, web-css, animations, chrome-ports, css-escapes, seams, s4b, paint-seams, registry-claims, diagnostics, animation-kinds, regen and corpus-dpr): 65 of 65 files, 2021 tests passed, 2 skipped.

Reviewed size: about 82 KB (31 files) against part 2.

🤖 Generated with Claude Code

thejackshelton and others added 27 commits October 5, 2026 11:48
…loor-merge.ts, merge=dragon-floor) takes the larger count and the union of names, refusing anything else; pnpm setup:git and the landing driver register it
… twin around the shared collectNodes; the identity keeps master's IDENTITY_NEW (SELD-R1b and PNT2 transform fixtures) beside MQ-R0's IDENTITY_LATER and IDENTITY_RULED
… runtime animator and the hit table; the block button's context

- flex.ts (Medium 1): a column flex item's content size suggestion measures a control with its own height treated as auto
  (columnIntrinsicBlockSize, master's T128 path), as it does a plain box; it kept the control's specified height before.
- rt-animator.ts (Medium 2): lengthBase and patchInput walk into form controls and their contents, so a length animation on a
  control, or on a box inside one, is read and written instead of dropped.
- rt-hit.ts (Medium 3): the hit table lists a control and its contents as the boxes they are laid out as (indexZoomed,
  fragmentOrders, boxNodes), so a tap on a button or on its text hits the button, and on an element inside it that element.
- block.ts (Low): the block button's formatting-context rule now cites layout_block_flow_hot.cc:38-45
  (LayoutBlockFlow::CreatesNewFormattingContext: align-content other than normal); ports.json swaps the box_fragment_builder.cc
  entry for it.
- compare.ts (Low): anonymous boxes inside a control (a button's text wrapper) are Dragon's own, as elsewhere.
- validate.ts (Low): a slider thumb whose parent is not a block container is refused (only block flow moves it).
- capture-form-data.ts: a button-contexts capture (14 cases, ltr and rtl): a block button in block flow (auto and fixed margins,
  a sibling's collapsing margin, a child's margin held by the button's formatting context, centring) and as a short column flex
  item (content size suggestion, flex-shrink 0, min-height 0, a specified height below the content).

Tests: controls.test compares every context case with Chrome at 0 LU (fails without the flex fix on c8-c10, and without the
formatting-context rule on c5); rt-animator.test reads and patches a control's and an inner box's lengths; rt-hit.test hits a
button, its label text, an inner element, and activates the button, in block and flex parents; compare-controls.test passes a
button's anonymous text box on its compared lines; the validator refuses a thumb in a flex track or directly in the range.
… (node --conditions=dragon-internal scripts/capture-form-data.ts, the MANUAL forms capture; every other forms output byte-identical)
…TING (generated outputs under the merge driver); generated outputs are rebuilt by regen-on-ci
Floor and pin files merge structurally (git merge driver dragon-floor)
…p + rename) and restored before a text merge on refusal; two insertions at one place of an ordered list are refused
…ength animation on an img was dropped on native

rt-animator.ts lengthBase (findBox) and patchInput (patchBox) walked only box children, so an img or iframe, a replaced leaf
since REPL-a (#72), was neither read for its length endpoints nor written with the frame's lengths: a width, height, margin or
padding transition on an img ran in the web output and silently did nothing on iOS and Android. findStyle and patchChild now
treat a replaced leaf's style as a box's (the same shape as FORM-a's control fix in #77, 5077d8b, which is not on master yet).

rt-hit.ts already lists a replaced leaf as its own target (replacedNode, rt-hit.test "hitTableOf hit-tests a replaced element
as a childless box"); no change there.

Test: rt-animator.test reads an img's width and margin-left and patches its width (fails without the fix).
…two runs never blame each other's writes on their own steps
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: cbfa388
Run: https://github.com/compiled-run/dragoncss/actions/runs/37341041399
…e the alias as written

- analysis/animations.ts longhandsNamed resolves a legacy alias, as css_animations.cc
  CalculateTransitionUpdateForStandardProperty does (ResolveCSSPropertyID), so
  transition: -webkit-logical-width 1s lists width instead of warning.
- Declaration.alias records the written alias; parse, keyframe, profile (set by ...),
  unproven-context and var() substitution messages name it.
- aliases.ts records the css_properties.json5 path, tag, sha256 and line ranges.
floor-merge: never-lower for pins, whole-file replace, no guessing in ordered lists
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: 205bbd9
Run: https://github.com/compiled-run/dragoncss/actions/runs/37342842729
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: f24f174
Run: https://github.com/compiled-run/dragoncss/actions/runs/37342969009
thejackshelton and others added 29 commits October 6, 2026 11:47
…erated) into form-a3-v2. Conflicts: ios-layout.ts lowerBox keeps GRID's gridParent and grid styles and FORM-a3's ControlBox return (a button lowered as a grid item keeps its placement); the root check keeps FORM-a3's control-box refusal; fixtures.ts GROUPS keeps controls and master's ctx-proof, display-legacy, env; p1-floor.json takes the larger counts (form-a-v2's)
…a4-v2. Conflicts: master's interaction states (SELD-R2) meet FORM-a4's range parts: cascadeElement takes both the range part and the interaction state, and partSelectorMatches passes the state on, so input:hover::-webkit-slider-thumb is probed and resolved per state; web-css emits a part's state diffs as .dgN::<pseudo> state rules beside the element's own; capture.ts keeps master's collectNodes and transform twin and FORM-a4's range-part capture; p1-floor.json takes the larger counts
…es per interaction state and is emitted as a :root:has(.dgN:hover) .dgN::-webkit-slider-thumb state rule (fails without passing the state to partSelectorMatches)
…ted and floor tests): chrome-ports-floor.json css-escapes-floor.json p1-floor.json; every change is a gain
Tests: git ls-files calls get a 64 MB buffer (fixes ENOBUFS that failed #91 and #194 in queue 59)
…ted and floor tests): packages/translate/test/p1-floor.json
Commands: pnpm regen; pnpm run parity:devices; pnpm regen
…ted and floor tests): packages/translate/test/p1-floor.json
INL1a 5/5 (C2): lowering inline boxes and <br>s, native runtime emit, 7 inline fixtures and lanes
…o form-a-v2. Conflicts: a ControlBox now carries a strut like LayoutBox and holds the same children (boxes, controls, replaced leaves, inline content); every walk over box children (layout, environment, validator, animator, hit testing, harness decoder, expected dumps, native Swift/Kotlin walks, parity walkers) visits controls and INL1a's inline boxes and <br>s; the validator checks control fields with the strut and keeps the slider-thumb and in-control abspos rules
…ted and floor tests): paint-seams-floor.json p1-floor.json; every change is a gain
…es a strut, validates and is laid out as a block-flow and as a flex child, and without its strut is refused (fails if block flow or flex item collection skips control boxes)
…1a C2) into form-a3-v2. Conflicts: ios-layout.ts lowerBox takes INL1a's Lowerer and inline runs and still returns a block <button> as a ControlBox, with the strut INL1a gives a box (a control child is not inline content); computed-checks.ts runs INL1a's checkInline on buttons too, beside checkButton, and keeps the button walk state
…nto form-a4-v2. Conflicts: resolve.ts visit keeps the range part argument and the UA shadow parts and then INL1a's pending inline boxes; ios-layout.ts gives every control box (button, range, thumb) INL1a's strut; computed-checks.ts keeps the per-tag control checks and runs INL1a's checkInline on every non-replaced element
…ted and floor tests): packages/translate/test/p1-floor.json
…ted and floor tests): packages/translate/test/p1-floor.json

This branch has not been deployed

No deployments
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