@ai-matrx/content-ir-react 0.17.1 → 0.18.1

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.
@@ -1,5 +1,5 @@
1
- import { D as DirectiveFailure, a as DirectiveFailureWords, b as DirectiveRecordRef } from './directive-host-BOML4BQ7.js';
2
- export { c as DirectiveApplyResult, d as DirectiveApplyState, e as DirectiveAskRequest, f as DirectiveCopyProps, g as DirectiveHost, h as DirectiveHostProvider, i as DirectiveHostProviderProps, j as DirectiveOpenItemOptions, k as DirectiveRecordProps, l as DirectiveScope, m as DirectiveScopeProvider, n as DirectiveShell, u as useDirectiveHost, o as useDirectiveScope } from './directive-host-BOML4BQ7.js';
1
+ import { D as DirectiveFailure, a as DirectiveFailureWords, b as DirectiveRecordRef } from './directive-host-Bw2_PrgL.js';
2
+ export { c as DirectiveApplyResult, d as DirectiveApplyState, e as DirectiveAskRequest, f as DirectiveCopyProps, g as DirectiveHost, h as DirectiveHostProvider, i as DirectiveHostProviderProps, j as DirectiveOpenItemOptions, k as DirectiveRecordProps, l as DirectiveScope, m as DirectiveScopeProvider, n as DirectiveShell, u as useDirectiveHost, o as useDirectiveScope } from './directive-host-Bw2_PrgL.js';
3
3
  import * as react from 'react';
4
4
  import { ComponentType } from 'react';
5
5
  import { DecodedDirective, DirectiveClass } from '@ai-matrx/content-ir';
@@ -40,14 +40,42 @@ declare function getDirectiveRenderer(directive: Pick<DecodedDirective, "slug" |
40
40
  /** Tests only. */
41
41
  declare function resetDirectiveRenderers(): void;
42
42
 
43
+ /**
44
+ * What the Apply control knows about its block, for the rest of the card:
45
+ * - `pending` — the ledger has not answered yet;
46
+ * - `open` — nothing applied: rows compare against the record as it is now;
47
+ * - `applied` — the change happened: rows draw it from `before` (the server's
48
+ * record of what it replaced), never from a live re-read, which
49
+ * now holds the NEW value.
50
+ */
51
+ type DirectiveApplyPhase = {
52
+ phase: "pending";
53
+ } | {
54
+ phase: "open";
55
+ } | {
56
+ phase: "applied";
57
+ before: ReadonlyArray<Record<string, unknown> | null> | null;
58
+ };
59
+ /** Does this card read the ledger before offering Apply? (One rule, card and control.) */
60
+ declare function readsApplyLedger(host: {
61
+ applyState?: unknown;
62
+ confirm?: unknown;
63
+ }, directive: DecodedDirective): boolean;
43
64
  interface ApplyDirectiveButtonProps {
44
65
  directive: DecodedDirective;
45
66
  label?: string;
46
67
  /** How many things this will write — shown during the wait so a long apply reads as work, not a frozen spinner. */
47
68
  itemCount?: number;
48
69
  onApplied?: () => void;
70
+ /** Told whenever what the control knows about its block changes. */
71
+ onPhase?: (phase: DirectiveApplyPhase) => void;
72
+ /**
73
+ * The block would change nothing (an update that sets no field). Apply stays
74
+ * — validation offers, never blocks — but muted, with the reason beside it.
75
+ */
76
+ nothingToChange?: boolean;
49
77
  }
50
- declare function ApplyDirectiveButton({ directive, label, itemCount, onApplied, }: ApplyDirectiveButtonProps): react.JSX.Element | null;
78
+ declare function ApplyDirectiveButton({ directive, label, itemCount, onApplied, onPhase, nothingToChange, }: ApplyDirectiveButtonProps): react.JSX.Element | null;
51
79
 
52
80
  /**
53
81
  * WHAT A DIRECTIVE ITEM WILL DO TO AN EXISTING RECORD — for the card, the
@@ -61,7 +89,7 @@ declare function ApplyDirectiveButton({ directive, label, itemCount, onApplied,
61
89
  * not the name of the record being changed. Naming the target is the host's
62
90
  * live lookup (`DirectiveHost.renderRecord`); what changes is right here.
63
91
  *
64
- * Law: common-docs/policies/destructive-and-expensive-actions.md — the sentence
92
+ * Law: common-docs/policies/no-dead-ends.md — the sentence
65
93
  * names what changes.
66
94
  */
67
95
  /** Classes whose items point at an EXISTING record by `id`. */
@@ -195,4 +223,4 @@ interface RecordValues {
195
223
  type RecordReader = (ref: DirectiveRecordRef) => Promise<Record<string, unknown> | null>;
196
224
  declare function useRecordValues(read: RecordReader | undefined, ref: DirectiveRecordRef | null): RecordValues;
197
225
 
198
- export { ApplyDirectiveButton, type ApplyDirectiveButtonProps, CHANGE_VALUE_MAX, type ChangeValue, DirectiveChangeList, DirectiveFailure, DirectiveFailureWords, DirectiveFallbackCard, type DirectiveFallbackCardProps, DirectiveProvisionalCard, type DirectiveProvisionalCardProps, DirectiveRecordRef, DirectiveRender, type DirectiveRenderProps, type DirectiveRenderer, type DirectiveRendererProps, type ItemChange, type ItemChangesOptions, type RecordReader, type RecordValues, type RecordValuesStatus, SideEffectDirectiveCard, type SideEffectDirectiveCardProps, TARGETING_CLASSES, defaultFailureWords, describeChangeValue, failureWords, fieldLabel, getDirectiveRenderer, itemChanges, itemRecordId, provisionalDirectiveSlug, recordFallbackName, registerDirectiveRenderer, resetDirectiveRenderers, useRecordValues };
226
+ export { ApplyDirectiveButton, type ApplyDirectiveButtonProps, CHANGE_VALUE_MAX, type ChangeValue, type DirectiveApplyPhase, DirectiveChangeList, DirectiveFailure, DirectiveFailureWords, DirectiveFallbackCard, type DirectiveFallbackCardProps, DirectiveProvisionalCard, type DirectiveProvisionalCardProps, DirectiveRecordRef, DirectiveRender, type DirectiveRenderProps, type DirectiveRenderer, type DirectiveRendererProps, type ItemChange, type ItemChangesOptions, type RecordReader, type RecordValues, type RecordValuesStatus, SideEffectDirectiveCard, type SideEffectDirectiveCardProps, TARGETING_CLASSES, defaultFailureWords, describeChangeValue, failureWords, fieldLabel, getDirectiveRenderer, itemChanges, itemRecordId, provisionalDirectiveSlug, readsApplyLedger, recordFallbackName, registerDirectiveRenderer, resetDirectiveRenderers, useRecordValues };
@@ -44,7 +44,7 @@ function resetDirectiveRenderers() {
44
44
  }
45
45
 
46
46
  // directives/ApplyDirectiveButton.tsx
47
- import { useEffect, useRef, useState } from "react";
47
+ import { useEffect, useRef, useState, useSyncExternalStore } from "react";
48
48
  import { nounLabel } from "@ai-matrx/content-ir";
49
49
 
50
50
  // directives/item-changes.ts
@@ -242,19 +242,52 @@ function classIcon(directiveClass, noun) {
242
242
 
243
243
  // directives/ApplyDirectiveButton.tsx
244
244
  import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
245
+ var applyOutcomes = /* @__PURE__ */ new WeakMap();
246
+ var EMPTY_AUTHORITY = {};
247
+ var outcomeListeners = /* @__PURE__ */ new Set();
248
+ function outcomesOf(host) {
249
+ let held = applyOutcomes.get(host);
250
+ if (!held) {
251
+ held = /* @__PURE__ */ new Map();
252
+ applyOutcomes.set(host, held);
253
+ }
254
+ return held;
255
+ }
256
+ function publishOutcome(host, identity, outcome) {
257
+ outcomesOf(host).set(identity, outcome);
258
+ for (const listener of outcomeListeners) listener();
259
+ }
260
+ function subscribeOutcomes(listener) {
261
+ outcomeListeners.add(listener);
262
+ return () => {
263
+ outcomeListeners.delete(listener);
264
+ };
265
+ }
266
+ function useApplyOutcome(host, identity) {
267
+ return useSyncExternalStore(
268
+ subscribeOutcomes,
269
+ () => applyOutcomes.get(host)?.get(identity) ?? null,
270
+ () => null
271
+ );
272
+ }
245
273
  var FINISH_POLL_MS = 2e3;
246
274
  var FINISH_POLLS_MAX = 30;
275
+ function readsApplyLedger(host, directive) {
276
+ return !!host.applyState && !!host.confirm && directive.parsed.executes && directive.items.length > 0;
277
+ }
247
278
  function ApplyDirectiveButton({
248
279
  directive,
249
280
  label = "Apply",
250
281
  itemCount,
251
- onApplied
282
+ onApplied,
283
+ onPhase,
284
+ nothingToChange = false
252
285
  }) {
253
286
  const host = useDirectiveHost();
254
287
  const scope = useDirectiveScope();
255
288
  const conversationId = scope.conversationId || void 0;
256
289
  const items = directive.items;
257
- const readsLedger = !!host.applyState && !!host.confirm && directive.parsed.executes && items.length > 0;
290
+ const readsLedger = readsApplyLedger(host, directive);
258
291
  const initial = readsLedger ? { status: "checking" } : { status: "idle" };
259
292
  const [state, setState] = useState(initial);
260
293
  const [elapsed, setElapsed] = useState(0);
@@ -268,6 +301,8 @@ function ApplyDirectiveButton({
268
301
  setAskingAgain(false);
269
302
  setElapsed(0);
270
303
  }
304
+ const shared = useApplyOutcome(host.confirm ?? EMPTY_AUTHORITY, identity);
305
+ const view = state.status === "asking" || !shared ? state : shared;
271
306
  const currentIdentity = useRef(identity);
272
307
  useEffect(() => {
273
308
  currentIdentity.current = identity;
@@ -295,7 +330,8 @@ function ApplyDirectiveButton({
295
330
  failed: 0,
296
331
  message: answer.message?.trim() ? answer.message.trim() : null,
297
332
  records: answer.records ?? [],
298
- copies: Math.max(1, answer.copies ?? 1)
333
+ copies: Math.max(1, answer.copies ?? 1),
334
+ before: answer.before ?? null
299
335
  } : prev
300
336
  );
301
337
  } else if (answer.state === "in_flight" && polls < FINISH_POLLS_MAX) {
@@ -320,24 +356,34 @@ function ApplyDirectiveButton({
320
356
  if (timer) clearTimeout(timer);
321
357
  };
322
358
  }, [shellKey, read, report]);
359
+ const appliedClean = view.status === "applied" && view.failed === 0 ? view : null;
360
+ const appliedBefore = appliedClean ? appliedClean.before : null;
361
+ const phaseKey = view.status === "checking" || view.status === "finishing" ? "pending" : appliedClean ? `applied:${JSON.stringify(appliedBefore)}` : "open";
362
+ useEffect(() => {
363
+ if (!onPhase) return;
364
+ if (phaseKey === "pending") onPhase({ phase: "pending" });
365
+ else if (phaseKey === "open") onPhase({ phase: "open" });
366
+ else onPhase({ phase: "applied", before: appliedBefore });
367
+ }, [phaseKey, onPhase, appliedBefore]);
368
+ const applyingSince = view.status === "applying" ? view.startedAt : null;
323
369
  useEffect(() => {
324
- if (state.status !== "applying") return;
325
- startedAt.current = Date.now();
370
+ if (applyingSince === null) return;
371
+ startedAt.current = applyingSince;
326
372
  const id = setInterval(
327
373
  () => setElapsed(Math.round((Date.now() - (startedAt.current ?? 0)) / 1e3)),
328
374
  1e3
329
375
  );
330
376
  return () => clearInterval(id);
331
- }, [state.status]);
377
+ }, [applyingSince]);
332
378
  if (!directive.parsed.executes) return null;
333
379
  const confirm = host.confirm;
334
380
  if (!confirm) return null;
335
381
  async function handleApply(again = false) {
336
382
  if (!confirm) return;
337
- const before = state;
383
+ const prior = view;
338
384
  const askedFor = identity;
339
385
  const stillHere = () => currentIdentity.current === askedFor;
340
- const priorCopies = state.status === "applied" ? state.copies : 0;
386
+ const priorCopies = view.status === "applied" ? view.copies : 0;
341
387
  if (host.ask) {
342
388
  if (again) setAskingAgain(true);
343
389
  else setState({ status: "asking" });
@@ -361,41 +407,43 @@ function ApplyDirectiveButton({
361
407
  if (!stillHere()) return;
362
408
  setAskingAgain(false);
363
409
  if (!ok) {
364
- setState(again ? before : { status: "idle" });
410
+ setState(again ? prior : { status: "idle" });
365
411
  return;
366
412
  }
367
413
  }
368
414
  setElapsed(0);
369
- setState({ status: "applying" });
415
+ setState((prev) => prev.status === "asking" ? { status: "idle" } : prev);
416
+ publishOutcome(confirm, askedFor, { status: "applying", startedAt: Date.now() });
370
417
  try {
371
418
  const shell = { __kind: directive.slug, items };
372
419
  if (conversationId) shell.conversationId = conversationId;
373
420
  if (again) shell.force = true;
374
421
  const result = await confirm(shell);
375
- if (!stillHere()) return;
376
- setState({
422
+ publishOutcome(confirm, askedFor, {
377
423
  status: "applied",
378
424
  applied: result.applied,
379
425
  failed: result.failed,
380
426
  message: result.message?.trim() ? result.message.trim() : null,
381
427
  records: result.records ?? [],
382
428
  // "Run again" made one more; a first apply (or a replay of one) is one.
383
- copies: again ? Math.max(1, priorCopies) + 1 : Math.max(1, priorCopies)
429
+ copies: again ? Math.max(1, priorCopies) + 1 : Math.max(1, priorCopies),
430
+ // "Run again" wrote the same values over themselves; what the FIRST
431
+ // apply replaced is still the change this block made.
432
+ before: again && prior.status === "applied" && prior.before ? prior.before : result.before ?? null
384
433
  });
385
- onApplied?.();
434
+ if (stillHere()) onApplied?.();
386
435
  } catch (error) {
387
- if (!stillHere()) return;
388
- setState({
436
+ publishOutcome(confirm, askedFor, {
389
437
  status: "error",
390
438
  message: error instanceof Error && error.message ? error.message : "That couldn't be applied just now. Please try again."
391
439
  });
392
440
  }
393
441
  }
394
- if (state.status === "applied") {
395
- const bad = state.failed > 0;
442
+ if (view.status === "applied") {
443
+ const bad = view.failed > 0;
396
444
  const trashed = directive.directiveClass === "delete";
397
445
  const label2 = nounText;
398
- const records = host.renderRecord ? state.records : [];
446
+ const records = host.renderRecord ? view.records : [];
399
447
  return /* @__PURE__ */ jsxs2(
400
448
  "span",
401
449
  {
@@ -409,26 +457,26 @@ function ApplyDirectiveButton({
409
457
  {
410
458
  words: failureWords(
411
459
  {
412
- raw: state.message ?? `${state.failed} failed`,
460
+ raw: view.message ?? `${view.failed} failed`,
413
461
  directiveClass: directive.directiveClass,
414
462
  noun: directive.noun,
415
463
  nounLabel: nounText,
416
- failed: state.failed,
417
- total: state.applied + state.failed
464
+ failed: view.failed,
465
+ total: view.applied + view.failed
418
466
  },
419
467
  host.explainFailure
420
468
  ),
421
- raw: state.message
469
+ raw: view.message
422
470
  }
423
- ) : /* @__PURE__ */ jsx3("span", { className: "min-w-0", children: state.message ?? `Applied ${state.applied}` })
471
+ ) : /* @__PURE__ */ jsx3("span", { className: "min-w-0", children: view.message ?? `Applied ${view.applied}` })
424
472
  ] }),
425
- state.copies > 1 ? /* @__PURE__ */ jsx3(
473
+ view.copies > 1 ? /* @__PURE__ */ jsx3(
426
474
  "span",
427
475
  {
428
476
  className: "shrink-0 text-muted-foreground",
429
- "data-apply-copies": state.copies,
477
+ "data-apply-copies": view.copies,
430
478
  title: records.length > 0 ? directive.directiveClass === "create" ? "Links to the latest copy." : "Links to the latest run." : void 0,
431
- children: directive.directiveClass === "create" ? `${state.copies} copies` : `Ran ${state.copies} times`
479
+ children: directive.directiveClass === "create" ? `${view.copies} copies` : `Ran ${view.copies} times`
432
480
  }
433
481
  ) : null,
434
482
  records.map((record) => /* @__PURE__ */ jsx3("span", { className: "inline-flex min-w-0 items-center", children: host.renderRecord?.({
@@ -455,7 +503,7 @@ function ApplyDirectiveButton({
455
503
  }
456
504
  );
457
505
  }
458
- if (state.status === "checking") {
506
+ if (view.status === "checking") {
459
507
  return /* @__PURE__ */ jsx3(
460
508
  "span",
461
509
  {
@@ -467,7 +515,7 @@ function ApplyDirectiveButton({
467
515
  }
468
516
  );
469
517
  }
470
- if (state.status === "finishing") {
518
+ if (view.status === "finishing") {
471
519
  return /* @__PURE__ */ jsxs2(
472
520
  "span",
473
521
  {
@@ -482,22 +530,25 @@ function ApplyDirectiveButton({
482
530
  );
483
531
  }
484
532
  const destructive = directive.directiveClass === "delete";
533
+ const muted = nothingToChange && view.status === "idle";
485
534
  return /* @__PURE__ */ jsxs2("span", { className: "inline-flex items-center gap-2", children: [
535
+ muted ? /* @__PURE__ */ jsx3("span", { className: "text-xs text-muted-foreground", "data-apply-nothing": "", children: "Nothing to change" }) : null,
486
536
  /* @__PURE__ */ jsxs2(
487
537
  "button",
488
538
  {
489
539
  type: "button",
490
540
  onClick: () => void handleApply(),
491
- disabled: state.status === "applying" || state.status === "asking" || items.length === 0,
492
- "data-apply-tone": destructive ? "destructive" : "default",
493
- className: `inline-flex items-center gap-1 rounded-md border px-2 py-1 text-xs font-medium disabled:opacity-50 ${destructive ? "border-destructive/50 bg-background text-destructive hover:bg-destructive/10" : "border-border bg-background hover:bg-accent"}`,
541
+ disabled: view.status === "applying" || view.status === "asking" || items.length === 0,
542
+ "data-apply-tone": muted ? "muted" : destructive ? "destructive" : "default",
543
+ title: muted ? "It sets no fields, so applying it changes nothing." : void 0,
544
+ className: `inline-flex items-center gap-1 rounded-md border px-2 py-1 text-xs font-medium disabled:opacity-50 ${muted ? "border-border/60 bg-background text-muted-foreground hover:bg-accent" : destructive ? "border-destructive/50 bg-background text-destructive hover:bg-destructive/10" : "border-border bg-background hover:bg-accent"}`,
494
545
  children: [
495
- state.status === "applying" ? /* @__PURE__ */ jsx3(LoaderIcon, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx3(PlayIcon, { className: "h-3.5 w-3.5" }),
496
- state.status === "applying" ? `Applying${itemCount ? ` ${itemCount} ${itemCount === 1 ? "item" : "items"}` : ""}\u2026${elapsed ? ` ${elapsed}s` : ""}` : label
546
+ view.status === "applying" ? /* @__PURE__ */ jsx3(LoaderIcon, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx3(PlayIcon, { className: "h-3.5 w-3.5" }),
547
+ view.status === "applying" ? `Applying${itemCount ? ` ${itemCount} ${itemCount === 1 ? "item" : "items"}` : ""}\u2026${elapsed ? ` ${elapsed}s` : ""}` : label
497
548
  ]
498
549
  }
499
550
  ),
500
- state.status === "error" ? /* @__PURE__ */ jsxs2(
551
+ view.status === "error" ? /* @__PURE__ */ jsxs2(
501
552
  "span",
502
553
  {
503
554
  className: "inline-flex max-w-sm items-start gap-1 text-xs text-destructive",
@@ -509,14 +560,14 @@ function ApplyDirectiveButton({
509
560
  {
510
561
  words: failureWords(
511
562
  {
512
- raw: state.message,
563
+ raw: view.message,
513
564
  directiveClass: directive.directiveClass,
514
565
  noun: directive.noun,
515
566
  nounLabel: nounText
516
567
  },
517
568
  host.explainFailure
518
569
  ),
519
- raw: state.message
570
+ raw: view.message
520
571
  }
521
572
  )
522
573
  ]
@@ -608,12 +659,13 @@ function ItemRow({
608
659
  directive,
609
660
  item,
610
661
  index,
611
- total
662
+ total,
663
+ phase
612
664
  }) {
613
665
  const host = useDirectiveHost();
614
666
  const recordId = TARGETING_CLASSES.has(directive.directiveClass) ? itemRecordId(item) : null;
615
667
  if (recordId) {
616
- return /* @__PURE__ */ jsx4(TargetRow, { directive, item, recordId });
668
+ return /* @__PURE__ */ jsx4(TargetRow, { directive, item, recordId, index, phase });
617
669
  }
618
670
  const titleColumn = nounTitleColumn(directive.noun, host.nouns);
619
671
  const title = itemTitle(item, titleColumn, index, total);
@@ -700,16 +752,20 @@ function DirectiveChangeList({
700
752
  function TargetRow({
701
753
  directive,
702
754
  item,
703
- recordId
755
+ recordId,
756
+ index,
757
+ phase
704
758
  }) {
705
759
  const host = useDirectiveHost();
706
760
  const fallback = recordFallbackName(nounLabel2(directive.noun, host.nouns), recordId);
707
761
  const isUpdate = directive.directiveClass === "update";
708
- const current = useRecordValues(
709
- isUpdate ? host.readRecord : void 0,
710
- isUpdate ? { noun: directive.noun, id: recordId } : null
762
+ const readsLive = isUpdate && phase.phase === "open";
763
+ const live = useRecordValues(
764
+ readsLive ? host.readRecord : void 0,
765
+ readsLive ? { noun: directive.noun, id: recordId } : null
711
766
  );
712
- const changes = isUpdate ? itemChanges(item, current.values, { titleColumn: nounTitleColumn(directive.noun, host.nouns) }) : [];
767
+ const compareTo = phase.phase === "applied" ? phase.before?.[index] ?? null : readsLive ? live.values : null;
768
+ const changes = isUpdate ? itemChanges(item, compareTo, { titleColumn: nounTitleColumn(directive.noun, host.nouns) }) : [];
713
769
  const name = host.renderRecord ? host.renderRecord({ noun: directive.noun, id: recordId, fallback, context: "row" }) : /* @__PURE__ */ jsx4("span", { className: "truncate font-medium text-foreground", children: fallback });
714
770
  return /* @__PURE__ */ jsxs3("div", { className: "py-1 text-xs", "data-directive-target": recordId, children: [
715
771
  /* @__PURE__ */ jsxs3("div", { className: "flex min-w-0 items-center gap-2", children: [
@@ -726,6 +782,10 @@ function SideEffectDirectiveCard({ directive }) {
726
782
  const total = items.length;
727
783
  const display = directiveDisplay(directive.directiveClass, directive.noun, host.nouns);
728
784
  const Icon = classIcon(directive.directiveClass, directive.noun);
785
+ const [phase, setPhase] = useState3(
786
+ () => readsApplyLedger(host, directive) ? { phase: "pending" } : { phase: "open" }
787
+ );
788
+ const nothingToChange = directive.directiveClass === "update" && total > 0 && items.every((item) => itemChanges(item).length === 0);
729
789
  const shown = expanded ? items : items.slice(0, ROWS_BEFORE_FOLD);
730
790
  const hidden = total - shown.length;
731
791
  return /* @__PURE__ */ jsxs3("div", { className: "my-3 rounded-md border border-border bg-card px-2.5 py-1.5", "data-directive": directive.slug, children: [
@@ -740,13 +800,21 @@ function SideEffectDirectiveCard({ directive }) {
740
800
  ] }),
741
801
  /* @__PURE__ */ jsxs3("div", { className: "ml-auto flex items-center gap-1", children: [
742
802
  host.renderCopy?.({ label: display.title, value: directive.shell, size: "icon" }),
743
- /* @__PURE__ */ jsx4(ApplyDirectiveButton, { directive, itemCount: total })
803
+ /* @__PURE__ */ jsx4(
804
+ ApplyDirectiveButton,
805
+ {
806
+ directive,
807
+ itemCount: total,
808
+ onPhase: setPhase,
809
+ nothingToChange
810
+ }
811
+ )
744
812
  ] })
745
813
  ] }),
746
814
  total === 0 ? (
747
815
  // Stated, never silent: an empty batch is a real (and suspicious) state.
748
816
  /* @__PURE__ */ jsx4("p", { className: "mt-1 text-xs text-muted-foreground", children: "No items \u2014 nothing would be written." })
749
- ) : /* @__PURE__ */ jsx4("div", { className: "mt-0.5 divide-y divide-border/60", children: shown.map((item, i) => /* @__PURE__ */ jsx4(ItemRow, { directive, item, index: i, total }, i)) }),
817
+ ) : /* @__PURE__ */ jsx4("div", { className: "mt-0.5 divide-y divide-border/60", children: shown.map((item, i) => /* @__PURE__ */ jsx4(ItemRow, { directive, item, index: i, total, phase }, i)) }),
750
818
  hidden > 0 || expanded ? /* @__PURE__ */ jsxs3(
751
819
  "button",
752
820
  {
@@ -889,6 +957,7 @@ export {
889
957
  itemChanges,
890
958
  itemRecordId,
891
959
  provisionalDirectiveSlug,
960
+ readsApplyLedger,
892
961
  recordFallbackName,
893
962
  registerDirectiveRenderer,
894
963
  resetDirectiveRenderers,