@xenosystem/blocks 0.13.17 → 0.13.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ops/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  } from "../chunk-WE6A2DTY.js";
9
9
  import {
10
10
  ICONS
11
- } from "../chunk-PI5WOATA.js";
11
+ } from "../chunk-7S2RKUAM.js";
12
12
  import "../chunk-2KG3PWR4.js";
13
13
 
14
14
  // src/ops/console/panel.ts
@@ -60,10 +60,10 @@ function spliceRedactedAppend(existing, addition, redact, carry = REDACTION_CARR
60
60
  const tail = existing.slice(cut);
61
61
  return head + redact(tail + addition);
62
62
  }
63
- function truncateLogMessage(text3, max) {
64
- if (max <= 0 || text3.length <= max) return text3;
65
- if (max <= MESSAGE_ELISION.length) return text3.slice(text3.length - max);
66
- return MESSAGE_ELISION + text3.slice(text3.length - (max - MESSAGE_ELISION.length));
63
+ function truncateLogMessage(text4, max) {
64
+ if (max <= 0 || text4.length <= max) return text4;
65
+ if (max <= MESSAGE_ELISION.length) return text4.slice(text4.length - max);
66
+ return MESSAGE_ELISION + text4.slice(text4.length - (max - MESSAGE_ELISION.length));
67
67
  }
68
68
  function formatRecord(record) {
69
69
  const time = new Date(record.ts).toISOString();
@@ -240,7 +240,7 @@ var ConsoleController = class {
240
240
  */
241
241
  merge(existing, patch) {
242
242
  const secrets = [...this.host.getSecrets?.() ?? []];
243
- const redact = (text3) => redactForLog(text3, secrets);
243
+ const redact = (text4) => redactForLog(text4, secrets);
244
244
  let message = existing.message;
245
245
  if (typeof patch.message === "string") message = redact(patch.message);
246
246
  if (typeof patch.appendMessage === "string" && patch.appendMessage.length > 0) {
@@ -870,24 +870,24 @@ function consoleRuntimeComponents(controller, rowHeight) {
870
870
  function ConsolePanelView({ controller, ...options }) {
871
871
  useSyncExternalStore(controller.subscribe, controller.getState, controller.getState);
872
872
  const view = consoleViewState(controller, options);
873
- const ui3 = (intent) => applyConsoleUi(controller, intent);
873
+ const ui4 = (intent) => applyConsoleUi(controller, intent);
874
874
  return /* @__PURE__ */ jsxs(Column, { children: [
875
875
  /* @__PURE__ */ jsx(
876
876
  Toolbar,
877
877
  {
878
878
  left: /* @__PURE__ */ jsxs(Fragment, { children: [
879
- /* @__PURE__ */ jsx(SearchField, { value: view.search, placeholder: "Filter\u2026", onChange: (value) => ui3({ kind: "search", value }) }),
880
- /* @__PURE__ */ jsx(SegmentedControl, { size: "sm", label: "Minimum level", options: view.levels, value: view.level, onChange: (level) => ui3({ kind: "level", level }) })
879
+ /* @__PURE__ */ jsx(SearchField, { value: view.search, placeholder: "Filter\u2026", onChange: (value) => ui4({ kind: "search", value }) }),
880
+ /* @__PURE__ */ jsx(SegmentedControl, { size: "sm", label: "Minimum level", options: view.levels, value: view.level, onChange: (level) => ui4({ kind: "level", level }) })
881
881
  ] }),
882
882
  right: /* @__PURE__ */ jsxs(ToolbarGroup, { end: true, children: [
883
883
  view.errors ? /* @__PURE__ */ jsx(Badge, { tone: "error", children: view.errors }) : null,
884
884
  view.warns ? /* @__PURE__ */ jsx(Badge, { tone: "warning", children: view.warns }) : null,
885
- /* @__PURE__ */ jsx(IconButton, { icon: /* @__PURE__ */ jsx(CopyGlyph, {}), label: "Copy visible", onClick: () => ui3({ kind: "copyVisible" }) }),
886
- /* @__PURE__ */ jsx(IconButton, { icon: /* @__PURE__ */ jsx(ClearGlyph, {}), label: "Clear", onClick: () => ui3({ kind: "clear" }) })
885
+ /* @__PURE__ */ jsx(IconButton, { icon: /* @__PURE__ */ jsx(CopyGlyph, {}), label: "Copy visible", onClick: () => ui4({ kind: "copyVisible" }) }),
886
+ /* @__PURE__ */ jsx(IconButton, { icon: /* @__PURE__ */ jsx(ClearGlyph, {}), label: "Clear", onClick: () => ui4({ kind: "clear" }) })
887
887
  ] })
888
888
  }
889
889
  ),
890
- view.multiSource ? /* @__PURE__ */ jsx(Stack, { direction: "row", gap: 4, pad: "inset", children: view.sources.map((source) => /* @__PURE__ */ jsx(FilterChip, { on: source.on, title: source.title, onChange: (on) => ui3({ kind: "source", id: source.id, on }), children: source.label }, source.id)) }) : null,
890
+ view.multiSource ? /* @__PURE__ */ jsx(Stack, { direction: "row", gap: 4, pad: "inset", children: view.sources.map((source) => /* @__PURE__ */ jsx(FilterChip, { on: source.on, title: source.title, onChange: (on) => ui4({ kind: "source", id: source.id, on }), children: source.label }, source.id)) }) : null,
891
891
  view.empty ? /* @__PURE__ */ jsx(EmptyState, { title: view.emptyTitle, hint: view.emptyHint }) : null,
892
892
  view.listed ? /* @__PURE__ */ jsx(ConsoleScroller, { controller, rowHeight: view.rowHeight, children: /* @__PURE__ */ jsx(VirtualWindow, { totalHeight: view.totalHeight, offsetTop: view.offsetTop, children: /* @__PURE__ */ jsx(RowList, { role: "log", children: view.rows.map((row) => /* @__PURE__ */ jsxs(Fragment, { children: [
893
893
  /* @__PURE__ */ jsx(
@@ -902,8 +902,8 @@ function ConsolePanelView({ controller, ...options }) {
902
902
  /* @__PURE__ */ jsx(Text, { children: row.message })
903
903
  ] }),
904
904
  trailing: row.repeat ? /* @__PURE__ */ jsx(Badge, { title: "Repeated consecutively", children: row.repeat }) : null,
905
- onClick: () => ui3({ kind: "row", id: row.id }),
906
- onContextMenuAt: (x, y) => ui3({ kind: "contextMenu", id: row.id, x, y })
905
+ onClick: () => ui4({ kind: "row", id: row.id }),
906
+ onContextMenuAt: (x, y) => ui4({ kind: "contextMenu", id: row.id, x, y })
907
907
  },
908
908
  row.id
909
909
  ),
@@ -2085,19 +2085,19 @@ var RetryGlyph = ICONS["icons/Retry"];
2085
2085
  function RunsPanelView({ controller, ...options }) {
2086
2086
  useSyncExternalStore2(controller.subscribe, controller.getState, controller.getState);
2087
2087
  const view = runsViewState(controller, options);
2088
- const ui3 = (intent) => applyRunsUi(controller, intent);
2088
+ const ui4 = (intent) => applyRunsUi(controller, intent);
2089
2089
  const chip2 = (c, onChange) => /* @__PURE__ */ jsx2(FilterChip2, { on: c.on, title: c.title, onChange, children: /* @__PURE__ */ jsx2(Badge2, { tone: c.tone, children: c.label }) }, c.id);
2090
2090
  return /* @__PURE__ */ jsxs2(Column2, { children: [
2091
2091
  /* @__PURE__ */ jsx2(
2092
2092
  Toolbar2,
2093
2093
  {
2094
- left: /* @__PURE__ */ jsx2(SearchField2, { value: view.search, placeholder: "Filter runs\u2026", onChange: (value) => ui3({ kind: "search", value }) }),
2095
- right: /* @__PURE__ */ jsx2(ToolbarGroup2, { end: true, children: /* @__PURE__ */ jsx2(TextButton, { onClick: () => ui3({ kind: "clearTerminal" }), children: "Clear finished" }) })
2094
+ left: /* @__PURE__ */ jsx2(SearchField2, { value: view.search, placeholder: "Filter runs\u2026", onChange: (value) => ui4({ kind: "search", value }) }),
2095
+ right: /* @__PURE__ */ jsx2(ToolbarGroup2, { end: true, children: /* @__PURE__ */ jsx2(TextButton, { onClick: () => ui4({ kind: "clearTerminal" }), children: "Clear finished" }) })
2096
2096
  }
2097
2097
  ),
2098
2098
  view.hasChips ? /* @__PURE__ */ jsxs2(Stack2, { direction: "row", gap: 4, pad: "inset", children: [
2099
- view.statuses.map((c) => chip2(c, (on) => ui3({ kind: "status", status: c.id, on }))),
2100
- view.sources.map((c) => chip2(c, (on) => ui3({ kind: "source", id: c.id, on })))
2099
+ view.statuses.map((c) => chip2(c, (on) => ui4({ kind: "status", status: c.id, on }))),
2100
+ view.sources.map((c) => chip2(c, (on) => ui4({ kind: "source", id: c.id, on })))
2101
2101
  ] }) : null,
2102
2102
  view.empty ? /* @__PURE__ */ jsx2(EmptyState2, { title: view.emptyTitle, hint: view.emptyHint }) : null,
2103
2103
  view.listed ? /* @__PURE__ */ jsx2(ScrollArea2, { children: /* @__PURE__ */ jsx2(RowList2, { children: view.rows.map((row) => /* @__PURE__ */ jsxs2(Fragment2, { children: [
@@ -2119,11 +2119,11 @@ function RunsPanelView({ controller, ...options }) {
2119
2119
  row.duration ? /* @__PURE__ */ jsx2(Text2, { role: "number", children: row.duration }) : null
2120
2120
  ] }),
2121
2121
  trailing: /* @__PURE__ */ jsxs2(Fragment2, { children: [
2122
- row.canCancel ? /* @__PURE__ */ jsx2(IconButton2, { size: "sm", icon: /* @__PURE__ */ jsx2(CancelGlyph, {}), label: row.cancelLabel, onClick: () => ui3({ kind: "cancel", runId: row.runId }) }) : null,
2123
- row.canRetry ? /* @__PURE__ */ jsx2(IconButton2, { size: "sm", icon: /* @__PURE__ */ jsx2(RetryGlyph, {}), label: row.retryLabel, onClick: () => ui3({ kind: "retry", runId: row.runId }) }) : null
2122
+ row.canCancel ? /* @__PURE__ */ jsx2(IconButton2, { size: "sm", icon: /* @__PURE__ */ jsx2(CancelGlyph, {}), label: row.cancelLabel, onClick: () => ui4({ kind: "cancel", runId: row.runId }) }) : null,
2123
+ row.canRetry ? /* @__PURE__ */ jsx2(IconButton2, { size: "sm", icon: /* @__PURE__ */ jsx2(RetryGlyph, {}), label: row.retryLabel, onClick: () => ui4({ kind: "retry", runId: row.runId }) }) : null
2124
2124
  ] }),
2125
- onClick: () => ui3({ kind: "row", runId: row.runId, stepPath: row.stepPath }),
2126
- onContextMenuAt: (x, y) => ui3({ kind: "contextMenu", runId: row.runId, x, y })
2125
+ onClick: () => ui4({ kind: "row", runId: row.runId, stepPath: row.stepPath }),
2126
+ onContextMenuAt: (x, y) => ui4({ kind: "contextMenu", runId: row.runId, x, y })
2127
2127
  },
2128
2128
  row.key
2129
2129
  ),
@@ -3433,291 +3433,361 @@ var diffManifest = {
3433
3433
  import { useSyncExternalStore as useSyncExternalStore4 } from "react";
3434
3434
  import {
3435
3435
  Badge as Badge4,
3436
+ Column as Column3,
3437
+ DiffLine,
3438
+ DiffSplitRow,
3436
3439
  EmptyState as EmptyState4,
3440
+ Hunk,
3441
+ Note as Note2,
3437
3442
  ScrollArea as ScrollArea3,
3443
+ Section,
3438
3444
  SegmentedControl as SegmentedControl2,
3439
3445
  StatusBar as StatusBar4,
3446
+ Text as Text3,
3440
3447
  TextButton as TextButton3,
3441
3448
  Toolbar as Toolbar4,
3442
3449
  ToolbarGroup as ToolbarGroup4
3443
3450
  } from "@xenosystem/components/chrome";
3444
- import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
3445
- var MARK = {
3446
- added: { background: "var(--xeno-hover)" },
3447
- removed: { background: "var(--xeno-border-subtle)" },
3448
- context: {}
3449
- };
3450
- var SIGIL = { added: "+", removed: "-", context: " " };
3451
- function visible(text3, show) {
3452
- return show ? text3.replace(/\t/g, "\u2192 ").replace(/ /g, "\xB7") : text3;
3453
- }
3454
- function LineText({
3455
- line,
3456
- language,
3457
- highlighter,
3458
- showWhitespace
3459
- }) {
3451
+
3452
+ // src/ops/diff/declaration.ts
3453
+ var visible = (text4, show) => show ? text4.replace(/\t/g, "\u2192 ").replace(/ /g, "\xB7") : text4;
3454
+ var runsOf = (line, language, highlighter, showWhitespace) => {
3460
3455
  if (line.segments?.length) {
3461
- return /* @__PURE__ */ jsx4(Fragment3, { children: line.segments.map((segment, i) => /* @__PURE__ */ jsx4(
3462
- "span",
3463
- {
3464
- style: segment.kind === "equal" ? void 0 : {
3465
- background: "var(--xeno-border)",
3466
- borderRadius: 2,
3467
- textDecoration: segment.kind === "delete" ? "line-through" : void 0
3468
- },
3469
- children: visible(segment.text, showWhitespace)
3470
- },
3471
- i
3472
- )) });
3456
+ return line.segments.map((s) => s.kind === "equal" ? { text: visible(s.text, showWhitespace) } : { text: visible(s.text, showWhitespace), mark: s.kind });
3473
3457
  }
3474
3458
  const tokens = highlighter?.highlight(line.text, language) ?? null;
3475
- if (tokens) {
3476
- return /* @__PURE__ */ jsx4(Fragment3, { children: tokens.map((token, i) => /* @__PURE__ */ jsx4("span", { className: token.token ? `xeno-tok-${token.token}` : void 0, children: visible(token.text, showWhitespace) }, i)) });
3477
- }
3478
- return /* @__PURE__ */ jsx4(Fragment3, { children: visible(line.text, showWhitespace) });
3479
- }
3480
- var GUTTER = {
3481
- display: "inline-block",
3482
- width: 40,
3483
- textAlign: "right",
3484
- paddingRight: 6,
3485
- opacity: 0.35,
3486
- fontVariantNumeric: "tabular-nums",
3487
- userSelect: "none"
3459
+ if (tokens) return tokens.map((t) => t.token ? { text: visible(t.text, showWhitespace), token: t.token } : { text: visible(t.text, showWhitespace) });
3460
+ return [{ text: visible(line.text, showWhitespace) }];
3488
3461
  };
3489
- function DecisionChip({ decision }) {
3490
- if (!decision) return null;
3491
- if (decision.state === "pending") return /* @__PURE__ */ jsx4(Badge4, { children: "deciding\u2026" });
3492
- if (decision.state === "conflict") {
3493
- return /* @__PURE__ */ jsx4(Badge4, { tone: "error", title: decision.message ?? "The file changed since this was reviewed.", children: "conflict \u2014 re-review" });
3494
- }
3495
- if (decision.state === "failed") {
3496
- return /* @__PURE__ */ jsx4(Badge4, { tone: "error", title: decision.message ?? void 0, children: "failed" });
3497
- }
3498
- if (isSettled(decision.state)) {
3499
- return /* @__PURE__ */ jsx4(Badge4, { tone: "success", children: decision.action === "accept" ? "accepted" : "rejected" });
3500
- }
3501
- return null;
3462
+ var SIGIL = { added: "+", removed: "-", context: " " };
3463
+ function diffViewState(c, options = {}) {
3464
+ const { highlighter, review = false } = options;
3465
+ const state = c.getState();
3466
+ const idle = state.status === "idle";
3467
+ const computing = state.status === "computing";
3468
+ const failed = state.status === "failed";
3469
+ const identical = !idle && !computing && !failed && state.identical;
3470
+ const active = !idle && !computing && !failed && !identical;
3471
+ const unified = state.view === "unified";
3472
+ const cell = (line, side, language, key) => line ? { key, kind: line.kind, gutter: [(side === "l" ? line.oldLineNumber : line.newLineNumber) ?? ""], runs: runsOf(line, language, highlighter, state.showWhitespace) } : { key, kind: "blank", gutter: [""], runs: [] };
3473
+ const files = active ? state.files.map((f) => ({
3474
+ key: f.file.path,
3475
+ path: f.file.path,
3476
+ title: f.file.oldPath && f.file.oldPath !== f.file.path ? `${f.file.oldPath} \u2192 ${f.file.path}` : f.file.path,
3477
+ status: f.file.status,
3478
+ counts: `+${f.additions} \u2212${f.deletions}`,
3479
+ collapsed: f.collapsed,
3480
+ omitted: !f.collapsed && f.file.omitted !== void 0,
3481
+ omission: f.file.omitted ? omissionLabel(f.file.omitted) : "",
3482
+ hunks: f.collapsed || f.file.omitted ? [] : f.file.hunks.map((h) => {
3483
+ const decision = f.decisions[h.id];
3484
+ const ds = decision?.state ?? "undecided";
3485
+ const attention = needsAttention(ds);
3486
+ return {
3487
+ key: h.id,
3488
+ path: f.file.path,
3489
+ id: h.id,
3490
+ header: h.header ?? `@@ -${h.oldStart},${h.oldLines} +${h.newStart},${h.newLines} @@`,
3491
+ focused: state.cursor?.path === f.file.path && state.cursor.hunkId === h.id,
3492
+ settled: isSettled(ds),
3493
+ chipPending: ds === "pending",
3494
+ chipConflict: ds === "conflict",
3495
+ conflictTitle: decision?.message ?? "The file changed since this was reviewed.",
3496
+ chipFailed: ds === "failed",
3497
+ failedTitle: decision?.message ?? void 0,
3498
+ chipApplied: isSettled(ds),
3499
+ appliedText: decision?.action === "accept" ? "accepted" : "rejected",
3500
+ review,
3501
+ attention,
3502
+ deciding: ds === "pending",
3503
+ unified,
3504
+ split: !unified,
3505
+ lines: unified ? h.lines.map((line, i) => ({
3506
+ key: `${h.id}-${i}`,
3507
+ kind: line.kind,
3508
+ gutter: [line.oldLineNumber ?? "", line.newLineNumber ?? ""],
3509
+ sigil: SIGIL[line.kind],
3510
+ runs: runsOf(line, f.file.language, highlighter, state.showWhitespace)
3511
+ })) : [],
3512
+ rows: unified ? [] : splitRows(h).map((row, i) => ({
3513
+ key: `${h.id}-s${i}`,
3514
+ left: cell(row.left, "l", f.file.language, `${h.id}-s${i}-l`),
3515
+ right: cell(row.right, "r", f.file.language, `${h.id}-s${i}-r`)
3516
+ }))
3517
+ };
3518
+ })
3519
+ })) : [];
3520
+ const conflicts = state.review?.conflicts ?? 0;
3521
+ return {
3522
+ idle,
3523
+ computing,
3524
+ failed,
3525
+ // NOT "no changes". A comparison that could not run has established nothing about the content.
3526
+ failedHint: state.message ?? "The host could not compute the differences.",
3527
+ identical,
3528
+ active,
3529
+ view: state.view,
3530
+ views: [{ label: "Unified", value: "unified" }, { label: "Split", value: "split" }],
3531
+ wrap: state.wrap,
3532
+ wrapLabel: state.wrap ? "No wrap" : "Wrap",
3533
+ whitespaceLabel: state.showWhitespace ? "Hide \u2423" : "Show \u2423",
3534
+ conflictNotice: conflicts > 0 ? `${conflicts} conflict${conflicts === 1 ? "" : "s"} \u2014 the file changed since review` : void 0,
3535
+ files,
3536
+ countLabel: `${state.totals.files} file${state.totals.files === 1 ? "" : "s"} \xB7 +${state.totals.additions} \u2212${state.totals.deletions}`,
3537
+ rightLabel: state.review ? `${state.review.decided}/${state.review.total} reviewed` : `${state.oldLabel} \u2192 ${state.newLabel}`
3538
+ };
3502
3539
  }
3503
- function DiffPanelView({ controller, highlighter, review = false }) {
3504
- const state = useSyncExternalStore4(controller.subscribe, controller.getState, controller.getState);
3505
- if (state.status === "idle") {
3506
- return /* @__PURE__ */ jsx4(EmptyState4, { title: "Diff", hint: "Nothing to compare yet." });
3507
- }
3508
- if (state.status === "computing") {
3509
- return /* @__PURE__ */ jsx4(EmptyState4, { title: "Comparing\u2026", hint: "The host is computing the differences." });
3510
- }
3511
- if (state.status === "failed") {
3512
- return /* @__PURE__ */ jsx4(
3513
- EmptyState4,
3514
- {
3515
- title: "Comparison failed",
3516
- hint: state.message ?? "The host could not compute the differences."
3517
- }
3518
- );
3519
- }
3520
- if (state.identical) {
3521
- return /* @__PURE__ */ jsx4(EmptyState4, { title: "No changes", hint: "The two sides are identical." });
3540
+ function applyDiffUi(c, intent) {
3541
+ switch (intent.kind) {
3542
+ case "view":
3543
+ c.setView(intent.view);
3544
+ break;
3545
+ case "toggleWrap":
3546
+ c.setWrap(!c.getState().wrap);
3547
+ break;
3548
+ case "toggleWhitespace":
3549
+ c.setShowWhitespace(!c.getState().showWhitespace);
3550
+ break;
3551
+ case "prev":
3552
+ c.previousHunk();
3553
+ break;
3554
+ case "next":
3555
+ c.nextHunk();
3556
+ break;
3557
+ case "collapse":
3558
+ c.setCollapsed(intent.path, intent.collapsed);
3559
+ break;
3560
+ case "contextMenu":
3561
+ c.requestContextMenu(intent.path, intent.id, intent.x, intent.y);
3562
+ break;
3563
+ case "decide":
3564
+ c.decide(intent.path, intent.id, intent.action);
3565
+ break;
3566
+ case "reReview":
3567
+ c.clearDecision(intent.path, intent.id);
3568
+ break;
3522
3569
  }
3523
- const mono = "var(--xeno-font-mono)";
3524
- const renderLine = (line, file, key) => /* @__PURE__ */ jsxs4(
3525
- "div",
3526
- {
3527
- style: {
3528
- ...MARK[line.kind],
3529
- whiteSpace: state.wrap ? "pre-wrap" : "pre",
3530
- fontFamily: mono,
3531
- fontSize: 11,
3532
- lineHeight: "16px"
3533
- },
3534
- children: [
3535
- /* @__PURE__ */ jsx4("span", { style: GUTTER, children: line.oldLineNumber ?? "" }),
3536
- /* @__PURE__ */ jsx4("span", { style: GUTTER, children: line.newLineNumber ?? "" }),
3537
- /* @__PURE__ */ jsxs4("span", { style: { opacity: 0.45, userSelect: "none" }, children: [
3538
- SIGIL[line.kind],
3539
- " "
3540
- ] }),
3541
- /* @__PURE__ */ jsx4(
3542
- LineText,
3543
- {
3544
- line,
3545
- language: file.file.language,
3546
- highlighter,
3547
- showWhitespace: state.showWhitespace
3548
- }
3549
- )
3550
- ]
3551
- },
3552
- key
3553
- );
3554
- const renderSplitCell = (line, file, side) => /* @__PURE__ */ jsxs4(
3555
- "div",
3556
- {
3557
- style: {
3558
- flex: 1,
3559
- minWidth: 0,
3560
- ...line ? MARK[line.kind] : { opacity: 0.25 },
3561
- whiteSpace: state.wrap ? "pre-wrap" : "pre",
3562
- overflow: "hidden",
3563
- fontFamily: mono,
3564
- fontSize: 11,
3565
- lineHeight: "16px"
3566
- },
3567
- children: [
3568
- /* @__PURE__ */ jsx4("span", { style: GUTTER, children: (side === "l" ? line?.oldLineNumber : line?.newLineNumber) ?? "" }),
3569
- line ? /* @__PURE__ */ jsx4(
3570
- LineText,
3571
- {
3572
- line,
3573
- language: file.file.language,
3574
- highlighter,
3575
- showWhitespace: state.showWhitespace
3576
- }
3577
- ) : null
3578
- ]
3579
- }
3580
- );
3581
- return /* @__PURE__ */ jsxs4("div", { style: { display: "flex", flexDirection: "column", height: "100%", minHeight: 0 }, children: [
3582
- /* @__PURE__ */ jsx4(
3583
- Toolbar4,
3570
+ }
3571
+ var diffViewManifest = {
3572
+ id: diffManifest.id,
3573
+ version: "1.0.0",
3574
+ title: "Diff",
3575
+ category: "ops",
3576
+ layout: { slot: "center" },
3577
+ inputs: [{ id: "view", type: "object", label: "View state", description: "The DiffViewModel the runtime derives from the controller on every draw." }],
3578
+ outputs: [{ id: "ui", type: "object", label: "UI intent", description: "{ kind, view?, path?, id?, collapsed?, action?, x?, y? } \u2014 applied by the runtime to the controller." }],
3579
+ commands: []
3580
+ };
3581
+ var EMPTY4 = {
3582
+ idle: true,
3583
+ computing: false,
3584
+ failed: false,
3585
+ failedHint: "",
3586
+ identical: false,
3587
+ active: false,
3588
+ view: "unified",
3589
+ views: [{ label: "Unified", value: "unified" }, { label: "Split", value: "split" }],
3590
+ wrap: false,
3591
+ wrapLabel: "Wrap",
3592
+ whitespaceLabel: "Show \u2423",
3593
+ files: [],
3594
+ countLabel: "0 files \xB7 +0 \u22120",
3595
+ rightLabel: ""
3596
+ };
3597
+ var BEHAVIOURS3 = {
3598
+ "ops/diffPanelView": (inputs) => inputs.view ?? EMPTY4
3599
+ };
3600
+ var text3 = (b) => ({ component: "text", props: { text: b } });
3601
+ var ui3 = (kind, fields = {}) => ({ emit: "ui", value: { object: { kind: { value: kind }, ...fields } } });
3602
+ var LINE = (kindKey) => ({
3603
+ component: "chrome/DiffLine",
3604
+ each: { item: kindKey, key: "key" },
3605
+ props: { kind: { item: "kind" }, gutter: { item: "gutter" }, sigil: { item: "sigil" }, wrap: { input: "view.wrap" }, runs: { item: "runs" } }
3606
+ });
3607
+ var DIFF_DECLARATION = {
3608
+ format: "xeno.block@1",
3609
+ manifest: diffViewManifest,
3610
+ behaviour: "ops/diffPanelView",
3611
+ composition: {
3612
+ component: "fragment",
3613
+ children: [
3614
+ { component: "chrome/EmptyState", when: { input: "view.idle" }, props: { title: { value: "Diff" }, hint: { value: "Nothing to compare yet." } } },
3615
+ { component: "chrome/EmptyState", when: { input: "view.computing" }, props: { title: { value: "Comparing\u2026" }, hint: { value: "The host is computing the differences." } } },
3616
+ { component: "chrome/EmptyState", when: { input: "view.failed" }, props: { title: { value: "Comparison failed" }, hint: { input: "view.failedHint" } } },
3617
+ { component: "chrome/EmptyState", when: { input: "view.identical" }, props: { title: { value: "No changes" }, hint: { value: "The two sides are identical." } } },
3584
3618
  {
3585
- left: /* @__PURE__ */ jsx4(
3586
- SegmentedControl2,
3619
+ component: "chrome/Column",
3620
+ when: { input: "view.active" },
3621
+ children: [
3587
3622
  {
3588
- label: "View",
3589
- value: state.view,
3590
- onChange: (value) => controller.setView(value),
3591
- options: [
3592
- { label: "Unified", value: "unified" },
3593
- { label: "Split", value: "split" }
3594
- ]
3595
- }
3596
- ),
3597
- right: /* @__PURE__ */ jsxs4(ToolbarGroup4, { end: true, children: [
3598
- /* @__PURE__ */ jsx4(TextButton3, { onClick: () => controller.setWrap(!state.wrap), children: state.wrap ? "No wrap" : "Wrap" }),
3599
- /* @__PURE__ */ jsx4(TextButton3, { onClick: () => controller.setShowWhitespace(!state.showWhitespace), children: state.showWhitespace ? "Hide \u2423" : "Show \u2423" }),
3600
- /* @__PURE__ */ jsx4(TextButton3, { onClick: () => controller.previousHunk(), children: "Prev" }),
3601
- /* @__PURE__ */ jsx4(TextButton3, { onClick: () => controller.nextHunk(), children: "Next" })
3602
- ] })
3603
- }
3604
- ),
3605
- state.review && state.review.conflicts > 0 ? /* @__PURE__ */ jsx4("div", { style: { padding: "2px 8px", fontSize: 9 }, children: /* @__PURE__ */ jsxs4(Badge4, { tone: "error", children: [
3606
- state.review.conflicts,
3607
- " conflict",
3608
- state.review.conflicts === 1 ? "" : "s",
3609
- " \u2014 the file changed since review"
3610
- ] }) }) : null,
3611
- /* @__PURE__ */ jsx4(ScrollArea3, { children: state.files.map((file) => /* @__PURE__ */ jsxs4("div", { style: { borderBottom: "1px solid var(--xeno-border-subtle)" }, children: [
3612
- /* @__PURE__ */ jsxs4(
3613
- "div",
3614
- {
3615
- style: {
3616
- display: "flex",
3617
- alignItems: "center",
3618
- gap: 6,
3619
- padding: "3px 8px",
3620
- position: "sticky",
3621
- top: 0,
3622
- background: "var(--xeno-surface)",
3623
- zIndex: 1
3623
+ component: "chrome/Toolbar",
3624
+ props: {
3625
+ left: { node: { component: "chrome/SegmentedControl", props: { label: { value: "View" }, value: { input: "view.view" }, options: { input: "view.views" } }, on: { onChange: ui3("view", { view: { arg: "" } }) } } },
3626
+ right: {
3627
+ node: {
3628
+ component: "chrome/ToolbarGroup",
3629
+ props: { end: { value: true } },
3630
+ children: [
3631
+ { component: "chrome/TextButton", children: [text3({ input: "view.wrapLabel" })], on: { onClick: ui3("toggleWrap") } },
3632
+ { component: "chrome/TextButton", children: [text3({ input: "view.whitespaceLabel" })], on: { onClick: ui3("toggleWhitespace") } },
3633
+ { component: "chrome/TextButton", children: [text3({ value: "Prev" })], on: { onClick: ui3("prev") } },
3634
+ { component: "chrome/TextButton", children: [text3({ value: "Next" })], on: { onClick: ui3("next") } }
3635
+ ]
3636
+ }
3637
+ }
3638
+ }
3624
3639
  },
3625
- children: [
3626
- /* @__PURE__ */ jsx4(TextButton3, { onClick: () => controller.setCollapsed(file.file.path, !file.collapsed), children: file.collapsed ? "\u25B8" : "\u25BE" }),
3627
- /* @__PURE__ */ jsx4("span", { style: { fontSize: 11, fontWeight: 600 }, children: file.file.oldPath && file.file.oldPath !== file.file.path ? `${file.file.oldPath} \u2192 ${file.file.path}` : file.file.path }),
3628
- /* @__PURE__ */ jsx4(Badge4, { children: file.file.status }),
3629
- /* @__PURE__ */ jsxs4("span", { style: { fontSize: 10, fontVariantNumeric: "tabular-nums", opacity: 0.6 }, children: [
3630
- "+",
3631
- file.additions,
3632
- " \u2212",
3633
- file.deletions
3634
- ] })
3635
- ]
3636
- }
3637
- ),
3638
- file.collapsed ? null : file.file.omitted ? (
3639
- // 🔴 Never an empty body. "Binary — not compared" and "no changes" are different
3640
- // facts, and a reviewer who reads the first as the second approves something nobody
3641
- // looked at.
3642
- /* @__PURE__ */ jsx4("div", { style: { padding: "6px 12px", fontSize: 10, opacity: 0.6 }, children: omissionLabel(file.file.omitted) })
3643
- ) : file.file.hunks.map((hunk) => {
3644
- const decision = file.decisions[hunk.id];
3645
- const focused = state.cursor?.path === file.file.path && state.cursor.hunkId === hunk.id;
3646
- return /* @__PURE__ */ jsxs4(
3647
- "div",
3640
+ { component: "chrome/Note", when: { input: "view.conflictNotice" }, children: [{ component: "chrome/Badge", props: { tone: { value: "error" } }, children: [text3({ input: "view.conflictNotice" })] }] },
3648
3641
  {
3649
- onContextMenu: (event) => {
3650
- event.preventDefault();
3651
- controller.requestContextMenu(
3652
- file.file.path,
3653
- hunk.id,
3654
- event.clientX,
3655
- event.clientY
3656
- );
3657
- },
3658
- style: {
3659
- borderLeft: focused ? "2px solid var(--xeno-active)" : "2px solid transparent",
3660
- opacity: isSettled(decision?.state ?? "undecided") ? 0.55 : 1
3661
- },
3642
+ component: "chrome/ScrollArea",
3662
3643
  children: [
3663
- /* @__PURE__ */ jsxs4(
3664
- "div",
3665
- {
3666
- style: {
3667
- display: "flex",
3668
- alignItems: "center",
3669
- gap: 6,
3670
- padding: "1px 8px",
3671
- fontSize: 9,
3672
- opacity: 0.55
3673
- },
3674
- children: [
3675
- /* @__PURE__ */ jsx4("span", { style: { fontFamily: mono }, children: hunk.header ?? `@@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@` }),
3676
- /* @__PURE__ */ jsx4(DecisionChip, { decision }),
3677
- review ? /* @__PURE__ */ jsx4("span", { style: { marginLeft: "auto", display: "flex", gap: 4 }, children: needsAttention(decision?.state ?? "undecided") ? /* @__PURE__ */ jsx4(
3678
- TextButton3,
3679
- {
3680
- onClick: () => controller.clearDecision(file.file.path, hunk.id),
3681
- children: "Re-review"
3682
- }
3683
- ) : /* @__PURE__ */ jsxs4(Fragment3, { children: [
3684
- /* @__PURE__ */ jsx4(
3685
- TextButton3,
3686
- {
3687
- disabled: decision?.state === "pending",
3688
- onClick: () => controller.decide(file.file.path, hunk.id, "accept"),
3689
- children: "Accept"
3644
+ {
3645
+ component: "chrome/Section",
3646
+ each: { input: "view.files", key: "key" },
3647
+ props: {
3648
+ title: { item: "title" },
3649
+ badge: { node: { component: "chrome/Badge", children: [text3({ item: "status" })] } },
3650
+ actions: { node: { component: "chrome/Text", props: { role: { value: "number" } }, children: [text3({ item: "counts" })] } },
3651
+ collapsible: { value: true },
3652
+ collapsed: { item: "collapsed" },
3653
+ flush: { value: true }
3654
+ },
3655
+ on: { onCollapsedChange: ui3("collapse", { path: { item: "path" }, collapsed: { arg: "" } }) },
3656
+ children: [
3657
+ // 🔴 Never an empty body. "Binary — not compared" and "no changes" are different facts.
3658
+ { component: "chrome/Text", when: { item: "omitted" }, props: { role: { value: "meta" } }, children: [text3({ item: "omission" })] },
3659
+ {
3660
+ component: "chrome/Hunk",
3661
+ each: { item: "hunks", key: "key" },
3662
+ props: {
3663
+ focused: { item: "focused" },
3664
+ settled: { item: "settled" },
3665
+ header: {
3666
+ node: {
3667
+ component: "fragment",
3668
+ children: [
3669
+ text3({ item: "header" }),
3670
+ { component: "chrome/Badge", when: { item: "chipPending" }, children: [text3({ value: "deciding\u2026" })] },
3671
+ { component: "chrome/Badge", when: { item: "chipConflict" }, props: { tone: { value: "error" }, title: { item: "conflictTitle" } }, children: [text3({ value: "conflict \u2014 re-review" })] },
3672
+ { component: "chrome/Badge", when: { item: "chipFailed" }, props: { tone: { value: "error" }, title: { item: "failedTitle" } }, children: [text3({ value: "failed" })] },
3673
+ { component: "chrome/Badge", when: { item: "chipApplied" }, props: { tone: { value: "success" } }, children: [text3({ item: "appliedText" })] }
3674
+ ]
3690
3675
  }
3691
- ),
3692
- /* @__PURE__ */ jsx4(
3693
- TextButton3,
3694
- {
3695
- disabled: decision?.state === "pending",
3696
- onClick: () => controller.decide(file.file.path, hunk.id, "reject"),
3697
- children: "Reject"
3676
+ },
3677
+ actions: {
3678
+ node: {
3679
+ component: "fragment",
3680
+ when: { item: "review" },
3681
+ children: [
3682
+ { component: "chrome/TextButton", when: { item: "attention" }, children: [text3({ value: "Re-review" })], on: { onClick: ui3("reReview", { path: { item: "path" }, id: { item: "id" } }) } },
3683
+ { component: "chrome/TextButton", when: { not: { item: "attention" } }, props: { disabled: { item: "deciding" } }, children: [text3({ value: "Accept" })], on: { onClick: ui3("decide", { path: { item: "path" }, id: { item: "id" }, action: { value: "accept" } }) } },
3684
+ { component: "chrome/TextButton", when: { not: { item: "attention" } }, props: { disabled: { item: "deciding" } }, children: [text3({ value: "Reject" })], on: { onClick: ui3("decide", { path: { item: "path" }, id: { item: "id" }, action: { value: "reject" } }) } }
3685
+ ]
3698
3686
  }
3699
- )
3700
- ] }) }) : null
3701
- ]
3702
- }
3703
- ),
3704
- state.view === "unified" ? hunk.lines.map((line, i) => renderLine(line, file, `${hunk.id}-${i}`)) : splitRows(hunk).map((row, i) => /* @__PURE__ */ jsxs4("div", { style: { display: "flex", gap: 1 }, children: [
3705
- renderSplitCell(row.left, file, "l"),
3706
- renderSplitCell(row.right, file, "r")
3707
- ] }, `${hunk.id}-s${i}`))
3687
+ }
3688
+ },
3689
+ on: { onContextMenuAt: ui3("contextMenu", { path: { item: "path" }, id: { item: "id" }, x: { arg: "" }, y: { arg: "", index: 1 } }) },
3690
+ children: [
3691
+ LINE("lines"),
3692
+ {
3693
+ component: "chrome/DiffSplitRow",
3694
+ each: { item: "rows", key: "key" },
3695
+ children: [
3696
+ { component: "chrome/DiffLine", props: { kind: { item: "left.kind" }, gutter: { item: "left.gutter" }, wrap: { input: "view.wrap" }, runs: { item: "left.runs" } } },
3697
+ { component: "chrome/DiffLine", props: { kind: { item: "right.kind" }, gutter: { item: "right.gutter" }, wrap: { input: "view.wrap" }, runs: { item: "right.runs" } } }
3698
+ ]
3699
+ }
3700
+ ]
3701
+ }
3702
+ ]
3703
+ }
3708
3704
  ]
3709
3705
  },
3710
- hunk.id
3711
- );
3712
- })
3713
- ] }, file.file.path)) }),
3714
- /* @__PURE__ */ jsx4(
3715
- StatusBar4,
3716
- {
3717
- left: `${state.totals.files} file${state.totals.files === 1 ? "" : "s"} \xB7 +${state.totals.additions} \u2212${state.totals.deletions}`,
3718
- right: state.review ? `${state.review.decided}/${state.review.total} reviewed` : `${state.oldLabel} \u2192 ${state.newLabel}`
3706
+ {
3707
+ component: "chrome/StatusBar",
3708
+ props: {
3709
+ left: { node: { component: "chrome/Text", props: { role: { value: "number" } }, children: [text3({ input: "view.countLabel" })] } },
3710
+ right: { node: { component: "chrome/Text", props: { role: { value: "meta" } }, children: [text3({ input: "view.rightLabel" })] } }
3711
+ }
3712
+ }
3713
+ ]
3719
3714
  }
3720
- )
3715
+ ]
3716
+ }
3717
+ };
3718
+
3719
+ // src/ops/diff/react/DiffPanelView.tsx
3720
+ import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
3721
+ function DiffPanelView({ controller, ...options }) {
3722
+ useSyncExternalStore4(controller.subscribe, controller.getState, controller.getState);
3723
+ const view = diffViewState(controller, options);
3724
+ const ui4 = (intent) => applyDiffUi(controller, intent);
3725
+ const hunk = (h) => /* @__PURE__ */ jsxs4(
3726
+ Hunk,
3727
+ {
3728
+ focused: h.focused,
3729
+ settled: h.settled,
3730
+ header: /* @__PURE__ */ jsxs4(Fragment3, { children: [
3731
+ h.header,
3732
+ h.chipPending ? /* @__PURE__ */ jsx4(Badge4, { children: "deciding\u2026" }) : null,
3733
+ h.chipConflict ? /* @__PURE__ */ jsx4(Badge4, { tone: "error", title: h.conflictTitle, children: "conflict \u2014 re-review" }) : null,
3734
+ h.chipFailed ? /* @__PURE__ */ jsx4(Badge4, { tone: "error", title: h.failedTitle, children: "failed" }) : null,
3735
+ h.chipApplied ? /* @__PURE__ */ jsx4(Badge4, { tone: "success", children: h.appliedText }) : null
3736
+ ] }),
3737
+ actions: h.review ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
3738
+ h.attention ? /* @__PURE__ */ jsx4(TextButton3, { onClick: () => ui4({ kind: "reReview", path: h.path, id: h.id }), children: "Re-review" }) : null,
3739
+ !h.attention ? /* @__PURE__ */ jsx4(TextButton3, { disabled: h.deciding, onClick: () => ui4({ kind: "decide", path: h.path, id: h.id, action: "accept" }), children: "Accept" }) : null,
3740
+ !h.attention ? /* @__PURE__ */ jsx4(TextButton3, { disabled: h.deciding, onClick: () => ui4({ kind: "decide", path: h.path, id: h.id, action: "reject" }), children: "Reject" }) : null
3741
+ ] }) : null,
3742
+ onContextMenuAt: (x, y) => ui4({ kind: "contextMenu", path: h.path, id: h.id, x, y }),
3743
+ children: [
3744
+ h.lines.map((l) => /* @__PURE__ */ jsx4(DiffLine, { kind: l.kind, gutter: l.gutter, sigil: l.sigil, wrap: view.wrap, runs: l.runs }, l.key)),
3745
+ h.rows.map((r) => /* @__PURE__ */ jsxs4(DiffSplitRow, { children: [
3746
+ /* @__PURE__ */ jsx4(DiffLine, { kind: r.left.kind, gutter: r.left.gutter, wrap: view.wrap, runs: r.left.runs }),
3747
+ /* @__PURE__ */ jsx4(DiffLine, { kind: r.right.kind, gutter: r.right.gutter, wrap: view.wrap, runs: r.right.runs })
3748
+ ] }, r.key))
3749
+ ]
3750
+ },
3751
+ h.key
3752
+ );
3753
+ return /* @__PURE__ */ jsxs4(Fragment3, { children: [
3754
+ view.idle ? /* @__PURE__ */ jsx4(EmptyState4, { title: "Diff", hint: "Nothing to compare yet." }) : null,
3755
+ view.computing ? /* @__PURE__ */ jsx4(EmptyState4, { title: "Comparing\u2026", hint: "The host is computing the differences." }) : null,
3756
+ view.failed ? /* @__PURE__ */ jsx4(EmptyState4, { title: "Comparison failed", hint: view.failedHint }) : null,
3757
+ view.identical ? /* @__PURE__ */ jsx4(EmptyState4, { title: "No changes", hint: "The two sides are identical." }) : null,
3758
+ view.active ? /* @__PURE__ */ jsxs4(Column3, { children: [
3759
+ /* @__PURE__ */ jsx4(
3760
+ Toolbar4,
3761
+ {
3762
+ left: /* @__PURE__ */ jsx4(SegmentedControl2, { label: "View", value: view.view, options: view.views, onChange: (value) => ui4({ kind: "view", view: value }) }),
3763
+ right: /* @__PURE__ */ jsxs4(ToolbarGroup4, { end: true, children: [
3764
+ /* @__PURE__ */ jsx4(TextButton3, { onClick: () => ui4({ kind: "toggleWrap" }), children: view.wrapLabel }),
3765
+ /* @__PURE__ */ jsx4(TextButton3, { onClick: () => ui4({ kind: "toggleWhitespace" }), children: view.whitespaceLabel }),
3766
+ /* @__PURE__ */ jsx4(TextButton3, { onClick: () => ui4({ kind: "prev" }), children: "Prev" }),
3767
+ /* @__PURE__ */ jsx4(TextButton3, { onClick: () => ui4({ kind: "next" }), children: "Next" })
3768
+ ] })
3769
+ }
3770
+ ),
3771
+ view.conflictNotice ? /* @__PURE__ */ jsx4(Note2, { children: /* @__PURE__ */ jsx4(Badge4, { tone: "error", children: view.conflictNotice }) }) : null,
3772
+ /* @__PURE__ */ jsx4(ScrollArea3, { children: view.files.map((f) => /* @__PURE__ */ jsxs4(
3773
+ Section,
3774
+ {
3775
+ title: f.title,
3776
+ badge: /* @__PURE__ */ jsx4(Badge4, { children: f.status }),
3777
+ actions: /* @__PURE__ */ jsx4(Text3, { role: "number", children: f.counts }),
3778
+ collapsible: true,
3779
+ collapsed: f.collapsed,
3780
+ flush: true,
3781
+ onCollapsedChange: (collapsed) => ui4({ kind: "collapse", path: f.path, collapsed }),
3782
+ children: [
3783
+ f.omitted ? /* @__PURE__ */ jsx4(Text3, { role: "meta", children: f.omission }) : null,
3784
+ f.hunks.map(hunk)
3785
+ ]
3786
+ },
3787
+ f.key
3788
+ )) }),
3789
+ /* @__PURE__ */ jsx4(StatusBar4, { left: /* @__PURE__ */ jsx4(Text3, { role: "number", children: view.countLabel }), right: /* @__PURE__ */ jsx4(Text3, { role: "meta", children: view.rightLabel }) })
3790
+ ] }) : null
3721
3791
  ] });
3722
3792
  }
3723
3793
 
@@ -3830,6 +3900,9 @@ function createDiffPanel(options = {}) {
3830
3900
  };
3831
3901
  }
3832
3902
  var diffPanel = createDiffPanel();
3903
+
3904
+ // src/ops/diff/index.ts
3905
+ var DIFF_RUNTIME_UNITS = [];
3833
3906
  export {
3834
3907
  ALL_STATUSES,
3835
3908
  BEHAVIOURS as CONSOLE_BEHAVIOURS,
@@ -3840,7 +3913,10 @@ export {
3840
3913
  ConsolePanelView,
3841
3914
  ConsoleScroller,
3842
3915
  DEFAULT_MAX_STEP_DEPTH,
3916
+ BEHAVIOURS3 as DIFF_BEHAVIOURS,
3917
+ DIFF_DECLARATION,
3843
3918
  DIFF_PANEL_ID,
3919
+ DIFF_RUNTIME_UNITS,
3844
3920
  DiffController,
3845
3921
  DiffPanelView,
3846
3922
  LEVEL_ORDER,
@@ -3861,6 +3937,7 @@ export {
3861
3937
  XENO_TERMINAL_THEME_KEYS,
3862
3938
  appendStepsAtPath,
3863
3939
  applyConsoleUi,
3940
+ applyDiffUi,
3864
3941
  applyRunsUi,
3865
3942
  canCancel,
3866
3943
  canRetry,
@@ -3879,6 +3956,8 @@ export {
3879
3956
  decodeRunPath,
3880
3957
  diffManifest,
3881
3958
  diffPanel,
3959
+ diffViewManifest,
3960
+ diffViewState,
3882
3961
  durationOf,
3883
3962
  encodeRunPath,
3884
3963
  flattenHunks,