@xenosystem/blocks 0.13.18 → 0.13.20

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.
@@ -607,7 +607,7 @@ interface ConsoleViewOptions {
607
607
  declare function consoleViewState(c: ConsoleController, options?: ConsoleViewOptions): ConsoleViewModel;
608
608
  declare function applyConsoleUi(c: ConsoleController, intent: ConsoleUiIntent): void;
609
609
  declare const consoleViewManifest: PanelManifest;
610
- declare const BEHAVIOURS$1: {
610
+ declare const BEHAVIOURS$2: {
611
611
  'ops/consolePanelView': (inputs: Record<string, unknown>) => ConsoleViewModel;
612
612
  };
613
613
  /** The runtime-bound unit: the scroller the virtualizer measures and follow-mode pins. */
@@ -1518,7 +1518,7 @@ interface RunsViewOptions {
1518
1518
  declare function runsViewState(c: RunsController, options?: RunsViewOptions): RunsViewModel;
1519
1519
  declare function applyRunsUi(c: RunsController, intent: RunsUiIntent): void;
1520
1520
  declare const runsViewManifest: PanelManifest;
1521
- declare const BEHAVIOURS: {
1521
+ declare const BEHAVIOURS$1: {
1522
1522
  'ops/runsPanelView': (inputs: Record<string, unknown>) => RunsViewModel;
1523
1523
  };
1524
1524
  declare const RUNS_RUNTIME_UNITS: readonly [];
@@ -2424,24 +2424,174 @@ declare const DIFF_PANEL_ID = "xeno.core.diff";
2424
2424
  declare const diffManifest: PanelManifest;
2425
2425
 
2426
2426
  /**
2427
- * The Diff panel view.
2427
+ * The Diff panel AS A DECLARATION — the twentieth block written the §8d way.
2428
2428
  *
2429
- * **Where primitives fit and where they do not.** Toolbar, badges, section headers and the empty
2430
- * states are primitives. The diff body is not: a two-column, line-numbered, monospaced grid whose
2431
- * rows must align across both columns has no primitive, and the one thing it must never be is a
2432
- * `RowList` — those rows are variable-height and this grid's correctness depends on them not being.
2429
+ * Presentation contract: input `view` (a {@link DiffViewModel} the runtime derives from the
2430
+ * controller on every draw — the status decided, every file, hunk and line as DATA), output `ui`
2431
+ * (a {@link DiffUiIntent}) applied by the runtime to the controller's own methods.
2433
2432
  *
2434
- * @module
2433
+ * A file is a `chrome/Section` (collapsible, the path as a case-keeping `Text` title, the status as its
2434
+ * badge, the counts as its actions); a
2435
+ * hunk is a `chrome/Hunk` (focused by the cursor, settled once applied, the range and the decision
2436
+ * chip in its header, the review actions at its end); a line is a `chrome/DiffLine` whose text is
2437
+ * RUNS — the host's word-level marks when it computed them (they carry more information than a
2438
+ * token colour, and a prose diff has no tokens at all), else the highlighter's tokens, else plain —
2439
+ * with whitespace made visible here, in the model, when asked. Split view pairs lines in a
2440
+ * `chrome/DiffSplitRow` with a blank side. No runtime-bound unit.
2441
+ *
2442
+ * Three facts the model keeps distinct because a reviewer reads them differently: a comparison that
2443
+ * FAILED has established nothing (never "no changes"); a file OMITTED (binary, too large) was not
2444
+ * compared (never an empty body); a decision in CONFLICT is not one that failed (the file moved, so
2445
+ * it must be made again).
2435
2446
  */
2436
2447
 
2437
- /** Props for {@link DiffPanelView}. */
2438
- interface DiffPanelViewProps {
2439
- controller: DiffController;
2448
+ /** The half of a renderer's config the view reads. */
2449
+ interface DiffViewOptions {
2440
2450
  /** Optional syntax seam. No highlighter means plain text, never a missing panel. */
2441
2451
  highlighter?: XenoDiffHighlighter;
2452
+ /** Show the review actions. */
2442
2453
  review?: boolean;
2443
2454
  }
2455
+ interface DiffViewRun {
2456
+ text: string;
2457
+ mark?: 'insert' | 'delete';
2458
+ token?: string;
2459
+ }
2460
+ interface DiffViewLine {
2461
+ key: string;
2462
+ kind: 'added' | 'removed' | 'context' | 'blank';
2463
+ gutter: (number | string)[];
2464
+ sigil?: string;
2465
+ runs: DiffViewRun[];
2466
+ }
2467
+ interface DiffViewSplitRow {
2468
+ key: string;
2469
+ left: DiffViewLine;
2470
+ right: DiffViewLine;
2471
+ }
2472
+ interface DiffViewHunk {
2473
+ key: string;
2474
+ path: string;
2475
+ id: string;
2476
+ header: string;
2477
+ focused: boolean;
2478
+ settled: boolean;
2479
+ chipPending: boolean;
2480
+ chipConflict: boolean;
2481
+ conflictTitle: string;
2482
+ chipFailed: boolean;
2483
+ failedTitle?: string;
2484
+ chipApplied: boolean;
2485
+ appliedText: string;
2486
+ /** Review actions shown; `attention` swaps accept/reject for re-review. */
2487
+ review: boolean;
2488
+ attention: boolean;
2489
+ deciding: boolean;
2490
+ unified: boolean;
2491
+ split: boolean;
2492
+ lines: DiffViewLine[];
2493
+ rows: DiffViewSplitRow[];
2494
+ }
2495
+ interface DiffViewFile {
2496
+ key: string;
2497
+ path: string;
2498
+ title: string;
2499
+ status: string;
2500
+ counts: string;
2501
+ collapsed: boolean;
2502
+ omitted: boolean;
2503
+ omission: string;
2504
+ hunks: DiffViewHunk[];
2505
+ }
2506
+ interface DiffViewModel {
2507
+ idle: boolean;
2508
+ computing: boolean;
2509
+ failed: boolean;
2510
+ failedHint: string;
2511
+ identical: boolean;
2512
+ active: boolean;
2513
+ view: XenoDiffView;
2514
+ views: {
2515
+ label: string;
2516
+ value: XenoDiffView;
2517
+ }[];
2518
+ wrap: boolean;
2519
+ wrapLabel: string;
2520
+ whitespaceLabel: string;
2521
+ conflictNotice?: string;
2522
+ files: DiffViewFile[];
2523
+ countLabel: string;
2524
+ rightLabel: string;
2525
+ }
2526
+ type DiffUiIntent = {
2527
+ kind: 'view';
2528
+ view: XenoDiffView;
2529
+ } | {
2530
+ kind: 'toggleWrap';
2531
+ } | {
2532
+ kind: 'toggleWhitespace';
2533
+ } | {
2534
+ kind: 'prev';
2535
+ } | {
2536
+ kind: 'next';
2537
+ } | {
2538
+ kind: 'collapse';
2539
+ path: string;
2540
+ collapsed: boolean;
2541
+ } | {
2542
+ kind: 'contextMenu';
2543
+ path: string;
2544
+ id: string;
2545
+ x: number;
2546
+ y: number;
2547
+ } | {
2548
+ kind: 'decide';
2549
+ path: string;
2550
+ id: string;
2551
+ action: 'accept' | 'reject';
2552
+ } | {
2553
+ kind: 'reReview';
2554
+ path: string;
2555
+ id: string;
2556
+ };
2557
+ /** Derive the view model from the live controller — the runtime calls this on every draw. */
2558
+ declare function diffViewState(c: DiffController, options?: DiffViewOptions): DiffViewModel;
2559
+ /** Apply a `ui` intent to the controller — the runtime's half of the contract. */
2560
+ declare function applyDiffUi(c: DiffController, intent: DiffUiIntent): void;
2561
+ declare const diffViewManifest: PanelManifest;
2562
+ declare const BEHAVIOURS: {
2563
+ 'ops/diffPanelView': (inputs: Record<string, unknown>) => DiffViewModel;
2564
+ };
2565
+ declare const DIFF_DECLARATION: BlockDeclaration;
2566
+
2567
+ /**
2568
+ * The Diff panel view — the CODE VIEW of `DIFF_DECLARATION`, drawing the same {@link DiffViewModel}
2569
+ * byte-identical (the equivalence suite holds the two together). No runtime-bound unit; every
2570
+ * intent goes through `applyDiffUi`.
2571
+ *
2572
+ * Composes `@xenosystem/components/chrome` — a file is a `Section`, a hunk a `Hunk`, a line a
2573
+ * `DiffLine` of runs; the host must ensure `@xenosystem/workbench/primitives.css` is present.
2574
+ *
2575
+ * @module
2576
+ */
2577
+
2578
+ /** Props for {@link DiffPanelView}. */
2579
+ interface DiffPanelViewProps extends DiffViewOptions {
2580
+ controller: DiffController;
2581
+ }
2444
2582
  /** The Diff panel view. */
2445
- declare function DiffPanelView({ controller, highlighter, review }: DiffPanelViewProps): ReactNode;
2583
+ declare function DiffPanelView({ controller, ...options }: DiffPanelViewProps): ReactNode;
2584
+
2585
+ /**
2586
+ * `@xenosystem/blocks/ops` — the canonical XENO Diff panel (`xeno.core.diff`).
2587
+ *
2588
+ * Renders a **host-computed** comparison and emits hunk decisions as intents. Bundles no diff
2589
+ * engine and no editor.
2590
+ *
2591
+ * @packageDocumentation
2592
+ */
2593
+
2594
+ /** The unit names the runtime supplies for this block — none; every unit is a chrome unit. */
2595
+ declare const DIFF_RUNTIME_UNITS: readonly [];
2446
2596
 
2447
- export { ALL_STATUSES, BEHAVIOURS$1 as CONSOLE_BEHAVIOURS, CONSOLE_DECLARATION, CONSOLE_PANEL_ID, CONSOLE_RUNTIME_UNITS, ConsoleController, type ConsoleControllerOptions, type ConsoleHostBridge, type ConsolePanelState, ConsolePanelView, type ConsolePanelViewProps, type ConsoleRenderContext, ConsoleScroller, type ConsoleUiIntent, type ConsoleViewModel, type ConsoleViewOptions, type ConsoleViewRow, type ConsoleViewSource, type ConsoleViewState, type CreateConsolePanelOptions, type CreateDiffPanelOptions, type CreateRunsPanelOptions, type CreateTerminalPanelOptions, DEFAULT_MAX_STEP_DEPTH, DIFF_PANEL_ID, DiffController, type DiffControllerOptions, type DiffFileView, type DiffHostBridge, type DiffPanelState, DiffPanelView, type DiffPanelViewProps, type DiffRenderContext, type DiffViewState, type FlattenOptions, LEVEL_ORDER, MESSAGE_ELISION, REDACTION_CARRY, BEHAVIOURS as RUNS_BEHAVIOURS, RUNS_DECLARATION, RUNS_PANEL_ID, RUNS_RUNTIME_UNITS, RUN_PATH_SEPARATOR, RUN_STATUSES, RunsController, type RunsControllerOptions, type RunsHostBridge, type RunsPanelState, RunsPanelView, type RunsPanelViewProps, type RunsRenderContext, type RunsUiIntent, type RunsViewChip, type RunsViewModel, type RunsViewOptions, type RunsViewRow, type RunsViewState, TERMINAL_PANEL_ID, TERMINAL_STATUSES, TerminalController, type TerminalControllerOptions, type TerminalHostBridge, type TerminalInstanceView, type TerminalPanelState, TerminalPanelView, type TerminalPanelViewProps, type TerminalRenderContext, type TerminalViewState, XENO_TERMINAL_THEME_KEYS, type XenoDiffAction, type XenoDiffDecision, type XenoDiffDecisionIntent, type XenoDiffDecisionState, type XenoDiffFile, type XenoDiffFileOmission, type XenoDiffHighlighter, type XenoDiffHunk, type XenoDiffLine, type XenoDiffLineKind, type XenoDiffModel, type XenoDiffNavigate, type XenoDiffSegment, type XenoDiffStatus, type XenoDiffView, type XenoLogActivate, type XenoLogDelta, type XenoLogFilter, type XenoLogLevel, type XenoLogPatch, type XenoLogRecord, type XenoLogSource, type XenoRun, type XenoRunAction, type XenoRunActivate, type XenoRunDelta, type XenoRunError, type XenoRunFilter, type XenoRunLifecycle, type XenoRunPath, type XenoRunRow, type XenoRunRunRow, type XenoRunStats, type XenoRunStatus, type XenoRunStep, type XenoRunStepAppend, type XenoRunStepPatch, type XenoRunStepRow, type XenoRunTruncation, type XenoTerminalData, type XenoTerminalEmulator, type XenoTerminalId, type XenoTerminalIntent, type XenoTerminalSession, type XenoTerminalStatus, type XenoTerminalTheme, appendStepsAtPath, applyConsoleUi, applyRunsUi, canCancel, canRetry, collapseAdjacent, computeStats, consoleManifest, consolePanel, consoleRuntimeComponents, consoleViewManifest, consoleViewState, countChanges, createConsolePanel, createDiffPanel, createRunsPanel, createTerminalPanel, decodeRunPath, diffManifest, diffPanel, durationOf, encodeRunPath, flattenHunks, flattenRunRows, formatDuration, formatRecord, isActive, isLive, isSettled, isTerminal, isWaiting, lifecycleOf, matchesSearch as matchesRunSearch, matchesSearch$1 as matchesSearch, measuredSize, meetsLevel, needsAttention, omissionLabel, patchStepAtPath, queueDurationOf, readTerminalTheme, resolveStepPath, runsManifest, runsPanel, runsViewManifest, runsViewState, safeJson, spliceRedactedAppend, splitRows, statusLabel, terminalManifest, terminalPanel, terminalThemeVar, toneOf, truncateLogMessage };
2597
+ export { ALL_STATUSES, BEHAVIOURS$2 as CONSOLE_BEHAVIOURS, CONSOLE_DECLARATION, CONSOLE_PANEL_ID, CONSOLE_RUNTIME_UNITS, ConsoleController, type ConsoleControllerOptions, type ConsoleHostBridge, type ConsolePanelState, ConsolePanelView, type ConsolePanelViewProps, type ConsoleRenderContext, ConsoleScroller, type ConsoleUiIntent, type ConsoleViewModel, type ConsoleViewOptions, type ConsoleViewRow, type ConsoleViewSource, type ConsoleViewState, type CreateConsolePanelOptions, type CreateDiffPanelOptions, type CreateRunsPanelOptions, type CreateTerminalPanelOptions, DEFAULT_MAX_STEP_DEPTH, BEHAVIOURS as DIFF_BEHAVIOURS, DIFF_DECLARATION, DIFF_PANEL_ID, DIFF_RUNTIME_UNITS, DiffController, type DiffControllerOptions, type DiffFileView, type DiffHostBridge, type DiffPanelState, DiffPanelView, type DiffPanelViewProps, type DiffRenderContext, type DiffUiIntent, type DiffViewFile, type DiffViewHunk, type DiffViewLine, type DiffViewModel, type DiffViewOptions, type DiffViewRun, type DiffViewSplitRow, type DiffViewState, type FlattenOptions, LEVEL_ORDER, MESSAGE_ELISION, REDACTION_CARRY, BEHAVIOURS$1 as RUNS_BEHAVIOURS, RUNS_DECLARATION, RUNS_PANEL_ID, RUNS_RUNTIME_UNITS, RUN_PATH_SEPARATOR, RUN_STATUSES, RunsController, type RunsControllerOptions, type RunsHostBridge, type RunsPanelState, RunsPanelView, type RunsPanelViewProps, type RunsRenderContext, type RunsUiIntent, type RunsViewChip, type RunsViewModel, type RunsViewOptions, type RunsViewRow, type RunsViewState, TERMINAL_PANEL_ID, TERMINAL_STATUSES, TerminalController, type TerminalControllerOptions, type TerminalHostBridge, type TerminalInstanceView, type TerminalPanelState, TerminalPanelView, type TerminalPanelViewProps, type TerminalRenderContext, type TerminalViewState, XENO_TERMINAL_THEME_KEYS, type XenoDiffAction, type XenoDiffDecision, type XenoDiffDecisionIntent, type XenoDiffDecisionState, type XenoDiffFile, type XenoDiffFileOmission, type XenoDiffHighlighter, type XenoDiffHunk, type XenoDiffLine, type XenoDiffLineKind, type XenoDiffModel, type XenoDiffNavigate, type XenoDiffSegment, type XenoDiffStatus, type XenoDiffView, type XenoLogActivate, type XenoLogDelta, type XenoLogFilter, type XenoLogLevel, type XenoLogPatch, type XenoLogRecord, type XenoLogSource, type XenoRun, type XenoRunAction, type XenoRunActivate, type XenoRunDelta, type XenoRunError, type XenoRunFilter, type XenoRunLifecycle, type XenoRunPath, type XenoRunRow, type XenoRunRunRow, type XenoRunStats, type XenoRunStatus, type XenoRunStep, type XenoRunStepAppend, type XenoRunStepPatch, type XenoRunStepRow, type XenoRunTruncation, type XenoTerminalData, type XenoTerminalEmulator, type XenoTerminalId, type XenoTerminalIntent, type XenoTerminalSession, type XenoTerminalStatus, type XenoTerminalTheme, appendStepsAtPath, applyConsoleUi, applyDiffUi, applyRunsUi, canCancel, canRetry, collapseAdjacent, computeStats, consoleManifest, consolePanel, consoleRuntimeComponents, consoleViewManifest, consoleViewState, countChanges, createConsolePanel, createDiffPanel, createRunsPanel, createTerminalPanel, decodeRunPath, diffManifest, diffPanel, diffViewManifest, diffViewState, durationOf, encodeRunPath, flattenHunks, flattenRunRows, formatDuration, formatRecord, isActive, isLive, isSettled, isTerminal, isWaiting, lifecycleOf, matchesSearch as matchesRunSearch, matchesSearch$1 as matchesSearch, measuredSize, meetsLevel, needsAttention, omissionLabel, patchStepAtPath, queueDurationOf, readTerminalTheme, resolveStepPath, runsManifest, runsPanel, runsViewManifest, runsViewState, safeJson, spliceRedactedAppend, splitRows, statusLabel, terminalManifest, terminalPanel, terminalThemeVar, toneOf, truncateLogMessage };
package/dist/ops/index.js CHANGED
@@ -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: { node: { component: "chrome/Text", children: [text3({ 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: /* @__PURE__ */ jsx4(Text3, { children: 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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xenosystem/blocks",
3
- "version": "0.13.18",
3
+ "version": "0.13.20",
4
4
  "description": "XENO Blocks — rung 3 of the front ladder: components composed into a contract-bearing unit (manifest: ports, intents, commands, agent surface), rendered by @xenosystem/workbench. ONE package, one subpath per family (/auth, /edit, /data, /time, /ops, /trust, /agent), one named export per block — XENO PACKAGE NAMING - STANDARD.md.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -64,7 +64,7 @@
64
64
  "peerDependencies": {
65
65
  "@duckdb/duckdb-wasm": ">=1.28.0",
66
66
  "@xenosystem/block-sdk": ">=0.3.4 <1",
67
- "@xenosystem/components": ">=0.26.1 <1",
67
+ "@xenosystem/components": ">=0.28.0 <1",
68
68
  "@xenosystem/data-core": "^0.1.0",
69
69
  "@xenosystem/elements": ">=0.0.1",
70
70
  "@xenosystem/elements-react": ">=0.0.1",
@@ -81,7 +81,7 @@
81
81
  "@types/react": "^19.0.0",
82
82
  "@types/react-dom": "^19.0.0",
83
83
  "@xenosystem/block-sdk": "^0.3.4",
84
- "@xenosystem/components": "^0.26.1",
84
+ "@xenosystem/components": "^0.28.0",
85
85
  "@xenosystem/data-core": "^0.1.0",
86
86
  "@xenosystem/elements": "0.0.1",
87
87
  "@xenosystem/elements-react": "0.0.1",