Skip to content

Commit 34a29e2

Browse files
committed
feat: add minimal devtools hub examples
1 parent bc9cfcb commit 34a29e2

43 files changed

Lines changed: 1363 additions & 78 deletions

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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
**`devframe`** is the framework-neutral container for one devtool integration, portable across viewers. Build a single tool (its RPC, its SPA, its diagnostics, its CLI/build/spa/embedded outputs) without caring how it'll be displayed. A devframe app runs standalone (CLI, static deploy, embedded SPA) just as well as it mounts inside a hub.
66

7-
**`@devframes/hub`** is the framework-neutral hub layer that sits on top of devframe and provides the multi-integration orchestration (docks, terminals, messages, commands). It does not ship UI — implementers (e.g. `@vitejs/devtools-kit`) provide their own UI on top of the hub's RPC + shared-state protocol. See `examples/minimal-vite-devtools-kit/` for a working ~120-line kit demonstrating the protocol end to end.
7+
**`@devframes/hub`** is the framework-neutral hub layer that sits on top of devframe and provides the multi-integration orchestration (docks, terminals, messages, commands). It does not ship UI — implementers (e.g. `@vitejs/devtools-kit`) provide their own UI on top of the hub's RPC + shared-state protocol. See `examples/minimal-vite-devtools-hub/` for a working ~120-line Vite host demonstrating the protocol end to end.
88

99
## Stack & Structure
1010

docs/errors/DF8403.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
outline: deep
3+
---
4+
5+
# DF8403: Duplicate Command Id
6+
7+
## Message
8+
9+
> Command id "`{id}`" is already used by another command or child command
10+
11+
## Cause
12+
13+
`ctx.commands.register(command)` or a command handle `update()` received a command tree where at least one id is already owned by another top-level command or child command. Command ids are globally unique per hub context, including child commands.
14+
15+
## Fix
16+
17+
- Namespace every command id under your tool, such as `my-tool:reload` or `my-tool:open-settings`.
18+
- Give each child command its own id instead of reusing the parent id.
19+
- Unregister the previous command before replacing it with a different command tree.
20+
21+
## Source
22+
23+
- [`packages/hub/src/node/host-commands.ts`](https://github.com/devframes/devframe/blob/main/packages/hub/src/node/host-commands.ts)`DevToolsCommandsHost.register()` and command handle `update()` throw when a command id is duplicated.

docs/guide/hub.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ Plus broadcast notifications (`devframe:terminals:updated`, `devframe:messages:u
8989

9090
## Example
9191

92-
See [`examples/minimal-vite-devtools-kit/`](https://github.com/devframes/devframe/tree/main/examples/minimal-vite-devtools-kit) for a ~120-line Vite plugin that wires the hub end to end with a vanilla DOM UI. Every framework's hub kit follows the same shape: a thin layer that adapts the framework's dev server to the hub.
92+
See [`examples/minimal-vite-devtools-hub/`](https://github.com/devframes/devframe/tree/main/examples/minimal-vite-devtools-hub) for a ~120-line Vite plugin that wires the hub end to end with a vanilla DOM UI. Every framework's hub host follows the same shape: a thin layer that adapts the framework's dev server to the hub.
9393

9494
## Diagnostics
9595

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.next
2+
dist
3+
next-env.d.ts
4+
node_modules
5+
out
6+
.turbo
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Minimal Next DevTools Hub
2+
3+
A protocol-witness example. The `src/client/devtools/minimal-next-devtools-hub.ts` file wires `@devframes/hub` into a Next.js App Router app by lazily starting a side-car RPC/WS server from a Node route handler.
4+
5+
## Run it
6+
7+
```sh
8+
pnpm install
9+
pnpm --filter minimal-next-devtools-hub dev
10+
```
11+
12+
Open the printed URL. You should see:
13+
14+
- A status line showing the RPC backend
15+
- A **Docks** list with hub built-ins and the mounted demo devframe
16+
- A **Commands** list populated from server-side registrations
17+
- A **Messages** list populated via `messages.add()` on the server
18+
- A **Terminals** list, empty unless a devframe registers one
19+
- Buttons that exercise `hub:open-path` and `hub:commands:execute`
20+
21+
## What the example proves
22+
23+
- `createHubContext()` boots a hub without any Vite-specific code path
24+
- A `DevToolsHost & HubHostCapabilities` impl plugs Next host specifics into the hub uniformly
25+
- `mountDevframe(ctx, def)` registers any `DevframeDefinition` as a dock
26+
- Hub built-in RPCs (`hub:open-path`, `hub:commands:execute`) work regardless of how the host was constructed
27+
- The browser-side `connectDevframe({ baseURL: '/__hub/' })` discovers the WS endpoint via the Next route handler at `/__hub/__connection.json`
28+
29+
## Files
30+
31+
| File | Role |
32+
|---|---|
33+
| `src/client/devtools/minimal-next-devtools-hub.ts` | The Next host — creates hub context and side-car WS |
34+
| `src/client/app/%5F_hub/%5F_connection.json/route.ts` | Connection-meta endpoint for `/__hub/__connection.json` that starts the singleton host |
35+
| `src/client/devtools/demo-devframe.ts` | A sample `DevframeDefinition` that plugs into the host |
36+
| `src/client/app/page.tsx` | The browser-side UI that consumes the hub protocol |
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "minimal-next-devtools-hub",
3+
"type": "module",
4+
"version": "0.4.1",
5+
"private": true,
6+
"description": "Protocol-witness example — a tiny Next.js DevTools Hub built on @devframes/hub that exercises every hub subsystem end-to-end.",
7+
"scripts": {
8+
"dev": "next dev src/client",
9+
"build": "next build src/client",
10+
"test": "vitest run --config vitest.config.ts"
11+
},
12+
"dependencies": {
13+
"@devframes/hub": "workspace:*",
14+
"devframe": "workspace:*",
15+
"next": "catalog:frontend",
16+
"react": "catalog:frontend",
17+
"react-dom": "catalog:frontend"
18+
},
19+
"devDependencies": {
20+
"@types/react": "catalog:types",
21+
"@types/react-dom": "catalog:types",
22+
"get-port-please": "catalog:deps",
23+
"pathe": "catalog:deps",
24+
"vitest": "catalog:testing",
25+
"ws": "catalog:deps"
26+
}
27+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { ensureMinimalNextDevToolsHub } from '../../../devtools/minimal-next-devtools-hub'
2+
3+
export const runtime = 'nodejs'
4+
export const dynamic = 'force-dynamic'
5+
6+
export async function GET() {
7+
const hub = await ensureMinimalNextDevToolsHub()
8+
return Response.json(hub.connectionMeta)
9+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
:root {
2+
color-scheme: light dark;
3+
font-family: system-ui, sans-serif;
4+
line-height: 1.5;
5+
}
6+
7+
body {
8+
margin: 0;
9+
padding: 1.5rem 2rem;
10+
}
11+
12+
main {
13+
max-width: 800px;
14+
margin-inline: auto;
15+
}
16+
17+
header h1 {
18+
margin-bottom: 0.25rem;
19+
}
20+
21+
header p {
22+
margin-top: 0;
23+
opacity: 0.7;
24+
}
25+
26+
section {
27+
margin-block: 1.5rem;
28+
}
29+
30+
h2 {
31+
font-size: 1rem;
32+
text-transform: uppercase;
33+
letter-spacing: 0.05em;
34+
opacity: 0.8;
35+
border-bottom: 1px solid currentcolor;
36+
padding-bottom: 0.25rem;
37+
}
38+
39+
ul {
40+
list-style: none;
41+
padding-left: 0;
42+
}
43+
44+
li {
45+
padding: 0.5rem 0.75rem;
46+
border: 1px solid color-mix(in srgb, currentcolor 15%, transparent);
47+
border-radius: 0.5rem;
48+
margin-bottom: 0.5rem;
49+
font-family: ui-monospace, monospace;
50+
font-size: 0.9rem;
51+
}
52+
53+
li.muted {
54+
opacity: 0.5;
55+
font-style: italic;
56+
}
57+
58+
code {
59+
font-family: ui-monospace, monospace;
60+
background: color-mix(in srgb, currentcolor 10%, transparent);
61+
padding: 0.1em 0.35em;
62+
border-radius: 0.25em;
63+
}
64+
65+
button {
66+
font: inherit;
67+
padding: 0.5rem 1rem;
68+
border-radius: 0.5rem;
69+
border: 1px solid currentcolor;
70+
background: transparent;
71+
cursor: pointer;
72+
}
73+
74+
button:hover {
75+
background: color-mix(in srgb, currentcolor 10%, transparent);
76+
}
77+
78+
.actions {
79+
display: flex;
80+
flex-wrap: wrap;
81+
gap: 0.75rem;
82+
}
83+
84+
#status {
85+
font-family: ui-monospace, monospace;
86+
font-size: 0.85rem;
87+
opacity: 0.7;
88+
}
89+
90+
#status.error span {
91+
color: #c33;
92+
}
93+
94+
#status.ready span {
95+
color: #2a7;
96+
}
97+
98+
.badge {
99+
margin-left: 0.35rem;
100+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type { Metadata } from 'next'
2+
import type { ReactNode } from 'react'
3+
import './globals.css'
4+
5+
export const metadata: Metadata = {
6+
title: 'Minimal Next DevTools Hub',
7+
description: 'A Next.js host for the @devframes/hub protocol.',
8+
}
9+
10+
export default function RootLayout({ children }: { children: ReactNode }) {
11+
return (
12+
<html lang="en">
13+
<body>{children}</body>
14+
</html>
15+
)
16+
}

0 commit comments

Comments
 (0)