From cdb9efc9d48ceff357eab031bf96c5082498986a Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Mon, 28 Sep 2026 01:24:24 +0000 Subject: [PATCH] Plugin Studio names each row from the plugin's own meta label, never a bare profile id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rail showed Gospel x Internet as "board": a row's name fell back to its profile id unless a hand-kept DISPLAY_NAME table had an entry. The manifest now carries each profile's .meta.json label (or title / displayName), and the rail shows it without a trailing aside in parentheses; with no label it shows a name made from the ids ("Gospel Resources · Charts"). DISPLAY_NAME is an override only and is empty. check:studio-rail fails if any row would show a bare id. --- internal/check-studio-rail.mjs | 2 ++ internal/profile-meta.mjs | 7 ++++-- .../plugin-studio/plugin-studio.vue | 23 ++++++++++++++----- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/internal/check-studio-rail.mjs b/internal/check-studio-rail.mjs index 383af0795..78862bed2 100644 --- a/internal/check-studio-rail.mjs +++ b/internal/check-studio-rail.mjs @@ -64,6 +64,8 @@ try { } } for (const b of byBundle.keys()) if (!seen.has(b)) fail(`bundle ${b} is missing from the rail`) + // A row is NAMED, never shown as a bare id (the Driver: Gospel x Internet read just "board"). + for (const r of ctx.rows) if (r.label === r.profile || r.label === r.bundle) fail(`${r.key} shows a bare id ("${r.label}") — give its .meta.json a "label"`) const canon = secs.flatMap((s) => s.nodes.filter((n) => n.canonical)) for (const r of ctx.rows) { const c = canon.filter((n) => n.target === r.key).length; if (c !== 1) fail(`${r.key} has ${c} canonical nodes (want 1)`) } const nProd = [...stageOf.values()].filter((v) => v === 'production').length diff --git a/internal/profile-meta.mjs b/internal/profile-meta.mjs index b6f015ec2..038b6c69b 100644 --- a/internal/profile-meta.mjs +++ b/internal/profile-meta.mjs @@ -17,18 +17,21 @@ export function readProfileMeta(dir, profileName) { try { const meta = JSON.parse(readFileSync(join(dir, `${profileName}.meta.json`), 'utf8')) const corner = meta?.widget?.corner + // The plugin's own display name (label, or title / displayName): what the Plugin Studio rail calls it. + const name = [meta?.label, meta?.title, meta?.displayName].find((v) => typeof v === 'string' && v.trim()) return { stage: meta?.stage === 'production' ? 'production' : 'experimental', heavy: meta?.heavy === true, widget: CORNERS.includes(corner) ? { corner } : null, + label: name ? name.trim() : '', } - } catch { return { stage: 'experimental', heavy: false, widget: null } } + } catch { return { stage: 'experimental', heavy: false, widget: null, label: '' } } } /** The optional manifest keys for one profile (stage always; heavy / widget only when set). */ export function profileMetaFields(dir, profileName) { const m = readProfileMeta(dir, profileName) - return { stage: m.stage, ...(m.heavy ? { heavy: true } : {}), ...(m.widget ? { widget: m.widget } : {}) } + return { stage: m.stage, ...(m.label ? { label: m.label } : {}), ...(m.heavy ? { heavy: true } : {}), ...(m.widget ? { widget: m.widget } : {}) } } /** A bundle is production if ANY of its profiles says so. */ diff --git a/plugin-designs/plugin-studio/plugin-studio.vue b/plugin-designs/plugin-studio/plugin-studio.vue index 20b5a2128..cac0ce815 100644 --- a/plugin-designs/plugin-studio/plugin-studio.vue +++ b/plugin-designs/plugin-studio/plugin-studio.vue @@ -411,11 +411,22 @@ for (const [path, mod] of Object.entries(import.meta.glob('../../library/i18n/lo if (m) STUDIO_STRINGS[m[1]] = mod } -// DISPLAY NAMES — what a row is CALLED, keyed by "/". Display only: the row key, the -// URL hash, the stored last-tab and the under the name are all unchanged, so links and memory -// keep working. The Driver's name for the cap-5 shell is "Research Gallery" (its meta.json label); -// the manifest does not publish meta labels, so the studio carries the one it needs here. -const DISPLAY_NAME = { 'doxa-maps-spa/doxa-maps-spa': 'Research Gallery' } +// DISPLAY NAMES — what a row is CALLED. Display only: the row key, the URL hash, the stored last-tab and +// the under the name are unchanged, so links and memory keep working. AUTO-DETECTED (the Driver: +// never hardcode): the plugin's own .meta.json label, carried by the manifest; failing that, a +// readable name made from its ids ("Gospel Internet · Board"), never a bare profile id (a row once read +// just "board"). DISPLAY_NAME is an OVERRIDE only, and empty unless a label must differ from the meta. +const DISPLAY_NAME = {} +const humanize = (id) => String(id).split(/[-_]+/).filter(Boolean).map(w => w[0].toUpperCase() + w.slice(1)).join(' ') +function rowLabel(bundle, p) { + const key = bundle + '/' + p.profile + if (DISPLAY_NAME[key]) return DISPLAY_NAME[key] + // A meta label often ends in an aside for the reader of the JSON ("Research Gallery (vertical-tab app + // shell: …)"); the rail shows the name before it. + const own = typeof p.label === 'string' ? p.label.replace(/\s*\([^()]*\)\s*$/, '').trim() : '' + if (own) return own + return p.profile === bundle ? humanize(bundle) : humanize(bundle) + ' · ' + humanize(p.profile) +} // Load a bundle exactly once per URL; resolves after its define() has run. // @@ -988,7 +999,7 @@ export default defineComponent({ bundle: b.bundle, // production | experimental, from the bundle's .meta.json via the manifest (never a list here) stage: b.stage === 'production' ? 'production' : 'experimental', - label: DISPLAY_NAME[b.bundle + '/' + p.profile] || p.profile, + label: rowLabel(b.bundle, p), // The REAL profile id, handed to the element in profile-config. Never the label: a display // name would mount with profile "Research Gallery", which it does not have. profile: p.profile,