Skip to content

Commit 1221ae0

Browse files
committed
Merge origin/main into cuddly-emus-lose
Resolve dependency conflicts in plugins/inspect and plugins/messages package.json: keep main's #238 layout (which moved @devframes/vite to devDependencies) and drop the now-unused nostics dependency, since these plugins reach nostics through devframe/utils/nostics.
2 parents d88cf5c + 3e6ef47 commit 1221ae0

150 files changed

Lines changed: 626 additions & 601 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎AGENTS.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,13 @@ Ahead-of-time build artifacts that live under `src/` - the shadow-root styleshee
4545
- Shared state via `devframe/utils/shared-state`; keep values serializable.
4646
- Utility imports use the package-path form `devframe/utils/*`, never relative `../utils/*`.
4747
- Dependencies go through the pnpm catalogs in `pnpm-workspace.yaml` (`cli`, `inlined`, `testing`, `types`) - add to a catalog and reference as `catalog:<name>`, don't pin versions in `package.json`.
48+
- **A plugin's default export is its `create<X>Devframe` factory, never a pre-built instance.** Don't write `const xDevframe = createXDevframe(); export default xDevframe` (or the inline `export default createXDevframe()` equivalent) - that eagerly constructs a `DevframeDefinition` the moment the module loads, at import time, whether or not any consumer wants that exact zero-config shape; a host that needs its own options (an id override, a data directory, …) ends up paying for a second, discarded instance alongside the one it actually uses. Alias the factory itself as the default export instead - `export default createXDevframe` - so importing the module costs nothing beyond defining the function, and every consumer calls it (with or without options) to get their own instance: `import createA11yDevframe from '@devframes/plugin-a11y'` then `createA11yDevframe(options)`.
4849

4950
### Framework adapter packages: two scopes, one shape
5051

5152
The framework adapter packages - `@devframes/vite`, `@devframes/nuxt`, `@devframes/next` - each split their surface into **two clearly-scoped subpaths**, because a consumer is always doing one of two distinct jobs. Keep all three parallel:
5253

53-
- **`.../dev-spa`** - **build & dev-serve a single devframe's SPA** with that tool (the "I'm authoring one devframe" scope). Vite: the `devframeVitePlugin` / `devframeViteBridge` / `devframeVite` plugins. Next: `withDevframe` + `createDevframeNextHandler`, with its React client at `.../dev-spa/client`. Nuxt: the Nuxt module (registered as `modules: ['@devframes/nuxt/dev-spa']`).
54+
- **`.../single`** - **build & dev-serve a single devframe's SPA** with that tool (the "I'm authoring one devframe" scope). Vite: the `devframeVitePlugin` / `devframeViteBridge` / `devframeVite` plugins. Next: `withDevframe` + `createDevframeNextHandler`, with its React client at `.../single/client`. Nuxt: the Nuxt module (registered as `modules: ['@devframes/nuxt/single']`).
5455
- **`.../hub`** - **mount a whole `@devframes/hub` (many integrations) inside that tool** (the "I'm standing up devtools" scope). Wraps `initHub`, defaults the UI slot to `@devframes/hub-ui`'s `createUi()` (overridable via `ui`, or `ui: false` for headless), and ships a browser client helper at `.../hub/client` (a thin, lifecycle-managing wrapper over `@devframes/hub/client`'s `createDevframeClientHost`). `@devframes/hub` and `@devframes/hub-ui` are **optional peers** of these packages; `hub-ui` is loaded lazily (a bundler-ignored dynamic `import()` in the Next hub) so it stays optional and its `import.meta.url` asset lookups resolve at request time.
5556
- **The bare root (`.`) throws** a helpful error pointing at the two subpaths - never put real code on it.
5657
- **Vite and Nuxt already have native hub viewers** (`@vitejs/devtools-kit`, `@nuxt/devtools`), so `@devframes/vite/hub` and `@devframes/nuxt/hub` still work but emit a one-time `console.warn` recommending those (silence with `{ quiet: true }`). `@devframes/next/hub` has no native counterpart, so it warns nothing.

‎alias.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,16 @@ export const alias = {
5252
'@devframes/hub': r('hub/src/index.ts'),
5353
'@devframes/hub-ui': r('hub-ui/src/index.ts'),
5454
'@devframes/nuxt/runtime/plugin.client': r('nuxt/src/runtime/plugin.client.ts'),
55-
'@devframes/nuxt/dev-spa': r('nuxt/src/dev-spa.ts'),
55+
'@devframes/nuxt/single': r('nuxt/src/single.ts'),
5656
'@devframes/nuxt/hub/client': r('nuxt/src/hub-client.ts'),
5757
'@devframes/nuxt/hub': r('nuxt/src/hub.ts'),
5858
'@devframes/nuxt': r('nuxt/src/index.ts'),
59-
'@devframes/next/dev-spa/client': r('next/src/client.tsx'),
60-
'@devframes/next/dev-spa': r('next/src/dev-spa.ts'),
59+
'@devframes/next/single/client': r('next/src/client.tsx'),
60+
'@devframes/next/single': r('next/src/single.ts'),
6161
'@devframes/next/hub/client': r('next/src/hub-client.tsx'),
6262
'@devframes/next/hub': r('next/src/hub.ts'),
6363
'@devframes/next': r('next/src/index.ts'),
64-
'@devframes/vite/dev-spa': r('vite/src/dev-spa.ts'),
64+
'@devframes/vite/single': r('vite/src/single.ts'),
6565
'@devframes/vite/hub/client': r('vite/src/hub-client.ts'),
6666
'@devframes/vite/hub': r('vite/src/hub.ts'),
6767
'@devframes/vite': r('vite/src/index.ts'),

‎docs/.vitepress/config.ts‎

Lines changed: 18 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,15 @@ function guideGroups(prefix: string) {
2323
items: [
2424
{ text: 'Introduction', link: `${prefix}/guide/` },
2525
{ text: 'Devframe Definition', link: `${prefix}/guide/devframe-definition` },
26-
{ text: 'Scoped Context', link: `${prefix}/guide/scoped-context` },
27-
{ text: 'Cross-Plugin Services', link: `${prefix}/guide/services` },
2826
{ text: 'RPC', link: `${prefix}/guide/rpc` },
2927
{ text: 'Shared State', link: `${prefix}/guide/shared-state` },
30-
{ text: 'Streaming', link: `${prefix}/guide/streaming` },
31-
{ text: 'When Clauses', link: `${prefix}/guide/when-clauses` },
28+
{ text: 'Client Assets', link: `${prefix}/guide/client-assets` },
3229
{ text: 'Structured Diagnostics', link: `${prefix}/guide/diagnostics` },
30+
{ text: 'Agent-Native', link: `${prefix}/guide/agent-native` },
31+
{ text: 'JSON-Render', link: `${prefix}/guide/json-render` },
32+
{ text: 'Streaming', link: `${prefix}/guide/streaming` },
33+
{ text: 'Scoped Context', link: `${prefix}/guide/scoped-context` },
34+
{ text: 'Standalone CLI', link: `${prefix}/guide/standalone-cli` },
3335
],
3436
},
3537
{
@@ -38,51 +40,41 @@ function guideGroups(prefix: string) {
3840
{ text: 'Client', link: `${prefix}/guide/client` },
3941
{ text: 'Transports', link: `${prefix}/guide/transports` },
4042
{ text: 'Security', link: `${prefix}/guide/security` },
41-
{ text: 'Deep Linking', link: `${prefix}/guide/deep-linking` },
42-
],
43-
},
44-
{
45-
text: 'JSON-Render',
46-
items: [
47-
{ text: 'JSON-Render', link: `${prefix}/guide/json-render` },
48-
{ text: 'Build Your Own JSON-Render Frontend', link: `${prefix}/guide/build-your-own-json-render-frontend` },
4943
],
5044
},
5145
{
5246
text: 'Hub',
5347
items: [
5448
{ text: 'Hub', link: `${prefix}/guide/hub` },
55-
{ text: 'Serve a Hub Anywhere', link: `${prefix}/guide/hub-initiate` },
5649
{ text: 'Client Scripts & Context', link: `${prefix}/guide/client-context` },
57-
{ text: 'Build Your Own Hub UI', link: `${prefix}/guide/build-your-own-hub-ui` },
50+
{ text: 'Serve a Hub Anywhere', link: `${prefix}/guide/hub-initiate` },
51+
{ text: 'Cross-Plugin Services', link: `${prefix}/guide/services` },
52+
{ text: 'Deep Linking', link: `${prefix}/guide/deep-linking` },
5853
],
5954
},
6055
{
61-
text: 'Agent-Native',
56+
text: 'Customization',
6257
items: [
63-
{ text: 'Agent-Native', link: `${prefix}/guide/agent-native` },
58+
{ text: 'Build Your Own JSON-Render Frontend', link: `${prefix}/guide/build-your-own-json-render-frontend` },
59+
{ text: 'Build Your Own Hub UI', link: `${prefix}/guide/build-your-own-hub-ui` },
6460
],
6561
},
6662
{
67-
text: 'Recipes',
63+
text: 'References',
6864
items: [
69-
{ text: 'Standalone CLI', link: `${prefix}/guide/standalone-cli` },
65+
{ text: 'When Clauses', link: `${prefix}/guide/when-clauses` },
66+
{ text: 'Examples', link: `${prefix}/examples/` },
7067
],
7168
},
7269
] satisfies { text: string, items: DefaultTheme.NavItemWithLink[] }[]
7370
}
7471

75-
/** Flattened guide list — used by the top nav dropdown, which renders one level. */
76-
function guideItems(prefix: string) {
77-
return guideGroups(prefix).flatMap(group => group.items) satisfies DefaultTheme.NavItemWithLink[]
78-
}
79-
8072
function adaptersItems(prefix: string) {
8173
return [
8274
{ text: 'Overview', link: `${prefix}/adapters/` },
83-
{ text: 'CLI (cac)', link: `${prefix}/adapters/cac` },
84-
{ text: 'Dev', link: `${prefix}/adapters/dev` },
8575
{ text: 'Initiate (middleware)', link: `${prefix}/adapters/initiate` },
76+
{ text: 'Dev', link: `${prefix}/adapters/dev` },
77+
{ text: 'CLI', link: `${prefix}/adapters/cac` },
8678
{ text: 'Build', link: `${prefix}/adapters/build` },
8779
{ text: 'Vite DevTools', link: `${prefix}/adapters/vite` },
8880
{ text: 'Embedded', link: `${prefix}/adapters/embedded` },
@@ -174,10 +166,7 @@ export function devframeNav(prefix = ''): DefaultTheme.NavItem[] {
174166
return [
175167
{
176168
text: 'Guide',
177-
items: [
178-
...guideItems(prefix),
179-
{ text: 'Examples', link: `${prefix}/examples/` },
180-
],
169+
items: guideGroups(prefix),
181170
},
182171
{
183172
text: 'Adapters',

‎docs/.vitepress/theme/style.css‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,13 @@
4141
padding-bottom: 1rem;
4242
font-style: italic;
4343
}
44+
45+
.VPMenuGroup > .title {
46+
opacity: 0.5;
47+
}
48+
49+
li:not(:first-child) > .VPMenuGroup > .title {
50+
margin-top: 0.5rem;
51+
padding-top: 0.5rem;
52+
border-top: 1px solid var(--vp-c-divider);
53+
}

‎docs/adapters/build.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ await createBuild(devframe, {
2323
| Option | Default | Description |
2424
|--------|---------|-------------|
2525
| `outDir` | `dist-static` | Output directory. Cleared on each build. |
26-
| `distDir` | `def.cli?.distDir` | Override the SPA dist directory. |
26+
| `distDir` | `def.cli?.distDir` | Override the SPA dist directory (a local path or a [remote assets](/guide/client-assets) package, materialized in full at build time). |
2727
| `pretty` | `false` | Pretty-print dump JSON (larger on disk). |
2828

2929
The resulting directory hosts on any static web server (`serve`, nginx, GitHub Pages, …). The client auto-detects `static` mode by resolving `./__connection.json` against `document.baseURI` and runs in read-only form.

‎docs/frameworks/index.md‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,20 @@ The framework packages — [`@devframes/vite`](./vite), [`@devframes/nuxt`](./nu
88

99
| Scope | Subpath | You are… |
1010
|-------|---------|----------|
11-
| **dev-spa** | `.../dev-spa` | building & dev-serving a **single devframe's SPA** with that tool |
11+
| **single** | `.../single` | building & dev-serving a **single devframe's SPA** with that tool |
1212
| **hub** | `.../hub` | mounting a whole **[devframes-hub](/guide/hub)** (many integrations) inside that tool |
1313

1414
The bare package root (`@devframes/vite`, `@devframes/nuxt`, `@devframes/next`) has no export — it throws with a pointer to the two subpaths, so an accidental bare import fails loudly instead of resolving to nothing.
1515

16-
| Package | dev-spa | hub |
17-
|---------|---------|-----|
16+
| Package | single | hub |
17+
|---------|--------|-----|
1818
| [`@devframes/vite`](./vite) | `devframeVitePlugin` / `devframeViteBridge` / `devframeVite` | `viteDevframeHub` (+ `/hub/client`) |
19-
| [`@devframes/nuxt`](./nuxt) | the Nuxt module (`modules: ['@devframes/nuxt/dev-spa']`) | the hub Nuxt module (+ `/hub/client`) |
20-
| [`@devframes/next`](./next) | `withDevframe` + `createDevframeNextHandler` (+ `/dev-spa/client`) | `nextDevframeHub` (+ `/hub/client`) |
19+
| [`@devframes/nuxt`](./nuxt) | the Nuxt module (`modules: ['@devframes/nuxt/single']`) | the hub Nuxt module (+ `/hub/client`) |
20+
| [`@devframes/next`](./next) | `withDevframe` + `createDevframeNextHandler` (+ `/single/client`) | `nextDevframeHub` (+ `/hub/client`) |
2121

22-
## dev-spa: author one devframe
22+
## single: author one devframe
2323

24-
The `dev-spa` scope is for when the thing you're building **is** a devframe — you author its UI with Vite/Nuxt/Next and want its RPC backend running during development. See each package's page for the details; for the framework-neutral CLI/build/embedded outputs, reach for the [adapters](/adapters/) instead.
24+
The `single` scope is for when the thing you're building **is** a devframe — you author its UI with Vite/Nuxt/Next and want its RPC backend running during development. See each package's page for the details; for the framework-neutral CLI/build/embedded outputs, reach for the [adapters](/adapters/) instead.
2525

2626
## hub: mount a devframes-hub
2727

‎docs/frameworks/next.md‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,19 @@ outline: deep
99
1010
`@devframes/next` hosts devframes from a Next.js App Router app. Next runs on webpack/Turbopack rather than Vite, so it hosts through a route handler instead of the [Vite](./vite): the package serves each devframe's SPA and its `__connection.json` from a single `fetch` handler your catch-all route delegates to, reusing devframe's own [`serveStaticHandler`](/adapters/dev) for SPA fallback, content types, and path-traversal guarding.
1111

12-
`@devframes/next` splits into two scopes: `@devframes/next/dev-spa` (author one devframe with Next) and [`@devframes/next/hub`](#mounting-a-hub) (mount a whole devframes-hub). The bare `@devframes/next` import throws with a pointer to both.
12+
`@devframes/next` splits into two scopes: `@devframes/next/single` (author one devframe with Next) and [`@devframes/next/hub`](#mounting-a-hub) (mount a whole devframes-hub). The bare `@devframes/next` import throws with a pointer to both.
1313

14-
The `dev-spa` scope comes in two parts:
14+
The `single` scope comes in two parts:
1515

1616
1. **`withDevframe()`** — applies the one Next config setting a devframe host needs.
1717
2. **`createDevframeNextHandler()`** — hosts a single devframe (the common case).
1818

19-
Plus a React client surface at `@devframes/next/dev-spa/client`.
19+
Plus a React client surface at `@devframes/next/single/client`.
2020

2121
## Config
2222

2323
```ts [next.config.mjs]
24-
import { withDevframe } from '@devframes/next/dev-spa'
24+
import { withDevframe } from '@devframes/next/single'
2525

2626
export default withDevframe({
2727
// ...your own Next config
@@ -35,7 +35,7 @@ export default withDevframe({
3535
`createDevframeNextHandler(definition)` statically serves the devframe's built SPA and starts a side-car RPC/WebSocket server, advertising it at `<base>/__connection.json`. Delegate your catch-all route to its `fetch`:
3636

3737
```ts [app/__my-tool/[[...path]]/route.ts]
38-
import { createDevframeNextHandler } from '@devframes/next/dev-spa'
38+
import { createDevframeNextHandler } from '@devframes/next/single'
3939
import myDevframe from '@/devframe'
4040

4141
export const runtime = 'nodejs'
@@ -88,11 +88,11 @@ export async function GET(request: Request): Promise<Response> {
8888

8989
## React client
9090

91-
`@devframes/next/dev-spa/client` connects to the RPC backend and provides the client to your component tree — the React counterpart to `@devframes/nuxt`'s `$rpc` plugin. Children render immediately, so your shell and a connection indicator stay visible while the client connects.
91+
`@devframes/next/single/client` connects to the RPC backend and provides the client to your component tree — the React counterpart to `@devframes/nuxt`'s `$rpc` plugin. Children render immediately, so your shell and a connection indicator stay visible while the client connects.
9292

9393
```tsx [app/providers.tsx]
9494
'use client'
95-
import { RpcProvider } from '@devframes/next/dev-spa/client'
95+
import { RpcProvider } from '@devframes/next/single/client'
9696

9797
export function Providers({ children }: { children: React.ReactNode }) {
9898
return <RpcProvider baseURL="/__my-tool/">{children}</RpcProvider>
@@ -103,7 +103,7 @@ export function Providers({ children }: { children: React.ReactNode }) {
103103

104104
```tsx [app/panel.tsx]
105105
'use client'
106-
import { useRpc, useRpcStatus } from '@devframes/next/dev-spa/client'
106+
import { useRpc, useRpcStatus } from '@devframes/next/single/client'
107107

108108
export function Panel() {
109109
const rpc = useRpc()?.scope('my-tool:')

‎docs/frameworks/nuxt.md‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ outline: deep
44

55
# Nuxt
66

7-
The `@devframes/nuxt/dev-spa` module wires a Nuxt-built SPA as a devframe client, and optionally serves the dev-time RPC bridge alongside `nuxt dev`. It runs inside the Nuxt app that consumes your devframe.
7+
The `@devframes/nuxt/single` module wires a Nuxt-built SPA as a devframe client, and optionally serves the dev-time RPC bridge alongside `nuxt dev`. It runs inside the Nuxt app that consumes your devframe.
88

9-
`@devframes/nuxt` splits into two scopes: `@devframes/nuxt/dev-spa` (this page — author one devframe with Nuxt) and [`@devframes/nuxt/hub`](#mounting-a-hub) (mount a whole devframes-hub). The bare `@devframes/nuxt` import throws with a pointer to both.
9+
`@devframes/nuxt` splits into two scopes: `@devframes/nuxt/single` (this page — author one devframe with Nuxt) and [`@devframes/nuxt/hub`](#mounting-a-hub) (mount a whole devframes-hub). The bare `@devframes/nuxt` import throws with a pointer to both.
1010

1111
It handles the four things every Nuxt-powered standalone devtool needs:
1212

@@ -19,7 +19,7 @@ It handles the four things every Nuxt-powered standalone devtool needs:
1919

2020
```ts [nuxt.config.ts]
2121
export default defineNuxtConfig({
22-
modules: ['@devframes/nuxt/dev-spa'],
22+
modules: ['@devframes/nuxt/single'],
2323
})
2424
```
2525

@@ -47,7 +47,7 @@ export function usePayload() {
4747

4848
```ts [nuxt.config.ts]
4949
export default defineNuxtConfig({
50-
modules: ['@devframes/nuxt/dev-spa'],
50+
modules: ['@devframes/nuxt/single'],
5151
devframe: {
5252
baseURL: './', // where the devframe snapshot lives, relative to the page
5353
skipAppDefaults: false, // opt out of the app.baseURL / vite.base defaults
@@ -66,7 +66,7 @@ Pass your devframe definition to wire `nuxt dev` up to the RPC backend:
6666
import devframe from './src/devframe' // defineDevframe(...) export
6767
6868
export default defineNuxtConfig({
69-
modules: [['@devframes/nuxt/dev-spa', { devframe }]],
69+
modules: [['@devframes/nuxt/single', { devframe }]],
7070
})
7171
```
7272

@@ -83,7 +83,7 @@ The bridge is **on by default** whenever `devframe` is set. Skip it (back to cli
8383

8484
```ts [nuxt.config.ts]
8585
export default defineNuxtConfig({
86-
modules: [['@devframes/nuxt/dev-spa', {
86+
modules: [['@devframes/nuxt/single', {
8787
devframe,
8888
devMiddleware: {
8989
port: 7777,

‎docs/frameworks/vite.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ outline: deep
44

55
# Vite
66

7-
`@devframes/vite` splits into two scopes: **`@devframes/vite/dev-spa`** (this page — dev-serve one devframe's SPA with Vite) and [**`@devframes/vite/hub`**](#mounting-a-hub) (mount a whole devframes-hub inside a Vite app). The bare `@devframes/vite` import throws with a pointer to both.
7+
`@devframes/vite` splits into two scopes: **`@devframes/vite/single`** (this page — dev-serve one devframe's SPA with Vite) and [**`@devframes/vite/hub`**](#mounting-a-hub) (mount a whole devframes-hub inside a Vite app). The bare `@devframes/vite` import throws with a pointer to both.
88

9-
The `dev-spa` scope exports two Vite plugins for mounting a single devframe inside an existing Vite dev server — `devframeVitePlugin` (static mount) and `devframeViteBridge` (RPC bridge) — plus `devframeVite`, a convenience wrapper that picks between them. Used by [`@devframes/nuxt`](./nuxt) and available for any Vite-based host (Astro, SolidStart, plain Vite apps).
9+
The `single` scope exports two Vite plugins for mounting a single devframe inside an existing Vite dev server — `devframeVitePlugin` (static mount) and `devframeViteBridge` (RPC bridge) — plus `devframeVite`, a convenience wrapper that picks between them. Used by [`@devframes/nuxt`](./nuxt) and available for any Vite-based host (Astro, SolidStart, plain Vite apps).
1010

1111
This sits below the [`vite` adapter](/adapters/vite) on the abstraction ladder: the adapter targets the full Vite DevTools dock; these are the lower-level Vite plugins you reach for when you want a devframe to ride along with an existing app's dev server without the DevTools dock.
1212

1313
```ts
14-
import { devframeViteBridge, devframeVitePlugin } from '@devframes/vite/dev-spa'
14+
import { devframeViteBridge, devframeVitePlugin } from '@devframes/vite/single'
1515
import { defineConfig } from 'vite'
1616
import devframe from './devframe'
1717

@@ -26,7 +26,7 @@ export default defineConfig({
2626

2727
## `devframeVitePlugin` — static mount
2828

29-
Mounts `def.cli.distDir` at `options.base` (`/__<id>/` by default) with SPA fallback. No RPC server is started — useful when you only need the SPA bundle served from a known path.
29+
Mounts `def.cli.distDir` at `options.base` (`/__<id>/` by default) with SPA fallback. No RPC server is started — useful when you only need the SPA bundle served from a known path. `distDir` may be a local directory or a [remote assets](/guide/client-assets) package.
3030

3131
| Option | Default | Description |
3232
|--------|---------|-------------|

0 commit comments

Comments
 (0)