Dev server opens the plugin studio, and its plugin frames load one element each
Some checks are pending
build-gate / build (push) Waiting to run
Some checks are pending
build-gate / build (push) Waiting to run
THE DEV SERVER LANDED ON THE WRONG PAGE. `bun run dev` served only the staging gallery at /, and had no route for a bundle leaf, so there was no URL at which the capability-5 plugin studio could be seen at all. Opening / showed plugins wrapped in per-bundle frames behind a wall of explanatory prose, which reads as "none of the elements are working" when in fact the elements were fine and the page was a chooser. / now mounts <plugin-studio> directly: a 605-byte host page with no chrome and no prose, the element filling the viewport, and the Mapbox token baked as the token ladder's first rung so maps work without anyone appending ?tk=. The gallery is not deleted — it still generates the built index and is load-bearing for the manifest — it simply moves to an explicit /__gallery URL and stops being the landing surface. tree-base is passed explicitly, without which the studio mounts EMPTY. It builds its tab list by fetching <base>manifest.json and falls back to a path relative to its own script URL, which in dev resolves to a file that does not exist. The dev server serves the same manifest-shaped model on its own route, so dev now shows the same tab list as production: 19 bundles, 30 profiles, five capability groups. PLUGIN FRAMES SHOWED THE GALLERY. The studio nests map profiles in a lazy iframe pointed at that profile's leaf page, and dev had no such page, so each frame fell through to the SPA fallback and rendered / — the gallery, recursively, once per map tab. A /__leaf/<bundle>/<profile> route now serves the dev equivalent of the built tree's leaf: one element, its bundle entry, the token, and nothing else.
This commit is contained in:
parent
012c79328a
commit
8124708d12
1 changed files with 119 additions and 2 deletions
|
|
@ -60,7 +60,19 @@ export function resolveDevAsset(rel, root = REPO_ROOT) {
|
|||
// integrity only exists after a build), so Copy embed previews what ships
|
||||
export function galleryModel(reg) {
|
||||
const iframe = (u) => `<iframe src="${u}?utm_source=YOUR_SOURCE" width="100%" height="600" style="border:none;border-radius:12px;" allowfullscreen></iframe>`
|
||||
const link = (...parts) => '/#/' + parts.map(encodeURIComponent).join('/')
|
||||
// A REAL DEV LEAF, not a hash deep-link into the gallery.
|
||||
//
|
||||
// THIS USED TO BE `'/#/' + parts.join('/')` — a hash route into the gallery PAGE. That was
|
||||
// coherent while `/` WAS the gallery, but the studio nests map profiles in a lazy iframe pointed
|
||||
// at exactly this URL (plugin-studio.vue:25, mode 'iframe'), so every map tab was iframing the
|
||||
// gallery. The Driver saw the staging blurb — "One shared core, one build, many paste-anywhere
|
||||
// maps..." — rendered INSIDE each plugin pane, and reported that the studio "is still loading
|
||||
// HTMLs for each plugin with this text." It was: the gallery, recursively, once per map tab.
|
||||
//
|
||||
// `/__leaf/<bundle>/<profile>[/<instance>]` instead serves a minimal page that mounts ONE element
|
||||
// and nothing else — the dev equivalent of the built tree's <bundle>/<profile>/index.html, which
|
||||
// is what this URL means in production. Same contract, so the studio behaves identically in both.
|
||||
const link = (...parts) => '/__leaf/' + parts.map(encodeURIComponent).join('/')
|
||||
const bundles = [...reg].sort((a, b) => a.bundle.localeCompare(b.bundle)).map((b) => {
|
||||
const scriptUrl = `/plugin-designs/${b.bundle}/index.js`
|
||||
const presets = new Map() // profile → its preset instances (empty for every other kind)
|
||||
|
|
@ -142,7 +154,112 @@ export function autoSpaDevPlugin(token = '') {
|
|||
try { reg = discoverSpaRegistry() } catch (e) { return json(res, { error: String(e) }, 500) }
|
||||
return json(res, path.endsWith('/manifest.json') ? galleryModel(reg) : reg)
|
||||
}
|
||||
if (path !== '/' && path !== '/index.html') return next()
|
||||
// ── `/__leaf/<bundle>/<profile>[/<instance>]` — ONE element, nothing else ────────────
|
||||
// The dev stand-in for the built tree's <bundle>/<profile>/index.html. The studio's iframe
|
||||
// panes point here (see the link() note above); without it they fell through to Vite's SPA
|
||||
// fallback and rendered `/` — i.e. the whole studio/gallery — inside every map tab.
|
||||
// Deliberately minimal: no chrome, no prose, no gallery. Just the tag, its bundle entry, and
|
||||
// the baked token, so what mounts here is the element and only the element.
|
||||
if (path.startsWith('/__leaf/')) {
|
||||
const parts = path.slice('/__leaf/'.length).split('/').filter(Boolean).map(s => {
|
||||
try { return decodeURIComponent(s) } catch { return '' }
|
||||
})
|
||||
const [bundle, profile, instance] = parts
|
||||
// Path traversal guard: these land in a filesystem-ish URL below.
|
||||
if (!bundle || !profile || parts.some(s => !s || s.includes('..') || s.includes('/'))) {
|
||||
res.statusCode = 400; return res.end('bad leaf path')
|
||||
}
|
||||
let reg
|
||||
try { reg = discoverSpaRegistry() } catch (e) { return json(res, { error: String(e) }, 500) }
|
||||
const entry = reg.find(b => b.bundle === bundle)
|
||||
if (!entry) return json(res, { error: 'unknown bundle', bundle }, 404)
|
||||
const tag = entry.tag
|
||||
let leafTok = token
|
||||
if (!leafTok) { try { leafTok = (readBuildConfig() || {}).token || '' } catch { leafTok = '' } }
|
||||
// profile-config carries the same shape the built leaves pass, so a profile/instance
|
||||
// renders the SAME view here as in production rather than the bundle's default.
|
||||
const pc = JSON.stringify(instance ? { profile, instanceId: instance } : { profile })
|
||||
res.statusCode = 200
|
||||
res.setHeader('Content-Type', 'text/html; charset=utf-8')
|
||||
res.setHeader('Cache-Control', 'no-cache')
|
||||
return res.end([
|
||||
'<!DOCTYPE html>',
|
||||
`<html lang="en"><head><meta charset="UTF-8" /><title>${bundle} · ${profile}</title>`,
|
||||
'<meta name="viewport" content="width=device-width, initial-scale=1.0" />',
|
||||
'<link rel="icon" href="data:,">',
|
||||
leafTok ? `<script>window.MAP_TOKEN=${JSON.stringify(leafTok)};</script>` : '',
|
||||
'<style>html,body{margin:0;padding:0;height:100%;background:#f3f3f1;}',
|
||||
`${tag}{display:block;width:100%;height:100vh;}</style>`,
|
||||
'</head><body>',
|
||||
`<${tag} profile-config='${pc}'></${tag}>`,
|
||||
`<script type="module" src="/plugin-designs/${bundle}/index.js"></script>`,
|
||||
'</body></html>',
|
||||
].filter(Boolean).join('\n'))
|
||||
}
|
||||
// ── `/` MOUNTS THE PLUGIN STUDIO, NOT THE STAGING GALLERY ────────────────────────────
|
||||
// DRIVER DIRECTION 2026-09-22: "when I run dev server on the dx maps it should just open up
|
||||
// a plugin studio that gives you access to all the plugins in one place, without a lot of
|
||||
// text garb that takes up so much space" — and, separately, "we do not need the extra
|
||||
// generated staging environment because the plugin studio is replacing these index html."
|
||||
//
|
||||
// WHY THIS ROUTE HAD TO EXIST AT ALL. The dev server previously served ONLY the gallery at
|
||||
// `/`, and had no route for a bundle leaf — so `bun run dev` could not show the cap-5 studio
|
||||
// by any URL. The Driver opened `/`, saw plugins wrapped in per-bundle frames behind a wall
|
||||
// of explanatory prose, and reasonably concluded "none of the elements are working". They
|
||||
// were working; the page was a CHOOSER presenting itself as the product.
|
||||
//
|
||||
// WHY MOUNT THE TAG RATHER THAN REDIRECT TO A LEAF: plugin-studio is a real cap-5
|
||||
// defineCustomElement element, so the honest dev surface is the element itself, loaded
|
||||
// through its own index.js — the same path the built bundle takes. That keeps HMR working
|
||||
// (Vite transforms the .vue on demand) and keeps registerStrategies/profileModules/Pinia
|
||||
// intact, which baking or iframing would bypass.
|
||||
//
|
||||
// The gallery is NOT deleted — it is still generated for the built tree and is still
|
||||
// load-bearing for the manifest. It simply stops being what a developer lands on. It stays
|
||||
// reachable at an explicit URL for anyone who wants the bundle index.
|
||||
if (path === '/' || path === '/index.html') {
|
||||
// Same resolution order the gallery uses: the .env/VITE_MAPBOX_TOKEN value passed into
|
||||
// autoSpaDevPlugin(token), else the build config's baked token. readBuildConfig() is
|
||||
// called HERE rather than reusing page()'s `cfg` — that const lives inside the page()
|
||||
// arrow function, so it is not in scope in this middleware (an earlier version referenced
|
||||
// it and the dev server 500'd with "cfg is not defined").
|
||||
let devTok = token
|
||||
if (!devTok) { try { devTok = (readBuildConfig() || {}).token || '' } catch { devTok = '' } }
|
||||
res.statusCode = 200
|
||||
res.setHeader('Content-Type', 'text/html; charset=utf-8')
|
||||
res.setHeader('Cache-Control', 'no-cache')
|
||||
return res.end([
|
||||
'<!DOCTYPE html>',
|
||||
'<html lang="en"><head><meta charset="UTF-8" />',
|
||||
'<meta name="viewport" content="width=device-width, initial-scale=1.0" />',
|
||||
'<title>doxa-maps plugin studio</title>',
|
||||
'<link rel="icon" href="data:,">',
|
||||
// The token ladder's baked rung, exactly as the built leaves receive it, so maps paint
|
||||
// in dev without anyone appending ?tk=.
|
||||
devTok ? `<script>window.MAP_TOKEN=${JSON.stringify(devTok)};</script>` : "",
|
||||
// No chrome, no prose: the element fills the viewport. "Without a lot of text garb
|
||||
// that takes up so much space" is a layout requirement, so the host page contributes
|
||||
// ZERO text — every pixel belongs to the studio element.
|
||||
'<style>html,body{margin:0;padding:0;height:100%;background:#f3f3f1;}',
|
||||
'plugin-studio{display:block;width:100%;height:100vh;}</style>',
|
||||
'</head><body>',
|
||||
// tree-base IS REQUIRED IN DEV — without it the studio mounts EMPTY.
|
||||
// The studio builds its tab list at runtime by fetching `<base>manifest.json`, and with
|
||||
// no base it falls back to '../manifest.json' RELATIVE TO ITS SCRIPT URL. In dev the
|
||||
// script is /plugin-designs/plugin-studio/index.js, so that resolves to
|
||||
// /plugin-designs/manifest.json — which does not exist. The shell then renders with zero
|
||||
// rows: 31 shadow descendants, no nested plugin tags. (Measured exactly that before this
|
||||
// line existed; the symptom reads as "no elements in the studio", not as a failed fetch.)
|
||||
// The dev server's manifest lives on its own route instead, serving the SAME
|
||||
// manifest-shaped model the built tree bakes (19 bundles / 30 profiles), so pointing the
|
||||
// studio at it gives dev the same tab list production has.
|
||||
'<plugin-studio tree-base="/__plugin-gallery"></plugin-studio>',
|
||||
'<script type="module" src="/plugin-designs/plugin-studio/index.js"></script>',
|
||||
'</body></html>',
|
||||
].filter(Boolean).join('\n'))
|
||||
}
|
||||
// The staging gallery, kept but DEMOTED to an explicit opt-in URL.
|
||||
if (path !== '/__gallery' && path !== '/__gallery/') return next()
|
||||
let html
|
||||
try { html = page() }
|
||||
catch (e) { console.error(`[plugin-gallery] ${e && e.stack ? e.stack : e}`); return next() }
|
||||
|
|
|
|||
Loading…
Reference in a new issue