diff --git a/alias.ts b/alias.ts
index 08db230d3..6cefefa01 100644
--- a/alias.ts
+++ b/alias.ts
@@ -78,63 +78,52 @@ export const alias = {
'@devframes/json-render-ui/spa': r('json-render-ui/src/spa.ts'),
'@devframes/json-render-ui': r('json-render-ui/src/index.ts'),
'json-render/dashboard': fileURLToPath(new URL('./examples/json-render/src/dashboard.ts', import.meta.url)),
- '@devframes/plugin-code-server/client': p('code-server/src/client/index.ts'),
- '@devframes/plugin-code-server/node': p('code-server/src/node/index.ts'),
- '@devframes/plugin-code-server/constants': p('code-server/src/constants.ts'),
- '@devframes/plugin-code-server/types': p('code-server/src/types.ts'),
- '@devframes/plugin-code-server/rpc': p('code-server/src/rpc/index.ts'),
- '@devframes/plugin-code-server/cli': p('code-server/src/cli.ts'),
- '@devframes/plugin-code-server/vite': p('code-server/src/vite.ts'),
- '@devframes/plugin-code-server': p('code-server/src/index.ts'),
- '@devframes/plugin-terminals/client': p('terminals/src/client/index.ts'),
- '@devframes/plugin-terminals/node': p('terminals/src/node/index.ts'),
- '@devframes/plugin-terminals/constants': p('terminals/src/constants.ts'),
- '@devframes/plugin-terminals/types': p('terminals/src/types.ts'),
- '@devframes/plugin-terminals/cli': p('terminals/src/cli.ts'),
- '@devframes/plugin-terminals/vite': p('terminals/src/vite.ts'),
- '@devframes/plugin-terminals': p('terminals/src/index.ts'),
- '@devframes/plugin-git': p('git/src/index.ts'),
+ '@devframes/plugin-code-server/node': p('code-server/src/node/setup.ts'),
+ '@devframes/plugin-code-server/constants': p('code-server/src/node/constants.ts'),
+ '@devframes/plugin-code-server/types': p('code-server/src/node/types.ts'),
+ '@devframes/plugin-code-server/rpc': p('code-server/src/node/rpc/index.ts'),
+ '@devframes/plugin-code-server/cli': p('code-server/src/node/cli.ts'),
+ '@devframes/plugin-code-server/vite': p('code-server/src/node/vite.ts'),
+ '@devframes/plugin-code-server': p('code-server/src/node/index.ts'),
+ '@devframes/plugin-terminals/client': p('terminals/app/client/index.ts'),
+ '@devframes/plugin-terminals/node': p('terminals/src/node/setup.ts'),
+ '@devframes/plugin-terminals/constants': p('terminals/src/node/constants.ts'),
+ '@devframes/plugin-terminals/types': p('terminals/src/node/types.ts'),
+ '@devframes/plugin-terminals/cli': p('terminals/src/node/cli.ts'),
+ '@devframes/plugin-terminals/vite': p('terminals/src/node/vite.ts'),
+ '@devframes/plugin-terminals': p('terminals/src/node/index.ts'),
+ '@devframes/plugin-git': p('git/src/node/index.ts'),
'devframe/recipes/interactive-auth': r('devframe/src/recipes/interactive-auth.ts'),
'devframe/recipes/common-rpc-functions': r('devframe/src/recipes/common-rpc-functions.ts'),
'devframe/client': r('devframe/src/client/index.ts'),
'devframe': r('devframe/src'),
- '@devframes/plugin-data-inspector/client': p('data-inspector/src/client/index.ts'),
- '@devframes/plugin-data-inspector/node': p('data-inspector/src/node/index.ts'),
- '@devframes/plugin-data-inspector/registry': p('data-inspector/src/registry/index.ts'),
- '@devframes/plugin-data-inspector/engine': p('data-inspector/src/engine/index.ts'),
'@devframes/plugin-data-inspector/inject': p('data-inspector/src/inject/index.ts'),
- '@devframes/plugin-data-inspector/cli': p('data-inspector/src/cli.ts'),
- '@devframes/plugin-data-inspector/vite': p('data-inspector/src/vite.ts'),
- '@devframes/plugin-data-inspector': p('data-inspector/src/index.ts'),
- '@devframes/plugin-inspect/client': p('inspect/src/client/index.ts'),
- '@devframes/plugin-inspect/node': p('inspect/src/node/index.ts'),
- '@devframes/plugin-inspect/cli': p('inspect/src/cli.ts'),
- '@devframes/plugin-inspect/vite': p('inspect/src/vite.ts'),
- '@devframes/plugin-inspect': p('inspect/src/index.ts'),
- '@devframes/plugin-og/client': p('og/src/client/index.ts'),
- '@devframes/plugin-og/node': p('og/src/node/index.ts'),
- '@devframes/plugin-og/rpc': p('og/src/rpc/index.ts'),
- '@devframes/plugin-og/cli': p('og/src/cli.ts'),
- '@devframes/plugin-og/vite': p('og/src/vite.ts'),
- '@devframes/plugin-og': p('og/src/index.ts'),
- '@devframes/plugin-a11y/client': p('a11y/src/client/index.ts'),
- '@devframes/plugin-a11y/node': p('a11y/src/node/index.ts'),
- '@devframes/plugin-a11y/cli': p('a11y/src/cli.ts'),
- '@devframes/plugin-a11y/vite': p('a11y/src/vite.ts'),
- '@devframes/plugin-a11y': p('a11y/src/index.ts'),
- '@devframes/plugin-messages/client': p('messages/src/client/index.ts'),
- '@devframes/plugin-messages/node': p('messages/src/node/index.ts'),
- '@devframes/plugin-messages/constants': p('messages/src/constants.ts'),
- '@devframes/plugin-messages/rpc': p('messages/src/rpc/index.ts'),
- '@devframes/plugin-messages/cli': p('messages/src/cli.ts'),
- '@devframes/plugin-messages/vite': p('messages/src/vite.ts'),
- '@devframes/plugin-messages': p('messages/src/index.ts'),
- '@devframes/plugin-assets/client': p('assets/src/client/index.ts'),
- '@devframes/plugin-assets/node': p('assets/src/node/index.ts'),
- '@devframes/plugin-assets/rpc': p('assets/src/rpc/index.ts'),
- '@devframes/plugin-assets/cli': p('assets/src/cli.ts'),
- '@devframes/plugin-assets/vite': p('assets/src/vite.ts'),
- '@devframes/plugin-assets': p('assets/src/index.ts'),
+ '@devframes/plugin-data-inspector/node': p('data-inspector/src/node/setup.ts'),
+ '@devframes/plugin-data-inspector/registry': p('data-inspector/src/node/registry/index.ts'),
+ '@devframes/plugin-data-inspector/engine': p('data-inspector/src/node/engine/index.ts'),
+ '@devframes/plugin-data-inspector/cli': p('data-inspector/src/node/cli.ts'),
+ '@devframes/plugin-data-inspector': p('data-inspector/src/node/index.ts'),
+ '@devframes/plugin-inspect/node': p('inspect/src/node/setup.ts'),
+ '@devframes/plugin-inspect/cli': p('inspect/src/node/cli.ts'),
+ '@devframes/plugin-inspect': p('inspect/src/node/index.ts'),
+ '@devframes/plugin-og/node': p('og/src/node/setup.ts'),
+ '@devframes/plugin-og/rpc': p('og/src/node/rpc/index.ts'),
+ '@devframes/plugin-og/cli': p('og/src/node/cli.ts'),
+ '@devframes/plugin-og': p('og/src/node/index.ts'),
+ '@devframes/plugin-a11y/client-script': p('a11y/src/client-script/index.ts'),
+ '@devframes/plugin-a11y/node': p('a11y/src/node/setup.ts'),
+ '@devframes/plugin-a11y/cli': p('a11y/src/node/cli.ts'),
+ '@devframes/plugin-a11y': p('a11y/src/node/index.ts'),
+ '@devframes/plugin-messages/client': p('messages/app/client/index.ts'),
+ '@devframes/plugin-messages/node': p('messages/src/node/setup.ts'),
+ '@devframes/plugin-messages/constants': p('messages/src/node/constants.ts'),
+ '@devframes/plugin-messages/rpc': p('messages/src/node/rpc/index.ts'),
+ '@devframes/plugin-messages/cli': p('messages/src/node/cli.ts'),
+ '@devframes/plugin-messages': p('messages/src/node/index.ts'),
+ '@devframes/plugin-assets/node': p('assets/src/node/setup.ts'),
+ '@devframes/plugin-assets/rpc': p('assets/src/node/rpc/index.ts'),
+ '@devframes/plugin-assets/cli': p('assets/src/node/cli.ts'),
+ '@devframes/plugin-assets': p('assets/src/node/index.ts'),
'@devframes/service-git': s('git/src/index.ts'),
'@devframes/service-open': s('open/src/index.ts'),
'@devframes/service-shiki': s('shiki/src/index.ts'),
diff --git a/docs/content/1.guide/1.tutorial-server-data-inspector.md b/docs/content/1.guide/1.tutorial-server-data-inspector.md
index 48cfbe90b..e31785259 100644
--- a/docs/content/1.guide/1.tutorial-server-data-inspector.md
+++ b/docs/content/1.guide/1.tutorial-server-data-inspector.md
@@ -13,6 +13,8 @@ You'll need [Node 24+](https://nodejs.org/) and a terminal. Every code block is
A devframe is two halves talking over a typed connection: the **node side** exposes functions, and the **browser side** calls them and renders the results. Devframe is everything in between: the wire, the UI hosting, auth, builds, and a CLI.
+The two halves live in their own folders: the node side under `src/node/`, the web app under `app/`. A `playgrounds/` folder holds hosts that boot the built tool. We'll fill these in as we go.
+
## Step 1: Define the tool
Everything starts with `defineDevframe`: your tool's name, plus a `setup` where you register what it can do. Create the project and the definition:
@@ -23,7 +25,7 @@ npm init -y && npm pkg set type=module
npm install devframe && npm install -D typescript
```
-```ts [src/data-inspector.ts]
+```ts [src/node/data-inspector.ts]
import { defineDevframe } from 'devframe'
// Some example server-side data, whatever you want to peek at while your
@@ -95,7 +97,7 @@ npm install react react-dom @devframes/vite
npm install -D vite @vitejs/plugin-react @types/react @types/react-dom
```
-```html [client/index.html]
+```html [app/index.html]
@@ -109,14 +111,14 @@ npm install -D vite @vitejs/plugin-react @types/react @types/react-dom
```
-```tsx [client/main.tsx]
+```tsx [app/main.tsx]
import { createRoot } from 'react-dom/client'
import { App } from './App'
createRoot(document.getElementById('app')!).render()
```
-```tsx [client/App.tsx]
+```tsx [app/App.tsx]
import type { DevframeRpcClient } from 'devframe/client'
import { connectDevframe } from 'devframe/client'
import { useEffect, useState } from 'react'
@@ -173,14 +175,13 @@ export function App() {
To try what we've built, let Vite serve the UI and hand RPC traffic to devframe:
-```ts [vite.client.config.ts]
+```ts [app/vite.config.ts]
import { devframeViteBridge } from '@devframes/vite/single'
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'
-import dataInspectorFrame from './src/data-inspector.ts'
+import dataInspectorFrame from '../src/node/data-inspector.ts'
export default defineConfig({
- root: 'client',
base: './', // relative asset URLs, so the built UI works under any mount path
build: { outDir: '../dist/client', emptyOutDir: true },
plugins: [
@@ -193,7 +194,7 @@ export default defineConfig({
```
```sh
-npx vite --config vite.client.config.ts
+npx vite --config app/vite.config.ts
```
Open the printed URL. The three keys and their types show up, and typing `config.port` or `users.0.name` and hitting **Query** prints the value. Button → `call` → your `handler` → back to the page: that's the whole devframe working.
@@ -201,19 +202,19 @@ Open the printed URL. The three keys and their types show up, and typing `config
> [!WARNING]
> `auth: false` trusts anything that can reach the port. It's off here to keep the tutorial simple; turn it on for anything you publish or expose beyond localhost. See [Security](/guide/security).
-From here on we reuse this same `src/data-inspector.ts` and `client/` unchanged; all that changes is where they run.
+From here on we reuse this same `src/node/data-inspector.ts` and `app/` unchanged; all that changes is where they run.
## Step 4: Dock it in a hub
A [hub](/guide/hub) puts many devframes behind one interface, each a **dock entry** you switch between, the tool's own UI in an iframe. Since our SPA uses a bare `connectDevframe()`, it already works anywhere; the hub just needs the built UI, so point the definition at it:
-```ts [src/data-inspector.ts]
+```ts [src/node/data-inspector.ts]
import { fileURLToPath } from 'node:url'
// …
const dataInspectorFrame = defineDevframe({
id: 'data-inspector',
/** … */
- clientAssets: fileURLToPath(new URL('../dist/client', import.meta.url)),
+ clientAssets: fileURLToPath(new URL('../../dist/client', import.meta.url)),
setup(ctx) { /* unchanged */ },
})
```
@@ -222,14 +223,14 @@ Build the UI and stand up a one-devframe hub:
```sh
npm install @devframes/hub @devframes/hub-ui
-npx vite build --config vite.client.config.ts
+npx vite build --config app/vite.config.ts
```
-```ts [vite.hub.config.ts]
+```ts [playgrounds/hub.config.ts]
import { createUi } from '@devframes/hub-ui'
import { viteDevframeHub } from '@devframes/vite/hub'
import { defineConfig } from 'vite'
-import dataInspectorFrame from './src/data-inspector.ts'
+import dataInspectorFrame from '../src/node/data-inspector.ts'
export default defineConfig({
plugins: [
@@ -242,7 +243,7 @@ export default defineConfig({
```
```sh
-npx vite --config vite.hub.config.ts
+npx vite --config playgrounds/hub.config.ts
```
Your inspector now sits in the hub's dock rail as a dock entry. Add more to `devframes: [...]` (your own or the [built-in devframes](/add-ons)) and each gets its own. (The hub prints a code to authorize on first connect.)
@@ -265,14 +266,14 @@ ctx.rpc.register({
```js [scripts/build.mjs]
import { createBuild } from 'devframe/adapters/build'
-import dataInspectorFrame from '../src/data-inspector.ts'
+import dataInspectorFrame from '../src/node/data-inspector.ts'
await createBuild(dataInspectorFrame, { outDir: 'dist-static' })
```
```sh
-npx vite build # refresh dist/client
-node scripts/build.mjs # → dist-static/
+npx vite build --config app/vite.config.ts # refresh dist/client
+node scripts/build.mjs # → dist-static/
```
Serve `dist-static/` anywhere and the meta list renders from the baked snapshot, no Node in sight. `query` takes an argument, so it still needs the live node side (next), or you can bake specific inputs ([Client Assets](/guide/client-assets)).
@@ -281,16 +282,16 @@ Serve `dist-static/` anywhere and the meta list renders from the baked snapshot,
The definition never depended on Vite. `createDevServer` runs the tool on its own, serving the UI from `clientAssets` and answering RPC live:
-```js [scripts/serve.mjs]
+```js [playgrounds/serve.mjs]
import { createDevServer } from 'devframe/adapters/dev'
-import dataInspectorFrame from '../src/data-inspector.ts'
+import dataInspectorFrame from '../src/node/data-inspector.ts'
await createDevServer(dataInspectorFrame, { openBrowser: true })
```
```sh
-npx vite build
-node scripts/serve.mjs
+npx vite build --config app/vite.config.ts
+node playgrounds/serve.mjs
```
Same UI, same live calls, no bundler in the loop: this is what you'd drop into your own Node program.
@@ -302,7 +303,7 @@ Finally, wrap that dev server in a CLI. `devframe/adapters/cac` turns a devframe
```js [bin.mjs]
#!/usr/bin/env node
import { createCac } from 'devframe/adapters/cac'
-import dataInspectorFrame from './src/data-inspector.ts'
+import dataInspectorFrame from './src/node/data-inspector.ts'
createCac(dataInspectorFrame).parse()
```
diff --git a/knip.jsonc b/knip.jsonc
index 92d0782e2..24548d877 100644
--- a/knip.jsonc
+++ b/knip.jsonc
@@ -19,6 +19,7 @@
// the default export is the conventional single-devframe import. Service
// packages (`createService`) follow the same rule.
"ignoreIssues": {
+ "plugins/*/src/node/index.ts": ["duplicates"],
"plugins/*/src/index.ts": ["duplicates"],
"services/*/src/index.ts": ["duplicates"],
// Deprecated rename aliases (old name re-exported next to its canonical
@@ -209,10 +210,11 @@
// the monorepo. Repeat the `plugins/*` entry glob (a workspace config
// replaces, not merges, it).
"entry": [
- "src/{index,cli,vite,constants,types}.ts",
- "src/{client,node,rpc,inject,engine,registry}/index.ts"
+ "src/node/{index,setup,cli}.ts",
+ "src/node/rpc/index.ts",
+ "app/**/*.stories.@(ts|tsx)"
],
- "ignoreDependencies": ["@devframes/plugin-inspect--assets"]
+ "ignoreDependencies": ["@devframes/plugin-inspect--assets", "@devframes/vite"]
},
"plugins/inspect/assets-pkg": {
// Assets-only package: no source, just a prebuilt `dist` produced by
@@ -225,32 +227,60 @@
// so knip can't see the dev-only workspace link. A workspace config
// replaces (not merges) the `plugins/*` glob's `entry`, so repeat it.
"plugins/og": {
- "entry": ["src/{index,cli,vite,constants,types}.ts", "src/{client,node,rpc,inject,engine,registry}/index.ts"],
- "ignoreDependencies": ["@devframes/plugin-og--assets"]
+ "entry": [
+ "src/node/{index,setup,cli}.ts",
+ "src/node/rpc/index.ts",
+ "app/**/*.stories.@(ts|tsx)"
+ ],
+ "ignoreDependencies": ["@devframes/plugin-og--assets", "@devframes/vite"]
},
"plugins/og/assets-pkg": { "entry": [], "project": [] },
"plugins/data-inspector": {
- "entry": ["src/{index,cli,vite,constants,types}.ts", "src/{client,node,rpc,inject,engine,registry}/index.ts"],
- "ignoreDependencies": ["@devframes/plugin-data-inspector--assets"]
+ "entry": [
+ "src/node/{index,setup,cli}.ts",
+ "src/node/rpc/index.ts",
+ "src/node/{engine,registry}/index.ts",
+ "src/inject/index.ts",
+ "app/**/*.stories.@(ts|tsx)"
+ ],
+ "ignoreDependencies": ["@devframes/plugin-data-inspector--assets", "@devframes/vite"]
},
"plugins/data-inspector/assets-pkg": { "entry": [], "project": [] },
"plugins/code-server": {
- "entry": ["src/{index,cli,vite,constants,types}.ts", "src/{client,node,rpc,inject,engine,registry}/index.ts"],
+ "entry": [
+ "src/node/{index,setup,cli,constants,types,vite}.ts",
+ "src/node/rpc/index.ts",
+ "app/**/*.stories.@(ts|tsx)"
+ ],
"ignoreDependencies": ["@devframes/plugin-code-server--assets"]
},
"plugins/code-server/assets-pkg": { "entry": [], "project": [] },
"plugins/assets": {
- "entry": ["src/{index,cli,vite,constants,types}.ts", "src/{client,node,rpc,inject,engine,registry}/index.ts"],
- "ignoreDependencies": ["@devframes/plugin-assets--assets"]
+ "entry": [
+ "src/node/{index,setup,cli,constants,types}.ts",
+ "src/node/rpc/index.ts",
+ "app/**/*.stories.@(ts|tsx)"
+ ],
+ "ignoreDependencies": ["@devframes/plugin-assets--assets", "@devframes/vite"]
},
"plugins/assets/assets-pkg": { "entry": [], "project": [] },
"plugins/messages": {
- "entry": ["src/{index,cli,vite,constants,types}.ts", "src/{client,node,rpc,inject,engine,registry}/index.ts"],
- "ignoreDependencies": ["@devframes/plugin-messages--assets"]
+ "entry": [
+ "src/node/{index,setup,cli,constants}.ts",
+ "src/node/rpc/index.ts",
+ "app/client/index.ts",
+ "app/**/*.stories.@(ts|tsx)"
+ ],
+ "ignoreDependencies": ["@devframes/plugin-messages--assets", "@devframes/vite"]
},
"plugins/messages/assets-pkg": { "entry": [], "project": [] },
"plugins/terminals": {
- "entry": ["src/{index,cli,vite,constants,types}.ts", "src/{client,node,rpc,inject,engine,registry}/index.ts"],
+ "entry": [
+ "src/node/{index,setup,cli,constants,types,vite}.ts",
+ "src/node/rpc/index.ts",
+ "app/client/index.ts",
+ "app/**/*.stories.@(ts|tsx)"
+ ],
"ignoreDependencies": ["@devframes/plugin-terminals--assets"]
},
"plugins/terminals/assets-pkg": { "entry": [], "project": [] },
@@ -269,19 +299,24 @@
// `plugins/*` glob's `entry` above, so repeat that pattern here
// alongside the stories.
"entry": [
- "src/{index,cli,vite,constants,types}.ts",
- "src/{client,node,rpc,inject,engine,registry}/index.ts",
- "src/**/*.stories.tsx"
+ "src/node/{index,setup,cli}.ts",
+ "src/node/rpc/index.ts",
+ "src/client-script/index.ts",
+ "app/**/*.stories.tsx"
],
- "ignoreDependencies": ["@devframes/plugin-a11y--assets"]
+ "ignoreDependencies": ["@devframes/plugin-a11y--assets", "@devframes/vite"]
},
"plugins/git": {
- // The React client build's root sits at `src/client` (`next dev
- // src/client`), not the workspace root, so knip's default PostCSS
- // config glob (workspace-root `postcss.config.*`) misses it.
+ // The React client build's root sits at `app/` (`next dev app`), not the
+ // workspace root, so knip's default PostCSS config glob (workspace-root
+ // `postcss.config.*`) misses it.
"postcss": {
- "config": ["src/client/postcss.config.mjs"]
+ "config": ["app/postcss.config.mjs"]
},
+ "entry": [
+ "src/node/{index,cli}.ts",
+ "app/**/*.stories.@(ts|tsx)"
+ ],
"ignoreDependencies": ["@devframes/plugin-git--assets"]
}
}
diff --git a/plugins/a11y/.storybook/main.ts b/plugins/a11y/.storybook/main.ts
index f3fb6eab1..a04d03afe 100644
--- a/plugins/a11y/.storybook/main.ts
+++ b/plugins/a11y/.storybook/main.ts
@@ -4,7 +4,7 @@ import { mergeConfig } from 'vite'
import { alias } from '../../../alias'
const config: StorybookConfig = {
- stories: ['../src/**/*.stories.@(ts|tsx)'],
+ stories: ['../{src,app}/**/*.stories.@(ts|tsx)'],
framework: {
name: 'storybook-solidjs-vite',
options: {},
diff --git a/plugins/a11y/.storybook/preview.tsx b/plugins/a11y/.storybook/preview.tsx
index 4e9db35ef..0dabc3eb2 100644
--- a/plugins/a11y/.storybook/preview.tsx
+++ b/plugins/a11y/.storybook/preview.tsx
@@ -1,7 +1,7 @@
import type { Preview } from 'storybook-solidjs-vite'
import 'virtual:uno.css'
import '@antfu/design/styles.css'
-import '../src/spa/styles.css'
+import '../app/styles.css'
// Drive the shared `@antfu/design` tokens off the toolbar theme toggle: dark mode
// is the `.dark` class on ``, and the canvas takes the semantic
diff --git a/plugins/a11y/src/spa/app.tsx b/plugins/a11y/app/app.tsx
similarity index 99%
rename from plugins/a11y/src/spa/app.tsx
rename to plugins/a11y/app/app.tsx
index 9226340cd..8cc919804 100644
--- a/plugins/a11y/src/spa/app.tsx
+++ b/plugins/a11y/app/app.tsx
@@ -1,8 +1,8 @@
-import type { Impact, PinTarget, Violation, ViolationNode } from '../shared/protocol.ts'
+import type { Impact, PinTarget, Violation, ViolationNode } from '../src/shared/protocol.ts'
import type { SelectedItem } from './lib/fix-prompt.ts'
import type { RouteGroupModel, SelectionApi } from './lib/violation-view.ts'
import { batch, createEffect, createMemo, createSignal, Match, on, Show, Switch } from 'solid-js'
-import { emptyCounts } from '../shared/protocol.ts'
+import { emptyCounts } from '../src/shared/protocol.ts'
import { EmptyState } from './components/EmptyState.tsx'
import { FixPromptsDialog } from './components/FixPromptsDialog.tsx'
import { Header } from './components/Header.tsx'
diff --git a/plugins/a11y/src/spa/components/EmptyState.stories.tsx b/plugins/a11y/app/components/EmptyState.stories.tsx
similarity index 100%
rename from plugins/a11y/src/spa/components/EmptyState.stories.tsx
rename to plugins/a11y/app/components/EmptyState.stories.tsx
diff --git a/plugins/a11y/src/spa/components/EmptyState.tsx b/plugins/a11y/app/components/EmptyState.tsx
similarity index 100%
rename from plugins/a11y/src/spa/components/EmptyState.tsx
rename to plugins/a11y/app/components/EmptyState.tsx
diff --git a/plugins/a11y/src/spa/components/FixPromptsDialog.stories.tsx b/plugins/a11y/app/components/FixPromptsDialog.stories.tsx
similarity index 100%
rename from plugins/a11y/src/spa/components/FixPromptsDialog.stories.tsx
rename to plugins/a11y/app/components/FixPromptsDialog.stories.tsx
diff --git a/plugins/a11y/src/spa/components/FixPromptsDialog.tsx b/plugins/a11y/app/components/FixPromptsDialog.tsx
similarity index 100%
rename from plugins/a11y/src/spa/components/FixPromptsDialog.tsx
rename to plugins/a11y/app/components/FixPromptsDialog.tsx
diff --git a/plugins/a11y/src/spa/components/Header.stories.tsx b/plugins/a11y/app/components/Header.stories.tsx
similarity index 100%
rename from plugins/a11y/src/spa/components/Header.stories.tsx
rename to plugins/a11y/app/components/Header.stories.tsx
diff --git a/plugins/a11y/src/spa/components/Header.tsx b/plugins/a11y/app/components/Header.tsx
similarity index 100%
rename from plugins/a11y/src/spa/components/Header.tsx
rename to plugins/a11y/app/components/Header.tsx
diff --git a/plugins/a11y/src/spa/components/MetaLine.stories.tsx b/plugins/a11y/app/components/MetaLine.stories.tsx
similarity index 100%
rename from plugins/a11y/src/spa/components/MetaLine.stories.tsx
rename to plugins/a11y/app/components/MetaLine.stories.tsx
diff --git a/plugins/a11y/src/spa/components/MetaLine.tsx b/plugins/a11y/app/components/MetaLine.tsx
similarity index 100%
rename from plugins/a11y/src/spa/components/MetaLine.tsx
rename to plugins/a11y/app/components/MetaLine.tsx
diff --git a/plugins/a11y/src/spa/components/RouteGroup.stories.tsx b/plugins/a11y/app/components/RouteGroup.stories.tsx
similarity index 100%
rename from plugins/a11y/src/spa/components/RouteGroup.stories.tsx
rename to plugins/a11y/app/components/RouteGroup.stories.tsx
diff --git a/plugins/a11y/src/spa/components/RouteGroup.tsx b/plugins/a11y/app/components/RouteGroup.tsx
similarity index 97%
rename from plugins/a11y/src/spa/components/RouteGroup.tsx
rename to plugins/a11y/app/components/RouteGroup.tsx
index c91c57f4c..c3c5c74f1 100644
--- a/plugins/a11y/src/spa/components/RouteGroup.tsx
+++ b/plugins/a11y/app/components/RouteGroup.tsx
@@ -1,4 +1,4 @@
-import type { ScanReport, Violation } from '../../shared/protocol.ts'
+import type { ScanReport, Violation } from '../../src/shared/protocol.ts'
import type { A11yChannel } from '../lib/channel.ts'
import type { SelectionApi } from '../lib/violation-view.ts'
import { For, Show } from 'solid-js'
diff --git a/plugins/a11y/src/spa/components/Summary.stories.tsx b/plugins/a11y/app/components/Summary.stories.tsx
similarity index 100%
rename from plugins/a11y/src/spa/components/Summary.stories.tsx
rename to plugins/a11y/app/components/Summary.stories.tsx
diff --git a/plugins/a11y/src/spa/components/Summary.tsx b/plugins/a11y/app/components/Summary.tsx
similarity index 97%
rename from plugins/a11y/src/spa/components/Summary.tsx
rename to plugins/a11y/app/components/Summary.tsx
index a04f233eb..50f6ea9fa 100644
--- a/plugins/a11y/src/spa/components/Summary.tsx
+++ b/plugins/a11y/app/components/Summary.tsx
@@ -1,4 +1,4 @@
-import type { Impact } from '../../shared/protocol.ts'
+import type { Impact } from '../../src/shared/protocol.ts'
import { For } from 'solid-js'
import { IMPACT_COLOR, IMPACT_LABEL } from '../lib/impact.ts'
diff --git a/plugins/a11y/src/spa/components/SummaryBar.stories.tsx b/plugins/a11y/app/components/SummaryBar.stories.tsx
similarity index 100%
rename from plugins/a11y/src/spa/components/SummaryBar.stories.tsx
rename to plugins/a11y/app/components/SummaryBar.stories.tsx
diff --git a/plugins/a11y/src/spa/components/SummaryBar.tsx b/plugins/a11y/app/components/SummaryBar.tsx
similarity index 98%
rename from plugins/a11y/src/spa/components/SummaryBar.tsx
rename to plugins/a11y/app/components/SummaryBar.tsx
index dc70547d6..81827c423 100644
--- a/plugins/a11y/src/spa/components/SummaryBar.tsx
+++ b/plugins/a11y/app/components/SummaryBar.tsx
@@ -1,4 +1,4 @@
-import type { Impact } from '../../shared/protocol.ts'
+import type { Impact } from '../../src/shared/protocol.ts'
import { Show } from 'solid-js'
import { Summary } from './Summary.tsx'
import { Switch } from './Switch.tsx'
diff --git a/plugins/a11y/src/spa/components/Switch.stories.tsx b/plugins/a11y/app/components/Switch.stories.tsx
similarity index 100%
rename from plugins/a11y/src/spa/components/Switch.stories.tsx
rename to plugins/a11y/app/components/Switch.stories.tsx
diff --git a/plugins/a11y/src/spa/components/Switch.tsx b/plugins/a11y/app/components/Switch.tsx
similarity index 100%
rename from plugins/a11y/src/spa/components/Switch.tsx
rename to plugins/a11y/app/components/Switch.tsx
diff --git a/plugins/a11y/src/spa/components/ViolationList.stories.tsx b/plugins/a11y/app/components/ViolationList.stories.tsx
similarity index 100%
rename from plugins/a11y/src/spa/components/ViolationList.stories.tsx
rename to plugins/a11y/app/components/ViolationList.stories.tsx
diff --git a/plugins/a11y/src/spa/components/ViolationList.tsx b/plugins/a11y/app/components/ViolationList.tsx
similarity index 100%
rename from plugins/a11y/src/spa/components/ViolationList.tsx
rename to plugins/a11y/app/components/ViolationList.tsx
diff --git a/plugins/a11y/src/spa/components/ViolationRow.stories.tsx b/plugins/a11y/app/components/ViolationRow.stories.tsx
similarity index 100%
rename from plugins/a11y/src/spa/components/ViolationRow.stories.tsx
rename to plugins/a11y/app/components/ViolationRow.stories.tsx
diff --git a/plugins/a11y/src/spa/components/ViolationRow.tsx b/plugins/a11y/app/components/ViolationRow.tsx
similarity index 98%
rename from plugins/a11y/src/spa/components/ViolationRow.tsx
rename to plugins/a11y/app/components/ViolationRow.tsx
index 5524c6cf9..de00674fd 100644
--- a/plugins/a11y/src/spa/components/ViolationRow.tsx
+++ b/plugins/a11y/app/components/ViolationRow.tsx
@@ -1,4 +1,4 @@
-import type { Violation } from '../../shared/protocol.ts'
+import type { Violation } from '../../src/shared/protocol.ts'
import type { A11yChannel } from '../lib/channel.ts'
import { For, Show } from 'solid-js'
import { IMPACT_COLOR, IMPACT_LABEL } from '../lib/impact.ts'
diff --git a/plugins/a11y/src/spa/components/_fixtures.ts b/plugins/a11y/app/components/_fixtures.ts
similarity index 93%
rename from plugins/a11y/src/spa/components/_fixtures.ts
rename to plugins/a11y/app/components/_fixtures.ts
index 3e36f67fd..54d30e7a7 100644
--- a/plugins/a11y/src/spa/components/_fixtures.ts
+++ b/plugins/a11y/app/components/_fixtures.ts
@@ -1,7 +1,7 @@
-import type { ScanReport, Violation } from '../../shared/protocol.ts'
+import type { ScanReport, Violation } from '../../src/shared/protocol.ts'
import type { A11yChannel } from '../lib/channel.ts'
import type { RouteGroupModel, SelectionApi } from '../lib/violation-view.ts'
-import { emptyCounts } from '../../shared/protocol.ts'
+import { emptyCounts } from '../../src/shared/protocol.ts'
function noop() {}
diff --git a/plugins/assets/src/spa/app/design.ts b/plugins/a11y/app/design.ts
similarity index 75%
rename from plugins/assets/src/spa/app/design.ts
rename to plugins/a11y/app/design.ts
index af3b99e68..0bf9debcd 100644
--- a/plugins/assets/src/spa/app/design.ts
+++ b/plugins/a11y/app/design.ts
@@ -1,3 +1,3 @@
// Re-exports the shared devframe class-helper builders (see
// `design/design.ts`) so this surface stays in lockstep with every other.
-export * from '../../../../../design/design'
+export * from '../../../design/design'
diff --git a/plugins/a11y/src/spa/index.html b/plugins/a11y/app/index.html
similarity index 100%
rename from plugins/a11y/src/spa/index.html
rename to plugins/a11y/app/index.html
diff --git a/plugins/a11y/src/spa/lib/channel.ts b/plugins/a11y/app/lib/channel.ts
similarity index 97%
rename from plugins/a11y/src/spa/lib/channel.ts
rename to plugins/a11y/app/lib/channel.ts
index cb0e92c07..7b416fe89 100644
--- a/plugins/a11y/src/spa/lib/channel.ts
+++ b/plugins/a11y/app/lib/channel.ts
@@ -1,8 +1,8 @@
import type { Accessor } from 'solid-js'
-import type { A11yChannelProtocol, A11yState, PageScriptConfig, PinTarget } from '../../shared/protocol.ts'
+import type { A11yChannelProtocol, A11yState, PageScriptConfig, PinTarget } from '../../src/shared/protocol.ts'
import { connectPanelChannel } from 'devframe/in-page-channel'
import { createSignal, onCleanup } from 'solid-js'
-import { A11Y_CHANNEL } from '../../shared/protocol.ts'
+import { A11Y_CHANNEL } from '../../src/shared/protocol.ts'
export interface A11yChannel {
/** Latest full route → report aggregate, or `null` until the page script reports in. */
diff --git a/plugins/a11y/src/spa/lib/devframe.ts b/plugins/a11y/app/lib/devframe.ts
similarity index 96%
rename from plugins/a11y/src/spa/lib/devframe.ts
rename to plugins/a11y/app/lib/devframe.ts
index 3a528ea62..2beacf3ea 100644
--- a/plugins/a11y/src/spa/lib/devframe.ts
+++ b/plugins/a11y/app/lib/devframe.ts
@@ -1,9 +1,9 @@
import type { DevframeConnectionStatus } from 'devframe/client'
import type { Accessor } from 'solid-js'
-import type { Impact, PageScriptConfig } from '../../shared/protocol.ts'
+import type { Impact, PageScriptConfig } from '../../src/shared/protocol.ts'
import { connectDevframe } from 'devframe/client'
import { createSignal } from 'solid-js'
-import { A11Y_DOCKS_ACTIVE_KEY } from '../../shared/protocol.ts'
+import { A11Y_DOCKS_ACTIVE_KEY } from '../../src/shared/protocol.ts'
interface ImpactMeta {
id: Impact
diff --git a/plugins/a11y/src/spa/lib/fix-prompt.ts b/plugins/a11y/app/lib/fix-prompt.ts
similarity index 97%
rename from plugins/a11y/src/spa/lib/fix-prompt.ts
rename to plugins/a11y/app/lib/fix-prompt.ts
index 0e439baa6..779010608 100644
--- a/plugins/a11y/src/spa/lib/fix-prompt.ts
+++ b/plugins/a11y/app/lib/fix-prompt.ts
@@ -1,4 +1,4 @@
-import type { Violation } from '../../shared/protocol.ts'
+import type { Violation } from '../../src/shared/protocol.ts'
/** One selected violation, with the route context it was found on. */
export interface SelectedItem {
diff --git a/plugins/a11y/src/spa/lib/impact.ts b/plugins/a11y/app/lib/impact.ts
similarity index 71%
rename from plugins/a11y/src/spa/lib/impact.ts
rename to plugins/a11y/app/lib/impact.ts
index bae18453f..6228d457c 100644
--- a/plugins/a11y/src/spa/lib/impact.ts
+++ b/plugins/a11y/app/lib/impact.ts
@@ -1,8 +1,8 @@
-import type { Impact } from '../../shared/protocol.ts'
+import type { Impact } from '../../src/shared/protocol.ts'
// The severity palette is shared with the page script, a single source in the
// protocol module so the panel and the highlight ring never drift.
-export { IMPACT_COLOR } from '../../shared/protocol.ts'
+export { IMPACT_COLOR } from '../../src/shared/protocol.ts'
export const IMPACT_LABEL: Record = {
critical: 'Critical',
diff --git a/plugins/a11y/src/spa/lib/violation-view.ts b/plugins/a11y/app/lib/violation-view.ts
similarity index 90%
rename from plugins/a11y/src/spa/lib/violation-view.ts
rename to plugins/a11y/app/lib/violation-view.ts
index ef13f482d..49ec1b8d6 100644
--- a/plugins/a11y/src/spa/lib/violation-view.ts
+++ b/plugins/a11y/app/lib/violation-view.ts
@@ -1,4 +1,4 @@
-import type { ScanReport, Violation } from '../../shared/protocol.ts'
+import type { ScanReport, Violation } from '../../src/shared/protocol.ts'
/** Controller the violation list uses to read/mutate the selection. */
export interface SelectionApi {
diff --git a/plugins/a11y/src/spa/main.tsx b/plugins/a11y/app/main.tsx
similarity index 100%
rename from plugins/a11y/src/spa/main.tsx
rename to plugins/a11y/app/main.tsx
diff --git a/plugins/a11y/src/spa/styles.css b/plugins/a11y/app/styles.css
similarity index 100%
rename from plugins/a11y/src/spa/styles.css
rename to plugins/a11y/app/styles.css
diff --git a/plugins/a11y/app/vite.config.ts b/plugins/a11y/app/vite.config.ts
new file mode 100644
index 000000000..e588e2bac
--- /dev/null
+++ b/plugins/a11y/app/vite.config.ts
@@ -0,0 +1,54 @@
+import type { Server } from 'node:http'
+import type { Plugin } from 'vite'
+import { fileURLToPath } from 'node:url'
+import createA11yDevframe from '@devframes/plugin-a11y'
+import { initDevframe } from 'devframe/initiate'
+import { resolveBasePath } from 'devframe/internal'
+import UnoCSS from 'unocss/vite'
+import { defineConfig } from 'vite'
+import solid from 'vite-plugin-solid'
+import { alias } from '../../../alias'
+
+/**
+ * Serve-only plugin that bridges the a11y node side (RPC + WebSocket +
+ * `__connection.json`) onto Vite's own dev server under the devframe base
+ * path, mounted as a **post** middleware so Vite serves the HMR SPA first and
+ * the devframe host only answers the routes it owns. Inert during `vite build`.
+ */
+function a11yDevBridge(): Plugin {
+ const devframe = createA11yDevframe()
+ return {
+ name: 'a11y-dev-bridge',
+ apply: 'serve',
+ configureServer(server) {
+ const instance = initDevframe(devframe, {
+ base: resolveBasePath(devframe, 'hosted'),
+ distDir: false,
+ server: server.httpServer as Server,
+ auth: false,
+ })
+ return () => server.middlewares.use(instance.nodeMiddleware)
+ },
+ }
+}
+
+/**
+ * `base: './'` for the build keeps the mount path portable: the same `app`
+ * output works whether devframe serves it at `/` (standalone) or
+ * `/__devframes_plugin_a11y/` (mounted in a hub). `connectDevframe` resolves
+ * its connection meta relative to `document.baseURI` to match.
+ *
+ * In `vite dev` the panel is served under the devframe base path so its
+ * `document.baseURI` matches production, and {@link a11yDevBridge} bridges the
+ * node side there with HMR.
+ */
+export default defineConfig(({ command }) => ({
+ base: command === 'serve' ? resolveBasePath(createA11yDevframe(), 'hosted') : './',
+ root: fileURLToPath(new URL('.', import.meta.url)),
+ resolve: { alias },
+ plugins: [solid(), UnoCSS(), a11yDevBridge()],
+ build: {
+ outDir: fileURLToPath(new URL('../assets-pkg/dist', import.meta.url)),
+ emptyOutDir: true,
+ },
+}))
diff --git a/plugins/a11y/bin.mjs b/plugins/a11y/bin.mjs
index 8e7267a04..fb3c9d931 100755
--- a/plugins/a11y/bin.mjs
+++ b/plugins/a11y/bin.mjs
@@ -1,6 +1,6 @@
#!/usr/bin/env node
import process from 'node:process'
-import { createA11yCli } from './dist/cli.mjs'
+import { createA11yCli } from './dist/node/cli.mjs'
async function main() {
const cli = createA11yCli()
diff --git a/plugins/a11y/package.json b/plugins/a11y/package.json
index 582b3ff85..fa80c33d6 100644
--- a/plugins/a11y/package.json
+++ b/plugins/a11y/package.json
@@ -22,13 +22,13 @@
],
"sideEffects": false,
"exports": {
- ".": "./dist/index.mjs",
- "./client": "./dist/client/index.mjs",
- "./cli": "./dist/cli.mjs",
- "./node": "./dist/node/index.mjs",
+ ".": "./dist/node/index.mjs",
+ "./client-script": "./dist/client-script/index.mjs",
+ "./cli": "./dist/node/cli.mjs",
+ "./node": "./dist/node/setup.mjs",
"./package.json": "./package.json"
},
- "types": "./dist/index.d.mts",
+ "types": "./dist/node/index.d.mts",
"bin": {
"devframes_plugin_a11y": "./bin.mjs"
},
@@ -37,16 +37,15 @@
"dist"
],
"scripts": {
- "build": "tsdown && vite build --config src/spa/vite.config.ts && vite build --config src/inject/vite.config.ts",
- "build:spa": "vite build --config src/spa/vite.config.ts",
- "build:inject": "vite build --config src/inject/vite.config.ts",
+ "build": "tsdown && vite build --config app/vite.config.ts",
+ "build:app": "vite build --config app/vite.config.ts",
"watch": "tsdown --watch",
- "dev": "node bin.mjs",
+ "dev": "vite --config app/vite.config.ts --host",
+ "play": "pnpm run build && node playgrounds/server.mjs",
+ "play:build": "pnpm run build && pnpm run cli:build && node playgrounds/server.mjs build",
"storybook": "storybook dev -p 6015",
"build-storybook": "storybook build",
"cli:build": "node bin.mjs build --out-dir dist/static",
- "demo": "node demo/server.mjs",
- "demo:build": "node demo/server.mjs build",
"typecheck": "tsc --noEmit",
"prepack": "turbo run build --filter=@devframes/plugin-a11y",
"test": "vitest run"
@@ -70,6 +69,7 @@
"devDependencies": {
"@antfu/design": "catalog:frontend",
"@devframes/plugin-a11y--assets": "workspace:*",
+ "@devframes/vite": "workspace:*",
"axe-core": "catalog:frontend",
"colorjs.io": "catalog:frontend",
"devframe": "workspace:*",
diff --git a/plugins/a11y/demo/index.html b/plugins/a11y/playgrounds/index.html
similarity index 99%
rename from plugins/a11y/demo/index.html
rename to plugins/a11y/playgrounds/index.html
index 5c0fda45d..8b05338db 100644
--- a/plugins/a11y/demo/index.html
+++ b/plugins/a11y/playgrounds/index.html
@@ -161,6 +161,6 @@ This week's roasts
-
+
diff --git a/plugins/a11y/demo/server.mjs b/plugins/a11y/playgrounds/server.mjs
similarity index 86%
rename from plugins/a11y/demo/server.mjs
rename to plugins/a11y/playgrounds/server.mjs
index 50d17da52..47d6b2863 100644
--- a/plugins/a11y/demo/server.mjs
+++ b/plugins/a11y/playgrounds/server.mjs
@@ -6,13 +6,13 @@
* panel (devtools iframe) can handshake their in-page channel:
*
* GET / → the demo page (intentional a11y bugs)
- * GET /__df-inject/inject.js → the page script bundle
+ * GET /__df-inject/index.mjs → the page script bundle
* GET /__devframes_plugin_a11y/** → the Solid panel SPA
*
* Two modes prove the plugin works either way:
*
- * node demo/server.mjs dev → live WebSocket RPC (`assets-pkg/dist`)
- * node demo/server.mjs build static → baked RPC dump, (`dist/static`)
+ * node playgrounds/server.mjs dev → live WebSocket RPC (`assets-pkg/dist`)
+ * node playgrounds/server.mjs build static → baked RPC dump, (`dist/static`)
*
* The scan/highlight loop is identical in both: it rides the in-page channel,
* not the devframe backend.
@@ -27,7 +27,7 @@ import { getPort } from 'devframe/utils/get-port'
import { mountStaticHandler } from 'devframe/utils/serve-static'
import { H3, toNodeHandler } from 'h3'
import { resolve } from 'pathe'
-import createA11yDevframe from '../src/index.ts'
+import createA11yDevframe from '../src/node/index.ts'
const HERE = fileURLToPath(new URL('.', import.meta.url))
const ROOT = resolve(HERE, '..')
@@ -35,12 +35,12 @@ const ROOT = resolve(HERE, '..')
const devframe = createA11yDevframe()
const mode = process.argv[2] === 'build' ? 'build' : 'dev'
const basePath = devframe.basePath
-const injectDir = resolve(ROOT, 'dist/inject')
+const injectDir = resolve(ROOT, 'dist/client-script')
const panelDir = mode === 'build' ? resolve(ROOT, 'dist/static') : resolve(ROOT, 'assets-pkg/dist')
function requireBuilt(file, hint) {
if (!existsSync(file)) {
- console.error(`\n[a11y-inspector demo] missing ${file}\n → run \`${hint}\` first.\n`)
+ console.error(`\n[a11y-inspector playground] missing ${file}\n → run \`${hint}\` first.\n`)
process.exit(1)
}
}
@@ -56,7 +56,7 @@ function banner(origin) {
}
async function main() {
- requireBuilt(resolve(injectDir, 'inject.js'), 'pnpm -C plugins/a11y build')
+ requireBuilt(resolve(injectDir, 'index.mjs'), 'pnpm -C plugins/a11y build')
requireBuilt(
resolve(panelDir, 'index.html'),
mode === 'build'
diff --git a/plugins/a11y/src/inject/index.ts b/plugins/a11y/src/client-script/index.ts
similarity index 100%
rename from plugins/a11y/src/inject/index.ts
rename to plugins/a11y/src/client-script/index.ts
diff --git a/plugins/a11y/src/inject/messages.ts b/plugins/a11y/src/client-script/messages.ts
similarity index 100%
rename from plugins/a11y/src/inject/messages.ts
rename to plugins/a11y/src/client-script/messages.ts
diff --git a/plugins/a11y/src/inject/overlay.ts b/plugins/a11y/src/client-script/overlay.ts
similarity index 100%
rename from plugins/a11y/src/inject/overlay.ts
rename to plugins/a11y/src/client-script/overlay.ts
diff --git a/plugins/a11y/src/inject/scanner.ts b/plugins/a11y/src/client-script/scanner.ts
similarity index 100%
rename from plugins/a11y/src/inject/scanner.ts
rename to plugins/a11y/src/client-script/scanner.ts
diff --git a/plugins/a11y/src/client/index.ts b/plugins/a11y/src/client/index.ts
deleted file mode 100644
index 38e7883a7..000000000
--- a/plugins/a11y/src/client/index.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import type { DevframeRpcClient, DevframeRpcClientOptions } from 'devframe/client'
-import { connectDevframe } from 'devframe/client'
-
-export type { DevframeRpcClient }
-export type { Impact, ScanReport, Violation, ViolationNode } from '../shared/protocol.ts'
-
-/**
- * Connect to the a11y inspector's devframe backend. A thin, typed wrapper
- * around devframe's {@link connectDevframe}; the panel derives its base from
- * `document.baseURI`, so no options are required in the common case. The
- * live scan/highlight loop itself rides the same-origin in-page channel,
- * independent of this connection.
- */
-export function connectA11y(options?: DevframeRpcClientOptions): Promise {
- return connectDevframe(options)
-}
diff --git a/plugins/a11y/src/client/shims.d.ts b/plugins/a11y/src/client/shims.d.ts
deleted file mode 100644
index 12545710f..000000000
--- a/plugins/a11y/src/client/shims.d.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-// Side-effect style imports used by the Solid SPA entry.
-declare module '*.css' {}
-declare module 'virtual:uno.css' {}
diff --git a/plugins/a11y/src/index.ts b/plugins/a11y/src/index.ts
deleted file mode 100644
index 38b1d4238..000000000
--- a/plugins/a11y/src/index.ts
+++ /dev/null
@@ -1,116 +0,0 @@
-import type { DevframeDefinition, RemoteAssets } from 'devframe'
-import { fileURLToPath } from 'node:url'
-import { defineDevframe } from 'devframe'
-import pkg from '../package.json' with { type: 'json' }
-import { setupA11y } from './node/index.ts'
-
-/** Default devframe id, driving the standalone CLI command and the hosted mount path `/__/`. */
-const DEFAULT_ID = 'devframes_plugin_a11y'
-const BASE_PATH = '/__devframes_plugin_a11y/'
-
-// The Solid panel SPA ships in the lockstep `@devframes/plugin-a11y--assets`
-// package, served on demand through devframe's remote-assets back-proxy. The
-// definition's `importMetaUrl` (below) supplies the default `resolveFrom`, so a
-// locally installed copy (a workspace link here) is served with zero network.
-// The page-script bundle (`dist/inject`, below) stays here.
-const distDir: RemoteAssets = {
- package: `${pkg.name}--assets`,
- version: pkg.version,
-}
-
-/**
- * Absolute path to the built **page script** module (`dist/inject/inject.js`),
- * the dock **client script** the client runtime imports into the host page to
- * scan it (its default export boots the page script; importing it does too).
- *
- * The definition already declares this as its dock `clientScript`, so a hub
- * serves it with no host wiring. Exported for hosts that mount the module
- * themselves (e.g. via `/@fs/` under Vite). Requires the built bundle
- * (`pnpm -C plugins/a11y build`).
- */
-export const a11yPageScriptBundlePath: string = fileURLToPath(new URL('../dist/inject/inject.js', import.meta.url))
-
-/** @deprecated Renamed; use {@link a11yPageScriptBundlePath}. */
-export const a11yAgentBundlePath: string = a11yPageScriptBundlePath
-
-export interface A11yDevframeOptions {
- /** Override the devframe id (and the default CLI command / mount path). */
- id?: string
- /** Override the display name shown in a host dock. */
- name?: string
- /** Override the dock icon. */
- icon?: string
- /**
- * Override the mount path. Defaults to `/__devframes_plugin_a11y/` so the
- * panel iframe shares an origin with the host page it scans.
- */
- basePath?: string
- /** Preferred standalone CLI port. */
- port?: number
- /**
- * Rescan on debounced user interaction (mouse/keyboard/touch), on top of the
- * DOM MutationObserver. Default `true`.
- */
- autoScan?: boolean
- /** Log newly-appeared violations to the browser console. Default `true`. */
- logIssues?: boolean
- /**
- * Auto-pin all of a route's violations the first time it's scanned.
- * Default `false`.
- */
- defaultHighlight?: boolean
- /** axe-core configuration. */
- axe?: {
- /** Rule tags to run (defaults to the broadened WCAG 2.0–2.2 + best-practice set). */
- tags?: string[]
- /** Extra axe `run` options merged over the defaults. */
- runOptions?: Record
- }
-}
-
-/**
- * Build a {@link DevframeDefinition} for the a11y inspector. The same
- * definition runs standalone (`/cli`, `/build`) and mounts into a host
- * (`/vite`, hub). The panel talks to the page script over the in-page channel
- * (`devframe/in-page-channel`), so the scan/highlight loop works identically in dev
- * (live WebSocket RPC) and in a baked static build.
- *
- * @experimental This plugin is experimental and may change without a major
- * version bump until it stabilizes.
- */
-export function createA11yDevframe(options: A11yDevframeOptions = {}): DevframeDefinition {
- const id = options.id ?? DEFAULT_ID
- return defineDevframe({
- id,
- name: options.name ?? 'A11y Inspector',
- version: pkg.version,
- packageName: pkg.name,
- importMetaUrl: import.meta.url,
- homepage: pkg.homepage,
- description: pkg.description,
- icon: options.icon ?? 'ph:person-simple-circle-duotone',
- basePath: options.basePath ?? BASE_PATH,
- /** Declare the page script by path; the hub serves it with no host wiring. */
- dock: {
- category: '~builtin',
- clientScript: { importFrom: a11yPageScriptBundlePath },
- },
- cli: {
- command: id,
- port: options.port ?? 9899,
- distDir,
- },
- setup(ctx) {
- setupA11y(ctx, {
- dockId: id,
- autoScan: options.autoScan,
- logIssues: options.logIssues,
- defaultHighlight: options.defaultHighlight,
- axe: options.axe,
- })
- },
- })
-}
-
-export default createA11yDevframe
-export type { Impact, ScanReport, Violation, ViolationNode } from './shared/protocol.ts'
diff --git a/plugins/a11y/src/inject/vite.config.ts b/plugins/a11y/src/inject/vite.config.ts
deleted file mode 100644
index 93a1ac922..000000000
--- a/plugins/a11y/src/inject/vite.config.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { fileURLToPath } from 'node:url'
-import { defineConfig } from 'vite'
-
-/**
- * Builds the page script into a single self-contained ES module
- * (`dist/inject/inject.js`) with axe-core bundled in. Loaded by the host app
- * via `