@stigmer/react 3.0.9-dev.20260615153829 → 3.0.9-dev.20260616060535

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.
@@ -0,0 +1,24 @@
1
+ import { type ReactNode } from "react";
2
+ /**
3
+ * Resolves a fence info-string to a registered highlight.js language name, or
4
+ * `null` when no grammar is registered for it (the caller then renders the code
5
+ * flat). Case-insensitive and whitespace-tolerant; never guesses.
6
+ *
7
+ * @param language - The raw language from a `language-*` class (e.g. `"go"`).
8
+ * @returns The normalized, registered language name, or `null`.
9
+ */
10
+ export declare function resolveLanguage(language: string | undefined): string | null;
11
+ /**
12
+ * Highlights `code` for the given fence `language` and returns themed React
13
+ * nodes — token `<span>`s carrying `hljs-*` classes that `styles.css` maps to
14
+ * `--stgm-syntax-*`.
15
+ *
16
+ * Returns `null` (so the caller can fall back to flat rendering) when the
17
+ * language has no registered grammar or tokenization throws. Highlighting is
18
+ * synchronous: there is no loading state to handle.
19
+ *
20
+ * @param code - The raw source text of the fenced block.
21
+ * @param language - The fence language (e.g. `"ts"`); unknown → `null`.
22
+ */
23
+ export declare function highlightToReact(code: string, language: string | undefined): ReactNode | null;
24
+ //# sourceMappingURL=code-highlight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-highlight.d.ts","sourceRoot":"","sources":["../../src/internal/code-highlight.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAkFjD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAI3E;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B,SAAS,GAAG,IAAI,CAQlB"}
@@ -0,0 +1,115 @@
1
+ import { Fragment } from "react";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { createLowlight } from "lowlight";
4
+ import { toJsxRuntime } from "hast-util-to-jsx-runtime";
5
+ // Dependency licensing (DD-012): `lowlight` and `hast-util-to-jsx-runtime` are
6
+ // MIT; `highlight.js` is BSD-3-Clause. BSD-3-Clause is a permissive, OSI-approved
7
+ // license, compatible with MIT/Apache-2.0, that imposes no obligations on SDK
8
+ // consumers beyond attribution — so it satisfies DD-012's "MIT or Apache-2.0
9
+ // compatible" rule. Recorded here so the choice is auditable at the point of use.
10
+ import bash from "highlight.js/lib/languages/bash";
11
+ import css from "highlight.js/lib/languages/css";
12
+ import dockerfile from "highlight.js/lib/languages/dockerfile";
13
+ import go from "highlight.js/lib/languages/go";
14
+ import java from "highlight.js/lib/languages/java";
15
+ import javascript from "highlight.js/lib/languages/javascript";
16
+ import json from "highlight.js/lib/languages/json";
17
+ import markdown from "highlight.js/lib/languages/markdown";
18
+ import python from "highlight.js/lib/languages/python";
19
+ import rust from "highlight.js/lib/languages/rust";
20
+ import sql from "highlight.js/lib/languages/sql";
21
+ import typescript from "highlight.js/lib/languages/typescript";
22
+ import xml from "highlight.js/lib/languages/xml";
23
+ import yaml from "highlight.js/lib/languages/yaml";
24
+ /**
25
+ * Syntax highlighter for fenced code blocks in SDK markdown surfaces.
26
+ *
27
+ * This is the single highlighting engine behind the shared `code` override in
28
+ * {@link file://./markdown-components.tsx} — so chat (Streamdown) and the
29
+ * artifact/skill viewers (react-markdown) colorize code identically.
30
+ *
31
+ * The engine emits highlight.js token classes (`hljs-keyword`, `hljs-string`,
32
+ * …) which `styles.css` maps onto the `--stgm-syntax-*` theme tokens. Colors
33
+ * are therefore 100% token-driven: they track the host's preset and color-mode
34
+ * with no hardcoded values, exactly like the CodeMirror YAML editor.
35
+ *
36
+ * **Eager, not lazy.** Highlighting is on the core path (almost every
37
+ * `SessionViewer` consumer renders agent messages containing code), so DD-013's
38
+ * lazy pattern (for rarely-used heavy deps) does not apply. The grammars are
39
+ * imported eagerly; because this module is only reachable through the markdown
40
+ * components, normal tree-shaking still keeps it out of bundles that never
41
+ * render markdown.
42
+ *
43
+ * **Curated grammar set.** Only the languages agents commonly emit are
44
+ * registered, to keep the payload small. Anything else falls back to flat
45
+ * rendering (see {@link resolveLanguage}) — a deterministic choice, never
46
+ * highlight.js auto-detection, consistent with this codebase's avoidance of
47
+ * fuzzy heuristics.
48
+ */
49
+ const lowlight = createLowlight({
50
+ bash,
51
+ css,
52
+ dockerfile,
53
+ go,
54
+ java,
55
+ javascript,
56
+ json,
57
+ markdown,
58
+ python,
59
+ rust,
60
+ sql,
61
+ typescript,
62
+ xml,
63
+ yaml,
64
+ });
65
+ // Fence info-strings agents use that aren't already registered as grammar
66
+ // aliases by highlight.js. `xml` is highlight.js's grammar for HTML markup.
67
+ lowlight.registerAlias({
68
+ bash: ["sh", "shell", "zsh", "console"],
69
+ dockerfile: ["docker"],
70
+ javascript: ["js", "jsx", "mjs", "cjs"],
71
+ markdown: ["md"],
72
+ python: ["py"],
73
+ typescript: ["ts", "tsx"],
74
+ xml: ["html"],
75
+ yaml: ["yml"],
76
+ });
77
+ const jsxRuntime = { Fragment, jsx, jsxs };
78
+ /**
79
+ * Resolves a fence info-string to a registered highlight.js language name, or
80
+ * `null` when no grammar is registered for it (the caller then renders the code
81
+ * flat). Case-insensitive and whitespace-tolerant; never guesses.
82
+ *
83
+ * @param language - The raw language from a `language-*` class (e.g. `"go"`).
84
+ * @returns The normalized, registered language name, or `null`.
85
+ */
86
+ export function resolveLanguage(language) {
87
+ if (!language)
88
+ return null;
89
+ const name = language.trim().toLowerCase();
90
+ return name && lowlight.registered(name) ? name : null;
91
+ }
92
+ /**
93
+ * Highlights `code` for the given fence `language` and returns themed React
94
+ * nodes — token `<span>`s carrying `hljs-*` classes that `styles.css` maps to
95
+ * `--stgm-syntax-*`.
96
+ *
97
+ * Returns `null` (so the caller can fall back to flat rendering) when the
98
+ * language has no registered grammar or tokenization throws. Highlighting is
99
+ * synchronous: there is no loading state to handle.
100
+ *
101
+ * @param code - The raw source text of the fenced block.
102
+ * @param language - The fence language (e.g. `"ts"`); unknown → `null`.
103
+ */
104
+ export function highlightToReact(code, language) {
105
+ const resolved = resolveLanguage(language);
106
+ if (resolved === null)
107
+ return null;
108
+ try {
109
+ return toJsxRuntime(lowlight.highlight(resolved, code), jsxRuntime);
110
+ }
111
+ catch {
112
+ return null;
113
+ }
114
+ }
115
+ //# sourceMappingURL=code-highlight.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-highlight.js","sourceRoot":"","sources":["../../src/internal/code-highlight.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAkB,MAAM,OAAO,CAAC;AACjD,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAExD,+EAA+E;AAC/E,kFAAkF;AAClF,8EAA8E;AAC9E,6EAA6E;AAC7E,kFAAkF;AAClF,OAAO,IAAI,MAAM,iCAAiC,CAAC;AACnD,OAAO,GAAG,MAAM,gCAAgC,CAAC;AACjD,OAAO,UAAU,MAAM,uCAAuC,CAAC;AAC/D,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAC/C,OAAO,IAAI,MAAM,iCAAiC,CAAC;AACnD,OAAO,UAAU,MAAM,uCAAuC,CAAC;AAC/D,OAAO,IAAI,MAAM,iCAAiC,CAAC;AACnD,OAAO,QAAQ,MAAM,qCAAqC,CAAC;AAC3D,OAAO,MAAM,MAAM,mCAAmC,CAAC;AACvD,OAAO,IAAI,MAAM,iCAAiC,CAAC;AACnD,OAAO,GAAG,MAAM,gCAAgC,CAAC;AACjD,OAAO,UAAU,MAAM,uCAAuC,CAAC;AAC/D,OAAO,GAAG,MAAM,gCAAgC,CAAC;AACjD,OAAO,IAAI,MAAM,iCAAiC,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,QAAQ,GAAG,cAAc,CAAC;IAC9B,IAAI;IACJ,GAAG;IACH,UAAU;IACV,EAAE;IACF,IAAI;IACJ,UAAU;IACV,IAAI;IACJ,QAAQ;IACR,MAAM;IACN,IAAI;IACJ,GAAG;IACH,UAAU;IACV,GAAG;IACH,IAAI;CACL,CAAC,CAAC;AAEH,0EAA0E;AAC1E,4EAA4E;AAC5E,QAAQ,CAAC,aAAa,CAAC;IACrB,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,CAAC;IACvC,UAAU,EAAE,CAAC,QAAQ,CAAC;IACtB,UAAU,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC;IACvC,QAAQ,EAAE,CAAC,IAAI,CAAC;IAChB,MAAM,EAAE,CAAC,IAAI,CAAC;IACd,UAAU,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC;IACzB,GAAG,EAAE,CAAC,MAAM,CAAC;IACb,IAAI,EAAE,CAAC,KAAK,CAAC;CACd,CAAC,CAAC;AAEH,MAAM,UAAU,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAW,CAAC;AAEpD;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,QAA4B;IAC1D,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3B,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC3C,OAAO,IAAI,IAAI,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;AACzD,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB,CAC9B,IAAY,EACZ,QAA4B;IAE5B,MAAM,QAAQ,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IAC3C,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,QAAQ,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,UAAU,CAAC,CAAC;IACtE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"markdown-components.d.ts","sourceRoot":"","sources":["../../src/internal/markdown-components.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,SAAS,MAAM,YAAY,CAAC;AAKnC;;;GAGG;AACH,eAAO,MAAM,cAAc,sBAAc,CAAC;AAI1C;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAExD;AAgBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGpE;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,EAAE,UA8KjC,CAAC"}
1
+ {"version":3,"file":"markdown-components.d.ts","sourceRoot":"","sources":["../../src/internal/markdown-components.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,SAAS,MAAM,YAAY,CAAC;AAQnC;;;GAGG;AACH,eAAO,MAAM,cAAc,sBAAc,CAAC;AAI1C;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAExD;AAgBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGpE;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,EAAE,UA4LjC,CAAC"}
@@ -1,6 +1,8 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import remarkGfm from "remark-gfm";
3
3
  import { cn } from "@stigmer/theme";
4
+ import { highlightToReact } from "./code-highlight";
5
+ const LANGUAGE_CLASS_PREFIX = "language-";
4
6
  /**
5
7
  * Remark plugins applied to all SDK markdown surfaces.
6
8
  * Includes GFM (tables, strikethrough, autolinks, task lists).
@@ -93,9 +95,18 @@ export const MARKDOWN_COMPONENTS = {
93
95
  return (_jsx("pre", { className: "mb-3 last:mb-0 overflow-x-auto rounded-md bg-muted p-3", ...props, children: children }));
94
96
  },
95
97
  code({ children, className: codeClassName, ...props }) {
96
- const isBlock = typeof codeClassName === "string" && codeClassName.startsWith("language-");
98
+ const isBlock = typeof codeClassName === "string" &&
99
+ codeClassName.startsWith(LANGUAGE_CLASS_PREFIX);
97
100
  if (isBlock) {
98
- return (_jsx("code", { className: cn("font-mono text-xs text-foreground", codeClassName), ...props, children: children }));
101
+ // Tokenize here, in the one component both renderers (Streamdown for
102
+ // chat, react-markdown for artifacts/skills) share, so highlighting is
103
+ // identical everywhere. Highlight only plain-string children — anything
104
+ // else (e.g. a streaming caret node) falls back to flat rendering.
105
+ const language = codeClassName.slice(LANGUAGE_CLASS_PREFIX.length);
106
+ const highlighted = typeof children === "string"
107
+ ? highlightToReact(children, language)
108
+ : null;
109
+ return (_jsx("code", { className: cn("hljs font-mono text-xs text-foreground", codeClassName), ...props, children: highlighted ?? children }));
99
110
  }
100
111
  return (_jsx("code", { className: "font-mono text-xs bg-muted px-1 py-0.5 rounded text-foreground", ...props, children: children }));
101
112
  },
@@ -1 +1 @@
1
- {"version":3,"file":"markdown-components.js","sourceRoot":"","sources":["../../src/internal/markdown-components.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"markdown-components.js","sourceRoot":"","sources":["../../src/internal/markdown-components.tsx"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stigmer/react",
3
- "version": "3.0.9-dev.20260615153829",
3
+ "version": "3.0.9-dev.20260616060535",
4
4
  "description": "React provider and client hook for the Stigmer platform SDK",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -37,9 +37,12 @@
37
37
  "dependencies": {
38
38
  "@bufbuild/protovalidate": "^1.1.1",
39
39
  "@dagrejs/dagre": "^1.1.4",
40
- "@stigmer/theme": "3.0.9-dev.20260615153829",
40
+ "@stigmer/theme": "3.0.9-dev.20260616060535",
41
41
  "diff": "^8.0.3",
42
42
  "fflate": "^0.8.2",
43
+ "hast-util-to-jsx-runtime": "^2.3.6",
44
+ "highlight.js": "^11.11.1",
45
+ "lowlight": "^3.3.0",
43
46
  "react-markdown": "^10.1.0",
44
47
  "remark-gfm": "^4.0.1",
45
48
  "sonner": "^2.0.0",
@@ -57,8 +60,8 @@
57
60
  "@codemirror/state": "^6.0.0",
58
61
  "@codemirror/view": "^6.0.0",
59
62
  "@lezer/highlight": "^1.0.0",
60
- "@stigmer/protos": "3.0.9-dev.20260615153829",
61
- "@stigmer/sdk": "3.0.9-dev.20260615153829",
63
+ "@stigmer/protos": "3.0.9-dev.20260616060535",
64
+ "@stigmer/sdk": "3.0.9-dev.20260616060535",
62
65
  "@tanstack/react-table": "^8.20.0",
63
66
  "@xyflow/react": "^12.0.0",
64
67
  "elkjs": "^0.9.0",
@@ -44,4 +44,19 @@ describe("ArtifactContentRenderer — markdown", () => {
44
44
  expect(container.querySelector("h1")).not.toBeNull();
45
45
  expect(container.querySelector("pre")).toBeNull();
46
46
  });
47
+
48
+ it("syntax-highlights fenced code blocks via the shared seam", () => {
49
+ const md = "# Notes\n\n```go\nfunc main() {}\n```\n";
50
+ const { container } = render(
51
+ <ArtifactContentRenderer content={md} fileName="notes.md" />,
52
+ );
53
+
54
+ // Same shared `MARKDOWN_COMPONENTS.code` override the chat stream uses, so
55
+ // the react-markdown artifact path colorizes code identically.
56
+ const code = container.querySelector("code.hljs");
57
+ expect(code).not.toBeNull();
58
+ expect(
59
+ container.querySelectorAll('span[class*="hljs-"]').length,
60
+ ).toBeGreaterThan(0);
61
+ });
47
62
  });
@@ -153,6 +153,22 @@ describe("MessageEntry — AI messages (Streamdown)", () => {
153
153
  const article = queryArticle(container, "AI response");
154
154
  expect(article!.querySelector("pre")).not.toBeNull();
155
155
  });
156
+
157
+ it("syntax-highlights fenced code in the chat stream (Streamdown path)", () => {
158
+ const msg = makeMessage(
159
+ MessageType.MESSAGE_AI,
160
+ "```go\nfunc main() {}\n```",
161
+ );
162
+ const { container } = render(<MessageEntry message={msg} />);
163
+
164
+ const article = queryArticle(container, "AI response");
165
+ // Streamdown routes fenced code through the shared MARKDOWN_COMPONENTS.code
166
+ // override, so chat highlights identically to the artifact viewer.
167
+ expect(article!.querySelector("code.hljs")).not.toBeNull();
168
+ expect(
169
+ article!.querySelectorAll('span[class*="hljs-"]').length,
170
+ ).toBeGreaterThan(0);
171
+ });
156
172
  });
157
173
 
158
174
  // ---------------------------------------------------------------------------
@@ -0,0 +1,59 @@
1
+ import { describe, it, expect, afterEach } from "vitest";
2
+ import { render, cleanup } from "@testing-library/react";
3
+ import { resolveLanguage, highlightToReact } from "../code-highlight";
4
+
5
+ afterEach(cleanup);
6
+
7
+ describe("resolveLanguage", () => {
8
+ it.each([
9
+ ["a registered name", "go", "go"],
10
+ ["uppercase is normalized", "GO", "go"],
11
+ ["surrounding whitespace is trimmed", " yaml ", "yaml"],
12
+ ["a registered alias (ts → typescript)", "ts", "ts"],
13
+ ["a registered alias (tsx)", "tsx", "tsx"],
14
+ ["a registered alias (html → xml)", "html", "html"],
15
+ ["a registered alias (sh → bash)", "sh", "sh"],
16
+ ])("resolves %s", (_label, input, expected) => {
17
+ expect(resolveLanguage(input)).toBe(expected);
18
+ });
19
+
20
+ it.each([
21
+ ["an unregistered language", "hcl"],
22
+ ["a nonsense language", "klingon"],
23
+ ["an empty string", ""],
24
+ ["whitespace only", " "],
25
+ ["undefined", undefined],
26
+ ])("returns null for %s", (_label, input) => {
27
+ expect(resolveLanguage(input)).toBeNull();
28
+ });
29
+ });
30
+
31
+ describe("highlightToReact", () => {
32
+ it("produces hljs token spans for a known language", () => {
33
+ const node = highlightToReact("func main() {}", "go");
34
+ expect(node).not.toBeNull();
35
+
36
+ const { container } = render(<code>{node}</code>);
37
+ // `func` is a Go keyword → at least one tokenized span must appear.
38
+ expect(
39
+ container.querySelectorAll('span[class*="hljs-"]').length,
40
+ ).toBeGreaterThan(0);
41
+ });
42
+
43
+ it("highlights via an alias (ts)", () => {
44
+ const node = highlightToReact("const x: number = 1;", "ts");
45
+ expect(node).not.toBeNull();
46
+
47
+ const { container } = render(<code>{node}</code>);
48
+ expect(
49
+ container.querySelectorAll('span[class*="hljs-"]').length,
50
+ ).toBeGreaterThan(0);
51
+ });
52
+
53
+ it.each([
54
+ ["an unregistered language", "hcl"],
55
+ ["no language", undefined],
56
+ ])("returns null for %s (caller falls back to flat)", (_label, language) => {
57
+ expect(highlightToReact("anything at all", language)).toBeNull();
58
+ });
59
+ });
@@ -1,5 +1,18 @@
1
- import { describe, it, expect } from "vitest";
2
- import { unwrapEnclosingMarkdownFence } from "../markdown-components";
1
+ import { describe, it, expect, afterEach } from "vitest";
2
+ import { render, cleanup } from "@testing-library/react";
3
+ import type { ComponentType, ReactNode } from "react";
4
+ import {
5
+ MARKDOWN_COMPONENTS,
6
+ unwrapEnclosingMarkdownFence,
7
+ } from "../markdown-components";
8
+
9
+ afterEach(cleanup);
10
+
11
+ /** The shared `code` override, typed for direct rendering in tests. */
12
+ const CodeComponent = MARKDOWN_COMPONENTS.code as ComponentType<{
13
+ className?: string;
14
+ children?: ReactNode;
15
+ }>;
3
16
 
4
17
  describe("unwrapEnclosingMarkdownFence", () => {
5
18
  describe("unwraps a whole-message markdown fence", () => {
@@ -51,3 +64,56 @@ describe("unwrapEnclosingMarkdownFence", () => {
51
64
  expect(unwrapEnclosingMarkdownFence(once)).toBe(once);
52
65
  });
53
66
  });
67
+
68
+ describe("MARKDOWN_COMPONENTS.code (shared highlight seam)", () => {
69
+ it("syntax-highlights a fenced block with a known language", () => {
70
+ const { container } = render(
71
+ <CodeComponent className="language-go">
72
+ {"func main() {}"}
73
+ </CodeComponent>,
74
+ );
75
+
76
+ const code = container.querySelector("code");
77
+ expect(code).not.toBeNull();
78
+ expect(code!.className).toContain("hljs");
79
+ expect(code!.className).toContain("language-go");
80
+ expect(
81
+ container.querySelectorAll('span[class*="hljs-"]').length,
82
+ ).toBeGreaterThan(0);
83
+ });
84
+
85
+ it("falls back to flat text for an unknown language (no token spans)", () => {
86
+ const { container } = render(
87
+ <CodeComponent className="language-hcl">
88
+ {'resource "x" {}'}
89
+ </CodeComponent>,
90
+ );
91
+
92
+ const code = container.querySelector("code");
93
+ expect(code).not.toBeNull();
94
+ expect(code!.textContent).toContain('resource "x" {}');
95
+ expect(container.querySelectorAll('span[class*="hljs-"]').length).toBe(0);
96
+ });
97
+
98
+ it("falls back to flat for non-string children (e.g. a streaming caret)", () => {
99
+ const { container } = render(
100
+ <CodeComponent className="language-go">
101
+ <span data-testid="caret">x</span>
102
+ </CodeComponent>,
103
+ );
104
+
105
+ // Renders children untouched, no throw, no tokenization attempted.
106
+ expect(container.querySelector('[data-testid="caret"]')).not.toBeNull();
107
+ expect(container.querySelectorAll('span[class*="hljs-"]').length).toBe(0);
108
+ });
109
+
110
+ it("leaves inline code unhighlighted and unboxed by hljs", () => {
111
+ const { container } = render(<CodeComponent>{"inlineToken"}</CodeComponent>);
112
+
113
+ const code = container.querySelector("code");
114
+ expect(code).not.toBeNull();
115
+ expect(code!.className).not.toContain("hljs");
116
+ expect(code!.className).toContain("bg-muted");
117
+ expect(code!.textContent).toBe("inlineToken");
118
+ });
119
+ });
@@ -0,0 +1,120 @@
1
+ import { Fragment, type ReactNode } from "react";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { createLowlight } from "lowlight";
4
+ import { toJsxRuntime } from "hast-util-to-jsx-runtime";
5
+
6
+ // Dependency licensing (DD-012): `lowlight` and `hast-util-to-jsx-runtime` are
7
+ // MIT; `highlight.js` is BSD-3-Clause. BSD-3-Clause is a permissive, OSI-approved
8
+ // license, compatible with MIT/Apache-2.0, that imposes no obligations on SDK
9
+ // consumers beyond attribution — so it satisfies DD-012's "MIT or Apache-2.0
10
+ // compatible" rule. Recorded here so the choice is auditable at the point of use.
11
+ import bash from "highlight.js/lib/languages/bash";
12
+ import css from "highlight.js/lib/languages/css";
13
+ import dockerfile from "highlight.js/lib/languages/dockerfile";
14
+ import go from "highlight.js/lib/languages/go";
15
+ import java from "highlight.js/lib/languages/java";
16
+ import javascript from "highlight.js/lib/languages/javascript";
17
+ import json from "highlight.js/lib/languages/json";
18
+ import markdown from "highlight.js/lib/languages/markdown";
19
+ import python from "highlight.js/lib/languages/python";
20
+ import rust from "highlight.js/lib/languages/rust";
21
+ import sql from "highlight.js/lib/languages/sql";
22
+ import typescript from "highlight.js/lib/languages/typescript";
23
+ import xml from "highlight.js/lib/languages/xml";
24
+ import yaml from "highlight.js/lib/languages/yaml";
25
+
26
+ /**
27
+ * Syntax highlighter for fenced code blocks in SDK markdown surfaces.
28
+ *
29
+ * This is the single highlighting engine behind the shared `code` override in
30
+ * {@link file://./markdown-components.tsx} — so chat (Streamdown) and the
31
+ * artifact/skill viewers (react-markdown) colorize code identically.
32
+ *
33
+ * The engine emits highlight.js token classes (`hljs-keyword`, `hljs-string`,
34
+ * …) which `styles.css` maps onto the `--stgm-syntax-*` theme tokens. Colors
35
+ * are therefore 100% token-driven: they track the host's preset and color-mode
36
+ * with no hardcoded values, exactly like the CodeMirror YAML editor.
37
+ *
38
+ * **Eager, not lazy.** Highlighting is on the core path (almost every
39
+ * `SessionViewer` consumer renders agent messages containing code), so DD-013's
40
+ * lazy pattern (for rarely-used heavy deps) does not apply. The grammars are
41
+ * imported eagerly; because this module is only reachable through the markdown
42
+ * components, normal tree-shaking still keeps it out of bundles that never
43
+ * render markdown.
44
+ *
45
+ * **Curated grammar set.** Only the languages agents commonly emit are
46
+ * registered, to keep the payload small. Anything else falls back to flat
47
+ * rendering (see {@link resolveLanguage}) — a deterministic choice, never
48
+ * highlight.js auto-detection, consistent with this codebase's avoidance of
49
+ * fuzzy heuristics.
50
+ */
51
+ const lowlight = createLowlight({
52
+ bash,
53
+ css,
54
+ dockerfile,
55
+ go,
56
+ java,
57
+ javascript,
58
+ json,
59
+ markdown,
60
+ python,
61
+ rust,
62
+ sql,
63
+ typescript,
64
+ xml,
65
+ yaml,
66
+ });
67
+
68
+ // Fence info-strings agents use that aren't already registered as grammar
69
+ // aliases by highlight.js. `xml` is highlight.js's grammar for HTML markup.
70
+ lowlight.registerAlias({
71
+ bash: ["sh", "shell", "zsh", "console"],
72
+ dockerfile: ["docker"],
73
+ javascript: ["js", "jsx", "mjs", "cjs"],
74
+ markdown: ["md"],
75
+ python: ["py"],
76
+ typescript: ["ts", "tsx"],
77
+ xml: ["html"],
78
+ yaml: ["yml"],
79
+ });
80
+
81
+ const jsxRuntime = { Fragment, jsx, jsxs } as const;
82
+
83
+ /**
84
+ * Resolves a fence info-string to a registered highlight.js language name, or
85
+ * `null` when no grammar is registered for it (the caller then renders the code
86
+ * flat). Case-insensitive and whitespace-tolerant; never guesses.
87
+ *
88
+ * @param language - The raw language from a `language-*` class (e.g. `"go"`).
89
+ * @returns The normalized, registered language name, or `null`.
90
+ */
91
+ export function resolveLanguage(language: string | undefined): string | null {
92
+ if (!language) return null;
93
+ const name = language.trim().toLowerCase();
94
+ return name && lowlight.registered(name) ? name : null;
95
+ }
96
+
97
+ /**
98
+ * Highlights `code` for the given fence `language` and returns themed React
99
+ * nodes — token `<span>`s carrying `hljs-*` classes that `styles.css` maps to
100
+ * `--stgm-syntax-*`.
101
+ *
102
+ * Returns `null` (so the caller can fall back to flat rendering) when the
103
+ * language has no registered grammar or tokenization throws. Highlighting is
104
+ * synchronous: there is no loading state to handle.
105
+ *
106
+ * @param code - The raw source text of the fenced block.
107
+ * @param language - The fence language (e.g. `"ts"`); unknown → `null`.
108
+ */
109
+ export function highlightToReact(
110
+ code: string,
111
+ language: string | undefined,
112
+ ): ReactNode | null {
113
+ const resolved = resolveLanguage(language);
114
+ if (resolved === null) return null;
115
+ try {
116
+ return toJsxRuntime(lowlight.highlight(resolved, code), jsxRuntime);
117
+ } catch {
118
+ return null;
119
+ }
120
+ }
@@ -2,6 +2,9 @@ import type { ComponentProps, JSX } from "react";
2
2
  import type { Components } from "react-markdown";
3
3
  import remarkGfm from "remark-gfm";
4
4
  import { cn } from "@stigmer/theme";
5
+ import { highlightToReact } from "./code-highlight";
6
+
7
+ const LANGUAGE_CLASS_PREFIX = "language-";
5
8
 
6
9
  type MdProps<T extends keyof JSX.IntrinsicElements> = ComponentProps<T>;
7
10
 
@@ -163,15 +166,29 @@ export const MARKDOWN_COMPONENTS: Components = {
163
166
 
164
167
  code({ children, className: codeClassName, ...props }: MdProps<"code">) {
165
168
  const isBlock =
166
- typeof codeClassName === "string" && codeClassName.startsWith("language-");
169
+ typeof codeClassName === "string" &&
170
+ codeClassName.startsWith(LANGUAGE_CLASS_PREFIX);
167
171
 
168
172
  if (isBlock) {
173
+ // Tokenize here, in the one component both renderers (Streamdown for
174
+ // chat, react-markdown for artifacts/skills) share, so highlighting is
175
+ // identical everywhere. Highlight only plain-string children — anything
176
+ // else (e.g. a streaming caret node) falls back to flat rendering.
177
+ const language = codeClassName.slice(LANGUAGE_CLASS_PREFIX.length);
178
+ const highlighted =
179
+ typeof children === "string"
180
+ ? highlightToReact(children, language)
181
+ : null;
182
+
169
183
  return (
170
184
  <code
171
- className={cn("font-mono text-xs text-foreground", codeClassName)}
185
+ className={cn(
186
+ "hljs font-mono text-xs text-foreground",
187
+ codeClassName,
188
+ )}
172
189
  {...props}
173
190
  >
174
- {children}
191
+ {highlighted ?? children}
175
192
  </code>
176
193
  );
177
194
  }