@btst/stack 4.2.0 → 4.4.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.
Files changed (72) hide show
  1. package/README.md +2 -2
  2. package/dist/packages/stack/src/plugins/ai-chat/api/compaction.cjs +249 -0
  3. package/dist/packages/stack/src/plugins/ai-chat/api/compaction.mjs +241 -0
  4. package/dist/packages/stack/src/plugins/ai-chat/api/operations.cjs +20 -0
  5. package/dist/packages/stack/src/plugins/ai-chat/api/operations.mjs +20 -0
  6. package/dist/packages/stack/src/plugins/ai-chat/api/plugin.cjs +5 -0
  7. package/dist/packages/stack/src/plugins/ai-chat/api/plugin.mjs +5 -0
  8. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.cjs +38 -3
  9. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.mjs +38 -3
  10. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-layout.cjs +173 -72
  11. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-layout.mjs +176 -75
  12. package/dist/packages/stack/src/plugins/ai-chat/client/context-checkpoint.cjs +30 -0
  13. package/dist/packages/stack/src/plugins/ai-chat/client/context-checkpoint.mjs +27 -0
  14. package/dist/packages/stack/src/plugins/ai-chat/schemas.cjs +2 -0
  15. package/dist/packages/stack/src/plugins/ai-chat/schemas.mjs +2 -0
  16. package/dist/packages/ui/src/components/markdown-content.cjs +9 -9
  17. package/dist/packages/ui/src/components/markdown-content.mjs +9 -9
  18. package/dist/plugins/ai-chat/api/index.d.cts +2 -2
  19. package/dist/plugins/ai-chat/api/index.d.mts +2 -2
  20. package/dist/plugins/ai-chat/api/index.d.ts +2 -2
  21. package/dist/plugins/ai-chat/client/components/index.d.cts +1 -1
  22. package/dist/plugins/ai-chat/client/components/index.d.mts +1 -1
  23. package/dist/plugins/ai-chat/client/components/index.d.ts +1 -1
  24. package/dist/plugins/ai-chat/client/hooks/index.d.cts +2 -2
  25. package/dist/plugins/ai-chat/client/hooks/index.d.mts +2 -2
  26. package/dist/plugins/ai-chat/client/hooks/index.d.ts +2 -2
  27. package/dist/plugins/ai-chat/client/index.d.cts +3 -3
  28. package/dist/plugins/ai-chat/client/index.d.mts +3 -3
  29. package/dist/plugins/ai-chat/client/index.d.ts +3 -3
  30. package/dist/plugins/ai-chat/query-keys.d.cts +1 -1
  31. package/dist/plugins/ai-chat/query-keys.d.mts +1 -1
  32. package/dist/plugins/ai-chat/query-keys.d.ts +1 -1
  33. package/dist/plugins/blog/api/index.d.cts +2 -2
  34. package/dist/plugins/blog/api/index.d.mts +2 -2
  35. package/dist/plugins/blog/api/index.d.ts +2 -2
  36. package/dist/plugins/blog/client/hooks/index.d.cts +2 -2
  37. package/dist/plugins/blog/client/hooks/index.d.mts +2 -2
  38. package/dist/plugins/blog/client/hooks/index.d.ts +2 -2
  39. package/dist/plugins/blog/client/index.d.cts +2 -2
  40. package/dist/plugins/blog/client/index.d.mts +2 -2
  41. package/dist/plugins/blog/client/index.d.ts +2 -2
  42. package/dist/plugins/blog/query-keys.d.cts +2 -2
  43. package/dist/plugins/blog/query-keys.d.mts +2 -2
  44. package/dist/plugins/blog/query-keys.d.ts +2 -2
  45. package/dist/shared/{stack.BTStHVTy.d.cts → stack.BGzgFx3R.d.cts} +5 -5
  46. package/dist/shared/{stack.DBH1Bjko.d.ts → stack.C4zspX0T.d.ts} +13 -2
  47. package/dist/shared/{stack.94kN3pEK.d.mts → stack.CjCSDfWy.d.mts} +5 -5
  48. package/dist/shared/{stack.D5b9l24t.d.ts → stack.Clil75lW.d.ts} +35 -35
  49. package/dist/shared/{stack.xAiy2VtO.d.cts → stack.DDQlE2Pm.d.cts} +10 -3
  50. package/dist/shared/{stack.Ce6V7eDj.d.cts → stack.DNw6Hv5o.d.cts} +35 -35
  51. package/dist/shared/{stack.CTneIW0u.d.mts → stack.DhZtP2JZ.d.mts} +35 -35
  52. package/dist/shared/{stack.C-eQN_Jt.d.ts → stack.DlehgNZZ.d.ts} +10 -3
  53. package/dist/shared/{stack.DfrRYOIl.d.ts → stack.Dpg_JjIo.d.ts} +5 -5
  54. package/dist/shared/{stack.B7Mj74BQ.d.mts → stack.Ps3MDhTC.d.mts} +13 -2
  55. package/dist/shared/{stack.C5FZr-jJ.d.mts → stack.tAUeRAtd.d.mts} +10 -3
  56. package/dist/shared/{stack.Dgt3MCtW.d.cts → stack.ymWsP4HZ.d.cts} +13 -2
  57. package/package.json +2 -2
  58. package/src/__tests__/markdown-link-navigation.test.tsx +29 -0
  59. package/src/plugins/ai-chat/__tests__/classnames-overrides.test.tsx +22 -2
  60. package/src/plugins/ai-chat/__tests__/compaction.test.ts +272 -0
  61. package/src/plugins/ai-chat/__tests__/page-widget.test.tsx +103 -1
  62. package/src/plugins/ai-chat/api/compaction.ts +310 -0
  63. package/src/plugins/ai-chat/api/operations.ts +25 -0
  64. package/src/plugins/ai-chat/api/plugin.ts +14 -0
  65. package/src/plugins/ai-chat/client/components/chat-interface.tsx +58 -3
  66. package/src/plugins/ai-chat/client/components/chat-layout.tsx +197 -72
  67. package/src/plugins/ai-chat/client/context-checkpoint.ts +47 -0
  68. package/src/plugins/ai-chat/client/localization/index.ts +5 -0
  69. package/src/plugins/ai-chat/schemas.ts +2 -0
  70. package/dist/shared/{stack.CKc_Bais.d.cts → stack.Csi-4Mgz.d.cts} +7 -7
  71. package/dist/shared/{stack.CKc_Bais.d.mts → stack.Csi-4Mgz.d.mts} +7 -7
  72. package/dist/shared/{stack.CKc_Bais.d.ts → stack.Csi-4Mgz.d.ts} +7 -7
@@ -6,7 +6,14 @@ import {
6
6
  useEffect,
7
7
  useRef,
8
8
  type CSSProperties,
9
+ type ReactNode,
9
10
  } from "react";
11
+ import { createPortal } from "react-dom";
12
+ import {
13
+ Dialog,
14
+ DialogContent,
15
+ DialogTitle,
16
+ } from "@workspace/ui/components/dialog";
10
17
  import { Button } from "@workspace/ui/components/button";
11
18
  import { Badge } from "@workspace/ui/components/badge";
12
19
  import {
@@ -19,6 +26,8 @@ import {
19
26
  PanelLeftClose,
20
27
  PanelLeft,
21
28
  Sparkles,
29
+ Maximize2,
30
+ Minimize2,
22
31
  Trash2,
23
32
  X,
24
33
  } from "lucide-react";
@@ -67,10 +76,12 @@ interface ChatLayoutBaseProps {
67
76
  interface ChatLayoutWidgetProps extends ChatLayoutBaseProps {
68
77
  /** Widget mode: compact embeddable panel with a floating trigger button */
69
78
  layout: "widget";
70
- /** Height of the widget panel. Default: `"min(600px, calc(100dvh - 112px))"` */
79
+ /** Height of the widget panel. Default: `"min(640px, calc(100dvh - 112px))"` */
71
80
  widgetHeight?: string | number;
72
- /** Width of the widget panel. Default: `"min(380px, calc(100vw - 32px))"` */
81
+ /** Width of the widget panel. Default: `"min(440px, calc(100vw - 32px))"` */
73
82
  widgetWidth?: string | number;
83
+ /** Show a button to expand the widget to the viewport. Default: false. Escape restores the compact size. */
84
+ expandable?: boolean;
74
85
  /** Fix the widget to the bottom right. Default: false (embedded). Override offsets with className or style. */
75
86
  floating?: boolean;
76
87
  /** Optional dismissible introduction shown while the widget is closed. */
@@ -91,6 +102,8 @@ interface ChatLayoutWidgetProps extends ChatLayoutBaseProps {
91
102
  * so that the built-in button does not appear alongside your own UI.
92
103
  */
93
104
  showTrigger?: boolean;
105
+ /** Icon shown in the closed widget's trigger button. Defaults to Sparkles. */
106
+ triggerIcon?: ReactNode;
94
107
  }
95
108
 
96
109
  interface ChatLayoutFullProps extends ChatLayoutBaseProps {
@@ -139,16 +152,17 @@ function ChatLayoutContent(props: ChatLayoutProps) {
139
152
  // Widget-specific props — TypeScript narrows props to ChatLayoutWidgetProps here
140
153
  const widgetHeight =
141
154
  props.layout === "widget"
142
- ? (props.widgetHeight ?? "min(600px, calc(100dvh - 112px))")
155
+ ? (props.widgetHeight ?? "min(640px, calc(100dvh - 112px))")
143
156
  : undefined;
144
157
  const widgetWidth =
145
158
  props.layout === "widget"
146
- ? (props.widgetWidth ?? "min(380px, calc(100vw - 32px))")
159
+ ? (props.widgetWidth ?? "min(440px, calc(100vw - 32px))")
147
160
  : undefined;
148
161
  const defaultOpen =
149
162
  props.layout === "widget" ? (props.defaultOpen ?? false) : false;
150
163
  const showTrigger =
151
164
  props.layout === "widget" ? (props.showTrigger ?? true) : true;
165
+ const triggerIcon = props.layout === "widget" ? props.triggerIcon : undefined;
152
166
 
153
167
  const [sidebarOpen, setSidebarOpen] = useState(true);
154
168
  const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
@@ -156,6 +170,40 @@ function ChatLayoutContent(props: ChatLayoutProps) {
156
170
  const [chatResetKey, setChatResetKey] = useState(0);
157
171
  // Widget open/closed state — starts with defaultOpen value
158
172
  const [widgetOpen, setWidgetOpen] = useState(defaultOpen);
173
+ const [expanded, setExpanded] = useState(false);
174
+ const expandable = props.layout === "widget" && props.expandable === true;
175
+ const widgetExpanded = expandable && expanded && widgetOpen;
176
+ useEffect(() => {
177
+ if (!expandable) setExpanded(false);
178
+ }, [expandable]);
179
+ const compactPanelRef = useRef<HTMLDivElement>(null);
180
+ const triggerRef = useRef<HTMLButtonElement>(null);
181
+ const expandRef = useRef<HTMLButtonElement>(null);
182
+ const [panelContainer, setPanelContainer] = useState<HTMLDivElement | null>(
183
+ null,
184
+ );
185
+ const expandLabel = widgetExpanded
186
+ ? tr("A11Y_COLLAPSE_CHAT", "aiChat.a11y.collapseChat", "Collapse chat")
187
+ : tr("A11Y_EXPAND_CHAT", "aiChat.a11y.expandChat", "Expand chat");
188
+
189
+ useEffect(() => {
190
+ if (layout !== "widget") return;
191
+ const container = document.createElement("div");
192
+ container.className = "flex h-full min-h-0 flex-col";
193
+ compactPanelRef.current?.appendChild(container);
194
+ setPanelContainer(container);
195
+ return () => container.remove();
196
+ }, [layout]);
197
+ // The portal target stays the same; only its DOM parent changes. This keeps
198
+ // the draft, attachments and active stream mounted while Dialog owns modality.
199
+ const mountExpandedPanel = useCallback(
200
+ (node: HTMLDivElement | null) => {
201
+ if (panelContainer) {
202
+ (node ?? compactPanelRef.current)?.appendChild(panelContainer);
203
+ }
204
+ },
205
+ [panelContainer],
206
+ );
159
207
  // Key to force widget ChatInterface remount on clear
160
208
  const [widgetResetKey, setWidgetResetKey] = useState(0);
161
209
  // Only mount the widget ChatInterface after the widget has been opened at least once.
@@ -243,90 +291,167 @@ function ChatLayoutContent(props: ChatLayoutProps) {
243
291
  </Button>
244
292
  </div>
245
293
  )}
246
- {/* Chat panel — always mounted to preserve conversation state, hidden when closed */}
294
+ {/* Compact panel remains available when the fullscreen dialog closes. */}
247
295
  <div
296
+ ref={compactPanelRef}
297
+ role="dialog"
298
+ tabIndex={-1}
299
+ aria-label={tr("A11Y_CHAT_TITLE", "aiChat.a11y.title", "AI Chat")}
248
300
  className={cn(
249
- "flex flex-col border rounded-xl overflow-hidden bg-background shadow-xl",
250
- widgetOpen ? "flex" : "hidden",
301
+ "overflow-hidden rounded-xl border bg-background shadow-xl",
302
+ (!widgetOpen || widgetExpanded) && "hidden",
251
303
  )}
252
304
  style={{ height: widgetHeight, width: widgetWidth }}
253
- >
254
- {/* Widget header with page context badge and action buttons */}
255
- <div className="flex items-center gap-1.5 px-3 py-1.5 border-b bg-muted/40">
256
- <Sparkles className="h-3 w-3 text-muted-foreground" />
257
- {pageAIContext ? (
258
- <Badge
259
- variant="secondary"
260
- className="text-xs"
261
- data-testid="page-context-badge"
262
- >
263
- {pageAIContext.routeName}
264
- </Badge>
265
- ) : (
266
- <span className="text-xs text-muted-foreground font-medium">
267
- {tr("A11Y_CHAT_TITLE", "aiChat.a11y.title", "AI Chat")}
268
- </span>
305
+ />
306
+ <Dialog open={widgetExpanded} onOpenChange={setExpanded}>
307
+ <DialogContent
308
+ showCloseButton={false}
309
+ style={{
310
+ ...style,
311
+ position: "fixed",
312
+ inset: 0,
313
+ top: 0,
314
+ right: 0,
315
+ bottom: 0,
316
+ left: 0,
317
+ width: "100vw",
318
+ height: "100dvh",
319
+ minWidth: 0,
320
+ minHeight: 0,
321
+ maxWidth: "none",
322
+ maxHeight: "none",
323
+ margin: 0,
324
+ transform: "none",
325
+ translate: "none",
326
+ zIndex: 50,
327
+ animation: "none",
328
+ }}
329
+ aria-describedby={undefined}
330
+ className={cn(
331
+ className,
332
+ classNames?.container,
333
+ "fixed inset-0 z-50 flex h-dvh w-screen max-w-none translate-x-0 translate-y-0 flex-col gap-0 rounded-none border-0 p-0 sm:max-w-none pt-[env(safe-area-inset-top)] pb-[env(safe-area-inset-bottom)] pl-[env(safe-area-inset-left)] pr-[env(safe-area-inset-right)]",
269
334
  )}
270
- <div className="flex-1" />
271
- <Button
272
- variant="ghost"
273
- size="icon"
274
- className="h-5 w-5"
275
- onClick={() => {
276
- track({ type: "conversation_cleared" });
277
- onClear?.();
278
- setWidgetResetKey((prev) => prev + 1);
279
- }}
280
- aria-label={tr(
281
- "A11Y_CLEAR_CHAT",
282
- "aiChat.a11y.clearChat",
283
- "Clear chat",
284
- )}
285
- title={tr(
286
- "A11Y_CLEAR_CHAT",
287
- "aiChat.a11y.clearChat",
288
- "Clear chat",
289
- )}
290
- >
291
- <Trash2 className="h-3.5 w-3.5" />
292
- </Button>
293
- <Button
294
- variant="ghost"
295
- size="icon"
296
- className="h-5 w-5"
297
- onClick={() => {
298
- setWidgetOpen(false);
299
- track({ type: "widget_closed" });
300
- }}
301
- aria-label={tr(
302
- "A11Y_CLOSE_CHAT",
303
- "aiChat.a11y.closeChat",
304
- "Close chat",
335
+ onCloseAutoFocus={(event) => {
336
+ const target = widgetOpen
337
+ ? (expandRef.current ?? compactPanelRef.current)
338
+ : triggerRef.current;
339
+ if (target) {
340
+ event.preventDefault();
341
+ target.focus();
342
+ }
343
+ }}
344
+ >
345
+ <DialogTitle className="sr-only">
346
+ {tr("A11Y_CHAT_TITLE", "aiChat.a11y.title", "AI Chat")}
347
+ </DialogTitle>
348
+ <div ref={mountExpandedPanel} className="min-h-0 flex-1" />
349
+ </DialogContent>
350
+ </Dialog>
351
+ {panelContainer &&
352
+ createPortal(
353
+ <>
354
+ {/* Widget header with page context badge and action buttons */}
355
+ <div className="flex shrink-0 items-center gap-1.5 px-3 py-1.5 border-b bg-muted/40">
356
+ <Sparkles className="h-3 w-3 text-muted-foreground" />
357
+ {pageAIContext ? (
358
+ <Badge
359
+ variant="secondary"
360
+ className="text-xs"
361
+ data-testid="page-context-badge"
362
+ >
363
+ {pageAIContext.routeName}
364
+ </Badge>
365
+ ) : (
366
+ <span className="text-xs text-muted-foreground font-medium">
367
+ {tr("A11Y_CHAT_TITLE", "aiChat.a11y.title", "AI Chat")}
368
+ </span>
369
+ )}
370
+ <div className="flex-1" />
371
+ {expandable && (
372
+ <Button
373
+ type="button"
374
+ variant="ghost"
375
+ size="icon"
376
+ className="h-7 w-7"
377
+ ref={expandRef}
378
+ onClick={() => setExpanded((prev) => !prev)}
379
+ aria-label={expandLabel}
380
+ title={expandLabel}
381
+ aria-expanded={widgetExpanded}
382
+ >
383
+ {widgetExpanded ? (
384
+ <Minimize2 className="h-3.5 w-3.5" aria-hidden="true" />
385
+ ) : (
386
+ <Maximize2 className="h-3.5 w-3.5" aria-hidden="true" />
387
+ )}
388
+ </Button>
389
+ )}
390
+ <Button
391
+ variant="ghost"
392
+ size="icon"
393
+ className="h-5 w-5"
394
+ onClick={() => {
395
+ track({ type: "conversation_cleared" });
396
+ onClear?.();
397
+ setWidgetResetKey((prev) => prev + 1);
398
+ }}
399
+ aria-label={tr(
400
+ "A11Y_CLEAR_CHAT",
401
+ "aiChat.a11y.clearChat",
402
+ "Clear chat",
403
+ )}
404
+ title={tr(
405
+ "A11Y_CLEAR_CHAT",
406
+ "aiChat.a11y.clearChat",
407
+ "Clear chat",
408
+ )}
409
+ >
410
+ <Trash2 className="h-3.5 w-3.5" />
411
+ </Button>
412
+ <Button
413
+ variant="ghost"
414
+ size="icon"
415
+ className="h-5 w-5"
416
+ onClick={() => {
417
+ setWidgetOpen(false);
418
+ setExpanded(false);
419
+ triggerRef.current?.focus();
420
+ track({ type: "widget_closed" });
421
+ }}
422
+ aria-label={tr(
423
+ "A11Y_CLOSE_CHAT",
424
+ "aiChat.a11y.closeChat",
425
+ "Close chat",
426
+ )}
427
+ >
428
+ <X className="h-3.5 w-3.5" />
429
+ </Button>
430
+ </div>
431
+ {widgetEverOpened && (
432
+ <ChatInterface
433
+ key={`widget-${conversationId ?? "new"}-${widgetResetKey}`}
434
+ id={conversationId}
435
+ variant="widget"
436
+ initialMessages={initialMessages}
437
+ onMessagesChange={onMessagesChange}
438
+ />
305
439
  )}
306
- >
307
- <X className="h-3.5 w-3.5" />
308
- </Button>
309
- </div>
310
- {widgetEverOpened && (
311
- <ChatInterface
312
- key={`widget-${conversationId ?? "new"}-${widgetResetKey}`}
313
- id={conversationId}
314
- variant="widget"
315
- initialMessages={initialMessages}
316
- onMessagesChange={onMessagesChange}
317
- />
440
+ </>,
441
+ panelContainer,
318
442
  )}
319
- </div>
320
443
 
321
444
  {/* Trigger button — rendered only when showTrigger is true */}
322
445
  {showTrigger && (
323
446
  <Button
447
+ ref={triggerRef}
324
448
  size="icon"
325
449
  className="h-12 w-12 rounded-full shadow-lg"
326
450
  onClick={() => {
327
451
  dismissTip("open");
328
452
  track({ type: widgetOpen ? "widget_closed" : "widget_opened" });
329
453
  setWidgetOpen((prev) => !prev);
454
+ setExpanded(false);
330
455
  setWidgetEverOpened(true);
331
456
  }}
332
457
  aria-label={
@@ -339,7 +464,7 @@ function ChatLayoutContent(props: ChatLayoutProps) {
339
464
  {widgetOpen ? (
340
465
  <X className="h-5 w-5" />
341
466
  ) : (
342
- <Sparkles className="h-5 w-5" />
467
+ (triggerIcon ?? <Sparkles className="h-5 w-5" />)
343
468
  )}
344
469
  </Button>
345
470
  )}
@@ -0,0 +1,47 @@
1
+ import type { UIMessage } from "ai";
2
+
3
+ /** Browser-only snapshot; never replaces or mutates the visible transcript. */
4
+ export interface ContextCheckpoint {
5
+ summary: string;
6
+ throughMessageId: string;
7
+ prefix: string;
8
+ }
9
+
10
+ export function captureContextCheckpoint(
11
+ messages: UIMessage[],
12
+ summary: string,
13
+ throughMessageId: string,
14
+ ): ContextCheckpoint | undefined {
15
+ const index = messages.findIndex(
16
+ (message) => message.id === throughMessageId,
17
+ );
18
+ if (index < 0) return undefined;
19
+ return {
20
+ summary,
21
+ throughMessageId,
22
+ prefix: JSON.stringify(messages.slice(0, index + 1)),
23
+ };
24
+ }
25
+
26
+ export function contextRequest(
27
+ messages: UIMessage[],
28
+ checkpoint?: ContextCheckpoint,
29
+ ) {
30
+ if (checkpoint) {
31
+ const index = messages.findIndex(
32
+ (message) => message.id === checkpoint.throughMessageId,
33
+ );
34
+ if (
35
+ index >= 0 &&
36
+ JSON.stringify(messages.slice(0, index + 1)) === checkpoint.prefix
37
+ ) {
38
+ return {
39
+ messages: messages.slice(index + 1),
40
+ contextSummary: checkpoint.summary,
41
+ };
42
+ }
43
+ }
44
+ // Editing or retrying a summarized turn invalidates the snapshot. Resend
45
+ // the original transcript so the server builds a fresh summary if needed.
46
+ return { messages };
47
+ }
@@ -12,6 +12,7 @@ export interface AiChatLocalization {
12
12
  CHAT_SEND_BUTTON: string;
13
13
  CHAT_EMPTY_STATE: string;
14
14
  CHAT_LOADING: string;
15
+ CHAT_COMPACTING?: string;
15
16
  CHAT_ERROR: string;
16
17
  CHAT_RETRY: string;
17
18
 
@@ -86,6 +87,8 @@ export interface AiChatLocalization {
86
87
  A11Y_CHAT_TITLE: string;
87
88
  A11Y_CLEAR_CHAT: string;
88
89
  A11Y_CLOSE_CHAT: string;
90
+ A11Y_EXPAND_CHAT: string;
91
+ A11Y_COLLAPSE_CHAT: string;
89
92
  A11Y_OPEN_CHAT: string;
90
93
  A11Y_DISMISS_CHAT_TIP: string;
91
94
  PAGE_CONTENT_READING: string;
@@ -194,6 +197,8 @@ export const AI_CHAT_LOCALIZATION: AiChatLocalization = {
194
197
  A11Y_CHAT_TITLE: "AI Chat",
195
198
  A11Y_CLEAR_CHAT: "Clear chat",
196
199
  A11Y_CLOSE_CHAT: "Close chat",
200
+ A11Y_EXPAND_CHAT: "Expand chat",
201
+ A11Y_COLLAPSE_CHAT: "Collapse chat",
197
202
  A11Y_OPEN_CHAT: "Open chat",
198
203
  A11Y_DISMISS_CHAT_TIP: "Dismiss chat tip",
199
204
  PAGE_CONTENT_READING: "Reading page content…",
@@ -36,6 +36,8 @@ export const chatRequestSchema = z.object({
36
36
  ]),
37
37
  ),
38
38
  conversationId: z.string().optional(),
39
+ /** Untrusted continuation summary for opt-in public context compaction. */
40
+ contextSummary: z.string().max(32_000).optional(),
39
41
  model: z.string().optional(),
40
42
  /**
41
43
  * Description of the current page context, injected into the AI system prompt.
@@ -1,6 +1,13 @@
1
1
  import { z } from 'zod';
2
2
 
3
3
  declare const createPostSchema: z.ZodObject<{
4
+ tags: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
5
+ name: z.ZodString;
6
+ }, z.core.$strip>, z.ZodObject<{
7
+ id: z.ZodString;
8
+ name: z.ZodString;
9
+ slug: z.ZodString;
10
+ }, z.core.$strip>]>>>>;
4
11
  slug: z.ZodOptional<z.ZodString>;
5
12
  publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
6
13
  createdAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
@@ -10,13 +17,6 @@ declare const createPostSchema: z.ZodObject<{
10
17
  excerpt: z.ZodString;
11
18
  image: z.ZodOptional<z.ZodString>;
12
19
  published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
13
- tags: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
14
- name: z.ZodString;
15
- }, z.core.$strip>, z.ZodObject<{
16
- id: z.ZodString;
17
- name: z.ZodString;
18
- slug: z.ZodString;
19
- }, z.core.$strip>]>>>>;
20
20
  }, z.core.$strip>;
21
21
  declare const updatePostSchema: z.ZodObject<{
22
22
  publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
@@ -1,6 +1,13 @@
1
1
  import { z } from 'zod';
2
2
 
3
3
  declare const createPostSchema: z.ZodObject<{
4
+ tags: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
5
+ name: z.ZodString;
6
+ }, z.core.$strip>, z.ZodObject<{
7
+ id: z.ZodString;
8
+ name: z.ZodString;
9
+ slug: z.ZodString;
10
+ }, z.core.$strip>]>>>>;
4
11
  slug: z.ZodOptional<z.ZodString>;
5
12
  publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
6
13
  createdAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
@@ -10,13 +17,6 @@ declare const createPostSchema: z.ZodObject<{
10
17
  excerpt: z.ZodString;
11
18
  image: z.ZodOptional<z.ZodString>;
12
19
  published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
13
- tags: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
14
- name: z.ZodString;
15
- }, z.core.$strip>, z.ZodObject<{
16
- id: z.ZodString;
17
- name: z.ZodString;
18
- slug: z.ZodString;
19
- }, z.core.$strip>]>>>>;
20
20
  }, z.core.$strip>;
21
21
  declare const updatePostSchema: z.ZodObject<{
22
22
  publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
@@ -1,6 +1,13 @@
1
1
  import { z } from 'zod';
2
2
 
3
3
  declare const createPostSchema: z.ZodObject<{
4
+ tags: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
5
+ name: z.ZodString;
6
+ }, z.core.$strip>, z.ZodObject<{
7
+ id: z.ZodString;
8
+ name: z.ZodString;
9
+ slug: z.ZodString;
10
+ }, z.core.$strip>]>>>>;
4
11
  slug: z.ZodOptional<z.ZodString>;
5
12
  publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
6
13
  createdAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
@@ -10,13 +17,6 @@ declare const createPostSchema: z.ZodObject<{
10
17
  excerpt: z.ZodString;
11
18
  image: z.ZodOptional<z.ZodString>;
12
19
  published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
13
- tags: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
14
- name: z.ZodString;
15
- }, z.core.$strip>, z.ZodObject<{
16
- id: z.ZodString;
17
- name: z.ZodString;
18
- slug: z.ZodString;
19
- }, z.core.$strip>]>>>>;
20
20
  }, z.core.$strip>;
21
21
  declare const updatePostSchema: z.ZodObject<{
22
22
  publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;