Announce Fetch content updates with the dom-update protocol event - #632
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FVXrJ8idMfvt667yJadvB8
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FVXrJ8idMfvt667yJadvB8
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FVXrJ8idMfvt667yJadvB8
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FVXrJ8idMfvt667yJadvB8
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FVXrJ8idMfvt667yJadvB8
Code ReviewRisk: Low — The change adds the The implementation supports synchronous runner registration, last-call-wins substitution, transitioner objects, fallback application after runner failures, and serialized/batched default view transitions. Documentation, exports, and coverage were updated consistently with the declared intent. Still open from earlier reviews (1 finding):
Review usage: 37,207 in (20,903 cached) / 544 out tokens — $0.0129 (openrouter/openai/gpt-5.6-luna, thinking: low) Reviewed by @weareikko/code-review v0.9.5 for commit 2945e5d. Previous review runsPrevious run archived 2026-08-13T12:11:07ZCode ReviewRisk: Medium — issues that should be addressed before merge. Fetch now emits a bubbling 1 issue found:
Still open from earlier reviews (1 finding):
Review usage: 100,548 in (70,895 cached) / 1,335 out tokens — $0.0261 (openrouter/openai/gpt-5.6-luna, thinking: low) Reviewed by @weareikko/code-review v0.9.5 for commit f65ddf0. Previous run archived 2026-08-13T12:05:23ZCode ReviewRisk: Medium — issues that should be addressed before merge. This change adds synchronous, last-write-wins 1 issue found:
Review usage: 56,841 in (27,432 cached) / 1,123 out tokens — $0.0231 (openrouter/openai/gpt-5.6-luna, thinking: low) Reviewed by @weareikko/code-review v0.9.5 for commit c80e3e3. Previous run archived 2026-08-13T08:57:46ZCode ReviewRisk: Low — No blocking issues found; the change is safe to merge. Adds a synchronous Review usage: 39,539 in (26,277 cached) / 1,133 out tokens — $0.0130 (openrouter/openai/gpt-5.6-luna, thinking: low) Reviewed by @weareikko/code-review v0.9.5 for commit 11db518. Previous run archived 2026-08-13T08:16:22ZCode ReviewRisk: Low — The change is safe to merge; no blocking defects were found. Adds the Review usage: 12,858 in / 392 out tokens — $0.0092 (openrouter/openai/gpt-5.6-luna, thinking: low) Reviewed by @weareikko/code-review v0.9.5 for commit c828cf1. |
Export sizeBundled per export with peer dependencies left external, dynamic imports excluded and the output minified; sizes are gzipped. @studiometa/ui
Unchanged (118)@studiometa/ui
@studiometa/ui-mapbox
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #632 +/- ##
============================================
+ Coverage 87.17% 87.24% +0.07%
Complexity 145 145
============================================
Files 146 147 +1
Lines 5083 5111 +28
Branches 957 962 +5
============================================
+ Hits 4431 4459 +28
Misses 569 569
Partials 83 83
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
wrap((apply) => ...) says what the listener does — wrap the DOM change in its own transition runner — where through only described the data path. The exported runner type follows: FetchThroughRunner becomes FetchUpdateWrapper. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FVXrJ8idMfvt667yJadvB8
The default path called document.startViewTransition directly, racing every other view transition on the page over the one-transition-per- document limit. It now runs through the shared viewTransition scheduler: updates requested in the same tick batch into a single transition (covered by a new simultaneous-updates spec), batches serialize behind in-flight transitions, and the unsupported-API fallback moves into the scheduler. The update() promise now settles when the transition finishes instead of when it becomes ready. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FVXrJ8idMfvt667yJadvB8
| apply(); | ||
| } | ||
| } | ||
| } else if (viewTransition) { |
There was a problem hiding this comment.
issue: Always emit the after-update event when scheduling fails
viewTransition() rejects when the native transition's finished promise rejects, so this await exits before fetch-update-after is emitted. The DOM update callback may already have run before the transition is cancelled or rejected, and this also makes Fetch.update() reject, changing the previous path's completion behavior. Catch scheduler failures (with appropriate warning/fallback handling) and emit fetch-update-after in a finally block.
Confidence: high.
Reviewed by @weareikko/code-review v0.9.5 for commit c80e3e3.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FVXrJ8idMfvt667yJadvB8
Satisfies the repo's func-style lint rule; identical commit on both branches carrying the shared helper so the file stays byte-identical. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FVXrJ8idMfvt667yJadvB8
What
Before applying fetched content,
Fetchnow dispatches a dedicated bubblingdom-updateprotocol event — emitted after thefetch-updateevent, whose payload is back to its original{ instance, url, requestInit, fragment }shape. The protocol is shared plumbing (emitDomUpdate()/runWrapped()inpackages/ui/src/utils/dom-update.ts) that other components announcing DOM changes can reuse.The event's
detail.wrap()(a bare object — no argument array) lets any listener substitute the runner that applies the fetched content — modeled on Turbo'sturbo:before-render+event.detail.rendersubstitution and on theevent.detail.waitUntil()extendable events added toDialogin #627.wrap()accepts aDomUpdateRunner, which is either form:(apply: () => void) => void | Promise<unknown>receiving theapplyfunction that injects the fetched content into the DOM; its return value is awaited beforefetch-update-afteris emittedupdate(mutate)method (theDomUpdateTransitionerinterface), e.g.MotionViewfrom@studiometa/ui-motion— itsupdate()receives the apply function and is awaited the same wayWhen no runner is registered, the default paths are unchanged: a View Transition when the
viewTransitionoption is enabled and supported, a direct update otherwise.Guardrails:
wrapmust be called synchronously while thedom-updateevent dispatches — later calls warn and are ignored.wrapcall during dispatch replaces any previous one.fetch-update-afteris always emitted.Because the event bubbles, the upcoming ambient
MotionViewcovers the common case with pure nesting and no attributes at all. When the transitioner lives elsewhere in the tree, anActionis the explicit escape hatch:The
DomUpdateRunnerandDomUpdateTransitionertypes are exported from the@studiometa/uibarrel; the protocol functions stay internal.Also in this PR
The default view transition path now runs through the shared
viewTransitionscheduler instead of callingdocument.startViewTransitiondirectly: same-tick updates batch into one transition (aFetchswap and aToasteranimation no longer race over the one-transition-per-document limit), batches serialize, and the unsupported-API fallback lives in the scheduler.update()now settles when the transition finishes (previously atready). Covered by a simultaneous-updates spec asserting two concurrent fetches share a singlestartViewTransitioncall.Test plan
dom-update+detail.wrap()(defaultdocument.startViewTransitionnot called, DOM untouched untilapply()runs), a duck-typed transitioner object whoseupdate(mutate)receives the apply function, last-call-wins, latewrapcalls warning and being ignored, sync throw and async rejection both still applying the content, warning and emittingfetch-update-after, and no double apply when the runner rejects after applying.npm run test— 98 files, 811 tests passed.npm run lint— 0 errors, 21 warnings (19 baseline + 2func-stylein the shareddom-update.ts, kept identical to the sibling PR introducing the same file).cd packages/docs && node scripts/validate-reference.ts— "Documentation validation passed".🤖 Generated with Claude Code
https://claude.ai/code/session_01FVXrJ8idMfvt667yJadvB8