@poodle64/librarian 2026.9.19 → 2026.9.20

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 CHANGED
@@ -228,7 +228,15 @@ again after sending a message folds only what is new.
228
228
  oncite={(citation) => showPage(citation.document_id)}
229
229
  >
230
230
  {#snippet composer()}
231
- <Composer bind:value {running} {name} note="It carries on from where it stopped." {onsubmit} {onstop} />
231
+ <Composer
232
+ bind:value
233
+ {running}
234
+ {name}
235
+ sendWhileRunning
236
+ note="It carries on from where it stopped."
237
+ {onsubmit}
238
+ {onstop}
239
+ />
232
240
  {/snippet}
233
241
  </Conversation>
234
242
  ```
@@ -244,6 +252,7 @@ end that way.
244
252
  | 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 |
245
253
  | 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 |
246
254
  | "Answered without a source" | `copy.notHeld: ''` for a persona that answers from no shelf |
255
+ | 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 |
247
256
 
248
257
  ### Citations
249
258
 
@@ -7,8 +7,10 @@
7
7
  chrome grows with the text and nothing else moves.
8
8
 
9
9
  While Milton is answering the box is disabled and the send button becomes a
10
- stop button. Queuing the next message is not built, and a box that takes
11
- typing it will then discard is worse than one that plainly waits.
10
+ stop button: an ask route takes one question per request, and a box that
11
+ takes typing it will then discard is worse than one that plainly waits. A
12
+ host whose session reads messages while it works (`sendWhileRunning`) keeps
13
+ the box open, with Send beside Stop.
12
14
  -->
13
15
  <script lang="ts">
14
16
  import ArrowUpIcon from '@lucide/svelte/icons/arrow-up';
@@ -52,6 +54,11 @@
52
54
  name?: string;
53
55
  /** Overrides for the package's own words. */
54
56
  copy?: Partial<LibrarianCopy>;
57
+ /** The session reads a message sent while it works — a job whose
58
+ * route writes it onto the running process — so the box stays open
59
+ * and Send sits beside Stop. Words only: the paperclip still waits for
60
+ * the run. Absent, a chat waits for its answer. */
61
+ sendWhileRunning?: boolean;
55
62
  /** One quiet line in the box's footer, where a scope choice would
56
63
  * sit, in the host's words: what sending does here ("It carries on
57
64
  * from where it stopped."). */
@@ -72,9 +79,13 @@
72
79
  onbriefing,
73
80
  name = DEFAULT_PERSONA,
74
81
  copy,
75
- note
82
+ note,
83
+ sendWhileRunning = false
76
84
  }: Props = $props();
77
85
 
86
+ /** Nothing can be said right now: a chat whose answer is still coming. */
87
+ const waiting = $derived(running && !sendWhileRunning);
88
+
78
89
  const words = $derived(resolveCopy(copy, name));
79
90
 
80
91
  // What Milton is being asked about — this document, this collection, or
@@ -124,21 +135,23 @@
124
135
  });
125
136
 
126
137
  // Focus comes back once Milton is done, so a reader can keep going without
127
- // reaching for the mouse. It has to be the transition OUT of `running`: a
138
+ // reaching for the mouse. It has to be the transition OUT of `waiting`: a
128
139
  // disabled element cannot hold focus, so the focus call in `submit()` below
129
- // survives only for a host that never sets `running` — measured, focus lands
130
- // on `body` the frame the box disables and comes back here.
140
+ // survives only for a box that never disables — measured, focus lands on
141
+ // `body` the frame the box disables and comes back here. A box that stayed
142
+ // open never lost focus, so it takes none back from wherever the reader
143
+ // went while the run worked.
131
144
  //
132
145
  // Plain, not `$state`: read and written in the same effect, which would
133
146
  // otherwise re-trigger itself forever.
134
- let wasRunning = false;
147
+ let wasWaiting = false;
135
148
  $effect(() => {
136
- if (wasRunning && !running) textarea?.focus();
137
- wasRunning = running;
149
+ if (wasWaiting && !waiting) textarea?.focus();
150
+ wasWaiting = waiting;
138
151
  });
139
152
 
140
153
  function submit() {
141
- if (!value.trim() || running) return;
154
+ if (!value.trim() || waiting) return;
142
155
  onsubmit();
143
156
  textarea?.focus();
144
157
  }
@@ -225,8 +238,8 @@
225
238
  oninput={grow}
226
239
  onkeydown={keydown}
227
240
  rows="1"
228
- disabled={running}
229
- placeholder={running ? words.answeringPlaceholder : words.askPlaceholder}
241
+ disabled={waiting}
242
+ placeholder={waiting ? words.answeringPlaceholder : words.askPlaceholder}
230
243
  class="ds-lib-input"
231
244
  ></textarea>
232
245
 
@@ -287,7 +300,8 @@
287
300
  <button type="button" class="ds-lib-stop" onclick={onstop} aria-label="Stop">
288
301
  <SquareIcon size={14} fill="currentColor" />
289
302
  </button>
290
- {:else}
303
+ {/if}
304
+ {#if !waiting}
291
305
  <button
292
306
  type="button"
293
307
  class="ds-lib-send"
@@ -25,6 +25,11 @@ interface Props {
25
25
  name?: string;
26
26
  /** Overrides for the package's own words. */
27
27
  copy?: Partial<LibrarianCopy>;
28
+ /** The session reads a message sent while it works — a job whose
29
+ * route writes it onto the running process — so the box stays open
30
+ * and Send sits beside Stop. Words only: the paperclip still waits for
31
+ * the run. Absent, a chat waits for its answer. */
32
+ sendWhileRunning?: boolean;
28
33
  /** One quiet line in the box's footer, where a scope choice would
29
34
  * sit, in the host's words: what sending does here ("It carries on
30
35
  * from where it stopped."). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poodle64/librarian",
3
- "version": "2026.9.19",
3
+ "version": "2026.9.20",
4
4
  "description": "An agent's conversation surface as a consumable Svelte 5 package: the stream client, transcript state and self-styling chat components (transcript, composer, markdown) every household app renders instead of rebuilding, speaking as whichever persona the app names.",
5
5
  "type": "module",
6
6
  "license": "MIT",