@poodle64/librarian 2026.9.9 → 2026.9.10

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/dist/citations.js CHANGED
@@ -119,7 +119,10 @@ export function formatVerified(iso) {
119
119
  const [, year, month, day] = match;
120
120
  const index = Number(month) - 1;
121
121
  const date = Number(day);
122
- if (index < 0 || index > 11 || date < 1 || date > 31)
122
+ // Round-tripped rather than bounds-checked: 1-31 admits "31 Apr 2026",
123
+ // and a mark that shows a day that never happened is worse than no mark.
124
+ const made = new Date(Date.UTC(Number(year), index, date));
125
+ if (made.getUTCMonth() !== index || made.getUTCDate() !== date)
123
126
  return null;
124
127
  return `${date} ${MONTHS[index]} ${year}`;
125
128
  }
@@ -105,9 +105,28 @@
105
105
  );
106
106
 
107
107
  const hasAnswer = $derived(texts.length > 0);
108
+
109
+ /**
110
+ * What went wrong, if anything did — in words, whether or not the run gave
111
+ * any.
112
+ *
113
+ * Two different failures reach a turn and only one of them carries a
114
+ * message. `library_error` (the stream never opened) sets `error`; a run
115
+ * that opened and then failed sets `is_error` on its terminal frame and
116
+ * says nothing at all — Claude Code's own `error_max_turns` and
117
+ * `error_during_execution` are exactly that shape. Reading only `error`
118
+ * left the second kind rendering as a clean, complete answer: no banner,
119
+ * a duration badge, and — worse — the "he holds nothing on this" line,
120
+ * which is a claim about the shelf made off a run that never finished
121
+ * looking.
122
+ */
123
+ const failure = $derived(
124
+ outcome?.error ?? (outcome?.isError ? words.answerFailed : null)
125
+ );
126
+
108
127
  // A failed turn settles too, and "Ask again" is the one thing a reader wants
109
128
  // from it — there is just nothing to copy.
110
- const settled = $derived(!running && (hasAnswer || Boolean(outcome?.error)));
129
+ const settled = $derived(!running && (hasAnswer || Boolean(failure)));
111
130
 
112
131
  /**
113
132
  * Milton answered and cited nothing, and we WATCHED him do it.
@@ -120,7 +139,7 @@
120
139
  * here knows whether there were any.
121
140
  */
122
141
  const notHeld = $derived(
123
- settled && hasAnswer && sources.length === 0 && Boolean(outcome) && !outcome?.error
142
+ settled && hasAnswer && sources.length === 0 && Boolean(outcome) && !failure
124
143
  );
125
144
 
126
145
  // A follow-up is offered once. Clicking it asks the question, which puts a
@@ -248,12 +267,12 @@
248
267
  </section>
249
268
  {/if}
250
269
 
251
- {#if outcome?.error}
270
+ {#if failure}
252
271
  <p
253
272
  class="border-status-error/40 bg-status-error/10 text-foreground max-w-[72ch] rounded-lg border px-3 py-2 text-sm"
254
273
  role="alert"
255
274
  >
256
- {outcome.error}
275
+ {failure}
257
276
  </p>
258
277
  {/if}
259
278
 
@@ -281,7 +300,7 @@
281
300
  <span>{words.askAgain}</span>
282
301
  </button>
283
302
  {/if}
284
- {#if outcome && !outcome.error}
303
+ {#if outcome && !failure}
285
304
  <span class="pl-1 font-mono text-xs tabular-nums opacity-70">
286
305
  {((outcome.durationMs ?? 0) / 1000).toFixed(1)}s
287
306
  </span>
package/dist/copy.d.ts CHANGED
@@ -19,6 +19,8 @@ export interface LibrarianCopy {
19
19
  notVerified: string;
20
20
  /** Shown under an answer that finished without citing anything. */
21
21
  notHeld: string;
22
+ /** Shown when the run itself ended in error and said nothing about why. */
23
+ answerFailed: string;
22
24
  /** Heading over the list of what an answer cited. */
23
25
  sources: string;
24
26
  /** Heading over the follow-up questions the librarian offered. */
package/dist/copy.js CHANGED
@@ -20,6 +20,9 @@ export const DEFAULT_COPY = {
20
20
  // reading it has no model of any of those, and the room's own instruction
21
21
  // to Milton forbids him naming them either.
22
22
  notHeld: 'Milton answered this one without a source. He may not hold a document that covers it.',
23
+ // A run can end `is_error` carrying no message at all, and a turn that
24
+ // simply stops is the one thing a reader must not have to guess about.
25
+ answerFailed: 'Milton stopped before he finished this one.',
23
26
  sources: 'Sources',
24
27
  suggestions: 'Ask next',
25
28
  scope: 'What Milton answers from',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poodle64/librarian",
3
- "version": "2026.9.9",
3
+ "version": "2026.9.10",
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",