@archetypeai/ds-cli 0.3.9 → 0.3.11

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.
Files changed (29) hide show
  1. package/bin.js +4 -0
  2. package/commands/create.js +76 -33
  3. package/commands/init.js +49 -19
  4. package/files/AGENTS.md +19 -3
  5. package/files/CLAUDE.md +21 -3
  6. package/files/rules/accessibility.md +49 -0
  7. package/files/rules/frontend-architecture.md +77 -0
  8. package/files/skills/apply-ds/SKILL.md +86 -82
  9. package/files/skills/apply-ds/scripts/audit.sh +169 -0
  10. package/files/skills/apply-ds/scripts/setup.sh +48 -166
  11. package/files/skills/create-dashboard/SKILL.md +12 -0
  12. package/files/skills/embedding-from-file/SKILL.md +415 -0
  13. package/files/skills/embedding-from-sensor/SKILL.md +406 -0
  14. package/files/skills/embedding-upload/SKILL.md +414 -0
  15. package/files/skills/fix-accessibility/SKILL.md +57 -9
  16. package/files/skills/newton-activity-monitor-lens-on-video/SKILL.md +817 -0
  17. package/files/skills/newton-camera-frame-analysis/SKILL.md +611 -0
  18. package/files/skills/newton-camera-frame-analysis/scripts/activity-monitor-frame.py +165 -0
  19. package/files/skills/newton-camera-frame-analysis/scripts/captures/logs/api_responses_20260206_105610.json +62 -0
  20. package/files/skills/newton-camera-frame-analysis/scripts/continuous_monitor.py +119 -0
  21. package/files/skills/newton-direct-query/SKILL.md +212 -0
  22. package/files/skills/newton-direct-query/scripts/direct_query.py +129 -0
  23. package/files/skills/newton-machine-state-from-file/SKILL.md +545 -0
  24. package/files/skills/newton-machine-state-from-sensor/SKILL.md +707 -0
  25. package/files/skills/newton-machine-state-upload/SKILL.md +986 -0
  26. package/lib/add-ds-config-codeagent.js +5 -1
  27. package/lib/is-interactive.js +19 -0
  28. package/lib/scaffold-ds-svelte-project.js +117 -0
  29. package/package.json +1 -1
package/bin.js CHANGED
@@ -31,12 +31,16 @@ Create flags:
31
31
  --fonts / --no-fonts Install internal fonts (default: prompt)
32
32
  --no-components Skip component installation (default: install all)
33
33
  --codeagent <cursor|claude|none> Agent configuration (default: prompt)
34
+ --defaults Skip prompts, use sensible defaults
34
35
 
35
36
  Init flags:
36
37
  --pm <npm|pnpm|bun|yarn> Package manager (default: auto-detect)
37
38
  --fonts / --no-fonts Install internal fonts (default: prompt)
38
39
  --no-components Skip component installation (default: install all)
39
40
  --codeagent <cursor|claude|none> Agent configuration (default: prompt)
41
+ --defaults Skip prompts, use sensible defaults
42
+
43
+ Non-interactive shells (no TTY) automatically use defaults.
40
44
 
41
45
  Add targets:
42
46
  ds-ui-svelte Install all design system components
@@ -8,10 +8,12 @@ import {
8
8
  installTokens,
9
9
  initShadcn,
10
10
  configureCss,
11
+ installLinting,
11
12
  installComponents,
12
13
  createDemoPage,
13
14
  installAgentConfig
14
15
  } from '../lib/scaffold-ds-svelte-project.js';
16
+ import { isInteractive, DEFAULTS, logDefaultsNotice } from '../lib/is-interactive.js';
15
17
 
16
18
  // parse flags
17
19
  export function parseFlags(args) {
@@ -20,7 +22,8 @@ export function parseFlags(args) {
20
22
  framework: null,
21
23
  pm: null,
22
24
  components: true,
23
- agent: null
25
+ agent: null,
26
+ defaults: false
24
27
  };
25
28
 
26
29
  for (let i = 0; i < args.length; i++) {
@@ -33,6 +36,8 @@ export function parseFlags(args) {
33
36
  flags.components = false;
34
37
  } else if (arg === '--codeagent' && args[i + 1]) {
35
38
  flags.agent = args[++i];
39
+ } else if (arg === '--defaults') {
40
+ flags.defaults = true;
36
41
  } else if (!arg.startsWith('--') && !flags.name) {
37
42
  flags.name = arg;
38
43
  }
@@ -44,12 +49,17 @@ export function parseFlags(args) {
44
49
  // create a new project
45
50
  export async function create(args) {
46
51
  const flags = parseFlags(args);
52
+ const interactive = isInteractive(flags);
47
53
 
48
54
  p.intro('Create a new project using Archetype AI Design System.');
49
55
 
50
56
  // specify project name
51
57
  let name = flags.name;
52
58
  if (!name) {
59
+ if (!interactive) {
60
+ p.log.error('Project name is required. Usage: ds create <project-name>');
61
+ process.exit(1);
62
+ }
53
63
  name = await p.text({
54
64
  message: 'What is your project name?',
55
65
  placeholder: 'my-app',
@@ -87,26 +97,38 @@ export async function create(args) {
87
97
  // specify framework
88
98
  let framework = flags.framework;
89
99
  if (!framework) {
90
- framework = await p.select({
91
- message: 'Which framework?',
92
- options: [{ value: 'svelte', label: 'SvelteKit', hint: 'Svelte 5 + Tailwind v4' }]
93
- });
94
- if (p.isCancel(framework)) {
95
- p.cancel('Setup cancelled.');
96
- process.exit(0);
100
+ if (!interactive) {
101
+ framework = DEFAULTS.framework;
102
+ } else {
103
+ framework = await p.select({
104
+ message: 'Which framework?',
105
+ options: [{ value: 'svelte', label: 'SvelteKit', hint: 'Svelte 5 + Tailwind v4, default' }]
106
+ });
107
+ if (p.isCancel(framework)) {
108
+ p.cancel('Setup cancelled.');
109
+ process.exit(0);
110
+ }
97
111
  }
98
112
  }
99
113
 
100
114
  // specify package manager
101
115
  let pmName = flags.pm;
102
116
  if (!pmName) {
103
- pmName = await p.select({
104
- message: 'Which package manager?',
105
- options: pmNames.map((n) => ({ value: n, label: n }))
106
- });
107
- if (p.isCancel(pmName)) {
108
- p.cancel('Setup cancelled.');
109
- process.exit(0);
117
+ if (!interactive) {
118
+ pmName = DEFAULTS.pm;
119
+ } else {
120
+ pmName = await p.select({
121
+ message: 'Which package manager?',
122
+ options: pmNames.map((n) => ({
123
+ value: n,
124
+ label: n,
125
+ ...(n === DEFAULTS.pm && { hint: 'default' })
126
+ }))
127
+ });
128
+ if (p.isCancel(pmName)) {
129
+ p.cancel('Setup cancelled.');
130
+ process.exit(0);
131
+ }
110
132
  }
111
133
  }
112
134
  const pm = getPm(pmName);
@@ -122,13 +144,17 @@ export async function create(args) {
122
144
  const projectPath = resolve(targetDir, name);
123
145
 
124
146
  if (existsSync(projectPath)) {
125
- const overwrite = await p.confirm({
126
- message: `Directory "${name}" already exists. Continue anyway?`,
127
- initialValue: false
128
- });
129
- if (p.isCancel(overwrite) || !overwrite) {
130
- p.cancel('Setup cancelled.');
131
- process.exit(0);
147
+ if (!interactive) {
148
+ p.log.warn(`Directory "${name}" already exists. Proceeding (non-interactive mode).`);
149
+ } else {
150
+ const overwrite = await p.confirm({
151
+ message: `Directory "${name}" already exists. Continue anyway?`,
152
+ initialValue: false
153
+ });
154
+ if (p.isCancel(overwrite) || !overwrite) {
155
+ p.cancel('Setup cancelled.');
156
+ process.exit(0);
157
+ }
132
158
  }
133
159
  }
134
160
 
@@ -151,6 +177,9 @@ export async function create(args) {
151
177
  const includeFonts = false;
152
178
  configureCss(projectPath, includeFonts);
153
179
 
180
+ // set up linting and formatting
181
+ await installLinting(pm, projectPath);
182
+
154
183
  // install components
155
184
  if (flags.components) {
156
185
  try {
@@ -169,20 +198,33 @@ export async function create(args) {
169
198
  // specify agent config
170
199
  let agent = flags.agent;
171
200
  if (!agent) {
172
- agent = await p.select({
173
- message: 'Install AI agent configuration?',
174
- options: [
175
- { value: 'none', label: 'None' },
176
- { value: 'cursor', label: 'Cursor', hint: 'AGENTS.md + skills + rules' },
177
- { value: 'claude', label: 'Claude Code', hint: 'CLAUDE.md + skills + rules' }
178
- ]
179
- });
180
- if (p.isCancel(agent)) {
181
- p.cancel('Setup cancelled.');
182
- process.exit(0);
201
+ if (!interactive) {
202
+ agent = DEFAULTS.agent;
203
+ } else {
204
+ agent = await p.select({
205
+ message: 'Install AI agent configuration?',
206
+ options: [
207
+ { value: 'none', label: 'None' },
208
+ { value: 'cursor', label: 'Cursor', hint: 'AGENTS.md + skills + rules' },
209
+ { value: 'claude', label: 'Claude Code', hint: 'CLAUDE.md + skills + rules, default' }
210
+ ]
211
+ });
212
+ if (p.isCancel(agent)) {
213
+ p.cancel('Setup cancelled.');
214
+ process.exit(0);
215
+ }
183
216
  }
184
217
  }
185
218
 
219
+ // log defaults notice
220
+ if (!interactive) {
221
+ const applied = {};
222
+ if (!flags.framework) applied.framework = framework;
223
+ if (!flags.pm) applied.pm = pmName;
224
+ if (!flags.agent) applied.codeagent = agent;
225
+ logDefaultsNotice(applied);
226
+ }
227
+
186
228
  const validAgents = ['cursor', 'claude', 'none'];
187
229
  if (!validAgents.includes(agent)) {
188
230
  p.log.error(`Invalid --codeagent value "${agent}". Must be one of: ${validAgents.join(', ')}`);
@@ -203,6 +245,7 @@ export async function create(args) {
203
245
  `Framework: ${framework}`,
204
246
  `Package Manager: ${pm.name}`,
205
247
  `Fonts: ${includeFonts ? 'installed' : 'skipped'}`,
248
+ `Linting: configured`,
206
249
  `Components: ${flags.components ? 'all' : 'skipped'}`,
207
250
  `Agent Configuration: ${agent}`
208
251
  ].join('\n');
package/commands/init.js CHANGED
@@ -8,16 +8,19 @@ import {
8
8
  installTokens,
9
9
  initShadcn,
10
10
  prependCss,
11
+ installLinting,
11
12
  installComponents,
12
13
  installAgentConfig
13
14
  } from '../lib/scaffold-ds-svelte-project.js';
15
+ import { isInteractive, DEFAULTS, logDefaultsNotice } from '../lib/is-interactive.js';
14
16
 
15
17
  // parse flags
16
18
  export function parseFlags(args) {
17
19
  const flags = {
18
20
  pm: null,
19
21
  components: true,
20
- agent: null
22
+ agent: null,
23
+ defaults: false
21
24
  };
22
25
 
23
26
  for (let i = 0; i < args.length; i++) {
@@ -28,6 +31,8 @@ export function parseFlags(args) {
28
31
  flags.components = false;
29
32
  } else if (arg === '--codeagent' && args[i + 1]) {
30
33
  flags.agent = args[++i];
34
+ } else if (arg === '--defaults') {
35
+ flags.defaults = true;
31
36
  }
32
37
  }
33
38
 
@@ -48,6 +53,7 @@ function hasTailwind(projectPath) {
48
53
  // initialize the design system in an existing project
49
54
  export async function init(args) {
50
55
  const flags = parseFlags(args);
56
+ const interactive = isInteractive(flags);
51
57
  const projectPath = process.cwd();
52
58
 
53
59
  // validate --pm flag
@@ -81,17 +87,33 @@ export async function init(args) {
81
87
  }
82
88
  }
83
89
  if (!pmName) {
84
- pmName = await p.select({
85
- message: 'Which package manager?',
86
- options: pmNames.map((n) => ({ value: n, label: n }))
87
- });
88
- if (p.isCancel(pmName)) {
89
- p.cancel('Setup cancelled.');
90
- process.exit(0);
90
+ if (!interactive) {
91
+ pmName = DEFAULTS.pm;
92
+ } else {
93
+ pmName = await p.select({
94
+ message: 'Which package manager?',
95
+ options: pmNames.map((n) => ({
96
+ value: n,
97
+ label: n,
98
+ ...(n === DEFAULTS.pm && { hint: 'default' })
99
+ }))
100
+ });
101
+ if (p.isCancel(pmName)) {
102
+ p.cancel('Setup cancelled.');
103
+ process.exit(0);
104
+ }
91
105
  }
92
106
  }
93
107
  const pm = getPm(pmName);
94
108
 
109
+ // log defaults notice
110
+ if (!interactive) {
111
+ const applied = {};
112
+ if (!flags.pm) applied.pm = pmName;
113
+ if (!flags.agent) applied.codeagent = DEFAULTS.agent;
114
+ logDefaultsNotice(applied);
115
+ }
116
+
95
117
  // verify package manager is installed
96
118
  if (!isPmInstalled(pmName)) {
97
119
  p.log.error(`${pmName} is not installed. Please install it first and try again.`);
@@ -120,6 +142,9 @@ export async function init(args) {
120
142
  const includeFonts = false;
121
143
  prependCss(projectPath, includeFonts);
122
144
 
145
+ // set up linting and formatting
146
+ await installLinting(pm, projectPath);
147
+
123
148
  // install components
124
149
  if (flags.components) {
125
150
  try {
@@ -138,17 +163,21 @@ export async function init(args) {
138
163
  // specify agent config
139
164
  let agent = flags.agent;
140
165
  if (!agent) {
141
- agent = await p.select({
142
- message: 'Install AI agent configuration?',
143
- options: [
144
- { value: 'none', label: 'None' },
145
- { value: 'cursor', label: 'Cursor', hint: 'AGENTS.md + skills + rules' },
146
- { value: 'claude', label: 'Claude Code', hint: 'CLAUDE.md + skills + rules' }
147
- ]
148
- });
149
- if (p.isCancel(agent)) {
150
- p.cancel('Setup cancelled.');
151
- process.exit(0);
166
+ if (!interactive) {
167
+ agent = DEFAULTS.agent;
168
+ } else {
169
+ agent = await p.select({
170
+ message: 'Install AI agent configuration?',
171
+ options: [
172
+ { value: 'none', label: 'None' },
173
+ { value: 'cursor', label: 'Cursor', hint: 'AGENTS.md + skills + rules' },
174
+ { value: 'claude', label: 'Claude Code', hint: 'CLAUDE.md + skills + rules, default' }
175
+ ]
176
+ });
177
+ if (p.isCancel(agent)) {
178
+ p.cancel('Setup cancelled.');
179
+ process.exit(0);
180
+ }
152
181
  }
153
182
  }
154
183
 
@@ -167,6 +196,7 @@ export async function init(args) {
167
196
  `Location: ${projectPath}`,
168
197
  `Package Manager: ${pm.name}`,
169
198
  `Fonts: ${includeFonts ? 'installed' : 'skipped'}`,
199
+ `Linting: configured`,
170
200
  `Components: ${flags.components ? 'all' : 'skipped'}`,
171
201
  `Agent Config: ${agent}`
172
202
  ].join('\n');
package/files/AGENTS.md CHANGED
@@ -28,7 +28,6 @@ Standard Tailwind is fine for:
28
28
 
29
29
  - Spacing/sizing: `p-4`, `w-full`, `gap-2`, `h-screen`
30
30
  - Layout: `flex`, `grid`, `absolute`, `relative`
31
- - One-off colors: gradients, illustrations, custom accents
32
31
 
33
32
  ## CSS Import Order
34
33
 
@@ -49,15 +48,32 @@ Standard Tailwind is fine for:
49
48
 
50
49
  Read these when relevant to your task:
51
50
 
52
- - `@skills/apply-ds` - setup tokens in new project
51
+ - `@skills/apply-ds` - apply DS tokens, components, and patterns to an existing demo
53
52
  - `@skills/build-pattern` - create composite patterns from primitives
54
53
  - `@skills/setup-chart` - set up charts with layerchart
55
54
  - `@skills/create-dashboard` - scaffold a full-viewport dashboard with menubar and panels
56
55
  - `@skills/fix-accessibility` - audit and fix a11y issues
57
56
  - `@skills/fix-metadata` - update page titles, favicons, and OG tags
58
57
  - `@skills/deploy-worker` - deploy SvelteKit projects to Cloudflare Workers
59
- - `@skills/explain-code` - explain code with structure and traced execution
58
+ - `@skills/embedding-from-file` - run an Embedding Lens by streaming sensor data from a CSV file
59
+ - `@skills/embedding-from-sensor` - run an Embedding Lens by streaming real-time data from a physical sensor
60
+ - `@skills/embedding-upload` - run an Embedding Lens by uploading a CSV file for server-side processing
61
+ - `@skills/newton-activity-monitor-lens-on-video` - analyze uploaded video files using Newton's activity monitor lens
62
+ - `@skills/newton-camera-frame-analysis` - live webcam frame analysis using Newton's vision model
63
+ - `@skills/newton-direct-query` - simple direct query to Newton model using the /query API endpoint
64
+ - `@skills/newton-machine-state-from-file` - run a Machine State Lens by streaming sensor data from a CSV file
65
+ - `@skills/newton-machine-state-from-sensor` - run a Machine State Lens by streaming real-time data from a physical sensor
66
+ - `@skills/newton-machine-state-upload` - run a Machine State Lens by uploading a CSV file for server-side processing
60
67
 
61
68
  ## Rules
62
69
 
63
70
  See `@rules/` for comprehensive guidance on design principles, components, styling, charts, and linting.
71
+
72
+ - `@rules/accessibility` — a11y guidelines and ARIA patterns
73
+ - `@rules/charts` — chart setup, layerchart conventions, data visualization
74
+ - `@rules/components` — component API patterns, props, variants, slots
75
+ - `@rules/design-principles` — visual design language, spacing, typography
76
+ - `@rules/frontend-architecture` — component decomposition, page composition, API logic extraction
77
+ - `@rules/linting` — linting and formatting rules
78
+ - `@rules/state` — state management with Svelte 5 runes
79
+ - `@rules/styling` — Tailwind v4, semantic tokens, theming
package/files/CLAUDE.md CHANGED
@@ -28,7 +28,6 @@ Standard Tailwind is fine for:
28
28
 
29
29
  - Spacing/sizing: `p-4`, `w-full`, `gap-2`, `h-screen`
30
30
  - Layout: `flex`, `grid`, `absolute`, `relative`
31
- - One-off colors: gradients, illustrations, custom accents
32
31
 
33
32
  ## CSS Import Order
34
33
 
@@ -47,17 +46,36 @@ Standard Tailwind is fine for:
47
46
 
48
47
  ## Skills
49
48
 
49
+ **Skill composition:** When building a demo that uses a Newton or Embedding API skill, also consult `@skills/create-dashboard` for dashboard layouts and `@skills/build-pattern` for extracting components. Only include chart components (SensorChart, ScatterChart) if the user's request involves time-series or explicitly mentions charts.
50
+
50
51
  Read these when relevant to your task:
51
52
 
52
- - `@skills/apply-ds` - setup tokens in new project
53
+ - `@skills/apply-ds` - apply DS tokens, components, and patterns to an existing demo
53
54
  - `@skills/build-pattern` - create composite patterns from primitives
54
55
  - `@skills/setup-chart` - set up charts with layerchart
55
56
  - `@skills/create-dashboard` - scaffold a full-viewport dashboard with menubar and panels
56
57
  - `@skills/fix-accessibility` - audit and fix a11y issues
57
58
  - `@skills/fix-metadata` - update page titles, favicons, and OG tags
58
59
  - `@skills/deploy-worker` - deploy SvelteKit projects to Cloudflare Workers
59
- - `@skills/explain-code` - explain code with structure and traced execution
60
+ - `@skills/embedding-from-file` - run an Embedding Lens by streaming sensor data from a CSV file
61
+ - `@skills/embedding-from-sensor` - run an Embedding Lens by streaming real-time data from a physical sensor
62
+ - `@skills/embedding-upload` - run an Embedding Lens by uploading a CSV file for server-side processing
63
+ - `@skills/newton-activity-monitor-lens-on-video` - analyze uploaded video files using Newton's activity monitor lens
64
+ - `@skills/newton-camera-frame-analysis` - live webcam frame analysis using Newton's vision model
65
+ - `@skills/newton-direct-query` - simple direct query to Newton model using the /query API endpoint
66
+ - `@skills/newton-machine-state-from-file` - run a Machine State Lens by streaming sensor data from a CSV file
67
+ - `@skills/newton-machine-state-from-sensor` - run a Machine State Lens by streaming real-time data from a physical sensor
68
+ - `@skills/newton-machine-state-upload` - run a Machine State Lens by uploading a CSV file for server-side processing
60
69
 
61
70
  ## Rules
62
71
 
63
72
  See `@rules/` for comprehensive guidance on design principles, components, styling, charts, and linting.
73
+
74
+ - `@rules/accessibility` — a11y guidelines and ARIA patterns
75
+ - `@rules/charts` — chart setup, layerchart conventions, data visualization
76
+ - `@rules/components` — component API patterns, props, variants, slots
77
+ - `@rules/design-principles` — visual design language, spacing, typography
78
+ - `@rules/frontend-architecture` — component decomposition, page composition, API logic extraction
79
+ - `@rules/linting` — linting and formatting rules
80
+ - `@rules/state` — state management with Svelte 5 runes
81
+ - `@rules/styling` — Tailwind v4, semantic tokens, theming
@@ -205,10 +205,59 @@ For custom interactive elements, ensure:
205
205
  >
206
206
  ```
207
207
 
208
+ ## Page Structure
209
+
210
+ ### Skip Link
211
+
212
+ Every page should have a skip link as the first focusable element:
213
+
214
+ ```svelte
215
+ <a
216
+ href="#main-content"
217
+ class="sr-only focus:not-sr-only focus:fixed focus:top-4 focus:left-4 focus:z-50 focus:rounded-md focus:bg-background focus:px-4 focus:py-2 focus:text-foreground focus:ring-2 focus:ring-ring"
218
+ >
219
+ Skip to content
220
+ </a>
221
+
222
+ <main id="main-content">
223
+ <!-- page content -->
224
+ </main>
225
+ ```
226
+
227
+ ### Semantic Landmarks
228
+
229
+ Use semantic HTML elements instead of generic `<div>` wrappers:
230
+
231
+ ```svelte
232
+ <!-- Before -->
233
+ <div class="header">...</div>
234
+ <div class="nav">...</div>
235
+ <div class="content">...</div>
236
+
237
+ <!-- After -->
238
+ <header>...</header>
239
+ <nav>...</nav>
240
+ <main id="main-content">...</main>
241
+ ```
242
+
243
+ ### Heading Hierarchy
244
+
245
+ Every page needs an `<h1>`. If the visual design doesn't include one, add it as screen-reader-only:
246
+
247
+ ```svelte
248
+ <h1 class="sr-only">Dashboard</h1>
249
+ ```
250
+
251
+ Never skip heading levels (e.g. `<h1>` → `<h3>`). Use the correct level for the document outline.
252
+
208
253
  ## Checklist
209
254
 
210
255
  When building components, verify:
211
256
 
257
+ - [ ] Page has a skip-to-content link as the first focusable element
258
+ - [ ] Page uses semantic landmarks (`<main>`, `<header>`, `<nav>`)
259
+ - [ ] Page has an `<h1>` (visible or `sr-only`)
260
+ - [ ] Heading hierarchy doesn't skip levels
212
261
  - [ ] Icon-only buttons have `aria-label`
213
262
  - [ ] Decorative icons have `aria-hidden="true"`
214
263
  - [ ] Interactive groups have `aria-label`
@@ -0,0 +1,77 @@
1
+ ---
2
+ paths:
3
+ - '**/routes/**/*.svelte'
4
+ - '**/+page.svelte'
5
+ - '**/+layout.svelte'
6
+ ---
7
+
8
+ # Frontend Architecture
9
+
10
+ When building a page that involves more than a simple static layout, decompose it into components rather than writing a monolithic `+page.svelte`.
11
+
12
+ ## When to extract a component
13
+
14
+ Extract a component when a section of UI has:
15
+
16
+ - 3+ primitives composed together (Card + Badge + Button = a status card)
17
+ - Its own reactive state (`$state`, `$derived`)
18
+ - Potential for reuse across pages or skills
19
+
20
+ Common extraction candidates: media inputs, status displays, result/summary views, streaming logs, file upload flows.
21
+
22
+ ## Gold-standard references
23
+
24
+ The project includes pattern components that demonstrate these conventions. Study them before building new ones:
25
+
26
+ - `$lib/components/ui/VideoPlayer.svelte` — media playback with controls, composes Card + AspectRatio + Button + Slider
27
+ - `$lib/components/ui/ExpandableLog.svelte` — streaming log display, composes Collapsible + Item + Badge
28
+ - `$lib/components/ui/StatusBadge.svelte` — health indicator with derived state, composes Badge + Avatar
29
+ - `$lib/components/ui/HealthscoreCard.svelte` — score card with derived state, composes Card sub-components
30
+ - `$lib/components/ui/Menubar.svelte` — branded header with snippet slots
31
+
32
+ Check if an existing pattern fits before building a new one. Use `@skills/build-pattern` to create new patterns that follow these same conventions.
33
+
34
+ ## Component conventions
35
+
36
+ Follow `@rules/components` for the full conventions. The essentials:
37
+
38
+ - `let { class: className, ...restProps } = $props();`
39
+ - `cn()` for all class merging — never raw string concatenation
40
+ - `$derived` for computed state
41
+ - Spread `...restProps` on the root element
42
+ - Compose from DS primitives (Card, Badge, Button, etc.) — not raw HTML
43
+
44
+ ## Page-level orchestration
45
+
46
+ `+page.svelte` is the orchestrator. It should:
47
+
48
+ - Own flow state (status, session IDs, error messages)
49
+ - Import and compose child components
50
+ - Pass data down via props
51
+ - Handle top-level layout (using `@skills/create-dashboard` for dashboard layouts)
52
+
53
+ Components should be presentational where possible — receive data via props, emit events up.
54
+
55
+ ## API and streaming logic extraction
56
+
57
+ SSE consumers, fetch wrappers, polling loops, and data transforms belong in a utility file, not inline in components or pages:
58
+
59
+ ```
60
+ src/lib/api/activity-monitor.js — SSE + session management
61
+ src/lib/api/machine-state.js — streaming + windowing
62
+ src/lib/api/embeddings.js — upload + embedding extraction
63
+ ```
64
+
65
+ This keeps components focused on rendering and makes API logic testable and reusable.
66
+
67
+ ## Streaming UI
68
+
69
+ For skills that stream results (SSE, polling), prefer:
70
+
71
+ - Progressive rendering — show results as they arrive, don't wait for completion
72
+ - Live counters or progress indicators
73
+ - Auto-scrolling log views (reference ExpandableLog pattern)
74
+
75
+ ## Override clause
76
+
77
+ If the user explicitly requests a single-file prototype, a minimal example, or specifies a different structure, follow their instruction. These guidelines apply to production-quality demos, not quick experiments.