Skip to content

feat(hub-ui): add @devframes/hub-ui — the reference UI filling the hub's ui slot - #170

Open
antfubot wants to merge 7 commits into
feat/hub-handlerfrom
feat/hub-ui
Open

feat(hub-ui): add @devframes/hub-ui — the reference UI filling the hub's ui slot#170
antfubot wants to merge 7 commits into
feat/hub-handlerfrom
feat/hub-ui

Conversation

@antfubot

@antfubot antfubot commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Layer 4 of the /__devframes/ standard-middleware stack (on #169). See plans/devframes-standard-middleware.md.

Intent

Batteries for the hub's headless ui slot: createUi() from @devframes/hub-ui returns a DevframeHubUi object pointing at two prebuilt artifacts —

  • embedded.js — the floating DockEmbedded bootstrap, a single self-contained ES module the hub serves at <base>embedded.js; one documented <script type="module"> tag gives any host page the Vite-DevTools-style floating dock. Always visible by design: visibility policy belongs to the entry's author (Vite DevTools keeps its passive/hidden model in its entry against the same slot).
  • standalone viewer — a vanilla shell mounting DockStandalone, served at the hub base; open /__devframes/ in a tab and the devtools are there.

The components are a port of Vite DevTools' web components (dock shell, panels, command palette, messages/toasts, views, json-render catalog — ~120 files) rewired onto @devframes/hub's client runtime: kit imports map to @devframes/hub/client (getDevframeRpcClient, attachFrameNavClient, context key), kit types to their Devframe* hub equivalents (kit-only shapes ported locally), RPCs to the hub built-ins (hub:commands:execute, hub:messages:*), and the client-script loader to native import() — no virtual modules, no mount-path constant, no __mode.json. Shadow-root styles compile ahead of time from the shared devframe design system (@antfu/design sage-green preset, named z-layers), so the dock stays styled inside any host page. Storybook follows the repo's shared vue3-vite setup.

The hub itself stays headless — this package is one implementation of the slot; any viewer supplies its own object to the same option and reuses all the infrastructure.

Components descend from vitejs/devtools (MIT).

Stack

  1. feat!: add devframe/initiate — initDevframe framework-agnostic middleware #167 feat/handler-core
  2. refactor(adapters): rebuild createDevServer, viteDevBridge, and @devframes/next on initDevframe #168 feat/handler-adapters
  3. feat(hub): add @devframes/hub/initiate — initHub, the headless hub behind one handler #169 feat/hub-handler
  4. feat/hub-ui (this PR)
  5. feat/handler-examples-docs — examples + framework guides + Bun smoke test

Created with the help of an agent.

…er hooks

createContextRpcServer owns everything about serving RPC that is
independent of how peers connect (auth wiring, session resolver,
auto-trust shim); createWsRpcPeerHooks shapes the per-peer lifecycle for
any crossws adapter. startHttpAndWs behavior is unchanged — it now
composes the two, so other transports (fetch-upgrade runtimes) can reuse
the same wiring.
… middleware

createHandler(def) serves a devframe's whole surface — SPA,
__connection.json discovery, WebSocket RPC, auth gate (on by default),
and the optional MCP route — through one fetch handler mountable on any
framework's catch-all route, plus a connect-style nodeMiddleware and Bun
fetch-upgrade websocket hooks.

WebSocket binding resolves by precedence: ws.port (explicit side-car) >
server (shared upgrade at <base>__ws) > ws.url alone (no local
transport; external server owns it) > Bun fetch-upgrade > eager auto
side-car. ws.url always overrides the advertised endpoint (the tunnel
pattern: bind locally, advertise the relay). A key option memoizes the
handler on globalThis so HMR module re-evaluation can't leak side-cars
(DF0053 on option changes; DF0054 for connectionMeta before ready).

BREAKING CHANGE: the WS route unifies on `__ws` (was `__devframe_ws`)
across every adapter, and the unused DEVFRAME_MOUNT_PATH /
DEVFRAME_DIRNAME constants are removed.
…ing a DevframeInstance

The factory is named for the instance it initiates (define → init
pairing with defineDevframe), reached from the devframe/initiate
subpath, and the web-standard request handler is a property —
initDevframe(def).handler — matching the content.handler mounting
model, so future capabilities extend the instance object instead of
overloading a handler-named factory.

- subpath: devframe/handler → devframe/initiate (src/adapters/initiate.ts)
- createHandler → initDevframe; CreateHandlerOptions → InitDevframeOptions
- DevframeHandler → DevframeInstance; fetch → handler
- diagnostics/docs updated (DF0053/DF0054 wording)
…frames/next on initDevframe

One wiring underneath every serving path: the adapters become thin
assemblies over the devframe/initiate instance.

- createDevServer = initDevframe + a node listener (listen-first so the
  shared WS tier reports the real port; DF0052 rejection preserved;
  registry/openBrowser/onReady unchanged; ws/rpcGroup/connectionMeta
  surfaced from the instance transport)
- viteDevBridge bridge mode = instance.nodeMiddleware on Vite's stack;
  the WS upgrade now shares Vite's own http server at <base>__ws (zero
  extra ports; pinned devMiddleware.port keeps the explicit side-car)
  and MCP moves onto the Vite origin at <base>__mcp
- @devframes/next reduces to memoization + defaults sugar (key-memoized
  instance; MCP same-origin through the catch-all route)
- initDevframe grows the host-integration options the adapters need:
  app, distDir: false, origin getter, getStorageDir,
  destroyUnmatchedUpgrades, onPeerConnect/onPeerDisconnect passthrough
- resolveDevServerPort / resolveMcpConnectionMeta move to adapters/_shared
  (re-exported from adapters/dev unchanged)

BREAKING CHANGE: viteDevBridge and @devframes/next advertise same-origin
relative endpoints now — websocket { path: '__ws' } on the host origin
(or { port, path: '__ws' } for a pinned side-car) and mcp
{ path: '__mcp' } — instead of side-car-port absolute paths.
…hind one handler

initHub() serves the whole multi-devframe devtools surface through one
web-standard handler under a single namespace (default /__devframes/):
every mounted devframe shares one hub context (merged RPC registry,
shared state, docks/terminals/messages/commands), one WebSocket
transport, and one hub Auth; frames serve at <base><id>/ with per-frame
discovery pointing at the shared socket.

- reserved layout: __connection.json, __ws, __index.json,
  __client-imports.js, __mcp (aggregate over the shared registry),
  embedded.js; frame ids validated against it (DF8000)
- DevframeHubUi slot (pure data): ui.viewer owns the namespace root,
  ui.embedded serves the floating bootstrap at embedded.js — omitted,
  the hub stays fully headless and the root serves the index document
- assembly modes: declarative devframes list (+ configure(ctx)) or a
  pre-built context (DF8002 when both); key memoization against
  dev-reload leaks (DF8001)
- devframe: the Bun WS tier is promoted to the public
  devframe/rpc/transports/ws-bun subpath, createContextRpcServer is
  exported from devframe/node, and adapters/mcp exports mountMcpHttp —
  the primitives initHub composes
- mountDevframe now mounts a frame's connection meta before its SPA
  statics so route-ordered hosts (h3) resolve the exact meta route ahead
  of the static catch-all
…b's ui slot

A port of Vite DevTools' web components onto @devframes/hub, prebuilt
into two entries the hub serves through DevframeHubUi:

- embedded.js — the floating DockEmbedded bootstrap (single self-
  contained ES module; always visible by design — visibility policy
  belongs to whoever authors an embedded entry)
- a standalone viewer SPA (vanilla shell mounting DockStandalone,
  relative asset paths, served at the hub base)

createUi() returns the slot object pointing at the built artifacts.
The components (dock shell, panels, command palette, messages/toasts,
views, json-render catalog) are Vue custom elements with shadow-root
styles compiled ahead of time from the shared devframe design system
(@antfu/design sage-green preset); imports are rewired from
@vitejs/devtools-kit onto @devframes/hub's client runtime, the
view-mode machinery is removed, and branding/element names/storage
keys move to the devframes namespace. Storybook follows the repo's
shared vue3-vite setup.

Co-developed with Vite DevTools — the components descend from
vitejs/devtools (MIT).
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.

1 participant