Summary
Mounting the a11y inspector by package name leaves its panel stuck on "No page connected":
initHub({ devframes: ['@devframes/plugin-a11y'] })
The panel loads and connects to the hub fine, but nothing ever boots the page script, so axe never runs. Every host has to know that this one devframe needs extra wiring.
I hit this building a Next.js DevTools integration (experimental.devframes mounting a hub from the dev server). Every other built-in devframe I mounted — terminals, inspect, code-server, data-inspector — works by package name; a11y is the only one that doesn't.
Why
The page script is a real file the plugin knows about, but the host is the only party that can put it on an origin the browser can import from:
plugins/a11y/src/index.ts exports a11yPageScriptBundlePath (an absolute path to dist/inject/inject.js) but its definition's dock only sets category: '~builtin'.
dock.clientScript.importFrom has to be a URL (or a bare specifier on a host that declares clientModuleResolution), so the plugin can't name it itself.
DevframeDockDefaults (packages/devframe/src/types/devframe.ts:235) has no clientScript field, so a definition can't declare one even as a path.
So the knowledge is split: the plugin knows the file, the host knows the URL space, and nothing joins them.
What hosts do today
examples/hub-next mounts the directory by hand and attaches the URL:
const bundle = mod.a11yPageScriptBundlePath
await ctx.host.mountStatic(A11Y_AGENT_MOUNT_BASE, dirname(bundle))
// …and per-mount:
{ devframe: def, dock: { clientScript: { importFrom: `${A11Y_AGENT_MOUNT_BASE}inject.js` } } }
The Next.js integration needs the same, which pushes the wart into user config:
// next.config.mjs
import createA11y, { a11yPageScriptBundlePath } from '@devframes/plugin-a11y'
devframes: [
{ devframe: createA11y(), dock: { clientScript: { importFrom: a11yPageScriptBundlePath } } },
]
Three hosts, three copies of the same recipe, and it only exists because one devframe ships a page script.
Proposal
Let a definition declare its page script as a file path, and have the hub mount it and rewrite it to the served URL.
- Add
clientScript?: { importFrom: string, importName?: string } to DevframeDockDefaults.
- In
prepareDevframe (packages/hub/src/node/install-devframe.ts), when the resolved clientScript.importFrom is an absolute path, mount its directory and replace it with the served URL — the same mount(base, dir) the hub already uses for each devframe's SPA dist (packages/hub/src/node/initiate.ts:475). Something like <base><id>/__page-script/.
- a11y then declares it itself:
dock: {
category: '~builtin',
clientScript: { importFrom: a11yPageScriptBundlePath },
}
devframes: ['@devframes/plugin-a11y'] then works on every host, and the { devframe, dock } form goes back to being for genuine per-mount customization. A URL or bare specifier in importFrom keeps working unchanged, so this is additive.
Notes
- Booting is separate.
createDevframeClientRuntime() imports dock client scripts, so hub UI providers using it get this for free. A host that doesn't run the client runtime still needs to boot the script itself, but with the above it at least has a URL to boot — it's on the dock entry already. Worth a line in the docs either way.
- Ordering matters. The panel handshakes with the page script over the in-page channel when it mounts, and
inject.js is ~1 MB. A panel that mounts before the script has run is left waiting, which looks exactly like a broken handshake. I fixed it host-side by awaiting the script before rendering the panel, but if the handshake retried (or the panel re-announced on channel join) hosts wouldn't have to care about load order at all.
- Observed on
devframe@0.9.6 (with *--assets@0.9.7).
Summary
Mounting the a11y inspector by package name leaves its panel stuck on "No page connected":
The panel loads and connects to the hub fine, but nothing ever boots the page script, so axe never runs. Every host has to know that this one devframe needs extra wiring.
I hit this building a Next.js DevTools integration (
experimental.devframesmounting a hub from the dev server). Every other built-in devframe I mounted — terminals, inspect, code-server, data-inspector — works by package name; a11y is the only one that doesn't.Why
The page script is a real file the plugin knows about, but the host is the only party that can put it on an origin the browser can import from:
plugins/a11y/src/index.tsexportsa11yPageScriptBundlePath(an absolute path todist/inject/inject.js) but its definition'sdockonly setscategory: '~builtin'.dock.clientScript.importFromhas to be a URL (or a bare specifier on a host that declaresclientModuleResolution), so the plugin can't name it itself.DevframeDockDefaults(packages/devframe/src/types/devframe.ts:235) has noclientScriptfield, so a definition can't declare one even as a path.So the knowledge is split: the plugin knows the file, the host knows the URL space, and nothing joins them.
What hosts do today
examples/hub-nextmounts the directory by hand and attaches the URL:The Next.js integration needs the same, which pushes the wart into user config:
Three hosts, three copies of the same recipe, and it only exists because one devframe ships a page script.
Proposal
Let a definition declare its page script as a file path, and have the hub mount it and rewrite it to the served URL.
clientScript?: { importFrom: string, importName?: string }toDevframeDockDefaults.prepareDevframe(packages/hub/src/node/install-devframe.ts), when the resolvedclientScript.importFromis an absolute path, mount its directory and replace it with the served URL — the samemount(base, dir)the hub already uses for each devframe's SPA dist (packages/hub/src/node/initiate.ts:475). Something like<base><id>/__page-script/.devframes: ['@devframes/plugin-a11y']then works on every host, and the{ devframe, dock }form goes back to being for genuine per-mount customization. A URL or bare specifier inimportFromkeeps working unchanged, so this is additive.Notes
createDevframeClientRuntime()imports dock client scripts, so hub UI providers using it get this for free. A host that doesn't run the client runtime still needs to boot the script itself, but with the above it at least has a URL to boot — it's on the dock entry already. Worth a line in the docs either way.inject.jsis ~1 MB. A panel that mounts before the script has run is left waiting, which looks exactly like a broken handshake. I fixed it host-side by awaiting the script before rendering the panel, but if the handshake retried (or the panel re-announced on channel join) hosts wouldn't have to care about load order at all.devframe@0.9.6(with*--assets@0.9.7).