@tangle-network/ui 11.1.0 → 11.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/{artifact-pane-DvJyPWV4.d.ts → artifact-pane-DpuWcAiF.d.ts} +10 -1
  3. package/dist/chat.js +4 -4
  4. package/dist/{chunk-52Y3FMFI.js → chunk-3M7BPSSW.js} +1 -1
  5. package/dist/{chunk-LELGOLFV.js → chunk-GGDYWMFF.js} +15 -7
  6. package/dist/{chunk-EWIWYC2I.js → chunk-HW4ROISY.js} +1 -1
  7. package/dist/{chunk-F22SQTBP.js → chunk-J3YZIQNI.js} +1 -1
  8. package/dist/{chunk-CSAIKY36.js → chunk-LTEKI7YJ.js} +12 -7
  9. package/dist/{chunk-FIFE5HCW.js → chunk-OTZWQJ5W.js} +3 -3
  10. package/dist/{chunk-FJBTCTZM.js → chunk-P5F43A65.js} +1 -1
  11. package/dist/{chunk-SZ44QDA6.js → chunk-RADUPP44.js} +12 -5
  12. package/dist/{chunk-PX5KV5HI.js → chunk-SRAKCVPO.js} +2 -2
  13. package/dist/{document-editor-pane-DyDEX_Zm.d.ts → document-editor-pane-C3cYEDWZ.d.ts} +2 -2
  14. package/dist/{document-editor-pane-WCTA3ZOE.js → document-editor-pane-OQNGM4RJ.js} +3 -3
  15. package/dist/editor.d.ts +3 -3
  16. package/dist/editor.js +3 -3
  17. package/dist/files.d.ts +5 -3
  18. package/dist/files.js +3 -3
  19. package/dist/hooks.js +3 -3
  20. package/dist/index.d.ts +2 -2
  21. package/dist/index.js +8 -8
  22. package/dist/markdown.js +1 -1
  23. package/dist/openui.js +2 -2
  24. package/dist/primitives.d.ts +1 -1
  25. package/dist/primitives.js +1 -1
  26. package/dist/run.js +4 -4
  27. package/dist/sdk-hooks.js +3 -3
  28. package/package.json +1 -1
  29. package/src/editor/document-editor-pane.stories.tsx +74 -0
  30. package/src/editor/document-editor-pane.tsx +10 -3
  31. package/src/files/file-artifact-pane.tsx +14 -2
  32. package/src/files/file-preview.stories.tsx +22 -1
  33. package/src/files/file-preview.tsx +2 -2
  34. package/src/markdown/markdown.stories.tsx +55 -1
  35. package/src/markdown/markdown.test.tsx +23 -0
  36. package/src/markdown/markdown.tsx +1 -1
  37. package/src/primitives/artifact-pane.tsx +52 -26
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @tangle-network/ui
2
2
 
3
+ ## 11.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - ac4eaf4: feat(files): host-aligned artifact surface props
8
+
9
+ - `ArtifactPane`: `headerClassName` + `hideTitleBlock` (header row collapses when empty).
10
+ - `FileArtifactPane`: threads both through all render paths; `headerActions` is `undefined` (not an empty fragment) when there are no actions.
11
+ - `DocumentEditorPane`: `previewClassName` now governs the whole preview box (gutter/border/surface/padding), enabling a full-bleed document body.
12
+ - `FilePreview`: text previews sit on `bg-card` for a white surface in light theme.
13
+
14
+ ### Patch Changes
15
+
16
+ - c5dfc7d: Use tokenized Tangle prose styling for markdown previews so light themes stay readable.
17
+
3
18
  ## 11.1.0
4
19
 
5
20
  ### Minor Changes
@@ -14,11 +14,20 @@ interface ArtifactPaneProps {
14
14
  children?: ReactNode;
15
15
  className?: string;
16
16
  contentClassName?: string;
17
+ /** Extra classes for the header band (e.g. to align its surface with the host chrome). */
18
+ headerClassName?: string;
19
+ /**
20
+ * Suppress the eyebrow/title/subtitle/meta block. Use when the host already
21
+ * shows the artifact's identity elsewhere (e.g. a surrounding path row), so the
22
+ * pane keeps only its tabs, toolbar, and header actions. The whole header row
23
+ * collapses when nothing else would render in it.
24
+ */
25
+ hideTitleBlock?: boolean;
17
26
  }
18
27
  /**
19
28
  * ArtifactPane — shared frame for files, previews, documents, inspectors, and
20
29
  * other artifact-like surfaces inside sandbox applications.
21
30
  */
22
- declare function ArtifactPane({ eyebrow, title, subtitle, meta, headerActions, tabs, toolbar, footer, emptyState, children, className, contentClassName, }: ArtifactPaneProps): react_jsx_runtime.JSX.Element;
31
+ declare function ArtifactPane({ eyebrow, title, subtitle, meta, headerActions, tabs, toolbar, footer, emptyState, children, className, contentClassName, headerClassName, hideTitleBlock, }: ArtifactPaneProps): react_jsx_runtime.JSX.Element;
23
32
 
24
33
  export { ArtifactPane as A, type ArtifactPaneProps as a };
package/dist/chat.js CHANGED
@@ -5,17 +5,17 @@ import {
5
5
  MessageList,
6
6
  ThinkingIndicator,
7
7
  UserMessage
8
- } from "./chunk-FIFE5HCW.js";
8
+ } from "./chunk-OTZWQJ5W.js";
9
9
  import "./chunk-AZWDI2JG.js";
10
- import "./chunk-PX5KV5HI.js";
10
+ import "./chunk-SRAKCVPO.js";
11
11
  import "./chunk-4ORVXSMU.js";
12
12
  import "./chunk-ULDNFLIM.js";
13
13
  import "./chunk-AAUNOHVL.js";
14
- import "./chunk-52Y3FMFI.js";
14
+ import "./chunk-3M7BPSSW.js";
15
15
  import "./chunk-GYPQXTJU.js";
16
16
  import "./chunk-MKTSMWVD.js";
17
17
  import "./chunk-ZRVH3WCA.js";
18
- import "./chunk-FJBTCTZM.js";
18
+ import "./chunk-P5F43A65.js";
19
19
  import "./chunk-WUQDUBJG.js";
20
20
  import "./chunk-RQHJBTEU.js";
21
21
  export {
@@ -17,7 +17,7 @@ import {
17
17
  } from "./chunk-MKTSMWVD.js";
18
18
  import {
19
19
  Markdown
20
- } from "./chunk-FJBTCTZM.js";
20
+ } from "./chunk-P5F43A65.js";
21
21
  import {
22
22
  CodeBlock
23
23
  } from "./chunk-WUQDUBJG.js";
@@ -6,10 +6,10 @@ import {
6
6
  } from "./chunk-ZRVH3WCA.js";
7
7
  import {
8
8
  ArtifactPane
9
- } from "./chunk-CSAIKY36.js";
9
+ } from "./chunk-LTEKI7YJ.js";
10
10
  import {
11
11
  Markdown
12
- } from "./chunk-FJBTCTZM.js";
12
+ } from "./chunk-P5F43A65.js";
13
13
  import {
14
14
  CodeBlock,
15
15
  CopyButton
@@ -407,7 +407,7 @@ function PdfPreview({ blobUrl, filename }) {
407
407
  ) });
408
408
  }
409
409
  function TextPreview({ content }) {
410
- return /* @__PURE__ */ jsx3("pre", { className: "bg-background rounded-[var(--radius-md)] border border-border p-4 overflow-auto max-h-[70vh] text-sm text-foreground font-mono leading-[1.55]", children: content });
410
+ return /* @__PURE__ */ jsx3("pre", { className: "bg-card rounded-[var(--radius-md)] border border-border p-4 overflow-auto max-h-[70vh] text-sm text-foreground font-mono leading-[1.55]", children: content });
411
411
  }
412
412
  function UnsupportedPreview({
413
413
  filename,
@@ -422,7 +422,7 @@ function UnsupportedPreview({
422
422
  ] });
423
423
  }
424
424
  function MarkdownPreview({ content }) {
425
- return /* @__PURE__ */ jsx3("div", { className: "rounded-[var(--radius-md)] border border-border bg-background p-5", children: /* @__PURE__ */ jsx3(Markdown, { className: "prose-sm max-w-none", children: content }) });
425
+ return /* @__PURE__ */ jsx3("div", { className: "rounded-[var(--radius-md)] border border-border bg-background p-5", children: /* @__PURE__ */ jsx3(Markdown, { children: content }) });
426
426
  }
427
427
  function EmptyPreview({ filename }) {
428
428
  return /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center justify-center py-16 text-muted-foreground", children: [
@@ -560,7 +560,7 @@ import { lazy, Suspense } from "react";
560
560
  import { Download as Download2, X as X3 } from "lucide-react";
561
561
  import { Fragment, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
562
562
  var LazyDocumentEditorPane = lazy(async () => {
563
- const module = await import("./document-editor-pane-WCTA3ZOE.js");
563
+ const module = await import("./document-editor-pane-OQNGM4RJ.js");
564
564
  return { default: module.DocumentEditorPane };
565
565
  });
566
566
  function FileArtifactPane({
@@ -580,6 +580,8 @@ function FileArtifactPane({
580
580
  toolbar,
581
581
  footer,
582
582
  className,
583
+ headerClassName,
584
+ hideTitleBlock,
583
585
  editor
584
586
  }) {
585
587
  const showTabs = tabs.length > 0 && onTabSelect && onTabClose;
@@ -594,7 +596,7 @@ function FileArtifactPane({
594
596
  ) : void 0;
595
597
  const isMarkdown = detectFileFormat(filename, mimeType) === "markdown" || (path ? detectFileFormat(path, mimeType) === "markdown" : false);
596
598
  const isEditableMarkdown = isMarkdown && editor?.enabled;
597
- const headerActions = /* @__PURE__ */ jsxs4(Fragment, { children: [
599
+ const headerActions = onDownload || onClose ? /* @__PURE__ */ jsxs4(Fragment, { children: [
598
600
  onDownload && /* @__PURE__ */ jsx5(
599
601
  "button",
600
602
  {
@@ -615,7 +617,7 @@ function FileArtifactPane({
615
617
  children: /* @__PURE__ */ jsx5(X3, { className: "h-4 w-4" })
616
618
  }
617
619
  )
618
- ] });
620
+ ] }) : void 0;
619
621
  if (isEditableMarkdown) {
620
622
  return /* @__PURE__ */ jsx5(
621
623
  Suspense,
@@ -632,6 +634,8 @@ function FileArtifactPane({
632
634
  className,
633
635
  tabs: paneTabs,
634
636
  headerActions,
637
+ headerClassName,
638
+ hideTitleBlock,
635
639
  children: /* @__PURE__ */ jsx5("div", { className: "flex min-h-[12rem] items-center justify-center rounded-[var(--radius-lg)] border border-dashed border-border bg-muted text-sm text-muted-foreground", children: "Loading editor\u2026" })
636
640
  }
637
641
  ),
@@ -647,6 +651,8 @@ function FileArtifactPane({
647
651
  className,
648
652
  tabs: paneTabs,
649
653
  headerActions,
654
+ headerClassName,
655
+ hideTitleBlock,
650
656
  markdown: content ?? "",
651
657
  mode: editor.mode,
652
658
  defaultMode: editor.defaultMode,
@@ -679,6 +685,8 @@ function FileArtifactPane({
679
685
  className,
680
686
  tabs: paneTabs,
681
687
  headerActions,
688
+ headerClassName,
689
+ hideTitleBlock,
682
690
  children: /* @__PURE__ */ jsx5(
683
691
  FilePreview,
684
692
  {
@@ -11,7 +11,7 @@ import {
11
11
  } from "./chunk-OEX7NZE3.js";
12
12
  import {
13
13
  parseToolEvent
14
- } from "./chunk-F22SQTBP.js";
14
+ } from "./chunk-J3YZIQNI.js";
15
15
 
16
16
  // src/hooks/use-dropdown-menu.ts
17
17
  import { useEffect, useRef, useState } from "react";
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-4ORVXSMU.js";
5
5
  import {
6
6
  Markdown
7
- } from "./chunk-FJBTCTZM.js";
7
+ } from "./chunk-P5F43A65.js";
8
8
  import {
9
9
  cn
10
10
  } from "./chunk-RQHJBTEU.js";
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-RQHJBTEU.js";
4
4
 
5
5
  // src/primitives/artifact-pane.tsx
6
- import { jsx, jsxs } from "react/jsx-runtime";
6
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
7
  function ArtifactPane({
8
8
  eyebrow,
9
9
  title,
@@ -16,9 +16,14 @@ function ArtifactPane({
16
16
  emptyState,
17
17
  children,
18
18
  className,
19
- contentClassName
19
+ contentClassName,
20
+ headerClassName,
21
+ hideTitleBlock = false
20
22
  }) {
21
23
  const hasContent = children !== void 0 && children !== null;
24
+ const showTitleRow = !hideTitleBlock || Boolean(headerActions);
25
+ const hasHeader = showTitleRow || Boolean(tabs) || Boolean(toolbar);
26
+ const toolbarNeedsDivider = showTitleRow || Boolean(tabs);
22
27
  return /* @__PURE__ */ jsxs(
23
28
  "section",
24
29
  {
@@ -27,20 +32,20 @@ function ArtifactPane({
27
32
  className
28
33
  ),
29
34
  children: [
30
- /* @__PURE__ */ jsxs("header", { className: "border-b border-border bg-muted/10", children: [
31
- /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3 px-3 py-2", children: [
32
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
35
+ hasHeader && /* @__PURE__ */ jsxs("header", { className: cn("border-b border-border bg-muted/10", headerClassName), children: [
36
+ showTitleRow && /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3 px-3 py-2", children: [
37
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: !hideTitleBlock && /* @__PURE__ */ jsxs(Fragment, { children: [
33
38
  eyebrow && /* @__PURE__ */ jsx("div", { className: "mb-1 inline-flex max-w-full items-center px-1 py-0.5 text-[10px] font-bold uppercase tracking-wider text-muted-foreground", children: eyebrow }),
34
39
  /* @__PURE__ */ jsx("div", { className: "min-w-0 text-[13px] font-medium text-foreground", children: title }),
35
40
  (subtitle || meta) && /* @__PURE__ */ jsxs("div", { className: "mt-1 flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 text-xs leading-relaxed text-muted-foreground", children: [
36
41
  subtitle && /* @__PURE__ */ jsx("span", { className: "truncate", children: subtitle }),
37
42
  meta && /* @__PURE__ */ jsx("span", { className: "flex items-center gap-2", children: meta })
38
43
  ] })
39
- ] }),
44
+ ] }) }),
40
45
  headerActions && /* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center gap-1.5", children: headerActions })
41
46
  ] }),
42
47
  tabs,
43
- toolbar && /* @__PURE__ */ jsx("div", { className: "border-t border-border px-3 py-2", children: toolbar })
48
+ toolbar && /* @__PURE__ */ jsx("div", { className: cn("px-3 py-2", toolbarNeedsDivider && "border-t border-border"), children: toolbar })
44
49
  ] }),
45
50
  /* @__PURE__ */ jsx("div", { className: cn("min-h-0 flex-1 overflow-auto", contentClassName), children: hasContent ? children : emptyState ? /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center p-6", children: emptyState }) : null }),
46
51
  footer && /* @__PURE__ */ jsx("footer", { className: "shrink-0 border-t border-border bg-card px-3 py-2", children: footer })
@@ -6,7 +6,7 @@ import {
6
6
  import {
7
7
  InlineThinkingItem,
8
8
  RunGroup
9
- } from "./chunk-PX5KV5HI.js";
9
+ } from "./chunk-SRAKCVPO.js";
10
10
  import {
11
11
  ToolCallGroup,
12
12
  ToolCallStep
@@ -16,10 +16,10 @@ import {
16
16
  } from "./chunk-ULDNFLIM.js";
17
17
  import {
18
18
  OpenUIArtifactRenderer
19
- } from "./chunk-52Y3FMFI.js";
19
+ } from "./chunk-3M7BPSSW.js";
20
20
  import {
21
21
  Markdown
22
- } from "./chunk-FJBTCTZM.js";
22
+ } from "./chunk-P5F43A65.js";
23
23
  import {
24
24
  cn
25
25
  } from "./chunk-RQHJBTEU.js";
@@ -16,7 +16,7 @@ var Markdown = memo(({ children, className }) => {
16
16
  return /* @__PURE__ */ jsx(
17
17
  "div",
18
18
  {
19
- className: cn("prose prose-sm dark:prose-invert max-w-none", className),
19
+ className: cn("tangle-prose max-w-none text-sm", className),
20
20
  children: /* @__PURE__ */ jsx(
21
21
  ReactMarkdown,
22
22
  {
@@ -6,10 +6,10 @@ import {
6
6
  } from "./chunk-Q56BYXQF.js";
7
7
  import {
8
8
  ArtifactPane
9
- } from "./chunk-CSAIKY36.js";
9
+ } from "./chunk-LTEKI7YJ.js";
10
10
  import {
11
11
  Markdown
12
- } from "./chunk-FJBTCTZM.js";
12
+ } from "./chunk-P5F43A65.js";
13
13
  import {
14
14
  cn
15
15
  } from "./chunk-RQHJBTEU.js";
@@ -1030,6 +1030,8 @@ function DocumentEditorPane({
1030
1030
  footer,
1031
1031
  className,
1032
1032
  contentClassName,
1033
+ headerClassName,
1034
+ hideTitleBlock,
1033
1035
  tabs,
1034
1036
  toolbar,
1035
1037
  markdown = "",
@@ -1126,10 +1128,13 @@ function DocumentEditorPane({
1126
1128
  "div",
1127
1129
  {
1128
1130
  className: cn(
1129
- "rounded-[var(--radius-lg)] border border-border bg-background p-5",
1131
+ // The outer margin is the card's gutter (rather than padding on the tab
1132
+ // panel) so `previewClassName` can govern the whole box — gutter, border,
1133
+ // surface, and inner padding — e.g. to render the document full-bleed.
1134
+ "m-4 rounded-[var(--radius-lg)] border border-border bg-card p-5",
1130
1135
  previewClassName
1131
1136
  ),
1132
- children: /* @__PURE__ */ jsx5(Markdown, { className: "prose-sm max-w-none", children: draft })
1137
+ children: /* @__PURE__ */ jsx5(Markdown, { children: draft })
1133
1138
  }
1134
1139
  );
1135
1140
  const localEditor = /* @__PURE__ */ jsx5(
@@ -1174,9 +1179,11 @@ function DocumentEditorPane({
1174
1179
  tabs,
1175
1180
  className,
1176
1181
  contentClassName,
1182
+ headerClassName,
1177
1183
  toolbar: editorToolbar,
1184
+ hideTitleBlock,
1178
1185
  children: [
1179
- /* @__PURE__ */ jsx5(TabsContent, { value: "preview", className: "mt-0 h-full px-4 py-4", children: preview }),
1186
+ /* @__PURE__ */ jsx5(TabsContent, { value: "preview", className: "mt-0 h-full", children: preview }),
1180
1187
  /* @__PURE__ */ jsx5(TabsContent, { value: "edit", className: "mt-0 h-full px-4 py-4", children: isCollaborative ? collaborativeEditor : localEditor })
1181
1188
  ]
1182
1189
  }
@@ -9,10 +9,10 @@ import {
9
9
  } from "./chunk-ULDNFLIM.js";
10
10
  import {
11
11
  OpenUIArtifactRenderer
12
- } from "./chunk-52Y3FMFI.js";
12
+ } from "./chunk-3M7BPSSW.js";
13
13
  import {
14
14
  Markdown
15
- } from "./chunk-FJBTCTZM.js";
15
+ } from "./chunk-P5F43A65.js";
16
16
  import {
17
17
  cn
18
18
  } from "./chunk-RQHJBTEU.js";
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode } from 'react';
3
- import { a as ArtifactPaneProps } from './artifact-pane-DvJyPWV4.js';
3
+ import { a as ArtifactPaneProps } from './artifact-pane-DpuWcAiF.js';
4
4
  import { HocuspocusProvider } from '@hocuspocus/provider';
5
5
  import * as Y from 'yjs';
6
6
 
@@ -119,6 +119,6 @@ interface DocumentEditorPaneProps extends Omit<ArtifactPaneProps, "children" | "
119
119
  * DocumentEditorPane — reusable markdown document surface with preview/edit
120
120
  * modes and optional collaborative editing backed by Yjs/Hocuspocus.
121
121
  */
122
- declare function DocumentEditorPane({ eyebrow, title, subtitle, meta, headerActions, footer, className, contentClassName, tabs, toolbar, markdown, mode, defaultMode, onModeChange, backend, placeholder, autoFocus, readOnly, onChange, onSave, saving, saveLabel, previewClassName, editorClassName, collaboration, }: DocumentEditorPaneProps): react_jsx_runtime.JSX.Element;
122
+ declare function DocumentEditorPane({ eyebrow, title, subtitle, meta, headerActions, footer, className, contentClassName, headerClassName, hideTitleBlock, tabs, toolbar, markdown, mode, defaultMode, onModeChange, backend, placeholder, autoFocus, readOnly, onChange, onSave, saving, saveLabel, previewClassName, editorClassName, collaboration, }: DocumentEditorPaneProps): react_jsx_runtime.JSX.Element;
123
123
 
124
124
  export { type Collaborator as C, type DocumentEditorBackend as D, type EditorContextValue as E, type ConnectionState as a, type DocumentEditorMode as b, DocumentEditorPane as c, type DocumentEditorPaneCollaborationConfig as d, type DocumentEditorPaneProps as e, EditorProvider as f, type EditorProviderProps as g, type EditorTokenRefreshResult as h, type EditorUser as i, useEditorContext as u };
@@ -1,10 +1,10 @@
1
1
  "use client";
2
2
  import {
3
3
  DocumentEditorPane
4
- } from "./chunk-SZ44QDA6.js";
4
+ } from "./chunk-RADUPP44.js";
5
5
  import "./chunk-Q56BYXQF.js";
6
- import "./chunk-CSAIKY36.js";
7
- import "./chunk-FJBTCTZM.js";
6
+ import "./chunk-LTEKI7YJ.js";
7
+ import "./chunk-P5F43A65.js";
8
8
  import "./chunk-WUQDUBJG.js";
9
9
  import "./chunk-RQHJBTEU.js";
10
10
  export {
package/dist/editor.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { C as Collaborator, a as ConnectionState } from './document-editor-pane-DyDEX_Zm.js';
2
- export { D as DocumentEditorBackend, b as DocumentEditorMode, c as DocumentEditorPane, d as DocumentEditorPaneCollaborationConfig, e as DocumentEditorPaneProps, E as EditorContextValue, f as EditorProvider, g as EditorProviderProps, h as EditorTokenRefreshResult, i as EditorUser, u as useEditorContext } from './document-editor-pane-DyDEX_Zm.js';
1
+ import { C as Collaborator, a as ConnectionState } from './document-editor-pane-C3cYEDWZ.js';
2
+ export { D as DocumentEditorBackend, b as DocumentEditorMode, c as DocumentEditorPane, d as DocumentEditorPaneCollaborationConfig, e as DocumentEditorPaneProps, E as EditorContextValue, f as EditorProvider, g as EditorProviderProps, h as EditorTokenRefreshResult, i as EditorUser, u as useEditorContext } from './document-editor-pane-C3cYEDWZ.js';
3
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
4
4
  import { Editor } from '@tiptap/react';
5
5
  import { HocuspocusProvider } from '@hocuspocus/provider';
6
6
  import 'react';
7
- import './artifact-pane-DvJyPWV4.js';
7
+ import './artifact-pane-DpuWcAiF.js';
8
8
  import 'yjs';
9
9
 
10
10
  /**
package/dist/editor.js CHANGED
@@ -11,10 +11,10 @@ import {
11
11
  useEditorConnection,
12
12
  useEditorContext,
13
13
  useYjsState
14
- } from "./chunk-SZ44QDA6.js";
14
+ } from "./chunk-RADUPP44.js";
15
15
  import "./chunk-Q56BYXQF.js";
16
- import "./chunk-CSAIKY36.js";
17
- import "./chunk-FJBTCTZM.js";
16
+ import "./chunk-LTEKI7YJ.js";
17
+ import "./chunk-P5F43A65.js";
18
18
  import "./chunk-WUQDUBJG.js";
19
19
  import "./chunk-RQHJBTEU.js";
20
20
  export {
package/dist/files.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { GitStatusEntry, ContextMenuItem, ContextMenuOpenContext, GitStatus } from '@pierre/trees';
3
3
  import { ReactNode, CSSProperties } from 'react';
4
- import { b as DocumentEditorMode, D as DocumentEditorBackend, d as DocumentEditorPaneCollaborationConfig } from './document-editor-pane-DyDEX_Zm.js';
5
- import { a as ArtifactPaneProps } from './artifact-pane-DvJyPWV4.js';
4
+ import { b as DocumentEditorMode, D as DocumentEditorBackend, d as DocumentEditorPaneCollaborationConfig } from './document-editor-pane-C3cYEDWZ.js';
5
+ import { a as ArtifactPaneProps } from './artifact-pane-DpuWcAiF.js';
6
6
  import '@hocuspocus/provider';
7
7
  import 'yjs';
8
8
 
@@ -164,13 +164,15 @@ interface FileArtifactPaneProps extends Omit<FilePreviewProps, "className"> {
164
164
  toolbar?: ArtifactPaneProps["toolbar"];
165
165
  footer?: ArtifactPaneProps["footer"];
166
166
  className?: string;
167
+ headerClassName?: ArtifactPaneProps["headerClassName"];
168
+ hideTitleBlock?: ArtifactPaneProps["hideTitleBlock"];
167
169
  editor?: FileArtifactPaneEditorOptions;
168
170
  }
169
171
  /**
170
172
  * FileArtifactPane — opinionated artifact frame for file previews with tabs and
171
173
  * header actions.
172
174
  */
173
- declare function FileArtifactPane({ filename, content, blobUrl, mimeType, onClose, onDownload, path, tabs, activeTabId, onTabSelect, onTabClose, eyebrow, meta, toolbar, footer, className, editor, }: FileArtifactPaneProps): react_jsx_runtime.JSX.Element;
175
+ declare function FileArtifactPane({ filename, content, blobUrl, mimeType, onClose, onDownload, path, tabs, activeTabId, onTabSelect, onTabClose, eyebrow, meta, toolbar, footer, className, headerClassName, hideTitleBlock, editor, }: FileArtifactPaneProps): react_jsx_runtime.JSX.Element;
174
176
 
175
177
  /**
176
178
  * Shared file-format detection for every file surface — the preview pane, the
package/dist/files.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  FileTree,
6
6
  RichFileTree,
7
7
  filterFileTree
8
- } from "./chunk-LELGOLFV.js";
8
+ } from "./chunk-GGDYWMFF.js";
9
9
  import {
10
10
  detectFileFormat,
11
11
  fileExtension,
@@ -13,8 +13,8 @@ import {
13
13
  getFormatLabel,
14
14
  getSyntaxLanguage
15
15
  } from "./chunk-ZRVH3WCA.js";
16
- import "./chunk-CSAIKY36.js";
17
- import "./chunk-FJBTCTZM.js";
16
+ import "./chunk-LTEKI7YJ.js";
17
+ import "./chunk-P5F43A65.js";
18
18
  import "./chunk-WUQDUBJG.js";
19
19
  import "./chunk-RQHJBTEU.js";
20
20
  export {
package/dist/hooks.js CHANGED
@@ -11,19 +11,19 @@ import {
11
11
  useSSEStream,
12
12
  useSdkSession,
13
13
  useToolCallStream
14
- } from "./chunk-EWIWYC2I.js";
14
+ } from "./chunk-HW4ROISY.js";
15
15
  import "./chunk-OEX7NZE3.js";
16
16
  import {
17
17
  useAutoScroll,
18
18
  useRunCollapseState,
19
19
  useRunGroups
20
20
  } from "./chunk-AZWDI2JG.js";
21
- import "./chunk-F22SQTBP.js";
21
+ import "./chunk-J3YZIQNI.js";
22
22
  import "./chunk-4ORVXSMU.js";
23
23
  import "./chunk-ULDNFLIM.js";
24
24
  import "./chunk-AAUNOHVL.js";
25
25
  import "./chunk-ZRVH3WCA.js";
26
- import "./chunk-FJBTCTZM.js";
26
+ import "./chunk-P5F43A65.js";
27
27
  import "./chunk-WUQDUBJG.js";
28
28
  import "./chunk-RQHJBTEU.js";
29
29
  export {
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { B as Button, a as ButtonProps, b as buttonVariants } from './button-CMQuQEW_.js';
2
2
  export { Avatar, AvatarFallback, AvatarImage, Badge, BadgeProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropZone, DropZoneProps, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, EmptyStateProps, InlineCode, InlineCodeProps, Input, InputProps, Label, Progress, SegmentedControl, SegmentedControlOption, SegmentedControlProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SidebarDropZone, SidebarDropZoneProps, Skeleton, SkeletonCard, SkeletonTable, StatCard, StatCardProps, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, Textarea, TextareaProps, ThemeToggle, Toast, ToastContainer, ToastProvider, UploadFile, UploadProgress, UploadProgressProps, badgeVariants, useTheme, useToast } from './primitives.js';
3
3
  export { Logo, LogoProps, TangleKnot } from '@tangle-network/brand';
4
- export { A as ArtifactPane, a as ArtifactPaneProps } from './artifact-pane-DvJyPWV4.js';
4
+ export { A as ArtifactPane, a as ArtifactPaneProps } from './artifact-pane-DpuWcAiF.js';
5
5
  export { AgentTimeline, AgentTimelineArtifactItem, AgentTimelineCustomItem, AgentTimelineItem, AgentTimelineMessageItem, AgentTimelineProps, AgentTimelineStatusItem, AgentTimelineTone, AgentTimelineToolGroupItem, AgentTimelineToolItem, ChatContainer, ChatContainerProps, ChatMessage, ChatMessageProps, MessageList, MessageListProps, MessageRole, ThinkingIndicator, ThinkingIndicatorProps, UserMessage, UserMessageProps } from './chat.js';
6
6
  export { ExpandedToolDetail, ExpandedToolDetailProps, InlineThinkingItem, InlineThinkingItemProps, InlineToolItem, InlineToolItemProps, LiveDuration, RunGroup, RunGroupProps } from './run.js';
7
7
  export { F as FeedSegment, T as ToolCallData, a as ToolCallFeed, b as ToolCallFeedProps, c as ToolCallStatus, d as ToolCallType, p as parseToolEvent } from './tool-call-feed-D9iofJgW.js';
@@ -34,7 +34,7 @@ import '@radix-ui/react-progress';
34
34
  import '@radix-ui/react-switch';
35
35
  import '@radix-ui/react-label';
36
36
  import '@pierre/trees';
37
- import './document-editor-pane-DyDEX_Zm.js';
37
+ import './document-editor-pane-C3cYEDWZ.js';
38
38
  import '@hocuspocus/provider';
39
39
  import 'yjs';
40
40
  import 'nanostores';
package/dist/index.js CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  useSSEStream,
18
18
  useSdkSession,
19
19
  useToolCallStream
20
- } from "./chunk-EWIWYC2I.js";
20
+ } from "./chunk-HW4ROISY.js";
21
21
  import {
22
22
  addMessage,
23
23
  addParts,
@@ -137,7 +137,7 @@ import {
137
137
  MessageList,
138
138
  ThinkingIndicator,
139
139
  UserMessage
140
- } from "./chunk-FIFE5HCW.js";
140
+ } from "./chunk-OTZWQJ5W.js";
141
141
  import {
142
142
  useAutoScroll,
143
143
  useRunCollapseState,
@@ -147,11 +147,11 @@ import "./chunk-47XH56SV.js";
147
147
  import {
148
148
  ToolCallFeed,
149
149
  parseToolEvent
150
- } from "./chunk-F22SQTBP.js";
150
+ } from "./chunk-J3YZIQNI.js";
151
151
  import {
152
152
  InlineThinkingItem,
153
153
  RunGroup
154
- } from "./chunk-PX5KV5HI.js";
154
+ } from "./chunk-SRAKCVPO.js";
155
155
  import {
156
156
  ExpandedToolDetail,
157
157
  InlineToolItem,
@@ -179,7 +179,7 @@ import {
179
179
  import "./chunk-RQGKSCEZ.js";
180
180
  import {
181
181
  OpenUIArtifactRenderer
182
- } from "./chunk-52Y3FMFI.js";
182
+ } from "./chunk-3M7BPSSW.js";
183
183
  import {
184
184
  Badge,
185
185
  Card,
@@ -210,7 +210,7 @@ import {
210
210
  FileTree,
211
211
  RichFileTree,
212
212
  filterFileTree
213
- } from "./chunk-LELGOLFV.js";
213
+ } from "./chunk-GGDYWMFF.js";
214
214
  import {
215
215
  detectFileFormat,
216
216
  fileExtension,
@@ -226,11 +226,11 @@ import {
226
226
  } from "./chunk-Q56BYXQF.js";
227
227
  import {
228
228
  ArtifactPane
229
- } from "./chunk-CSAIKY36.js";
229
+ } from "./chunk-LTEKI7YJ.js";
230
230
  import "./chunk-5Z5ZYMOJ.js";
231
231
  import {
232
232
  Markdown
233
- } from "./chunk-FJBTCTZM.js";
233
+ } from "./chunk-P5F43A65.js";
234
234
  import {
235
235
  CodeBlock,
236
236
  CopyButton
package/dist/markdown.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import "./chunk-5Z5ZYMOJ.js";
2
2
  import {
3
3
  Markdown
4
- } from "./chunk-FJBTCTZM.js";
4
+ } from "./chunk-P5F43A65.js";
5
5
  import {
6
6
  CodeBlock,
7
7
  CopyButton
package/dist/openui.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import "./chunk-RQGKSCEZ.js";
2
2
  import {
3
3
  OpenUIArtifactRenderer
4
- } from "./chunk-52Y3FMFI.js";
4
+ } from "./chunk-3M7BPSSW.js";
5
5
  import "./chunk-GYPQXTJU.js";
6
6
  import "./chunk-MKTSMWVD.js";
7
- import "./chunk-FJBTCTZM.js";
7
+ import "./chunk-P5F43A65.js";
8
8
  import "./chunk-WUQDUBJG.js";
9
9
  import "./chunk-RQHJBTEU.js";
10
10
  export {
@@ -13,7 +13,7 @@ import * as ProgressPrimitive from '@radix-ui/react-progress';
13
13
  import * as SwitchPrimitives from '@radix-ui/react-switch';
14
14
  import * as LabelPrimitive from '@radix-ui/react-label';
15
15
  export { Logo, LogoProps, TangleKnot } from '@tangle-network/brand';
16
- export { A as ArtifactPane, a as ArtifactPaneProps } from './artifact-pane-DvJyPWV4.js';
16
+ export { A as ArtifactPane, a as ArtifactPaneProps } from './artifact-pane-DpuWcAiF.js';
17
17
  export { C as CodeBlock, a as CodeBlockProps, b as CopyButton } from './code-block-0kSpWMnf.js';
18
18
 
19
19
  declare const Card: React$1.ForwardRefExoticComponent<React$1.HTMLAttributes<HTMLDivElement> & {
@@ -96,7 +96,7 @@ import {
96
96
  } from "./chunk-Q56BYXQF.js";
97
97
  import {
98
98
  ArtifactPane
99
- } from "./chunk-CSAIKY36.js";
99
+ } from "./chunk-LTEKI7YJ.js";
100
100
  import {
101
101
  CodeBlock,
102
102
  CopyButton
package/dist/run.js CHANGED
@@ -2,11 +2,11 @@ import "./chunk-47XH56SV.js";
2
2
  import {
3
3
  ToolCallFeed,
4
4
  parseToolEvent
5
- } from "./chunk-F22SQTBP.js";
5
+ } from "./chunk-J3YZIQNI.js";
6
6
  import {
7
7
  InlineThinkingItem,
8
8
  RunGroup
9
- } from "./chunk-PX5KV5HI.js";
9
+ } from "./chunk-SRAKCVPO.js";
10
10
  import {
11
11
  ExpandedToolDetail,
12
12
  InlineToolItem,
@@ -14,11 +14,11 @@ import {
14
14
  } from "./chunk-4ORVXSMU.js";
15
15
  import "./chunk-ULDNFLIM.js";
16
16
  import "./chunk-AAUNOHVL.js";
17
- import "./chunk-52Y3FMFI.js";
17
+ import "./chunk-3M7BPSSW.js";
18
18
  import "./chunk-GYPQXTJU.js";
19
19
  import "./chunk-MKTSMWVD.js";
20
20
  import "./chunk-ZRVH3WCA.js";
21
- import "./chunk-FJBTCTZM.js";
21
+ import "./chunk-P5F43A65.js";
22
22
  import "./chunk-WUQDUBJG.js";
23
23
  import "./chunk-RQHJBTEU.js";
24
24
  export {
package/dist/sdk-hooks.js CHANGED
@@ -5,19 +5,19 @@ import {
5
5
  useSSEStream,
6
6
  useSdkSession,
7
7
  useToolCallStream
8
- } from "./chunk-EWIWYC2I.js";
8
+ } from "./chunk-HW4ROISY.js";
9
9
  import "./chunk-OEX7NZE3.js";
10
10
  import {
11
11
  useAutoScroll,
12
12
  useRunCollapseState,
13
13
  useRunGroups
14
14
  } from "./chunk-AZWDI2JG.js";
15
- import "./chunk-F22SQTBP.js";
15
+ import "./chunk-J3YZIQNI.js";
16
16
  import "./chunk-4ORVXSMU.js";
17
17
  import "./chunk-ULDNFLIM.js";
18
18
  import "./chunk-AAUNOHVL.js";
19
19
  import "./chunk-ZRVH3WCA.js";
20
- import "./chunk-FJBTCTZM.js";
20
+ import "./chunk-P5F43A65.js";
21
21
  import "./chunk-WUQDUBJG.js";
22
22
  import "./chunk-RQHJBTEU.js";
23
23
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/ui",
3
- "version": "11.1.0",
3
+ "version": "11.2.0",
4
4
  "description": "Generic React UI components for Tangle products — primitives, chat, run, files, editor, markdown.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -0,0 +1,74 @@
1
+ import type { Meta, StoryObj } from '@storybook/react'
2
+ import { DocumentEditorPane } from './document-editor-pane'
3
+
4
+ const MARKDOWN_SOURCE = `# GTM Agent Repository Overview
5
+
6
+ Source: \`github.com/tangle-network/gtm-agent\`
7
+
8
+ ---
9
+
10
+ ## What It Is
11
+
12
+ GTM Agent is an AI-powered go-to-market workspace where founders get a sandboxed environment.
13
+
14
+ ## Architecture
15
+
16
+ | Layer | Owns | Location |
17
+ | --- | --- | --- |
18
+ | Engine | Stream normalization | \`@tangle-network/agent-app\` |
19
+ | Shell | Auth, billing, RBAC, vault | \`src/routes/**\` |
20
+
21
+ ## Tech Stack
22
+
23
+ - Frontend: Remix + Tailwind v4
24
+ - Database: SQLite + Drizzle ORM
25
+ - Sandbox: \`@tangle-network/sandbox\`
26
+
27
+ [Launch](https://gtm.tangle.tools)
28
+ `
29
+
30
+ const meta: Meta<typeof DocumentEditorPane> = {
31
+ title: 'Editor/DocumentEditorPane',
32
+ component: DocumentEditorPane,
33
+ parameters: { layout: 'fullscreen', backgrounds: { default: 'dark' } },
34
+ decorators: [
35
+ (Story) => (
36
+ <div className="h-[620px] w-[900px] bg-background text-foreground">
37
+ <Story />
38
+ </div>
39
+ ),
40
+ ],
41
+ }
42
+
43
+ export default meta
44
+ type Story = StoryObj<typeof DocumentEditorPane>
45
+
46
+ export const PreviewDark: Story = {
47
+ name: 'Preview (dark theme)',
48
+ args: {
49
+ title: 'gtm-agent-repo.md',
50
+ subtitle: 'knowledge/gtm-agent-repo.md',
51
+ eyebrow: 'Artifact',
52
+ markdown: MARKDOWN_SOURCE,
53
+ mode: 'preview',
54
+ },
55
+ }
56
+
57
+ export const PreviewLight: Story = {
58
+ name: 'Preview (light theme)',
59
+ parameters: { backgrounds: { default: 'light' } },
60
+ decorators: [
61
+ (Story) => (
62
+ <div data-theme="tangle-light" className="h-[620px] w-[900px] bg-background text-foreground">
63
+ <Story />
64
+ </div>
65
+ ),
66
+ ],
67
+ args: {
68
+ title: 'gtm-agent-repo.md',
69
+ subtitle: 'knowledge/gtm-agent-repo.md',
70
+ eyebrow: 'Artifact',
71
+ markdown: MARKDOWN_SOURCE,
72
+ mode: 'preview',
73
+ },
74
+ }
@@ -182,6 +182,8 @@ export function DocumentEditorPane({
182
182
  footer,
183
183
  className,
184
184
  contentClassName,
185
+ headerClassName,
186
+ hideTitleBlock,
185
187
  tabs,
186
188
  toolbar,
187
189
  markdown = "",
@@ -287,11 +289,14 @@ export function DocumentEditorPane({
287
289
  const preview = (
288
290
  <div
289
291
  className={cn(
290
- "rounded-[var(--radius-lg)] border border-border bg-background p-5",
292
+ // The outer margin is the card's gutter (rather than padding on the tab
293
+ // panel) so `previewClassName` can govern the whole box — gutter, border,
294
+ // surface, and inner padding — e.g. to render the document full-bleed.
295
+ "m-4 rounded-[var(--radius-lg)] border border-border bg-card p-5",
291
296
  previewClassName,
292
297
  )}
293
298
  >
294
- <Markdown className="prose-sm max-w-none">{draft}</Markdown>
299
+ <Markdown>{draft}</Markdown>
295
300
  </div>
296
301
  );
297
302
 
@@ -337,9 +342,11 @@ export function DocumentEditorPane({
337
342
  tabs={tabs}
338
343
  className={className}
339
344
  contentClassName={contentClassName}
345
+ headerClassName={headerClassName}
340
346
  toolbar={editorToolbar}
347
+ hideTitleBlock={hideTitleBlock}
341
348
  >
342
- <TabsContent value="preview" className="mt-0 h-full px-4 py-4">
349
+ <TabsContent value="preview" className="mt-0 h-full">
343
350
  {preview}
344
351
  </TabsContent>
345
352
  <TabsContent value="edit" className="mt-0 h-full px-4 py-4">
@@ -44,6 +44,8 @@ export interface FileArtifactPaneProps extends Omit<FilePreviewProps, "className
44
44
  toolbar?: ArtifactPaneProps["toolbar"];
45
45
  footer?: ArtifactPaneProps["footer"];
46
46
  className?: string;
47
+ headerClassName?: ArtifactPaneProps["headerClassName"];
48
+ hideTitleBlock?: ArtifactPaneProps["hideTitleBlock"];
47
49
  editor?: FileArtifactPaneEditorOptions;
48
50
  }
49
51
 
@@ -68,6 +70,8 @@ export function FileArtifactPane({
68
70
  toolbar,
69
71
  footer,
70
72
  className,
73
+ headerClassName,
74
+ hideTitleBlock,
71
75
  editor,
72
76
  }: FileArtifactPaneProps) {
73
77
  const showTabs = tabs.length > 0 && onTabSelect && onTabClose;
@@ -83,7 +87,9 @@ export function FileArtifactPane({
83
87
  detectFileFormat(filename, mimeType) === "markdown" ||
84
88
  (path ? detectFileFormat(path, mimeType) === "markdown" : false);
85
89
  const isEditableMarkdown = isMarkdown && editor?.enabled;
86
- const headerActions = (
90
+ // Undefined (not an empty fragment) when there are no actions, so ArtifactPane
91
+ // can collapse the header row entirely under hideTitleBlock.
92
+ const headerActions = onDownload || onClose ? (
87
93
  <>
88
94
  {onDownload && (
89
95
  <button
@@ -106,7 +112,7 @@ export function FileArtifactPane({
106
112
  </button>
107
113
  )}
108
114
  </>
109
- );
115
+ ) : undefined;
110
116
 
111
117
  if (isEditableMarkdown) {
112
118
  return (
@@ -122,6 +128,8 @@ export function FileArtifactPane({
122
128
  className={className}
123
129
  tabs={paneTabs}
124
130
  headerActions={headerActions}
131
+ headerClassName={headerClassName}
132
+ hideTitleBlock={hideTitleBlock}
125
133
  >
126
134
  <div className="flex min-h-[12rem] items-center justify-center rounded-[var(--radius-lg)] border border-dashed border-border bg-muted text-sm text-muted-foreground">
127
135
  Loading editor…
@@ -139,6 +147,8 @@ export function FileArtifactPane({
139
147
  className={className}
140
148
  tabs={paneTabs}
141
149
  headerActions={headerActions}
150
+ headerClassName={headerClassName}
151
+ hideTitleBlock={hideTitleBlock}
142
152
  markdown={content ?? ""}
143
153
  mode={editor.mode}
144
154
  defaultMode={editor.defaultMode}
@@ -170,6 +180,8 @@ export function FileArtifactPane({
170
180
  className={className}
171
181
  tabs={paneTabs}
172
182
  headerActions={headerActions}
183
+ headerClassName={headerClassName}
184
+ hideTitleBlock={hideTitleBlock}
173
185
  >
174
186
  <FilePreview
175
187
  filename={filename}
@@ -43,7 +43,15 @@ Markdown still renders as **prose**, not as a code block.
43
43
 
44
44
  - Themed headings
45
45
  - \`inline code\`
46
- - [links](https://tangle.tools)`
46
+ - [links](https://tangle.tools)
47
+
48
+ ---
49
+
50
+ | Element | Expected |
51
+ | --- | --- |
52
+ | Body | readable foreground |
53
+ | Table | subtle borders |
54
+ | Code | tokenized accent |`
47
55
 
48
56
  const meta: Meta<typeof FilePreview> = {
49
57
  title: 'Files/FilePreview',
@@ -85,3 +93,16 @@ export const MarkdownProse: Story = {
85
93
  name: 'Markdown (rendered prose)',
86
94
  args: { filename: 'README.md', content: MARKDOWN_SOURCE },
87
95
  }
96
+
97
+ export const MarkdownProseLight: Story = {
98
+ name: 'Markdown (light theme)',
99
+ parameters: { backgrounds: { default: 'light' } },
100
+ decorators: [
101
+ (Story) => (
102
+ <div data-theme="tangle-light" className="h-[560px] w-[760px] bg-background text-foreground">
103
+ <Story />
104
+ </div>
105
+ ),
106
+ ],
107
+ args: { filename: 'README.md', content: MARKDOWN_SOURCE },
108
+ }
@@ -171,7 +171,7 @@ function PdfPreview({ blobUrl, filename }: { blobUrl: string; filename: string }
171
171
 
172
172
  function TextPreview({ content }: { content: string }) {
173
173
  return (
174
- <pre className="bg-background rounded-[var(--radius-md)] border border-border p-4 overflow-auto max-h-[70vh] text-sm text-foreground font-mono leading-[1.55]">
174
+ <pre className="bg-card rounded-[var(--radius-md)] border border-border p-4 overflow-auto max-h-[70vh] text-sm text-foreground font-mono leading-[1.55]">
175
175
  {content}
176
176
  </pre>
177
177
  );
@@ -199,7 +199,7 @@ function UnsupportedPreview({
199
199
  function MarkdownPreview({ content }: { content: string }) {
200
200
  return (
201
201
  <div className="rounded-[var(--radius-md)] border border-border bg-background p-5">
202
- <Markdown className="prose-sm max-w-none">{content}</Markdown>
202
+ <Markdown>{content}</Markdown>
203
203
  </div>
204
204
  );
205
205
  }
@@ -10,7 +10,7 @@ const markdownMeta: Meta<typeof Markdown> = {
10
10
  parameters: { layout: 'centered', backgrounds: { default: 'dark' } },
11
11
  decorators: [
12
12
  (Story) => (
13
- <div className="w-[680px] p-6 rounded-xl bg-card text-foreground">
13
+ <div className="w-[680px] rounded-xl bg-card p-6 text-foreground">
14
14
  <Story />
15
15
  </div>
16
16
  ),
@@ -20,6 +20,60 @@ const markdownMeta: Meta<typeof Markdown> = {
20
20
  export default markdownMeta
21
21
  type Story = StoryObj<typeof Markdown>
22
22
 
23
+ const CONTRAST_MARKDOWN = `# GTM Agent Repository Overview
24
+
25
+ Source: \`github.com/tangle-network/gtm-agent\` Live at: [gtm.tangle.tools](https://gtm.tangle.tools)
26
+
27
+ ---
28
+
29
+ ## What It Is
30
+
31
+ GTM Agent is an AI-powered go-to-market workspace where founders get a sandboxed environment with an AI co-founder.
32
+
33
+ ## Architecture
34
+
35
+ | Layer | Owns | Location |
36
+ |------|------|----------|
37
+ | Engine | Stream normalization, DB port, harness | \`@tangle-network/agent-app\` |
38
+ | Shell | Auth, billing, RBAC, vault | \`src/routes/**\` |
39
+ | Runtime | Customer motion | Live sandbox |
40
+
41
+ ## Tech Stack
42
+
43
+ - **Frontend:** Remix + Tailwind v4
44
+ - **Database:** SQLite + Drizzle ORM
45
+ - **Sandbox:** \`@tangle-network/sandbox\`
46
+
47
+ > Prime directive: ship the shell, never hardcode customer motion.
48
+
49
+ \`inline code\` stays readable beside paragraphs.
50
+
51
+ \`\`\`ts
52
+ export function readable(theme: 'light' | 'dark') {
53
+ return theme
54
+ }
55
+ \`\`\`
56
+ `
57
+
58
+ export const LightThemeRegression: Story = {
59
+ name: 'Light theme regression',
60
+ parameters: { backgrounds: { default: 'light' } },
61
+ render: () => (
62
+ <div data-theme="tangle-light" className="rounded-xl bg-background p-6 text-foreground">
63
+ <Markdown>{CONTRAST_MARKDOWN}</Markdown>
64
+ </div>
65
+ ),
66
+ }
67
+
68
+ export const DarkThemeRegression: Story = {
69
+ name: 'Dark theme regression',
70
+ render: () => (
71
+ <div className="rounded-xl bg-background p-6 text-foreground">
72
+ <Markdown>{CONTRAST_MARKDOWN}</Markdown>
73
+ </div>
74
+ ),
75
+ }
76
+
23
77
  export const Prose: Story = {
24
78
  name: 'Prose',
25
79
  args: {
@@ -0,0 +1,23 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { render } from "@testing-library/react";
3
+ import { Markdown } from "./markdown";
4
+
5
+ describe("Markdown", () => {
6
+ it("uses the tokenized Tangle prose class instead of host typography defaults", () => {
7
+ const { container } = render(<Markdown>{"# Heading\n\nBody with `code`."}</Markdown>);
8
+ const surface = container.firstElementChild;
9
+
10
+ expect(surface).toHaveClass("tangle-prose");
11
+ expect(surface).not.toHaveClass("prose");
12
+ expect(surface).not.toHaveClass("dark:prose-invert");
13
+ });
14
+
15
+ it("keeps inline code on semantic tokens", () => {
16
+ const { container } = render(<Markdown>{"Use `token` values."}</Markdown>);
17
+ const inlineCode = container.querySelector("code");
18
+
19
+ expect(inlineCode).toHaveClass("border-border");
20
+ expect(inlineCode).toHaveClass("bg-card");
21
+ expect(inlineCode).toHaveClass("text-[var(--code-keyword)]");
22
+ });
23
+ });
@@ -17,7 +17,7 @@ export interface MarkdownProps {
17
17
  export const Markdown = memo(({ children, className }: MarkdownProps) => {
18
18
  return (
19
19
  <div
20
- className={cn("prose prose-sm dark:prose-invert max-w-none", className)}
20
+ className={cn("tangle-prose max-w-none text-sm", className)}
21
21
  >
22
22
  <ReactMarkdown
23
23
  remarkPlugins={[remarkGfm]}
@@ -14,6 +14,15 @@ export interface ArtifactPaneProps {
14
14
  children?: ReactNode;
15
15
  className?: string;
16
16
  contentClassName?: string;
17
+ /** Extra classes for the header band (e.g. to align its surface with the host chrome). */
18
+ headerClassName?: string;
19
+ /**
20
+ * Suppress the eyebrow/title/subtitle/meta block. Use when the host already
21
+ * shows the artifact's identity elsewhere (e.g. a surrounding path row), so the
22
+ * pane keeps only its tabs, toolbar, and header actions. The whole header row
23
+ * collapses when nothing else would render in it.
24
+ */
25
+ hideTitleBlock?: boolean;
17
26
  }
18
27
 
19
28
  /**
@@ -33,8 +42,17 @@ export function ArtifactPane({
33
42
  children,
34
43
  className,
35
44
  contentClassName,
45
+ headerClassName,
46
+ hideTitleBlock = false,
36
47
  }: ArtifactPaneProps) {
37
48
  const hasContent = children !== undefined && children !== null;
49
+ // With the title block hidden, still render the top row if there are header
50
+ // actions to place; the block itself just collapses to empty.
51
+ const showTitleRow = !hideTitleBlock || Boolean(headerActions);
52
+ const hasHeader = showTitleRow || Boolean(tabs) || Boolean(toolbar);
53
+ // The toolbar's top divider only reads as a separator when something sits
54
+ // above it; as the header's first row it would double the host's own border.
55
+ const toolbarNeedsDivider = showTitleRow || Boolean(tabs);
38
56
 
39
57
  return (
40
58
  <section
@@ -43,35 +61,43 @@ export function ArtifactPane({
43
61
  className,
44
62
  )}
45
63
  >
46
- <header className="border-b border-border bg-muted/10">
47
- <div className="flex items-start justify-between gap-3 px-3 py-2">
48
- <div className="min-w-0 flex-1">
49
- {eyebrow && (
50
- <div className="mb-1 inline-flex max-w-full items-center px-1 py-0.5 text-[10px] font-bold uppercase tracking-wider text-muted-foreground">
51
- {eyebrow}
64
+ {hasHeader && (
65
+ <header className={cn("border-b border-border bg-muted/10", headerClassName)}>
66
+ {showTitleRow && (
67
+ <div className="flex items-start justify-between gap-3 px-3 py-2">
68
+ <div className="min-w-0 flex-1">
69
+ {!hideTitleBlock && (
70
+ <>
71
+ {eyebrow && (
72
+ <div className="mb-1 inline-flex max-w-full items-center px-1 py-0.5 text-[10px] font-bold uppercase tracking-wider text-muted-foreground">
73
+ {eyebrow}
74
+ </div>
75
+ )}
76
+ <div className="min-w-0 text-[13px] font-medium text-foreground">
77
+ {title}
78
+ </div>
79
+ {(subtitle || meta) && (
80
+ <div className="mt-1 flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 text-xs leading-relaxed text-muted-foreground">
81
+ {subtitle && <span className="truncate">{subtitle}</span>}
82
+ {meta && <span className="flex items-center gap-2">{meta}</span>}
83
+ </div>
84
+ )}
85
+ </>
86
+ )}
52
87
  </div>
53
- )}
54
- <div className="min-w-0 text-[13px] font-medium text-foreground">
55
- {title}
88
+ {headerActions && (
89
+ <div className="flex shrink-0 items-center gap-1.5">{headerActions}</div>
90
+ )}
91
+ </div>
92
+ )}
93
+ {tabs}
94
+ {toolbar && (
95
+ <div className={cn("px-3 py-2", toolbarNeedsDivider && "border-t border-border")}>
96
+ {toolbar}
56
97
  </div>
57
- {(subtitle || meta) && (
58
- <div className="mt-1 flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 text-xs leading-relaxed text-muted-foreground">
59
- {subtitle && <span className="truncate">{subtitle}</span>}
60
- {meta && <span className="flex items-center gap-2">{meta}</span>}
61
- </div>
62
- )}
63
- </div>
64
- {headerActions && (
65
- <div className="flex shrink-0 items-center gap-1.5">{headerActions}</div>
66
98
  )}
67
- </div>
68
- {tabs}
69
- {toolbar && (
70
- <div className="border-t border-border px-3 py-2">
71
- {toolbar}
72
- </div>
73
- )}
74
- </header>
99
+ </header>
100
+ )}
75
101
 
76
102
  <div className={cn("min-h-0 flex-1 overflow-auto", contentClassName)}>
77
103
  {hasContent ? (