@vpxa/aikit 0.1.156 → 0.1.158

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