@ai-matrx/content-ir-react 0.17.1 → 0.18.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 +22 -0
- package/dist/{directive-host-BOML4BQ7.d.cts → directive-host-Bw2_PrgL.d.cts} +14 -0
- package/dist/{directive-host-BOML4BQ7.d.ts → directive-host-Bw2_PrgL.d.ts} +14 -0
- package/dist/directives.cjs +56 -17
- package/dist/directives.cjs.map +1 -1
- package/dist/directives.d.cts +33 -5
- package/dist/directives.d.ts +33 -5
- package/dist/directives.js +56 -17
- package/dist/directives.js.map +1 -1
- package/dist/{host-types-CKu4ap5K.d.cts → host-types-YP_l_Q_t.d.cts} +1 -1
- package/dist/{host-types-CQ2oIzUy.d.ts → host-types-eSjFmXyF.d.ts} +1 -1
- package/dist/host.cjs.map +1 -1
- package/dist/host.d.cts +3 -3
- package/dist/host.d.ts +3 -3
- package/dist/host.js.map +1 -1
- package/dist/index.cjs +56 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +33 -5
- package/dist/index.d.ts +33 -5
- package/dist/index.js +56 -17
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +55 -17
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +2 -2
- package/dist/react.d.ts +2 -2
- package/dist/react.js +55 -17
- package/dist/react.js.map +1 -1
- package/dist/{route-D0f5H63n.d.ts → route-CtYw2cTN.d.ts} +14 -0
- package/dist/{route-D2Yz3El4.d.cts → route-KoRMp3Qn.d.cts} +14 -0
- package/dist/route.d.cts +1 -1
- package/dist/route.d.ts +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.18.0 — 2026-10-02
|
|
4
|
+
|
|
5
|
+
The G6A review of the action cards (nightly clone). Guard:
|
|
6
|
+
`__tests__/directive-card-g6a.test.tsx` (3 red on 0.17.1).
|
|
7
|
+
|
|
8
|
+
- **An applied update says what it changed, from the record of the change.** After
|
|
9
|
+
an Update applied, a reload read the record live for the old value — which by
|
|
10
|
+
then IS the new value — and the row said "Title → X (no change)". The rows now
|
|
11
|
+
compare against the live record only while the block is open; once applied they
|
|
12
|
+
draw the old value from `before` — new on `DirectiveApplyResult` and
|
|
13
|
+
`DirectiveApplyState`, each item's replaced values by item index, which aidream
|
|
14
|
+
ledgers at apply time (1122747fb5) — and without it say only "Title → new".
|
|
15
|
+
Until the ledger answers, a row compares against nothing. New exports:
|
|
16
|
+
`DirectiveApplyPhase`, `readsApplyLedger`; `ApplyDirectiveButton` takes `onPhase`.
|
|
17
|
+
- **An update that sets no field says so before the click.** "Nothing to change"
|
|
18
|
+
beside a muted Apply (`data-apply-tone="muted"`, `data-apply-nothing`). Apply
|
|
19
|
+
still works — validation offers, never blocks.
|
|
20
|
+
|
|
21
|
+
Consumer action: a host's `confirm` passes `before` from the server's per-item
|
|
22
|
+
receipts, and its `applyState` passes `before` from each item of
|
|
23
|
+
`/directives/apply_state`.
|
|
24
|
+
|
|
3
25
|
## 0.17.1
|
|
4
26
|
|
|
5
27
|
Automatic changed-only republish (docs/metadata drift since the last tag — see
|
|
@@ -15,6 +15,14 @@ interface DirectiveApplyResult {
|
|
|
15
15
|
* (no dead ends). Rendered through `renderRecord`; absent → the sentence alone.
|
|
16
16
|
*/
|
|
17
17
|
records?: DirectiveRecordRef[];
|
|
18
|
+
/**
|
|
19
|
+
* For an UPDATE: what each item's fields held just BEFORE the write, by item
|
|
20
|
+
* index (`null` for an item the server did not record). The card draws the
|
|
21
|
+
* applied change from this record — never from a live re-read, which after the
|
|
22
|
+
* apply answers the NEW value (G6A review, 2026-10-02: "(no change)" for a
|
|
23
|
+
* change that happened).
|
|
24
|
+
*/
|
|
25
|
+
before?: ReadonlyArray<Record<string, unknown> | null>;
|
|
18
26
|
}
|
|
19
27
|
/**
|
|
20
28
|
* What the server's action ledger says about ONE block, read on mount so a
|
|
@@ -43,6 +51,12 @@ interface DirectiveApplyState {
|
|
|
43
51
|
* two records, and the card says so (reviewer, 2026-10-02).
|
|
44
52
|
*/
|
|
45
53
|
copies?: number;
|
|
54
|
+
/**
|
|
55
|
+
* For an applied UPDATE: each item's replaced values, by item index, from the
|
|
56
|
+
* ledger row (`null` where the ledger has none — a row from before 2026-10-02).
|
|
57
|
+
* Absent → the card says only what each field was set to.
|
|
58
|
+
*/
|
|
59
|
+
before?: ReadonlyArray<Record<string, unknown> | null>;
|
|
46
60
|
}
|
|
47
61
|
/** One record a directive names: the noun it is, and its id. */
|
|
48
62
|
interface DirectiveRecordRef {
|
|
@@ -15,6 +15,14 @@ interface DirectiveApplyResult {
|
|
|
15
15
|
* (no dead ends). Rendered through `renderRecord`; absent → the sentence alone.
|
|
16
16
|
*/
|
|
17
17
|
records?: DirectiveRecordRef[];
|
|
18
|
+
/**
|
|
19
|
+
* For an UPDATE: what each item's fields held just BEFORE the write, by item
|
|
20
|
+
* index (`null` for an item the server did not record). The card draws the
|
|
21
|
+
* applied change from this record — never from a live re-read, which after the
|
|
22
|
+
* apply answers the NEW value (G6A review, 2026-10-02: "(no change)" for a
|
|
23
|
+
* change that happened).
|
|
24
|
+
*/
|
|
25
|
+
before?: ReadonlyArray<Record<string, unknown> | null>;
|
|
18
26
|
}
|
|
19
27
|
/**
|
|
20
28
|
* What the server's action ledger says about ONE block, read on mount so a
|
|
@@ -43,6 +51,12 @@ interface DirectiveApplyState {
|
|
|
43
51
|
* two records, and the card says so (reviewer, 2026-10-02).
|
|
44
52
|
*/
|
|
45
53
|
copies?: number;
|
|
54
|
+
/**
|
|
55
|
+
* For an applied UPDATE: each item's replaced values, by item index, from the
|
|
56
|
+
* ledger row (`null` where the ledger has none — a row from before 2026-10-02).
|
|
57
|
+
* Absent → the card says only what each field was set to.
|
|
58
|
+
*/
|
|
59
|
+
before?: ReadonlyArray<Record<string, unknown> | null>;
|
|
46
60
|
}
|
|
47
61
|
/** One record a directive names: the noun it is, and its id. */
|
|
48
62
|
interface DirectiveRecordRef {
|
package/dist/directives.cjs
CHANGED
|
@@ -39,6 +39,7 @@ __export(directives_exports, {
|
|
|
39
39
|
itemChanges: () => itemChanges,
|
|
40
40
|
itemRecordId: () => itemRecordId,
|
|
41
41
|
provisionalDirectiveSlug: () => provisionalDirectiveSlug,
|
|
42
|
+
readsApplyLedger: () => readsApplyLedger,
|
|
42
43
|
recordFallbackName: () => recordFallbackName,
|
|
43
44
|
registerDirectiveRenderer: () => registerDirectiveRenderer,
|
|
44
45
|
resetDirectiveRenderers: () => resetDirectiveRenderers,
|
|
@@ -290,17 +291,22 @@ function classIcon(directiveClass, noun) {
|
|
|
290
291
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
291
292
|
var FINISH_POLL_MS = 2e3;
|
|
292
293
|
var FINISH_POLLS_MAX = 30;
|
|
294
|
+
function readsApplyLedger(host, directive) {
|
|
295
|
+
return !!host.applyState && !!host.confirm && directive.parsed.executes && directive.items.length > 0;
|
|
296
|
+
}
|
|
293
297
|
function ApplyDirectiveButton({
|
|
294
298
|
directive,
|
|
295
299
|
label = "Apply",
|
|
296
300
|
itemCount,
|
|
297
|
-
onApplied
|
|
301
|
+
onApplied,
|
|
302
|
+
onPhase,
|
|
303
|
+
nothingToChange = false
|
|
298
304
|
}) {
|
|
299
305
|
const host = useDirectiveHost();
|
|
300
306
|
const scope = useDirectiveScope();
|
|
301
307
|
const conversationId = scope.conversationId || void 0;
|
|
302
308
|
const items = directive.items;
|
|
303
|
-
const readsLedger =
|
|
309
|
+
const readsLedger = readsApplyLedger(host, directive);
|
|
304
310
|
const initial = readsLedger ? { status: "checking" } : { status: "idle" };
|
|
305
311
|
const [state, setState] = (0, import_react2.useState)(initial);
|
|
306
312
|
const [elapsed, setElapsed] = (0, import_react2.useState)(0);
|
|
@@ -341,7 +347,8 @@ function ApplyDirectiveButton({
|
|
|
341
347
|
failed: 0,
|
|
342
348
|
message: answer.message?.trim() ? answer.message.trim() : null,
|
|
343
349
|
records: answer.records ?? [],
|
|
344
|
-
copies: Math.max(1, answer.copies ?? 1)
|
|
350
|
+
copies: Math.max(1, answer.copies ?? 1),
|
|
351
|
+
before: answer.before ?? null
|
|
345
352
|
} : prev
|
|
346
353
|
);
|
|
347
354
|
} else if (answer.state === "in_flight" && polls < FINISH_POLLS_MAX) {
|
|
@@ -366,6 +373,15 @@ function ApplyDirectiveButton({
|
|
|
366
373
|
if (timer) clearTimeout(timer);
|
|
367
374
|
};
|
|
368
375
|
}, [shellKey, read, report]);
|
|
376
|
+
const appliedClean = state.status === "applied" && state.failed === 0 ? state : null;
|
|
377
|
+
const appliedBefore = appliedClean ? appliedClean.before : null;
|
|
378
|
+
const phaseKey = state.status === "checking" || state.status === "finishing" ? "pending" : appliedClean ? `applied:${JSON.stringify(appliedBefore)}` : "open";
|
|
379
|
+
(0, import_react2.useEffect)(() => {
|
|
380
|
+
if (!onPhase) return;
|
|
381
|
+
if (phaseKey === "pending") onPhase({ phase: "pending" });
|
|
382
|
+
else if (phaseKey === "open") onPhase({ phase: "open" });
|
|
383
|
+
else onPhase({ phase: "applied", before: appliedBefore });
|
|
384
|
+
}, [phaseKey, onPhase, appliedBefore]);
|
|
369
385
|
(0, import_react2.useEffect)(() => {
|
|
370
386
|
if (state.status !== "applying") return;
|
|
371
387
|
startedAt.current = Date.now();
|
|
@@ -380,7 +396,7 @@ function ApplyDirectiveButton({
|
|
|
380
396
|
if (!confirm) return null;
|
|
381
397
|
async function handleApply(again = false) {
|
|
382
398
|
if (!confirm) return;
|
|
383
|
-
const
|
|
399
|
+
const prior = state;
|
|
384
400
|
const askedFor = identity;
|
|
385
401
|
const stillHere = () => currentIdentity.current === askedFor;
|
|
386
402
|
const priorCopies = state.status === "applied" ? state.copies : 0;
|
|
@@ -407,7 +423,7 @@ function ApplyDirectiveButton({
|
|
|
407
423
|
if (!stillHere()) return;
|
|
408
424
|
setAskingAgain(false);
|
|
409
425
|
if (!ok) {
|
|
410
|
-
setState(again ?
|
|
426
|
+
setState(again ? prior : { status: "idle" });
|
|
411
427
|
return;
|
|
412
428
|
}
|
|
413
429
|
}
|
|
@@ -426,7 +442,10 @@ function ApplyDirectiveButton({
|
|
|
426
442
|
message: result.message?.trim() ? result.message.trim() : null,
|
|
427
443
|
records: result.records ?? [],
|
|
428
444
|
// "Run again" made one more; a first apply (or a replay of one) is one.
|
|
429
|
-
copies: again ? Math.max(1, priorCopies) + 1 : Math.max(1, priorCopies)
|
|
445
|
+
copies: again ? Math.max(1, priorCopies) + 1 : Math.max(1, priorCopies),
|
|
446
|
+
// "Run again" wrote the same values over themselves; what the FIRST
|
|
447
|
+
// apply replaced is still the change this block made.
|
|
448
|
+
before: again && prior.status === "applied" && prior.before ? prior.before : result.before ?? null
|
|
430
449
|
});
|
|
431
450
|
onApplied?.();
|
|
432
451
|
} catch (error) {
|
|
@@ -528,15 +547,18 @@ function ApplyDirectiveButton({
|
|
|
528
547
|
);
|
|
529
548
|
}
|
|
530
549
|
const destructive = directive.directiveClass === "delete";
|
|
550
|
+
const muted = nothingToChange && state.status === "idle";
|
|
531
551
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "inline-flex items-center gap-2", children: [
|
|
552
|
+
muted ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-xs text-muted-foreground", "data-apply-nothing": "", children: "Nothing to change" }) : null,
|
|
532
553
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
533
554
|
"button",
|
|
534
555
|
{
|
|
535
556
|
type: "button",
|
|
536
557
|
onClick: () => void handleApply(),
|
|
537
558
|
disabled: state.status === "applying" || state.status === "asking" || items.length === 0,
|
|
538
|
-
"data-apply-tone": destructive ? "destructive" : "default",
|
|
539
|
-
|
|
559
|
+
"data-apply-tone": muted ? "muted" : destructive ? "destructive" : "default",
|
|
560
|
+
title: muted ? "It sets no fields, so applying it changes nothing." : void 0,
|
|
561
|
+
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"}`,
|
|
540
562
|
children: [
|
|
541
563
|
state.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" }),
|
|
542
564
|
state.status === "applying" ? `Applying${itemCount ? ` ${itemCount} ${itemCount === 1 ? "item" : "items"}` : ""}\u2026${elapsed ? ` ${elapsed}s` : ""}` : label
|
|
@@ -646,12 +668,13 @@ function ItemRow({
|
|
|
646
668
|
directive,
|
|
647
669
|
item,
|
|
648
670
|
index,
|
|
649
|
-
total
|
|
671
|
+
total,
|
|
672
|
+
phase
|
|
650
673
|
}) {
|
|
651
674
|
const host = useDirectiveHost();
|
|
652
675
|
const recordId = TARGETING_CLASSES.has(directive.directiveClass) ? itemRecordId(item) : null;
|
|
653
676
|
if (recordId) {
|
|
654
|
-
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TargetRow, { directive, item, recordId });
|
|
677
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TargetRow, { directive, item, recordId, index, phase });
|
|
655
678
|
}
|
|
656
679
|
const titleColumn = (0, import_content_ir3.nounTitleColumn)(directive.noun, host.nouns);
|
|
657
680
|
const title = (0, import_content_ir3.itemTitle)(item, titleColumn, index, total);
|
|
@@ -738,16 +761,20 @@ function DirectiveChangeList({
|
|
|
738
761
|
function TargetRow({
|
|
739
762
|
directive,
|
|
740
763
|
item,
|
|
741
|
-
recordId
|
|
764
|
+
recordId,
|
|
765
|
+
index,
|
|
766
|
+
phase
|
|
742
767
|
}) {
|
|
743
768
|
const host = useDirectiveHost();
|
|
744
769
|
const fallback = recordFallbackName((0, import_content_ir3.nounLabel)(directive.noun, host.nouns), recordId);
|
|
745
770
|
const isUpdate = directive.directiveClass === "update";
|
|
746
|
-
const
|
|
747
|
-
|
|
748
|
-
|
|
771
|
+
const readsLive = isUpdate && phase.phase === "open";
|
|
772
|
+
const live = useRecordValues(
|
|
773
|
+
readsLive ? host.readRecord : void 0,
|
|
774
|
+
readsLive ? { noun: directive.noun, id: recordId } : null
|
|
749
775
|
);
|
|
750
|
-
const
|
|
776
|
+
const compareTo = phase.phase === "applied" ? phase.before?.[index] ?? null : readsLive ? live.values : null;
|
|
777
|
+
const changes = isUpdate ? itemChanges(item, compareTo, { titleColumn: (0, import_content_ir3.nounTitleColumn)(directive.noun, host.nouns) }) : [];
|
|
751
778
|
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 });
|
|
752
779
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "py-1 text-xs", "data-directive-target": recordId, children: [
|
|
753
780
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
@@ -764,6 +791,10 @@ function SideEffectDirectiveCard({ directive }) {
|
|
|
764
791
|
const total = items.length;
|
|
765
792
|
const display = (0, import_content_ir3.directiveDisplay)(directive.directiveClass, directive.noun, host.nouns);
|
|
766
793
|
const Icon = classIcon(directive.directiveClass, directive.noun);
|
|
794
|
+
const [phase, setPhase] = (0, import_react4.useState)(
|
|
795
|
+
() => readsApplyLedger(host, directive) ? { phase: "pending" } : { phase: "open" }
|
|
796
|
+
);
|
|
797
|
+
const nothingToChange = directive.directiveClass === "update" && total > 0 && items.every((item) => itemChanges(item).length === 0);
|
|
767
798
|
const shown = expanded ? items : items.slice(0, ROWS_BEFORE_FOLD);
|
|
768
799
|
const hidden = total - shown.length;
|
|
769
800
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "my-3 rounded-md border border-border bg-card px-2.5 py-1.5", "data-directive": directive.slug, children: [
|
|
@@ -778,13 +809,21 @@ function SideEffectDirectiveCard({ directive }) {
|
|
|
778
809
|
] }),
|
|
779
810
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "ml-auto flex items-center gap-1", children: [
|
|
780
811
|
host.renderCopy?.({ label: display.title, value: directive.shell, size: "icon" }),
|
|
781
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
812
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
813
|
+
ApplyDirectiveButton,
|
|
814
|
+
{
|
|
815
|
+
directive,
|
|
816
|
+
itemCount: total,
|
|
817
|
+
onPhase: setPhase,
|
|
818
|
+
nothingToChange
|
|
819
|
+
}
|
|
820
|
+
)
|
|
782
821
|
] })
|
|
783
822
|
] }),
|
|
784
823
|
total === 0 ? (
|
|
785
824
|
// Stated, never silent: an empty batch is a real (and suspicious) state.
|
|
786
825
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "mt-1 text-xs text-muted-foreground", children: "No items \u2014 nothing would be written." })
|
|
787
|
-
) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mt-0.5 divide-y divide-border/60", children: shown.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ItemRow, { directive, item, index: i, total }, i)) }),
|
|
826
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mt-0.5 divide-y divide-border/60", children: shown.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ItemRow, { directive, item, index: i, total, phase }, i)) }),
|
|
788
827
|
hidden > 0 || expanded ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
789
828
|
"button",
|
|
790
829
|
{
|