Skip to content

fix(signals): a mount a probing pass opened is born held whatever its own pass reads ahead (S-58′) - #4723

Open
ryansolid wants to merge 3 commits into
nextfrom
tembo/gate-leaf-window
Open

ryansolid wants to merge 3 commits into
nextfrom
tembo/gate-leaf-window

Conversation

@ryansolid

Copy link
Copy Markdown
Member

Summary

#4720's "any hop count" held for a plain-only leaf and broke once the leaf's one grouped effect also read ahead. The maintainer's table on plain next:

leaf reads <Show> 1-hop / 0-hop gate, grouped split
m0() only [F] [F] [F]
latest(m2) only painted painted painted
latest(m2), m0() (e1) [F] painted early [F]
latest(m0), m0() [F] painted early [F]

The born-held arm for a stamped mount sat inside frameRead's !verdict branch, so a mount whose own pass had opened a window was a verdict reader first and was served the screen: A10's pair rule, which belongs to a mainline leaf, not to a mount. read's pending arm handed such a reader to observeFlight the same way. Both now test the stamp first: a mount a probing pass opened observes the flight on its first pass whatever it read ahead, and is born held. The two bottom rows now read [F] in every column; the latest(m2)-only row still paints everywhere, since it reads nothing of the flight plainly.

Pins: S-58 case ′ (the e1 memos, the mixed leaf, three variants: gate: "memo", gate: "cond", split) and web/test/display-ahead-gate-leaf-reads.spec.ts, which is the table above as a test. The #3851 and #3540 matrices that guard the limits (a <Loading> inside the branch still shows its fallback ahead; memos and hidden mounts never count) are unchanged.

Size

Core floor −2 B brotli (the clause left frameRead's big condition; −21 B minified). isPending/latest +7 B (+47 B minified), within its cap; the three page scenarios that carry it move +47 B minified past their recorded minified and have their caps raised with ledger lines.

How did you test this change?

Full signals suite (6,745 pass, seat shadow at the base's six classes), the web default, hydrate and async-gen configs, the regression gate, the rulings check, node scripts/size/size.mjs (every scenario within its cap), and the table in real runner output before and after the change.


Want tembo to make any changes? Add a comment with @tembo and i'll get back to work!

View on Tembo  Review in Tembo  View Agent Settings

… own pass reads ahead (S-58′)

Co-authored-by: tembo[bot] <208362400+tembo-io[bot]@users.noreply.github.com>
@tembo tembo Bot added the tembo Pull request created by Tembo label Nov 8, 2026
@changeset-bot

changeset-bot Bot commented Nov 8, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a8ed5a1

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 15 packages
Name Type
@solidjs/signals Patch
test-integration Patch
@solidjs/web Patch
@solidjs/babel-plugin Patch
@solidjs/compiler Patch
@solidjs/diagnostics Patch
@solidjs/element Patch
@solidjs/h Patch
@solidjs/html Patch
solid-js Patch
@solidjs/universal Patch
board-example Patch
canvas-example Patch
server-dbmon-example Patch
todos-server-example Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented Nov 8, 2026 •

Copy link
Copy Markdown

Size (brotli, eager entry chunk)

scenario head vs base minified vs base minified vs recorded cap lazy chunks (not counted)
signals: core floor (createSignal/Memo/Effect/Root/flush) 7.42 KB 0 B 0 B −136 B 7.43 KB ✅
signals: + createStore 15.11 KB 0 B 0 B −21 B 15.13 KB ✅
signals: + isPending/latest 12.77 KB +18 B (+0.1%) +92 B 0 B 12.77 KB ✅
signals: + until/refresh (truth readers, no optimism) 8.10 KB 0 B 0 B −21 B 8.12 KB ✅
app: render + one signal (the simple-app floor) 9.79 KB 0 B 0 B −195 B 9.80 KB ✅
app: hydrating (no stores) with Show/For/Loading/Errored/lazy 19.05 KB 0 B 0 B −45 B 19.05 KB ✅ lazy-page.js 0.04 KB
app: hydrating + every store primitive family 33.83 KB −39 B (−0.1%) +92 B +92 B 33.89 KB ✅ lazy-page.js 0.04 KB
app: CSR with Show/For/Loading/Errored/lazy 13.73 KB 0 B 0 B −45 B 13.73 KB ✅ lazy-page.js 0.04 KB
app: CSR, observe tier (same app on the observe artifacts) 15.45 KB 0 B 0 B −45 B 15.50 KB ✅ lazy-page.js 0.04 KB
app: CSR, observe tier + attribution engine enabled 30.08 KB 0 B 0 B −45 B 30.12 KB ✅ lazy-page.js 0.04 KB
app: compiled floor (one template, one text hole, one delegated click) 9.97 KB 0 B 0 B −247 B 9.98 KB ✅
app: compiled CSR (JSX todo app: spread/merge/omit, events, class/style, keyed For, Show, Loading + lazy, store) 26.73 KB 0 B 0 B −35 B 26.75 KB ✅ stats.js 0.18 KB
app: compiled hydrating (the same JSX todo app through hydrate(), compiled hydratable) 32.92 KB 0 B 0 B −35 B 33.00 KB ✅ stats.js 0.20 KB
frames: eager client consumer (frames client + transport, lazy codec) 11.75 KB 0 B 0 B 0 B 11.77 KB ✅
page: base server components (hydrating + dynamic + frames + sf reference) 34.09 KB 0 B 0 B −32 B 34.11 KB ✅ assets.js 0.78 KB, decode.js 7.09 KB, lazy-page.js 0.04 KB, regions.js 1.06 KB, trace.js 9.28 KB, wire.js 3.48 KB
page: live server components (base + live/GET + action + isPending/latest) 40.73 KB −7 B (−0.0%) +92 B +92 B 40.76 KB ✅ assets.js 0.78 KB, decode.js 7.09 KB, lazy-page.js 0.04 KB, regions.js 1.06 KB, trace.js 9.29 KB, wire.js 3.48 KB
page: compiled base server components (the base page as JSX: templates with class/style/attributes/events, For/Show; no spread) 36.04 KB 0 B 0 B −46 B 36.00 KB ⚠️ over by 36 B, 66 B minified headroom assets.js 0.78 KB, decode.js 7.09 KB, regions.js 1.06 KB, sc-comments.js 0.20 KB, trace.js 9.29 KB, wire.js 3.48 KB
page: compiled live server components (the compiled base page + live/GET + action + isPending/latest) 44.85 KB +14 B (+0.0%) +92 B +81 B 44.85 KB ✅ eager (counted): web.js 23.66 KB; assets.js 0.78 KB, decode.js 7.09 KB, regions.js 1.08 KB, sc-comments.js 0.19 KB, trace.js 9.28 KB, wire.js 3.48 KB
page: base + router (base page + @solidjs/router: createRouter, two routes, preload, useNavigate) 41.56 KB 0 B 0 B −120 B 41.60 KB ✅ assets.js 0.78 KB, decode.js 7.09 KB, lazy-page.js 0.04 KB, regions.js 1.07 KB, server.js 1.03 KB, serverForms.js 3.58 KB, trace.js 9.25 KB, wire.js 3.48 KB
page: live + router (live page + @solidjs/router: createRouter, two routes, preload, useNavigate) 50.56 KB −17 B (−0.0%) +92 B +92 B 50.59 KB ✅ eager (counted): client.js 28.84 KB; assets.js 0.78 KB, decode.js 7.09 KB, lazy-page.js 0.04 KB, regions.js 1.07 KB, server.js 1.03 KB, serverForms.js 3.31 KB, trace.js 9.27 KB, wire.js 3.50 KB
server: floor (getRequestEvent + isServer) 1.27 KB 0 B 0 B 0 B 1.29 KB ✅
server: renderToString (the server-render floor) 22.90 KB 0 B 0 B 0 B 22.92 KB ✅

⚠️ Over the brotli cap within the minified allowance (passes)

  • page: compiled base server components (the base page as JSX: templates with class/style/attributes/events, For/Show; no spread): over brotli cap by 36 B; minified 108,500 B vs 108,546 B recorded with the cap (−46 B) — 66 B of the 20 B minified allowance left; +0 B minified over this PR's base

Bundled with Rolldown (what Vite ships), brotli q11, decimal KB. A scenario fails only when it is over its brotli cap and its minified size is more than 20 B over the minified recorded with the cap; over the cap within that allowance is brotli layout noise and passes with a warning. Caps and their recorded minified in scripts/size/scenarios.js; the floor and page caps in floor-caps.json are frozen (lower only, or Size-Exception: in the PR body). npm run ratchet lowers caps per RC; it never raises one (scripts/size/README.md).

Copy link
Copy Markdown
Member Author

The change checks out against the rules. It closes the gap D-25's status line admitted ("a mount whose own pass reads another node ahead still attaches early"). It also agrees with the #4284 ruling that JSX grouping never changes the result: a grouped leaf now keeps the same seat as <Show> and the split leaf. The size is fine (core −2 B brotli, isPending/latest +7 B, hello world unchanged).

Two things before merge:

  1. Rebase over next. chore(size): batch re-record after #4714, #4711, #4717, #4719 and #4720 #4718 (dbd746b) just re-recorded the same three cap lines this PR edits in scripts/size/scenarios.js: the store-families, compiled-live and live+router scenarios. So this conflicts, and those caps and capMinified values need re-measuring on top of the new record. The rebase also picks up ci: don't fail the required job when the Coveralls reporter can't install #4721, so a Coveralls reporter outage no longer fails job. This PR's job failure was only that; the tests passed.

  2. The new early arm in frameRead also fires during lane work. The clause it replaces only seated a stamped mount when (!lane || (c uninitialized && el pending)) && !verdict. The new arm runs for any uninitialized CONFIG_MOUNT_AHEAD pass, and it runs ahead of the lane-over return and the staleReader path. CONFIG_MOUNT_AHEAD is stamped whenever the creator probed, lane or not. So under an optimistic lane, a mount opened by a probing pass that reads a held node which is not itself pending now calls joinPass into the hold's transaction. Before, it was served the screen or read the re-based staging. That is D-37 ("lane work never makes a flush the hold's"). This comes from reading the code and is not reproduced. Keeping the old guard on the new arm should close it: (!lane || (el as Computed<any>)._statusFlags & STATUS_PENDING). A runner row would settle it either way: S-58 case ′ with the write inside an action that sets a guess on id.


Generated by Claude Code

tembo Bot and others added 2 commits November 8, 2026 10:59
Co-authored-by: Ryan <ryansolid@gmail.com>
Co-authored-by: tembo[bot] <208362400+tembo-io[bot]@users.noreply.github.com>
@tembo

tembo Bot commented Nov 8, 2026

Copy link
Copy Markdown

Both done, on top of next @ 701cbb3 (merge e52caa5, fix a8ed5a1).

1. Rebase. Took #4718's record for the three cap lines and re-measured on top of it. Core floor 7,421 B and the compiled floor 9,973 B are unchanged; isPending/latest is 12,766 B / 35,587 B minified, +18 B brotli over the batch record, recorded 12.77 KB with a ledger line. Every other cap passes as #4718 recorded it.

2. The lane guard. Confirmed by instrumentation, not just by reading: in case ′ with the write inside an action, the arm fired once under the lane, for the zero-hop memo gate whose leaf reads the guess's target plainly ([latest(m2), id()]: held, not pending), and joined the flush to the hold. No screen changed in any of the rows, but the join was real. The arm keeps the old guard, (!lane || el pending), as you wrote it.

The row you asked for found something else. Case ′ in an action, through <Show>, the truthiness memo and the split leaf, painted S-46's frame on next at the action's landing, latest(m2) 4 | detail 1, and kept it after detail answered. Only the single-memo gate held, and only because it re-creates its branch at the landing. The plain-only leaf under the same guess tore the same way, from the first lane flush. The path is not frameRead at all: under a guess lane the stamped mount's first pass reads the lane's re-asked node through lanes.ts laneRead's render-effect arm, which serves lane work the screen (A15's stale reader, D-28). Served, the mount initializes with the committed value inside the lane's frame; the lane reveals that frame at the landing, and the mount is not a first pass any more, so nothing holds it. laneRead now returns NOT_PENDING for a stamped, uninitialized mount reading a pending node, and the mount waits on the flight through read's throw, as in a mainline flush. 0 B on the core floor; the +18 B above is the guard and this arm together.

Rows: S-58 case ″ (your row: the mixed leaf in an action, forbidding both the in-lane frame and the landing frame) and case ‴ (the plain-only leaf in an action), each through <Show>, both hand-written gates and a split leaf. Both are [none] on every frame. The 2026-11-08 INTERNALS §7 entry and D-25's status line carry the two findings.

Gates locally: signals 6,745 pass with the shadow at six; web default and hydrate configs green; the server config's two failures (foreign-raw-text-4704 and the adapter rebuild case) fail identically on plain next in this sandbox, so they are its build state, not this branch. Rulings and rules-index checks pass; the size gate passes against the recorded caps.

@codspeed

codspeed Bot commented Nov 8, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 7.8%

1 warning

⚠️ 2 benchmarks are dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

❌ 4 regressed benchmarks
✅ 177 untouched benchmarks
⏩ 25 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
❌ projection derive: write one NESTED field (reference) 2.1 ms 2.4 ms -12.2%
❌ dbmon shallow full tick 34.3 ms 37 ms -7.16%
❌ fresh stores: create + first write + first commit (reporter shape) 14.7 ms 15.6 ms -5.88%
❌ dbmon full tick — shallow reconcile 224 ms 237.8 ms -5.81%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing tembo/gate-leaf-window (a8ed5a1) with next (701cbb3)

Open in CodSpeed

Footnotes

  1. 25 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 42473742484

Warning

No base build found for commit 701cbb3 on next.
Coverage changes can't be calculated without a base build.
If a base build is processing, this comment will update automatically when it completes.

Coverage: 78.945%

Details

  • Patch coverage: No coverable lines changed in this PR.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

Requires a base build to compare against. How to fix this →


Coverage Stats

Coverage Status
Relevant Lines: 1372
Covered Lines: 1143
Line Coverage: 83.31%
Relevant Branches: 1112
Covered Branches: 818
Branch Coverage: 73.56%
Branches in Coverage %: Yes
Coverage Strength: 30.05 hits per line

💛 - Coveralls

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tembo Pull request created by Tembo

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants