@ai-matrx/content-ir-react 0.20.2 → 0.21.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,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.21.0 — 2026-10-02
4
+
5
+ The G10B review of the action cards (nightly clone). Guard:
6
+ `__tests__/directive-card-g10b.test.tsx` (5 red on 0.20.2).
7
+
8
+ - **A nameless new item says so.** New `itemOwnName(item, titleColumn)` — the item's own
9
+ name, or null — and `NO_TITLE_WORD`. A create card whose title is empty reads "No title"
10
+ (muted), never "Item"; its copy control says "this Task". One rule for the card and a
11
+ host's confirm.
12
+ - **A record's id is never shown.** `recordFallbackName(nounLabel)` — the name used when a
13
+ record's own cannot be read — is the record's type ("Task"), never "Task a8a00001". It no
14
+ longer takes an id. A link to a missing record hovered "Open Task a8a00001".
15
+ - **The confirm opens with the record in hand.** Pointing at or focusing Apply on an update
16
+ or delete card calls `DirectiveHost.readRecord` for each target, so a host that keeps a
17
+ recent read answers the confirm's read without a second round trip.
18
+
19
+ ### Consumer action
20
+
21
+ - Drop the second argument from any `recordFallbackName(label, id)` call.
22
+ - Name a new item in your confirm through `itemOwnName` (null → say it has no title).
23
+ - Keep a short-lived copy of a finished `readRecord` read (keyed by `recordRevision(id)`),
24
+ so the hover warm-up reaches the confirm.
25
+
3
26
  ## 0.20.2
4
27
 
5
28
  Automatic changed-only republish (docs/metadata drift since the last tag — see
@@ -30,6 +30,7 @@ __export(directives_exports, {
30
30
  DirectiveRender: () => DirectiveRender,
31
31
  DirectiveScopeProvider: () => DirectiveScopeProvider,
32
32
  EMPTY_VALUE_WORD: () => EMPTY_VALUE_WORD,
33
+ NO_TITLE_WORD: () => NO_TITLE_WORD,
33
34
  SideEffectDirectiveCard: () => SideEffectDirectiveCard,
34
35
  TARGETING_CLASSES: () => TARGETING_CLASSES,
35
36
  defaultFailureWords: () => defaultFailureWords,
@@ -40,6 +41,7 @@ __export(directives_exports, {
40
41
  fieldLabel: () => fieldLabel,
41
42
  getDirectiveRenderer: () => getDirectiveRenderer,
42
43
  itemChanges: () => itemChanges,
44
+ itemOwnName: () => itemOwnName,
43
45
  itemRecordId: () => itemRecordId,
44
46
  noteRecordChanged: () => noteRecordChanged,
45
47
  provisionalDirectiveSlug: () => provisionalDirectiveSlug,
@@ -98,11 +100,11 @@ function resetDirectiveRenderers() {
98
100
 
99
101
  // directives/ApplyDirectiveButton.tsx
100
102
  var import_react3 = require("react");
101
- var import_content_ir2 = require("@ai-matrx/content-ir");
103
+ var import_content_ir3 = require("@ai-matrx/content-ir");
102
104
 
103
105
  // directives/item-changes.ts
104
- var import_uuid = require("@ai-matrx/kit/uuid");
105
106
  var import_text_case = require("@ai-matrx/kit/text-case");
107
+ var import_content_ir2 = require("@ai-matrx/content-ir");
106
108
  var TARGETING_CLASSES = /* @__PURE__ */ new Set(["update", "delete"]);
107
109
  var NOT_A_CHANGE = /* @__PURE__ */ new Set(["__kind", "id"]);
108
110
  var CHANGE_VALUE_MAX = 280;
@@ -118,10 +120,14 @@ function fieldLabel(key) {
118
120
  function directiveFieldLabel(key, titleColumn) {
119
121
  return titleColumn && key === titleColumn ? "Title" : fieldLabel(key);
120
122
  }
123
+ function itemOwnName(item, titleColumn) {
124
+ const probe = (0, import_content_ir2.itemTitle)(item, titleColumn, 0, 2);
125
+ return probe === "Item 1 of 2" ? null : probe;
126
+ }
127
+ var NO_TITLE_WORD = "No title";
121
128
  var EMPTY_VALUE_WORD = "None";
122
- function recordFallbackName(nounLabel3, id) {
123
- const short = (0, import_uuid.isUuidShape)(id) ? id.slice(0, 8) : id.length > 12 ? `${id.slice(0, 12)}\u2026` : id;
124
- return `${nounLabel3} ${short}`;
129
+ function recordFallbackName(nounLabel3) {
130
+ return nounLabel3.trim() || "Record";
125
131
  }
126
132
  function describeChangeValue(value, word) {
127
133
  return describeValue(value, word);
@@ -443,7 +449,7 @@ function ApplyDirectiveButton({
443
449
  (0, import_react3.useEffect)(() => {
444
450
  currentIdentity.current = identity;
445
451
  }, [identity]);
446
- const nounText = (0, import_content_ir2.nounLabel)(directive.noun, host.nouns);
452
+ const nounText = (0, import_content_ir3.nounLabel)(directive.noun, host.nouns);
447
453
  const shellKey = readsLedger ? JSON.stringify([directive.slug, items, conversationId ?? null]) : "";
448
454
  const read = host.applyState;
449
455
  const report = host.reportError;
@@ -623,7 +629,7 @@ function ApplyDirectiveButton({
623
629
  ) : null,
624
630
  records.map((record) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "inline-flex min-w-0 items-center", children: host.renderRecord?.({
625
631
  ...record,
626
- fallback: recordFallbackName(label2, record.id),
632
+ fallback: recordFallbackName(label2),
627
633
  context: "applied",
628
634
  trashed
629
635
  }) }, `${record.noun}:${record.id}`)),
@@ -671,6 +677,14 @@ function ApplyDirectiveButton({
671
677
  }
672
678
  );
673
679
  }
680
+ const warm = () => {
681
+ const read2 = host.readRecord;
682
+ if (!read2 || !TARGETING_CLASSES.has(directive.directiveClass)) return;
683
+ for (const item of items) {
684
+ const id = itemRecordId(item);
685
+ if (id) void read2({ noun: directive.noun, id }).catch(() => null);
686
+ }
687
+ };
674
688
  const destructive = directive.directiveClass === "delete";
675
689
  const muted = nothingToChange && view.status === "idle";
676
690
  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: [
@@ -680,6 +694,8 @@ function ApplyDirectiveButton({
680
694
  {
681
695
  type: "button",
682
696
  onClick: () => void handleApply(),
697
+ onPointerEnter: warm,
698
+ onFocus: warm,
683
699
  disabled: view.status === "applying" || view.status === "asking" || items.length === 0,
684
700
  "data-apply-tone": muted ? "muted" : destructive ? "destructive" : "default",
685
701
  title: muted ? "It sets no fields, so applying it changes nothing." : void 0,
@@ -753,7 +769,7 @@ function FailureNotice({
753
769
 
754
770
  // directives/SideEffectDirectiveCard.tsx
755
771
  var import_react4 = require("react");
756
- var import_content_ir3 = require("@ai-matrx/content-ir");
772
+ var import_content_ir4 = require("@ai-matrx/content-ir");
757
773
  var import_jsx_runtime4 = require("react/jsx-runtime");
758
774
  function directiveValueWord(host, noun) {
759
775
  const valueLabel = host.valueLabel;
@@ -776,20 +792,28 @@ function ItemRow({
776
792
  if (recordId) {
777
793
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TargetRow, { directive, item, recordId, index, phase });
778
794
  }
779
- const titleColumn = (0, import_content_ir3.nounTitleColumn)(directive.noun, host.nouns);
780
- const title = (0, import_content_ir3.itemTitle)(item, titleColumn, index, total);
781
- const subtitle = (0, import_content_ir3.itemSubtitle)(item);
795
+ const titleColumn = (0, import_content_ir4.nounTitleColumn)(directive.noun, host.nouns);
796
+ const ownName = itemOwnName(item, titleColumn);
797
+ const title = ownName ?? NO_TITLE_WORD;
798
+ const subtitle = (0, import_content_ir4.itemSubtitle)(item);
782
799
  const valueWord = directiveValueWord(host, directive.noun);
783
- const facts = (0, import_content_ir3.itemFacts)(titleColumn ? omitKey(item, titleColumn) : item).map((fact) => ({
800
+ const facts = (0, import_content_ir4.itemFacts)(titleColumn ? omitKey(item, titleColumn) : item).map((fact) => ({
784
801
  ...fact,
785
802
  label: directiveFieldLabel(fact.key, titleColumn),
786
803
  value: valueWord?.(fact.key, fact.value) ?? fact.value
787
804
  }));
788
805
  const kind = host.itemKind?.(directive.slug) ?? null;
789
- const display = (0, import_content_ir3.directiveDisplay)(directive.directiveClass, directive.noun, host.nouns);
806
+ const display = (0, import_content_ir4.directiveDisplay)(directive.directiveClass, directive.noun, host.nouns);
790
807
  const openItem = host.openItem;
791
808
  const body = /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
792
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "min-w-0 shrink-0 truncate font-medium text-foreground", children: title }),
809
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
810
+ "span",
811
+ {
812
+ className: `min-w-0 shrink-0 truncate font-medium ${ownName ? "text-foreground" : "italic text-muted-foreground"}`,
813
+ "data-item-untitled": ownName ? void 0 : "",
814
+ children: title
815
+ }
816
+ ),
793
817
  /* @__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
818
  fact.label,
795
819
  " ",
@@ -805,7 +829,7 @@ function ItemRow({
805
829
  type: "button",
806
830
  onClick: () => openItem({
807
831
  // Stamped so the panel's kind pipeline types it from its own first key.
808
- item: (0, import_content_ir3.asKindInstance)(item, kind) ?? item,
832
+ item: (0, import_content_ir4.asKindInstance)(item, kind) ?? item,
809
833
  itemKind: kind,
810
834
  title,
811
835
  subtitle: `${display.title} \xB7 item ${index + 1} of ${total}`,
@@ -818,7 +842,12 @@ function ItemRow({
818
842
  children: body
819
843
  }
820
844
  ) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "flex min-w-0 flex-1 items-center gap-2 px-1 py-0.5", children: body }),
821
- host.renderCopy?.({ label: title, value: item, kind, size: "xs" })
845
+ host.renderCopy?.({
846
+ label: ownName ?? `this ${(0, import_content_ir4.nounLabel)(directive.noun, host.nouns)}`,
847
+ value: item,
848
+ kind,
849
+ size: "xs"
850
+ })
822
851
  ] });
823
852
  }
824
853
  function DirectiveChangeList({
@@ -872,7 +901,7 @@ function TargetRow({
872
901
  phase
873
902
  }) {
874
903
  const host = useDirectiveHost();
875
- const fallback = recordFallbackName((0, import_content_ir3.nounLabel)(directive.noun, host.nouns), recordId);
904
+ const fallback = recordFallbackName((0, import_content_ir4.nounLabel)(directive.noun, host.nouns));
876
905
  const isUpdate = directive.directiveClass === "update";
877
906
  const readsLive = isUpdate && phase.phase === "open";
878
907
  const live = useRecordValues(
@@ -881,7 +910,7 @@ function TargetRow({
881
910
  );
882
911
  const compareTo = phase.phase === "applied" ? phase.before?.[index] ?? null : readsLive ? live.values : null;
883
912
  const changes = isUpdate ? itemChanges(item, compareTo, {
884
- titleColumn: (0, import_content_ir3.nounTitleColumn)(directive.noun, host.nouns),
913
+ titleColumn: (0, import_content_ir4.nounTitleColumn)(directive.noun, host.nouns),
885
914
  valueWord: directiveValueWord(host, directive.noun)
886
915
  }) : [];
887
916
  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 });
@@ -889,7 +918,7 @@ function TargetRow({
889
918
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex min-w-0 items-center gap-2", children: [
890
919
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "flex min-w-0 flex-1 items-center gap-1 px-1", children: name }),
891
920
  host.renderCopy?.({
892
- label: `this ${(0, import_content_ir3.nounLabel)(directive.noun, host.nouns).toLowerCase()}`,
921
+ label: `this ${(0, import_content_ir4.nounLabel)(directive.noun, host.nouns).toLowerCase()}`,
893
922
  value: item,
894
923
  size: "xs",
895
924
  record: { noun: directive.noun, id: recordId }
@@ -903,7 +932,7 @@ function SideEffectDirectiveCard({ directive }) {
903
932
  const [expanded, setExpanded] = (0, import_react4.useState)(false);
904
933
  const items = directive.items;
905
934
  const total = items.length;
906
- const display = (0, import_content_ir3.directiveDisplay)(directive.directiveClass, directive.noun, host.nouns);
935
+ const display = (0, import_content_ir4.directiveDisplay)(directive.directiveClass, directive.noun, host.nouns);
907
936
  const Icon = classIcon(directive.directiveClass, directive.noun);
908
937
  const [phase, setPhase] = (0, import_react4.useState)(
909
938
  () => readsApplyLedger(host, directive) ? { phase: "pending" } : { phase: "open" }
@@ -954,12 +983,12 @@ function SideEffectDirectiveCard({ directive }) {
954
983
  }
955
984
 
956
985
  // directives/DirectiveFallbackCard.tsx
957
- var import_content_ir4 = require("@ai-matrx/content-ir");
986
+ var import_content_ir5 = require("@ai-matrx/content-ir");
958
987
  var import_jsx_runtime5 = require("react/jsx-runtime");
959
988
  function DirectiveFallbackCard({ directive, reason }) {
960
989
  const host = useDirectiveHost();
961
990
  const count = directive.items.length;
962
- const display = (0, import_content_ir4.directiveDisplay)(directive.directiveClass, directive.noun, host.nouns);
991
+ const display = (0, import_content_ir5.directiveDisplay)(directive.directiveClass, directive.noun, host.nouns);
963
992
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "my-3 inline-flex flex-wrap items-center gap-2 rounded-md border border-border bg-muted px-2.5 py-1 text-sm", children: [
964
993
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(BoxesIcon, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }),
965
994
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-foreground", children: display.title }),
@@ -982,7 +1011,7 @@ function DirectiveFallbackCard({ directive, reason }) {
982
1011
  }
983
1012
 
984
1013
  // directives/DirectiveProvisionalCard.tsx
985
- var import_content_ir5 = require("@ai-matrx/content-ir");
1014
+ var import_content_ir6 = require("@ai-matrx/content-ir");
986
1015
  var import_jsx_runtime6 = require("react/jsx-runtime");
987
1016
  var COMPLETE_SLUG_RE = /^\{\s*"__kind"\s*:\s*"([^"]+)"/;
988
1017
  function provisionalDirectiveSlug(content) {
@@ -992,8 +1021,8 @@ function provisionalDirectiveSlug(content) {
992
1021
  function DirectiveProvisionalCard({ content, streaming, className }) {
993
1022
  const host = useDirectiveHost();
994
1023
  const slug = provisionalDirectiveSlug(content);
995
- const known = slug ? (0, import_content_ir5.parseDirectiveSlug)(slug) : null;
996
- const display = known ? (0, import_content_ir5.directiveDisplay)(known.directiveClass, known.noun, host.nouns) : null;
1024
+ const known = slug ? (0, import_content_ir6.parseDirectiveSlug)(slug) : null;
1025
+ const display = known ? (0, import_content_ir6.directiveDisplay)(known.directiveClass, known.noun, host.nouns) : null;
997
1026
  const Icon = known ? classIcon(known.directiveClass, known.noun) : BoxesIcon;
998
1027
  const title = display?.title ?? "Structured action";
999
1028
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
@@ -1022,7 +1051,7 @@ function DirectiveProvisionalCard({ content, streaming, className }) {
1022
1051
  }
1023
1052
 
1024
1053
  // directives/DirectiveRender.tsx
1025
- var import_content_ir6 = require("@ai-matrx/content-ir");
1054
+ var import_content_ir7 = require("@ai-matrx/content-ir");
1026
1055
  var import_jsx_runtime7 = require("react/jsx-runtime");
1027
1056
  function isWholeJson(content) {
1028
1057
  try {
@@ -1038,13 +1067,13 @@ function DirectiveRender({ conversationId, ...props }) {
1038
1067
  }
1039
1068
  function DirectiveRenderInner({ content, className, streaming }) {
1040
1069
  const host = useDirectiveHost();
1041
- const directive = (0, import_content_ir6.tryDecodeDirectiveContent)(content, (message) => {
1070
+ const directive = (0, import_content_ir7.tryDecodeDirectiveContent)(content, (message) => {
1042
1071
  const line = `[kind-directives] ${message}`;
1043
1072
  if (host.reportError) host.reportError(line);
1044
1073
  else console.error(line);
1045
1074
  });
1046
1075
  if (!directive) {
1047
- if (typeof content === "string" && (0, import_content_ir6.looksLikeDirectiveHead)(content) && !isWholeJson(content)) {
1076
+ if (typeof content === "string" && (0, import_content_ir7.looksLikeDirectiveHead)(content) && !isWholeJson(content)) {
1048
1077
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DirectiveProvisionalCard, { content, streaming: streaming !== false, className });
1049
1078
  }
1050
1079
  const raw = typeof content === "string" ? content : JSON.stringify(content, null, 2);