@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/dist/react.cjs CHANGED
@@ -139,9 +139,10 @@ var import_content_ir = require("@ai-matrx/content-ir");
139
139
 
140
140
  // directives/item-changes.ts
141
141
  var import_uuid = require("@ai-matrx/kit/uuid");
142
+ var import_text_case = require("@ai-matrx/kit/text-case");
142
143
  var TARGETING_CLASSES = /* @__PURE__ */ new Set(["update", "delete"]);
143
144
  var NOT_A_CHANGE = /* @__PURE__ */ new Set(["__kind", "id"]);
144
- var CHANGE_VALUE_MAX = 60;
145
+ var CHANGE_VALUE_MAX = 280;
145
146
  function itemRecordId(item) {
146
147
  const id = item.id;
147
148
  if (typeof id === "string" && id.trim()) return id.trim();
@@ -149,18 +150,24 @@ function itemRecordId(item) {
149
150
  return null;
150
151
  }
151
152
  function fieldLabel(key) {
152
- const spaced = key.replace(/_/g, " ").trim();
153
- return spaced.charAt(0).toUpperCase() + spaced.slice(1);
153
+ return (0, import_text_case.humanizeIdentifier)(key) || key;
154
154
  }
155
+ function directiveFieldLabel(key, titleColumn) {
156
+ return titleColumn && key === titleColumn ? "Title" : fieldLabel(key);
157
+ }
158
+ var EMPTY_VALUE_WORD = "None";
155
159
  function recordFallbackName(nounLabel3, id) {
156
160
  const short = (0, import_uuid.isUuidShape)(id) ? id.slice(0, 8) : id.length > 12 ? `${id.slice(0, 12)}\u2026` : id;
157
161
  return `${nounLabel3} ${short}`;
158
162
  }
159
- function describeValue(value) {
160
- if (value === null || value === void 0) return { value: "cleared", full: "cleared", clears: true };
163
+ var EMPTY = { value: EMPTY_VALUE_WORD, full: EMPTY_VALUE_WORD, clears: true };
164
+ function describeValue(value, word) {
165
+ if (value === null || value === void 0) return EMPTY;
161
166
  if (typeof value === "string") {
162
167
  const trimmed = value.trim();
163
- if (!trimmed) return { value: "cleared", full: "cleared", clears: true };
168
+ if (!trimmed) return EMPTY;
169
+ const named = word?.(trimmed) ?? null;
170
+ if (named) return { value: named, full: named, clears: false };
164
171
  const oneLine = trimmed.replace(/\s+/g, " ");
165
172
  const shown = oneLine.length > CHANGE_VALUE_MAX ? `${oneLine.slice(0, CHANGE_VALUE_MAX - 1)}\u2026` : oneLine;
166
173
  return { value: shown, full: trimmed, clears: false };
@@ -170,11 +177,11 @@ function describeValue(value) {
170
177
  }
171
178
  if (Array.isArray(value)) {
172
179
  const n2 = value.length;
173
- const text2 = n2 === 0 ? "cleared" : `${n2} ${n2 === 1 ? "entry" : "entries"}`;
180
+ const text2 = n2 === 0 ? EMPTY_VALUE_WORD : `${n2} ${n2 === 1 ? "entry" : "entries"}`;
174
181
  return { value: text2, full: safeJson(value), clears: n2 === 0 };
175
182
  }
176
183
  const n = Object.keys(value).length;
177
- const text = n === 0 ? "cleared" : `${n} ${n === 1 ? "field" : "fields"}`;
184
+ const text = n === 0 ? EMPTY_VALUE_WORD : `${n} ${n === 1 ? "field" : "fields"}`;
178
185
  return { value: text, full: safeJson(value), clears: n === 0 };
179
186
  }
180
187
  function safeJson(value) {
@@ -199,10 +206,12 @@ function itemChanges(item, current, options = {}) {
199
206
  const titleColumn = options.titleColumn ?? null;
200
207
  for (const [key, raw] of Object.entries(item)) {
201
208
  if (NOT_A_CHANGE.has(key)) continue;
202
- const label = key === titleColumn ? "Title" : fieldLabel(key);
203
- const change = { key, label, ...describeValue(raw) };
209
+ const label = directiveFieldLabel(key, titleColumn);
210
+ const valueWord = options.valueWord;
211
+ const word = key === titleColumn || !valueWord ? void 0 : (v) => valueWord(key, v);
212
+ const change = { key, label, ...describeValue(raw, word) };
204
213
  if (current && Object.prototype.hasOwnProperty.call(current, key)) {
205
- change.before = describeValue(current[key]);
214
+ change.before = describeValue(current[key], word);
206
215
  change.same = sameValue(current[key], raw);
207
216
  }
208
217
  out.push(change);
@@ -658,7 +667,7 @@ function ApplyDirectiveButton({
658
667
  type: "button",
659
668
  disabled: askingAgain,
660
669
  onClick: () => void handleApply(!bad),
661
- 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",
670
+ 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",
662
671
  "data-apply-action": bad ? "retry" : "run-again",
663
672
  children: [
664
673
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RotateCcwIcon, { className: "h-3 w-3" }),
@@ -698,8 +707,8 @@ function ApplyDirectiveButton({
698
707
  }
699
708
  const destructive = directive.directiveClass === "delete";
700
709
  const muted = nothingToChange && view.status === "idle";
701
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "inline-flex items-center gap-2", children: [
702
- muted ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-xs text-muted-foreground", "data-apply-nothing": "", children: "Nothing to change" }) : null,
710
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "flex min-w-0 flex-wrap items-center justify-end gap-x-2 gap-y-1", children: [
711
+ muted ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "min-w-0 text-xs text-muted-foreground", "data-apply-nothing": "", children: "Nothing to change" }) : null,
703
712
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
704
713
  "button",
705
714
  {
@@ -708,7 +717,7 @@ function ApplyDirectiveButton({
708
717
  disabled: view.status === "applying" || view.status === "asking" || items.length === 0,
709
718
  "data-apply-tone": muted ? "muted" : destructive ? "destructive" : "default",
710
719
  title: muted ? "It sets no fields, so applying it changes nothing." : void 0,
711
- 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"}`,
720
+ 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"}`,
712
721
  children: [
713
722
  view.status === "applying" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(LoaderIcon, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PlayIcon, { className: "h-3.5 w-3.5" }),
714
723
  view.status === "applying" ? `Applying${itemCount ? ` ${itemCount} ${itemCount === 1 ? "item" : "items"}` : ""}\u2026${elapsed ? ` ${elapsed}s` : ""}` : label
@@ -759,7 +768,7 @@ function FailureNotice({
759
768
  type: "button",
760
769
  onClick: () => setOpen((v) => !v),
761
770
  "aria-expanded": open,
762
- className: "ml-1 underline underline-offset-2 hover:no-underline",
771
+ className: "ml-1 whitespace-nowrap underline underline-offset-2 hover:no-underline",
763
772
  "data-apply-failure-details-toggle": true,
764
773
  children: open ? "Hide details" : "Details"
765
774
  }
@@ -858,6 +867,14 @@ function getDirectiveRenderer(directive) {
858
867
  var import_react6 = require("react");
859
868
  var import_content_ir5 = require("@ai-matrx/content-ir");
860
869
  var import_jsx_runtime7 = require("react/jsx-runtime");
870
+ function directiveValueWord(host, noun) {
871
+ const valueLabel = host.valueLabel;
872
+ return valueLabel ? (field, value) => valueLabel({ noun, field, value }) : null;
873
+ }
874
+ function omitKey(item, key) {
875
+ const { [key]: _omitted, ...rest } = item;
876
+ return rest;
877
+ }
861
878
  var ROWS_BEFORE_FOLD = 3;
862
879
  function ItemRow({
863
880
  directive,
@@ -874,16 +891,21 @@ function ItemRow({
874
891
  const titleColumn = (0, import_content_ir5.nounTitleColumn)(directive.noun, host.nouns);
875
892
  const title = (0, import_content_ir5.itemTitle)(item, titleColumn, index, total);
876
893
  const subtitle = (0, import_content_ir5.itemSubtitle)(item);
877
- const facts = (0, import_content_ir5.itemFacts)(item);
894
+ const valueWord = directiveValueWord(host, directive.noun);
895
+ const facts = (0, import_content_ir5.itemFacts)(titleColumn ? omitKey(item, titleColumn) : item).map((fact) => ({
896
+ ...fact,
897
+ label: directiveFieldLabel(fact.key, titleColumn),
898
+ value: valueWord?.(fact.key, fact.value) ?? fact.value
899
+ }));
878
900
  const kind = host.itemKind?.(directive.slug) ?? null;
879
901
  const display = (0, import_content_ir5.directiveDisplay)(directive.directiveClass, directive.noun, host.nouns);
880
902
  const openItem = host.openItem;
881
903
  const body = /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
882
904
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "min-w-0 shrink-0 truncate font-medium text-foreground", children: title }),
883
- /* @__PURE__ */ (0, import_jsx_runtime7.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_runtime7.jsxs)("span", { className: "truncate text-[11px] text-muted-foreground", children: [
884
- fact.value,
905
+ /* @__PURE__ */ (0, import_jsx_runtime7.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_runtime7.jsxs)("span", { className: "truncate text-[11px] text-muted-foreground", "data-item-fact": fact.key, children: [
906
+ fact.label,
885
907
  " ",
886
- fact.label
908
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-foreground/80", children: fact.value })
887
909
  ] }, fact.key)) }),
888
910
  subtitle && facts.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "hidden truncate text-[11px] text-muted-foreground sm:inline", children: subtitle }) : null,
889
911
  openItem ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(MaximizeIcon, { className: "ml-auto h-3 w-3 shrink-0 text-muted-foreground" }) : null
@@ -918,35 +940,36 @@ function DirectiveChangeList({
918
940
  if (changes.length === 0) {
919
941
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: `text-[11px] text-muted-foreground${className ? ` ${className}` : ""}`, children: "Sets no fields \u2014 nothing would change." });
920
942
  }
921
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("ul", { className: `space-y-0.5${className ? ` ${className}` : ""}`, "data-directive-changes": "", children: changes.map((change) => {
943
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("ul", { className: `@container space-y-1${className ? ` ${className}` : ""}`, "data-directive-changes": "", children: changes.map((change) => {
922
944
  const before = change.before && !change.same ? change.before : null;
923
- const was = before ? before.clears ? "empty" : before.value : null;
924
945
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
925
946
  "li",
926
947
  {
927
- className: "flex min-w-0 items-baseline gap-1.5 text-[11px]",
928
- title: before ? `${change.label}: ${before.clears ? "empty" : before.full} \u2192 ${change.full}` : `${change.label} \u2192 ${change.full}`,
948
+ className: "flex min-w-0 flex-col text-[11px] @xs:flex-row @xs:flex-wrap @xs:items-baseline @xs:gap-x-1.5",
949
+ title: before ? `${change.label}: ${before.full} \u2192 ${change.full}` : `${change.label} \u2192 ${change.full}`,
929
950
  "data-change-key": change.key,
930
951
  children: [
931
952
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "shrink-0 text-muted-foreground", children: change.label }),
932
- was !== null ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
953
+ before ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
933
954
  "span",
934
955
  {
935
- className: "min-w-0 max-w-[40%] shrink truncate text-muted-foreground line-through",
956
+ className: "min-w-0 text-muted-foreground line-through [overflow-wrap:anywhere]",
936
957
  "data-change-before": "",
937
- children: was
958
+ children: before.value
938
959
  }
939
960
  ) : null,
940
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { "aria-hidden": true, className: "shrink-0 text-muted-foreground", children: "\u2192" }),
941
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
942
- "span",
943
- {
944
- className: `min-w-0 truncate ${change.clears ? "italic text-muted-foreground" : "text-foreground"}`,
945
- "data-change-after": "",
946
- children: change.value
947
- }
948
- ),
949
- change.same ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "shrink-0 text-muted-foreground", children: "(no change)" }) : null
961
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "flex min-w-0 items-baseline gap-1.5", children: [
962
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { "aria-hidden": true, className: "shrink-0 text-muted-foreground", children: "\u2192" }),
963
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
964
+ "span",
965
+ {
966
+ className: `min-w-0 [overflow-wrap:anywhere] ${change.clears ? "italic text-muted-foreground" : "text-foreground"}`,
967
+ "data-change-after": "",
968
+ children: change.value
969
+ }
970
+ ),
971
+ change.same ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "shrink-0 text-muted-foreground", children: "(no change)" }) : null
972
+ ] })
950
973
  ]
951
974
  },
952
975
  change.key
@@ -969,12 +992,20 @@ function TargetRow({
969
992
  readsLive ? { noun: directive.noun, id: recordId } : null
970
993
  );
971
994
  const compareTo = phase.phase === "applied" ? phase.before?.[index] ?? null : readsLive ? live.values : null;
972
- const changes = isUpdate ? itemChanges(item, compareTo, { titleColumn: (0, import_content_ir5.nounTitleColumn)(directive.noun, host.nouns) }) : [];
995
+ const changes = isUpdate ? itemChanges(item, compareTo, {
996
+ titleColumn: (0, import_content_ir5.nounTitleColumn)(directive.noun, host.nouns),
997
+ valueWord: directiveValueWord(host, directive.noun)
998
+ }) : [];
973
999
  const name = host.renderRecord ? host.renderRecord({ noun: directive.noun, id: recordId, fallback, context: "row" }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "truncate font-medium text-foreground", children: fallback });
974
1000
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "py-1 text-xs", "data-directive-target": recordId, children: [
975
1001
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex min-w-0 items-center gap-2", children: [
976
1002
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "flex min-w-0 flex-1 items-center gap-1 px-1", children: name }),
977
- host.renderCopy?.({ label: fallback, value: item, size: "xs" })
1003
+ host.renderCopy?.({
1004
+ label: `this ${(0, import_content_ir5.nounLabel)(directive.noun, host.nouns).toLowerCase()}`,
1005
+ value: item,
1006
+ size: "xs",
1007
+ record: { noun: directive.noun, id: recordId }
1008
+ })
978
1009
  ] }),
979
1010
  isUpdate ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DirectiveChangeList, { changes, className: "mt-0.5 px-1" }) : null
980
1011
  ] });
@@ -1002,7 +1033,7 @@ function SideEffectDirectiveCard({ directive }) {
1002
1033
  " ",
1003
1034
  total === 1 ? "item" : "items"
1004
1035
  ] }),
1005
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "ml-auto flex items-center gap-1", children: [
1036
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "ml-auto flex min-w-0 flex-wrap items-center justify-end gap-1", children: [
1006
1037
  host.renderCopy?.({ label: display.title, value: directive.shell, size: "icon" }),
1007
1038
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1008
1039
  ApplyDirectiveButton,
@@ -1024,7 +1055,7 @@ function SideEffectDirectiveCard({ directive }) {
1024
1055
  {
1025
1056
  type: "button",
1026
1057
  onClick: () => setExpanded((v) => !v),
1027
- className: "mt-0.5 inline-flex items-center gap-1 rounded px-1 py-0.5 text-[11px] text-muted-foreground hover:text-foreground",
1058
+ 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",
1028
1059
  children: [
1029
1060
  expanded ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChevronDownIcon, { className: "h-3 w-3" }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChevronRightIcon, { className: "h-3 w-3" }),
1030
1061
  expanded ? "Show less" : `Show ${hidden} more`