feat(view): stream component resources and JSON - #296
Draft
petehunt wants to merge 3 commits into
Draft
Conversation
petehunt
force-pushed
the
agent/view-resources
branch
from
August 6, 2026 21:44
e7d8a44 to
0ea7e50
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Depends on #292 and #295. Review commit
e7d8a44for this layer.Motivation
Deferred views can stream HTML as soon as a component finishes, but component-specific CSS, JavaScript, and data still create a waterfall if they are only discovered in that completed fragment:
A component already knows its dependencies before slow rendering work begins. This PR lets it publish those requirements through the request context immediately, so the browser can fetch and execute a module while the component is still rendering.
API
A component requires external assets before its first slow await:
The external module can start immediately and wait for its separately streamed data, regardless of which arrives first:
cx.send_json(&value)is also available when the key should be generated. It returns aJsonKeythat can be rendered into the fragment, for example as adata-*attribute.Streaming behavior
Resource and JSON requirements are request-scoped response events. The view response emits the shell first, then prioritizes queued events ahead of the fragment that requested them:
Events wake the response stream while deferred component futures remain pending. A view that queues events without deferred work also receives a streaming body, so the API is not limited to deferred components.
This builds on #292 and #295 without introducing spawned tasks. Deferred rendering remains structured: dropping the response body drops its pending component futures and request-scoped event queue.
Why requirements live on
CxAttaching dependencies to a completed
Viewwould compose naturally, but the renderer could not discover them until the component returned. Publishing throughCxlets a component flush requirements before an expensive await while still returning an ordinaryViewthat composes normally.This diff intentionally supports only external stylesheets and JavaScript modules. Additional resource metadata or declarative view-attached assets can be explored separately without changing the streaming protocol.
Deduplication and JSON keys
Asset requirements are deduplicated by asset identity and resource kind on the server, then deduplicated again by the browser helper. Requiring one module or stylesheet from several concurrent or nested components inserts one DOM resource element.
Named JSON keys have deterministic conflict behavior:
@topcoat/namespaceGenerated keys use a random response identifier plus a monotonic response-local counter inside that reserved namespace. They cannot collide with application-provided keys and are safe to retain across boosted page transitions.
topcoat.json(key)returns an already-resolved promise when the value has arrived, or a pending promise that resolves when its template is observed.CSP and escaping
The response emits inert annotated
<template>elements. The existing externaldefer_script()helper converts resource templates into external<link>and<script type="module">elements and parses JSON templates. It does not generate inline executable scripts.Resource URLs and keys are escaped as HTML attribute values. JSON is serialized on the server, escaped as HTML text, and parsed from the template content in the browser. Deployments still need their CSP to allow the external asset origin, but do not need
unsafe-inlinefor this mechanism.Coverage
Validation
cargo +nightly fmt --allcargo topcoat fmt(expected Leptos parse errors only)cargo clippy --workspace --all-targets --all-features --locked -- -D warningscargo test --workspace --all-featuresRUSTDOCFLAGS="--cfg docsrs -Dwarnings" cargo +nightly doc --workspace --all-features --no-deps --lockednode --check crates/topcoat/browser/defer.jsAI disclosure
OpenAI Codex (GPT-5) helped design, implement, test, and prepare this change for review.