@ai-matrx/chat 0.4.15 → 0.4.18

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 (59) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/agent-copy/AlchemySurfaceBridge.js +1 -1
  3. package/dist/agents/components/agent-widgets/chat-assistant/AssistantCardStack.js +1 -1
  4. package/dist/agents/components/ambient-assistant/ScrollAssistantLauncher.js +1 -1
  5. package/dist/agents/components/conversation-actions/rename/ConversationRenameDialogHost.js +1 -1
  6. package/dist/agents/components/messages-display/assistant/AgentEmptyMessageDisplay.js +1 -1
  7. package/dist/agents/components/messages-display/assistant/AssistantMessageFooter.js +3 -1
  8. package/dist/agents/components/messages-display/user/AgentUserMessage.js +95 -85
  9. package/dist/agents/components/messages-display/user/useCollapsibleMessageText.d.ts +1 -1
  10. package/dist/agents/components/messages-display/user/useCollapsibleMessageText.js +5 -4
  11. package/dist/agents/components/shared/AgentConversationColumn.d.ts +7 -1
  12. package/dist/agents/components/shared/AgentConversationColumn.js +14 -7
  13. package/dist/agents/components/shared/transcript-layout.d.ts +31 -0
  14. package/dist/agents/components/shared/transcript-layout.js +38 -0
  15. package/dist/agents/redux/execution-system/message-crud/answer-text-splice.js +1 -1
  16. package/dist/agents/redux/execution-system/messages/messages.selectors.js +1 -1
  17. package/dist/canvas/workspace/ChatPanelTitleMenu.js +8 -2
  18. package/dist/canvas/workspace/ShellChatDock.js +60 -10
  19. package/dist/canvas/workspace/shell-chat-route.d.ts +5 -4
  20. package/dist/canvas/workspace/shell-chat-route.js +2 -4
  21. package/dist/canvas/workspace/side-chat-address.d.ts +91 -0
  22. package/dist/canvas/workspace/side-chat-address.js +110 -0
  23. package/dist/canvas/workspace/useCanvasWorkspaceConversation.d.ts +31 -12
  24. package/dist/canvas/workspace/useCanvasWorkspaceConversation.js +105 -36
  25. package/dist/cx-chat/components/ChatMobileAgentName.js +1 -1
  26. package/dist/cx-chat/components/ChatSidebarClient.js +1 -1
  27. package/dist/cx-chat/components/ChatWelcomeClient.js +1 -1
  28. package/dist/cx-chat/components/core/ChatConversationClient.js +1 -1
  29. package/dist/cx-chat/components/user-input/ConversationInput.js +1 -1
  30. package/dist/cx-conversation/ConversationInput.js +1 -1
  31. package/dist/cx-conversation/MessageList.js +1 -1
  32. package/dist/host/db-types.d.ts +9 -12
  33. package/dist/host/ui-slots.js +17 -2
  34. package/dist/surfaces/components/chrome/SurfaceAgentsHeaderButton.d.ts +1 -1
  35. package/dist/surfaces/components/chrome/SurfaceAgentsHeaderButton.js +1 -1
  36. package/dist/tool-call-visualization/db-renderer/DbToolRenderer.js +1 -1
  37. package/dist/{next → ui}/lazy/AgentAssistantMessage.js +3 -4
  38. package/dist/{next → ui}/lazy/AgentPickerSheet.js +3 -4
  39. package/dist/{next → ui}/lazy/ChatVariableInputs.js +5 -7
  40. package/dist/{next → ui}/lazy/ConversationRenameDialogHostImpl.d.ts +1 -1
  41. package/dist/{next → ui}/lazy/ConversationRenameDialogHostImpl.js +3 -3
  42. package/dist/{next → ui}/lazy/CxAssistantMessage.js +3 -4
  43. package/dist/{next → ui}/lazy/DbToolRendererImpl.js +3 -3
  44. package/dist/{next → ui}/lazy/IconResolver.js +3 -4
  45. package/dist/{next → ui}/lazy/PublicChatVariableInputs.js +5 -7
  46. package/dist/{next → ui}/lazy/ScrollAssistantLaunchers.d.ts +1 -1
  47. package/dist/{next → ui}/lazy/ScrollAssistantLaunchers.js +3 -5
  48. package/dist/{next → ui}/lazy/SurfaceAgentsPanelImpl.js +2 -3
  49. package/dist/utils/client-lazy.d.ts +16 -0
  50. package/dist/utils/client-lazy.js +27 -0
  51. package/package.json +7 -7
  52. /package/dist/{next → ui}/lazy/AgentAssistantMessage.d.ts +0 -0
  53. /package/dist/{next → ui}/lazy/AgentPickerSheet.d.ts +0 -0
  54. /package/dist/{next → ui}/lazy/ChatVariableInputs.d.ts +0 -0
  55. /package/dist/{next → ui}/lazy/CxAssistantMessage.d.ts +0 -0
  56. /package/dist/{next → ui}/lazy/DbToolRendererImpl.d.ts +0 -0
  57. /package/dist/{next → ui}/lazy/IconResolver.d.ts +0 -0
  58. /package/dist/{next → ui}/lazy/PublicChatVariableInputs.d.ts +0 -0
  59. /package/dist/{next → ui}/lazy/SurfaceAgentsPanelImpl.d.ts +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,48 @@
1
1
  # @ai-matrx/chat
2
2
 
3
+ ## 0.4.18 — Side Chat: an unsent chat's menu, and a taken id heals itself
4
+
5
+ - The chat panel's title menu (`ChatPanelTitleMenu`) shows Rename and Open in full chat only once
6
+ the conversation exists on the server (not cache-only, or it has a conversation-list row). An
7
+ unsent New chat shows New chat and the conversation list only.
8
+ - A reserved new-chat id is released (and the page's memory takes over) the moment the first send
9
+ STARTS, not when the server confirms — a kept id could be sent again from a second tab or after a
10
+ reload, and the server answered 409 `conversation_already_exists` forever.
11
+ - Self-heal: when a send is refused with 409 `conversation_already_exists`, the panel starts the
12
+ same chat (same agent / job) again under a FRESH id and puts what the person sent back into its
13
+ composer. `isConversationIdTaken` names the refusal.
14
+
15
+ Consumer action: none.
16
+
17
+ ## 0.4.17 — a lighter first chat screen
18
+
19
+ - The message selectors and the answer-text splice strip thinking with THE rule
20
+ (`@ai-matrx/content-ir/source` `stripThinking`) instead of importing `@ai-matrx/print/markdown`
21
+ (KaTeX + an HTML parser on the first screen). The chat room marks its surface through
22
+ `@ai-matrx/alchemy/react/surface` (not the whole workspace). With `@ai-matrx/icons` 0.3.4 the
23
+ bare Vite canary's first chat screen drops from 2,429 kB to the number in
24
+ `side-panel-bundle-budget.json` (gzip); nothing a person can do changed.
25
+
26
+ ## 0.4.16 — the Side Chat's URL contract
27
+
28
+ - ONE param family on every home opens, closes and aims the Side Chat (the shell chat dock):
29
+ `side_chat` = `open` | `closed` | `new` | `<conversation uuid>`; `side_chat_agent` =
30
+ `<agent uuid>` | `mandate:<key>`; `side_chat_mode` = `chat` | `work` | `advanced`. The link wins
31
+ over the remembered conversation, the family cookie and the defaults for its visit, and never
32
+ writes the person's open / closed choice. Invalid values are ignored with a `[side-chat]` warning.
33
+ - After reading, the one-shot params are consumed; the address names the shown conversation as
34
+ `side_chat=<uuid>` while the panel is open, and drops it when the person closes the panel.
35
+ - `/chat` and `/code` show the Side Chat when (and only when) a `side_chat*` param asks.
36
+ - New module `canvas/workspace/side-chat-address` (`readSideChatLink`, `sideChatOpen`,
37
+ `addressAfterSideChatLink`, `sideChatAddress`, `sideChatSignature`, the param names).
38
+ - Removed (no legacy): `?pageChat=` and the boards / Education `?chat=`, `ShellChatHome.addressParam`,
39
+ `conversationInAddress`, `addressWithConversation`; `useCanvasWorkspaceConversation`'s
40
+ `addressParam` option is now `address: { link, open, hostedElsewhere, onWrite? }`.
41
+
42
+ Consumer action: any link that opened the dock with `?pageChat=<id>` or a board's / Education's
43
+ `?chat=<id>` now writes `?side_chat=<id>` (matrx-frontend: `continueWithAgentHref`). A host calling
44
+ `useCanvasWorkspaceConversation` with `addressParam` passes `address` instead.
45
+
3
46
  ## 0.4.15
4
47
 
5
48
  Automatic changed-only republish (docs/metadata drift since the last tag — see
@@ -4,7 +4,7 @@ import { useEffect, useId, useRef } from "react";
4
4
  import {
5
5
  createSurfaceTransferHandle
6
6
  } from "@ai-matrx/alchemy/operate";
7
- import { ContentTransferSurfaceProvider } from "@ai-matrx/alchemy/react/workspace";
7
+ import { ContentTransferSurfaceProvider } from "@ai-matrx/alchemy/react/surface";
8
8
  import { getSurfaceManifest } from "../host/ui-slots.js";
9
9
  function useAlchemySurfaceHandle(surfaceName, getScope) {
10
10
  const instanceId = useId();
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useRef, useMemo } from "react";
4
- import { AgentAssistantMessage } from "../../../../next/lazy/AgentAssistantMessage.js";
4
+ import { AgentAssistantMessage } from "../../../../ui/lazy/AgentAssistantMessage.js";
5
5
  import { useAppSelector, useAppDispatch } from "../../../../store/hooks.js";
6
6
  import { selectConversationMessages } from "../../../redux/execution-system/messages/messages.selectors.js";
7
7
  import {
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
- import { ScrollAssistantLauncherImpl, ScrollVoiceAssistantLauncherImpl } from "../../../next/lazy/ScrollAssistantLaunchers.js";
3
+ import { ScrollAssistantLauncherImpl, ScrollVoiceAssistantLauncherImpl } from "../../../ui/lazy/ScrollAssistantLaunchers.js";
4
4
  import { usePathname } from "../../../host/navigation.js";
5
5
  import { useEffect, useState } from "react";
6
6
  import { useIsMobile } from "@ai-matrx/kit/media-query";
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
- import { ConversationRenameDialogHostImpl } from "../../../../next/lazy/ConversationRenameDialogHostImpl.js";
3
+ import { ConversationRenameDialogHostImpl } from "../../../../ui/lazy/ConversationRenameDialogHostImpl.js";
4
4
  function ConversationRenameDialogHost() {
5
5
  return /* @__PURE__ */ jsx(ConversationRenameDialogHostImpl, {});
6
6
  }
@@ -3,7 +3,7 @@ import { useAppSelector } from "../../../../store/hooks.js";
3
3
  import { selectInstanceAgentName } from "../../../redux/execution-system/instance-ui-state/instance-ui-state.selectors.js";
4
4
  import { selectInstanceAgentDescription } from "../../../redux/execution-system/instance-ui-state/instance-ui-state.selectors.js";
5
5
  import { Webhook } from "lucide-react";
6
- import { IconResolver } from "../../../../next/lazy/IconResolver.js";
6
+ import { IconResolver } from "../../../../ui/lazy/IconResolver.js";
7
7
  import { selectVisibleInputDefinitions } from "../../../redux/execution-system/instance-variable-values/bound-variable.selectors.js";
8
8
  import { selectIsVariableFormShown } from "../../../redux/execution-system/selectors/aggregate.selectors.js";
9
9
  import { emptyStateInstruction } from "./empty-state-instruction.js";
@@ -31,6 +31,7 @@ import {
31
31
  import { buildConversationMessageTitle } from "../../../utils/conversation-message-title.js";
32
32
  import { resolveAssistantEditTarget } from "../message-options/resolveAssistantEditTarget.js";
33
33
  import { MessageTimestamp } from "../MessageTimestamp.js";
34
+ import { useTranscriptLayout } from "../../shared/transcript-layout.js";
34
35
  function useAssistantMessageActions({
35
36
  messageId,
36
37
  conversationId,
@@ -162,7 +163,8 @@ function AssistantMessageFooter(props) {
162
163
  showOptions,
163
164
  isLatestAssistant
164
165
  } = useAssistantMessageActions(props);
165
- const isHoverOnly = density === "compact" && !isLatestAssistant;
166
+ const answerActions = useTranscriptLayout().answerActions;
167
+ const isHoverOnly = answerActions === "hover" || density === "compact" && !isLatestAssistant;
166
168
  const rowRef = useRef(null);
167
169
  useEffect(() => {
168
170
  const row = rowRef.current;
@@ -30,6 +30,8 @@ import {
30
30
  } from "../../../redux/execution-system/messages/message-context-receipt.js";
31
31
  import { useMachineFramesVisible } from "../../shared/transcript-audience.js";
32
32
  import { useCollapsibleMessageText } from "./useCollapsibleMessageText.js";
33
+ import { useTranscriptLayout } from "../../shared/transcript-layout.js";
34
+ const COMPACT_COLLAPSE_THRESHOLD_PX = 192;
33
35
  import {
34
36
  selectHostSubmittedFirstTurnValues,
35
37
  selectOwnSubmittedFirstTurnValues
@@ -197,7 +199,8 @@ function AgentUserMessage({
197
199
  bodyBlocks,
198
200
  trimmedText
199
201
  ]);
200
- const { isCollapsed, setIsCollapsed, shouldBeCollapsible, measureRef } = useCollapsibleMessageText(collapseSignature);
202
+ const compactUserMessage = useTranscriptLayout().userMessage === "compact";
203
+ const { isCollapsed, setIsCollapsed, shouldBeCollapsible, measureRef } = useCollapsibleMessageText(collapseSignature, compactUserMessage ? COMPACT_COLLAPSE_THRESHOLD_PX : void 0);
201
204
  const authoredByHost = metadata?.authored_by === "host";
202
205
  const isSuperAdmin = useAppSelector(selectIsSuperAdmin);
203
206
  useEffect(() => {
@@ -281,95 +284,102 @@ function AgentUserMessage({
281
284
  }, title: "Collapse", "aria-label": "Collapse" })
282
285
  }
283
286
  ),
284
- /* @__PURE__ */ jsx("div", { className: "bg-muted border border-border rounded-lg px-2 py-2", children: /* @__PURE__ */ jsxs("div", { className: "relative min-w-0", children: [
285
- /* @__PURE__ */ jsxs(
286
- "div",
287
- {
288
- ref: measureRef,
289
- className: cn(
290
- "space-y-1.5 overflow-hidden transition-all duration-300",
291
- // Clamp leaves room below the fade: the first lines (what was
292
- // sent, the context strip) read at full contrast, and the
293
- // expand chevron never sits on top of text.
294
- shouldBeCollapsible && isCollapsed && !editingText && "max-h-24 pb-7"
295
- ),
296
- children: [
297
- machineFramesVisible && isFirstTurnMessage && /* @__PURE__ */ jsx(FirstTurnVariables, { conversationId }),
298
- showLaunchInputs && /* @__PURE__ */ jsx(
299
- FirstTurnLaunchInputs,
300
- {
301
- conversationId,
302
- onOpenChange: (open) => {
303
- if (open) setIsCollapsed(false);
304
- }
305
- }
306
- ),
307
- !database ? null : machineFramesVisible && contextReceipt && (contextReceipt.rows?.length ?? 0) > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-1.5", children: /* @__PURE__ */ jsx(
308
- MessageContextReceipt,
309
- {
310
- conversationId,
311
- messageId,
312
- receipt: contextReceipt,
313
- mismatches: contextMismatches
314
- }
315
- ) }) : machineFramesVisible && contextSnapshot && contextSnapshot.length > 0 && /* @__PURE__ */ jsx(
316
- ContextPolicyChipStrip,
317
- {
318
- conversationId,
319
- agentId,
320
- entries: contextSnapshot
321
- }
322
- ),
323
- /* @__PURE__ */ jsx(
324
- EditInPlace,
325
- {
326
- value: content,
327
- canEdit: canEditText,
328
- editing: editingText,
329
- onEditingChange: setEditingText,
330
- write: (text) => routeUserEditAction(dispatch, { actionId: "save", conversationId, messageId, newContent: text, surfaceKey }),
331
- actions: USER_EDIT_ACTIONS.filter((a) => a.id !== "save").map((a) => ({
332
- id: a.id,
333
- label: a.label,
334
- run: (text) => routeUserEditAction(dispatch, { actionId: a.id, conversationId, messageId, newContent: text, surfaceKey })
335
- })),
336
- discardDescription: "Your message stays exactly as it was sent; what you typed here is dropped.",
337
- editor: {
338
- imagePolicy: "other",
339
- surfaceName: "matrx-user/chat",
340
- sourceFeature: "chat",
341
- contentSource: { type: "chat-message", conversationId, messageId }
342
- },
343
- children: /* @__PURE__ */ jsx(
344
- AgentUserMessageContent,
287
+ /* @__PURE__ */ jsx(
288
+ "div",
289
+ {
290
+ className: compactUserMessage ? "bg-muted rounded-md px-3 py-1.5" : "bg-muted border border-border rounded-lg px-2 py-2",
291
+ "data-user-message-style": compactUserMessage ? "compact" : "card",
292
+ children: /* @__PURE__ */ jsxs("div", { className: "relative min-w-0", children: [
293
+ /* @__PURE__ */ jsxs(
294
+ "div",
295
+ {
296
+ ref: measureRef,
297
+ className: cn(
298
+ "space-y-1.5 overflow-hidden transition-all duration-300",
299
+ // Clamp leaves room below the fade: the first lines (what was
300
+ // sent, the context strip) read at full contrast, and the
301
+ // expand chevron never sits on top of text.
302
+ shouldBeCollapsible && isCollapsed && !editingText && "max-h-24 pb-7"
303
+ ),
304
+ children: [
305
+ machineFramesVisible && isFirstTurnMessage && /* @__PURE__ */ jsx(FirstTurnVariables, { conversationId }),
306
+ showLaunchInputs && /* @__PURE__ */ jsx(
307
+ FirstTurnLaunchInputs,
308
+ {
309
+ conversationId,
310
+ onOpenChange: (open) => {
311
+ if (open) setIsCollapsed(false);
312
+ }
313
+ }
314
+ ),
315
+ !database ? null : machineFramesVisible && contextReceipt && (contextReceipt.rows?.length ?? 0) > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-1.5", children: /* @__PURE__ */ jsx(
316
+ MessageContextReceipt,
317
+ {
318
+ conversationId,
319
+ messageId,
320
+ receipt: contextReceipt,
321
+ mismatches: contextMismatches
322
+ }
323
+ ) }) : machineFramesVisible && contextSnapshot && contextSnapshot.length > 0 && /* @__PURE__ */ jsx(
324
+ ContextPolicyChipStrip,
345
325
  {
346
326
  conversationId,
347
- text: trimmedText,
348
- attachmentParts,
349
- bodyBlocks
327
+ agentId,
328
+ entries: contextSnapshot
329
+ }
330
+ ),
331
+ /* @__PURE__ */ jsx(
332
+ EditInPlace,
333
+ {
334
+ value: content,
335
+ canEdit: canEditText,
336
+ editing: editingText,
337
+ onEditingChange: setEditingText,
338
+ write: (text) => routeUserEditAction(dispatch, { actionId: "save", conversationId, messageId, newContent: text, surfaceKey }),
339
+ actions: USER_EDIT_ACTIONS.filter((a) => a.id !== "save").map((a) => ({
340
+ id: a.id,
341
+ label: a.label,
342
+ run: (text) => routeUserEditAction(dispatch, { actionId: a.id, conversationId, messageId, newContent: text, surfaceKey })
343
+ })),
344
+ discardDescription: "Your message stays exactly as it was sent; what you typed here is dropped.",
345
+ editor: {
346
+ imagePolicy: "other",
347
+ surfaceName: "matrx-user/chat",
348
+ sourceFeature: "chat",
349
+ contentSource: { type: "chat-message", conversationId, messageId }
350
+ },
351
+ children: /* @__PURE__ */ jsx(
352
+ AgentUserMessageContent,
353
+ {
354
+ conversationId,
355
+ text: trimmedText,
356
+ attachmentParts,
357
+ bodyBlocks
358
+ }
359
+ )
350
360
  }
351
361
  )
362
+ ]
363
+ }
364
+ ),
365
+ shouldBeCollapsible && isCollapsed && /* @__PURE__ */ jsxs(Fragment, { children: [
366
+ /* @__PURE__ */ jsx("div", { className: "absolute bottom-0 left-0 right-0 h-10 bg-gradient-to-t from-muted via-muted/80 to-transparent pointer-events-none" }),
367
+ /* @__PURE__ */ jsx("div", { className: "absolute bottom-0 left-0 right-0 flex justify-center pb-1", children: /* @__PURE__ */ jsx(
368
+ Button,
369
+ {
370
+ variant: "quiet",
371
+ onClick: (e) => {
372
+ e.stopPropagation();
373
+ setIsCollapsed(false);
374
+ },
375
+ title: "Expand message",
376
+ children: /* @__PURE__ */ jsx(ChevronDown, { className: "w-4 h-4" })
352
377
  }
353
- )
354
- ]
355
- }
356
- ),
357
- shouldBeCollapsible && isCollapsed && /* @__PURE__ */ jsxs(Fragment, { children: [
358
- /* @__PURE__ */ jsx("div", { className: "absolute bottom-0 left-0 right-0 h-10 bg-gradient-to-t from-muted via-muted/80 to-transparent pointer-events-none" }),
359
- /* @__PURE__ */ jsx("div", { className: "absolute bottom-0 left-0 right-0 flex justify-center pb-1", children: /* @__PURE__ */ jsx(
360
- Button,
361
- {
362
- variant: "quiet",
363
- onClick: (e) => {
364
- e.stopPropagation();
365
- setIsCollapsed(false);
366
- },
367
- title: "Expand message",
368
- children: /* @__PURE__ */ jsx(ChevronDown, { className: "w-4 h-4" })
369
- }
370
- ) })
371
- ] })
372
- ] }) }),
378
+ ) })
379
+ ] })
380
+ ] })
381
+ }
382
+ ),
373
383
  /* @__PURE__ */ jsx(
374
384
  "div",
375
385
  {
@@ -23,7 +23,7 @@
23
23
  * physically clicks expand. A late remeasure re-collapses unless the user has
24
24
  * explicitly toggled.
25
25
  */
26
- export declare function useCollapsibleMessageText(contentKey: string): {
26
+ export declare function useCollapsibleMessageText(contentKey: string, thresholdPx?: number): {
27
27
  isCollapsed: boolean;
28
28
  setIsCollapsed: (value: boolean | ((prev: boolean) => boolean)) => void;
29
29
  shouldBeCollapsible: boolean;
@@ -1,6 +1,6 @@
1
1
  import { useCallback, useLayoutEffect, useRef, useState } from "react";
2
2
  const COLLAPSE_THRESHOLD_PX = 48;
3
- function useCollapsibleMessageText(contentKey) {
3
+ function useCollapsibleMessageText(contentKey, thresholdPx = COLLAPSE_THRESHOLD_PX) {
4
4
  const [isCollapsed, setIsCollapsedState] = useState(true);
5
5
  const [shouldBeCollapsible, setShouldBeCollapsible] = useState(false);
6
6
  const measureRef = useRef(null);
@@ -10,8 +10,9 @@ function useCollapsibleMessageText(contentKey) {
10
10
  const node = measureRef.current;
11
11
  if (!node) return void 0;
12
12
  const measure = () => {
13
- const contentHeight = node.scrollHeight;
14
- const isLong = contentHeight > COLLAPSE_THRESHOLD_PX;
13
+ const padding = typeof getComputedStyle === "function" ? parseFloat(getComputedStyle(node).paddingBottom) || 0 : 0;
14
+ const contentHeight = node.scrollHeight - padding;
15
+ const isLong = contentHeight > thresholdPx;
15
16
  const contentChanged = previousContentRef.current !== contentKey;
16
17
  setShouldBeCollapsible(isLong);
17
18
  if (contentChanged) {
@@ -31,7 +32,7 @@ function useCollapsibleMessageText(contentKey) {
31
32
  const observer = new ResizeObserver(measure);
32
33
  observer.observe(node);
33
34
  return () => observer.disconnect();
34
- }, [contentKey]);
35
+ }, [contentKey, thresholdPx]);
35
36
  const setIsCollapsed = useCallback(
36
37
  (value) => {
37
38
  userToggledRef.current = true;
@@ -2,6 +2,7 @@ import type { ComposerPresentation } from "../inputs/smart-input/composer/compos
2
2
  import type { AttachedContextRailItem } from "../inputs/smart-input/ConversationContextRail.js";
3
3
  import type { VariablesPanelStyle } from "../inputs/variable-input-variations/variable-input-options.js";
4
4
  import { type TranscriptAudience } from "./transcript-audience.js";
5
+ import { type TranscriptLayout } from "./transcript-layout.js";
5
6
  interface SmartInputForwardProps {
6
7
  /** See `SmartAgentInput`'s `draftAlias`: the per-record unsent-draft key. */
7
8
  draftAlias?: string;
@@ -114,6 +115,11 @@ interface AgentConversationColumnBaseProps {
114
115
  * ./transcript-audience.tsx.
115
116
  */
116
117
  audience?: TranscriptAudience;
118
+ /**
119
+ * The host's layout knobs (width, alignment, the person's message style). Omitted = today's
120
+ * web chat exactly. See ./transcript-layout.tsx.
121
+ */
122
+ layout?: TranscriptLayout;
117
123
  }
118
- export declare function AgentConversationColumn({ conversationId, displayConversationId, surfaceKey, constrainWidth, edgeToEdgeScroll, smartInputProps, landingContent, hideInput, hideCreatorPanel, afterMessages, aboveInput, deferColdMarkdown, audience, }: AgentConversationColumnProps): import("react").JSX.Element;
124
+ export declare function AgentConversationColumn({ conversationId, displayConversationId, surfaceKey, constrainWidth, edgeToEdgeScroll, smartInputProps, landingContent, hideInput, hideCreatorPanel, afterMessages, aboveInput, deferColdMarkdown, audience, layout, }: AgentConversationColumnProps): import("react").JSX.Element;
119
125
  export {};
@@ -32,9 +32,13 @@ import {
32
32
  } from "./transcript-audience.js";
33
33
  import { cn } from "@ai-matrx/design-system";
34
34
  import {
35
- ASSISTANT_MESSAGE_COLUMN_CLASS,
36
35
  ASSISTANT_MESSAGE_COLUMN_INSET_CLASS
37
36
  } from "./assistant-message-layout.js";
37
+ import {
38
+ TranscriptLayoutProvider,
39
+ resolveTranscriptLayout,
40
+ transcriptColumnClass
41
+ } from "./transcript-layout.js";
38
42
  import {
39
43
  isWarRoomThreadAgentSurface,
40
44
  traceWarRoomRenderPath
@@ -58,8 +62,11 @@ function AgentConversationColumn({
58
62
  afterMessages,
59
63
  aboveInput,
60
64
  deferColdMarkdown = false,
61
- audience = "builder"
65
+ audience = "builder",
66
+ layout
62
67
  }) {
68
+ const resolvedLayout = resolveTranscriptLayout(layout);
69
+ const columnClass = transcriptColumnClass(resolvedLayout);
63
70
  const dispatch = useAppDispatch();
64
71
  const displayId = displayConversationId ?? conversationId;
65
72
  const scrollRef = useRef(null);
@@ -202,8 +209,8 @@ function AgentConversationColumn({
202
209
  });
203
210
  }, []);
204
211
  const edgeScroll = constrainWidth && edgeToEdgeScroll;
205
- const centerWrap = edgeScroll ? cn(ASSISTANT_MESSAGE_COLUMN_CLASS, ASSISTANT_MESSAGE_COLUMN_INSET_CLASS) : "contents";
206
- return /* @__PURE__ */ jsx(ConversationAbilitiesProvider, { conversationId: displayId, children: /* @__PURE__ */ jsx(TranscriptAudienceProvider, { audience, children: /* @__PURE__ */ jsxs(
212
+ const centerWrap = edgeScroll ? cn(columnClass, ASSISTANT_MESSAGE_COLUMN_INSET_CLASS) : "contents";
213
+ return /* @__PURE__ */ jsx(ConversationAbilitiesProvider, { conversationId: displayId, children: /* @__PURE__ */ jsx(TranscriptAudienceProvider, { audience, children: /* @__PURE__ */ jsx(TranscriptLayoutProvider, { layout: resolvedLayout, children: /* @__PURE__ */ jsxs(
207
214
  "div",
208
215
  {
209
216
  className: cn(
@@ -211,7 +218,7 @@ function AgentConversationColumn({
211
218
  // Legacy: pad the whole column. Edge mode: padding lives on the
212
219
  // centered inner wrappers instead so the scroll area runs to the edge.
213
220
  !edgeScroll && "px-2",
214
- constrainWidth && !edgeScroll && ASSISTANT_MESSAGE_COLUMN_CLASS,
221
+ constrainWidth && !edgeScroll && columnClass,
215
222
  constrainWidth && !edgeScroll && "pb-2",
216
223
  edgeScroll && "w-full pb-2"
217
224
  ),
@@ -319,7 +326,7 @@ function AgentConversationColumn({
319
326
  animate: { opacity: 1, y: 0 },
320
327
  transition: { duration: 0.28, ease: [0.32, 0.72, 0, 1] },
321
328
  className: edgeScroll ? cn(
322
- ASSISTANT_MESSAGE_COLUMN_CLASS,
329
+ columnClass,
323
330
  ASSISTANT_MESSAGE_COLUMN_INSET_CLASS
324
331
  ) : void 0,
325
332
  children: [
@@ -348,7 +355,7 @@ function AgentConversationColumn({
348
355
  )
349
356
  ]
350
357
  }
351
- ) }) });
358
+ ) }) }) });
352
359
  }
353
360
  export {
354
361
  AgentConversationColumn
@@ -0,0 +1,31 @@
1
+ /**
2
+ * How a host lays the ONE conversation column out (HOST-OWNED-SOURCE-PLAN P28e look bar).
3
+ *
4
+ * Knobs, never forks: every value has today's web default, and a host that says nothing gets
5
+ * exactly the web chat. Matrx 2 (desktop) fills its pane, starts at the left edge and draws the
6
+ * person's turn as a compact block — the look its coding screen had before the column.
7
+ *
8
+ * - `width`: "reading" (the canonical `max-w-3xl` reading column) | "fill" (the host's pane).
9
+ * - `align`: "center" | "start" — where a reading-width column sits in a wider pane.
10
+ * - `userMessage`: "card" (the bordered card that collapses a long message behind a chevron) |
11
+ * "compact" (a tight block; only a genuinely long message collapses).
12
+ * - `answerActions`: "shown" (the answer's action row always visible on the latest answer) |
13
+ * "hover" (revealed on hover / keyboard focus — a finished answer ends on its last word, not on
14
+ * a lone ⋯ that reads as "still writing" in a host with few actions).
15
+ */
16
+ import { type ReactNode } from "react";
17
+ export interface TranscriptLayout {
18
+ width?: "reading" | "fill";
19
+ align?: "center" | "start";
20
+ userMessage?: "card" | "compact";
21
+ answerActions?: "shown" | "hover";
22
+ }
23
+ export declare const DEFAULT_TRANSCRIPT_LAYOUT: Required<TranscriptLayout>;
24
+ export declare function resolveTranscriptLayout(layout?: TranscriptLayout): Required<TranscriptLayout>;
25
+ /** The column's width + placement classes for a resolved layout. Web default = the canonical class. */
26
+ export declare function transcriptColumnClass(layout: Required<TranscriptLayout>): string;
27
+ export declare function TranscriptLayoutProvider(props: {
28
+ layout?: TranscriptLayout;
29
+ children: ReactNode;
30
+ }): import("react").FunctionComponentElement<import("react").ProviderProps<Required<TranscriptLayout>>>;
31
+ export declare function useTranscriptLayout(): Required<TranscriptLayout>;
@@ -0,0 +1,38 @@
1
+ "use client";
2
+ import { createContext, createElement, useContext, useMemo } from "react";
3
+ import {
4
+ ASSISTANT_MESSAGE_COLUMN_CLASS
5
+ } from "./assistant-message-layout.js";
6
+ const DEFAULT_TRANSCRIPT_LAYOUT = {
7
+ width: "reading",
8
+ align: "center",
9
+ userMessage: "card",
10
+ answerActions: "shown"
11
+ };
12
+ const TranscriptLayoutContext = createContext(DEFAULT_TRANSCRIPT_LAYOUT);
13
+ function resolveTranscriptLayout(layout) {
14
+ return { ...DEFAULT_TRANSCRIPT_LAYOUT, ...layout ?? {} };
15
+ }
16
+ function transcriptColumnClass(layout) {
17
+ if (layout.width === "fill") return "w-full";
18
+ if (layout.align === "start") return "w-full max-w-3xl";
19
+ return ASSISTANT_MESSAGE_COLUMN_CLASS;
20
+ }
21
+ function TranscriptLayoutProvider(props) {
22
+ const { width, align, userMessage, answerActions } = resolveTranscriptLayout(props.layout);
23
+ const value = useMemo(
24
+ () => ({ width, align, userMessage, answerActions }),
25
+ [width, align, userMessage, answerActions]
26
+ );
27
+ return createElement(TranscriptLayoutContext.Provider, { value }, props.children);
28
+ }
29
+ function useTranscriptLayout() {
30
+ return useContext(TranscriptLayoutContext);
31
+ }
32
+ export {
33
+ DEFAULT_TRANSCRIPT_LAYOUT,
34
+ TranscriptLayoutProvider,
35
+ resolveTranscriptLayout,
36
+ transcriptColumnClass,
37
+ useTranscriptLayout
38
+ };
@@ -6,7 +6,7 @@ import {
6
6
  spliceSave,
7
7
  tokenizeSource
8
8
  } from "@ai-matrx/content-ir/source";
9
- import { removeThinkingContent } from "@ai-matrx/print/markdown";
9
+ import { stripThinking as removeThinkingContent } from "@ai-matrx/content-ir/source";
10
10
  function isTextPart(part) {
11
11
  return part.type === "text" || part.type === void 0;
12
12
  }
@@ -17,7 +17,7 @@ import {
17
17
  readEnvelope,
18
18
  reconstructRegionValue
19
19
  } from "@ai-matrx/content-ir";
20
- import { removeThinkingContent } from "@ai-matrx/print/markdown";
20
+ import { stripThinking as removeThinkingContent } from "@ai-matrx/content-ir/source";
21
21
  import { NON_ANSWER_BLOCK_TYPES } from "../active-requests/active-requests.selectors.js";
22
22
  import {
23
23
  buildMessageCitationIndex,
@@ -5,7 +5,10 @@ import { ChevronDown, ExternalLink, PencilLine, Plus } from "lucide-react";
5
5
  import { AppLink } from "../../host/ui-slots.js";
6
6
  import { Popover, PopoverContent, PopoverTrigger } from "@ai-matrx/design-system";
7
7
  import { useAppSelector } from "../../store/hooks.js";
8
- import { selectConversationTitle } from "../../agents/redux/execution-system/conversations/conversations.selectors.js";
8
+ import {
9
+ selectConversationTitle,
10
+ selectIsCacheOnly
11
+ } from "../../agents/redux/execution-system/conversations/conversations.selectors.js";
9
12
  import { selectConversationListItemById } from "../../agents/redux/conversation-list/conversation-list.selectors.js";
10
13
  import { conversationRenameOpener } from "../../agents/components/conversation-actions/rename/conversationRenameOpener.js";
11
14
  import { conversationTitleText } from "@ai-matrx/content-ir/surfaces";
@@ -33,6 +36,9 @@ function ChatPanelTitleMenu({
33
36
  const [open, setOpen] = useState(false);
34
37
  const realTitle = useChatRealTitle(conversationId);
35
38
  const title = realTitle ?? "New chat";
39
+ const started = useAppSelector(
40
+ (state) => conversationId ? !selectIsCacheOnly(conversationId)(state) || selectConversationListItemById(conversationId)(state) !== null : false
41
+ );
36
42
  const close = () => setOpen(false);
37
43
  return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
38
44
  /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "quiet", iconEnd: /* @__PURE__ */ jsx(ChevronDown, {}), className: "min-w-0", title, children: title }) }),
@@ -56,7 +62,7 @@ function ChatPanelTitleMenu({
56
62
  }
57
63
  }
58
64
  ),
59
- conversationId ? /* @__PURE__ */ jsxs(Fragment, { children: [
65
+ conversationId && started ? /* @__PURE__ */ jsxs(Fragment, { children: [
60
66
  /* @__PURE__ */ jsx(
61
67
  Tile,
62
68
  {