@poodle64/librarian 2026.10.4 → 2026.10.6

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 (52) hide show
  1. package/README.md +8 -6
  2. package/dist/components/activity-group/activity-group.svelte +99 -17
  3. package/dist/components/activity-group/activity-group.svelte.d.ts +4 -1
  4. package/dist/components/agent-transcript/agent-transcript.svelte +82 -26
  5. package/dist/components/agent-transcript/agent-transcript.svelte.d.ts +4 -3
  6. package/dist/components/artefact-pane/artefact-pane.svelte +278 -78
  7. package/dist/components/conversation/conversation.svelte +6 -6
  8. package/dist/components/conversation/conversation.svelte.d.ts +4 -4
  9. package/dist/components/markdown/markdown.d.ts +27 -10
  10. package/dist/components/markdown/markdown.js +99 -22
  11. package/dist/components/markdown/markdown.svelte +122 -28
  12. package/dist/components/markdown/markdown.svelte.d.ts +12 -4
  13. package/dist/components/source-fold/index.d.ts +2 -0
  14. package/dist/components/source-fold/index.js +2 -0
  15. package/dist/components/source-fold/source-fold.svelte +225 -0
  16. package/dist/components/source-fold/source-fold.svelte.d.ts +17 -0
  17. package/dist/components/source-list/source-list.svelte +5 -3
  18. package/dist/components/source-list/source-list.svelte.d.ts +2 -0
  19. package/dist/components/thinking-row/thinking-row.svelte +37 -16
  20. package/dist/components/thinking-row/thinking-row.svelte.d.ts +2 -0
  21. package/dist/components/tool-row/tool-row.svelte +130 -38
  22. package/dist/components/visual-block/chart-draw.svelte +26 -0
  23. package/dist/components/visual-block/chart-draw.svelte.d.ts +7 -0
  24. package/dist/components/visual-block/chart.svelte +73 -0
  25. package/dist/components/visual-block/chart.svelte.d.ts +8 -0
  26. package/dist/components/visual-block/entity-card.svelte +94 -0
  27. package/dist/components/visual-block/entity-card.svelte.d.ts +7 -0
  28. package/dist/components/visual-block/timeline.svelte +99 -0
  29. package/dist/components/visual-block/timeline.svelte.d.ts +7 -0
  30. package/dist/components/visual-block/visual-block.svelte +53 -0
  31. package/dist/components/visual-block/visual-block.svelte.d.ts +13 -0
  32. package/dist/components/written-card/written-card.svelte +97 -49
  33. package/dist/components/written-card/written-card.svelte.d.ts +1 -1
  34. package/dist/copy.d.ts +54 -0
  35. package/dist/copy.js +44 -1
  36. package/dist/diff.d.ts +8 -0
  37. package/dist/diff.js +37 -0
  38. package/dist/steps.d.ts +28 -0
  39. package/dist/steps.js +168 -0
  40. package/dist/transcript.svelte.d.ts +4 -0
  41. package/dist/transcript.svelte.js +13 -2
  42. package/dist/visuals.d.ts +46 -0
  43. package/dist/visuals.js +84 -0
  44. package/dist/web-sources.d.ts +19 -3
  45. package/dist/web-sources.js +39 -7
  46. package/dist/written.d.ts +26 -1
  47. package/dist/written.js +42 -0
  48. package/package.json +8 -1
  49. package/dist/components/web-list/index.d.ts +0 -2
  50. package/dist/components/web-list/index.js +0 -2
  51. package/dist/components/web-list/web-list.svelte +0 -84
  52. package/dist/components/web-list/web-list.svelte.d.ts +0 -9
@@ -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,53 @@
1
+ <!--
2
+ One fenced block of a language the host has a renderer for.
3
+
4
+ While its closing fence has not arrived the block is a placeholder: half a
5
+ JSON document is not a chart. A closed block is parsed by its renderer, and
6
+ one that will not parse is shown as the code it was, never a blank.
7
+ -->
8
+ <script lang="ts">
9
+ import type { ChatCopy } from '../../copy';
10
+ import type { Visual } from '../../visuals';
11
+
12
+ interface Props {
13
+ lang: string;
14
+ visual: Visual;
15
+ source: string;
16
+ closed: boolean;
17
+ streaming: boolean;
18
+ words: ChatCopy;
19
+ }
20
+
21
+ let { lang, visual, source, closed, streaming, words }: Props = $props();
22
+
23
+ const drawn = $derived.by(() => {
24
+ if (!closed) return null;
25
+ try {
26
+ return { value: visual.parse(source) };
27
+ } catch {
28
+ return null;
29
+ }
30
+ });
31
+ const Drawing = $derived(visual.component);
32
+ </script>
33
+
34
+ {#if !closed && streaming}
35
+ <p class="ds-lib-drawing" role="status">{words.drawing}</p>
36
+ {:else if drawn}
37
+ <Drawing value={drawn.value} {source} />
38
+ {:else}
39
+ <!-- Marked, so the highlighter that follows the prose leaves what this owns. -->
40
+ <pre data-highlighted="true"><code class="language-{lang}">{source}</code></pre>
41
+ {/if}
42
+
43
+ <style>
44
+ .ds-lib-drawing {
45
+ margin: 0.75em 0;
46
+ border: 1px dashed var(--ds-color-border);
47
+ border-radius: var(--ds-radius-lg);
48
+ padding: 1.25rem 1rem;
49
+ color: var(--ds-color-muted-foreground);
50
+ font-size: 0.875rem;
51
+ text-align: center;
52
+ }
53
+ </style>
@@ -0,0 +1,13 @@
1
+ import type { ChatCopy } from '../../copy';
2
+ import type { Visual } from '../../visuals';
3
+ interface Props {
4
+ lang: string;
5
+ visual: Visual;
6
+ source: string;
7
+ closed: boolean;
8
+ streaming: boolean;
9
+ words: ChatCopy;
10
+ }
11
+ declare const VisualBlock: import("svelte").Component<Props, {}, "">;
12
+ type VisualBlock = ReturnType<typeof VisualBlock>;
13
+ export default VisualBlock;
@@ -1,12 +1,14 @@
1
1
  <!--
2
- A document the persona wrote, as a card in the turn: its name and version,
3
- Open (the reading column shows that version) and a download link for the
4
- source and for each format the app renders it as.
2
+ A document the persona wrote, as a card in the turn: a page icon, its title,
3
+ "Minute · 1 page · v1" and Open (the reading column shows that version),
4
+ with a chip under it that downloads it: Word where the app renders one, else
5
+ the source as written.
5
6
  -->
6
7
  <script lang="ts">
8
+ import DownloadIcon from '@lucide/svelte/icons/download';
7
9
  import FileTextIcon from '@lucide/svelte/icons/file-text';
8
10
  import { fill, resolveCopy, type ChatCopy } from '../../copy';
9
- import { downloads, type Written, type WrittenUrl } from '../../written';
11
+ import { chipFor, writtenMeta, type Written, type WrittenUrl } from '../../written';
10
12
 
11
13
  interface Props {
12
14
  written: Written;
@@ -14,7 +16,7 @@
14
16
  open?: boolean;
15
17
  /** Omit and the card lists and links but does not open. */
16
18
  onopen?: () => void;
17
- /** Omit and no download links render. */
19
+ /** Omit and no download chip renders. */
18
20
  url?: WrittenUrl;
19
21
  copy?: Partial<ChatCopy>;
20
22
  }
@@ -22,54 +24,75 @@
22
24
  let { written, open = false, onopen, url, copy }: Props = $props();
23
25
 
24
26
  const words = $derived(resolveCopy(copy));
27
+ const chip = $derived(chipFor(written, words));
25
28
  </script>
26
29
 
27
- <div class="ds-lib-written" class:is-open={open}>
28
- <span class="ds-lib-written-icon" aria-hidden="true"><FileTextIcon size={16} /></span>
29
- <span class="ds-lib-written-text">
30
- <span class="ds-lib-written-name">{written.name}</span>
31
- <span class="ds-lib-written-version">{fill(words.writtenVersion, { n: written.version })}</span>
32
- </span>
33
- {#if onopen}
34
- <button type="button" class="ds-lib-written-open" onclick={onopen}>
35
- {open ? words.showingArtefact : words.openArtefact}
36
- </button>
37
- {/if}
30
+ <div class="ds-lib-written">
31
+ <div class="ds-lib-written-card" class:is-open={open}>
32
+ <span class="ds-lib-written-icon" aria-hidden="true"><FileTextIcon size={20} /></span>
33
+ <span class="ds-lib-written-text">
34
+ <span class="ds-lib-written-name">{written.title}</span>
35
+ <span class="ds-lib-written-meta">{writtenMeta(written, words)}</span>
36
+ </span>
37
+ {#if onopen}
38
+ <button type="button" class="ds-lib-written-open" onclick={onopen}>
39
+ {open ? words.showingArtefact : words.openArtefact}
40
+ </button>
41
+ {/if}
42
+ </div>
38
43
  {#if url}
39
- <ul class="ds-lib-written-links" aria-label={words.download}>
40
- {#each downloads(written, words) as link (link.format)}
41
- <li><a href={url(written.name, written.version, link.format)}>{link.label}</a></li>
42
- {/each}
43
- </ul>
44
+ <a
45
+ class="ds-lib-written-chip"
46
+ href={url(written.name, written.version, chip.format)}
47
+ download={chip.file}
48
+ >
49
+ <FileTextIcon size={14} aria-hidden="true" />
50
+ <span class="ds-lib-written-file">{chip.file}</span>
51
+ <span class="ds-lib-written-format">
52
+ {chip.label} · {fill(words.writtenVersion, { n: written.version })}
53
+ </span>
54
+ <span class="ds-lib-written-download">
55
+ <DownloadIcon size={14} aria-hidden="true" />{words.download}
56
+ </span>
57
+ </a>
44
58
  {/if}
45
59
  </div>
46
60
 
47
61
  <style>
48
62
  .ds-lib-written {
49
63
  display: flex;
50
- flex-wrap: wrap;
64
+ flex-direction: column;
65
+ align-items: flex-start;
66
+ gap: 0.5rem;
67
+ }
68
+
69
+ .ds-lib-written-card {
70
+ display: flex;
71
+ width: 100%;
72
+ max-width: 36rem;
51
73
  align-items: center;
52
- gap: 0.5rem 0.75rem;
74
+ gap: 0.75rem;
53
75
  border: 1px solid var(--ds-color-border);
54
76
  border-radius: var(--ds-radius-lg);
55
77
  background: var(--ds-color-surface-1);
56
78
  padding: 0.625rem 0.75rem;
57
79
  }
58
80
 
59
- .ds-lib-written.is-open {
81
+ .ds-lib-written-card.is-open {
60
82
  border-color: var(--ds-color-primary);
61
83
  }
62
84
 
85
+ /* A sheet of paper: light in either theme, as the document is. */
63
86
  .ds-lib-written-icon {
64
87
  display: flex;
65
- width: 2rem;
66
- height: 2rem;
88
+ width: 2.5rem;
89
+ height: 2.75rem;
67
90
  flex: none;
68
91
  align-items: center;
69
92
  justify-content: center;
70
- border: 1px solid var(--ds-color-border);
71
- border-radius: var(--ds-radius-md);
72
- color: var(--ds-color-muted-foreground);
93
+ border-radius: var(--ds-radius-sm);
94
+ background: var(--ds-paper, #f1f1ef);
95
+ color: var(--ds-paper-ink, #1c1c1c);
73
96
  }
74
97
 
75
98
  .ds-lib-written-text {
@@ -80,51 +103,76 @@
80
103
  }
81
104
 
82
105
  .ds-lib-written-name {
83
- font-size: 0.875rem;
84
- font-weight: 500;
106
+ font-size: 0.9375rem;
107
+ font-weight: 600;
85
108
  line-height: 1.25rem;
86
109
  overflow-wrap: anywhere;
87
110
  }
88
111
 
89
- .ds-lib-written-version {
112
+ .ds-lib-written-meta {
90
113
  color: var(--ds-color-muted-foreground);
91
114
  font-size: var(--ds-text-2xs);
92
115
  }
93
116
 
94
117
  .ds-lib-written-open {
95
- border: 1px solid var(--ds-color-border);
118
+ flex: none;
119
+ border: 0;
96
120
  border-radius: var(--ds-radius-md);
97
121
  background: none;
98
- padding: 0.25rem 0.625rem;
99
- color: inherit;
122
+ padding: 0.5rem 0.625rem;
123
+ color: var(--ds-color-muted-foreground);
100
124
  font: inherit;
101
125
  font-size: 0.8125rem;
102
126
  cursor: pointer;
103
127
  }
104
128
 
129
+ .ds-lib-written-open:hover {
130
+ color: var(--ds-color-foreground);
131
+ }
132
+
105
133
  .ds-lib-written-open:focus-visible,
106
- .ds-lib-written-links a:focus-visible {
134
+ .ds-lib-written-chip:focus-visible {
107
135
  outline: 2px solid var(--ds-color-ring);
108
136
  outline-offset: 2px;
109
137
  }
110
138
 
111
- .ds-lib-written-links {
112
- display: flex;
113
- flex-basis: 100%;
114
- flex-wrap: wrap;
115
- gap: 0.75rem;
116
- margin: 0;
117
- padding: 0;
118
- font-size: var(--ds-text-2xs);
119
- list-style: none;
139
+ .ds-lib-written-chip {
140
+ display: inline-flex;
141
+ max-width: 100%;
142
+ min-height: 2.25rem;
143
+ align-items: center;
144
+ gap: 0.5rem;
145
+ border: 1px solid var(--ds-color-border);
146
+ border-radius: var(--ds-radius-full);
147
+ padding: 0.25rem 0.75rem;
148
+ color: var(--ds-color-foreground);
149
+ font-size: 0.8125rem;
150
+ text-decoration: none;
151
+ }
152
+
153
+ .ds-lib-written-chip:hover {
154
+ background: var(--ds-color-surface-2);
120
155
  }
121
156
 
122
- .ds-lib-written-links a {
157
+ .ds-lib-written-file {
158
+ min-width: 0;
159
+ font-weight: 600;
160
+ overflow-wrap: anywhere;
161
+ }
162
+
163
+ .ds-lib-written-format {
123
164
  color: var(--ds-color-muted-foreground);
124
- text-decoration: underline;
165
+ font-size: var(--ds-text-2xs);
166
+ white-space: nowrap;
125
167
  }
126
168
 
127
- .ds-lib-written-links a:hover {
128
- color: var(--ds-color-foreground);
169
+ .ds-lib-written-download {
170
+ display: inline-flex;
171
+ align-items: center;
172
+ gap: 0.25rem;
173
+ border-inline-start: 1px solid var(--ds-color-border);
174
+ padding-inline-start: 0.5rem;
175
+ font-size: var(--ds-text-2xs);
176
+ white-space: nowrap;
129
177
  }
130
178
  </style>
@@ -6,7 +6,7 @@ interface Props {
6
6
  open?: boolean;
7
7
  /** Omit and the card lists and links but does not open. */
8
8
  onopen?: () => void;
9
- /** Omit and no download links render. */
9
+ /** Omit and no download chip renders. */
10
10
  url?: WrittenUrl;
11
11
  copy?: Partial<ChatCopy>;
12
12
  }
package/dist/copy.d.ts CHANGED
@@ -78,6 +78,60 @@ export interface ChatCopy {
78
78
  editQuestion: string;
79
79
  editSend: string;
80
80
  editFilesNote: string;
81
+ /** The fold over a turn's steps: its sentence is composed from the steps,
82
+ * each phrase lower-case and joined by `stepsAnd` ("Read your attachment,
83
+ * searched the library and the web, and wrote a minute"). `{kind}` is the
84
+ * document's own kind, or `documentKind`. */
85
+ stepsRead: string;
86
+ stepsSearchedLibrary: string;
87
+ stepsSearchedWeb: string;
88
+ stepsSearchedBoth: string;
89
+ stepsOpened: string;
90
+ stepsWrote: string;
91
+ stepsEdited: string;
92
+ stepsThought: string;
93
+ stepsOther: string;
94
+ stepsAnd: string;
95
+ /** What is happening now, while the turn runs. */
96
+ nowRead: string;
97
+ nowLibrary: string;
98
+ nowWeb: string;
99
+ nowOpen: string;
100
+ nowWrite: string;
101
+ nowEdit: string;
102
+ nowThought: string;
103
+ nowOther: string;
104
+ stepCount: string;
105
+ stepCountOne: string;
106
+ /** The right-hand count of a step's row. */
107
+ stepFound: string;
108
+ stepPagesRead: string;
109
+ stepPageRead: string;
110
+ /** A thinking row: while it streams, once done, and with its duration. */
111
+ thinking: string;
112
+ thought: string;
113
+ thoughtFor: string;
114
+ /** The kind of a document that names none. */
115
+ documentKind: string;
116
+ pagesCount: string;
117
+ pageCount: string;
118
+ /** The pane: when a version was written, what changed, and copying it. */
119
+ paneWritten: string;
120
+ paneEdited: string;
121
+ justNow: string;
122
+ changedSince: string;
123
+ copyDocument: string;
124
+ copiedDocument: string;
125
+ /** The sources fold: its summary and its two groups. */
126
+ sourcesCount: string;
127
+ sourcesCountOne: string;
128
+ sourcesLibrary: string;
129
+ sourcesWeb: string;
130
+ fromTheLibrary: string;
131
+ readToday: string;
132
+ readOn: string;
133
+ /** A fenced block still being written. */
134
+ drawing: string;
81
135
  /** Web pages an answer drew on, listed apart from its numbered sources. */
82
136
  fromTheWeb: string;
83
137
  /** A document the persona wrote: its version (`{n}`), the version switcher's
package/dist/copy.js CHANGED
@@ -86,8 +86,51 @@ export function copyFor(name = DEFAULT_PERSONA) {
86
86
  editQuestion: 'Edit',
87
87
  editSend: 'Send',
88
88
  editFilesNote: 'Files attached to the original question are not sent again.',
89
+ stepsRead: 'read your attachment',
90
+ stepsSearchedLibrary: 'searched the library',
91
+ stepsSearchedWeb: 'searched the web',
92
+ stepsSearchedBoth: 'searched the library and the web',
93
+ stepsOpened: 'opened a document',
94
+ stepsWrote: 'wrote a {kind}',
95
+ stepsEdited: 'edited the {kind}',
96
+ stepsThought: 'thought',
97
+ stepsOther: 'looked into it',
98
+ stepsAnd: 'and',
99
+ nowRead: 'Reading your attachment…',
100
+ nowLibrary: 'Searching the library…',
101
+ nowWeb: 'Searching the web…',
102
+ nowOpen: 'Opening a document…',
103
+ nowWrite: 'Writing a {kind}…',
104
+ nowEdit: 'Editing the {kind}…',
105
+ nowThought: 'Thinking…',
106
+ nowOther: 'Working on it…',
107
+ stepCount: '{n} steps',
108
+ stepCountOne: '1 step',
109
+ stepFound: '{n} found',
110
+ stepPagesRead: '{n} pages read',
111
+ stepPageRead: '1 page read',
112
+ thinking: 'Thinking…',
113
+ thought: 'Thought',
114
+ thoughtFor: 'Thought for {n}s',
115
+ documentKind: 'document',
116
+ pagesCount: '{n} pages',
117
+ pageCount: '1 page',
118
+ paneWritten: 'written {when}',
119
+ paneEdited: 'edited {when}',
120
+ justNow: 'just now',
121
+ changedSince: 'Changed since v{n}: {changes}',
122
+ copyDocument: 'Copy',
123
+ copiedDocument: 'Copied',
124
+ sourcesCount: '{n} sources',
125
+ sourcesCountOne: '1 source',
126
+ sourcesLibrary: '{n} library',
127
+ sourcesWeb: '{n} web',
128
+ fromTheLibrary: 'From the library',
129
+ readToday: 'read today',
130
+ readOn: 'read {date}',
131
+ drawing: 'Drawing…',
89
132
  fromTheWeb: 'From the web',
90
- writtenVersion: 'Version {n}',
133
+ writtenVersion: 'v{n}',
91
134
  writtenVersions: 'Version',
92
135
  formatMarkdown: 'Markdown',
93
136
  formatHtml: 'HTML',
package/dist/diff.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * What a version changed, read off the rendered document: each block of the
3
+ * new one that the old one has no block of the same text for is marked
4
+ * `data-changed`, for a bar beside it. A block is a top-level paragraph,
5
+ * heading, quote or code block, a list item, or a table row.
6
+ */
7
+ /** Mark each block of `now` that `before` has no block of the same text for, on the DOM `render` sanitised. */
8
+ export declare function markChanges(now: Element, before: Element): void;
package/dist/diff.js ADDED
@@ -0,0 +1,37 @@
1
+ /**
2
+ * What a version changed, read off the rendered document: each block of the
3
+ * new one that the old one has no block of the same text for is marked
4
+ * `data-changed`, for a bar beside it. A block is a top-level paragraph,
5
+ * heading, quote or code block, a list item, or a table row.
6
+ */
7
+ function blocksOf(body) {
8
+ const blocks = [];
9
+ for (const el of body.children) {
10
+ if (el.tagName === 'UL' || el.tagName === 'OL')
11
+ blocks.push(...el.querySelectorAll(':scope > li'));
12
+ else if (el.tagName === 'TABLE')
13
+ blocks.push(...el.querySelectorAll('tr'));
14
+ else
15
+ blocks.push(el);
16
+ }
17
+ return blocks;
18
+ }
19
+ const textOf = (el) => (el.textContent ?? '').replace(/\s+/g, ' ').trim();
20
+ /** Mark each block of `now` that `before` has no block of the same text for, on the DOM `render` sanitised. */
21
+ export function markChanges(now, before) {
22
+ const held = new Map();
23
+ for (const block of blocksOf(before)) {
24
+ const text = textOf(block);
25
+ held.set(text, (held.get(text) ?? 0) + 1);
26
+ }
27
+ for (const block of blocksOf(now)) {
28
+ const text = textOf(block);
29
+ if (!text)
30
+ continue;
31
+ const left = held.get(text) ?? 0;
32
+ if (left > 0)
33
+ held.set(text, left - 1);
34
+ else
35
+ block.setAttribute('data-changed', '');
36
+ }
37
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * A turn's steps, said in one sentence.
3
+ *
4
+ * The turn folds its steps behind one line composed from them ("Read your
5
+ * attachment, searched the library and the web, and wrote a minute"), the way
6
+ * Claude.ai folds its own. Composed here from what each step IS (its tool and,
7
+ * for a document, the document's own kind), never from the persona's words.
8
+ */
9
+ import { type ChatCopy } from './copy';
10
+ import type { ActivityStep, Block } from './transcript.svelte';
11
+ import type { Written } from './written';
12
+ export type StepKind = 'attachment' | 'library' | 'web' | 'open' | 'write' | 'edit' | 'thought' | 'other';
13
+ /** What sort of step a block is. */
14
+ export declare function stepKind(block: Block): StepKind;
15
+ /**
16
+ * The fold's line: the sentence, and what trails it ("6 steps", or the
17
+ * version a lone edit made).
18
+ */
19
+ export declare function stepsSummary(steps: ActivityStep[], written: Written[], words: ChatCopy): {
20
+ sentence: string;
21
+ trail: string;
22
+ };
23
+ /** What is happening now: the step still running, or the latest one. */
24
+ export declare function stepsNow(steps: ActivityStep[], written: Written[], words: ChatCopy): string;
25
+ /** The count at a step's right hand, where its result shows one. */
26
+ export declare function stepMeta(block: Block, words: ChatCopy): string;
27
+ /** How long a thinking block ran, in whole seconds, where it was timed. */
28
+ export declare function thoughtSeconds(block: Block): number | undefined;