@ai-matrx/content-ir-react 0.19.1 → 0.20.1

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,53 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.20.1 — 2026-10-02
4
+
5
+ G9 leftovers (nightly clone). Guard: `__tests__/directive-card-g9.test.tsx` (3 red on 0.20.0).
6
+
7
+ - **A button never wraps its label.** At 375px "Apply" broke to "Appl / y" beside
8
+ "Nothing to change". Apply, Run again, Retry, Details and Show more are `whitespace-nowrap`
9
+ (`shrink-0`); the header group wraps and the reason text is what wraps.
10
+ - **A screen-reader label never names an id.** A record row's copy control read
11
+ "Task a8a00001". `DirectiveCopyProps.label` is now type-generic ("this task") and the new
12
+ `DirectiveCopyProps.record` ({ noun, id }) lets the host swap in the record's live name.
13
+
14
+ ### Consumer action
15
+
16
+ - In `renderCopy`, when `props.record` is set, label the control with the record's live name
17
+ (the same one `renderRecord` shows), keeping `props.label` while it loads.
18
+
19
+ ## 0.20.0 — 2026-10-02
20
+
21
+ The G8A review of the action cards (nightly clone). Guard:
22
+ `__tests__/directive-card-g8a.test.tsx` (4 red on 0.19.2).
23
+
24
+ - **One value, one word.** New host seam `DirectiveHost.valueLabel({ noun, field, value })`
25
+ — the app's word for a pick-list value, the same word its forms and screens show. The
26
+ change list words old AND new through it ("Status Inbox → Completed", never
27
+ "incomplete → completed"); card facts too. New: `DirectiveValueRef`,
28
+ `DirectiveValueWord`, `ItemChangesOptions.valueWord`, `directiveValueWord(host, noun)`.
29
+ - **An empty value reads "None"** — before and after, card and confirm (was "empty" /
30
+ "cleared"). `EMPTY_VALUE_WORD`.
31
+ - **A value someone must judge is never cut.** The change list wraps every value (no CSS
32
+ truncation) and, below a 20rem container, stacks label / old / new one per line
33
+ (container query — a narrow panel stacks too). `CHANGE_VALUE_MAX` 60 → 280: only prose
34
+ longer than that is shortened.
35
+ - **One field-label rule.** `directiveFieldLabel(key, titleColumn)` — the title column reads
36
+ "Title", every other field through THE humanizer — for change rows AND card facts. A
37
+ create card no longer repeats its title column as a fact ("workbook name"). Facts read
38
+ "Label value".
39
+
40
+ ### Consumer action
41
+
42
+ - Pass `valueLabel` on your `DirectiveHost` and the same `valueWord` to any `itemChanges` you
43
+ call yourself (a host confirm), so card and confirm say the same words.
44
+
45
+ ## 0.19.2
46
+ ### Directive field labels use THE humanizer
47
+
48
+ - `fieldLabel` (directive item changes) goes through `humanizeIdentifier`: `due_date` → "Due Date".
49
+ - Consumer action: none.
50
+
3
51
  ## 0.19.1 — 2026-10-02
4
52
 
5
53
  - **One record-revision store per page.** Every entry of this package (`.`,
@@ -121,7 +121,14 @@ interface DirectiveOpenItemOptions {
121
121
  total: number;
122
122
  }
123
123
  interface DirectiveCopyProps {
124
+ /**
125
+ * What the control is called to a screen reader. Never an id: for a record it
126
+ * is type-generic ("this task"); a host with the record's live name replaces
127
+ * it through `record`.
128
+ */
124
129
  label: string;
130
+ /** The existing record this control copies, when it copies one — to name it. */
131
+ record?: DirectiveRecordRef;
125
132
  /** The value to copy — the whole shell or one item. */
126
133
  value: unknown;
127
134
  /** The item's kind, when copying an item. */
@@ -174,6 +181,15 @@ interface DirectiveHost {
174
181
  openItem?: (options: DirectiveOpenItemOptions) => void;
175
182
  /** Host copy controls. */
176
183
  renderCopy?: (props: DirectiveCopyProps) => ReactNode;
184
+ /**
185
+ * The app's word for one stored value of one pick-list field — the SAME word
186
+ * the record's own screens and the write forms show ("incomplete" on a task
187
+ * reads "Inbox"). One value, one display word everywhere (G8A review,
188
+ * 2026-10-02: the card said "Status incomplete → completed"). Return null for
189
+ * a field that is not a pick-list: its value is shown as written. Absent →
190
+ * every value is shown as stored.
191
+ */
192
+ valueLabel?: (ref: DirectiveValueRef) => string | null;
177
193
  /** The mirrored noun catalog (label / family / title column). */
178
194
  nouns?: DirectiveNounCatalog;
179
195
  /** slug → the kind ONE item is (THE DIRECTIVE⇄KIND SEAM), or null. */
@@ -189,6 +205,12 @@ interface DirectiveHost {
189
205
  */
190
206
  explainFailure?: (failure: DirectiveFailure) => DirectiveFailureWords | null;
191
207
  }
208
+ /** One stored value of one field of one record type. */
209
+ interface DirectiveValueRef {
210
+ noun: string;
211
+ field: string;
212
+ value: string;
213
+ }
192
214
  /** One failed apply, as the card knows it. */
193
215
  interface DirectiveFailure {
194
216
  /** The server's / host's own text — technical; shown only behind Details. */
@@ -233,4 +255,4 @@ declare function DirectiveHostProvider({ host, children }: DirectiveHostProvider
233
255
  */
234
256
  declare function useDirectiveHost(): DirectiveHost;
235
257
 
236
- export { type DirectiveFailure as D, type DirectiveFailureWords as a, type DirectiveRecordRef as b, type DirectiveApplyResult as c, type DirectiveApplyState as d, type DirectiveAskRequest as e, type DirectiveCopyProps as f, type DirectiveHost as g, DirectiveHostProvider as h, type DirectiveHostProviderProps as i, type DirectiveOpenItemOptions as j, type DirectiveRecordProps as k, type DirectiveScope as l, DirectiveScopeProvider as m, type DirectiveShell as n, useDirectiveScope as o, useDirectiveHost as u };
258
+ export { type DirectiveHost as D, type DirectiveFailure as a, type DirectiveFailureWords as b, type DirectiveRecordRef as c, type DirectiveApplyResult as d, type DirectiveApplyState as e, type DirectiveAskRequest as f, type DirectiveCopyProps as g, DirectiveHostProvider as h, type DirectiveHostProviderProps as i, type DirectiveOpenItemOptions as j, type DirectiveRecordProps as k, type DirectiveScope as l, DirectiveScopeProvider as m, type DirectiveShell as n, type DirectiveValueRef as o, useDirectiveScope as p, useDirectiveHost as u };
@@ -121,7 +121,14 @@ interface DirectiveOpenItemOptions {
121
121
  total: number;
122
122
  }
123
123
  interface DirectiveCopyProps {
124
+ /**
125
+ * What the control is called to a screen reader. Never an id: for a record it
126
+ * is type-generic ("this task"); a host with the record's live name replaces
127
+ * it through `record`.
128
+ */
124
129
  label: string;
130
+ /** The existing record this control copies, when it copies one — to name it. */
131
+ record?: DirectiveRecordRef;
125
132
  /** The value to copy — the whole shell or one item. */
126
133
  value: unknown;
127
134
  /** The item's kind, when copying an item. */
@@ -174,6 +181,15 @@ interface DirectiveHost {
174
181
  openItem?: (options: DirectiveOpenItemOptions) => void;
175
182
  /** Host copy controls. */
176
183
  renderCopy?: (props: DirectiveCopyProps) => ReactNode;
184
+ /**
185
+ * The app's word for one stored value of one pick-list field — the SAME word
186
+ * the record's own screens and the write forms show ("incomplete" on a task
187
+ * reads "Inbox"). One value, one display word everywhere (G8A review,
188
+ * 2026-10-02: the card said "Status incomplete → completed"). Return null for
189
+ * a field that is not a pick-list: its value is shown as written. Absent →
190
+ * every value is shown as stored.
191
+ */
192
+ valueLabel?: (ref: DirectiveValueRef) => string | null;
177
193
  /** The mirrored noun catalog (label / family / title column). */
178
194
  nouns?: DirectiveNounCatalog;
179
195
  /** slug → the kind ONE item is (THE DIRECTIVE⇄KIND SEAM), or null. */
@@ -189,6 +205,12 @@ interface DirectiveHost {
189
205
  */
190
206
  explainFailure?: (failure: DirectiveFailure) => DirectiveFailureWords | null;
191
207
  }
208
+ /** One stored value of one field of one record type. */
209
+ interface DirectiveValueRef {
210
+ noun: string;
211
+ field: string;
212
+ value: string;
213
+ }
192
214
  /** One failed apply, as the card knows it. */
193
215
  interface DirectiveFailure {
194
216
  /** The server's / host's own text — technical; shown only behind Details. */
@@ -233,4 +255,4 @@ declare function DirectiveHostProvider({ host, children }: DirectiveHostProvider
233
255
  */
234
256
  declare function useDirectiveHost(): DirectiveHost;
235
257
 
236
- export { type DirectiveFailure as D, type DirectiveFailureWords as a, type DirectiveRecordRef as b, type DirectiveApplyResult as c, type DirectiveApplyState as d, type DirectiveAskRequest as e, type DirectiveCopyProps as f, type DirectiveHost as g, DirectiveHostProvider as h, type DirectiveHostProviderProps as i, type DirectiveOpenItemOptions as j, type DirectiveRecordProps as k, type DirectiveScope as l, DirectiveScopeProvider as m, type DirectiveShell as n, useDirectiveScope as o, useDirectiveHost as u };
258
+ export { type DirectiveHost as D, type DirectiveFailure as a, type DirectiveFailureWords as b, type DirectiveRecordRef as c, type DirectiveApplyResult as d, type DirectiveApplyState as e, type DirectiveAskRequest as f, type DirectiveCopyProps as g, DirectiveHostProvider as h, type DirectiveHostProviderProps as i, type DirectiveOpenItemOptions as j, type DirectiveRecordProps as k, type DirectiveScope as l, DirectiveScopeProvider as m, type DirectiveShell as n, type DirectiveValueRef as o, useDirectiveScope as p, useDirectiveHost as u };
@@ -29,10 +29,13 @@ __export(directives_exports, {
29
29
  DirectiveProvisionalCard: () => DirectiveProvisionalCard,
30
30
  DirectiveRender: () => DirectiveRender,
31
31
  DirectiveScopeProvider: () => DirectiveScopeProvider,
32
+ EMPTY_VALUE_WORD: () => EMPTY_VALUE_WORD,
32
33
  SideEffectDirectiveCard: () => SideEffectDirectiveCard,
33
34
  TARGETING_CLASSES: () => TARGETING_CLASSES,
34
35
  defaultFailureWords: () => defaultFailureWords,
35
36
  describeChangeValue: () => describeChangeValue,
37
+ directiveFieldLabel: () => directiveFieldLabel,
38
+ directiveValueWord: () => directiveValueWord,
36
39
  failureWords: () => failureWords,
37
40
  fieldLabel: () => fieldLabel,
38
41
  getDirectiveRenderer: () => getDirectiveRenderer,
@@ -99,9 +102,10 @@ var import_content_ir2 = require("@ai-matrx/content-ir");
99
102
 
100
103
  // directives/item-changes.ts
101
104
  var import_uuid = require("@ai-matrx/kit/uuid");
105
+ var import_text_case = require("@ai-matrx/kit/text-case");
102
106
  var TARGETING_CLASSES = /* @__PURE__ */ new Set(["update", "delete"]);
103
107
  var NOT_A_CHANGE = /* @__PURE__ */ new Set(["__kind", "id"]);
104
- var CHANGE_VALUE_MAX = 60;
108
+ var CHANGE_VALUE_MAX = 280;
105
109
  function itemRecordId(item) {
106
110
  const id = item.id;
107
111
  if (typeof id === "string" && id.trim()) return id.trim();
@@ -109,21 +113,27 @@ function itemRecordId(item) {
109
113
  return null;
110
114
  }
111
115
  function fieldLabel(key) {
112
- const spaced = key.replace(/_/g, " ").trim();
113
- return spaced.charAt(0).toUpperCase() + spaced.slice(1);
116
+ return (0, import_text_case.humanizeIdentifier)(key) || key;
114
117
  }
118
+ function directiveFieldLabel(key, titleColumn) {
119
+ return titleColumn && key === titleColumn ? "Title" : fieldLabel(key);
120
+ }
121
+ var EMPTY_VALUE_WORD = "None";
115
122
  function recordFallbackName(nounLabel3, id) {
116
123
  const short = (0, import_uuid.isUuidShape)(id) ? id.slice(0, 8) : id.length > 12 ? `${id.slice(0, 12)}\u2026` : id;
117
124
  return `${nounLabel3} ${short}`;
118
125
  }
119
- function describeChangeValue(value) {
120
- return describeValue(value);
126
+ function describeChangeValue(value, word) {
127
+ return describeValue(value, word);
121
128
  }
122
- function describeValue(value) {
123
- if (value === null || value === void 0) return { value: "cleared", full: "cleared", clears: true };
129
+ var EMPTY = { value: EMPTY_VALUE_WORD, full: EMPTY_VALUE_WORD, clears: true };
130
+ function describeValue(value, word) {
131
+ if (value === null || value === void 0) return EMPTY;
124
132
  if (typeof value === "string") {
125
133
  const trimmed = value.trim();
126
- if (!trimmed) return { value: "cleared", full: "cleared", clears: true };
134
+ if (!trimmed) return EMPTY;
135
+ const named = word?.(trimmed) ?? null;
136
+ if (named) return { value: named, full: named, clears: false };
127
137
  const oneLine = trimmed.replace(/\s+/g, " ");
128
138
  const shown = oneLine.length > CHANGE_VALUE_MAX ? `${oneLine.slice(0, CHANGE_VALUE_MAX - 1)}\u2026` : oneLine;
129
139
  return { value: shown, full: trimmed, clears: false };
@@ -133,11 +143,11 @@ function describeValue(value) {
133
143
  }
134
144
  if (Array.isArray(value)) {
135
145
  const n2 = value.length;
136
- const text2 = n2 === 0 ? "cleared" : `${n2} ${n2 === 1 ? "entry" : "entries"}`;
146
+ const text2 = n2 === 0 ? EMPTY_VALUE_WORD : `${n2} ${n2 === 1 ? "entry" : "entries"}`;
137
147
  return { value: text2, full: safeJson(value), clears: n2 === 0 };
138
148
  }
139
149
  const n = Object.keys(value).length;
140
- const text = n === 0 ? "cleared" : `${n} ${n === 1 ? "field" : "fields"}`;
150
+ const text = n === 0 ? EMPTY_VALUE_WORD : `${n} ${n === 1 ? "field" : "fields"}`;
141
151
  return { value: text, full: safeJson(value), clears: n === 0 };
142
152
  }
143
153
  function safeJson(value) {
@@ -162,10 +172,12 @@ function itemChanges(item, current, options = {}) {
162
172
  const titleColumn = options.titleColumn ?? null;
163
173
  for (const [key, raw] of Object.entries(item)) {
164
174
  if (NOT_A_CHANGE.has(key)) continue;
165
- const label = key === titleColumn ? "Title" : fieldLabel(key);
166
- const change = { key, label, ...describeValue(raw) };
175
+ const label = directiveFieldLabel(key, titleColumn);
176
+ const valueWord = options.valueWord;
177
+ const word = key === titleColumn || !valueWord ? void 0 : (v) => valueWord(key, v);
178
+ const change = { key, label, ...describeValue(raw, word) };
167
179
  if (current && Object.prototype.hasOwnProperty.call(current, key)) {
168
- change.before = describeValue(current[key]);
180
+ change.before = describeValue(current[key], word);
169
181
  change.same = sameValue(current[key], raw);
170
182
  }
171
183
  out.push(change);
@@ -621,7 +633,7 @@ function ApplyDirectiveButton({
621
633
  type: "button",
622
634
  disabled: askingAgain,
623
635
  onClick: () => void handleApply(!bad),
624
- 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",
636
+ className: "inline-flex shrink-0 items-center gap-1 whitespace-nowrap rounded px-1 py-0.5 text-muted-foreground hover:bg-accent hover:text-accent-foreground disabled:opacity-50",
625
637
  "data-apply-action": bad ? "retry" : "run-again",
626
638
  children: [
627
639
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(RotateCcwIcon, { className: "h-3 w-3" }),
@@ -661,8 +673,8 @@ function ApplyDirectiveButton({
661
673
  }
662
674
  const destructive = directive.directiveClass === "delete";
663
675
  const muted = nothingToChange && view.status === "idle";
664
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "inline-flex items-center gap-2", children: [
665
- muted ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-xs text-muted-foreground", "data-apply-nothing": "", children: "Nothing to change" }) : null,
676
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "flex min-w-0 flex-wrap items-center justify-end gap-x-2 gap-y-1", children: [
677
+ muted ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "min-w-0 text-xs text-muted-foreground", "data-apply-nothing": "", children: "Nothing to change" }) : null,
666
678
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
667
679
  "button",
668
680
  {
@@ -671,7 +683,7 @@ function ApplyDirectiveButton({
671
683
  disabled: view.status === "applying" || view.status === "asking" || items.length === 0,
672
684
  "data-apply-tone": muted ? "muted" : destructive ? "destructive" : "default",
673
685
  title: muted ? "It sets no fields, so applying it changes nothing." : void 0,
674
- 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"}`,
686
+ className: `inline-flex shrink-0 items-center gap-1 whitespace-nowrap 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"}`,
675
687
  children: [
676
688
  view.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" }),
677
689
  view.status === "applying" ? `Applying${itemCount ? ` ${itemCount} ${itemCount === 1 ? "item" : "items"}` : ""}\u2026${elapsed ? ` ${elapsed}s` : ""}` : label
@@ -722,7 +734,7 @@ function FailureNotice({
722
734
  type: "button",
723
735
  onClick: () => setOpen((v) => !v),
724
736
  "aria-expanded": open,
725
- className: "ml-1 underline underline-offset-2 hover:no-underline",
737
+ className: "ml-1 whitespace-nowrap underline underline-offset-2 hover:no-underline",
726
738
  "data-apply-failure-details-toggle": true,
727
739
  children: open ? "Hide details" : "Details"
728
740
  }
@@ -743,6 +755,14 @@ function FailureNotice({
743
755
  var import_react4 = require("react");
744
756
  var import_content_ir3 = require("@ai-matrx/content-ir");
745
757
  var import_jsx_runtime4 = require("react/jsx-runtime");
758
+ function directiveValueWord(host, noun) {
759
+ const valueLabel = host.valueLabel;
760
+ return valueLabel ? (field, value) => valueLabel({ noun, field, value }) : null;
761
+ }
762
+ function omitKey(item, key) {
763
+ const { [key]: _omitted, ...rest } = item;
764
+ return rest;
765
+ }
746
766
  var ROWS_BEFORE_FOLD = 3;
747
767
  function ItemRow({
748
768
  directive,
@@ -759,16 +779,21 @@ function ItemRow({
759
779
  const titleColumn = (0, import_content_ir3.nounTitleColumn)(directive.noun, host.nouns);
760
780
  const title = (0, import_content_ir3.itemTitle)(item, titleColumn, index, total);
761
781
  const subtitle = (0, import_content_ir3.itemSubtitle)(item);
762
- const facts = (0, import_content_ir3.itemFacts)(item);
782
+ const valueWord = directiveValueWord(host, directive.noun);
783
+ const facts = (0, import_content_ir3.itemFacts)(titleColumn ? omitKey(item, titleColumn) : item).map((fact) => ({
784
+ ...fact,
785
+ label: directiveFieldLabel(fact.key, titleColumn),
786
+ value: valueWord?.(fact.key, fact.value) ?? fact.value
787
+ }));
763
788
  const kind = host.itemKind?.(directive.slug) ?? null;
764
789
  const display = (0, import_content_ir3.directiveDisplay)(directive.directiveClass, directive.noun, host.nouns);
765
790
  const openItem = host.openItem;
766
791
  const body = /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
767
792
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "min-w-0 shrink-0 truncate font-medium text-foreground", children: title }),
768
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "hidden min-w-0 flex-1 items-baseline gap-2 overflow-hidden sm:flex", children: facts.map((fact) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "truncate text-[11px] text-muted-foreground", children: [
769
- fact.value,
793
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "hidden min-w-0 flex-1 items-baseline gap-2 overflow-hidden sm:flex", children: facts.map((fact) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "truncate text-[11px] text-muted-foreground", "data-item-fact": fact.key, children: [
794
+ fact.label,
770
795
  " ",
771
- fact.label
796
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-foreground/80", children: fact.value })
772
797
  ] }, fact.key)) }),
773
798
  subtitle && facts.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "hidden truncate text-[11px] text-muted-foreground sm:inline", children: subtitle }) : null,
774
799
  openItem ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MaximizeIcon, { className: "ml-auto h-3 w-3 shrink-0 text-muted-foreground" }) : null
@@ -803,35 +828,36 @@ function DirectiveChangeList({
803
828
  if (changes.length === 0) {
804
829
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: `text-[11px] text-muted-foreground${className ? ` ${className}` : ""}`, children: "Sets no fields \u2014 nothing would change." });
805
830
  }
806
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: `space-y-0.5${className ? ` ${className}` : ""}`, "data-directive-changes": "", children: changes.map((change) => {
831
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: `@container space-y-1${className ? ` ${className}` : ""}`, "data-directive-changes": "", children: changes.map((change) => {
807
832
  const before = change.before && !change.same ? change.before : null;
808
- const was = before ? before.clears ? "empty" : before.value : null;
809
833
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
810
834
  "li",
811
835
  {
812
- className: "flex min-w-0 items-baseline gap-1.5 text-[11px]",
813
- title: before ? `${change.label}: ${before.clears ? "empty" : before.full} \u2192 ${change.full}` : `${change.label} \u2192 ${change.full}`,
836
+ className: "flex min-w-0 flex-col text-[11px] @xs:flex-row @xs:flex-wrap @xs:items-baseline @xs:gap-x-1.5",
837
+ title: before ? `${change.label}: ${before.full} \u2192 ${change.full}` : `${change.label} \u2192 ${change.full}`,
814
838
  "data-change-key": change.key,
815
839
  children: [
816
840
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "shrink-0 text-muted-foreground", children: change.label }),
817
- was !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
841
+ before ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
818
842
  "span",
819
843
  {
820
- className: "min-w-0 max-w-[40%] shrink truncate text-muted-foreground line-through",
844
+ className: "min-w-0 text-muted-foreground line-through [overflow-wrap:anywhere]",
821
845
  "data-change-before": "",
822
- children: was
846
+ children: before.value
823
847
  }
824
848
  ) : null,
825
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { "aria-hidden": true, className: "shrink-0 text-muted-foreground", children: "\u2192" }),
826
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
827
- "span",
828
- {
829
- className: `min-w-0 truncate ${change.clears ? "italic text-muted-foreground" : "text-foreground"}`,
830
- "data-change-after": "",
831
- children: change.value
832
- }
833
- ),
834
- change.same ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "shrink-0 text-muted-foreground", children: "(no change)" }) : null
849
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "flex min-w-0 items-baseline gap-1.5", children: [
850
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { "aria-hidden": true, className: "shrink-0 text-muted-foreground", children: "\u2192" }),
851
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
852
+ "span",
853
+ {
854
+ className: `min-w-0 [overflow-wrap:anywhere] ${change.clears ? "italic text-muted-foreground" : "text-foreground"}`,
855
+ "data-change-after": "",
856
+ children: change.value
857
+ }
858
+ ),
859
+ change.same ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "shrink-0 text-muted-foreground", children: "(no change)" }) : null
860
+ ] })
835
861
  ]
836
862
  },
837
863
  change.key
@@ -854,12 +880,20 @@ function TargetRow({
854
880
  readsLive ? { noun: directive.noun, id: recordId } : null
855
881
  );
856
882
  const compareTo = phase.phase === "applied" ? phase.before?.[index] ?? null : readsLive ? live.values : null;
857
- const changes = isUpdate ? itemChanges(item, compareTo, { titleColumn: (0, import_content_ir3.nounTitleColumn)(directive.noun, host.nouns) }) : [];
883
+ const changes = isUpdate ? itemChanges(item, compareTo, {
884
+ titleColumn: (0, import_content_ir3.nounTitleColumn)(directive.noun, host.nouns),
885
+ valueWord: directiveValueWord(host, directive.noun)
886
+ }) : [];
858
887
  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 });
859
888
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "py-1 text-xs", "data-directive-target": recordId, children: [
860
889
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex min-w-0 items-center gap-2", children: [
861
890
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "flex min-w-0 flex-1 items-center gap-1 px-1", children: name }),
862
- host.renderCopy?.({ label: fallback, value: item, size: "xs" })
891
+ host.renderCopy?.({
892
+ label: `this ${(0, import_content_ir3.nounLabel)(directive.noun, host.nouns).toLowerCase()}`,
893
+ value: item,
894
+ size: "xs",
895
+ record: { noun: directive.noun, id: recordId }
896
+ })
863
897
  ] }),
864
898
  isUpdate ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(DirectiveChangeList, { changes, className: "mt-0.5 px-1" }) : null
865
899
  ] });
@@ -887,7 +921,7 @@ function SideEffectDirectiveCard({ directive }) {
887
921
  " ",
888
922
  total === 1 ? "item" : "items"
889
923
  ] }),
890
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "ml-auto flex items-center gap-1", children: [
924
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "ml-auto flex min-w-0 flex-wrap items-center justify-end gap-1", children: [
891
925
  host.renderCopy?.({ label: display.title, value: directive.shell, size: "icon" }),
892
926
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
893
927
  ApplyDirectiveButton,
@@ -909,7 +943,7 @@ function SideEffectDirectiveCard({ directive }) {
909
943
  {
910
944
  type: "button",
911
945
  onClick: () => setExpanded((v) => !v),
912
- className: "mt-0.5 inline-flex items-center gap-1 rounded px-1 py-0.5 text-[11px] text-muted-foreground hover:text-foreground",
946
+ className: "mt-0.5 inline-flex items-center gap-1 whitespace-nowrap rounded px-1 py-0.5 text-[11px] text-muted-foreground hover:text-foreground",
913
947
  children: [
914
948
  expanded ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ChevronDownIcon, { className: "h-3 w-3" }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ChevronRightIcon, { className: "h-3 w-3" }),
915
949
  expanded ? "Show less" : `Show ${hidden} more`