@poodle64/librarian 2026.10.3 → 2026.10.5

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 (54) hide show
  1. package/README.md +12 -4
  2. package/dist/chat.svelte.d.ts +40 -0
  3. package/dist/chat.svelte.js +57 -3
  4. package/dist/client.d.ts +3 -0
  5. package/dist/client.js +4 -1
  6. package/dist/components/activity-group/activity-group.svelte +72 -77
  7. package/dist/components/activity-group/activity-group.svelte.d.ts +6 -1
  8. package/dist/components/agent-transcript/agent-transcript.svelte +225 -9
  9. package/dist/components/agent-transcript/agent-transcript.svelte.d.ts +19 -0
  10. package/dist/components/artefact-pane/artefact-pane.svelte +387 -43
  11. package/dist/components/artefact-pane/artefact-pane.svelte.d.ts +10 -1
  12. package/dist/components/conversation/conversation.svelte +74 -4
  13. package/dist/components/conversation/conversation.svelte.d.ts +15 -0
  14. package/dist/components/markdown/markdown.d.ts +30 -2
  15. package/dist/components/markdown/markdown.js +142 -5
  16. package/dist/components/markdown/markdown.svelte +130 -16
  17. package/dist/components/markdown/markdown.svelte.d.ts +12 -0
  18. package/dist/components/source-fold/index.d.ts +2 -0
  19. package/dist/components/source-fold/index.js +2 -0
  20. package/dist/components/source-fold/source-fold.svelte +225 -0
  21. package/dist/components/source-fold/source-fold.svelte.d.ts +17 -0
  22. package/dist/components/source-list/source-list.svelte +5 -3
  23. package/dist/components/source-list/source-list.svelte.d.ts +2 -0
  24. package/dist/components/thinking-row/thinking-row.svelte +38 -16
  25. package/dist/components/thinking-row/thinking-row.svelte.d.ts +3 -0
  26. package/dist/components/tool-row/tool-row.svelte +161 -49
  27. package/dist/components/tool-row/tool-row.svelte.d.ts +2 -3
  28. package/dist/components/visual-block/entity-card.svelte +94 -0
  29. package/dist/components/visual-block/entity-card.svelte.d.ts +7 -0
  30. package/dist/components/visual-block/timeline.svelte +99 -0
  31. package/dist/components/visual-block/timeline.svelte.d.ts +7 -0
  32. package/dist/components/visual-block/vega-chart.svelte +110 -0
  33. package/dist/components/visual-block/vega-chart.svelte.d.ts +7 -0
  34. package/dist/components/visual-block/visual-block.svelte +53 -0
  35. package/dist/components/visual-block/visual-block.svelte.d.ts +13 -0
  36. package/dist/components/written-card/index.d.ts +2 -0
  37. package/dist/components/written-card/index.js +2 -0
  38. package/dist/components/written-card/written-card.svelte +178 -0
  39. package/dist/components/written-card/written-card.svelte.d.ts +15 -0
  40. package/dist/copy.d.ts +73 -0
  41. package/dist/copy.js +56 -0
  42. package/dist/diff.d.ts +8 -0
  43. package/dist/diff.js +37 -0
  44. package/dist/steps.d.ts +28 -0
  45. package/dist/steps.js +168 -0
  46. package/dist/transcript.svelte.d.ts +19 -34
  47. package/dist/transcript.svelte.js +64 -56
  48. package/dist/visuals.d.ts +38 -0
  49. package/dist/visuals.js +82 -0
  50. package/dist/web-sources.d.ts +34 -0
  51. package/dist/web-sources.js +90 -0
  52. package/dist/written.d.ts +71 -0
  53. package/dist/written.js +83 -0
  54. package/package.json +14 -1
@@ -1,40 +1,68 @@
1
1
  <!--
2
- One tool call, as a row in the transcript.
2
+ One tool call, as a row in the turn's fold.
3
3
 
4
- Not a card: a status dot, an action in bold, its target beside it, one
5
- dimmed sub-line. Borders and badge pills make a run of five calls read as
6
- five separate events rather than one train of thought.
7
-
8
- The row says what the persona DID, in a reader's own words — never the
9
- tool's name or the raw command it ran: the host's `DescribeTool`, or the
10
- package's own `describe()`. Expanding a settled row shows what came back,
11
- never what was typed.
4
+ Not a card: the kind of step as an icon, an action in bold, its target
5
+ beside it, and at the right hand what the result counted ("9 found") and a
6
+ chevron. The row says what the persona DID, in a reader's own words — never
7
+ the tool's name or the raw command it ran: the host's `DescribeTool`, or the
8
+ package's own `describe()`. Expanding it shows what went in and what came
9
+ back, the way Claude Code's own panel does; a web search shows the pages it
10
+ found instead, as small cards.
12
11
  -->
13
12
  <script lang="ts">
13
+ import BookOpenTextIcon from '@lucide/svelte/icons/book-open-text';
14
14
  import ChevronRightIcon from '@lucide/svelte/icons/chevron-right';
15
+ import CircleIcon from '@lucide/svelte/icons/circle';
16
+ import FilePenIcon from '@lucide/svelte/icons/file-pen';
17
+ import FileTextIcon from '@lucide/svelte/icons/file-text';
18
+ import GlobeIcon from '@lucide/svelte/icons/globe';
19
+ import LibraryIcon from '@lucide/svelte/icons/library';
20
+ import PencilIcon from '@lucide/svelte/icons/pencil';
15
21
  import { describe, type ToolBlock, type ToolWords } from '../../transcript.svelte';
22
+ import { resolveCopy, type ChatCopy } from '../../copy';
23
+ import { stepKind, stepMeta, type StepKind } from '../../steps';
24
+ import { searchPages } from '../../web-sources';
16
25
 
17
26
  interface Props {
18
27
  block: ToolBlock;
19
28
  running: boolean;
20
- /** Identical consecutive steps folded into this row — five pages of one
21
- * document is one act of reading to a human. */
22
- repeats?: number;
23
29
  /** The call in the persona's words. Absent takes the package's own. */
24
30
  words?: ToolWords;
31
+ copy?: Partial<ChatCopy>;
25
32
  }
26
33
 
27
- let { block, running, repeats = 1, words }: Props = $props();
34
+ let { block, running, words, copy }: Props = $props();
28
35
  let open = $state(false);
29
36
 
37
+ const ICONS = {
38
+ attachment: FileTextIcon,
39
+ library: LibraryIcon,
40
+ web: GlobeIcon,
41
+ open: BookOpenTextIcon,
42
+ write: FilePenIcon,
43
+ edit: PencilIcon,
44
+ thought: CircleIcon,
45
+ other: CircleIcon
46
+ } satisfies Record<StepKind, unknown>;
47
+
48
+ const label = $derived(resolveCopy(copy));
30
49
  const settled = $derived(block.result !== undefined);
31
50
  const said = $derived(words ?? describe(block));
51
+ const Icon = $derived(ICONS[stepKind(block)]);
52
+ const meta = $derived(stepMeta(block, label));
53
+ const pages = $derived(searchPages(block));
32
54
  const tone = $derived(block.isError ? 'error' : settled ? 'success' : 'info');
33
- const again = $derived(
34
- said.repeat
35
- ? `· ${repeats} ${repeats === 1 ? said.repeat[0] : said.repeat[1]}`
36
- : `× ${repeats}`
37
- );
55
+ // Pretty while it parses; the raw text while it is still arriving.
56
+ const input = $derived.by(() => {
57
+ try {
58
+ const parsed: unknown = JSON.parse(block.rawInput);
59
+ return typeof parsed === 'object' && parsed !== null && Object.keys(parsed).length === 0
60
+ ? ''
61
+ : JSON.stringify(parsed, null, 2);
62
+ } catch {
63
+ return block.rawInput;
64
+ }
65
+ });
38
66
  </script>
39
67
 
40
68
  <div class="ds-lib-tool">
@@ -42,24 +70,45 @@
42
70
  type="button"
43
71
  class="ds-lib-tool-row"
44
72
  onclick={() => (open = !open)}
45
- disabled={!settled}
46
73
  aria-expanded={open}
47
74
  >
48
- <span class="ds-lib-tool-chevron" class:is-open={open}>
49
- {#if settled}<ChevronRightIcon size={12} />{/if}
75
+ <span class="ds-lib-tool-icon" data-tone={tone} class:is-live={!settled && running} aria-hidden="true">
76
+ <Icon size={14} />
50
77
  </span>
51
- <span class="ds-lib-tool-dot" data-tone={tone} class:is-live={!settled && running}></span>
52
78
  <span class="ds-lib-tool-said">
53
79
  <span class="ds-lib-tool-verb">{said.verb}</span>
54
80
  {#if said.object}<span class="ds-lib-tool-object">{said.object}</span>{/if}
55
- {#if repeats > 1}<span>{again}</span>{/if}
81
+ </span>
82
+ {#if block.isError}<span class="ds-lib-tool-failed">{label.stepFailed}</span>{/if}
83
+ {#if meta}<span class="ds-lib-tool-meta">{meta}</span>{/if}
84
+ <span class="ds-lib-tool-chevron" class:is-open={open} aria-hidden="true">
85
+ <ChevronRightIcon size={14} />
56
86
  </span>
57
87
  </button>
58
88
 
59
89
  {#if open}
60
90
  <div class="ds-lib-tool-body">
61
- {#if block.result}
62
- <pre class="ds-lib-tool-result">{block.result}</pre>
91
+ {#if pages.length > 0}
92
+ <ul class="ds-lib-tool-pages">
93
+ {#each pages as page (page.url)}
94
+ <li>
95
+ <a class="ds-lib-tool-page" href={page.url} target="_blank" rel="noopener noreferrer">
96
+ <span class="ds-lib-tool-page-host"><GlobeIcon size={11} />{page.host}</span>
97
+ <span class="ds-lib-tool-page-title">{page.title}</span>
98
+ {#if page.snippet}<span class="ds-lib-tool-page-snippet">{page.snippet}</span>{/if}
99
+ </a>
100
+ </li>
101
+ {/each}
102
+ </ul>
103
+ {:else}
104
+ {#if input}
105
+ <p class="ds-lib-tool-label">{label.stepInput}</p>
106
+ <pre class="ds-lib-tool-result">{input}</pre>
107
+ {/if}
108
+ {#if block.result}
109
+ <p class="ds-lib-tool-label">{label.stepResult}</p>
110
+ <pre class="ds-lib-tool-result">{block.result}</pre>
111
+ {/if}
63
112
  {/if}
64
113
  </div>
65
114
  {:else if said.detail}
@@ -77,8 +126,8 @@
77
126
  display: flex;
78
127
  width: 100%;
79
128
  align-items: center;
80
- gap: 0.5rem;
81
- padding-block: 0.125rem;
129
+ gap: 0.625rem;
130
+ padding-block: 0.25rem;
82
131
  border: 0;
83
132
  background: none;
84
133
  color: inherit;
@@ -87,16 +136,23 @@
87
136
  cursor: pointer;
88
137
  }
89
138
 
90
- .ds-lib-tool-row:disabled {
91
- cursor: default;
139
+ .ds-lib-tool-icon {
140
+ display: flex;
141
+ flex: none;
142
+ color: var(--ds-color-muted-foreground);
143
+ }
144
+
145
+ .ds-lib-tool-icon[data-tone='error'] {
146
+ color: var(--ds-color-status-error);
147
+ }
148
+
149
+ .ds-lib-tool-icon.is-live {
150
+ animation: ds-lib-tool-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
92
151
  }
93
152
 
94
153
  .ds-lib-tool-chevron {
95
154
  display: flex;
96
- width: 0.75rem;
97
155
  flex: none;
98
- align-items: center;
99
- justify-content: center;
100
156
  color: var(--ds-color-muted-foreground);
101
157
  transition: transform 150ms ease;
102
158
  }
@@ -105,28 +161,70 @@
105
161
  transform: rotate(90deg);
106
162
  }
107
163
 
108
- .ds-lib-tool-dot {
109
- width: 0.375rem;
110
- height: 0.375rem;
164
+ .ds-lib-tool-meta {
111
165
  flex: none;
112
- border-radius: var(--ds-radius-full);
113
- background: var(--ds-color-status-info);
166
+ color: var(--ds-color-muted-foreground);
167
+ font-size: var(--ds-text-2xs);
168
+ font-variant-numeric: tabular-nums;
114
169
  }
115
170
 
116
- .ds-lib-tool-dot[data-tone='error'] {
117
- background: var(--ds-color-status-error);
171
+ .ds-lib-tool-pages {
172
+ display: grid;
173
+ grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
174
+ gap: 0.5rem;
175
+ margin: 0;
176
+ padding: 0;
177
+ list-style: none;
118
178
  }
119
179
 
120
- .ds-lib-tool-dot[data-tone='success'] {
121
- background: var(--ds-color-status-success);
180
+ .ds-lib-tool-page {
181
+ display: flex;
182
+ height: 100%;
183
+ flex-direction: column;
184
+ gap: 0.125rem;
185
+ border: 1px solid var(--ds-color-border);
186
+ border-radius: var(--ds-radius-md);
187
+ padding: 0.5rem 0.625rem;
188
+ color: inherit;
189
+ font-size: var(--ds-text-2xs);
190
+ line-height: 1.35;
191
+ text-decoration: none;
122
192
  }
123
193
 
124
- .ds-lib-tool-dot.is-live {
125
- animation: ds-lib-tool-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
194
+ .ds-lib-tool-page:hover {
195
+ background: var(--ds-color-surface-2);
196
+ }
197
+
198
+ .ds-lib-tool-page:focus-visible {
199
+ outline: 2px solid var(--ds-color-ring);
200
+ outline-offset: 2px;
201
+ }
202
+
203
+ .ds-lib-tool-page-host {
204
+ display: flex;
205
+ align-items: center;
206
+ gap: 0.25rem;
207
+ color: var(--ds-color-muted-foreground);
208
+ }
209
+
210
+ .ds-lib-tool-page-title {
211
+ color: var(--ds-color-foreground);
212
+ font-size: 0.8125rem;
213
+ font-weight: 600;
214
+ }
215
+
216
+ .ds-lib-tool-page-snippet {
217
+ display: -webkit-box;
218
+ -webkit-box-orient: vertical;
219
+ -webkit-line-clamp: 3;
220
+ line-clamp: 3;
221
+ overflow: hidden;
222
+ color: var(--ds-color-muted-foreground);
126
223
  }
127
224
 
128
225
  .ds-lib-tool-said {
129
226
  min-width: 0;
227
+ flex: 1;
130
228
  overflow: hidden;
131
229
  text-overflow: ellipsis;
132
230
  white-space: nowrap;
@@ -144,14 +242,14 @@
144
242
 
145
243
  .ds-lib-tool-body {
146
244
  margin-top: 0.25rem;
147
- margin-inline-start: 1.375rem;
245
+ margin-inline-start: 0.4375rem;
148
246
  border-inline-start: 1px solid var(--ds-color-border);
149
- padding-inline-start: 0.75rem;
247
+ padding-inline-start: 1rem;
150
248
  }
151
249
 
152
250
  .ds-lib-tool-result {
153
- margin-top: 0.25rem;
154
- max-height: 18rem;
251
+ margin: 0.125rem 0 0;
252
+ max-height: 12rem;
155
253
  overflow: auto;
156
254
  color: var(--ds-color-muted-foreground);
157
255
  font-family: var(--ds-font-code);
@@ -159,9 +257,23 @@
159
257
  white-space: pre-wrap;
160
258
  }
161
259
 
260
+ .ds-lib-tool-label {
261
+ margin: 0.25rem 0 0;
262
+ color: var(--ds-color-muted-foreground);
263
+ font-size: var(--ds-text-2xs);
264
+ letter-spacing: 0.05em;
265
+ text-transform: uppercase;
266
+ }
267
+
268
+ .ds-lib-tool-failed {
269
+ flex: none;
270
+ color: var(--ds-color-status-error);
271
+ font-size: var(--ds-text-2xs);
272
+ }
273
+
162
274
  .ds-lib-tool-lines {
163
275
  margin: 0;
164
- padding-inline-start: 1.375rem;
276
+ padding-inline-start: 1.5rem;
165
277
  color: var(--ds-color-muted-foreground);
166
278
  font-size: var(--ds-text-2xs);
167
279
  }
@@ -173,7 +285,7 @@
173
285
  }
174
286
 
175
287
  @media (prefers-reduced-motion: reduce) {
176
- .ds-lib-tool-dot.is-live {
288
+ .ds-lib-tool-icon.is-live {
177
289
  animation: none;
178
290
  }
179
291
 
@@ -1,12 +1,11 @@
1
1
  import { type ToolBlock, type ToolWords } from '../../transcript.svelte';
2
+ import { type ChatCopy } from '../../copy';
2
3
  interface Props {
3
4
  block: ToolBlock;
4
5
  running: boolean;
5
- /** Identical consecutive steps folded into this row — five pages of one
6
- * document is one act of reading to a human. */
7
- repeats?: number;
8
6
  /** The call in the persona's words. Absent takes the package's own. */
9
7
  words?: ToolWords;
8
+ copy?: Partial<ChatCopy>;
10
9
  }
11
10
  declare const ToolRow: import("svelte").Component<Props, {}, "">;
12
11
  type ToolRow = ReturnType<typeof ToolRow>;
@@ -0,0 +1,94 @@
1
+ <!--
2
+ One thing, its kind, a line on it, and its facts.
3
+ -->
4
+ <script lang="ts">
5
+ import type { CardValue } from '../../visuals';
6
+
7
+ interface Props {
8
+ value: unknown;
9
+ source: string;
10
+ }
11
+
12
+ let { value }: Props = $props();
13
+ const card = $derived(value as CardValue);
14
+ </script>
15
+
16
+ <figure class="ds-lib-entity">
17
+ <header class="ds-lib-entity-head">
18
+ <span class="ds-lib-entity-name">{card.name}</span>
19
+ {#if card.kind}<span class="ds-lib-entity-kind">{card.kind}</span>{/if}
20
+ </header>
21
+ {#if card.summary}<p class="ds-lib-entity-summary">{card.summary}</p>{/if}
22
+ {#if card.facts.length > 0}
23
+ <dl class="ds-lib-entity-facts">
24
+ {#each card.facts as fact, i (i)}
25
+ <div class="ds-lib-entity-fact">
26
+ <dt>{fact.label}</dt>
27
+ <dd>{fact.value}</dd>
28
+ </div>
29
+ {/each}
30
+ </dl>
31
+ {/if}
32
+ </figure>
33
+
34
+ <style>
35
+ .ds-lib-entity {
36
+ margin: 0.75em 0;
37
+ border: 1px solid var(--ds-color-border);
38
+ border-radius: var(--ds-radius-lg);
39
+ background: var(--ds-color-surface-1);
40
+ padding: 0.75rem 1rem;
41
+ font-size: 0.875rem;
42
+ line-height: 1.5;
43
+ }
44
+
45
+ .ds-lib-entity-head {
46
+ display: flex;
47
+ flex-wrap: wrap;
48
+ align-items: baseline;
49
+ gap: 0.25rem 0.625rem;
50
+ }
51
+
52
+ .ds-lib-entity-name {
53
+ color: var(--ds-color-foreground);
54
+ font-size: 1rem;
55
+ font-weight: 600;
56
+ }
57
+
58
+ .ds-lib-entity-kind {
59
+ color: var(--ds-color-muted-foreground);
60
+ font-size: var(--ds-text-2xs);
61
+ letter-spacing: 0.05em;
62
+ text-transform: uppercase;
63
+ }
64
+
65
+ .ds-lib-entity-summary {
66
+ margin: 0.25rem 0 0;
67
+ color: var(--ds-color-muted-foreground);
68
+ }
69
+
70
+ .ds-lib-entity-facts {
71
+ display: grid;
72
+ grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
73
+ gap: 0.5rem 1rem;
74
+ margin: 0.625rem 0 0;
75
+ border-top: 1px solid var(--ds-color-border);
76
+ padding-top: 0.625rem;
77
+ }
78
+
79
+ .ds-lib-entity-fact {
80
+ margin: 0;
81
+ }
82
+
83
+ .ds-lib-entity-fact dt {
84
+ color: var(--ds-color-muted-foreground);
85
+ font-size: var(--ds-text-2xs);
86
+ }
87
+
88
+ .ds-lib-entity-fact dd {
89
+ margin: 0;
90
+ color: var(--ds-color-foreground);
91
+ font-weight: 500;
92
+ overflow-wrap: anywhere;
93
+ }
94
+ </style>
@@ -0,0 +1,7 @@
1
+ interface Props {
2
+ value: unknown;
3
+ source: string;
4
+ }
5
+ declare const EntityCard: import("svelte").Component<Props, {}, "">;
6
+ type EntityCard = ReturnType<typeof EntityCard>;
7
+ export default EntityCard;
@@ -0,0 +1,99 @@
1
+ <!--
2
+ A run of dated events, as a vertical line with a mark at each.
3
+ -->
4
+ <script lang="ts">
5
+ import type { TimelineValue } from '../../visuals';
6
+
7
+ interface Props {
8
+ value: unknown;
9
+ source: string;
10
+ }
11
+
12
+ let { value }: Props = $props();
13
+ const timeline = $derived(value as TimelineValue);
14
+ </script>
15
+
16
+ <figure class="ds-lib-timeline">
17
+ {#if timeline.title}<figcaption class="ds-lib-timeline-title">{timeline.title}</figcaption>{/if}
18
+ <ol class="ds-lib-timeline-events">
19
+ {#each timeline.events as event, i (i)}
20
+ <li class="ds-lib-timeline-event">
21
+ {#if event.date}<time class="ds-lib-timeline-date">{event.date}</time>{/if}
22
+ <span class="ds-lib-timeline-name">{event.title}</span>
23
+ {#if event.detail}<span class="ds-lib-timeline-detail">{event.detail}</span>{/if}
24
+ </li>
25
+ {/each}
26
+ </ol>
27
+ </figure>
28
+
29
+ <style>
30
+ .ds-lib-timeline {
31
+ margin: 0.75em 0;
32
+ border: 1px solid var(--ds-color-border);
33
+ border-radius: var(--ds-radius-lg);
34
+ background: var(--ds-color-surface-1);
35
+ padding: 0.75rem 1rem;
36
+ font-size: 0.875rem;
37
+ line-height: 1.5;
38
+ }
39
+
40
+ .ds-lib-timeline-title {
41
+ margin-bottom: 0.5rem;
42
+ color: var(--ds-color-foreground);
43
+ font-weight: 600;
44
+ }
45
+
46
+ .ds-lib-timeline-events {
47
+ position: relative;
48
+ margin: 0;
49
+ padding: 0;
50
+ list-style: none;
51
+ }
52
+
53
+ .ds-lib-timeline-events::before {
54
+ content: '';
55
+ position: absolute;
56
+ inset-block: 0.5rem;
57
+ inset-inline-start: 0.28125rem;
58
+ width: 1px;
59
+ background: var(--ds-color-border-strong, var(--ds-color-border));
60
+ }
61
+
62
+ .ds-lib-timeline-event {
63
+ position: relative;
64
+ display: flex;
65
+ flex-direction: column;
66
+ margin: 0;
67
+ padding: 0 0 0.75rem 1.5rem;
68
+ }
69
+
70
+ .ds-lib-timeline-event:last-child {
71
+ padding-bottom: 0;
72
+ }
73
+
74
+ .ds-lib-timeline-event::before {
75
+ content: '';
76
+ position: absolute;
77
+ inset-block-start: 0.4rem;
78
+ inset-inline-start: 0;
79
+ width: 0.5625rem;
80
+ height: 0.5625rem;
81
+ border-radius: var(--ds-radius-full);
82
+ background: var(--chart-1, var(--ds-color-primary));
83
+ }
84
+
85
+ .ds-lib-timeline-date {
86
+ color: var(--ds-color-muted-foreground);
87
+ font-family: var(--ds-font-code);
88
+ font-size: var(--ds-text-2xs);
89
+ }
90
+
91
+ .ds-lib-timeline-name {
92
+ color: var(--ds-color-foreground);
93
+ font-weight: 600;
94
+ }
95
+
96
+ .ds-lib-timeline-detail {
97
+ color: var(--ds-color-muted-foreground);
98
+ }
99
+ </style>
@@ -0,0 +1,7 @@
1
+ interface Props {
2
+ value: unknown;
3
+ source: string;
4
+ }
5
+ declare const Timeline: import("svelte").Component<Props, {}, "">;
6
+ type Timeline = ReturnType<typeof Timeline>;
7
+ export default Timeline;
@@ -0,0 +1,110 @@
1
+ <!--
2
+ One Vega-Lite v5 spec, drawn with the app's own tokens. The persona writes
3
+ the data and the marks; colour, type, axes and size come from here and
4
+ override whatever the spec says. Loaded on demand: vega is not small.
5
+ -->
6
+ <script lang="ts">
7
+ import { onMount } from 'svelte';
8
+
9
+ interface Props {
10
+ value: unknown;
11
+ source: string;
12
+ }
13
+
14
+ let { value, source }: Props = $props();
15
+ let host = $state<HTMLDivElement | null>(null);
16
+ let failed = $state(false);
17
+
18
+ const css = (name: string): string =>
19
+ getComputedStyle(document.documentElement).getPropertyValue(name).trim();
20
+
21
+ function config() {
22
+ const ink = css('--ds-color-foreground') || '#231f20';
23
+ const muted = css('--ds-color-muted-foreground') || '#57595c';
24
+ const line = css('--ds-color-border-strong') || '#00000038';
25
+ const font = getComputedStyle(document.body).fontFamily;
26
+ const series = [1, 2, 3, 4, 5, 6, 7, 8, 9].map((i) => css(`--chart-${i}`)).filter(Boolean);
27
+ return {
28
+ background: null,
29
+ font,
30
+ ...(series.length > 0 ? { range: { category: series } } : {}),
31
+ mark: { color: series[0] ?? ink },
32
+ bar: { cornerRadius: 1 },
33
+ view: { stroke: null },
34
+ axis: {
35
+ labelColor: muted,
36
+ titleColor: muted,
37
+ domainColor: line,
38
+ tickColor: line,
39
+ gridColor: line,
40
+ gridOpacity: 0.5,
41
+ labelFont: font,
42
+ titleFont: font,
43
+ labelFontSize: 12,
44
+ titleFontSize: 12,
45
+ titleFontWeight: 600
46
+ },
47
+ legend: { labelColor: ink, titleColor: muted, labelFont: font, titleFont: font },
48
+ title: { color: ink, font, fontSize: 14, fontWeight: 600, anchor: 'start' }
49
+ };
50
+ }
51
+
52
+ let latest = 0;
53
+ // The view drawn last, ended before the next is drawn and when the chart goes.
54
+ let drawn: { finalize: () => void } | undefined;
55
+ async function draw() {
56
+ if (!host) return;
57
+ const mine = ++latest;
58
+ try {
59
+ const { default: embed } = await import('vega-embed');
60
+ if (mine !== latest) return;
61
+ const { $schema: _schema, config: _config, usermeta: _usermeta, ...spec } = value as Record<string, unknown>;
62
+ drawn?.finalize();
63
+ drawn = await embed(
64
+ host,
65
+ { ...spec, width: 'container', autosize: { type: 'fit', contains: 'padding' } } as never,
66
+ { actions: false, renderer: 'svg', config: config() as never }
67
+ );
68
+ failed = false;
69
+ } catch {
70
+ if (mine === latest) failed = true;
71
+ }
72
+ }
73
+
74
+ onMount(() => {
75
+ // The palette follows the theme, and the theme is a class on the root.
76
+ const watch = new MutationObserver(() => void draw());
77
+ watch.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
78
+ return () => {
79
+ watch.disconnect();
80
+ drawn?.finalize();
81
+ };
82
+ });
83
+
84
+ $effect(() => {
85
+ void value;
86
+ void host;
87
+ void draw();
88
+ });
89
+ </script>
90
+
91
+ {#if failed}
92
+ <pre data-highlighted="true"><code>{source}</code></pre>
93
+ {:else}
94
+ <figure class="ds-lib-chart"><div bind:this={host} class="ds-lib-chart-host"></div></figure>
95
+ {/if}
96
+
97
+ <style>
98
+ .ds-lib-chart {
99
+ margin: 0.75em 0;
100
+ border: 1px solid var(--ds-color-border);
101
+ border-radius: var(--ds-radius-lg);
102
+ background: var(--ds-color-surface-1);
103
+ padding: 0.75rem 0.875rem;
104
+ }
105
+
106
+ .ds-lib-chart-host {
107
+ width: 100%;
108
+ min-height: 6rem;
109
+ }
110
+ </style>
@@ -0,0 +1,7 @@
1
+ interface Props {
2
+ value: unknown;
3
+ source: string;
4
+ }
5
+ declare const VegaChart: import("svelte").Component<Props, {}, "">;
6
+ type VegaChart = ReturnType<typeof VegaChart>;
7
+ export default VegaChart;