@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.
package/dist/index.js CHANGED
@@ -753,7 +753,7 @@ import { looksLikeDirectiveHead, tryDecodeDirectiveContent } from "@ai-matrx/con
753
753
  import { directiveDisplay } from "@ai-matrx/content-ir";
754
754
 
755
755
  // directives/ApplyDirectiveButton.tsx
756
- import { useEffect, useRef, useState } from "react";
756
+ import { useEffect, useRef, useState, useSyncExternalStore as useSyncExternalStore2 } from "react";
757
757
  import { nounLabel } from "@ai-matrx/content-ir";
758
758
 
759
759
  // directives/item-changes.ts
@@ -951,6 +951,34 @@ function classIcon(directiveClass, noun) {
951
951
 
952
952
  // directives/ApplyDirectiveButton.tsx
953
953
  import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
954
+ var applyOutcomes = /* @__PURE__ */ new WeakMap();
955
+ var EMPTY_AUTHORITY = {};
956
+ var outcomeListeners = /* @__PURE__ */ new Set();
957
+ function outcomesOf(host) {
958
+ let held = applyOutcomes.get(host);
959
+ if (!held) {
960
+ held = /* @__PURE__ */ new Map();
961
+ applyOutcomes.set(host, held);
962
+ }
963
+ return held;
964
+ }
965
+ function publishOutcome(host, identity, outcome) {
966
+ outcomesOf(host).set(identity, outcome);
967
+ for (const listener of outcomeListeners) listener();
968
+ }
969
+ function subscribeOutcomes(listener) {
970
+ outcomeListeners.add(listener);
971
+ return () => {
972
+ outcomeListeners.delete(listener);
973
+ };
974
+ }
975
+ function useApplyOutcome(host, identity) {
976
+ return useSyncExternalStore2(
977
+ subscribeOutcomes,
978
+ () => applyOutcomes.get(host)?.get(identity) ?? null,
979
+ () => null
980
+ );
981
+ }
954
982
  var FINISH_POLL_MS = 2e3;
955
983
  var FINISH_POLLS_MAX = 30;
956
984
  function readsApplyLedger(host, directive) {
@@ -982,6 +1010,8 @@ function ApplyDirectiveButton({
982
1010
  setAskingAgain(false);
983
1011
  setElapsed(0);
984
1012
  }
1013
+ const shared = useApplyOutcome(host.confirm ?? EMPTY_AUTHORITY, identity);
1014
+ const view = state.status === "asking" || !shared ? state : shared;
985
1015
  const currentIdentity = useRef(identity);
986
1016
  useEffect(() => {
987
1017
  currentIdentity.current = identity;
@@ -1035,33 +1065,34 @@ function ApplyDirectiveButton({
1035
1065
  if (timer) clearTimeout(timer);
1036
1066
  };
1037
1067
  }, [shellKey, read, report]);
1038
- const appliedClean = state.status === "applied" && state.failed === 0 ? state : null;
1068
+ const appliedClean = view.status === "applied" && view.failed === 0 ? view : null;
1039
1069
  const appliedBefore = appliedClean ? appliedClean.before : null;
1040
- const phaseKey = state.status === "checking" || state.status === "finishing" ? "pending" : appliedClean ? `applied:${JSON.stringify(appliedBefore)}` : "open";
1070
+ const phaseKey = view.status === "checking" || view.status === "finishing" ? "pending" : appliedClean ? `applied:${JSON.stringify(appliedBefore)}` : "open";
1041
1071
  useEffect(() => {
1042
1072
  if (!onPhase) return;
1043
1073
  if (phaseKey === "pending") onPhase({ phase: "pending" });
1044
1074
  else if (phaseKey === "open") onPhase({ phase: "open" });
1045
1075
  else onPhase({ phase: "applied", before: appliedBefore });
1046
1076
  }, [phaseKey, onPhase, appliedBefore]);
1077
+ const applyingSince = view.status === "applying" ? view.startedAt : null;
1047
1078
  useEffect(() => {
1048
- if (state.status !== "applying") return;
1049
- startedAt.current = Date.now();
1079
+ if (applyingSince === null) return;
1080
+ startedAt.current = applyingSince;
1050
1081
  const id = setInterval(
1051
1082
  () => setElapsed(Math.round((Date.now() - (startedAt.current ?? 0)) / 1e3)),
1052
1083
  1e3
1053
1084
  );
1054
1085
  return () => clearInterval(id);
1055
- }, [state.status]);
1086
+ }, [applyingSince]);
1056
1087
  if (!directive.parsed.executes) return null;
1057
1088
  const confirm = host.confirm;
1058
1089
  if (!confirm) return null;
1059
1090
  async function handleApply(again = false) {
1060
1091
  if (!confirm) return;
1061
- const prior = state;
1092
+ const prior = view;
1062
1093
  const askedFor = identity;
1063
1094
  const stillHere = () => currentIdentity.current === askedFor;
1064
- const priorCopies = state.status === "applied" ? state.copies : 0;
1095
+ const priorCopies = view.status === "applied" ? view.copies : 0;
1065
1096
  if (host.ask) {
1066
1097
  if (again) setAskingAgain(true);
1067
1098
  else setState({ status: "asking" });
@@ -1090,14 +1121,14 @@ function ApplyDirectiveButton({
1090
1121
  }
1091
1122
  }
1092
1123
  setElapsed(0);
1093
- setState({ status: "applying" });
1124
+ setState((prev) => prev.status === "asking" ? { status: "idle" } : prev);
1125
+ publishOutcome(confirm, askedFor, { status: "applying", startedAt: Date.now() });
1094
1126
  try {
1095
1127
  const shell = { __kind: directive.slug, items };
1096
1128
  if (conversationId) shell.conversationId = conversationId;
1097
1129
  if (again) shell.force = true;
1098
1130
  const result = await confirm(shell);
1099
- if (!stillHere()) return;
1100
- setState({
1131
+ publishOutcome(confirm, askedFor, {
1101
1132
  status: "applied",
1102
1133
  applied: result.applied,
1103
1134
  failed: result.failed,
@@ -1109,20 +1140,19 @@ function ApplyDirectiveButton({
1109
1140
  // apply replaced is still the change this block made.
1110
1141
  before: again && prior.status === "applied" && prior.before ? prior.before : result.before ?? null
1111
1142
  });
1112
- onApplied?.();
1143
+ if (stillHere()) onApplied?.();
1113
1144
  } catch (error) {
1114
- if (!stillHere()) return;
1115
- setState({
1145
+ publishOutcome(confirm, askedFor, {
1116
1146
  status: "error",
1117
1147
  message: error instanceof Error && error.message ? error.message : "That couldn't be applied just now. Please try again."
1118
1148
  });
1119
1149
  }
1120
1150
  }
1121
- if (state.status === "applied") {
1122
- const bad = state.failed > 0;
1151
+ if (view.status === "applied") {
1152
+ const bad = view.failed > 0;
1123
1153
  const trashed = directive.directiveClass === "delete";
1124
1154
  const label2 = nounText;
1125
- const records = host.renderRecord ? state.records : [];
1155
+ const records = host.renderRecord ? view.records : [];
1126
1156
  return /* @__PURE__ */ jsxs2(
1127
1157
  "span",
1128
1158
  {
@@ -1136,26 +1166,26 @@ function ApplyDirectiveButton({
1136
1166
  {
1137
1167
  words: failureWords(
1138
1168
  {
1139
- raw: state.message ?? `${state.failed} failed`,
1169
+ raw: view.message ?? `${view.failed} failed`,
1140
1170
  directiveClass: directive.directiveClass,
1141
1171
  noun: directive.noun,
1142
1172
  nounLabel: nounText,
1143
- failed: state.failed,
1144
- total: state.applied + state.failed
1173
+ failed: view.failed,
1174
+ total: view.applied + view.failed
1145
1175
  },
1146
1176
  host.explainFailure
1147
1177
  ),
1148
- raw: state.message
1178
+ raw: view.message
1149
1179
  }
1150
- ) : /* @__PURE__ */ jsx4("span", { className: "min-w-0", children: state.message ?? `Applied ${state.applied}` })
1180
+ ) : /* @__PURE__ */ jsx4("span", { className: "min-w-0", children: view.message ?? `Applied ${view.applied}` })
1151
1181
  ] }),
1152
- state.copies > 1 ? /* @__PURE__ */ jsx4(
1182
+ view.copies > 1 ? /* @__PURE__ */ jsx4(
1153
1183
  "span",
1154
1184
  {
1155
1185
  className: "shrink-0 text-muted-foreground",
1156
- "data-apply-copies": state.copies,
1186
+ "data-apply-copies": view.copies,
1157
1187
  title: records.length > 0 ? directive.directiveClass === "create" ? "Links to the latest copy." : "Links to the latest run." : void 0,
1158
- children: directive.directiveClass === "create" ? `${state.copies} copies` : `Ran ${state.copies} times`
1188
+ children: directive.directiveClass === "create" ? `${view.copies} copies` : `Ran ${view.copies} times`
1159
1189
  }
1160
1190
  ) : null,
1161
1191
  records.map((record) => /* @__PURE__ */ jsx4("span", { className: "inline-flex min-w-0 items-center", children: host.renderRecord?.({
@@ -1182,7 +1212,7 @@ function ApplyDirectiveButton({
1182
1212
  }
1183
1213
  );
1184
1214
  }
1185
- if (state.status === "checking") {
1215
+ if (view.status === "checking") {
1186
1216
  return /* @__PURE__ */ jsx4(
1187
1217
  "span",
1188
1218
  {
@@ -1194,7 +1224,7 @@ function ApplyDirectiveButton({
1194
1224
  }
1195
1225
  );
1196
1226
  }
1197
- if (state.status === "finishing") {
1227
+ if (view.status === "finishing") {
1198
1228
  return /* @__PURE__ */ jsxs2(
1199
1229
  "span",
1200
1230
  {
@@ -1209,7 +1239,7 @@ function ApplyDirectiveButton({
1209
1239
  );
1210
1240
  }
1211
1241
  const destructive = directive.directiveClass === "delete";
1212
- const muted = nothingToChange && state.status === "idle";
1242
+ const muted = nothingToChange && view.status === "idle";
1213
1243
  return /* @__PURE__ */ jsxs2("span", { className: "inline-flex items-center gap-2", children: [
1214
1244
  muted ? /* @__PURE__ */ jsx4("span", { className: "text-xs text-muted-foreground", "data-apply-nothing": "", children: "Nothing to change" }) : null,
1215
1245
  /* @__PURE__ */ jsxs2(
@@ -1217,17 +1247,17 @@ function ApplyDirectiveButton({
1217
1247
  {
1218
1248
  type: "button",
1219
1249
  onClick: () => void handleApply(),
1220
- disabled: state.status === "applying" || state.status === "asking" || items.length === 0,
1250
+ disabled: view.status === "applying" || view.status === "asking" || items.length === 0,
1221
1251
  "data-apply-tone": muted ? "muted" : destructive ? "destructive" : "default",
1222
1252
  title: muted ? "It sets no fields, so applying it changes nothing." : void 0,
1223
1253
  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"}`,
1224
1254
  children: [
1225
- state.status === "applying" ? /* @__PURE__ */ jsx4(LoaderIcon, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx4(PlayIcon, { className: "h-3.5 w-3.5" }),
1226
- state.status === "applying" ? `Applying${itemCount ? ` ${itemCount} ${itemCount === 1 ? "item" : "items"}` : ""}\u2026${elapsed ? ` ${elapsed}s` : ""}` : label
1255
+ view.status === "applying" ? /* @__PURE__ */ jsx4(LoaderIcon, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx4(PlayIcon, { className: "h-3.5 w-3.5" }),
1256
+ view.status === "applying" ? `Applying${itemCount ? ` ${itemCount} ${itemCount === 1 ? "item" : "items"}` : ""}\u2026${elapsed ? ` ${elapsed}s` : ""}` : label
1227
1257
  ]
1228
1258
  }
1229
1259
  ),
1230
- state.status === "error" ? /* @__PURE__ */ jsxs2(
1260
+ view.status === "error" ? /* @__PURE__ */ jsxs2(
1231
1261
  "span",
1232
1262
  {
1233
1263
  className: "inline-flex max-w-sm items-start gap-1 text-xs text-destructive",
@@ -1239,14 +1269,14 @@ function ApplyDirectiveButton({
1239
1269
  {
1240
1270
  words: failureWords(
1241
1271
  {
1242
- raw: state.message,
1272
+ raw: view.message,
1243
1273
  directiveClass: directive.directiveClass,
1244
1274
  noun: directive.noun,
1245
1275
  nounLabel: nounText
1246
1276
  },
1247
1277
  host.explainFailure
1248
1278
  ),
1249
- raw: state.message
1279
+ raw: view.message
1250
1280
  }
1251
1281
  )
1252
1282
  ]