@poodle64/librarian 2026.9.13 → 2026.9.15

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 (28) hide show
  1. package/README.md +77 -19
  2. package/dist/client.d.ts +16 -1
  3. package/dist/client.js +55 -8
  4. package/dist/components/activity-group/activity-group.svelte +76 -8
  5. package/dist/components/agent-transcript/agent-transcript.svelte +346 -150
  6. package/dist/components/agent-transcript/agent-transcript.svelte.d.ts +7 -0
  7. package/dist/components/artefact-card/artefact-card.svelte +30 -2
  8. package/dist/components/artefact-pane/artefact-pane.svelte +141 -50
  9. package/dist/components/composer/composer.svelte +255 -38
  10. package/dist/components/composer/composer.svelte.d.ts +6 -0
  11. package/dist/components/conversation/conversation.svelte +254 -81
  12. package/dist/components/conversation/conversation.svelte.d.ts +7 -0
  13. package/dist/components/document-pane/document-pane.svelte +216 -33
  14. package/dist/components/markdown/markdown.svelte +73 -24
  15. package/dist/components/scope-statement/scope-statement.svelte +79 -12
  16. package/dist/components/source-list/index.d.ts +2 -0
  17. package/dist/components/source-list/index.js +2 -0
  18. package/dist/components/source-list/source-list.svelte +137 -0
  19. package/dist/components/source-list/source-list.svelte.d.ts +13 -0
  20. package/dist/components/thinking-row/thinking-row.svelte +69 -18
  21. package/dist/components/tool-row/tool-row.svelte +132 -26
  22. package/dist/components/working/working.svelte +51 -7
  23. package/dist/components/working/working.svelte.d.ts +3 -0
  24. package/dist/copy.d.ts +45 -10
  25. package/dist/copy.js +77 -32
  26. package/dist/transcript.svelte.d.ts +10 -0
  27. package/dist/transcript.svelte.js +4 -1
  28. package/package.json +4 -2
@@ -1,17 +1,18 @@
1
1
  <!--
2
2
  A study artefact, open in the reading column.
3
3
 
4
- The same shell shape `DocumentPane` uses (a column at `lg`, a bottom sheet
5
- below it) because decision 2 of the ask-milton surface is that a colleague
6
- never has to learn which of the two things is in the column before they
7
- open it — only ever one, in the same place, closing whichever was there.
4
+ The same shell shape `DocumentPane` uses (a column at 64rem, a bottom sheet
5
+ below it) because decision 2 of the agent surface is that a colleague never
6
+ has to learn which of the two things is in the column before they open it —
7
+ only ever one, in the same place, closing whichever was there.
8
8
  -->
9
9
  <script lang="ts">
10
10
  import XIcon from '@lucide/svelte/icons/x';
11
11
  import { segment, type TextBlock, type Turn } from '../../transcript.svelte';
12
- import { resolveCitations, splitSources, trustMark, type Citation } from '../../citations';
12
+ import { resolveCitations, splitSources, type Citation } from '../../citations';
13
13
  import { resolveCopy, type LibrarianCopy } from '../../copy';
14
14
  import Markdown from '../markdown/markdown.svelte';
15
+ import SourceList from '../source-list/source-list.svelte';
15
16
 
16
17
  interface Props {
17
18
  turn: Turn;
@@ -52,69 +53,159 @@
52
53
 
53
54
  <svelte:window onkeydown={keydown} />
54
55
 
55
- <aside
56
- aria-label="Study artefact"
57
- class="bg-surface-1 border-border fixed inset-x-0 bottom-0 z-40 flex h-[80svh] flex-col rounded-t-2xl border-t shadow-2xl lg:relative lg:h-auto lg:w-[560px] lg:shrink-0 lg:rounded-none lg:border-t-0 lg:border-l lg:shadow-none"
58
- >
59
- <!-- The sheet's grabber. Below `lg` this is an overlay a reader has to be
56
+ <aside aria-label="Study artefact" class="ds-lib-pane">
57
+ <!-- The sheet's grabber. On a phone this is an overlay a reader has to be
60
58
  able to see the top edge of; on a desktop it is a column, so there is
61
59
  nothing here to grab. -->
62
- <div class="flex justify-center pt-2 pb-1 lg:hidden" aria-hidden="true">
63
- <span class="bg-border h-1 w-9 rounded-full"></span>
64
- </div>
60
+ <div class="ds-lib-pane-grabber" aria-hidden="true"><span></span></div>
65
61
 
66
- <header class="border-border flex items-start gap-2 border-b px-4 py-3 lg:pt-3">
67
- <div class="min-w-0 flex-1">
68
- <h2 class="text-foreground truncate text-sm font-semibold">{turn.title ?? 'Briefing'}</h2>
69
- <p class="text-muted-foreground truncate text-xs">
62
+ <header class="ds-lib-pane-header">
63
+ <div class="ds-lib-pane-heading">
64
+ <h2 class="ds-lib-pane-title">{turn.title ?? 'Briefing'}</h2>
65
+ <p class="ds-lib-pane-subtitle">
70
66
  Briefing doc · {sources.length} source{sources.length === 1 ? '' : 's'}
71
67
  </p>
72
68
  </div>
73
69
  <button
74
70
  bind:this={closeButton}
75
71
  type="button"
72
+ class="ds-lib-pane-close"
76
73
  onclick={onclose}
77
74
  aria-label={words.closeSource}
78
- class="text-muted-foreground hover:text-foreground hover:bg-surface-2 focus-visible:ring-ring -mt-1 flex size-8 shrink-0 items-center justify-center rounded-lg transition-colors focus-visible:ring-2 focus-visible:outline-none"
79
75
  >
80
- <XIcon class="size-4" />
76
+ <XIcon size={16} />
81
77
  </button>
82
78
  </header>
83
79
 
84
- <div class="min-h-0 flex-1 overflow-y-auto overscroll-contain px-4 py-3">
80
+ <div class="ds-lib-pane-body">
85
81
  <Markdown content={split.citations.length > 0 ? split.body : rawAnswer} />
86
82
 
87
83
  {#if sources.length > 0}
88
- <section class="mt-6">
89
- <h3 class="text-muted-foreground mb-1.5 text-xs font-medium tracking-wide uppercase">
90
- {words.sources}
91
- </h3>
92
- <ol class="flex flex-col gap-1">
93
- {#each sources as source (source.n)}
94
- {@const mark = trustMark(source, words)}
95
- <li>
96
- <button
97
- type="button"
98
- onclick={() => oncite?.(source)}
99
- disabled={!source.document_id || !oncite}
100
- class="border-border hover:border-border-strong hover:bg-surface-2 focus-visible:ring-ring flex w-full items-baseline gap-2 rounded-lg border px-2.5 py-1.5 text-left text-sm transition-colors focus-visible:ring-2 focus-visible:outline-none disabled:cursor-default disabled:hover:bg-transparent"
101
- >
102
- <span
103
- class="bg-primary/15 text-foreground shrink-0 rounded px-1.5 font-mono text-xs tabular-nums"
104
- >{source.n}</span
105
- >
106
- <span class="min-w-0">
107
- <span class="text-foreground">{source.title}</span>
108
- {#if source.section}<span class="text-muted-foreground">
109
- · {source.section}</span
110
- >{/if}
111
- {#if mark}<span class="text-muted-foreground/80"> · {mark}</span>{/if}
112
- </span>
113
- </button>
114
- </li>
115
- {/each}
116
- </ol>
117
- </section>
84
+ <div class="ds-lib-pane-sources">
85
+ <SourceList {sources} {words} {oncite} level={3} />
86
+ </div>
118
87
  {/if}
119
88
  </div>
120
89
  </aside>
90
+
91
+ <style>
92
+ .ds-lib-pane {
93
+ position: fixed;
94
+ inset-inline: 0;
95
+ bottom: 0;
96
+ z-index: 40;
97
+ display: flex;
98
+ height: 80svh;
99
+ flex-direction: column;
100
+ border-top: 1px solid var(--ds-color-border);
101
+ border-start-start-radius: var(--ds-radius-xl);
102
+ border-start-end-radius: var(--ds-radius-xl);
103
+ background: var(--ds-color-surface-1);
104
+ box-shadow: 0 -12px 40px rgb(0 0 0 / 0.25);
105
+ }
106
+
107
+ .ds-lib-pane-grabber {
108
+ display: flex;
109
+ justify-content: center;
110
+ padding-top: 0.5rem;
111
+ padding-bottom: 0.25rem;
112
+ }
113
+
114
+ .ds-lib-pane-grabber span {
115
+ width: 2.25rem;
116
+ height: 0.25rem;
117
+ border-radius: var(--ds-radius-full);
118
+ background: var(--ds-color-border);
119
+ }
120
+
121
+ .ds-lib-pane-header {
122
+ display: flex;
123
+ align-items: flex-start;
124
+ gap: 0.5rem;
125
+ border-bottom: 1px solid var(--ds-color-border);
126
+ padding: 0.75rem 1rem;
127
+ }
128
+
129
+ .ds-lib-pane-heading {
130
+ min-width: 0;
131
+ flex: 1;
132
+ }
133
+
134
+ .ds-lib-pane-title,
135
+ .ds-lib-pane-subtitle {
136
+ margin: 0;
137
+ overflow: hidden;
138
+ text-overflow: ellipsis;
139
+ white-space: nowrap;
140
+ }
141
+
142
+ .ds-lib-pane-title {
143
+ color: var(--ds-color-foreground);
144
+ font-family: inherit;
145
+ font-size: 0.875rem;
146
+ font-weight: 600;
147
+ }
148
+
149
+ .ds-lib-pane-subtitle {
150
+ color: var(--ds-color-muted-foreground);
151
+ font-size: var(--ds-text-2xs);
152
+ }
153
+
154
+ .ds-lib-pane-close {
155
+ display: flex;
156
+ width: 2rem;
157
+ height: 2rem;
158
+ flex: none;
159
+ align-items: center;
160
+ justify-content: center;
161
+ margin-top: -0.25rem;
162
+ border: 0;
163
+ border-radius: var(--ds-radius-lg);
164
+ background: none;
165
+ color: var(--ds-color-muted-foreground);
166
+ cursor: pointer;
167
+ transition:
168
+ color 150ms ease,
169
+ background-color 150ms ease;
170
+ }
171
+
172
+ .ds-lib-pane-close:hover {
173
+ background: var(--ds-color-surface-2);
174
+ color: var(--ds-color-foreground);
175
+ }
176
+
177
+ .ds-lib-pane-close:focus-visible {
178
+ outline: 2px solid var(--ds-color-ring);
179
+ outline-offset: 2px;
180
+ }
181
+
182
+ .ds-lib-pane-body {
183
+ min-height: 0;
184
+ flex: 1;
185
+ overflow-y: auto;
186
+ overscroll-behavior: contain;
187
+ padding: 0.75rem 1rem;
188
+ }
189
+
190
+ .ds-lib-pane-sources {
191
+ margin-top: 1.5rem;
192
+ }
193
+
194
+ @media (min-width: 64rem) {
195
+ .ds-lib-pane {
196
+ position: relative;
197
+ inset: auto;
198
+ height: auto;
199
+ width: 35rem;
200
+ flex: none;
201
+ border-top: 0;
202
+ border-inline-start: 1px solid var(--ds-color-border);
203
+ border-radius: 0;
204
+ box-shadow: none;
205
+ }
206
+
207
+ .ds-lib-pane-grabber {
208
+ display: none;
209
+ }
210
+ }
211
+ </style>
@@ -23,6 +23,7 @@
23
23
  MAX_FILES,
24
24
  rejectionMessage
25
25
  } from '../../attachments';
26
+ import { DEFAULT_PERSONA, resolveCopy, type LibrarianCopy } from '../../copy';
26
27
 
27
28
  export type Scope = 'document' | 'collection' | 'library';
28
29
 
@@ -44,6 +45,11 @@
44
45
  * control renders — studying is a mode of asking, never a tab, so a
45
46
  * host with nothing to build offers none rather than a disabled one. */
46
47
  onbriefing?: () => void;
48
+ /** Who is being asked. A slug is fine — `penny` renders as "Penny" — and
49
+ * both placeholders are composed from it. */
50
+ name?: string;
51
+ /** Overrides for the package's own words. */
52
+ copy?: Partial<LibrarianCopy>;
47
53
  }
48
54
 
49
55
  let {
@@ -57,9 +63,13 @@
57
63
  onscope,
58
64
  onsubmit,
59
65
  onstop,
60
- onbriefing
66
+ onbriefing,
67
+ name = DEFAULT_PERSONA,
68
+ copy
61
69
  }: Props = $props();
62
70
 
71
+ const words = $derived(resolveCopy(copy, name));
72
+
63
73
  // What Milton is being asked about — this document, this collection, or
64
74
  // the whole library. Callers that have no narrower scope pass neither
65
75
  // name, leaving "The whole library" the sole entry — and a sole entry is
@@ -177,30 +187,25 @@
177
187
  </script>
178
188
 
179
189
  <div
190
+ class="ds-lib-composer-lift"
180
191
  style="transform: translateY(-{lift}px); padding-bottom: {lift
181
192
  ? '0px'
182
193
  : 'env(safe-area-inset-bottom, 0px)'}"
183
- class="transition-transform duration-150"
184
194
  >
185
- <div
186
- class="border-border bg-surface-1 focus-within:border-primary/60 rounded-xl border transition-colors"
187
- >
195
+ <div class="ds-lib-box">
188
196
  {#if files.length > 0}
189
- <ul class="flex flex-wrap gap-1.5 px-3 pt-3">
197
+ <ul class="ds-lib-files">
190
198
  {#each files as file, index (file.name + file.size)}
191
- <li
192
- class="border-border bg-surface-2 flex max-w-full items-center gap-1.5 rounded-lg border py-1 pr-1 pl-2 text-xs"
193
- >
194
- <span class="text-foreground truncate">{file.name}</span>
195
- <span class="text-muted-foreground shrink-0 tabular-nums">{formatSize(file.size)}</span
196
- >
199
+ <li class="ds-lib-file">
200
+ <span class="ds-lib-file-name">{file.name}</span>
201
+ <span class="ds-lib-file-size">{formatSize(file.size)}</span>
197
202
  <button
198
203
  type="button"
204
+ class="ds-lib-file-remove"
199
205
  onclick={() => remove(index)}
200
206
  aria-label="Remove {file.name}"
201
- class="text-muted-foreground hover:text-foreground hover:bg-surface-3 focus-visible:ring-ring flex size-5 shrink-0 items-center justify-center rounded transition-colors focus-visible:ring-2 focus-visible:outline-none"
202
207
  >
203
- <XIcon class="size-3" />
208
+ <XIcon size={12} />
204
209
  </button>
205
210
  </li>
206
211
  {/each}
@@ -214,11 +219,11 @@
214
219
  onkeydown={keydown}
215
220
  rows="1"
216
221
  disabled={running}
217
- placeholder={running ? 'Milton is answering…' : 'Ask Milton…'}
218
- class="text-foreground placeholder:text-muted-foreground w-full resize-none bg-transparent px-4 pt-3 pb-2 text-base outline-none disabled:opacity-60"
222
+ placeholder={running ? words.answeringPlaceholder : words.askPlaceholder}
223
+ class="ds-lib-input"
219
224
  ></textarea>
220
225
 
221
- <div class="flex items-center gap-2 px-3 pb-2.5">
226
+ <div class="ds-lib-controls">
222
227
  {#if attachments}
223
228
  <input
224
229
  bind:this={picker}
@@ -226,75 +231,287 @@
226
231
  multiple
227
232
  accept={ACCEPT_ATTRIBUTE}
228
233
  onchange={pick}
229
- class="hidden"
234
+ class="ds-lib-picker"
230
235
  tabindex="-1"
231
236
  aria-hidden="true"
232
237
  />
233
238
  <button
234
239
  type="button"
240
+ class="ds-lib-icon-button"
235
241
  onclick={() => picker?.click()}
236
242
  disabled={running || files.length >= MAX_FILES}
237
243
  aria-label="Attach a file"
238
- class="text-muted-foreground hover:text-foreground hover:bg-surface-2 focus-visible:ring-ring flex size-8 shrink-0 items-center justify-center rounded-full transition-colors focus-visible:ring-2 focus-visible:outline-none disabled:opacity-30"
239
244
  >
240
- <PaperclipIcon class="size-4" />
245
+ <PaperclipIcon size={16} />
241
246
  </button>
242
247
  {/if}
243
248
 
244
249
  {#if onbriefing}
245
250
  <button
246
251
  type="button"
252
+ class="ds-lib-icon-button"
247
253
  onclick={onbriefing}
248
254
  disabled={running}
249
255
  aria-label="Ask for a briefing"
250
256
  title="Ask for a briefing"
251
- class="text-muted-foreground hover:text-foreground hover:bg-surface-2 focus-visible:ring-ring flex size-8 shrink-0 items-center justify-center rounded-full transition-colors focus-visible:ring-2 focus-visible:outline-none disabled:opacity-30"
252
257
  >
253
- <FileTextIcon class="size-4" />
258
+ <FileTextIcon size={16} />
254
259
  </button>
255
260
  {/if}
256
261
 
257
262
  {#if hasChoice}
258
- <div class="flex min-w-0 flex-wrap items-center gap-1">
263
+ <div class="ds-lib-scopes">
259
264
  {#each choices as choice (choice.id)}
260
265
  <button
261
266
  type="button"
267
+ class="ds-lib-scope-chip"
268
+ class:is-chosen={scope === choice.id}
262
269
  onclick={() => onscope(choice.id)}
263
- class="max-w-full truncate rounded-full px-2 py-0.5 text-xs transition-colors {scope ===
264
- choice.id
265
- ? 'bg-primary/15 text-foreground border-primary/40 border'
266
- : 'text-muted-foreground hover:text-foreground border border-transparent'}"
267
270
  >
268
271
  {choice.label}
269
272
  </button>
270
273
  {/each}
271
274
  </div>
272
275
  {/if}
273
- <span class="flex-1"></span>
276
+ <span class="ds-lib-spacer"></span>
274
277
  {#if running}
275
- <button
276
- type="button"
277
- onclick={onstop}
278
- aria-label="Stop"
279
- class="bg-foreground text-background hover:bg-foreground/90 flex size-8 items-center justify-center rounded-full transition-colors"
280
- >
281
- <SquareIcon class="size-3.5 fill-current" />
278
+ <button type="button" class="ds-lib-stop" onclick={onstop} aria-label="Stop">
279
+ <SquareIcon size={14} fill="currentColor" />
282
280
  </button>
283
281
  {:else}
284
282
  <button
285
283
  type="button"
284
+ class="ds-lib-send"
286
285
  onclick={submit}
287
286
  disabled={!value.trim()}
288
287
  aria-label="Send"
289
- class="bg-primary text-primary-foreground hover:bg-primary/90 disabled:hover:bg-primary flex size-8 items-center justify-center rounded-full transition-all disabled:opacity-30"
290
288
  >
291
- <ArrowUpIcon class="size-4" />
289
+ <ArrowUpIcon size={16} />
292
290
  </button>
293
291
  {/if}
294
292
  </div>
295
293
  </div>
296
294
 
297
295
  {#if rejected}
298
- <p class="text-status-error px-1 pt-1.5 text-xs" role="status" aria-live="polite">{rejected}</p>
296
+ <p class="ds-lib-rejected" role="status" aria-live="polite">{rejected}</p>
299
297
  {/if}
300
298
  </div>
299
+
300
+ <style>
301
+ .ds-lib-composer-lift {
302
+ transition: transform 150ms ease;
303
+ }
304
+
305
+ .ds-lib-box {
306
+ border: 1px solid var(--ds-color-border);
307
+ border-radius: var(--ds-radius-xl);
308
+ background: var(--ds-color-surface-1);
309
+ transition: border-color 150ms ease;
310
+ }
311
+
312
+ .ds-lib-box:focus-within {
313
+ border-color: color-mix(in oklab, var(--ds-color-primary) 60%, transparent);
314
+ }
315
+
316
+ .ds-lib-files {
317
+ display: flex;
318
+ flex-wrap: wrap;
319
+ gap: 0.375rem;
320
+ margin: 0;
321
+ padding: 0.75rem 0.75rem 0;
322
+ list-style: none;
323
+ }
324
+
325
+ .ds-lib-file {
326
+ display: flex;
327
+ max-width: 100%;
328
+ align-items: center;
329
+ gap: 0.375rem;
330
+ border: 1px solid var(--ds-color-border);
331
+ border-radius: var(--ds-radius-lg);
332
+ background: var(--ds-color-surface-2);
333
+ padding: 0.25rem 0.25rem 0.25rem 0.5rem;
334
+ font-size: var(--ds-text-2xs);
335
+ }
336
+
337
+ .ds-lib-file-name {
338
+ overflow: hidden;
339
+ text-overflow: ellipsis;
340
+ white-space: nowrap;
341
+ color: var(--ds-color-foreground);
342
+ }
343
+
344
+ .ds-lib-file-size {
345
+ flex: none;
346
+ color: var(--ds-color-muted-foreground);
347
+ font-variant-numeric: tabular-nums;
348
+ }
349
+
350
+ .ds-lib-file-remove {
351
+ display: flex;
352
+ width: 1.25rem;
353
+ height: 1.25rem;
354
+ flex: none;
355
+ align-items: center;
356
+ justify-content: center;
357
+ border: 0;
358
+ border-radius: var(--ds-radius-sm);
359
+ background: none;
360
+ color: var(--ds-color-muted-foreground);
361
+ cursor: pointer;
362
+ transition:
363
+ color 150ms ease,
364
+ background-color 150ms ease;
365
+ }
366
+
367
+ .ds-lib-file-remove:hover {
368
+ background: var(--ds-color-surface-3);
369
+ color: var(--ds-color-foreground);
370
+ }
371
+
372
+ .ds-lib-input {
373
+ display: block;
374
+ width: 100%;
375
+ resize: none;
376
+ border: 0;
377
+ background: transparent;
378
+ padding: 0.75rem 1rem 0.5rem;
379
+ color: var(--ds-color-foreground);
380
+ font: inherit;
381
+ font-size: 1rem;
382
+ outline: none;
383
+ }
384
+
385
+ .ds-lib-input::placeholder {
386
+ color: var(--ds-color-muted-foreground);
387
+ }
388
+
389
+ .ds-lib-input:disabled {
390
+ opacity: 0.6;
391
+ }
392
+
393
+ .ds-lib-controls {
394
+ display: flex;
395
+ align-items: center;
396
+ gap: 0.5rem;
397
+ padding: 0 0.75rem 0.625rem;
398
+ }
399
+
400
+ .ds-lib-picker {
401
+ display: none;
402
+ }
403
+
404
+ .ds-lib-icon-button,
405
+ .ds-lib-stop,
406
+ .ds-lib-send {
407
+ display: flex;
408
+ width: 2rem;
409
+ height: 2rem;
410
+ flex: none;
411
+ align-items: center;
412
+ justify-content: center;
413
+ border: 0;
414
+ border-radius: var(--ds-radius-full);
415
+ cursor: pointer;
416
+ transition:
417
+ color 150ms ease,
418
+ background-color 150ms ease,
419
+ opacity 150ms ease;
420
+ }
421
+
422
+ .ds-lib-icon-button {
423
+ background: none;
424
+ color: var(--ds-color-muted-foreground);
425
+ }
426
+
427
+ .ds-lib-icon-button:hover:not(:disabled) {
428
+ background: var(--ds-color-surface-2);
429
+ color: var(--ds-color-foreground);
430
+ }
431
+
432
+ .ds-lib-icon-button:disabled,
433
+ .ds-lib-send:disabled {
434
+ cursor: default;
435
+ opacity: 0.3;
436
+ }
437
+
438
+ .ds-lib-icon-button:focus-visible,
439
+ .ds-lib-stop:focus-visible,
440
+ .ds-lib-send:focus-visible,
441
+ .ds-lib-scope-chip:focus-visible,
442
+ .ds-lib-file-remove:focus-visible {
443
+ outline: 2px solid var(--ds-color-ring);
444
+ outline-offset: 2px;
445
+ }
446
+
447
+ .ds-lib-stop {
448
+ background: var(--ds-color-foreground);
449
+ color: var(--ds-color-background);
450
+ }
451
+
452
+ .ds-lib-send {
453
+ background: var(--ds-color-primary);
454
+ color: var(--ds-color-primary-foreground);
455
+ }
456
+
457
+ .ds-lib-send:hover:not(:disabled),
458
+ .ds-lib-stop:hover {
459
+ opacity: 0.9;
460
+ }
461
+
462
+ /* Three chips in a narrow column clipped all three to fragments
463
+ ("defence-s…", "All colle…"). Wrapping beats truncating: a chip a reader
464
+ cannot finish reading is not a control, it is decoration. */
465
+ .ds-lib-scopes {
466
+ display: flex;
467
+ min-width: 0;
468
+ flex-wrap: wrap;
469
+ align-items: center;
470
+ gap: 0.25rem;
471
+ }
472
+
473
+ .ds-lib-scope-chip {
474
+ max-width: 100%;
475
+ overflow: hidden;
476
+ border: 1px solid transparent;
477
+ border-radius: var(--ds-radius-full);
478
+ background: none;
479
+ padding: 0.125rem 0.5rem;
480
+ color: var(--ds-color-muted-foreground);
481
+ font: inherit;
482
+ font-size: var(--ds-text-2xs);
483
+ text-overflow: ellipsis;
484
+ white-space: nowrap;
485
+ cursor: pointer;
486
+ transition:
487
+ color 150ms ease,
488
+ background-color 150ms ease;
489
+ }
490
+
491
+ .ds-lib-scope-chip:hover {
492
+ color: var(--ds-color-foreground);
493
+ }
494
+
495
+ .ds-lib-scope-chip.is-chosen {
496
+ border-color: color-mix(in oklab, var(--ds-color-primary) 40%, transparent);
497
+ background: color-mix(in oklab, var(--ds-color-primary) 15%, transparent);
498
+ color: var(--ds-color-foreground);
499
+ }
500
+
501
+ .ds-lib-spacer {
502
+ flex: 1;
503
+ }
504
+
505
+ .ds-lib-rejected {
506
+ margin: 0;
507
+ padding: 0.375rem 0.25rem 0;
508
+ color: var(--ds-color-status-error);
509
+ font-size: var(--ds-text-2xs);
510
+ }
511
+
512
+ @media (prefers-reduced-motion: reduce) {
513
+ .ds-lib-composer-lift {
514
+ transition: none;
515
+ }
516
+ }
517
+ </style>
@@ -1,3 +1,4 @@
1
+ import { type LibrarianCopy } from '../../copy';
1
2
  export type Scope = 'document' | 'collection' | 'library';
2
3
  interface Props {
3
4
  value: string;
@@ -17,6 +18,11 @@ interface Props {
17
18
  * control renders — studying is a mode of asking, never a tab, so a
18
19
  * host with nothing to build offers none rather than a disabled one. */
19
20
  onbriefing?: () => void;
21
+ /** Who is being asked. A slug is fine — `penny` renders as "Penny" — and
22
+ * both placeholders are composed from it. */
23
+ name?: string;
24
+ /** Overrides for the package's own words. */
25
+ copy?: Partial<LibrarianCopy>;
20
26
  }
21
27
  declare const Composer: import("svelte").Component<Props, {}, "value" | "files">;
22
28
  type Composer = ReturnType<typeof Composer>;