Skip to content

fix(web): observe the ExpandedLayRate region once the modal has portaled - #1213

Merged
mforce merged 3 commits into
mainfrom
fix/1203-lay-rate-observer
Oct 11, 2026
Merged

mforce merged 3 commits into
mainfrom
fix/1203-lay-rate-observer

Conversation

@mforce

@mforce mforce commented Oct 11, 2026 •

Copy link
Copy Markdown
Owner

Closes #1203

What changed

Modal renders its children into a portal on a second commit. The effect that created the region's ResizeObserver ran on the first commit, found scrollerRef null and returned, and nothing re-ran it until days changed. The region is now observed from a ref callback, which runs in the portal's own commit. Its cleanup disconnects the observer when the node unmounts or sync changes.

What the observation feeds, and why nothing visible changes today

sync() reads the region's width and scroll position to place the window box on the map, light the edge cues, enable the pager, choose fixed slots or a stretched strip, and write the "days shown" caption. The observer is what re-reads it when the window resizes or the phone rotates; without it only a scroll does.

The Dashboard never shows the defect. It sets the expanded report to null before every read, on open and on every range or flock change, so the region always mounts after days went to 0 and back. That identity change re-ran the old effect once the region existed, and the observer attached. The region only mounts with days unchanged when a caller passes data on the first commit, which is what the test does and what the Dashboard does not. The component no longer depends on its caller cycling data through null.

Before and after frames from a stack rebuilt at each head are byte-identical for the scenario closest to the defect (reopen the chart with its report already held, then shrink the window from 1280 to 700). Checksums are in the comment below.

Siblings checked

git grep -n ResizeObserver web/src finds two other users. useDayStrip observes the readout box and its dock from an effect keyed on the selected day, which is when the box mounts, so it attaches on first selection in both the card and the modal. FieldConsole's two observers watch nodes rendered in the same commit as their component, with no portal. Neither changed.

Proof

  • New test follows the region when it resizes, and lets go of it on unmount, run against main's ExpandedLayRate.tsx in this worktree: fails with expected undefined to be defined (no observer on the region). Against this branch: passes.
  • npx vitest run src/components/ExpandedLayRate.test.tsx src/components/DayStrip.test.tsx --maxWorkers=2: 39 passed
  • npm run typecheck and npm run lint: clean
  • Isolated stack cw1203 on port 8203, built once at 2517f991 and once with main's component, both torn down with down -v and the image removed.

File-size guard

The chart file crossed the 500-line limit by six lines, so the private ExpandedRangePicker moved to web/src/components/ExpandedRangePicker.tsx with the imports it owns (commit 7d09bac7). No behaviour change; FileSizeLimitTests passes locally and no legacy entry was added.

Modal renders its children into a portal on a second commit, so the
mount effect that created the ResizeObserver found scrollerRef null and
returned. After a resize the window box, edge cues, pager and caption
kept describing the old width until the next scroll. A ref callback runs
in the portal's own commit; its cleanup disconnects on unmount.

Closes #1203
@mforce mforce added this to the Platform hardening milestone Oct 11, 2026
@mforce

mforce commented Oct 11, 2026

Copy link
Copy Markdown
Owner Author

Before and after frames, 1:1, light theme, from the isolated stack rebuilt at main's component and at 2517f991. Scenario: sign in to the readme farm, expand the Lay rate chart, close it, reopen it with its report already held, shrink the viewport from 1280x800 to 700x800. The four pairs (1280 and 700, light and dark) are byte-identical, so the fix changes nothing the Dashboard renders today.

0d8ecd5494c28abf4dfacea5b231615a  before-1280-dark.png   after-1280-dark.png
54a18b49d02aa4b46ba026e86ea46824  before-1280-light.png  after-1280-light.png
ec5cd9ff4dc2ab6ac0b4cd6b36a27114  before-700-dark.png    after-700-dark.png
be8d5f424e5215aea49f299049d28943  before-700-light.png   after-700-light.png

Before, 700 light
After, 700 light

@mforce

mforce commented Oct 11, 2026

Copy link
Copy Markdown
Owner Author

Review round 1 (Codex, read-only) at 2517f991: no findings. The file-size failure is known and being fixed.

  • Observer lifetime. The callback ref observes the real .lay-expand-scroll node once it has portaled. React 19 runs the returned cleanup on removal or replacement, so close and unmount disconnect it. The callback sets no state, so there is no render loop.
  • Test. The new test uses the real MUI Modal and fails on main for the right reason: the region is never observed (observed.get(region) is undefined). It does not fail because of the mock's shape.
  • Why the screenshots match. Dashboard clears the report and passes null while it loads, so the region mounts when the day count changes and the old effect retried. A caller that passes data on its first mount would have stayed stale. The body states this correctly.
  • Siblings. The other ResizeObserver sites (useDayStrip, FieldConsole) do not have this portal timing bug.

VERDICT: 0 product defects, 0 test/doc-only findings.

The chart file crossed the 500-line limit by six lines. The range picker
is a private sub-component used only here, so it moves to its own file
with the imports it owns. No behaviour change.
@mforce
mforce merged commit 6025557 into main Oct 11, 2026
21 of 22 checks passed
@mforce
mforce deleted the fix/1203-lay-rate-observer branch October 11, 2026 03:43
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.

fix(web): attach the ExpandedLayRate resize observer after the modal portals

1 participant