From b8ebb85d880380e03ee50ae437e6d42bc20b026e Mon Sep 17 00:00:00 2001 From: "Anthony Fu (via agent)" Date: Thu, 3 Sep 2026 02:19:41 +0000 Subject: [PATCH 1/7] refactor(plugin-a11y): restructure to app/ + src/{node,client-script} + playgrounds/ Establish the canonical built-in plugin layout on a11y: - app/ holds the framework SPA (was src/spa), built by Vite into the --assets package - src/node/ holds all node-side code (definition, setup, cli, rpc) - src/client-script/ holds both browser scripts (client runtime + page script), built by tsdown - playgrounds/ holds the same-origin playground host (was demo/) - dev serves the panel under its base path with HMR + a live node-side bridge - play builds then boots the playground against the built plugin - rename the ./client export subpath to ./client-script --- alias.ts | 10 +- knip.jsonc | 9 +- plugins/a11y/.storybook/main.ts | 2 +- plugins/a11y/.storybook/preview.tsx | 2 +- plugins/a11y/{src/spa => app}/app.tsx | 4 +- .../components/EmptyState.stories.tsx | 0 .../spa => app}/components/EmptyState.tsx | 0 .../components/FixPromptsDialog.stories.tsx | 0 .../components/FixPromptsDialog.tsx | 0 .../spa => app}/components/Header.stories.tsx | 0 .../{src/spa => app}/components/Header.tsx | 0 .../components/MetaLine.stories.tsx | 0 .../{src/spa => app}/components/MetaLine.tsx | 0 .../components/RouteGroup.stories.tsx | 0 .../spa => app}/components/RouteGroup.tsx | 2 +- .../components/Summary.stories.tsx | 0 .../{src/spa => app}/components/Summary.tsx | 2 +- .../components/SummaryBar.stories.tsx | 0 .../spa => app}/components/SummaryBar.tsx | 2 +- .../spa => app}/components/Switch.stories.tsx | 0 .../{src/spa => app}/components/Switch.tsx | 0 .../components/ViolationList.stories.tsx | 0 .../spa => app}/components/ViolationList.tsx | 0 .../components/ViolationRow.stories.tsx | 0 .../spa => app}/components/ViolationRow.tsx | 2 +- .../{src/spa => app}/components/_fixtures.ts | 4 +- plugins/a11y/{src/spa => app}/design.ts | 2 +- plugins/a11y/{src/spa => app}/index.html | 0 plugins/a11y/{src/spa => app}/lib/channel.ts | 4 +- plugins/a11y/{src/spa => app}/lib/devframe.ts | 4 +- .../a11y/{src/spa => app}/lib/fix-prompt.ts | 2 +- plugins/a11y/{src/spa => app}/lib/impact.ts | 4 +- .../{src/spa => app}/lib/violation-view.ts | 2 +- plugins/a11y/{src/spa => app}/main.tsx | 0 plugins/a11y/{src/spa => app}/styles.css | 0 plugins/a11y/app/vite.config.ts | 53 ++++++++ plugins/a11y/bin.mjs | 2 +- plugins/a11y/package.json | 23 ++-- plugins/a11y/{demo => playgrounds}/index.html | 2 +- plugins/a11y/{demo => playgrounds}/server.mjs | 14 +- .../src/{ => client-script}/client/index.ts | 2 +- .../src/{ => client-script}/client/shims.d.ts | 0 .../page-script}/index.ts | 4 +- .../page-script}/messages.ts | 4 +- .../page-script}/overlay.ts | 4 +- .../page-script}/scanner.ts | 4 +- plugins/a11y/src/index.ts | 116 ----------------- plugins/a11y/src/inject/vite.config.ts | 22 ---- plugins/a11y/src/{ => node}/cli.ts | 0 plugins/a11y/src/node/index.ts | 122 ++++++++++++++++-- .../{ => node}/rpc/functions/get-config.ts | 4 +- plugins/a11y/src/{ => node}/rpc/index.ts | 0 plugins/a11y/src/node/setup.ts | 16 +++ plugins/a11y/src/spa/vite.config.ts | 22 ---- plugins/a11y/tests/_utils.ts | 2 +- plugins/a11y/tests/fix-prompt.test.ts | 4 +- plugins/a11y/tests/inject-messages.test.ts | 4 +- plugins/a11y/tests/protocol.test.ts | 2 +- plugins/a11y/tests/static-build.test.ts | 2 +- plugins/a11y/tsconfig.json | 2 +- plugins/a11y/tsdown.config.ts | 54 +++++--- plugins/a11y/uno.config.ts | 2 +- pnpm-lock.yaml | 11 +- ...pshot.d.ts => client-script.snapshot.d.ts} | 2 +- ....snapshot.js => client-script.snapshot.js} | 2 +- .../client-script/page-script.snapshot.d.ts | 13 ++ .../client-script/page-script.snapshot.js | 7 + .../plugin-a11y/index.snapshot.d.ts | 35 +---- .../@devframes/plugin-a11y/index.snapshot.js | 16 +-- tsconfig.base.json | 16 +-- 70 files changed, 335 insertions(+), 310 deletions(-) rename plugins/a11y/{src/spa => app}/app.tsx (99%) rename plugins/a11y/{src/spa => app}/components/EmptyState.stories.tsx (100%) rename plugins/a11y/{src/spa => app}/components/EmptyState.tsx (100%) rename plugins/a11y/{src/spa => app}/components/FixPromptsDialog.stories.tsx (100%) rename plugins/a11y/{src/spa => app}/components/FixPromptsDialog.tsx (100%) rename plugins/a11y/{src/spa => app}/components/Header.stories.tsx (100%) rename plugins/a11y/{src/spa => app}/components/Header.tsx (100%) rename plugins/a11y/{src/spa => app}/components/MetaLine.stories.tsx (100%) rename plugins/a11y/{src/spa => app}/components/MetaLine.tsx (100%) rename plugins/a11y/{src/spa => app}/components/RouteGroup.stories.tsx (100%) rename plugins/a11y/{src/spa => app}/components/RouteGroup.tsx (97%) rename plugins/a11y/{src/spa => app}/components/Summary.stories.tsx (100%) rename plugins/a11y/{src/spa => app}/components/Summary.tsx (97%) rename plugins/a11y/{src/spa => app}/components/SummaryBar.stories.tsx (100%) rename plugins/a11y/{src/spa => app}/components/SummaryBar.tsx (98%) rename plugins/a11y/{src/spa => app}/components/Switch.stories.tsx (100%) rename plugins/a11y/{src/spa => app}/components/Switch.tsx (100%) rename plugins/a11y/{src/spa => app}/components/ViolationList.stories.tsx (100%) rename plugins/a11y/{src/spa => app}/components/ViolationList.tsx (100%) rename plugins/a11y/{src/spa => app}/components/ViolationRow.stories.tsx (100%) rename plugins/a11y/{src/spa => app}/components/ViolationRow.tsx (98%) rename plugins/a11y/{src/spa => app}/components/_fixtures.ts (93%) rename plugins/a11y/{src/spa => app}/design.ts (76%) rename plugins/a11y/{src/spa => app}/index.html (100%) rename plugins/a11y/{src/spa => app}/lib/channel.ts (97%) rename plugins/a11y/{src/spa => app}/lib/devframe.ts (96%) rename plugins/a11y/{src/spa => app}/lib/fix-prompt.ts (97%) rename plugins/a11y/{src/spa => app}/lib/impact.ts (71%) rename plugins/a11y/{src/spa => app}/lib/violation-view.ts (90%) rename plugins/a11y/{src/spa => app}/main.tsx (100%) rename plugins/a11y/{src/spa => app}/styles.css (100%) create mode 100644 plugins/a11y/app/vite.config.ts rename plugins/a11y/{demo => playgrounds}/index.html (99%) rename plugins/a11y/{demo => playgrounds}/server.mjs (86%) rename plugins/a11y/src/{ => client-script}/client/index.ts (96%) rename plugins/a11y/src/{ => client-script}/client/shims.d.ts (100%) rename plugins/a11y/src/{inject => client-script/page-script}/index.ts (99%) rename plugins/a11y/src/{inject => client-script/page-script}/messages.ts (98%) rename plugins/a11y/src/{inject => client-script/page-script}/overlay.ts (98%) rename plugins/a11y/src/{inject => client-script/page-script}/scanner.ts (96%) delete mode 100644 plugins/a11y/src/index.ts delete mode 100644 plugins/a11y/src/inject/vite.config.ts rename plugins/a11y/src/{ => node}/cli.ts (100%) rename plugins/a11y/src/{ => node}/rpc/functions/get-config.ts (95%) rename plugins/a11y/src/{ => node}/rpc/index.ts (100%) create mode 100644 plugins/a11y/src/node/setup.ts delete mode 100644 plugins/a11y/src/spa/vite.config.ts rename tests/__snapshots__/tsnapi/@devframes/plugin-a11y/{client.snapshot.d.ts => client-script.snapshot.d.ts} (93%) rename tests/__snapshots__/tsnapi/@devframes/plugin-a11y/{client.snapshot.js => client-script.snapshot.js} (84%) create mode 100644 tests/__snapshots__/tsnapi/@devframes/plugin-a11y/client-script/page-script.snapshot.d.ts create mode 100644 tests/__snapshots__/tsnapi/@devframes/plugin-a11y/client-script/page-script.snapshot.js diff --git a/alias.ts b/alias.ts index 08db230d3..a55f94120 100644 --- a/alias.ts +++ b/alias.ts @@ -117,11 +117,11 @@ export const alias = { '@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-a11y/client-script/page-script': p('a11y/src/client-script/page-script/index.ts'), + '@devframes/plugin-a11y/client-script': p('a11y/src/client-script/client/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/src/client/index.ts'), '@devframes/plugin-messages/node': p('messages/src/node/index.ts'), '@devframes/plugin-messages/constants': p('messages/src/constants.ts'), diff --git a/knip.jsonc b/knip.jsonc index 92d0782e2..3fd1b29e0 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -269,11 +269,12 @@ // `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/{client,page-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 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/a11y/src/spa/design.ts b/plugins/a11y/app/design.ts similarity index 76% rename from plugins/a11y/src/spa/design.ts rename to plugins/a11y/app/design.ts index d8e49c73a..0bf9debcd 100644 --- a/plugins/a11y/src/spa/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..1e8c47994 --- /dev/null +++ b/plugins/a11y/app/vite.config.ts @@ -0,0 +1,53 @@ +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 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: devframe.basePath, + 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' ? createA11yDevframe().basePath : './', + 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..bfc4fafc7 100644 --- a/plugins/a11y/package.json +++ b/plugins/a11y/package.json @@ -22,13 +22,14 @@ ], "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/client/index.mjs", + "./client-script/page-script": "./dist/client-script/page-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 +38,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 +70,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..4406505d2 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/page-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/client/index.ts b/plugins/a11y/src/client-script/client/index.ts similarity index 96% rename from plugins/a11y/src/client/index.ts rename to plugins/a11y/src/client-script/client/index.ts index 38e7883a7..907d2589a 100644 --- a/plugins/a11y/src/client/index.ts +++ b/plugins/a11y/src/client-script/client/index.ts @@ -2,7 +2,7 @@ import type { DevframeRpcClient, DevframeRpcClientOptions } from 'devframe/clien import { connectDevframe } from 'devframe/client' export type { DevframeRpcClient } -export type { Impact, ScanReport, Violation, ViolationNode } from '../shared/protocol.ts' +export type { Impact, ScanReport, Violation, ViolationNode } from '../../shared/protocol.ts' /** * Connect to the a11y inspector's devframe backend. A thin, typed wrapper diff --git a/plugins/a11y/src/client/shims.d.ts b/plugins/a11y/src/client-script/client/shims.d.ts similarity index 100% rename from plugins/a11y/src/client/shims.d.ts rename to plugins/a11y/src/client-script/client/shims.d.ts diff --git a/plugins/a11y/src/inject/index.ts b/plugins/a11y/src/client-script/page-script/index.ts similarity index 99% rename from plugins/a11y/src/inject/index.ts rename to plugins/a11y/src/client-script/page-script/index.ts index a639486a9..5b6874267 100644 --- a/plugins/a11y/src/inject/index.ts +++ b/plugins/a11y/src/client-script/page-script/index.ts @@ -15,7 +15,7 @@ * export receives the hub's client-script context and additionally mirrors * each scan into the hub's messages feed. */ -import type { A11yChannelProtocol, PageScriptConfig, PinTarget, ScanReport } from '../shared/protocol.ts' +import type { A11yChannelProtocol, PageScriptConfig, PinTarget, ScanReport } from '../../shared/protocol.ts' import type { A11yPageScriptContext } from './messages.ts' import type { PinInfo } from './overlay.ts' import { createPageScriptChannel } from 'devframe/in-page-channel' @@ -24,7 +24,7 @@ import { A11Y_DEFAULT_DOCK_ID, A11Y_NODE_ATTR, A11Y_STORAGE_KEY, -} from '../shared/protocol.ts' +} from '../../shared/protocol.ts' import { createMessagesReporter } from './messages.ts' import { createOverlay } from './overlay.ts' import { resolveElement, scan } from './scanner.ts' diff --git a/plugins/a11y/src/inject/messages.ts b/plugins/a11y/src/client-script/page-script/messages.ts similarity index 98% rename from plugins/a11y/src/inject/messages.ts rename to plugins/a11y/src/client-script/page-script/messages.ts index 2703069e0..bbc3fe86f 100644 --- a/plugins/a11y/src/inject/messages.ts +++ b/plugins/a11y/src/client-script/page-script/messages.ts @@ -9,8 +9,8 @@ * simply has no messages client and skips the feed. Mirrors the pattern used * by the terminals and code-server plugins for `ctx.terminals`. */ -import type { Impact, ScanReport } from '../shared/protocol.ts' -import { A11Y_DEFAULT_DOCK_ID } from '../shared/protocol.ts' +import type { Impact, ScanReport } from '../../shared/protocol.ts' +import { A11Y_DEFAULT_DOCK_ID } from '../../shared/protocol.ts' /** * Structural slice of the hub's `DevframeMessageAction` the page script emits: a diff --git a/plugins/a11y/src/inject/overlay.ts b/plugins/a11y/src/client-script/page-script/overlay.ts similarity index 98% rename from plugins/a11y/src/inject/overlay.ts rename to plugins/a11y/src/client-script/page-script/overlay.ts index 777b48ae6..4482ca579 100644 --- a/plugins/a11y/src/inject/overlay.ts +++ b/plugins/a11y/src/client-script/page-script/overlay.ts @@ -1,5 +1,5 @@ -import type { Impact } from '../shared/protocol.ts' -import { IMPACT_COLOR } from '../shared/protocol.ts' +import type { Impact } from '../../shared/protocol.ts' +import { IMPACT_COLOR } from '../../shared/protocol.ts' const PREFERS_REDUCED_MOTION = typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches diff --git a/plugins/a11y/src/inject/scanner.ts b/plugins/a11y/src/client-script/page-script/scanner.ts similarity index 96% rename from plugins/a11y/src/inject/scanner.ts rename to plugins/a11y/src/client-script/page-script/scanner.ts index a7a68c31d..84ad6aea9 100644 --- a/plugins/a11y/src/inject/scanner.ts +++ b/plugins/a11y/src/client-script/page-script/scanner.ts @@ -1,6 +1,6 @@ -import type { ScanReport, Violation, ViolationNode } from '../shared/protocol.ts' +import type { ScanReport, Violation, ViolationNode } from '../../shared/protocol.ts' import axe from 'axe-core' -import { A11Y_NODE_ATTR, DEFAULT_AXE_TAGS, emptyCounts, IMPACT_ORDER } from '../shared/protocol.ts' +import { A11Y_NODE_ATTR, DEFAULT_AXE_TAGS, emptyCounts, IMPACT_ORDER } from '../../shared/protocol.ts' const IMPACTS = new Set(IMPACT_ORDER) let counter = 0 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 `