You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A gallery coverage audit (against the full @webjsdev/core + @webjsdev/server surface) found three shipped, browser-facing features with zero demo anywhere in the scaffold gallery. This issue combines all three into one gallery-expansion effort (was #990 streaming, #991 view transitions, #992<webjs-suspense>; #991 and #992 are folded here):
They can ship as one PR with three commits, or be split; keep them one tracked item.
Shared implementation notes (all three)
Each is one single-concept demo: packages/cli/templates/gallery/app/features/<name>/page.ts + packages/cli/templates/gallery/modules/<name>/, registered in the FEATURES array in packages/cli/lib/create.js (around L1340), and added to the galleryModules prune list in packages/cli/templates/scripts/clear-gallery.mjs.
The gallery ships in every UI template; generators emit strings, so an escaping bug only shows in a freshly generated app (follow webjs-scaffold-sync). No backticks inside html bodies (invariant 9). app/ is routing-only, logic in modules/, server-only code behind .server.ts, light DOM + Tailwind, one action per file.
Verify each with generate + boot + webjs check. These features' exports are already classified/exempt in test/scaffolds/gallery-coverage.json, so no manifest change is expected; confirm test/scaffolds/gallery-coverage.test.js and the other scaffold-*.test.js stay green.
No demo shows an action returning a stream/async-generator consumed with for await (const chunk of await streamTokens()). The api-template stream route (packages/cli/lib/api-gallery.js) returns a raw HTTP ReadableStream from a hand-written route.ts, a DIFFERENT mechanism, so the gap is real. This is a marquee use case (token-by-token AI output over the normal action call site, back-pressured, cancelled on client disconnect).
Add: modules/streaming/actions/streamTokens.server.ts (an async function* action), modules/streaming/components/token-stream.ts (a component doing for await, appending to a signal), app/features/streaming/page.ts.
Detection is on the return value (no config export), per @webjsdev/server/src/action-stream.js (isStreamable). A streamed result is never cached / ETagged / seeded.
2. View Transitions
view-transition and data-webjs-permanent appear nowhere in the scaffold. The client-router demo covers soft nav / prefetch / navigate() / revalidate() / data-no-router / clientRouter:false, but not View Transitions (a headline client-router feature).
Reference: .agents/skills/webjs/references/client-router-and-streaming.md (View Transitions section). Opt in with <meta name="view-transition"> (value same-origin), which wraps all three swap paths; data-webjs-permanent + an id persists a live element across a swap.
Add either a scoped app/features/view-transitions/ page pair or extend the client-router demo; include one data-webjs-permanent element. Decide where the meta opt-in scopes (a root-layout meta turns it on app-wide).
Landmine: startViewTransition is not universal; when unavailable the swap runs synchronously with no flash and no throw, so the demo must degrade cleanly. iOS WebKit repaint quirks around client-router nav are documented (dogfood: mobile navbar flickers on forward nav (backdrop-blur sticky header) #610); keep the transition simple.
Page-level Suspense is demoed (app/features/async-render/page.ts) but the component-level <webjs-suspense .fallback=...> streaming boundary element is not shown. It is the only way to show a first-paint fallback for a SLOW component and stream the content in.
Add either a second, <webjs-suspense>-wrapped slow region to the async-render demo, or a dedicated app/features/suspense/. Contrast it with plain async-render (which blocks SSR so the data is in the first paint with no fallback): <webjs-suspense> is the deliberate choice for SLOW data.
<webjs-suspense> is a core element re-exported from @webjsdev/core; .fallback=${...} MUST be an unquoted property hole (invariant 4). Needs genuinely slow data (an artificial delay in the action) to be visible; a throwing component inside a boundary is isolated.
Acceptance criteria
A streaming-action demo shows an async-generator action consumed with for await, rendering chunks as they arrive
A View Transitions demo shows a transition on a soft navigation, with the <meta> opt-in and one data-webjs-permanent persisted element, degrading cleanly where startViewTransition is unavailable
A <webjs-suspense> demo wraps a slow region in .fallback=... and streams the resolved content in, with the contrast to page-level Suspense clear in the comments
All three cards are registered in FEATURES and pruned by gallery:clear
All three ship in every UI template; generate + boot + webjs check pass; the scaffold coverage + gallery suites stay green
Problem
A gallery coverage audit (against the full
@webjsdev/core+@webjsdev/serversurface) found three shipped, browser-facing features with zero demo anywhere in the scaffold gallery. This issue combines all three into one gallery-expansion effort (was #990 streaming, #991 view transitions, #992<webjs-suspense>; #991 and #992 are folded here):ReadableStream/ async iterable / async generator, consumed at the call site withfor await.<meta name="view-transition">+data-webjs-permanent.<webjs-suspense>streaming boundary (feat: <webjs-suspense> streaming SSR boundary + per-component streaming (follow-up to #469) #471) - a first-paint fallback that streams the resolved content in.They can ship as one PR with three commits, or be split; keep them one tracked item.
Shared implementation notes (all three)
packages/cli/templates/gallery/app/features/<name>/page.ts+packages/cli/templates/gallery/modules/<name>/, registered in theFEATURESarray inpackages/cli/lib/create.js(around L1340), and added to thegalleryModulesprune list inpackages/cli/templates/scripts/clear-gallery.mjs.htmlbodies (invariant 9).app/is routing-only, logic inmodules/, server-only code behind.server.ts, light DOM + Tailwind, one action per file.webjs check. These features' exports are already classified/exempt intest/scaffolds/gallery-coverage.json, so no manifest change is expected; confirmtest/scaffolds/gallery-coverage.test.jsand the otherscaffold-*.test.jsstay green.1. Streaming server-action results (#489)
No demo shows an action returning a stream/async-generator consumed with
for await (const chunk of await streamTokens()). The api-templatestreamroute (packages/cli/lib/api-gallery.js) returns a raw HTTPReadableStreamfrom a hand-writtenroute.ts, a DIFFERENT mechanism, so the gap is real. This is a marquee use case (token-by-token AI output over the normal action call site, back-pressured, cancelled on client disconnect).modules/streaming/actions/streamTokens.server.ts(anasync function*action),modules/streaming/components/token-stream.ts(a component doingfor await, appending to a signal),app/features/streaming/page.ts.@webjsdev/server/src/action-stream.js(isStreamable). A streamed result is never cached / ETagged / seeded.2. View Transitions
view-transitionanddata-webjs-permanentappear nowhere in the scaffold. The client-router demo covers soft nav / prefetch /navigate()/revalidate()/data-no-router/clientRouter:false, but not View Transitions (a headline client-router feature)..agents/skills/webjs/references/client-router-and-streaming.md(View Transitions section). Opt in with<meta name="view-transition">(valuesame-origin), which wraps all three swap paths;data-webjs-permanent+ anidpersists a live element across a swap.app/features/view-transitions/page pair or extend the client-router demo; include onedata-webjs-permanentelement. Decide where the meta opt-in scopes (a root-layout meta turns it on app-wide).startViewTransitionis not universal; when unavailable the swap runs synchronously with no flash and no throw, so the demo must degrade cleanly. iOS WebKit repaint quirks around client-router nav are documented (dogfood: mobile navbar flickers on forward nav (backdrop-blur sticky header) #610); keep the transition simple.3.
<webjs-suspense>component streaming (#471)Page-level
Suspenseis demoed (app/features/async-render/page.ts) but the component-level<webjs-suspense .fallback=...>streaming boundary element is not shown. It is the only way to show a first-paint fallback for a SLOW component and stream the content in.<webjs-suspense>-wrapped slow region to the async-render demo, or a dedicatedapp/features/suspense/. Contrast it with plain async-render (which blocks SSR so the data is in the first paint with no fallback):<webjs-suspense>is the deliberate choice for SLOW data.<webjs-suspense>is a core element re-exported from@webjsdev/core;.fallback=${...}MUST be an unquoted property hole (invariant 4). Needs genuinely slow data (an artificial delay in the action) to be visible; a throwing component inside a boundary is isolated.Acceptance criteria
for await, rendering chunks as they arrive<meta>opt-in and onedata-webjs-permanentpersisted element, degrading cleanly wherestartViewTransitionis unavailable<webjs-suspense>demo wraps a slow region in.fallback=...and streams the resolved content in, with the contrast to page-levelSuspenseclear in the commentsFEATURESand pruned bygallery:clearwebjs checkpass; the scaffold coverage + gallery suites stay green