From 49b344b1b089bb5a4234bdd49398157b2f06d24b Mon Sep 17 00:00:00 2001 From: prismiwi2015 Date: Sun, 16 Aug 2026 22:00:57 +0200 Subject: [PATCH] Give the import page a way in on a desktop With OpenStation active the Forms admin menu is not registered -- every surface is reached through the dock tile instead -- so the import page, which has no native window and had no dock row, existed at a URL nobody could navigate to. The feature shipped unreachable on exactly the sites it was built for. The tile gains an "Import forms" row: the one row carrying a real `url` rather than a callback, because the shell opens such a row as a window of its own, and an admin page whose buttons POST is the wrong shape for a native window rendering into the shell's DOM. Gated on the same capability as the builder, and on there being an admin URL in the config to point at. Found by running the plugin on the desktop site it integrates with, rather than on a bare WordPress. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_0119U4sRRWGcTQdwYreTwpAp --- assets/js/dock.js | 6 ++++++ assets/js/dock.min.js | 2 +- docs/architecture.md | 13 +++++++++++++ src/dock.ts | 14 ++++++++++++++ tests/vitest/dock.test.ts | 27 +++++++++++++++++++++++++++ 5 files changed, 61 insertions(+), 1 deletion(-) diff --git a/assets/js/dock.js b/assets/js/dock.js index acd9067..0de9010 100644 --- a/assets/js/dock.js +++ b/assets/js/dock.js @@ -48,6 +48,12 @@ var allTerrainFormsDock = function(exports) { windowId: THEMES }); } + if (config2?.canEdit && config2?.adminUrl) { + submenu.push({ + title: "Import forms", + url: `${config2.adminUrl}admin.php?page=allterrain-forms-import` + }); + } if (config2?.canEdit && config2?.devMode) { submenu.push({ title: "Demo data", diff --git a/assets/js/dock.min.js b/assets/js/dock.min.js index 96867d6..0b1c417 100644 --- a/assets/js/dock.min.js +++ b/assets/js/dock.min.js @@ -1 +1 @@ -var allTerrainFormsDock=function(s){"use strict";const u=window.allTerrainForms,r="allterrain-forms",o="allterrain-forms-entries",a="allterrain-forms-themes",i="allterrain-forms-analytics";function t(e){l()?.openWindow?.(e,{source:"dock"})}function l(){return window.wp?.os??null}function c(e){const n=[];return e?.canEdit&&n.push({title:"Forms",url:"",onSelect:()=>t(r),windowId:r}),e?.canRead&&n.push({title:"Form entries",url:"",onSelect:()=>t(o),windowId:o}),e?.canRead&&n.push({title:"Analytics",url:"",onSelect:()=>t(i),windowId:i}),e?.canEdit&&n.push({title:"Themes",url:"",onSelect:()=>t(a),windowId:a}),e?.canEdit&&e?.devMode&&n.push({title:"Demo data",url:"",onSelect:()=>{t(i),document.dispatchEvent(new CustomEvent("atf-open-demo-panel"))},windowId:i}),n}function d(){const e=l();if(!e?.registerSystemTile)return;const n=c(u);try{e.registerSystemTile({id:"allterrain-forms",title:"AllTerrain Forms",icon:"dashicons-feedback",order:5,onOpen:()=>t(u?.canEdit?r:o),isOpen:()=>!!(e.windowManager?.getById?.(r)||e.windowManager?.getById?.(o)||e.windowManager?.getById?.(a)),submenu:n})}catch{}}function m(){const e=l();return e?.ready?(e.ready(d),!0):e?.whenReady?(e.whenReady(d),!0):e?.registerSystemTile?(d(),!0):!1}return m()||document.addEventListener("os-init",()=>void m(),{once:!0}),s.submenuFor=c,Object.defineProperty(s,Symbol.toStringTag,{value:"Module"}),s}({}); +var allTerrainFormsDock=function(a){"use strict";const u=window.allTerrainForms,r="allterrain-forms",i="allterrain-forms-entries",s="allterrain-forms-themes",o="allterrain-forms-analytics";function t(e){l()?.openWindow?.(e,{source:"dock"})}function l(){return window.wp?.os??null}function c(e){const n=[];return e?.canEdit&&n.push({title:"Forms",url:"",onSelect:()=>t(r),windowId:r}),e?.canRead&&n.push({title:"Form entries",url:"",onSelect:()=>t(i),windowId:i}),e?.canRead&&n.push({title:"Analytics",url:"",onSelect:()=>t(o),windowId:o}),e?.canEdit&&n.push({title:"Themes",url:"",onSelect:()=>t(s),windowId:s}),e?.canEdit&&e?.adminUrl&&n.push({title:"Import forms",url:`${e.adminUrl}admin.php?page=allterrain-forms-import`}),e?.canEdit&&e?.devMode&&n.push({title:"Demo data",url:"",onSelect:()=>{t(o),document.dispatchEvent(new CustomEvent("atf-open-demo-panel"))},windowId:o}),n}function d(){const e=l();if(!e?.registerSystemTile)return;const n=c(u);try{e.registerSystemTile({id:"allterrain-forms",title:"AllTerrain Forms",icon:"dashicons-feedback",order:5,onOpen:()=>t(u?.canEdit?r:i),isOpen:()=>!!(e.windowManager?.getById?.(r)||e.windowManager?.getById?.(i)||e.windowManager?.getById?.(s)),submenu:n})}catch{}}function m(){const e=l();return e?.ready?(e.ready(d),!0):e?.whenReady?(e.whenReady(d),!0):e?.registerSystemTile?(d(),!0):!1}return m()||document.addEventListener("os-init",()=>void m(),{once:!0}),a.submenuFor=c,Object.defineProperty(a,Symbol.toStringTag,{value:"Module"}),a}({}); diff --git a/docs/architecture.md b/docs/architecture.md index be4bbef..9505885 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -232,6 +232,19 @@ can be read aloud, selected and searched; resizing is a reflow rather than a redraw; and the plugin ships no charting dependency to every site that installs it. +### Import, the one page with no native window + +**Forms → Import** is deliberately not a native window. It is a server-rendered +page whose buttons `POST` to `admin-post.php`, which is the right shape for a +one-time migration and the wrong shape for a window rendering into the shell's +own DOM. With the shell up it is reached instead through the dock tile's +`Import forms` row — the one row carrying a real `url`, which the shell opens +as a window of its own. + +That row is load-bearing rather than convenient. With the shell active the +Forms admin menu is not registered at all (see below), so a page with neither a +native window nor a dock row exists at a URL nobody can navigate to. + ### The admin-URL handoff Every surface — builder, entries, analytics, Theme Studio — also has an admin diff --git a/src/dock.ts b/src/dock.ts index c45e2b6..bced9d7 100644 --- a/src/dock.ts +++ b/src/dock.ts @@ -42,6 +42,7 @@ interface RuntimeConfig { canEdit?: boolean; canRead?: boolean; devMode?: boolean; + adminUrl?: string; } const config: RuntimeConfig | undefined = ( window as unknown as { allTerrainForms?: RuntimeConfig } ).allTerrainForms; @@ -130,6 +131,19 @@ export function submenuFor( config: RuntimeConfig | undefined ): SubmenuRow[] { } ); } + // The one row that is a URL rather than a native window, because the import + // page is one: a server-rendered page whose buttons POST to `admin-post.php`. + // The shell opens a row with a real `url` as a window of its own, which is + // the only way this page is reachable on a desktop — with the shell up, the + // Forms admin menu is not registered, so a page with no native window and no + // row here exists at a URL nobody can get to. + if ( config?.canEdit && config?.adminUrl ) { + submenu.push( { + title: 'Import forms', + url: `${ config.adminUrl }admin.php?page=allterrain-forms-import`, + } ); + } + // Developer mode only, and last — it writes several hundred entries into the // database, which is not something to leave one hover away from "Themes" on a // site that is collecting real enquiries. diff --git a/tests/vitest/dock.test.ts b/tests/vitest/dock.test.ts index 364fa80..0f7d9c4 100644 --- a/tests/vitest/dock.test.ts +++ b/tests/vitest/dock.test.ts @@ -98,3 +98,30 @@ describe( 'the rest of the menu', () => { expect( submenuFor( config( {} ) ) ).toEqual( [] ); } ); } ); + +describe( 'the import row', () => { + const admin = { canEdit: true, canRead: true, adminUrl: 'http://example.com/wp-admin/' }; + + it( 'is offered to somebody who may build forms', () => { + expect( titles( admin ) ).toContain( 'Import forms' ); + } ); + + it( 'carries a real URL rather than a callback', () => { + // The import page is a server-rendered page whose buttons POST, not a + // native window — the shell opens a row with a `url` as its own window, + // and that is the only way the page is reachable with the shell up. + const row = submenuFor( config( admin ) ).find( ( candidate ) => candidate.title === 'Import forms' ); + + expect( row?.url ).toBe( 'http://example.com/wp-admin/admin.php?page=allterrain-forms-import' ); + expect( row?.onSelect ).toBeUndefined(); + } ); + + it( 'is absent for somebody who may only read entries', () => { + expect( titles( { canRead: true, adminUrl: 'http://example.com/wp-admin/' } ) ).not.toContain( 'Import forms' ); + } ); + + it( 'is absent when there is no admin URL to point at', () => { + // Rather than a row that opens `undefinedadmin.php`. + expect( titles( { canEdit: true, canRead: true } ) ).not.toContain( 'Import forms' ); + } ); +} );