@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.
- package/README.md +12 -4
- package/dist/chat.svelte.d.ts +40 -0
- package/dist/chat.svelte.js +57 -3
- package/dist/client.d.ts +3 -0
- package/dist/client.js +4 -1
- package/dist/components/activity-group/activity-group.svelte +72 -77
- package/dist/components/activity-group/activity-group.svelte.d.ts +6 -1
- package/dist/components/agent-transcript/agent-transcript.svelte +225 -9
- package/dist/components/agent-transcript/agent-transcript.svelte.d.ts +19 -0
- package/dist/components/artefact-pane/artefact-pane.svelte +387 -43
- package/dist/components/artefact-pane/artefact-pane.svelte.d.ts +10 -1
- package/dist/components/conversation/conversation.svelte +74 -4
- package/dist/components/conversation/conversation.svelte.d.ts +15 -0
- package/dist/components/markdown/markdown.d.ts +30 -2
- package/dist/components/markdown/markdown.js +142 -5
- package/dist/components/markdown/markdown.svelte +130 -16
- package/dist/components/markdown/markdown.svelte.d.ts +12 -0
- package/dist/components/source-fold/index.d.ts +2 -0
- package/dist/components/source-fold/index.js +2 -0
- package/dist/components/source-fold/source-fold.svelte +225 -0
- package/dist/components/source-fold/source-fold.svelte.d.ts +17 -0
- package/dist/components/source-list/source-list.svelte +5 -3
- package/dist/components/source-list/source-list.svelte.d.ts +2 -0
- package/dist/components/thinking-row/thinking-row.svelte +38 -16
- package/dist/components/thinking-row/thinking-row.svelte.d.ts +3 -0
- package/dist/components/tool-row/tool-row.svelte +161 -49
- package/dist/components/tool-row/tool-row.svelte.d.ts +2 -3
- package/dist/components/visual-block/entity-card.svelte +94 -0
- package/dist/components/visual-block/entity-card.svelte.d.ts +7 -0
- package/dist/components/visual-block/timeline.svelte +99 -0
- package/dist/components/visual-block/timeline.svelte.d.ts +7 -0
- package/dist/components/visual-block/vega-chart.svelte +110 -0
- package/dist/components/visual-block/vega-chart.svelte.d.ts +7 -0
- package/dist/components/visual-block/visual-block.svelte +53 -0
- package/dist/components/visual-block/visual-block.svelte.d.ts +13 -0
- package/dist/components/written-card/index.d.ts +2 -0
- package/dist/components/written-card/index.js +2 -0
- package/dist/components/written-card/written-card.svelte +178 -0
- package/dist/components/written-card/written-card.svelte.d.ts +15 -0
- package/dist/copy.d.ts +73 -0
- package/dist/copy.js +56 -0
- package/dist/diff.d.ts +8 -0
- package/dist/diff.js +37 -0
- package/dist/steps.d.ts +28 -0
- package/dist/steps.js +168 -0
- package/dist/transcript.svelte.d.ts +19 -34
- package/dist/transcript.svelte.js +64 -56
- package/dist/visuals.d.ts +38 -0
- package/dist/visuals.js +82 -0
- package/dist/web-sources.d.ts +34 -0
- package/dist/web-sources.js +90 -0
- package/dist/written.d.ts +71 -0
- package/dist/written.js +83 -0
- package/package.json +14 -1
|
@@ -1,40 +1,68 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
One tool call, as a row in the
|
|
2
|
+
One tool call, as a row in the turn's fold.
|
|
3
3
|
|
|
4
|
-
Not a card:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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,
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
:
|
|
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-
|
|
49
|
-
|
|
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
|
-
|
|
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
|
|
62
|
-
<
|
|
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.
|
|
81
|
-
padding-block: 0.
|
|
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-
|
|
91
|
-
|
|
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-
|
|
109
|
-
width: 0.375rem;
|
|
110
|
-
height: 0.375rem;
|
|
164
|
+
.ds-lib-tool-meta {
|
|
111
165
|
flex: none;
|
|
112
|
-
|
|
113
|
-
|
|
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-
|
|
117
|
-
|
|
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-
|
|
121
|
-
|
|
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-
|
|
125
|
-
|
|
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:
|
|
245
|
+
margin-inline-start: 0.4375rem;
|
|
148
246
|
border-inline-start: 1px solid var(--ds-color-border);
|
|
149
|
-
padding-inline-start:
|
|
247
|
+
padding-inline-start: 1rem;
|
|
150
248
|
}
|
|
151
249
|
|
|
152
250
|
.ds-lib-tool-result {
|
|
153
|
-
margin
|
|
154
|
-
max-height:
|
|
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.
|
|
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-
|
|
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,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,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>
|