From bf7e0e64640a633a4bdb132867861d65e694c19d Mon Sep 17 00:00:00 2001 From: doxa-maps-88655c70 <88627970+doxa-maps-88655c70@users.noreply.github.com> Date: Fri, 25 Sep 2026 03:27:21 +0000 Subject: [PATCH] Dev server: serve assets from data/ and under the tree base, so a fresh clone's plugins get their data Opening every plugin in the studio under the dev server on a fresh worktree, 13 of them mounted with empty data: every table, boundary and people-groups file 404'd. Two causes. - The dev asset lookup read public/assets (a mirror generate-tree writes, absent on a fresh clone) and plugin-designs/*/data, but never data/, the tracked source that mirror is copied from. It now falls back to data/, which is what DX gets when they clone. - Plugins nested in the studio resolve assets against the tree base the studio hands them (/__plugin-gallery/), as they do against /assets in the built tree, but only /assets/ was served. Both prefixes are served now. After: the same sweep shows the data 404s gone from all 13. --- internal/plugin-gallery/dev-plugin.mjs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/internal/plugin-gallery/dev-plugin.mjs b/internal/plugin-gallery/dev-plugin.mjs index e532351e4..b41ea1e1d 100644 --- a/internal/plugin-gallery/dev-plugin.mjs +++ b/internal/plugin-gallery/dev-plugin.mjs @@ -6,7 +6,7 @@ * galleryModel(discoverSpaRegistry()) * GET /__plugin-gallery/manifest.json that model, manifest-shaped (the page fetches it live) * GET /__plugin-gallery/registry.json the discovery registry, machine-readable (smoke tools) - * GET /assets/ shared asset root (see resolveDevAsset) + * GET /assets/ (and /__plugin-gallery/assets/) shared asset root (see resolveDevAsset) * Wire-up: plugins: [vue(), cssInjectedByJsPlugin(), autoSpaDevPlugin(token)] in vite.config.js. * Verify: bun run smoke:gallery (internal/plugin-gallery-smoke.mjs — every element must boot) */ @@ -26,7 +26,7 @@ import { readBuildConfig, renderGallery } from '../generate-gallery.mjs' // resources-map, cities-map, poster walls, tables) fetch `assetsBase + '/tables/*.json'` // and `'/boundaries/*.geojson'`. publicDir is false ON PURPOSE (a bundle build must never // inline public/), so dev has to serve the asset tree explicitly, the way the built site -// does: public/assets first (exactly what generate-tree ships), else the per-bundle source +// does: public/assets first (exactly what generate-tree ships), else the tracked data/ source, else the per-bundle source // of truth plugin-designs//data/ when public/ has been cleaned. const DEV_ASSET_MIME = { '.json': 'application/json; charset=utf-8', '.geojson': 'application/geo+json; charset=utf-8', @@ -39,6 +39,12 @@ export function resolveDevAsset(rel, root = REPO_ROOT) { const isFile = (p) => existsSync(p) && statSync(p).isFile() const shipped = resolve(root, 'public', 'assets', rel) if (isFile(shipped)) return shipped + // data/ is the TRACKED source of truth public/assets mirrors (see .gitignore): generate-tree + // writes the mirror, so on a fresh clone — which is what DX receives — public/assets holds almost + // nothing and every table, boundary and people-groups file 404'd under the dev server until the + // first full build. Measured 2026-09-25 across the studio: 13 plugins mounted with empty data. + const source = resolve(root, 'data', rel) + if (isFile(source)) return source const profiles = resolve(root, 'plugin-designs') for (const name of readdirSync(profiles)) { if (name.startsWith('_') || name.startsWith('.')) continue @@ -232,12 +238,17 @@ export function autoSpaDevPlugin(token = '') { // /assets/* — see resolveDevAsset. A miss is a REAL 404, never Vite's HTML // fallback: HTML-for-JSON made three maps die silently at JSON.parse before // they ever created a map, with no console error and no error card. - if (path.startsWith('/assets/')) { + // /assets/* too: a plugin nested in a cap-5 shell resolves its assets against the tree base + // the shell hands it (plugin-studio passes /__plugin-gallery/), exactly as it would against + // /assets in the built tree. Only /assets/ was served, so every nested data table 404'd. + const assetPrefix = path.startsWith('/assets/') ? '/assets/' + : path.startsWith('/__plugin-gallery/assets/') ? '/__plugin-gallery/assets/' : '' + if (assetPrefix) { let rel = '' - try { rel = decodeURIComponent(path.slice('/assets/'.length)) } catch { rel = '' } + try { rel = decodeURIComponent(path.slice(assetPrefix.length)) } catch { rel = '' } if (!rel || rel.split('/').includes('..')) { res.statusCode = 400; return res.end('bad asset path') } const file = resolveDevAsset(rel) - if (!file) return json(res, { error: 'asset not found', asset: rel, looked: ['public/assets/', 'plugin-designs/*/data/'] }, 404) + if (!file) return json(res, { error: 'asset not found', asset: rel, looked: ['public/assets/', 'data/', 'plugin-designs/*/data/'] }, 404) res.statusCode = 200 res.setHeader('Content-Type', DEV_ASSET_MIME[extname(file).toLowerCase()] || 'application/octet-stream') res.setHeader('Cache-Control', 'no-cache')