From 22440899d543e01dc5cc9e2cc77a005fb1e44521 Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Sun, 27 Sep 2026 22:13:27 +0000 Subject: [PATCH] Plugin Studio: the Experimental section no longer reads CAP 0, and clicking a plugin no longer unfolds it The Experimental section is a stage, not a tier, so its heading showed CAP 0 and the Research Gallery (capability 5) looked like a capability-0 plugin. The heading now reads EXP and each of its rows names its own capability. Clicking a plugin opens it without unfolding its folder or its parts; only the twisty folds and unfolds, and a closed folder that stands for the plugin stays closed. --- .../plugin-studio/plugin-studio.vue | 7 ++++-- .../plugin-studio/src/StudioTree.vue | 25 +++++++++++++------ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/plugin-designs/plugin-studio/plugin-studio.vue b/plugin-designs/plugin-studio/plugin-studio.vue index 270fe253f..0fa4f4d43 100644 --- a/plugin-designs/plugin-studio/plugin-studio.vue +++ b/plugin-designs/plugin-studio/plugin-studio.vue @@ -632,7 +632,8 @@ export default defineComponent({ const pushElement = (r, depth, parent) => { const t = treeOf.get(r.key) nodes.push({ - id: r.key, parent, depth, label: r.label, desc: r.tag, icon: r.icon, target: r.key, canonical: true, + // In the Experimental section the heading carries no tier, so each row says its own. + id: r.key, parent, depth, label: r.label, desc: cap === 'experimental' ? 'CAP ' + r.cap + ' · ' + r.tag : r.tag, icon: r.icon, target: r.key, canonical: true, kids: t ? t.nodes[0].kids : 0, title: r.label + ' · <' + r.tag + '> · CAP ' + r.cap + ' ' + this.tr('cap' + r.cap) }) if (!t) return @@ -660,7 +661,9 @@ export default defineComponent({ }) for (const r of rows) pushElement(r, 1, fid) } - if (cap === 'experimental') return { id: 'stage-experimental', cap: 0, label: this.tr('stageExperimental'), title: this.tr('stageExperimental'), icon: 'layers', nodes } + // The Experimental section is a STAGE, not a tier: it has no capability number of its own (it read + // "CAP 0", and the Driver took the Research Gallery for a capability-0 plugin). + if (cap === 'experimental') return { id: 'stage-experimental', cap: 0, badge: 'EXP', short: 'E', label: this.tr('stageExperimental'), title: this.tr('stageExperimental'), icon: 'layers', nodes } return { id: 'cap-' + cap, cap, label: this.tr('cap' + cap), title: this.tr('cap' + cap) + ' · ' + (MACHINE_TYPE[cap] || ''), icon: CAP_ICON[cap] || 'embed', nodes } }).filter(Boolean) }, diff --git a/plugin-designs/plugin-studio/src/StudioTree.vue b/plugin-designs/plugin-studio/src/StudioTree.vue index b5a5d543c..a5f2da911 100644 --- a/plugin-designs/plugin-studio/src/StudioTree.vue +++ b/plugin-designs/plugin-studio/src/StudioTree.vue @@ -36,10 +36,10 @@
@@ -70,7 +70,7 @@ @focus="focusId = item.id"> @@ -209,7 +209,7 @@ export default defineComponent({ if (this.kept && !nodes.length) continue out.push({ id: 'sec:' + s.id, section: true, sectionId: s.id, level: 1, depth: 0, expandable: true, - cap: s.cap, label: s.label, title: s.title || '', count: s.nodes.filter(n => n.depth === 0).length + cap: s.cap, badge: s.badge || 'CAP ' + s.cap, label: s.label, title: s.title || '', count: s.nodes.filter(n => n.depth === 0).length }) if (!this.kept && this.secClosed[s.id]) continue for (const n of nodes) { @@ -273,11 +273,11 @@ export default defineComponent({ }, setRow(id, el) { if (!this._rows) this._rows = {}; if (el) this._rows[id] = el; else delete this._rows[id] }, // Choosing a plugin opens it in the pane (through the layout's pick, which also closes the mobile - // drawer) and unfolds its parts — you asked for that plugin, so show what it is made of. Only the - // twisty or ← folds. A part with no page of its own opens the composite it lives in. + // drawer). It does NOT unfold the row: the Driver clicks UPG 100 List to see UPG 100 List, not every + // element inside it. Only the twisty (or → / ←) folds and unfolds. A part with no page of its own + // opens the composite it lives in. activate(item) { if (item.section) { this.toggle(item); return } - if (item.expandable && !this.isOpen(item)) this.setOpen(item, true) if (!item.target) return if (this.pick) this.pick(item.target); else this.$emit('select', item.target) }, @@ -333,7 +333,16 @@ export default defineComponent({ row = s.nodes.find(n => isCanonical(n) && n.target === this.active) if (row) { if (this.secClosed[s.id]) { this.secClosed = { ...this.secClosed, [s.id]: false }; this.saveState() } break } } - // A plugin inside a BUNDLE FOLDER: open the folder(s) above it, or it cannot be seen. + // A closed bundle folder whose own target IS this plugin already stands for it: highlight the folder, + // don't unfold it. Otherwise open the folder(s) above the plugin, or it cannot be seen. + if (row) { + for (let p = row.parent; p;) { + const pn = this.byId.get(p) + if (!pn) break + if (pn.target === this.active && !this.nodeOpen[p]) { row = pn; break } + p = pn.parent + } + } if (row) { let p = row.parent, opened = false while (p) { if (!this.nodeOpen[p]) { this.nodeOpen = { ...this.nodeOpen, [p]: true }; opened = true } p = this.byId.get(p) && this.byId.get(p).parent }