inline-chat-kit 0.57.0 → 0.58.0

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/CHANGELOG.md CHANGED
@@ -8,6 +8,47 @@ The versions before 1.0 follow the pre-release convention: **a breaking change
8
8
  or new public API bumps the minor**, and the patch is for fixes. Anything that would break an
9
9
  existing install is called out under **Breaking**, with what to do about it.
10
10
 
11
+ ## 0.58.0 — 2026-09-23
12
+
13
+ The composer at the bottom, as an alternative to the inline one — roadmap I2.
14
+ One prop; nothing changes for a host that does not pass it.
15
+
16
+ ### Added
17
+
18
+ - **`composer="inline" | "docked"` on `ChatExperience`.** `"docked"` keeps the
19
+ input at the bottom edge and stacks the conversation above it. The live
20
+ input is still the last turn, so a sent message still becomes its bubble —
21
+ the same element, carried up into the conversation by its layout animation
22
+ as a fresh composer takes its place. A sent message is still brought to the
23
+ top of the view and held there while its answer is written, as it is inline;
24
+ the composer stays on the bottom edge while that happens, and the workspace
25
+ gives up whatever a phone's keyboard covers.
26
+ - **`nextTurn: "after-answer" | "at-send"` on `useChatTurns`.** `"at-send"`
27
+ opens the next input the moment a message goes, so it can be typed into
28
+ while the answer arrives. A send while one is in flight is now refused in
29
+ either mode: it used to start a second run and take the first one's abort
30
+ away.
31
+ - **`busy` on `ChatInput` and `ChatTurnRow`.** Open to type into, closed to
32
+ send from: Enter does nothing, the send glyph is a stop when there is an
33
+ `onStop` and inert when there is not.
34
+ - **`dock` on `Conversation`.** The last child is pinned to the bottom of the
35
+ view — an auto margin while the content is short, `sticky` once it is long —
36
+ and the room an anchor needs is put above it rather than below, where
37
+ `sticky` could not hold it.
38
+
39
+ - **`highlights` and `bookmarks` on `ChatExperience`, `highlights` on
40
+ `ChatTurnRow`, `marking` on `TextHighlighter`.** `highlights={false}` draws
41
+ answers as prose — no marker layer, nothing over the text in the tab order,
42
+ no highlight menu — and takes the saved highlights and the selection-mode
43
+ pair with it. `bookmarks={false}` keeps marking and drops the keeping. Both
44
+ are decided in code rather than offered in the interface, which is also how
45
+ `composer`, `pane`, `surface`, `headerActions` and `composerMenu` work.
46
+
47
+ ### Fixed
48
+
49
+ - **A responding bubble drew a stop with nothing behind it** when the host
50
+ had not passed `onStop`. It is drawn only with somewhere to report to.
51
+
11
52
  ## 0.57.0 — 2026-09-22
12
53
 
13
54
  A pane the host draws itself. `ChatExperience` placed its artifact pane beside
package/README.md CHANGED
@@ -216,6 +216,7 @@ input that morphs into its own bubble rather than a record of what was typed.
216
216
  | `onDraft` | `(id, value) => void` | | |
217
217
  | `onSubmit` | `(id, value) => void` | | |
218
218
  | `onStop` | `() => void` | | |
219
+ | `busy` | `boolean` | | An answer is arriving elsewhere: the composer offers a stop, not a send |
219
220
  | `onEdit` | `(id) => void` | | |
220
221
  | `onCancelEdit` | `(id) => void` | | |
221
222
  | `onCopy` | `(value) => void` | writes to the clipboard | |
@@ -250,6 +251,53 @@ Every callback is optional; a row with none of them renders and can be marked.
250
251
  The row carries `id="turn-<id>"` so a host can scroll to one, and `aria-busy`
251
252
  while its answer is arriving.
252
253
 
254
+ ### The composer at the bottom
255
+
256
+ The kit's argument is that the input is the message: it stands at the end of
257
+ the conversation and becomes the bubble where you typed it. Some products want
258
+ the shape every other chat has instead — the box at the bottom, the
259
+ conversation stacking above it. That is one prop, not a second kit:
260
+
261
+ ```tsx
262
+ <ChatExperience onSend={send} composer="docked" />
263
+ ```
264
+
265
+ What changes, and what does not:
266
+
267
+ - **The box stays at the bottom.** `Conversation` pins its last child there —
268
+ pushed down by an auto margin while the conversation is shorter than the
269
+ view, held by `position: sticky` once it is longer. Both put its bottom on
270
+ the same edge, so nothing moves when the conversation crosses from one to
271
+ the other.
272
+ - **It still becomes the bubble.** The live input *is* the last turn, as it
273
+ always was, so the morph is the one the kit already has. What is new is the
274
+ travel: a fresh composer takes the last place, the sent one is no longer
275
+ last and no longer sticky, and its `layout` animation carries it up into the
276
+ conversation where it now belongs.
277
+ - **The next question can be typed while this one is answered.**
278
+ `useChatTurns` opens the next input at send rather than at settle
279
+ (`nextTurn: "at-send"`), the composer is `busy` while the answer arrives —
280
+ Enter does nothing and the send glyph is a stop — and a send while an answer
281
+ is in flight is refused rather than started on top of it. Queueing it is a
282
+ separate thing.
283
+ - **A sent message still goes to the top.** The same anchor the inline
284
+ composer uses: your question is brought to the top of the view and held
285
+ there while its answer is written underneath. The composer does not move
286
+ with it — it is `sticky`, so the room the anchor scrolls into passes behind
287
+ it. The room itself goes *above* the dock rather than below: `sticky` only
288
+ ever pulls an element up, so room underneath would leave the composer
289
+ stranded in the middle of the view.
290
+ - **The keyboard is allowed for.** On a phone the software keyboard comes up
291
+ over the bottom edge; the workspace gives up what `visualViewport` says is
292
+ covered, so the composer rides on the keyboard's top edge.
293
+ - Everything else is the same: the parts, the pane, the highlighter, editing
294
+ a sent message in place, `labels`, `surface="panes"`.
295
+
296
+ With a `chat` of your own, give its hook `nextTurn: "at-send"`. Assembling the
297
+ rows by hand, the same shape is `<Conversation dock>` with the live row last,
298
+ `busy={isStreaming}` on it, and `onStop` on it rather than on the bubble being
299
+ answered.
300
+
253
301
  ### Two rules everything follows
254
302
 
255
303
  **Surfaces nest in three steps.** A **ground** is what a group of things sits
@@ -951,6 +999,7 @@ instant the reader scrolls away, with a button offering the way back.
951
999
  | `scrollButton` | `boolean` | `true` | The way back |
952
1000
  | `scrollButtonLabel` | `string` | `"Jump to the latest"` | |
953
1001
  | `follow` | `boolean` | `true` | `false` makes it a plain scroll container |
1002
+ | `dock` | `boolean` | `false` | The last child is pinned to the bottom edge — see [The composer at the bottom](#the-composer-at-the-bottom) |
954
1003
  | `className` | `string` | | Goes on the root, which is the box you lay out |
955
1004
  | `viewportClassName` | `string` | | Goes on the element that scrolls — padding belongs here |
956
1005
 
@@ -1436,17 +1485,36 @@ for that one instance.
1436
1485
 
1437
1486
  ### Turning off what you do not use
1438
1487
 
1439
- `ChatExperience` draws a theme toggle and Share in the header and a "+" menu in
1440
- the composer. A host that has none of those says so:
1488
+ Everything here is decided **in code**, not offered in the interface. A
1489
+ product either marks passages or it does not, keeps a theme toggle or does
1490
+ not; a control for choosing is a question nobody asked.
1441
1491
 
1442
1492
  ```tsx
1443
1493
  <ChatExperience
1444
1494
  onSend={send}
1495
+ composer="docked" // or "inline", the default
1496
+ highlights={false} // no marker layer, no highlight menu, no saved ones
1497
+ bookmarks={false} // marking stays, keeping goes
1445
1498
  headerActions={false} // or ["theme"], or ["share"]
1446
1499
  composerMenu={false} // or your own: [{ id, label, icon?, onSelect }]
1500
+ pane="none" // draw the artifact pane yourself
1501
+ surface="panes" // the conversation as a card of its own
1447
1502
  />
1448
1503
  ```
1449
1504
 
1505
+ | Prop | Off means |
1506
+ | --- | --- |
1507
+ | `highlights={false}` | Answers are prose: no marker layer, nothing over the text in the tab order, no highlight menu — and no saved highlights or selection-mode pair, since both are about marking |
1508
+ | `bookmarks={false}` | A passage can still be marked and replied to; nothing is kept. Use `onHighlight` to keep them yourself |
1509
+ | `onThreadReply` omitted | No threads. A thread is opened from a highlight, so `highlights={false}` takes them too |
1510
+ | `headerActions={false}` | No theme toggle, no Share. Your own `actions` are unaffected |
1511
+ | `composerMenu={false}` | No "+" in the composer |
1512
+ | `selectionToggle` omitted | No marker/precise pair in the header (the default) |
1513
+ | `cursor` omitted | No pointer-following cursor (the default) |
1514
+
1515
+ Assembling the rows by hand, the same switch is `highlights` on
1516
+ `<ChatTurnRow>`, which is `marking` on `<TextHighlighter>`.
1517
+
1450
1518
  The saved highlights button is not one of the header's actions — it appears
1451
1519
  only once there is a highlight, and it is the only way back to them. An entry in
1452
1520
  your own menu with id `"attach"` and no `onSelect` opens the file picker.
@@ -150,6 +150,23 @@ export interface ChatExperienceProps {
150
150
  * `ChatLayout`.
151
151
  */
152
152
  surface?: "flush" | "panes";
153
+ /**
154
+ * Where the composer lives.
155
+ *
156
+ * `"inline"` (the default): the input is the message. It stands at the end
157
+ * of the conversation, and what you type becomes the bubble where you typed
158
+ * it — the argument this kit makes.
159
+ *
160
+ * `"docked"`: the input stays at the bottom of the view, the way most chats
161
+ * keep it, and the conversation stacks above it. Sent, it still becomes the
162
+ * bubble — the same element, travelling into the conversation — and a fresh
163
+ * one takes its place at the bottom straight away, so the next question can
164
+ * be typed while this one is being answered. Everything else is the same:
165
+ * the parts, the pane, the highlighter, editing in place.
166
+ *
167
+ * With a `chat` of your own, give its `useChatTurns` `nextTurn: "at-send"`.
168
+ */
169
+ composer?: "inline" | "docked";
153
170
  /** The theme, if the host keeps it. Left off, this manages its own and puts
154
171
  a toggle in the header; `data-theme` on the root element either way, and
155
172
  unset until somebody chooses, so the kit follows the system preference —
@@ -159,8 +176,32 @@ export interface ChatExperienceProps {
159
176
  /** The pointer-following cursor, and the rule that hides the real one. Only
160
177
  ever where there is a pointer to replace. */
161
178
  cursor?: boolean;
162
- /** The freeform-marker / precise-selection pair in the header. */
179
+ /** The freeform-marker / precise-selection pair in the header. Drawn only
180
+ where there is marking to choose a mode for. */
163
181
  selectionToggle?: boolean;
182
+ /**
183
+ * Whether an answer can be marked at all.
184
+ *
185
+ * On by default: a marker over a passage is how a reader asks about one
186
+ * sentence rather than the whole answer. `false` draws answers as prose —
187
+ * no marker layer, no highlight menu, nothing in the tab order over the
188
+ * text — and takes the saved highlights and the selection-mode pair with
189
+ * it, because both are about marking. A thread is opened from a highlight,
190
+ * so `onThreadReply` has nothing to open it from either.
191
+ *
192
+ * Set in code, not offered in the interface: a product either works this
193
+ * way or it does not.
194
+ */
195
+ highlights?: boolean;
196
+ /**
197
+ * Whether marked passages are kept.
198
+ *
199
+ * On by default, and only ever visible once there is one: the header grows
200
+ * a button with the count, and it opens the sheet that lists them.
201
+ * `false` keeps marking and threads and drops the keeping — for a host that
202
+ * would rather store them itself, through `onHighlight`.
203
+ */
204
+ bookmarks?: boolean;
164
205
  /** How far below the top edge a sent message comes to rest. Sets the
165
206
  conversation's own top padding too — the two have to agree, so one number
166
207
  writes both. */
@@ -177,4 +218,4 @@ export interface ChatExperienceProps {
177
218
  onDecideApproval?: (write: PartWriter, turnId: string, partId: string, decision: Decision) => void;
178
219
  className?: string;
179
220
  }
180
- export declare function ChatExperience({ onSend, chat: hostChat, renderPart, labels, headerActions: builtInActions, composerMenu, onTranscribe, onThreadReply, title: titleProp, backHref, backLabel, actions, placeholder, empty, contextTotal, contextBase, artifact, openArtifactId: openArtifactProp, onOpenArtifactChange, pane: paneMode, surface, theme: themeProp, onThemeChange, cursor, selectionToggle, anchorOffset, endOffset, animationConfig, foldMotion, feedDelay, onAnswerQuestion, onEditQuestion, onDecideApproval, className, }: ChatExperienceProps): import("react").JSX.Element;
221
+ export declare function ChatExperience({ onSend, chat: hostChat, renderPart, labels, headerActions: builtInActions, composerMenu, onTranscribe, onThreadReply, title: titleProp, backHref, backLabel, actions, placeholder, empty, contextTotal, contextBase, artifact, openArtifactId: openArtifactProp, onOpenArtifactChange, pane: paneMode, surface, composer, theme: themeProp, onThemeChange, cursor, selectionToggle, highlights: marking, bookmarks, anchorOffset, endOffset, animationConfig, foldMotion, feedDelay, onAnswerQuestion, onEditQuestion, onDecideApproval, className, }: ChatExperienceProps): import("react").JSX.Element;