@ai-matrx/content-ir-react 0.18.1 → 0.19.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 +18 -0
- package/dist/directives.cjs +88 -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 +86 -45
- package/dist/directives.js.map +1 -1
- package/dist/index.cjs +90 -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 +88 -47
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +85 -47
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +83 -45
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.19.0 — 2026-10-02
|
|
4
|
+
|
|
5
|
+
- **A record's old value is the value it holds NOW.** `useRecordValues` read a
|
|
6
|
+
record ONCE per `noun:id`, so after Update A applied, Update B naming the same
|
|
7
|
+
record — its row, and a host confirm whose body stayed mounted between
|
|
8
|
+
questions — still showed A's old value as "old" (G7: "G6A renamed target"
|
|
9
|
+
while the database held "G6A second rename"). Every apply now announces the
|
|
10
|
+
records it touched (`noteRecordChanged`, for update/delete targets and the
|
|
11
|
+
written records, even when the apply fails) and every `useRecordValues`
|
|
12
|
+
naming one reads it again. New exports: `noteRecordChanged`,
|
|
13
|
+
`recordRevision`, `useRecordRevision`. Guard:
|
|
14
|
+
`__tests__/directive-record-values-g7.test.tsx` (2 red on 0.18.1).
|
|
15
|
+
|
|
16
|
+
Consumer action: a host whose own writers change a record calls
|
|
17
|
+
`noteRecordChanged(id)`; a host `readRecord` that shares in-flight reads keys
|
|
18
|
+
them by `recordRevision(id)`, so a read started before a write is never handed
|
|
19
|
+
to a reader asking after it.
|
|
20
|
+
|
|
3
21
|
## 0.18.1 — 2026-10-02
|
|
4
22
|
|
|
5
23
|
- **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,64 @@ 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 revisions = /* @__PURE__ */ new Map();
|
|
179
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
180
|
+
function recordRevision(id) {
|
|
181
|
+
return revisions.get(id) ?? 0;
|
|
182
|
+
}
|
|
183
|
+
function noteRecordChanged(id) {
|
|
184
|
+
if (!id) return;
|
|
185
|
+
revisions.set(id, recordRevision(id) + 1);
|
|
186
|
+
for (const listener of listeners) listener();
|
|
187
|
+
}
|
|
188
|
+
function subscribeRevisions(listener) {
|
|
189
|
+
listeners.add(listener);
|
|
190
|
+
return () => {
|
|
191
|
+
listeners.delete(listener);
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function useRecordRevision(id) {
|
|
195
|
+
return (0, import_react2.useSyncExternalStore)(
|
|
196
|
+
subscribeRevisions,
|
|
197
|
+
() => recordRevision(id),
|
|
198
|
+
() => 0
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
function useRecordValues(read, ref) {
|
|
202
|
+
const revision = useRecordRevision(ref?.id ?? "");
|
|
203
|
+
const key = ref && read ? `${ref.noun}:${ref.id}#${revision}` : "";
|
|
204
|
+
const [result, setResult] = (0, import_react2.useState)({
|
|
205
|
+
key: "",
|
|
206
|
+
values: null,
|
|
207
|
+
status: "idle"
|
|
208
|
+
});
|
|
209
|
+
(0, import_react2.useEffect)(() => {
|
|
210
|
+
if (!key || !read) return;
|
|
211
|
+
const split = key.indexOf(":");
|
|
212
|
+
const target = {
|
|
213
|
+
noun: key.slice(0, split),
|
|
214
|
+
id: key.slice(split + 1, key.lastIndexOf("#"))
|
|
215
|
+
};
|
|
216
|
+
let live = true;
|
|
217
|
+
setResult({ key, values: null, status: "loading" });
|
|
218
|
+
read(target).then((values) => {
|
|
219
|
+
if (!live) return;
|
|
220
|
+
setResult({ key, values, status: values ? "ready" : "unavailable" });
|
|
221
|
+
}).catch(() => {
|
|
222
|
+
if (!live) return;
|
|
223
|
+
setResult({ key, values: null, status: "unavailable" });
|
|
224
|
+
});
|
|
225
|
+
return () => {
|
|
226
|
+
live = false;
|
|
227
|
+
};
|
|
228
|
+
}, [key, read]);
|
|
229
|
+
if (!key) return { values: null, status: "idle" };
|
|
230
|
+
if (result.key !== key) return { values: null, status: "loading" };
|
|
231
|
+
return { values: result.values, status: result.status };
|
|
232
|
+
}
|
|
233
|
+
|
|
173
234
|
// directives/failure-words.ts
|
|
174
235
|
var PAST = {
|
|
175
236
|
create: "created",
|
|
@@ -289,6 +350,13 @@ function classIcon(directiveClass, noun) {
|
|
|
289
350
|
|
|
290
351
|
// directives/ApplyDirectiveButton.tsx
|
|
291
352
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
353
|
+
function announceChangedRecords(directive, items) {
|
|
354
|
+
if (!TARGETING_CLASSES.has(directive.directiveClass)) return;
|
|
355
|
+
for (const item of items) {
|
|
356
|
+
const id = itemRecordId(item);
|
|
357
|
+
if (id) noteRecordChanged(id);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
292
360
|
var applyOutcomes = /* @__PURE__ */ new WeakMap();
|
|
293
361
|
var EMPTY_AUTHORITY = {};
|
|
294
362
|
var outcomeListeners = /* @__PURE__ */ new Set();
|
|
@@ -311,7 +379,7 @@ function subscribeOutcomes(listener) {
|
|
|
311
379
|
};
|
|
312
380
|
}
|
|
313
381
|
function useApplyOutcome(host, identity) {
|
|
314
|
-
return (0,
|
|
382
|
+
return (0, import_react3.useSyncExternalStore)(
|
|
315
383
|
subscribeOutcomes,
|
|
316
384
|
() => applyOutcomes.get(host)?.get(identity) ?? null,
|
|
317
385
|
() => null
|
|
@@ -336,12 +404,12 @@ function ApplyDirectiveButton({
|
|
|
336
404
|
const items = directive.items;
|
|
337
405
|
const readsLedger = readsApplyLedger(host, directive);
|
|
338
406
|
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,
|
|
407
|
+
const [state, setState] = (0, import_react3.useState)(initial);
|
|
408
|
+
const [elapsed, setElapsed] = (0, import_react3.useState)(0);
|
|
409
|
+
const [askingAgain, setAskingAgain] = (0, import_react3.useState)(false);
|
|
410
|
+
const startedAt = (0, import_react3.useRef)(null);
|
|
343
411
|
const identity = JSON.stringify([directive.slug, items, conversationId ?? null]);
|
|
344
|
-
const [stateFor, setStateFor] = (0,
|
|
412
|
+
const [stateFor, setStateFor] = (0, import_react3.useState)(identity);
|
|
345
413
|
if (stateFor !== identity) {
|
|
346
414
|
setStateFor(identity);
|
|
347
415
|
setState(initial);
|
|
@@ -350,15 +418,15 @@ function ApplyDirectiveButton({
|
|
|
350
418
|
}
|
|
351
419
|
const shared = useApplyOutcome(host.confirm ?? EMPTY_AUTHORITY, identity);
|
|
352
420
|
const view = state.status === "asking" || !shared ? state : shared;
|
|
353
|
-
const currentIdentity = (0,
|
|
354
|
-
(0,
|
|
421
|
+
const currentIdentity = (0, import_react3.useRef)(identity);
|
|
422
|
+
(0, import_react3.useEffect)(() => {
|
|
355
423
|
currentIdentity.current = identity;
|
|
356
424
|
}, [identity]);
|
|
357
425
|
const nounText = (0, import_content_ir2.nounLabel)(directive.noun, host.nouns);
|
|
358
426
|
const shellKey = readsLedger ? JSON.stringify([directive.slug, items, conversationId ?? null]) : "";
|
|
359
427
|
const read = host.applyState;
|
|
360
428
|
const report = host.reportError;
|
|
361
|
-
(0,
|
|
429
|
+
(0, import_react3.useEffect)(() => {
|
|
362
430
|
if (!shellKey || !read) return;
|
|
363
431
|
const [slug, shellItems, inConversation] = JSON.parse(shellKey);
|
|
364
432
|
const shell = { __kind: slug, items: shellItems };
|
|
@@ -406,14 +474,14 @@ function ApplyDirectiveButton({
|
|
|
406
474
|
const appliedClean = view.status === "applied" && view.failed === 0 ? view : null;
|
|
407
475
|
const appliedBefore = appliedClean ? appliedClean.before : null;
|
|
408
476
|
const phaseKey = view.status === "checking" || view.status === "finishing" ? "pending" : appliedClean ? `applied:${JSON.stringify(appliedBefore)}` : "open";
|
|
409
|
-
(0,
|
|
477
|
+
(0, import_react3.useEffect)(() => {
|
|
410
478
|
if (!onPhase) return;
|
|
411
479
|
if (phaseKey === "pending") onPhase({ phase: "pending" });
|
|
412
480
|
else if (phaseKey === "open") onPhase({ phase: "open" });
|
|
413
481
|
else onPhase({ phase: "applied", before: appliedBefore });
|
|
414
482
|
}, [phaseKey, onPhase, appliedBefore]);
|
|
415
483
|
const applyingSince = view.status === "applying" ? view.startedAt : null;
|
|
416
|
-
(0,
|
|
484
|
+
(0, import_react3.useEffect)(() => {
|
|
417
485
|
if (applyingSince === null) return;
|
|
418
486
|
startedAt.current = applyingSince;
|
|
419
487
|
const id = setInterval(
|
|
@@ -465,7 +533,13 @@ function ApplyDirectiveButton({
|
|
|
465
533
|
const shell = { __kind: directive.slug, items };
|
|
466
534
|
if (conversationId) shell.conversationId = conversationId;
|
|
467
535
|
if (again) shell.force = true;
|
|
468
|
-
|
|
536
|
+
let result;
|
|
537
|
+
try {
|
|
538
|
+
result = await confirm(shell);
|
|
539
|
+
} finally {
|
|
540
|
+
announceChangedRecords(directive, items);
|
|
541
|
+
}
|
|
542
|
+
for (const record of result.records ?? []) noteRecordChanged(record.id);
|
|
469
543
|
publishOutcome(confirm, askedFor, {
|
|
470
544
|
status: "applied",
|
|
471
545
|
applied: result.applied,
|
|
@@ -626,7 +700,7 @@ function FailureNotice({
|
|
|
626
700
|
words,
|
|
627
701
|
raw
|
|
628
702
|
}) {
|
|
629
|
-
const [open, setOpen] = (0,
|
|
703
|
+
const [open, setOpen] = (0, import_react3.useState)(false);
|
|
630
704
|
const detail = raw?.trim() ?? "";
|
|
631
705
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "flex min-w-0 flex-col gap-0.5", "data-apply-failure": true, children: [
|
|
632
706
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "min-w-0", children: [
|
|
@@ -659,39 +733,6 @@ function FailureNotice({
|
|
|
659
733
|
// directives/SideEffectDirectiveCard.tsx
|
|
660
734
|
var import_react4 = require("react");
|
|
661
735
|
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
736
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
696
737
|
var ROWS_BEFORE_FOLD = 3;
|
|
697
738
|
function ItemRow({
|