@poodle64/librarian 2026.9.2 → 2026.9.4

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.
@@ -41,7 +41,9 @@
41
41
 
42
42
  // What the agent is being asked about — chat with this document, this
43
43
  // collection, or the whole library. Callers that have no narrower scope
44
- // pass neither name and only "All collections" ever renders.
44
+ // pass neither name, leaving "All collections" the sole entry — and a
45
+ // sole entry is not a choice, so `hasChoice` below hides it rather than
46
+ // rendering a chip with nothing to switch to.
45
47
  const choices = $derived(
46
48
  [
47
49
  documentName ? { id: 'document' as const, label: documentName } : null,
@@ -50,6 +52,12 @@
50
52
  ].filter((c) => c !== null)
51
53
  );
52
54
 
55
+ // A room with one shelf set has one scope, fixed by the caller never
56
+ // passing a name — no pick to make, so the row renders nothing rather
57
+ // than a chip that only ever reselects itself (design-system, the
58
+ // fixed-scope Composer defect).
59
+ const hasChoice = $derived(choices.length > 1);
60
+
53
61
  // Three chips in a narrow column clipped all three to fragments
54
62
  // ("defence-s…", "All colle…"). Wrapping beats truncating: a chip a reader
55
63
  // cannot finish reading is not a control, it is decoration.
@@ -76,20 +84,22 @@
76
84
  ></textarea>
77
85
 
78
86
  <div class="flex items-center gap-2 px-3 pb-2.5">
79
- <div class="flex min-w-0 flex-wrap items-center gap-1">
80
- {#each choices as choice (choice.id)}
81
- <button
82
- type="button"
83
- onclick={() => onscope(choice.id)}
84
- class="max-w-full truncate rounded-full px-2 py-0.5 text-xs transition-colors {scope ===
85
- choice.id
86
- ? 'bg-primary/15 text-foreground border-primary/40 border'
87
- : 'text-muted-foreground hover:text-foreground border border-transparent'}"
88
- >
89
- {choice.label}
90
- </button>
91
- {/each}
92
- </div>
87
+ {#if hasChoice}
88
+ <div class="flex min-w-0 flex-wrap items-center gap-1">
89
+ {#each choices as choice (choice.id)}
90
+ <button
91
+ type="button"
92
+ onclick={() => onscope(choice.id)}
93
+ class="max-w-full truncate rounded-full px-2 py-0.5 text-xs transition-colors {scope ===
94
+ choice.id
95
+ ? 'bg-primary/15 text-foreground border-primary/40 border'
96
+ : 'text-muted-foreground hover:text-foreground border border-transparent'}"
97
+ >
98
+ {choice.label}
99
+ </button>
100
+ {/each}
101
+ </div>
102
+ {/if}
93
103
  <span class="flex-1"></span>
94
104
  {#if running}
95
105
  <button
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The scope chip renders only when there is a genuine pick between two or
3
+ * more scopes (design-system, the fixed-scope Composer defect). A room with
4
+ * one shelf set passes neither `documentName` nor `collectionName`, leaving
5
+ * "All collections" as the sole entry — and a sole entry is console
6
+ * furniture, not a control.
7
+ */
8
+ import { describe, it, expect } from 'vitest';
9
+ import { render, screen } from '@testing-library/svelte';
10
+ import Composer from './composer.svelte';
11
+ describe('Composer scope chip', () => {
12
+ it('renders nothing when the caller has fixed the scope', () => {
13
+ render(Composer, {
14
+ props: {
15
+ value: '',
16
+ running: false,
17
+ scope: 'library',
18
+ onscope: () => { },
19
+ onsubmit: () => { },
20
+ onstop: () => { }
21
+ }
22
+ });
23
+ expect(screen.queryByText('All collections')).not.toBeInTheDocument();
24
+ });
25
+ it('renders chips once a narrower scope is offered', () => {
26
+ render(Composer, {
27
+ props: {
28
+ value: '',
29
+ running: false,
30
+ scope: 'library',
31
+ collectionName: 'household-legal',
32
+ onscope: () => { },
33
+ onsubmit: () => { },
34
+ onstop: () => { }
35
+ }
36
+ });
37
+ expect(screen.getByText('All collections')).toBeInTheDocument();
38
+ expect(screen.getByText('household-legal')).toBeInTheDocument();
39
+ });
40
+ });
@@ -146,6 +146,12 @@
146
146
  requirement numbers — so it is the main way a reader picks a reference
147
147
  out of a paragraph. Bordered rather than merely tinted, because a tint
148
148
  alone is invisible against this palette's flat surfaces. */
149
+ /* Inline, in running text: wraps at word boundaries like the prose around it
150
+ rather than scrolling in its own box — that pattern is reserved for wide
151
+ BLOCK content (a table, a fenced code block) that a reader scrolls past;
152
+ an inline identifier a reader is reading stays in flow. A long citation
153
+ title is exactly the case: nowrap clipped it at the container edge on a
154
+ phone with no way to read the rest. */
149
155
  .agent-prose :global(code) {
150
156
  font-family: var(--ds-font-mono, monospace);
151
157
  font-size: 0.8125em;
@@ -153,7 +159,8 @@
153
159
  border: 1px solid var(--border);
154
160
  border-radius: 0.35em;
155
161
  padding: 0.1em 0.35em;
156
- white-space: nowrap;
162
+ white-space: normal;
163
+ overflow-wrap: anywhere;
157
164
  }
158
165
 
159
166
  .agent-prose :global(pre) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poodle64/librarian",
3
- "version": "2026.9.2",
3
+ "version": "2026.9.4",
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",