From fba3187cd49b984324897cc2a2feea7b6e05fb9e Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Sat, 3 Oct 2026 05:39:23 +0000 Subject: [PATCH] Dev server: Plugin Studio and the leaf pages own the address too The dev pages passed no urlOwner, so on the dev server a shell read ?tab= but never wrote it: a clicked tab was lost on reload and a copied link opened the first tab. The root page, every /__leaf/ page and the Studio staging page now own the address, as the built leaves do. A framed leaf (Studio iframes its map rows) stays unowned, so it never adds entries to the top page's Back stack. --- internal/plugin-gallery/dev-plugin.mjs | 15 ++++++++++++++- plugin-designs/plugin-studio/index.html | 4 +++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/internal/plugin-gallery/dev-plugin.mjs b/internal/plugin-gallery/dev-plugin.mjs index 5b6ff2b62..34cece14a 100644 --- a/internal/plugin-gallery/dev-plugin.mjs +++ b/internal/plugin-gallery/dev-plugin.mjs @@ -197,6 +197,16 @@ export function leafRewrite(path, treeName = TREE_NAME) { return '/__leaf/' + segs.join('/') } +// urlOwner 'app' (library/composables/useViewAddress.js) for a dev page that is OURS, the rule generate-tree bakes +// into the built leaf: its shell writes ?tab=, so a clicked tab survives a reload and a copied link opens it. A +// FRAMED page drops it: Plugin Studio iframes its map rows from /__leaf/, and a history push from an iframe lands +// in the top page's Back stack. Write the element unowned and put this classic script right after it; it runs +// before the deferred module script upgrades the element. `<` is escaped: profile/instance come from the URL. +export function ownIfTopScript(cfg = {}) { + const owned = JSON.stringify(JSON.stringify({ ...cfg, urlOwner: 'app' })).replace(/try{if(window.self===window.top)document.currentScript.previousElementSibling.setAttribute('profile-config',${owned})}catch(e){}` +} + export function autoSpaDevPlugin(token = '') { // One generator for both galleries (internal/generate-gallery.mjs), driven by the same build // config the built page bakes. What dev changes: the model comes from discovery, the live @@ -365,7 +375,8 @@ export function autoSpaDevPlugin(token = '') { // renders the SAME view here as in production rather than the bundle's default. // assetsBase too, as the built leaf's BASE_CONFIG carries it ("../../assets" there; root-absolute // here, because a /__leaf/ URL's depth differs from the tree's and the pool is served at /assets). - const pc = JSON.stringify(instance ? { profile, instanceId: instance, assetsBase: '/assets' } : { profile, assetsBase: '/assets' }) + const leafPc = instance ? { profile, instanceId: instance, assetsBase: '/assets' } : { profile, assetsBase: '/assets' } + const pc = JSON.stringify(leafPc) // The composition tier of THIS profile, read from the same discovery the gallery uses. // `nested-elements-mfe` (cap-4) is the StackLayout scroll-stack tier — see the style note // below. An unknown profile falls through to the fixed-viewport leaf, the safe default. @@ -430,6 +441,7 @@ export function autoSpaDevPlugin(token = '') { // live 19-bundle tree. Passing the base makes a nested shell see exactly what the top // level sees. Elements that ignore tree-base simply drop an unknown attribute. `<${tag} tree-base="/__plugin-gallery" profile-config='${pc}'>`, + ownIfTopScript(leafPc), ``, '', ].filter(Boolean).join('\n')) @@ -499,6 +511,7 @@ export function autoSpaDevPlugin(token = '') { // 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. '', + ownIfTopScript(), '', '', ].filter(Boolean).join('\n')) diff --git a/plugin-designs/plugin-studio/index.html b/plugin-designs/plugin-studio/index.html index 5e3eb716a..0dea59ca5 100644 --- a/plugin-designs/plugin-studio/index.html +++ b/plugin-designs/plugin-studio/index.html @@ -35,7 +35,9 @@ real tag; map elements nest as their own SPA. Switch with no page reload.

- + +