Skip to content

Scaffolded 'static' (default) and 'page-vite' apps never send BLOCK_READY — the host times out and collapses them #206

Description

@ZacxDev

Two of the three civitai app init templates — including the default — appear to scaffold an app that can never signal readiness to the host, so the host times out and collapses it. This is the same symptom as #196 but a different cause, and the dev-tunnel preflight added in #198 deliberately does not catch it (it is gated to SDK projects).

The contract

The host marks a block ready only on a BLOCK_READY message. In civitai/civitai, src/components/AppBlocks/PageBlockHost.tsx has exactly one transition into ready, inside the BLOCK_READY handler:

// PageBlockHost.tsx:864
const off = onMessage<unknown>('BLOCK_READY', () => {
  ...
  setStatus((current) => (current === 'loading' ? 'ready' : current));  // :871

Enumerating every setStatus( in that file, :871 is the only path to 'ready'. The failure path is :733, loading → 'timeout', after BLOCK_READY_TIMEOUT_MS = 10_000 (pageBlockHostLogic.ts:571).

A timeout is not a visible error card. hostRenderDecision.ts documents:

Every TERMINAL-FAILURE state collapses → 'collapse' (the host renders null; the slot takes no space — no visible broken card, no reserved gap). This covers: … 'timeout' (no BLOCK_READY in 10s) …

Who actually sends it

Not the SDK. @civitai/[email protected]'s runtime JS contains no BLOCK_READY at all — only a type in dist/blocks/messages.d.ts:467 and a README line. The sender is @civitai/blocks-react:

// @civitai/[email protected] — dist/internal/iframeTransport.js:311
this.dispatch('BLOCK_READY', { height: 0 });

What each template does

template depends on blocks-react messages it sends reaches data-block-ready="true"
page-money yes via IframeTransport yes
page-vite no RESIZE_IFRAME only no
static (default) no RESIZE_IFRAME only no

Grepping the whole of internal/scaffold/templates/page-vite/ and .../static/ for BLOCK_ returns zero matches. Each contains exactly one postMessage call, sending RESIZE_IFRAME — which PageBlockHost.tsx does not reference at any line, so it is not treated as a liveness signal either.

All three manifests declare page + iframe, so all three route through the same host path. static is the default: civitai app init "My Block" with no --template produces it (internal/cmd/app_init.go:59).

Ruled out

  • A server-injected ready shim. No bootstrap/shim injection found in the block-serving path; every server-side BLOCK_READY reference is the analytics beacon (app-views.service.ts, track/block-render.ts), not a sender.
  • A render mode that skips the handshake. hostRenderDecision collapses on timeout regardless, and IframeHost.tsx:1956 gates data-block-ready on the same isReady.

Not verified

I have not loaded a scaffolded page-vite or static app in a real host browser and watched it time out — the tunnel endpoint is not exposed and the feature is kill-switched, the same limit recorded on #198. The evidence above is read directly from both sides of the contract (host source and template source), not observed end-to-end. Worth one browser confirmation before acting, since a fix touches the default template.

Possible directions

Not prescribing one — the trade-off is a product call:

  1. Emit BLOCK_READY from the raw templates. A few lines of window.parent.postMessage({ type: 'BLOCK_READY', height: 0 }, '*') keeps them dependency-free. Needs care: IframeTransport freezes parentOrigin to the first sender and dedupes re-inits (iframeTransport.js:291-295), so a hand-rolled version should not re-emit on every render.
  2. Give them @civitai/blocks-react. Correct by construction, but turns the "no-build" static template into a build.
  3. Document them as non-hosted previews and change the default to one that works.

Whichever way, a scaffold seam guard like internal/scaffold/dev_embed_contract_test.go — asserting each template that declares page emits the ready ack — would keep this from regressing silently.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions