@ai-matrx/content-ir-react 0.16.1 → 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,19 @@
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
+
3
17
  ## 0.16.1 — 2026-10-02
4
18
 
5
19
  - **A record's title column reads "Title" everywhere.** `itemChanges(item, current,
@@ -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,
@@ -167,6 +169,30 @@ function itemChanges(item, current, options = {}) {
167
169
  return out;
168
170
  }
169
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
+
170
196
  // directives/icons.tsx
171
197
  var import_jsx_runtime2 = require("react/jsx-runtime");
172
198
  function Svg({ children, ...props }) {
@@ -424,10 +450,23 @@ function ApplyDirectiveButton({
424
450
  children: [
425
451
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "inline-flex min-w-0 items-start gap-1", children: [
426
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" }),
427
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "min-w-0", children: [
428
- state.message ?? `Applied ${state.applied}`,
429
- bad && !state.message ? ` \xB7 ${state.failed} failed` : ""
430
- ] })
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}` })
431
470
  ] }),
432
471
  state.copies > 1 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
433
472
  "span",
@@ -504,10 +543,64 @@ function ApplyDirectiveButton({
504
543
  ]
505
544
  }
506
545
  ),
507
- 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: [
508
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AlertIcon, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }),
509
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: state.message })
510
- ] }) : 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
511
604
  ] });
512
605
  }
513
606