@agent-native/core 0.39.2 → 0.40.1

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 (154) hide show
  1. package/README.md +1 -1
  2. package/dist/action.js +12 -0
  3. package/dist/action.js.map +1 -1
  4. package/dist/cli/create.d.ts.map +1 -1
  5. package/dist/cli/create.js +5 -1
  6. package/dist/cli/create.js.map +1 -1
  7. package/dist/cli/skills.d.ts +4 -3
  8. package/dist/cli/skills.d.ts.map +1 -1
  9. package/dist/cli/skills.js +756 -694
  10. package/dist/cli/skills.js.map +1 -1
  11. package/dist/client/blocks/AiEditableField.d.ts +8 -0
  12. package/dist/client/blocks/AiEditableField.d.ts.map +1 -0
  13. package/dist/client/blocks/AiEditableField.js +10 -0
  14. package/dist/client/blocks/AiEditableField.js.map +1 -0
  15. package/dist/client/blocks/BlockView.d.ts +3 -3
  16. package/dist/client/blocks/BlockView.d.ts.map +1 -1
  17. package/dist/client/blocks/BlockView.js +15 -3
  18. package/dist/client/blocks/BlockView.js.map +1 -1
  19. package/dist/client/blocks/SchemaBlockEditor.js +2 -2
  20. package/dist/client/blocks/SchemaBlockEditor.js.map +1 -1
  21. package/dist/client/blocks/index.d.ts +5 -2
  22. package/dist/client/blocks/index.d.ts.map +1 -1
  23. package/dist/client/blocks/index.js +6 -3
  24. package/dist/client/blocks/index.js.map +1 -1
  25. package/dist/client/blocks/library/ApiEndpointBlock.d.ts.map +1 -1
  26. package/dist/client/blocks/library/ApiEndpointBlock.js +20 -6
  27. package/dist/client/blocks/library/ApiEndpointBlock.js.map +1 -1
  28. package/dist/client/blocks/library/DiffBlock.d.ts +29 -0
  29. package/dist/client/blocks/library/DiffBlock.d.ts.map +1 -1
  30. package/dist/client/blocks/library/DiffBlock.js +190 -30
  31. package/dist/client/blocks/library/DiffBlock.js.map +1 -1
  32. package/dist/client/blocks/library/FileTreeBlock.d.ts.map +1 -1
  33. package/dist/client/blocks/library/FileTreeBlock.js +46 -7
  34. package/dist/client/blocks/library/FileTreeBlock.js.map +1 -1
  35. package/dist/client/blocks/library/HighlightedCode.d.ts +10 -0
  36. package/dist/client/blocks/library/HighlightedCode.d.ts.map +1 -0
  37. package/dist/client/blocks/library/HighlightedCode.js +92 -0
  38. package/dist/client/blocks/library/HighlightedCode.js.map +1 -0
  39. package/dist/client/blocks/library/JsonExplorerBlock.d.ts +9 -4
  40. package/dist/client/blocks/library/JsonExplorerBlock.d.ts.map +1 -1
  41. package/dist/client/blocks/library/JsonExplorerBlock.js +66 -30
  42. package/dist/client/blocks/library/JsonExplorerBlock.js.map +1 -1
  43. package/dist/client/blocks/library/MermaidBlock.d.ts.map +1 -1
  44. package/dist/client/blocks/library/MermaidBlock.js +73 -44
  45. package/dist/client/blocks/library/MermaidBlock.js.map +1 -1
  46. package/dist/client/blocks/library/OpenApiSpecBlock.d.ts.map +1 -1
  47. package/dist/client/blocks/library/OpenApiSpecBlock.js +3 -2
  48. package/dist/client/blocks/library/OpenApiSpecBlock.js.map +1 -1
  49. package/dist/client/blocks/library/checklist.d.ts.map +1 -1
  50. package/dist/client/blocks/library/checklist.js +1 -0
  51. package/dist/client/blocks/library/checklist.js.map +1 -1
  52. package/dist/client/blocks/library/code-tabs.d.ts.map +1 -1
  53. package/dist/client/blocks/library/code-tabs.js +183 -102
  54. package/dist/client/blocks/library/code-tabs.js.map +1 -1
  55. package/dist/client/blocks/library/columns.config.d.ts +60 -0
  56. package/dist/client/blocks/library/columns.config.d.ts.map +1 -0
  57. package/dist/client/blocks/library/columns.config.js +37 -0
  58. package/dist/client/blocks/library/columns.config.js.map +1 -0
  59. package/dist/client/blocks/library/columns.d.ts +25 -0
  60. package/dist/client/blocks/library/columns.d.ts.map +1 -0
  61. package/dist/client/blocks/library/columns.js +199 -0
  62. package/dist/client/blocks/library/columns.js.map +1 -0
  63. package/dist/client/blocks/library/dev-doc-ui.d.ts +2 -1
  64. package/dist/client/blocks/library/dev-doc-ui.d.ts.map +1 -1
  65. package/dist/client/blocks/library/dev-doc-ui.js +2 -1
  66. package/dist/client/blocks/library/dev-doc-ui.js.map +1 -1
  67. package/dist/client/blocks/library/html.d.ts +1 -1
  68. package/dist/client/blocks/library/html.d.ts.map +1 -1
  69. package/dist/client/blocks/library/html.js +34 -4
  70. package/dist/client/blocks/library/html.js.map +1 -1
  71. package/dist/client/blocks/library/json-explorer.config.d.ts +3 -1
  72. package/dist/client/blocks/library/json-explorer.config.d.ts.map +1 -1
  73. package/dist/client/blocks/library/json-explorer.config.js +30 -1
  74. package/dist/client/blocks/library/json-explorer.config.js.map +1 -1
  75. package/dist/client/blocks/library/server-specs.d.ts.map +1 -1
  76. package/dist/client/blocks/library/server-specs.js +13 -3
  77. package/dist/client/blocks/library/server-specs.js.map +1 -1
  78. package/dist/client/blocks/library/specs.d.ts +4 -4
  79. package/dist/client/blocks/library/specs.d.ts.map +1 -1
  80. package/dist/client/blocks/library/specs.js +21 -16
  81. package/dist/client/blocks/library/specs.js.map +1 -1
  82. package/dist/client/blocks/library/table.config.d.ts +3 -0
  83. package/dist/client/blocks/library/table.config.d.ts.map +1 -1
  84. package/dist/client/blocks/library/table.config.js +13 -1
  85. package/dist/client/blocks/library/table.config.js.map +1 -1
  86. package/dist/client/blocks/library/table.d.ts.map +1 -1
  87. package/dist/client/blocks/library/table.js +90 -9
  88. package/dist/client/blocks/library/table.js.map +1 -1
  89. package/dist/client/blocks/library/tabs.config.d.ts +16 -8
  90. package/dist/client/blocks/library/tabs.config.d.ts.map +1 -1
  91. package/dist/client/blocks/library/tabs.config.js +10 -4
  92. package/dist/client/blocks/library/tabs.config.js.map +1 -1
  93. package/dist/client/blocks/library/tabs.d.ts.map +1 -1
  94. package/dist/client/blocks/library/tabs.js +146 -21
  95. package/dist/client/blocks/library/tabs.js.map +1 -1
  96. package/dist/client/blocks/server.d.ts +2 -1
  97. package/dist/client/blocks/server.d.ts.map +1 -1
  98. package/dist/client/blocks/server.js +1 -0
  99. package/dist/client/blocks/server.js.map +1 -1
  100. package/dist/client/blocks/types.d.ts +99 -9
  101. package/dist/client/blocks/types.d.ts.map +1 -1
  102. package/dist/client/blocks/types.js.map +1 -1
  103. package/dist/client/index.d.ts +1 -1
  104. package/dist/client/index.d.ts.map +1 -1
  105. package/dist/client/index.js +2 -2
  106. package/dist/client/index.js.map +1 -1
  107. package/dist/client/rich-markdown-editor/BubbleToolbar.d.ts.map +1 -1
  108. package/dist/client/rich-markdown-editor/BubbleToolbar.js +13 -3
  109. package/dist/client/rich-markdown-editor/BubbleToolbar.js.map +1 -1
  110. package/dist/client/rich-markdown-editor/DragHandle.d.ts +49 -4
  111. package/dist/client/rich-markdown-editor/DragHandle.d.ts.map +1 -1
  112. package/dist/client/rich-markdown-editor/DragHandle.js +656 -88
  113. package/dist/client/rich-markdown-editor/DragHandle.js.map +1 -1
  114. package/dist/client/rich-markdown-editor/RegistryBlockNode.d.ts +10 -1
  115. package/dist/client/rich-markdown-editor/RegistryBlockNode.d.ts.map +1 -1
  116. package/dist/client/rich-markdown-editor/RegistryBlockNode.js +180 -15
  117. package/dist/client/rich-markdown-editor/RegistryBlockNode.js.map +1 -1
  118. package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts +2 -1
  119. package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts.map +1 -1
  120. package/dist/client/rich-markdown-editor/SharedRichEditor.js +3 -1
  121. package/dist/client/rich-markdown-editor/SharedRichEditor.js.map +1 -1
  122. package/dist/client/rich-markdown-editor/SlashCommandMenu.d.ts +5 -0
  123. package/dist/client/rich-markdown-editor/SlashCommandMenu.d.ts.map +1 -1
  124. package/dist/client/rich-markdown-editor/SlashCommandMenu.js +33 -5
  125. package/dist/client/rich-markdown-editor/SlashCommandMenu.js.map +1 -1
  126. package/dist/client/rich-markdown-editor/index.d.ts +3 -3
  127. package/dist/client/rich-markdown-editor/index.d.ts.map +1 -1
  128. package/dist/client/rich-markdown-editor/index.js +2 -2
  129. package/dist/client/rich-markdown-editor/index.js.map +1 -1
  130. package/dist/client/rich-markdown-editor/registrySlashCommands.d.ts +14 -0
  131. package/dist/client/rich-markdown-editor/registrySlashCommands.d.ts.map +1 -1
  132. package/dist/client/rich-markdown-editor/registrySlashCommands.js +38 -0
  133. package/dist/client/rich-markdown-editor/registrySlashCommands.js.map +1 -1
  134. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts +1 -0
  135. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
  136. package/dist/client/rich-markdown-editor/useCollabReconcile.js +4 -0
  137. package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
  138. package/dist/db/client.d.ts.map +1 -1
  139. package/dist/db/client.js +17 -1
  140. package/dist/db/client.js.map +1 -1
  141. package/dist/deploy/build.js +68 -0
  142. package/dist/deploy/build.js.map +1 -1
  143. package/dist/sharing/access.d.ts +4 -2
  144. package/dist/sharing/access.d.ts.map +1 -1
  145. package/dist/sharing/access.js +8 -3
  146. package/dist/sharing/access.js.map +1 -1
  147. package/dist/sharing/actions/set-resource-visibility.d.ts.map +1 -1
  148. package/dist/sharing/actions/set-resource-visibility.js +2 -3
  149. package/dist/sharing/actions/set-resource-visibility.js.map +1 -1
  150. package/dist/sharing/registry.d.ts +13 -0
  151. package/dist/sharing/registry.d.ts.map +1 -1
  152. package/dist/sharing/registry.js.map +1 -1
  153. package/dist/styles/rich-markdown-editor.css +15 -0
  154. package/package.json +16 -1
@@ -0,0 +1,92 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { cn } from "../../utils.js";
4
+ let highlighterLoader = null;
5
+ function loadHighlighter() {
6
+ if (!highlighterLoader) {
7
+ highlighterLoader = (async () => {
8
+ const [{ createHighlighterCore }, { createOnigurumaEngine }] = await Promise.all([
9
+ import("shiki/core"),
10
+ import("shiki/engine/oniguruma"),
11
+ ]);
12
+ return createHighlighterCore({
13
+ themes: [
14
+ import("shiki/themes/github-light-default.mjs"),
15
+ import("shiki/themes/github-dark-default.mjs"),
16
+ ],
17
+ langs: [
18
+ import("shiki/langs/javascript.mjs"),
19
+ import("shiki/langs/typescript.mjs"),
20
+ import("shiki/langs/jsx.mjs"),
21
+ import("shiki/langs/tsx.mjs"),
22
+ import("shiki/langs/json.mjs"),
23
+ import("shiki/langs/css.mjs"),
24
+ import("shiki/langs/html.mjs"),
25
+ import("shiki/langs/markdown.mjs"),
26
+ import("shiki/langs/bash.mjs"),
27
+ import("shiki/langs/shellscript.mjs"),
28
+ import("shiki/langs/python.mjs"),
29
+ import("shiki/langs/yaml.mjs"),
30
+ import("shiki/langs/sql.mjs"),
31
+ ],
32
+ engine: createOnigurumaEngine(import("shiki/wasm")),
33
+ });
34
+ })().catch((error) => {
35
+ highlighterLoader = null;
36
+ throw error;
37
+ });
38
+ }
39
+ return highlighterLoader;
40
+ }
41
+ const LANG_ALIASES = {
42
+ js: "javascript",
43
+ ts: "typescript",
44
+ sh: "bash",
45
+ shell: "bash",
46
+ zsh: "bash",
47
+ py: "python",
48
+ yml: "yaml",
49
+ md: "markdown",
50
+ bq: "sql",
51
+ bigquery: "sql",
52
+ };
53
+ export function HighlightedCode({ code, language, }) {
54
+ const [html, setHtml] = useState(null);
55
+ useEffect(() => {
56
+ let cancelled = false;
57
+ loadHighlighter()
58
+ .then((highlighter) => {
59
+ const requested = (language || "text").toLowerCase();
60
+ const resolved = LANG_ALIASES[requested] ?? requested;
61
+ const loaded = highlighter.getLoadedLanguages();
62
+ const lang = loaded.includes(resolved) ? resolved : "text";
63
+ return highlighter.codeToHtml(code, {
64
+ lang,
65
+ themes: {
66
+ light: "github-light-default",
67
+ dark: "github-dark-default",
68
+ },
69
+ defaultColor: false,
70
+ });
71
+ })
72
+ .then((out) => {
73
+ if (!cancelled)
74
+ setHtml(out);
75
+ })
76
+ .catch(() => {
77
+ if (!cancelled)
78
+ setHtml(null);
79
+ });
80
+ return () => {
81
+ cancelled = true;
82
+ };
83
+ }, [code, language]);
84
+ if (html) {
85
+ return (_jsx("div", { className: "plan-shiki", dangerouslySetInnerHTML: { __html: html } }));
86
+ }
87
+ return (_jsx("pre", { children: _jsx("code", { className: language ? `language-${language}` : undefined, children: code }) }));
88
+ }
89
+ export function CodeSurface({ code, language, className, }) {
90
+ return (_jsx("div", { className: cn("plan-code-surface", className ?? "mt-5"), children: _jsx(HighlightedCode, { code: code, language: language }) }));
91
+ }
92
+ //# sourceMappingURL=HighlightedCode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HighlightedCode.js","sourceRoot":"","sources":["../../../../src/client/blocks/library/HighlightedCode.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useState } from \"react\";\nimport { cn } from \"../../utils.js\";\n\ntype ShikiHighlighter = {\n codeToHtml: (\n code: string,\n options: {\n lang: string;\n themes: { light: string; dark: string };\n defaultColor?: false | \"light\" | \"dark\";\n },\n ) => string | Promise<string>;\n getLoadedLanguages: () => string[];\n};\n\nlet highlighterLoader: Promise<ShikiHighlighter> | null = null;\nfunction loadHighlighter(): Promise<ShikiHighlighter> {\n if (!highlighterLoader) {\n highlighterLoader = (async () => {\n const [{ createHighlighterCore }, { createOnigurumaEngine }] =\n await Promise.all([\n import(\"shiki/core\"),\n import(\"shiki/engine/oniguruma\"),\n ]);\n return createHighlighterCore({\n themes: [\n import(\"shiki/themes/github-light-default.mjs\"),\n import(\"shiki/themes/github-dark-default.mjs\"),\n ],\n langs: [\n import(\"shiki/langs/javascript.mjs\"),\n import(\"shiki/langs/typescript.mjs\"),\n import(\"shiki/langs/jsx.mjs\"),\n import(\"shiki/langs/tsx.mjs\"),\n import(\"shiki/langs/json.mjs\"),\n import(\"shiki/langs/css.mjs\"),\n import(\"shiki/langs/html.mjs\"),\n import(\"shiki/langs/markdown.mjs\"),\n import(\"shiki/langs/bash.mjs\"),\n import(\"shiki/langs/shellscript.mjs\"),\n import(\"shiki/langs/python.mjs\"),\n import(\"shiki/langs/yaml.mjs\"),\n import(\"shiki/langs/sql.mjs\"),\n ],\n engine: createOnigurumaEngine(import(\"shiki/wasm\")),\n }) as unknown as Promise<ShikiHighlighter>;\n })().catch((error) => {\n highlighterLoader = null;\n throw error;\n });\n }\n return highlighterLoader;\n}\n\nconst LANG_ALIASES: Record<string, string> = {\n js: \"javascript\",\n ts: \"typescript\",\n sh: \"bash\",\n shell: \"bash\",\n zsh: \"bash\",\n py: \"python\",\n yml: \"yaml\",\n md: \"markdown\",\n bq: \"sql\",\n bigquery: \"sql\",\n};\n\nexport function HighlightedCode({\n code,\n language,\n}: {\n code: string;\n language?: string;\n}) {\n const [html, setHtml] = useState<string | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n loadHighlighter()\n .then((highlighter) => {\n const requested = (language || \"text\").toLowerCase();\n const resolved = LANG_ALIASES[requested] ?? requested;\n const loaded = highlighter.getLoadedLanguages();\n const lang = loaded.includes(resolved) ? resolved : \"text\";\n return highlighter.codeToHtml(code, {\n lang,\n themes: {\n light: \"github-light-default\",\n dark: \"github-dark-default\",\n },\n defaultColor: false,\n });\n })\n .then((out) => {\n if (!cancelled) setHtml(out as string);\n })\n .catch(() => {\n if (!cancelled) setHtml(null);\n });\n return () => {\n cancelled = true;\n };\n }, [code, language]);\n\n if (html) {\n return (\n <div className=\"plan-shiki\" dangerouslySetInnerHTML={{ __html: html }} />\n );\n }\n return (\n <pre>\n <code className={language ? `language-${language}` : undefined}>\n {code}\n </code>\n </pre>\n );\n}\n\nexport function CodeSurface({\n code,\n language,\n className,\n}: {\n code: string;\n language?: string;\n className?: string;\n}) {\n return (\n <div className={cn(\"plan-code-surface\", className ?? \"mt-5\")}>\n <HighlightedCode code={code} language={language} />\n </div>\n );\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import type { BlockEditProps, BlockReadProps } from "../types.js";
2
- import type { JsonExplorerData } from "./json-explorer.config.js";
2
+ import { type JsonExplorerData } from "./json-explorer.config.js";
3
3
  /**
4
4
  * Read-only renderer for a `json-explorer` block. Parses `data.json` defensively
5
5
  * and renders the collapsible tree; on a parse error it shows the raw payload in
@@ -7,13 +7,18 @@ import type { JsonExplorerData } from "./json-explorer.config.js";
7
7
  * all" control toggles every node at once via a global pulse counter.
8
8
  */
9
9
  export declare function JsonExplorerRead({ data, blockId, title, summary, }: BlockReadProps<JsonExplorerData>): import("react/jsx-runtime").JSX.Element;
10
+ export declare function JsonExplorerSurface({ data, className, label, }: {
11
+ data: Pick<JsonExplorerData, "json" | "collapsedDepth">;
12
+ className?: string;
13
+ label?: string;
14
+ }): import("react/jsx-runtime").JSX.Element;
10
15
  /**
11
16
  * Panel editor for a `json-explorer` block: a monospace textarea bound to the
12
17
  * raw `json`, a "Format" button that pretty-prints via `JSON.parse` →
13
18
  * `JSON.stringify(_, null, 2)` (guarded — shows an INLINE error, never
14
- * `window.alert`), a `collapsedDepth` number input, and a `title` input. Renders
15
- * BARE content (no `<section>`); the registry's panel surface supplies the
16
- * popover chrome.
19
+ * `window.alert`), an auto-expand depth picker/input, and a `title` input.
20
+ * Renders BARE content (no `<section>`); the registry's panel surface supplies
21
+ * the popover chrome.
17
22
  */
18
23
  export declare function JsonExplorerEdit({ data, onChange, editable, }: BlockEditProps<JsonExplorerData>): import("react/jsx-runtime").JSX.Element;
19
24
  //# sourceMappingURL=JsonExplorerBlock.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"JsonExplorerBlock.d.ts","sourceRoot":"","sources":["../../../../src/client/blocks/library/JsonExplorerBlock.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAwOlE;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,IAAI,EACJ,OAAO,EACP,KAAK,EACL,OAAO,GACR,EAAE,cAAc,CAAC,gBAAgB,CAAC,2CA4ElC;AAID;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,IAAI,EACJ,QAAQ,EACR,QAAQ,GACT,EAAE,cAAc,CAAC,gBAAgB,CAAC,2CA+FlC"}
1
+ {"version":3,"file":"JsonExplorerBlock.d.ts","sourceRoot":"","sources":["../../../../src/client/blocks/library/JsonExplorerBlock.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,2BAA2B,CAAC;AA2QnC;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,IAAI,EACJ,OAAO,EACP,KAAK,EACL,OAAO,GACR,EAAE,cAAc,CAAC,gBAAgB,CAAC,2CAUlC;AAED,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,SAAS,EACT,KAAc,GACf,EAAE;IACD,IAAI,EAAE,IAAI,CAAC,gBAAgB,EAAE,MAAM,GAAG,gBAAgB,CAAC,CAAC;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,2CA6EA;AAID;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,IAAI,EACJ,QAAQ,EACR,QAAQ,GACT,EAAE,cAAc,CAAC,gBAAgB,CAAC,2CA6HlC"}
@@ -1,7 +1,8 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useId, useMemo, useState } from "react";
2
+ import { useId, useMemo, useState, } from "react";
3
3
  import { IconChevronRight } from "@tabler/icons-react";
4
4
  import { cn } from "../../utils.js";
5
+ import { JSON_EXPLORER_DEFAULT_COLLAPSED_DEPTH, JSON_EXPLORER_MAX_COLLAPSED_DEPTH, } from "./json-explorer.config.js";
5
6
  import { DevInput, DevLabel, DevTextarea } from "./dev-doc-ui.js";
6
7
  /**
7
8
  * Read + Edit renderers for a `json-explorer` block — a browser-devtools /
@@ -35,6 +36,15 @@ const NULL_CLASS = "text-plan-muted italic";
35
36
  const KEY_CLASS = "text-rose-700 dark:text-rose-300";
36
37
  /** Structural punctuation (braces, brackets, commas, colons). */
37
38
  const PUNCT_CLASS = "text-plan-muted";
39
+ const JSON_EXPLORER_DEPTH_PRESETS = [
40
+ { label: "Off", value: 0 },
41
+ { label: "2 levels", value: JSON_EXPLORER_DEFAULT_COLLAPSED_DEPTH },
42
+ { label: "3 levels", value: 3 },
43
+ { label: "All", value: JSON_EXPLORER_MAX_COLLAPSED_DEPTH },
44
+ ];
45
+ function clampCollapsedDepth(value) {
46
+ return Math.max(0, Math.min(JSON_EXPLORER_MAX_COLLAPSED_DEPTH, value));
47
+ }
38
48
  function parseJson(raw) {
39
49
  const trimmed = raw.trim();
40
50
  if (!trimmed) {
@@ -82,17 +92,29 @@ function LeafValue({ value }) {
82
92
  * "pulse" (`forceOpen`) flips. Leaves render inline with their type color.
83
93
  */
84
94
  function JsonNode({ label, value, depth, collapsedDepth, forceOpen, trailingComma, }) {
85
- const seededOpen = depth < collapsedDepth;
95
+ const seededOpen = forceOpen?.open ?? depth < collapsedDepth;
86
96
  // `forceOpen` is the global pulse: when the user hits expand/collapse all we
87
- // flip every node, but per-node toggles still win afterward (the pulse is part
88
- // of the state key via `useMemo` reseed below).
89
- const [open, setOpen] = useState(seededOpen);
90
- // Re-seed when the global pulse changes. Keyed by `forceOpen` identity.
91
- useMemo(() => {
92
- if (forceOpen !== null)
93
- setOpen(forceOpen);
94
- // eslint-disable-next-line react-hooks/exhaustive-deps
95
- }, [forceOpen]);
97
+ // flip every node, but per-node toggles still win afterward.
98
+ const [openState, setOpenState] = useState({ forceOpen, open: seededOpen });
99
+ const [subtreePulse, setSubtreePulse] = useState(null);
100
+ let open = openState.open;
101
+ if (forceOpen !== openState.forceOpen) {
102
+ open = forceOpen?.open ?? openState.open;
103
+ setOpenState({ forceOpen, open });
104
+ }
105
+ const handleToggle = (event) => {
106
+ const nextOpen = !open;
107
+ setOpenState((prev) => ({ ...prev, open: nextOpen }));
108
+ if (event.altKey) {
109
+ setSubtreePulse((prev) => ({
110
+ open: nextOpen,
111
+ nonce: (prev?.nonce ?? 0) + 1,
112
+ }));
113
+ }
114
+ else {
115
+ setSubtreePulse(null);
116
+ }
117
+ };
96
118
  const keyEl = label !== undefined ? (_jsxs(_Fragment, { children: [_jsx("span", { className: KEY_CLASS, children: typeof label === "number" ? label : JSON.stringify(label) }), _jsx("span", { className: PUNCT_CLASS, children: ": " })] })) : null;
97
119
  if (!isContainer(value)) {
98
120
  return (_jsxs("div", { className: "flex items-start py-0.5 leading-relaxed", children: [_jsx("span", { className: "select-none whitespace-pre", children: keyEl }), _jsx(LeafValue, { value: value }), trailingComma && _jsx("span", { className: PUNCT_CLASS, children: "," })] }));
@@ -104,7 +126,9 @@ function JsonNode({ label, value, depth, collapsedDepth, forceOpen, trailingComm
104
126
  const openBrace = isArray ? "[" : "{";
105
127
  const closeBrace = isArray ? "]" : "}";
106
128
  const empty = entries.length === 0;
107
- return (_jsxs("div", { className: "leading-relaxed", children: [_jsxs("button", { type: "button", "data-plan-interactive": true, "aria-expanded": open, disabled: empty, onClick: () => setOpen((value) => !value), className: cn("group flex w-full items-start gap-1 rounded py-0.5 text-left transition-colors", !empty && "hover:bg-accent/40", empty && "cursor-default"), children: [_jsx(IconChevronRight, { className: cn("mt-1 size-3.5 shrink-0 text-plan-muted transition-transform", open && "rotate-90", empty && "opacity-0") }), _jsxs("span", { className: "min-w-0 whitespace-pre-wrap break-words", children: [keyEl, _jsx("span", { className: PUNCT_CLASS, children: openBrace }), !open && !empty && (_jsx("span", { className: "ml-1 text-plan-muted", children: containerSummary(value) })), (!open || empty) && (_jsxs("span", { className: PUNCT_CLASS, children: [empty ? "" : "…", closeBrace, trailingComma ? "," : ""] }))] })] }), open && !empty && (_jsxs(_Fragment, { children: [_jsx("div", { className: "ml-[7px] border-l border-plan-line pl-3.5", children: entries.map(([entryKey, entryValue], index) => (_jsx(JsonNode, { label: entryKey, value: entryValue, depth: depth + 1, collapsedDepth: collapsedDepth, forceOpen: forceOpen, trailingComma: index < entries.length - 1 }, String(entryKey)))) }), _jsx("div", { className: "flex items-start", children: _jsxs("span", { className: "ml-[18px] whitespace-pre", children: [_jsx("span", { className: PUNCT_CLASS, children: closeBrace }), trailingComma && _jsx("span", { className: PUNCT_CLASS, children: "," })] }) })] }))] }));
129
+ const childForceOpen = subtreePulse ?? forceOpen;
130
+ const childPulseNonce = childForceOpen?.nonce ?? 0;
131
+ return (_jsxs("div", { className: "leading-relaxed", children: [_jsxs("button", { type: "button", "data-plan-interactive": true, "aria-expanded": open, disabled: empty, onClick: handleToggle, className: cn("group flex w-full items-start gap-1 rounded py-0.5 text-left transition-colors", !empty && "hover:bg-accent/40", empty && "cursor-default"), children: [_jsx(IconChevronRight, { className: cn("mt-1 size-3.5 shrink-0 text-plan-muted transition-transform", open && "rotate-90", empty && "opacity-0") }), _jsxs("span", { className: "min-w-0 whitespace-pre-wrap break-words", children: [keyEl, _jsx("span", { className: PUNCT_CLASS, children: openBrace }), !open && !empty && (_jsx("span", { className: "ml-1 text-plan-muted", children: containerSummary(value) })), (!open || empty) && (_jsxs("span", { className: PUNCT_CLASS, children: [empty ? "" : "…", closeBrace, trailingComma ? "," : ""] }))] })] }), open && !empty && (_jsxs(_Fragment, { children: [_jsx("div", { className: "ml-[7px] border-l border-plan-line pl-3.5", children: entries.map(([entryKey, entryValue], index) => (_jsx(JsonNode, { label: entryKey, value: entryValue, depth: depth + 1, collapsedDepth: collapsedDepth, forceOpen: childForceOpen, trailingComma: index < entries.length - 1 }, `${String(entryKey)}:${childPulseNonce}`))) }), _jsx("div", { className: "flex items-start", children: _jsxs("span", { className: "ml-[18px] whitespace-pre", children: [_jsx("span", { className: PUNCT_CLASS, children: closeBrace }), trailingComma && _jsx("span", { className: PUNCT_CLASS, children: "," })] }) })] }))] }));
108
132
  }
109
133
  /* ── Read (collapsible devtools tree) ──────────────────────────────────────── */
110
134
  /**
@@ -114,37 +138,44 @@ function JsonNode({ label, value, depth, collapsedDepth, forceOpen, trailingComm
114
138
  * all" control toggles every node at once via a global pulse counter.
115
139
  */
116
140
  export function JsonExplorerRead({ data, blockId, title, summary, }) {
141
+ const heading = data.title ?? title;
142
+ return (_jsxs("section", { className: "plan-block", "data-block-id": blockId, children: [heading && _jsx("div", { className: "plan-block-label", children: heading }), _jsx(JsonExplorerSurface, { data: data }), summary && _jsx("p", { className: "mt-5 text-plan-muted", children: summary })] }));
143
+ }
144
+ export function JsonExplorerSurface({ data, className, label = "JSON", }) {
117
145
  const parsed = useMemo(() => parseJson(data.json), [data.json]);
118
- const collapsedDepth = data.collapsedDepth ?? 1;
146
+ const collapsedDepth = data.collapsedDepth ?? JSON_EXPLORER_DEFAULT_COLLAPSED_DEPTH;
119
147
  // `pulse` carries a boolean (expand/collapse) plus a nonce so repeated clicks
120
148
  // of the same action still re-fire the reseed in each node.
121
149
  const [pulse, setPulse] = useState(null);
122
- const heading = data.title ?? title;
123
- return (_jsxs("section", { className: "plan-block", "data-block-id": blockId, children: [heading && _jsx("div", { className: "plan-block-label", children: heading }), _jsxs("div", { className: "overflow-hidden rounded-xl border border-plan-line bg-plan-code", children: [_jsxs("div", { className: "flex items-center justify-between gap-2 border-b border-plan-line px-3 py-1.5", children: [_jsx("span", { className: "font-mono text-xs uppercase tracking-wide text-plan-muted", children: "JSON" }), parsed.ok && isContainer(parsed.value) && (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("button", { type: "button", "data-plan-interactive": true, onClick: () => setPulse((prev) => ({
124
- open: true,
125
- nonce: (prev?.nonce ?? 0) + 1,
126
- })), className: "rounded px-1.5 py-0.5 text-xs text-plan-muted transition-colors hover:bg-accent/60 hover:text-plan-text", children: "Expand all" }), _jsx("span", { className: "text-plan-muted", children: "\u00B7" }), _jsx("button", { type: "button", "data-plan-interactive": true, onClick: () => setPulse((prev) => ({
127
- open: false,
128
- nonce: (prev?.nonce ?? 0) + 1,
129
- })), className: "rounded px-1.5 py-0.5 text-xs text-plan-muted transition-colors hover:bg-accent/60 hover:text-plan-text", children: "Collapse all" })] }))] }), _jsx("div", { className: "overflow-auto px-3 py-2.5 font-mono text-sm text-plan-code-text", children: parsed.ok ? (_jsx(JsonNode
130
- // Remount the whole tree when the global pulse fires so every node
131
- // re-seeds from the new open/closed state cleanly.
132
- , { value: parsed.value, depth: 0, collapsedDepth: collapsedDepth, forceOpen: pulse?.open ?? null }, pulse?.nonce ?? 0)) : (_jsxs("div", { className: "space-y-2", children: [_jsx("pre", { className: "overflow-auto whitespace-pre-wrap break-words text-plan-code-text", children: data.json || "—" }), _jsxs("p", { className: "text-xs text-red-600 dark:text-red-300", children: ["Could not parse JSON: ", parsed.error] })] })) })] }), summary && _jsx("p", { className: "mt-5 text-plan-muted", children: summary })] }));
150
+ return (_jsxs("div", { className: cn("overflow-hidden rounded-xl border border-plan-line bg-plan-code", className), children: [_jsxs("div", { className: "flex items-center justify-between gap-2 border-b border-plan-line px-3 py-1.5", children: [_jsx("span", { className: "font-mono text-xs uppercase tracking-wide text-plan-muted", children: label }), parsed.ok && isContainer(parsed.value) && (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("button", { type: "button", "data-plan-interactive": true, onClick: () => setPulse((prev) => ({
151
+ open: true,
152
+ nonce: (prev?.nonce ?? 0) + 1,
153
+ })), className: "rounded px-1.5 py-0.5 text-xs text-plan-muted transition-colors hover:bg-accent/60 hover:text-plan-text", children: "Expand all" }), _jsx("span", { className: "text-plan-muted", children: "\u00B7" }), _jsx("button", { type: "button", "data-plan-interactive": true, onClick: () => setPulse((prev) => ({
154
+ open: false,
155
+ nonce: (prev?.nonce ?? 0) + 1,
156
+ })), className: "rounded px-1.5 py-0.5 text-xs text-plan-muted transition-colors hover:bg-accent/60 hover:text-plan-text", children: "Collapse all" })] }))] }), _jsx("div", { className: "overflow-auto px-3 py-2.5 font-mono text-sm text-plan-code-text", children: parsed.ok ? (_jsx(JsonNode
157
+ // Remount the whole tree when the global pulse fires so every node
158
+ // re-seeds from the new open/closed state cleanly.
159
+ , { value: parsed.value, depth: 0, collapsedDepth: collapsedDepth, forceOpen: pulse }, pulse?.nonce ?? 0)) : (_jsxs("div", { className: "space-y-2", children: [_jsx("pre", { className: "overflow-auto whitespace-pre-wrap break-words text-plan-code-text", children: data.json || "—" }), _jsxs("p", { className: "text-xs text-red-600 dark:text-red-300", children: ["Could not parse JSON: ", parsed.error] })] })) })] }));
133
160
  }
134
161
  /* ── Edit (panel form) ─────────────────────────────────────────────────────── */
135
162
  /**
136
163
  * Panel editor for a `json-explorer` block: a monospace textarea bound to the
137
164
  * raw `json`, a "Format" button that pretty-prints via `JSON.parse` →
138
165
  * `JSON.stringify(_, null, 2)` (guarded — shows an INLINE error, never
139
- * `window.alert`), a `collapsedDepth` number input, and a `title` input. Renders
140
- * BARE content (no `<section>`); the registry's panel surface supplies the
141
- * popover chrome.
166
+ * `window.alert`), an auto-expand depth picker/input, and a `title` input.
167
+ * Renders BARE content (no `<section>`); the registry's panel surface supplies
168
+ * the popover chrome.
142
169
  */
143
170
  export function JsonExplorerEdit({ data, onChange, editable, }) {
144
171
  const jsonId = useId();
145
172
  const titleId = useId();
146
173
  const depthId = useId();
147
174
  const [formatError, setFormatError] = useState(null);
175
+ const collapsedDepth = data.collapsedDepth ?? JSON_EXPLORER_DEFAULT_COLLAPSED_DEPTH;
176
+ const setCollapsedDepth = (value) => {
177
+ onChange({ ...data, collapsedDepth: clampCollapsedDepth(value) });
178
+ };
148
179
  const handleFormat = () => {
149
180
  try {
150
181
  const formatted = JSON.stringify(JSON.parse(data.json), null, 2);
@@ -158,14 +189,19 @@ export function JsonExplorerEdit({ data, onChange, editable, }) {
158
189
  return (_jsxs("div", { className: "grid gap-3", "data-plan-interactive": true, children: [_jsxs("div", { className: "grid gap-1.5", children: [_jsx(DevLabel, { htmlFor: titleId, children: "Title" }), _jsx(DevInput, { id: titleId, value: data.title ?? "", readOnly: !editable, onChange: (event) => onChange({ ...data, title: event.target.value || undefined }), placeholder: "Optional heading" })] }), _jsxs("div", { className: "grid gap-1.5", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx(DevLabel, { htmlFor: jsonId, children: "JSON payload" }), editable && (_jsx("button", { type: "button", "data-plan-interactive": true, onClick: handleFormat, className: "inline-flex h-7 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md border border-input bg-background px-2 text-xs font-medium ring-offset-background transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", children: "Format" }))] }), _jsx(DevTextarea, { id: jsonId, value: data.json, readOnly: !editable, spellCheck: false, onChange: (event) => {
159
190
  setFormatError(null);
160
191
  onChange({ ...data, json: event.target.value });
161
- }, className: "min-h-56 font-mono text-xs", placeholder: '{\n "id": "abc123",\n "active": true\n}' }), formatError && (_jsx("p", { className: "text-xs text-red-600 dark:text-red-300", children: formatError })), _jsx("p", { className: "text-xs text-muted-foreground", children: "Raw JSON text is the source of truth. Use Format to pretty-print it." })] }), _jsxs("div", { className: "grid gap-1.5", children: [_jsx(DevLabel, { htmlFor: depthId, children: "Collapsed depth" }), _jsx(DevInput, { id: depthId, type: "number", min: 0, max: 20, value: data.collapsedDepth ?? 1, readOnly: !editable, onChange: (event) => {
192
+ }, className: "min-h-56 font-mono text-xs", placeholder: '{\n "id": "abc123",\n "active": true\n}' }), formatError && (_jsx("p", { className: "text-xs text-red-600 dark:text-red-300", children: formatError })), _jsx("p", { className: "text-xs text-muted-foreground", children: "Raw JSON text is the source of truth. Use Format to pretty-print it." })] }), _jsxs("div", { className: "grid gap-1.5", children: [_jsx(DevLabel, { htmlFor: depthId, children: "Auto expand" }), _jsx("div", { className: "flex flex-wrap gap-1", children: JSON_EXPLORER_DEPTH_PRESETS.map((preset) => {
193
+ const active = collapsedDepth === preset.value;
194
+ return (_jsx("button", { type: "button", "data-plan-interactive": true, "aria-pressed": active, disabled: !editable, onClick: () => setCollapsedDepth(preset.value), className: cn("inline-flex h-7 items-center justify-center rounded-md border px-2 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", active
195
+ ? "border-primary bg-primary text-primary-foreground"
196
+ : "border-input bg-background text-muted-foreground hover:bg-accent hover:text-accent-foreground"), children: preset.label }, preset.label));
197
+ }) }), _jsx(DevInput, { id: depthId, type: "number", min: 0, max: JSON_EXPLORER_MAX_COLLAPSED_DEPTH, value: collapsedDepth, readOnly: !editable, onChange: (event) => {
162
198
  const next = Number.parseInt(event.target.value, 10);
163
199
  onChange({
164
200
  ...data,
165
201
  collapsedDepth: Number.isFinite(next)
166
- ? Math.max(0, Math.min(20, next))
202
+ ? clampCollapsedDepth(next)
167
203
  : undefined,
168
204
  });
169
- }, className: "w-24" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Nodes deeper than this start collapsed (default 1)." })] })] }));
205
+ }, className: "w-24" }), _jsxs("p", { className: "text-xs text-muted-foreground", children: ["Levels open automatically. Use 0 to start collapsed,", " ", JSON_EXPLORER_MAX_COLLAPSED_DEPTH, " for all."] })] })] }));
170
206
  }
171
207
  //# sourceMappingURL=JsonExplorerBlock.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"JsonExplorerBlock.js","sourceRoot":"","sources":["../../../../src/client/blocks/library/JsonExplorerBlock.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useId, useMemo, useState } from \"react\";\nimport { IconChevronRight } from \"@tabler/icons-react\";\nimport { cn } from \"../../utils.js\";\nimport type { BlockEditProps, BlockReadProps } from \"../types.js\";\nimport type { JsonExplorerData } from \"./json-explorer.config.js\";\nimport { DevInput, DevLabel, DevTextarea } from \"./dev-doc-ui.js\";\n\n/**\n * Read + Edit renderers for a `json-explorer` block — a browser-devtools /\n * Postman-style collapsible JSON tree. The raw JSON TEXT (`data.json`) is the\n * source of truth; the Read renderer parses it defensively and, on any parse\n * error, falls back to the raw text plus the error message (it never throws).\n * Lives in core so any app can register the dev-doc block (no shadcn import).\n *\n * Progressive disclosure is the whole point: object/array nodes show a chevron\n * and a one-line summary (\"{…} 3 keys\" / \"[…] 5 items\"); each node tracks its\n * own open/closed state (`useState`) seeded by `collapsedDepth` so deep payloads\n * stay scannable. Leaf values are type-colored (string = green, number = blue,\n * boolean = violet, null = muted); keys use a stable accent color; subtle indent\n * guide lines mark nesting.\n *\n * DARK/LIGHT: the plan editor toggles a `.dark` class on <html>. Every color\n * token — value types, keys, guide lines, chrome — uses Tailwind `dark:` variants\n * or the theme-aware plan CSS-var utilities, so the tree reads correctly in BOTH\n * modes (no hardcoded dark-only palette).\n */\n\n/* ── Theme-aware value-type color tokens ───────────────────────────────────── */\n\n/** String leaves: green in both modes. */\nconst STRING_CLASS = \"text-emerald-700 dark:text-emerald-300\";\n/** Number leaves: blue in both modes. */\nconst NUMBER_CLASS = \"text-blue-700 dark:text-blue-300\";\n/** Boolean leaves: violet in both modes. */\nconst BOOLEAN_CLASS = \"text-violet-700 dark:text-violet-300\";\n/** `null`/`undefined` leaves: muted (theme-aware plan var). */\nconst NULL_CLASS = \"text-plan-muted italic\";\n/** Object keys: a stable, saturated accent that reads in both modes. */\nconst KEY_CLASS = \"text-rose-700 dark:text-rose-300\";\n/** Structural punctuation (braces, brackets, commas, colons). */\nconst PUNCT_CLASS = \"text-plan-muted\";\n\ntype JsonValue =\n | string\n | number\n | boolean\n | null\n | JsonValue[]\n | { [key: string]: JsonValue };\ntype JsonObject = { [key: string]: JsonValue };\n\ninterface ParseResult {\n ok: boolean;\n value?: JsonValue;\n error?: string;\n}\n\nfunction parseJson(raw: string): ParseResult {\n const trimmed = raw.trim();\n if (!trimmed) {\n return { ok: false, error: \"Empty payload — add some JSON to explore.\" };\n }\n try {\n return { ok: true, value: JSON.parse(trimmed) as JsonValue };\n } catch (error) {\n return {\n ok: false,\n error: error instanceof Error ? error.message : \"Invalid JSON\",\n };\n }\n}\n\nfunction isContainer(value: JsonValue): value is JsonValue[] | JsonObject {\n return value !== null && typeof value === \"object\";\n}\n\n/** One-line summary for a collapsed container, devtools style. */\nfunction containerSummary(value: JsonValue[] | JsonObject): string {\n if (Array.isArray(value)) {\n const count = value.length;\n return `[…] ${count} ${count === 1 ? \"item\" : \"items\"}`;\n }\n const count = Object.keys(value).length;\n return `{…} ${count} ${count === 1 ? \"key\" : \"keys\"}`;\n}\n\n/** Render a leaf (primitive) value with its type color. */\nfunction LeafValue({ value }: { value: string | number | boolean | null }) {\n if (value === null) {\n return <span className={NULL_CLASS}>null</span>;\n }\n if (typeof value === \"string\") {\n return <span className={STRING_CLASS}>{JSON.stringify(value)}</span>;\n }\n if (typeof value === \"number\") {\n return <span className={NUMBER_CLASS}>{String(value)}</span>;\n }\n // boolean\n return <span className={BOOLEAN_CLASS}>{String(value)}</span>;\n}\n\ninterface JsonNodeProps {\n /** The object key or array index label for this node (root has none). */\n label?: string | number;\n value: JsonValue;\n depth: number;\n /** Depth beyond which nodes start collapsed. */\n collapsedDepth: number;\n /** Global expand/collapse pulse — overrides per-node seed when changed. */\n forceOpen: boolean | null;\n /** True when this node is followed by a sibling (renders a trailing comma). */\n trailingComma?: boolean;\n}\n\n/**\n * A single tree node. Containers (object/array) get their own collapse state,\n * seeded from `collapsedDepth` and re-seeded whenever the global expand/collapse\n * \"pulse\" (`forceOpen`) flips. Leaves render inline with their type color.\n */\nfunction JsonNode({\n label,\n value,\n depth,\n collapsedDepth,\n forceOpen,\n trailingComma,\n}: JsonNodeProps) {\n const seededOpen = depth < collapsedDepth;\n // `forceOpen` is the global pulse: when the user hits expand/collapse all we\n // flip every node, but per-node toggles still win afterward (the pulse is part\n // of the state key via `useMemo` reseed below).\n const [open, setOpen] = useState(seededOpen);\n // Re-seed when the global pulse changes. Keyed by `forceOpen` identity.\n useMemo(() => {\n if (forceOpen !== null) setOpen(forceOpen);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [forceOpen]);\n\n const keyEl =\n label !== undefined ? (\n <>\n <span className={KEY_CLASS}>\n {typeof label === \"number\" ? label : JSON.stringify(label)}\n </span>\n <span className={PUNCT_CLASS}>: </span>\n </>\n ) : null;\n\n if (!isContainer(value)) {\n return (\n <div className=\"flex items-start py-0.5 leading-relaxed\">\n <span className=\"select-none whitespace-pre\">{keyEl}</span>\n <LeafValue value={value} />\n {trailingComma && <span className={PUNCT_CLASS}>,</span>}\n </div>\n );\n }\n\n const isArray = Array.isArray(value);\n const entries: Array<[string | number, JsonValue]> = isArray\n ? (value as JsonValue[]).map((item, index) => [index, item])\n : Object.entries(value as JsonObject);\n const openBrace = isArray ? \"[\" : \"{\";\n const closeBrace = isArray ? \"]\" : \"}\";\n const empty = entries.length === 0;\n\n return (\n <div className=\"leading-relaxed\">\n <button\n type=\"button\"\n data-plan-interactive\n aria-expanded={open}\n disabled={empty}\n onClick={() => setOpen((value) => !value)}\n className={cn(\n \"group flex w-full items-start gap-1 rounded py-0.5 text-left transition-colors\",\n !empty && \"hover:bg-accent/40\",\n empty && \"cursor-default\",\n )}\n >\n <IconChevronRight\n className={cn(\n \"mt-1 size-3.5 shrink-0 text-plan-muted transition-transform\",\n open && \"rotate-90\",\n empty && \"opacity-0\",\n )}\n />\n <span className=\"min-w-0 whitespace-pre-wrap break-words\">\n {keyEl}\n <span className={PUNCT_CLASS}>{openBrace}</span>\n {!open && !empty && (\n <span className=\"ml-1 text-plan-muted\">\n {containerSummary(value)}\n </span>\n )}\n {(!open || empty) && (\n <span className={PUNCT_CLASS}>\n {empty ? \"\" : \"…\"}\n {closeBrace}\n {trailingComma ? \",\" : \"\"}\n </span>\n )}\n </span>\n </button>\n\n {open && !empty && (\n <>\n {/* Indent guide: a subtle vertical rule marks the nesting level. */}\n <div className=\"ml-[7px] border-l border-plan-line pl-3.5\">\n {entries.map(([entryKey, entryValue], index) => (\n <JsonNode\n key={String(entryKey)}\n label={entryKey}\n value={entryValue}\n depth={depth + 1}\n collapsedDepth={collapsedDepth}\n forceOpen={forceOpen}\n trailingComma={index < entries.length - 1}\n />\n ))}\n </div>\n <div className=\"flex items-start\">\n {/* Align the closing brace under the chevron column. */}\n <span className=\"ml-[18px] whitespace-pre\">\n <span className={PUNCT_CLASS}>{closeBrace}</span>\n {trailingComma && <span className={PUNCT_CLASS}>,</span>}\n </span>\n </div>\n </>\n )}\n </div>\n );\n}\n\n/* ── Read (collapsible devtools tree) ──────────────────────────────────────── */\n\n/**\n * Read-only renderer for a `json-explorer` block. Parses `data.json` defensively\n * and renders the collapsible tree; on a parse error it shows the raw payload in\n * a monospace block plus the error (never throws). An \"Expand all / Collapse\n * all\" control toggles every node at once via a global pulse counter.\n */\nexport function JsonExplorerRead({\n data,\n blockId,\n title,\n summary,\n}: BlockReadProps<JsonExplorerData>) {\n const parsed = useMemo(() => parseJson(data.json), [data.json]);\n const collapsedDepth = data.collapsedDepth ?? 1;\n // `pulse` carries a boolean (expand/collapse) plus a nonce so repeated clicks\n // of the same action still re-fire the reseed in each node.\n const [pulse, setPulse] = useState<{ open: boolean; nonce: number } | null>(\n null,\n );\n const heading = data.title ?? title;\n\n return (\n <section className=\"plan-block\" data-block-id={blockId}>\n {heading && <div className=\"plan-block-label\">{heading}</div>}\n <div className=\"overflow-hidden rounded-xl border border-plan-line bg-plan-code\">\n <div className=\"flex items-center justify-between gap-2 border-b border-plan-line px-3 py-1.5\">\n <span className=\"font-mono text-xs uppercase tracking-wide text-plan-muted\">\n JSON\n </span>\n {parsed.ok && isContainer(parsed.value as JsonValue) && (\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n data-plan-interactive\n onClick={() =>\n setPulse((prev) => ({\n open: true,\n nonce: (prev?.nonce ?? 0) + 1,\n }))\n }\n className=\"rounded px-1.5 py-0.5 text-xs text-plan-muted transition-colors hover:bg-accent/60 hover:text-plan-text\"\n >\n Expand all\n </button>\n <span className=\"text-plan-muted\">·</span>\n <button\n type=\"button\"\n data-plan-interactive\n onClick={() =>\n setPulse((prev) => ({\n open: false,\n nonce: (prev?.nonce ?? 0) + 1,\n }))\n }\n className=\"rounded px-1.5 py-0.5 text-xs text-plan-muted transition-colors hover:bg-accent/60 hover:text-plan-text\"\n >\n Collapse all\n </button>\n </div>\n )}\n </div>\n <div className=\"overflow-auto px-3 py-2.5 font-mono text-sm text-plan-code-text\">\n {parsed.ok ? (\n <JsonNode\n // Remount the whole tree when the global pulse fires so every node\n // re-seeds from the new open/closed state cleanly.\n key={pulse?.nonce ?? 0}\n value={parsed.value as JsonValue}\n depth={0}\n collapsedDepth={collapsedDepth}\n forceOpen={pulse?.open ?? null}\n />\n ) : (\n <div className=\"space-y-2\">\n <pre className=\"overflow-auto whitespace-pre-wrap break-words text-plan-code-text\">\n {data.json || \"—\"}\n </pre>\n <p className=\"text-xs text-red-600 dark:text-red-300\">\n Could not parse JSON: {parsed.error}\n </p>\n </div>\n )}\n </div>\n </div>\n {summary && <p className=\"mt-5 text-plan-muted\">{summary}</p>}\n </section>\n );\n}\n\n/* ── Edit (panel form) ─────────────────────────────────────────────────────── */\n\n/**\n * Panel editor for a `json-explorer` block: a monospace textarea bound to the\n * raw `json`, a \"Format\" button that pretty-prints via `JSON.parse` →\n * `JSON.stringify(_, null, 2)` (guarded — shows an INLINE error, never\n * `window.alert`), a `collapsedDepth` number input, and a `title` input. Renders\n * BARE content (no `<section>`); the registry's panel surface supplies the\n * popover chrome.\n */\nexport function JsonExplorerEdit({\n data,\n onChange,\n editable,\n}: BlockEditProps<JsonExplorerData>) {\n const jsonId = useId();\n const titleId = useId();\n const depthId = useId();\n const [formatError, setFormatError] = useState<string | null>(null);\n\n const handleFormat = () => {\n try {\n const formatted = JSON.stringify(JSON.parse(data.json), null, 2);\n setFormatError(null);\n onChange({ ...data, json: formatted });\n } catch (error) {\n setFormatError(\n error instanceof Error ? error.message : \"Invalid JSON — cannot format\",\n );\n }\n };\n\n return (\n <div className=\"grid gap-3\" data-plan-interactive>\n <div className=\"grid gap-1.5\">\n <DevLabel htmlFor={titleId}>Title</DevLabel>\n <DevInput\n id={titleId}\n value={data.title ?? \"\"}\n readOnly={!editable}\n onChange={(event) =>\n onChange({ ...data, title: event.target.value || undefined })\n }\n placeholder=\"Optional heading\"\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <div className=\"flex items-center justify-between\">\n <DevLabel htmlFor={jsonId}>JSON payload</DevLabel>\n {editable && (\n <button\n type=\"button\"\n data-plan-interactive\n onClick={handleFormat}\n className=\"inline-flex h-7 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md border border-input bg-background px-2 text-xs font-medium ring-offset-background transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50\"\n >\n Format\n </button>\n )}\n </div>\n <DevTextarea\n id={jsonId}\n value={data.json}\n readOnly={!editable}\n spellCheck={false}\n onChange={(event) => {\n setFormatError(null);\n onChange({ ...data, json: event.target.value });\n }}\n className=\"min-h-56 font-mono text-xs\"\n placeholder={'{\\n \"id\": \"abc123\",\\n \"active\": true\\n}'}\n />\n {formatError && (\n <p className=\"text-xs text-red-600 dark:text-red-300\">\n {formatError}\n </p>\n )}\n <p className=\"text-xs text-muted-foreground\">\n Raw JSON text is the source of truth. Use Format to pretty-print it.\n </p>\n </div>\n\n <div className=\"grid gap-1.5\">\n <DevLabel htmlFor={depthId}>Collapsed depth</DevLabel>\n <DevInput\n id={depthId}\n type=\"number\"\n min={0}\n max={20}\n value={data.collapsedDepth ?? 1}\n readOnly={!editable}\n onChange={(event) => {\n const next = Number.parseInt(event.target.value, 10);\n onChange({\n ...data,\n collapsedDepth: Number.isFinite(next)\n ? Math.max(0, Math.min(20, next))\n : undefined,\n });\n }}\n className=\"w-24\"\n />\n <p className=\"text-xs text-muted-foreground\">\n Nodes deeper than this start collapsed (default 1).\n </p>\n </div>\n </div>\n );\n}\n"]}
1
+ {"version":3,"file":"JsonExplorerBlock.js","sourceRoot":"","sources":["../../../../src/client/blocks/library/JsonExplorerBlock.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n useId,\n useMemo,\n useState,\n type MouseEvent as ReactMouseEvent,\n} from \"react\";\nimport { IconChevronRight } from \"@tabler/icons-react\";\nimport { cn } from \"../../utils.js\";\nimport type { BlockEditProps, BlockReadProps } from \"../types.js\";\nimport {\n JSON_EXPLORER_DEFAULT_COLLAPSED_DEPTH,\n JSON_EXPLORER_MAX_COLLAPSED_DEPTH,\n type JsonExplorerData,\n} from \"./json-explorer.config.js\";\nimport { DevInput, DevLabel, DevTextarea } from \"./dev-doc-ui.js\";\n\n/**\n * Read + Edit renderers for a `json-explorer` block — a browser-devtools /\n * Postman-style collapsible JSON tree. The raw JSON TEXT (`data.json`) is the\n * source of truth; the Read renderer parses it defensively and, on any parse\n * error, falls back to the raw text plus the error message (it never throws).\n * Lives in core so any app can register the dev-doc block (no shadcn import).\n *\n * Progressive disclosure is the whole point: object/array nodes show a chevron\n * and a one-line summary (\"{…} 3 keys\" / \"[…] 5 items\"); each node tracks its\n * own open/closed state (`useState`) seeded by `collapsedDepth` so deep payloads\n * stay scannable. Leaf values are type-colored (string = green, number = blue,\n * boolean = violet, null = muted); keys use a stable accent color; subtle indent\n * guide lines mark nesting.\n *\n * DARK/LIGHT: the plan editor toggles a `.dark` class on <html>. Every color\n * token — value types, keys, guide lines, chrome — uses Tailwind `dark:` variants\n * or the theme-aware plan CSS-var utilities, so the tree reads correctly in BOTH\n * modes (no hardcoded dark-only palette).\n */\n\n/* ── Theme-aware value-type color tokens ───────────────────────────────────── */\n\n/** String leaves: green in both modes. */\nconst STRING_CLASS = \"text-emerald-700 dark:text-emerald-300\";\n/** Number leaves: blue in both modes. */\nconst NUMBER_CLASS = \"text-blue-700 dark:text-blue-300\";\n/** Boolean leaves: violet in both modes. */\nconst BOOLEAN_CLASS = \"text-violet-700 dark:text-violet-300\";\n/** `null`/`undefined` leaves: muted (theme-aware plan var). */\nconst NULL_CLASS = \"text-plan-muted italic\";\n/** Object keys: a stable, saturated accent that reads in both modes. */\nconst KEY_CLASS = \"text-rose-700 dark:text-rose-300\";\n/** Structural punctuation (braces, brackets, commas, colons). */\nconst PUNCT_CLASS = \"text-plan-muted\";\n\nconst JSON_EXPLORER_DEPTH_PRESETS = [\n { label: \"Off\", value: 0 },\n { label: \"2 levels\", value: JSON_EXPLORER_DEFAULT_COLLAPSED_DEPTH },\n { label: \"3 levels\", value: 3 },\n { label: \"All\", value: JSON_EXPLORER_MAX_COLLAPSED_DEPTH },\n] as const;\n\nfunction clampCollapsedDepth(value: number): number {\n return Math.max(0, Math.min(JSON_EXPLORER_MAX_COLLAPSED_DEPTH, value));\n}\n\ntype JsonValue =\n | string\n | number\n | boolean\n | null\n | JsonValue[]\n | { [key: string]: JsonValue };\ntype JsonObject = { [key: string]: JsonValue };\n\ninterface ParseResult {\n ok: boolean;\n value?: JsonValue;\n error?: string;\n}\n\ninterface JsonTreePulse {\n open: boolean;\n nonce: number;\n}\n\nfunction parseJson(raw: string): ParseResult {\n const trimmed = raw.trim();\n if (!trimmed) {\n return { ok: false, error: \"Empty payload — add some JSON to explore.\" };\n }\n try {\n return { ok: true, value: JSON.parse(trimmed) as JsonValue };\n } catch (error) {\n return {\n ok: false,\n error: error instanceof Error ? error.message : \"Invalid JSON\",\n };\n }\n}\n\nfunction isContainer(value: JsonValue): value is JsonValue[] | JsonObject {\n return value !== null && typeof value === \"object\";\n}\n\n/** One-line summary for a collapsed container, devtools style. */\nfunction containerSummary(value: JsonValue[] | JsonObject): string {\n if (Array.isArray(value)) {\n const count = value.length;\n return `[…] ${count} ${count === 1 ? \"item\" : \"items\"}`;\n }\n const count = Object.keys(value).length;\n return `{…} ${count} ${count === 1 ? \"key\" : \"keys\"}`;\n}\n\n/** Render a leaf (primitive) value with its type color. */\nfunction LeafValue({ value }: { value: string | number | boolean | null }) {\n if (value === null) {\n return <span className={NULL_CLASS}>null</span>;\n }\n if (typeof value === \"string\") {\n return <span className={STRING_CLASS}>{JSON.stringify(value)}</span>;\n }\n if (typeof value === \"number\") {\n return <span className={NUMBER_CLASS}>{String(value)}</span>;\n }\n // boolean\n return <span className={BOOLEAN_CLASS}>{String(value)}</span>;\n}\n\ninterface JsonNodeProps {\n /** The object key or array index label for this node (root has none). */\n label?: string | number;\n value: JsonValue;\n depth: number;\n /** Depth beyond which nodes start collapsed. */\n collapsedDepth: number;\n /** Global or parent expand/collapse pulse — overrides per-node seed when changed. */\n forceOpen: JsonTreePulse | null;\n /** True when this node is followed by a sibling (renders a trailing comma). */\n trailingComma?: boolean;\n}\n\n/**\n * A single tree node. Containers (object/array) get their own collapse state,\n * seeded from `collapsedDepth` and re-seeded whenever the global expand/collapse\n * \"pulse\" (`forceOpen`) flips. Leaves render inline with their type color.\n */\nfunction JsonNode({\n label,\n value,\n depth,\n collapsedDepth,\n forceOpen,\n trailingComma,\n}: JsonNodeProps) {\n const seededOpen = forceOpen?.open ?? depth < collapsedDepth;\n // `forceOpen` is the global pulse: when the user hits expand/collapse all we\n // flip every node, but per-node toggles still win afterward.\n const [openState, setOpenState] = useState<{\n forceOpen: JsonTreePulse | null;\n open: boolean;\n }>({ forceOpen, open: seededOpen });\n const [subtreePulse, setSubtreePulse] = useState<JsonTreePulse | null>(null);\n\n let open = openState.open;\n if (forceOpen !== openState.forceOpen) {\n open = forceOpen?.open ?? openState.open;\n setOpenState({ forceOpen, open });\n }\n\n const handleToggle = (event: ReactMouseEvent<HTMLButtonElement>) => {\n const nextOpen = !open;\n setOpenState((prev) => ({ ...prev, open: nextOpen }));\n if (event.altKey) {\n setSubtreePulse((prev) => ({\n open: nextOpen,\n nonce: (prev?.nonce ?? 0) + 1,\n }));\n } else {\n setSubtreePulse(null);\n }\n };\n\n const keyEl =\n label !== undefined ? (\n <>\n <span className={KEY_CLASS}>\n {typeof label === \"number\" ? label : JSON.stringify(label)}\n </span>\n <span className={PUNCT_CLASS}>: </span>\n </>\n ) : null;\n\n if (!isContainer(value)) {\n return (\n <div className=\"flex items-start py-0.5 leading-relaxed\">\n <span className=\"select-none whitespace-pre\">{keyEl}</span>\n <LeafValue value={value} />\n {trailingComma && <span className={PUNCT_CLASS}>,</span>}\n </div>\n );\n }\n\n const isArray = Array.isArray(value);\n const entries: Array<[string | number, JsonValue]> = isArray\n ? (value as JsonValue[]).map((item, index) => [index, item])\n : Object.entries(value as JsonObject);\n const openBrace = isArray ? \"[\" : \"{\";\n const closeBrace = isArray ? \"]\" : \"}\";\n const empty = entries.length === 0;\n const childForceOpen = subtreePulse ?? forceOpen;\n const childPulseNonce = childForceOpen?.nonce ?? 0;\n\n return (\n <div className=\"leading-relaxed\">\n <button\n type=\"button\"\n data-plan-interactive\n aria-expanded={open}\n disabled={empty}\n onClick={handleToggle}\n className={cn(\n \"group flex w-full items-start gap-1 rounded py-0.5 text-left transition-colors\",\n !empty && \"hover:bg-accent/40\",\n empty && \"cursor-default\",\n )}\n >\n <IconChevronRight\n className={cn(\n \"mt-1 size-3.5 shrink-0 text-plan-muted transition-transform\",\n open && \"rotate-90\",\n empty && \"opacity-0\",\n )}\n />\n <span className=\"min-w-0 whitespace-pre-wrap break-words\">\n {keyEl}\n <span className={PUNCT_CLASS}>{openBrace}</span>\n {!open && !empty && (\n <span className=\"ml-1 text-plan-muted\">\n {containerSummary(value)}\n </span>\n )}\n {(!open || empty) && (\n <span className={PUNCT_CLASS}>\n {empty ? \"\" : \"…\"}\n {closeBrace}\n {trailingComma ? \",\" : \"\"}\n </span>\n )}\n </span>\n </button>\n\n {open && !empty && (\n <>\n {/* Indent guide: a subtle vertical rule marks the nesting level. */}\n <div className=\"ml-[7px] border-l border-plan-line pl-3.5\">\n {entries.map(([entryKey, entryValue], index) => (\n <JsonNode\n key={`${String(entryKey)}:${childPulseNonce}`}\n label={entryKey}\n value={entryValue}\n depth={depth + 1}\n collapsedDepth={collapsedDepth}\n forceOpen={childForceOpen}\n trailingComma={index < entries.length - 1}\n />\n ))}\n </div>\n <div className=\"flex items-start\">\n {/* Align the closing brace under the chevron column. */}\n <span className=\"ml-[18px] whitespace-pre\">\n <span className={PUNCT_CLASS}>{closeBrace}</span>\n {trailingComma && <span className={PUNCT_CLASS}>,</span>}\n </span>\n </div>\n </>\n )}\n </div>\n );\n}\n\n/* ── Read (collapsible devtools tree) ──────────────────────────────────────── */\n\n/**\n * Read-only renderer for a `json-explorer` block. Parses `data.json` defensively\n * and renders the collapsible tree; on a parse error it shows the raw payload in\n * a monospace block plus the error (never throws). An \"Expand all / Collapse\n * all\" control toggles every node at once via a global pulse counter.\n */\nexport function JsonExplorerRead({\n data,\n blockId,\n title,\n summary,\n}: BlockReadProps<JsonExplorerData>) {\n const heading = data.title ?? title;\n\n return (\n <section className=\"plan-block\" data-block-id={blockId}>\n {heading && <div className=\"plan-block-label\">{heading}</div>}\n <JsonExplorerSurface data={data} />\n {summary && <p className=\"mt-5 text-plan-muted\">{summary}</p>}\n </section>\n );\n}\n\nexport function JsonExplorerSurface({\n data,\n className,\n label = \"JSON\",\n}: {\n data: Pick<JsonExplorerData, \"json\" | \"collapsedDepth\">;\n className?: string;\n label?: string;\n}) {\n const parsed = useMemo(() => parseJson(data.json), [data.json]);\n const collapsedDepth =\n data.collapsedDepth ?? JSON_EXPLORER_DEFAULT_COLLAPSED_DEPTH;\n // `pulse` carries a boolean (expand/collapse) plus a nonce so repeated clicks\n // of the same action still re-fire the reseed in each node.\n const [pulse, setPulse] = useState<{ open: boolean; nonce: number } | null>(\n null,\n );\n\n return (\n <div\n className={cn(\n \"overflow-hidden rounded-xl border border-plan-line bg-plan-code\",\n className,\n )}\n >\n <div className=\"flex items-center justify-between gap-2 border-b border-plan-line px-3 py-1.5\">\n <span className=\"font-mono text-xs uppercase tracking-wide text-plan-muted\">\n {label}\n </span>\n {parsed.ok && isContainer(parsed.value as JsonValue) && (\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n data-plan-interactive\n onClick={() =>\n setPulse((prev) => ({\n open: true,\n nonce: (prev?.nonce ?? 0) + 1,\n }))\n }\n className=\"rounded px-1.5 py-0.5 text-xs text-plan-muted transition-colors hover:bg-accent/60 hover:text-plan-text\"\n >\n Expand all\n </button>\n <span className=\"text-plan-muted\">·</span>\n <button\n type=\"button\"\n data-plan-interactive\n onClick={() =>\n setPulse((prev) => ({\n open: false,\n nonce: (prev?.nonce ?? 0) + 1,\n }))\n }\n className=\"rounded px-1.5 py-0.5 text-xs text-plan-muted transition-colors hover:bg-accent/60 hover:text-plan-text\"\n >\n Collapse all\n </button>\n </div>\n )}\n </div>\n <div className=\"overflow-auto px-3 py-2.5 font-mono text-sm text-plan-code-text\">\n {parsed.ok ? (\n <JsonNode\n // Remount the whole tree when the global pulse fires so every node\n // re-seeds from the new open/closed state cleanly.\n key={pulse?.nonce ?? 0}\n value={parsed.value as JsonValue}\n depth={0}\n collapsedDepth={collapsedDepth}\n forceOpen={pulse}\n />\n ) : (\n <div className=\"space-y-2\">\n <pre className=\"overflow-auto whitespace-pre-wrap break-words text-plan-code-text\">\n {data.json || \"—\"}\n </pre>\n <p className=\"text-xs text-red-600 dark:text-red-300\">\n Could not parse JSON: {parsed.error}\n </p>\n </div>\n )}\n </div>\n </div>\n );\n}\n\n/* ── Edit (panel form) ─────────────────────────────────────────────────────── */\n\n/**\n * Panel editor for a `json-explorer` block: a monospace textarea bound to the\n * raw `json`, a \"Format\" button that pretty-prints via `JSON.parse` →\n * `JSON.stringify(_, null, 2)` (guarded — shows an INLINE error, never\n * `window.alert`), an auto-expand depth picker/input, and a `title` input.\n * Renders BARE content (no `<section>`); the registry's panel surface supplies\n * the popover chrome.\n */\nexport function JsonExplorerEdit({\n data,\n onChange,\n editable,\n}: BlockEditProps<JsonExplorerData>) {\n const jsonId = useId();\n const titleId = useId();\n const depthId = useId();\n const [formatError, setFormatError] = useState<string | null>(null);\n const collapsedDepth =\n data.collapsedDepth ?? JSON_EXPLORER_DEFAULT_COLLAPSED_DEPTH;\n\n const setCollapsedDepth = (value: number) => {\n onChange({ ...data, collapsedDepth: clampCollapsedDepth(value) });\n };\n\n const handleFormat = () => {\n try {\n const formatted = JSON.stringify(JSON.parse(data.json), null, 2);\n setFormatError(null);\n onChange({ ...data, json: formatted });\n } catch (error) {\n setFormatError(\n error instanceof Error ? error.message : \"Invalid JSON — cannot format\",\n );\n }\n };\n\n return (\n <div className=\"grid gap-3\" data-plan-interactive>\n <div className=\"grid gap-1.5\">\n <DevLabel htmlFor={titleId}>Title</DevLabel>\n <DevInput\n id={titleId}\n value={data.title ?? \"\"}\n readOnly={!editable}\n onChange={(event) =>\n onChange({ ...data, title: event.target.value || undefined })\n }\n placeholder=\"Optional heading\"\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <div className=\"flex items-center justify-between\">\n <DevLabel htmlFor={jsonId}>JSON payload</DevLabel>\n {editable && (\n <button\n type=\"button\"\n data-plan-interactive\n onClick={handleFormat}\n className=\"inline-flex h-7 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md border border-input bg-background px-2 text-xs font-medium ring-offset-background transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50\"\n >\n Format\n </button>\n )}\n </div>\n <DevTextarea\n id={jsonId}\n value={data.json}\n readOnly={!editable}\n spellCheck={false}\n onChange={(event) => {\n setFormatError(null);\n onChange({ ...data, json: event.target.value });\n }}\n className=\"min-h-56 font-mono text-xs\"\n placeholder={'{\\n \"id\": \"abc123\",\\n \"active\": true\\n}'}\n />\n {formatError && (\n <p className=\"text-xs text-red-600 dark:text-red-300\">\n {formatError}\n </p>\n )}\n <p className=\"text-xs text-muted-foreground\">\n Raw JSON text is the source of truth. Use Format to pretty-print it.\n </p>\n </div>\n\n <div className=\"grid gap-1.5\">\n <DevLabel htmlFor={depthId}>Auto expand</DevLabel>\n <div className=\"flex flex-wrap gap-1\">\n {JSON_EXPLORER_DEPTH_PRESETS.map((preset) => {\n const active = collapsedDepth === preset.value;\n return (\n <button\n key={preset.label}\n type=\"button\"\n data-plan-interactive\n aria-pressed={active}\n disabled={!editable}\n onClick={() => setCollapsedDepth(preset.value)}\n className={cn(\n \"inline-flex h-7 items-center justify-center rounded-md border px-2 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50\",\n active\n ? \"border-primary bg-primary text-primary-foreground\"\n : \"border-input bg-background text-muted-foreground hover:bg-accent hover:text-accent-foreground\",\n )}\n >\n {preset.label}\n </button>\n );\n })}\n </div>\n <DevInput\n id={depthId}\n type=\"number\"\n min={0}\n max={JSON_EXPLORER_MAX_COLLAPSED_DEPTH}\n value={collapsedDepth}\n readOnly={!editable}\n onChange={(event) => {\n const next = Number.parseInt(event.target.value, 10);\n onChange({\n ...data,\n collapsedDepth: Number.isFinite(next)\n ? clampCollapsedDepth(next)\n : undefined,\n });\n }}\n className=\"w-24\"\n />\n <p className=\"text-xs text-muted-foreground\">\n Levels open automatically. Use 0 to start collapsed,{\" \"}\n {JSON_EXPLORER_MAX_COLLAPSED_DEPTH} for all.\n </p>\n </div>\n </div>\n );\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"MermaidBlock.d.ts","sourceRoot":"","sources":["../../../../src/client/blocks/library/MermaidBlock.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAyJvD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,OAAO,EACP,KAAK,EACL,OAAO,GACR,EAAE,cAAc,CAAC,WAAW,CAAC,2CAW7B;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,QAAQ,EACR,QAAQ,GACT,EAAE,cAAc,CAAC,WAAW,CAAC,2CAwC7B"}
1
+ {"version":3,"file":"MermaidBlock.d.ts","sourceRoot":"","sources":["../../../../src/client/blocks/library/MermaidBlock.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AA4OvD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,OAAO,EACP,KAAK,EACL,OAAO,GACR,EAAE,cAAc,CAAC,WAAW,CAAC,2CAW7B;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,QAAQ,EACR,QAAQ,GACT,EAAE,cAAc,CAAC,WAAW,CAAC,2CAwC7B"}
@@ -1,30 +1,58 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useId, useMemo, useState } from "react";
3
3
  import { DevInput, DevLabel } from "./dev-doc-ui.js";
4
- /**
5
- * Read + Edit renderers for a `mermaid` block — a Mermaid diagram definition
6
- * (flowchart, sequence, etc.) edited as raw text and rendered with Mermaid's
7
- * `handDrawn` look so it matches the plan's hand-drawn / sketchy house style.
8
- * Lives in core so any app can register the dev-doc block; it stays app-agnostic
9
- * (no shadcn / next-themes import).
10
- *
11
- * `mermaid` is a browser-only runtime (it touches `document`/DOM measurement),
12
- * so the Read renderer SSR-guards: it renders a lightweight placeholder until a
13
- * `useEffect` confirms it is mounted, then dynamically imports `mermaid` and
14
- * injects the rendered SVG. Parse errors never throw — they fall back to the raw
15
- * source in a styled monospace block plus the error message. (The dynamic import
16
- * uses a runtime specifier so this module never forces `mermaid` into the core
17
- * package's own dependency graph — the host app provides it.)
18
- *
19
- * Dark mode: the plan editor toggles a `.dark` class on <html>. The Read renderer
20
- * reads `document.documentElement.classList.contains("dark")` (re-checking on a
21
- * `MutationObserver` of the html class) and re-renders the diagram with Mermaid's
22
- * `dark` theme (vs `neutral` in light) — the resolved theme is in the render
23
- * effect's deps so toggling dark/light updates the SVG live.
24
- */
25
- /** Module specifier kept in a variable so the bundler/tsc treats it as a runtime
26
- * import (core does not depend on `mermaid`; the host app provides it). */
27
- const MERMAID_MODULE = "mermaid";
4
+ function errorMessage(error) {
5
+ return error instanceof Error ? error.message : "Failed to render diagram";
6
+ }
7
+ function sanitizeSvgMarkup(svg) {
8
+ if (typeof DOMParser === "undefined")
9
+ return svg;
10
+ const doc = new DOMParser().parseFromString(svg, "image/svg+xml");
11
+ doc
12
+ .querySelectorAll("script, foreignObject")
13
+ .forEach((node) => node.remove());
14
+ for (const element of Array.from(doc.querySelectorAll("*"))) {
15
+ for (const attr of Array.from(element.attributes)) {
16
+ const name = attr.name.toLowerCase();
17
+ const value = attr.value.trim().toLowerCase();
18
+ if (name.startsWith("on") ||
19
+ ((name === "href" || name.endsWith(":href")) &&
20
+ value.startsWith("javascript:"))) {
21
+ element.removeAttribute(attr.name);
22
+ }
23
+ }
24
+ }
25
+ return doc.documentElement.outerHTML;
26
+ }
27
+ async function renderExcalidrawSvg(source, isDark) {
28
+ const [{ parseMermaidToExcalidraw }, excalidraw] = await Promise.all([
29
+ import("@excalidraw/mermaid-to-excalidraw"),
30
+ import("@excalidraw/excalidraw"),
31
+ ]);
32
+ const { elements, files } = await parseMermaidToExcalidraw(source);
33
+ const excalidrawElements = excalidraw.convertToExcalidrawElements(elements);
34
+ const svg = await excalidraw.exportToSvg({
35
+ elements: excalidrawElements,
36
+ appState: {
37
+ theme: isDark ? "dark" : "light",
38
+ viewBackgroundColor: "transparent",
39
+ exportWithDarkMode: isDark,
40
+ },
41
+ files: files ?? {},
42
+ });
43
+ return sanitizeSvgMarkup(svg.outerHTML);
44
+ }
45
+ async function renderMermaidSvg(source, id, isDark) {
46
+ const mermaid = (await import("mermaid")).default;
47
+ mermaid.initialize({
48
+ startOnLoad: false,
49
+ securityLevel: "strict",
50
+ look: "handDrawn",
51
+ theme: isDark ? "dark" : "neutral",
52
+ });
53
+ const { svg } = await mermaid.render(id, source);
54
+ return sanitizeSvgMarkup(svg);
55
+ }
28
56
  /** Read the live dark-mode flag from the document root (next-themes-free). */
29
57
  function useIsDark() {
30
58
  const [isDark, setIsDark] = useState(false);
@@ -62,27 +90,28 @@ function MermaidDiagram({ source, idSeed, }) {
62
90
  }
63
91
  (async () => {
64
92
  try {
65
- const mermaid = (await import(MERMAID_MODULE))
66
- .default;
67
- mermaid.initialize({
68
- startOnLoad: false,
69
- securityLevel: "strict",
70
- look: "handDrawn",
71
- theme: isDark ? "dark" : "neutral",
72
- });
73
- // Unique id per render pass so re-renders (theme/source change) never
74
- // collide with a stale, still-mounted SVG node id.
75
- const { svg } = await mermaid.render(`${renderId}-${isDark ? "d" : "l"}`, trimmed);
93
+ const svg = await renderExcalidrawSvg(trimmed, isDark);
76
94
  if (!cancelled)
77
95
  setState({ svg });
78
96
  }
79
- catch (error) {
80
- if (!cancelled) {
81
- setState({
82
- error: error instanceof Error
83
- ? error.message
84
- : "Failed to render diagram",
85
- });
97
+ catch (excalidrawError) {
98
+ try {
99
+ // Fallback keeps diagrams usable if a Mermaid feature is not supported
100
+ // by the Excalidraw converter in a given host app.
101
+ const svg = await renderMermaidSvg(trimmed, `${renderId}-${isDark ? "d" : "l"}`, isDark);
102
+ if (!cancelled)
103
+ setState({ svg });
104
+ }
105
+ catch (mermaidError) {
106
+ const excalidrawMessage = errorMessage(excalidrawError);
107
+ const mermaidMessage = errorMessage(mermaidError);
108
+ if (!cancelled) {
109
+ setState({
110
+ error: excalidrawMessage === mermaidMessage
111
+ ? mermaidMessage
112
+ : `Excalidraw: ${excalidrawMessage}; Mermaid fallback: ${mermaidMessage}`,
113
+ });
114
+ }
86
115
  }
87
116
  }
88
117
  })();
@@ -101,8 +130,8 @@ function MermaidDiagram({ source, idSeed, }) {
101
130
  if (!state.svg) {
102
131
  return (_jsx("div", { className: "mt-2 flex min-h-24 items-center justify-center rounded-lg border border-plan-line bg-plan-code text-sm text-plan-muted", children: "Add a diagram definition to render it." }));
103
132
  }
104
- return (_jsx("div", { className: "mt-2 flex justify-center overflow-auto [&_svg]:max-w-full [&_svg]:h-auto",
105
- // Mermaid output is already sanitized under `securityLevel: "strict"`.
133
+ return (_jsx("div", { className: "mt-2 flex justify-center overflow-auto [&_svg]:h-auto [&_svg]:max-w-full",
134
+ // Excalidraw and Mermaid output are sanitized before injection.
106
135
  dangerouslySetInnerHTML: { __html: state.svg } }));
107
136
  }
108
137
  /**