@agent-native/core 0.44.3 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/dist/cli/connect.d.ts +2 -1
  2. package/dist/cli/connect.d.ts.map +1 -1
  3. package/dist/cli/connect.js +185 -5
  4. package/dist/cli/connect.js.map +1 -1
  5. package/dist/cli/index.js +27 -0
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/cli/plan-local.d.ts +43 -0
  8. package/dist/cli/plan-local.d.ts.map +1 -0
  9. package/dist/cli/plan-local.js +477 -0
  10. package/dist/cli/plan-local.js.map +1 -0
  11. package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
  12. package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
  13. package/dist/cli/pr-visual-recap-workflow.js +1 -1
  14. package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
  15. package/dist/cli/recap.d.ts +164 -0
  16. package/dist/cli/recap.d.ts.map +1 -1
  17. package/dist/cli/recap.js +657 -10
  18. package/dist/cli/recap.js.map +1 -1
  19. package/dist/cli/skills.d.ts +6 -2
  20. package/dist/cli/skills.d.ts.map +1 -1
  21. package/dist/cli/skills.js +440 -37
  22. package/dist/cli/skills.js.map +1 -1
  23. package/dist/client/blocks/library/AnnotatedCodeBlock.d.ts.map +1 -1
  24. package/dist/client/blocks/library/AnnotatedCodeBlock.js +18 -4
  25. package/dist/client/blocks/library/AnnotatedCodeBlock.js.map +1 -1
  26. package/dist/client/blocks/library/ApiEndpointBlock.d.ts.map +1 -1
  27. package/dist/client/blocks/library/ApiEndpointBlock.js +11 -7
  28. package/dist/client/blocks/library/ApiEndpointBlock.js.map +1 -1
  29. package/dist/client/blocks/library/DiffBlock.d.ts.map +1 -1
  30. package/dist/client/blocks/library/DiffBlock.js +90 -7
  31. package/dist/client/blocks/library/DiffBlock.js.map +1 -1
  32. package/dist/client/blocks/library/HighlightedCode.d.ts +2 -1
  33. package/dist/client/blocks/library/HighlightedCode.d.ts.map +1 -1
  34. package/dist/client/blocks/library/HighlightedCode.js +2 -2
  35. package/dist/client/blocks/library/HighlightedCode.js.map +1 -1
  36. package/dist/client/blocks/library/JsonExplorerBlock.d.ts.map +1 -1
  37. package/dist/client/blocks/library/JsonExplorerBlock.js +57 -13
  38. package/dist/client/blocks/library/JsonExplorerBlock.js.map +1 -1
  39. package/dist/client/blocks/library/annotation-rail.d.ts +15 -5
  40. package/dist/client/blocks/library/annotation-rail.d.ts.map +1 -1
  41. package/dist/client/blocks/library/annotation-rail.js +35 -24
  42. package/dist/client/blocks/library/annotation-rail.js.map +1 -1
  43. package/dist/client/blocks/library/code-filename-label.d.ts +8 -0
  44. package/dist/client/blocks/library/code-filename-label.d.ts.map +1 -0
  45. package/dist/client/blocks/library/code-filename-label.js +15 -0
  46. package/dist/client/blocks/library/code-filename-label.js.map +1 -0
  47. package/dist/client/blocks/library/code.d.ts.map +1 -1
  48. package/dist/client/blocks/library/code.js +28 -6
  49. package/dist/client/blocks/library/code.js.map +1 -1
  50. package/dist/client/blocks/library/columns.d.ts.map +1 -1
  51. package/dist/client/blocks/library/columns.js +11 -1
  52. package/dist/client/blocks/library/columns.js.map +1 -1
  53. package/dist/client/blocks/library/diff.config.d.ts +1 -1
  54. package/dist/client/blocks/library/diff.config.js.map +1 -1
  55. package/dist/client/blocks/library/narrow-container.d.ts +13 -0
  56. package/dist/client/blocks/library/narrow-container.d.ts.map +1 -0
  57. package/dist/client/blocks/library/narrow-container.js +32 -0
  58. package/dist/client/blocks/library/narrow-container.js.map +1 -0
  59. package/dist/client/blocks/library/question-form.d.ts.map +1 -1
  60. package/dist/client/blocks/library/question-form.js +8 -9
  61. package/dist/client/blocks/library/question-form.js.map +1 -1
  62. package/dist/client/blocks/library/tabs.d.ts.map +1 -1
  63. package/dist/client/blocks/library/tabs.js +11 -5
  64. package/dist/client/blocks/library/tabs.js.map +1 -1
  65. package/dist/client/blocks/types.d.ts +2 -0
  66. package/dist/client/blocks/types.d.ts.map +1 -1
  67. package/dist/client/blocks/types.js.map +1 -1
  68. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  69. package/dist/client/composer/TiptapComposer.js +4 -1
  70. package/dist/client/composer/TiptapComposer.js.map +1 -1
  71. package/dist/client/db-admin/TableEditor.d.ts.map +1 -1
  72. package/dist/client/db-admin/TableEditor.js +3 -1
  73. package/dist/client/db-admin/TableEditor.js.map +1 -1
  74. package/dist/db/client.d.ts +19 -0
  75. package/dist/db/client.d.ts.map +1 -1
  76. package/dist/db/client.js +43 -2
  77. package/dist/db/client.js.map +1 -1
  78. package/dist/db/migrations.d.ts.map +1 -1
  79. package/dist/db/migrations.js +9 -2
  80. package/dist/db/migrations.js.map +1 -1
  81. package/dist/deploy/build.d.ts.map +1 -1
  82. package/dist/deploy/build.js +8 -0
  83. package/dist/deploy/build.js.map +1 -1
  84. package/dist/extensions/html-shell.js +1 -1
  85. package/dist/extensions/html-shell.js.map +1 -1
  86. package/dist/jobs/scheduler.d.ts.map +1 -1
  87. package/dist/jobs/scheduler.js +5 -1
  88. package/dist/jobs/scheduler.js.map +1 -1
  89. package/dist/mcp/build-server.d.ts +1 -0
  90. package/dist/mcp/build-server.d.ts.map +1 -1
  91. package/dist/mcp/build-server.js +7 -3
  92. package/dist/mcp/build-server.js.map +1 -1
  93. package/dist/mcp/oauth-route.d.ts.map +1 -1
  94. package/dist/mcp/oauth-route.js +56 -19
  95. package/dist/mcp/oauth-route.js.map +1 -1
  96. package/dist/mcp/oauth-store.d.ts +1 -0
  97. package/dist/mcp/oauth-store.d.ts.map +1 -1
  98. package/dist/mcp/oauth-store.js +9 -0
  99. package/dist/mcp/oauth-store.js.map +1 -1
  100. package/dist/mcp/server.d.ts.map +1 -1
  101. package/dist/mcp/server.js +9 -4
  102. package/dist/mcp/server.js.map +1 -1
  103. package/dist/mcp-client/errors.js +3 -3
  104. package/dist/mcp-client/errors.js.map +1 -1
  105. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  106. package/dist/server/agent-chat-plugin.js +3 -1
  107. package/dist/server/agent-chat-plugin.js.map +1 -1
  108. package/dist/server/agent-teams.d.ts.map +1 -1
  109. package/dist/server/agent-teams.js +10 -2
  110. package/dist/server/agent-teams.js.map +1 -1
  111. package/dist/server/auth.d.ts.map +1 -1
  112. package/dist/server/auth.js +7 -3
  113. package/dist/server/auth.js.map +1 -1
  114. package/dist/server/recap-image-route.d.ts.map +1 -1
  115. package/dist/server/recap-image-route.js +3 -6
  116. package/dist/server/recap-image-route.js.map +1 -1
  117. package/dist/server/sentry.d.ts.map +1 -1
  118. package/dist/server/sentry.js +12 -5
  119. package/dist/server/sentry.js.map +1 -1
  120. package/dist/server/social-og-image.d.ts.map +1 -1
  121. package/dist/server/social-og-image.js +3 -1
  122. package/dist/server/social-og-image.js.map +1 -1
  123. package/dist/styles/blocks.css +36 -10
  124. package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +22 -6
  125. package/docs/content/plan-plugin.md +18 -1
  126. package/docs/content/pr-visual-recap.md +37 -10
  127. package/docs/content/template-plan.md +45 -1
  128. package/package.json +1 -1
  129. package/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +22 -6
@@ -157,7 +157,7 @@ export const DEFAULT_CODE_MAX_LINES = 40;
157
157
  * is {@link DEFAULT_CODE_MAX_LINES}; the surface only collapses when the code is
158
158
  * actually longer than that.
159
159
  */
160
- export function CodeSurface({ code, language, maxLines = DEFAULT_CODE_MAX_LINES, className, }) {
160
+ export function CodeSurface({ code, language, maxLines = DEFAULT_CODE_MAX_LINES, showLanguageLabel = true, className, }) {
161
161
  const [expanded, setExpanded] = useState(false);
162
162
  const lineCount = useMemo(() => (code ? code.replace(/\n$/, "").split("\n").length : 0), [code]);
163
163
  const cap = typeof maxLines === "number" && maxLines > 0 ? maxLines : null;
@@ -165,7 +165,7 @@ export function CodeSurface({ code, language, maxLines = DEFAULT_CODE_MAX_LINES,
165
165
  const collapsed = collapsible && !expanded;
166
166
  const hiddenLines = collapsible ? lineCount - cap : 0;
167
167
  const label = prettyLanguageName(language);
168
- return (_jsxs("div", { className: cn("plan-code-surface", className ?? "mt-5"), "data-collapsed": collapsed ? "true" : undefined, children: [label && (_jsx("div", { className: "plan-code-surface-bar", children: _jsx("span", { className: "plan-code-surface-lang", children: label }) })), _jsxs("div", { className: "plan-code-surface-scroll", style: collapsed
168
+ return (_jsxs("div", { className: cn("plan-code-surface", className ?? "mt-5"), "data-collapsed": collapsed ? "true" : undefined, children: [showLanguageLabel && label && (_jsx("div", { className: "plan-code-surface-bar", children: _jsx("span", { className: "plan-code-surface-lang", children: label }) })), _jsxs("div", { className: "plan-code-surface-scroll", style: collapsed
169
169
  ? { "--plan-code-max-lines": cap }
170
170
  : undefined, children: [_jsx(HighlightedCode, { code: code, language: language }), collapsed && (_jsx("div", { className: "plan-code-surface-fade", "aria-hidden": "true" }))] }), collapsible && (_jsx("button", { type: "button", "data-plan-interactive": true, className: "plan-code-surface-toggle", onClick: () => setExpanded((value) => !value), children: collapsed
171
171
  ? `Show ${hiddenLines} more line${hiddenLines === 1 ? "" : "s"}`
@@ -1 +1 @@
1
- {"version":3,"file":"HighlightedCode.js","sourceRoot":"","sources":["../../../../src/client/blocks/library/HighlightedCode.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useMemo, useState, type CSSProperties } 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\n/**\n * Human-facing label for a code language hint (the value stored on a code block\n * / code tab). Returns `null` for empty / unknown / plain hints so callers can\n * keep the surface clean (no \"Plain text\" chrome) when the language is unknown\n * or auto-detected. Web languages lead because plans skew that way.\n */\nconst LANGUAGE_LABELS: Record<string, string> = {\n typescript: \"TypeScript\",\n ts: \"TypeScript\",\n tsx: \"TSX\",\n javascript: \"JavaScript\",\n js: \"JavaScript\",\n jsx: \"JSX\",\n json: \"JSON\",\n html: \"HTML\",\n css: \"CSS\",\n scss: \"SCSS\",\n bash: \"Bash\",\n sh: \"Bash\",\n shell: \"Shell\",\n zsh: \"Bash\",\n python: \"Python\",\n py: \"Python\",\n sql: \"SQL\",\n yaml: \"YAML\",\n yml: \"YAML\",\n markdown: \"Markdown\",\n md: \"Markdown\",\n graphql: \"GraphQL\",\n go: \"Go\",\n rust: \"Rust\",\n ruby: \"Ruby\",\n java: \"Java\",\n c: \"C\",\n cpp: \"C++\",\n csharp: \"C#\",\n php: \"PHP\",\n swift: \"Swift\",\n kotlin: \"Kotlin\",\n diff: \"Diff\",\n dockerfile: \"Dockerfile\",\n xml: \"XML\",\n};\n\nexport function prettyLanguageName(language?: string | null): string | null {\n const raw = language\n ?.trim()\n .toLowerCase()\n .replace(/^language-/, \"\");\n if (!raw || raw === \"text\" || raw === \"plaintext\" || raw === \"plain\") {\n return null;\n }\n return LANGUAGE_LABELS[raw] ?? raw;\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\n/**\n * Default number of code lines shown before a code surface collapses behind a\n * \"Show N more lines\" toggle. Long code panes (read view, code tabs, API specs)\n * stay scannable instead of dominating the document, matching the rest of the\n * plan's progressively-disclosed surfaces.\n */\nexport const DEFAULT_CODE_MAX_LINES = 40;\n\n/**\n * Read-only code surface used across the plan blocks (code tabs, API specs) and\n * the markdown read view. Syntax-highlights via {@link HighlightedCode} (Shiki,\n * client-only with a plain `<pre>` SSR fallback), follows the current\n * light/dark `--plan-code` palette, and collapses to `maxLines` with an\n * expand/collapse toggle so long snippets do not run away.\n *\n * `maxLines` of `0` / `null` disables collapsing (show everything). The default\n * is {@link DEFAULT_CODE_MAX_LINES}; the surface only collapses when the code is\n * actually longer than that.\n */\nexport function CodeSurface({\n code,\n language,\n maxLines = DEFAULT_CODE_MAX_LINES,\n className,\n}: {\n code: string;\n language?: string;\n maxLines?: number | null;\n className?: string;\n}) {\n const [expanded, setExpanded] = useState(false);\n\n const lineCount = useMemo(\n () => (code ? code.replace(/\\n$/, \"\").split(\"\\n\").length : 0),\n [code],\n );\n const cap = typeof maxLines === \"number\" && maxLines > 0 ? maxLines : null;\n const collapsible = cap != null && lineCount > cap;\n const collapsed = collapsible && !expanded;\n const hiddenLines = collapsible ? lineCount - cap : 0;\n const label = prettyLanguageName(language);\n\n return (\n <div\n className={cn(\"plan-code-surface\", className ?? \"mt-5\")}\n data-collapsed={collapsed ? \"true\" : undefined}\n >\n {label && (\n <div className=\"plan-code-surface-bar\">\n <span className=\"plan-code-surface-lang\">{label}</span>\n </div>\n )}\n <div\n className=\"plan-code-surface-scroll\"\n style={\n collapsed\n ? ({ \"--plan-code-max-lines\": cap } as CSSProperties)\n : undefined\n }\n >\n <HighlightedCode code={code} language={language} />\n {collapsed && (\n <div className=\"plan-code-surface-fade\" aria-hidden=\"true\" />\n )}\n </div>\n {collapsible && (\n <button\n type=\"button\"\n data-plan-interactive\n className=\"plan-code-surface-toggle\"\n onClick={() => setExpanded((value) => !value)}\n >\n {collapsed\n ? `Show ${hiddenLines} more line${hiddenLines === 1 ? \"\" : \"s\"}`\n : \"Show less\"}\n </button>\n )}\n </div>\n );\n}\n"]}
1
+ {"version":3,"file":"HighlightedCode.js","sourceRoot":"","sources":["../../../../src/client/blocks/library/HighlightedCode.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useMemo, useState, type CSSProperties } 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\n/**\n * Human-facing label for a code language hint (the value stored on a code block\n * / code tab). Returns `null` for empty / unknown / plain hints so callers can\n * keep the surface clean (no \"Plain text\" chrome) when the language is unknown\n * or auto-detected. Web languages lead because plans skew that way.\n */\nconst LANGUAGE_LABELS: Record<string, string> = {\n typescript: \"TypeScript\",\n ts: \"TypeScript\",\n tsx: \"TSX\",\n javascript: \"JavaScript\",\n js: \"JavaScript\",\n jsx: \"JSX\",\n json: \"JSON\",\n html: \"HTML\",\n css: \"CSS\",\n scss: \"SCSS\",\n bash: \"Bash\",\n sh: \"Bash\",\n shell: \"Shell\",\n zsh: \"Bash\",\n python: \"Python\",\n py: \"Python\",\n sql: \"SQL\",\n yaml: \"YAML\",\n yml: \"YAML\",\n markdown: \"Markdown\",\n md: \"Markdown\",\n graphql: \"GraphQL\",\n go: \"Go\",\n rust: \"Rust\",\n ruby: \"Ruby\",\n java: \"Java\",\n c: \"C\",\n cpp: \"C++\",\n csharp: \"C#\",\n php: \"PHP\",\n swift: \"Swift\",\n kotlin: \"Kotlin\",\n diff: \"Diff\",\n dockerfile: \"Dockerfile\",\n xml: \"XML\",\n};\n\nexport function prettyLanguageName(language?: string | null): string | null {\n const raw = language\n ?.trim()\n .toLowerCase()\n .replace(/^language-/, \"\");\n if (!raw || raw === \"text\" || raw === \"plaintext\" || raw === \"plain\") {\n return null;\n }\n return LANGUAGE_LABELS[raw] ?? raw;\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\n/**\n * Default number of code lines shown before a code surface collapses behind a\n * \"Show N more lines\" toggle. Long code panes (read view, code tabs, API specs)\n * stay scannable instead of dominating the document, matching the rest of the\n * plan's progressively-disclosed surfaces.\n */\nexport const DEFAULT_CODE_MAX_LINES = 40;\n\n/**\n * Read-only code surface used across the plan blocks (code tabs, API specs) and\n * the markdown read view. Syntax-highlights via {@link HighlightedCode} (Shiki,\n * client-only with a plain `<pre>` SSR fallback), follows the current\n * light/dark `--plan-code` palette, and collapses to `maxLines` with an\n * expand/collapse toggle so long snippets do not run away.\n *\n * `maxLines` of `0` / `null` disables collapsing (show everything). The default\n * is {@link DEFAULT_CODE_MAX_LINES}; the surface only collapses when the code is\n * actually longer than that.\n */\nexport function CodeSurface({\n code,\n language,\n maxLines = DEFAULT_CODE_MAX_LINES,\n showLanguageLabel = true,\n className,\n}: {\n code: string;\n language?: string;\n maxLines?: number | null;\n showLanguageLabel?: boolean;\n className?: string;\n}) {\n const [expanded, setExpanded] = useState(false);\n\n const lineCount = useMemo(\n () => (code ? code.replace(/\\n$/, \"\").split(\"\\n\").length : 0),\n [code],\n );\n const cap = typeof maxLines === \"number\" && maxLines > 0 ? maxLines : null;\n const collapsible = cap != null && lineCount > cap;\n const collapsed = collapsible && !expanded;\n const hiddenLines = collapsible ? lineCount - cap : 0;\n const label = prettyLanguageName(language);\n\n return (\n <div\n className={cn(\"plan-code-surface\", className ?? \"mt-5\")}\n data-collapsed={collapsed ? \"true\" : undefined}\n >\n {showLanguageLabel && label && (\n <div className=\"plan-code-surface-bar\">\n <span className=\"plan-code-surface-lang\">{label}</span>\n </div>\n )}\n <div\n className=\"plan-code-surface-scroll\"\n style={\n collapsed\n ? ({ \"--plan-code-max-lines\": cap } as CSSProperties)\n : undefined\n }\n >\n <HighlightedCode code={code} language={language} />\n {collapsed && (\n <div className=\"plan-code-surface-fade\" aria-hidden=\"true\" />\n )}\n </div>\n {collapsible && (\n <button\n type=\"button\"\n data-plan-interactive\n className=\"plan-code-surface-toggle\"\n onClick={() => setExpanded((value) => !value)}\n >\n {collapsed\n ? `Show ${hiddenLines} more line${hiddenLines === 1 ? \"\" : \"s\"}`\n : \"Show less\"}\n </button>\n )}\n </div>\n );\n}\n"]}
@@ -1 +1 @@
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,2CAgFA;AAID;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,IAAI,EACJ,QAAQ,EACR,QAAQ,GACT,EAAE,cAAc,CAAC,gBAAgB,CAAC,2CA6HlC"}
1
+ {"version":3,"file":"JsonExplorerBlock.d.ts","sourceRoot":"","sources":["../../../../src/client/blocks/library/JsonExplorerBlock.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,2BAA2B,CAAC;AAySnC;;;;;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,2CAqHA;AAID;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,IAAI,EACJ,QAAQ,EACR,QAAQ,GACT,EAAE,cAAc,CAAC,gBAAgB,CAAC,2CA6HlC"}
@@ -1,5 +1,5 @@
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 { useCallback, useEffect, useId, useMemo, useState, } from "react";
3
3
  import { IconChevronRight } from "@tabler/icons-react";
4
4
  import { cn } from "../../utils.js";
5
5
  import { JSON_EXPLORER_DEFAULT_COLLAPSED_DEPTH, JSON_EXPLORER_MAX_COLLAPSED_DEPTH, } from "./json-explorer.config.js";
@@ -63,6 +63,13 @@ function parseJson(raw) {
63
63
  function isContainer(value) {
64
64
  return value !== null && typeof value === "object";
65
65
  }
66
+ function isNonEmptyContainer(value) {
67
+ if (!isContainer(value))
68
+ return false;
69
+ return Array.isArray(value)
70
+ ? value.length > 0
71
+ : Object.keys(value).length > 0;
72
+ }
66
73
  /** One-line summary for a collapsed container, devtools style. */
67
74
  function containerSummary(value) {
68
75
  if (Array.isArray(value)) {
@@ -91,7 +98,7 @@ function LeafValue({ value }) {
91
98
  * seeded from `collapsedDepth` and re-seeded whenever the global expand/collapse
92
99
  * "pulse" (`forceOpen`) flips. Leaves render inline with their type color.
93
100
  */
94
- function JsonNode({ label, value, depth, collapsedDepth, forceOpen, trailingComma, }) {
101
+ function JsonNode({ nodeId, label, value, depth, collapsedDepth, forceOpen, onContainerStateChange, trailingComma, }) {
95
102
  const seededOpen = forceOpen?.open ?? depth < collapsedDepth;
96
103
  // `forceOpen` is the global pulse: when the user hits expand/collapse all we
97
104
  // flip every node, but per-node toggles still win afterward.
@@ -100,8 +107,12 @@ function JsonNode({ label, value, depth, collapsedDepth, forceOpen, trailingComm
100
107
  let open = openState.open;
101
108
  if (forceOpen !== openState.forceOpen) {
102
109
  open = forceOpen?.open ?? openState.open;
103
- setOpenState({ forceOpen, open });
104
110
  }
111
+ useEffect(() => {
112
+ if (forceOpen === openState.forceOpen)
113
+ return;
114
+ setOpenState({ forceOpen, open });
115
+ }, [forceOpen, open, openState.forceOpen]);
105
116
  const handleToggle = (event) => {
106
117
  const nextOpen = !open;
107
118
  setOpenState((prev) => ({ ...prev, open: nextOpen }));
@@ -116,19 +127,28 @@ function JsonNode({ label, value, depth, collapsedDepth, forceOpen, trailingComm
116
127
  }
117
128
  };
118
129
  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;
119
- if (!isContainer(value)) {
130
+ const container = isContainer(value);
131
+ const entries = container
132
+ ? Array.isArray(value)
133
+ ? value.map((item, index) => [index, item])
134
+ : Object.entries(value)
135
+ : [];
136
+ const empty = entries.length === 0;
137
+ useEffect(() => {
138
+ if (!container || empty || !onContainerStateChange)
139
+ return;
140
+ onContainerStateChange(nodeId, open);
141
+ return () => onContainerStateChange(nodeId, null);
142
+ }, [container, empty, nodeId, onContainerStateChange, open]);
143
+ if (!container) {
120
144
  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: "," })] }));
121
145
  }
122
146
  const isArray = Array.isArray(value);
123
- const entries = isArray
124
- ? value.map((item, index) => [index, item])
125
- : Object.entries(value);
126
147
  const openBrace = isArray ? "[" : "{";
127
148
  const closeBrace = isArray ? "]" : "}";
128
- const empty = entries.length === 0;
129
149
  const childForceOpen = subtreePulse ?? forceOpen;
130
150
  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: "," })] }) })] }))] }));
151
+ 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, { nodeId: `${nodeId}/${JSON.stringify(entryKey)}`, label: entryKey, value: entryValue, depth: depth + 1, collapsedDepth: collapsedDepth, forceOpen: childForceOpen, onContainerStateChange: onContainerStateChange, 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: "," })] }) })] }))] }));
132
152
  }
133
153
  /* ── Read (collapsible devtools tree) ──────────────────────────────────────── */
134
154
  /**
@@ -147,16 +167,40 @@ export function JsonExplorerSurface({ data, className, label = "JSON", }) {
147
167
  // `pulse` carries a boolean (expand/collapse) plus a nonce so repeated clicks
148
168
  // of the same action still re-fire the reseed in each node.
149
169
  const [pulse, setPulse] = useState(null);
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) => ({
170
+ const [containerOpenStates, setContainerOpenStates] = useState({});
171
+ const handleContainerStateChange = useCallback((nodeId, open) => {
172
+ setContainerOpenStates((prev) => {
173
+ if (open === null) {
174
+ if (!(nodeId in prev))
175
+ return prev;
176
+ const next = { ...prev };
177
+ delete next[nodeId];
178
+ return next;
179
+ }
180
+ if (prev[nodeId] === open)
181
+ return prev;
182
+ return { ...prev, [nodeId]: open };
183
+ });
184
+ }, []);
185
+ const openStates = Object.values(containerOpenStates);
186
+ const hasRegisteredContainers = openStates.length > 0;
187
+ const fullyExpanded = hasRegisteredContainers && openStates.every((open) => open);
188
+ const fullyCollapsed = hasRegisteredContainers && openStates.every((open) => !open);
189
+ const parsedValue = parsed.ok ? parsed.value : null;
190
+ const showTreeActions = parsedValue
191
+ ? isNonEmptyContainer(parsedValue)
192
+ : false;
193
+ const actionButtonClass = "rounded px-1.5 py-0.5 text-xs text-plan-muted transition-colors hover:bg-accent/60 hover:text-plan-text disabled:pointer-events-none disabled:opacity-40";
194
+ return (_jsxs("div", { className: cn("group 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 }), showTreeActions && (_jsxs("div", { "data-json-explorer-actions": "true", className: "pointer-events-none flex items-center gap-1 opacity-0 transition-opacity group-hover:pointer-events-auto group-hover:opacity-100 group-focus-within:pointer-events-auto group-focus-within:opacity-100", children: [_jsx("button", { type: "button", "data-plan-interactive": true, disabled: fullyExpanded, onClick: () => setPulse((prev) => ({
151
195
  open: true,
152
196
  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) => ({
197
+ })), className: actionButtonClass, children: "Expand all" }), _jsx("span", { className: "text-plan-muted", children: "\u00B7" }), _jsx("button", { type: "button", "data-plan-interactive": true, disabled: fullyCollapsed, onClick: () => setPulse((prev) => ({
154
198
  open: false,
155
199
  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 [font-size:var(--plan-doc-code-size)] text-plan-code-text", "data-code-surface": true, children: parsed.ok ? (_jsx(JsonNode
200
+ })), className: actionButtonClass, children: "Collapse all" })] }))] }), _jsx("div", { className: "overflow-auto px-3 py-2.5 font-mono [font-size:var(--plan-doc-code-size)] text-plan-code-text", "data-code-surface": true, children: parsed.ok ? (_jsx(JsonNode
157
201
  // Remount the whole tree when the global pulse fires so every node
158
202
  // 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] })] })) })] }));
203
+ , { nodeId: "$", value: parsed.value, depth: 0, collapsedDepth: collapsedDepth, forceOpen: pulse, onContainerStateChange: handleContainerStateChange }, 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] })] })) })] }));
160
204
  }
161
205
  /* ── Edit (panel form) ─────────────────────────────────────────────────────── */
162
206
  /**
@@ -1 +1 @@
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\n className=\"overflow-auto px-3 py-2.5 font-mono [font-size:var(--plan-doc-code-size)] text-plan-code-text\"\n data-code-surface\n >\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
+ {"version":3,"file":"JsonExplorerBlock.js","sourceRoot":"","sources":["../../../../src/client/blocks/library/JsonExplorerBlock.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n useCallback,\n useEffect,\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\nfunction isNonEmptyContainer(\n value: JsonValue,\n): value is JsonValue[] | JsonObject {\n if (!isContainer(value)) return false;\n return Array.isArray(value)\n ? value.length > 0\n : Object.keys(value).length > 0;\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 /** Stable id used by the root surface to derive aggregate expansion state. */\n nodeId: string;\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 onContainerStateChange?: (nodeId: string, open: boolean | null) => void;\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 nodeId,\n label,\n value,\n depth,\n collapsedDepth,\n forceOpen,\n onContainerStateChange,\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 }\n\n useEffect(() => {\n if (forceOpen === openState.forceOpen) return;\n setOpenState({ forceOpen, open });\n }, [forceOpen, open, openState.forceOpen]);\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 const container = isContainer(value);\n const entries: Array<[string | number, JsonValue]> = container\n ? Array.isArray(value)\n ? (value as JsonValue[]).map((item, index) => [index, item])\n : Object.entries(value as JsonObject)\n : [];\n const empty = entries.length === 0;\n\n useEffect(() => {\n if (!container || empty || !onContainerStateChange) return;\n onContainerStateChange(nodeId, open);\n return () => onContainerStateChange(nodeId, null);\n }, [container, empty, nodeId, onContainerStateChange, open]);\n\n if (!container) {\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 openBrace = isArray ? \"[\" : \"{\";\n const closeBrace = isArray ? \"]\" : \"}\";\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 nodeId={`${nodeId}/${JSON.stringify(entryKey)}`}\n label={entryKey}\n value={entryValue}\n depth={depth + 1}\n collapsedDepth={collapsedDepth}\n forceOpen={childForceOpen}\n onContainerStateChange={onContainerStateChange}\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 const [containerOpenStates, setContainerOpenStates] = useState<\n Record<string, boolean>\n >({});\n const handleContainerStateChange = useCallback(\n (nodeId: string, open: boolean | null) => {\n setContainerOpenStates((prev) => {\n if (open === null) {\n if (!(nodeId in prev)) return prev;\n const next = { ...prev };\n delete next[nodeId];\n return next;\n }\n if (prev[nodeId] === open) return prev;\n return { ...prev, [nodeId]: open };\n });\n },\n [],\n );\n const openStates = Object.values(containerOpenStates);\n const hasRegisteredContainers = openStates.length > 0;\n const fullyExpanded =\n hasRegisteredContainers && openStates.every((open) => open);\n const fullyCollapsed =\n hasRegisteredContainers && openStates.every((open) => !open);\n const parsedValue = parsed.ok ? (parsed.value as JsonValue) : null;\n const showTreeActions = parsedValue\n ? isNonEmptyContainer(parsedValue)\n : false;\n const actionButtonClass =\n \"rounded px-1.5 py-0.5 text-xs text-plan-muted transition-colors hover:bg-accent/60 hover:text-plan-text disabled:pointer-events-none disabled:opacity-40\";\n\n return (\n <div\n className={cn(\n \"group 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 {showTreeActions && (\n <div\n data-json-explorer-actions=\"true\"\n className=\"pointer-events-none flex items-center gap-1 opacity-0 transition-opacity group-hover:pointer-events-auto group-hover:opacity-100 group-focus-within:pointer-events-auto group-focus-within:opacity-100\"\n >\n <button\n type=\"button\"\n data-plan-interactive\n disabled={fullyExpanded}\n onClick={() =>\n setPulse((prev) => ({\n open: true,\n nonce: (prev?.nonce ?? 0) + 1,\n }))\n }\n className={actionButtonClass}\n >\n Expand all\n </button>\n <span className=\"text-plan-muted\">·</span>\n <button\n type=\"button\"\n data-plan-interactive\n disabled={fullyCollapsed}\n onClick={() =>\n setPulse((prev) => ({\n open: false,\n nonce: (prev?.nonce ?? 0) + 1,\n }))\n }\n className={actionButtonClass}\n >\n Collapse all\n </button>\n </div>\n )}\n </div>\n <div\n className=\"overflow-auto px-3 py-2.5 font-mono [font-size:var(--plan-doc-code-size)] text-plan-code-text\"\n data-code-surface\n >\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 nodeId=\"$\"\n value={parsed.value as JsonValue}\n depth={0}\n collapsedDepth={collapsedDepth}\n forceOpen={pulse}\n onContainerStateChange={handleContainerStateChange}\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"]}
@@ -113,17 +113,27 @@ export interface AnnotationAnchor {
113
113
  /** Bottom of the hovered line (for the below-fallback placement). */
114
114
  lineBottom: number;
115
115
  }
116
+ export declare function resolveAnnotationHoverCardPosition(anchor: AnnotationAnchor, card: {
117
+ width: number;
118
+ height: number;
119
+ }, viewport: {
120
+ width: number;
121
+ height: number;
122
+ }): {
123
+ top: number;
124
+ left: number;
125
+ };
116
126
  /**
117
127
  * The single on-hover note card, portaled to `document.body` and positioned
118
128
  * `fixed` so it escapes the code block's `overflow` and never reflows the code.
119
129
  *
120
130
  * Placement: by default it sits to the RIGHT of the code block's right edge,
121
131
  * vertically centered on the hovered line — so it never overlaps the code text.
122
- * If there isn't room to the right (it would overflow the viewport), it clamps
123
- * within the viewport, and if the right gutter is too narrow for the card it
124
- * falls back to BELOW the hovered line (left-aligned to the code block). The card
125
- * keeps itself open while hovered (`onMouseEnter`/`onMouseLeave` forwarded) so it
126
- * stays readable; the caller adds the small hover-intent close delay.
132
+ * If there isn't room to the right, it uses the LEFT of the code block when the
133
+ * card can fit there without covering code. Only when neither side fits does it
134
+ * fall back to BELOW the hovered line (left-aligned to the code block). The card
135
+ * keeps itself open while hovered (`onMouseEnter`/`onMouseLeave` forwarded) so
136
+ * it stays readable; the caller adds the small hover-intent close delay.
127
137
  */
128
138
  export declare function AnnotationHoverCard<A extends RailAnnotation>({ item, anchor, ctx, showMarker, onMouseEnter, onMouseLeave, }: {
129
139
  item: ResolvedAnnotation<A>;
@@ -1 +1 @@
1
- {"version":3,"file":"annotation-rail.d.ts","sourceRoot":"","sources":["../../../../src/client/blocks/library/annotation-rail.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"annotation-rail.d.ts","sourceRoot":"","sources":["../../../../src/client/blocks/library/annotation-rail.tsx"],"names":[],"mappings":"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"}
@@ -126,23 +126,51 @@ export function AnnotationHiddenStack({ items, ctx, showMarker = false, }) {
126
126
  const HOVER_CARD_WIDTH = 280;
127
127
  const HOVER_CARD_GAP = 12;
128
128
  const VIEWPORT_MARGIN = 8;
129
+ export function resolveAnnotationHoverCardPosition(anchor, card, viewport) {
130
+ const rightLeft = anchor.codeRight + HOVER_CARD_GAP;
131
+ const fitsRight = rightLeft + card.width + VIEWPORT_MARGIN <= viewport.width;
132
+ const leftLeft = anchor.codeLeft - HOVER_CARD_GAP - card.width;
133
+ const fitsLeft = leftLeft >= VIEWPORT_MARGIN;
134
+ let left;
135
+ let top;
136
+ if (fitsRight) {
137
+ // Default: to the right of the code, centered on the hovered line.
138
+ left = rightLeft;
139
+ top = anchor.lineCenter - card.height / 2;
140
+ }
141
+ else if (fitsLeft) {
142
+ // Prefer the left gutter over covering the code below the hovered line.
143
+ left = leftLeft;
144
+ top = anchor.lineCenter - card.height / 2;
145
+ }
146
+ else {
147
+ // No clean side gutter → drop below the line, aligned to the code's left.
148
+ left = anchor.codeLeft;
149
+ top = anchor.lineBottom + HOVER_CARD_GAP;
150
+ }
151
+ // Clamp within the viewport so the card is never cut off.
152
+ left = Math.max(VIEWPORT_MARGIN, Math.min(left, viewport.width - card.width - VIEWPORT_MARGIN));
153
+ top = Math.max(VIEWPORT_MARGIN, Math.min(top, viewport.height - card.height - VIEWPORT_MARGIN));
154
+ return { top, left };
155
+ }
129
156
  /**
130
157
  * The single on-hover note card, portaled to `document.body` and positioned
131
158
  * `fixed` so it escapes the code block's `overflow` and never reflows the code.
132
159
  *
133
160
  * Placement: by default it sits to the RIGHT of the code block's right edge,
134
161
  * vertically centered on the hovered line — so it never overlaps the code text.
135
- * If there isn't room to the right (it would overflow the viewport), it clamps
136
- * within the viewport, and if the right gutter is too narrow for the card it
137
- * falls back to BELOW the hovered line (left-aligned to the code block). The card
138
- * keeps itself open while hovered (`onMouseEnter`/`onMouseLeave` forwarded) so it
139
- * stays readable; the caller adds the small hover-intent close delay.
162
+ * If there isn't room to the right, it uses the LEFT of the code block when the
163
+ * card can fit there without covering code. Only when neither side fits does it
164
+ * fall back to BELOW the hovered line (left-aligned to the code block). The card
165
+ * keeps itself open while hovered (`onMouseEnter`/`onMouseLeave` forwarded) so
166
+ * it stays readable; the caller adds the small hover-intent close delay.
140
167
  */
141
168
  export function AnnotationHoverCard({ item, anchor, ctx, showMarker = false, onMouseEnter, onMouseLeave, }) {
142
169
  const cardRef = useRef(null);
143
170
  const [pos, setPos] = useState(null);
144
171
  // Measure the rendered card, then resolve a non-overlapping position: right of
145
- // the code if it fits, else clamp into the viewport, else drop below the line.
172
+ // the code if it fits, then left if that side has a clean gutter, otherwise
173
+ // below the line.
146
174
  useLayoutEffect(() => {
147
175
  if (typeof window === "undefined")
148
176
  return;
@@ -152,24 +180,7 @@ export function AnnotationHoverCard({ item, anchor, ctx, showMarker = false, onM
152
180
  const height = rect && rect.height > 0 ? rect.height : 0;
153
181
  const vw = window.innerWidth || 0;
154
182
  const vh = window.innerHeight || 0;
155
- const rightLeft = anchor.codeRight + HOVER_CARD_GAP;
156
- const fitsRight = rightLeft + width + VIEWPORT_MARGIN <= vw;
157
- let left;
158
- let top;
159
- if (fitsRight) {
160
- // Default: to the right of the code, centered on the hovered line.
161
- left = rightLeft;
162
- top = anchor.lineCenter - height / 2;
163
- }
164
- else {
165
- // No room to the right → drop below the line, aligned to the code's left.
166
- left = anchor.codeLeft;
167
- top = anchor.lineBottom + HOVER_CARD_GAP;
168
- }
169
- // Clamp within the viewport so the card is never cut off.
170
- left = Math.max(VIEWPORT_MARGIN, Math.min(left, vw - width - VIEWPORT_MARGIN));
171
- top = Math.max(VIEWPORT_MARGIN, Math.min(top, vh - height - VIEWPORT_MARGIN));
172
- setPos({ top, left });
183
+ setPos(resolveAnnotationHoverCardPosition(anchor, { width, height }, { width: vw, height: vh }));
173
184
  }, [
174
185
  anchor.codeRight,
175
186
  anchor.codeLeft,