inline-chat-kit 0.54.3 → 0.55.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,60 @@ 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.55.0 — 2026-09-13
12
+
13
+ ### Added
14
+
15
+ - **`ChatExperience` — the whole thing, assembled.** Every other export here is
16
+ a piece: a header, a scroll container, a turn, a pane. Putting them together
17
+ was a page's job right up until two pages did it, and then it was a copy.
18
+
19
+ ```tsx
20
+ <ChatExperience onSend={ask} title="Chat" placeholder="Ask anything…" />
21
+ ```
22
+
23
+ It holds a sent turn at the top while its answer is written and lets go when
24
+ it settles; titles the header from the first question actually asked rather
25
+ than from the first keystroke; places the artifact pane *beside* the
26
+ conversation rather than inside it, which is the difference between a pane
27
+ and a modal; announces a full context window once, through the kit's own live
28
+ region rather than a second one; and asks whether there is a pointer before
29
+ drawing a cursor for it. A host gives it content and identity — `onSend`, the
30
+ copy, what is in the pane, where "back" goes.
31
+
32
+ The pieces are all still exported. Assemble them yourself when your app needs
33
+ a shape this one does not have.
34
+
35
+ - **`inline-chat-kit/demo`** — the scripted showcase behind its own entry
36
+ point: the landing page, the banner, and the particle-physics answers. An app
37
+ that never imports it never carries a line of it; the library entry is 1.8 kB
38
+ and the demo is 22.
39
+
40
+ ### Fixed
41
+
42
+ - **Two variables the kit expected a host to define.** The chat stylesheet said
43
+ `var(--bg)` and the landing page said `--color-bg-page`, `--font-geist-sans`
44
+ and `--font-jetbrains-mono` — none of which this package ships. They rendered
45
+ correctly in a host that happened to have them and silently wrong in one that
46
+ did not: a header see-through over a scrolling answer, and an intro in serif
47
+ on a white page. All of it is on `--ick-` tokens now.
48
+
49
+ ### Internal
50
+
51
+ - **The fork that caused the last three bug reports is gone.** Two apps each
52
+ kept their own copy of the assembly — 865 lines and 890, ~90% identical —
53
+ along with 537 byte-identical lines of scripted answers and two stylesheets.
54
+ Every fix went into one of them.
55
+
56
+ - **`noForks.test.ts`** measures it from now on: any file under `apps/` sharing
57
+ more than half its substantial lines with a file in the kit fails, naming
58
+ both files and the share. Comments are stripped so a quotation is not a fork,
59
+ and whitespace is normalised so a reformat is not a defence. Watched failing
60
+ before it was trusted — a planted copy reports `100% (327 of 327 lines)`.
61
+
62
+ - The getting-started example is now the one-liner, compiled on every build and
63
+ quoted into the page character for character, as before.
64
+
11
65
  ## 0.54.3 — 2026-09-13
12
66
 
13
67
  ### Fixed
@@ -0,0 +1,115 @@
1
+ import { ReactNode } from 'react';
2
+ import { ChatHeaderAction } from '../ChatHeader/ChatHeader';
3
+ import { ReplyThreadPopupProps } from '../ReplyThreadPopup/ReplyThreadPopup';
4
+ import { UseChatTurnsOptions } from '../useChatTurns/useChatTurns';
5
+ import { FoldMotion } from '../QuestionGroup/QuestionGroup';
6
+ import { InlineAnimConfig } from '../ChatInput/ChatInput';
7
+ import { TranscribeHandler } from '../voice/useVoiceInput';
8
+ import { TurnPartUpdate } from '../turnParts/turnParts';
9
+ import { Answer } from '../QuestionCard/types';
10
+ import { Decision } from '../Approval/Approval';
11
+ /**
12
+ * The whole thing, assembled.
13
+ *
14
+ * ## Why this is in the kit and not in your page
15
+ *
16
+ * Every other export here is a piece: a header, a scroll container, a turn, a
17
+ * pane. Putting them together is a page's job right up until two pages do it,
18
+ * and then it is a copy — which is exactly what happened. The playground's
19
+ * page and the website's page were 865 and 890 lines of ~90% identical code,
20
+ * along with 537 identical lines of scripted answers and 300 of stylesheet.
21
+ *
22
+ * That copy is where the last three reported faults lived. Not in the kit: in
23
+ * the fact that a fix went into one page and the other kept the old one. The
24
+ * header rendered see-through because the second copy never defined `--bg`.
25
+ * The empty state snapped out of existence because the second copy never got
26
+ * the `AnimatePresence`. The sent message stayed pinned to the top for the
27
+ * rest of the session because the second copy never got the released anchor.
28
+ *
29
+ * So the assembly lives here, where a fix is made once and arrives everywhere
30
+ * by `npm install`. What stays a host's: the answers, the copy, the artifact's
31
+ * contents, the logo, where "back" goes. Content and identity — the two things
32
+ * a library cannot know and should not guess.
33
+ *
34
+ * ## What it does that a page would have to remember to
35
+ *
36
+ * Holds the sent turn at the top while its answer is written and lets go when
37
+ * it settles. Keeps a title from the first question actually asked rather than
38
+ * from the first keystroke. Announces a full context window once, through the
39
+ * kit's own live region rather than a second one. Places the artifact pane
40
+ * *beside* the conversation rather than inside it, which is the difference
41
+ * between a pane and a modal. Asks whether there is a pointer before drawing a
42
+ * cursor for it.
43
+ */
44
+ /** The opening block, before anybody has asked anything. */
45
+ export interface ChatExperienceEmpty {
46
+ title: string;
47
+ description?: string;
48
+ /** One opener per branch of your `onSend`, ideally: everything the kit can
49
+ draw becomes reachable by pressing something rather than by knowing what
50
+ to type. Sent, not typed into the box — an opener that only fills the
51
+ input asks somebody to press send on a sentence they did not write. */
52
+ suggestions?: string[];
53
+ }
54
+ /** What the pane shows while an artifact is open. The kit draws the pane; what
55
+ is inside it is the host's, which is why this returns children. */
56
+ export interface ChatExperienceArtifact {
57
+ title: string;
58
+ meta?: string;
59
+ children: ReactNode;
60
+ }
61
+ /** A part the host drives, handed the writer for the turn it belongs to. */
62
+ export type PartWriter = (turnId: string, part: TurnPartUpdate) => void;
63
+ export interface ChatExperienceProps {
64
+ /** Where answers come from. Return a string, a promise of one, or an async
65
+ iterable of deltas — see `useChatTurns`. */
66
+ onSend: UseChatTurnsOptions["onSend"];
67
+ /** Speech to text. The kit records; the host transcribes. Omit and the
68
+ microphone does not appear. */
69
+ onTranscribe?: TranscribeHandler;
70
+ /** A follow-up asked on a passage. Omit and replying in a thread is off. */
71
+ onThreadReply?: ReplyThreadPopupProps["onSendMessage"];
72
+ /** Shown in the header until a question has actually been asked. */
73
+ title?: string;
74
+ /** Where the back control goes. */
75
+ backHref?: string;
76
+ backLabel?: string;
77
+ /** Extra header actions, added after the ones this manages. */
78
+ actions?: ChatHeaderAction[];
79
+ placeholder?: string;
80
+ empty?: ChatExperienceEmpty;
81
+ /** The context meter. Leave `contextTotal` off and there is no meter. A real
82
+ app reads this off its API's usage; a demo can count it off the text. */
83
+ contextTotal?: number;
84
+ /** What a system prompt and the tool definitions cost before anybody types. */
85
+ contextBase?: number;
86
+ /** The pane beside the conversation, asked for the artifact that is open. */
87
+ artifact?: (openId: string) => ChatExperienceArtifact | null;
88
+ /** The theme, if the host keeps it. Left off, this manages its own and puts
89
+ a toggle in the header; `data-theme` on the root element either way, and
90
+ unset until somebody chooses, so the kit follows the system preference —
91
+ which is what it is there for. */
92
+ theme?: "light" | "dark" | null;
93
+ onThemeChange?: (theme: "light" | "dark") => void;
94
+ /** The pointer-following cursor, and the rule that hides the real one. Only
95
+ ever where there is a pointer to replace. */
96
+ cursor?: boolean;
97
+ /** The freeform-marker / precise-selection pair in the header. */
98
+ selectionToggle?: boolean;
99
+ /** How far below the top edge a sent message comes to rest. Sets the
100
+ conversation's own top padding too — the two have to agree, so one number
101
+ writes both. */
102
+ anchorOffset?: number;
103
+ /** Room left under the composer once an answer settles. */
104
+ endOffset?: number;
105
+ /** Motion, opened up so a tuning panel can reach it. */
106
+ animationConfig?: InlineAnimConfig;
107
+ foldMotion?: FoldMotion;
108
+ /** How long the feed waits before its first row arrives. */
109
+ feedDelay?: number;
110
+ onAnswerQuestion?: (write: PartWriter, turnId: string, partId: string, questionId: string, answer: Answer) => void;
111
+ onEditQuestion?: (write: PartWriter, turnId: string, partId: string, index: number) => void;
112
+ onDecideApproval?: (write: PartWriter, turnId: string, partId: string, decision: Decision) => void;
113
+ className?: string;
114
+ }
115
+ export declare function ChatExperience({ onSend, onTranscribe, onThreadReply, title, backHref, backLabel, actions, placeholder, empty, contextTotal, contextBase, artifact, theme: themeProp, onThemeChange, cursor, selectionToggle, anchorOffset, endOffset, animationConfig, foldMotion, feedDelay, onAnswerQuestion, onEditQuestion, onDecideApproval, className, }: ChatExperienceProps): import("react").JSX.Element;