@poodle64/librarian 2026.9.10 → 2026.9.11
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 +23 -5
- package/dist/components/activity-group/activity-group.svelte +5 -0
- package/dist/components/thinking-row/thinking-row.svelte +13 -2
- package/dist/components/thinking-row/thinking-row.svelte.d.ts +2 -2
- package/dist/transcript.svelte.d.ts +25 -3
- package/dist/transcript.svelte.js +33 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ src/lib/
|
|
|
28
28
|
markdown/ sanitised, streaming-safe markdown + highlighting
|
|
29
29
|
activity-group/ a whole investigation, as one quiet line
|
|
30
30
|
tool-row/ one tool call
|
|
31
|
-
thinking-row/ one
|
|
31
|
+
thinking-row/ one thought, or one line of between-tool narration
|
|
32
32
|
working/ the pre-first-token "something is happening" indicator
|
|
33
33
|
```
|
|
34
34
|
|
|
@@ -231,6 +231,23 @@ one asks it through `onsuggest` and takes the whole row with it — the moment
|
|
|
231
231
|
between the click and the new turn arriving is otherwise long enough to ask a
|
|
232
232
|
second question by mistake.
|
|
233
233
|
|
|
234
|
+
### Where the answer starts
|
|
235
|
+
|
|
236
|
+
Milton narrates between tool calls — "Let me also check whether…" — and the
|
|
237
|
+
caller stream gives that nowhere to arrive: it carries no `thinking` blocks
|
|
238
|
+
at all, so narration is an ordinary `text` block, identical to the answer
|
|
239
|
+
except in POSITION. `segment()` reads that position: a text block with any
|
|
240
|
+
tool call still to come in the turn is narration and folds into the activity
|
|
241
|
+
group as a thinking-shaped row; the run of text after the LAST tool call is
|
|
242
|
+
the answer. While a turn streams the judgement is provisional — a block that
|
|
243
|
+
is currently last renders as prose, and a tool call arriving after it
|
|
244
|
+
re-homes it — which is why `segment()` is pure and re-derived per event
|
|
245
|
+
rather than deciding once.
|
|
246
|
+
|
|
247
|
+
The cost is an answer Milton interrupts to go back to the shelf: its first
|
|
248
|
+
half folds away. Position is the only signal the stream gives, and a rule
|
|
249
|
+
read off the prose itself would be unexplainable the first time it misfired.
|
|
250
|
+
|
|
234
251
|
### Changing the words
|
|
235
252
|
|
|
236
253
|
Every user-visible string this package renders lives in
|
|
@@ -271,15 +288,16 @@ pnpm run test # build + vitest
|
|
|
271
288
|
pnpm run screenshots # the state grid, real engine (see below)
|
|
272
289
|
```
|
|
273
290
|
|
|
274
|
-
`docs/screenshots/` is
|
|
291
|
+
`docs/screenshots/` is eleven states x three widths x both themes, taken by
|
|
275
292
|
`scripts/screenshots.mjs` against the console's `/librarian` lab route
|
|
276
293
|
running from its own static build. The same script asserts what a screenshot
|
|
277
294
|
cannot: that nothing scrolls sideways at any width, that the source pane
|
|
278
295
|
opens and closes from the keyboard with focus returning to the chip and is
|
|
279
296
|
really draggable, that the scope statement folds once there is a
|
|
280
|
-
conversation over it and reopens from that line,
|
|
281
|
-
|
|
282
|
-
|
|
297
|
+
conversation over it and reopens from that line, that a follow-up chip asks
|
|
298
|
+
its question and takes the rest of the row with it, and that Milton's
|
|
299
|
+
between-tool narration is nowhere in the answer prose before the activity
|
|
300
|
+
line is opened. It exits non-zero on any of them.
|
|
283
301
|
|
|
284
302
|
```bash
|
|
285
303
|
pnpm --filter @poodle64/console run build
|
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
to mention. So there is exactly one row in every state — "Working…" while it
|
|
8
8
|
runs, a count of what was done once it settles — and the steps are behind a
|
|
9
9
|
disclosure for the reader who wants them.
|
|
10
|
+
|
|
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
15
|
-->
|
|
11
16
|
<script lang="ts">
|
|
12
17
|
import ChevronRightIcon from '@lucide/svelte/icons/chevron-right';
|
|
@@ -1,9 +1,20 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
One line of Milton's working-out, behind a chevron.
|
|
3
|
+
|
|
4
|
+
Two different blocks render as this row and deliberately read the same. A
|
|
5
|
+
real `thinking` block is one; the other is a `text` block Milton wrote
|
|
6
|
+
BETWEEN two tool calls — "Let me also check whether…" — which the caller
|
|
7
|
+
stream gives no way to tell from the answer except by position, and which
|
|
8
|
+
read as answer prose on production until `segment()` started re-homing it
|
|
9
|
+
here. To a reader both are the same thing: what he was working through, not
|
|
10
|
+
what he concluded. So both fold, and both fold by default.
|
|
11
|
+
-->
|
|
1
12
|
<script lang="ts">
|
|
2
13
|
import ChevronRightIcon from '@lucide/svelte/icons/chevron-right';
|
|
3
|
-
import type { ThinkingBlock } from '../../transcript.svelte';
|
|
14
|
+
import type { TextBlock, ThinkingBlock } from '../../transcript.svelte';
|
|
4
15
|
|
|
5
16
|
interface Props {
|
|
6
|
-
block: ThinkingBlock;
|
|
17
|
+
block: ThinkingBlock | TextBlock;
|
|
7
18
|
active: boolean;
|
|
8
19
|
}
|
|
9
20
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { ThinkingBlock } from '../../transcript.svelte';
|
|
1
|
+
import type { TextBlock, ThinkingBlock } from '../../transcript.svelte';
|
|
2
2
|
interface Props {
|
|
3
|
-
block: ThinkingBlock;
|
|
3
|
+
block: ThinkingBlock | TextBlock;
|
|
4
4
|
active: boolean;
|
|
5
5
|
}
|
|
6
6
|
declare const ThinkingRow: import("svelte").Component<Props, {}, "">;
|
|
@@ -91,8 +91,11 @@ export declare class Transcript {
|
|
|
91
91
|
apply(event: AgentEvent): void;
|
|
92
92
|
}
|
|
93
93
|
export interface ActivityStep {
|
|
94
|
-
/** The block this step stands for; a repeated step keeps the FIRST.
|
|
95
|
-
|
|
94
|
+
/** The block this step stands for; a repeated step keeps the FIRST.
|
|
95
|
+
*
|
|
96
|
+
* A `text` block here is interstitial narration, not the answer — see
|
|
97
|
+
* `segment()`. */
|
|
98
|
+
block: Block;
|
|
96
99
|
/** How many identical consecutive steps collapsed into this one. */
|
|
97
100
|
repeats: number;
|
|
98
101
|
}
|
|
@@ -124,7 +127,7 @@ export interface Turn {
|
|
|
124
127
|
/**
|
|
125
128
|
* Fold a turn's flat block list into what a reader should actually see.
|
|
126
129
|
*
|
|
127
|
-
*
|
|
130
|
+
* Three problems this solves, all reported off a real transcript:
|
|
128
131
|
*
|
|
129
132
|
* 1. Fifteen tool rows stood between the question and the first word of the
|
|
130
133
|
* answer, so the answer had to be scrolled to. Contiguous activity becomes
|
|
@@ -132,6 +135,25 @@ export interface Turn {
|
|
|
132
135
|
* 2. "Reading pspf guidelines 2026" appeared five times in a row — five pages
|
|
133
136
|
* of one document, which is one act of reading to a human. Consecutive
|
|
134
137
|
* steps with the same label collapse to one row carrying a count.
|
|
138
|
+
* 3. Milton's between-tool narration was rendering unfolded, as answer prose.
|
|
139
|
+
* The caller stream carries no `thinking` blocks at all — measured on
|
|
140
|
+
* production 11/09/2026 — so "Let me also check whether…" arrives as an
|
|
141
|
+
* ordinary `text` block, indistinguishable from the answer except by
|
|
142
|
+
* POSITION. A text block with any tool call still to come in this turn is
|
|
143
|
+
* narration; only the run of text after the LAST tool call is the answer.
|
|
144
|
+
* Narration folds into the activity group as a thinking-shaped step.
|
|
145
|
+
*
|
|
146
|
+
* That third rule is provisional while a turn streams, and deliberately so: a
|
|
147
|
+
* text block that is currently last IS the answer as far as anything can know,
|
|
148
|
+
* and renders as prose. The tool call that arrives after it re-homes it into
|
|
149
|
+
* the group. Nothing here holds state to make that work — `segment` is pure
|
|
150
|
+
* and re-derived on every event, so re-homing is just the next call returning
|
|
151
|
+
* a different shape.
|
|
152
|
+
*
|
|
153
|
+
* The cost of the rule is a real answer that Milton interrupts to go back to
|
|
154
|
+
* the shelf: its first half folds away. That is the trade taken knowingly —
|
|
155
|
+
* the position of a block is the only signal the stream gives, and a rule read
|
|
156
|
+
* off the prose itself would be unexplainable the first time it misfired.
|
|
135
157
|
*/
|
|
136
158
|
export declare function segment(blocks: Block[]): Segment[];
|
|
137
159
|
/** One line describing a whole investigation, for the collapsed state.
|
|
@@ -267,7 +267,7 @@ function renderResult(content) {
|
|
|
267
267
|
/**
|
|
268
268
|
* Fold a turn's flat block list into what a reader should actually see.
|
|
269
269
|
*
|
|
270
|
-
*
|
|
270
|
+
* Three problems this solves, all reported off a real transcript:
|
|
271
271
|
*
|
|
272
272
|
* 1. Fifteen tool rows stood between the question and the first word of the
|
|
273
273
|
* answer, so the answer had to be scrolled to. Contiguous activity becomes
|
|
@@ -275,20 +275,44 @@ function renderResult(content) {
|
|
|
275
275
|
* 2. "Reading pspf guidelines 2026" appeared five times in a row — five pages
|
|
276
276
|
* of one document, which is one act of reading to a human. Consecutive
|
|
277
277
|
* steps with the same label collapse to one row carrying a count.
|
|
278
|
+
* 3. Milton's between-tool narration was rendering unfolded, as answer prose.
|
|
279
|
+
* The caller stream carries no `thinking` blocks at all — measured on
|
|
280
|
+
* production 11/09/2026 — so "Let me also check whether…" arrives as an
|
|
281
|
+
* ordinary `text` block, indistinguishable from the answer except by
|
|
282
|
+
* POSITION. A text block with any tool call still to come in this turn is
|
|
283
|
+
* narration; only the run of text after the LAST tool call is the answer.
|
|
284
|
+
* Narration folds into the activity group as a thinking-shaped step.
|
|
285
|
+
*
|
|
286
|
+
* That third rule is provisional while a turn streams, and deliberately so: a
|
|
287
|
+
* text block that is currently last IS the answer as far as anything can know,
|
|
288
|
+
* and renders as prose. The tool call that arrives after it re-homes it into
|
|
289
|
+
* the group. Nothing here holds state to make that work — `segment` is pure
|
|
290
|
+
* and re-derived on every event, so re-homing is just the next call returning
|
|
291
|
+
* a different shape.
|
|
292
|
+
*
|
|
293
|
+
* The cost of the rule is a real answer that Milton interrupts to go back to
|
|
294
|
+
* the shelf: its first half folds away. That is the trade taken knowingly —
|
|
295
|
+
* the position of a block is the only signal the stream gives, and a rule read
|
|
296
|
+
* off the prose itself would be unexplainable the first time it misfired.
|
|
278
297
|
*/
|
|
279
298
|
export function segment(blocks) {
|
|
280
299
|
const out = [];
|
|
281
300
|
let current = null;
|
|
282
|
-
|
|
283
|
-
|
|
301
|
+
let lastTool = -1;
|
|
302
|
+
for (let i = 0; i < blocks.length; i += 1)
|
|
303
|
+
if (blocks[i].kind === 'tool')
|
|
304
|
+
lastTool = i;
|
|
305
|
+
for (const [i, block] of blocks.entries()) {
|
|
306
|
+
if (block.kind === 'text' && i > lastTool) {
|
|
284
307
|
current = null;
|
|
285
308
|
out.push(block);
|
|
286
309
|
continue;
|
|
287
310
|
}
|
|
288
311
|
// A thinking block with no text is a row whose chevron opens on nothing.
|
|
289
312
|
// Claude Code's thinking display defaults to "omitted", so most arrive
|
|
290
|
-
// empty — rendering them is worse than dropping them.
|
|
291
|
-
|
|
313
|
+
// empty — rendering them is worse than dropping them. An empty narration
|
|
314
|
+
// block is the same row, for the same reason.
|
|
315
|
+
if (block.kind !== 'tool' && !block.text.trim())
|
|
292
316
|
continue;
|
|
293
317
|
if (!current) {
|
|
294
318
|
current = {
|
|
@@ -318,6 +342,10 @@ function sameStep(a, b) {
|
|
|
318
342
|
return false;
|
|
319
343
|
if (a.kind === 'thinking')
|
|
320
344
|
return true;
|
|
345
|
+
// Two narration sentences are two things Milton said; collapsing them to
|
|
346
|
+
// one row with a count would lose the second one entirely.
|
|
347
|
+
if (a.kind === 'text')
|
|
348
|
+
return false;
|
|
321
349
|
const left = describe(a);
|
|
322
350
|
const right = describe(b);
|
|
323
351
|
return left.verb === right.verb && left.object === right.object;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poodle64/librarian",
|
|
3
|
-
"version": "2026.9.
|
|
3
|
+
"version": "2026.9.11",
|
|
4
4
|
"description": "Milton's conversation surface as a consumable Svelte 5 package: the stream client, transcript state and chat components (transcript, composer, markdown) every household app renders instead of rebuilding.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|