@archetypeai/ds-cli 0.9.0 → 0.10.0

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 (32) hide show
  1. package/README.md +3 -3
  2. package/commands/create.js +2 -2
  3. package/commands/init.js +2 -2
  4. package/files/AGENTS.md +128 -70
  5. package/files/CLAUDE.md +128 -70
  6. package/files/ds-manifest.json +998 -1023
  7. package/lib/add-ds-config-codeagent.js +3 -57
  8. package/package.json +2 -2
  9. package/files/LICENSE +0 -21
  10. package/files/rules/accessibility.md +0 -268
  11. package/files/rules/charts.md +0 -256
  12. package/files/rules/components.md +0 -251
  13. package/files/rules/design-principles.md +0 -71
  14. package/files/rules/frontend-architecture.md +0 -86
  15. package/files/rules/linting.md +0 -31
  16. package/files/rules/state.md +0 -373
  17. package/files/rules/styling.md +0 -142
  18. package/files/skills/apply-ds/SKILL.md +0 -121
  19. package/files/skills/apply-ds/scripts/audit.sh +0 -169
  20. package/files/skills/apply-ds/scripts/setup.sh +0 -153
  21. package/files/skills/build-component/SKILL.md +0 -153
  22. package/files/skills/create-dashboard/SKILL.md +0 -220
  23. package/files/skills/deploy-worker/SKILL.md +0 -231
  24. package/files/skills/deploy-worker/references/wrangler-commands.md +0 -327
  25. package/files/skills/fix-accessibility/SKILL.md +0 -232
  26. package/files/skills/fix-metadata/SKILL.md +0 -118
  27. package/files/skills/fix-metadata/assets/favicon.ico +0 -0
  28. package/files/skills/setup-chart/SKILL.md +0 -223
  29. package/files/skills/setup-chart/data/embedding.csv +0 -42
  30. package/files/skills/setup-chart/data/timeseries.csv +0 -173
  31. package/files/skills/setup-chart/references/scatter-chart.md +0 -229
  32. package/files/skills/setup-chart/references/sensor-chart.md +0 -156
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, copyFileSync, readdirSync } from 'fs';
1
+ import { existsSync, copyFileSync } from 'fs';
2
2
  import { join, dirname } from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import * as p from '@clack/prompts';
@@ -6,29 +6,6 @@ import * as p from '@clack/prompts';
6
6
  const __dirname = dirname(fileURLToPath(import.meta.url));
7
7
  const FILES_DIR = join(__dirname, '..', 'files');
8
8
 
9
- function copyDir(src, dest) {
10
- if (!existsSync(src)) return;
11
-
12
- mkdirSync(dest, { recursive: true });
13
-
14
- const entries = readdirSync(src, { withFileTypes: true });
15
- for (const entry of entries) {
16
- const srcPath = join(src, entry.name);
17
- const destPath = join(dest, entry.name);
18
-
19
- if (entry.isDirectory()) {
20
- copyDir(srcPath, destPath);
21
- } else {
22
- if (existsSync(destPath)) {
23
- p.log.warn(`Skipped (exists): ${destPath}`);
24
- } else {
25
- copyFileSync(srcPath, destPath);
26
- p.log.success(`Copied: ${destPath}`);
27
- }
28
- }
29
- }
30
- }
31
-
32
9
  function copyFile(src, dest) {
33
10
  if (!existsSync(src)) {
34
11
  p.log.warn(`Source not found: ${src}`);
@@ -52,23 +29,7 @@ export function setupCursor(projectDir) {
52
29
  copyFile(join(FILES_DIR, 'AGENTS.md'), join(projectDir, 'AGENTS.md'));
53
30
  copyFile(join(FILES_DIR, 'ds-manifest.json'), join(projectDir, 'ds-manifest.json'));
54
31
 
55
- // copy skills to .cursor/skills/
56
- const skillsSrc = join(FILES_DIR, 'skills');
57
- if (existsSync(skillsSrc)) {
58
- copyDir(skillsSrc, join(projectDir, '.cursor', 'skills'));
59
- }
60
-
61
- // copy rules to .cursor/rules/
62
- const rulesSrc = join(FILES_DIR, 'rules');
63
- if (existsSync(rulesSrc)) {
64
- copyDir(rulesSrc, join(projectDir, '.cursor', 'rules'));
65
- }
66
-
67
- // copy LICENSE to .cursor/
68
- copyFile(join(FILES_DIR, 'LICENSE'), join(projectDir, '.cursor', 'LICENSE'));
69
-
70
32
  p.log.success('Cursor setup complete');
71
- p.log.info('Tip: Reload Cursor window (Cmd+Shift+P > "Reload Window")');
72
33
  }
73
34
 
74
35
  export function setupClaude(projectDir) {
@@ -78,21 +39,6 @@ export function setupClaude(projectDir) {
78
39
  copyFile(join(FILES_DIR, 'CLAUDE.md'), join(projectDir, 'CLAUDE.md'));
79
40
  copyFile(join(FILES_DIR, 'ds-manifest.json'), join(projectDir, 'ds-manifest.json'));
80
41
 
81
- // copy skills to .claude/skills/
82
- const skillsSrc = join(FILES_DIR, 'skills');
83
- if (existsSync(skillsSrc)) {
84
- copyDir(skillsSrc, join(projectDir, '.claude', 'skills'));
85
- }
86
-
87
- // copy rules to .claude/rules/
88
- const rulesSrc = join(FILES_DIR, 'rules');
89
- if (existsSync(rulesSrc)) {
90
- copyDir(rulesSrc, join(projectDir, '.claude', 'rules'));
91
- }
92
-
93
- // copy LICENSE to .claude/
94
- copyFile(join(FILES_DIR, 'LICENSE'), join(projectDir, '.claude', 'LICENSE'));
95
-
96
42
  p.log.success('Claude Code setup complete');
97
43
  }
98
44
 
@@ -130,8 +76,8 @@ Usage:
130
76
  agent = await p.select({
131
77
  message: 'Which agent configuration?',
132
78
  options: [
133
- { value: 'cursor', label: 'Cursor', hint: 'AGENTS.md + skills + rules' },
134
- { value: 'claude', label: 'Claude Code', hint: 'CLAUDE.md + skills + rules, default' }
79
+ { value: 'cursor', label: 'Cursor', hint: 'AGENTS.md + ds-manifest.json' },
80
+ { value: 'claude', label: 'Claude Code', hint: 'CLAUDE.md + ds-manifest.json, default' }
135
81
  ]
136
82
  });
137
83
  if (p.isCancel(agent)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archetypeai/ds-cli",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Archetype AI Design System CLI Tool",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,7 +21,7 @@
21
21
  "@clack/prompts": "^0.10.0"
22
22
  },
23
23
  "scripts": {
24
- "build": "rm -rf dist && mkdir -p dist/commands dist/lib dist/files && cp bin.js package.json ../LICENSE dist/ && cp commands/*.js dist/commands/ && cp lib/*.js dist/lib/ && cp -r ../ds-config-codeagent/labs/AGENTS.md ../ds-config-codeagent/labs/CLAUDE.md ../ds-config-codeagent/labs/ds-manifest.json dist/files/ && cp -r ../ds-config-codeagent/labs/skills ../ds-config-codeagent/labs/rules dist/files/ && cp ../LICENSE dist/files/ && chmod +x dist/bin.js && cp ../docs/DS-CLI.md dist/README.md"
24
+ "build": "rm -rf dist && mkdir -p dist/commands dist/lib dist/files && cp bin.js package.json ../LICENSE dist/ && cp commands/*.js dist/commands/ && cp lib/*.js dist/lib/ && cp ../ds-config-codeagent/AGENTS.md ../ds-config-codeagent/CLAUDE.md ../ds-config-codeagent/ds-manifest.json dist/files/ && chmod +x dist/bin.js && cp ../docs/DS-CLI.md dist/README.md"
25
25
  },
26
26
  "repository": {
27
27
  "type": "git",
package/files/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Archetype AI
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1,268 +0,0 @@
1
- # Accessibility Rules
2
-
3
- ## Screen Reader Support
4
-
5
- Use `sr-only` for text that should be accessible but visually hidden:
6
-
7
- ```svelte
8
- <button>
9
- <XIcon />
10
- <span class="sr-only">Close</span>
11
- </button>
12
- ```
13
-
14
- The `sr-only` class hides content visually while keeping it accessible to screen readers.
15
-
16
- ## aria-label
17
-
18
- ### Icon-Only Buttons
19
-
20
- Always provide `aria-label` for buttons that only contain an icon:
21
-
22
- ```svelte
23
- <!-- Correct -->
24
- <Button size="icon" aria-label="Search">
25
- <SearchIcon />
26
- </Button>
27
-
28
- <!-- Wrong - no accessible name -->
29
- <Button size="icon">
30
- <SearchIcon />
31
- </Button>
32
- ```
33
-
34
- ### Interactive Groups
35
-
36
- Provide `aria-label` for grouped interactive elements:
37
-
38
- ```svelte
39
- <ButtonGroup.Root aria-label="Pagination">
40
- <Button variant="outline" size="icon-sm" aria-label="Previous page">
41
- <ChevronLeftIcon />
42
- </Button>
43
- <ButtonGroup.Text>Page 1 of 10</ButtonGroup.Text>
44
- <Button variant="outline" size="icon-sm" aria-label="Next page">
45
- <ChevronRightIcon />
46
- </Button>
47
- </ButtonGroup.Root>
48
- ```
49
-
50
- ### Toggles
51
-
52
- ```svelte
53
- <Toggle variant="outline" aria-label="Toggle bookmark">
54
- <BookmarkIcon />
55
- </Toggle>
56
- ```
57
-
58
- ## aria-hidden
59
-
60
- Use `aria-hidden="true"` for decorative elements that shouldn't be announced:
61
-
62
- ```svelte
63
- <!-- Decorative icons next to text -->
64
- <CardTitle>Sensor Status</CardTitle>
65
- <Icon strokeWidth={1.25} class="text-muted-foreground size-6" aria-hidden="true" />
66
-
67
- <!-- Icons in buttons WITH text labels -->
68
- <Button>
69
- <PlusIcon aria-hidden="true" />
70
- Add Item
71
- </Button>
72
- ```
73
-
74
- Do NOT use `aria-hidden` on icon-only buttons - use `aria-label` instead.
75
-
76
- ## role Attributes
77
-
78
- ### Lists
79
-
80
- ```svelte
81
- <div role="list" data-slot="item-group">
82
- {#each items as item}
83
- <div role="listitem">{item}</div>
84
- {/each}
85
- </div>
86
- ```
87
-
88
- ### Groups
89
-
90
- ```svelte
91
- <!-- Input groups -->
92
- <div role="group" data-slot="input-group">
93
- <Input />
94
- <Button>Submit</Button>
95
- </div>
96
-
97
- <!-- Button groups -->
98
- <div role="group" aria-label="Text formatting" data-slot="button-group">
99
- <Button>Bold</Button>
100
- <Button>Italic</Button>
101
- </div>
102
- ```
103
-
104
- ### Status
105
-
106
- ```svelte
107
- <!-- Spinners/loading indicators -->
108
- <svg role="status" aria-label="Loading" class="animate-spin"> ... </svg>
109
- ```
110
-
111
- ## aria-invalid Styling
112
-
113
- Components use `aria-invalid` for validation states. The styling pattern:
114
-
115
- ```svelte
116
- <input
117
- class={cn(
118
- 'border-input focus-visible:ring-ring/50',
119
- 'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40',
120
- 'aria-invalid:border-destructive'
121
- )}
122
- aria-invalid={hasError}
123
- />
124
- ```
125
-
126
- This applies to: inputs, textareas, selects, checkboxes, buttons, badges.
127
-
128
- To trigger invalid styling, set `aria-invalid="true"`:
129
-
130
- ```svelte
131
- <Input aria-invalid={errors.email ? true : undefined} />
132
- ```
133
-
134
- ## Focus Management
135
-
136
- ### Focus Ring Pattern
137
-
138
- All interactive elements use this focus pattern:
139
-
140
- ```svelte
141
- <button
142
- class={cn(
143
- 'outline-none',
144
- 'focus-visible:border-ring',
145
- 'focus-visible:ring-ring/50',
146
- 'focus-visible:ring-[3px]'
147
- )}
148
- >
149
- ```
150
-
151
- Key points:
152
-
153
- - `outline-none` removes default browser outline
154
- - `focus-visible` only shows on keyboard focus (not mouse clicks)
155
- - `ring-[3px]` provides visible focus indicator
156
- - `ring-ring/50` uses semantic ring color at 50% opacity
157
-
158
- ### Disabled States
159
-
160
- ```svelte
161
- <button
162
- class="disabled:pointer-events-none disabled:opacity-50"
163
- disabled={isDisabled}
164
- >
165
- ```
166
-
167
- For links styled as buttons (can't use `disabled` attribute):
168
-
169
- ```svelte
170
- <a
171
- href={disabled ? undefined : href}
172
- aria-disabled={disabled}
173
- role={disabled ? 'button' : undefined}
174
- tabindex={disabled ? -1 : undefined}
175
- class="aria-disabled:pointer-events-none aria-disabled:opacity-50"
176
- >
177
- ```
178
-
179
- ## Keyboard Navigation
180
-
181
- ### bits-ui Primitives
182
-
183
- Most keyboard navigation is handled automatically by bits-ui:
184
-
185
- - Dialog: Escape to close, focus trap
186
- - Select: Arrow keys to navigate, Enter to select
187
- - Command: Arrow keys, Enter, type to filter
188
- - Popover: Escape to close
189
-
190
- ### Custom Components
191
-
192
- For custom interactive elements, ensure:
193
-
194
- ```svelte
195
- <div
196
- role="button"
197
- tabindex="0"
198
- onkeydown={(e) => {
199
- if (e.key === 'Enter' || e.key === ' ') {
200
- e.preventDefault();
201
- handleClick();
202
- }
203
- }}
204
- onclick={handleClick}
205
- >
206
- ```
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
-
253
- ## Checklist
254
-
255
- When building components, verify:
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
261
- - [ ] Icon-only buttons have `aria-label`
262
- - [ ] Decorative icons have `aria-hidden="true"`
263
- - [ ] Interactive groups have `aria-label`
264
- - [ ] Form inputs support `aria-invalid` styling
265
- - [ ] Focus states are visible (`focus-visible:ring-*`)
266
- - [ ] Disabled states prevent interaction and reduce opacity
267
- - [ ] Loading states use `role="status"`
268
- - [ ] Lists use `role="list"` and `role="listitem"`
@@ -1,256 +0,0 @@
1
- ---
2
- paths:
3
- - '**/chart*/**'
4
- - '**/*Chart*.svelte'
5
- - '**/*chart*.svelte'
6
- - '**/dashboard*/**'
7
- ---
8
-
9
- # Chart Rules
10
-
11
- ## Stack
12
-
13
- - **layerchart** - Svelte-native charting library
14
- - **d3-scale** - Scale functions (scaleUtc, scaleLinear, scaleOrdinal)
15
- - **d3-shape** - Curve functions (curveNatural, curveMonotoneX)
16
- - **Chart primitives** - Chart.Container, Chart.Tooltip from the design system
17
-
18
- ## Chart.Container
19
-
20
- Wrap all charts in Chart.Container with a config prop:
21
-
22
- ```svelte
23
- <script>
24
- import * as Chart from '@archetypeai/ds-ui-svelte-labs/primitives/chart';
25
-
26
- const chartConfig = {
27
- temperature: { label: 'Temperature', color: 'var(--chart-1)' },
28
- humidity: { label: 'Humidity', color: 'var(--chart-2)' }
29
- };
30
- </script>
31
-
32
- <Chart.Container config={chartConfig} class="aspect-auto h-[220px] w-full">
33
- <LineChart ... />
34
- </Chart.Container>
35
- ```
36
-
37
- The config maps data keys to labels and colors for tooltips and legends.
38
-
39
- ## Chart Token Colors
40
-
41
- Use semantic chart colors from the theme:
42
-
43
- ```javascript
44
- const chartColors = [
45
- 'var(--chart-1)', // purple
46
- 'var(--chart-2)', // red-orange
47
- 'var(--chart-3)', // green
48
- 'var(--chart-4)', // yellow
49
- 'var(--chart-5)' // coral
50
- ];
51
- ```
52
-
53
- In series config:
54
-
55
- ```javascript
56
- let series = $derived(
57
- Object.entries(signals).map(([key, label], i) => ({
58
- key,
59
- label,
60
- color: chartColors[i % chartColors.length]
61
- }))
62
- );
63
- ```
64
-
65
- ## LineChart Pattern
66
-
67
- Basic line chart with layerchart:
68
-
69
- ```svelte
70
- <script>
71
- import { LineChart } from 'layerchart';
72
- import { scaleUtc, scaleLinear } from 'd3-scale';
73
- import { curveNatural } from 'd3-shape';
74
-
75
- let { data, xKey = 'timestamp', yMin, yMax } = $props();
76
-
77
- const series = [{ key: 'value', label: 'Value', color: 'var(--chart-1)' }];
78
- </script>
79
-
80
- <LineChart
81
- {data}
82
- x={xKey}
83
- xScale={scaleUtc()}
84
- yScale={scaleLinear()}
85
- yDomain={[yMin, yMax]}
86
- {series}
87
- props={{
88
- spline: {
89
- curve: curveNatural,
90
- strokeWidth: 1.5
91
- }
92
- }}
93
- />
94
- ```
95
-
96
- ## D3 Scale Usage
97
-
98
- ### Time Scale (x-axis with dates)
99
-
100
- ```javascript
101
- import { scaleUtc } from 'd3-scale';
102
-
103
- xScale={scaleUtc()}
104
- ```
105
-
106
- ### Linear Scale (numeric values)
107
-
108
- ```javascript
109
- import { scaleLinear } from 'd3-scale';
110
-
111
- yScale={scaleLinear()}
112
- yDomain={[0, 100]} // fixed domain
113
- ```
114
-
115
- ### Dynamic Domain
116
-
117
- ```javascript
118
- // Let layerchart calculate domain from data
119
- yDomain = { undefined };
120
-
121
- // Or calculate manually
122
- let yDomain = $derived([
123
- Math.min(...data.map((d) => d.value)),
124
- Math.max(...data.map((d) => d.value))
125
- ]);
126
- ```
127
-
128
- ## Streaming Data Pattern
129
-
130
- For real-time charts with a sliding window:
131
-
132
- ```svelte
133
- <script>
134
- let {
135
- data = [],
136
- maxPoints = 100, // sliding window size
137
- ...
138
- } = $props();
139
-
140
- // Slice to maxPoints for display
141
- let displayData = $derived(
142
- maxPoints && data.length > maxPoints
143
- ? data.slice(-maxPoints)
144
- : data
145
- );
146
-
147
- // Add index for stable x positioning
148
- let indexedData = $derived(
149
- displayData.map((d, i) => ({ ...d, _index: i }))
150
- );
151
-
152
- // Use index-based x scale for streaming
153
- let useIndexX = $derived(maxPoints !== undefined);
154
- let xDomain = $derived(
155
- useIndexX ? [0, (maxPoints || displayData.length) - 1] : undefined
156
- );
157
- </script>
158
-
159
- <LineChart
160
- data={indexedData}
161
- x={useIndexX ? '_index' : xKey}
162
- xScale={useIndexX ? scaleLinear() : scaleUtc()}
163
- {xDomain}
164
- ...
165
- />
166
- ```
167
-
168
- ## Axis Configuration
169
-
170
- ```svelte
171
- <LineChart
172
- axis="both" // "both" | "x" | "y" | "none"
173
- props={{
174
- xAxis: {
175
- format: (date) => formatTime(date) // custom formatter
176
- },
177
- yAxis: {
178
- ticks: [0, 25, 50, 75, 100], // explicit tick values
179
- format: (v) => `${v}%`
180
- },
181
- grid: {
182
- y: true,
183
- x: false,
184
- yTicks: [0, 25, 50, 75, 100]
185
- }
186
- }}
187
- />
188
- ```
189
-
190
- ## Time Formatting
191
-
192
- Relative time (MM:SS from start):
193
-
194
- ```javascript
195
- let baseTimestamp = $derived(
196
- displayData.length > 0 && displayData[0][xKey] ? new Date(displayData[0][xKey]).getTime() : 0
197
- );
198
-
199
- function formatTime(date) {
200
- if (!baseTimestamp) return '00:00';
201
- const diffMs = date.getTime() - baseTimestamp;
202
- const totalSeconds = Math.floor(diffMs / 1000);
203
- const minutes = Math.floor(totalSeconds / 60);
204
- const seconds = totalSeconds % 60;
205
- return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
206
- }
207
- ```
208
-
209
- ## Curve Types
210
-
211
- ```javascript
212
- import { curveNatural, curveMonotoneX, curveStep, curveLinear } from 'd3-shape';
213
-
214
- props={{
215
- spline: {
216
- curve: curveNatural, // smooth natural curve
217
- // curve: curveMonotoneX, // monotonic (no overshooting)
218
- // curve: curveStep, // stepped line
219
- // curve: curveLinear, // straight lines
220
- strokeWidth: 1.5
221
- }
222
- }}
223
- ```
224
-
225
- ## Tooltip Integration
226
-
227
- ```svelte
228
- <LineChart
229
- tooltip={false} // disable default tooltip
230
- ...
231
- />
232
-
233
- <!-- Or use custom Chart.Tooltip -->
234
- <Chart.Container config={chartConfig}>
235
- <LineChart tooltip={{ ... }} />
236
- </Chart.Container>
237
- ```
238
-
239
- ## Legend Pattern
240
-
241
- Manual legend below chart:
242
-
243
- ```svelte
244
- {#if series.length > 0}
245
- <div class="flex items-center justify-center gap-10">
246
- {#each series as s (s.key)}
247
- <div class="flex items-center gap-2">
248
- <div class="size-2 rounded-full bg-(--legend-color)" style:--legend-color={s.color}></div>
249
- <span class="text-foreground text-sm">{s.label}</span>
250
- </div>
251
- {/each}
252
- </div>
253
- {/if}
254
- ```
255
-
256
- For a complete chart example, see the `sensor-chart` component (import from `@archetypeai/ds-ui-svelte-labs/primitives/sensor-chart`; editable source at https://design-system-labs.archetypeai.workers.dev/r/sensor-chart.json).