@ai-matrx/content-ir-react 0.16.0 → 0.17.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,29 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.17.0 — 2026-10-02
4
+
5
+ - **A failed apply speaks plainly.** The card no longer prints the server's text as its
6
+ sentence (G5 review: "unknown / non-writable noun 'task'. Writable: [...]"). It says what
7
+ failed and what to do — "The task wasn't created. Try again in a moment." / "1 of 2 weren't
8
+ created. Retry to try those again." — and keeps the raw text behind a **Details** toggle
9
+ (`data-apply-failure`, `data-apply-failure-details`). New host seam
10
+ `DirectiveHost.explainFailure(failure) → { what, next } | null` names failures the host
11
+ recognises; `defaultFailureWords` / `failureWords` are exported. Guard:
12
+ `__tests__/directive-failure-words.test.tsx` (3 red on 0.16.1).
13
+
14
+ Consumer action: a host whose `confirm` rejected with a person-ready sentence should map it
15
+ in `explainFailure`; otherwise that sentence now sits behind Details under the default words.
16
+
17
+ ## 0.16.1 — 2026-10-02
18
+
19
+ - **A record's title column reads "Title" everywhere.** `itemChanges(item, current,
20
+ { titleColumn })` labels the noun's title column "Title" (a note's is stored in
21
+ `label`, and the confirm said "Label" while the form said "Title"); the card's update
22
+ row passes `nounTitleColumn`. Guard: `__tests__/directive-card-g3.test.tsx` ("a
23
+ record's title column…", 2 red on 0.16.0).
24
+
25
+ Consumer action: a host's own confirm passes `{ titleColumn }` to `itemChanges`.
26
+
3
27
  ## 0.16.0 — 2026-10-02
4
28
 
5
29
  The G3 review of the action cards (nightly clone). Guard: `__tests__/directive-card-g3.test.tsx`
@@ -166,6 +166,32 @@ interface DirectiveHost {
166
166
  itemKind?: DirectiveItemKindLookup;
167
167
  /** Where a decode failure or an unhonourable shell is reported. Default: console.error. */
168
168
  reportError?: (message: string) => void;
169
+ /**
170
+ * Plain words for a failed apply the host recognises ("This button can't
171
+ * create a task yet." + "Create it from Tasks instead."). The card never shows
172
+ * the server's raw text as its sentence: it shows these words — or its own
173
+ * verb-aware default — and keeps the raw text behind "Details". Return null
174
+ * to use the default.
175
+ */
176
+ explainFailure?: (failure: DirectiveFailure) => DirectiveFailureWords | null;
177
+ }
178
+ /** One failed apply, as the card knows it. */
179
+ interface DirectiveFailure {
180
+ /** The server's / host's own text — technical; shown only behind Details. */
181
+ raw: string;
182
+ directiveClass: string;
183
+ noun: string;
184
+ /** The noun's display label ("Task"). */
185
+ nounLabel: string;
186
+ /** Items that failed, when some applied and some did not. */
187
+ failed?: number;
188
+ /** Items in the block. */
189
+ total?: number;
190
+ }
191
+ /** What failed and what to do — each one short sentence. */
192
+ interface DirectiveFailureWords {
193
+ what: string;
194
+ next: string;
169
195
  }
170
196
  /** Where a directive renders — today, the conversation it sits in (if any). */
171
197
  interface DirectiveScope {
@@ -193,4 +219,4 @@ declare function DirectiveHostProvider({ host, children }: DirectiveHostProvider
193
219
  */
194
220
  declare function useDirectiveHost(): DirectiveHost;
195
221
 
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 };
222
+ 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 };
@@ -166,6 +166,32 @@ interface DirectiveHost {
166
166
  itemKind?: DirectiveItemKindLookup;
167
167
  /** Where a decode failure or an unhonourable shell is reported. Default: console.error. */
168
168
  reportError?: (message: string) => void;
169
+ /**
170
+ * Plain words for a failed apply the host recognises ("This button can't
171
+ * create a task yet." + "Create it from Tasks instead."). The card never shows
172
+ * the server's raw text as its sentence: it shows these words — or its own
173
+ * verb-aware default — and keeps the raw text behind "Details". Return null
174
+ * to use the default.
175
+ */
176
+ explainFailure?: (failure: DirectiveFailure) => DirectiveFailureWords | null;
177
+ }
178
+ /** One failed apply, as the card knows it. */
179
+ interface DirectiveFailure {
180
+ /** The server's / host's own text — technical; shown only behind Details. */
181
+ raw: string;
182
+ directiveClass: string;
183
+ noun: string;
184
+ /** The noun's display label ("Task"). */
185
+ nounLabel: string;
186
+ /** Items that failed, when some applied and some did not. */
187
+ failed?: number;
188
+ /** Items in the block. */
189
+ total?: number;
190
+ }
191
+ /** What failed and what to do — each one short sentence. */
192
+ interface DirectiveFailureWords {
193
+ what: string;
194
+ next: string;
169
195
  }
170
196
  /** Where a directive renders — today, the conversation it sits in (if any). */
171
197
  interface DirectiveScope {
@@ -193,4 +219,4 @@ declare function DirectiveHostProvider({ host, children }: DirectiveHostProvider
193
219
  */
194
220
  declare function useDirectiveHost(): DirectiveHost;
195
221
 
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 };
222
+ 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 };
@@ -31,7 +31,9 @@ __export(directives_exports, {
31
31
  DirectiveScopeProvider: () => DirectiveScopeProvider,
32
32
  SideEffectDirectiveCard: () => SideEffectDirectiveCard,
33
33
  TARGETING_CLASSES: () => TARGETING_CLASSES,
34
+ defaultFailureWords: () => defaultFailureWords,
34
35
  describeChangeValue: () => describeChangeValue,
36
+ failureWords: () => failureWords,
35
37
  fieldLabel: () => fieldLabel,
36
38
  getDirectiveRenderer: () => getDirectiveRenderer,
37
39
  itemChanges: () => itemChanges,
@@ -151,11 +153,13 @@ function sameValue(a, b) {
151
153
  if (typeof a === "object" || typeof b === "object") return safeJson(a) === safeJson(b);
152
154
  return false;
153
155
  }
154
- function itemChanges(item, current) {
156
+ function itemChanges(item, current, options = {}) {
155
157
  const out = [];
158
+ const titleColumn = options.titleColumn ?? null;
156
159
  for (const [key, raw] of Object.entries(item)) {
157
160
  if (NOT_A_CHANGE.has(key)) continue;
158
- const change = { key, label: fieldLabel(key), ...describeValue(raw) };
161
+ const label = key === titleColumn ? "Title" : fieldLabel(key);
162
+ const change = { key, label, ...describeValue(raw) };
159
163
  if (current && Object.prototype.hasOwnProperty.call(current, key)) {
160
164
  change.before = describeValue(current[key]);
161
165
  change.same = sameValue(current[key], raw);
@@ -165,6 +169,30 @@ function itemChanges(item, current) {
165
169
  return out;
166
170
  }
167
171
 
172
+ // directives/failure-words.ts
173
+ var PAST = {
174
+ create: "created",
175
+ update: "updated",
176
+ delete: "deleted"
177
+ };
178
+ function defaultFailureWords(failure) {
179
+ const verbed = PAST[failure.directiveClass] ?? "applied";
180
+ const noun = failure.nounLabel.trim().toLowerCase() || "item";
181
+ if (failure.failed && failure.total && failure.total > 1) {
182
+ return {
183
+ what: `${failure.failed} of ${failure.total} weren't ${verbed}.`,
184
+ next: "Retry to try those again."
185
+ };
186
+ }
187
+ return {
188
+ what: `The ${noun} wasn't ${verbed}.`,
189
+ next: "Try again in a moment."
190
+ };
191
+ }
192
+ function failureWords(failure, explain) {
193
+ return explain?.(failure) ?? defaultFailureWords(failure);
194
+ }
195
+
168
196
  // directives/icons.tsx
169
197
  var import_jsx_runtime2 = require("react/jsx-runtime");
170
198
  function Svg({ children, ...props }) {
@@ -422,10 +450,23 @@ function ApplyDirectiveButton({
422
450
  children: [
423
451
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "inline-flex min-w-0 items-start gap-1", children: [
424
452
  bad ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AlertIcon, { className: "mt-px h-3.5 w-3.5 shrink-0" }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CheckCircleIcon, { className: "mt-px h-3.5 w-3.5 shrink-0" }),
425
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "min-w-0", children: [
426
- state.message ?? `Applied ${state.applied}`,
427
- bad && !state.message ? ` \xB7 ${state.failed} failed` : ""
428
- ] })
453
+ bad ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
454
+ FailureNotice,
455
+ {
456
+ words: failureWords(
457
+ {
458
+ raw: state.message ?? `${state.failed} failed`,
459
+ directiveClass: directive.directiveClass,
460
+ noun: directive.noun,
461
+ nounLabel: nounText,
462
+ failed: state.failed,
463
+ total: state.applied + state.failed
464
+ },
465
+ host.explainFailure
466
+ ),
467
+ raw: state.message
468
+ }
469
+ ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "min-w-0", children: state.message ?? `Applied ${state.applied}` })
429
470
  ] }),
430
471
  state.copies > 1 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
431
472
  "span",
@@ -502,10 +543,64 @@ function ApplyDirectiveButton({
502
543
  ]
503
544
  }
504
545
  ),
505
- state.status === "error" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "inline-flex max-w-sm items-start gap-1 text-xs text-destructive", children: [
506
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AlertIcon, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }),
507
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: state.message })
508
- ] }) : null
546
+ state.status === "error" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
547
+ "span",
548
+ {
549
+ className: "inline-flex max-w-sm items-start gap-1 text-xs text-destructive",
550
+ "data-apply-state": "error",
551
+ children: [
552
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AlertIcon, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }),
553
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
554
+ FailureNotice,
555
+ {
556
+ words: failureWords(
557
+ {
558
+ raw: state.message,
559
+ directiveClass: directive.directiveClass,
560
+ noun: directive.noun,
561
+ nounLabel: nounText
562
+ },
563
+ host.explainFailure
564
+ ),
565
+ raw: state.message
566
+ }
567
+ )
568
+ ]
569
+ }
570
+ ) : null
571
+ ] });
572
+ }
573
+ function FailureNotice({
574
+ words,
575
+ raw
576
+ }) {
577
+ const [open, setOpen] = (0, import_react2.useState)(false);
578
+ const detail = raw?.trim() ?? "";
579
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "flex min-w-0 flex-col gap-0.5", "data-apply-failure": true, children: [
580
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "min-w-0", children: [
581
+ words.what,
582
+ " ",
583
+ words.next,
584
+ detail ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
585
+ "button",
586
+ {
587
+ type: "button",
588
+ onClick: () => setOpen((v) => !v),
589
+ "aria-expanded": open,
590
+ className: "ml-1 underline underline-offset-2 hover:no-underline",
591
+ "data-apply-failure-details-toggle": true,
592
+ children: open ? "Hide details" : "Details"
593
+ }
594
+ ) : null
595
+ ] }),
596
+ open && detail ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
597
+ "span",
598
+ {
599
+ className: "block max-w-sm select-text break-words font-mono text-[11px] text-muted-foreground",
600
+ "data-apply-failure-details": true,
601
+ children: detail
602
+ }
603
+ ) : null
509
604
  ] });
510
605
  }
511
606
 
@@ -652,7 +747,7 @@ function TargetRow({
652
747
  isUpdate ? host.readRecord : void 0,
653
748
  isUpdate ? { noun: directive.noun, id: recordId } : null
654
749
  );
655
- const changes = isUpdate ? itemChanges(item, current.values) : [];
750
+ const changes = isUpdate ? itemChanges(item, current.values, { titleColumn: (0, import_content_ir3.nounTitleColumn)(directive.noun, host.nouns) }) : [];
656
751
  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
752
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "py-1 text-xs", "data-directive-target": recordId, children: [
658
753
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex min-w-0 items-center gap-2", children: [