@agent-native/core 0.41.0 → 0.42.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 (109) hide show
  1. package/dist/action.d.ts +13 -1
  2. package/dist/action.d.ts.map +1 -1
  3. package/dist/action.js.map +1 -1
  4. package/dist/agent/production-agent.d.ts +8 -0
  5. package/dist/agent/production-agent.d.ts.map +1 -1
  6. package/dist/agent/production-agent.js +93 -0
  7. package/dist/agent/production-agent.js.map +1 -1
  8. package/dist/cli/app-skill.d.ts +16 -0
  9. package/dist/cli/app-skill.d.ts.map +1 -1
  10. package/dist/cli/app-skill.js +33 -3
  11. package/dist/cli/app-skill.js.map +1 -1
  12. package/dist/cli/create.d.ts.map +1 -1
  13. package/dist/cli/create.js +57 -0
  14. package/dist/cli/create.js.map +1 -1
  15. package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
  16. package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
  17. package/dist/cli/pr-visual-recap-workflow.js +1 -1
  18. package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
  19. package/dist/cli/recap.d.ts.map +1 -1
  20. package/dist/cli/recap.js +14 -3
  21. package/dist/cli/recap.js.map +1 -1
  22. package/dist/cli/skills.d.ts +34 -3
  23. package/dist/cli/skills.d.ts.map +1 -1
  24. package/dist/cli/skills.js +172 -48
  25. package/dist/cli/skills.js.map +1 -1
  26. package/dist/cli/workspacify.d.ts.map +1 -1
  27. package/dist/cli/workspacify.js +19 -4
  28. package/dist/cli/workspacify.js.map +1 -1
  29. package/dist/client/AssistantChat.d.ts.map +1 -1
  30. package/dist/client/AssistantChat.js +2 -2
  31. package/dist/client/AssistantChat.js.map +1 -1
  32. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  33. package/dist/client/agent-chat-adapter.js +172 -5
  34. package/dist/client/agent-chat-adapter.js.map +1 -1
  35. package/dist/client/blocks/library/AnnotatedCodeBlock.d.ts +19 -0
  36. package/dist/client/blocks/library/AnnotatedCodeBlock.d.ts.map +1 -1
  37. package/dist/client/blocks/library/AnnotatedCodeBlock.js +5 -57
  38. package/dist/client/blocks/library/AnnotatedCodeBlock.js.map +1 -1
  39. package/dist/client/blocks/library/ApiEndpointBlock.d.ts.map +1 -1
  40. package/dist/client/blocks/library/ApiEndpointBlock.js +116 -7
  41. package/dist/client/blocks/library/ApiEndpointBlock.js.map +1 -1
  42. package/dist/client/blocks/library/DataModelBlock.d.ts.map +1 -1
  43. package/dist/client/blocks/library/DataModelBlock.js +75 -9
  44. package/dist/client/blocks/library/DataModelBlock.js.map +1 -1
  45. package/dist/client/blocks/library/DiffBlock.d.ts +1 -1
  46. package/dist/client/blocks/library/DiffBlock.d.ts.map +1 -1
  47. package/dist/client/blocks/library/DiffBlock.js +195 -34
  48. package/dist/client/blocks/library/DiffBlock.js.map +1 -1
  49. package/dist/client/blocks/library/HighlightedCode.d.ts +1 -1
  50. package/dist/client/blocks/library/HighlightedCode.js +1 -1
  51. package/dist/client/blocks/library/HighlightedCode.js.map +1 -1
  52. package/dist/client/blocks/library/annotation-rail.d.ts +96 -0
  53. package/dist/client/blocks/library/annotation-rail.d.ts.map +1 -0
  54. package/dist/client/blocks/library/annotation-rail.js +120 -0
  55. package/dist/client/blocks/library/annotation-rail.js.map +1 -0
  56. package/dist/client/blocks/library/api-endpoint.config.d.ts +31 -6
  57. package/dist/client/blocks/library/api-endpoint.config.d.ts.map +1 -1
  58. package/dist/client/blocks/library/api-endpoint.config.js +30 -6
  59. package/dist/client/blocks/library/api-endpoint.config.js.map +1 -1
  60. package/dist/client/blocks/library/code.d.ts.map +1 -1
  61. package/dist/client/blocks/library/code.js +32 -15
  62. package/dist/client/blocks/library/code.js.map +1 -1
  63. package/dist/client/blocks/library/columns.d.ts.map +1 -1
  64. package/dist/client/blocks/library/columns.js +56 -35
  65. package/dist/client/blocks/library/columns.js.map +1 -1
  66. package/dist/client/blocks/library/data-model.config.d.ts +17 -0
  67. package/dist/client/blocks/library/data-model.config.d.ts.map +1 -1
  68. package/dist/client/blocks/library/data-model.config.js +15 -0
  69. package/dist/client/blocks/library/data-model.config.js.map +1 -1
  70. package/dist/client/blocks/library/diff.config.d.ts +28 -6
  71. package/dist/client/blocks/library/diff.config.d.ts.map +1 -1
  72. package/dist/client/blocks/library/diff.config.js +30 -6
  73. package/dist/client/blocks/library/diff.config.js.map +1 -1
  74. package/dist/client/blocks/types.d.ts +2 -2
  75. package/dist/client/blocks/types.d.ts.map +1 -1
  76. package/dist/client/blocks/types.js.map +1 -1
  77. package/dist/client/rich-markdown-editor/DragHandle.d.ts.map +1 -1
  78. package/dist/client/rich-markdown-editor/DragHandle.js +75 -9
  79. package/dist/client/rich-markdown-editor/DragHandle.js.map +1 -1
  80. package/dist/client/rich-markdown-editor/RegistryBlockNode.d.ts +25 -1
  81. package/dist/client/rich-markdown-editor/RegistryBlockNode.d.ts.map +1 -1
  82. package/dist/client/rich-markdown-editor/RegistryBlockNode.js +29 -6
  83. package/dist/client/rich-markdown-editor/RegistryBlockNode.js.map +1 -1
  84. package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts +8 -1
  85. package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts.map +1 -1
  86. package/dist/client/rich-markdown-editor/SharedRichEditor.js +5 -1
  87. package/dist/client/rich-markdown-editor/SharedRichEditor.js.map +1 -1
  88. package/dist/extensions/actions.d.ts.map +1 -1
  89. package/dist/extensions/actions.js +159 -12
  90. package/dist/extensions/actions.js.map +1 -1
  91. package/dist/extensions/store.d.ts +21 -0
  92. package/dist/extensions/store.d.ts.map +1 -1
  93. package/dist/extensions/store.js +33 -1
  94. package/dist/extensions/store.js.map +1 -1
  95. package/dist/server/recap-image-route.d.ts.map +1 -1
  96. package/dist/server/recap-image-route.js +12 -3
  97. package/dist/server/recap-image-route.js.map +1 -1
  98. package/dist/templates/default/pnpm-workspace.yaml +7 -0
  99. package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +30 -5
  100. package/dist/templates/workspace-root/package.json +0 -5
  101. package/dist/templates/workspace-root/pnpm-workspace.yaml +14 -0
  102. package/docs/content/plan-plugin.md +107 -0
  103. package/docs/content/skills-guide.md +8 -0
  104. package/docs/content/visual-plans.md +2 -0
  105. package/package.json +5 -1
  106. package/src/templates/default/pnpm-workspace.yaml +7 -0
  107. package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +30 -5
  108. package/src/templates/workspace-root/package.json +0 -5
  109. package/src/templates/workspace-root/pnpm-workspace.yaml +14 -0
@@ -1,5 +1,24 @@
1
1
  import type { BlockEditProps, BlockReadProps } from "../types.js";
2
2
  import type { AnnotatedCodeData } from "./annotated-code.config.js";
3
+ /**
4
+ * "Explain this code" walkthrough block: a standard syntax-highlighted code
5
+ * surface on the left with line-anchored annotation cards on the right (the
6
+ * Stripe-docs / Sourcegraph layout). Each annotated line range gets a subtle
7
+ * highlight band + an accent rail down the gutter; its card shows the `lines`
8
+ * range, optional `label`, and the always-visible markdown `note` (via
9
+ * `ctx.renderMarkdown`). Hovering a card highlights its lines and vice-versa.
10
+ *
11
+ * Syntax highlighting reuses the shared `highlightCode` lowlight helper (the same
12
+ * colorful palette as the `code-tabs` block) per line, so it matches the app's
13
+ * standard code styling and supports per-line bands without an async loader. The
14
+ * surface uses the plan `--plan-code*`/`--plan-*` tokens and Tailwind `dark:`
15
+ * pairs, so it reads correctly in BOTH light and dark mode. Code lines render as
16
+ * `<span>`s (never one `<pre>` per line) so they don't pick up document
17
+ * code/pre chrome. Lives in core so any app can register the dev-doc block.
18
+ *
19
+ * Editing is panel-driven (config-style, like the diff/HTML blocks): a monospace
20
+ * code Textarea, filename/language Inputs, and add/remove-able annotation rows.
21
+ */
3
22
  declare function AnnotatedCodeRead({ data, blockId, title, summary, ctx, }: BlockReadProps<AnnotatedCodeData>): import("react/jsx-runtime").JSX.Element;
4
23
  declare function AnnotatedCodeEdit({ data, onChange, editable, }: BlockEditProps<AnnotatedCodeData>): import("react/jsx-runtime").JSX.Element;
5
24
  export { AnnotatedCodeRead, AnnotatedCodeEdit };
@@ -1 +1 @@
1
- {"version":3,"file":"AnnotatedCodeBlock.d.ts","sourceRoot":"","sources":["../../../../src/client/blocks/library/AnnotatedCodeBlock.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAEV,iBAAiB,EAClB,MAAM,4BAA4B,CAAC;AAsEpC,iBAAS,iBAAiB,CAAC,EACzB,IAAI,EACJ,OAAO,EACP,KAAK,EACL,OAAO,EACP,GAAG,GACJ,EAAE,cAAc,CAAC,iBAAiB,CAAC,2CA2KnC;AAMD,iBAAS,iBAAiB,CAAC,EACzB,IAAI,EACJ,QAAQ,EACR,QAAQ,GACT,EAAE,cAAc,CAAC,iBAAiB,CAAC,2CAiJnC;AAED,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,CAAC"}
1
+ {"version":3,"file":"AnnotatedCodeBlock.d.ts","sourceRoot":"","sources":["../../../../src/client/blocks/library/AnnotatedCodeBlock.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAEV,iBAAiB,EAClB,MAAM,4BAA4B,CAAC;AAcpC;;;;;;;;;;;;;;;;;;GAkBG;AAIH,iBAAS,iBAAiB,CAAC,EACzB,IAAI,EACJ,OAAO,EACP,KAAK,EACL,OAAO,EACP,GAAG,GACJ,EAAE,cAAc,CAAC,iBAAiB,CAAC,2CA2HnC;AAMD,iBAAS,iBAAiB,CAAC,EACzB,IAAI,EACJ,QAAQ,EACR,QAAQ,GACT,EAAE,cAAc,CAAC,iBAAiB,CAAC,2CAiJnC;AAED,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,CAAC"}
@@ -3,6 +3,7 @@ import { useMemo, useState } from "react";
3
3
  import { IconCode, IconPlus, IconTrash } from "@tabler/icons-react";
4
4
  import { cn } from "../../utils.js";
5
5
  import { highlightCode, inferLanguageFromFilename, normalizeCodeLanguage, } from "./code-highlight.js";
6
+ import { AnnotationNoteRail, buildLineMarkerMap, hasRailAnnotations, resolveAnnotations, } from "./annotation-rail.js";
6
7
  import { DevInput, DevLabel, DevTextarea } from "./dev-doc-ui.js";
7
8
  /**
8
9
  * "Explain this code" walkthrough block: a standard syntax-highlighted code
@@ -23,36 +24,6 @@ import { DevInput, DevLabel, DevTextarea } from "./dev-doc-ui.js";
23
24
  * Editing is panel-driven (config-style, like the diff/HTML blocks): a monospace
24
25
  * code Textarea, filename/language Inputs, and add/remove-able annotation rows.
25
26
  */
26
- /* ── Line-ref parsing ──────────────────────────────────────────────────────── */
27
- /**
28
- * Parse a 1-based `lines` ref (`"3"` or `"3-5"`) into an inclusive `[start,end]`
29
- * pair, clamped to `[1, lineCount]`. Returns `null` for malformed or fully
30
- * out-of-range refs so callers can ignore them gracefully. A reversed range
31
- * (`"5-3"`) is normalized; a partially out-of-range range is clamped.
32
- */
33
- function parseLineRange(ref, lineCount) {
34
- const match = /^\s*(\d+)\s*(?:-\s*(\d+)\s*)?$/.exec(ref);
35
- if (!match)
36
- return null;
37
- let start = Number.parseInt(match[1], 10);
38
- let end = match[2] != null ? Number.parseInt(match[2], 10) : start;
39
- if (!Number.isFinite(start) || !Number.isFinite(end))
40
- return null;
41
- if (start > end)
42
- [start, end] = [end, start];
43
- // Fully outside the file → ignore.
44
- if (end < 1 || start > lineCount)
45
- return null;
46
- return { start: Math.max(1, start), end: Math.min(lineCount, end) };
47
- }
48
- /** Human label for a resolved annotation's line span ("Line 8" / "Lines 3–6"). */
49
- function rangeLabel(item) {
50
- if (!item.range)
51
- return `Lines ${item.annotation.lines}`;
52
- return item.range.start === item.range.end
53
- ? `Line ${item.range.start}`
54
- : `Lines ${item.range.start}–${item.range.end}`;
55
- }
56
27
  /* ── Read ──────────────────────────────────────────────────────────────────── */
57
28
  function AnnotatedCodeRead({ data, blockId, title, summary, ctx, }) {
58
29
  const [activeIndex, setActiveIndex] = useState(null);
@@ -62,28 +33,10 @@ function AnnotatedCodeRead({ data, blockId, title, summary, ctx, }) {
62
33
  inferLanguageFromFilename(data.filename), [data.language, data.filename]);
63
34
  // Highlight each line once; empty lines keep their height with a NBSP.
64
35
  const highlightedLines = useMemo(() => lines.map((text) => (text.length ? highlightCode(text, language) : " ")), [lines, language]);
65
- const resolved = useMemo(() => (data.annotations ?? []).map((annotation, index) => ({
66
- index,
67
- marker: index + 1,
68
- annotation,
69
- range: parseLineRange(annotation.lines, lineCount),
70
- })), [data.annotations, lineCount]);
36
+ const resolved = useMemo(() => resolveAnnotations(data.annotations, () => lineCount), [data.annotations, lineCount]);
71
37
  // line number (1-based) → resolved annotations covering it.
72
- const lineMarkers = useMemo(() => {
73
- const map = new Map();
74
- for (const item of resolved) {
75
- if (!item.range)
76
- continue;
77
- for (let n = item.range.start; n <= item.range.end; n += 1) {
78
- const list = map.get(n) ?? [];
79
- list.push(item);
80
- map.set(n, list);
81
- }
82
- }
83
- return map;
84
- }, [resolved]);
85
- const sideAnnotations = resolved.filter((item) => item.range);
86
- const hasAnnotations = sideAnnotations.length > 0;
38
+ const lineMarkers = useMemo(() => buildLineMarkerMap(resolved), [resolved]);
39
+ const hasAnnotations = hasRailAnnotations(resolved);
87
40
  const langChip = data.language?.trim();
88
41
  const codeSurface = (_jsxs("div", { className: "overflow-hidden rounded-xl border border-plan-line bg-plan-code", children: [(data.filename || langChip) && (_jsxs("div", { className: "flex items-center gap-2 border-b border-plan-line bg-plan-block/50 px-3.5 py-2", children: [_jsx(IconCode, { className: "size-3.5 shrink-0 text-plan-muted" }), _jsx("span", { className: "min-w-0 flex-1 truncate font-mono text-[13px] font-medium text-plan-code-text", children: data.filename || "snippet" }), langChip && (_jsx("span", { className: "shrink-0 rounded border border-plan-line px-1.5 py-0.5 font-mono text-[10px] uppercase tracking-wide text-plan-muted", children: langChip }))] })), _jsx("div", { className: "overflow-x-auto py-1.5", children: _jsx("div", { className: "min-w-full font-mono text-[12.5px] leading-[22px]", children: lines.map((_text, idx) => {
89
42
  const lineNo = idx + 1;
@@ -103,12 +56,7 @@ function AnnotatedCodeRead({ data, blockId, title, summary, ctx, }) {
103
56
  : "bg-amber-400/45 dark:bg-amber-300/35"
104
57
  : null) }), _jsx("span", { className: "w-11 shrink-0 select-none px-3 text-right text-[11px] tabular-nums text-plan-muted/60", children: lineNo }), _jsx("span", { className: "flex-1 whitespace-pre pr-4 text-plan-code-text", children: highlightedLines[idx] })] }, lineNo));
105
58
  }) }) })] }));
106
- return (_jsxs("section", { className: "plan-block", "data-block-id": blockId, children: [title && _jsx("div", { className: "plan-block-label", children: title }), hasAnnotations ? (_jsxs("div", { className: "grid items-start gap-3 md:grid-cols-[minmax(0,1fr)_minmax(190px,250px)]", children: [codeSurface, _jsx("div", { className: "flex flex-col gap-2.5", children: sideAnnotations.map((item) => {
107
- const isActive = activeIndex === item.index;
108
- return (_jsxs("div", { onMouseEnter: () => setActiveIndex(item.index), onMouseLeave: () => setActiveIndex(null), className: cn("rounded-lg border px-3.5 py-2.5 transition-colors", isActive
109
- ? "border-amber-400/70 bg-amber-50 dark:border-amber-300/40 dark:bg-amber-300/[0.08]"
110
- : "border-plan-line bg-plan-block/40 hover:border-amber-400/50"), children: [_jsxs("div", { className: "flex flex-wrap items-baseline gap-x-2 gap-y-0.5", children: [_jsx("span", { className: "text-[11px] font-semibold uppercase tracking-wide text-plan-muted", children: rangeLabel(item) }), item.annotation.label && (_jsx("span", { className: "text-[13px] font-semibold text-plan-text", children: item.annotation.label }))] }), _jsx("div", { className: "plan-annotation-note mt-1 text-[13px] leading-relaxed text-plan-text/85", children: ctx.renderMarkdown ? (ctx.renderMarkdown(item.annotation.note)) : (_jsx("p", { children: item.annotation.note })) })] }, item.index));
111
- }) })] })) : (codeSurface), summary && _jsx("p", { className: "mt-5 text-plan-muted", children: summary })] }));
59
+ return (_jsxs("section", { className: "plan-block", "data-block-id": blockId, children: [title && _jsx("div", { className: "plan-block-label", children: title }), hasAnnotations ? (_jsxs("div", { className: "grid items-start gap-3 md:grid-cols-[minmax(0,1fr)_minmax(190px,250px)]", children: [codeSurface, _jsx(AnnotationNoteRail, { items: resolved, activeIndex: activeIndex, onActiveChange: setActiveIndex, ctx: ctx })] })) : (codeSurface), summary && _jsx("p", { className: "mt-5 text-plan-muted", children: summary })] }));
112
60
  }
113
61
  /* ── Edit (panel) ──────────────────────────────────────────────────────────── */
114
62
  const codeAreaClass = "min-h-[160px] font-mono text-xs leading-5";
@@ -1 +1 @@
1
- {"version":3,"file":"AnnotatedCodeBlock.js","sourceRoot":"","sources":["../../../../src/client/blocks/library/AnnotatedCodeBlock.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo, useState } from \"react\";\nimport { IconCode, IconPlus, IconTrash } from \"@tabler/icons-react\";\nimport { cn } from \"../../utils.js\";\nimport type { BlockEditProps, BlockReadProps } from \"../types.js\";\nimport type {\n AnnotatedCodeAnnotation,\n AnnotatedCodeData,\n} from \"./annotated-code.config.js\";\nimport {\n highlightCode,\n inferLanguageFromFilename,\n normalizeCodeLanguage,\n} from \"./code-highlight.js\";\nimport { DevInput, DevLabel, DevTextarea } from \"./dev-doc-ui.js\";\n\n/**\n * \"Explain this code\" walkthrough block: a standard syntax-highlighted code\n * surface on the left with line-anchored annotation cards on the right (the\n * Stripe-docs / Sourcegraph layout). Each annotated line range gets a subtle\n * highlight band + an accent rail down the gutter; its card shows the `lines`\n * range, optional `label`, and the always-visible markdown `note` (via\n * `ctx.renderMarkdown`). Hovering a card highlights its lines and vice-versa.\n *\n * Syntax highlighting reuses the shared `highlightCode` lowlight helper (the same\n * colorful palette as the `code-tabs` block) per line, so it matches the app's\n * standard code styling and supports per-line bands without an async loader. The\n * surface uses the plan `--plan-code*`/`--plan-*` tokens and Tailwind `dark:`\n * pairs, so it reads correctly in BOTH light and dark mode. Code lines render as\n * `<span>`s (never one `<pre>` per line) so they don't pick up document\n * code/pre chrome. Lives in core so any app can register the dev-doc block.\n *\n * Editing is panel-driven (config-style, like the diff/HTML blocks): a monospace\n * code Textarea, filename/language Inputs, and add/remove-able annotation rows.\n */\n\n/* ── Line-ref parsing ──────────────────────────────────────────────────────── */\n\n/**\n * Parse a 1-based `lines` ref (`\"3\"` or `\"3-5\"`) into an inclusive `[start,end]`\n * pair, clamped to `[1, lineCount]`. Returns `null` for malformed or fully\n * out-of-range refs so callers can ignore them gracefully. A reversed range\n * (`\"5-3\"`) is normalized; a partially out-of-range range is clamped.\n */\nfunction parseLineRange(\n ref: string,\n lineCount: number,\n): { start: number; end: number } | null {\n const match = /^\\s*(\\d+)\\s*(?:-\\s*(\\d+)\\s*)?$/.exec(ref);\n if (!match) return null;\n let start = Number.parseInt(match[1], 10);\n let end = match[2] != null ? Number.parseInt(match[2], 10) : start;\n if (!Number.isFinite(start) || !Number.isFinite(end)) return null;\n if (start > end) [start, end] = [end, start];\n // Fully outside the file → ignore.\n if (end < 1 || start > lineCount) return null;\n return { start: Math.max(1, start), end: Math.min(lineCount, end) };\n}\n\ninterface ResolvedAnnotation {\n /** Index in the original `annotations` array (stable hover key). */\n index: number;\n /** 1-based marker number (authoring order). */\n marker: number;\n annotation: AnnotatedCodeAnnotation;\n range: { start: number; end: number } | null;\n}\n\n/** Human label for a resolved annotation's line span (\"Line 8\" / \"Lines 3–6\"). */\nfunction rangeLabel(item: ResolvedAnnotation): string {\n if (!item.range) return `Lines ${item.annotation.lines}`;\n return item.range.start === item.range.end\n ? `Line ${item.range.start}`\n : `Lines ${item.range.start}–${item.range.end}`;\n}\n\n/* ── Read ──────────────────────────────────────────────────────────────────── */\n\nfunction AnnotatedCodeRead({\n data,\n blockId,\n title,\n summary,\n ctx,\n}: BlockReadProps<AnnotatedCodeData>) {\n const [activeIndex, setActiveIndex] = useState<number | null>(null);\n\n const lines = useMemo(\n () => data.code.replace(/\\n$/, \"\").split(\"\\n\"),\n [data.code],\n );\n const lineCount = lines.length;\n\n const language = useMemo(\n () =>\n normalizeCodeLanguage(data.language) ??\n inferLanguageFromFilename(data.filename),\n [data.language, data.filename],\n );\n\n // Highlight each line once; empty lines keep their height with a NBSP.\n const highlightedLines = useMemo(\n () =>\n lines.map((text) => (text.length ? highlightCode(text, language) : \" \")),\n [lines, language],\n );\n\n const resolved = useMemo<ResolvedAnnotation[]>(\n () =>\n (data.annotations ?? []).map((annotation, index) => ({\n index,\n marker: index + 1,\n annotation,\n range: parseLineRange(annotation.lines, lineCount),\n })),\n [data.annotations, lineCount],\n );\n\n // line number (1-based) → resolved annotations covering it.\n const lineMarkers = useMemo(() => {\n const map = new Map<number, ResolvedAnnotation[]>();\n for (const item of resolved) {\n if (!item.range) continue;\n for (let n = item.range.start; n <= item.range.end; n += 1) {\n const list = map.get(n) ?? [];\n list.push(item);\n map.set(n, list);\n }\n }\n return map;\n }, [resolved]);\n\n const sideAnnotations = resolved.filter((item) => item.range);\n const hasAnnotations = sideAnnotations.length > 0;\n const langChip = data.language?.trim();\n\n const codeSurface = (\n <div className=\"overflow-hidden rounded-xl border border-plan-line bg-plan-code\">\n {(data.filename || langChip) && (\n <div className=\"flex items-center gap-2 border-b border-plan-line bg-plan-block/50 px-3.5 py-2\">\n <IconCode className=\"size-3.5 shrink-0 text-plan-muted\" />\n <span className=\"min-w-0 flex-1 truncate font-mono text-[13px] font-medium text-plan-code-text\">\n {data.filename || \"snippet\"}\n </span>\n {langChip && (\n <span className=\"shrink-0 rounded border border-plan-line px-1.5 py-0.5 font-mono text-[10px] uppercase tracking-wide text-plan-muted\">\n {langChip}\n </span>\n )}\n </div>\n )}\n <div className=\"overflow-x-auto py-1.5\">\n <div className=\"min-w-full font-mono text-[12.5px] leading-[22px]\">\n {lines.map((_text, idx) => {\n const lineNo = idx + 1;\n const markers = lineMarkers.get(lineNo);\n const isAnnotated = !!markers?.length;\n const isActive =\n activeIndex != null &&\n !!markers?.some((m) => m.index === activeIndex);\n return (\n <div\n key={lineNo}\n className={cn(\n \"flex w-full\",\n isActive\n ? \"bg-amber-400/20 dark:bg-amber-300/15\"\n : isAnnotated\n ? \"bg-amber-400/[0.07] dark:bg-amber-300/[0.07]\"\n : null,\n )}\n onMouseEnter={\n isAnnotated && markers\n ? () => setActiveIndex(markers[0].index)\n : undefined\n }\n onMouseLeave={\n isAnnotated ? () => setActiveIndex(null) : undefined\n }\n >\n {/* Accent rail: amber on annotated lines, brighter when active. */}\n <span\n aria-hidden\n className={cn(\n \"w-[3px] shrink-0 self-stretch\",\n isAnnotated\n ? isActive\n ? \"bg-amber-500 dark:bg-amber-400\"\n : \"bg-amber-400/45 dark:bg-amber-300/35\"\n : null,\n )}\n />\n <span className=\"w-11 shrink-0 select-none px-3 text-right text-[11px] tabular-nums text-plan-muted/60\">\n {lineNo}\n </span>\n <span className=\"flex-1 whitespace-pre pr-4 text-plan-code-text\">\n {highlightedLines[idx]}\n </span>\n </div>\n );\n })}\n </div>\n </div>\n </div>\n );\n\n return (\n <section className=\"plan-block\" data-block-id={blockId}>\n {title && <div className=\"plan-block-label\">{title}</div>}\n {hasAnnotations ? (\n <div className=\"grid items-start gap-3 md:grid-cols-[minmax(0,1fr)_minmax(190px,250px)]\">\n {codeSurface}\n <div className=\"flex flex-col gap-2.5\">\n {sideAnnotations.map((item) => {\n const isActive = activeIndex === item.index;\n return (\n <div\n key={item.index}\n onMouseEnter={() => setActiveIndex(item.index)}\n onMouseLeave={() => setActiveIndex(null)}\n className={cn(\n \"rounded-lg border px-3.5 py-2.5 transition-colors\",\n isActive\n ? \"border-amber-400/70 bg-amber-50 dark:border-amber-300/40 dark:bg-amber-300/[0.08]\"\n : \"border-plan-line bg-plan-block/40 hover:border-amber-400/50\",\n )}\n >\n <div className=\"flex flex-wrap items-baseline gap-x-2 gap-y-0.5\">\n <span className=\"text-[11px] font-semibold uppercase tracking-wide text-plan-muted\">\n {rangeLabel(item)}\n </span>\n {item.annotation.label && (\n <span className=\"text-[13px] font-semibold text-plan-text\">\n {item.annotation.label}\n </span>\n )}\n </div>\n <div className=\"plan-annotation-note mt-1 text-[13px] leading-relaxed text-plan-text/85\">\n {ctx.renderMarkdown ? (\n ctx.renderMarkdown(item.annotation.note)\n ) : (\n <p>{item.annotation.note}</p>\n )}\n </div>\n </div>\n );\n })}\n </div>\n </div>\n ) : (\n codeSurface\n )}\n {summary && <p className=\"mt-5 text-plan-muted\">{summary}</p>}\n </section>\n );\n}\n\n/* ── Edit (panel) ──────────────────────────────────────────────────────────── */\n\nconst codeAreaClass = \"min-h-[160px] font-mono text-xs leading-5\";\n\nfunction AnnotatedCodeEdit({\n data,\n onChange,\n editable,\n}: BlockEditProps<AnnotatedCodeData>) {\n const annotations = data.annotations ?? [];\n const patch = (next: Partial<AnnotatedCodeData>) =>\n onChange({ ...data, ...next });\n\n const updateAnnotation = (\n index: number,\n next: Partial<AnnotatedCodeAnnotation>,\n ) =>\n patch({\n annotations: annotations.map((annotation, i) =>\n i === index ? { ...annotation, ...next } : annotation,\n ),\n });\n\n const removeAnnotation = (index: number) =>\n patch({ annotations: annotations.filter((_, i) => i !== index) });\n\n const addAnnotation = () => {\n if (annotations.length >= 80) return; // schema max\n patch({\n annotations: [...annotations, { lines: \"1\", label: \"\", note: \"\" }],\n });\n };\n\n return (\n <div className=\"flex flex-col gap-3\" data-plan-interactive>\n <div className=\"grid gap-3 sm:grid-cols-2\">\n <div className=\"flex flex-col gap-1.5\">\n <DevLabel htmlFor=\"annotated-code-filename\" className=\"text-xs\">\n Filename\n </DevLabel>\n <DevInput\n id=\"annotated-code-filename\"\n value={data.filename ?? \"\"}\n placeholder=\"src/server/auth.ts\"\n disabled={!editable}\n onChange={(event) =>\n patch({ filename: event.target.value || undefined })\n }\n />\n </div>\n <div className=\"flex flex-col gap-1.5\">\n <DevLabel htmlFor=\"annotated-code-language\" className=\"text-xs\">\n Language\n </DevLabel>\n <DevInput\n id=\"annotated-code-language\"\n value={data.language ?? \"\"}\n placeholder=\"ts\"\n disabled={!editable}\n onChange={(event) =>\n patch({ language: event.target.value || undefined })\n }\n />\n </div>\n </div>\n\n <div className=\"flex flex-col gap-1.5\">\n <DevLabel htmlFor=\"annotated-code-code\" className=\"text-xs\">\n Code\n </DevLabel>\n <DevTextarea\n id=\"annotated-code-code\"\n spellCheck={false}\n className={codeAreaClass}\n value={data.code}\n disabled={!editable}\n onChange={(event) => patch({ code: event.target.value })}\n />\n </div>\n\n <div className=\"flex flex-col gap-2\">\n <div className=\"flex items-center justify-between\">\n <DevLabel className=\"text-xs\">Annotations</DevLabel>\n {editable && annotations.length < 80 && (\n <button\n type=\"button\"\n data-plan-interactive\n onClick={addAnnotation}\n className=\"flex cursor-pointer items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-plan-muted transition-colors hover:bg-plan-block/60 hover:text-plan-text\"\n >\n <IconPlus className=\"size-3.5\" />\n Add annotation\n </button>\n )}\n </div>\n {annotations.length === 0 && (\n <p className=\"text-xs text-plan-muted\">\n No annotations yet. Add one to anchor a note to a line range.\n </p>\n )}\n {annotations.map((annotation, index) => (\n <div\n key={index}\n className=\"flex flex-col gap-2 rounded-md border border-plan-line bg-plan-block/30 p-2\"\n >\n <div className=\"grid gap-2 sm:grid-cols-[120px_minmax(0,1fr)_auto]\">\n <DevInput\n aria-label={`Annotation ${index + 1} lines`}\n value={annotation.lines}\n placeholder=\"3-5\"\n disabled={!editable}\n onChange={(event) =>\n updateAnnotation(index, { lines: event.target.value })\n }\n />\n <DevInput\n aria-label={`Annotation ${index + 1} label`}\n value={annotation.label ?? \"\"}\n placeholder=\"Label (optional)\"\n disabled={!editable}\n onChange={(event) =>\n updateAnnotation(index, {\n label: event.target.value || undefined,\n })\n }\n />\n {editable && (\n <button\n type=\"button\"\n data-plan-interactive\n aria-label={`Remove annotation ${index + 1}`}\n onClick={() => removeAnnotation(index)}\n className=\"flex size-9 shrink-0 cursor-pointer items-center justify-center rounded-md text-plan-muted transition-colors hover:bg-muted hover:text-foreground\"\n >\n <IconTrash className=\"size-4\" />\n </button>\n )}\n </div>\n <DevTextarea\n aria-label={`Annotation ${index + 1} note`}\n className=\"min-h-[60px] text-sm\"\n value={annotation.note}\n placeholder=\"Explain what these lines do…\"\n disabled={!editable}\n onChange={(event) =>\n updateAnnotation(index, { note: event.target.value })\n }\n />\n </div>\n ))}\n </div>\n </div>\n );\n}\n\nexport { AnnotatedCodeRead, AnnotatedCodeEdit };\n"]}
1
+ {"version":3,"file":"AnnotatedCodeBlock.js","sourceRoot":"","sources":["../../../../src/client/blocks/library/AnnotatedCodeBlock.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo, useState } from \"react\";\nimport { IconCode, IconPlus, IconTrash } from \"@tabler/icons-react\";\nimport { cn } from \"../../utils.js\";\nimport type { BlockEditProps, BlockReadProps } from \"../types.js\";\nimport type {\n AnnotatedCodeAnnotation,\n AnnotatedCodeData,\n} from \"./annotated-code.config.js\";\nimport {\n highlightCode,\n inferLanguageFromFilename,\n normalizeCodeLanguage,\n} from \"./code-highlight.js\";\nimport {\n AnnotationNoteRail,\n buildLineMarkerMap,\n hasRailAnnotations,\n resolveAnnotations,\n} from \"./annotation-rail.js\";\nimport { DevInput, DevLabel, DevTextarea } from \"./dev-doc-ui.js\";\n\n/**\n * \"Explain this code\" walkthrough block: a standard syntax-highlighted code\n * surface on the left with line-anchored annotation cards on the right (the\n * Stripe-docs / Sourcegraph layout). Each annotated line range gets a subtle\n * highlight band + an accent rail down the gutter; its card shows the `lines`\n * range, optional `label`, and the always-visible markdown `note` (via\n * `ctx.renderMarkdown`). Hovering a card highlights its lines and vice-versa.\n *\n * Syntax highlighting reuses the shared `highlightCode` lowlight helper (the same\n * colorful palette as the `code-tabs` block) per line, so it matches the app's\n * standard code styling and supports per-line bands without an async loader. The\n * surface uses the plan `--plan-code*`/`--plan-*` tokens and Tailwind `dark:`\n * pairs, so it reads correctly in BOTH light and dark mode. Code lines render as\n * `<span>`s (never one `<pre>` per line) so they don't pick up document\n * code/pre chrome. Lives in core so any app can register the dev-doc block.\n *\n * Editing is panel-driven (config-style, like the diff/HTML blocks): a monospace\n * code Textarea, filename/language Inputs, and add/remove-able annotation rows.\n */\n\n/* ── Read ──────────────────────────────────────────────────────────────────── */\n\nfunction AnnotatedCodeRead({\n data,\n blockId,\n title,\n summary,\n ctx,\n}: BlockReadProps<AnnotatedCodeData>) {\n const [activeIndex, setActiveIndex] = useState<number | null>(null);\n\n const lines = useMemo(\n () => data.code.replace(/\\n$/, \"\").split(\"\\n\"),\n [data.code],\n );\n const lineCount = lines.length;\n\n const language = useMemo(\n () =>\n normalizeCodeLanguage(data.language) ??\n inferLanguageFromFilename(data.filename),\n [data.language, data.filename],\n );\n\n // Highlight each line once; empty lines keep their height with a NBSP.\n const highlightedLines = useMemo(\n () =>\n lines.map((text) => (text.length ? highlightCode(text, language) : \" \")),\n [lines, language],\n );\n\n const resolved = useMemo(\n () => resolveAnnotations(data.annotations, () => lineCount),\n [data.annotations, lineCount],\n );\n\n // line number (1-based) → resolved annotations covering it.\n const lineMarkers = useMemo(() => buildLineMarkerMap(resolved), [resolved]);\n\n const hasAnnotations = hasRailAnnotations(resolved);\n const langChip = data.language?.trim();\n\n const codeSurface = (\n <div className=\"overflow-hidden rounded-xl border border-plan-line bg-plan-code\">\n {(data.filename || langChip) && (\n <div className=\"flex items-center gap-2 border-b border-plan-line bg-plan-block/50 px-3.5 py-2\">\n <IconCode className=\"size-3.5 shrink-0 text-plan-muted\" />\n <span className=\"min-w-0 flex-1 truncate font-mono text-[13px] font-medium text-plan-code-text\">\n {data.filename || \"snippet\"}\n </span>\n {langChip && (\n <span className=\"shrink-0 rounded border border-plan-line px-1.5 py-0.5 font-mono text-[10px] uppercase tracking-wide text-plan-muted\">\n {langChip}\n </span>\n )}\n </div>\n )}\n <div className=\"overflow-x-auto py-1.5\">\n <div className=\"min-w-full font-mono text-[12.5px] leading-[22px]\">\n {lines.map((_text, idx) => {\n const lineNo = idx + 1;\n const markers = lineMarkers.get(lineNo);\n const isAnnotated = !!markers?.length;\n const isActive =\n activeIndex != null &&\n !!markers?.some((m) => m.index === activeIndex);\n return (\n <div\n key={lineNo}\n className={cn(\n \"flex w-full\",\n isActive\n ? \"bg-amber-400/20 dark:bg-amber-300/15\"\n : isAnnotated\n ? \"bg-amber-400/[0.07] dark:bg-amber-300/[0.07]\"\n : null,\n )}\n onMouseEnter={\n isAnnotated && markers\n ? () => setActiveIndex(markers[0].index)\n : undefined\n }\n onMouseLeave={\n isAnnotated ? () => setActiveIndex(null) : undefined\n }\n >\n {/* Accent rail: amber on annotated lines, brighter when active. */}\n <span\n aria-hidden\n className={cn(\n \"w-[3px] shrink-0 self-stretch\",\n isAnnotated\n ? isActive\n ? \"bg-amber-500 dark:bg-amber-400\"\n : \"bg-amber-400/45 dark:bg-amber-300/35\"\n : null,\n )}\n />\n <span className=\"w-11 shrink-0 select-none px-3 text-right text-[11px] tabular-nums text-plan-muted/60\">\n {lineNo}\n </span>\n <span className=\"flex-1 whitespace-pre pr-4 text-plan-code-text\">\n {highlightedLines[idx]}\n </span>\n </div>\n );\n })}\n </div>\n </div>\n </div>\n );\n\n return (\n <section className=\"plan-block\" data-block-id={blockId}>\n {title && <div className=\"plan-block-label\">{title}</div>}\n {hasAnnotations ? (\n <div className=\"grid items-start gap-3 md:grid-cols-[minmax(0,1fr)_minmax(190px,250px)]\">\n {codeSurface}\n <AnnotationNoteRail\n items={resolved}\n activeIndex={activeIndex}\n onActiveChange={setActiveIndex}\n ctx={ctx}\n />\n </div>\n ) : (\n codeSurface\n )}\n {summary && <p className=\"mt-5 text-plan-muted\">{summary}</p>}\n </section>\n );\n}\n\n/* ── Edit (panel) ──────────────────────────────────────────────────────────── */\n\nconst codeAreaClass = \"min-h-[160px] font-mono text-xs leading-5\";\n\nfunction AnnotatedCodeEdit({\n data,\n onChange,\n editable,\n}: BlockEditProps<AnnotatedCodeData>) {\n const annotations = data.annotations ?? [];\n const patch = (next: Partial<AnnotatedCodeData>) =>\n onChange({ ...data, ...next });\n\n const updateAnnotation = (\n index: number,\n next: Partial<AnnotatedCodeAnnotation>,\n ) =>\n patch({\n annotations: annotations.map((annotation, i) =>\n i === index ? { ...annotation, ...next } : annotation,\n ),\n });\n\n const removeAnnotation = (index: number) =>\n patch({ annotations: annotations.filter((_, i) => i !== index) });\n\n const addAnnotation = () => {\n if (annotations.length >= 80) return; // schema max\n patch({\n annotations: [...annotations, { lines: \"1\", label: \"\", note: \"\" }],\n });\n };\n\n return (\n <div className=\"flex flex-col gap-3\" data-plan-interactive>\n <div className=\"grid gap-3 sm:grid-cols-2\">\n <div className=\"flex flex-col gap-1.5\">\n <DevLabel htmlFor=\"annotated-code-filename\" className=\"text-xs\">\n Filename\n </DevLabel>\n <DevInput\n id=\"annotated-code-filename\"\n value={data.filename ?? \"\"}\n placeholder=\"src/server/auth.ts\"\n disabled={!editable}\n onChange={(event) =>\n patch({ filename: event.target.value || undefined })\n }\n />\n </div>\n <div className=\"flex flex-col gap-1.5\">\n <DevLabel htmlFor=\"annotated-code-language\" className=\"text-xs\">\n Language\n </DevLabel>\n <DevInput\n id=\"annotated-code-language\"\n value={data.language ?? \"\"}\n placeholder=\"ts\"\n disabled={!editable}\n onChange={(event) =>\n patch({ language: event.target.value || undefined })\n }\n />\n </div>\n </div>\n\n <div className=\"flex flex-col gap-1.5\">\n <DevLabel htmlFor=\"annotated-code-code\" className=\"text-xs\">\n Code\n </DevLabel>\n <DevTextarea\n id=\"annotated-code-code\"\n spellCheck={false}\n className={codeAreaClass}\n value={data.code}\n disabled={!editable}\n onChange={(event) => patch({ code: event.target.value })}\n />\n </div>\n\n <div className=\"flex flex-col gap-2\">\n <div className=\"flex items-center justify-between\">\n <DevLabel className=\"text-xs\">Annotations</DevLabel>\n {editable && annotations.length < 80 && (\n <button\n type=\"button\"\n data-plan-interactive\n onClick={addAnnotation}\n className=\"flex cursor-pointer items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-plan-muted transition-colors hover:bg-plan-block/60 hover:text-plan-text\"\n >\n <IconPlus className=\"size-3.5\" />\n Add annotation\n </button>\n )}\n </div>\n {annotations.length === 0 && (\n <p className=\"text-xs text-plan-muted\">\n No annotations yet. Add one to anchor a note to a line range.\n </p>\n )}\n {annotations.map((annotation, index) => (\n <div\n key={index}\n className=\"flex flex-col gap-2 rounded-md border border-plan-line bg-plan-block/30 p-2\"\n >\n <div className=\"grid gap-2 sm:grid-cols-[120px_minmax(0,1fr)_auto]\">\n <DevInput\n aria-label={`Annotation ${index + 1} lines`}\n value={annotation.lines}\n placeholder=\"3-5\"\n disabled={!editable}\n onChange={(event) =>\n updateAnnotation(index, { lines: event.target.value })\n }\n />\n <DevInput\n aria-label={`Annotation ${index + 1} label`}\n value={annotation.label ?? \"\"}\n placeholder=\"Label (optional)\"\n disabled={!editable}\n onChange={(event) =>\n updateAnnotation(index, {\n label: event.target.value || undefined,\n })\n }\n />\n {editable && (\n <button\n type=\"button\"\n data-plan-interactive\n aria-label={`Remove annotation ${index + 1}`}\n onClick={() => removeAnnotation(index)}\n className=\"flex size-9 shrink-0 cursor-pointer items-center justify-center rounded-md text-plan-muted transition-colors hover:bg-muted hover:text-foreground\"\n >\n <IconTrash className=\"size-4\" />\n </button>\n )}\n </div>\n <DevTextarea\n aria-label={`Annotation ${index + 1} note`}\n className=\"min-h-[60px] text-sm\"\n value={annotation.note}\n placeholder=\"Explain what these lines do…\"\n disabled={!editable}\n onChange={(event) =>\n updateAnnotation(index, { note: event.target.value })\n }\n />\n </div>\n ))}\n </div>\n </div>\n );\n}\n\nexport { AnnotatedCodeRead, AnnotatedCodeEdit };\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"ApiEndpointBlock.d.ts","sourceRoot":"","sources":["../../../../src/client/blocks/library/ApiEndpointBlock.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EACV,eAAe,EAKhB,MAAM,0BAA0B,CAAC;AAgHlC;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,EAC9B,IAAI,EACJ,OAAO,EACP,KAAK,EACL,OAAO,EACP,GAAG,GACJ,EAAE,cAAc,CAAC,eAAe,CAAC,2CAqNjC;AAMD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,EAC9B,IAAI,EACJ,QAAQ,EACR,QAAQ,GACT,EAAE,cAAc,CAAC,eAAe,CAAC,2CAsTjC"}
1
+ {"version":3,"file":"ApiEndpointBlock.d.ts","sourceRoot":"","sources":["../../../../src/client/blocks/library/ApiEndpointBlock.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAEV,eAAe,EAKhB,MAAM,0BAA0B,CAAC;AAsOlC;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,EAC9B,IAAI,EACJ,OAAO,EACP,KAAK,EACL,OAAO,EACP,GAAG,GACJ,EAAE,cAAc,CAAC,eAAe,CAAC,2CAsRjC;AAkBD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,EAC9B,IAAI,EACJ,QAAQ,EACR,QAAQ,GACT,EAAE,cAAc,CAAC,eAAe,CAAC,2CAiXjC"}
@@ -1,8 +1,8 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState } from "react";
3
- import { IconChevronRight, IconLock, IconPlus, IconTrash, } from "@tabler/icons-react";
3
+ import { IconArrowNarrowRight, IconChevronRight, IconLock, IconPlus, IconTrash, } from "@tabler/icons-react";
4
4
  import { cn } from "../../utils.js";
5
- import { API_ENDPOINT_METHODS, API_PARAM_LOCATIONS, } from "./api-endpoint.config.js";
5
+ import { API_ENDPOINT_CHANGES, API_ENDPOINT_METHODS, API_PARAM_LOCATIONS, } from "./api-endpoint.config.js";
6
6
  import { JsonExplorerSurface } from "./JsonExplorerBlock.js";
7
7
  import { DevBadge, DevInput, DevSwitch, DevTextarea, DevSelect, } from "./dev-doc-ui.js";
8
8
  import { CodeSurface } from "./HighlightedCode.js";
@@ -46,6 +46,71 @@ function statusPillClass(status) {
46
46
  // 3xx and everything else → neutral slate.
47
47
  return "bg-slate-200 text-slate-700 dark:bg-slate-500/20 dark:text-slate-300";
48
48
  }
49
+ /* ── Theme-aware change tokens (shared vocabulary with file-tree/data-model) ── */
50
+ /**
51
+ * Change-chip palette — IDENTICAL to `FileTreeBlock`'s `CHANGE_BADGE` so a route /
52
+ * param / response chip reads the same as a file or field change chip elsewhere
53
+ * in the recap. Tinted background + saturated text in BOTH the `.dark` plan theme
54
+ * and light mode via Tailwind `dark:` variants (never a dark-only palette).
55
+ */
56
+ const CHANGE_BADGE = {
57
+ added: "bg-emerald-100 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-300",
58
+ modified: "bg-blue-100 text-blue-700 dark:bg-blue-500/15 dark:text-blue-300",
59
+ removed: "bg-red-100 text-red-700 dark:bg-red-500/15 dark:text-red-300",
60
+ renamed: "bg-violet-100 text-violet-700 dark:bg-violet-500/15 dark:text-violet-300",
61
+ };
62
+ /** Single-letter glyph shown in the compact chip (VS Code gutter convention). */
63
+ const CHANGE_GLYPH = {
64
+ added: "A",
65
+ modified: "M",
66
+ removed: "D",
67
+ renamed: "R",
68
+ };
69
+ /** Human label for the chip text + its `title` / `aria-label`. */
70
+ const CHANGE_LABEL = {
71
+ added: "Added",
72
+ modified: "Modified",
73
+ removed: "Removed",
74
+ renamed: "Renamed",
75
+ };
76
+ /** Accent ink echoing a change color, for the name/path it applies to. */
77
+ const CHANGE_INK = {
78
+ added: "text-emerald-700 dark:text-emerald-300",
79
+ modified: "text-blue-700 dark:text-blue-300",
80
+ removed: "text-red-600 line-through dark:text-red-300",
81
+ renamed: "text-violet-700 dark:text-violet-300",
82
+ };
83
+ /**
84
+ * A change chip: compact single-glyph badge (A/M/D/R) by default, or a labeled
85
+ * pill (`variant="label"`) for the endpoint header where there is room. Matches
86
+ * the file-tree change badge so the recap reads consistently.
87
+ */
88
+ function ChangeChip({ change, variant = "glyph", className, }) {
89
+ if (variant === "label") {
90
+ return (_jsx("span", { title: CHANGE_LABEL[change], className: cn("shrink-0 rounded px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wide", CHANGE_BADGE[change], className), children: CHANGE_LABEL[change] }));
91
+ }
92
+ return (_jsx("span", { title: CHANGE_LABEL[change], "aria-label": CHANGE_LABEL[change], className: cn("flex size-4 shrink-0 items-center justify-center rounded text-[10px] font-bold leading-none", CHANGE_BADGE[change], className), children: CHANGE_GLYPH[change] }));
93
+ }
94
+ /**
95
+ * Before → after for a modified param: the prior `was` value struck through, a
96
+ * narrow arrow, then the current value (e.g. `optional → required`, or the old
97
+ * type → the new type). When `was` is absent we just show the current value.
98
+ */
99
+ function WasArrowCurrent({ was, current, }) {
100
+ if (!was)
101
+ return _jsx(_Fragment, { children: current });
102
+ return (_jsxs("span", { className: "inline-flex items-center gap-1", children: [_jsx("span", { className: "text-plan-muted line-through", children: was }), _jsx(IconArrowNarrowRight, { className: "size-3 shrink-0 text-plan-muted" }), current] }));
103
+ }
104
+ /**
105
+ * A param carries a single `was` (prior value) for a `modified` change, but that
106
+ * value may describe either the required flag or the type. Decide which column
107
+ * the before→after belongs to: a `was` of `required`/`optional` is a required
108
+ * flag flip; anything else is treated as the prior type.
109
+ */
110
+ function wasIsRequiredFlag(was) {
111
+ const v = was.trim().toLowerCase();
112
+ return v === "required" || v === "optional";
113
+ }
49
114
  /** Guess a fence language from a content type so examples highlight nicely. */
50
115
  function fenceLangForContentType(contentType) {
51
116
  const ct = (contentType ?? "").toLowerCase();
@@ -94,10 +159,44 @@ export function ApiEndpointRead({ data, blockId, title, summary, ctx, }) {
94
159
  hasRequest ||
95
160
  responses.length > 0 ||
96
161
  Boolean(data.auth);
97
- return (_jsxs("section", { className: "plan-block", "data-block-id": blockId, children: [title && _jsx("div", { className: "plan-block-label", children: title }), _jsxs("div", { className: "overflow-hidden rounded-xl border border-plan-line bg-plan-block", children: [_jsxs("button", { type: "button", "data-plan-interactive": true, "aria-expanded": open, onClick: () => setOpen((value) => !value), className: cn("flex w-full items-center gap-3 px-4 py-3 text-left transition-colors", "hover:bg-accent/40"), children: [_jsx(IconChevronRight, { className: cn("size-4 shrink-0 text-plan-muted transition-transform", open && "rotate-90") }), _jsx("span", { className: cn("shrink-0 rounded-md px-2 py-1 font-mono text-xs font-bold uppercase tracking-wide", METHOD_PILL[data.method]), children: data.method }), _jsx("span", { className: cn("min-w-0 truncate font-mono text-sm font-semibold text-plan-text", data.deprecated && "text-plan-muted line-through"), children: data.path }), data.deprecated && (_jsx(DevBadge, { className: "shrink-0 border-amber-500/40 text-amber-600 dark:text-amber-300", children: "Deprecated" })), (data.summary || summary) && (_jsx("span", { className: "ml-1 min-w-0 flex-1 truncate text-sm text-plan-muted", children: data.summary || summary })), data.auth && (_jsx(IconLock, { className: "size-3.5 shrink-0 text-plan-muted", "aria-label": "Requires authentication" }))] }), open && hasBody && (_jsxs("div", { className: "border-t border-plan-line px-4 py-4", children: [data.auth && (_jsxs("div", { className: "mb-4 flex items-center gap-2 text-xs text-plan-muted", children: [_jsx(IconLock, { className: "size-3.5 shrink-0" }), _jsxs("span", { children: [_jsx("span", { className: "font-medium text-plan-text", children: "Auth:" }), " ", data.auth] })] })), data.description?.trim() && (_jsx("div", { className: "an-api-endpoint-desc", children: ctx.renderMarkdown?.(data.description) })), params.length > 0 && (_jsxs("div", { className: "mt-5", children: [_jsx("div", { className: "text-xs font-semibold uppercase tracking-wide text-plan-muted", children: "Parameters" }), _jsx("div", { className: "mt-2 overflow-hidden rounded-lg border border-plan-line", children: _jsxs("table", { className: "w-full border-collapse text-sm", children: [_jsx("thead", { children: _jsxs("tr", { className: "bg-accent/30 text-left text-xs uppercase tracking-wide text-plan-muted", children: [_jsx("th", { className: "px-3 py-2 font-medium", children: "Name" }), _jsx("th", { className: "px-3 py-2 font-medium", children: "In" }), _jsx("th", { className: "px-3 py-2 font-medium", children: "Type" }), _jsx("th", { className: "px-3 py-2 font-medium", children: "Required" }), _jsx("th", { className: "px-3 py-2 font-medium", children: "Description" })] }) }), _jsx("tbody", { children: params.map((param, index) => (_jsxs("tr", { className: "border-t border-plan-line align-top", children: [_jsx("td", { className: "px-3 py-2 font-mono text-xs font-semibold text-plan-text", children: param.name }), _jsx("td", { className: "px-3 py-2", children: _jsx("span", { className: cn("rounded px-1.5 py-0.5 font-mono text-[11px] font-semibold", PARAM_IN_BADGE[param.in]), children: param.in }) }), _jsx("td", { className: "px-3 py-2 font-mono text-xs text-plan-muted", children: param.type || "—" }), _jsx("td", { className: "px-3 py-2 text-xs", children: param.required ? (_jsx("span", { className: "font-medium text-red-600 dark:text-red-300", children: "required" })) : (_jsx("span", { className: "text-plan-muted", children: "optional" })) }), _jsx("td", { className: "px-3 py-2 text-xs text-plan-muted", children: param.description || "—" })] }, `${param.name}-${index}`))) })] }) })] })), hasRequest && (_jsxs("div", { className: "mt-5", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-xs font-semibold uppercase tracking-wide text-plan-muted", children: "Request body" }), data.request?.contentType && (_jsx("span", { className: "rounded bg-accent/40 px-1.5 py-0.5 font-mono text-[11px] text-plan-muted", children: data.request.contentType }))] }), data.request?.example && (_jsx(ApiExample, { example: data.request.example, contentType: data.request.contentType, className: "mt-2 an-api-endpoint-example" }))] })), responses.length > 0 && (_jsxs("div", { className: "mt-5", children: [_jsx("div", { className: "text-xs font-semibold uppercase tracking-wide text-plan-muted", children: "Responses" }), _jsx("div", { className: "mt-2 flex flex-col gap-3", children: responses.map((response, index) => (_jsxs("div", { className: "rounded-lg border border-plan-line", children: [_jsxs("div", { className: "flex items-center gap-2 px-3 py-2", children: [_jsx("span", { className: cn("rounded px-2 py-0.5 font-mono text-xs font-bold", statusPillClass(response.status)), children: response.status }), response.description && (_jsx("span", { className: "text-sm text-plan-muted", children: response.description }))] }), response.example && (_jsx("div", { className: "border-t border-plan-line px-3 pb-3 pt-3 an-api-endpoint-example", children: _jsx(ApiExample, { example: response.example, className: "mt-0" }) }))] }, `${response.status}-${index}`))) })] }))] }))] })] }));
162
+ return (_jsxs("section", { className: "plan-block", "data-block-id": blockId, children: [title && _jsx("div", { className: "plan-block-label", children: title }), _jsxs("div", { className: "overflow-hidden rounded-xl border border-plan-line bg-plan-block", children: [_jsxs("button", { type: "button", "data-plan-interactive": true, "aria-expanded": open, onClick: () => setOpen((value) => !value), className: cn("flex w-full items-center gap-3 px-4 py-3 text-left transition-colors", "hover:bg-accent/40"), children: [_jsx(IconChevronRight, { className: cn("size-4 shrink-0 text-plan-muted transition-transform", open && "rotate-90") }), _jsx("span", { className: cn("shrink-0 rounded-md px-2 py-1 font-mono text-xs font-bold uppercase tracking-wide", METHOD_PILL[data.method]), children: data.method }), _jsx("span", { className: cn("min-w-0 truncate font-mono text-sm font-semibold",
163
+ // `change` ink composes with `deprecated`: a deprecated route
164
+ // still mutes/strikes its path; a changed route tints it (a
165
+ // removed route also strikes via CHANGE_INK).
166
+ data.change ? CHANGE_INK[data.change] : "text-plan-text", data.deprecated && "text-plan-muted line-through"), children: data.path }), data.change && _jsx(ChangeChip, { change: data.change, variant: "label" }), data.deprecated && (_jsx(DevBadge, { className: "shrink-0 border-amber-500/40 text-amber-600 dark:text-amber-300", children: "Deprecated" })), (data.summary || summary) && (_jsx("span", { className: "ml-1 min-w-0 flex-1 truncate text-sm text-plan-muted", children: data.summary || summary })), data.auth && (_jsx(IconLock, { className: "size-3.5 shrink-0 text-plan-muted", "aria-label": "Requires authentication" }))] }), open && hasBody && (_jsxs("div", { className: "border-t border-plan-line px-4 py-4", children: [data.auth && (_jsxs("div", { className: "mb-4 flex items-center gap-2 text-xs text-plan-muted", children: [_jsx(IconLock, { className: "size-3.5 shrink-0" }), _jsxs("span", { children: [_jsx("span", { className: "font-medium text-plan-text", children: "Auth:" }), " ", data.auth] })] })), data.description?.trim() && (_jsx("div", { className: "an-api-endpoint-desc", children: ctx.renderMarkdown?.(data.description) })), params.length > 0 && (_jsxs("div", { className: "mt-5", children: [_jsx("div", { className: "text-xs font-semibold uppercase tracking-wide text-plan-muted", children: "Parameters" }), _jsx("div", { className: "mt-2 overflow-hidden rounded-lg border border-plan-line", children: _jsxs("table", { className: "w-full border-collapse text-sm", children: [_jsx("thead", { children: _jsxs("tr", { className: "bg-accent/30 text-left text-xs uppercase tracking-wide text-plan-muted", children: [_jsx("th", { className: "px-3 py-2 font-medium", children: "Name" }), _jsx("th", { className: "px-3 py-2 font-medium", children: "In" }), _jsx("th", { className: "px-3 py-2 font-medium", children: "Type" }), _jsx("th", { className: "px-3 py-2 font-medium", children: "Required" }), _jsx("th", { className: "px-3 py-2 font-medium", children: "Description" })] }) }), _jsx("tbody", { children: params.map((param, index) => {
167
+ const change = param.change;
168
+ // A `modified` `was` describes either the required flag
169
+ // or the prior type; route it to the right column.
170
+ const wasForRequired = change === "modified" &&
171
+ param.was &&
172
+ wasIsRequiredFlag(param.was)
173
+ ? param.was
174
+ : undefined;
175
+ const wasForType = change === "modified" &&
176
+ param.was &&
177
+ !wasIsRequiredFlag(param.was)
178
+ ? param.was
179
+ : undefined;
180
+ return (_jsxs("tr", { className: "border-t border-plan-line align-top", children: [_jsx("td", { className: "px-3 py-2 font-mono text-xs font-semibold", children: _jsxs("span", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: cn(change
181
+ ? CHANGE_INK[change]
182
+ : "text-plan-text"), children: param.name }), change && _jsx(ChangeChip, { change: change })] }) }), _jsx("td", { className: "px-3 py-2", children: _jsx("span", { className: cn("rounded px-1.5 py-0.5 font-mono text-[11px] font-semibold", PARAM_IN_BADGE[param.in]), children: param.in }) }), _jsx("td", { className: "px-3 py-2 font-mono text-xs text-plan-muted", children: _jsx(WasArrowCurrent, { was: wasForType, current: _jsx("span", { className: cn(wasForType && "text-plan-text"), children: param.type || "—" }) }) }), _jsx("td", { className: "px-3 py-2 text-xs", children: _jsx(WasArrowCurrent, { was: wasForRequired, current: param.required ? (_jsx("span", { className: "font-medium text-red-600 dark:text-red-300", children: "required" })) : (_jsx("span", { className: "text-plan-muted", children: "optional" })) }) }), _jsx("td", { className: "px-3 py-2 text-xs text-plan-muted", children: param.description || "—" })] }, `${param.name}-${index}`));
183
+ }) })] }) })] })), hasRequest && (_jsxs("div", { className: "mt-5", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-xs font-semibold uppercase tracking-wide text-plan-muted", children: "Request body" }), data.request?.contentType && (_jsx("span", { className: "rounded bg-accent/40 px-1.5 py-0.5 font-mono text-[11px] text-plan-muted", children: data.request.contentType }))] }), data.request?.example && (_jsx(ApiExample, { example: data.request.example, contentType: data.request.contentType, className: "mt-2 an-api-endpoint-example" }))] })), responses.length > 0 && (_jsxs("div", { className: "mt-5", children: [_jsx("div", { className: "text-xs font-semibold uppercase tracking-wide text-plan-muted", children: "Responses" }), _jsx("div", { className: "mt-2 flex flex-col gap-3", children: responses.map((response, index) => (_jsxs("div", { className: "rounded-lg border border-plan-line", children: [_jsxs("div", { className: "flex items-center gap-2 px-3 py-2", children: [_jsx("span", { className: cn("rounded px-2 py-0.5 font-mono text-xs font-bold", statusPillClass(response.status)), children: response.status }), response.description && (_jsx("span", { className: cn("text-sm", response.change
184
+ ? CHANGE_INK[response.change]
185
+ : "text-plan-muted"), children: response.description })), response.change && (_jsx(ChangeChip, { change: response.change, variant: "label", className: "ml-auto" }))] }), response.example && (_jsx("div", { className: "border-t border-plan-line px-3 pb-3 pt-3 an-api-endpoint-example", children: _jsx(ApiExample, { example: response.example, className: "mt-0" }) }))] }, `${response.status}-${index}`))) })] }))] }))] })] }));
98
186
  }
99
187
  /* ── Edit (panel form) ─────────────────────────────────────────────────────── */
100
188
  const fieldLabelClass = "text-xs font-medium text-muted-foreground";
189
+ /**
190
+ * Options for a change `DevSelect` — a leading "No change" entry (decodes to
191
+ * `undefined`) plus the four diff states, mirroring the file-tree editor.
192
+ */
193
+ const CHANGE_SELECT_OPTIONS = [
194
+ { value: "none", label: "No change" },
195
+ ...API_ENDPOINT_CHANGES.map((change) => ({
196
+ value: change,
197
+ label: CHANGE_LABEL[change],
198
+ })),
199
+ ];
101
200
  /**
102
201
  * Panel editor for an `api-endpoint` block. A property form: method (Select),
103
202
  * path/summary/auth (Input), description (Textarea), deprecated (Switch), plus
@@ -129,17 +228,27 @@ export function ApiEndpointEdit({ data, onChange, editable, }) {
129
228
  return (_jsxs("div", { className: "flex flex-col gap-4", "data-plan-interactive": true, children: [_jsxs("div", { className: "grid grid-cols-[120px_minmax(0,1fr)] gap-2", children: [_jsxs("label", { className: "flex flex-col gap-1.5", children: [_jsx("span", { className: fieldLabelClass, children: "Method" }), _jsx(DevSelect, { className: "h-9", value: data.method, disabled: !editable, onValueChange: (value) => patch({ method: value }), options: API_ENDPOINT_METHODS.map((method) => ({
130
229
  value: method,
131
230
  label: method,
132
- })) })] }), _jsxs("label", { className: "flex flex-col gap-1.5", children: [_jsx("span", { className: fieldLabelClass, children: "Path" }), _jsx(DevInput, { className: "h-9 font-mono", value: data.path, disabled: !editable, placeholder: "/api/resource", onChange: (event) => patch({ path: event.target.value }) })] })] }), _jsxs("label", { className: "flex flex-col gap-1.5", children: [_jsx("span", { className: fieldLabelClass, children: "Summary" }), _jsx(DevInput, { className: "h-9", value: data.summary ?? "", disabled: !editable, placeholder: "Short one-line description", onChange: (event) => patch({ summary: event.target.value || undefined }) })] }), _jsxs("label", { className: "flex flex-col gap-1.5", children: [_jsx("span", { className: fieldLabelClass, children: "Description (markdown)" }), _jsx(DevTextarea, { className: "min-h-[80px]", value: data.description ?? "", disabled: !editable, placeholder: "Longer description, rendered as markdown", onChange: (event) => patch({ description: event.target.value || undefined }) })] }), _jsxs("div", { className: "grid grid-cols-[minmax(0,1fr)_auto] items-end gap-3", children: [_jsxs("label", { className: "flex flex-col gap-1.5", children: [_jsx("span", { className: fieldLabelClass, children: "Auth" }), _jsx(DevInput, { className: "h-9", value: data.auth ?? "", disabled: !editable, placeholder: "e.g. Bearer token", onChange: (event) => patch({ auth: event.target.value || undefined }) })] }), _jsxs("label", { className: "flex items-center gap-2 pb-2", children: [_jsx(DevSwitch, { checked: Boolean(data.deprecated), disabled: !editable, onCheckedChange: (checked) => patch({ deprecated: checked || undefined }) }), _jsx("span", { className: fieldLabelClass, children: "Deprecated" })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: fieldLabelClass, children: "Parameters" }), editable && (_jsxs("button", { type: "button", "data-plan-interactive": true, className: "flex items-center gap-1 rounded-md px-2 py-1 text-xs text-muted-foreground hover:bg-accent/60 hover:text-foreground", onClick: addParam, children: [_jsx(IconPlus, { className: "size-3.5" }), "Add"] }))] }), params.map((param, index) => (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border border-input p-2", children: [_jsxs("div", { className: "grid grid-cols-[minmax(0,1fr)_96px_auto] gap-2", children: [_jsx(DevInput, { className: "h-8 font-mono text-xs", value: param.name, disabled: !editable, placeholder: "name", onChange: (event) => updateParam(index, { name: event.target.value }) }), _jsx(DevSelect, { className: "h-8", value: param.in, disabled: !editable, onValueChange: (value) => updateParam(index, { in: value }), options: API_PARAM_LOCATIONS.map((location) => ({
231
+ })) })] }), _jsxs("label", { className: "flex flex-col gap-1.5", children: [_jsx("span", { className: fieldLabelClass, children: "Path" }), _jsx(DevInput, { className: "h-9 font-mono", value: data.path, disabled: !editable, placeholder: "/api/resource", onChange: (event) => patch({ path: event.target.value }) })] })] }), _jsxs("label", { className: "flex flex-col gap-1.5", children: [_jsx("span", { className: fieldLabelClass, children: "Summary" }), _jsx(DevInput, { className: "h-9", value: data.summary ?? "", disabled: !editable, placeholder: "Short one-line description", onChange: (event) => patch({ summary: event.target.value || undefined }) })] }), _jsxs("label", { className: "flex flex-col gap-1.5", children: [_jsx("span", { className: fieldLabelClass, children: "Description (markdown)" }), _jsx(DevTextarea, { className: "min-h-[80px]", value: data.description ?? "", disabled: !editable, placeholder: "Longer description, rendered as markdown", onChange: (event) => patch({ description: event.target.value || undefined }) })] }), _jsxs("div", { className: "grid grid-cols-[minmax(0,1fr)_120px_auto] items-end gap-3", children: [_jsxs("label", { className: "flex flex-col gap-1.5", children: [_jsx("span", { className: fieldLabelClass, children: "Auth" }), _jsx(DevInput, { className: "h-9", value: data.auth ?? "", disabled: !editable, placeholder: "e.g. Bearer token", onChange: (event) => patch({ auth: event.target.value || undefined }) })] }), _jsxs("label", { className: "flex flex-col gap-1.5", children: [_jsx("span", { className: fieldLabelClass, children: "Change" }), _jsx(DevSelect, { className: "h-9", value: data.change ?? "none", disabled: !editable, onValueChange: (value) => patch({
232
+ change: value === "none" ? undefined : value,
233
+ }), options: CHANGE_SELECT_OPTIONS })] }), _jsxs("label", { className: "flex items-center gap-2 pb-2", children: [_jsx(DevSwitch, { checked: Boolean(data.deprecated), disabled: !editable, onCheckedChange: (checked) => patch({ deprecated: checked || undefined }) }), _jsx("span", { className: fieldLabelClass, children: "Deprecated" })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: fieldLabelClass, children: "Parameters" }), editable && (_jsxs("button", { type: "button", "data-plan-interactive": true, className: "flex items-center gap-1 rounded-md px-2 py-1 text-xs text-muted-foreground hover:bg-accent/60 hover:text-foreground", onClick: addParam, children: [_jsx(IconPlus, { className: "size-3.5" }), "Add"] }))] }), params.map((param, index) => (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border border-input p-2", children: [_jsxs("div", { className: "grid grid-cols-[minmax(0,1fr)_96px_auto] gap-2", children: [_jsx(DevInput, { className: "h-8 font-mono text-xs", value: param.name, disabled: !editable, placeholder: "name", onChange: (event) => updateParam(index, { name: event.target.value }) }), _jsx(DevSelect, { className: "h-8", value: param.in, disabled: !editable, onValueChange: (value) => updateParam(index, { in: value }), options: API_PARAM_LOCATIONS.map((location) => ({
133
234
  value: location,
134
235
  label: location,
135
236
  })) }), editable && (_jsx("button", { type: "button", "data-plan-interactive": true, "aria-label": "Remove parameter", className: "flex size-8 items-center justify-center rounded-md text-muted-foreground hover:bg-accent/60 hover:text-foreground", onClick: () => removeParam(index), children: _jsx(IconTrash, { className: "size-4" }) }))] }), _jsxs("div", { className: "grid grid-cols-[minmax(0,1fr)_auto] items-center gap-2", children: [_jsx(DevInput, { className: "h-8 font-mono text-xs", value: param.type ?? "", disabled: !editable, placeholder: "type (e.g. string)", onChange: (event) => updateParam(index, { type: event.target.value || undefined }) }), _jsxs("label", { className: "flex items-center gap-1.5 whitespace-nowrap text-xs text-muted-foreground", children: [_jsx("input", { type: "checkbox", className: "size-3.5 cursor-pointer accent-primary", checked: Boolean(param.required), disabled: !editable, onChange: (event) => updateParam(index, {
136
237
  required: event.target.checked || undefined,
137
238
  }) }), "Required"] })] }), _jsx(DevInput, { className: "h-8 text-xs", value: param.description ?? "", disabled: !editable, placeholder: "description", onChange: (event) => updateParam(index, {
138
239
  description: event.target.value || undefined,
139
- }) })] }, index)))] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("span", { className: fieldLabelClass, children: "Request body" }), _jsx(DevInput, { className: "h-8 font-mono text-xs", value: data.request?.contentType ?? "", disabled: !editable, placeholder: "content type (e.g. application/json)", onChange: (event) => updateRequest({ contentType: event.target.value || undefined }) }), _jsx(DevTextarea, { className: "min-h-[80px] font-mono text-xs", value: data.request?.example ?? "", disabled: !editable, placeholder: '{ "example": "request body" }', onChange: (event) => updateRequest({ example: event.target.value || undefined }) })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: fieldLabelClass, children: "Responses" }), editable && (_jsxs("button", { type: "button", "data-plan-interactive": true, className: "flex items-center gap-1 rounded-md px-2 py-1 text-xs text-muted-foreground hover:bg-accent/60 hover:text-foreground", onClick: addResponse, children: [_jsx(IconPlus, { className: "size-3.5" }), "Add"] }))] }), responses.map((response, index) => (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border border-input p-2", children: [_jsxs("div", { className: "grid grid-cols-[96px_minmax(0,1fr)_auto] gap-2", children: [_jsx(DevInput, { className: "h-8 font-mono text-xs", value: response.status, disabled: !editable, placeholder: "200", onChange: (event) => updateResponse(index, { status: event.target.value }) }), _jsx(DevInput, { className: "h-8 text-xs", value: response.description ?? "", disabled: !editable, placeholder: "description", onChange: (event) => updateResponse(index, {
240
+ }) }), _jsxs("div", { className: "grid grid-cols-[120px_minmax(0,1fr)] gap-2", children: [_jsx(DevSelect, { className: "h-8", value: param.change ?? "none", disabled: !editable, onValueChange: (value) => updateParam(index, {
241
+ change: value === "none"
242
+ ? undefined
243
+ : value,
244
+ }), options: CHANGE_SELECT_OPTIONS }), _jsx(DevInput, { className: "h-8 font-mono text-xs", value: param.was ?? "", disabled: !editable || param.change !== "modified", placeholder: "was (e.g. optional, or old type)", onChange: (event) => updateParam(index, { was: event.target.value || undefined }) })] })] }, index)))] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("span", { className: fieldLabelClass, children: "Request body" }), _jsx(DevInput, { className: "h-8 font-mono text-xs", value: data.request?.contentType ?? "", disabled: !editable, placeholder: "content type (e.g. application/json)", onChange: (event) => updateRequest({ contentType: event.target.value || undefined }) }), _jsx(DevTextarea, { className: "min-h-[80px] font-mono text-xs", value: data.request?.example ?? "", disabled: !editable, placeholder: '{ "example": "request body" }', onChange: (event) => updateRequest({ example: event.target.value || undefined }) })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: fieldLabelClass, children: "Responses" }), editable && (_jsxs("button", { type: "button", "data-plan-interactive": true, className: "flex items-center gap-1 rounded-md px-2 py-1 text-xs text-muted-foreground hover:bg-accent/60 hover:text-foreground", onClick: addResponse, children: [_jsx(IconPlus, { className: "size-3.5" }), "Add"] }))] }), responses.map((response, index) => (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border border-input p-2", children: [_jsxs("div", { className: "grid grid-cols-[96px_minmax(0,1fr)_auto] gap-2", children: [_jsx(DevInput, { className: "h-8 font-mono text-xs", value: response.status, disabled: !editable, placeholder: "200", onChange: (event) => updateResponse(index, { status: event.target.value }) }), _jsx(DevInput, { className: "h-8 text-xs", value: response.description ?? "", disabled: !editable, placeholder: "description", onChange: (event) => updateResponse(index, {
140
245
  description: event.target.value || undefined,
141
246
  }) }), editable && (_jsx("button", { type: "button", "data-plan-interactive": true, "aria-label": "Remove response", className: "flex size-8 items-center justify-center rounded-md text-muted-foreground hover:bg-accent/60 hover:text-foreground", onClick: () => removeResponse(index), children: _jsx(IconTrash, { className: "size-4" }) }))] }), _jsx(DevTextarea, { className: "min-h-[64px] font-mono text-xs", value: response.example ?? "", disabled: !editable, placeholder: '{ "example": "response body" }', onChange: (event) => updateResponse(index, {
142
247
  example: event.target.value || undefined,
143
- }) })] }, index)))] })] }));
248
+ }) }), _jsxs("label", { className: "flex items-center gap-2", children: [_jsx("span", { className: fieldLabelClass, children: "Change" }), _jsx(DevSelect, { className: "h-8 w-[120px]", value: response.change ?? "none", disabled: !editable, onValueChange: (value) => updateResponse(index, {
249
+ change: value === "none"
250
+ ? undefined
251
+ : value,
252
+ }), options: CHANGE_SELECT_OPTIONS })] })] }, index)))] })] }));
144
253
  }
145
254
  //# sourceMappingURL=ApiEndpointBlock.js.map