Skip to content

Commit ace6ec7

Browse files
committed
Merge origin/main into feat/json-render-packages
Resolve pnpm-lock.yaml by regenerating from the merged manifests.
2 parents 6cd18c8 + 57f3ef2 commit ace6ec7

46 files changed

Lines changed: 1752 additions & 1094 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: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,12 @@ The `pnpm test` script intentionally runs `build` first so `tsnapi` snapshots co
4242

4343
### Design system
4444

45-
All five built-in plugins — and every example under `examples/` — share one design system, [`@antfu/design`](https://github.com/antfu/design), so they look and feel like one product across frameworks (Git is React/Next, terminals is Svelte, code-server is vanilla DOM, inspect is Vue, a11y is Solid, the examples are Preact/Next/vanilla). It's a dev dependency consumed at build time: its UnoCSS preset and shipped styles drive every surface, and its Vue components are the canonical reference every framework matches. There is no shared internal design package — each app wires the preset itself and owns its own component ports.
45+
All five built-in plugins — and every example under `examples/` — share one design system, [`@antfu/design`](https://github.com/antfu/design), so they look and feel like one product across frameworks (Git is React/Next, terminals is Svelte, code-server is Vue, inspect is Vue, a11y is Solid, the examples are Preact/Next/vanilla). It's a dev dependency consumed at build time: its UnoCSS preset and shipped styles drive every surface, and its Vue components are the canonical reference every framework matches. There is no shared internal design package — each app wires the preset itself and owns its own component ports.
4646

4747
- **Respect the skills.** This design system is built to the `antfu` and `antfu-design` skills (UnoCSS-first, class-based semantic tokens, dual light/dark, anti-slop) — load and follow them when building or changing any UI here. The surfaces deliberately echo the upstream devtools they descend from; reference their UI/UX when in doubt: [`antfu/node-modules-inspector`](https://github.com/antfu/node-modules-inspector), [`antfu/vite-plugin-inspect`](https://github.com/antfu/vite-plugin-inspect), [`eslint/config-inspector`](https://github.com/eslint/config-inspector), and [`vitejs/devtools``packages/rolldown`](https://github.com/vitejs/devtools/tree/main/packages/rolldown).
4848
- **One preset, wired per app.** Each consumer's `uno.config.ts` composes the same stack: `presetAnthonyDesign({ primary })` (from `@antfu/design/unocss`, tuned to devframe's sage green) + `presetWind4()` + `presetIcons()` (Phosphor) + `presetWebFonts()` (DM Sans / DM Mono) + `transformerDirectives()` + `transformerVariantGroup()`, plus the named `z-*` layers the nav/overlay surfaces reference (`z-nav`, `z-dropdown`, `z-tooltip`, `z-toast`, `z-modal-*`, `z-drawer-*`) — `presetAnthonyDesign` blocks plain `z-<number>` so every layer is named. Keep the block identical across apps so the surfaces stay consistent.
4949
- **Tokens are semantic shortcuts.** Build UI from `@antfu/design`'s class vocabulary — surfaces `bg-base` / `bg-secondary` / `bg-active`, text `color-base` / `color-muted` / `color-faint` / `color-active`, `border-base`, `op-fade` / `op-mute` — never a hardcoded palette. Import `@antfu/design/styles.css` (or cherry-pick `@antfu/design/styles/base.css` + `scrollbar.css`) once per page; dark mode is the `.dark` class on `<html>`, flipped from the OS preference in the SPA entry.
50-
- **Vue uses the components directly; other frameworks port them.** The Vue surface (inspect) imports components straight from `@antfu/design/components/*` (`ActionButton`, `ActionIconButton`, `DisplayBadge`, `LayoutTabs`, `LayoutToolbar`, `LayoutCard`, …). Every non-Vue surface ports the components it needs into its own framework — React in git and the Next examples, Svelte in terminals, Solid in a11y, Preact in the Preact examples, vanilla DOM helpers in code-server and the Vite hub — mirroring the upstream component's markup, classes and behavior so it renders identically. Port on demand: recreate only what a surface uses, and keep each port faithful to its `@antfu/design` source.
50+
- **Vue uses the components directly; other frameworks port them.** The Vue surface (inspect) imports components straight from `@antfu/design/components/*` (`ActionButton`, `ActionIconButton`, `DisplayBadge`, `LayoutTabs`, `LayoutToolbar`, `LayoutCard`, …). Every non-Vue surface ports the components it needs into its own framework — React in git and the Next examples, Svelte in terminals, Solid in a11y, Preact in the Preact examples, vanilla DOM helpers in the Vite hub — mirroring the upstream component's markup, classes and behavior so it renders identically. Port on demand: recreate only what a surface uses, and keep each port faithful to its `@antfu/design` source.
5151
- **One nav, three buttons, one tab selector — strictly.** Every surface opens with the same top bar — a `LayoutToolbar`-style row led by a brand block (a primary-tinted `i-ph:*` icon + the product name). Buttons come in exactly three forms: a **text button** (`ActionButton``btn-action` / `btn-primary`), a **bordered icon button** (`ActionIconButton``btn-icon-square`), and a **borderless icon button** (round `btn-icon`). Multi-view tools (inspect, git) switch views with the one shared segmented selector (`LayoutTabs` `variant="segment"`: a `bg-secondary` track with `data-[state=active]:bg-base` triggers). Don't invent bespoke nav bars, button shapes, or tab styles.
5252
- **Icons** come from the shared Phosphor set (`i-ph:*`, duotone preferred) via `presetIcons` — use them everywhere instead of per-consumer icon libraries or bespoke SVG.
5353
- **A surface keeping its own component CSS** (inspect, a11y) sources every color from `@antfu/design`'s semantic shortcuts via `--at-apply` (expanded by `transformerDirectives`) rather than hardcoding a palette, so it tracks the shared theme and the `.dark` class.

docs/plugins/code-server.md

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,30 @@ outline: deep
44

55
# Code Server
66

7-
Run [code-server](https://github.com/coder/code-server) (VS Code in the browser) as a devframe panel. The plugin detects a local install, launches it on demand, and embeds the editor in an auto-authenticated iframe. Its launcher UI is plain **vanilla TypeScript** — a state-driven view with no UI framework at all, which makes the framework-neutral point from the opposite end.
7+
Run VS Code in the browser as a devframe panel. The plugin detects a local editor binary, launches it on demand, and embeds the editor in an auto-authenticated iframe. Its launcher is a small **Vue** SPA built on the shared `@antfu/design` system, matching the rest of the devframe surfaces.
88

9-
Package: `@devframes/plugin-code-server` · framework: **Vanilla TypeScript**
9+
Package: `@devframes/plugin-code-server` · framework: **Vue**
1010

1111
## What it does
1212

13-
- **Detection** — on startup it runs `code-server --version`. When the binary is missing, the launcher renders install instructions instead of a launch button.
14-
- **Launch** — the launcher's button starts code-server as a managed child process bound to a free port, scoped to the workspace, and probes readiness via its `/healthz` endpoint.
15-
- **Auto-auth** — the plugin generates a random token, sets code-server's `HASHED_PASSWORD` to its SHA-256, and hands the matching session cookie back to the already-authorized devframe client, so the editor opens already signed in.
13+
- **Detection** — on startup it probes the resolved binary with `--version`. When none is found, the launcher renders install instructions instead of a launch button.
14+
- **Launch** — the launcher's button starts the editor as a managed child process bound to a free port, scoped to the workspace, and probes readiness before embedding it.
15+
- **Auto-auth** — the plugin generates fresh auth material per launch and hands it to the already-authorized devframe client, so the editor opens already signed in. `code-server` uses a session cookie; `code serve-web` uses a connection token on the URL.
1616

17-
The editor iframe points at code-server's own origin, so its WebSocket traffic flows directly without a reverse proxy.
17+
The editor iframe points at the server's own origin, so its WebSocket traffic flows directly without a reverse proxy.
18+
19+
## Backends
20+
21+
`mode: 'local'` (the default) embeds a server running on this machine. Pick the backend with `backend`, or leave it unset to auto-detect — the plugin prefers `code-server` on `PATH`, then falls back to the `code` CLI's `serve-web`.
22+
23+
| `backend` | Binary | Auth handoff |
24+
|-----------|--------|--------------|
25+
| `'code-server'` | Coder's [`code-server`](https://github.com/coder/code-server) | password + session cookie |
26+
| `'ms-code-serve-web'` | Microsoft's [`code serve-web`](https://code.visualstudio.com/docs/remote/vscode-server) | connection token (`?tkn=`) |
27+
28+
## Tunnel mode
29+
30+
`mode: 'tunnel'` runs Microsoft's `code tunnel` and embeds the hosted `vscode.dev` editor. The first launch surfaces a device-login prompt (a verification URL and a one-time code) in the launcher; authentication is handled by `vscode.dev`. Tunnel mode always uses the `code` CLI.
1831

1932
## Standalone
2033

@@ -42,8 +55,12 @@ export default defineConfig({
4255
import { createCodeServerDevframe } from '@devframes/plugin-code-server'
4356

4457
export default createCodeServerDevframe({
45-
// bin: 'code-server', // binary to detect / launch (default: PATH)
46-
// serverPort: 8080, // force a port (default: free port near 8080)
58+
// backend: 'code-server', // 'code-server' | 'ms-code-serve-web' (default: auto-detect)
59+
// mode: 'local', // 'local' | 'tunnel'
60+
// serverPort: 8080, // force a port (default: free port near 8080)
61+
// startOnBoot: true, // launch during setup instead of on demand
62+
// reuseExistingServer: true, // adopt a server already answering on the port
63+
// tunnel: { name: 'my-box' },
4764
})
4865
```
4966

@@ -53,12 +70,12 @@ All functions are namespaced `devframes:plugin:code-server:*`:
5370

5471
| Function | Type | Purpose |
5572
|----------|------|---------|
56-
| `detect` | `query` | Re-probe for the binary; returns `{ installed, version, bin }`. |
57-
| `status` | `query` | Current status plus the auth cookie when running. |
73+
| `detect` | `query` | Re-probe for the binary; returns `{ installed, version, bin, backend, mode }`. |
74+
| `status` | `query` | Current status plus the connect descriptor when running. |
5875
| `start` | `action` | Launch and wait for readiness. |
5976
| `stop` | `action` | Stop the process. |
6077

61-
Status (minus the auth cookie) is mirrored into the `…:state` shared state for reactive UIs.
78+
Status (minus the connect descriptor) is mirrored into the `…:state` shared state for reactive UIs.
6279

6380
## Source
6481

docs/plugins/index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ Each plugin is built with a **different UI framework**. That is deliberate: devf
1515
| [Accessibility Inspector](./a11y) | Solid | Run axe-core against a host app, list WCAG violations, and highlight the offending element in the page on hover. |
1616
| [Git](./git) | React (Next.js) | A repository dashboard — status, a commit graph, branches, and diffs, with optional staging and committing. |
1717
| [Terminals](./terminals) | Svelte | Stream read-only command output and run fully interactive PTY shells in the browser. |
18-
| [Code Server](./code-server) | Vanilla TypeScript | Launch code-server (VS Code in the browser) on demand and embed it in an auto-authenticated iframe. |
18+
| [Code Server](./code-server) | Vue | Launch VS Code in the browser (code-server, `code serve-web`, or a tunnel) on demand and embed it in an auto-authenticated iframe. |
1919

2020
## One client, any framework
2121

22-
The collection spans Vue, Solid, React, Svelte, and framework-free TypeScript, yet every plugin shares the same node-side surface: register RPC functions, publish shared state, and connect from the browser with `connectDevframe`. Whatever renders the UI — a reactive framework or a handful of DOM calls — talks to the backend through the same protocol.
22+
The collection spans Vue, Solid, React, and Svelte, yet every plugin shares the same node-side surface: register RPC functions, publish shared state, and connect from the browser with `connectDevframe`. Whatever renders the UI, it talks to the backend through the same protocol — the framework-free Vite hub example drives it with a handful of DOM calls.
2323

2424
This is the framework-agnostic promise in practice. The browser bundle is the author's to choose; devframe handles the transport, the data model, the adapters, and the agent surface underneath.
2525

packages/devframe/src/client/scope.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type {
33
DevframeRpcServerFunctions,
44
DevframeSettings,
55
RpcSharedStateGetOptions,
6+
ScopedRpcFn,
67
ScopedServerFunctions,
78
ScopedSharedStates,
89
} from 'devframe/types'
@@ -43,21 +44,21 @@ export interface DevframeScopedClientRpc<NS extends string = string> {
4344

4445
/** Call a server RPC function. Bare names resolve within this namespace. */
4546
call: {
46-
<T extends keyof ScopedServerFunctions<NS> & string>(method: T, ...args: Parameters<Extract<ScopedServerFunctions<NS>[T], AnyRpcFn>>): Promise<Awaited<ReturnType<Extract<ScopedServerFunctions<NS>[T], AnyRpcFn>>>>
47+
<T extends keyof ScopedServerFunctions<NS> & string>(method: T, ...args: Parameters<ScopedRpcFn<DevframeRpcServerFunctions, NS, T>>): Promise<Awaited<ReturnType<ScopedRpcFn<DevframeRpcServerFunctions, NS, T>>>>
4748
<T extends keyof DevframeRpcServerFunctions & string>(method: T, ...args: Parameters<Extract<DevframeRpcServerFunctions[T], AnyRpcFn>>): Promise<Awaited<ReturnType<Extract<DevframeRpcServerFunctions[T], AnyRpcFn>>>>
4849
(method: string, ...args: any[]): Promise<any>
4950
}
5051

5152
/** Call a server RPC event (fire-and-forget). */
5253
callEvent: {
53-
<T extends keyof ScopedServerFunctions<NS> & string>(method: T, ...args: Parameters<Extract<ScopedServerFunctions<NS>[T], AnyRpcFn>>): void
54+
<T extends keyof ScopedServerFunctions<NS> & string>(method: T, ...args: Parameters<ScopedRpcFn<DevframeRpcServerFunctions, NS, T>>): void
5455
<T extends keyof DevframeRpcServerFunctions & string>(method: T, ...args: Parameters<Extract<DevframeRpcServerFunctions[T], AnyRpcFn>>): void
5556
(method: string, ...args: any[]): void
5657
}
5758

5859
/** Call an optional server RPC function (no error if unregistered). */
5960
callOptional: {
60-
<T extends keyof ScopedServerFunctions<NS> & string>(method: T, ...args: Parameters<Extract<ScopedServerFunctions<NS>[T], AnyRpcFn>>): Promise<Awaited<ReturnType<Extract<ScopedServerFunctions<NS>[T], AnyRpcFn>>> | undefined>
61+
<T extends keyof ScopedServerFunctions<NS> & string>(method: T, ...args: Parameters<ScopedRpcFn<DevframeRpcServerFunctions, NS, T>>): Promise<Awaited<ReturnType<ScopedRpcFn<DevframeRpcServerFunctions, NS, T>>> | undefined>
6162
<T extends keyof DevframeRpcServerFunctions & string>(method: T, ...args: Parameters<Extract<DevframeRpcServerFunctions[T], AnyRpcFn>>): Promise<Awaited<ReturnType<Extract<DevframeRpcServerFunctions[T], AnyRpcFn>>> | undefined>
6263
(method: string, ...args: any[]): Promise<any>
6364
}

packages/devframe/src/types/scope.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ import type { DevframeViewHost } from './views'
1717
// work with even when a registry entry's type can't be proven callable.
1818
type AnyRpcFn = (...args: any[]) => any
1919

20+
/**
21+
* Resolve a scoped bare name `T` back to its fully-qualified entry in
22+
* `Registry` (`<NS>:<T>`). Indexing the plain registry behind a `keyof`
23+
* guard keeps TypeScript from rejecting a generic index into the
24+
* key-remapped `Scoped*Functions<NS>` maps (TS2536) once a plugin augments
25+
* the registry, while preserving each name's argument/return typing.
26+
*/
27+
export type ScopedRpcFn<Registry, NS extends string, T extends string>
28+
= `${NS}:${T}` extends keyof Registry ? Extract<Registry[`${NS}:${T}`], AnyRpcFn> : AnyRpcFn
29+
2030
/**
2131
* Augmentable registry mapping a scope namespace to the shape of its
2232
* persisted settings. Tools augment it so `ctx.scope('my-plugin')`
@@ -157,7 +167,7 @@ export interface DevframeScopedNodeRpc<NS extends string = string> {
157167
* (containing `:`) to call another scope's function.
158168
*/
159169
call: {
160-
<T extends keyof ScopedServerFunctions<NS> & string>(method: T, ...args: Parameters<Extract<ScopedServerFunctions<NS>[T], AnyRpcFn>>): Promise<Awaited<ReturnType<Extract<ScopedServerFunctions<NS>[T], AnyRpcFn>>>>
170+
<T extends keyof ScopedServerFunctions<NS> & string>(method: T, ...args: Parameters<ScopedRpcFn<DevframeRpcServerFunctions, NS, T>>): Promise<Awaited<ReturnType<ScopedRpcFn<DevframeRpcServerFunctions, NS, T>>>>
161171
<T extends keyof DevframeRpcServerFunctions & string>(method: T, ...args: Parameters<Extract<DevframeRpcServerFunctions[T], AnyRpcFn>>): Promise<Awaited<ReturnType<Extract<DevframeRpcServerFunctions[T], AnyRpcFn>>>>
162172
(method: string, ...args: any[]): Promise<any>
163173
}
@@ -167,7 +177,7 @@ export interface DevframeScopedNodeRpc<NS extends string = string> {
167177
* names are resolved within this namespace.
168178
*/
169179
broadcast: {
170-
<T extends keyof ScopedClientFunctions<NS> & string>(options: ScopedBroadcastOptions<T, Parameters<Extract<ScopedClientFunctions<NS>[T], AnyRpcFn>>>): Promise<void>
180+
<T extends keyof ScopedClientFunctions<NS> & string>(options: ScopedBroadcastOptions<T, Parameters<ScopedRpcFn<DevframeRpcClientFunctions, NS, T>>>): Promise<void>
171181
(options: ScopedBroadcastOptions<string, any[]>): Promise<void>
172182
}
173183

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
1-
import type { StorybookConfig } from '@storybook/html-vite'
1+
import type { StorybookConfig } from '@storybook/vue3-vite'
2+
import vue from '@vitejs/plugin-vue'
23
import UnoCSS from 'unocss/vite'
4+
import { mergeConfig } from 'vite'
5+
import { alias } from '../../../alias'
36

47
const config: StorybookConfig = {
5-
stories: ['../src/**/*.stories.@(ts|tsx)'],
8+
stories: ['../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
9+
addons: ['@storybook/addon-docs'],
610
framework: {
7-
name: '@storybook/html-vite',
11+
name: '@storybook/vue3-vite',
812
options: {},
913
},
10-
viteFinal(viteConfig) {
11-
viteConfig.plugins ??= []
12-
viteConfig.plugins.push(UnoCSS())
13-
// Dev tool reached from arbitrary hostnames (LAN IPs, tunnels, tailnets),
14-
// e.g. when iframed by the storybook-hub example: accept any Host header.
15-
viteConfig.server = { ...viteConfig.server, allowedHosts: true }
16-
return viteConfig
14+
docs: {},
15+
async viteFinal(config) {
16+
return mergeConfig(config, {
17+
resolve: { alias },
18+
plugins: [vue(), UnoCSS()],
19+
// Dev tool reached from arbitrary hostnames (LAN IPs, tunnels,
20+
// tailnets), e.g. when iframed by the storybook-hub example.
21+
server: { allowedHosts: true },
22+
})
1723
},
1824
}
19-
2025
export default config

plugins/code-server/.storybook/preview.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import type { Decorator, Preview } from '@storybook/html-vite'
1+
import type { Decorator, Preview } from '@storybook/vue3-vite'
22
import 'virtual:uno.css'
33
import '@antfu/design/styles.css'
4-
import '../src/client/style.css'
4+
import '../src/spa/style.css'
55

66
// Drive the shared `@antfu/design` tokens off the toolbar theme toggle: dark mode
77
// is the `.dark` class on `<html>`, and the canvas takes the semantic
@@ -13,13 +13,19 @@ function applyTheme(theme: string): void {
1313

1414
const withTheme: Decorator = (story, context) => {
1515
applyTheme(context.globals.theme ?? 'dark')
16-
return story(context)
16+
return { components: { story }, template: '<story />' }
1717
}
1818

1919
const preview: Preview = {
2020
parameters: {
2121
layout: 'fullscreen',
22-
controls: { expanded: true },
22+
controls: {
23+
expanded: true,
24+
matchers: {
25+
color: /(background|color)$/i,
26+
date: /Date$/i,
27+
},
28+
},
2329
},
2430
globalTypes: {
2531
theme: {

0 commit comments

Comments
 (0)