@ai-matrx/content-ir-react 0.15.2 → 0.16.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.
package/dist/react.cjs CHANGED
@@ -124,7 +124,7 @@ function useContentIrKindVersion(kind, sources) {
124
124
  }
125
125
 
126
126
  // react/KindInstanceRender.tsx
127
- var import_react6 = require("react");
127
+ var import_react7 = require("react");
128
128
  var import_content_ir8 = require("@ai-matrx/content-ir");
129
129
 
130
130
  // directives/DirectiveRender.tsx
@@ -184,11 +184,26 @@ function safeJson(value) {
184
184
  return String(value);
185
185
  }
186
186
  }
187
- function itemChanges(item) {
187
+ function sameValue(a, b) {
188
+ if (a === b) return true;
189
+ if (a === null || a === void 0 || b === null || b === void 0) {
190
+ const emptyA = a === null || a === void 0 || a === "";
191
+ const emptyB = b === null || b === void 0 || b === "";
192
+ return emptyA && emptyB;
193
+ }
194
+ if (typeof a === "object" || typeof b === "object") return safeJson(a) === safeJson(b);
195
+ return false;
196
+ }
197
+ function itemChanges(item, current) {
188
198
  const out = [];
189
199
  for (const [key, raw] of Object.entries(item)) {
190
200
  if (NOT_A_CHANGE.has(key)) continue;
191
- out.push({ key, label: fieldLabel(key), ...describeValue(raw) });
201
+ const change = { key, label: fieldLabel(key), ...describeValue(raw) };
202
+ if (current && Object.prototype.hasOwnProperty.call(current, key)) {
203
+ change.before = describeValue(current[key]);
204
+ change.same = sameValue(current[key], raw);
205
+ }
206
+ out.push(change);
192
207
  }
193
208
  return out;
194
209
  }
@@ -301,12 +316,23 @@ function ApplyDirectiveButton({
301
316
  const conversationId = scope.conversationId || void 0;
302
317
  const items = directive.items;
303
318
  const readsLedger = !!host.applyState && !!host.confirm && directive.parsed.executes && items.length > 0;
304
- const [state, setState] = (0, import_react4.useState)(
305
- readsLedger ? { status: "checking" } : { status: "idle" }
306
- );
319
+ const initial = readsLedger ? { status: "checking" } : { status: "idle" };
320
+ const [state, setState] = (0, import_react4.useState)(initial);
307
321
  const [elapsed, setElapsed] = (0, import_react4.useState)(0);
308
322
  const [askingAgain, setAskingAgain] = (0, import_react4.useState)(false);
309
323
  const startedAt = (0, import_react4.useRef)(null);
324
+ const identity = JSON.stringify([directive.slug, items, conversationId ?? null]);
325
+ const [stateFor, setStateFor] = (0, import_react4.useState)(identity);
326
+ if (stateFor !== identity) {
327
+ setStateFor(identity);
328
+ setState(initial);
329
+ setAskingAgain(false);
330
+ setElapsed(0);
331
+ }
332
+ const currentIdentity = (0, import_react4.useRef)(identity);
333
+ (0, import_react4.useEffect)(() => {
334
+ currentIdentity.current = identity;
335
+ }, [identity]);
310
336
  const nounText = (0, import_content_ir.nounLabel)(directive.noun, host.nouns);
311
337
  const shellKey = readsLedger ? JSON.stringify([directive.slug, items, conversationId ?? null]) : "";
312
338
  const read = host.applyState;
@@ -329,7 +355,8 @@ function ApplyDirectiveButton({
329
355
  applied: shellItems.length,
330
356
  failed: 0,
331
357
  message: answer.message?.trim() ? answer.message.trim() : null,
332
- records: answer.records ?? []
358
+ records: answer.records ?? [],
359
+ copies: Math.max(1, answer.copies ?? 1)
333
360
  } : prev
334
361
  );
335
362
  } else if (answer.state === "in_flight" && polls < FINISH_POLLS_MAX) {
@@ -369,6 +396,9 @@ function ApplyDirectiveButton({
369
396
  async function handleApply(again = false) {
370
397
  if (!confirm) return;
371
398
  const before = state;
399
+ const askedFor = identity;
400
+ const stillHere = () => currentIdentity.current === askedFor;
401
+ const priorCopies = state.status === "applied" ? state.copies : 0;
372
402
  if (host.ask) {
373
403
  if (again) setAskingAgain(true);
374
404
  else setState({ status: "asking" });
@@ -381,6 +411,7 @@ function ApplyDirectiveButton({
381
411
  ...again ? { again: true } : {}
382
412
  });
383
413
  } catch (error) {
414
+ if (!stillHere()) return;
384
415
  setAskingAgain(false);
385
416
  setState({
386
417
  status: "error",
@@ -388,6 +419,7 @@ function ApplyDirectiveButton({
388
419
  });
389
420
  return;
390
421
  }
422
+ if (!stillHere()) return;
391
423
  setAskingAgain(false);
392
424
  if (!ok) {
393
425
  setState(again ? before : { status: "idle" });
@@ -401,15 +433,19 @@ function ApplyDirectiveButton({
401
433
  if (conversationId) shell.conversationId = conversationId;
402
434
  if (again) shell.force = true;
403
435
  const result = await confirm(shell);
436
+ if (!stillHere()) return;
404
437
  setState({
405
438
  status: "applied",
406
439
  applied: result.applied,
407
440
  failed: result.failed,
408
441
  message: result.message?.trim() ? result.message.trim() : null,
409
- records: result.records ?? []
442
+ records: result.records ?? [],
443
+ // "Run again" made one more; a first apply (or a replay of one) is one.
444
+ copies: again ? Math.max(1, priorCopies) + 1 : Math.max(1, priorCopies)
410
445
  });
411
446
  onApplied?.();
412
447
  } catch (error) {
448
+ if (!stillHere()) return;
413
449
  setState({
414
450
  status: "error",
415
451
  message: error instanceof Error && error.message ? error.message : "That couldn't be applied just now. Please try again."
@@ -434,6 +470,15 @@ function ApplyDirectiveButton({
434
470
  bad && !state.message ? ` \xB7 ${state.failed} failed` : ""
435
471
  ] })
436
472
  ] }),
473
+ state.copies > 1 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
474
+ "span",
475
+ {
476
+ className: "shrink-0 text-muted-foreground",
477
+ "data-apply-copies": state.copies,
478
+ title: records.length > 0 ? directive.directiveClass === "create" ? "Links to the latest copy." : "Links to the latest run." : void 0,
479
+ children: directive.directiveClass === "create" ? `${state.copies} copies` : `Ran ${state.copies} times`
480
+ }
481
+ ) : null,
437
482
  records.map((record) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "inline-flex min-w-0 items-center", children: host.renderRecord?.({
438
483
  ...record,
439
484
  fallback: recordFallbackName(label2, record.id),
@@ -484,6 +529,7 @@ function ApplyDirectiveButton({
484
529
  }
485
530
  );
486
531
  }
532
+ const destructive = directive.directiveClass === "delete";
487
533
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "inline-flex items-center gap-2", children: [
488
534
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
489
535
  "button",
@@ -491,7 +537,8 @@ function ApplyDirectiveButton({
491
537
  type: "button",
492
538
  onClick: () => void handleApply(),
493
539
  disabled: state.status === "applying" || state.status === "asking" || items.length === 0,
494
- className: "inline-flex items-center gap-1 rounded-md border border-border bg-background px-2 py-1 text-xs font-medium hover:bg-accent disabled:opacity-50",
540
+ "data-apply-tone": destructive ? "destructive" : "default",
541
+ 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"}`,
495
542
  children: [
496
543
  state.status === "applying" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(LoaderIcon, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PlayIcon, { className: "h-3.5 w-3.5" }),
497
544
  state.status === "applying" ? `Applying${itemCount ? ` ${itemCount} ${itemCount === 1 ? "item" : "items"}` : ""}\u2026${elapsed ? ` ${elapsed}s` : ""}` : label
@@ -584,8 +631,41 @@ function getDirectiveRenderer(directive) {
584
631
  }
585
632
 
586
633
  // directives/SideEffectDirectiveCard.tsx
587
- var import_react5 = require("react");
634
+ var import_react6 = require("react");
588
635
  var import_content_ir5 = require("@ai-matrx/content-ir");
636
+
637
+ // directives/record-values.ts
638
+ var import_react5 = require("react");
639
+ function useRecordValues(read, ref) {
640
+ const key = ref && read ? `${ref.noun}:${ref.id}` : "";
641
+ const [result, setResult] = (0, import_react5.useState)({
642
+ key: "",
643
+ values: null,
644
+ status: "idle"
645
+ });
646
+ (0, import_react5.useEffect)(() => {
647
+ if (!key || !read) return;
648
+ const split = key.indexOf(":");
649
+ const target = { noun: key.slice(0, split), id: key.slice(split + 1) };
650
+ let live = true;
651
+ setResult({ key, values: null, status: "loading" });
652
+ read(target).then((values) => {
653
+ if (!live) return;
654
+ setResult({ key, values, status: values ? "ready" : "unavailable" });
655
+ }).catch(() => {
656
+ if (!live) return;
657
+ setResult({ key, values: null, status: "unavailable" });
658
+ });
659
+ return () => {
660
+ live = false;
661
+ };
662
+ }, [key, read]);
663
+ if (!key) return { values: null, status: "idle" };
664
+ if (result.key !== key) return { values: null, status: "loading" };
665
+ return { values: result.values, status: result.status };
666
+ }
667
+
668
+ // directives/SideEffectDirectiveCard.tsx
589
669
  var import_jsx_runtime7 = require("react/jsx-runtime");
590
670
  var ROWS_BEFORE_FOLD = 3;
591
671
  function ItemRow({
@@ -639,44 +719,73 @@ function ItemRow({
639
719
  host.renderCopy?.({ label: title, value: item, kind, size: "xs" })
640
720
  ] });
641
721
  }
642
- function TargetRow({
643
- directive,
644
- item,
645
- recordId
722
+ function DirectiveChangeList({
723
+ changes,
724
+ className
646
725
  }) {
647
- const host = useDirectiveHost();
648
- const fallback = recordFallbackName((0, import_content_ir5.nounLabel)(directive.noun, host.nouns), recordId);
649
- const changes = directive.directiveClass === "update" ? itemChanges(item) : [];
650
- const name = host.renderRecord ? host.renderRecord({ noun: directive.noun, id: recordId, fallback, context: "row" }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "truncate font-medium text-foreground", children: fallback });
651
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "py-1 text-xs", "data-directive-target": recordId, children: [
652
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex min-w-0 items-center gap-2", children: [
653
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "flex min-w-0 flex-1 items-center gap-1 px-1", children: name }),
654
- host.renderCopy?.({ label: fallback, value: item, size: "xs" })
655
- ] }),
656
- directive.directiveClass === "update" ? changes.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "px-1 text-[11px] text-muted-foreground", children: "Sets no fields \u2014 nothing would change." }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("ul", { className: "mt-0.5 space-y-0.5 px-1", children: changes.map((change) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
726
+ if (changes.length === 0) {
727
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: `text-[11px] text-muted-foreground${className ? ` ${className}` : ""}`, children: "Sets no fields \u2014 nothing would change." });
728
+ }
729
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("ul", { className: `space-y-0.5${className ? ` ${className}` : ""}`, "data-directive-changes": "", children: changes.map((change) => {
730
+ const before = change.before && !change.same ? change.before : null;
731
+ const was = before ? before.clears ? "empty" : before.value : null;
732
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
657
733
  "li",
658
734
  {
659
735
  className: "flex min-w-0 items-baseline gap-1.5 text-[11px]",
660
- title: `${change.label} \u2192 ${change.full}`,
736
+ title: before ? `${change.label}: ${before.clears ? "empty" : before.full} \u2192 ${change.full}` : `${change.label} \u2192 ${change.full}`,
737
+ "data-change-key": change.key,
661
738
  children: [
662
739
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "shrink-0 text-muted-foreground", children: change.label }),
740
+ was !== null ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
741
+ "span",
742
+ {
743
+ className: "min-w-0 max-w-[40%] shrink truncate text-muted-foreground line-through",
744
+ "data-change-before": "",
745
+ children: was
746
+ }
747
+ ) : null,
663
748
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { "aria-hidden": true, className: "shrink-0 text-muted-foreground", children: "\u2192" }),
664
749
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
665
750
  "span",
666
751
  {
667
752
  className: `min-w-0 truncate ${change.clears ? "italic text-muted-foreground" : "text-foreground"}`,
753
+ "data-change-after": "",
668
754
  children: change.value
669
755
  }
670
- )
756
+ ),
757
+ change.same ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "shrink-0 text-muted-foreground", children: "(no change)" }) : null
671
758
  ]
672
759
  },
673
760
  change.key
674
- )) }) : null
761
+ );
762
+ }) });
763
+ }
764
+ function TargetRow({
765
+ directive,
766
+ item,
767
+ recordId
768
+ }) {
769
+ const host = useDirectiveHost();
770
+ const fallback = recordFallbackName((0, import_content_ir5.nounLabel)(directive.noun, host.nouns), recordId);
771
+ const isUpdate = directive.directiveClass === "update";
772
+ const current = useRecordValues(
773
+ isUpdate ? host.readRecord : void 0,
774
+ isUpdate ? { noun: directive.noun, id: recordId } : null
775
+ );
776
+ const changes = isUpdate ? itemChanges(item, current.values) : [];
777
+ const name = host.renderRecord ? host.renderRecord({ noun: directive.noun, id: recordId, fallback, context: "row" }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "truncate font-medium text-foreground", children: fallback });
778
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "py-1 text-xs", "data-directive-target": recordId, children: [
779
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex min-w-0 items-center gap-2", children: [
780
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "flex min-w-0 flex-1 items-center gap-1 px-1", children: name }),
781
+ host.renderCopy?.({ label: fallback, value: item, size: "xs" })
782
+ ] }),
783
+ isUpdate ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DirectiveChangeList, { changes, className: "mt-0.5 px-1" }) : null
675
784
  ] });
676
785
  }
677
786
  function SideEffectDirectiveCard({ directive }) {
678
787
  const host = useDirectiveHost();
679
- const [expanded, setExpanded] = (0, import_react5.useState)(false);
788
+ const [expanded, setExpanded] = (0, import_react6.useState)(false);
680
789
  const items = directive.items;
681
790
  const total = items.length;
682
791
  const display = (0, import_content_ir5.directiveDisplay)(directive.directiveClass, directive.noun, host.nouns);
@@ -809,10 +918,10 @@ function KindInstanceRenderInner({
809
918
  className
810
919
  }) {
811
920
  const host = useContentIrHost();
812
- const [routingStatus, setRoutingStatus] = (0, import_react6.useState)(
921
+ const [routingStatus, setRoutingStatus] = (0, import_react7.useState)(
813
922
  () => kindIsRoutable(kind, host) ? "routable" : "checking"
814
923
  );
815
- (0, import_react6.useEffect)(() => {
924
+ (0, import_react7.useEffect)(() => {
816
925
  let cancelled = false;
817
926
  let warmed = false;
818
927
  const syncRoutingStatus = () => {
@@ -860,7 +969,7 @@ function KindInstanceRenderInner({
860
969
  }
861
970
 
862
971
  // react/ProvisionalKindBoundary.tsx
863
- var import_react7 = __toESM(require("react"), 1);
972
+ var import_react8 = __toESM(require("react"), 1);
864
973
 
865
974
  // route/partial-kind-route.ts
866
975
  var import_content_ir9 = require("@ai-matrx/content-ir");
@@ -874,7 +983,7 @@ function markKindPartialUnsafe(kind) {
874
983
 
875
984
  // react/ProvisionalKindBoundary.tsx
876
985
  var import_jsx_runtime10 = require("react/jsx-runtime");
877
- var ProvisionalKindBoundaryInner = class extends import_react7.default.Component {
986
+ var ProvisionalKindBoundaryInner = class extends import_react8.default.Component {
878
987
  state = { failed: false };
879
988
  static getDerivedStateFromError() {
880
989
  return { failed: true };
@@ -910,7 +1019,7 @@ function ProvisionalKindFrame({ children }) {
910
1019
  }
911
1020
 
912
1021
  // react/GenericStructuredView.tsx
913
- var import_react8 = require("react");
1022
+ var import_react9 = require("react");
914
1023
  var import_content_ir10 = require("@ai-matrx/content-ir");
915
1024
  var import_jsx_runtime11 = require("react/jsx-runtime");
916
1025
  function readStructuredValue(content, metadata) {