Skip to content

feat(hub): shared-iframe docks with postMessage soft navigation#128

Merged
antfu merged 7 commits into
mainfrom
docs/shared-iframe-soft-nav
Jul 23, 2026
Merged

feat(hub): shared-iframe docks with postMessage soft navigation#128
antfu merged 7 commits into
mainfrom
docs/shared-iframe-soft-nav

Conversation

@antfubot

@antfubot antfubot commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

What

Lets a foreign multi-tab devtool (e.g. Nuxt DevTools) be hosted as first-class hub docks that share one live iframe and switch views via client-side (soft) navigation. Ships the hub side plus the design spec (plans/shared-iframe-soft-nav.md) that hands the UI contract to Vite DevTools.

Builds on two already-merged foundations: #129 (client-only docks.register()/update()) and #130 (grouped-entry category as an in-group sub-category).

Design (see plans/shared-iframe-soft-nav.md)

Two cleanly separated layers, mirroring the a11y precedent (durable data model on the hub; live loop on a direct channel):

  • Hub data modelframeId is a shared-iframe axis orthogonal to groupId; per-tab members are client-only docks materialized from a manifest.
  • Live loop — a versioned, origin-locked host↔iframe postMessage protocol (ready/manifest/navigate/navigated) with an idempotent echo guard and hard-nav fallback. The embedded app ships only a small postMessage shim and takes no hub/RPC dependency.

Implementation (hub side)

  • TypesnavTarget + subTabs on DevframeViewIframe, plus NavTarget / FrameSubTabsConfig (packages/hub/src/types/docks.ts).
  • AdapterattachFrameNavClient (packages/hub/src/client/frame-nav.ts): runs the handshake, reconciles the declarative manifest snapshot into client-only member docks (<frameId>:<tabId>), drives the bidirectional nav loop, degrades to no-shim on timeout.
  • Auto-attach — the client host attaches one adapter per frameId when a subTabs anchor iframe mounts (dom:iframe:mounted), and tears it down on removal/dispose (packages/hub/src/client/host.ts).
  • Tests — handshake, manifest reconcile add/remove, echo-guard, internal-nav highlight, origin-lock, malformed-envelope rejection, timeout→no-shim, active-removed→null. Hub API snapshots updated.

Gates run locally on the hub package: tsc --noEmit, eslint, and the full hub vitest suite (97 passing) — all green.

Remaining (out of this PR)

  • Vite DevTools UI contract (one kept-alive iframe per frameId, emit dom:iframe:mounted, render members).
  • Nuxt DevTools postMessage nav shim.
  • A worked example + Client Context guide section.

This PR was created with the help of an agent.

Add a design proposal for hosting a foreign multi-tab devtool (e.g. Nuxt
DevTools) as first-class hub docks that share one live iframe and switch
views via client-side soft navigation.

Splits the design into a hub-owned data model (a frameId shared-iframe axis
orthogonal to groupId, plus client-only dock registration) and a versioned,
origin-locked host<->iframe postMessage protocol for the live nav loop. The
embedded app ships only a small postMessage shim and takes no hub/RPC
dependency. Includes the viewer UI-behavior contract for Vite DevTools, the
adapter behavior, fallback semantics, message tables, sequences, and TS
type sketches.
@netlify

netlify Bot commented Jul 23, 2026

Copy link
Copy Markdown

Deploy Preview for devfra ready!

Name Link
🔨 Latest commit 990fd74
🔍 Latest deploy log https://app.netlify.com/projects/devfra/deploys/6a61ac4001e3f90008d5dda0
😎 Deploy Preview https://deploy-preview-128--devfra.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

antfubot added 2 commits July 23, 2026 02:51
Implement the hub side of the shared-iframe soft-nav design: extend
DevframeViewIframe with navTarget + a subTabs anchor flag (and NavTarget /
FrameSubTabsConfig types), and ship a viewer-side frame-nav adapter
(attachFrameNavClient) that the client host auto-attaches to a subTabs
anchor iframe.

The adapter runs a versioned, origin-locked host<->iframe postMessage
handshake, materializes one client-only member dock per reported tab (via
the client docks.register from #129, keyed <frameId>:<tabId>), and drives a
bidirectional nav loop: selecting a member posts navigate; the app's
navigated report moves the dock highlight. An idempotent guard breaks the
echo, a declarative manifest snapshot reconciles add/update/remove
(clearing selection when the active tab vanishes), and a handshake timeout
degrades to no-shim (anchor stays a single plain iframe dock).

Grouping stays orthogonal to frameId and follows the #130 category rule.
Covered by client host + adapter tests; hub API snapshots updated.
@antfubot antfubot changed the title docs: design for shared-iframe docks with soft navigation feat(hub): shared-iframe docks with postMessage soft navigation Jul 23, 2026
antfubot added 4 commits July 23, 2026 04:05
The mutable string[] in NavTarget.query and the unknown-typed state field
broke DevframeViewIframe's round-trip through shared-state's Immutable<T>
(Immutable<unknown> collapses to {}), so downstream type guards over
devframe:docks (e.g. storybook-hub's isIframeDock) stopped narrowing and
CI typecheck failed. Make query arrays readonly and drop the history-state
field (path + query cover routers; a recursive serializable state trips
TS2589). Narrow query values with typeof, not Array.isArray.
… hubs

Add a 'Tabbed Tool' to both minimal hub examples: a single SPA (Overview /
Components / Timeline / Settings) mounted as a subTabs anchor. Each SPA ships
the ~40-line devframe:frame-nav postMessage shim (announces a tab manifest,
answers navigate, reports navigated); the hub's client host attaches the
frame-nav adapter, materializing one client-only dock per tab that all share
one iframe.

To satisfy the viewer contract, both shells now keep one iframe alive per
frameId (shown/hidden instead of re-src'd), drive selection through
docks.switchEntry so the adapter hears entry:activated, set
state.domElements.iframe + emit dom:iframe:mounted on mount, and re-render
when the adapter reconciles member docks. Verified end-to-end in a browser:
opening the anchor reveals its tabs, selecting a tab soft-navigates the shared
iframe with no reload, and navigating inside the frame moves the hub's active
dock (bidirectional).
…ft-nav

# Conflicts:
#	examples/minimal-next-devframe-hub/src/client/app/page.tsx
#	examples/minimal-vite-devframe-hub/src/client/main.ts
Add a 'Shared-iframe soft navigation' section to the Client Context guide
(the anchor's subTabs opt-in, the devframe:frame-nav postMessage protocol,
the client-only member docks, and the viewer's dom:iframe:mounted seam), a
frameId note + demo pointer in the Hub grouping guide, and a mention in the
examples list.

Refocus plans/shared-iframe-soft-nav.md now that the hub side has shipped:
replace the status banner with a concise 'what shipped + where' list and turn
the implementation checklist into the remaining downstream work (the Vite
DevTools UI contract and the Nuxt shim).
@antfu
antfu merged commit 5764cf9 into main Jul 23, 2026
12 checks passed
@antfu
antfu deleted the docs/shared-iframe-soft-nav branch July 23, 2026 06:07
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