Skip to content

Client: Apply payload and report what values alone could not do - #2274

Merged
marcoroth merged 2 commits into
slots/valuesfrom
slots/apply
Aug 18, 2026
Merged

Client: Apply payload and report what values alone could not do#2274
marcoroth merged 2 commits into
slots/valuesfrom
slots/apply

Conversation

@marcoroth

Copy link
Copy Markdown
Owner

This pull request adds apply, which takes what DynamicsCompiler produces as it comes, and reports what values alone could not do.

const report = slots.apply(payload)
// { applied: 7, deferred: [] }

SlotIndex could write one slot at a time and nothing more, so every caller with a payload wrote the same walk over it, and the three that did each got it wrong differently first. A payload names its template, its version and which rendering it is, so nothing has to be said about where it goes, and a partial's values nest inside the slot that rendered it and are handed to that partial's own region. One call covers a page however many templates it was built from.

What values cannot do is the other half of the answer. A version that does not match applies nothing, since indices compiled against another template would land somewhere plausible and wrong. A branch the page never had is built when the server parked its markup, and deferred when it did not. A collection whose rows have changed still fills the rows both sides agree on and reports the rest. A caller reads deferred to know what to fetch.

DynamicsCompiler also put its slot visitor at the front of the visitor stack while a handler compiling the same template to markup puts it at the back. Every visitor in between saw a different tree in each case, and one that adds a dynamic node to one and not the other moves every index after it, which is the whole failure the visitor was made the source of indices to prevent. Whatever the caller passes runs first now, and the slot visitor last, so the two compiles number the same tree.

Two row bugs had to go first, both of them slot.rows being built at scan time and never maintained. prune() walked a region's slots but never its rows, so a row whose markers had left the page stayed indexed and reconcile kept asking for it to be removed. And reconcile read its order from Map insertion order, fixed at first scan, so asked for the order the page was already in it reported every row as needing a move. prune() rebuilds each collection from the rows still connected, in document order, and reconcile reads the document.

@marcoroth marcoroth added the reactivity Reactive ERB templates: diff and re-render only what changed label Aug 18, 2026
@github-actions github-actions Bot added ruby Ruby source for the gem and its libraries typescript TypeScript source across the javascript/ packages rbs RBS type signatures in sig/ engine Herb engine and Rails template compilation rubygem The herb RubyGem and its packaging labels Aug 18, 2026
@nx-cloud

nx-cloud Bot commented Aug 18, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit fb2a798

Command Status Duration Result
nx run-many -t test --all --parallel --exclude=... ✅ Succeeded 3m 9s View ↗
nx run-many -t build --all --exclude=herb-langu... ✅ Succeeded 15s View ↗
nx build @herb-tools/tailwind-class-sorter ✅ Succeeded 1s View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-08-18 03:42:21 UTC

@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown

🌿 Interactive Playground and Documentation Preview

A preview deployment has been built for this pull request. Try out the changes live in the interactive playground:


🌱 Grown from commit fb2a798


✅ Preview deployment has been cleaned up.

@pkg-pr-new

pkg-pr-new Bot commented Aug 18, 2026

Copy link
Copy Markdown
npx https://pkg.pr.new/@herb-tools/formatter@2274
npx https://pkg.pr.new/@herb-tools/language-server@2274
npx https://pkg.pr.new/@herb-tools/linter@2274

commit: fb2a798

@marcoroth marcoroth added the client-runtime Browser runtime for the Herb slots. label Aug 18, 2026
`SlotIndex` could write one slot at a time and nothing more, so every caller with
a payload wrote the same walk over it, and the three that did each got it wrong
differently first. `apply` takes what `DynamicsCompiler` produces as it comes.

A payload names its template, its version and which rendering it is, so nothing
has to be said about where it goes, and a partial's values nest inside the slot
that rendered it and are handed to that partial's own region. The values compiler
stamps the occurrence at the start of the rendering rather than the end, where
the region marker counts it, so a template rendering itself agrees with itself.

What values cannot do is the other half of the answer. A version that does not
match applies nothing, since indices compiled against another template would land
somewhere plausible and wrong. A branch the page never had is built when the
server parked its markup and reported otherwise. A collection whose rows have
changed still fills the rows both sides agree on and reports the rest. A caller
reads `deferred` to know what to fetch, and the round trip now goes through
`apply` rather than through a loop written for the test.

Two row bugs had to go first, both of them `slot.rows` being built at scan time
and never maintained. `prune()` walked a region's slots but never its rows, so a
row whose markers had left the page stayed indexed and `reconcile` kept asking
for it to be removed. And `reconcile` read its order from Map insertion order,
fixed at first scan, so asked for the order the page was already in it reported
every row as needing a move. `prune()` rebuilds each collection from the rows
still connected, in document order, and `reconcile` reads the document.

`fillSlots` handled only comment-delimited slots while `blankSlots` handled
element anchors too, so `materialize` could not fill a `data-herb-child` or an
attribute slot in a parked branch. Nothing caught it because the one test that
materializes such a branch passed no values and wrote them afterwards.

(cherry picked from commit ac3ee1e9ddb951d69babc49b59e91c9659385ca8)
(cherry picked from commit 81b0dbc)
…ler runs it

`DynamicsCompiler` put its slot visitor at the front of the stack while a handler
compiling the same template to markup puts it at the back. Every visitor in
between sees a different tree in each case, and one that adds a dynamic node to
one and not the other moves every index after it. Which is the whole failure the
visitor was made the source of indices to prevent.

Whatever the caller passes runs first now, and the slot visitor last, so the two
compiles number the same tree.

(cherry picked from commit 85bfad2b073adc9c7a10a5024f9fe1dbe3be8977)
(cherry picked from commit c62981c)
@marcoroth
marcoroth merged commit 0dfd3d9 into main Aug 18, 2026
72 of 79 checks passed
@marcoroth
marcoroth deleted the slots/apply branch August 18, 2026 12:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

client-runtime Browser runtime for the Herb slots. engine Herb engine and Rails template compilation rbs RBS type signatures in sig/ reactivity Reactive ERB templates: diff and re-render only what changed ruby Ruby source for the gem and its libraries rubygem The herb RubyGem and its packaging typescript TypeScript source across the javascript/ packages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant