@ai-matrx/rich-content 0.2.165 → 0.2.167

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
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.167
4
+
5
+ UNVERIFIED: this release has no Unreleased entry and the source comparison could not be made (the caller supplied no source comparison). Whether source changed is unknown; consumer action is unknown.
6
+ Check the package's gitHead on npm against this version's gitHead: `git diff <previous gitHead> <this gitHead> -- apps/shared/rich-content/src`.
7
+
8
+ ## 0.2.166
9
+
10
+ SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
11
+ so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
12
+ Diff: `git diff 6a0868df3e c3135c57b9 -- apps/shared/rich-content/src`
13
+
14
+ Changed shipped source files (1):
15
+ - `apps/shared/rich-content/src/rich-document/variants/shared/AlchemyDocumentMenu.tsx`
16
+
3
17
  ## 0.2.165
4
18
  ### Unreleased
5
19
 
@@ -22,6 +22,7 @@ import { ShikiCodeView } from "./highlight/ShikiCodeView.js";
22
22
  import { parseFenceMeta } from "@ai-matrx/content-ir/source";
23
23
  import { agentForPromptKey } from "../utils/lifted/code-editor-agents.js";
24
24
  import { useOpenSmartCodeEditorWindow } from "../host/app-bindings.js";
25
+ import { useBlockHostSlot } from "../display/block-host.js";
25
26
  import {
26
27
  normalizeViewLanguage,
27
28
  mapLanguageForMonaco,
@@ -74,6 +75,7 @@ const CodeBlockBody = ({
74
75
  setEditedCode(initialCode);
75
76
  }
76
77
  const [isCopied, setIsCopied] = useState(false);
78
+ const host = useBlockHostSlot();
77
79
  const [isFullScreen, setIsFullScreen] = useState(false);
78
80
  const [isCollapsed, setIsCollapsed] = useState(false);
79
81
  const [isEditing, setIsEditing] = useState(false);
@@ -241,7 +243,8 @@ const CodeBlockBody = ({
241
243
  {
242
244
  ref: containerRef,
243
245
  className: cn(
244
- "w-full my-4 rounded-t-xl rounded-b-lg border border-neutral-200 dark:border-neutral-700",
246
+ "w-full my-4 rounded-t-xl rounded-b-lg border transition-colors",
247
+ host.selected ? "border-primary" : "border-neutral-200 dark:border-neutral-700",
245
248
  // `overflow-clip` (NOT `overflow-hidden`) so this box still clips to
246
249
  // its rounded corners without becoming a scroll container — an
247
250
  // overflow-hidden ancestor would trap StickyButtons' `position:
@@ -287,7 +290,8 @@ const CodeBlockBody = ({
287
290
  allowEdit,
288
291
  customBuiltinKeys,
289
292
  headerLeftSlot,
290
- extraMenuItems
293
+ extraMenuItems,
294
+ headerRightSlot: host.actions
291
295
  }
292
296
  ),
293
297
  showStickyButtons && /* @__PURE__ */ jsx(
@@ -54,6 +54,8 @@ interface CodeBlockHeaderProps {
54
54
  * (e.g. "Data" from JsonBlock) appear as their own section.
55
55
  */
56
56
  extraMenuItems?: CodeBlockMenuItem[];
57
+ /** Rendered after the kebab: a host's own tools for this block (the editor's "</>", Remove). */
58
+ headerRightSlot?: React.ReactNode;
57
59
  }
58
60
  export declare const CodeBlockHeader: React.FC<CodeBlockHeaderProps>;
59
61
  export declare const EditButton: ({ isEditing, toggleEdit, }: {
@@ -67,7 +67,8 @@ const CodeBlockHeader = ({
67
67
  allowEdit = true,
68
68
  customBuiltinKeys = [],
69
69
  headerLeftSlot,
70
- extraMenuItems
70
+ extraMenuItems,
71
+ headerRightSlot
71
72
  }) => {
72
73
  const canCollapse = linesCount > 5;
73
74
  return /* @__PURE__ */ jsxs(
@@ -143,7 +144,8 @@ const CodeBlockHeader = ({
143
144
  isCompleteHTML,
144
145
  handleViewHTML,
145
146
  isCreatingPage,
146
- extraMenuItems
147
+ extraMenuItems,
148
+ headerRightSlot
147
149
  }
148
150
  )
149
151
  ]
@@ -183,7 +185,8 @@ const CodeBlockButtons = ({
183
185
  isCompleteHTML = false,
184
186
  handleViewHTML,
185
187
  isCreatingPage = false,
186
- extraMenuItems
188
+ extraMenuItems,
189
+ headerRightSlot
187
190
  }) => {
188
191
  const menu = useAdvancedMenu();
189
192
  const kebabRef = useRef(null);
@@ -395,6 +398,7 @@ const CodeBlockButtons = ({
395
398
  }
396
399
  }
397
400
  ) }),
401
+ headerRightSlot,
398
402
  /* @__PURE__ */ jsx(
399
403
  AdvancedMenu,
400
404
  {
@@ -0,0 +1,40 @@
1
+ /**
2
+ * THE BLOCK HOST SLOT — one frame per block (Arman, 2026-10-14: "create a nice and clean wrapper
3
+ * that replaces whatever the components already have or you don't have any type of visible
4
+ * wrapper or anything that takes up space").
5
+ *
6
+ * A host that draws a rendered block with tools of its own (the rich editor's "</>", Remove,
7
+ * drag) never wraps the block in a second box. It provides `BlockHostProvider`; a block whose
8
+ * card has a header row calls `useBlockHostSlot()` and draws its `actions` among its own header
9
+ * buttons, so the host's tools join that row, and shows `selected` as its own border colour.
10
+ * A block that claims no slot leaves the host to draw a small overlay inside the block's corner
11
+ * (the provider's `onClaimedChange` reports false).
12
+ *
13
+ * Only the outermost block that claims the slot gets it: a block nested inside another block's
14
+ * render (markdown inside a card) draws nothing, so the tools appear once. With no host (Read, chat),
15
+ * every export here renders nothing and changes nothing.
16
+ */
17
+ import { type ReactNode } from "react";
18
+ export interface BlockHostProviderProps {
19
+ /** The host's own tools for this block, drawn in the block's header row. */
20
+ renderActions: () => ReactNode;
21
+ /** The block is selected in its host; the block shows it on its own border. */
22
+ selected?: boolean;
23
+ /** Told whether a header in the block took the tools (false → the host draws its overlay). */
24
+ onClaimedChange?: (claimed: boolean) => void;
25
+ children: ReactNode;
26
+ }
27
+ export declare function BlockHostProvider({ renderActions, selected, onClaimedChange, children }: BlockHostProviderProps): import("react").JSX.Element;
28
+ export interface BlockHostSlot {
29
+ /** The host's tools for the block's header row; null without a host or for a nested block. */
30
+ actions: ReactNode;
31
+ /** The host has this block selected: show it as the block's own border colour. */
32
+ selected: boolean;
33
+ }
34
+ /**
35
+ * Claims the host slot for a block's header. Call it in the component that draws the card;
36
+ * `enabled: false` while that component draws no header of its own (a child block does).
37
+ */
38
+ export declare function useBlockHostSlot(enabled?: boolean): BlockHostSlot;
39
+ /** The host's tools as an element, for a header that needs nothing else from the slot. */
40
+ export declare function BlockHostActions(): import("react").JSX.Element;
@@ -0,0 +1,40 @@
1
+ "use client";
2
+ import { Fragment, jsx } from "react/jsx-runtime";
3
+ import { createContext, useContext, useLayoutEffect, useMemo, useRef, useState } from "react";
4
+ const BlockHostContext = createContext(null);
5
+ let nextClaimId = 1;
6
+ function BlockHostProvider({ renderActions, selected = false, onClaimedChange, children }) {
7
+ const [claimants, setClaimants] = useState([]);
8
+ const owner = claimants.length ? Math.min(...claimants) : null;
9
+ const report = useRef(onClaimedChange);
10
+ report.current = onClaimedChange;
11
+ const claim = useMemo(
12
+ () => (id) => {
13
+ setClaimants((current) => current.includes(id) ? current : [...current, id]);
14
+ return () => setClaimants((current) => current.filter((other) => other !== id));
15
+ },
16
+ []
17
+ );
18
+ useLayoutEffect(() => {
19
+ report.current?.(owner !== null);
20
+ }, [owner]);
21
+ const value = useMemo(() => ({ renderActions, selected, owner, claim }), [renderActions, selected, owner, claim]);
22
+ return /* @__PURE__ */ jsx(BlockHostContext.Provider, { value, children });
23
+ }
24
+ function useBlockHostSlot(enabled = true) {
25
+ const host = useContext(BlockHostContext);
26
+ const id = useRef(0);
27
+ if (id.current === 0) id.current = nextClaimId++;
28
+ const claim = enabled ? host?.claim : void 0;
29
+ useLayoutEffect(() => claim ? claim(id.current) : void 0, [claim]);
30
+ if (!host || host.owner !== id.current) return { actions: null, selected: false };
31
+ return { actions: host.renderActions(), selected: host.selected };
32
+ }
33
+ function BlockHostActions() {
34
+ return /* @__PURE__ */ jsx(Fragment, { children: useBlockHostSlot().actions });
35
+ }
36
+ export {
37
+ BlockHostActions,
38
+ BlockHostProvider,
39
+ useBlockHostSlot
40
+ };
@@ -4,6 +4,7 @@ import { useState } from "react";
4
4
  import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@ai-matrx/design-system";
5
5
  import { ChevronDown } from "lucide-react";
6
6
  import { ControlRow } from "@ai-matrx/design-system/controls";
7
+ import { useBlockHostSlot } from "../block-host.js";
7
8
  const ChatCollapsibleWrapper = ({
8
9
  icon,
9
10
  title,
@@ -15,6 +16,7 @@ const ChatCollapsibleWrapper = ({
15
16
  maxWidth = "max-w-5xl"
16
17
  }) => {
17
18
  const [isOpen, setIsOpen] = useState(initialOpen);
19
+ const host = useBlockHostSlot();
18
20
  const handleOpenChange = (open) => {
19
21
  setIsOpen(open);
20
22
  if (onStateChange) {
@@ -26,7 +28,7 @@ const ChatCollapsibleWrapper = ({
26
28
  {
27
29
  open: isOpen,
28
30
  onOpenChange: handleOpenChange,
29
- className: `border-2 border-zinc-200 dark:border-zinc-700 rounded-3xl shadow-sm my-1 w-full ${maxWidth} mx-auto hover:bg-accent/50 hover:shadow-sm ${className}`,
31
+ className: `border-2 ${host.selected ? "border-primary" : "border-zinc-200 dark:border-zinc-700"} transition-colors rounded-3xl shadow-sm my-1 w-full ${maxWidth} mx-auto hover:bg-accent/50 hover:shadow-sm ${className}`,
30
32
  children: [
31
33
  /* @__PURE__ */ jsxs("div", { className: "relative flex w-full items-center justify-between rounded-t-lg hover:bg-accent/50 hover:shadow-sm", children: [
32
34
  /* @__PURE__ */ jsxs(CollapsibleTrigger, { className: "flex-1 flex items-center gap-2 py-1 pl-4 pr-1 font-medium hover:bg-accent/50 hover:shadow-sm", children: [
@@ -39,8 +41,11 @@ const ChatCollapsibleWrapper = ({
39
41
  }
40
42
  )
41
43
  ] }),
42
- controls && // One ControlRow: every action carries its own 3px half-gap, the row adds none.
43
- /* @__PURE__ */ jsx(ControlRow, { nowrap: true, className: "shrink-0 pr-3", onClick: (e) => e.stopPropagation(), children: controls })
44
+ (controls || host.actions) && // One ControlRow: every action carries its own 3px half-gap, the row adds none.
45
+ /* @__PURE__ */ jsxs(ControlRow, { nowrap: true, className: "shrink-0 pr-3", onClick: (e) => e.stopPropagation(), children: [
46
+ controls,
47
+ host.actions
48
+ ] })
44
49
  ] }),
45
50
  /* @__PURE__ */ jsx(CollapsibleContent, { className: "bg-transparent dark:bg-transparent border-none", children: /* @__PURE__ */ jsxs("div", { className: "relative p-0 border-none", children: [
46
51
  /* @__PURE__ */ jsx("div", { className: "absolute top-0 left-8 right-8 h-px bg-zinc-200 dark:bg-zinc-700" }),
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useClipboard } from "@ai-matrx/kit/clipboard";
4
4
  import { useEffect, useMemo, useState } from "react";
5
5
  import { clientLazy } from "../../../utils/client-lazy.js";
@@ -11,6 +11,7 @@ import { cn } from "@ai-matrx/design-system";
11
11
  import { parseChartSpec } from "./chart-spec.js";
12
12
  import { useCanvasPresentation } from "@ai-matrx/canvas/react";
13
13
  import { chartPaneLayout } from "../canvas-adaptive.js";
14
+ import { useBlockHostSlot } from "../../block-host.js";
14
15
  import { Button } from "@ai-matrx/design-system/controls";
15
16
  const CHART_TYPE_LABEL = {
16
17
  bar: "Bar chart",
@@ -35,40 +36,54 @@ const ChartBlock = ({ content = "", isStreamActive = false, className }) => {
35
36
  const [showSource, setShowSource] = useState(false);
36
37
  const title = spec?.title ?? (spec ? CHART_TYPE_LABEL[spec.type] ?? "Chart" : "Chart");
37
38
  const presentation = useCanvasPresentation();
39
+ const host = useBlockHostSlot();
38
40
  const paneLayout = chartPaneLayout(presentation, spec?.type ?? "");
39
41
  const handleCopy = async () => {
40
42
  if (!await copyText(source, void 0, "Couldn't copy the chart source")) return;
41
43
  setCopied(true);
42
44
  setTimeout(() => setCopied(false), 1500);
43
45
  };
44
- return /* @__PURE__ */ jsxs("div", { className: cn("my-3 overflow-hidden rounded-lg border border-border bg-card", className), children: [
45
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 border-b border-border bg-muted/50 px-3 py-1.5", children: [
46
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
47
- /* @__PURE__ */ jsx(BarChart3, { className: "h-3.5 w-3.5 shrink-0 text-primary" }),
48
- /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-medium text-foreground", children: title }),
49
- spec && /* @__PURE__ */ jsx("span", { className: "hidden shrink-0 text-xs text-muted-foreground sm:inline", children: CHART_TYPE_LABEL[spec.type] ?? "Chart" }),
50
- isStreamActive && /* @__PURE__ */ jsx("span", { className: "shrink-0 animate-pulse text-xs text-muted-foreground", children: "building\u2026" })
51
- ] }),
52
- !isStreamActive && /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [
53
- /* @__PURE__ */ jsx(IconBtn, { label: copied ? "Copied" : "Copy chart source", onClick: handleCopy, children: copied ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" }) }),
54
- spec && presentation === null && /* @__PURE__ */ jsx(IconBtn, { label: "View fullscreen", onClick: () => setFullscreen(true), children: /* @__PURE__ */ jsx(Expand, { className: "h-3.5 w-3.5" }) })
55
- ] })
56
- ] }),
57
- /* @__PURE__ */ jsx("div", { className: "p-3", children: isStreamActive ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", "aria-busy": "true", children: [
58
- /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-1/3" }),
59
- /* @__PURE__ */ jsx(Skeleton, { className: "h-48 w-full" })
60
- ] }) : error ? /* @__PURE__ */ jsx(ChartError, { error, source, show: showSource, onToggle: () => setShowSource((v) => !v) }) : spec ? /* @__PURE__ */ jsx(
61
- "div",
62
- {
63
- className: paneLayout ? "w-full" : "h-[340px] w-full",
64
- style: paneLayout ? { height: paneLayout.height } : void 0,
65
- "data-chart-legend": paneLayout?.legend ?? "default",
66
- "data-chart-bars": paneLayout?.horizontalBars ? "horizontal" : "vertical",
67
- children: /* @__PURE__ */ jsx(ChartCanvas, { spec, paneLayout })
68
- }
69
- ) : null }),
70
- fullscreen && spec && /* @__PURE__ */ jsx(ChartFullscreen, { spec, title, onClose: () => setFullscreen(false) })
71
- ] });
46
+ return /* @__PURE__ */ jsxs(
47
+ "div",
48
+ {
49
+ className: cn(
50
+ "my-3 overflow-hidden rounded-lg border bg-card transition-colors",
51
+ host.selected ? "border-primary" : "border-border",
52
+ className
53
+ ),
54
+ children: [
55
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 border-b border-border bg-muted/50 px-3 py-1.5", children: [
56
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
57
+ /* @__PURE__ */ jsx(BarChart3, { className: "h-3.5 w-3.5 shrink-0 text-primary" }),
58
+ /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-medium text-foreground", children: title }),
59
+ spec && /* @__PURE__ */ jsx("span", { className: "hidden shrink-0 text-xs text-muted-foreground sm:inline", children: CHART_TYPE_LABEL[spec.type] ?? "Chart" }),
60
+ isStreamActive && /* @__PURE__ */ jsx("span", { className: "shrink-0 animate-pulse text-xs text-muted-foreground", children: "building\u2026" })
61
+ ] }),
62
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [
63
+ !isStreamActive && /* @__PURE__ */ jsxs(Fragment, { children: [
64
+ /* @__PURE__ */ jsx(IconBtn, { label: copied ? "Copied" : "Copy chart source", onClick: handleCopy, children: copied ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" }) }),
65
+ spec && presentation === null && /* @__PURE__ */ jsx(IconBtn, { label: "View fullscreen", onClick: () => setFullscreen(true), children: /* @__PURE__ */ jsx(Expand, { className: "h-3.5 w-3.5" }) })
66
+ ] }),
67
+ host.actions
68
+ ] })
69
+ ] }),
70
+ /* @__PURE__ */ jsx("div", { className: "p-3", children: isStreamActive ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", "aria-busy": "true", children: [
71
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-1/3" }),
72
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-48 w-full" })
73
+ ] }) : error ? /* @__PURE__ */ jsx(ChartError, { error, source, show: showSource, onToggle: () => setShowSource((v) => !v) }) : spec ? /* @__PURE__ */ jsx(
74
+ "div",
75
+ {
76
+ className: paneLayout ? "w-full" : "h-[340px] w-full",
77
+ style: paneLayout ? { height: paneLayout.height } : void 0,
78
+ "data-chart-legend": paneLayout?.legend ?? "default",
79
+ "data-chart-bars": paneLayout?.horizontalBars ? "horizontal" : "vertical",
80
+ children: /* @__PURE__ */ jsx(ChartCanvas, { spec, paneLayout })
81
+ }
82
+ ) : null }),
83
+ fullscreen && spec && /* @__PURE__ */ jsx(ChartFullscreen, { spec, title, onClose: () => setFullscreen(false) })
84
+ ]
85
+ }
86
+ );
72
87
  };
73
88
  function IconBtn({ label, onClick, children }) {
74
89
  return /* @__PURE__ */ jsx(Button, { variant: "quiet", "aria-label": label, title: label, onClick, children });
@@ -20,6 +20,7 @@ import IconButton from "../../../utils/lifted/IconButton.js";
20
20
  import { AdvancedMenu } from "../../../host/app-bindings.js";
21
21
  import { useIsMobile } from "@ai-matrx/kit/media-query";
22
22
  import { Button } from "@ai-matrx/design-system/controls";
23
+ import { useBlockHostSlot } from "../../block-host.js";
23
24
  const BlockHeaderWrapper = ({
24
25
  icon: Icon,
25
26
  iconClassName = "bg-blue-500 dark:bg-blue-600",
@@ -42,6 +43,7 @@ const BlockHeaderWrapper = ({
42
43
  canvasButtonClassName = "bg-brand-secondary text-brand-secondary-foreground shadow-sm hover:bg-brand-secondary/80 hover:shadow-md transform hover:scale-105 transition-all"
43
44
  }) => {
44
45
  const [isFullScreen, setIsFullScreen] = useState(false);
46
+ const host = useBlockHostSlot();
45
47
  const [menuOpen, setMenuOpen] = useState(false);
46
48
  const [descriptionExpanded, setDescriptionExpanded] = useState(false);
47
49
  const menuAnchorRef = useRef(null);
@@ -170,7 +172,7 @@ const BlockHeaderWrapper = ({
170
172
  /* @__PURE__ */ jsxs(
171
173
  "div",
172
174
  {
173
- className: `${headerGradient} rounded-xl p-2 shadow-md border ${headerBorder}`,
175
+ className: `${headerGradient} rounded-xl p-2 shadow-md border transition-colors ${host.selected ? "border-primary" : headerBorder}`,
174
176
  children: [
175
177
  /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3 mb-3", children: [
176
178
  /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 flex-1", children: [
@@ -282,7 +284,8 @@ const BlockHeaderWrapper = ({
282
284
  categorizeItems: false
283
285
  }
284
286
  )
285
- ] })
287
+ ] }),
288
+ host.actions
286
289
  ] })
287
290
  ] }),
288
291
  controls && /* @__PURE__ */ jsx("div", { children: controls })
@@ -21,6 +21,7 @@ import IconButton from "../../../utils/lifted/IconButton.js";
21
21
  import { toast } from "../../../host/toast.js";
22
22
  import { reportCanvasOpenDrop } from "../../../host/app-bindings.js";
23
23
  import { ErrorAlchemyMenu } from "../../../host/ErrorActions.js";
24
+ import { useBlockHostSlot } from "../../block-host.js";
24
25
  const ContentBlockWrapper = ({
25
26
  children,
26
27
  title,
@@ -46,6 +47,7 @@ const ContentBlockWrapper = ({
46
47
  closeOnEscape = true
47
48
  }) => {
48
49
  const [isFullScreen, setIsFullScreen] = useState(defaultFullscreen);
50
+ const host = useBlockHostSlot();
49
51
  const { open: openCanvas } = useCanvas();
50
52
  const { openArtifact } = useOpenArtifactInCanvas();
51
53
  const { isCanvasAvailable } = useCanvasOpenGuard();
@@ -161,7 +163,7 @@ const ContentBlockWrapper = ({
161
163
  }
162
164
  ) }),
163
165
  /* @__PURE__ */ jsxs("div", { className: isFullScreen ? "flex-1 overflow-y-auto p-3" : "p-3", children: [
164
- (title || subtitle || headerContent || actionButtons.length > 0) && /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
166
+ (title || subtitle || headerContent || actionButtons.length > 0 || host.actions) && /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
165
167
  (title || subtitle) && /* @__PURE__ */ jsxs("div", { className: "mb-3", children: [
166
168
  title && /* @__PURE__ */ jsx(
167
169
  "h1",
@@ -173,7 +175,7 @@ const ContentBlockWrapper = ({
173
175
  subtitle && /* @__PURE__ */ jsx("div", { className: "text-center mt-2", children: /* @__PURE__ */ jsx("span", { className: "text-sm bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300 px-3 py-1 rounded-full", children: subtitle }) })
174
176
  ] }),
175
177
  headerContent,
176
- actionButtons.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-1 flex-wrap", children: [
178
+ (actionButtons.length > 0 || host.actions) && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-1 flex-wrap", children: [
177
179
  onSave && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 mr-2", children: [
178
180
  isSaving && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 text-blue-600 dark:text-blue-400 text-xs", title: "Saving...", children: /* @__PURE__ */ jsx(Cloud, { className: "h-4 w-4 animate-pulse" }) }),
179
181
  !isSaving && lastSaved && /* @__PURE__ */ jsx(
@@ -199,7 +201,8 @@ const ContentBlockWrapper = ({
199
201
  className: action.className
200
202
  },
201
203
  index
202
- ))
204
+ )),
205
+ host.actions
203
206
  ] })
204
207
  ] }),
205
208
  /* @__PURE__ */ jsx("div", { className: contentClassName, children })
@@ -18,6 +18,7 @@ import {
18
18
  import { toast } from "../../../host/toast.js";
19
19
  import { useOpenSaveToTable } from "../../../host/tables.js";
20
20
  import { Button } from "@ai-matrx/design-system/controls";
21
+ import { useBlockHostSlot } from "../../block-host.js";
21
22
  function parseCsv(content, delimiter) {
22
23
  return parseDelimited(content, { delimiter, skipEmptyLines: "greedy" }).data.map(
23
24
  (row) => row.map((cell) => cell.trim())
@@ -36,6 +37,7 @@ const CsvBlock = ({
36
37
  notify: (message, kind) => kind === "error" ? toast.error(message) : toast.success(message)
37
38
  });
38
39
  const [copied, setCopied] = useState(false);
40
+ const host = useBlockHostSlot();
39
41
  const [sortCol, setSortCol] = useState(null);
40
42
  const [sortDir, setSortDir] = useState(null);
41
43
  const [editCell, setEditCell] = useState(
@@ -116,7 +118,8 @@ const CsvBlock = ({
116
118
  "div",
117
119
  {
118
120
  className: cn(
119
- "my-3 rounded-lg border border-border bg-card overflow-hidden",
121
+ "my-3 rounded-lg border bg-card overflow-hidden transition-colors",
122
+ host.selected ? "border-primary" : "border-border",
120
123
  className
121
124
  ),
122
125
  children: [
@@ -132,7 +135,8 @@ const CsvBlock = ({
132
135
  ] }),
133
136
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
134
137
  openSaveToTable ? /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: /* @__PURE__ */ jsx(Table2, {}), onClick: handleSaveAsTable, title: "Save to a table", children: /* @__PURE__ */ jsx("span", { children: "Save to a table" }) }) : null,
135
- /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}), glyphTone: copied ? "success" : void 0, onClick: handleCopy, "aria-label": "Copy" })
138
+ /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}), glyphTone: copied ? "success" : void 0, onClick: handleCopy, "aria-label": "Copy" }),
139
+ host.actions
136
140
  ] })
137
141
  ] }),
138
142
  /* @__PURE__ */ jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm border-collapse", children: [
@@ -123,6 +123,7 @@ import {
123
123
  DropdownMenuSeparator,
124
124
  DropdownMenuTrigger
125
125
  } from "@ai-matrx/design-system";
126
+ import { useBlockHostSlot } from "../../block-host.js";
126
127
  function backgroundVariantFromDiagram(diagram) {
127
128
  if (diagram.renderHints?.background === "lines")
128
129
  return BackgroundVariant.Lines;
@@ -2056,6 +2057,7 @@ const InteractiveDiagramBlock = ({
2056
2057
  }) => {
2057
2058
  const isWorkspace = presentation === "workspace";
2058
2059
  const [isFullScreen, setIsFullScreen] = useState(false);
2060
+ const host = useBlockHostSlot(!isWorkspace);
2059
2061
  const [editing, setEditing] = useState(defaultEditing);
2060
2062
  const canEdit = Boolean(onDiagramChange);
2061
2063
  const [cardShowMiniMap, setCardShowMiniMap] = useState(false);
@@ -2179,7 +2181,7 @@ const InteractiveDiagramBlock = ({
2179
2181
  // own <Controls/> (Zoom in/out, Fit view) and the hand-rolled
2180
2182
  // Arrange-map/Restore-layout/background/minimap/export buttons
2181
2183
  // below all render at 26-34px, well under the iOS floor.
2182
- isWorkspace ? "relative h-full min-h-0 w-full overflow-hidden bg-background matrx-touch-targets" : `w-full rounded-xl border border-border matrx-touch-targets ${isFullScreen ? "fixed inset-0 z-50 flex items-center justify-center p-2" : "py-2"}`
2184
+ isWorkspace ? "relative h-full min-h-0 w-full overflow-hidden bg-background matrx-touch-targets" : `w-full rounded-xl border transition-colors ${host.selected ? "border-primary" : "border-border"} matrx-touch-targets ${isFullScreen ? "fixed inset-0 z-50 flex items-center justify-center p-2" : "py-2"}`
2183
2185
  ),
2184
2186
  children: /* @__PURE__ */ jsxs(
2185
2187
  "div",
@@ -2299,7 +2301,8 @@ const InteractiveDiagramBlock = ({
2299
2301
  onClick: exportDiagramJSON,
2300
2302
  variant: "outline"
2301
2303
  }
2302
- )
2304
+ ),
2305
+ host.actions
2303
2306
  ] })
2304
2307
  ] }) }),
2305
2308
  isWorkspace && /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute bottom-3 left-1/2 z-20 -translate-x-1/2", children: /* @__PURE__ */ jsxs("div", { className: "pointer-events-auto flex items-center gap-1 rounded-xl border border-border/70 bg-card/90 p-1 shadow-lg backdrop-blur-xl", children: [
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useClipboard } from "@ai-matrx/kit/clipboard";
4
4
  import { useMemo, useState } from "react";
5
5
  import { clientLazy } from "../../../utils/client-lazy.js";
@@ -13,6 +13,7 @@ import { ErrorAlchemyMenu } from "../../../host/ErrorActions.js";
13
13
  import { useCanvasPresentation } from "@ai-matrx/canvas/react";
14
14
  import { diffIsSplit } from "../canvas-adaptive.js";
15
15
  import { Button } from "@ai-matrx/design-system/controls";
16
+ import { useBlockHostSlot } from "../../block-host.js";
16
17
  const DiffCanvas = clientLazy(() => import("./DiffCanvas.js"), {
17
18
  loading: () => /* @__PURE__ */ jsx(Skeleton, { className: "h-40 w-full" })
18
19
  });
@@ -75,6 +76,7 @@ const DiffSpecBlock = ({ content = "", isStreamActive = false, className }) => {
75
76
  const spec = parsed && !("error" in parsed) ? parsed : null;
76
77
  const error = parsed && "error" in parsed ? parsed.error : null;
77
78
  const [copied, setCopied] = useState(false);
79
+ const host = useBlockHostSlot();
78
80
  const [split, setSplit] = useState(null);
79
81
  const presentation = useCanvasPresentation();
80
82
  const effectiveSplit = diffIsSplit({
@@ -88,16 +90,19 @@ const DiffSpecBlock = ({ content = "", isStreamActive = false, className }) => {
88
90
  setCopied(true);
89
91
  setTimeout(() => setCopied(false), 1500);
90
92
  };
91
- return /* @__PURE__ */ jsxs("div", { className: cn("my-3 overflow-hidden rounded-lg border border-border bg-card", className), children: [
93
+ return /* @__PURE__ */ jsxs("div", { className: cn("my-3 overflow-hidden rounded-lg border bg-card transition-colors", host.selected ? "border-primary" : "border-border", className), children: [
92
94
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 border-b border-border bg-muted/50 px-3 py-1.5", children: [
93
95
  /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
94
96
  /* @__PURE__ */ jsx(GitCompareArrows, { className: "h-3.5 w-3.5 shrink-0 text-primary" }),
95
97
  /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-medium text-foreground", children: spec?.title ?? "Changes" }),
96
98
  isStreamActive && /* @__PURE__ */ jsx("span", { className: "shrink-0 animate-pulse text-xs text-muted-foreground", children: "\u2026" })
97
99
  ] }),
98
- !isStreamActive && spec && /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [
99
- /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: effectiveSplit ? /* @__PURE__ */ jsx(Rows3, {}) : /* @__PURE__ */ jsx(Columns2, {}), "aria-label": effectiveSplit ? "Unified view" : "Split view", title: effectiveSplit ? "Unified view" : "Split view", onClick: () => setSplit(!effectiveSplit) }),
100
- /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}), "aria-label": copied ? "Copied" : "Copy source", title: copied ? "Copied" : "Copy source", onClick: handleCopy })
100
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [
101
+ !isStreamActive && spec && /* @__PURE__ */ jsxs(Fragment, { children: [
102
+ /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: effectiveSplit ? /* @__PURE__ */ jsx(Rows3, {}) : /* @__PURE__ */ jsx(Columns2, {}), "aria-label": effectiveSplit ? "Unified view" : "Split view", title: effectiveSplit ? "Unified view" : "Split view", onClick: () => setSplit(!effectiveSplit) }),
103
+ /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}), "aria-label": copied ? "Copied" : "Copy source", title: copied ? "Copied" : "Copy source", onClick: handleCopy })
104
+ ] }),
105
+ host.actions
101
106
  ] })
102
107
  ] }),
103
108
  /* @__PURE__ */ jsx("div", { className: "max-h-[60dvh] overflow-auto", children: isStreamActive ? /* @__PURE__ */ jsx("div", { className: "p-3", children: /* @__PURE__ */ jsx(Skeleton, { className: "h-40 w-full" }) }) : error ? /* @__PURE__ */ jsxs("div", { className: "m-3 flex items-start gap-2 rounded-md border border-destructive/30 bg-destructive/5 p-3 text-sm", children: [
@@ -58,6 +58,7 @@ const JsonTableView = lazy(
58
58
  () => import("./JsonTableView.js")
59
59
  );
60
60
  import { JsonTreeViewer, RawJsonExplorer } from "../../../host/app-bindings.js";
61
+ import { useBlockHostSlot } from "../../block-host.js";
61
62
  function PaneFallback({ label = "Loading\u2026" }) {
62
63
  return /* @__PURE__ */ jsx("div", { className: "p-4 text-xs text-muted-foreground italic", children: label });
63
64
  }
@@ -85,6 +86,7 @@ const JsonBlock = ({
85
86
  const openConvertToShape = useOpenConvertToShapeWindow();
86
87
  const routedAbove = useContext(JsonKindRouteContext);
87
88
  const [mode, setMode] = useState("code");
89
+ const host = useBlockHostSlot(mode !== "code");
88
90
  const openSaveToTable = useOpenSaveToTable();
89
91
  const [formatOverride, setFormatOverride] = useState(null);
90
92
  const effectiveContent = formatOverride ?? content;
@@ -369,7 +371,8 @@ const JsonBlock = ({
369
371
  "div",
370
372
  {
371
373
  className: cn(
372
- "w-full my-4 rounded-xl overflow-hidden border border-neutral-200 dark:border-neutral-700 bg-background",
374
+ "w-full my-4 rounded-xl overflow-hidden border bg-background transition-colors",
375
+ host.selected ? "border-primary" : "border-neutral-200 dark:border-neutral-700",
373
376
  className
374
377
  ),
375
378
  children: [
@@ -379,7 +382,8 @@ const JsonBlock = ({
379
382
  toggle: viewToggle,
380
383
  menuItems: fullMenuItems,
381
384
  caption: mode === "table" ? tabularCaption || "" : "",
382
- content: effectiveContent
385
+ content: effectiveContent,
386
+ hostActions: host.actions
383
387
  }
384
388
  ),
385
389
  /* @__PURE__ */ jsxs("div", { className: "bg-card", children: [
@@ -479,7 +483,8 @@ const JsonViewHeader = ({
479
483
  toggle,
480
484
  menuItems,
481
485
  caption,
482
- content
486
+ content,
487
+ hostActions
483
488
  }) => {
484
489
  const { copyText } = useClipboard({
485
490
  notify: (message, kind) => kind === "error" ? toast.error(message) : toast.success(message)
@@ -546,7 +551,8 @@ const JsonViewHeader = ({
546
551
  width: "260px",
547
552
  maxWidth: "300px"
548
553
  }
549
- )
554
+ ),
555
+ hostActions
550
556
  ] })
551
557
  ] });
552
558
  };
@@ -8,6 +8,7 @@ import { Copy, Check, Eye, Code2, FileText } from "lucide-react";
8
8
  import { NestedRichContent } from "../../../levels/standard/NestedRichContent.js";
9
9
  import MatrxMiniLoader from "../../../utils/lifted/MatrxMiniLoader.js";
10
10
  import { Button } from "@ai-matrx/design-system/controls";
11
+ import { useBlockHostSlot } from "../../block-host.js";
11
12
  const CodeBlock = lazy(
12
13
  () => import("../../../code-block/CodeBlock.js")
13
14
  );
@@ -23,6 +24,7 @@ const MarkdownPreviewBlock = ({
23
24
  });
24
25
  const [mode, setMode] = useState("preview");
25
26
  const [copied, setCopied] = useState(false);
27
+ const host = useBlockHostSlot();
26
28
  const handleCopy = async () => {
27
29
  if (!await copyText(content)) return;
28
30
  setCopied(true);
@@ -32,7 +34,8 @@ const MarkdownPreviewBlock = ({
32
34
  "div",
33
35
  {
34
36
  className: cn(
35
- "my-3 rounded-lg border border-border bg-card overflow-hidden",
37
+ "my-3 rounded-lg border bg-card overflow-hidden transition-colors",
38
+ host.selected ? "border-primary" : "border-border",
36
39
  className
37
40
  ),
38
41
  children: [
@@ -46,7 +49,8 @@ const MarkdownPreviewBlock = ({
46
49
  /* @__PURE__ */ jsx(Button, { variant: "quiet", pressed: mode === "preview", icon: /* @__PURE__ */ jsx(Eye, {}), onClick: () => setMode("preview"), children: "Preview" }),
47
50
  /* @__PURE__ */ jsx(Button, { variant: "quiet", pressed: mode === "source", icon: /* @__PURE__ */ jsx(Code2, {}), onClick: () => setMode("source"), children: "Source" })
48
51
  ] }),
49
- /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}), glyphTone: copied ? "success" : void 0, onClick: handleCopy, "aria-label": "Copy" })
52
+ /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}), glyphTone: copied ? "success" : void 0, onClick: handleCopy, "aria-label": "Copy" }),
53
+ host.actions
50
54
  ] })
51
55
  ] }),
52
56
  mode === "preview" ? /* @__PURE__ */ jsx("div", { className: "px-4 py-3", children: renderedPreview && !isStreamActive ? renderedPreview : /* @__PURE__ */ jsx(NestedRichContent, { source: content, isStreaming: isStreamActive }) }) : /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(MatrxMiniLoader, {}), children: /* @__PURE__ */ jsx(
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { Button } from "@ai-matrx/design-system/controls";
4
4
  import { Suspense, lazy, useEffect, useState } from "react";
5
5
  import {
@@ -54,6 +54,7 @@ import {
54
54
  resolveMermaidTheme,
55
55
  DEFAULT_MERMAID_PREFERENCES
56
56
  } from "../../../mermaid/types.js";
57
+ import { useBlockHostSlot } from "../../block-host.js";
57
58
  const CodeBlock = lazy(
58
59
  () => import("../../../code-block/CodeBlock.js")
59
60
  );
@@ -100,6 +101,7 @@ const MermaidBlock = ({
100
101
  };
101
102
  const [showSource, setShowSource] = useState(false);
102
103
  const [copied, setCopied] = useState(false);
104
+ const host = useBlockHostSlot();
103
105
  const [fullscreen, setFullscreen] = useState(false);
104
106
  const [svgEl, setSvgEl] = useState(null);
105
107
  const [frameMaxHeight, setFrameMaxHeight] = useState(520);
@@ -221,7 +223,8 @@ const MermaidBlock = ({
221
223
  "div",
222
224
  {
223
225
  className: cn(
224
- "my-3 overflow-hidden rounded-lg border border-border bg-card",
226
+ "my-3 overflow-hidden rounded-lg border bg-card transition-colors",
227
+ host.selected ? "border-primary" : "border-border",
225
228
  className
226
229
  ),
227
230
  children: [
@@ -236,122 +239,125 @@ const MermaidBlock = ({
236
239
  ] }) : null,
237
240
  isStreamActive && /* @__PURE__ */ jsx("span", { className: "shrink-0 animate-pulse text-xs text-muted-foreground", children: "drawing\u2026" })
238
241
  ] }),
239
- !isStreamActive && /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [
240
- /* @__PURE__ */ jsxs(DropdownMenu, { children: [
241
- /* @__PURE__ */ jsx(SimpleTooltip, { text: "Diagram style", children: /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: /* @__PURE__ */ jsx(Palette, {}), "aria-label": "Diagram style" }) }) }),
242
- /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", className: "w-44", children: [
243
- /* @__PURE__ */ jsx(DropdownMenuLabel, { className: "text-xs", children: "Theme" }),
244
- THEME_CHOICES.map((theme) => /* @__PURE__ */ jsxs(
245
- DropdownMenuItem,
246
- {
247
- onClick: () => setOption("theme", theme),
248
- children: [
249
- /* @__PURE__ */ jsx("span", { className: "flex-1 capitalize", children: theme }),
250
- effective.theme === theme && /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" })
251
- ]
252
- },
253
- theme
254
- )),
255
- /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
256
- /* @__PURE__ */ jsx(DropdownMenuLabel, { className: "text-xs", children: "Style" }),
257
- LOOK_CHOICES.map((look) => /* @__PURE__ */ jsxs(
258
- DropdownMenuItem,
259
- {
260
- onClick: () => setOption("look", look),
261
- children: [
262
- /* @__PURE__ */ jsx("span", { className: "flex-1", children: look === "handDrawn" ? "Hand-drawn" : "Classic" }),
263
- effective.look === look && /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" })
264
- ]
265
- },
266
- look
267
- )),
268
- /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
269
- /* @__PURE__ */ jsx(DropdownMenuLabel, { className: "text-xs", children: "Layout" }),
270
- LAYOUT_CHOICES.map((layout) => /* @__PURE__ */ jsxs(
271
- DropdownMenuItem,
272
- {
273
- onClick: () => setOption("layout", layout),
274
- children: [
275
- /* @__PURE__ */ jsx("span", { className: "flex-1 uppercase", children: layout }),
276
- effective.layout === layout && /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" })
277
- ]
278
- },
279
- layout
280
- )),
281
- /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
282
- /* @__PURE__ */ jsx(
283
- DropdownMenuItem,
284
- {
285
- onClick: () => {
286
- setMermaidPreferences(effective);
287
- toast.success("Saved as your default diagram style");
242
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [
243
+ !isStreamActive && /* @__PURE__ */ jsxs(Fragment, { children: [
244
+ /* @__PURE__ */ jsxs(DropdownMenu, { children: [
245
+ /* @__PURE__ */ jsx(SimpleTooltip, { text: "Diagram style", children: /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: /* @__PURE__ */ jsx(Palette, {}), "aria-label": "Diagram style" }) }) }),
246
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", className: "w-44", children: [
247
+ /* @__PURE__ */ jsx(DropdownMenuLabel, { className: "text-xs", children: "Theme" }),
248
+ THEME_CHOICES.map((theme) => /* @__PURE__ */ jsxs(
249
+ DropdownMenuItem,
250
+ {
251
+ onClick: () => setOption("theme", theme),
252
+ children: [
253
+ /* @__PURE__ */ jsx("span", { className: "flex-1 capitalize", children: theme }),
254
+ effective.theme === theme && /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" })
255
+ ]
288
256
  },
289
- children: "Save as my default"
290
- }
291
- )
292
- ] })
293
- ] }),
294
- /* @__PURE__ */ jsxs(DropdownMenu, { children: [
295
- /* @__PURE__ */ jsx(SimpleTooltip, { text: "Export diagram", children: /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: /* @__PURE__ */ jsx(Download, {}), "aria-label": "Export diagram" }) }) }),
296
- /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", className: "w-48", children: [
297
- /* @__PURE__ */ jsx(
298
- DropdownMenuItem,
299
- {
300
- onClick: () => {
301
- const svg = currentSvg();
302
- if (svg) downloadMermaidSvg(svg, title);
303
- else toast.error("Nothing rendered yet");
257
+ theme
258
+ )),
259
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
260
+ /* @__PURE__ */ jsx(DropdownMenuLabel, { className: "text-xs", children: "Style" }),
261
+ LOOK_CHOICES.map((look) => /* @__PURE__ */ jsxs(
262
+ DropdownMenuItem,
263
+ {
264
+ onClick: () => setOption("look", look),
265
+ children: [
266
+ /* @__PURE__ */ jsx("span", { className: "flex-1", children: look === "handDrawn" ? "Hand-drawn" : "Classic" }),
267
+ effective.look === look && /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" })
268
+ ]
304
269
  },
305
- children: "Download SVG"
306
- }
307
- ),
308
- /* @__PURE__ */ jsx(
309
- DropdownMenuItem,
310
- {
311
- onClick: async () => {
312
- const svg = currentSvg();
313
- if (!svg) {
314
- toast.error("Nothing rendered yet");
315
- return;
316
- }
317
- try {
318
- await downloadMermaidPng(svg, title);
319
- } catch (err) {
320
- console.error("[MermaidBlock] PNG export failed", err);
321
- toast.error("PNG export failed");
322
- }
270
+ look
271
+ )),
272
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
273
+ /* @__PURE__ */ jsx(DropdownMenuLabel, { className: "text-xs", children: "Layout" }),
274
+ LAYOUT_CHOICES.map((layout) => /* @__PURE__ */ jsxs(
275
+ DropdownMenuItem,
276
+ {
277
+ onClick: () => setOption("layout", layout),
278
+ children: [
279
+ /* @__PURE__ */ jsx("span", { className: "flex-1 uppercase", children: layout }),
280
+ effective.layout === layout && /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" })
281
+ ]
323
282
  },
324
- children: "Download PNG"
325
- }
326
- ),
327
- /* @__PURE__ */ jsx(
328
- DropdownMenuItem,
329
- {
330
- onClick: () => downloadMermaidSource(source, title),
331
- children: "Download source (.mmd)"
332
- }
333
- ),
334
- /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
335
- /* @__PURE__ */ jsxs(DropdownMenuItem, { onClick: handleSaveToWorkspace, children: [
336
- /* @__PURE__ */ jsx(FolderUp, { className: "mr-1.5 h-3.5 w-3.5" }),
337
- "Save to my files"
283
+ layout
284
+ )),
285
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
286
+ /* @__PURE__ */ jsx(
287
+ DropdownMenuItem,
288
+ {
289
+ onClick: () => {
290
+ setMermaidPreferences(effective);
291
+ toast.success("Saved as your default diagram style");
292
+ },
293
+ children: "Save as my default"
294
+ }
295
+ )
296
+ ] })
297
+ ] }),
298
+ /* @__PURE__ */ jsxs(DropdownMenu, { children: [
299
+ /* @__PURE__ */ jsx(SimpleTooltip, { text: "Export diagram", children: /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: /* @__PURE__ */ jsx(Download, {}), "aria-label": "Export diagram" }) }) }),
300
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", className: "w-48", children: [
301
+ /* @__PURE__ */ jsx(
302
+ DropdownMenuItem,
303
+ {
304
+ onClick: () => {
305
+ const svg = currentSvg();
306
+ if (svg) downloadMermaidSvg(svg, title);
307
+ else toast.error("Nothing rendered yet");
308
+ },
309
+ children: "Download SVG"
310
+ }
311
+ ),
312
+ /* @__PURE__ */ jsx(
313
+ DropdownMenuItem,
314
+ {
315
+ onClick: async () => {
316
+ const svg = currentSvg();
317
+ if (!svg) {
318
+ toast.error("Nothing rendered yet");
319
+ return;
320
+ }
321
+ try {
322
+ await downloadMermaidPng(svg, title);
323
+ } catch (err) {
324
+ console.error("[MermaidBlock] PNG export failed", err);
325
+ toast.error("PNG export failed");
326
+ }
327
+ },
328
+ children: "Download PNG"
329
+ }
330
+ ),
331
+ /* @__PURE__ */ jsx(
332
+ DropdownMenuItem,
333
+ {
334
+ onClick: () => downloadMermaidSource(source, title),
335
+ children: "Download source (.mmd)"
336
+ }
337
+ ),
338
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
339
+ /* @__PURE__ */ jsxs(DropdownMenuItem, { onClick: handleSaveToWorkspace, children: [
340
+ /* @__PURE__ */ jsx(FolderUp, { className: "mr-1.5 h-3.5 w-3.5" }),
341
+ "Save to my files"
342
+ ] })
338
343
  ] })
339
- ] })
344
+ ] }),
345
+ /* @__PURE__ */ jsx(SimpleTooltip, { text: copied ? "Copied" : "Copy diagram source", children: /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}), "aria-label": "Copy diagram source", onClick: handleCopy }) }),
346
+ /* @__PURE__ */ jsx(SimpleTooltip, { text: showSource ? "Hide source" : "Show source", children: /* @__PURE__ */ jsx(Button, { variant: "quiet", pressed: showSource, icon: /* @__PURE__ */ jsx(Code2, {}), "aria-label": showSource ? "Hide source" : "Show source", onClick: () => setShowSource((v) => !v) }) }),
347
+ /* @__PURE__ */ jsx(SimpleTooltip, { text: "View fullscreen", children: /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: /* @__PURE__ */ jsx(Expand, {}), "aria-label": "View fullscreen", onClick: () => setFullscreen(true) }) }),
348
+ isCanvasAvailable && /* @__PURE__ */ jsx(SimpleTooltip, { text: "Open in canvas", children: /* @__PURE__ */ jsx(
349
+ Button,
350
+ {
351
+ type: "button",
352
+ "aria-label": "Open in canvas",
353
+ onClick: () => handleOpenCanvas(),
354
+ variant: "quiet",
355
+ icon: /* @__PURE__ */ jsx(Maximize2, {}),
356
+ children: "Canvas"
357
+ }
358
+ ) })
340
359
  ] }),
341
- /* @__PURE__ */ jsx(SimpleTooltip, { text: copied ? "Copied" : "Copy diagram source", children: /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}), "aria-label": "Copy diagram source", onClick: handleCopy }) }),
342
- /* @__PURE__ */ jsx(SimpleTooltip, { text: showSource ? "Hide source" : "Show source", children: /* @__PURE__ */ jsx(Button, { variant: "quiet", pressed: showSource, icon: /* @__PURE__ */ jsx(Code2, {}), "aria-label": showSource ? "Hide source" : "Show source", onClick: () => setShowSource((v) => !v) }) }),
343
- /* @__PURE__ */ jsx(SimpleTooltip, { text: "View fullscreen", children: /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: /* @__PURE__ */ jsx(Expand, {}), "aria-label": "View fullscreen", onClick: () => setFullscreen(true) }) }),
344
- isCanvasAvailable && /* @__PURE__ */ jsx(SimpleTooltip, { text: "Open in canvas", children: /* @__PURE__ */ jsx(
345
- Button,
346
- {
347
- type: "button",
348
- "aria-label": "Open in canvas",
349
- onClick: () => handleOpenCanvas(),
350
- variant: "quiet",
351
- icon: /* @__PURE__ */ jsx(Maximize2, {}),
352
- children: "Canvas"
353
- }
354
- ) })
360
+ host.actions
355
361
  ] })
356
362
  ] }),
357
363
  /* @__PURE__ */ jsx(
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { downloadFile } from "@ai-matrx/kit/download";
4
4
  import { useClipboard } from "@ai-matrx/kit/clipboard";
5
5
  import { useEffect, useMemo, useState } from "react";
@@ -10,6 +10,7 @@ import { Skeleton } from "@ai-matrx/design-system";
10
10
  import SandboxedHtml from "../common/SandboxedHtml.js";
11
11
  import { cn } from "@ai-matrx/design-system";
12
12
  import { Button } from "@ai-matrx/design-system/controls";
13
+ import { useBlockHostSlot } from "../../block-host.js";
13
14
  function readSvgMeta(svg) {
14
15
  let aspect = 16 / 9;
15
16
  const viewBox = /viewBox\s*=\s*["']\s*[\d.+-]+\s+[\d.+-]+\s+([\d.]+)\s+([\d.]+)\s*["']/i.exec(svg);
@@ -38,6 +39,7 @@ const SvgBlock = ({ content = "", isStreamActive = false, className }) => {
38
39
  const { aspect, title } = useMemo(() => readSvgMeta(svg), [svg]);
39
40
  const srcDoc = useMemo(() => toSrcDoc(svg), [svg]);
40
41
  const [copied, setCopied] = useState(false);
42
+ const host = useBlockHostSlot();
41
43
  const [fullscreen, setFullscreen] = useState(false);
42
44
  const handleCopy = async () => {
43
45
  if (!await copyText(svg, void 0, "Couldn't copy the SVG")) return;
@@ -52,7 +54,7 @@ const SvgBlock = ({ content = "", isStreamActive = false, className }) => {
52
54
  }
53
55
  };
54
56
  const hasSvg = /<svg[\s>]/i.test(svg);
55
- return /* @__PURE__ */ jsxs("div", { className: cn("my-3 overflow-hidden rounded-lg border border-border bg-card", className), children: [
57
+ return /* @__PURE__ */ jsxs("div", { className: cn("my-3 overflow-hidden rounded-lg border bg-card transition-colors", host.selected ? "border-primary" : "border-border", className), children: [
56
58
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 border-b border-border bg-muted/50 px-3 py-1.5", children: [
57
59
  /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
58
60
  /* @__PURE__ */ jsx(ImageIcon, { className: "h-3.5 w-3.5 shrink-0 text-primary" }),
@@ -60,10 +62,13 @@ const SvgBlock = ({ content = "", isStreamActive = false, className }) => {
60
62
  /* @__PURE__ */ jsx("span", { className: "hidden shrink-0 text-xs text-muted-foreground sm:inline", children: "SVG" }),
61
63
  isStreamActive && /* @__PURE__ */ jsx("span", { className: "shrink-0 animate-pulse text-xs text-muted-foreground", children: "drawing\u2026" })
62
64
  ] }),
63
- !isStreamActive && hasSvg && /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [
64
- /* @__PURE__ */ jsx(IconBtn, { label: copied ? "Copied" : "Copy SVG source", onClick: handleCopy, children: copied ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" }) }),
65
- /* @__PURE__ */ jsx(IconBtn, { label: "Download SVG", onClick: handleDownload, children: /* @__PURE__ */ jsx(Download, { className: "h-3.5 w-3.5" }) }),
66
- /* @__PURE__ */ jsx(IconBtn, { label: "View fullscreen", onClick: () => setFullscreen(true), children: /* @__PURE__ */ jsx(Expand, { className: "h-3.5 w-3.5" }) })
65
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [
66
+ !isStreamActive && hasSvg && /* @__PURE__ */ jsxs(Fragment, { children: [
67
+ /* @__PURE__ */ jsx(IconBtn, { label: copied ? "Copied" : "Copy SVG source", onClick: handleCopy, children: copied ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" }) }),
68
+ /* @__PURE__ */ jsx(IconBtn, { label: "Download SVG", onClick: handleDownload, children: /* @__PURE__ */ jsx(Download, { className: "h-3.5 w-3.5" }) }),
69
+ /* @__PURE__ */ jsx(IconBtn, { label: "View fullscreen", onClick: () => setFullscreen(true), children: /* @__PURE__ */ jsx(Expand, { className: "h-3.5 w-3.5" }) })
70
+ ] }),
71
+ host.actions
67
72
  ] })
68
73
  ] }),
69
74
  /* @__PURE__ */ jsx("div", { className: "p-3", children: isStreamActive || !hasSvg ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", "aria-busy": "true", children: [
@@ -6,6 +6,7 @@ import { useState } from "react";
6
6
  import { cn } from "@ai-matrx/design-system";
7
7
  import { Copy, Check, ChevronRight, ChevronDown } from "lucide-react";
8
8
  import { Button } from "@ai-matrx/design-system/controls";
9
+ import { useBlockHostSlot } from "../../block-host.js";
9
10
  function parseTomlLines(content) {
10
11
  return content.split("\n").map((raw) => {
11
12
  const trimmed = raw.trim();
@@ -70,6 +71,7 @@ const TomlBlock = ({ content, className }) => {
70
71
  notify: (message, kind) => kind === "error" ? toast.error(message) : toast.success(message)
71
72
  });
72
73
  const [copied, setCopied] = useState(false);
74
+ const host = useBlockHostSlot();
73
75
  const [collapsedSections, setCollapsedSections] = useState(
74
76
  /* @__PURE__ */ new Set()
75
77
  );
@@ -105,13 +107,17 @@ const TomlBlock = ({ content, className }) => {
105
107
  "div",
106
108
  {
107
109
  className: cn(
108
- "my-3 rounded-lg border border-border bg-card overflow-hidden",
110
+ "my-3 rounded-lg border bg-card overflow-hidden transition-colors",
111
+ host.selected ? "border-primary" : "border-border",
109
112
  className
110
113
  ),
111
114
  children: [
112
115
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-3 py-1.5 border-b border-border/50 bg-muted/30", children: [
113
116
  /* @__PURE__ */ jsx("span", { className: "text-xs font-mono font-semibold text-violet-600 dark:text-violet-400", children: "TOML" }),
114
- /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}), glyphTone: copied ? "success" : void 0, onClick: handleCopy, "aria-label": "Copy" })
117
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [
118
+ /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}), glyphTone: copied ? "success" : void 0, onClick: handleCopy, "aria-label": "Copy" }),
119
+ host.actions
120
+ ] })
115
121
  ] }),
116
122
  /* @__PURE__ */ jsx("div", { className: "px-3 py-2 font-mono text-sm leading-relaxed overflow-x-auto", children: lines.map((line, idx) => {
117
123
  if (hiddenLines.has(idx)) return null;
@@ -8,6 +8,7 @@ import { Copy, Check } from "lucide-react";
8
8
  import { useCanvasPresentation } from "@ai-matrx/canvas/react";
9
9
  import { treeWrapsLines } from "../canvas-adaptive.js";
10
10
  import { Button } from "@ai-matrx/design-system/controls";
11
+ import { useBlockHostSlot } from "../../block-host.js";
11
12
  const CONNECTORS = [
12
13
  "\u251C\u2500",
13
14
  "\u2514\u2500",
@@ -91,6 +92,7 @@ const TreeBlock = ({ content, className }) => {
91
92
  notify: (message, kind) => kind === "error" ? toast.error(message) : toast.success(message)
92
93
  });
93
94
  const [copied, setCopied] = useState(false);
95
+ const host = useBlockHostSlot();
94
96
  const lines = parseTreeLines(content);
95
97
  const wrap = treeWrapsLines(useCanvasPresentation());
96
98
  const handleCopy = async () => {
@@ -102,13 +104,17 @@ const TreeBlock = ({ content, className }) => {
102
104
  "div",
103
105
  {
104
106
  className: cn(
105
- "my-3 rounded-lg border border-border bg-card overflow-hidden",
107
+ "my-3 rounded-lg border bg-card overflow-hidden transition-colors",
108
+ host.selected ? "border-primary" : "border-border",
106
109
  className
107
110
  ),
108
111
  children: [
109
112
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-3 py-1.5 border-b border-border/50 bg-muted/30", children: [
110
113
  /* @__PURE__ */ jsx("span", { className: "text-xs font-mono font-semibold text-teal-600 dark:text-teal-400", children: "Tree" }),
111
- /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}), glyphTone: copied ? "success" : void 0, onClick: handleCopy, "aria-label": "Copy" })
114
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [
115
+ /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}), glyphTone: copied ? "success" : void 0, onClick: handleCopy, "aria-label": "Copy" }),
116
+ host.actions
117
+ ] })
112
118
  ] }),
113
119
  /* @__PURE__ */ jsx(
114
120
  "div",
@@ -10,6 +10,7 @@ import { cn } from "@ai-matrx/design-system";
10
10
  import { tokenizeXml } from "./xml-tokenize.js";
11
11
  import { useMarkdownStreaming } from "../../../markdown-core/streaming-context.js";
12
12
  import { Button } from "@ai-matrx/design-system/controls";
13
+ import { useBlockHostSlot } from "../../block-host.js";
13
14
  function renderAttributes(attrs) {
14
15
  return attrs.map((attr, i) => /* @__PURE__ */ jsxs("span", { children: [
15
16
  " ",
@@ -33,6 +34,7 @@ const XmlBlock = ({
33
34
  notify: (message, kind) => kind === "error" ? toast.error(message) : toast.success(message)
34
35
  });
35
36
  const [copied, setCopied] = useState(false);
37
+ const host = useBlockHostSlot();
36
38
  const [collapsed, setCollapsed] = useState(/* @__PURE__ */ new Set());
37
39
  const [cardCollapsed, setCardCollapsed] = useState(false);
38
40
  const tokens = tokenizeXml(content);
@@ -94,7 +96,8 @@ const XmlBlock = ({
94
96
  "div",
95
97
  {
96
98
  className: cn(
97
- "my-3 rounded-lg border border-border bg-card overflow-hidden",
99
+ "my-3 rounded-lg border bg-card overflow-hidden transition-colors",
100
+ host.selected ? "border-primary" : "border-border",
98
101
  className
99
102
  ),
100
103
  children: [
@@ -110,7 +113,8 @@ const XmlBlock = ({
110
113
  }
111
114
  ) : null
112
115
  ] }),
113
- /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}), glyphTone: copied ? "success" : void 0, "aria-label": "Copy XML", onClick: handleCopy, className: "size-11 lg:size-auto" })
116
+ /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}), glyphTone: copied ? "success" : void 0, "aria-label": "Copy XML", onClick: handleCopy, className: "size-11 lg:size-auto" }),
117
+ host.actions
114
118
  ] }),
115
119
  !cardCollapsed && /* @__PURE__ */ jsx(
116
120
  "div",
@@ -6,6 +6,7 @@ import { useState } from "react";
6
6
  import { cn } from "@ai-matrx/design-system";
7
7
  import { ChevronRight, ChevronDown, Copy, Check } from "lucide-react";
8
8
  import { Button } from "@ai-matrx/design-system/controls";
9
+ import { useBlockHostSlot } from "../../block-host.js";
9
10
  function parseYamlLines(content) {
10
11
  return content.split("\n").map((raw) => {
11
12
  const indent = raw.search(/\S|$/);
@@ -78,6 +79,7 @@ const YamlBlock = ({ content, className }) => {
78
79
  notify: (message, kind) => kind === "error" ? toast.error(message) : toast.success(message)
79
80
  });
80
81
  const [copied, setCopied] = useState(false);
82
+ const host = useBlockHostSlot();
81
83
  const [collapsed, setCollapsed] = useState(/* @__PURE__ */ new Set());
82
84
  const lines = parseYamlLines(content);
83
85
  const handleCopy = async () => {
@@ -121,13 +123,17 @@ const YamlBlock = ({ content, className }) => {
121
123
  "div",
122
124
  {
123
125
  className: cn(
124
- "my-3 rounded-lg border border-border bg-card overflow-hidden",
126
+ "my-3 rounded-lg border bg-card overflow-hidden transition-colors",
127
+ host.selected ? "border-primary" : "border-border",
125
128
  className
126
129
  ),
127
130
  children: [
128
131
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-3 py-1.5 border-b border-border/50 bg-muted/30", children: [
129
132
  /* @__PURE__ */ jsx("span", { className: "text-xs font-mono font-semibold text-amber-600 dark:text-amber-400", children: "YAML" }),
130
- /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}), glyphTone: copied ? "success" : void 0, onClick: handleCopy, "aria-label": "Copy" })
133
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [
134
+ /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}), glyphTone: copied ? "success" : void 0, onClick: handleCopy, "aria-label": "Copy" }),
135
+ host.actions
136
+ ] })
131
137
  ] }),
132
138
  /* @__PURE__ */ jsx("div", { className: "px-3 py-2 font-mono text-sm leading-relaxed overflow-x-auto", children: visibleLines.map((idx) => {
133
139
  const line = lines[idx];
@@ -1,7 +1,6 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  import { useAlchemyDisclosure } from "../../../host/app-bindings.js";
4
- import * as React from "react";
5
4
  import { ContentTransferMenu } from "@ai-matrx/alchemy/react/workspace";
6
5
  import { unwrapKindEnvelopes } from "@ai-matrx/content-ir/source";
7
6
  import { directSource } from "@ai-matrx/alchemy/operate";
@@ -15,7 +14,6 @@ function AlchemyDocumentMenu({
15
14
  richCopy
16
15
  }) {
17
16
  useAlchemyDisclosure();
18
- const [hasThinking, setHasThinking] = React.useState(false);
19
17
  const source = {
20
18
  id: sourceId,
21
19
  label: "Document",
@@ -29,7 +27,6 @@ function AlchemyDocumentMenu({
29
27
  "The document changed. Reopen Alchemy Menu for the current document."
30
28
  );
31
29
  const unthought = removeThinkingContent(ctx.content);
32
- setHasThinking(unthought !== ctx.content);
33
30
  const text = unwrapKindEnvelopes(unthought);
34
31
  return directSource(
35
32
  { kind: "markdown", text },
@@ -42,28 +39,6 @@ function AlchemyDocumentMenu({
42
39
  );
43
40
  }
44
41
  };
45
- const includingThinkingSource = {
46
- id: `${sourceId}:including-thinking`,
47
- label: "Document",
48
- capture: async ({ signal }) => {
49
- signal.throwIfAborted();
50
- const ctx = getCtx();
51
- const capturedSourceId = ctx.instanceKey("alchemy");
52
- if (capturedSourceId !== sourceId)
53
- throw new Error(
54
- "The document changed. Reopen Alchemy Menu for the current document."
55
- );
56
- return directSource(
57
- { kind: "markdown", text: ctx.content },
58
- {
59
- id: `${capturedSourceId}:including-thinking`,
60
- sourceId: capturedSourceId,
61
- revision: ctx.content,
62
- label: "Document"
63
- }
64
- );
65
- }
66
- };
67
42
  return /* @__PURE__ */ jsx(
68
43
  ContentTransferMenu,
69
44
  {
@@ -80,17 +55,7 @@ function AlchemyDocumentMenu({
80
55
  // The split button carries the three ways to copy (COPY_LABELS); the palette keeps the rest.
81
56
  richCopy: (flavor) => richCopy(flavor === "default" ? "markdown" : flavor),
82
57
  richCopyFlavors: []
83
- } : {},
84
- variants: hasThinking ? [
85
- {
86
- id: "include-thinking",
87
- label: "Raw with thinking",
88
- copyLabel: "Raw with thinking",
89
- section: "copy",
90
- hint: "Includes the original reasoning content",
91
- source: includingThinkingSource
92
- }
93
- ] : []
58
+ } : {}
94
59
  },
95
60
  sourceId
96
61
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-matrx/rich-content",
3
- "version": "0.2.165",
3
+ "version": "0.2.167",
4
4
  "description": "The AI Matrx formatted-text engine: the one markdown renderer, streaming markdown, the block splitter and renderer, the generic blocks, <RichContent> levels, the rich-document action registry and menus, copy, and the selection toolbar — as portable React.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -131,20 +131,20 @@
131
131
  "unist-util-visit-parents": "^6.0.2",
132
132
  "yaml": "^2.9.1",
133
133
  "@ai-matrx/agents": "latest",
134
+ "@ai-matrx/alchemy": "latest",
135
+ "@ai-matrx/api": "latest",
134
136
  "@ai-matrx/content-ir": "latest",
135
137
  "@ai-matrx/content-ir-react": "latest",
138
+ "@ai-matrx/canvas": "latest",
136
139
  "@ai-matrx/data": "latest",
137
- "@ai-matrx/alchemy": "latest",
138
140
  "@ai-matrx/diff": "latest",
139
- "@ai-matrx/design-system": "latest",
141
+ "@ai-matrx/kit": "latest",
142
+ "@ai-matrx/media": "latest",
140
143
  "@ai-matrx/print": "latest",
141
- "@ai-matrx/api": "latest",
142
144
  "@ai-matrx/records": "latest",
143
145
  "@ai-matrx/records-ui": "latest",
144
146
  "@ai-matrx/icons": "latest",
145
- "@ai-matrx/canvas": "latest",
146
- "@ai-matrx/media": "latest",
147
- "@ai-matrx/kit": "latest"
147
+ "@ai-matrx/design-system": "latest"
148
148
  },
149
149
  "peerDependencies": {
150
150
  "react": ">=19.0.0",
@@ -205,5 +205,5 @@
205
205
  "verify:modules-tarball": "node ./scripts/verify-modules-tarball.mjs",
206
206
  "verify:tarball": "node ./scripts/verify-tarball.mjs"
207
207
  },
208
- "gitHead": "6a0868df3e9f82a0f02d077c67e206a003dc3a0e"
208
+ "gitHead": "ebc4f61cd17a04b88022d1a42f79b586e89c30a1"
209
209
  }