@vpxa/aikit 0.1.332 → 0.1.334
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -2
- package/packages/blocks-core/dist/index.mjs +2 -2
- package/packages/browser/dist/index.js +1 -1
- package/packages/server/dist/bin.js +1 -1
- package/packages/server/dist/index.js +1 -1
- package/packages/server/dist/{server-BPpxMa6G.js → server-DxWt52oI.js} +44 -44
- package/packages/server/dist/{server-http-D0sBtn4y.js → server-http-BiklDGRo.js} +1 -1
- package/packages/server/dist/{server-http-BPeLCfm4.js → server-http-DXD_Vzek.js} +1 -1
- package/packages/server/dist/{server-BlN0Hbye.js → server-oGprQYV-.js} +44 -44
- package/packages/server/dist/{server-stdio-C24ENRdr.js → server-stdio-BoExYZAT.js} +1 -1
- package/packages/server/dist/{server-stdio-uwA3lNY2.js → server-stdio-CkZ6eSpg.js} +1 -1
- package/scaffold/dist/adapters/skills.mjs +1 -1
- package/packages/viewers/README.md +0 -798
|
@@ -1,798 +0,0 @@
|
|
|
1
|
-
# AI Kit Viewers
|
|
2
|
-
|
|
3
|
-
Self-contained HTML viewers for AI Kit skills. Each viewer is built from React 19 source, bundled with Vite, flattened into a single HTML file with `vite-plugin-singlefile`, and then embedded into scaffold skill definitions.
|
|
4
|
-
|
|
5
|
-
This folder is a standalone package. It is not part of the parent pnpm workspace. Install and build it from this directory with `--ignore-workspace`.
|
|
6
|
-
|
|
7
|
-
## Overview
|
|
8
|
-
|
|
9
|
-
Use this package when a skill needs a rich interactive artifact that can ship as one HTML asset.
|
|
10
|
-
|
|
11
|
-
Current viewers:
|
|
12
|
-
|
|
13
|
-
- `c4-viewer.html` -> C4 architecture viewer powered by ReactFlow and ELK layout
|
|
14
|
-
- `tour-viewer.html` -> interactive code tour viewer powered by ReactFlow and a BFS tree layout
|
|
15
|
-
|
|
16
|
-
Why single-file HTML:
|
|
17
|
-
|
|
18
|
-
- AI Kit skill definitions embed assets as template literals in `.mjs` files
|
|
19
|
-
- A self-contained HTML file avoids external JS, CSS, font, and image dependencies
|
|
20
|
-
- Embedding one file is simpler and more reliable than coordinating asset directories
|
|
21
|
-
- Browser rendering is predictable inside skill-driven workflows
|
|
22
|
-
|
|
23
|
-
## Quick Start
|
|
24
|
-
|
|
25
|
-
Run these commands from `packages/viewers`.
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
pnpm install --ignore-workspace
|
|
29
|
-
pnpm build
|
|
30
|
-
pnpm embed
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
Available scripts:
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
pnpm dev:c4
|
|
37
|
-
pnpm dev:tour
|
|
38
|
-
pnpm build
|
|
39
|
-
pnpm embed
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
What each command does:
|
|
43
|
-
|
|
44
|
-
- `pnpm install --ignore-workspace` installs this package independently from the monorepo root
|
|
45
|
-
- `pnpm dev:c4` serves the C4 entry HTML with `vite.c4.config.ts`
|
|
46
|
-
- `pnpm dev:tour` serves the Tour entry HTML with `vite.tour.config.ts`
|
|
47
|
-
- `pnpm build` writes single-file artifacts into `dist/`
|
|
48
|
-
- `pnpm embed` injects built HTML into scaffold skill definitions
|
|
49
|
-
|
|
50
|
-
After `pnpm embed`, rebuild the parent scaffold output from the `aikit-mcp` root if you need refreshed generated artifacts. `embed.mjs` prints that reminder explicitly.
|
|
51
|
-
|
|
52
|
-
## Package Layout
|
|
53
|
-
|
|
54
|
-
```text
|
|
55
|
-
viewers/
|
|
56
|
-
├── README.md
|
|
57
|
-
├── package.json
|
|
58
|
-
├── tsconfig.json
|
|
59
|
-
├── pnpm-lock.yaml
|
|
60
|
-
├── .gitignore
|
|
61
|
-
├── c4-viewer.html
|
|
62
|
-
├── tour-viewer.html
|
|
63
|
-
├── vite.c4.config.ts
|
|
64
|
-
├── vite.tour.config.ts
|
|
65
|
-
├── embed.mjs
|
|
66
|
-
├── src/
|
|
67
|
-
│ ├── env.d.ts
|
|
68
|
-
│ ├── shared/
|
|
69
|
-
│ │ ├── theme.css
|
|
70
|
-
│ │ ├── types.ts
|
|
71
|
-
│ │ ├── ThemeProvider.tsx
|
|
72
|
-
│ │ ├── Layout.tsx
|
|
73
|
-
│ │ ├── Header.tsx
|
|
74
|
-
│ │ ├── Footer.tsx
|
|
75
|
-
│ │ ├── Sidebar.tsx
|
|
76
|
-
│ │ ├── Card.tsx
|
|
77
|
-
│ │ ├── Badge.tsx
|
|
78
|
-
│ │ ├── simple-template.css
|
|
79
|
-
│ │ └── simple-template.html
|
|
80
|
-
│ ├── c4/
|
|
81
|
-
│ │ ├── C4Viewer.tsx
|
|
82
|
-
│ │ └── main.tsx
|
|
83
|
-
│ └── tour/
|
|
84
|
-
│ ├── TourViewer.tsx
|
|
85
|
-
│ └── main.tsx
|
|
86
|
-
└── dist/
|
|
87
|
-
├── c4-viewer.html
|
|
88
|
-
└── tour-viewer.html
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
Important distinction:
|
|
92
|
-
|
|
93
|
-
- Root `*-viewer.html` files are Vite entry templates
|
|
94
|
-
- `dist/*.html` files are the final self-contained artifacts
|
|
95
|
-
- `embed.mjs` reads from `dist/`, not from the root entry templates
|
|
96
|
-
- `package.json` `exports` points at the built viewer HTML assets, which keeps workspace health satisfied without changing the standalone build flow
|
|
97
|
-
|
|
98
|
-
## Architecture
|
|
99
|
-
|
|
100
|
-
## Build Stack
|
|
101
|
-
|
|
102
|
-
- React 19 for the viewer UI
|
|
103
|
-
- `@xyflow/react` v12 for graph rendering and interaction
|
|
104
|
-
- `elkjs` for auto-layout in the C4 viewer
|
|
105
|
-
- Vite 8 for dev/build
|
|
106
|
-
- `vite-plugin-singlefile` to inline JS and CSS into one HTML document
|
|
107
|
-
|
|
108
|
-
## Runtime Shape
|
|
109
|
-
|
|
110
|
-
Each full viewer follows the same structure:
|
|
111
|
-
|
|
112
|
-
1. Root HTML entry provides a `<div id="root"></div>` and optional JSON payload script tag
|
|
113
|
-
2. `src/<viewer>/main.tsx` reads `window.__VIEWER_CONFIG__` first, then falls back to legacy inline JSON
|
|
114
|
-
3. The viewer component wraps its content in `ThemeProvider`
|
|
115
|
-
4. `Layout`, `Header`, and `Footer` provide the shared shell
|
|
116
|
-
5. ReactFlow renders the interactive canvas
|
|
117
|
-
6. Vite emits a single HTML file into `dist/`
|
|
118
|
-
7. `embed.mjs` injects that HTML into the relevant skill definition file
|
|
119
|
-
|
|
120
|
-
## Shared Template System
|
|
121
|
-
|
|
122
|
-
`src/shared/` is the design system and shell contract for full viewers.
|
|
123
|
-
|
|
124
|
-
Treat it as the source of truth for:
|
|
125
|
-
|
|
126
|
-
- theme tokens
|
|
127
|
-
- layout variants
|
|
128
|
-
- header and footer behavior
|
|
129
|
-
- shared TypeScript interfaces
|
|
130
|
-
- reusable UI primitives
|
|
131
|
-
|
|
132
|
-
Two template tiers live here:
|
|
133
|
-
|
|
134
|
-
- Full template: React + ReactFlow viewers such as C4 and Tour
|
|
135
|
-
- Simple template: `simple-template.html` + `simple-template.css` for pure HTML/CSS rendering patterns
|
|
136
|
-
|
|
137
|
-
Use the full template for interactive graph viewers. Use the simple template only when a viewer does not need React or graph interaction.
|
|
138
|
-
|
|
139
|
-
## Shared Components
|
|
140
|
-
|
|
141
|
-
Every new full viewer should use the shared shell components unless there is a strong reason not to.
|
|
142
|
-
|
|
143
|
-
## `ThemeProvider`
|
|
144
|
-
|
|
145
|
-
Purpose:
|
|
146
|
-
|
|
147
|
-
- owns the light/dark/system theme mode state
|
|
148
|
-
- resolves system preference with `matchMedia('(prefers-color-scheme: dark)')`
|
|
149
|
-
- persists mode to `localStorage` under `aikit-theme-mode`
|
|
150
|
-
- writes the active theme to `document.documentElement.dataset.theme`
|
|
151
|
-
|
|
152
|
-
API:
|
|
153
|
-
|
|
154
|
-
```tsx
|
|
155
|
-
<ThemeProvider defaultMode="system">...</ThemeProvider>
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
Context values:
|
|
159
|
-
|
|
160
|
-
```ts
|
|
161
|
-
{
|
|
162
|
-
mode: 'light' | 'dark' | 'system';
|
|
163
|
-
resolvedMode: 'light' | 'dark';
|
|
164
|
-
setMode: (mode: ThemeMode) => void;
|
|
165
|
-
toggleMode: () => void;
|
|
166
|
-
}
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
Rules:
|
|
170
|
-
|
|
171
|
-
- Full viewers must be wrapped in `ThemeProvider`
|
|
172
|
-
- `defaultMode` should stay `system` unless you have a documented override
|
|
173
|
-
- Theme toggle behavior should remain centralized here, not reimplemented in individual viewers
|
|
174
|
-
|
|
175
|
-
## `Layout`
|
|
176
|
-
|
|
177
|
-
Purpose:
|
|
178
|
-
|
|
179
|
-
- applies the top-level CSS Grid layout class
|
|
180
|
-
|
|
181
|
-
Props:
|
|
182
|
-
|
|
183
|
-
```ts
|
|
184
|
-
interface LayoutProps {
|
|
185
|
-
children: React.ReactNode;
|
|
186
|
-
variant?: 'full' | 'sidebar-left' | 'sidebar-right';
|
|
187
|
-
className?: string;
|
|
188
|
-
style?: React.CSSProperties;
|
|
189
|
-
}
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
Usage:
|
|
193
|
-
|
|
194
|
-
```tsx
|
|
195
|
-
<Layout variant="full">
|
|
196
|
-
<Header />
|
|
197
|
-
<main />
|
|
198
|
-
<Footer />
|
|
199
|
-
</Layout>
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
## `Header`
|
|
203
|
-
|
|
204
|
-
Purpose:
|
|
205
|
-
|
|
206
|
-
- renders the sticky top bar
|
|
207
|
-
- shows the viewer title and optional subtitle
|
|
208
|
-
- exposes the shared theme toggle button
|
|
209
|
-
|
|
210
|
-
Props:
|
|
211
|
-
|
|
212
|
-
```ts
|
|
213
|
-
interface HeaderConfig {
|
|
214
|
-
title: string;
|
|
215
|
-
subtitle?: string;
|
|
216
|
-
showThemeToggle?: boolean;
|
|
217
|
-
}
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
Defaults:
|
|
221
|
-
|
|
222
|
-
- `title` defaults to `AI KIT`
|
|
223
|
-
- `showThemeToggle` defaults to `true`
|
|
224
|
-
|
|
225
|
-
Usage:
|
|
226
|
-
|
|
227
|
-
```tsx
|
|
228
|
-
<Header title="My Viewer" subtitle="Context for this artifact" />
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
Rule:
|
|
232
|
-
|
|
233
|
-
- Pass a viewer-specific title instead of changing the shared default
|
|
234
|
-
|
|
235
|
-
## `Footer`
|
|
236
|
-
|
|
237
|
-
Purpose:
|
|
238
|
-
|
|
239
|
-
- renders attribution
|
|
240
|
-
- optionally shows a timestamp
|
|
241
|
-
- optionally shows a legend
|
|
242
|
-
|
|
243
|
-
Props:
|
|
244
|
-
|
|
245
|
-
```ts
|
|
246
|
-
interface FooterConfig {
|
|
247
|
-
showTimestamp?: boolean;
|
|
248
|
-
showAttribution?: boolean;
|
|
249
|
-
legend?: Array<{ label: string; color: string; icon?: string }>;
|
|
250
|
-
}
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
Usage:
|
|
254
|
-
|
|
255
|
-
```tsx
|
|
256
|
-
<Footer
|
|
257
|
-
legend={[
|
|
258
|
-
{ label: 'system', color: 'var(--c4-system)' },
|
|
259
|
-
{ label: 'container', color: 'var(--c4-container)' },
|
|
260
|
-
]}
|
|
261
|
-
/>
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
Rule:
|
|
265
|
-
|
|
266
|
-
- Leave attribution enabled unless the host explicitly requires a different footer contract
|
|
267
|
-
|
|
268
|
-
## `Sidebar`
|
|
269
|
-
|
|
270
|
-
Purpose:
|
|
271
|
-
|
|
272
|
-
- provides a collapsible inspector panel
|
|
273
|
-
- supports left or right placement
|
|
274
|
-
- becomes an overlay on mobile
|
|
275
|
-
|
|
276
|
-
Props:
|
|
277
|
-
|
|
278
|
-
```ts
|
|
279
|
-
interface SidebarProps {
|
|
280
|
-
children: React.ReactNode;
|
|
281
|
-
position?: 'left' | 'right';
|
|
282
|
-
collapsible?: boolean;
|
|
283
|
-
defaultCollapsed?: boolean;
|
|
284
|
-
width?: number;
|
|
285
|
-
className?: string;
|
|
286
|
-
title?: string;
|
|
287
|
-
}
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
Use this when a viewer needs metadata, filters, or a drill-down panel. Do not build a custom sidebar first.
|
|
291
|
-
|
|
292
|
-
## `Card`
|
|
293
|
-
|
|
294
|
-
Purpose:
|
|
295
|
-
|
|
296
|
-
- reusable surface for detail panes, metrics, or inspector content
|
|
297
|
-
- supports a gradient orb effect and clickable states
|
|
298
|
-
|
|
299
|
-
Props:
|
|
300
|
-
|
|
301
|
-
```ts
|
|
302
|
-
{
|
|
303
|
-
children: React.ReactNode;
|
|
304
|
-
className?: string;
|
|
305
|
-
gradient?: boolean;
|
|
306
|
-
onClick?: () => void;
|
|
307
|
-
variant?: 'default' | 'outlined' | 'elevated';
|
|
308
|
-
}
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
Rule:
|
|
312
|
-
|
|
313
|
-
- Use `gradient` for featured blocks only. Do not turn every card into a visual accent.
|
|
314
|
-
|
|
315
|
-
## `Badge`
|
|
316
|
-
|
|
317
|
-
Purpose:
|
|
318
|
-
|
|
319
|
-
- compact status and category labels
|
|
320
|
-
|
|
321
|
-
Props:
|
|
322
|
-
|
|
323
|
-
```ts
|
|
324
|
-
{
|
|
325
|
-
children: React.ReactNode;
|
|
326
|
-
className?: string;
|
|
327
|
-
variant?: 'default' | 'primary' | 'success' | 'warning' | 'error' | 'info';
|
|
328
|
-
size?: 'sm' | 'md';
|
|
329
|
-
}
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
Rule:
|
|
333
|
-
|
|
334
|
-
- Prefer semantic variants over custom inline styling
|
|
335
|
-
|
|
336
|
-
## `types.ts`
|
|
337
|
-
|
|
338
|
-
Purpose:
|
|
339
|
-
|
|
340
|
-
- shared viewer contract for theme, layout, header, footer, C4 data, tour data, and the top-level `ViewerConfig`
|
|
341
|
-
|
|
342
|
-
Use existing types first. Extend them only when a new viewer needs a real shared contract.
|
|
343
|
-
|
|
344
|
-
## Theme System
|
|
345
|
-
|
|
346
|
-
Theme state is split across React state and CSS custom properties.
|
|
347
|
-
|
|
348
|
-
Mechanics:
|
|
349
|
-
|
|
350
|
-
- `ThemeProvider` tracks `mode: 'light' | 'dark' | 'system'`
|
|
351
|
-
- `resolvedMode` is always `'light'` or `'dark'`
|
|
352
|
-
- the provider writes `data-theme="light"` or `data-theme="dark"` on `<html>`
|
|
353
|
-
- `theme.css` defines the token sets for `:root` and `[data-theme="dark"]`
|
|
354
|
-
- if no explicit mode is active, `@media (prefers-color-scheme: dark)` provides the system fallback
|
|
355
|
-
|
|
356
|
-
### Key Theme Tokens
|
|
357
|
-
|
|
358
|
-
Core surface tokens:
|
|
359
|
-
|
|
360
|
-
- `--background`
|
|
361
|
-
- `--foreground`
|
|
362
|
-
- `--card`
|
|
363
|
-
- `--card-foreground`
|
|
364
|
-
- `--primary`
|
|
365
|
-
- `--primary-foreground`
|
|
366
|
-
- `--muted`
|
|
367
|
-
- `--muted-foreground`
|
|
368
|
-
- `--accent`
|
|
369
|
-
- `--accent-foreground`
|
|
370
|
-
- `--border`
|
|
371
|
-
|
|
372
|
-
Supplementary tokens used by shared components:
|
|
373
|
-
|
|
374
|
-
- `--sidebar`
|
|
375
|
-
- `--sidebar-foreground`
|
|
376
|
-
- `--color-success`
|
|
377
|
-
- `--color-warning`
|
|
378
|
-
- `--color-error`
|
|
379
|
-
- `--color-info`
|
|
380
|
-
- spacing tokens such as `--space-1` through `--space-24`
|
|
381
|
-
- shape and motion tokens such as `--radius`, `--shadow-*`, and `--transition-*`
|
|
382
|
-
|
|
383
|
-
C4-specific tokens:
|
|
384
|
-
|
|
385
|
-
- `--c4-person`
|
|
386
|
-
- `--c4-system`
|
|
387
|
-
- `--c4-container`
|
|
388
|
-
- `--c4-component`
|
|
389
|
-
- `--c4-database`
|
|
390
|
-
- `--c4-queue`
|
|
391
|
-
- `--c4-external`
|
|
392
|
-
- `--c4-boundary-border`
|
|
393
|
-
|
|
394
|
-
Conventions:
|
|
395
|
-
|
|
396
|
-
- Do not hardcode colors in new viewer UI code
|
|
397
|
-
- Read from CSS variables with `var(--token)`
|
|
398
|
-
- If you need a new semantic token, add it to `theme.css` instead of scattering literals
|
|
399
|
-
- Treat any remaining literal-color spots in older code as legacy, not as a pattern to copy
|
|
400
|
-
|
|
401
|
-
Note on palette language:
|
|
402
|
-
|
|
403
|
-
- The system is designed around shared CSS custom-property tokens and a Nord-inspired dark palette
|
|
404
|
-
- The current implementation stores concrete color values directly in `theme.css`
|
|
405
|
-
- If you migrate tokens to `oklch()`, keep the same semantic token names so viewer code stays unchanged
|
|
406
|
-
|
|
407
|
-
## Data Format
|
|
408
|
-
|
|
409
|
-
New viewers should prefer `window.__VIEWER_CONFIG__`.
|
|
410
|
-
|
|
411
|
-
Top-level shape:
|
|
412
|
-
|
|
413
|
-
```ts
|
|
414
|
-
interface ViewerConfig {
|
|
415
|
-
type: 'c4' | 'tour' | 'custom';
|
|
416
|
-
theme?: Partial<ThemeConfig>;
|
|
417
|
-
layout?: Partial<LayoutConfig>;
|
|
418
|
-
header?: Partial<HeaderConfig>;
|
|
419
|
-
footer?: Partial<FooterConfig>;
|
|
420
|
-
data: unknown;
|
|
421
|
-
}
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
## Preferred Injection Pattern
|
|
425
|
-
|
|
426
|
-
Use this in `src/<name>/main.tsx`:
|
|
427
|
-
|
|
428
|
-
```tsx
|
|
429
|
-
const config = (window as any).__VIEWER_CONFIG__;
|
|
430
|
-
const dataEl = document.getElementById('<viewer>-data');
|
|
431
|
-
const data = config?.data || (dataEl ? JSON.parse(dataEl.textContent || '{}') : {});
|
|
432
|
-
```
|
|
433
|
-
|
|
434
|
-
Why this order matters:
|
|
435
|
-
|
|
436
|
-
- `window.__VIEWER_CONFIG__` is the current contract for AI Kit-driven rendering
|
|
437
|
-
- inline script tags keep local preview and legacy flows working
|
|
438
|
-
- the fallback keeps the entry HTML usable as a manual demo and as a development fixture
|
|
439
|
-
|
|
440
|
-
## Current Viewers
|
|
441
|
-
|
|
442
|
-
### C4 Viewer
|
|
443
|
-
|
|
444
|
-
Source files:
|
|
445
|
-
|
|
446
|
-
- `src/c4/C4Viewer.tsx`
|
|
447
|
-
- `src/c4/main.tsx`
|
|
448
|
-
- `c4-viewer.html`
|
|
449
|
-
- `vite.c4.config.ts`
|
|
450
|
-
|
|
451
|
-
Behavior:
|
|
452
|
-
|
|
453
|
-
- reads `window.__VIEWER_CONFIG__` when `type === 'c4'`
|
|
454
|
-
- falls back to legacy `window.__C4_DATA__` and then `#diagram-data`
|
|
455
|
-
- normalizes legacy `elements` -> `nodes` and `relationships` -> `edges`
|
|
456
|
-
- uses ELK layered layout for automatic node positioning
|
|
457
|
-
- builds a legend for the footer from the node types in the payload
|
|
458
|
-
|
|
459
|
-
ReactFlow conventions already used here:
|
|
460
|
-
|
|
461
|
-
- `animated: true` on edges
|
|
462
|
-
- `proOptions: { hideAttribution: true }`
|
|
463
|
-
- includes `<Background />`, `<Controls />`, and `<MiniMap />`
|
|
464
|
-
- wraps the canvas in `ReactFlowProvider`
|
|
465
|
-
|
|
466
|
-
Data shape:
|
|
467
|
-
|
|
468
|
-
```ts
|
|
469
|
-
interface C4ViewerConfig {
|
|
470
|
-
title?: string;
|
|
471
|
-
description?: string;
|
|
472
|
-
layout?: {
|
|
473
|
-
direction?: string;
|
|
474
|
-
spacing?: number;
|
|
475
|
-
layerSpacing?: number;
|
|
476
|
-
};
|
|
477
|
-
nodes: Array<{
|
|
478
|
-
id: string;
|
|
479
|
-
type: 'person' | 'system' | 'container' | 'component' | 'database' | 'queue' | 'external' | 'boundary';
|
|
480
|
-
label: string;
|
|
481
|
-
technology?: string;
|
|
482
|
-
description?: string;
|
|
483
|
-
}>;
|
|
484
|
-
edges: Array<{
|
|
485
|
-
source: string;
|
|
486
|
-
target: string;
|
|
487
|
-
label?: string;
|
|
488
|
-
style?: 'sync' | 'async' | 'dashed' | 'dotted' | 'solid';
|
|
489
|
-
}>;
|
|
490
|
-
}
|
|
491
|
-
```
|
|
492
|
-
|
|
493
|
-
Example payload:
|
|
494
|
-
|
|
495
|
-
```html
|
|
496
|
-
<script type="application/json" id="diagram-data">
|
|
497
|
-
{
|
|
498
|
-
"title": "Sample System",
|
|
499
|
-
"description": "Replace this JSON with your C4 diagram data",
|
|
500
|
-
"layout": { "direction": "DOWN", "spacing": 80, "layerSpacing": 120 },
|
|
501
|
-
"nodes": [
|
|
502
|
-
{ "id": "user", "type": "person", "label": "User", "description": "End user" },
|
|
503
|
-
{ "id": "web", "type": "system", "label": "Web App", "technology": "React", "description": "Frontend" }
|
|
504
|
-
],
|
|
505
|
-
"edges": [
|
|
506
|
-
{ "source": "user", "target": "web", "label": "Uses" }
|
|
507
|
-
]
|
|
508
|
-
}
|
|
509
|
-
</script>
|
|
510
|
-
```
|
|
511
|
-
|
|
512
|
-
### Tour Viewer
|
|
513
|
-
|
|
514
|
-
Source files:
|
|
515
|
-
|
|
516
|
-
- `src/tour/TourViewer.tsx`
|
|
517
|
-
- `src/tour/main.tsx`
|
|
518
|
-
- `tour-viewer.html`
|
|
519
|
-
- `vite.tour.config.ts`
|
|
520
|
-
|
|
521
|
-
Behavior:
|
|
522
|
-
|
|
523
|
-
- reads `window.__VIEWER_CONFIG__` first
|
|
524
|
-
- falls back to `#tour-data`
|
|
525
|
-
- computes a BFS layout from `steps` and `dependencies`
|
|
526
|
-
- renders a graph above and a detail panel below
|
|
527
|
-
- supports active-step selection and previous/next navigation
|
|
528
|
-
|
|
529
|
-
ReactFlow conventions already used here:
|
|
530
|
-
|
|
531
|
-
- `animated: true` on edges
|
|
532
|
-
- `proOptions: { hideAttribution: true }`
|
|
533
|
-
- includes `<Background />`, `<Controls />`, and `<MiniMap />`
|
|
534
|
-
- wraps the canvas in `ReactFlowProvider`
|
|
535
|
-
|
|
536
|
-
Current data shape:
|
|
537
|
-
|
|
538
|
-
```ts
|
|
539
|
-
{
|
|
540
|
-
title?: string;
|
|
541
|
-
description?: string;
|
|
542
|
-
estimatedTime?: string;
|
|
543
|
-
steps: Array<{
|
|
544
|
-
stepNumber: number;
|
|
545
|
-
id: string;
|
|
546
|
-
title: string;
|
|
547
|
-
file: string;
|
|
548
|
-
explanation: string;
|
|
549
|
-
learnsConcept: string;
|
|
550
|
-
duration: string;
|
|
551
|
-
}>;
|
|
552
|
-
dependencies: Array<{
|
|
553
|
-
source: string;
|
|
554
|
-
target: string;
|
|
555
|
-
}>;
|
|
556
|
-
}
|
|
557
|
-
```
|
|
558
|
-
|
|
559
|
-
## How to Create a New Viewer
|
|
560
|
-
|
|
561
|
-
Use this process for every new full viewer.
|
|
562
|
-
|
|
563
|
-
### 1. Create the source directory
|
|
564
|
-
|
|
565
|
-
```text
|
|
566
|
-
src/<name>/
|
|
567
|
-
<Name>Viewer.tsx
|
|
568
|
-
main.tsx
|
|
569
|
-
```
|
|
570
|
-
|
|
571
|
-
### 2. Create `<Name>Viewer.tsx`
|
|
572
|
-
|
|
573
|
-
Use the shared shell.
|
|
574
|
-
|
|
575
|
-
```tsx
|
|
576
|
-
import { ReactFlowProvider, ReactFlow, Background, Controls, MiniMap } from '@xyflow/react';
|
|
577
|
-
import '@xyflow/react/dist/style.css';
|
|
578
|
-
|
|
579
|
-
import { ThemeProvider } from '../shared/ThemeProvider';
|
|
580
|
-
import { Layout } from '../shared/Layout';
|
|
581
|
-
import { Header } from '../shared/Header';
|
|
582
|
-
import { Footer } from '../shared/Footer';
|
|
583
|
-
import '../shared/theme.css';
|
|
584
|
-
|
|
585
|
-
interface MyViewerData {
|
|
586
|
-
title?: string;
|
|
587
|
-
description?: string;
|
|
588
|
-
nodes: Array<{ id: string; label: string }>;
|
|
589
|
-
edges: Array<{ source: string; target: string }>;
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
function MyViewerInner({ data }: { data: MyViewerData }) {
|
|
593
|
-
return (
|
|
594
|
-
<Layout variant="full">
|
|
595
|
-
<Header title={data.title || 'My Viewer'} subtitle={data.description} showThemeToggle={true} />
|
|
596
|
-
<div style={{ minHeight: 0, height: '100%', overflow: 'hidden' }}>
|
|
597
|
-
<ReactFlow
|
|
598
|
-
nodes={[]}
|
|
599
|
-
edges={[]}
|
|
600
|
-
proOptions={{ hideAttribution: true }}
|
|
601
|
-
>
|
|
602
|
-
<Background color="var(--muted)" gap={24} size={1.5} />
|
|
603
|
-
<Controls />
|
|
604
|
-
<MiniMap />
|
|
605
|
-
</ReactFlow>
|
|
606
|
-
</div>
|
|
607
|
-
<Footer />
|
|
608
|
-
</Layout>
|
|
609
|
-
);
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
export function MyViewer({ data }: { data: MyViewerData }) {
|
|
613
|
-
return (
|
|
614
|
-
<ThemeProvider>
|
|
615
|
-
<ReactFlowProvider>
|
|
616
|
-
<MyViewerInner data={data} />
|
|
617
|
-
</ReactFlowProvider>
|
|
618
|
-
</ThemeProvider>
|
|
619
|
-
);
|
|
620
|
-
}
|
|
621
|
-
```
|
|
622
|
-
|
|
623
|
-
### 3. Create `main.tsx`
|
|
624
|
-
|
|
625
|
-
Use the standard data loader.
|
|
626
|
-
|
|
627
|
-
```tsx
|
|
628
|
-
import { createRoot } from 'react-dom/client';
|
|
629
|
-
import { MyViewer } from './MyViewer';
|
|
630
|
-
|
|
631
|
-
const config = (window as any).__VIEWER_CONFIG__;
|
|
632
|
-
const dataEl = document.getElementById('my-viewer-data');
|
|
633
|
-
const data = config?.data || (dataEl ? JSON.parse(dataEl.textContent || '{}') : {});
|
|
634
|
-
|
|
635
|
-
createRoot(document.getElementById('root')!).render(<MyViewer data={data} />);
|
|
636
|
-
```
|
|
637
|
-
|
|
638
|
-
### 4. Create the root entry HTML template
|
|
639
|
-
|
|
640
|
-
Name it `<name>-viewer.html` and keep it minimal.
|
|
641
|
-
|
|
642
|
-
```html
|
|
643
|
-
<!DOCTYPE html>
|
|
644
|
-
<html lang="en">
|
|
645
|
-
<head>
|
|
646
|
-
<meta charset="UTF-8" />
|
|
647
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
648
|
-
<title>AI KIT - My Viewer</title>
|
|
649
|
-
</head>
|
|
650
|
-
<body>
|
|
651
|
-
<script type="application/json" id="my-viewer-data">
|
|
652
|
-
{
|
|
653
|
-
"title": "Sample Viewer",
|
|
654
|
-
"description": "Replace this JSON with sample viewer data"
|
|
655
|
-
}
|
|
656
|
-
</script>
|
|
657
|
-
<div id="root"></div>
|
|
658
|
-
<script type="module" src="./src/my-viewer/main.tsx"></script>
|
|
659
|
-
</body>
|
|
660
|
-
</html>
|
|
661
|
-
```
|
|
662
|
-
|
|
663
|
-
### 5. Create `vite.<name>.config.ts`
|
|
664
|
-
|
|
665
|
-
Copy an existing Vite config and change the input file.
|
|
666
|
-
|
|
667
|
-
```ts
|
|
668
|
-
import react from '@vitejs/plugin-react';
|
|
669
|
-
import { defineConfig } from 'vite';
|
|
670
|
-
import { viteSingleFile } from 'vite-plugin-singlefile';
|
|
671
|
-
|
|
672
|
-
export default defineConfig({
|
|
673
|
-
plugins: [react(), viteSingleFile()],
|
|
674
|
-
root: '.',
|
|
675
|
-
build: {
|
|
676
|
-
outDir: 'dist',
|
|
677
|
-
emptyOutDir: false,
|
|
678
|
-
rollupOptions: {
|
|
679
|
-
input: 'my-viewer.html',
|
|
680
|
-
},
|
|
681
|
-
},
|
|
682
|
-
});
|
|
683
|
-
```
|
|
684
|
-
|
|
685
|
-
### 6. Update `package.json`
|
|
686
|
-
|
|
687
|
-
Add a dev script and include the new Vite config in the build flow.
|
|
688
|
-
|
|
689
|
-
Example:
|
|
690
|
-
|
|
691
|
-
```json
|
|
692
|
-
{
|
|
693
|
-
"scripts": {
|
|
694
|
-
"dev:my-viewer": "vite --config vite.my-viewer.config.ts",
|
|
695
|
-
"build": "vite build --config vite.c4.config.ts && vite build --config vite.tour.config.ts && vite build --config vite.my-viewer.config.ts"
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
```
|
|
699
|
-
|
|
700
|
-
### 7. Build locally
|
|
701
|
-
|
|
702
|
-
```bash
|
|
703
|
-
pnpm build
|
|
704
|
-
```
|
|
705
|
-
|
|
706
|
-
Confirm that `dist/my-viewer.html` exists and opens correctly.
|
|
707
|
-
|
|
708
|
-
### 8. Embed if the viewer belongs in a skill asset
|
|
709
|
-
|
|
710
|
-
Update `embed.mjs` if the new viewer should be injected into a skill file.
|
|
711
|
-
|
|
712
|
-
Current embed targets:
|
|
713
|
-
|
|
714
|
-
- `dist/c4-viewer.html` -> `../../definitions/skills/c4-architecture.mjs`
|
|
715
|
-
- `dist/tour-viewer.html` -> `../../definitions/skills/docs.mjs`
|
|
716
|
-
|
|
717
|
-
Pattern:
|
|
718
|
-
|
|
719
|
-
```js
|
|
720
|
-
await updateSkillAsset('my-skill.mjs', 'my-viewer.html', myViewerHtml);
|
|
721
|
-
```
|
|
722
|
-
|
|
723
|
-
Then run:
|
|
724
|
-
|
|
725
|
-
```bash
|
|
726
|
-
pnpm embed
|
|
727
|
-
```
|
|
728
|
-
|
|
729
|
-
## Build and Deploy Flow
|
|
730
|
-
|
|
731
|
-
The end-to-end path is:
|
|
732
|
-
|
|
733
|
-
```text
|
|
734
|
-
src/<viewer>/*
|
|
735
|
-
-> root <name>-viewer.html entry template
|
|
736
|
-
-> vite.<name>.config.ts
|
|
737
|
-
-> dist/<name>-viewer.html
|
|
738
|
-
-> embed.mjs
|
|
739
|
-
-> scaffold/definitions/skills/*.mjs asset arrays
|
|
740
|
-
```
|
|
741
|
-
|
|
742
|
-
`embed.mjs` behavior matters:
|
|
743
|
-
|
|
744
|
-
- it reads the built HTML from `dist/`
|
|
745
|
-
- it escapes backslashes, backticks, and `${` for safe template-literal embedding
|
|
746
|
-
- it replaces an existing asset entry if the same file marker is already present
|
|
747
|
-
- otherwise it appends a new asset object before the closing `];`
|
|
748
|
-
|
|
749
|
-
That replacement strategy is designed to recover cleanly from previous partial embeds.
|
|
750
|
-
|
|
751
|
-
## Conventions for LLM Agents
|
|
752
|
-
|
|
753
|
-
Follow these rules when modifying or creating viewers.
|
|
754
|
-
|
|
755
|
-
1. Full viewers must use `ThemeProvider`, `Layout`, `Header`, and `Footer`.
|
|
756
|
-
2. Use shared components before creating new shell primitives.
|
|
757
|
-
3. Do not hardcode colors in new code. Use `var(--token)` from `theme.css`.
|
|
758
|
-
4. Theme state belongs on `<html data-theme="light|dark">` and is resolved by `ThemeProvider`.
|
|
759
|
-
5. Keep `ThemeMode` as `'light' | 'dark' | 'system'` unless a cross-viewer contract change is intentional.
|
|
760
|
-
6. `Header` defaults to `AI KIT`. Pass the viewer-specific title through props.
|
|
761
|
-
7. Root `*-viewer.html` files are entry templates, not build artifacts.
|
|
762
|
-
8. Each viewer gets its own `vite.<name>.config.ts` file.
|
|
763
|
-
9. ReactFlow viewers must include `<Background />`, `<Controls />`, and `<MiniMap />`.
|
|
764
|
-
10. ReactFlow edges should be animated unless the viewer has a documented reason not to animate them.
|
|
765
|
-
11. Set `proOptions: { hideAttribution: true }` on ReactFlow instances.
|
|
766
|
-
12. Keep the package standalone. Use `pnpm install --ignore-workspace` in this folder.
|
|
767
|
-
13. Prefer `window.__VIEWER_CONFIG__` and retain an inline JSON fallback for previewability.
|
|
768
|
-
14. Reuse shared types from `src/shared/types.ts` when possible.
|
|
769
|
-
15. If you add a shared token or component, document it here in the same change.
|
|
770
|
-
16. If you add a viewer intended for a skill, wire it into `embed.mjs` and verify the target skill file.
|
|
771
|
-
|
|
772
|
-
## Common Mistakes
|
|
773
|
-
|
|
774
|
-
- Editing `dist/*.html` directly instead of source files
|
|
775
|
-
- Treating root `*-viewer.html` as final output
|
|
776
|
-
- Running `pnpm install` without `--ignore-workspace`
|
|
777
|
-
- Recreating theme logic inside a viewer instead of using `ThemeProvider`
|
|
778
|
-
- Adding bespoke layout wrappers instead of using `Layout`
|
|
779
|
-
- Copying literal colors from older code instead of using theme tokens
|
|
780
|
-
- Forgetting to add a new Vite config to the build script
|
|
781
|
-
- Forgetting to update `embed.mjs` for a new skill-bound viewer
|
|
782
|
-
|
|
783
|
-
## Minimal Checklist for a New Viewer
|
|
784
|
-
|
|
785
|
-
- Source lives under `src/<name>/`
|
|
786
|
-
- Root entry HTML exists as `<name>-viewer.html`
|
|
787
|
-
- Vite config exists as `vite.<name>.config.ts`
|
|
788
|
-
- Viewer uses `ThemeProvider`, `Layout`, `Header`, and `Footer`
|
|
789
|
-
- ReactFlow config includes `Background`, `Controls`, `MiniMap`, animated edges, and hidden attribution
|
|
790
|
-
- Data loader prefers `window.__VIEWER_CONFIG__`
|
|
791
|
-
- `package.json` scripts are updated
|
|
792
|
-
- `pnpm build` succeeds
|
|
793
|
-
- `pnpm embed` updated and run if needed
|
|
794
|
-
- This README updated if the shared contract changed
|
|
795
|
-
|
|
796
|
-
## Source of Truth
|
|
797
|
-
|
|
798
|
-
When this README and the code disagree, trust the code in this folder. Then update the README in the same change.
|