Skip to content

feat(hub): static hub builds with buildHub - #339

Merged
antfu merged 6 commits into
mainfrom
few-clowns-shout
Sep 3, 2026
Merged

feat(hub): static hub builds with buildHub#339
antfu merged 6 commits into
mainfrom
few-clowns-shout

Conversation

@antfubot

@antfubot antfubot commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

What

Production/static build mode for the hub, so a built app served from any static file server ships working devtools.

  • buildHub() (new @devframes/hub/build subpath): the multi-devframe counterpart of createBuild. Copies each devframe's SPA to <outDir>/<id>/ and absolute-path page scripts to <id>/__page-script/, copies the UI slot's viewer/embedded.js/renderer modules, writes the discovery documents, a backend: 'static' __connection.json at the hub base and every frame base, and bakes the shared RPC dump — including a snapshot of every shared-state key (docks with rewritten page-script URLs, commands, renderer manifest), so createDevframeClientRuntime() and every panel boot with no live server.
  • devframe core: writeStaticRpcDump extracted and shared with createBuild; the shared-state dump gains an undefined fallback (un-baked keys resolve like the live handler instead of failing); a served ConnectionMeta.baseUrl re-points relative resolution, so a frame SPA that fetches its per-frame meta still finds the hub's dump.
  • Static degradation: createMessagesClient keeps handles local on a static backend (the a11y page script reports unchanged), and dock-activation deep links (e.g. the messages panel's "View in a11y inspector") ride a same-origin BroadcastChannel — new HUB_EVENTS.broadcastChannel.docksActivate — since no server can relay them. Fixed a DataCloneError posting Vue reactive proxies from the messages panel.
  • @devframes/vite/hub: opt-in build: true bakes the hub into vite build output at <outDir><base> and injects embedded.js into the built HTML.
  • Examples: a11y-messages-playground (hand-rolled host, buildHub directly) and hub-vite-minimal (vite kit + @devframes/hub-ui) both gain build/preview; the playground bakes a demo message whose activate action exercises the static navigation path.
  • Build opt-out: a devframe declaring capabilities.build: false (same flag createBuild honors) is silently skipped by buildHub — no dock, no SPA copy, no frame in __index.json, none of its RPCs in the dump. The terminals and code-server plugins now declare it (assets already did), so a hub mounting every built-in bakes only the tools that mean something statically.
  • Verification: new a11y-messages-hub-static Playwright suite — dock rail from the baked shared state, the a11y page script scanning the production app and highlighting over the in-page channel, the baked messages feed, and cross-dock activation over the BroadcastChannel. Manually verified hub-vite-minimal's embedded dock + standalone viewer statically (baked OG snapshot, graceful `not found in dump store` for live-server tools). Unit tests for buildHub (DF8006 guard included); docs for the guide, hub-api reference, events reference, vite kit page, and the new error code.

Also: the two Next static-export copy scripts use fs.promises.cp (the sync native copy EACCESes on shared-mount filesystems).


Created with the help of an agent.

The sync variant's native directory copy fails (EACCES) on shared-mount
filesystems (virtiofs, Docker Desktop mounts), breaking the workspace
build there.
Bake a whole hub into a static deploy, the multi-devframe counterpart of
devframe's createBuild: buildHub() from @devframes/hub/build copies each
devframe's SPA and page script, writes a static connection meta at the hub
and every frame base, and bakes the shared RPC dump (static/snapshot RPCs
plus every shared-state key), so the client runtime and every panel boot
from any static file server with no live server.

- devframe core: shared writeStaticRpcDump (reused by createBuild), an
  undefined fallback for un-baked shared-state keys, and per-frame metas
  can re-point relative resolution at the hub's own meta via a served
  baseUrl.
- static client degradation: createMessagesClient keeps handles local on
  a static backend, and dock activation (messages panel deep links) rides
  a same-origin BroadcastChannel instead of the RPC relay.
- @devframes/vite/hub: opt-in build option bakes the hub into vite build
  output and injects embedded.js into the built HTML.
- examples: a11y-messages-playground and hub-vite-minimal ship production
  builds (build + preview), covered by a static-hub e2e suite (a11y
  scanning the production app over the in-page channel, baked messages
  feed, cross-dock activation).
@coldtea-pr-lens

coldtea-pr-lens Bot commented Sep 3, 2026

Copy link
Copy Markdown

◈ PR Lens

🟢 +1 new · 🟠 ~10 changed · 🔴 -0 removed · 2 flows · 23 files · commit e6d7c24


Architecture

Architecture diagram for devframes/devframe at e6d7c24

11 components touched across 5 lanes.

Open full size


Inside the changed components — 2 views

Component view — Static Hub Build Pipeline

Static build orchestration in @⁠devframes/hub/build, Vite plugin integration, and live-only devframe filtering.

Architecture view of Component view — Static Hub Build Pipeline in devframes/devframe

Component view — Static Client Runtime & Offline Communications

Client runtime behavior under static backend mode, including BroadcastChannel dock switching and in-page channel DOM scanning.

Architecture view of Component view — Static Client Runtime & Offline Communications in devframes/devframe

Data flow

Data flow diagram for devframes/devframe at e6d7c24

Static hub build pipeline · Static offline dock activation

Open full size


The other flows — 1 sequence

Static offline dock activation

Sequence diagram of Static offline dock activation in devframes/devframe

Drill down
Client Runtimes & UI — 2 components
🟡 CHANGED Headless Client Runtime

Client runtime managing dock lifecycle, shared state, local message fallback, and BroadcastChannel dock switching for static deployments.

🟡 CHANGED Browser RPC Client

Browser RPC client handling connection meta resolution and fallback across live and static backends.

Framework Kits & Hosts — 2 components
🟡 CHANGED Vite Framework Kit

Vite framework kit providing dev server integration and build-time static hub export via `build: true`.

🟡 CHANGED Vite Reference Hub Host

Vite application examples demonstrating static hub building and preview deployment.

Hub Orchestration — 2 components
🟡 CHANGED Hub Core & Lifecycle

Core hub service orchestrating shared context, devframe registration, routing, and live host runtime.

🟢 NEW Static Hub Builder

Static hub builder that bakes multi-devframe SPAs, connection metas, discovery documents, and RPC dump shards into static directories.

Devframe Core Engine — 1 component
🟡 CHANGED Host & Build Adapters

Build and host adapters, providing shared static RPC dump serialization (`writeStaticRpcDump`).

Built-in Devframes — 4 components
🟡 CHANGED A11y Inspector Devframe

Accessibility inspector devframe supporting static app scanning over in-page channel.

🟡 CHANGED Messages Devframe

Messages feed devframe supporting static snapshot viewing and BroadcastChannel dock navigation.

🟡 CHANGED Terminals Devframe

Terminal devframe marked live-only (`capabilities.build: false`) to skip static builds.

🟡 CHANGED Code Server Devframe

Code Server devframe marked live-only (`capabilities.build: false`) to skip static builds.


View

  • Architecture lens
  • Data flow lens
  • Expand every detail
  • Show unchanged neighbours

Tip

Push a new commit and the whole comment re-renders for the new head. An older run never overwrites a newer one, so a slow render cannot put a stale diagram back.

🪧 More tips
  • Run PR Lens on your own machine: npx skills add coldteadotai/pr-lens installs the agent skill. Then tell your coding agent: "Diagram the change you just made with PR Lens and attach it to the pull request."
  • Draw a diff before it is even a pull request: npx @coldtea/pr-lens-cli analyze --base origin/main reads the diff with your own model key, and npx @coldtea/pr-lens-cli render .pr-lens/graph.json draws the same lenses on your machine.
  • The boxes under View are live. Tick Architecture lens or Data flow lens to choose which diagrams appear, or Expand every detail to open every drill-down at once. The comment redraws in place a few seconds later.
  • Show unchanged neighbours lists the components this change did not touch alongside the ones it did, so the drill-down shows what the changed code sits next to.
  • GitHub will not let you zoom an image in a comment. The link under each diagram opens it full size on a page of its own, where you can.
  • The CLI's render picks up .github/pr-lens.yml automatically and applies your corrections (renames, exclusions, lane pins) at draw time.
  • Would you rather run it from CI on a key of your own? Add .github/workflows/pr-lens.yml with coldteadotai/pr-lens/packages/action@v0 and a model key in your repository secrets, say GEMINI_API_KEY. The Action asks Gemini by default, or OpenAI and any endpoint speaking /chat/completions through its provider input.
  • PR Lens is free for open source. A star on the repository is what keeps it going.
  • The diagrams follow your GitHub theme, so dark mode gets the dark render and light mode the light one, and the moving dots show this pull request's data in motion.

◈ Rendered by PR Lens · crafted with ❤️ by the Coldtea team · Something drawn wrong?

@vercel

vercel Bot commented Sep 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
devframe Ready Ready Preview Sep 3, 2026 8:01am UTC

A devframe whose value is inherently live (terminals over PTY sessions,
the code-server process proxy, the assets manager) declares
capabilities.build: false; buildHub now honors it the way createBuild
does: the devframe is never mounted, so no dock, no SPA copy, no frame in
__index.json, and none of its RPCs join the static dump. DF8007 logs each
skip so a missing dock stays traceable. The terminals and code-server
plugins declare the capability (assets already did).
The skip is the declared behavior, not a condition to report; drop the
DF8007 diagnostic and its docs page.
# Conflicts:
#	docs/content/6.errors/DF8005.md
#	examples/next-runtime-snapshot/scripts/build-spa.mjs
#	packages/hub/src/node/initiate.ts
#	plugins/code-server/src/index.ts
#	plugins/git/scripts/build-spa.mjs
#	plugins/terminals/src/index.ts
tsdown hoists the module computing a11yClientScriptBundlePath into a
shared chunk at the dist root, where the import.meta.url-relative path
escapes the package (plugins/dist/...), so a hub consuming the built
package mounted no page script. Resolving the package's own
./client-script export is depth-independent; the relative path stays as
the unbuilt-bundle fallback.
@antfu
antfu merged commit f38e886 into main Sep 3, 2026
14 checks passed
@antfu
antfu deleted the few-clowns-shout branch September 3, 2026 08:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants