@ai-matrx/content-ir-react 0.17.1 → 0.18.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 +35 -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 +115 -46
- 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 +116 -47
- 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 +115 -46
- 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 +116 -47
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +114 -46
- 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 +115 -47
- 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,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.18.1 — 2026-10-02
|
|
4
|
+
|
|
5
|
+
- **An apply belongs to the block, not to the card that asked.** A note re-renders
|
|
6
|
+
its blocks while the confirm dialog is open; the card that asked was unmounted,
|
|
7
|
+
the apply still ran, and the fresh card — which had read the ledger before the
|
|
8
|
+
write landed — offered "Apply" for an update that had already run. From the yes
|
|
9
|
+
on, the apply's progress and outcome are held per apply authority (the host's
|
|
10
|
+
`confirm`) and block identity, and every card drawing that block shows them.
|
|
11
|
+
Guard: `__tests__/directive-card-g6a.test.tsx` ("a card re-mounted while its
|
|
12
|
+
confirm was open…", red on 0.18.0).
|
|
13
|
+
|
|
14
|
+
No consumer action.
|
|
15
|
+
|
|
16
|
+
## 0.18.0 — 2026-10-02
|
|
17
|
+
|
|
18
|
+
The G6A review of the action cards (nightly clone). Guard:
|
|
19
|
+
`__tests__/directive-card-g6a.test.tsx` (3 red on 0.17.1).
|
|
20
|
+
|
|
21
|
+
- **An applied update says what it changed, from the record of the change.** After
|
|
22
|
+
an Update applied, a reload read the record live for the old value — which by
|
|
23
|
+
then IS the new value — and the row said "Title → X (no change)". The rows now
|
|
24
|
+
compare against the live record only while the block is open; once applied they
|
|
25
|
+
draw the old value from `before` — new on `DirectiveApplyResult` and
|
|
26
|
+
`DirectiveApplyState`, each item's replaced values by item index, which aidream
|
|
27
|
+
ledgers at apply time (1122747fb5) — and without it say only "Title → new".
|
|
28
|
+
Until the ledger answers, a row compares against nothing. New exports:
|
|
29
|
+
`DirectiveApplyPhase`, `readsApplyLedger`; `ApplyDirectiveButton` takes `onPhase`.
|
|
30
|
+
- **An update that sets no field says so before the click.** "Nothing to change"
|
|
31
|
+
beside a muted Apply (`data-apply-tone="muted"`, `data-apply-nothing`). Apply
|
|
32
|
+
still works — validation offers, never blocks.
|
|
33
|
+
|
|
34
|
+
Consumer action: a host's `confirm` passes `before` from the server's per-item
|
|
35
|
+
receipts, and its `applyState` passes `before` from each item of
|
|
36
|
+
`/directives/apply_state`.
|
|
37
|
+
|
|
3
38
|
## 0.17.1
|
|
4
39
|
|
|
5
40
|
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,
|
|
@@ -288,19 +289,52 @@ function classIcon(directiveClass, noun) {
|
|
|
288
289
|
|
|
289
290
|
// directives/ApplyDirectiveButton.tsx
|
|
290
291
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
292
|
+
var applyOutcomes = /* @__PURE__ */ new WeakMap();
|
|
293
|
+
var EMPTY_AUTHORITY = {};
|
|
294
|
+
var outcomeListeners = /* @__PURE__ */ new Set();
|
|
295
|
+
function outcomesOf(host) {
|
|
296
|
+
let held = applyOutcomes.get(host);
|
|
297
|
+
if (!held) {
|
|
298
|
+
held = /* @__PURE__ */ new Map();
|
|
299
|
+
applyOutcomes.set(host, held);
|
|
300
|
+
}
|
|
301
|
+
return held;
|
|
302
|
+
}
|
|
303
|
+
function publishOutcome(host, identity, outcome) {
|
|
304
|
+
outcomesOf(host).set(identity, outcome);
|
|
305
|
+
for (const listener of outcomeListeners) listener();
|
|
306
|
+
}
|
|
307
|
+
function subscribeOutcomes(listener) {
|
|
308
|
+
outcomeListeners.add(listener);
|
|
309
|
+
return () => {
|
|
310
|
+
outcomeListeners.delete(listener);
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
function useApplyOutcome(host, identity) {
|
|
314
|
+
return (0, import_react2.useSyncExternalStore)(
|
|
315
|
+
subscribeOutcomes,
|
|
316
|
+
() => applyOutcomes.get(host)?.get(identity) ?? null,
|
|
317
|
+
() => null
|
|
318
|
+
);
|
|
319
|
+
}
|
|
291
320
|
var FINISH_POLL_MS = 2e3;
|
|
292
321
|
var FINISH_POLLS_MAX = 30;
|
|
322
|
+
function readsApplyLedger(host, directive) {
|
|
323
|
+
return !!host.applyState && !!host.confirm && directive.parsed.executes && directive.items.length > 0;
|
|
324
|
+
}
|
|
293
325
|
function ApplyDirectiveButton({
|
|
294
326
|
directive,
|
|
295
327
|
label = "Apply",
|
|
296
328
|
itemCount,
|
|
297
|
-
onApplied
|
|
329
|
+
onApplied,
|
|
330
|
+
onPhase,
|
|
331
|
+
nothingToChange = false
|
|
298
332
|
}) {
|
|
299
333
|
const host = useDirectiveHost();
|
|
300
334
|
const scope = useDirectiveScope();
|
|
301
335
|
const conversationId = scope.conversationId || void 0;
|
|
302
336
|
const items = directive.items;
|
|
303
|
-
const readsLedger =
|
|
337
|
+
const readsLedger = readsApplyLedger(host, directive);
|
|
304
338
|
const initial = readsLedger ? { status: "checking" } : { status: "idle" };
|
|
305
339
|
const [state, setState] = (0, import_react2.useState)(initial);
|
|
306
340
|
const [elapsed, setElapsed] = (0, import_react2.useState)(0);
|
|
@@ -314,6 +348,8 @@ function ApplyDirectiveButton({
|
|
|
314
348
|
setAskingAgain(false);
|
|
315
349
|
setElapsed(0);
|
|
316
350
|
}
|
|
351
|
+
const shared = useApplyOutcome(host.confirm ?? EMPTY_AUTHORITY, identity);
|
|
352
|
+
const view = state.status === "asking" || !shared ? state : shared;
|
|
317
353
|
const currentIdentity = (0, import_react2.useRef)(identity);
|
|
318
354
|
(0, import_react2.useEffect)(() => {
|
|
319
355
|
currentIdentity.current = identity;
|
|
@@ -341,7 +377,8 @@ function ApplyDirectiveButton({
|
|
|
341
377
|
failed: 0,
|
|
342
378
|
message: answer.message?.trim() ? answer.message.trim() : null,
|
|
343
379
|
records: answer.records ?? [],
|
|
344
|
-
copies: Math.max(1, answer.copies ?? 1)
|
|
380
|
+
copies: Math.max(1, answer.copies ?? 1),
|
|
381
|
+
before: answer.before ?? null
|
|
345
382
|
} : prev
|
|
346
383
|
);
|
|
347
384
|
} else if (answer.state === "in_flight" && polls < FINISH_POLLS_MAX) {
|
|
@@ -366,24 +403,34 @@ function ApplyDirectiveButton({
|
|
|
366
403
|
if (timer) clearTimeout(timer);
|
|
367
404
|
};
|
|
368
405
|
}, [shellKey, read, report]);
|
|
406
|
+
const appliedClean = view.status === "applied" && view.failed === 0 ? view : null;
|
|
407
|
+
const appliedBefore = appliedClean ? appliedClean.before : null;
|
|
408
|
+
const phaseKey = view.status === "checking" || view.status === "finishing" ? "pending" : appliedClean ? `applied:${JSON.stringify(appliedBefore)}` : "open";
|
|
409
|
+
(0, import_react2.useEffect)(() => {
|
|
410
|
+
if (!onPhase) return;
|
|
411
|
+
if (phaseKey === "pending") onPhase({ phase: "pending" });
|
|
412
|
+
else if (phaseKey === "open") onPhase({ phase: "open" });
|
|
413
|
+
else onPhase({ phase: "applied", before: appliedBefore });
|
|
414
|
+
}, [phaseKey, onPhase, appliedBefore]);
|
|
415
|
+
const applyingSince = view.status === "applying" ? view.startedAt : null;
|
|
369
416
|
(0, import_react2.useEffect)(() => {
|
|
370
|
-
if (
|
|
371
|
-
startedAt.current =
|
|
417
|
+
if (applyingSince === null) return;
|
|
418
|
+
startedAt.current = applyingSince;
|
|
372
419
|
const id = setInterval(
|
|
373
420
|
() => setElapsed(Math.round((Date.now() - (startedAt.current ?? 0)) / 1e3)),
|
|
374
421
|
1e3
|
|
375
422
|
);
|
|
376
423
|
return () => clearInterval(id);
|
|
377
|
-
}, [
|
|
424
|
+
}, [applyingSince]);
|
|
378
425
|
if (!directive.parsed.executes) return null;
|
|
379
426
|
const confirm = host.confirm;
|
|
380
427
|
if (!confirm) return null;
|
|
381
428
|
async function handleApply(again = false) {
|
|
382
429
|
if (!confirm) return;
|
|
383
|
-
const
|
|
430
|
+
const prior = view;
|
|
384
431
|
const askedFor = identity;
|
|
385
432
|
const stillHere = () => currentIdentity.current === askedFor;
|
|
386
|
-
const priorCopies =
|
|
433
|
+
const priorCopies = view.status === "applied" ? view.copies : 0;
|
|
387
434
|
if (host.ask) {
|
|
388
435
|
if (again) setAskingAgain(true);
|
|
389
436
|
else setState({ status: "asking" });
|
|
@@ -407,41 +454,43 @@ function ApplyDirectiveButton({
|
|
|
407
454
|
if (!stillHere()) return;
|
|
408
455
|
setAskingAgain(false);
|
|
409
456
|
if (!ok) {
|
|
410
|
-
setState(again ?
|
|
457
|
+
setState(again ? prior : { status: "idle" });
|
|
411
458
|
return;
|
|
412
459
|
}
|
|
413
460
|
}
|
|
414
461
|
setElapsed(0);
|
|
415
|
-
setState({ status: "
|
|
462
|
+
setState((prev) => prev.status === "asking" ? { status: "idle" } : prev);
|
|
463
|
+
publishOutcome(confirm, askedFor, { status: "applying", startedAt: Date.now() });
|
|
416
464
|
try {
|
|
417
465
|
const shell = { __kind: directive.slug, items };
|
|
418
466
|
if (conversationId) shell.conversationId = conversationId;
|
|
419
467
|
if (again) shell.force = true;
|
|
420
468
|
const result = await confirm(shell);
|
|
421
|
-
|
|
422
|
-
setState({
|
|
469
|
+
publishOutcome(confirm, askedFor, {
|
|
423
470
|
status: "applied",
|
|
424
471
|
applied: result.applied,
|
|
425
472
|
failed: result.failed,
|
|
426
473
|
message: result.message?.trim() ? result.message.trim() : null,
|
|
427
474
|
records: result.records ?? [],
|
|
428
475
|
// "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)
|
|
476
|
+
copies: again ? Math.max(1, priorCopies) + 1 : Math.max(1, priorCopies),
|
|
477
|
+
// "Run again" wrote the same values over themselves; what the FIRST
|
|
478
|
+
// apply replaced is still the change this block made.
|
|
479
|
+
before: again && prior.status === "applied" && prior.before ? prior.before : result.before ?? null
|
|
430
480
|
});
|
|
431
|
-
onApplied?.();
|
|
481
|
+
if (stillHere()) onApplied?.();
|
|
432
482
|
} catch (error) {
|
|
433
|
-
|
|
434
|
-
setState({
|
|
483
|
+
publishOutcome(confirm, askedFor, {
|
|
435
484
|
status: "error",
|
|
436
485
|
message: error instanceof Error && error.message ? error.message : "That couldn't be applied just now. Please try again."
|
|
437
486
|
});
|
|
438
487
|
}
|
|
439
488
|
}
|
|
440
|
-
if (
|
|
441
|
-
const bad =
|
|
489
|
+
if (view.status === "applied") {
|
|
490
|
+
const bad = view.failed > 0;
|
|
442
491
|
const trashed = directive.directiveClass === "delete";
|
|
443
492
|
const label2 = nounText;
|
|
444
|
-
const records = host.renderRecord ?
|
|
493
|
+
const records = host.renderRecord ? view.records : [];
|
|
445
494
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
446
495
|
"span",
|
|
447
496
|
{
|
|
@@ -455,26 +504,26 @@ function ApplyDirectiveButton({
|
|
|
455
504
|
{
|
|
456
505
|
words: failureWords(
|
|
457
506
|
{
|
|
458
|
-
raw:
|
|
507
|
+
raw: view.message ?? `${view.failed} failed`,
|
|
459
508
|
directiveClass: directive.directiveClass,
|
|
460
509
|
noun: directive.noun,
|
|
461
510
|
nounLabel: nounText,
|
|
462
|
-
failed:
|
|
463
|
-
total:
|
|
511
|
+
failed: view.failed,
|
|
512
|
+
total: view.applied + view.failed
|
|
464
513
|
},
|
|
465
514
|
host.explainFailure
|
|
466
515
|
),
|
|
467
|
-
raw:
|
|
516
|
+
raw: view.message
|
|
468
517
|
}
|
|
469
|
-
) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "min-w-0", children:
|
|
518
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "min-w-0", children: view.message ?? `Applied ${view.applied}` })
|
|
470
519
|
] }),
|
|
471
|
-
|
|
520
|
+
view.copies > 1 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
472
521
|
"span",
|
|
473
522
|
{
|
|
474
523
|
className: "shrink-0 text-muted-foreground",
|
|
475
|
-
"data-apply-copies":
|
|
524
|
+
"data-apply-copies": view.copies,
|
|
476
525
|
title: records.length > 0 ? directive.directiveClass === "create" ? "Links to the latest copy." : "Links to the latest run." : void 0,
|
|
477
|
-
children: directive.directiveClass === "create" ? `${
|
|
526
|
+
children: directive.directiveClass === "create" ? `${view.copies} copies` : `Ran ${view.copies} times`
|
|
478
527
|
}
|
|
479
528
|
) : null,
|
|
480
529
|
records.map((record) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "inline-flex min-w-0 items-center", children: host.renderRecord?.({
|
|
@@ -501,7 +550,7 @@ function ApplyDirectiveButton({
|
|
|
501
550
|
}
|
|
502
551
|
);
|
|
503
552
|
}
|
|
504
|
-
if (
|
|
553
|
+
if (view.status === "checking") {
|
|
505
554
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
506
555
|
"span",
|
|
507
556
|
{
|
|
@@ -513,7 +562,7 @@ function ApplyDirectiveButton({
|
|
|
513
562
|
}
|
|
514
563
|
);
|
|
515
564
|
}
|
|
516
|
-
if (
|
|
565
|
+
if (view.status === "finishing") {
|
|
517
566
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
518
567
|
"span",
|
|
519
568
|
{
|
|
@@ -528,22 +577,25 @@ function ApplyDirectiveButton({
|
|
|
528
577
|
);
|
|
529
578
|
}
|
|
530
579
|
const destructive = directive.directiveClass === "delete";
|
|
580
|
+
const muted = nothingToChange && view.status === "idle";
|
|
531
581
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "inline-flex items-center gap-2", children: [
|
|
582
|
+
muted ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-xs text-muted-foreground", "data-apply-nothing": "", children: "Nothing to change" }) : null,
|
|
532
583
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
533
584
|
"button",
|
|
534
585
|
{
|
|
535
586
|
type: "button",
|
|
536
587
|
onClick: () => void handleApply(),
|
|
537
|
-
disabled:
|
|
538
|
-
"data-apply-tone": destructive ? "destructive" : "default",
|
|
539
|
-
|
|
588
|
+
disabled: view.status === "applying" || view.status === "asking" || items.length === 0,
|
|
589
|
+
"data-apply-tone": muted ? "muted" : destructive ? "destructive" : "default",
|
|
590
|
+
title: muted ? "It sets no fields, so applying it changes nothing." : void 0,
|
|
591
|
+
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
592
|
children: [
|
|
541
|
-
|
|
542
|
-
|
|
593
|
+
view.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" }),
|
|
594
|
+
view.status === "applying" ? `Applying${itemCount ? ` ${itemCount} ${itemCount === 1 ? "item" : "items"}` : ""}\u2026${elapsed ? ` ${elapsed}s` : ""}` : label
|
|
543
595
|
]
|
|
544
596
|
}
|
|
545
597
|
),
|
|
546
|
-
|
|
598
|
+
view.status === "error" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
547
599
|
"span",
|
|
548
600
|
{
|
|
549
601
|
className: "inline-flex max-w-sm items-start gap-1 text-xs text-destructive",
|
|
@@ -555,14 +607,14 @@ function ApplyDirectiveButton({
|
|
|
555
607
|
{
|
|
556
608
|
words: failureWords(
|
|
557
609
|
{
|
|
558
|
-
raw:
|
|
610
|
+
raw: view.message,
|
|
559
611
|
directiveClass: directive.directiveClass,
|
|
560
612
|
noun: directive.noun,
|
|
561
613
|
nounLabel: nounText
|
|
562
614
|
},
|
|
563
615
|
host.explainFailure
|
|
564
616
|
),
|
|
565
|
-
raw:
|
|
617
|
+
raw: view.message
|
|
566
618
|
}
|
|
567
619
|
)
|
|
568
620
|
]
|
|
@@ -646,12 +698,13 @@ function ItemRow({
|
|
|
646
698
|
directive,
|
|
647
699
|
item,
|
|
648
700
|
index,
|
|
649
|
-
total
|
|
701
|
+
total,
|
|
702
|
+
phase
|
|
650
703
|
}) {
|
|
651
704
|
const host = useDirectiveHost();
|
|
652
705
|
const recordId = TARGETING_CLASSES.has(directive.directiveClass) ? itemRecordId(item) : null;
|
|
653
706
|
if (recordId) {
|
|
654
|
-
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TargetRow, { directive, item, recordId });
|
|
707
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TargetRow, { directive, item, recordId, index, phase });
|
|
655
708
|
}
|
|
656
709
|
const titleColumn = (0, import_content_ir3.nounTitleColumn)(directive.noun, host.nouns);
|
|
657
710
|
const title = (0, import_content_ir3.itemTitle)(item, titleColumn, index, total);
|
|
@@ -738,16 +791,20 @@ function DirectiveChangeList({
|
|
|
738
791
|
function TargetRow({
|
|
739
792
|
directive,
|
|
740
793
|
item,
|
|
741
|
-
recordId
|
|
794
|
+
recordId,
|
|
795
|
+
index,
|
|
796
|
+
phase
|
|
742
797
|
}) {
|
|
743
798
|
const host = useDirectiveHost();
|
|
744
799
|
const fallback = recordFallbackName((0, import_content_ir3.nounLabel)(directive.noun, host.nouns), recordId);
|
|
745
800
|
const isUpdate = directive.directiveClass === "update";
|
|
746
|
-
const
|
|
747
|
-
|
|
748
|
-
|
|
801
|
+
const readsLive = isUpdate && phase.phase === "open";
|
|
802
|
+
const live = useRecordValues(
|
|
803
|
+
readsLive ? host.readRecord : void 0,
|
|
804
|
+
readsLive ? { noun: directive.noun, id: recordId } : null
|
|
749
805
|
);
|
|
750
|
-
const
|
|
806
|
+
const compareTo = phase.phase === "applied" ? phase.before?.[index] ?? null : readsLive ? live.values : null;
|
|
807
|
+
const changes = isUpdate ? itemChanges(item, compareTo, { titleColumn: (0, import_content_ir3.nounTitleColumn)(directive.noun, host.nouns) }) : [];
|
|
751
808
|
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
809
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "py-1 text-xs", "data-directive-target": recordId, children: [
|
|
753
810
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
@@ -764,6 +821,10 @@ function SideEffectDirectiveCard({ directive }) {
|
|
|
764
821
|
const total = items.length;
|
|
765
822
|
const display = (0, import_content_ir3.directiveDisplay)(directive.directiveClass, directive.noun, host.nouns);
|
|
766
823
|
const Icon = classIcon(directive.directiveClass, directive.noun);
|
|
824
|
+
const [phase, setPhase] = (0, import_react4.useState)(
|
|
825
|
+
() => readsApplyLedger(host, directive) ? { phase: "pending" } : { phase: "open" }
|
|
826
|
+
);
|
|
827
|
+
const nothingToChange = directive.directiveClass === "update" && total > 0 && items.every((item) => itemChanges(item).length === 0);
|
|
767
828
|
const shown = expanded ? items : items.slice(0, ROWS_BEFORE_FOLD);
|
|
768
829
|
const hidden = total - shown.length;
|
|
769
830
|
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 +839,21 @@ function SideEffectDirectiveCard({ directive }) {
|
|
|
778
839
|
] }),
|
|
779
840
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "ml-auto flex items-center gap-1", children: [
|
|
780
841
|
host.renderCopy?.({ label: display.title, value: directive.shell, size: "icon" }),
|
|
781
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
842
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
843
|
+
ApplyDirectiveButton,
|
|
844
|
+
{
|
|
845
|
+
directive,
|
|
846
|
+
itemCount: total,
|
|
847
|
+
onPhase: setPhase,
|
|
848
|
+
nothingToChange
|
|
849
|
+
}
|
|
850
|
+
)
|
|
782
851
|
] })
|
|
783
852
|
] }),
|
|
784
853
|
total === 0 ? (
|
|
785
854
|
// Stated, never silent: an empty batch is a real (and suspicious) state.
|
|
786
855
|
/* @__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)) }),
|
|
856
|
+
) : /* @__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
857
|
hidden > 0 || expanded ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
789
858
|
"button",
|
|
790
859
|
{
|