@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/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.16.0 — 2026-10-02
4
+
5
+ The G3 review of the action cards (nightly clone). Guard: `__tests__/directive-card-g3.test.tsx`
6
+ (7 cases, every one red on 0.15.2).
7
+
8
+ - **An update says old → new.** New host seam `DirectiveHost.readRecord({noun, id})` →
9
+ the record's current fields (or null). `itemChanges(item, current)` now carries `before`
10
+ and `same`; the new `DirectiveChangeList` draws "Label old → new" (old struck through,
11
+ "empty" for a blank field, "(no change)" when it already holds the value). ONE component
12
+ for the card row and a host's confirm dialog. `useRecordValues(read, ref)` reads it.
13
+ - **A Delete card's Apply looks destructive** (`data-apply-tone="destructive"`), not the
14
+ neutral grey of a create's.
15
+ - **Run again shows the copies.** `DirectiveApplyState.copies` (1 + every Run again) and
16
+ `records` = the LATEST copy's. The tally says "2 copies" (create) / "Ran 2 times" and
17
+ links the newest copy; a yes to Run again counts one more.
18
+ - **The card's state belongs to the block, not its slot.** A host that keys blocks by
19
+ position handed one card instance a different directive when a block was inserted
20
+ above it; an applied Update card flipped back to "Apply" until a reload. The Apply
21
+ control now resets and re-reads the ledger whenever the block it draws changes, and an
22
+ apply that finishes after its block moved away never lands on the new one.
23
+
24
+ Consumer action: pass `readRecord` on the directive host for old → new; a host's own
25
+ confirm dialog draws changes through `DirectiveChangeList`; an `applyState` answer passes
26
+ `copies` when its ledger knows it.
27
+
3
28
  ## 0.15.2 — 2026-10-02
4
29
 
5
30
  - While "Run again" asks, the tally stays on screen (only its own button goes
@@ -32,8 +32,17 @@ interface DirectiveApplyState {
32
32
  state: "not_applied" | "in_flight" | "applied";
33
33
  /** The ledger row's sentence ("Created task."), never composed client-side. */
34
34
  message?: string | null;
35
- /** The records the original apply wrote — the tally's doors. */
35
+ /**
36
+ * The records the LATEST apply wrote — the tally's doors. After "Run again"
37
+ * that is the newest copy, never the first.
38
+ */
36
39
  records?: DirectiveRecordRef[];
40
+ /**
41
+ * How many times the ledger holds this block as applied: 1 for the original,
42
+ * one more for every "Run again". Absent → 1. A create that ran twice made
43
+ * two records, and the card says so (reviewer, 2026-10-02).
44
+ */
45
+ copies?: number;
37
46
  }
38
47
  /** One record a directive names: the noun it is, and its id. */
39
48
  interface DirectiveRecordRef {
@@ -132,6 +141,14 @@ interface DirectiveHost {
132
141
  * is idempotent. Absent → the card starts at Apply, as before.
133
142
  */
134
143
  applyState?: (shell: DirectiveShell) => Promise<DirectiveApplyState>;
144
+ /**
145
+ * Read the CURRENT values of ONE existing record, so an update says what it
146
+ * replaces — "Status open → done", never just "→ done" (reviewer,
147
+ * 2026-10-02). Resolve `null` when the record cannot be read (no access, no
148
+ * such record, a noun the host cannot read); reject only for a real failure.
149
+ * Absent → an update row shows the new values alone, as before.
150
+ */
151
+ readRecord?: (ref: DirectiveRecordRef) => Promise<Record<string, unknown> | null>;
135
152
  /**
136
153
  * Draw ONE existing record by its live name, as a way to open it. Used on
137
154
  * update/delete rows (the record that will change), in the host's own ask
@@ -176,4 +193,4 @@ declare function DirectiveHostProvider({ host, children }: DirectiveHostProvider
176
193
  */
177
194
  declare function useDirectiveHost(): DirectiveHost;
178
195
 
179
- export { type DirectiveApplyResult as D, type DirectiveApplyState as a, type DirectiveAskRequest as b, type DirectiveCopyProps as c, type DirectiveHost as d, DirectiveHostProvider as e, type DirectiveHostProviderProps as f, type DirectiveOpenItemOptions as g, type DirectiveRecordProps as h, type DirectiveRecordRef as i, type DirectiveScope as j, DirectiveScopeProvider as k, type DirectiveShell as l, useDirectiveScope as m, useDirectiveHost as u };
196
+ export { type DirectiveRecordRef as D, type DirectiveApplyResult as a, type DirectiveApplyState as b, type DirectiveAskRequest as c, type DirectiveCopyProps as d, type DirectiveHost as e, DirectiveHostProvider as f, type DirectiveHostProviderProps as g, type DirectiveOpenItemOptions as h, type DirectiveRecordProps as i, type DirectiveScope as j, DirectiveScopeProvider as k, type DirectiveShell as l, useDirectiveScope as m, useDirectiveHost as u };
@@ -32,8 +32,17 @@ interface DirectiveApplyState {
32
32
  state: "not_applied" | "in_flight" | "applied";
33
33
  /** The ledger row's sentence ("Created task."), never composed client-side. */
34
34
  message?: string | null;
35
- /** The records the original apply wrote — the tally's doors. */
35
+ /**
36
+ * The records the LATEST apply wrote — the tally's doors. After "Run again"
37
+ * that is the newest copy, never the first.
38
+ */
36
39
  records?: DirectiveRecordRef[];
40
+ /**
41
+ * How many times the ledger holds this block as applied: 1 for the original,
42
+ * one more for every "Run again". Absent → 1. A create that ran twice made
43
+ * two records, and the card says so (reviewer, 2026-10-02).
44
+ */
45
+ copies?: number;
37
46
  }
38
47
  /** One record a directive names: the noun it is, and its id. */
39
48
  interface DirectiveRecordRef {
@@ -132,6 +141,14 @@ interface DirectiveHost {
132
141
  * is idempotent. Absent → the card starts at Apply, as before.
133
142
  */
134
143
  applyState?: (shell: DirectiveShell) => Promise<DirectiveApplyState>;
144
+ /**
145
+ * Read the CURRENT values of ONE existing record, so an update says what it
146
+ * replaces — "Status open → done", never just "→ done" (reviewer,
147
+ * 2026-10-02). Resolve `null` when the record cannot be read (no access, no
148
+ * such record, a noun the host cannot read); reject only for a real failure.
149
+ * Absent → an update row shows the new values alone, as before.
150
+ */
151
+ readRecord?: (ref: DirectiveRecordRef) => Promise<Record<string, unknown> | null>;
135
152
  /**
136
153
  * Draw ONE existing record by its live name, as a way to open it. Used on
137
154
  * update/delete rows (the record that will change), in the host's own ask
@@ -176,4 +193,4 @@ declare function DirectiveHostProvider({ host, children }: DirectiveHostProvider
176
193
  */
177
194
  declare function useDirectiveHost(): DirectiveHost;
178
195
 
179
- export { type DirectiveApplyResult as D, type DirectiveApplyState as a, type DirectiveAskRequest as b, type DirectiveCopyProps as c, type DirectiveHost as d, DirectiveHostProvider as e, type DirectiveHostProviderProps as f, type DirectiveOpenItemOptions as g, type DirectiveRecordProps as h, type DirectiveRecordRef as i, type DirectiveScope as j, DirectiveScopeProvider as k, type DirectiveShell as l, useDirectiveScope as m, useDirectiveHost as u };
196
+ export { type DirectiveRecordRef as D, type DirectiveApplyResult as a, type DirectiveApplyState as b, type DirectiveAskRequest as c, type DirectiveCopyProps as d, type DirectiveHost as e, DirectiveHostProvider as f, type DirectiveHostProviderProps as g, type DirectiveOpenItemOptions as h, type DirectiveRecordProps as i, type DirectiveScope as j, DirectiveScopeProvider as k, type DirectiveShell as l, useDirectiveScope as m, useDirectiveHost as u };
@@ -23,6 +23,7 @@ var directives_exports = {};
23
23
  __export(directives_exports, {
24
24
  ApplyDirectiveButton: () => ApplyDirectiveButton,
25
25
  CHANGE_VALUE_MAX: () => CHANGE_VALUE_MAX,
26
+ DirectiveChangeList: () => DirectiveChangeList,
26
27
  DirectiveFallbackCard: () => DirectiveFallbackCard,
27
28
  DirectiveHostProvider: () => DirectiveHostProvider,
28
29
  DirectiveProvisionalCard: () => DirectiveProvisionalCard,
@@ -30,6 +31,7 @@ __export(directives_exports, {
30
31
  DirectiveScopeProvider: () => DirectiveScopeProvider,
31
32
  SideEffectDirectiveCard: () => SideEffectDirectiveCard,
32
33
  TARGETING_CLASSES: () => TARGETING_CLASSES,
34
+ describeChangeValue: () => describeChangeValue,
33
35
  fieldLabel: () => fieldLabel,
34
36
  getDirectiveRenderer: () => getDirectiveRenderer,
35
37
  itemChanges: () => itemChanges,
@@ -39,7 +41,8 @@ __export(directives_exports, {
39
41
  registerDirectiveRenderer: () => registerDirectiveRenderer,
40
42
  resetDirectiveRenderers: () => resetDirectiveRenderers,
41
43
  useDirectiveHost: () => useDirectiveHost,
42
- useDirectiveScope: () => useDirectiveScope
44
+ useDirectiveScope: () => useDirectiveScope,
45
+ useRecordValues: () => useRecordValues
43
46
  });
44
47
  module.exports = __toCommonJS(directives_exports);
45
48
 
@@ -107,6 +110,9 @@ function recordFallbackName(nounLabel3, id) {
107
110
  const short = (0, import_uuid.isUuidShape)(id) ? id.slice(0, 8) : id.length > 12 ? `${id.slice(0, 12)}\u2026` : id;
108
111
  return `${nounLabel3} ${short}`;
109
112
  }
113
+ function describeChangeValue(value) {
114
+ return describeValue(value);
115
+ }
110
116
  function describeValue(value) {
111
117
  if (value === null || value === void 0) return { value: "cleared", full: "cleared", clears: true };
112
118
  if (typeof value === "string") {
@@ -135,11 +141,26 @@ function safeJson(value) {
135
141
  return String(value);
136
142
  }
137
143
  }
138
- function itemChanges(item) {
144
+ function sameValue(a, b) {
145
+ if (a === b) return true;
146
+ if (a === null || a === void 0 || b === null || b === void 0) {
147
+ const emptyA = a === null || a === void 0 || a === "";
148
+ const emptyB = b === null || b === void 0 || b === "";
149
+ return emptyA && emptyB;
150
+ }
151
+ if (typeof a === "object" || typeof b === "object") return safeJson(a) === safeJson(b);
152
+ return false;
153
+ }
154
+ function itemChanges(item, current) {
139
155
  const out = [];
140
156
  for (const [key, raw] of Object.entries(item)) {
141
157
  if (NOT_A_CHANGE.has(key)) continue;
142
- out.push({ key, label: fieldLabel(key), ...describeValue(raw) });
158
+ const change = { key, label: fieldLabel(key), ...describeValue(raw) };
159
+ if (current && Object.prototype.hasOwnProperty.call(current, key)) {
160
+ change.before = describeValue(current[key]);
161
+ change.same = sameValue(current[key], raw);
162
+ }
163
+ out.push(change);
143
164
  }
144
165
  return out;
145
166
  }
@@ -252,12 +273,23 @@ function ApplyDirectiveButton({
252
273
  const conversationId = scope.conversationId || void 0;
253
274
  const items = directive.items;
254
275
  const readsLedger = !!host.applyState && !!host.confirm && directive.parsed.executes && items.length > 0;
255
- const [state, setState] = (0, import_react2.useState)(
256
- readsLedger ? { status: "checking" } : { status: "idle" }
257
- );
276
+ const initial = readsLedger ? { status: "checking" } : { status: "idle" };
277
+ const [state, setState] = (0, import_react2.useState)(initial);
258
278
  const [elapsed, setElapsed] = (0, import_react2.useState)(0);
259
279
  const [askingAgain, setAskingAgain] = (0, import_react2.useState)(false);
260
280
  const startedAt = (0, import_react2.useRef)(null);
281
+ const identity = JSON.stringify([directive.slug, items, conversationId ?? null]);
282
+ const [stateFor, setStateFor] = (0, import_react2.useState)(identity);
283
+ if (stateFor !== identity) {
284
+ setStateFor(identity);
285
+ setState(initial);
286
+ setAskingAgain(false);
287
+ setElapsed(0);
288
+ }
289
+ const currentIdentity = (0, import_react2.useRef)(identity);
290
+ (0, import_react2.useEffect)(() => {
291
+ currentIdentity.current = identity;
292
+ }, [identity]);
261
293
  const nounText = (0, import_content_ir2.nounLabel)(directive.noun, host.nouns);
262
294
  const shellKey = readsLedger ? JSON.stringify([directive.slug, items, conversationId ?? null]) : "";
263
295
  const read = host.applyState;
@@ -280,7 +312,8 @@ function ApplyDirectiveButton({
280
312
  applied: shellItems.length,
281
313
  failed: 0,
282
314
  message: answer.message?.trim() ? answer.message.trim() : null,
283
- records: answer.records ?? []
315
+ records: answer.records ?? [],
316
+ copies: Math.max(1, answer.copies ?? 1)
284
317
  } : prev
285
318
  );
286
319
  } else if (answer.state === "in_flight" && polls < FINISH_POLLS_MAX) {
@@ -320,6 +353,9 @@ function ApplyDirectiveButton({
320
353
  async function handleApply(again = false) {
321
354
  if (!confirm) return;
322
355
  const before = state;
356
+ const askedFor = identity;
357
+ const stillHere = () => currentIdentity.current === askedFor;
358
+ const priorCopies = state.status === "applied" ? state.copies : 0;
323
359
  if (host.ask) {
324
360
  if (again) setAskingAgain(true);
325
361
  else setState({ status: "asking" });
@@ -332,6 +368,7 @@ function ApplyDirectiveButton({
332
368
  ...again ? { again: true } : {}
333
369
  });
334
370
  } catch (error) {
371
+ if (!stillHere()) return;
335
372
  setAskingAgain(false);
336
373
  setState({
337
374
  status: "error",
@@ -339,6 +376,7 @@ function ApplyDirectiveButton({
339
376
  });
340
377
  return;
341
378
  }
379
+ if (!stillHere()) return;
342
380
  setAskingAgain(false);
343
381
  if (!ok) {
344
382
  setState(again ? before : { status: "idle" });
@@ -352,15 +390,19 @@ function ApplyDirectiveButton({
352
390
  if (conversationId) shell.conversationId = conversationId;
353
391
  if (again) shell.force = true;
354
392
  const result = await confirm(shell);
393
+ if (!stillHere()) return;
355
394
  setState({
356
395
  status: "applied",
357
396
  applied: result.applied,
358
397
  failed: result.failed,
359
398
  message: result.message?.trim() ? result.message.trim() : null,
360
- records: result.records ?? []
399
+ records: result.records ?? [],
400
+ // "Run again" made one more; a first apply (or a replay of one) is one.
401
+ copies: again ? Math.max(1, priorCopies) + 1 : Math.max(1, priorCopies)
361
402
  });
362
403
  onApplied?.();
363
404
  } catch (error) {
405
+ if (!stillHere()) return;
364
406
  setState({
365
407
  status: "error",
366
408
  message: error instanceof Error && error.message ? error.message : "That couldn't be applied just now. Please try again."
@@ -385,6 +427,15 @@ function ApplyDirectiveButton({
385
427
  bad && !state.message ? ` \xB7 ${state.failed} failed` : ""
386
428
  ] })
387
429
  ] }),
430
+ state.copies > 1 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
431
+ "span",
432
+ {
433
+ className: "shrink-0 text-muted-foreground",
434
+ "data-apply-copies": state.copies,
435
+ title: records.length > 0 ? directive.directiveClass === "create" ? "Links to the latest copy." : "Links to the latest run." : void 0,
436
+ children: directive.directiveClass === "create" ? `${state.copies} copies` : `Ran ${state.copies} times`
437
+ }
438
+ ) : null,
388
439
  records.map((record) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "inline-flex min-w-0 items-center", children: host.renderRecord?.({
389
440
  ...record,
390
441
  fallback: recordFallbackName(label2, record.id),
@@ -435,6 +486,7 @@ function ApplyDirectiveButton({
435
486
  }
436
487
  );
437
488
  }
489
+ const destructive = directive.directiveClass === "delete";
438
490
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "inline-flex items-center gap-2", children: [
439
491
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
440
492
  "button",
@@ -442,7 +494,8 @@ function ApplyDirectiveButton({
442
494
  type: "button",
443
495
  onClick: () => void handleApply(),
444
496
  disabled: state.status === "applying" || state.status === "asking" || items.length === 0,
445
- 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",
497
+ "data-apply-tone": destructive ? "destructive" : "default",
498
+ 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"}`,
446
499
  children: [
447
500
  state.status === "applying" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(LoaderIcon, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PlayIcon, { className: "h-3.5 w-3.5" }),
448
501
  state.status === "applying" ? `Applying${itemCount ? ` ${itemCount} ${itemCount === 1 ? "item" : "items"}` : ""}\u2026${elapsed ? ` ${elapsed}s` : ""}` : label
@@ -457,8 +510,41 @@ function ApplyDirectiveButton({
457
510
  }
458
511
 
459
512
  // directives/SideEffectDirectiveCard.tsx
460
- var import_react3 = require("react");
513
+ var import_react4 = require("react");
461
514
  var import_content_ir3 = require("@ai-matrx/content-ir");
515
+
516
+ // directives/record-values.ts
517
+ var import_react3 = require("react");
518
+ function useRecordValues(read, ref) {
519
+ const key = ref && read ? `${ref.noun}:${ref.id}` : "";
520
+ const [result, setResult] = (0, import_react3.useState)({
521
+ key: "",
522
+ values: null,
523
+ status: "idle"
524
+ });
525
+ (0, import_react3.useEffect)(() => {
526
+ if (!key || !read) return;
527
+ const split = key.indexOf(":");
528
+ const target = { noun: key.slice(0, split), id: key.slice(split + 1) };
529
+ let live = true;
530
+ setResult({ key, values: null, status: "loading" });
531
+ read(target).then((values) => {
532
+ if (!live) return;
533
+ setResult({ key, values, status: values ? "ready" : "unavailable" });
534
+ }).catch(() => {
535
+ if (!live) return;
536
+ setResult({ key, values: null, status: "unavailable" });
537
+ });
538
+ return () => {
539
+ live = false;
540
+ };
541
+ }, [key, read]);
542
+ if (!key) return { values: null, status: "idle" };
543
+ if (result.key !== key) return { values: null, status: "loading" };
544
+ return { values: result.values, status: result.status };
545
+ }
546
+
547
+ // directives/SideEffectDirectiveCard.tsx
462
548
  var import_jsx_runtime4 = require("react/jsx-runtime");
463
549
  var ROWS_BEFORE_FOLD = 3;
464
550
  function ItemRow({
@@ -512,44 +598,73 @@ function ItemRow({
512
598
  host.renderCopy?.({ label: title, value: item, kind, size: "xs" })
513
599
  ] });
514
600
  }
515
- function TargetRow({
516
- directive,
517
- item,
518
- recordId
601
+ function DirectiveChangeList({
602
+ changes,
603
+ className
519
604
  }) {
520
- const host = useDirectiveHost();
521
- const fallback = recordFallbackName((0, import_content_ir3.nounLabel)(directive.noun, host.nouns), recordId);
522
- const changes = directive.directiveClass === "update" ? itemChanges(item) : [];
523
- const name = host.renderRecord ? host.renderRecord({ noun: directive.noun, id: recordId, fallback, context: "row" }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "truncate font-medium text-foreground", children: fallback });
524
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "py-1 text-xs", "data-directive-target": recordId, children: [
525
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex min-w-0 items-center gap-2", children: [
526
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "flex min-w-0 flex-1 items-center gap-1 px-1", children: name }),
527
- host.renderCopy?.({ label: fallback, value: item, size: "xs" })
528
- ] }),
529
- directive.directiveClass === "update" ? changes.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "px-1 text-[11px] text-muted-foreground", children: "Sets no fields \u2014 nothing would change." }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: "mt-0.5 space-y-0.5 px-1", children: changes.map((change) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
605
+ if (changes.length === 0) {
606
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: `text-[11px] text-muted-foreground${className ? ` ${className}` : ""}`, children: "Sets no fields \u2014 nothing would change." });
607
+ }
608
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: `space-y-0.5${className ? ` ${className}` : ""}`, "data-directive-changes": "", children: changes.map((change) => {
609
+ const before = change.before && !change.same ? change.before : null;
610
+ const was = before ? before.clears ? "empty" : before.value : null;
611
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
530
612
  "li",
531
613
  {
532
614
  className: "flex min-w-0 items-baseline gap-1.5 text-[11px]",
533
- title: `${change.label} \u2192 ${change.full}`,
615
+ title: before ? `${change.label}: ${before.clears ? "empty" : before.full} \u2192 ${change.full}` : `${change.label} \u2192 ${change.full}`,
616
+ "data-change-key": change.key,
534
617
  children: [
535
618
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "shrink-0 text-muted-foreground", children: change.label }),
619
+ was !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
620
+ "span",
621
+ {
622
+ className: "min-w-0 max-w-[40%] shrink truncate text-muted-foreground line-through",
623
+ "data-change-before": "",
624
+ children: was
625
+ }
626
+ ) : null,
536
627
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { "aria-hidden": true, className: "shrink-0 text-muted-foreground", children: "\u2192" }),
537
628
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
538
629
  "span",
539
630
  {
540
631
  className: `min-w-0 truncate ${change.clears ? "italic text-muted-foreground" : "text-foreground"}`,
632
+ "data-change-after": "",
541
633
  children: change.value
542
634
  }
543
- )
635
+ ),
636
+ change.same ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "shrink-0 text-muted-foreground", children: "(no change)" }) : null
544
637
  ]
545
638
  },
546
639
  change.key
547
- )) }) : null
640
+ );
641
+ }) });
642
+ }
643
+ function TargetRow({
644
+ directive,
645
+ item,
646
+ recordId
647
+ }) {
648
+ const host = useDirectiveHost();
649
+ const fallback = recordFallbackName((0, import_content_ir3.nounLabel)(directive.noun, host.nouns), recordId);
650
+ const isUpdate = directive.directiveClass === "update";
651
+ const current = useRecordValues(
652
+ isUpdate ? host.readRecord : void 0,
653
+ isUpdate ? { noun: directive.noun, id: recordId } : null
654
+ );
655
+ const changes = isUpdate ? itemChanges(item, current.values) : [];
656
+ const name = host.renderRecord ? host.renderRecord({ noun: directive.noun, id: recordId, fallback, context: "row" }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "truncate font-medium text-foreground", children: fallback });
657
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "py-1 text-xs", "data-directive-target": recordId, children: [
658
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex min-w-0 items-center gap-2", children: [
659
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "flex min-w-0 flex-1 items-center gap-1 px-1", children: name }),
660
+ host.renderCopy?.({ label: fallback, value: item, size: "xs" })
661
+ ] }),
662
+ isUpdate ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(DirectiveChangeList, { changes, className: "mt-0.5 px-1" }) : null
548
663
  ] });
549
664
  }
550
665
  function SideEffectDirectiveCard({ directive }) {
551
666
  const host = useDirectiveHost();
552
- const [expanded, setExpanded] = (0, import_react3.useState)(false);
667
+ const [expanded, setExpanded] = (0, import_react4.useState)(false);
553
668
  const items = directive.items;
554
669
  const total = items.length;
555
670
  const display = (0, import_content_ir3.directiveDisplay)(directive.directiveClass, directive.noun, host.nouns);