Skip to content

fix(shim): live viewport size and a ResizeObserver that fires - #49

Merged
pathscale merged 5 commits into
masterfrom
fix/shim-live-viewport-resize-observer
Sep 25, 2026
Merged

pathscale merged 5 commits into
masterfrom
fix/shim-live-viewport-resize-observer

Conversation

@pathscale

@pathscale pathscale commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

Two web-API shim bugs in apps/chuzz/src/document_loader.rs, found while debugging an @pathscale/ui Select on crates.vip. The dropdown opened at the left edge of the window and stayed there. Both stubs were written when nothing could measure a box from script; getBoundingClientRect now returns laid-out boxes, so both can be real.

1. innerWidth / innerHeight reported a fixed 1440x960 forever

The engine (ps-blitz-script 0.4.11) owns them as live accessors over the viewport. A page's scripts run before the host sets that viewport, so at boot they read 0. The shim took 0 for "missing" and replaced the accessors with 1440x960 for good.

Fix: capture the engine's getters and answer with them, falling back to 1440x960 only while they still read 0. screen and matchMedia's dimension branch read the same live size. Assigning innerWidth keeps the assigned value (replaceable attribute). No getter reads a shimmed name, so the old recursion cannot return.

2. ResizeObserver never fired

A silent stub. A component that measures, moves and relies on the observer to settle was left wherever its first frame put it.

Fix: a real observer over border boxes, measured on animation frames. With no engine resize notification, measuring is armed by observe(), resize/scroll, pointer, keyboard and input events, and by a size change it finds; it disarms after 30 quiet frames, so a page that observes something permanently still goes idle for rendered QA's idle and drift checks. Entries follow the specification's shape; contentRect and the box sizes describe the border box because computed style does not report padding to script.

Measured (chuzz-headless, 1344x900, a Select in a table row, stock @pathscale/ui 3.2.4)

before after
innerWidth x innerHeight after the viewport is set 1440x960 1344x900
ResizeObserver on a table, then resized nothing 0 to 968, then 968 to 600
Select list vs its trigger left 88 under a trigger at 512, never corrected first frame at 8, then 479 under its trigger at 479

The remaining one-frame misplacement is @pathscale/ui's own, fixed in pathscale/ui#296.

Checks

  • cargo build --release -p chuzz --bin chuzz-headless --no-default-features --features capture,javascript: no warnings.
  • cargo test --release -p chuzz --no-default-features --features capture,javascript: 81 pass, 3 fail. The 3 are capture::tests that need system-fonts (a blank capture without a font); they fail identically on master with the same features.
  • cargo fmt -p chuzz -- --check: clean.
  • Not run: a build with system-fonts, the GUI.

3. ResizeObserver reports the content box

Follow-up now that ps-blitz reports padding and border (pathscale/ps-blitz#102): the content box is the border box less both, which is what an observer watches by default; { box: 'border-box' } is honoured. On an engine without the edges they read as zero and the content box falls back to the border box (the previous behaviour), so this is safe on the published engine.

Measured on a box with content 100x20, padding 10, border 2: published engine 124x44 (fallback); ps-blitz#102 content 100x20 at x 12, border box 124x44.

Fixed in the engine instead (pathscale/ps-blitz#102)

  • MutationObserver: the shim's stub never fired. ps-blitz now defines a real one; the shim's typeof ... === 'undefined' guard then leaves it alone. chuzz picks it up with the next ps-blitz release (caret ^0.4).
  • getComputedStyle padding, border and margin, and clientWidth as the padding box.

Release

Bumps chuzz to 0.1.40, so merging publishes a new headless-v0.1.40 host. Fleet sites whose QA pins the headless-host action need their pin moved to pick it up.

Against the published engine

ps-blitz 0.4.12 is on crates.io and this branch resolves it through ^0.4 with no manifest change. A release build from crates.io (no local override) on the probe page: MutationObserver reports attribute and child changes, ResizeObserver reports a 100x20 content box (border box 124x44), innerWidth is 1344, and the Select opens under its trigger.

Hard-coded local path

scripts/qa-visible-app.sh made its default artifact directory under /private/tmp, which exists only on macOS; it now uses ${TMPDIR:-/tmp}, where the rest of the script already looks. bash -n passes and the default resolves under TMPDIR.

meh added 5 commits September 25, 2026 08:43
The engine owns innerWidth and innerHeight as live accessors over the
document viewport, but a page's scripts run before the host sets that
viewport, so at boot they read 0. The shim took the 0 for a missing value
and replaced the accessors with a fixed 1440x960 for good. A 1344-wide
headless page reported 1440 for its whole life, and anything clamped to
the viewport was placed against a width the page does not have.

The shim now captures the engine's getters and answers with them, falling
back to 1440x960 only while they still read 0. screen and the dimension
branch of matchMedia read the same live size. Assigning innerWidth keeps
the assigned value, as a browser's replaceable attribute does.

Measured in chuzz-headless on a 1344x900 page: innerWidth read 1440x960
after the viewport was set; it now reads 1344x900.
The stub never fired, on the grounds that nothing could measure a box from
script so any entry would carry a zero size. getBoundingClientRect now
returns laid-out boxes, and the silence had become the bug: a component
that measures, moves and relies on the observer to settle was left where
its first frame put it. @pathscale/ui's Select opened its list at the
left edge of the window and kept it there.

The observer measures border boxes on animation frames. The engine has no
resize notification, so measuring is armed by observe(), resize and
scroll, pointer, keyboard and input events, and by a size change it finds,
and disarms after 30 frames with no change, so a page that observes
something permanently still goes idle. Entries follow the specification's
shape; contentRect and the box sizes describe the border box because
computed style does not report padding to script. The first observation
reports any size other than 0x0, and a throwing callback is reported
without stopping the others.

Measured in chuzz-headless: an observed table reported nothing before;
it now reports 0 to 968 on observe and 968 to 600 when resized. The
stock Select list moved from left 88, under a trigger at 512, to
left 479, under its trigger at 479.
The observer measured border boxes only, because computed style reported
no padding or border to script. ps-blitz now reports them
(pathscale/ps-blitz#102), so the content box is the border box less
both, which is what an observer watches by default; { box: 'border-box' }
is honoured. On an engine without the edges they read as zero and the
content box falls back to the border box, which is the previous behaviour.

Measured on a box with content 100x20, padding 10 and border 2: the
published engine reports 124x44 (the fallback); ps-blitz#102 reports a
content box of 100x20 at x 12 and a border box of 124x44.
Ships the live viewport size and a ResizeObserver that fires, so
consumers of the headless host (every fleet site's QA) get them.
/private/tmp exists only on macOS, and naming it pinned the script to one
kind of machine. The default artifact directory now goes under TMPDIR,
falling back to /tmp, which is where the rest of the script already looks.
@pathscale
pathscale merged commit 094d77b into master Sep 25, 2026
5 checks passed
@pathscale
pathscale deleted the fix/shim-live-viewport-resize-observer branch September 25, 2026 07:34
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