@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.
- package/dist/cli/connect.d.ts +2 -1
- package/dist/cli/connect.d.ts.map +1 -1
- package/dist/cli/connect.js +185 -5
- package/dist/cli/connect.js.map +1 -1
- package/dist/cli/index.js +27 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/plan-local.d.ts +43 -0
- package/dist/cli/plan-local.d.ts.map +1 -0
- package/dist/cli/plan-local.js +477 -0
- package/dist/cli/plan-local.js.map +1 -0
- package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.js +1 -1
- package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
- package/dist/cli/recap.d.ts +164 -0
- package/dist/cli/recap.d.ts.map +1 -1
- package/dist/cli/recap.js +657 -10
- package/dist/cli/recap.js.map +1 -1
- package/dist/cli/skills.d.ts +6 -2
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +440 -37
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/blocks/library/AnnotatedCodeBlock.d.ts.map +1 -1
- package/dist/client/blocks/library/AnnotatedCodeBlock.js +18 -4
- package/dist/client/blocks/library/AnnotatedCodeBlock.js.map +1 -1
- package/dist/client/blocks/library/ApiEndpointBlock.d.ts.map +1 -1
- package/dist/client/blocks/library/ApiEndpointBlock.js +11 -7
- package/dist/client/blocks/library/ApiEndpointBlock.js.map +1 -1
- package/dist/client/blocks/library/DiffBlock.d.ts.map +1 -1
- package/dist/client/blocks/library/DiffBlock.js +90 -7
- package/dist/client/blocks/library/DiffBlock.js.map +1 -1
- package/dist/client/blocks/library/HighlightedCode.d.ts +2 -1
- package/dist/client/blocks/library/HighlightedCode.d.ts.map +1 -1
- package/dist/client/blocks/library/HighlightedCode.js +2 -2
- package/dist/client/blocks/library/HighlightedCode.js.map +1 -1
- package/dist/client/blocks/library/JsonExplorerBlock.d.ts.map +1 -1
- package/dist/client/blocks/library/JsonExplorerBlock.js +57 -13
- package/dist/client/blocks/library/JsonExplorerBlock.js.map +1 -1
- package/dist/client/blocks/library/annotation-rail.d.ts +15 -5
- package/dist/client/blocks/library/annotation-rail.d.ts.map +1 -1
- package/dist/client/blocks/library/annotation-rail.js +35 -24
- package/dist/client/blocks/library/annotation-rail.js.map +1 -1
- package/dist/client/blocks/library/code-filename-label.d.ts +8 -0
- package/dist/client/blocks/library/code-filename-label.d.ts.map +1 -0
- package/dist/client/blocks/library/code-filename-label.js +15 -0
- package/dist/client/blocks/library/code-filename-label.js.map +1 -0
- package/dist/client/blocks/library/code.d.ts.map +1 -1
- package/dist/client/blocks/library/code.js +28 -6
- package/dist/client/blocks/library/code.js.map +1 -1
- package/dist/client/blocks/library/columns.d.ts.map +1 -1
- package/dist/client/blocks/library/columns.js +11 -1
- package/dist/client/blocks/library/columns.js.map +1 -1
- package/dist/client/blocks/library/diff.config.d.ts +1 -1
- package/dist/client/blocks/library/diff.config.js.map +1 -1
- package/dist/client/blocks/library/narrow-container.d.ts +13 -0
- package/dist/client/blocks/library/narrow-container.d.ts.map +1 -0
- package/dist/client/blocks/library/narrow-container.js +32 -0
- package/dist/client/blocks/library/narrow-container.js.map +1 -0
- package/dist/client/blocks/library/question-form.d.ts.map +1 -1
- package/dist/client/blocks/library/question-form.js +8 -9
- package/dist/client/blocks/library/question-form.js.map +1 -1
- package/dist/client/blocks/library/tabs.d.ts.map +1 -1
- package/dist/client/blocks/library/tabs.js +11 -5
- package/dist/client/blocks/library/tabs.js.map +1 -1
- package/dist/client/blocks/types.d.ts +2 -0
- package/dist/client/blocks/types.d.ts.map +1 -1
- package/dist/client/blocks/types.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +4 -1
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/db-admin/TableEditor.d.ts.map +1 -1
- package/dist/client/db-admin/TableEditor.js +3 -1
- package/dist/client/db-admin/TableEditor.js.map +1 -1
- package/dist/db/client.d.ts +19 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +43 -2
- package/dist/db/client.js.map +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +9 -2
- package/dist/db/migrations.js.map +1 -1
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +8 -0
- package/dist/deploy/build.js.map +1 -1
- package/dist/extensions/html-shell.js +1 -1
- package/dist/extensions/html-shell.js.map +1 -1
- package/dist/jobs/scheduler.d.ts.map +1 -1
- package/dist/jobs/scheduler.js +5 -1
- package/dist/jobs/scheduler.js.map +1 -1
- package/dist/mcp/build-server.d.ts +1 -0
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +7 -3
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/mcp/oauth-route.d.ts.map +1 -1
- package/dist/mcp/oauth-route.js +56 -19
- package/dist/mcp/oauth-route.js.map +1 -1
- package/dist/mcp/oauth-store.d.ts +1 -0
- package/dist/mcp/oauth-store.d.ts.map +1 -1
- package/dist/mcp/oauth-store.js +9 -0
- package/dist/mcp/oauth-store.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +9 -4
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp-client/errors.js +3 -3
- package/dist/mcp-client/errors.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +3 -1
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-teams.d.ts.map +1 -1
- package/dist/server/agent-teams.js +10 -2
- package/dist/server/agent-teams.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +7 -3
- package/dist/server/auth.js.map +1 -1
- package/dist/server/recap-image-route.d.ts.map +1 -1
- package/dist/server/recap-image-route.js +3 -6
- package/dist/server/recap-image-route.js.map +1 -1
- package/dist/server/sentry.d.ts.map +1 -1
- package/dist/server/sentry.js +12 -5
- package/dist/server/sentry.js.map +1 -1
- package/dist/server/social-og-image.d.ts.map +1 -1
- package/dist/server/social-og-image.js +3 -1
- package/dist/server/social-og-image.js.map +1 -1
- package/dist/styles/blocks.css +36 -10
- package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +22 -6
- package/docs/content/plan-plugin.md +18 -1
- package/docs/content/pr-visual-recap.md +37 -10
- package/docs/content/template-plan.md +45 -1
- package/package.json +1 -1
- 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":"
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* keeps itself open while hovered (`onMouseEnter`/`onMouseLeave` forwarded) so
|
|
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
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
* keeps itself open while hovered (`onMouseEnter`/`onMouseLeave` forwarded) so
|
|
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,
|
|
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
|
-
|
|
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,
|