@poodle64/librarian 2026.10.0 → 2026.10.1

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
@@ -174,7 +174,7 @@ on it fires once and never again.
174
174
  | Attachments | nothing: `ask()` posts multipart (`question`, `resume`, `collection`, `files[]`) whenever `files` is non-empty, and JSON when it is not. The route must accept both |
175
175
  | Reading a cited document | `loadDocument(document_id) => Promise<{title, sections: [{anchor, heading, text}]}>`, proxied through the app's own authenticated route (caspo: `GET /api/sources/documents/{id}/content`) |
176
176
  | Asking again | `onregenerate`: re-send the last question as a NEW turn; the package exposes the action and never re-asks by itself |
177
- | Choosing a collection | `Composer`'s `collections` (`{ name, summary }[]`, the ones the person may read) and `collection` (the chosen name; undefined is all of them), with `oncollection(name \| undefined)`. It renders from two collections up, each by its summary, never its name |
177
+ | Choosing a collection | `Composer`'s `collections` (`{ name, title?, description? }[]`, the ones the person may read) and `collection` (the chosen name; undefined is all of them), with `oncollection(name \| undefined)`. It renders from two collections up, each by its title (the name made readable where there is none) with the description as its hover, never a retrieval summary |
178
178
  | The empty state | `welcome` and up to three `examples` |
179
179
  | What this surface covers | `scope`: one statement per persona, in the host's own words. Rendered above the first turn and folded to a line once the conversation starts |
180
180
  | The words themselves | `copy`: a partial of `ChatCopy`. Every component resolves it itself, so overriding one line does not mean restating the rest |
@@ -34,10 +34,21 @@
34
34
  const DEFAULT_TURN_LIMIT = 8;
35
35
 
36
36
  /** A collection a person may read, as the app lists it: `name` is what a
37
- * question is narrowed to, `summary` is what the picker shows. */
37
+ * question is narrowed to, `title` is what the picker shows (the name,
38
+ * made readable, where there is none) and `description` is its one-line
39
+ * hover. A retrieval summary is never a field here: it is filing history. */
38
40
  export interface CollectionChoice {
39
41
  name: string;
40
- summary: string;
42
+ title?: string;
43
+ description?: string;
44
+ }
45
+
46
+ /** `defence-housing` as a reader would first read it: "Defence housing". */
47
+ function titleOf(choice: CollectionChoice): string {
48
+ const given = choice.title?.trim();
49
+ if (given) return given;
50
+ const words = choice.name.replace(/[-_]+/g, ' ').trim();
51
+ return words ? words[0]!.toLocaleUpperCase() + words.slice(1) : choice.name;
41
52
  }
42
53
 
43
54
  interface Props {
@@ -121,7 +132,7 @@
121
132
  // Who the question is put to: every collection the person may read, or one
122
133
  // of them. A person with one collection (or none) has nothing to pick, so
123
134
  // the row renders nothing rather than a chip that only reselects itself.
124
- // A collection is shown by its summary, the words a reader knows it by.
135
+ // A collection is shown by its title, the words a reader knows it by.
125
136
  const hasChoice = $derived(collections.length > 1);
126
137
 
127
138
  // Three chips in a narrow column clipped all three to fragments
@@ -323,9 +334,10 @@
323
334
  class="ds-lib-scope-chip"
324
335
  aria-pressed={collection === choice.name}
325
336
  class:is-chosen={collection === choice.name}
337
+ title={choice.description || undefined}
326
338
  onclick={() => oncollection?.(choice.name)}
327
339
  >
328
- {choice.summary || choice.name}
340
+ {titleOf(choice)}
329
341
  </button>
330
342
  {/each}
331
343
  </div>
@@ -1,9 +1,12 @@
1
1
  import { type ChatCopy } from '../../copy';
2
2
  /** A collection a person may read, as the app lists it: `name` is what a
3
- * question is narrowed to, `summary` is what the picker shows. */
3
+ * question is narrowed to, `title` is what the picker shows (the name,
4
+ * made readable, where there is none) and `description` is its one-line
5
+ * hover. A retrieval summary is never a field here: it is filing history. */
4
6
  export interface CollectionChoice {
5
7
  name: string;
6
- summary: string;
8
+ title?: string;
9
+ description?: string;
7
10
  }
8
11
  interface Props {
9
12
  value: string;
package/dist/copy.d.ts CHANGED
@@ -43,7 +43,7 @@ export interface ChatCopy {
43
43
  answeringPlaceholder: string;
44
44
  /** The composer's collection picker: its group's accessible name, and the
45
45
  * choice that asks across everything the person may read. A collection is
46
- * shown by its own summary, supplied by the app. */
46
+ * shown by its own title, supplied by the app. */
47
47
  collectionsLabel: string;
48
48
  allCollections: string;
49
49
  /** Past a few turns, the quiet line above the composer that offers to
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poodle64/librarian",
3
- "version": "2026.10.0",
3
+ "version": "2026.10.1",
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",