@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
package/README.md CHANGED
@@ -34,11 +34,14 @@ src/lib/
34
34
  agent-transcript/ one question and everything the persona did answering it
35
35
  document-pane/ the cited document, open at the cited passage
36
36
  artefact-card/ an artefact's card, in the host's words
37
- artefact-pane/ an artefact that is its answer's prose, in the column
37
+ artefact-pane/ an artefact that is its answer's prose, or one version of a written document, in the column
38
+ written-card/ a document the persona wrote: kind, pages, version, Open, and a download chip
39
+ source-fold/ what an answer drew on, folded: the library's numbered sources, then the web pages numbered on from them
40
+ visual-block/ a fenced block of a registered language: its renderer, Drawing… while it arrives, its code when invalid
38
41
  composer/ the input box: attachments, one collection control, a note, send/stop
39
42
  markdown/ sanitised, streaming-safe markdown + highlighting
40
- activity-group/ a whole investigation, as one quiet line
41
- tool-row/ one tool call
43
+ activity-group/ what the persona did, one collapsed row per step
44
+ tool-row/ one tool call, opening on its input and result
42
45
  thinking-row/ one thought, or one line of between-tool narration
43
46
  working/ the pre-first-token "something is happening" indicator
44
47
  ```
@@ -394,10 +397,15 @@ end that way.
394
397
 
395
398
  | Concern | How |
396
399
  | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
397
- | The persona's tools | `describeTool(block) => ToolWords \| undefined`: the row (`verb`, `object`, `detail`), the activity line's counts (`tally`), and what it read (`source`). `undefined` leaves a call to the package's own words |
400
+ | The persona's tools | `describeTool(block) => ToolWords \| undefined`: the row (`verb`, `object`, `detail`) and what it read (`source`). `undefined` leaves a call to the package's own words |
398
401
  | What an answer read | `ToolWords.source` lists under the answer, under `copy.sources`; tapping one calls `oncite`. Nothing checked it, so it carries no trust mark |
399
402
  | An artefact | `turn.artefact = { title, summary }` cards under the prose once the turn settles; `isAnswer: true` is an artefact that IS the prose, and replaces it |
400
403
  | Opening it | `onopenartefact(turn)` and `showing` (the turn id the host shows). Without a handler, an `isAnswer` artefact opens in `ArtefactPane` and any other offers no action |
404
+ | Editing a question | `Conversation`'s `onedit(position, text)` (`Chat.edit`) offers an edit control on each plain question while nothing is being answered; the turns from there on are dropped and the new question streams in at that position (`ask({ edit })`). Files are not sent again |
405
+ | Documents it wrote | `artefact` frames and a stored turn's `artefacts` (each with `title`, `kind`, `changes`, `pages`) become `turn.written`: the newest version of each is a card with Open and a download chip (Word where `formats` has it, else the source). The pane marks what a version changed since the one before: a "Changed since vN" line from `changes`, and a bar beside each changed block. `loadWritten(name, version)` and `writtenUrl(name, version, format)` (`Chat.loadArtefact` / `Chat.artefactUrl` over the transport) let the pane show a version: Markdown sanitised, HTML only in a `sandbox=""` frame |
406
+ | The turn's steps | One fold, "Read your attachment, searched the library and the web, and wrote a minute · 6 steps", composed from the steps (`stepsSummary`); it says what is happening now while the turn runs, and opens on one row per step (a search row counts what it found, a web search opens on the pages it found) |
407
+ | Web sources | Pages from the turn's supacrawl calls and the answer's Markdown links fold with the library's sources ("6 sources · 4 library · 2 web"), numbered on from the library's, and a link to one in the answer reads as that number's chip |
408
+ | Drawing a fence | `visuals` on `Conversation` is a registry keyed on a fence's language, `{ component, parse }` each; `defaultVisuals` (from `@poodle64/librarian/visuals`) draws `vega-lite` (one Vega-Lite v5 spec, in the app's tokens), `timeline` and `card`. An app passes `{ ...defaultVisuals, openui }`. A block whose closing fence has not arrived is "Drawing…", one that does not parse is its code |
401
409
  | "Answered without a source" | `copy.notHeld: ''` for a persona that answers from no sources |
402
410
  | Saying something mid-run | `Composer`'s `sendWhileRunning`: while the session works the box stays open and Send sits beside Stop. For a route that reads a message while the run works; a chat leaves it off and waits for its answer |
403
411
 
@@ -22,6 +22,7 @@
22
22
  import type { AgentEvent } from './client';
23
23
  import type { Citation } from './citations';
24
24
  import { type Artefact, type Turn } from './transcript.svelte';
25
+ import type { Written } from './written';
25
26
  /** One conversation in the list. */
26
27
  export interface ConversationSummary {
27
28
  /** The agent's own session id: what resumes it, what names it in the address. */
@@ -47,6 +48,20 @@ export interface StoredTurn {
47
48
  collection?: string | null;
48
49
  /** Where this answer's read-only view lives while it is shared. */
49
50
  shared?: string | null;
51
+ /** The names of the files attached to the question. */
52
+ files?: string[];
53
+ /** The document versions written answering it, in order. */
54
+ artefacts?: Written[];
55
+ /** Every tool the agent called, in order, with its (truncated) result. */
56
+ steps?: StoredStep[];
57
+ }
58
+ /** One tool call of a stored turn. */
59
+ export interface StoredStep {
60
+ id: string;
61
+ name: string;
62
+ input: Record<string, unknown>;
63
+ result: string;
64
+ is_error: boolean;
50
65
  }
51
66
  /** A conversation reopened. */
52
67
  export interface ConversationRead extends ConversationSummary {
@@ -79,6 +94,8 @@ export interface AskRequest {
79
94
  kind?: string;
80
95
  /** The one collection to ask, by name: `client.ask()`'s `collection`. */
81
96
  collection?: string;
97
+ /** The position of the question this replaces: `client.ask()`'s `edit`. */
98
+ edit?: number;
82
99
  }
83
100
  /**
84
101
  * A persona's chat routes, as the host's own client reaches them. Every
@@ -114,6 +131,14 @@ export interface ChatTransport {
114
131
  * stops sharing it. Resolves to where it is read while shared, null once
115
132
  * it is not. */
116
133
  share?(id: string, turn: number, share: boolean): Promise<string | null>;
134
+ /** `GET conversations/{id}/artefact`, as `source`: one version of a document
135
+ * the agent wrote. */
136
+ loadArtefact?(id: string, name: string, version: number): Promise<{
137
+ text: string;
138
+ mediaType: string;
139
+ }>;
140
+ /** Where that version downloads as `format` (`source`, or one of its `formats`). */
141
+ artefactUrl?(id: string, name: string, version: number, format: string): string;
117
142
  }
118
143
  /** A job's routes. */
119
144
  export interface JobTransport {
@@ -188,6 +213,14 @@ export declare class Chat {
188
213
  /** The last question again, as a new turn: the conversation is append-only,
189
214
  * because the agent's transcript is. Same kind, same collection. */
190
215
  again(): Promise<boolean>;
216
+ /**
217
+ * Replace the question at `position` (its place among `turns`) with `text`:
218
+ * the turns from there on go from the page and from the server, and the new
219
+ * question streams in at that position. The files that went with the old
220
+ * one are not sent again. Same kind and collection as the one replaced.
221
+ * Resolves like `ask`; a refusal puts the dropped turns back.
222
+ */
223
+ edit(position: number, text: string): Promise<boolean>;
191
224
  /** Stop the answer being written, wherever it is being written. */
192
225
  stop(): Promise<void>;
193
226
  /** Read today's allowance again. One that cannot be read is left as it was. */
@@ -207,6 +240,13 @@ export declare class Chat {
207
240
  /** Share an answer the agent holds, or stop sharing it. False when that
208
241
  * was not recorded. */
209
242
  share(turn: Turn, share: boolean): Promise<boolean>;
243
+ /** One version of a document the agent wrote in the open conversation. */
244
+ loadArtefact(name: string, version: number): Promise<{
245
+ text: string;
246
+ mediaType: string;
247
+ }>;
248
+ /** Where that version downloads as `format`; empty with no conversation open. */
249
+ artefactUrl(name: string, version: number, format: string): string;
210
250
  }
211
251
  /** A turn as read back, ready for `Conversation`: given one and no handlers,
212
252
  * that is the read-only view of a shared answer. */
@@ -172,6 +172,33 @@ export class Chat {
172
172
  ? this.ask(last.question, last.kind, last.collection)
173
173
  : Promise.resolve(false);
174
174
  }
175
+ /**
176
+ * Replace the question at `position` (its place among `turns`) with `text`:
177
+ * the turns from there on go from the page and from the server, and the new
178
+ * question streams in at that position. The files that went with the old
179
+ * one are not sent again. Same kind and collection as the one replaced.
180
+ * Resolves like `ask`; a refusal puts the dropped turns back.
181
+ */
182
+ async edit(position, text) {
183
+ const body = text.trim();
184
+ const resume = this.conversationId;
185
+ const was = this.#turns[position];
186
+ if (!body || !resume || !was || this.#job || this.busy || this.quota?.reached)
187
+ return false;
188
+ const removed = this.#turns.splice(position);
189
+ const held = removed.filter((t) => this.#held.delete(t.id)).map((t) => t.id);
190
+ this.#version += 1;
191
+ const asked = await this.#ask(body, [], false, was.kind, was.collection, position);
192
+ // Refused before the server dropped anything: the page is as it was left.
193
+ // A conversation found already being answered was shown whole instead.
194
+ if (!asked && this.#turns.length === position) {
195
+ this.#turns.push(...removed);
196
+ for (const id of held)
197
+ this.#held.add(id);
198
+ this.#version += 1;
199
+ }
200
+ return asked;
201
+ }
175
202
  /** Stop the answer being written, wherever it is being written. */
176
203
  async stop() {
177
204
  const id = this.conversationId;
@@ -318,7 +345,7 @@ export class Chat {
318
345
  this.#running = false;
319
346
  this.answering = null;
320
347
  }
321
- async #ask(text, files, typed, kind, collection) {
348
+ async #ask(text, files, typed, kind, collection, edit) {
322
349
  const chat = this.#chat;
323
350
  const mine = this.#generation;
324
351
  const stream = { controller: new AbortController(), stopWanted: false };
@@ -330,6 +357,7 @@ export class Chat {
330
357
  blocks: [],
331
358
  outcome: null,
332
359
  citations: [],
360
+ files: files.map((f) => f.name),
333
361
  kind,
334
362
  collection,
335
363
  artefact: this.#card(kind)
@@ -346,7 +374,8 @@ export class Chat {
346
374
  resume: this.conversationId,
347
375
  signal: stream.controller.signal,
348
376
  kind,
349
- collection
377
+ collection,
378
+ edit
350
379
  })) {
351
380
  const named = event.type === 'system' && event.subtype === 'init' ? event.session_id : undefined;
352
381
  if (stream.stopWanted) {
@@ -415,6 +444,18 @@ export class Chat {
415
444
  this.#finish();
416
445
  return true;
417
446
  }
447
+ /** One version of a document the agent wrote in the open conversation. */
448
+ loadArtefact(name, version) {
449
+ const id = this.conversationId;
450
+ if (!id || !this.#chat?.loadArtefact)
451
+ return Promise.reject(new Error('no artefact'));
452
+ return this.#chat.loadArtefact(id, name, version);
453
+ }
454
+ /** Where that version downloads as `format`; empty with no conversation open. */
455
+ artefactUrl(name, version, format) {
456
+ const id = this.conversationId;
457
+ return id && this.#chat?.artefactUrl ? this.#chat.artefactUrl(id, name, version, format) : '';
458
+ }
418
459
  /** What a turn of `kind` opens, if the host says it opens anything. */
419
460
  #card(kind) {
420
461
  return kind ? this.#artefact?.(kind) : undefined;
@@ -547,13 +588,26 @@ export class Chat {
547
588
  /** A turn as read back, ready for `Conversation`: given one and no handlers,
548
589
  * that is the read-only view of a shared answer. */
549
590
  export function storedTurn(conversation, index, stored) {
591
+ const blocks = (stored.steps ?? []).map((step, at) => ({
592
+ kind: 'tool',
593
+ index: at,
594
+ id: step.id,
595
+ name: step.name,
596
+ rawInput: JSON.stringify(step.input ?? {}),
597
+ result: step.result,
598
+ isError: step.is_error
599
+ }));
600
+ if (stored.answer)
601
+ blocks.push({ kind: 'text', index: blocks.length, text: stored.answer });
550
602
  return {
551
603
  id: `${conversation}:${index}`,
552
604
  question: stored.question,
553
605
  at: epoch(stored.at) ?? undefined,
554
- blocks: stored.answer ? [{ kind: 'text', index: 0, text: stored.answer }] : [],
606
+ blocks,
555
607
  outcome: null,
556
608
  citations: stored.citations ?? [],
609
+ files: stored.files ?? [],
610
+ written: stored.artefacts ?? [],
557
611
  kind: stored.kind ?? undefined,
558
612
  collection: stored.collection ?? undefined,
559
613
  shared: stored.shared ?? undefined
package/dist/client.d.ts CHANGED
@@ -71,6 +71,9 @@ export interface AskOptions {
71
71
  /** The one collection this question is asked of, by name. Absent asks
72
72
  * across every collection the person may read. */
73
73
  collection?: string;
74
+ /** The position (0-based) of the person's question this one replaces: the
75
+ * server drops that turn and every one after it. Needs `resume`. */
76
+ edit?: number;
74
77
  /** Files the persona reads for this question. Switches the request to multipart. */
75
78
  files?: File[];
76
79
  /** What kind of turn this ask should become. Absent means an ordinary
package/dist/client.js CHANGED
@@ -29,7 +29,8 @@ function requestInit(options, signal) {
29
29
  question: options.question,
30
30
  resume: options.resume ?? null,
31
31
  collection: options.collection,
32
- kind: options.kind
32
+ kind: options.kind,
33
+ edit: options.edit
33
34
  }),
34
35
  signal
35
36
  };
@@ -44,6 +45,8 @@ function requestInit(options, signal) {
44
45
  form.append('files[]', file, file.name);
45
46
  if (options.kind)
46
47
  form.append('kind', options.kind);
48
+ if (options.edit !== undefined)
49
+ form.append('edit', String(options.edit));
47
50
  return { method: 'POST', credentials: 'include', body: form, signal };
48
51
  }
49
52
  /**
@@ -1,21 +1,24 @@
1
1
  <!--
2
- A whole investigation, as ONE line the reader can open.
2
+ What the persona did before it answered, folded behind one line.
3
3
 
4
- Not a stack. Fifteen tool rows between the question and the first word of the
5
- answer is the machinery on show, and a running commentary of "Let me
6
- check…" is worse: it narrates a mechanism the persona is under instruction
7
- never to mention. So there is exactly one row in every state — "Working…"
8
- while it runs, a count of what was done once it settles — and the steps are
9
- behind a disclosure for the reader who wants them.
4
+ The line is a sentence composed from the steps ("Read your attachment,
5
+ searched the library and the web, and wrote a minute · 6 steps"), and while
6
+ the turn runs it says what is happening now. It opens on one row per step,
7
+ in order, the way Claude Code's own panel lists them: each says what the step
8
+ did in a reader's words and opens on what went in and what came back.
10
9
 
11
- That commentary is a step in here too. It reaches the caller as an ordinary
12
- `text` block, not a `thinking` one, so `segment()` re-homes any text with a
13
- tool call still to come into this group; it renders as a ThinkingRow beside
14
- the tools, folded like the rest.
10
+ The persona's between-tool commentary is a step in here as well. It reaches
11
+ the caller as an ordinary `text` block, not a `thinking` one, so `segment()`
12
+ re-homes any text with a tool call still to come into this group; it renders
13
+ as a ThinkingRow beside the tools, folded like the rest.
15
14
  -->
16
15
  <script lang="ts">
17
- import ChevronRightIcon from '@lucide/svelte/icons/chevron-right';
18
- import { summariseActivity, type ActivityGroup } from '../../transcript.svelte';
16
+ import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
17
+ import ChevronUpIcon from '@lucide/svelte/icons/chevron-up';
18
+ import type { ActivityGroup } from '../../transcript.svelte';
19
+ import { resolveCopy, type ChatCopy } from '../../copy';
20
+ import { stepsNow, stepsSummary } from '../../steps';
21
+ import type { Written } from '../../written';
19
22
  import ToolRow from '../tool-row/tool-row.svelte';
20
23
  import ThinkingRow from '../thinking-row/thinking-row.svelte';
21
24
 
@@ -23,43 +26,45 @@
23
26
  group: ActivityGroup;
24
27
  /** True while this group is the live one. */
25
28
  live: boolean;
29
+ /** The documents the turn wrote: a step that wrote one says its kind. */
30
+ written?: Written[];
31
+ copy?: Partial<ChatCopy>;
26
32
  }
27
33
 
28
- let { group, live }: Props = $props();
29
- let expanded = $state(false);
34
+ let { group, live, written = [], copy }: Props = $props();
35
+ let open = $state(false);
30
36
 
31
- // A live group has nothing to count yet, so counting it reads as "0
32
- // searches" flickering up to the real number.
33
- const label = $derived(live ? 'Working…' : summariseActivity(group));
37
+ const words = $derived(resolveCopy(copy));
38
+ const summary = $derived(stepsSummary(group.steps, written, words));
39
+ const now = $derived(stepsNow(group.steps, written, words));
34
40
  </script>
35
41
 
36
- <div>
42
+ <div class="ds-lib-steps">
37
43
  <button
38
44
  type="button"
39
- class="ds-lib-activity-toggle"
40
- onclick={() => (expanded = !expanded)}
41
- aria-expanded={expanded}
45
+ class="ds-lib-steps-head"
46
+ onclick={() => (open = !open)}
47
+ aria-expanded={open}
42
48
  >
43
- <span class="ds-lib-activity-chevron" class:is-open={expanded}>
44
- <ChevronRightIcon size={14} />
49
+ <span class="ds-lib-steps-line">
50
+ {#if live}
51
+ <span class="ds-lib-steps-now">{now}</span>
52
+ {:else}
53
+ {summary.sentence}<span class="ds-lib-steps-trail">{` · ${summary.trail}`}</span>
54
+ {/if}
55
+ </span>
56
+ <span class="ds-lib-steps-chevron" aria-hidden="true">
57
+ {#if open}<ChevronUpIcon size={15} />{:else}<ChevronDownIcon size={15} />{/if}
45
58
  </span>
46
- {#if live}
47
- <span class="ds-lib-activity-dot"></span>
48
- {/if}
49
- <span>{label}</span>
50
59
  </button>
51
- {#if expanded}
52
- <div class="ds-lib-activity-steps">
53
- {#each group.steps as step (step.block.index)}
60
+
61
+ {#if open}
62
+ <div class="ds-lib-steps-rows">
63
+ {#each group.steps as step, i (step.block.index)}
54
64
  {#if step.block.kind === 'tool'}
55
- <ToolRow
56
- block={step.block}
57
- repeats={step.repeats}
58
- words={step.words}
59
- running={live}
60
- />
65
+ <ToolRow block={step.block} words={step.words} running={live} {copy} />
61
66
  {:else}
62
- <ThinkingRow block={step.block} active={false} />
67
+ <ThinkingRow block={step.block} active={live && i === group.steps.length - 1} {copy} />
63
68
  {/if}
64
69
  {/each}
65
70
  </div>
@@ -67,67 +72,57 @@
67
72
  </div>
68
73
 
69
74
  <style>
70
- .ds-lib-activity-toggle {
75
+ .ds-lib-steps-head {
71
76
  display: flex;
72
- align-items: center;
73
- gap: 0.375rem;
77
+ width: 100%;
78
+ align-items: flex-start;
79
+ gap: 0.5rem;
74
80
  border: 0;
75
81
  background: none;
76
- padding: 0;
82
+ padding: 0.25rem 0;
77
83
  color: var(--ds-color-muted-foreground);
78
84
  font: inherit;
79
85
  font-size: 0.875rem;
80
- line-height: 1.25rem;
86
+ line-height: 1.375rem;
87
+ text-align: start;
81
88
  cursor: pointer;
82
- transition: color 150ms ease;
83
89
  }
84
90
 
85
- .ds-lib-activity-toggle:hover {
91
+ .ds-lib-steps-head:hover {
86
92
  color: var(--ds-color-foreground);
87
93
  }
88
94
 
89
- .ds-lib-activity-chevron {
90
- display: flex;
91
- flex: none;
92
- transition: transform 150ms ease;
95
+ .ds-lib-steps-head:focus-visible {
96
+ outline: 2px solid var(--ds-color-ring);
97
+ outline-offset: 2px;
93
98
  }
94
99
 
95
- .ds-lib-activity-chevron.is-open {
96
- transform: rotate(90deg);
100
+ .ds-lib-steps-line {
101
+ flex: 1;
97
102
  }
98
103
 
99
- .ds-lib-activity-dot {
100
- width: 0.375rem;
101
- height: 0.375rem;
104
+ .ds-lib-steps-now {
105
+ color: var(--ds-color-foreground);
106
+ font-weight: 500;
107
+ }
108
+
109
+ .ds-lib-steps-trail {
110
+ font-size: var(--ds-text-2xs);
111
+ opacity: 0.8;
112
+ }
113
+
114
+ .ds-lib-steps-chevron {
115
+ display: flex;
102
116
  flex: none;
103
- border-radius: var(--ds-radius-full);
104
- background: var(--ds-color-status-info);
105
- animation: ds-lib-activity-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
117
+ padding-top: 0.25rem;
106
118
  }
107
119
 
108
- .ds-lib-activity-steps {
120
+ .ds-lib-steps-rows {
109
121
  display: flex;
110
122
  flex-direction: column;
111
- gap: 0.375rem;
112
- margin-top: 0.5rem;
113
- margin-inline-start: 0.4375rem;
123
+ gap: 0.125rem;
124
+ margin-top: 0.25rem;
114
125
  border-inline-start: 1px solid var(--ds-color-border);
115
126
  padding-inline-start: 0.75rem;
116
127
  }
117
-
118
- @keyframes ds-lib-activity-pulse {
119
- 50% {
120
- opacity: 0.4;
121
- }
122
- }
123
-
124
- @media (prefers-reduced-motion: reduce) {
125
- .ds-lib-activity-dot {
126
- animation: none;
127
- }
128
-
129
- .ds-lib-activity-chevron {
130
- transition: none;
131
- }
132
- }
133
128
  </style>
@@ -1,8 +1,13 @@
1
- import { type ActivityGroup } from '../../transcript.svelte';
1
+ import type { ActivityGroup } from '../../transcript.svelte';
2
+ import { type ChatCopy } from '../../copy';
3
+ import type { Written } from '../../written';
2
4
  interface Props {
3
5
  group: ActivityGroup;
4
6
  /** True while this group is the live one. */
5
7
  live: boolean;
8
+ /** The documents the turn wrote: a step that wrote one says its kind. */
9
+ written?: Written[];
10
+ copy?: Partial<ChatCopy>;
6
11
  }
7
12
  declare const ActivityGroup: import("svelte").Component<Props, {}, "">;
8
13
  type ActivityGroup = ReturnType<typeof ActivityGroup>;