@ai-matrx/content-ir-react 0.18.1 → 0.19.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/CHANGELOG.md +30 -0
- package/dist/directives.cjs +97 -47
- package/dist/directives.cjs.map +1 -1
- package/dist/directives.d.cts +10 -1
- package/dist/directives.d.ts +10 -1
- package/dist/directives.js +95 -45
- package/dist/directives.js.map +1 -1
- package/dist/index.cjs +99 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +97 -47
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +94 -47
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +92 -45
- package/dist/react.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.19.1 — 2026-10-02
|
|
4
|
+
|
|
5
|
+
- **One record-revision store per page.** Every entry of this package (`.`,
|
|
6
|
+
`./directives`, …) is bundled self-contained, so 0.19.0's module-level counter
|
|
7
|
+
was a separate counter per entry: an apply announced through one entry was
|
|
8
|
+
never seen by a reader imported through the other. The store now lives in a
|
|
9
|
+
`Symbol.for("@ai-matrx/content-ir-react/record-revisions")` slot on
|
|
10
|
+
`globalThis`. Guard: `directive-record-values-g7.test.tsx` ("the store is ONE
|
|
11
|
+
per page…", red on 0.19.0).
|
|
12
|
+
|
|
13
|
+
No consumer action.
|
|
14
|
+
|
|
15
|
+
## 0.19.0 — 2026-10-02
|
|
16
|
+
|
|
17
|
+
- **A record's old value is the value it holds NOW.** `useRecordValues` read a
|
|
18
|
+
record ONCE per `noun:id`, so after Update A applied, Update B naming the same
|
|
19
|
+
record — its row, and a host confirm whose body stayed mounted between
|
|
20
|
+
questions — still showed A's old value as "old" (G7: "G6A renamed target"
|
|
21
|
+
while the database held "G6A second rename"). Every apply now announces the
|
|
22
|
+
records it touched (`noteRecordChanged`, for update/delete targets and the
|
|
23
|
+
written records, even when the apply fails) and every `useRecordValues`
|
|
24
|
+
naming one reads it again. New exports: `noteRecordChanged`,
|
|
25
|
+
`recordRevision`, `useRecordRevision`. Guard:
|
|
26
|
+
`__tests__/directive-record-values-g7.test.tsx` (2 red on 0.18.1).
|
|
27
|
+
|
|
28
|
+
Consumer action: a host whose own writers change a record calls
|
|
29
|
+
`noteRecordChanged(id)`; a host `readRecord` that shares in-flight reads keys
|
|
30
|
+
them by `recordRevision(id)`, so a read started before a write is never handed
|
|
31
|
+
to a reader asking after it.
|
|
32
|
+
|
|
3
33
|
## 0.18.1 — 2026-10-02
|
|
4
34
|
|
|
5
35
|
- **An apply belongs to the block, not to the card that asked.** A note re-renders
|
package/dist/directives.cjs
CHANGED
|
@@ -38,13 +38,16 @@ __export(directives_exports, {
|
|
|
38
38
|
getDirectiveRenderer: () => getDirectiveRenderer,
|
|
39
39
|
itemChanges: () => itemChanges,
|
|
40
40
|
itemRecordId: () => itemRecordId,
|
|
41
|
+
noteRecordChanged: () => noteRecordChanged,
|
|
41
42
|
provisionalDirectiveSlug: () => provisionalDirectiveSlug,
|
|
42
43
|
readsApplyLedger: () => readsApplyLedger,
|
|
43
44
|
recordFallbackName: () => recordFallbackName,
|
|
45
|
+
recordRevision: () => recordRevision,
|
|
44
46
|
registerDirectiveRenderer: () => registerDirectiveRenderer,
|
|
45
47
|
resetDirectiveRenderers: () => resetDirectiveRenderers,
|
|
46
48
|
useDirectiveHost: () => useDirectiveHost,
|
|
47
49
|
useDirectiveScope: () => useDirectiveScope,
|
|
50
|
+
useRecordRevision: () => useRecordRevision,
|
|
48
51
|
useRecordValues: () => useRecordValues
|
|
49
52
|
});
|
|
50
53
|
module.exports = __toCommonJS(directives_exports);
|
|
@@ -91,7 +94,7 @@ function resetDirectiveRenderers() {
|
|
|
91
94
|
}
|
|
92
95
|
|
|
93
96
|
// directives/ApplyDirectiveButton.tsx
|
|
94
|
-
var
|
|
97
|
+
var import_react3 = require("react");
|
|
95
98
|
var import_content_ir2 = require("@ai-matrx/content-ir");
|
|
96
99
|
|
|
97
100
|
// directives/item-changes.ts
|
|
@@ -170,6 +173,73 @@ function itemChanges(item, current, options = {}) {
|
|
|
170
173
|
return out;
|
|
171
174
|
}
|
|
172
175
|
|
|
176
|
+
// directives/record-values.ts
|
|
177
|
+
var import_react2 = require("react");
|
|
178
|
+
var STORE_SLOT = /* @__PURE__ */ Symbol.for("@ai-matrx/content-ir-react/record-revisions");
|
|
179
|
+
function revisionStore() {
|
|
180
|
+
const slots = globalThis;
|
|
181
|
+
let store = slots[STORE_SLOT];
|
|
182
|
+
if (!store) {
|
|
183
|
+
store = { revisions: /* @__PURE__ */ new Map(), listeners: /* @__PURE__ */ new Set() };
|
|
184
|
+
slots[STORE_SLOT] = store;
|
|
185
|
+
}
|
|
186
|
+
return store;
|
|
187
|
+
}
|
|
188
|
+
var { revisions, listeners } = revisionStore();
|
|
189
|
+
function recordRevision(id) {
|
|
190
|
+
return revisions.get(id) ?? 0;
|
|
191
|
+
}
|
|
192
|
+
function noteRecordChanged(id) {
|
|
193
|
+
if (!id) return;
|
|
194
|
+
revisions.set(id, recordRevision(id) + 1);
|
|
195
|
+
for (const listener of listeners) listener();
|
|
196
|
+
}
|
|
197
|
+
function subscribeRevisions(listener) {
|
|
198
|
+
listeners.add(listener);
|
|
199
|
+
return () => {
|
|
200
|
+
listeners.delete(listener);
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function useRecordRevision(id) {
|
|
204
|
+
return (0, import_react2.useSyncExternalStore)(
|
|
205
|
+
subscribeRevisions,
|
|
206
|
+
() => recordRevision(id),
|
|
207
|
+
() => 0
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
function useRecordValues(read, ref) {
|
|
211
|
+
const revision = useRecordRevision(ref?.id ?? "");
|
|
212
|
+
const key = ref && read ? `${ref.noun}:${ref.id}#${revision}` : "";
|
|
213
|
+
const [result, setResult] = (0, import_react2.useState)({
|
|
214
|
+
key: "",
|
|
215
|
+
values: null,
|
|
216
|
+
status: "idle"
|
|
217
|
+
});
|
|
218
|
+
(0, import_react2.useEffect)(() => {
|
|
219
|
+
if (!key || !read) return;
|
|
220
|
+
const split = key.indexOf(":");
|
|
221
|
+
const target = {
|
|
222
|
+
noun: key.slice(0, split),
|
|
223
|
+
id: key.slice(split + 1, key.lastIndexOf("#"))
|
|
224
|
+
};
|
|
225
|
+
let live = true;
|
|
226
|
+
setResult({ key, values: null, status: "loading" });
|
|
227
|
+
read(target).then((values) => {
|
|
228
|
+
if (!live) return;
|
|
229
|
+
setResult({ key, values, status: values ? "ready" : "unavailable" });
|
|
230
|
+
}).catch(() => {
|
|
231
|
+
if (!live) return;
|
|
232
|
+
setResult({ key, values: null, status: "unavailable" });
|
|
233
|
+
});
|
|
234
|
+
return () => {
|
|
235
|
+
live = false;
|
|
236
|
+
};
|
|
237
|
+
}, [key, read]);
|
|
238
|
+
if (!key) return { values: null, status: "idle" };
|
|
239
|
+
if (result.key !== key) return { values: null, status: "loading" };
|
|
240
|
+
return { values: result.values, status: result.status };
|
|
241
|
+
}
|
|
242
|
+
|
|
173
243
|
// directives/failure-words.ts
|
|
174
244
|
var PAST = {
|
|
175
245
|
create: "created",
|
|
@@ -289,6 +359,13 @@ function classIcon(directiveClass, noun) {
|
|
|
289
359
|
|
|
290
360
|
// directives/ApplyDirectiveButton.tsx
|
|
291
361
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
362
|
+
function announceChangedRecords(directive, items) {
|
|
363
|
+
if (!TARGETING_CLASSES.has(directive.directiveClass)) return;
|
|
364
|
+
for (const item of items) {
|
|
365
|
+
const id = itemRecordId(item);
|
|
366
|
+
if (id) noteRecordChanged(id);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
292
369
|
var applyOutcomes = /* @__PURE__ */ new WeakMap();
|
|
293
370
|
var EMPTY_AUTHORITY = {};
|
|
294
371
|
var outcomeListeners = /* @__PURE__ */ new Set();
|
|
@@ -311,7 +388,7 @@ function subscribeOutcomes(listener) {
|
|
|
311
388
|
};
|
|
312
389
|
}
|
|
313
390
|
function useApplyOutcome(host, identity) {
|
|
314
|
-
return (0,
|
|
391
|
+
return (0, import_react3.useSyncExternalStore)(
|
|
315
392
|
subscribeOutcomes,
|
|
316
393
|
() => applyOutcomes.get(host)?.get(identity) ?? null,
|
|
317
394
|
() => null
|
|
@@ -336,12 +413,12 @@ function ApplyDirectiveButton({
|
|
|
336
413
|
const items = directive.items;
|
|
337
414
|
const readsLedger = readsApplyLedger(host, directive);
|
|
338
415
|
const initial = readsLedger ? { status: "checking" } : { status: "idle" };
|
|
339
|
-
const [state, setState] = (0,
|
|
340
|
-
const [elapsed, setElapsed] = (0,
|
|
341
|
-
const [askingAgain, setAskingAgain] = (0,
|
|
342
|
-
const startedAt = (0,
|
|
416
|
+
const [state, setState] = (0, import_react3.useState)(initial);
|
|
417
|
+
const [elapsed, setElapsed] = (0, import_react3.useState)(0);
|
|
418
|
+
const [askingAgain, setAskingAgain] = (0, import_react3.useState)(false);
|
|
419
|
+
const startedAt = (0, import_react3.useRef)(null);
|
|
343
420
|
const identity = JSON.stringify([directive.slug, items, conversationId ?? null]);
|
|
344
|
-
const [stateFor, setStateFor] = (0,
|
|
421
|
+
const [stateFor, setStateFor] = (0, import_react3.useState)(identity);
|
|
345
422
|
if (stateFor !== identity) {
|
|
346
423
|
setStateFor(identity);
|
|
347
424
|
setState(initial);
|
|
@@ -350,15 +427,15 @@ function ApplyDirectiveButton({
|
|
|
350
427
|
}
|
|
351
428
|
const shared = useApplyOutcome(host.confirm ?? EMPTY_AUTHORITY, identity);
|
|
352
429
|
const view = state.status === "asking" || !shared ? state : shared;
|
|
353
|
-
const currentIdentity = (0,
|
|
354
|
-
(0,
|
|
430
|
+
const currentIdentity = (0, import_react3.useRef)(identity);
|
|
431
|
+
(0, import_react3.useEffect)(() => {
|
|
355
432
|
currentIdentity.current = identity;
|
|
356
433
|
}, [identity]);
|
|
357
434
|
const nounText = (0, import_content_ir2.nounLabel)(directive.noun, host.nouns);
|
|
358
435
|
const shellKey = readsLedger ? JSON.stringify([directive.slug, items, conversationId ?? null]) : "";
|
|
359
436
|
const read = host.applyState;
|
|
360
437
|
const report = host.reportError;
|
|
361
|
-
(0,
|
|
438
|
+
(0, import_react3.useEffect)(() => {
|
|
362
439
|
if (!shellKey || !read) return;
|
|
363
440
|
const [slug, shellItems, inConversation] = JSON.parse(shellKey);
|
|
364
441
|
const shell = { __kind: slug, items: shellItems };
|
|
@@ -406,14 +483,14 @@ function ApplyDirectiveButton({
|
|
|
406
483
|
const appliedClean = view.status === "applied" && view.failed === 0 ? view : null;
|
|
407
484
|
const appliedBefore = appliedClean ? appliedClean.before : null;
|
|
408
485
|
const phaseKey = view.status === "checking" || view.status === "finishing" ? "pending" : appliedClean ? `applied:${JSON.stringify(appliedBefore)}` : "open";
|
|
409
|
-
(0,
|
|
486
|
+
(0, import_react3.useEffect)(() => {
|
|
410
487
|
if (!onPhase) return;
|
|
411
488
|
if (phaseKey === "pending") onPhase({ phase: "pending" });
|
|
412
489
|
else if (phaseKey === "open") onPhase({ phase: "open" });
|
|
413
490
|
else onPhase({ phase: "applied", before: appliedBefore });
|
|
414
491
|
}, [phaseKey, onPhase, appliedBefore]);
|
|
415
492
|
const applyingSince = view.status === "applying" ? view.startedAt : null;
|
|
416
|
-
(0,
|
|
493
|
+
(0, import_react3.useEffect)(() => {
|
|
417
494
|
if (applyingSince === null) return;
|
|
418
495
|
startedAt.current = applyingSince;
|
|
419
496
|
const id = setInterval(
|
|
@@ -465,7 +542,13 @@ function ApplyDirectiveButton({
|
|
|
465
542
|
const shell = { __kind: directive.slug, items };
|
|
466
543
|
if (conversationId) shell.conversationId = conversationId;
|
|
467
544
|
if (again) shell.force = true;
|
|
468
|
-
|
|
545
|
+
let result;
|
|
546
|
+
try {
|
|
547
|
+
result = await confirm(shell);
|
|
548
|
+
} finally {
|
|
549
|
+
announceChangedRecords(directive, items);
|
|
550
|
+
}
|
|
551
|
+
for (const record of result.records ?? []) noteRecordChanged(record.id);
|
|
469
552
|
publishOutcome(confirm, askedFor, {
|
|
470
553
|
status: "applied",
|
|
471
554
|
applied: result.applied,
|
|
@@ -626,7 +709,7 @@ function FailureNotice({
|
|
|
626
709
|
words,
|
|
627
710
|
raw
|
|
628
711
|
}) {
|
|
629
|
-
const [open, setOpen] = (0,
|
|
712
|
+
const [open, setOpen] = (0, import_react3.useState)(false);
|
|
630
713
|
const detail = raw?.trim() ?? "";
|
|
631
714
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "flex min-w-0 flex-col gap-0.5", "data-apply-failure": true, children: [
|
|
632
715
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "min-w-0", children: [
|
|
@@ -659,39 +742,6 @@ function FailureNotice({
|
|
|
659
742
|
// directives/SideEffectDirectiveCard.tsx
|
|
660
743
|
var import_react4 = require("react");
|
|
661
744
|
var import_content_ir3 = require("@ai-matrx/content-ir");
|
|
662
|
-
|
|
663
|
-
// directives/record-values.ts
|
|
664
|
-
var import_react3 = require("react");
|
|
665
|
-
function useRecordValues(read, ref) {
|
|
666
|
-
const key = ref && read ? `${ref.noun}:${ref.id}` : "";
|
|
667
|
-
const [result, setResult] = (0, import_react3.useState)({
|
|
668
|
-
key: "",
|
|
669
|
-
values: null,
|
|
670
|
-
status: "idle"
|
|
671
|
-
});
|
|
672
|
-
(0, import_react3.useEffect)(() => {
|
|
673
|
-
if (!key || !read) return;
|
|
674
|
-
const split = key.indexOf(":");
|
|
675
|
-
const target = { noun: key.slice(0, split), id: key.slice(split + 1) };
|
|
676
|
-
let live = true;
|
|
677
|
-
setResult({ key, values: null, status: "loading" });
|
|
678
|
-
read(target).then((values) => {
|
|
679
|
-
if (!live) return;
|
|
680
|
-
setResult({ key, values, status: values ? "ready" : "unavailable" });
|
|
681
|
-
}).catch(() => {
|
|
682
|
-
if (!live) return;
|
|
683
|
-
setResult({ key, values: null, status: "unavailable" });
|
|
684
|
-
});
|
|
685
|
-
return () => {
|
|
686
|
-
live = false;
|
|
687
|
-
};
|
|
688
|
-
}, [key, read]);
|
|
689
|
-
if (!key) return { values: null, status: "idle" };
|
|
690
|
-
if (result.key !== key) return { values: null, status: "loading" };
|
|
691
|
-
return { values: result.values, status: result.status };
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
// directives/SideEffectDirectiveCard.tsx
|
|
695
745
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
696
746
|
var ROWS_BEFORE_FOLD = 3;
|
|
697
747
|
function ItemRow({
|