@agent-native/core 0.41.1 → 0.43.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/README.md +17 -56
- package/dist/action.d.ts +13 -1
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js.map +1 -1
- package/dist/agent/production-agent.d.ts +8 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +93 -0
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/cli/app-skill.d.ts +16 -0
- package/dist/cli/app-skill.d.ts.map +1 -1
- package/dist/cli/app-skill.js +33 -3
- package/dist/cli/app-skill.js.map +1 -1
- 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.map +1 -1
- package/dist/cli/recap.js +38 -16
- package/dist/cli/recap.js.map +1 -1
- package/dist/cli/skills.d.ts +30 -3
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +180 -114
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +2 -2
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +172 -5
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/blocks/index.d.ts +11 -0
- package/dist/client/blocks/index.d.ts.map +1 -1
- package/dist/client/blocks/index.js +11 -0
- package/dist/client/blocks/index.js.map +1 -1
- package/dist/client/blocks/library/AnnotatedCodeBlock.d.ts +19 -0
- package/dist/client/blocks/library/AnnotatedCodeBlock.d.ts.map +1 -1
- package/dist/client/blocks/library/AnnotatedCodeBlock.js +6 -58
- 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 +116 -7
- package/dist/client/blocks/library/ApiEndpointBlock.js.map +1 -1
- package/dist/client/blocks/library/DataModelBlock.d.ts.map +1 -1
- package/dist/client/blocks/library/DataModelBlock.js +75 -9
- package/dist/client/blocks/library/DataModelBlock.js.map +1 -1
- package/dist/client/blocks/library/DiffBlock.d.ts +1 -1
- package/dist/client/blocks/library/DiffBlock.d.ts.map +1 -1
- package/dist/client/blocks/library/DiffBlock.js +265 -39
- package/dist/client/blocks/library/DiffBlock.js.map +1 -1
- package/dist/client/blocks/library/FileTreeBlock.d.ts.map +1 -1
- package/dist/client/blocks/library/FileTreeBlock.js +27 -4
- package/dist/client/blocks/library/FileTreeBlock.js.map +1 -1
- package/dist/client/blocks/library/HighlightedCode.d.ts +1 -1
- package/dist/client/blocks/library/HighlightedCode.js +1 -1
- package/dist/client/blocks/library/HighlightedCode.js.map +1 -1
- package/dist/client/blocks/library/JsonExplorerBlock.js +1 -1
- package/dist/client/blocks/library/JsonExplorerBlock.js.map +1 -1
- package/dist/client/blocks/library/MermaidBlock.js +1 -1
- package/dist/client/blocks/library/MermaidBlock.js.map +1 -1
- package/dist/client/blocks/library/annotation-rail.d.ts +115 -0
- package/dist/client/blocks/library/annotation-rail.d.ts.map +1 -0
- package/dist/client/blocks/library/annotation-rail.js +139 -0
- package/dist/client/blocks/library/annotation-rail.js.map +1 -0
- package/dist/client/blocks/library/api-endpoint.config.d.ts +31 -6
- package/dist/client/blocks/library/api-endpoint.config.d.ts.map +1 -1
- package/dist/client/blocks/library/api-endpoint.config.js +30 -6
- package/dist/client/blocks/library/api-endpoint.config.js.map +1 -1
- package/dist/client/blocks/library/callout.config.d.ts +29 -0
- package/dist/client/blocks/library/callout.config.d.ts.map +1 -0
- package/dist/client/blocks/library/callout.config.js +33 -0
- package/dist/client/blocks/library/callout.config.js.map +1 -0
- package/dist/client/blocks/library/callout.d.ts +20 -0
- package/dist/client/blocks/library/callout.d.ts.map +1 -0
- package/dist/client/blocks/library/callout.js +61 -0
- package/dist/client/blocks/library/callout.js.map +1 -0
- package/dist/client/blocks/library/checklist.d.ts.map +1 -1
- package/dist/client/blocks/library/checklist.js +3 -3
- package/dist/client/blocks/library/checklist.js.map +1 -1
- package/dist/client/blocks/library/code.d.ts.map +1 -1
- package/dist/client/blocks/library/code.js +32 -15
- 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 +56 -35
- package/dist/client/blocks/library/columns.js.map +1 -1
- package/dist/client/blocks/library/data-model.config.d.ts +17 -0
- package/dist/client/blocks/library/data-model.config.d.ts.map +1 -1
- package/dist/client/blocks/library/data-model.config.js +15 -0
- package/dist/client/blocks/library/data-model.config.js.map +1 -1
- package/dist/client/blocks/library/decision.config.d.ts +37 -0
- package/dist/client/blocks/library/decision.config.d.ts.map +1 -0
- package/dist/client/blocks/library/decision.config.js +32 -0
- package/dist/client/blocks/library/decision.config.js.map +1 -0
- package/dist/client/blocks/library/decision.d.ts +19 -0
- package/dist/client/blocks/library/decision.d.ts.map +1 -0
- package/dist/client/blocks/library/decision.js +119 -0
- package/dist/client/blocks/library/decision.js.map +1 -0
- package/dist/client/blocks/library/diagram.config.d.ts +64 -0
- package/dist/client/blocks/library/diagram.config.d.ts.map +1 -0
- package/dist/client/blocks/library/diagram.config.js +111 -0
- package/dist/client/blocks/library/diagram.config.js.map +1 -0
- package/dist/client/blocks/library/diagram.d.ts +16 -0
- package/dist/client/blocks/library/diagram.d.ts.map +1 -0
- package/dist/client/blocks/library/diagram.js +261 -0
- package/dist/client/blocks/library/diagram.js.map +1 -0
- package/dist/client/blocks/library/diff.config.d.ts +28 -6
- package/dist/client/blocks/library/diff.config.d.ts.map +1 -1
- package/dist/client/blocks/library/diff.config.js +30 -6
- package/dist/client/blocks/library/diff.config.js.map +1 -1
- package/dist/client/blocks/library/question-form.config.d.ts +69 -0
- package/dist/client/blocks/library/question-form.config.d.ts.map +1 -0
- package/dist/client/blocks/library/question-form.config.js +58 -0
- package/dist/client/blocks/library/question-form.config.js.map +1 -0
- package/dist/client/blocks/library/question-form.d.ts +20 -0
- package/dist/client/blocks/library/question-form.d.ts.map +1 -0
- package/dist/client/blocks/library/question-form.js +286 -0
- package/dist/client/blocks/library/question-form.js.map +1 -0
- package/dist/client/blocks/library/sanitize-html.d.ts +5 -0
- package/dist/client/blocks/library/sanitize-html.d.ts.map +1 -0
- package/dist/client/blocks/library/sanitize-html.js +240 -0
- package/dist/client/blocks/library/sanitize-html.js.map +1 -0
- package/dist/client/blocks/library/server-specs.d.ts.map +1 -1
- package/dist/client/blocks/library/server-specs.js +59 -0
- package/dist/client/blocks/library/server-specs.js.map +1 -1
- package/dist/client/blocks/library/specs.d.ts.map +1 -1
- package/dist/client/blocks/library/specs.js +11 -0
- package/dist/client/blocks/library/specs.js.map +1 -1
- package/dist/client/blocks/library/tabs.d.ts.map +1 -1
- package/dist/client/blocks/library/tabs.js +12 -12
- package/dist/client/blocks/library/tabs.js.map +1 -1
- package/dist/client/blocks/library/wireframe-kit.d.ts +260 -0
- package/dist/client/blocks/library/wireframe-kit.d.ts.map +1 -0
- package/dist/client/blocks/library/wireframe-kit.js +920 -0
- package/dist/client/blocks/library/wireframe-kit.js.map +1 -0
- package/dist/client/blocks/library/wireframe.config.d.ts +123 -0
- package/dist/client/blocks/library/wireframe.config.d.ts.map +1 -0
- package/dist/client/blocks/library/wireframe.config.js +294 -0
- package/dist/client/blocks/library/wireframe.config.js.map +1 -0
- package/dist/client/blocks/library/wireframe.d.ts +15 -0
- package/dist/client/blocks/library/wireframe.d.ts.map +1 -0
- package/dist/client/blocks/library/wireframe.js +206 -0
- package/dist/client/blocks/library/wireframe.js.map +1 -0
- package/dist/client/blocks/registry.d.ts +9 -0
- package/dist/client/blocks/registry.d.ts.map +1 -1
- package/dist/client/blocks/registry.js +12 -5
- package/dist/client/blocks/registry.js.map +1 -1
- package/dist/client/blocks/server.d.ts +1 -0
- package/dist/client/blocks/server.d.ts.map +1 -1
- package/dist/client/blocks/server.js +1 -0
- package/dist/client/blocks/server.js.map +1 -1
- package/dist/client/blocks/types.d.ts +10 -2
- package/dist/client/blocks/types.d.ts.map +1 -1
- package/dist/client/blocks/types.js.map +1 -1
- package/dist/client/rich-markdown-editor/DragHandle.d.ts.map +1 -1
- package/dist/client/rich-markdown-editor/DragHandle.js +152 -21
- package/dist/client/rich-markdown-editor/DragHandle.js.map +1 -1
- package/dist/client/rich-markdown-editor/RegistryBlockNode.d.ts +25 -1
- package/dist/client/rich-markdown-editor/RegistryBlockNode.d.ts.map +1 -1
- package/dist/client/rich-markdown-editor/RegistryBlockNode.js +29 -6
- package/dist/client/rich-markdown-editor/RegistryBlockNode.js.map +1 -1
- package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts +8 -1
- package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts.map +1 -1
- package/dist/client/rich-markdown-editor/SharedRichEditor.js +5 -1
- package/dist/client/rich-markdown-editor/SharedRichEditor.js.map +1 -1
- package/dist/extensions/actions.d.ts.map +1 -1
- package/dist/extensions/actions.js +159 -12
- package/dist/extensions/actions.js.map +1 -1
- package/dist/extensions/store.d.ts +21 -0
- package/dist/extensions/store.d.ts.map +1 -1
- package/dist/extensions/store.js +33 -1
- package/dist/extensions/store.js.map +1 -1
- package/dist/server/recap-image-route.d.ts.map +1 -1
- package/dist/server/recap-image-route.js +12 -3
- package/dist/server/recap-image-route.js.map +1 -1
- package/dist/styles/agent-native.css +1 -0
- package/dist/styles/blocks.css +1380 -0
- package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +30 -5
- package/docs/content/plan-plugin.md +107 -0
- package/docs/content/pr-visual-recap.md +2 -2
- package/docs/content/skills-guide.md +8 -0
- package/docs/content/template-plan.md +94 -17
- package/package.json +2 -1
- package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +30 -5
- package/docs/content/visual-plans.md +0 -80
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useMemo, useState } from "react";
|
|
3
|
-
import { IconChevronRight, IconColumns, IconDotsVertical, IconFileDiff, IconList, } from "@tabler/icons-react";
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { IconChevronRight, IconColumns, IconDotsVertical, IconFileDiff, IconList, IconPlus, IconTrash, } from "@tabler/icons-react";
|
|
4
4
|
import { common, createLowlight } from "lowlight";
|
|
5
5
|
import { cn } from "../../utils.js";
|
|
6
|
+
import { AnnotationGutterMarker, InlineAnnotationNote, buildLineMarkerMap, hasRailAnnotations, resolveAnnotations, } from "./annotation-rail.js";
|
|
6
7
|
import { DevInput, DevLabel, DevTextarea, DevSelect } from "./dev-doc-ui.js";
|
|
7
8
|
/**
|
|
8
9
|
* Split text into lines, each KEEPING its trailing newline (so the change
|
|
@@ -228,6 +229,19 @@ function SyntaxHighlightedLine({ code, language, }) {
|
|
|
228
229
|
}, [code, language]);
|
|
229
230
|
return _jsx(_Fragment, { children: highlighted ?? code });
|
|
230
231
|
}
|
|
232
|
+
/** The default side an annotation targets when `side` is omitted. */
|
|
233
|
+
function annotationSide(annotation) {
|
|
234
|
+
return annotation.side === "before" ? "before" : "after";
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Count 1-based source lines in a side's text, matching how `buildRows` numbers
|
|
238
|
+
* `oldNo`/`newNo`: a trailing newline does not add a phantom final line.
|
|
239
|
+
*/
|
|
240
|
+
function countLines(text) {
|
|
241
|
+
if (text === "")
|
|
242
|
+
return 0;
|
|
243
|
+
return splitLines(text).length;
|
|
244
|
+
}
|
|
231
245
|
/** Number of context lines above which an unchanged run is collapsed. */
|
|
232
246
|
const COLLAPSE_THRESHOLD = 6;
|
|
233
247
|
/** Context lines kept visible at each edge of a collapsed run. */
|
|
@@ -272,9 +286,32 @@ function buildRows(changes) {
|
|
|
272
286
|
* Group rows into segments, collapsing interior runs of >COLLAPSE_THRESHOLD
|
|
273
287
|
* context rows (keeping CONTEXT_EDGE visible at each side). Leading/trailing runs
|
|
274
288
|
* collapse too, but keep only the inner edge visible.
|
|
289
|
+
*
|
|
290
|
+
* `isAnchored` marks context rows that carry an annotation (or sit adjacent to
|
|
291
|
+
* one): an anchored row is NEVER hidden inside a collapsed run, so a note that
|
|
292
|
+
* targets an unchanged line stays reachable. An anchor splits its run into the
|
|
293
|
+
* separately-collapsible spans on either side of it, with CONTEXT_EDGE rows kept
|
|
294
|
+
* visible around the anchor.
|
|
275
295
|
*/
|
|
276
|
-
function segmentRows(rows) {
|
|
296
|
+
function segmentRows(rows, isAnchored) {
|
|
277
297
|
const segments = [];
|
|
298
|
+
// Collapse one contiguous context run [from, to) that contains NO anchors.
|
|
299
|
+
const collapseRun = (run, atStart, atEnd) => {
|
|
300
|
+
if (run.length <= COLLAPSE_THRESHOLD) {
|
|
301
|
+
for (const row of run)
|
|
302
|
+
segments.push(row);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const head = atStart ? [] : run.slice(0, CONTEXT_EDGE);
|
|
306
|
+
const tail = atEnd ? [] : run.slice(run.length - CONTEXT_EDGE);
|
|
307
|
+
const hidden = run.slice(head.length, run.length - tail.length);
|
|
308
|
+
for (const row of head)
|
|
309
|
+
segments.push(row);
|
|
310
|
+
if (hidden.length > 0)
|
|
311
|
+
segments.push({ collapsed: true, rows: hidden });
|
|
312
|
+
for (const row of tail)
|
|
313
|
+
segments.push(row);
|
|
314
|
+
};
|
|
278
315
|
let i = 0;
|
|
279
316
|
while (i < rows.length) {
|
|
280
317
|
if (rows[i].kind !== "context") {
|
|
@@ -286,23 +323,29 @@ function segmentRows(rows) {
|
|
|
286
323
|
let j = i;
|
|
287
324
|
while (j < rows.length && rows[j].kind === "context")
|
|
288
325
|
j += 1;
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
326
|
+
const fullRun = rows.slice(i, j);
|
|
327
|
+
const runAtStart = i === 0;
|
|
328
|
+
const runAtEnd = j === rows.length;
|
|
329
|
+
if (!isAnchored || !fullRun.some(isAnchored)) {
|
|
330
|
+
collapseRun(fullRun, runAtStart, runAtEnd);
|
|
293
331
|
}
|
|
294
332
|
else {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
333
|
+
// Walk the run, emitting anchored rows verbatim and collapsing the
|
|
334
|
+
// unanchored spans between them. An anchored row is always visible; the
|
|
335
|
+
// spans on each side of it collapse independently.
|
|
336
|
+
let spanStart = 0;
|
|
337
|
+
for (let k = 0; k <= fullRun.length; k += 1) {
|
|
338
|
+
const atAnchor = k < fullRun.length && isAnchored(fullRun[k]);
|
|
339
|
+
if (atAnchor || k === fullRun.length) {
|
|
340
|
+
const span = fullRun.slice(spanStart, k);
|
|
341
|
+
if (span.length > 0) {
|
|
342
|
+
collapseRun(span, runAtStart && spanStart === 0, runAtEnd && k === fullRun.length);
|
|
343
|
+
}
|
|
344
|
+
if (k < fullRun.length)
|
|
345
|
+
segments.push(fullRun[k]);
|
|
346
|
+
spanStart = k + 1;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
306
349
|
}
|
|
307
350
|
i = j;
|
|
308
351
|
}
|
|
@@ -329,10 +372,42 @@ const SIGN = {
|
|
|
329
372
|
removed: "−",
|
|
330
373
|
context: " ",
|
|
331
374
|
};
|
|
332
|
-
const LINE_NO_CLASS = "select-none px-2 py-0 text-right font-mono
|
|
333
|
-
const DIFF_LINE_CLASS = "block min-w-max flex-1 whitespace-pre px-2 py-0 font-mono
|
|
375
|
+
const LINE_NO_CLASS = "select-none px-2 py-0 text-right font-mono [font-size:var(--plan-code-size)] leading-5 text-muted-foreground tabular-nums";
|
|
376
|
+
const DIFF_LINE_CLASS = "block min-w-max flex-1 whitespace-pre px-2 py-0 font-mono [font-size:var(--plan-code-size)] leading-5 text-foreground";
|
|
334
377
|
const DEFAULT_VISIBLE_DIFF_LINES = 15;
|
|
335
378
|
const MAX_DIFF_LCS_CELLS = 1_000_000;
|
|
379
|
+
/**
|
|
380
|
+
* Below this rendered container width (px) a diff drops side-by-side `split`
|
|
381
|
+
* mode and falls back to `unified`: split's two line-number gutters double the
|
|
382
|
+
* width the code needs, exactly where space is tightest (e.g. a diff nested in a
|
|
383
|
+
* vertical-tabs content column). Measured from the block's own box, not the
|
|
384
|
+
* viewport, so the fallback fires by available width at any nesting depth.
|
|
385
|
+
*/
|
|
386
|
+
const SPLIT_MIN_WIDTH = 560;
|
|
387
|
+
/**
|
|
388
|
+
* Observe the rendered inline width of an element. Returns a ref to attach plus
|
|
389
|
+
* the measured content-box width (null until the first ResizeObserver tick).
|
|
390
|
+
* Lets the diff pick an effective mode from the width it was actually handed — a
|
|
391
|
+
* measurement CSS container queries can't drive, since switching split↔unified
|
|
392
|
+
* is a structural (DOM) change, not a style toggle.
|
|
393
|
+
*/
|
|
394
|
+
function useContainerWidth() {
|
|
395
|
+
const ref = useRef(null);
|
|
396
|
+
const [width, setWidth] = useState(null);
|
|
397
|
+
useEffect(() => {
|
|
398
|
+
const el = ref.current;
|
|
399
|
+
if (!el || typeof ResizeObserver === "undefined")
|
|
400
|
+
return;
|
|
401
|
+
const observer = new ResizeObserver((entries) => {
|
|
402
|
+
const entry = entries[0];
|
|
403
|
+
if (entry)
|
|
404
|
+
setWidth(entry.contentRect.width);
|
|
405
|
+
});
|
|
406
|
+
observer.observe(el);
|
|
407
|
+
return () => observer.disconnect();
|
|
408
|
+
}, []);
|
|
409
|
+
return [ref, width];
|
|
410
|
+
}
|
|
336
411
|
function splitDiffFilename(filename) {
|
|
337
412
|
const value = filename?.trim() || "diff";
|
|
338
413
|
const segments = value.split("/").filter(Boolean);
|
|
@@ -345,21 +420,81 @@ function DiffLineText({ language, text }) {
|
|
|
345
420
|
return (_jsx("span", { className: DIFF_LINE_CLASS, children: _jsx(SyntaxHighlightedLine, { code: code, language: language }) }));
|
|
346
421
|
}
|
|
347
422
|
/* ── Read ──────────────────────────────────────────────────────────────────── */
|
|
348
|
-
function DiffRead({ data, blockId, title, summary }) {
|
|
423
|
+
function DiffRead({ data, blockId, title, summary, ctx, }) {
|
|
349
424
|
const [mode, setMode] = useState(data.mode ?? "unified");
|
|
350
425
|
const [expanded, setExpanded] = useState(() => new Set());
|
|
351
426
|
const [showAllRows, setShowAllRows] = useState(false);
|
|
427
|
+
const [activeIndex, setActiveIndex] = useState(null);
|
|
428
|
+
const [containerRef, containerWidth] = useContainerWidth();
|
|
352
429
|
const rows = useMemo(() => buildRows(diffLines(data.before, data.after)), [data.before, data.after]);
|
|
353
430
|
const language = useMemo(() => resolveDiffLanguage(data), [data.filename, data.language]);
|
|
354
431
|
const fileParts = useMemo(() => splitDiffFilename(data.filename), [data.filename]);
|
|
355
432
|
const splitLineCount = useMemo(() => pairSplitRows(rows).length, [rows]);
|
|
433
|
+
// Resolve annotations against the side they target. A `before` annotation's
|
|
434
|
+
// `lines` ref is clamped to the OLD file's line count and matched on `oldNo`;
|
|
435
|
+
// an `after` (default) ref to the NEW file and matched on `newNo`. Markers are
|
|
436
|
+
// authoring-order across BOTH sides so a note ↔ row ↔ rail card share one id.
|
|
437
|
+
const beforeLineCount = useMemo(() => countLines(data.before), [data.before]);
|
|
438
|
+
const afterLineCount = useMemo(() => countLines(data.after), [data.after]);
|
|
439
|
+
const resolved = useMemo(() => resolveAnnotations(data.annotations, (annotation) => annotationSide(annotation) === "before"
|
|
440
|
+
? beforeLineCount
|
|
441
|
+
: afterLineCount), [data.annotations, beforeLineCount, afterLineCount]);
|
|
442
|
+
const hasAnnotations = hasRailAnnotations(resolved);
|
|
443
|
+
// Effective render mode. Annotated diffs always render unified so the inline
|
|
444
|
+
// notes read in flow (there is no side rail); and any diff in a container
|
|
445
|
+
// narrower than SPLIT_MIN_WIDTH falls back to unified so split's doubled
|
|
446
|
+
// gutters never crush the code. `canSplit` also gates the mode toggle so it is
|
|
447
|
+
// hidden whenever split is unavailable.
|
|
448
|
+
const narrow = containerWidth != null && containerWidth < SPLIT_MIN_WIDTH;
|
|
449
|
+
const canSplit = !hasAnnotations && !narrow;
|
|
450
|
+
const effectiveMode = canSplit ? mode : "unified";
|
|
451
|
+
// Side-scoped line → markers maps so a row only lights from its own side.
|
|
452
|
+
const beforeMarkers = useMemo(() => buildLineMarkerMap(resolved.filter((r) => annotationSide(r.annotation) === "before")), [resolved]);
|
|
453
|
+
const afterMarkers = useMemo(() => buildLineMarkerMap(resolved.filter((r) => annotationSide(r.annotation) !== "before")), [resolved]);
|
|
454
|
+
const markersForRow = useMemo(() => {
|
|
455
|
+
return (row, side) => {
|
|
456
|
+
const out = [];
|
|
457
|
+
if ((side === undefined || side === "old") &&
|
|
458
|
+
row.oldNo != null &&
|
|
459
|
+
row.kind !== "added") {
|
|
460
|
+
out.push(...(beforeMarkers.get(row.oldNo) ?? []));
|
|
461
|
+
}
|
|
462
|
+
if ((side === undefined || side === "new") &&
|
|
463
|
+
row.newNo != null &&
|
|
464
|
+
row.kind !== "removed") {
|
|
465
|
+
out.push(...(afterMarkers.get(row.newNo) ?? []));
|
|
466
|
+
}
|
|
467
|
+
return out;
|
|
468
|
+
};
|
|
469
|
+
}, [beforeMarkers, afterMarkers]);
|
|
470
|
+
// A context row that carries a marker is an anchor: never collapse it away.
|
|
471
|
+
const anchoredRow = useMemo(() => {
|
|
472
|
+
if (!hasAnnotations)
|
|
473
|
+
return undefined;
|
|
474
|
+
return (row) => markersForRow(row).length > 0;
|
|
475
|
+
}, [hasAnnotations, markersForRow]);
|
|
356
476
|
const added = rows.filter((r) => r.kind === "added").length;
|
|
357
477
|
const removed = rows.filter((r) => r.kind === "removed").length;
|
|
358
478
|
const unchanged = data.before === data.after;
|
|
359
|
-
const totalVisibleLineCount =
|
|
479
|
+
const totalVisibleLineCount = effectiveMode === "split" ? splitLineCount : rows.length;
|
|
360
480
|
const shouldLimitRows = totalVisibleLineCount > DEFAULT_VISIBLE_DIFF_LINES;
|
|
361
|
-
|
|
362
|
-
const
|
|
481
|
+
// Never truncate away an annotated row: extend the window past the last one.
|
|
482
|
+
const effectiveRowLimit = useMemo(() => {
|
|
483
|
+
if (showAllRows || !shouldLimitRows)
|
|
484
|
+
return undefined;
|
|
485
|
+
let limit = DEFAULT_VISIBLE_DIFF_LINES;
|
|
486
|
+
if (hasAnnotations) {
|
|
487
|
+
for (let idx = rows.length - 1; idx >= limit; idx -= 1) {
|
|
488
|
+
if (markersForRow(rows[idx]).length > 0) {
|
|
489
|
+
limit = idx + 1;
|
|
490
|
+
break;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return limit;
|
|
495
|
+
}, [showAllRows, shouldLimitRows, hasAnnotations, rows, markersForRow]);
|
|
496
|
+
const rowLimit = effectiveRowLimit;
|
|
497
|
+
const displayedRows = effectiveMode === "unified" && rowLimit ? rows.slice(0, rowLimit) : rows;
|
|
363
498
|
const toggleRun = (index) => setExpanded((prev) => {
|
|
364
499
|
const next = new Set(prev);
|
|
365
500
|
if (next.has(index))
|
|
@@ -368,31 +503,93 @@ function DiffRead({ data, blockId, title, summary }) {
|
|
|
368
503
|
next.add(index);
|
|
369
504
|
return next;
|
|
370
505
|
});
|
|
371
|
-
return (_jsxs("section", { className: "plan-block group/diff-block", "data-block-id": blockId, children: [title && _jsx("div", { className: "plan-block-label", children: title }), _jsxs("div", { className: "overflow-hidden rounded-md border border-border bg-background", children: [_jsxs("div", { className: "flex min-h-10 flex-wrap items-center gap-2 border-b border-border bg-muted/60 px-3 py-1.5", children: [_jsx(IconFileDiff, { className: "size-4 shrink-0 text-muted-foreground" }), _jsxs("span", { className: "flex min-w-0 flex-1 items-baseline gap-1.5 font-mono", title: data.filename || undefined, children: [_jsx("span", { className: "min-w-0 max-w-[16rem] truncate text-[13px] font-semibold leading-5 text-foreground", children: fileParts.basename }), fileParts.directory && (_jsx("span", { className: "min-w-0 flex-1 truncate text-[11px] leading-5 text-muted-foreground/70", children: fileParts.directory }))] }), _jsxs("span", { className: "ml-1 flex shrink-0 items-center gap-2 font-mono text-xs", children: [_jsxs("span", { className: "text-emerald-700 dark:text-emerald-300", children: ["+", added] }), _jsxs("span", { className: "text-destructive", children: ["\u2212", removed] })] }), _jsxs("div", { className: "pointer-events-none ml-auto flex shrink-0 items-center overflow-hidden rounded-md border border-border bg-background opacity-0 transition-opacity group-hover/diff-block:pointer-events-auto group-hover/diff-block:opacity-100 group-focus-within/diff-block:pointer-events-auto group-focus-within/diff-block:opacity-100", children: [_jsx(ModeButton, { active: mode === "unified", onClick: () => setMode("unified"), icon: _jsx(IconList, { className: "size-3.5" }), label: "Unified" }), _jsx(ModeButton, { active: mode === "split", onClick: () => setMode("split"), icon: _jsx(IconColumns, { className: "size-3.5" }), label: "Split" })] })] }), unchanged ? (_jsx("div", { className: "px-4 py-6 text-center font-mono text-sm text-muted-foreground", children: "No changes" })) :
|
|
506
|
+
return (_jsxs("section", { ref: containerRef, className: "plan-block group/diff-block", "data-block-id": blockId, children: [title && _jsx("div", { className: "plan-block-label", children: title }), summary && (_jsx("p", { className: "mb-3 text-sm leading-relaxed text-plan-muted", children: summary })), _jsxs("div", { className: "overflow-hidden rounded-md border border-border bg-background", children: [_jsxs("div", { className: "flex min-h-10 flex-wrap items-center gap-2 border-b border-border bg-muted/60 px-3 py-1.5", children: [_jsx(IconFileDiff, { className: "size-4 shrink-0 text-muted-foreground" }), _jsxs("span", { className: "flex min-w-0 flex-1 items-baseline gap-1.5 font-mono", title: data.filename || undefined, children: [_jsx("span", { className: "min-w-0 max-w-[16rem] truncate text-[13px] font-semibold leading-5 text-foreground", children: fileParts.basename }), fileParts.directory && (_jsx("span", { className: "min-w-0 flex-1 truncate text-[11px] leading-5 text-muted-foreground/70", children: fileParts.directory }))] }), _jsxs("span", { className: "ml-1 flex shrink-0 items-center gap-2 font-mono text-xs", children: [_jsxs("span", { className: "text-emerald-700 dark:text-emerald-300", children: ["+", added] }), _jsxs("span", { className: "text-destructive", children: ["\u2212", removed] })] }), canSplit && (_jsxs("div", { className: "pointer-events-none ml-auto flex shrink-0 items-center overflow-hidden rounded-md border border-border bg-background opacity-0 transition-opacity group-hover/diff-block:pointer-events-auto group-hover/diff-block:opacity-100 group-focus-within/diff-block:pointer-events-auto group-focus-within/diff-block:opacity-100", children: [_jsx(ModeButton, { active: mode === "unified", onClick: () => setMode("unified"), icon: _jsx(IconList, { className: "size-3.5" }), label: "Unified" }), _jsx(ModeButton, { active: mode === "split", onClick: () => setMode("split"), icon: _jsx(IconColumns, { className: "size-3.5" }), label: "Split" })] }))] }), unchanged ? (_jsx("div", { className: "px-4 py-6 text-center font-mono text-sm text-muted-foreground", children: "No changes" })) : effectiveMode === "split" ? (_jsx(SplitView, { rows: rows, language: language, rowLimit: rowLimit, markersForRow: markersForRow, activeIndex: activeIndex, onActiveChange: setActiveIndex })) : (_jsx(UnifiedView, { rows: displayedRows, language: language, expanded: expanded, onToggleRun: toggleRun, markersForRow: markersForRow, anchoredRow: anchoredRow, activeIndex: activeIndex, onActiveChange: setActiveIndex, ctx: ctx })), !unchanged && shouldLimitRows && (_jsxs("button", { type: "button", "data-plan-interactive": true, "aria-expanded": showAllRows, onClick: () => setShowAllRows((current) => !current), className: "flex h-7 w-full items-center justify-center gap-1.5 border-t border-border bg-background px-2 text-[11px] font-medium text-muted-foreground transition-colors hover:bg-muted/70 hover:text-foreground", children: [_jsx(IconChevronRight, { className: cn("size-3 shrink-0 transition-transform", showAllRows ? "-rotate-90" : "rotate-90") }), showAllRows
|
|
372
507
|
? "Show fewer"
|
|
373
|
-
: `Show all ${totalVisibleLineCount} lines`] }))] })
|
|
508
|
+
: `Show all ${totalVisibleLineCount} lines`] }))] })] }));
|
|
374
509
|
}
|
|
375
510
|
function ModeButton({ active, onClick, icon, label, }) {
|
|
376
511
|
return (_jsxs("button", { type: "button", "data-plan-interactive": true, onClick: onClick, "aria-pressed": active, className: cn("flex cursor-pointer items-center gap-1 px-2 py-1 text-xs font-medium transition-colors", active
|
|
377
512
|
? "bg-accent text-accent-foreground"
|
|
378
513
|
: "text-muted-foreground hover:bg-muted/80 hover:text-foreground"), children: [icon, label] }));
|
|
379
514
|
}
|
|
515
|
+
/**
|
|
516
|
+
* The numbered marker pip(s) for a row plus the active-state it derives. Returns
|
|
517
|
+
* `null` when the row carries no annotation so unannotated diffs render an empty
|
|
518
|
+
* marker column (or no column at all when the whole diff is unannotated).
|
|
519
|
+
*/
|
|
520
|
+
function rowMarkerInfo(markers, activeIndex) {
|
|
521
|
+
if (markers.length === 0)
|
|
522
|
+
return null;
|
|
523
|
+
const isActive = markers.some((m) => m.index === activeIndex);
|
|
524
|
+
return { isActive, primaryIndex: markers[0].index };
|
|
525
|
+
}
|
|
526
|
+
/** Shared amber wash for an annotated row, brighter when active. */
|
|
527
|
+
function annotatedRowBg(info) {
|
|
528
|
+
if (!info)
|
|
529
|
+
return null;
|
|
530
|
+
return info.isActive
|
|
531
|
+
? "bg-amber-400/20 dark:bg-amber-300/15"
|
|
532
|
+
: "bg-amber-400/[0.07] dark:bg-amber-300/[0.07]";
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Whether `row` is the FIRST line of `marker`'s resolved range. The numbered pip
|
|
536
|
+
* renders only on this line, so a multi-line annotation shows a single marker at
|
|
537
|
+
* the top of its span instead of repeating the same number down every line it
|
|
538
|
+
* covers. The amber band still washes the whole range (via `annotatedRowBg`), so
|
|
539
|
+
* the span stays visually grouped without the column of duplicate numbers.
|
|
540
|
+
*/
|
|
541
|
+
function isMarkerRangeStart(row, marker) {
|
|
542
|
+
if (!marker.range)
|
|
543
|
+
return false;
|
|
544
|
+
const lineNo = annotationSide(marker.annotation) === "before" ? row.oldNo : row.newNo;
|
|
545
|
+
return lineNo === marker.range.start;
|
|
546
|
+
}
|
|
380
547
|
/* ── Unified view ──────────────────────────────────────────────────────────── */
|
|
381
|
-
function UnifiedView({ rows, language, expanded, onToggleRun, }) {
|
|
382
|
-
const segments = useMemo(() => segmentRows(rows), [rows]);
|
|
548
|
+
function UnifiedView({ rows, language, expanded, onToggleRun, markersForRow, anchoredRow, activeIndex, onActiveChange, ctx, }) {
|
|
549
|
+
const segments = useMemo(() => segmentRows(rows, anchoredRow), [rows, anchoredRow]);
|
|
550
|
+
// Any annotation present ⇒ reserve the marker column so rows stay aligned.
|
|
551
|
+
const showMarkerColumn = useMemo(() => rows.some((row) => markersForRow(row).length > 0), [rows, markersForRow]);
|
|
552
|
+
const rowProps = {
|
|
553
|
+
language,
|
|
554
|
+
markersForRow,
|
|
555
|
+
activeIndex,
|
|
556
|
+
onActiveChange,
|
|
557
|
+
showMarkerColumn,
|
|
558
|
+
};
|
|
559
|
+
// A diff row plus any notes anchored to it: each annotation renders ONE
|
|
560
|
+
// full-width inline note directly under the FIRST line of its range (matched
|
|
561
|
+
// by `isMarkerRangeStart`), so the row's numbered pip and its note read as a
|
|
562
|
+
// single GitHub-style unit and the note never repeats down a multi-line span.
|
|
563
|
+
const renderRow = (row, key) => {
|
|
564
|
+
const notes = markersForRow(row).filter((marker) => isMarkerRangeStart(row, marker));
|
|
565
|
+
return (_jsxs("div", { children: [_jsx(UnifiedRow, { row: row, ...rowProps }), notes.map((item) => (_jsx(InlineAnnotationNote, { item: item, active: item.index === activeIndex, onActiveChange: onActiveChange, ctx: ctx }, `note-${item.index}`)))] }, key));
|
|
566
|
+
};
|
|
383
567
|
let runIndex = 0;
|
|
384
|
-
return (_jsx("div", { className: "overflow-x-auto", children: _jsx("div", { className: "w-max min-w-full font-mono
|
|
568
|
+
return (_jsx("div", { className: "overflow-x-auto", children: _jsx("div", { className: "w-max min-w-full font-mono [font-size:var(--plan-code-size)] leading-5", children: segments.map((segment, idx) => {
|
|
385
569
|
if ("collapsed" in segment) {
|
|
386
570
|
const key = runIndex++;
|
|
387
571
|
const open = expanded.has(key);
|
|
388
572
|
return (_jsxs("div", { children: [_jsx(CollapsedRow, { count: segment.rows.length, open: open, onClick: () => onToggleRun(key) }), open &&
|
|
389
|
-
segment.rows.map((row, ri) => (
|
|
573
|
+
segment.rows.map((row, ri) => renderRow(row, `run-${key}-${ri}`))] }, `run-${key}`));
|
|
390
574
|
}
|
|
391
|
-
return
|
|
575
|
+
return renderRow(segment, String(idx));
|
|
392
576
|
}) }) }));
|
|
393
577
|
}
|
|
394
|
-
function UnifiedRow({ language, row }) {
|
|
395
|
-
|
|
578
|
+
function UnifiedRow({ language, row, markersForRow, activeIndex, onActiveChange, showMarkerColumn, }) {
|
|
579
|
+
const markers = markersForRow(row);
|
|
580
|
+
const info = rowMarkerInfo(markers, activeIndex);
|
|
581
|
+
const startMarker = markers.find((marker) => isMarkerRangeStart(row, marker));
|
|
582
|
+
return (_jsxs("div", { className: cn("flex min-h-5 min-w-full", ROW_BG[row.kind], annotatedRowBg(info)), onMouseEnter: info ? () => onActiveChange(info.primaryIndex) : undefined, onMouseLeave: info ? () => onActiveChange(null) : undefined, children: [_jsx("span", { className: cn(LINE_NO_CLASS, "w-[52px]"), children: row.oldNo ?? "" }), _jsx("span", { className: cn(LINE_NO_CLASS, "w-[52px]"), children: row.newNo ?? "" }), _jsx("span", { className: cn("w-6 shrink-0 select-none py-0 text-center font-semibold leading-5", GUTTER_BG[row.kind], SIGN_COLOR[row.kind]), children: SIGN[row.kind] }), showMarkerColumn && (_jsx(MarkerCell, { startMarker: startMarker, active: startMarker != null && startMarker.index === activeIndex })), _jsx(DiffLineText, { text: row.text, language: language })] }));
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* The fixed-width marker column rendered between the sign gutter and the code.
|
|
586
|
+
* `startMarker` is set only on the FIRST line of an annotation's range, so the
|
|
587
|
+
* numbered pip appears once at the top of a span; every other row (the rest of a
|
|
588
|
+
* span, and every unannotated row in a diff that has annotations) renders an
|
|
589
|
+
* empty spacer so the code text stays aligned.
|
|
590
|
+
*/
|
|
591
|
+
function MarkerCell({ startMarker, active, }) {
|
|
592
|
+
return (_jsx("span", { className: "flex w-6 shrink-0 select-none items-center justify-center py-0", children: startMarker != null && (_jsx(AnnotationGutterMarker, { marker: startMarker.marker, active: active })) }));
|
|
396
593
|
}
|
|
397
594
|
function CollapsedRow({ count, open, onClick, }) {
|
|
398
595
|
return (_jsxs("button", { type: "button", "data-plan-interactive": true, onClick: onClick, className: "flex w-full cursor-pointer items-center gap-2 border-y border-border bg-muted/70 px-3 py-1 text-left text-xs text-muted-foreground transition-colors hover:bg-muted hover:text-foreground", children: [_jsx(IconDotsVertical, { className: "size-3.5 shrink-0" }), _jsxs("span", { children: [open ? "Hide" : "Show", " ", count, " unchanged line", count === 1 ? "" : "s"] })] }));
|
|
@@ -426,28 +623,57 @@ function pairSplitRows(rows) {
|
|
|
426
623
|
}
|
|
427
624
|
return out;
|
|
428
625
|
}
|
|
429
|
-
function SplitView({ language, rowLimit, rows, }) {
|
|
626
|
+
function SplitView({ language, rowLimit, rows, markersForRow, activeIndex, onActiveChange, }) {
|
|
430
627
|
const pairs = useMemo(() => pairSplitRows(rows), [rows]);
|
|
431
628
|
const displayedPairs = rowLimit ? pairs.slice(0, rowLimit) : pairs;
|
|
432
|
-
|
|
629
|
+
// Reserve the marker column on a side only if any visible row there has one.
|
|
630
|
+
const showOldMarkers = useMemo(() => displayedPairs.some((pair) => pair.left && markersForRow(pair.left, "old").length > 0), [displayedPairs, markersForRow]);
|
|
631
|
+
const showNewMarkers = useMemo(() => displayedPairs.some((pair) => pair.right && markersForRow(pair.right, "new").length > 0), [displayedPairs, markersForRow]);
|
|
632
|
+
const cellProps = { language, markersForRow, activeIndex, onActiveChange };
|
|
633
|
+
return (_jsxs("div", { className: "flex w-full bg-background font-mono [font-size:var(--plan-code-size)] leading-5", children: [_jsx("div", { className: "min-w-0 flex-1 overflow-x-auto border-r border-border", children: _jsx("div", { className: "inline-block min-w-full", children: displayedPairs.map((pair, idx) => (_jsx(SplitCell, { row: pair.left, side: "old", showMarkerColumn: showOldMarkers, ...cellProps }, `old-${idx}`))) }) }), _jsx("div", { className: "min-w-0 flex-1 overflow-x-auto", children: _jsx("div", { className: "inline-block min-w-full", children: displayedPairs.map((pair, idx) => (_jsx(SplitCell, { row: pair.right, side: "new", showMarkerColumn: showNewMarkers, ...cellProps }, `new-${idx}`))) }) })] }));
|
|
433
634
|
}
|
|
434
|
-
function SplitCell({ language, row, side, }) {
|
|
635
|
+
function SplitCell({ language, row, side, markersForRow, activeIndex, onActiveChange, showMarkerColumn, }) {
|
|
435
636
|
if (!row) {
|
|
436
|
-
return (_jsxs("div", { className: "flex min-h-5 min-w-full bg-muted/40 opacity-70", children: [_jsx("span", { className: cn(LINE_NO_CLASS, "w-[52px]") }), _jsx("span", { className: "w-6 shrink-0 bg-muted/60" }), _jsx("span", { className: DIFF_LINE_CLASS, children: " " })] }));
|
|
637
|
+
return (_jsxs("div", { className: "flex min-h-5 min-w-full bg-muted/40 opacity-70", children: [_jsx("span", { className: cn(LINE_NO_CLASS, "w-[52px]") }), _jsx("span", { className: "w-6 shrink-0 bg-muted/60" }), showMarkerColumn && _jsx("span", { className: "w-6 shrink-0" }), _jsx("span", { className: DIFF_LINE_CLASS, children: " " })] }));
|
|
437
638
|
}
|
|
438
639
|
const sign = side === "old" ? "−" : "+";
|
|
439
640
|
const showSign = row.kind !== "context";
|
|
440
|
-
|
|
641
|
+
const markers = markersForRow(row, side);
|
|
642
|
+
const info = rowMarkerInfo(markers, activeIndex);
|
|
643
|
+
const startMarker = markers.find((marker) => isMarkerRangeStart(row, marker));
|
|
644
|
+
return (_jsxs("div", { className: cn("flex min-h-5 min-w-full", ROW_BG[row.kind], annotatedRowBg(info)), onMouseEnter: info ? () => onActiveChange(info.primaryIndex) : undefined, onMouseLeave: info ? () => onActiveChange(null) : undefined, children: [_jsx("span", { className: cn(LINE_NO_CLASS, "w-[52px]"), children: side === "old" ? (row.oldNo ?? "") : (row.newNo ?? "") }), _jsx("span", { className: cn("w-6 shrink-0 select-none py-0 text-center font-semibold leading-5", GUTTER_BG[row.kind], SIGN_COLOR[row.kind]), children: showSign ? sign : " " }), showMarkerColumn && (_jsx(MarkerCell, { startMarker: startMarker, active: startMarker != null && startMarker.index === activeIndex })), _jsx(DiffLineText, { text: row.text, language: language })] }));
|
|
441
645
|
}
|
|
442
646
|
/* ── Edit (panel) ──────────────────────────────────────────────────────────── */
|
|
443
647
|
const codeAreaClass = "min-h-[140px] font-mono text-xs leading-5";
|
|
444
648
|
function DiffEdit({ data, onChange, editable }) {
|
|
445
649
|
const patch = (next) => onChange({ ...data, ...next });
|
|
446
650
|
const mode = data.mode ?? "unified";
|
|
651
|
+
const annotations = data.annotations ?? [];
|
|
652
|
+
const updateAnnotation = (index, next) => patch({
|
|
653
|
+
annotations: annotations.map((annotation, i) => i === index ? { ...annotation, ...next } : annotation),
|
|
654
|
+
});
|
|
655
|
+
const removeAnnotation = (index) => patch({ annotations: annotations.filter((_, i) => i !== index) });
|
|
656
|
+
const addAnnotation = () => {
|
|
657
|
+
if (annotations.length >= 80)
|
|
658
|
+
return; // schema max
|
|
659
|
+
patch({
|
|
660
|
+
annotations: [
|
|
661
|
+
...annotations,
|
|
662
|
+
{ side: "after", lines: "1", label: "", note: "" },
|
|
663
|
+
],
|
|
664
|
+
});
|
|
665
|
+
};
|
|
447
666
|
return (_jsxs("div", { className: "flex flex-col gap-3", "data-plan-interactive": true, children: [_jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(DevLabel, { htmlFor: "diff-filename", className: "text-xs", children: "Filename" }), _jsx(DevInput, { id: "diff-filename", value: data.filename ?? "", placeholder: "src/add.ts", disabled: !editable, onChange: (event) => patch({ filename: event.target.value || undefined }) })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(DevLabel, { htmlFor: "diff-language", className: "text-xs", children: "Language" }), _jsx(DevInput, { id: "diff-language", value: data.language ?? "", placeholder: "ts", disabled: !editable, onChange: (event) => patch({ language: event.target.value || undefined }) })] })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(DevLabel, { className: "text-xs", children: "Layout" }), _jsx(DevSelect, { value: mode, disabled: !editable, onValueChange: (value) => patch({ mode: value }), options: [
|
|
448
667
|
{ value: "unified", label: "Unified" },
|
|
449
668
|
{ value: "split", label: "Split (side-by-side)" },
|
|
450
|
-
] })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(DevLabel, { htmlFor: "diff-before", className: "text-xs", children: "Before" }), _jsx(DevTextarea, { id: "diff-before", spellCheck: false, className: codeAreaClass, value: data.before, disabled: !editable, onChange: (event) => patch({ before: event.target.value }) })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(DevLabel, { htmlFor: "diff-after", className: "text-xs", children: "After" }), _jsx(DevTextarea, { id: "diff-after", spellCheck: false, className: codeAreaClass, value: data.after, disabled: !editable, onChange: (event) => patch({ after: event.target.value }) })] })] }))
|
|
669
|
+
] })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(DevLabel, { htmlFor: "diff-before", className: "text-xs", children: "Before" }), _jsx(DevTextarea, { id: "diff-before", spellCheck: false, className: codeAreaClass, value: data.before, disabled: !editable, onChange: (event) => patch({ before: event.target.value }) })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(DevLabel, { htmlFor: "diff-after", className: "text-xs", children: "After" }), _jsx(DevTextarea, { id: "diff-after", spellCheck: false, className: codeAreaClass, value: data.after, disabled: !editable, onChange: (event) => patch({ after: event.target.value }) })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx(DevLabel, { className: "text-xs", children: "Annotations" }), editable && annotations.length < 80 && (_jsxs("button", { type: "button", "data-plan-interactive": true, onClick: addAnnotation, 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", children: [_jsx(IconPlus, { className: "size-3.5" }), "Add annotation"] }))] }), annotations.length === 0 && (_jsx("p", { className: "text-xs text-plan-muted", children: "No annotations yet. Add one to anchor a note to a line range on the before or after side." })), annotations.map((annotation, index) => (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border border-plan-line bg-plan-block/30 p-2", children: [_jsxs("div", { className: "grid gap-2 sm:grid-cols-[110px_110px_minmax(0,1fr)_auto]", children: [_jsx(DevSelect, { "aria-label": `Annotation ${index + 1} side`, value: annotation.side ?? "after", disabled: !editable, onValueChange: (value) => updateAnnotation(index, {
|
|
670
|
+
side: value,
|
|
671
|
+
}), options: [
|
|
672
|
+
{ value: "after", label: "After" },
|
|
673
|
+
{ value: "before", label: "Before" },
|
|
674
|
+
] }), _jsx(DevInput, { "aria-label": `Annotation ${index + 1} lines`, value: annotation.lines, placeholder: "3-5", disabled: !editable, onChange: (event) => updateAnnotation(index, { lines: event.target.value }) }), _jsx(DevInput, { "aria-label": `Annotation ${index + 1} label`, value: annotation.label ?? "", placeholder: "Label (optional)", disabled: !editable, onChange: (event) => updateAnnotation(index, {
|
|
675
|
+
label: event.target.value || undefined,
|
|
676
|
+
}) }), editable && (_jsx("button", { type: "button", "data-plan-interactive": true, "aria-label": `Remove annotation ${index + 1}`, onClick: () => removeAnnotation(index), 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", children: _jsx(IconTrash, { className: "size-4" }) }))] }), _jsx(DevTextarea, { "aria-label": `Annotation ${index + 1} note`, className: "min-h-[60px] text-sm", value: annotation.note, placeholder: "Explain what these lines do\u2026", disabled: !editable, onChange: (event) => updateAnnotation(index, { note: event.target.value }) })] }, index)))] })] }));
|
|
451
677
|
}
|
|
452
678
|
export { DiffRead, DiffEdit };
|
|
453
679
|
//# sourceMappingURL=DiffBlock.js.map
|