Skip to content

feat(examples): add dock switcher UI to minimal hub examples#28

Merged
antfu merged 1 commit into
mainfrom
antfu/asuncion
May 28, 2026
Merged

feat(examples): add dock switcher UI to minimal hub examples#28
antfu merged 1 commit into
mainfrom
antfu/asuncion

Conversation

@antfu

@antfu antfu commented May 28, 2026

Copy link
Copy Markdown
Contributor

Summary

The two minimal hub examples (minimal-vite-devframe-hub and minimal-next-devframe-hub) previously listed docks as read-only entries — there was no way to see what mounting an iframe through mountDevframe actually looks like end-to-end. This PR turns both examples into faithful protocol witnesses for the iframe-mount path: a sidebar of clickable docks and a main pane that swaps the active iframe on click.

Each example now ships two demo devframes with tiny static SPAs, and each host implements DevframeHost.mountStatic() for real — Vite reuses serveStaticNodeMiddleware from devframe/utils/serve-static, while Next.js registers mounted SPA directories in a module-scoped map and serves them through a catch-all App Router route. The existing commands/messages/terminals panels still demonstrate the other hub subsystems, now arranged into a footer below the iframe.

This PR was created with the help of an agent.

Copilot AI review requested due to automatic review settings May 28, 2026 04:35
@netlify

netlify Bot commented May 28, 2026

Copy link
Copy Markdown

Deploy Preview for devfra ready!

Name Link
🔨 Latest commit 300f89e
🔍 Latest deploy log https://app.netlify.com/projects/devfra/deploys/6a17c6302421e90008ffa5b9
😎 Deploy Preview https://deploy-preview-28--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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Turns the two minimal hub examples into faithful witnesses of the iframe-mount path by adding a clickable dock sidebar, a main iframe pane that swaps URL on click, second demo devframes with static SPAs, and real implementations of DevframeHost.mountStatic() in both hosts.

Changes:

  • Implements mountStatic() for Vite (using shared serveStaticNodeMiddleware) and for Next (module-scoped mount map + an App Router catch-all route under _[id]/[[...path]]).
  • Adds second devframe definitions (demo-tool-b, next-demo-tool-b) with cli.distDir pointing at new static SPAs.
  • Rewrites both client UIs (Vite main.ts/index.html and Next page.tsx) and their CSS into a header/sidebar/main/footer grid that filters devframe:docks to iframe entries and renders the selected one in an iframe.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
examples/minimal-vite-devframe-hub/vite.config.ts Registers the new second demo devframe.
examples/minimal-vite-devframe-hub/src/minimal-vite-devframe-hub.ts Wires mountStatic to serveStaticNodeMiddleware.
examples/minimal-vite-devframe-hub/src/devframe.ts Adds cli.distDir for the first demo SPA.
examples/minimal-vite-devframe-hub/src/devframe-b.ts New second demo devframe definition.
examples/minimal-vite-devframe-hub/src/client/main.ts Adds iframe-dock filter, selection state, and click switching.
examples/minimal-vite-devframe-hub/src/client/style.css Reworks layout into grid with sidebar/main/footer.
examples/minimal-vite-devframe-hub/index.html New shell with sidebar dock list and main iframe pane.
examples/minimal-vite-devframe-hub/spa/demo-tool/index.html Tiny SPA for first Vite demo.
examples/minimal-vite-devframe-hub/spa/demo-tool-b/index.html Tiny SPA for second Vite demo.
examples/minimal-next-devframe-hub/src/client/devframe/minimal-next-devframe-hub.ts Module-scoped mount registry, real mountStatic, second devframe default.
examples/minimal-next-devframe-hub/src/client/devframe/demo-devframe.ts Adds cli.distDir for the first Next demo.
examples/minimal-next-devframe-hub/src/client/devframe/demo-devframe-b.ts New second Next demo devframe.
examples/minimal-next-devframe-hub/src/client/app/%5F_[id]/[[...path]]/route.ts New catch-all route that resolves mounts and streams static files.
examples/minimal-next-devframe-hub/src/client/app/page.tsx React UI with iframe-dock filter, sidebar buttons, and main iframe.
examples/minimal-next-devframe-hub/src/client/app/globals.css Matches Vite layout rework.
examples/minimal-next-devframe-hub/spa/next-demo-tool/index.html Tiny SPA for first Next demo.
examples/minimal-next-devframe-hub/spa/next-demo-tool-b/index.html Tiny SPA for second Next demo.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}

async function resolveTarget(absDir: string, urlPath: string): Promise<ResolvedFile | null> {
let cleaned = decodeURIComponent(urlPath || '/').replace(/[?#].*$/, '')
Comment on lines +44 to +80
async function resolveTarget(absDir: string, urlPath: string): Promise<ResolvedFile | null> {
let cleaned = decodeURIComponent(urlPath || '/').replace(/[?#].*$/, '')
if (cleaned.endsWith('/'))
cleaned = cleaned.slice(0, -1)
if (cleaned.startsWith('/'))
cleaned = cleaned.slice(1)

const abs = normalize(join(absDir, cleaned))
if (abs !== absDir && !abs.startsWith(absDir + sep))
return null

const direct = await statFile(abs)
if (direct)
return direct

// Directory → index.html
try {
const s = await stat(abs)
if (s.isDirectory()) {
const candidate = await statFile(join(abs, 'index.html'))
if (candidate)
return candidate
}
}
catch {
// not found / not a directory — continue
}

// SPA fallback for extensionless paths
if (!/\.[a-z0-9]+$/i.test(cleaned)) {
const fallback = await statFile(join(absDir, 'index.html'))
if (fallback)
return fallback
}

return null
}
Comment on lines +82 to +92
export async function GET(request: Request): Promise<Response> {
await ensureMinimalNextDevframeHub()

const pathname = new URL(request.url).pathname
const hit = getStaticMount(pathname)
if (!hit)
return new Response(null, { status: 404 })

const file = await resolveTarget(resolve(hit.distDir), hit.relative)
if (!file)
return new Response(null, { status: 404 })
@antfu antfu merged commit 524c6b6 into main May 28, 2026
13 checks passed
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