@ai-matrx/content-ir-react 0.18.0 → 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.
@@ -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,6 +242,34 @@ 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;
247
275
  function readsApplyLedger(host, directive) {
@@ -273,6 +301,8 @@ function ApplyDirectiveButton({
273
301
  setAskingAgain(false);
274
302
  setElapsed(0);
275
303
  }
304
+ const shared = useApplyOutcome(host.confirm ?? EMPTY_AUTHORITY, identity);
305
+ const view = state.status === "asking" || !shared ? state : shared;
276
306
  const currentIdentity = useRef(identity);
277
307
  useEffect(() => {
278
308
  currentIdentity.current = identity;
@@ -326,33 +356,34 @@ function ApplyDirectiveButton({
326
356
  if (timer) clearTimeout(timer);
327
357
  };
328
358
  }, [shellKey, read, report]);
329
- const appliedClean = state.status === "applied" && state.failed === 0 ? state : null;
359
+ const appliedClean = view.status === "applied" && view.failed === 0 ? view : null;
330
360
  const appliedBefore = appliedClean ? appliedClean.before : null;
331
- const phaseKey = state.status === "checking" || state.status === "finishing" ? "pending" : appliedClean ? `applied:${JSON.stringify(appliedBefore)}` : "open";
361
+ const phaseKey = view.status === "checking" || view.status === "finishing" ? "pending" : appliedClean ? `applied:${JSON.stringify(appliedBefore)}` : "open";
332
362
  useEffect(() => {
333
363
  if (!onPhase) return;
334
364
  if (phaseKey === "pending") onPhase({ phase: "pending" });
335
365
  else if (phaseKey === "open") onPhase({ phase: "open" });
336
366
  else onPhase({ phase: "applied", before: appliedBefore });
337
367
  }, [phaseKey, onPhase, appliedBefore]);
368
+ const applyingSince = view.status === "applying" ? view.startedAt : null;
338
369
  useEffect(() => {
339
- if (state.status !== "applying") return;
340
- startedAt.current = Date.now();
370
+ if (applyingSince === null) return;
371
+ startedAt.current = applyingSince;
341
372
  const id = setInterval(
342
373
  () => setElapsed(Math.round((Date.now() - (startedAt.current ?? 0)) / 1e3)),
343
374
  1e3
344
375
  );
345
376
  return () => clearInterval(id);
346
- }, [state.status]);
377
+ }, [applyingSince]);
347
378
  if (!directive.parsed.executes) return null;
348
379
  const confirm = host.confirm;
349
380
  if (!confirm) return null;
350
381
  async function handleApply(again = false) {
351
382
  if (!confirm) return;
352
- const prior = state;
383
+ const prior = view;
353
384
  const askedFor = identity;
354
385
  const stillHere = () => currentIdentity.current === askedFor;
355
- const priorCopies = state.status === "applied" ? state.copies : 0;
386
+ const priorCopies = view.status === "applied" ? view.copies : 0;
356
387
  if (host.ask) {
357
388
  if (again) setAskingAgain(true);
358
389
  else setState({ status: "asking" });
@@ -381,14 +412,14 @@ function ApplyDirectiveButton({
381
412
  }
382
413
  }
383
414
  setElapsed(0);
384
- setState({ status: "applying" });
415
+ setState((prev) => prev.status === "asking" ? { status: "idle" } : prev);
416
+ publishOutcome(confirm, askedFor, { status: "applying", startedAt: Date.now() });
385
417
  try {
386
418
  const shell = { __kind: directive.slug, items };
387
419
  if (conversationId) shell.conversationId = conversationId;
388
420
  if (again) shell.force = true;
389
421
  const result = await confirm(shell);
390
- if (!stillHere()) return;
391
- setState({
422
+ publishOutcome(confirm, askedFor, {
392
423
  status: "applied",
393
424
  applied: result.applied,
394
425
  failed: result.failed,
@@ -400,20 +431,19 @@ function ApplyDirectiveButton({
400
431
  // apply replaced is still the change this block made.
401
432
  before: again && prior.status === "applied" && prior.before ? prior.before : result.before ?? null
402
433
  });
403
- onApplied?.();
434
+ if (stillHere()) onApplied?.();
404
435
  } catch (error) {
405
- if (!stillHere()) return;
406
- setState({
436
+ publishOutcome(confirm, askedFor, {
407
437
  status: "error",
408
438
  message: error instanceof Error && error.message ? error.message : "That couldn't be applied just now. Please try again."
409
439
  });
410
440
  }
411
441
  }
412
- if (state.status === "applied") {
413
- const bad = state.failed > 0;
442
+ if (view.status === "applied") {
443
+ const bad = view.failed > 0;
414
444
  const trashed = directive.directiveClass === "delete";
415
445
  const label2 = nounText;
416
- const records = host.renderRecord ? state.records : [];
446
+ const records = host.renderRecord ? view.records : [];
417
447
  return /* @__PURE__ */ jsxs2(
418
448
  "span",
419
449
  {
@@ -427,26 +457,26 @@ function ApplyDirectiveButton({
427
457
  {
428
458
  words: failureWords(
429
459
  {
430
- raw: state.message ?? `${state.failed} failed`,
460
+ raw: view.message ?? `${view.failed} failed`,
431
461
  directiveClass: directive.directiveClass,
432
462
  noun: directive.noun,
433
463
  nounLabel: nounText,
434
- failed: state.failed,
435
- total: state.applied + state.failed
464
+ failed: view.failed,
465
+ total: view.applied + view.failed
436
466
  },
437
467
  host.explainFailure
438
468
  ),
439
- raw: state.message
469
+ raw: view.message
440
470
  }
441
- ) : /* @__PURE__ */ jsx3("span", { className: "min-w-0", children: state.message ?? `Applied ${state.applied}` })
471
+ ) : /* @__PURE__ */ jsx3("span", { className: "min-w-0", children: view.message ?? `Applied ${view.applied}` })
442
472
  ] }),
443
- state.copies > 1 ? /* @__PURE__ */ jsx3(
473
+ view.copies > 1 ? /* @__PURE__ */ jsx3(
444
474
  "span",
445
475
  {
446
476
  className: "shrink-0 text-muted-foreground",
447
- "data-apply-copies": state.copies,
477
+ "data-apply-copies": view.copies,
448
478
  title: records.length > 0 ? directive.directiveClass === "create" ? "Links to the latest copy." : "Links to the latest run." : void 0,
449
- children: directive.directiveClass === "create" ? `${state.copies} copies` : `Ran ${state.copies} times`
479
+ children: directive.directiveClass === "create" ? `${view.copies} copies` : `Ran ${view.copies} times`
450
480
  }
451
481
  ) : null,
452
482
  records.map((record) => /* @__PURE__ */ jsx3("span", { className: "inline-flex min-w-0 items-center", children: host.renderRecord?.({
@@ -473,7 +503,7 @@ function ApplyDirectiveButton({
473
503
  }
474
504
  );
475
505
  }
476
- if (state.status === "checking") {
506
+ if (view.status === "checking") {
477
507
  return /* @__PURE__ */ jsx3(
478
508
  "span",
479
509
  {
@@ -485,7 +515,7 @@ function ApplyDirectiveButton({
485
515
  }
486
516
  );
487
517
  }
488
- if (state.status === "finishing") {
518
+ if (view.status === "finishing") {
489
519
  return /* @__PURE__ */ jsxs2(
490
520
  "span",
491
521
  {
@@ -500,7 +530,7 @@ function ApplyDirectiveButton({
500
530
  );
501
531
  }
502
532
  const destructive = directive.directiveClass === "delete";
503
- const muted = nothingToChange && state.status === "idle";
533
+ const muted = nothingToChange && view.status === "idle";
504
534
  return /* @__PURE__ */ jsxs2("span", { className: "inline-flex items-center gap-2", children: [
505
535
  muted ? /* @__PURE__ */ jsx3("span", { className: "text-xs text-muted-foreground", "data-apply-nothing": "", children: "Nothing to change" }) : null,
506
536
  /* @__PURE__ */ jsxs2(
@@ -508,17 +538,17 @@ function ApplyDirectiveButton({
508
538
  {
509
539
  type: "button",
510
540
  onClick: () => void handleApply(),
511
- disabled: state.status === "applying" || state.status === "asking" || items.length === 0,
541
+ disabled: view.status === "applying" || view.status === "asking" || items.length === 0,
512
542
  "data-apply-tone": muted ? "muted" : destructive ? "destructive" : "default",
513
543
  title: muted ? "It sets no fields, so applying it changes nothing." : void 0,
514
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"}`,
515
545
  children: [
516
- state.status === "applying" ? /* @__PURE__ */ jsx3(LoaderIcon, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx3(PlayIcon, { className: "h-3.5 w-3.5" }),
517
- state.status === "applying" ? `Applying${itemCount ? ` ${itemCount} ${itemCount === 1 ? "item" : "items"}` : ""}\u2026${elapsed ? ` ${elapsed}s` : ""}` : label
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
518
548
  ]
519
549
  }
520
550
  ),
521
- state.status === "error" ? /* @__PURE__ */ jsxs2(
551
+ view.status === "error" ? /* @__PURE__ */ jsxs2(
522
552
  "span",
523
553
  {
524
554
  className: "inline-flex max-w-sm items-start gap-1 text-xs text-destructive",
@@ -530,14 +560,14 @@ function ApplyDirectiveButton({
530
560
  {
531
561
  words: failureWords(
532
562
  {
533
- raw: state.message,
563
+ raw: view.message,
534
564
  directiveClass: directive.directiveClass,
535
565
  noun: directive.noun,
536
566
  nounLabel: nounText
537
567
  },
538
568
  host.explainFailure
539
569
  ),
540
- raw: state.message
570
+ raw: view.message
541
571
  }
542
572
  )
543
573
  ]