@ai-matrx/content-ir-react 0.15.1 → 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.
@@ -1,4 +1,5 @@
1
- export { D as DirectiveApplyResult, a as DirectiveApplyState, b as DirectiveAskRequest, c as DirectiveCopyProps, d as DirectiveHost, e as DirectiveHostProvider, f as DirectiveHostProviderProps, g as DirectiveOpenItemOptions, h as DirectiveRecordProps, i as DirectiveRecordRef, j as DirectiveScope, k as DirectiveScopeProvider, l as DirectiveShell, u as useDirectiveHost, m as useDirectiveScope } from './directive-host-DJfF35FD.js';
1
+ import { D as DirectiveRecordRef } from './directive-host-C4TcPpxX.js';
2
+ export { a as DirectiveApplyResult, b as DirectiveApplyState, c as DirectiveAskRequest, d as DirectiveCopyProps, e as DirectiveHost, f as DirectiveHostProvider, g as DirectiveHostProviderProps, h as DirectiveOpenItemOptions, i as DirectiveRecordProps, j as DirectiveScope, k as DirectiveScopeProvider, l as DirectiveShell, u as useDirectiveHost, m as useDirectiveScope } from './directive-host-C4TcPpxX.js';
2
3
  import * as react from 'react';
3
4
  import { ComponentType } from 'react';
4
5
  import { DecodedDirective, DirectiveClass } from '@ai-matrx/content-ir';
@@ -48,9 +49,75 @@ interface ApplyDirectiveButtonProps {
48
49
  }
49
50
  declare function ApplyDirectiveButton({ directive, label, itemCount, onApplied, }: ApplyDirectiveButtonProps): react.JSX.Element | null;
50
51
 
52
+ /**
53
+ * WHAT A DIRECTIVE ITEM WILL DO TO AN EXISTING RECORD — for the card, the
54
+ * confirm dialog, and anything else that must say it before the click.
55
+ *
56
+ * WHY (reviewer, 2026-09-30): an Update card and a Delete card showed each item
57
+ * as "Item" — they never named the record they would change, and an Update card
58
+ * never said what it would change. The item-summary helpers name a thing to be
59
+ * CREATED from its own fields; an update or delete item carries the TARGET's id
60
+ * and, for an update, the NEW values — so its "title" field is the new title,
61
+ * not the name of the record being changed. Naming the target is the host's
62
+ * live lookup (`DirectiveHost.renderRecord`); what changes is right here.
63
+ *
64
+ * Law: common-docs/policies/destructive-and-expensive-actions.md — the sentence
65
+ * names what changes.
66
+ */
67
+ /** Classes whose items point at an EXISTING record by `id`. */
68
+ declare const TARGETING_CLASSES: ReadonlySet<string>;
69
+ /** How long a changed value may read before it is shortened (full value on hover). */
70
+ declare const CHANGE_VALUE_MAX = 60;
71
+ /** The id of the existing record an update/delete item targets, or null. */
72
+ declare function itemRecordId(item: Record<string, unknown>): string | null;
73
+ /** "due_date" → "Due date". Underscores read as developer output. */
74
+ declare function fieldLabel(key: string): string;
75
+ /** A short, honest name for a record we could not look up: "Task 1a2b3c4d". */
76
+ declare function recordFallbackName(nounLabel: string, id: string): string;
77
+ /** One value as a row reads it: shortened, the whole of it, and whether it is empty. */
78
+ interface ChangeValue {
79
+ value: string;
80
+ full: string;
81
+ clears: boolean;
82
+ }
83
+ interface ItemChange extends ChangeValue {
84
+ key: string;
85
+ /** "Due date". */
86
+ label: string;
87
+ /**
88
+ * What the field holds NOW, when the host could read the record — so an
89
+ * update reads "old → new", never just the new value (reviewer, 2026-10-02).
90
+ * Absent when the current record is unknown.
91
+ */
92
+ before?: ChangeValue;
93
+ /** True when the record already holds exactly this value — nothing changes. */
94
+ same?: boolean;
95
+ }
96
+ /** One value, described for a change row (`value` shortened, `full` whole). */
97
+ declare function describeChangeValue(value: unknown): ChangeValue;
98
+ /**
99
+ * Every field an UPDATE item sets, in the order the item carries them — ALL of
100
+ * them, never a sample: a person approving an overwrite sees the whole of it.
101
+ *
102
+ * Pass `current` — the record as it is now (`DirectiveHost.readRecord`) — and
103
+ * each change also carries what it REPLACES (`before`) and whether it changes
104
+ * anything at all (`same`). A field the record does not carry gets no `before`.
105
+ */
106
+ declare function itemChanges(item: Record<string, unknown>, current?: Record<string, unknown> | null): ItemChange[];
107
+
51
108
  interface SideEffectDirectiveCardProps {
52
109
  directive: DecodedDirective;
53
110
  }
111
+ /**
112
+ * THE CHANGE LIST — every field an update sets, as "Label old → new" when the
113
+ * record's current value is known, "Label → new" when it is not. ONE component
114
+ * for the card row and the host's confirm dialog, so the two can never read
115
+ * differently (reviewer, 2026-10-02: the confirm showed only the new value).
116
+ */
117
+ declare function DirectiveChangeList({ changes, className, }: {
118
+ changes: ItemChange[];
119
+ className?: string;
120
+ }): react.JSX.Element;
54
121
  declare function SideEffectDirectiveCard({ directive }: SideEffectDirectiveCardProps): react.JSX.Element;
55
122
 
56
123
  interface DirectiveFallbackCardProps {
@@ -94,45 +161,13 @@ interface DirectiveRenderProps {
94
161
  }
95
162
  declare function DirectiveRender({ conversationId, ...props }: DirectiveRenderProps): react.JSX.Element;
96
163
 
97
- /**
98
- * WHAT A DIRECTIVE ITEM WILL DO TO AN EXISTING RECORD — for the card, the
99
- * confirm dialog, and anything else that must say it before the click.
100
- *
101
- * WHY (reviewer, 2026-09-30): an Update card and a Delete card showed each item
102
- * as "Item" — they never named the record they would change, and an Update card
103
- * never said what it would change. The item-summary helpers name a thing to be
104
- * CREATED from its own fields; an update or delete item carries the TARGET's id
105
- * and, for an update, the NEW values — so its "title" field is the new title,
106
- * not the name of the record being changed. Naming the target is the host's
107
- * live lookup (`DirectiveHost.renderRecord`); what changes is right here.
108
- *
109
- * Law: common-docs/policies/destructive-and-expensive-actions.md — the sentence
110
- * names what changes.
111
- */
112
- /** Classes whose items point at an EXISTING record by `id`. */
113
- declare const TARGETING_CLASSES: ReadonlySet<string>;
114
- /** How long a changed value may read before it is shortened (full value on hover). */
115
- declare const CHANGE_VALUE_MAX = 60;
116
- /** The id of the existing record an update/delete item targets, or null. */
117
- declare function itemRecordId(item: Record<string, unknown>): string | null;
118
- /** "due_date" → "Due date". Underscores read as developer output. */
119
- declare function fieldLabel(key: string): string;
120
- /** A short, honest name for a record we could not look up: "Task 1a2b3c4d". */
121
- declare function recordFallbackName(nounLabel: string, id: string): string;
122
- interface ItemChange {
123
- key: string;
124
- /** "Due date". */
125
- label: string;
126
- /** What it becomes, shortened for a row; `full` carries the whole value. */
127
- value: string;
128
- full: string;
129
- /** True when the new value empties the field. */
130
- clears: boolean;
164
+ type RecordValuesStatus = "idle" | "loading" | "ready" | "unavailable";
165
+ interface RecordValues {
166
+ /** The record's fields now, or null until (or unless) they are read. */
167
+ values: Record<string, unknown> | null;
168
+ status: RecordValuesStatus;
131
169
  }
132
- /**
133
- * Every field an UPDATE item sets, in the order the item carries them — ALL of
134
- * them, never a sample: a person approving an overwrite sees the whole of it.
135
- */
136
- declare function itemChanges(item: Record<string, unknown>): ItemChange[];
170
+ type RecordReader = (ref: DirectiveRecordRef) => Promise<Record<string, unknown> | null>;
171
+ declare function useRecordValues(read: RecordReader | undefined, ref: DirectiveRecordRef | null): RecordValues;
137
172
 
138
- export { ApplyDirectiveButton, type ApplyDirectiveButtonProps, CHANGE_VALUE_MAX, DirectiveFallbackCard, type DirectiveFallbackCardProps, DirectiveProvisionalCard, type DirectiveProvisionalCardProps, DirectiveRender, type DirectiveRenderProps, type DirectiveRenderer, type DirectiveRendererProps, type ItemChange, SideEffectDirectiveCard, type SideEffectDirectiveCardProps, TARGETING_CLASSES, fieldLabel, getDirectiveRenderer, itemChanges, itemRecordId, provisionalDirectiveSlug, recordFallbackName, registerDirectiveRenderer, resetDirectiveRenderers };
173
+ export { ApplyDirectiveButton, type ApplyDirectiveButtonProps, CHANGE_VALUE_MAX, type ChangeValue, DirectiveChangeList, DirectiveFallbackCard, type DirectiveFallbackCardProps, DirectiveProvisionalCard, type DirectiveProvisionalCardProps, DirectiveRecordRef, DirectiveRender, type DirectiveRenderProps, type DirectiveRenderer, type DirectiveRendererProps, type ItemChange, type RecordReader, type RecordValues, type RecordValuesStatus, SideEffectDirectiveCard, type SideEffectDirectiveCardProps, TARGETING_CLASSES, describeChangeValue, fieldLabel, getDirectiveRenderer, itemChanges, itemRecordId, provisionalDirectiveSlug, recordFallbackName, registerDirectiveRenderer, resetDirectiveRenderers, useRecordValues };
@@ -66,6 +66,9 @@ function recordFallbackName(nounLabel3, id) {
66
66
  const short = isUuidShape(id) ? id.slice(0, 8) : id.length > 12 ? `${id.slice(0, 12)}\u2026` : id;
67
67
  return `${nounLabel3} ${short}`;
68
68
  }
69
+ function describeChangeValue(value) {
70
+ return describeValue(value);
71
+ }
69
72
  function describeValue(value) {
70
73
  if (value === null || value === void 0) return { value: "cleared", full: "cleared", clears: true };
71
74
  if (typeof value === "string") {
@@ -94,11 +97,26 @@ function safeJson(value) {
94
97
  return String(value);
95
98
  }
96
99
  }
97
- function itemChanges(item) {
100
+ function sameValue(a, b) {
101
+ if (a === b) return true;
102
+ if (a === null || a === void 0 || b === null || b === void 0) {
103
+ const emptyA = a === null || a === void 0 || a === "";
104
+ const emptyB = b === null || b === void 0 || b === "";
105
+ return emptyA && emptyB;
106
+ }
107
+ if (typeof a === "object" || typeof b === "object") return safeJson(a) === safeJson(b);
108
+ return false;
109
+ }
110
+ function itemChanges(item, current) {
98
111
  const out = [];
99
112
  for (const [key, raw] of Object.entries(item)) {
100
113
  if (NOT_A_CHANGE.has(key)) continue;
101
- out.push({ key, label: fieldLabel(key), ...describeValue(raw) });
114
+ const change = { key, label: fieldLabel(key), ...describeValue(raw) };
115
+ if (current && Object.prototype.hasOwnProperty.call(current, key)) {
116
+ change.before = describeValue(current[key]);
117
+ change.same = sameValue(current[key], raw);
118
+ }
119
+ out.push(change);
102
120
  }
103
121
  return out;
104
122
  }
@@ -211,11 +229,23 @@ function ApplyDirectiveButton({
211
229
  const conversationId = scope.conversationId || void 0;
212
230
  const items = directive.items;
213
231
  const readsLedger = !!host.applyState && !!host.confirm && directive.parsed.executes && items.length > 0;
214
- const [state, setState] = useState(
215
- readsLedger ? { status: "checking" } : { status: "idle" }
216
- );
232
+ const initial = readsLedger ? { status: "checking" } : { status: "idle" };
233
+ const [state, setState] = useState(initial);
217
234
  const [elapsed, setElapsed] = useState(0);
235
+ const [askingAgain, setAskingAgain] = useState(false);
218
236
  const startedAt = useRef(null);
237
+ const identity = JSON.stringify([directive.slug, items, conversationId ?? null]);
238
+ const [stateFor, setStateFor] = useState(identity);
239
+ if (stateFor !== identity) {
240
+ setStateFor(identity);
241
+ setState(initial);
242
+ setAskingAgain(false);
243
+ setElapsed(0);
244
+ }
245
+ const currentIdentity = useRef(identity);
246
+ useEffect(() => {
247
+ currentIdentity.current = identity;
248
+ }, [identity]);
219
249
  const nounText = nounLabel(directive.noun, host.nouns);
220
250
  const shellKey = readsLedger ? JSON.stringify([directive.slug, items, conversationId ?? null]) : "";
221
251
  const read = host.applyState;
@@ -238,7 +268,8 @@ function ApplyDirectiveButton({
238
268
  applied: shellItems.length,
239
269
  failed: 0,
240
270
  message: answer.message?.trim() ? answer.message.trim() : null,
241
- records: answer.records ?? []
271
+ records: answer.records ?? [],
272
+ copies: Math.max(1, answer.copies ?? 1)
242
273
  } : prev
243
274
  );
244
275
  } else if (answer.state === "in_flight" && polls < FINISH_POLLS_MAX) {
@@ -278,8 +309,12 @@ function ApplyDirectiveButton({
278
309
  async function handleApply(again = false) {
279
310
  if (!confirm) return;
280
311
  const before = state;
312
+ const askedFor = identity;
313
+ const stillHere = () => currentIdentity.current === askedFor;
314
+ const priorCopies = state.status === "applied" ? state.copies : 0;
281
315
  if (host.ask) {
282
- setState({ status: "asking" });
316
+ if (again) setAskingAgain(true);
317
+ else setState({ status: "asking" });
283
318
  let ok = false;
284
319
  try {
285
320
  ok = await host.ask({
@@ -289,12 +324,16 @@ function ApplyDirectiveButton({
289
324
  ...again ? { again: true } : {}
290
325
  });
291
326
  } catch (error) {
327
+ if (!stillHere()) return;
328
+ setAskingAgain(false);
292
329
  setState({
293
330
  status: "error",
294
331
  message: error instanceof Error && error.message ? error.message : "We could not ask you to confirm this, so nothing was done."
295
332
  });
296
333
  return;
297
334
  }
335
+ if (!stillHere()) return;
336
+ setAskingAgain(false);
298
337
  if (!ok) {
299
338
  setState(again ? before : { status: "idle" });
300
339
  return;
@@ -307,15 +346,19 @@ function ApplyDirectiveButton({
307
346
  if (conversationId) shell.conversationId = conversationId;
308
347
  if (again) shell.force = true;
309
348
  const result = await confirm(shell);
349
+ if (!stillHere()) return;
310
350
  setState({
311
351
  status: "applied",
312
352
  applied: result.applied,
313
353
  failed: result.failed,
314
354
  message: result.message?.trim() ? result.message.trim() : null,
315
- records: result.records ?? []
355
+ records: result.records ?? [],
356
+ // "Run again" made one more; a first apply (or a replay of one) is one.
357
+ copies: again ? Math.max(1, priorCopies) + 1 : Math.max(1, priorCopies)
316
358
  });
317
359
  onApplied?.();
318
360
  } catch (error) {
361
+ if (!stillHere()) return;
319
362
  setState({
320
363
  status: "error",
321
364
  message: error instanceof Error && error.message ? error.message : "That couldn't be applied just now. Please try again."
@@ -340,6 +383,15 @@ function ApplyDirectiveButton({
340
383
  bad && !state.message ? ` \xB7 ${state.failed} failed` : ""
341
384
  ] })
342
385
  ] }),
386
+ state.copies > 1 ? /* @__PURE__ */ jsx3(
387
+ "span",
388
+ {
389
+ className: "shrink-0 text-muted-foreground",
390
+ "data-apply-copies": state.copies,
391
+ title: records.length > 0 ? directive.directiveClass === "create" ? "Links to the latest copy." : "Links to the latest run." : void 0,
392
+ children: directive.directiveClass === "create" ? `${state.copies} copies` : `Ran ${state.copies} times`
393
+ }
394
+ ) : null,
343
395
  records.map((record) => /* @__PURE__ */ jsx3("span", { className: "inline-flex min-w-0 items-center", children: host.renderRecord?.({
344
396
  ...record,
345
397
  fallback: recordFallbackName(label2, record.id),
@@ -350,8 +402,9 @@ function ApplyDirectiveButton({
350
402
  "button",
351
403
  {
352
404
  type: "button",
405
+ disabled: askingAgain,
353
406
  onClick: () => void handleApply(!bad),
354
- className: "inline-flex items-center gap-1 rounded px-1 py-0.5 text-muted-foreground hover:bg-accent hover:text-accent-foreground",
407
+ className: "inline-flex items-center gap-1 rounded px-1 py-0.5 text-muted-foreground hover:bg-accent hover:text-accent-foreground disabled:opacity-50",
355
408
  "data-apply-action": bad ? "retry" : "run-again",
356
409
  children: [
357
410
  /* @__PURE__ */ jsx3(RotateCcwIcon, { className: "h-3 w-3" }),
@@ -389,6 +442,7 @@ function ApplyDirectiveButton({
389
442
  }
390
443
  );
391
444
  }
445
+ const destructive = directive.directiveClass === "delete";
392
446
  return /* @__PURE__ */ jsxs2("span", { className: "inline-flex items-center gap-2", children: [
393
447
  /* @__PURE__ */ jsxs2(
394
448
  "button",
@@ -396,7 +450,8 @@ function ApplyDirectiveButton({
396
450
  type: "button",
397
451
  onClick: () => void handleApply(),
398
452
  disabled: state.status === "applying" || state.status === "asking" || items.length === 0,
399
- 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",
453
+ "data-apply-tone": destructive ? "destructive" : "default",
454
+ 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"}`,
400
455
  children: [
401
456
  state.status === "applying" ? /* @__PURE__ */ jsx3(LoaderIcon, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx3(PlayIcon, { className: "h-3.5 w-3.5" }),
402
457
  state.status === "applying" ? `Applying${itemCount ? ` ${itemCount} ${itemCount === 1 ? "item" : "items"}` : ""}\u2026${elapsed ? ` ${elapsed}s` : ""}` : label
@@ -411,7 +466,7 @@ function ApplyDirectiveButton({
411
466
  }
412
467
 
413
468
  // directives/SideEffectDirectiveCard.tsx
414
- import { useState as useState2 } from "react";
469
+ import { useState as useState3 } from "react";
415
470
  import {
416
471
  asKindInstance,
417
472
  directiveDisplay,
@@ -421,6 +476,39 @@ import {
421
476
  nounLabel as nounLabel2,
422
477
  nounTitleColumn
423
478
  } from "@ai-matrx/content-ir";
479
+
480
+ // directives/record-values.ts
481
+ import { useEffect as useEffect2, useState as useState2 } from "react";
482
+ function useRecordValues(read, ref) {
483
+ const key = ref && read ? `${ref.noun}:${ref.id}` : "";
484
+ const [result, setResult] = useState2({
485
+ key: "",
486
+ values: null,
487
+ status: "idle"
488
+ });
489
+ useEffect2(() => {
490
+ if (!key || !read) return;
491
+ const split = key.indexOf(":");
492
+ const target = { noun: key.slice(0, split), id: key.slice(split + 1) };
493
+ let live = true;
494
+ setResult({ key, values: null, status: "loading" });
495
+ read(target).then((values) => {
496
+ if (!live) return;
497
+ setResult({ key, values, status: values ? "ready" : "unavailable" });
498
+ }).catch(() => {
499
+ if (!live) return;
500
+ setResult({ key, values: null, status: "unavailable" });
501
+ });
502
+ return () => {
503
+ live = false;
504
+ };
505
+ }, [key, read]);
506
+ if (!key) return { values: null, status: "idle" };
507
+ if (result.key !== key) return { values: null, status: "loading" };
508
+ return { values: result.values, status: result.status };
509
+ }
510
+
511
+ // directives/SideEffectDirectiveCard.tsx
424
512
  import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
425
513
  var ROWS_BEFORE_FOLD = 3;
426
514
  function ItemRow({
@@ -474,44 +562,73 @@ function ItemRow({
474
562
  host.renderCopy?.({ label: title, value: item, kind, size: "xs" })
475
563
  ] });
476
564
  }
477
- function TargetRow({
478
- directive,
479
- item,
480
- recordId
565
+ function DirectiveChangeList({
566
+ changes,
567
+ className
481
568
  }) {
482
- const host = useDirectiveHost();
483
- const fallback = recordFallbackName(nounLabel2(directive.noun, host.nouns), recordId);
484
- const changes = directive.directiveClass === "update" ? itemChanges(item) : [];
485
- const name = host.renderRecord ? host.renderRecord({ noun: directive.noun, id: recordId, fallback, context: "row" }) : /* @__PURE__ */ jsx4("span", { className: "truncate font-medium text-foreground", children: fallback });
486
- return /* @__PURE__ */ jsxs3("div", { className: "py-1 text-xs", "data-directive-target": recordId, children: [
487
- /* @__PURE__ */ jsxs3("div", { className: "flex min-w-0 items-center gap-2", children: [
488
- /* @__PURE__ */ jsx4("span", { className: "flex min-w-0 flex-1 items-center gap-1 px-1", children: name }),
489
- host.renderCopy?.({ label: fallback, value: item, size: "xs" })
490
- ] }),
491
- directive.directiveClass === "update" ? changes.length === 0 ? /* @__PURE__ */ jsx4("p", { className: "px-1 text-[11px] text-muted-foreground", children: "Sets no fields \u2014 nothing would change." }) : /* @__PURE__ */ jsx4("ul", { className: "mt-0.5 space-y-0.5 px-1", children: changes.map((change) => /* @__PURE__ */ jsxs3(
569
+ if (changes.length === 0) {
570
+ return /* @__PURE__ */ jsx4("p", { className: `text-[11px] text-muted-foreground${className ? ` ${className}` : ""}`, children: "Sets no fields \u2014 nothing would change." });
571
+ }
572
+ return /* @__PURE__ */ jsx4("ul", { className: `space-y-0.5${className ? ` ${className}` : ""}`, "data-directive-changes": "", children: changes.map((change) => {
573
+ const before = change.before && !change.same ? change.before : null;
574
+ const was = before ? before.clears ? "empty" : before.value : null;
575
+ return /* @__PURE__ */ jsxs3(
492
576
  "li",
493
577
  {
494
578
  className: "flex min-w-0 items-baseline gap-1.5 text-[11px]",
495
- title: `${change.label} \u2192 ${change.full}`,
579
+ title: before ? `${change.label}: ${before.clears ? "empty" : before.full} \u2192 ${change.full}` : `${change.label} \u2192 ${change.full}`,
580
+ "data-change-key": change.key,
496
581
  children: [
497
582
  /* @__PURE__ */ jsx4("span", { className: "shrink-0 text-muted-foreground", children: change.label }),
583
+ was !== null ? /* @__PURE__ */ jsx4(
584
+ "span",
585
+ {
586
+ className: "min-w-0 max-w-[40%] shrink truncate text-muted-foreground line-through",
587
+ "data-change-before": "",
588
+ children: was
589
+ }
590
+ ) : null,
498
591
  /* @__PURE__ */ jsx4("span", { "aria-hidden": true, className: "shrink-0 text-muted-foreground", children: "\u2192" }),
499
592
  /* @__PURE__ */ jsx4(
500
593
  "span",
501
594
  {
502
595
  className: `min-w-0 truncate ${change.clears ? "italic text-muted-foreground" : "text-foreground"}`,
596
+ "data-change-after": "",
503
597
  children: change.value
504
598
  }
505
- )
599
+ ),
600
+ change.same ? /* @__PURE__ */ jsx4("span", { className: "shrink-0 text-muted-foreground", children: "(no change)" }) : null
506
601
  ]
507
602
  },
508
603
  change.key
509
- )) }) : null
604
+ );
605
+ }) });
606
+ }
607
+ function TargetRow({
608
+ directive,
609
+ item,
610
+ recordId
611
+ }) {
612
+ const host = useDirectiveHost();
613
+ const fallback = recordFallbackName(nounLabel2(directive.noun, host.nouns), recordId);
614
+ const isUpdate = directive.directiveClass === "update";
615
+ const current = useRecordValues(
616
+ isUpdate ? host.readRecord : void 0,
617
+ isUpdate ? { noun: directive.noun, id: recordId } : null
618
+ );
619
+ const changes = isUpdate ? itemChanges(item, current.values) : [];
620
+ const name = host.renderRecord ? host.renderRecord({ noun: directive.noun, id: recordId, fallback, context: "row" }) : /* @__PURE__ */ jsx4("span", { className: "truncate font-medium text-foreground", children: fallback });
621
+ return /* @__PURE__ */ jsxs3("div", { className: "py-1 text-xs", "data-directive-target": recordId, children: [
622
+ /* @__PURE__ */ jsxs3("div", { className: "flex min-w-0 items-center gap-2", children: [
623
+ /* @__PURE__ */ jsx4("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
+ isUpdate ? /* @__PURE__ */ jsx4(DirectiveChangeList, { changes, className: "mt-0.5 px-1" }) : null
510
627
  ] });
511
628
  }
512
629
  function SideEffectDirectiveCard({ directive }) {
513
630
  const host = useDirectiveHost();
514
- const [expanded, setExpanded] = useState2(false);
631
+ const [expanded, setExpanded] = useState3(false);
515
632
  const items = directive.items;
516
633
  const total = items.length;
517
634
  const display = directiveDisplay(directive.directiveClass, directive.noun, host.nouns);
@@ -663,6 +780,7 @@ function DirectiveRenderInner({ content, className, streaming }) {
663
780
  export {
664
781
  ApplyDirectiveButton,
665
782
  CHANGE_VALUE_MAX,
783
+ DirectiveChangeList,
666
784
  DirectiveFallbackCard,
667
785
  DirectiveHostProvider,
668
786
  DirectiveProvisionalCard,
@@ -670,6 +788,7 @@ export {
670
788
  DirectiveScopeProvider,
671
789
  SideEffectDirectiveCard,
672
790
  TARGETING_CLASSES,
791
+ describeChangeValue,
673
792
  fieldLabel,
674
793
  getDirectiveRenderer,
675
794
  itemChanges,
@@ -679,6 +798,7 @@ export {
679
798
  registerDirectiveRenderer,
680
799
  resetDirectiveRenderers,
681
800
  useDirectiveHost,
682
- useDirectiveScope
801
+ useDirectiveScope,
802
+ useRecordValues
683
803
  };
684
804
  //# sourceMappingURL=directives.js.map