@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/dist/directives.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as DirectiveFailure, a as DirectiveFailureWords, b as DirectiveRecordRef } from './directive-host-
|
|
2
|
-
export { c as DirectiveApplyResult, d as DirectiveApplyState, e as DirectiveAskRequest, f as DirectiveCopyProps, g as DirectiveHost, h as DirectiveHostProvider, i as DirectiveHostProviderProps, j as DirectiveOpenItemOptions, k as DirectiveRecordProps, l as DirectiveScope, m as DirectiveScopeProvider, n as DirectiveShell, u as useDirectiveHost, o as useDirectiveScope } from './directive-host-
|
|
1
|
+
import { D as DirectiveFailure, a as DirectiveFailureWords, b as DirectiveRecordRef } from './directive-host-Bw2_PrgL.js';
|
|
2
|
+
export { c as DirectiveApplyResult, d as DirectiveApplyState, e as DirectiveAskRequest, f as DirectiveCopyProps, g as DirectiveHost, h as DirectiveHostProvider, i as DirectiveHostProviderProps, j as DirectiveOpenItemOptions, k as DirectiveRecordProps, l as DirectiveScope, m as DirectiveScopeProvider, n as DirectiveShell, u as useDirectiveHost, o as useDirectiveScope } from './directive-host-Bw2_PrgL.js';
|
|
3
3
|
import * as react from 'react';
|
|
4
4
|
import { ComponentType } from 'react';
|
|
5
5
|
import { DecodedDirective, DirectiveClass } from '@ai-matrx/content-ir';
|
|
@@ -40,14 +40,42 @@ declare function getDirectiveRenderer(directive: Pick<DecodedDirective, "slug" |
|
|
|
40
40
|
/** Tests only. */
|
|
41
41
|
declare function resetDirectiveRenderers(): void;
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* What the Apply control knows about its block, for the rest of the card:
|
|
45
|
+
* - `pending` — the ledger has not answered yet;
|
|
46
|
+
* - `open` — nothing applied: rows compare against the record as it is now;
|
|
47
|
+
* - `applied` — the change happened: rows draw it from `before` (the server's
|
|
48
|
+
* record of what it replaced), never from a live re-read, which
|
|
49
|
+
* now holds the NEW value.
|
|
50
|
+
*/
|
|
51
|
+
type DirectiveApplyPhase = {
|
|
52
|
+
phase: "pending";
|
|
53
|
+
} | {
|
|
54
|
+
phase: "open";
|
|
55
|
+
} | {
|
|
56
|
+
phase: "applied";
|
|
57
|
+
before: ReadonlyArray<Record<string, unknown> | null> | null;
|
|
58
|
+
};
|
|
59
|
+
/** Does this card read the ledger before offering Apply? (One rule, card and control.) */
|
|
60
|
+
declare function readsApplyLedger(host: {
|
|
61
|
+
applyState?: unknown;
|
|
62
|
+
confirm?: unknown;
|
|
63
|
+
}, directive: DecodedDirective): boolean;
|
|
43
64
|
interface ApplyDirectiveButtonProps {
|
|
44
65
|
directive: DecodedDirective;
|
|
45
66
|
label?: string;
|
|
46
67
|
/** How many things this will write — shown during the wait so a long apply reads as work, not a frozen spinner. */
|
|
47
68
|
itemCount?: number;
|
|
48
69
|
onApplied?: () => void;
|
|
70
|
+
/** Told whenever what the control knows about its block changes. */
|
|
71
|
+
onPhase?: (phase: DirectiveApplyPhase) => void;
|
|
72
|
+
/**
|
|
73
|
+
* The block would change nothing (an update that sets no field). Apply stays
|
|
74
|
+
* — validation offers, never blocks — but muted, with the reason beside it.
|
|
75
|
+
*/
|
|
76
|
+
nothingToChange?: boolean;
|
|
49
77
|
}
|
|
50
|
-
declare function ApplyDirectiveButton({ directive, label, itemCount, onApplied, }: ApplyDirectiveButtonProps): react.JSX.Element | null;
|
|
78
|
+
declare function ApplyDirectiveButton({ directive, label, itemCount, onApplied, onPhase, nothingToChange, }: ApplyDirectiveButtonProps): react.JSX.Element | null;
|
|
51
79
|
|
|
52
80
|
/**
|
|
53
81
|
* WHAT A DIRECTIVE ITEM WILL DO TO AN EXISTING RECORD — for the card, the
|
|
@@ -61,7 +89,7 @@ declare function ApplyDirectiveButton({ directive, label, itemCount, onApplied,
|
|
|
61
89
|
* not the name of the record being changed. Naming the target is the host's
|
|
62
90
|
* live lookup (`DirectiveHost.renderRecord`); what changes is right here.
|
|
63
91
|
*
|
|
64
|
-
* Law: common-docs/policies/
|
|
92
|
+
* Law: common-docs/policies/no-dead-ends.md — the sentence
|
|
65
93
|
* names what changes.
|
|
66
94
|
*/
|
|
67
95
|
/** Classes whose items point at an EXISTING record by `id`. */
|
|
@@ -195,4 +223,4 @@ interface RecordValues {
|
|
|
195
223
|
type RecordReader = (ref: DirectiveRecordRef) => Promise<Record<string, unknown> | null>;
|
|
196
224
|
declare function useRecordValues(read: RecordReader | undefined, ref: DirectiveRecordRef | null): RecordValues;
|
|
197
225
|
|
|
198
|
-
export { ApplyDirectiveButton, type ApplyDirectiveButtonProps, CHANGE_VALUE_MAX, type ChangeValue, DirectiveChangeList, DirectiveFailure, DirectiveFailureWords, DirectiveFallbackCard, type DirectiveFallbackCardProps, DirectiveProvisionalCard, type DirectiveProvisionalCardProps, DirectiveRecordRef, DirectiveRender, type DirectiveRenderProps, type DirectiveRenderer, type DirectiveRendererProps, type ItemChange, type ItemChangesOptions, type RecordReader, type RecordValues, type RecordValuesStatus, SideEffectDirectiveCard, type SideEffectDirectiveCardProps, TARGETING_CLASSES, defaultFailureWords, describeChangeValue, failureWords, fieldLabel, getDirectiveRenderer, itemChanges, itemRecordId, provisionalDirectiveSlug, recordFallbackName, registerDirectiveRenderer, resetDirectiveRenderers, useRecordValues };
|
|
226
|
+
export { ApplyDirectiveButton, type ApplyDirectiveButtonProps, CHANGE_VALUE_MAX, type ChangeValue, type DirectiveApplyPhase, DirectiveChangeList, DirectiveFailure, DirectiveFailureWords, DirectiveFallbackCard, type DirectiveFallbackCardProps, DirectiveProvisionalCard, type DirectiveProvisionalCardProps, DirectiveRecordRef, DirectiveRender, type DirectiveRenderProps, type DirectiveRenderer, type DirectiveRendererProps, type ItemChange, type ItemChangesOptions, type RecordReader, type RecordValues, type RecordValuesStatus, SideEffectDirectiveCard, type SideEffectDirectiveCardProps, TARGETING_CLASSES, defaultFailureWords, describeChangeValue, failureWords, fieldLabel, getDirectiveRenderer, itemChanges, itemRecordId, provisionalDirectiveSlug, readsApplyLedger, recordFallbackName, registerDirectiveRenderer, resetDirectiveRenderers, useRecordValues };
|
package/dist/directives.js
CHANGED
|
@@ -44,7 +44,7 @@ function resetDirectiveRenderers() {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
// directives/ApplyDirectiveButton.tsx
|
|
47
|
-
import { useEffect, useRef, useState } from "react";
|
|
47
|
+
import { useEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
48
48
|
import { nounLabel } from "@ai-matrx/content-ir";
|
|
49
49
|
|
|
50
50
|
// directives/item-changes.ts
|
|
@@ -242,19 +242,52 @@ function classIcon(directiveClass, noun) {
|
|
|
242
242
|
|
|
243
243
|
// directives/ApplyDirectiveButton.tsx
|
|
244
244
|
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
245
|
+
var applyOutcomes = /* @__PURE__ */ new WeakMap();
|
|
246
|
+
var EMPTY_AUTHORITY = {};
|
|
247
|
+
var outcomeListeners = /* @__PURE__ */ new Set();
|
|
248
|
+
function outcomesOf(host) {
|
|
249
|
+
let held = applyOutcomes.get(host);
|
|
250
|
+
if (!held) {
|
|
251
|
+
held = /* @__PURE__ */ new Map();
|
|
252
|
+
applyOutcomes.set(host, held);
|
|
253
|
+
}
|
|
254
|
+
return held;
|
|
255
|
+
}
|
|
256
|
+
function publishOutcome(host, identity, outcome) {
|
|
257
|
+
outcomesOf(host).set(identity, outcome);
|
|
258
|
+
for (const listener of outcomeListeners) listener();
|
|
259
|
+
}
|
|
260
|
+
function subscribeOutcomes(listener) {
|
|
261
|
+
outcomeListeners.add(listener);
|
|
262
|
+
return () => {
|
|
263
|
+
outcomeListeners.delete(listener);
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function useApplyOutcome(host, identity) {
|
|
267
|
+
return useSyncExternalStore(
|
|
268
|
+
subscribeOutcomes,
|
|
269
|
+
() => applyOutcomes.get(host)?.get(identity) ?? null,
|
|
270
|
+
() => null
|
|
271
|
+
);
|
|
272
|
+
}
|
|
245
273
|
var FINISH_POLL_MS = 2e3;
|
|
246
274
|
var FINISH_POLLS_MAX = 30;
|
|
275
|
+
function readsApplyLedger(host, directive) {
|
|
276
|
+
return !!host.applyState && !!host.confirm && directive.parsed.executes && directive.items.length > 0;
|
|
277
|
+
}
|
|
247
278
|
function ApplyDirectiveButton({
|
|
248
279
|
directive,
|
|
249
280
|
label = "Apply",
|
|
250
281
|
itemCount,
|
|
251
|
-
onApplied
|
|
282
|
+
onApplied,
|
|
283
|
+
onPhase,
|
|
284
|
+
nothingToChange = false
|
|
252
285
|
}) {
|
|
253
286
|
const host = useDirectiveHost();
|
|
254
287
|
const scope = useDirectiveScope();
|
|
255
288
|
const conversationId = scope.conversationId || void 0;
|
|
256
289
|
const items = directive.items;
|
|
257
|
-
const readsLedger =
|
|
290
|
+
const readsLedger = readsApplyLedger(host, directive);
|
|
258
291
|
const initial = readsLedger ? { status: "checking" } : { status: "idle" };
|
|
259
292
|
const [state, setState] = useState(initial);
|
|
260
293
|
const [elapsed, setElapsed] = useState(0);
|
|
@@ -268,6 +301,8 @@ function ApplyDirectiveButton({
|
|
|
268
301
|
setAskingAgain(false);
|
|
269
302
|
setElapsed(0);
|
|
270
303
|
}
|
|
304
|
+
const shared = useApplyOutcome(host.confirm ?? EMPTY_AUTHORITY, identity);
|
|
305
|
+
const view = state.status === "asking" || !shared ? state : shared;
|
|
271
306
|
const currentIdentity = useRef(identity);
|
|
272
307
|
useEffect(() => {
|
|
273
308
|
currentIdentity.current = identity;
|
|
@@ -295,7 +330,8 @@ function ApplyDirectiveButton({
|
|
|
295
330
|
failed: 0,
|
|
296
331
|
message: answer.message?.trim() ? answer.message.trim() : null,
|
|
297
332
|
records: answer.records ?? [],
|
|
298
|
-
copies: Math.max(1, answer.copies ?? 1)
|
|
333
|
+
copies: Math.max(1, answer.copies ?? 1),
|
|
334
|
+
before: answer.before ?? null
|
|
299
335
|
} : prev
|
|
300
336
|
);
|
|
301
337
|
} else if (answer.state === "in_flight" && polls < FINISH_POLLS_MAX) {
|
|
@@ -320,24 +356,34 @@ function ApplyDirectiveButton({
|
|
|
320
356
|
if (timer) clearTimeout(timer);
|
|
321
357
|
};
|
|
322
358
|
}, [shellKey, read, report]);
|
|
359
|
+
const appliedClean = view.status === "applied" && view.failed === 0 ? view : null;
|
|
360
|
+
const appliedBefore = appliedClean ? appliedClean.before : null;
|
|
361
|
+
const phaseKey = view.status === "checking" || view.status === "finishing" ? "pending" : appliedClean ? `applied:${JSON.stringify(appliedBefore)}` : "open";
|
|
362
|
+
useEffect(() => {
|
|
363
|
+
if (!onPhase) return;
|
|
364
|
+
if (phaseKey === "pending") onPhase({ phase: "pending" });
|
|
365
|
+
else if (phaseKey === "open") onPhase({ phase: "open" });
|
|
366
|
+
else onPhase({ phase: "applied", before: appliedBefore });
|
|
367
|
+
}, [phaseKey, onPhase, appliedBefore]);
|
|
368
|
+
const applyingSince = view.status === "applying" ? view.startedAt : null;
|
|
323
369
|
useEffect(() => {
|
|
324
|
-
if (
|
|
325
|
-
startedAt.current =
|
|
370
|
+
if (applyingSince === null) return;
|
|
371
|
+
startedAt.current = applyingSince;
|
|
326
372
|
const id = setInterval(
|
|
327
373
|
() => setElapsed(Math.round((Date.now() - (startedAt.current ?? 0)) / 1e3)),
|
|
328
374
|
1e3
|
|
329
375
|
);
|
|
330
376
|
return () => clearInterval(id);
|
|
331
|
-
}, [
|
|
377
|
+
}, [applyingSince]);
|
|
332
378
|
if (!directive.parsed.executes) return null;
|
|
333
379
|
const confirm = host.confirm;
|
|
334
380
|
if (!confirm) return null;
|
|
335
381
|
async function handleApply(again = false) {
|
|
336
382
|
if (!confirm) return;
|
|
337
|
-
const
|
|
383
|
+
const prior = view;
|
|
338
384
|
const askedFor = identity;
|
|
339
385
|
const stillHere = () => currentIdentity.current === askedFor;
|
|
340
|
-
const priorCopies =
|
|
386
|
+
const priorCopies = view.status === "applied" ? view.copies : 0;
|
|
341
387
|
if (host.ask) {
|
|
342
388
|
if (again) setAskingAgain(true);
|
|
343
389
|
else setState({ status: "asking" });
|
|
@@ -361,41 +407,43 @@ function ApplyDirectiveButton({
|
|
|
361
407
|
if (!stillHere()) return;
|
|
362
408
|
setAskingAgain(false);
|
|
363
409
|
if (!ok) {
|
|
364
|
-
setState(again ?
|
|
410
|
+
setState(again ? prior : { status: "idle" });
|
|
365
411
|
return;
|
|
366
412
|
}
|
|
367
413
|
}
|
|
368
414
|
setElapsed(0);
|
|
369
|
-
setState({ status: "
|
|
415
|
+
setState((prev) => prev.status === "asking" ? { status: "idle" } : prev);
|
|
416
|
+
publishOutcome(confirm, askedFor, { status: "applying", startedAt: Date.now() });
|
|
370
417
|
try {
|
|
371
418
|
const shell = { __kind: directive.slug, items };
|
|
372
419
|
if (conversationId) shell.conversationId = conversationId;
|
|
373
420
|
if (again) shell.force = true;
|
|
374
421
|
const result = await confirm(shell);
|
|
375
|
-
|
|
376
|
-
setState({
|
|
422
|
+
publishOutcome(confirm, askedFor, {
|
|
377
423
|
status: "applied",
|
|
378
424
|
applied: result.applied,
|
|
379
425
|
failed: result.failed,
|
|
380
426
|
message: result.message?.trim() ? result.message.trim() : null,
|
|
381
427
|
records: result.records ?? [],
|
|
382
428
|
// "Run again" made one more; a first apply (or a replay of one) is one.
|
|
383
|
-
copies: again ? Math.max(1, priorCopies) + 1 : Math.max(1, priorCopies)
|
|
429
|
+
copies: again ? Math.max(1, priorCopies) + 1 : Math.max(1, priorCopies),
|
|
430
|
+
// "Run again" wrote the same values over themselves; what the FIRST
|
|
431
|
+
// apply replaced is still the change this block made.
|
|
432
|
+
before: again && prior.status === "applied" && prior.before ? prior.before : result.before ?? null
|
|
384
433
|
});
|
|
385
|
-
onApplied?.();
|
|
434
|
+
if (stillHere()) onApplied?.();
|
|
386
435
|
} catch (error) {
|
|
387
|
-
|
|
388
|
-
setState({
|
|
436
|
+
publishOutcome(confirm, askedFor, {
|
|
389
437
|
status: "error",
|
|
390
438
|
message: error instanceof Error && error.message ? error.message : "That couldn't be applied just now. Please try again."
|
|
391
439
|
});
|
|
392
440
|
}
|
|
393
441
|
}
|
|
394
|
-
if (
|
|
395
|
-
const bad =
|
|
442
|
+
if (view.status === "applied") {
|
|
443
|
+
const bad = view.failed > 0;
|
|
396
444
|
const trashed = directive.directiveClass === "delete";
|
|
397
445
|
const label2 = nounText;
|
|
398
|
-
const records = host.renderRecord ?
|
|
446
|
+
const records = host.renderRecord ? view.records : [];
|
|
399
447
|
return /* @__PURE__ */ jsxs2(
|
|
400
448
|
"span",
|
|
401
449
|
{
|
|
@@ -409,26 +457,26 @@ function ApplyDirectiveButton({
|
|
|
409
457
|
{
|
|
410
458
|
words: failureWords(
|
|
411
459
|
{
|
|
412
|
-
raw:
|
|
460
|
+
raw: view.message ?? `${view.failed} failed`,
|
|
413
461
|
directiveClass: directive.directiveClass,
|
|
414
462
|
noun: directive.noun,
|
|
415
463
|
nounLabel: nounText,
|
|
416
|
-
failed:
|
|
417
|
-
total:
|
|
464
|
+
failed: view.failed,
|
|
465
|
+
total: view.applied + view.failed
|
|
418
466
|
},
|
|
419
467
|
host.explainFailure
|
|
420
468
|
),
|
|
421
|
-
raw:
|
|
469
|
+
raw: view.message
|
|
422
470
|
}
|
|
423
|
-
) : /* @__PURE__ */ jsx3("span", { className: "min-w-0", children:
|
|
471
|
+
) : /* @__PURE__ */ jsx3("span", { className: "min-w-0", children: view.message ?? `Applied ${view.applied}` })
|
|
424
472
|
] }),
|
|
425
|
-
|
|
473
|
+
view.copies > 1 ? /* @__PURE__ */ jsx3(
|
|
426
474
|
"span",
|
|
427
475
|
{
|
|
428
476
|
className: "shrink-0 text-muted-foreground",
|
|
429
|
-
"data-apply-copies":
|
|
477
|
+
"data-apply-copies": view.copies,
|
|
430
478
|
title: records.length > 0 ? directive.directiveClass === "create" ? "Links to the latest copy." : "Links to the latest run." : void 0,
|
|
431
|
-
children: directive.directiveClass === "create" ? `${
|
|
479
|
+
children: directive.directiveClass === "create" ? `${view.copies} copies` : `Ran ${view.copies} times`
|
|
432
480
|
}
|
|
433
481
|
) : null,
|
|
434
482
|
records.map((record) => /* @__PURE__ */ jsx3("span", { className: "inline-flex min-w-0 items-center", children: host.renderRecord?.({
|
|
@@ -455,7 +503,7 @@ function ApplyDirectiveButton({
|
|
|
455
503
|
}
|
|
456
504
|
);
|
|
457
505
|
}
|
|
458
|
-
if (
|
|
506
|
+
if (view.status === "checking") {
|
|
459
507
|
return /* @__PURE__ */ jsx3(
|
|
460
508
|
"span",
|
|
461
509
|
{
|
|
@@ -467,7 +515,7 @@ function ApplyDirectiveButton({
|
|
|
467
515
|
}
|
|
468
516
|
);
|
|
469
517
|
}
|
|
470
|
-
if (
|
|
518
|
+
if (view.status === "finishing") {
|
|
471
519
|
return /* @__PURE__ */ jsxs2(
|
|
472
520
|
"span",
|
|
473
521
|
{
|
|
@@ -482,22 +530,25 @@ function ApplyDirectiveButton({
|
|
|
482
530
|
);
|
|
483
531
|
}
|
|
484
532
|
const destructive = directive.directiveClass === "delete";
|
|
533
|
+
const muted = nothingToChange && view.status === "idle";
|
|
485
534
|
return /* @__PURE__ */ jsxs2("span", { className: "inline-flex items-center gap-2", children: [
|
|
535
|
+
muted ? /* @__PURE__ */ jsx3("span", { className: "text-xs text-muted-foreground", "data-apply-nothing": "", children: "Nothing to change" }) : null,
|
|
486
536
|
/* @__PURE__ */ jsxs2(
|
|
487
537
|
"button",
|
|
488
538
|
{
|
|
489
539
|
type: "button",
|
|
490
540
|
onClick: () => void handleApply(),
|
|
491
|
-
disabled:
|
|
492
|
-
"data-apply-tone": destructive ? "destructive" : "default",
|
|
493
|
-
|
|
541
|
+
disabled: view.status === "applying" || view.status === "asking" || items.length === 0,
|
|
542
|
+
"data-apply-tone": muted ? "muted" : destructive ? "destructive" : "default",
|
|
543
|
+
title: muted ? "It sets no fields, so applying it changes nothing." : void 0,
|
|
544
|
+
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"}`,
|
|
494
545
|
children: [
|
|
495
|
-
|
|
496
|
-
|
|
546
|
+
view.status === "applying" ? /* @__PURE__ */ jsx3(LoaderIcon, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx3(PlayIcon, { className: "h-3.5 w-3.5" }),
|
|
547
|
+
view.status === "applying" ? `Applying${itemCount ? ` ${itemCount} ${itemCount === 1 ? "item" : "items"}` : ""}\u2026${elapsed ? ` ${elapsed}s` : ""}` : label
|
|
497
548
|
]
|
|
498
549
|
}
|
|
499
550
|
),
|
|
500
|
-
|
|
551
|
+
view.status === "error" ? /* @__PURE__ */ jsxs2(
|
|
501
552
|
"span",
|
|
502
553
|
{
|
|
503
554
|
className: "inline-flex max-w-sm items-start gap-1 text-xs text-destructive",
|
|
@@ -509,14 +560,14 @@ function ApplyDirectiveButton({
|
|
|
509
560
|
{
|
|
510
561
|
words: failureWords(
|
|
511
562
|
{
|
|
512
|
-
raw:
|
|
563
|
+
raw: view.message,
|
|
513
564
|
directiveClass: directive.directiveClass,
|
|
514
565
|
noun: directive.noun,
|
|
515
566
|
nounLabel: nounText
|
|
516
567
|
},
|
|
517
568
|
host.explainFailure
|
|
518
569
|
),
|
|
519
|
-
raw:
|
|
570
|
+
raw: view.message
|
|
520
571
|
}
|
|
521
572
|
)
|
|
522
573
|
]
|
|
@@ -608,12 +659,13 @@ function ItemRow({
|
|
|
608
659
|
directive,
|
|
609
660
|
item,
|
|
610
661
|
index,
|
|
611
|
-
total
|
|
662
|
+
total,
|
|
663
|
+
phase
|
|
612
664
|
}) {
|
|
613
665
|
const host = useDirectiveHost();
|
|
614
666
|
const recordId = TARGETING_CLASSES.has(directive.directiveClass) ? itemRecordId(item) : null;
|
|
615
667
|
if (recordId) {
|
|
616
|
-
return /* @__PURE__ */ jsx4(TargetRow, { directive, item, recordId });
|
|
668
|
+
return /* @__PURE__ */ jsx4(TargetRow, { directive, item, recordId, index, phase });
|
|
617
669
|
}
|
|
618
670
|
const titleColumn = nounTitleColumn(directive.noun, host.nouns);
|
|
619
671
|
const title = itemTitle(item, titleColumn, index, total);
|
|
@@ -700,16 +752,20 @@ function DirectiveChangeList({
|
|
|
700
752
|
function TargetRow({
|
|
701
753
|
directive,
|
|
702
754
|
item,
|
|
703
|
-
recordId
|
|
755
|
+
recordId,
|
|
756
|
+
index,
|
|
757
|
+
phase
|
|
704
758
|
}) {
|
|
705
759
|
const host = useDirectiveHost();
|
|
706
760
|
const fallback = recordFallbackName(nounLabel2(directive.noun, host.nouns), recordId);
|
|
707
761
|
const isUpdate = directive.directiveClass === "update";
|
|
708
|
-
const
|
|
709
|
-
|
|
710
|
-
|
|
762
|
+
const readsLive = isUpdate && phase.phase === "open";
|
|
763
|
+
const live = useRecordValues(
|
|
764
|
+
readsLive ? host.readRecord : void 0,
|
|
765
|
+
readsLive ? { noun: directive.noun, id: recordId } : null
|
|
711
766
|
);
|
|
712
|
-
const
|
|
767
|
+
const compareTo = phase.phase === "applied" ? phase.before?.[index] ?? null : readsLive ? live.values : null;
|
|
768
|
+
const changes = isUpdate ? itemChanges(item, compareTo, { titleColumn: nounTitleColumn(directive.noun, host.nouns) }) : [];
|
|
713
769
|
const name = host.renderRecord ? host.renderRecord({ noun: directive.noun, id: recordId, fallback, context: "row" }) : /* @__PURE__ */ jsx4("span", { className: "truncate font-medium text-foreground", children: fallback });
|
|
714
770
|
return /* @__PURE__ */ jsxs3("div", { className: "py-1 text-xs", "data-directive-target": recordId, children: [
|
|
715
771
|
/* @__PURE__ */ jsxs3("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
@@ -726,6 +782,10 @@ function SideEffectDirectiveCard({ directive }) {
|
|
|
726
782
|
const total = items.length;
|
|
727
783
|
const display = directiveDisplay(directive.directiveClass, directive.noun, host.nouns);
|
|
728
784
|
const Icon = classIcon(directive.directiveClass, directive.noun);
|
|
785
|
+
const [phase, setPhase] = useState3(
|
|
786
|
+
() => readsApplyLedger(host, directive) ? { phase: "pending" } : { phase: "open" }
|
|
787
|
+
);
|
|
788
|
+
const nothingToChange = directive.directiveClass === "update" && total > 0 && items.every((item) => itemChanges(item).length === 0);
|
|
729
789
|
const shown = expanded ? items : items.slice(0, ROWS_BEFORE_FOLD);
|
|
730
790
|
const hidden = total - shown.length;
|
|
731
791
|
return /* @__PURE__ */ jsxs3("div", { className: "my-3 rounded-md border border-border bg-card px-2.5 py-1.5", "data-directive": directive.slug, children: [
|
|
@@ -740,13 +800,21 @@ function SideEffectDirectiveCard({ directive }) {
|
|
|
740
800
|
] }),
|
|
741
801
|
/* @__PURE__ */ jsxs3("div", { className: "ml-auto flex items-center gap-1", children: [
|
|
742
802
|
host.renderCopy?.({ label: display.title, value: directive.shell, size: "icon" }),
|
|
743
|
-
/* @__PURE__ */ jsx4(
|
|
803
|
+
/* @__PURE__ */ jsx4(
|
|
804
|
+
ApplyDirectiveButton,
|
|
805
|
+
{
|
|
806
|
+
directive,
|
|
807
|
+
itemCount: total,
|
|
808
|
+
onPhase: setPhase,
|
|
809
|
+
nothingToChange
|
|
810
|
+
}
|
|
811
|
+
)
|
|
744
812
|
] })
|
|
745
813
|
] }),
|
|
746
814
|
total === 0 ? (
|
|
747
815
|
// Stated, never silent: an empty batch is a real (and suspicious) state.
|
|
748
816
|
/* @__PURE__ */ jsx4("p", { className: "mt-1 text-xs text-muted-foreground", children: "No items \u2014 nothing would be written." })
|
|
749
|
-
) : /* @__PURE__ */ jsx4("div", { className: "mt-0.5 divide-y divide-border/60", children: shown.map((item, i) => /* @__PURE__ */ jsx4(ItemRow, { directive, item, index: i, total }, i)) }),
|
|
817
|
+
) : /* @__PURE__ */ jsx4("div", { className: "mt-0.5 divide-y divide-border/60", children: shown.map((item, i) => /* @__PURE__ */ jsx4(ItemRow, { directive, item, index: i, total, phase }, i)) }),
|
|
750
818
|
hidden > 0 || expanded ? /* @__PURE__ */ jsxs3(
|
|
751
819
|
"button",
|
|
752
820
|
{
|
|
@@ -889,6 +957,7 @@ export {
|
|
|
889
957
|
itemChanges,
|
|
890
958
|
itemRecordId,
|
|
891
959
|
provisionalDirectiveSlug,
|
|
960
|
+
readsApplyLedger,
|
|
892
961
|
recordFallbackName,
|
|
893
962
|
registerDirectiveRenderer,
|
|
894
963
|
resetDirectiveRenderers,
|