@ai-matrx/content-ir-react 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +1 -1
  3. package/dist/{component-resolution.types-DLO2w-0n.d.cts → component-resolution.types-DY4pGymy.d.cts} +1 -1
  4. package/dist/{component-resolution.types-DLO2w-0n.d.ts → component-resolution.types-DY4pGymy.d.ts} +1 -1
  5. package/dist/{component-resolution.types-C1MzCf2s.d.cts → component-resolution.types-DwKSF64x.d.cts} +1 -1
  6. package/dist/{component-resolution.types-C1MzCf2s.d.ts → component-resolution.types-DwKSF64x.d.ts} +1 -1
  7. package/dist/db-component.d.cts +1 -1
  8. package/dist/db-component.d.ts +1 -1
  9. package/dist/{directive-host-BOML4BQ7.d.cts → directive-host-Bw2_PrgL.d.cts} +14 -0
  10. package/dist/{directive-host-BOML4BQ7.d.ts → directive-host-Bw2_PrgL.d.ts} +14 -0
  11. package/dist/directives.cjs +56 -17
  12. package/dist/directives.cjs.map +1 -1
  13. package/dist/directives.d.cts +33 -5
  14. package/dist/directives.d.ts +33 -5
  15. package/dist/directives.js +56 -17
  16. package/dist/directives.js.map +1 -1
  17. package/dist/{host-types-Dxzs0C3f.d.cts → host-types-YP_l_Q_t.d.cts} +2 -2
  18. package/dist/{host-types-uKO1zZo3.d.ts → host-types-eSjFmXyF.d.ts} +2 -2
  19. package/dist/host.cjs.map +1 -1
  20. package/dist/host.d.cts +4 -4
  21. package/dist/host.d.ts +4 -4
  22. package/dist/host.js.map +1 -1
  23. package/dist/index.cjs +56 -17
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.cts +36 -8
  26. package/dist/index.d.ts +36 -8
  27. package/dist/index.js +56 -17
  28. package/dist/index.js.map +1 -1
  29. package/dist/react.cjs +55 -17
  30. package/dist/react.cjs.map +1 -1
  31. package/dist/react.d.cts +4 -4
  32. package/dist/react.d.ts +4 -4
  33. package/dist/react.js +55 -17
  34. package/dist/react.js.map +1 -1
  35. package/dist/resolver.d.cts +2 -2
  36. package/dist/resolver.d.ts +2 -2
  37. package/dist/{route-CfKxIEdj.d.ts → route-CtYw2cTN.d.ts} +16 -2
  38. package/dist/{route-DjGv9skd.d.cts → route-KoRMp3Qn.d.cts} +16 -2
  39. package/dist/route.cjs.map +1 -1
  40. package/dist/route.d.cts +2 -2
  41. package/dist/route.d.ts +2 -2
  42. package/dist/route.js.map +1 -1
  43. package/package.json +3 -3
package/dist/react.d.cts CHANGED
@@ -1,9 +1,9 @@
1
- import { c as KindDefinitionSource, C as ContentIrHost } from './host-types-Dxzs0C3f.cjs';
2
- import { d as ComponentResolutionSource } from './component-resolution.types-C1MzCf2s.cjs';
1
+ import { c as KindDefinitionSource, C as ContentIrHost } from './host-types-YP_l_Q_t.cjs';
2
+ import { d as ComponentResolutionSource } from './component-resolution.types-DwKSF64x.cjs';
3
3
  import * as react from 'react';
4
4
  import react__default, { ReactNode } from 'react';
5
5
  import '@ai-matrx/content-ir';
6
- import './directive-host-BOML4BQ7.cjs';
6
+ import './directive-host-Bw2_PrgL.cjs';
7
7
 
8
8
  /**
9
9
  * The two registries this hook watches. Passed explicitly by a host whose
@@ -87,7 +87,7 @@ declare function KindInstanceRender(props: KindInstanceRenderProps): react.JSX.E
87
87
  /**
88
88
  * The safety net + the "still arriving" affordance for a provisional kind
89
89
  * render (streaming partial kinds — see `route/partial-kind-route.ts` and
90
- * `common-docs/systems/content-ir-system/STREAMING_PARTIAL_KINDS.md`).
90
+ * `common-docs/systems/architecture/content-ir/STREAMING_PARTIAL_KINDS.md`).
91
91
  *
92
92
  * TWO JOBS
93
93
  * --------
package/dist/react.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { c as KindDefinitionSource, C as ContentIrHost } from './host-types-uKO1zZo3.js';
2
- import { d as ComponentResolutionSource } from './component-resolution.types-C1MzCf2s.js';
1
+ import { c as KindDefinitionSource, C as ContentIrHost } from './host-types-eSjFmXyF.js';
2
+ import { d as ComponentResolutionSource } from './component-resolution.types-DwKSF64x.js';
3
3
  import * as react from 'react';
4
4
  import react__default, { ReactNode } from 'react';
5
5
  import '@ai-matrx/content-ir';
6
- import './directive-host-BOML4BQ7.js';
6
+ import './directive-host-Bw2_PrgL.js';
7
7
 
8
8
  /**
9
9
  * The two registries this hook watches. Passed explicitly by a host whose
@@ -87,7 +87,7 @@ declare function KindInstanceRender(props: KindInstanceRenderProps): react.JSX.E
87
87
  /**
88
88
  * The safety net + the "still arriving" affordance for a provisional kind
89
89
  * render (streaming partial kinds — see `route/partial-kind-route.ts` and
90
- * `common-docs/systems/content-ir-system/STREAMING_PARTIAL_KINDS.md`).
90
+ * `common-docs/systems/architecture/content-ir/STREAMING_PARTIAL_KINDS.md`).
91
91
  *
92
92
  * TWO JOBS
93
93
  * --------
package/dist/react.js CHANGED
@@ -291,17 +291,22 @@ function classIcon(directiveClass, noun) {
291
291
  import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
292
292
  var FINISH_POLL_MS = 2e3;
293
293
  var FINISH_POLLS_MAX = 30;
294
+ function readsApplyLedger(host, directive) {
295
+ return !!host.applyState && !!host.confirm && directive.parsed.executes && directive.items.length > 0;
296
+ }
294
297
  function ApplyDirectiveButton({
295
298
  directive,
296
299
  label = "Apply",
297
300
  itemCount,
298
- onApplied
301
+ onApplied,
302
+ onPhase,
303
+ nothingToChange = false
299
304
  }) {
300
305
  const host = useDirectiveHost();
301
306
  const scope = useDirectiveScope();
302
307
  const conversationId = scope.conversationId || void 0;
303
308
  const items = directive.items;
304
- const readsLedger = !!host.applyState && !!host.confirm && directive.parsed.executes && items.length > 0;
309
+ const readsLedger = readsApplyLedger(host, directive);
305
310
  const initial = readsLedger ? { status: "checking" } : { status: "idle" };
306
311
  const [state, setState] = useState(initial);
307
312
  const [elapsed, setElapsed] = useState(0);
@@ -342,7 +347,8 @@ function ApplyDirectiveButton({
342
347
  failed: 0,
343
348
  message: answer.message?.trim() ? answer.message.trim() : null,
344
349
  records: answer.records ?? [],
345
- copies: Math.max(1, answer.copies ?? 1)
350
+ copies: Math.max(1, answer.copies ?? 1),
351
+ before: answer.before ?? null
346
352
  } : prev
347
353
  );
348
354
  } else if (answer.state === "in_flight" && polls < FINISH_POLLS_MAX) {
@@ -367,6 +373,15 @@ function ApplyDirectiveButton({
367
373
  if (timer) clearTimeout(timer);
368
374
  };
369
375
  }, [shellKey, read, report]);
376
+ const appliedClean = state.status === "applied" && state.failed === 0 ? state : null;
377
+ const appliedBefore = appliedClean ? appliedClean.before : null;
378
+ const phaseKey = state.status === "checking" || state.status === "finishing" ? "pending" : appliedClean ? `applied:${JSON.stringify(appliedBefore)}` : "open";
379
+ useEffect(() => {
380
+ if (!onPhase) return;
381
+ if (phaseKey === "pending") onPhase({ phase: "pending" });
382
+ else if (phaseKey === "open") onPhase({ phase: "open" });
383
+ else onPhase({ phase: "applied", before: appliedBefore });
384
+ }, [phaseKey, onPhase, appliedBefore]);
370
385
  useEffect(() => {
371
386
  if (state.status !== "applying") return;
372
387
  startedAt.current = Date.now();
@@ -381,7 +396,7 @@ function ApplyDirectiveButton({
381
396
  if (!confirm) return null;
382
397
  async function handleApply(again = false) {
383
398
  if (!confirm) return;
384
- const before = state;
399
+ const prior = state;
385
400
  const askedFor = identity;
386
401
  const stillHere = () => currentIdentity.current === askedFor;
387
402
  const priorCopies = state.status === "applied" ? state.copies : 0;
@@ -408,7 +423,7 @@ function ApplyDirectiveButton({
408
423
  if (!stillHere()) return;
409
424
  setAskingAgain(false);
410
425
  if (!ok) {
411
- setState(again ? before : { status: "idle" });
426
+ setState(again ? prior : { status: "idle" });
412
427
  return;
413
428
  }
414
429
  }
@@ -427,7 +442,10 @@ function ApplyDirectiveButton({
427
442
  message: result.message?.trim() ? result.message.trim() : null,
428
443
  records: result.records ?? [],
429
444
  // "Run again" made one more; a first apply (or a replay of one) is one.
430
- copies: again ? Math.max(1, priorCopies) + 1 : Math.max(1, priorCopies)
445
+ copies: again ? Math.max(1, priorCopies) + 1 : Math.max(1, priorCopies),
446
+ // "Run again" wrote the same values over themselves; what the FIRST
447
+ // apply replaced is still the change this block made.
448
+ before: again && prior.status === "applied" && prior.before ? prior.before : result.before ?? null
431
449
  });
432
450
  onApplied?.();
433
451
  } catch (error) {
@@ -529,15 +547,18 @@ function ApplyDirectiveButton({
529
547
  );
530
548
  }
531
549
  const destructive = directive.directiveClass === "delete";
550
+ const muted = nothingToChange && state.status === "idle";
532
551
  return /* @__PURE__ */ jsxs2("span", { className: "inline-flex items-center gap-2", children: [
552
+ muted ? /* @__PURE__ */ jsx4("span", { className: "text-xs text-muted-foreground", "data-apply-nothing": "", children: "Nothing to change" }) : null,
533
553
  /* @__PURE__ */ jsxs2(
534
554
  "button",
535
555
  {
536
556
  type: "button",
537
557
  onClick: () => void handleApply(),
538
558
  disabled: state.status === "applying" || state.status === "asking" || items.length === 0,
539
- "data-apply-tone": destructive ? "destructive" : "default",
540
- 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"}`,
559
+ "data-apply-tone": muted ? "muted" : destructive ? "destructive" : "default",
560
+ title: muted ? "It sets no fields, so applying it changes nothing." : void 0,
561
+ 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"}`,
541
562
  children: [
542
563
  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" }),
543
564
  state.status === "applying" ? `Applying${itemCount ? ` ${itemCount} ${itemCount === 1 ? "item" : "items"}` : ""}\u2026${elapsed ? ` ${elapsed}s` : ""}` : label
@@ -735,12 +756,13 @@ function ItemRow({
735
756
  directive,
736
757
  item,
737
758
  index,
738
- total
759
+ total,
760
+ phase
739
761
  }) {
740
762
  const host = useDirectiveHost();
741
763
  const recordId = TARGETING_CLASSES.has(directive.directiveClass) ? itemRecordId(item) : null;
742
764
  if (recordId) {
743
- return /* @__PURE__ */ jsx7(TargetRow, { directive, item, recordId });
765
+ return /* @__PURE__ */ jsx7(TargetRow, { directive, item, recordId, index, phase });
744
766
  }
745
767
  const titleColumn = nounTitleColumn(directive.noun, host.nouns);
746
768
  const title = itemTitle(item, titleColumn, index, total);
@@ -827,16 +849,20 @@ function DirectiveChangeList({
827
849
  function TargetRow({
828
850
  directive,
829
851
  item,
830
- recordId
852
+ recordId,
853
+ index,
854
+ phase
831
855
  }) {
832
856
  const host = useDirectiveHost();
833
857
  const fallback = recordFallbackName(nounLabel2(directive.noun, host.nouns), recordId);
834
858
  const isUpdate = directive.directiveClass === "update";
835
- const current = useRecordValues(
836
- isUpdate ? host.readRecord : void 0,
837
- isUpdate ? { noun: directive.noun, id: recordId } : null
859
+ const readsLive = isUpdate && phase.phase === "open";
860
+ const live = useRecordValues(
861
+ readsLive ? host.readRecord : void 0,
862
+ readsLive ? { noun: directive.noun, id: recordId } : null
838
863
  );
839
- const changes = isUpdate ? itemChanges(item, current.values, { titleColumn: nounTitleColumn(directive.noun, host.nouns) }) : [];
864
+ const compareTo = phase.phase === "applied" ? phase.before?.[index] ?? null : readsLive ? live.values : null;
865
+ const changes = isUpdate ? itemChanges(item, compareTo, { titleColumn: nounTitleColumn(directive.noun, host.nouns) }) : [];
840
866
  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 });
841
867
  return /* @__PURE__ */ jsxs5("div", { className: "py-1 text-xs", "data-directive-target": recordId, children: [
842
868
  /* @__PURE__ */ jsxs5("div", { className: "flex min-w-0 items-center gap-2", children: [
@@ -853,6 +879,10 @@ function SideEffectDirectiveCard({ directive }) {
853
879
  const total = items.length;
854
880
  const display = directiveDisplay3(directive.directiveClass, directive.noun, host.nouns);
855
881
  const Icon = classIcon(directive.directiveClass, directive.noun);
882
+ const [phase, setPhase] = useState3(
883
+ () => readsApplyLedger(host, directive) ? { phase: "pending" } : { phase: "open" }
884
+ );
885
+ const nothingToChange = directive.directiveClass === "update" && total > 0 && items.every((item) => itemChanges(item).length === 0);
856
886
  const shown = expanded ? items : items.slice(0, ROWS_BEFORE_FOLD);
857
887
  const hidden = total - shown.length;
858
888
  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: [
@@ -867,13 +897,21 @@ function SideEffectDirectiveCard({ directive }) {
867
897
  ] }),
868
898
  /* @__PURE__ */ jsxs5("div", { className: "ml-auto flex items-center gap-1", children: [
869
899
  host.renderCopy?.({ label: display.title, value: directive.shell, size: "icon" }),
870
- /* @__PURE__ */ jsx7(ApplyDirectiveButton, { directive, itemCount: total })
900
+ /* @__PURE__ */ jsx7(
901
+ ApplyDirectiveButton,
902
+ {
903
+ directive,
904
+ itemCount: total,
905
+ onPhase: setPhase,
906
+ nothingToChange
907
+ }
908
+ )
871
909
  ] })
872
910
  ] }),
873
911
  total === 0 ? (
874
912
  // Stated, never silent: an empty batch is a real (and suspicious) state.
875
913
  /* @__PURE__ */ jsx7("p", { className: "mt-1 text-xs text-muted-foreground", children: "No items \u2014 nothing would be written." })
876
- ) : /* @__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)) }),
914
+ ) : /* @__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)) }),
877
915
  hidden > 0 || expanded ? /* @__PURE__ */ jsxs5(
878
916
  "button",
879
917
  {