feat(examples): add dock switcher UI to minimal hub examples#28
Merged
Conversation
✅ Deploy Preview for devfra ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
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 sharedserveStaticNodeMiddleware) 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) withcli.distDirpointing at new static SPAs. - Rewrites both client UIs (Vite
main.ts/index.htmland Nextpage.tsx) and their CSS into a header/sidebar/main/footer grid that filtersdevframe:docksto 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 }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The two minimal hub examples (
minimal-vite-devframe-hubandminimal-next-devframe-hub) previously listed docks as read-only entries — there was no way to see what mounting an iframe throughmountDevframeactually 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 reusesserveStaticNodeMiddlewarefromdevframe/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.