@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.d.cts CHANGED
@@ -1,9 +1,9 @@
1
- import { c as KindDefinitionSource, C as ContentIrHost } from './host-types-DVIaaGR_.cjs';
1
+ import { c as KindDefinitionSource, C as ContentIrHost } from './host-types-a0XEr5b3.cjs';
2
2
  import { d as ComponentResolutionSource } from './component-resolution.types-C1MzCf2s.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-DJfF35FD.cjs';
6
+ import './directive-host-C4TcPpxX.cjs';
7
7
 
8
8
  /**
9
9
  * The two registries this hook watches. Passed explicitly by a host whose
package/dist/react.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { c as KindDefinitionSource, C as ContentIrHost } from './host-types-9NfBeqrG.js';
1
+ import { c as KindDefinitionSource, C as ContentIrHost } from './host-types-CEc86R-v.js';
2
2
  import { d as ComponentResolutionSource } from './component-resolution.types-C1MzCf2s.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-DJfF35FD.js';
6
+ import './directive-host-C4TcPpxX.js';
7
7
 
8
8
  /**
9
9
  * The two registries this hook watches. Passed explicitly by a host whose
package/dist/react.js CHANGED
@@ -80,7 +80,7 @@ function useContentIrKindVersion(kind, sources) {
80
80
  }
81
81
 
82
82
  // react/KindInstanceRender.tsx
83
- import { useEffect as useEffect2, useState as useState3 } from "react";
83
+ import { useEffect as useEffect3, useState as useState4 } from "react";
84
84
  import {
85
85
  envelopeFromCompleteValue as envelopeFromCompleteValue2,
86
86
  IR_ENVELOPE_KEY,
@@ -144,11 +144,26 @@ function safeJson(value) {
144
144
  return String(value);
145
145
  }
146
146
  }
147
- function itemChanges(item) {
147
+ function sameValue(a, b) {
148
+ if (a === b) return true;
149
+ if (a === null || a === void 0 || b === null || b === void 0) {
150
+ const emptyA = a === null || a === void 0 || a === "";
151
+ const emptyB = b === null || b === void 0 || b === "";
152
+ return emptyA && emptyB;
153
+ }
154
+ if (typeof a === "object" || typeof b === "object") return safeJson(a) === safeJson(b);
155
+ return false;
156
+ }
157
+ function itemChanges(item, current) {
148
158
  const out = [];
149
159
  for (const [key, raw] of Object.entries(item)) {
150
160
  if (NOT_A_CHANGE.has(key)) continue;
151
- out.push({ key, label: fieldLabel(key), ...describeValue(raw) });
161
+ const change = { key, label: fieldLabel(key), ...describeValue(raw) };
162
+ if (current && Object.prototype.hasOwnProperty.call(current, key)) {
163
+ change.before = describeValue(current[key]);
164
+ change.same = sameValue(current[key], raw);
165
+ }
166
+ out.push(change);
152
167
  }
153
168
  return out;
154
169
  }
@@ -261,12 +276,23 @@ function ApplyDirectiveButton({
261
276
  const conversationId = scope.conversationId || void 0;
262
277
  const items = directive.items;
263
278
  const readsLedger = !!host.applyState && !!host.confirm && directive.parsed.executes && items.length > 0;
264
- const [state, setState] = useState(
265
- readsLedger ? { status: "checking" } : { status: "idle" }
266
- );
279
+ const initial = readsLedger ? { status: "checking" } : { status: "idle" };
280
+ const [state, setState] = useState(initial);
267
281
  const [elapsed, setElapsed] = useState(0);
268
282
  const [askingAgain, setAskingAgain] = useState(false);
269
283
  const startedAt = useRef(null);
284
+ const identity = JSON.stringify([directive.slug, items, conversationId ?? null]);
285
+ const [stateFor, setStateFor] = useState(identity);
286
+ if (stateFor !== identity) {
287
+ setStateFor(identity);
288
+ setState(initial);
289
+ setAskingAgain(false);
290
+ setElapsed(0);
291
+ }
292
+ const currentIdentity = useRef(identity);
293
+ useEffect(() => {
294
+ currentIdentity.current = identity;
295
+ }, [identity]);
270
296
  const nounText = nounLabel(directive.noun, host.nouns);
271
297
  const shellKey = readsLedger ? JSON.stringify([directive.slug, items, conversationId ?? null]) : "";
272
298
  const read = host.applyState;
@@ -289,7 +315,8 @@ function ApplyDirectiveButton({
289
315
  applied: shellItems.length,
290
316
  failed: 0,
291
317
  message: answer.message?.trim() ? answer.message.trim() : null,
292
- records: answer.records ?? []
318
+ records: answer.records ?? [],
319
+ copies: Math.max(1, answer.copies ?? 1)
293
320
  } : prev
294
321
  );
295
322
  } else if (answer.state === "in_flight" && polls < FINISH_POLLS_MAX) {
@@ -329,6 +356,9 @@ function ApplyDirectiveButton({
329
356
  async function handleApply(again = false) {
330
357
  if (!confirm) return;
331
358
  const before = state;
359
+ const askedFor = identity;
360
+ const stillHere = () => currentIdentity.current === askedFor;
361
+ const priorCopies = state.status === "applied" ? state.copies : 0;
332
362
  if (host.ask) {
333
363
  if (again) setAskingAgain(true);
334
364
  else setState({ status: "asking" });
@@ -341,6 +371,7 @@ function ApplyDirectiveButton({
341
371
  ...again ? { again: true } : {}
342
372
  });
343
373
  } catch (error) {
374
+ if (!stillHere()) return;
344
375
  setAskingAgain(false);
345
376
  setState({
346
377
  status: "error",
@@ -348,6 +379,7 @@ function ApplyDirectiveButton({
348
379
  });
349
380
  return;
350
381
  }
382
+ if (!stillHere()) return;
351
383
  setAskingAgain(false);
352
384
  if (!ok) {
353
385
  setState(again ? before : { status: "idle" });
@@ -361,15 +393,19 @@ function ApplyDirectiveButton({
361
393
  if (conversationId) shell.conversationId = conversationId;
362
394
  if (again) shell.force = true;
363
395
  const result = await confirm(shell);
396
+ if (!stillHere()) return;
364
397
  setState({
365
398
  status: "applied",
366
399
  applied: result.applied,
367
400
  failed: result.failed,
368
401
  message: result.message?.trim() ? result.message.trim() : null,
369
- records: result.records ?? []
402
+ records: result.records ?? [],
403
+ // "Run again" made one more; a first apply (or a replay of one) is one.
404
+ copies: again ? Math.max(1, priorCopies) + 1 : Math.max(1, priorCopies)
370
405
  });
371
406
  onApplied?.();
372
407
  } catch (error) {
408
+ if (!stillHere()) return;
373
409
  setState({
374
410
  status: "error",
375
411
  message: error instanceof Error && error.message ? error.message : "That couldn't be applied just now. Please try again."
@@ -394,6 +430,15 @@ function ApplyDirectiveButton({
394
430
  bad && !state.message ? ` \xB7 ${state.failed} failed` : ""
395
431
  ] })
396
432
  ] }),
433
+ state.copies > 1 ? /* @__PURE__ */ jsx4(
434
+ "span",
435
+ {
436
+ className: "shrink-0 text-muted-foreground",
437
+ "data-apply-copies": state.copies,
438
+ title: records.length > 0 ? directive.directiveClass === "create" ? "Links to the latest copy." : "Links to the latest run." : void 0,
439
+ children: directive.directiveClass === "create" ? `${state.copies} copies` : `Ran ${state.copies} times`
440
+ }
441
+ ) : null,
397
442
  records.map((record) => /* @__PURE__ */ jsx4("span", { className: "inline-flex min-w-0 items-center", children: host.renderRecord?.({
398
443
  ...record,
399
444
  fallback: recordFallbackName(label2, record.id),
@@ -444,6 +489,7 @@ function ApplyDirectiveButton({
444
489
  }
445
490
  );
446
491
  }
492
+ const destructive = directive.directiveClass === "delete";
447
493
  return /* @__PURE__ */ jsxs2("span", { className: "inline-flex items-center gap-2", children: [
448
494
  /* @__PURE__ */ jsxs2(
449
495
  "button",
@@ -451,7 +497,8 @@ function ApplyDirectiveButton({
451
497
  type: "button",
452
498
  onClick: () => void handleApply(),
453
499
  disabled: state.status === "applying" || state.status === "asking" || items.length === 0,
454
- 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",
500
+ "data-apply-tone": destructive ? "destructive" : "default",
501
+ 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"}`,
455
502
  children: [
456
503
  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" }),
457
504
  state.status === "applying" ? `Applying${itemCount ? ` ${itemCount} ${itemCount === 1 ? "item" : "items"}` : ""}\u2026${elapsed ? ` ${elapsed}s` : ""}` : label
@@ -546,7 +593,7 @@ function getDirectiveRenderer(directive) {
546
593
  }
547
594
 
548
595
  // directives/SideEffectDirectiveCard.tsx
549
- import { useState as useState2 } from "react";
596
+ import { useState as useState3 } from "react";
550
597
  import {
551
598
  asKindInstance,
552
599
  directiveDisplay as directiveDisplay3,
@@ -556,6 +603,39 @@ import {
556
603
  nounLabel as nounLabel2,
557
604
  nounTitleColumn
558
605
  } from "@ai-matrx/content-ir";
606
+
607
+ // directives/record-values.ts
608
+ import { useEffect as useEffect2, useState as useState2 } from "react";
609
+ function useRecordValues(read, ref) {
610
+ const key = ref && read ? `${ref.noun}:${ref.id}` : "";
611
+ const [result, setResult] = useState2({
612
+ key: "",
613
+ values: null,
614
+ status: "idle"
615
+ });
616
+ useEffect2(() => {
617
+ if (!key || !read) return;
618
+ const split = key.indexOf(":");
619
+ const target = { noun: key.slice(0, split), id: key.slice(split + 1) };
620
+ let live = true;
621
+ setResult({ key, values: null, status: "loading" });
622
+ read(target).then((values) => {
623
+ if (!live) return;
624
+ setResult({ key, values, status: values ? "ready" : "unavailable" });
625
+ }).catch(() => {
626
+ if (!live) return;
627
+ setResult({ key, values: null, status: "unavailable" });
628
+ });
629
+ return () => {
630
+ live = false;
631
+ };
632
+ }, [key, read]);
633
+ if (!key) return { values: null, status: "idle" };
634
+ if (result.key !== key) return { values: null, status: "loading" };
635
+ return { values: result.values, status: result.status };
636
+ }
637
+
638
+ // directives/SideEffectDirectiveCard.tsx
559
639
  import { Fragment, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
560
640
  var ROWS_BEFORE_FOLD = 3;
561
641
  function ItemRow({
@@ -609,44 +689,73 @@ function ItemRow({
609
689
  host.renderCopy?.({ label: title, value: item, kind, size: "xs" })
610
690
  ] });
611
691
  }
612
- function TargetRow({
613
- directive,
614
- item,
615
- recordId
692
+ function DirectiveChangeList({
693
+ changes,
694
+ className
616
695
  }) {
617
- const host = useDirectiveHost();
618
- const fallback = recordFallbackName(nounLabel2(directive.noun, host.nouns), recordId);
619
- const changes = directive.directiveClass === "update" ? itemChanges(item) : [];
620
- 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 });
621
- return /* @__PURE__ */ jsxs5("div", { className: "py-1 text-xs", "data-directive-target": recordId, children: [
622
- /* @__PURE__ */ jsxs5("div", { className: "flex min-w-0 items-center gap-2", children: [
623
- /* @__PURE__ */ jsx7("span", { className: "flex min-w-0 flex-1 items-center gap-1 px-1", children: name }),
624
- host.renderCopy?.({ label: fallback, value: item, size: "xs" })
625
- ] }),
626
- directive.directiveClass === "update" ? changes.length === 0 ? /* @__PURE__ */ jsx7("p", { className: "px-1 text-[11px] text-muted-foreground", children: "Sets no fields \u2014 nothing would change." }) : /* @__PURE__ */ jsx7("ul", { className: "mt-0.5 space-y-0.5 px-1", children: changes.map((change) => /* @__PURE__ */ jsxs5(
696
+ if (changes.length === 0) {
697
+ return /* @__PURE__ */ jsx7("p", { className: `text-[11px] text-muted-foreground${className ? ` ${className}` : ""}`, children: "Sets no fields \u2014 nothing would change." });
698
+ }
699
+ return /* @__PURE__ */ jsx7("ul", { className: `space-y-0.5${className ? ` ${className}` : ""}`, "data-directive-changes": "", children: changes.map((change) => {
700
+ const before = change.before && !change.same ? change.before : null;
701
+ const was = before ? before.clears ? "empty" : before.value : null;
702
+ return /* @__PURE__ */ jsxs5(
627
703
  "li",
628
704
  {
629
705
  className: "flex min-w-0 items-baseline gap-1.5 text-[11px]",
630
- title: `${change.label} \u2192 ${change.full}`,
706
+ title: before ? `${change.label}: ${before.clears ? "empty" : before.full} \u2192 ${change.full}` : `${change.label} \u2192 ${change.full}`,
707
+ "data-change-key": change.key,
631
708
  children: [
632
709
  /* @__PURE__ */ jsx7("span", { className: "shrink-0 text-muted-foreground", children: change.label }),
710
+ was !== null ? /* @__PURE__ */ jsx7(
711
+ "span",
712
+ {
713
+ className: "min-w-0 max-w-[40%] shrink truncate text-muted-foreground line-through",
714
+ "data-change-before": "",
715
+ children: was
716
+ }
717
+ ) : null,
633
718
  /* @__PURE__ */ jsx7("span", { "aria-hidden": true, className: "shrink-0 text-muted-foreground", children: "\u2192" }),
634
719
  /* @__PURE__ */ jsx7(
635
720
  "span",
636
721
  {
637
722
  className: `min-w-0 truncate ${change.clears ? "italic text-muted-foreground" : "text-foreground"}`,
723
+ "data-change-after": "",
638
724
  children: change.value
639
725
  }
640
- )
726
+ ),
727
+ change.same ? /* @__PURE__ */ jsx7("span", { className: "shrink-0 text-muted-foreground", children: "(no change)" }) : null
641
728
  ]
642
729
  },
643
730
  change.key
644
- )) }) : null
731
+ );
732
+ }) });
733
+ }
734
+ function TargetRow({
735
+ directive,
736
+ item,
737
+ recordId
738
+ }) {
739
+ const host = useDirectiveHost();
740
+ const fallback = recordFallbackName(nounLabel2(directive.noun, host.nouns), recordId);
741
+ const isUpdate = directive.directiveClass === "update";
742
+ const current = useRecordValues(
743
+ isUpdate ? host.readRecord : void 0,
744
+ isUpdate ? { noun: directive.noun, id: recordId } : null
745
+ );
746
+ const changes = isUpdate ? itemChanges(item, current.values) : [];
747
+ 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 });
748
+ return /* @__PURE__ */ jsxs5("div", { className: "py-1 text-xs", "data-directive-target": recordId, children: [
749
+ /* @__PURE__ */ jsxs5("div", { className: "flex min-w-0 items-center gap-2", children: [
750
+ /* @__PURE__ */ jsx7("span", { className: "flex min-w-0 flex-1 items-center gap-1 px-1", children: name }),
751
+ host.renderCopy?.({ label: fallback, value: item, size: "xs" })
752
+ ] }),
753
+ isUpdate ? /* @__PURE__ */ jsx7(DirectiveChangeList, { changes, className: "mt-0.5 px-1" }) : null
645
754
  ] });
646
755
  }
647
756
  function SideEffectDirectiveCard({ directive }) {
648
757
  const host = useDirectiveHost();
649
- const [expanded, setExpanded] = useState2(false);
758
+ const [expanded, setExpanded] = useState3(false);
650
759
  const items = directive.items;
651
760
  const total = items.length;
652
761
  const display = directiveDisplay3(directive.directiveClass, directive.noun, host.nouns);
@@ -784,10 +893,10 @@ function KindInstanceRenderInner({
784
893
  className
785
894
  }) {
786
895
  const host = useContentIrHost();
787
- const [routingStatus, setRoutingStatus] = useState3(
896
+ const [routingStatus, setRoutingStatus] = useState4(
788
897
  () => kindIsRoutable(kind, host) ? "routable" : "checking"
789
898
  );
790
- useEffect2(() => {
899
+ useEffect3(() => {
791
900
  let cancelled = false;
792
901
  let warmed = false;
793
902
  const syncRoutingStatus = () => {