@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.
package/dist/index.d.cts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { C as ComponentResolutionSource, a as ComponentResolution, b as ContentIrErrorReporter } from './component-resolution.types-DY4pGymy.cjs';
2
2
  export { c as ComponentRole, d as ContentIrErrorReport, J as JsonObject, K as KindComponentRow, S as SystemComponentEntry, e as consoleErrorReporter } from './component-resolution.types-DY4pGymy.cjs';
3
- import { C as ContentIrHost, K as KindRouteEnv, a as KindDefinitionSource, D as DirectiveFailure, b as DirectiveFailureWords, c as DirectiveRecordRef } from './route-D2Yz3El4.cjs';
4
- export { d as ContentIrRenderSeams, e as DB_KIND_COMPONENT_KEY, f as DIRECTIVE_BLOCK_TYPE, g as DirectiveApplyResult, h as DirectiveApplyState, i as DirectiveAskRequest, j as DirectiveCopyProps, k as DirectiveHost, l as DirectiveHostProvider, m as DirectiveHostProviderProps, n as DirectiveOpenItemOptions, o as DirectiveRecordProps, p as DirectiveScope, q as DirectiveScopeProvider, r as DirectiveShell, G as GENERIC_STRUCTURED_COMPONENT_KEY, s as GenericFallbackReason, I as IR_PROVISIONAL_KEY, t as IR_ROUTE_KEY, u as IrRenderBlock, v as IrRoutableBlock, w as IrRouteMarker, x as KindRouteBinding, y as KindRouteOptions, P as PartialRenderOptions, z as ProvisionalKindRender, S as StructuredValueRenderProps, A as SupersededKindRender, B as applyIrKindRoute, E as createKindRouteBinding, F as envelopeFromPartialKind, H as isPartialReadyKind, J as isProvisionalBlock, L as kindServerDataFromStoredValue, M as markKindPartialUnsafe, N as readIrRouteMarker, O as resetPartialUnsafeKinds, Q as resetSourcelessDbRowReports, R as resolveAnnouncedKindLoading, T as resolveProvisionalKindRender, U as resolveSupersededKindRender, V as routeEnvOf, W as useDirectiveHost, X as useDirectiveScope } from './route-D2Yz3El4.cjs';
3
+ import { C as ContentIrHost, K as KindRouteEnv, a as KindDefinitionSource, D as DirectiveFailure, b as DirectiveFailureWords, c as DirectiveRecordRef } from './route-KoRMp3Qn.cjs';
4
+ export { d as ContentIrRenderSeams, e as DB_KIND_COMPONENT_KEY, f as DIRECTIVE_BLOCK_TYPE, g as DirectiveApplyResult, h as DirectiveApplyState, i as DirectiveAskRequest, j as DirectiveCopyProps, k as DirectiveHost, l as DirectiveHostProvider, m as DirectiveHostProviderProps, n as DirectiveOpenItemOptions, o as DirectiveRecordProps, p as DirectiveScope, q as DirectiveScopeProvider, r as DirectiveShell, G as GENERIC_STRUCTURED_COMPONENT_KEY, s as GenericFallbackReason, I as IR_PROVISIONAL_KEY, t as IR_ROUTE_KEY, u as IrRenderBlock, v as IrRoutableBlock, w as IrRouteMarker, x as KindRouteBinding, y as KindRouteOptions, P as PartialRenderOptions, z as ProvisionalKindRender, S as StructuredValueRenderProps, A as SupersededKindRender, B as applyIrKindRoute, E as createKindRouteBinding, F as envelopeFromPartialKind, H as isPartialReadyKind, J as isProvisionalBlock, L as kindServerDataFromStoredValue, M as markKindPartialUnsafe, N as readIrRouteMarker, O as resetPartialUnsafeKinds, Q as resetSourcelessDbRowReports, R as resolveAnnouncedKindLoading, T as resolveProvisionalKindRender, U as resolveSupersededKindRender, V as routeEnvOf, W as useDirectiveHost, X as useDirectiveScope } from './route-KoRMp3Qn.cjs';
5
5
  import * as react from 'react';
6
6
  import react__default, { ReactNode, ComponentType } from 'react';
7
7
  export { ComponentResolver, ComponentResolverOptions } from './resolver.cjs';
@@ -247,14 +247,42 @@ declare function getDirectiveRenderer(directive: Pick<DecodedDirective, "slug" |
247
247
  /** Tests only. */
248
248
  declare function resetDirectiveRenderers(): void;
249
249
 
250
+ /**
251
+ * What the Apply control knows about its block, for the rest of the card:
252
+ * - `pending` — the ledger has not answered yet;
253
+ * - `open` — nothing applied: rows compare against the record as it is now;
254
+ * - `applied` — the change happened: rows draw it from `before` (the server's
255
+ * record of what it replaced), never from a live re-read, which
256
+ * now holds the NEW value.
257
+ */
258
+ type DirectiveApplyPhase = {
259
+ phase: "pending";
260
+ } | {
261
+ phase: "open";
262
+ } | {
263
+ phase: "applied";
264
+ before: ReadonlyArray<Record<string, unknown> | null> | null;
265
+ };
266
+ /** Does this card read the ledger before offering Apply? (One rule, card and control.) */
267
+ declare function readsApplyLedger(host: {
268
+ applyState?: unknown;
269
+ confirm?: unknown;
270
+ }, directive: DecodedDirective): boolean;
250
271
  interface ApplyDirectiveButtonProps {
251
272
  directive: DecodedDirective;
252
273
  label?: string;
253
274
  /** How many things this will write — shown during the wait so a long apply reads as work, not a frozen spinner. */
254
275
  itemCount?: number;
255
276
  onApplied?: () => void;
277
+ /** Told whenever what the control knows about its block changes. */
278
+ onPhase?: (phase: DirectiveApplyPhase) => void;
279
+ /**
280
+ * The block would change nothing (an update that sets no field). Apply stays
281
+ * — validation offers, never blocks — but muted, with the reason beside it.
282
+ */
283
+ nothingToChange?: boolean;
256
284
  }
257
- declare function ApplyDirectiveButton({ directive, label, itemCount, onApplied, }: ApplyDirectiveButtonProps): react.JSX.Element | null;
285
+ declare function ApplyDirectiveButton({ directive, label, itemCount, onApplied, onPhase, nothingToChange, }: ApplyDirectiveButtonProps): react.JSX.Element | null;
258
286
 
259
287
  /**
260
288
  * WHAT A DIRECTIVE ITEM WILL DO TO AN EXISTING RECORD — for the card, the
@@ -268,7 +296,7 @@ declare function ApplyDirectiveButton({ directive, label, itemCount, onApplied,
268
296
  * not the name of the record being changed. Naming the target is the host's
269
297
  * live lookup (`DirectiveHost.renderRecord`); what changes is right here.
270
298
  *
271
- * Law: common-docs/policies/destructive-and-expensive-actions.md — the sentence
299
+ * Law: common-docs/policies/no-dead-ends.md — the sentence
272
300
  * names what changes.
273
301
  */
274
302
  /** Classes whose items point at an EXISTING record by `id`. */
@@ -665,4 +693,4 @@ declare class DbKindComponentErrorBoundary extends react.Component<DbKindCompone
665
693
  render(): react.ReactNode;
666
694
  }
667
695
 
668
- export { ApplyDirectiveButton, type ApplyDirectiveButtonProps, CHANGE_VALUE_MAX, type ChangeValue, type CompiledDbKindComponent, ComponentResolution, ComponentResolutionSource, ContentIrErrorReporter, ContentIrHost, ContentIrRenderProvider, type ContentIrRenderProviderProps, type DbKindActionResult, type DbKindCompileResult, type DbKindComponentCache, type DbKindComponentCachePorts, type DbKindComponentCompiler, DbKindComponentErrorBoundary, type DbKindComponentErrorBoundaryProps, type DbKindComponentIncident, type DbKindComponentIncidentSink, type DbKindComponentIncidentType, type DbKindComponentRenderProps, DelegatedOutput, type DelegatedOutputProps, DirectiveChangeList, DirectiveFailure, DirectiveFailureWords, DirectiveFallbackCard, type DirectiveFallbackCardProps, DirectiveProvisionalCard, type DirectiveProvisionalCardProps, DirectiveRecordRef, DirectiveRender, type DirectiveRenderProps, type DirectiveRenderer, type DirectiveRendererProps, GenericStructuredView, type GenericStructuredViewProps, type ItemChange, type ItemChangesOptions, type KindComponentUiOptions, KindDefinitionSource, KindInstanceRender, type KindInstanceRenderProps, KindRouteEnv, type KindVersionSources, NodeOutcomeView, type NodeOutcomeViewProps, ProvisionalKindBoundary, ProvisionalKindFrame, type RecordReader, type RecordValues, type RecordValuesStatus, type ResolveKindValue, type RunDbKindAction, RunResultView, type RunResultViewProps, SideEffectDirectiveCard, type SideEffectDirectiveCardProps, TARGETING_CLASSES, createDbKindComponentCache, defaultFailureWords, describeChangeValue, failureWords, fieldLabel, getDirectiveRenderer, isDbKindComponentBodyPending, isRecordValue, itemChanges, itemRecordId, kindIsRoutable, provisionalDirectiveSlug, recordFallbackName, registerDirectiveRenderer, resetDirectiveRenderers, useContentIrHost, useContentIrHostOrNull, useContentIrKindVersion, useKindRouteEnv, useRecordValues };
696
+ export { ApplyDirectiveButton, type ApplyDirectiveButtonProps, CHANGE_VALUE_MAX, type ChangeValue, type CompiledDbKindComponent, ComponentResolution, ComponentResolutionSource, ContentIrErrorReporter, ContentIrHost, ContentIrRenderProvider, type ContentIrRenderProviderProps, type DbKindActionResult, type DbKindCompileResult, type DbKindComponentCache, type DbKindComponentCachePorts, type DbKindComponentCompiler, DbKindComponentErrorBoundary, type DbKindComponentErrorBoundaryProps, type DbKindComponentIncident, type DbKindComponentIncidentSink, type DbKindComponentIncidentType, type DbKindComponentRenderProps, DelegatedOutput, type DelegatedOutputProps, type DirectiveApplyPhase, DirectiveChangeList, DirectiveFailure, DirectiveFailureWords, DirectiveFallbackCard, type DirectiveFallbackCardProps, DirectiveProvisionalCard, type DirectiveProvisionalCardProps, DirectiveRecordRef, DirectiveRender, type DirectiveRenderProps, type DirectiveRenderer, type DirectiveRendererProps, GenericStructuredView, type GenericStructuredViewProps, type ItemChange, type ItemChangesOptions, type KindComponentUiOptions, KindDefinitionSource, KindInstanceRender, type KindInstanceRenderProps, KindRouteEnv, type KindVersionSources, NodeOutcomeView, type NodeOutcomeViewProps, ProvisionalKindBoundary, ProvisionalKindFrame, type RecordReader, type RecordValues, type RecordValuesStatus, type ResolveKindValue, type RunDbKindAction, RunResultView, type RunResultViewProps, SideEffectDirectiveCard, type SideEffectDirectiveCardProps, TARGETING_CLASSES, createDbKindComponentCache, defaultFailureWords, describeChangeValue, failureWords, fieldLabel, getDirectiveRenderer, isDbKindComponentBodyPending, isRecordValue, itemChanges, itemRecordId, kindIsRoutable, provisionalDirectiveSlug, readsApplyLedger, recordFallbackName, registerDirectiveRenderer, resetDirectiveRenderers, useContentIrHost, useContentIrHostOrNull, useContentIrKindVersion, useKindRouteEnv, useRecordValues };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { C as ComponentResolutionSource, a as ComponentResolution, b as ContentIrErrorReporter } from './component-resolution.types-DY4pGymy.js';
2
2
  export { c as ComponentRole, d as ContentIrErrorReport, J as JsonObject, K as KindComponentRow, S as SystemComponentEntry, e as consoleErrorReporter } from './component-resolution.types-DY4pGymy.js';
3
- import { C as ContentIrHost, K as KindRouteEnv, a as KindDefinitionSource, D as DirectiveFailure, b as DirectiveFailureWords, c as DirectiveRecordRef } from './route-D0f5H63n.js';
4
- export { d as ContentIrRenderSeams, e as DB_KIND_COMPONENT_KEY, f as DIRECTIVE_BLOCK_TYPE, g as DirectiveApplyResult, h as DirectiveApplyState, i as DirectiveAskRequest, j as DirectiveCopyProps, k as DirectiveHost, l as DirectiveHostProvider, m as DirectiveHostProviderProps, n as DirectiveOpenItemOptions, o as DirectiveRecordProps, p as DirectiveScope, q as DirectiveScopeProvider, r as DirectiveShell, G as GENERIC_STRUCTURED_COMPONENT_KEY, s as GenericFallbackReason, I as IR_PROVISIONAL_KEY, t as IR_ROUTE_KEY, u as IrRenderBlock, v as IrRoutableBlock, w as IrRouteMarker, x as KindRouteBinding, y as KindRouteOptions, P as PartialRenderOptions, z as ProvisionalKindRender, S as StructuredValueRenderProps, A as SupersededKindRender, B as applyIrKindRoute, E as createKindRouteBinding, F as envelopeFromPartialKind, H as isPartialReadyKind, J as isProvisionalBlock, L as kindServerDataFromStoredValue, M as markKindPartialUnsafe, N as readIrRouteMarker, O as resetPartialUnsafeKinds, Q as resetSourcelessDbRowReports, R as resolveAnnouncedKindLoading, T as resolveProvisionalKindRender, U as resolveSupersededKindRender, V as routeEnvOf, W as useDirectiveHost, X as useDirectiveScope } from './route-D0f5H63n.js';
3
+ import { C as ContentIrHost, K as KindRouteEnv, a as KindDefinitionSource, D as DirectiveFailure, b as DirectiveFailureWords, c as DirectiveRecordRef } from './route-CtYw2cTN.js';
4
+ export { d as ContentIrRenderSeams, e as DB_KIND_COMPONENT_KEY, f as DIRECTIVE_BLOCK_TYPE, g as DirectiveApplyResult, h as DirectiveApplyState, i as DirectiveAskRequest, j as DirectiveCopyProps, k as DirectiveHost, l as DirectiveHostProvider, m as DirectiveHostProviderProps, n as DirectiveOpenItemOptions, o as DirectiveRecordProps, p as DirectiveScope, q as DirectiveScopeProvider, r as DirectiveShell, G as GENERIC_STRUCTURED_COMPONENT_KEY, s as GenericFallbackReason, I as IR_PROVISIONAL_KEY, t as IR_ROUTE_KEY, u as IrRenderBlock, v as IrRoutableBlock, w as IrRouteMarker, x as KindRouteBinding, y as KindRouteOptions, P as PartialRenderOptions, z as ProvisionalKindRender, S as StructuredValueRenderProps, A as SupersededKindRender, B as applyIrKindRoute, E as createKindRouteBinding, F as envelopeFromPartialKind, H as isPartialReadyKind, J as isProvisionalBlock, L as kindServerDataFromStoredValue, M as markKindPartialUnsafe, N as readIrRouteMarker, O as resetPartialUnsafeKinds, Q as resetSourcelessDbRowReports, R as resolveAnnouncedKindLoading, T as resolveProvisionalKindRender, U as resolveSupersededKindRender, V as routeEnvOf, W as useDirectiveHost, X as useDirectiveScope } from './route-CtYw2cTN.js';
5
5
  import * as react from 'react';
6
6
  import react__default, { ReactNode, ComponentType } from 'react';
7
7
  export { ComponentResolver, ComponentResolverOptions } from './resolver.js';
@@ -247,14 +247,42 @@ declare function getDirectiveRenderer(directive: Pick<DecodedDirective, "slug" |
247
247
  /** Tests only. */
248
248
  declare function resetDirectiveRenderers(): void;
249
249
 
250
+ /**
251
+ * What the Apply control knows about its block, for the rest of the card:
252
+ * - `pending` — the ledger has not answered yet;
253
+ * - `open` — nothing applied: rows compare against the record as it is now;
254
+ * - `applied` — the change happened: rows draw it from `before` (the server's
255
+ * record of what it replaced), never from a live re-read, which
256
+ * now holds the NEW value.
257
+ */
258
+ type DirectiveApplyPhase = {
259
+ phase: "pending";
260
+ } | {
261
+ phase: "open";
262
+ } | {
263
+ phase: "applied";
264
+ before: ReadonlyArray<Record<string, unknown> | null> | null;
265
+ };
266
+ /** Does this card read the ledger before offering Apply? (One rule, card and control.) */
267
+ declare function readsApplyLedger(host: {
268
+ applyState?: unknown;
269
+ confirm?: unknown;
270
+ }, directive: DecodedDirective): boolean;
250
271
  interface ApplyDirectiveButtonProps {
251
272
  directive: DecodedDirective;
252
273
  label?: string;
253
274
  /** How many things this will write — shown during the wait so a long apply reads as work, not a frozen spinner. */
254
275
  itemCount?: number;
255
276
  onApplied?: () => void;
277
+ /** Told whenever what the control knows about its block changes. */
278
+ onPhase?: (phase: DirectiveApplyPhase) => void;
279
+ /**
280
+ * The block would change nothing (an update that sets no field). Apply stays
281
+ * — validation offers, never blocks — but muted, with the reason beside it.
282
+ */
283
+ nothingToChange?: boolean;
256
284
  }
257
- declare function ApplyDirectiveButton({ directive, label, itemCount, onApplied, }: ApplyDirectiveButtonProps): react.JSX.Element | null;
285
+ declare function ApplyDirectiveButton({ directive, label, itemCount, onApplied, onPhase, nothingToChange, }: ApplyDirectiveButtonProps): react.JSX.Element | null;
258
286
 
259
287
  /**
260
288
  * WHAT A DIRECTIVE ITEM WILL DO TO AN EXISTING RECORD — for the card, the
@@ -268,7 +296,7 @@ declare function ApplyDirectiveButton({ directive, label, itemCount, onApplied,
268
296
  * not the name of the record being changed. Naming the target is the host's
269
297
  * live lookup (`DirectiveHost.renderRecord`); what changes is right here.
270
298
  *
271
- * Law: common-docs/policies/destructive-and-expensive-actions.md — the sentence
299
+ * Law: common-docs/policies/no-dead-ends.md — the sentence
272
300
  * names what changes.
273
301
  */
274
302
  /** Classes whose items point at an EXISTING record by `id`. */
@@ -665,4 +693,4 @@ declare class DbKindComponentErrorBoundary extends react.Component<DbKindCompone
665
693
  render(): react.ReactNode;
666
694
  }
667
695
 
668
- export { ApplyDirectiveButton, type ApplyDirectiveButtonProps, CHANGE_VALUE_MAX, type ChangeValue, type CompiledDbKindComponent, ComponentResolution, ComponentResolutionSource, ContentIrErrorReporter, ContentIrHost, ContentIrRenderProvider, type ContentIrRenderProviderProps, type DbKindActionResult, type DbKindCompileResult, type DbKindComponentCache, type DbKindComponentCachePorts, type DbKindComponentCompiler, DbKindComponentErrorBoundary, type DbKindComponentErrorBoundaryProps, type DbKindComponentIncident, type DbKindComponentIncidentSink, type DbKindComponentIncidentType, type DbKindComponentRenderProps, DelegatedOutput, type DelegatedOutputProps, DirectiveChangeList, DirectiveFailure, DirectiveFailureWords, DirectiveFallbackCard, type DirectiveFallbackCardProps, DirectiveProvisionalCard, type DirectiveProvisionalCardProps, DirectiveRecordRef, DirectiveRender, type DirectiveRenderProps, type DirectiveRenderer, type DirectiveRendererProps, GenericStructuredView, type GenericStructuredViewProps, type ItemChange, type ItemChangesOptions, type KindComponentUiOptions, KindDefinitionSource, KindInstanceRender, type KindInstanceRenderProps, KindRouteEnv, type KindVersionSources, NodeOutcomeView, type NodeOutcomeViewProps, ProvisionalKindBoundary, ProvisionalKindFrame, type RecordReader, type RecordValues, type RecordValuesStatus, type ResolveKindValue, type RunDbKindAction, RunResultView, type RunResultViewProps, SideEffectDirectiveCard, type SideEffectDirectiveCardProps, TARGETING_CLASSES, createDbKindComponentCache, defaultFailureWords, describeChangeValue, failureWords, fieldLabel, getDirectiveRenderer, isDbKindComponentBodyPending, isRecordValue, itemChanges, itemRecordId, kindIsRoutable, provisionalDirectiveSlug, recordFallbackName, registerDirectiveRenderer, resetDirectiveRenderers, useContentIrHost, useContentIrHostOrNull, useContentIrKindVersion, useKindRouteEnv, useRecordValues };
696
+ export { ApplyDirectiveButton, type ApplyDirectiveButtonProps, CHANGE_VALUE_MAX, type ChangeValue, type CompiledDbKindComponent, ComponentResolution, ComponentResolutionSource, ContentIrErrorReporter, ContentIrHost, ContentIrRenderProvider, type ContentIrRenderProviderProps, type DbKindActionResult, type DbKindCompileResult, type DbKindComponentCache, type DbKindComponentCachePorts, type DbKindComponentCompiler, DbKindComponentErrorBoundary, type DbKindComponentErrorBoundaryProps, type DbKindComponentIncident, type DbKindComponentIncidentSink, type DbKindComponentIncidentType, type DbKindComponentRenderProps, DelegatedOutput, type DelegatedOutputProps, type DirectiveApplyPhase, DirectiveChangeList, DirectiveFailure, DirectiveFailureWords, DirectiveFallbackCard, type DirectiveFallbackCardProps, DirectiveProvisionalCard, type DirectiveProvisionalCardProps, DirectiveRecordRef, DirectiveRender, type DirectiveRenderProps, type DirectiveRenderer, type DirectiveRendererProps, GenericStructuredView, type GenericStructuredViewProps, type ItemChange, type ItemChangesOptions, type KindComponentUiOptions, KindDefinitionSource, KindInstanceRender, type KindInstanceRenderProps, KindRouteEnv, type KindVersionSources, NodeOutcomeView, type NodeOutcomeViewProps, ProvisionalKindBoundary, ProvisionalKindFrame, type RecordReader, type RecordValues, type RecordValuesStatus, type ResolveKindValue, type RunDbKindAction, RunResultView, type RunResultViewProps, SideEffectDirectiveCard, type SideEffectDirectiveCardProps, TARGETING_CLASSES, createDbKindComponentCache, defaultFailureWords, describeChangeValue, failureWords, fieldLabel, getDirectiveRenderer, isDbKindComponentBodyPending, isRecordValue, itemChanges, itemRecordId, kindIsRoutable, provisionalDirectiveSlug, readsApplyLedger, recordFallbackName, registerDirectiveRenderer, resetDirectiveRenderers, useContentIrHost, useContentIrHostOrNull, useContentIrKindVersion, useKindRouteEnv, useRecordValues };
package/dist/index.js CHANGED
@@ -753,7 +753,7 @@ import { looksLikeDirectiveHead, tryDecodeDirectiveContent } from "@ai-matrx/con
753
753
  import { directiveDisplay } from "@ai-matrx/content-ir";
754
754
 
755
755
  // directives/ApplyDirectiveButton.tsx
756
- import { useEffect, useRef, useState } from "react";
756
+ import { useEffect, useRef, useState, useSyncExternalStore as useSyncExternalStore2 } from "react";
757
757
  import { nounLabel } from "@ai-matrx/content-ir";
758
758
 
759
759
  // directives/item-changes.ts
@@ -951,19 +951,52 @@ function classIcon(directiveClass, noun) {
951
951
 
952
952
  // directives/ApplyDirectiveButton.tsx
953
953
  import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
954
+ var applyOutcomes = /* @__PURE__ */ new WeakMap();
955
+ var EMPTY_AUTHORITY = {};
956
+ var outcomeListeners = /* @__PURE__ */ new Set();
957
+ function outcomesOf(host) {
958
+ let held = applyOutcomes.get(host);
959
+ if (!held) {
960
+ held = /* @__PURE__ */ new Map();
961
+ applyOutcomes.set(host, held);
962
+ }
963
+ return held;
964
+ }
965
+ function publishOutcome(host, identity, outcome) {
966
+ outcomesOf(host).set(identity, outcome);
967
+ for (const listener of outcomeListeners) listener();
968
+ }
969
+ function subscribeOutcomes(listener) {
970
+ outcomeListeners.add(listener);
971
+ return () => {
972
+ outcomeListeners.delete(listener);
973
+ };
974
+ }
975
+ function useApplyOutcome(host, identity) {
976
+ return useSyncExternalStore2(
977
+ subscribeOutcomes,
978
+ () => applyOutcomes.get(host)?.get(identity) ?? null,
979
+ () => null
980
+ );
981
+ }
954
982
  var FINISH_POLL_MS = 2e3;
955
983
  var FINISH_POLLS_MAX = 30;
984
+ function readsApplyLedger(host, directive) {
985
+ return !!host.applyState && !!host.confirm && directive.parsed.executes && directive.items.length > 0;
986
+ }
956
987
  function ApplyDirectiveButton({
957
988
  directive,
958
989
  label = "Apply",
959
990
  itemCount,
960
- onApplied
991
+ onApplied,
992
+ onPhase,
993
+ nothingToChange = false
961
994
  }) {
962
995
  const host = useDirectiveHost();
963
996
  const scope = useDirectiveScope();
964
997
  const conversationId = scope.conversationId || void 0;
965
998
  const items = directive.items;
966
- const readsLedger = !!host.applyState && !!host.confirm && directive.parsed.executes && items.length > 0;
999
+ const readsLedger = readsApplyLedger(host, directive);
967
1000
  const initial = readsLedger ? { status: "checking" } : { status: "idle" };
968
1001
  const [state, setState] = useState(initial);
969
1002
  const [elapsed, setElapsed] = useState(0);
@@ -977,6 +1010,8 @@ function ApplyDirectiveButton({
977
1010
  setAskingAgain(false);
978
1011
  setElapsed(0);
979
1012
  }
1013
+ const shared = useApplyOutcome(host.confirm ?? EMPTY_AUTHORITY, identity);
1014
+ const view = state.status === "asking" || !shared ? state : shared;
980
1015
  const currentIdentity = useRef(identity);
981
1016
  useEffect(() => {
982
1017
  currentIdentity.current = identity;
@@ -1004,7 +1039,8 @@ function ApplyDirectiveButton({
1004
1039
  failed: 0,
1005
1040
  message: answer.message?.trim() ? answer.message.trim() : null,
1006
1041
  records: answer.records ?? [],
1007
- copies: Math.max(1, answer.copies ?? 1)
1042
+ copies: Math.max(1, answer.copies ?? 1),
1043
+ before: answer.before ?? null
1008
1044
  } : prev
1009
1045
  );
1010
1046
  } else if (answer.state === "in_flight" && polls < FINISH_POLLS_MAX) {
@@ -1029,24 +1065,34 @@ function ApplyDirectiveButton({
1029
1065
  if (timer) clearTimeout(timer);
1030
1066
  };
1031
1067
  }, [shellKey, read, report]);
1068
+ const appliedClean = view.status === "applied" && view.failed === 0 ? view : null;
1069
+ const appliedBefore = appliedClean ? appliedClean.before : null;
1070
+ const phaseKey = view.status === "checking" || view.status === "finishing" ? "pending" : appliedClean ? `applied:${JSON.stringify(appliedBefore)}` : "open";
1032
1071
  useEffect(() => {
1033
- if (state.status !== "applying") return;
1034
- startedAt.current = Date.now();
1072
+ if (!onPhase) return;
1073
+ if (phaseKey === "pending") onPhase({ phase: "pending" });
1074
+ else if (phaseKey === "open") onPhase({ phase: "open" });
1075
+ else onPhase({ phase: "applied", before: appliedBefore });
1076
+ }, [phaseKey, onPhase, appliedBefore]);
1077
+ const applyingSince = view.status === "applying" ? view.startedAt : null;
1078
+ useEffect(() => {
1079
+ if (applyingSince === null) return;
1080
+ startedAt.current = applyingSince;
1035
1081
  const id = setInterval(
1036
1082
  () => setElapsed(Math.round((Date.now() - (startedAt.current ?? 0)) / 1e3)),
1037
1083
  1e3
1038
1084
  );
1039
1085
  return () => clearInterval(id);
1040
- }, [state.status]);
1086
+ }, [applyingSince]);
1041
1087
  if (!directive.parsed.executes) return null;
1042
1088
  const confirm = host.confirm;
1043
1089
  if (!confirm) return null;
1044
1090
  async function handleApply(again = false) {
1045
1091
  if (!confirm) return;
1046
- const before = state;
1092
+ const prior = view;
1047
1093
  const askedFor = identity;
1048
1094
  const stillHere = () => currentIdentity.current === askedFor;
1049
- const priorCopies = state.status === "applied" ? state.copies : 0;
1095
+ const priorCopies = view.status === "applied" ? view.copies : 0;
1050
1096
  if (host.ask) {
1051
1097
  if (again) setAskingAgain(true);
1052
1098
  else setState({ status: "asking" });
@@ -1070,41 +1116,43 @@ function ApplyDirectiveButton({
1070
1116
  if (!stillHere()) return;
1071
1117
  setAskingAgain(false);
1072
1118
  if (!ok) {
1073
- setState(again ? before : { status: "idle" });
1119
+ setState(again ? prior : { status: "idle" });
1074
1120
  return;
1075
1121
  }
1076
1122
  }
1077
1123
  setElapsed(0);
1078
- setState({ status: "applying" });
1124
+ setState((prev) => prev.status === "asking" ? { status: "idle" } : prev);
1125
+ publishOutcome(confirm, askedFor, { status: "applying", startedAt: Date.now() });
1079
1126
  try {
1080
1127
  const shell = { __kind: directive.slug, items };
1081
1128
  if (conversationId) shell.conversationId = conversationId;
1082
1129
  if (again) shell.force = true;
1083
1130
  const result = await confirm(shell);
1084
- if (!stillHere()) return;
1085
- setState({
1131
+ publishOutcome(confirm, askedFor, {
1086
1132
  status: "applied",
1087
1133
  applied: result.applied,
1088
1134
  failed: result.failed,
1089
1135
  message: result.message?.trim() ? result.message.trim() : null,
1090
1136
  records: result.records ?? [],
1091
1137
  // "Run again" made one more; a first apply (or a replay of one) is one.
1092
- copies: again ? Math.max(1, priorCopies) + 1 : Math.max(1, priorCopies)
1138
+ copies: again ? Math.max(1, priorCopies) + 1 : Math.max(1, priorCopies),
1139
+ // "Run again" wrote the same values over themselves; what the FIRST
1140
+ // apply replaced is still the change this block made.
1141
+ before: again && prior.status === "applied" && prior.before ? prior.before : result.before ?? null
1093
1142
  });
1094
- onApplied?.();
1143
+ if (stillHere()) onApplied?.();
1095
1144
  } catch (error) {
1096
- if (!stillHere()) return;
1097
- setState({
1145
+ publishOutcome(confirm, askedFor, {
1098
1146
  status: "error",
1099
1147
  message: error instanceof Error && error.message ? error.message : "That couldn't be applied just now. Please try again."
1100
1148
  });
1101
1149
  }
1102
1150
  }
1103
- if (state.status === "applied") {
1104
- const bad = state.failed > 0;
1151
+ if (view.status === "applied") {
1152
+ const bad = view.failed > 0;
1105
1153
  const trashed = directive.directiveClass === "delete";
1106
1154
  const label2 = nounText;
1107
- const records = host.renderRecord ? state.records : [];
1155
+ const records = host.renderRecord ? view.records : [];
1108
1156
  return /* @__PURE__ */ jsxs2(
1109
1157
  "span",
1110
1158
  {
@@ -1118,26 +1166,26 @@ function ApplyDirectiveButton({
1118
1166
  {
1119
1167
  words: failureWords(
1120
1168
  {
1121
- raw: state.message ?? `${state.failed} failed`,
1169
+ raw: view.message ?? `${view.failed} failed`,
1122
1170
  directiveClass: directive.directiveClass,
1123
1171
  noun: directive.noun,
1124
1172
  nounLabel: nounText,
1125
- failed: state.failed,
1126
- total: state.applied + state.failed
1173
+ failed: view.failed,
1174
+ total: view.applied + view.failed
1127
1175
  },
1128
1176
  host.explainFailure
1129
1177
  ),
1130
- raw: state.message
1178
+ raw: view.message
1131
1179
  }
1132
- ) : /* @__PURE__ */ jsx4("span", { className: "min-w-0", children: state.message ?? `Applied ${state.applied}` })
1180
+ ) : /* @__PURE__ */ jsx4("span", { className: "min-w-0", children: view.message ?? `Applied ${view.applied}` })
1133
1181
  ] }),
1134
- state.copies > 1 ? /* @__PURE__ */ jsx4(
1182
+ view.copies > 1 ? /* @__PURE__ */ jsx4(
1135
1183
  "span",
1136
1184
  {
1137
1185
  className: "shrink-0 text-muted-foreground",
1138
- "data-apply-copies": state.copies,
1186
+ "data-apply-copies": view.copies,
1139
1187
  title: records.length > 0 ? directive.directiveClass === "create" ? "Links to the latest copy." : "Links to the latest run." : void 0,
1140
- children: directive.directiveClass === "create" ? `${state.copies} copies` : `Ran ${state.copies} times`
1188
+ children: directive.directiveClass === "create" ? `${view.copies} copies` : `Ran ${view.copies} times`
1141
1189
  }
1142
1190
  ) : null,
1143
1191
  records.map((record) => /* @__PURE__ */ jsx4("span", { className: "inline-flex min-w-0 items-center", children: host.renderRecord?.({
@@ -1164,7 +1212,7 @@ function ApplyDirectiveButton({
1164
1212
  }
1165
1213
  );
1166
1214
  }
1167
- if (state.status === "checking") {
1215
+ if (view.status === "checking") {
1168
1216
  return /* @__PURE__ */ jsx4(
1169
1217
  "span",
1170
1218
  {
@@ -1176,7 +1224,7 @@ function ApplyDirectiveButton({
1176
1224
  }
1177
1225
  );
1178
1226
  }
1179
- if (state.status === "finishing") {
1227
+ if (view.status === "finishing") {
1180
1228
  return /* @__PURE__ */ jsxs2(
1181
1229
  "span",
1182
1230
  {
@@ -1191,22 +1239,25 @@ function ApplyDirectiveButton({
1191
1239
  );
1192
1240
  }
1193
1241
  const destructive = directive.directiveClass === "delete";
1242
+ const muted = nothingToChange && view.status === "idle";
1194
1243
  return /* @__PURE__ */ jsxs2("span", { className: "inline-flex items-center gap-2", children: [
1244
+ muted ? /* @__PURE__ */ jsx4("span", { className: "text-xs text-muted-foreground", "data-apply-nothing": "", children: "Nothing to change" }) : null,
1195
1245
  /* @__PURE__ */ jsxs2(
1196
1246
  "button",
1197
1247
  {
1198
1248
  type: "button",
1199
1249
  onClick: () => void handleApply(),
1200
- disabled: state.status === "applying" || state.status === "asking" || items.length === 0,
1201
- "data-apply-tone": destructive ? "destructive" : "default",
1202
- 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"}`,
1250
+ disabled: view.status === "applying" || view.status === "asking" || items.length === 0,
1251
+ "data-apply-tone": muted ? "muted" : destructive ? "destructive" : "default",
1252
+ title: muted ? "It sets no fields, so applying it changes nothing." : void 0,
1253
+ 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"}`,
1203
1254
  children: [
1204
- state.status === "applying" ? /* @__PURE__ */ jsx4(LoaderIcon, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx4(PlayIcon, { className: "h-3.5 w-3.5" }),
1205
- state.status === "applying" ? `Applying${itemCount ? ` ${itemCount} ${itemCount === 1 ? "item" : "items"}` : ""}\u2026${elapsed ? ` ${elapsed}s` : ""}` : label
1255
+ view.status === "applying" ? /* @__PURE__ */ jsx4(LoaderIcon, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx4(PlayIcon, { className: "h-3.5 w-3.5" }),
1256
+ view.status === "applying" ? `Applying${itemCount ? ` ${itemCount} ${itemCount === 1 ? "item" : "items"}` : ""}\u2026${elapsed ? ` ${elapsed}s` : ""}` : label
1206
1257
  ]
1207
1258
  }
1208
1259
  ),
1209
- state.status === "error" ? /* @__PURE__ */ jsxs2(
1260
+ view.status === "error" ? /* @__PURE__ */ jsxs2(
1210
1261
  "span",
1211
1262
  {
1212
1263
  className: "inline-flex max-w-sm items-start gap-1 text-xs text-destructive",
@@ -1218,14 +1269,14 @@ function ApplyDirectiveButton({
1218
1269
  {
1219
1270
  words: failureWords(
1220
1271
  {
1221
- raw: state.message,
1272
+ raw: view.message,
1222
1273
  directiveClass: directive.directiveClass,
1223
1274
  noun: directive.noun,
1224
1275
  nounLabel: nounText
1225
1276
  },
1226
1277
  host.explainFailure
1227
1278
  ),
1228
- raw: state.message
1279
+ raw: view.message
1229
1280
  }
1230
1281
  )
1231
1282
  ]
@@ -1405,12 +1456,13 @@ function ItemRow({
1405
1456
  directive,
1406
1457
  item,
1407
1458
  index,
1408
- total
1459
+ total,
1460
+ phase
1409
1461
  }) {
1410
1462
  const host = useDirectiveHost();
1411
1463
  const recordId = TARGETING_CLASSES.has(directive.directiveClass) ? itemRecordId(item) : null;
1412
1464
  if (recordId) {
1413
- return /* @__PURE__ */ jsx7(TargetRow, { directive, item, recordId });
1465
+ return /* @__PURE__ */ jsx7(TargetRow, { directive, item, recordId, index, phase });
1414
1466
  }
1415
1467
  const titleColumn = nounTitleColumn(directive.noun, host.nouns);
1416
1468
  const title = itemTitle(item, titleColumn, index, total);
@@ -1497,16 +1549,20 @@ function DirectiveChangeList({
1497
1549
  function TargetRow({
1498
1550
  directive,
1499
1551
  item,
1500
- recordId
1552
+ recordId,
1553
+ index,
1554
+ phase
1501
1555
  }) {
1502
1556
  const host = useDirectiveHost();
1503
1557
  const fallback = recordFallbackName(nounLabel2(directive.noun, host.nouns), recordId);
1504
1558
  const isUpdate = directive.directiveClass === "update";
1505
- const current = useRecordValues(
1506
- isUpdate ? host.readRecord : void 0,
1507
- isUpdate ? { noun: directive.noun, id: recordId } : null
1559
+ const readsLive = isUpdate && phase.phase === "open";
1560
+ const live = useRecordValues(
1561
+ readsLive ? host.readRecord : void 0,
1562
+ readsLive ? { noun: directive.noun, id: recordId } : null
1508
1563
  );
1509
- const changes = isUpdate ? itemChanges(item, current.values, { titleColumn: nounTitleColumn(directive.noun, host.nouns) }) : [];
1564
+ const compareTo = phase.phase === "applied" ? phase.before?.[index] ?? null : readsLive ? live.values : null;
1565
+ const changes = isUpdate ? itemChanges(item, compareTo, { titleColumn: nounTitleColumn(directive.noun, host.nouns) }) : [];
1510
1566
  const name = host.renderRecord ? host.renderRecord({ noun: directive.noun, id: recordId, fallback, context: "row" }) : /* @__PURE__ */ jsx7("span", { className: "truncate font-medium text-foreground", children: fallback });
1511
1567
  return /* @__PURE__ */ jsxs5("div", { className: "py-1 text-xs", "data-directive-target": recordId, children: [
1512
1568
  /* @__PURE__ */ jsxs5("div", { className: "flex min-w-0 items-center gap-2", children: [
@@ -1523,6 +1579,10 @@ function SideEffectDirectiveCard({ directive }) {
1523
1579
  const total = items.length;
1524
1580
  const display = directiveDisplay3(directive.directiveClass, directive.noun, host.nouns);
1525
1581
  const Icon = classIcon(directive.directiveClass, directive.noun);
1582
+ const [phase, setPhase] = useState3(
1583
+ () => readsApplyLedger(host, directive) ? { phase: "pending" } : { phase: "open" }
1584
+ );
1585
+ const nothingToChange = directive.directiveClass === "update" && total > 0 && items.every((item) => itemChanges(item).length === 0);
1526
1586
  const shown = expanded ? items : items.slice(0, ROWS_BEFORE_FOLD);
1527
1587
  const hidden = total - shown.length;
1528
1588
  return /* @__PURE__ */ jsxs5("div", { className: "my-3 rounded-md border border-border bg-card px-2.5 py-1.5", "data-directive": directive.slug, children: [
@@ -1537,13 +1597,21 @@ function SideEffectDirectiveCard({ directive }) {
1537
1597
  ] }),
1538
1598
  /* @__PURE__ */ jsxs5("div", { className: "ml-auto flex items-center gap-1", children: [
1539
1599
  host.renderCopy?.({ label: display.title, value: directive.shell, size: "icon" }),
1540
- /* @__PURE__ */ jsx7(ApplyDirectiveButton, { directive, itemCount: total })
1600
+ /* @__PURE__ */ jsx7(
1601
+ ApplyDirectiveButton,
1602
+ {
1603
+ directive,
1604
+ itemCount: total,
1605
+ onPhase: setPhase,
1606
+ nothingToChange
1607
+ }
1608
+ )
1541
1609
  ] })
1542
1610
  ] }),
1543
1611
  total === 0 ? (
1544
1612
  // Stated, never silent: an empty batch is a real (and suspicious) state.
1545
1613
  /* @__PURE__ */ jsx7("p", { className: "mt-1 text-xs text-muted-foreground", children: "No items \u2014 nothing would be written." })
1546
- ) : /* @__PURE__ */ jsx7("div", { className: "mt-0.5 divide-y divide-border/60", children: shown.map((item, i) => /* @__PURE__ */ jsx7(ItemRow, { directive, item, index: i, total }, i)) }),
1614
+ ) : /* @__PURE__ */ jsx7("div", { className: "mt-0.5 divide-y divide-border/60", children: shown.map((item, i) => /* @__PURE__ */ jsx7(ItemRow, { directive, item, index: i, total, phase }, i)) }),
1547
1615
  hidden > 0 || expanded ? /* @__PURE__ */ jsxs5(
1548
1616
  "button",
1549
1617
  {
@@ -2071,6 +2139,7 @@ export {
2071
2139
  markKindPartialUnsafe,
2072
2140
  provisionalDirectiveSlug,
2073
2141
  readIrRouteMarker,
2142
+ readsApplyLedger,
2074
2143
  recordFallbackName,
2075
2144
  registerDirectiveRenderer,
2076
2145
  resetDirectiveRenderers,