@ai-matrx/records-ui 0.79.0 → 0.82.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/dist/index.cjs CHANGED
@@ -88,6 +88,7 @@ __export(src_exports, {
88
88
  FORMULA_OP_LABEL: () => FORMULA_OP_LABEL,
89
89
  FORMULA_OP_VALUES: () => FORMULA_OP_VALUES,
90
90
  FORM_FLOWS: () => FORM_FLOWS,
91
+ FORM_NOT_HERE_LINE: () => FORM_NOT_HERE_LINE,
91
92
  FROZEN_COLUMN_WIDTH: () => FROZEN_COLUMN_WIDTH,
92
93
  FieldControl: () => FieldControl,
93
94
  FieldEditor: () => FieldEditor,
@@ -135,6 +136,7 @@ __export(src_exports, {
135
136
  PAGE_VIEW_LABEL: () => PAGE_VIEW_LABEL,
136
137
  PARITY_LABEL: () => PARITY_LABEL,
137
138
  PARITY_MADE_OF: () => PARITY_MADE_OF,
139
+ PORTAL_NOT_HERE_LINE: () => PORTAL_NOT_HERE_LINE,
138
140
  PROPOSED_CHANGE_ACT_LABEL: () => PROPOSED_CHANGE_ACT_LABEL,
139
141
  PROPOSED_CHANGE_ACT_VALUES: () => PROPOSED_CHANGE_ACT_VALUES,
140
142
  Peek: () => Peek,
@@ -149,6 +151,7 @@ __export(src_exports, {
149
151
  PublicViewPage: () => PublicViewPage,
150
152
  ROLLUP_AGG_LABEL: () => ROLLUP_AGG_LABEL,
151
153
  ROLLUP_AGG_VALUES: () => ROLLUP_AGG_VALUES,
154
+ RULE_NOT_HERE_LINE: () => RULE_NOT_HERE_LINE,
152
155
  RecordChat: () => RecordChat,
153
156
  RecordChip: () => RecordChip,
154
157
  RecordForm: () => RecordForm,
@@ -163,6 +166,7 @@ __export(src_exports, {
163
166
  SAVED_VIEWS_UNAVAILABLE: () => SAVED_VIEWS_UNAVAILABLE,
164
167
  SAY_WHAT_YOU_ARE_WAITING_FOR_AFTER_MS: () => SAY_WHAT_YOU_ARE_WAITING_FOR_AFTER_MS,
165
168
  SERIES_COLORS: () => SERIES_COLORS,
169
+ SHARE_NOT_YOURS_LINE: () => SHARE_NOT_YOURS_LINE,
166
170
  SOMEBODY: () => SOMEBODY,
167
171
  STAGE_RULE_ON_FAIL_LABEL: () => STAGE_RULE_ON_FAIL_LABEL,
168
172
  STORE_ANSWERS_THESE: () => STORE_ANSWERS_THESE,
@@ -196,6 +200,7 @@ __export(src_exports, {
196
200
  actorWords: () => actorWords,
197
201
  addFields: () => addFields,
198
202
  archivedRowLine: () => archivedRowLine,
203
+ asRefusal: () => asRefusal,
199
204
  askableFields: () => askableFields,
200
205
  blockFromSpec: () => blockFromSpec,
201
206
  bodyForReading: () => bodyForReading,
@@ -261,11 +266,15 @@ __export(src_exports, {
261
266
  previewLine: () => previewLine,
262
267
  previewWords: () => previewWords,
263
268
  publiclyAnswerable: () => publiclyAnswerable,
269
+ railFromParam: () => railFromParam,
264
270
  recordName: () => recordName,
265
271
  recordNameIn: () => recordNameIn,
266
272
  recordsDataSource: () => recordsDataSource,
273
+ refusal: () => refusal,
267
274
  refusalForAPerson: () => refusalForAPerson,
275
+ refusalFromThrown: () => refusalFromThrown,
268
276
  refusalLineForAPerson: () => refusalLineForAPerson,
277
+ refusalOr: () => refusalOr,
269
278
  renderValue: () => renderValue,
270
279
  revokeConsequence: () => revokeConsequence,
271
280
  rowName: () => rowName,
@@ -279,6 +288,7 @@ __export(src_exports, {
279
288
  tableName: () => tableName,
280
289
  tableRightsAt: () => tableRightsAt,
281
290
  tokenFor: () => tokenFor,
291
+ unknownRailLine: () => unknownRailLine,
282
292
  unknownViewLine: () => unknownViewLine,
283
293
  useCanShare: () => useCanShare,
284
294
  useEmbedHandshake: () => useEmbedHandshake,
@@ -826,13 +836,13 @@ function ReferenceBuilder({
826
836
  // src/PersonPicker.tsx
827
837
  var import_react9 = require("react");
828
838
  var import_react10 = require("@ai-matrx/records/react");
829
- var import_design_system4 = require("@ai-matrx/design-system");
839
+ var import_design_system5 = require("@ai-matrx/design-system");
830
840
 
831
841
  // src/editors.tsx
832
842
  var import_react7 = require("react");
833
843
  var import_records4 = require("@ai-matrx/records");
834
844
  var import_react8 = require("@ai-matrx/records/react");
835
- var import_design_system3 = require("@ai-matrx/design-system");
845
+ var import_design_system4 = require("@ai-matrx/design-system");
836
846
 
837
847
  // src/parity.ts
838
848
  var import_records3 = require("@ai-matrx/records");
@@ -934,8 +944,119 @@ function fieldTypeLabel(field) {
934
944
  // src/RelationPicker.tsx
935
945
  var import_react5 = require("react");
936
946
  var import_react6 = require("@ai-matrx/records/react");
947
+ var import_design_system3 = require("@ai-matrx/design-system");
948
+
949
+ // src/Refusal.tsx
937
950
  var import_design_system2 = require("@ai-matrx/design-system");
938
- var import_jsx_runtime3 = (
951
+ var import_jsx_runtime3 = require("react/jsx-runtime");
952
+ function hintIsMachineIdentity(hint) {
953
+ return isMachineIdentity(hint);
954
+ }
955
+ function hintForAPerson(hint) {
956
+ const said = (hint ?? "").trim();
957
+ if (said === "") return null;
958
+ return isMachineIdentity(said) ? null : said;
959
+ }
960
+ function RefusalNotice({
961
+ error,
962
+ className,
963
+ actions,
964
+ onKeepEditing,
965
+ onDiscard
966
+ }) {
967
+ const plain = refusalForAPerson(error);
968
+ const doors = onKeepEditing || onDiscard ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-wrap items-center gap-1 pt-0.5", "data-refusal-doors": "", children: [
969
+ onKeepEditing ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
970
+ "button",
971
+ {
972
+ type: "button",
973
+ "data-refusal-keep-editing": "",
974
+ className: "rounded border px-2 py-0.5 text-xs hover:bg-muted",
975
+ onClick: onKeepEditing,
976
+ children: "Keep editing"
977
+ }
978
+ ) : null,
979
+ onDiscard ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
980
+ "button",
981
+ {
982
+ type: "button",
983
+ "data-refusal-discard": "",
984
+ className: "rounded border px-2 py-0.5 text-xs hover:bg-muted",
985
+ onClick: onDiscard,
986
+ children: "Discard"
987
+ }
988
+ ) : null
989
+ ] }) : null;
990
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
991
+ import_design_system2.Alert,
992
+ {
993
+ variant: "destructive",
994
+ role: "alert",
995
+ "data-refusal-notice": "",
996
+ className: (0, import_design_system2.cn)("text-xs", className),
997
+ ...plain.forEngineers ? { title: plain.forEngineers } : {},
998
+ children: [
999
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_design_system2.AlertTitle, { className: "text-xs font-medium", children: plain.title }),
1000
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_design_system2.AlertDescription, { className: "space-y-1 text-xs", children: [
1001
+ plain.sentence.replace(/\.$/, "") === plain.title ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: plain.sentence }),
1002
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "opacity-80", children: plain.remedy }),
1003
+ plain.forEngineers ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1004
+ "p",
1005
+ {
1006
+ className: "sr-only",
1007
+ "aria-hidden": "true",
1008
+ "data-for-engineers": "",
1009
+ ...error.sqlstate ? { "data-sqlstate": error.sqlstate } : {},
1010
+ children: plain.forEngineers
1011
+ }
1012
+ ) : null,
1013
+ actions,
1014
+ doors
1015
+ ] })
1016
+ ]
1017
+ }
1018
+ );
1019
+ }
1020
+ function RefusalLine({ error, className }) {
1021
+ const plain = refusalForAPerson(error);
1022
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1023
+ "p",
1024
+ {
1025
+ role: "alert",
1026
+ "data-refusal-line": "",
1027
+ className: (0, import_design_system2.cn)("text-xs text-destructive", className),
1028
+ title: plain.forEngineers || void 0,
1029
+ children: refusalLineForAPerson(error)
1030
+ }
1031
+ );
1032
+ }
1033
+
1034
+ // src/refusals.ts
1035
+ function refusal(code, sentence2, remedy, diagnostic) {
1036
+ return {
1037
+ code,
1038
+ message: sentence2,
1039
+ ...remedy ? { hint: remedy } : {},
1040
+ ...diagnostic ? { diagnostic } : {}
1041
+ };
1042
+ }
1043
+ function refusalFromThrown(thrown, sentence2, remedy, code = "internal") {
1044
+ const said = thrown instanceof Error ? thrown.message : String(thrown ?? "");
1045
+ return refusal(code, sentence2, remedy, said ? `Thrown: ${said}` : null);
1046
+ }
1047
+ function refusalOr(error, code, sentence2) {
1048
+ return error ?? refusal(code, sentence2);
1049
+ }
1050
+ function asRefusal(thrown, sentence2, remedy, code = "internal") {
1051
+ const t = thrown;
1052
+ if (t && typeof t === "object" && typeof t.code === "string" && typeof t.message === "string" && !(thrown instanceof Error)) {
1053
+ return t;
1054
+ }
1055
+ return refusalFromThrown(thrown, sentence2, remedy, code);
1056
+ }
1057
+
1058
+ // src/RelationPicker.tsx
1059
+ var import_jsx_runtime4 = (
939
1060
  // The Field's own name is already printed above this control by
940
1061
  // `FieldLabel`, so the sentence does not say it a second time.
941
1062
  require("react/jsx-runtime")
@@ -945,7 +1066,7 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
945
1066
  const client = (0, import_react6.useOptionalRecordsClient)();
946
1067
  const [rows, setRows] = (0, import_react5.useState)(null);
947
1068
  const [words, setWords] = (0, import_react5.useState)({});
948
- const [refusal, setRefusal] = (0, import_react5.useState)(null);
1069
+ const [refusal2, setRefusal] = (0, import_react5.useState)(null);
949
1070
  const [search, setSearch] = (0, import_react5.useState)("");
950
1071
  const [open, setOpen] = (0, import_react5.useState)(false);
951
1072
  const [target, setTarget] = (0, import_react5.useState)(null);
@@ -965,7 +1086,7 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
965
1086
  void client.list({ table_id: target2, limit: 200 }).then((result) => {
966
1087
  if (cancelled) return;
967
1088
  if (result.ok) setRows(result.data.rows);
968
- else setRefusal(refusalLineForAPerson(result.error));
1089
+ else setRefusal(result.error);
969
1090
  });
970
1091
  return () => {
971
1092
  cancelled = true;
@@ -999,14 +1120,17 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
999
1120
  void client.tableList().then((result) => {
1000
1121
  if (cancelled) return;
1001
1122
  if (!result.ok) {
1002
- setTargetRefusal(refusalLineForAPerson(result.error));
1123
+ setTargetRefusal(result.error);
1003
1124
  return;
1004
1125
  }
1005
1126
  const found = result.data.find((t) => t.id === id2) ?? null;
1006
1127
  if (found) setTarget(found);
1007
1128
  else
1008
1129
  setTargetRefusal(
1009
- "That Table is not one you can open, so a new record cannot be added to it from here."
1130
+ refusal(
1131
+ "door",
1132
+ "That Table is not one you can open, so a new record cannot be added to it from here."
1133
+ )
1010
1134
  );
1011
1135
  });
1012
1136
  return () => {
@@ -1014,10 +1138,10 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1014
1138
  };
1015
1139
  }, [client, open, allowCreate, target, targetRefusal, field.relation_target]);
1016
1140
  if (!client) {
1017
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { id, className: (0, import_design_system2.cn)("text-xs text-muted-foreground", className), children: NO_STORE_ON_THIS_SCREEN_REASON });
1141
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { id, className: (0, import_design_system3.cn)("text-xs text-muted-foreground", className), children: NO_STORE_ON_THIS_SCREEN_REASON });
1018
1142
  }
1019
1143
  if (!field.relation_target) {
1020
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
1144
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
1021
1145
  fieldName(field),
1022
1146
  " points at records, but it does not say which Table yet, so there is nothing to pick from. A table admin sets its target in the field editor."
1023
1147
  ] });
@@ -1041,13 +1165,17 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1041
1165
  const tableId = field.relation_target;
1042
1166
  if (!tableId || title === "" || busy) return;
1043
1167
  if (!target) {
1044
- setCreateRefusal("Still reading that Table. Try again in a moment.");
1168
+ setCreateRefusal(refusal("unreachable", "Still reading that Table.", "Try again in a moment."));
1045
1169
  return;
1046
1170
  }
1047
1171
  const titleField = target.title_field;
1048
1172
  if (!titleField) {
1049
1173
  setCreateRefusal(
1050
- `${target.name} has no column that names a record yet, so a new one would have nothing to show here. A table admin picks its title column in that table's settings.`
1174
+ refusal(
1175
+ "not_supported",
1176
+ `${target.name} has no column that names a record yet, so a new one would have nothing to show here.`,
1177
+ "A table admin picks its title column in that table's settings."
1178
+ )
1051
1179
  );
1052
1180
  return;
1053
1181
  }
@@ -1056,7 +1184,7 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1056
1184
  const written = await client.recordWrite({ table_id: tableId, data: { [titleField]: title } });
1057
1185
  if (!written.ok) {
1058
1186
  setBusy(false);
1059
- setCreateRefusal(refusalLineForAPerson(written.error));
1187
+ setCreateRefusal(written.error);
1060
1188
  return;
1061
1189
  }
1062
1190
  const createdId = String(written.data);
@@ -1071,8 +1199,8 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1071
1199
  const visible = (rows ?? []).filter(
1072
1200
  (row) => search.trim() === "" ? true : titleOf(row.id).toLowerCase().includes(search.trim().toLowerCase())
1073
1201
  );
1074
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: (0, import_design_system2.cn)("flex flex-wrap items-center gap-1", className), children: [
1075
- picked.map((recordId) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1202
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: (0, import_design_system3.cn)("flex flex-wrap items-center gap-1", className), children: [
1203
+ picked.map((recordId) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1076
1204
  RecordChip,
1077
1205
  {
1078
1206
  title: titleOf(recordId),
@@ -1080,11 +1208,11 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1080
1208
  },
1081
1209
  recordId
1082
1210
  )),
1083
- disabled ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_design_system2.Popover, { open, onOpenChange: setOpen, children: [
1084
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_design_system2.PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_design_system2.Button, { id, type: "button", size: "sm", variant: "outline", "aria-label": `Pick for ${fieldName(field)}`, children: picked.length === 0 ? "Pick" : "Change" }) }),
1085
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_design_system2.PopoverContent, { className: "w-64 p-1", align: "start", children: [
1086
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1087
- import_design_system2.BasicInput,
1211
+ disabled ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system3.Popover, { open, onOpenChange: setOpen, children: [
1212
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system3.PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system3.Button, { id, type: "button", size: "sm", variant: "outline", "aria-label": `Pick for ${fieldName(field)}`, children: picked.length === 0 ? "Pick" : "Change" }) }),
1213
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system3.PopoverContent, { sizing: "content", className: "p-1", align: "start", children: [
1214
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1215
+ import_design_system3.BasicInput,
1088
1216
  {
1089
1217
  value: search,
1090
1218
  onChange: (e) => setSearch(e.target.value),
@@ -1092,33 +1220,34 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1092
1220
  className: "mb-1 h-7 text-xs"
1093
1221
  }
1094
1222
  ),
1095
- refusal ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "p-2 text-xs text-destructive", children: refusal }) : null,
1096
- !rows && !refusal ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "p-2 text-xs text-muted-foreground", children: "Reading\u2026" }) : null,
1097
- full ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: "p-2 text-xs text-muted-foreground", children: [
1223
+ refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RefusalLine, { error: refusal2, className: "p-2" }) : null,
1224
+ !rows && !refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "p-2 text-xs text-muted-foreground", children: "Reading\u2026" }) : null,
1225
+ full ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "p-2 text-xs text-muted-foreground", children: [
1098
1226
  fieldName(field),
1099
1227
  " can point at ",
1100
1228
  field.relation_max,
1101
1229
  " at most. Remove one to add another."
1102
1230
  ] }) : null,
1103
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_design_system2.ScrollArea, { className: "max-h-56", children: [
1104
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ul", { children: visible.map((row) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1231
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system3.ScrollArea, { className: "max-h-56", children: [
1232
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { children: visible.map((row) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1105
1233
  "button",
1106
1234
  {
1107
1235
  type: "button",
1108
1236
  onClick: () => toggle(row.id),
1109
- className: (0, import_design_system2.cn)(
1237
+ title: titleOf(row.id),
1238
+ className: (0, import_design_system3.cn)(
1110
1239
  "w-full truncate rounded px-2 py-1 text-left text-xs hover:bg-muted",
1111
1240
  picked.includes(row.id) ? "bg-accent text-accent-foreground" : ""
1112
1241
  ),
1113
1242
  children: titleOf(row.id)
1114
1243
  }
1115
1244
  ) }, row.id)) }),
1116
- rows && visible.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "p-2 text-xs text-muted-foreground", children: "Nothing in that table matches." }) : null
1245
+ rows && visible.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "p-2 text-xs text-muted-foreground", children: "Nothing in that table matches." }) : null
1117
1246
  ] }),
1118
- allowCreate && !full ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-1 border-t pt-1", children: [
1119
- targetRefusal ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "p-2 text-xs text-destructive", children: targetRefusal }) : !target ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "p-2 text-xs text-muted-foreground", children: "Reading that table\u2026" }) : creating ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-1 p-1", children: [
1120
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1121
- import_design_system2.BasicInput,
1247
+ allowCreate && !full ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mt-1 border-t pt-1", children: [
1248
+ targetRefusal ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RefusalLine, { error: targetRefusal, className: "p-2" }) : !target ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "p-2 text-xs text-muted-foreground", children: "Reading that table\u2026" }) : creating ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-center gap-1 p-1", children: [
1249
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1250
+ import_design_system3.BasicInput,
1122
1251
  {
1123
1252
  autoFocus: true,
1124
1253
  value: newTitle,
@@ -1134,8 +1263,8 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1134
1263
  "aria-label": `Name the new ${targetWord}`
1135
1264
  }
1136
1265
  ),
1137
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1138
- import_design_system2.Button,
1266
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1267
+ import_design_system3.Button,
1139
1268
  {
1140
1269
  type: "button",
1141
1270
  size: "sm",
@@ -1145,12 +1274,13 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1145
1274
  children: busy ? "Creating\u2026" : "Create"
1146
1275
  }
1147
1276
  )
1148
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1149
- import_design_system2.Button,
1277
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1278
+ import_design_system3.Button,
1150
1279
  {
1151
1280
  type: "button",
1152
1281
  size: "sm",
1153
1282
  variant: "ghost",
1283
+ title: search.trim() !== "" ? `Create \u201C${search.trim()}\u201D` : `New ${targetWord}`,
1154
1284
  className: "w-full justify-start text-xs",
1155
1285
  disabled: busy,
1156
1286
  onClick: () => {
@@ -1160,10 +1290,25 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1160
1290
  setCreating(true);
1161
1291
  }
1162
1292
  },
1163
- children: search.trim() !== "" ? `Create \u201C${search.trim()}\u201D` : `New ${targetWord}`
1293
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "block w-full truncate text-left", children: search.trim() !== "" ? `Create \u201C${search.trim()}\u201D` : `New ${targetWord}` })
1164
1294
  }
1165
1295
  ),
1166
- createRefusal ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "p-2 text-xs text-destructive", children: createRefusal }) : null
1296
+ createRefusal ? (
1297
+ // The typed title is still in the search box: the notice owns the
1298
+ // two doors out (lane REFUSAL-SWEEP).
1299
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1300
+ RefusalNotice,
1301
+ {
1302
+ error: createRefusal,
1303
+ className: "mt-1 max-w-72",
1304
+ onKeepEditing: () => setCreateRefusal(null),
1305
+ onDiscard: () => {
1306
+ setCreateRefusal(null);
1307
+ setSearch("");
1308
+ }
1309
+ }
1310
+ )
1311
+ ) : null
1167
1312
  ] }) : null
1168
1313
  ] })
1169
1314
  ] })
@@ -1171,12 +1316,12 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1171
1316
  }
1172
1317
 
1173
1318
  // src/editors.tsx
1174
- var import_jsx_runtime4 = require("react/jsx-runtime");
1319
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1175
1320
  function FieldLabel({ field, htmlFor, children }) {
1176
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-baseline gap-1.5", children: [
1177
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system3.Label, { htmlFor, className: "text-xs font-medium", children: fieldName(field) }),
1178
- field.required ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-xs text-destructive", children: "required" }) : null,
1179
- field.unit ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-xs text-muted-foreground", children: field.unit }) : null,
1321
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-baseline gap-1.5", children: [
1322
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system4.Label, { htmlFor, className: "text-xs font-medium", children: fieldName(field) }),
1323
+ field.required ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-xs text-destructive", children: "required" }) : null,
1324
+ field.unit ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-xs text-muted-foreground", children: field.unit }) : null,
1180
1325
  children
1181
1326
  ] });
1182
1327
  }
@@ -1187,18 +1332,18 @@ function FieldControl({ field, value, onChange, disabled, id }) {
1187
1332
  case "formula":
1188
1333
  case "lookup":
1189
1334
  case "rollup":
1190
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { id: inputId, className: "rounded border border-dashed px-2 py-1.5 text-xs text-muted-foreground", children: [
1335
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { id: inputId, className: "rounded border border-dashed px-2 py-1.5 text-xs text-muted-foreground", children: [
1191
1336
  value === null || value === void 0 ? "\u2014" : String(value),
1192
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "ml-2 italic", children: [
1337
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "ml-2 italic", children: [
1193
1338
  fieldName(field),
1194
1339
  " is worked out by the system, so it cannot be typed in"
1195
1340
  ] })
1196
1341
  ] });
1197
1342
  case "json":
1198
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(JsonControl, { field, value, onChange, disabled, id: inputId });
1343
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(JsonControl, { field, value, onChange, disabled, id: inputId });
1199
1344
  case "long_text":
1200
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1201
- import_design_system3.BasicTextarea,
1345
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1346
+ import_design_system4.BasicTextarea,
1202
1347
  {
1203
1348
  id: inputId,
1204
1349
  rows: 3,
@@ -1210,8 +1355,8 @@ function FieldControl({ field, value, onChange, disabled, id }) {
1210
1355
  case "number":
1211
1356
  case "currency":
1212
1357
  case "percent":
1213
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1214
- import_design_system3.BasicInput,
1358
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1359
+ import_design_system4.BasicInput,
1215
1360
  {
1216
1361
  id: inputId,
1217
1362
  type: "number",
@@ -1223,8 +1368,8 @@ function FieldControl({ field, value, onChange, disabled, id }) {
1223
1368
  }
1224
1369
  );
1225
1370
  case "datetime":
1226
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1227
- import_design_system3.BasicInput,
1371
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1372
+ import_design_system4.BasicInput,
1228
1373
  {
1229
1374
  id: inputId,
1230
1375
  type: field.format === "datetime" ? "datetime-local" : "date",
@@ -1236,8 +1381,8 @@ function FieldControl({ field, value, onChange, disabled, id }) {
1236
1381
  case "email":
1237
1382
  case "phone":
1238
1383
  case "url":
1239
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1240
- import_design_system3.BasicInput,
1384
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1385
+ import_design_system4.BasicInput,
1241
1386
  {
1242
1387
  id: inputId,
1243
1388
  type: kind === "email" ? "email" : kind === "phone" ? "tel" : "url",
@@ -1247,19 +1392,19 @@ function FieldControl({ field, value, onChange, disabled, id }) {
1247
1392
  }
1248
1393
  );
1249
1394
  case "checkbox":
1250
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CheckboxControl, { field, value, onChange, disabled, id: inputId });
1395
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CheckboxControl, { field, value, onChange, disabled, id: inputId });
1251
1396
  case "select":
1252
1397
  case "multi_select":
1253
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(OptionControl, { field, value, onChange, disabled, id: inputId });
1398
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(OptionControl, { field, value, onChange, disabled, id: inputId });
1254
1399
  case "member":
1255
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PersonPicker, { field, value, onChange, disabled, id: inputId });
1400
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PersonPicker, { field, value, onChange, disabled, id: inputId });
1256
1401
  case "relation":
1257
1402
  case "attachment":
1258
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RelationPicker, { field, value, onChange, disabled, id: inputId });
1403
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RelationPicker, { field, value, onChange, disabled, id: inputId });
1259
1404
  default:
1260
1405
  if (typeof value === "boolean" || field.config?.["kind"] === "boolean") {
1261
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1262
- import_design_system3.Checkbox,
1406
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1407
+ import_design_system4.Checkbox,
1263
1408
  {
1264
1409
  id: inputId,
1265
1410
  checked: value === true,
@@ -1268,8 +1413,8 @@ function FieldControl({ field, value, onChange, disabled, id }) {
1268
1413
  }
1269
1414
  );
1270
1415
  }
1271
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1272
- import_design_system3.BasicInput,
1416
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1417
+ import_design_system4.BasicInput,
1273
1418
  {
1274
1419
  id: inputId,
1275
1420
  value: value === null || value === void 0 ? "" : String(value),
@@ -1281,9 +1426,9 @@ function FieldControl({ field, value, onChange, disabled, id }) {
1281
1426
  }
1282
1427
  function CheckboxControl({ field, value, onChange, disabled, id }) {
1283
1428
  const unanswered = value === null || value === void 0;
1284
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-center gap-2", children: [
1285
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1286
- import_design_system3.Checkbox,
1429
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-2", children: [
1430
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1431
+ import_design_system4.Checkbox,
1287
1432
  {
1288
1433
  id,
1289
1434
  checked: value === true,
@@ -1292,10 +1437,10 @@ function CheckboxControl({ field, value, onChange, disabled, id }) {
1292
1437
  onCheckedChange: (next) => onChange(next === true)
1293
1438
  }
1294
1439
  ),
1295
- unanswered ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-xs italic text-muted-foreground", children: "not answered yet" }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1296
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-xs text-muted-foreground", children: value === true ? "Yes" : "No" }),
1297
- field.required ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1298
- import_design_system3.Button,
1440
+ unanswered ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-xs italic text-muted-foreground", children: "not answered yet" }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
1441
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-xs text-muted-foreground", children: value === true ? "Yes" : "No" }),
1442
+ field.required ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1443
+ import_design_system4.Button,
1299
1444
  {
1300
1445
  size: "sm",
1301
1446
  variant: "ghost",
@@ -1312,9 +1457,9 @@ function JsonControl({ field, value, onChange, disabled, id }) {
1312
1457
  const initial = value === null || value === void 0 ? "" : typeof value === "string" ? value : JSON.stringify(value, null, 2);
1313
1458
  const [text, setText] = (0, import_react7.useState)(initial);
1314
1459
  const [broken, setBroken] = (0, import_react7.useState)(null);
1315
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-col gap-1", children: [
1316
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1317
- import_design_system3.BasicTextarea,
1460
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex flex-col gap-1", children: [
1461
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1462
+ import_design_system4.BasicTextarea,
1318
1463
  {
1319
1464
  id,
1320
1465
  rows: 4,
@@ -1333,38 +1478,54 @@ function JsonControl({ field, value, onChange, disabled, id }) {
1333
1478
  onChange(JSON.parse(next));
1334
1479
  setBroken(null);
1335
1480
  } catch (thrown) {
1336
- setBroken(thrown instanceof Error ? thrown.message : String(thrown));
1481
+ setBroken(
1482
+ refusalFromThrown(
1483
+ thrown,
1484
+ `${fieldName(field)} is not readable as structured data yet.`,
1485
+ "Fix the text so it reads as structured data, or discard it to put back what was there.",
1486
+ "invalid_argument"
1487
+ )
1488
+ );
1337
1489
  }
1338
1490
  }
1339
1491
  }
1340
1492
  ),
1341
- broken ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "text-xs text-destructive", children: [
1342
- fieldName(field),
1343
- " is not readable as structured data yet: ",
1344
- broken
1345
- ] }) : null
1493
+ broken ? (
1494
+ // The typed text is still in the box: the notice owns the two doors out.
1495
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1496
+ RefusalNotice,
1497
+ {
1498
+ error: broken,
1499
+ onKeepEditing: () => id ? document.getElementById(id)?.focus() : void 0,
1500
+ onDiscard: () => {
1501
+ setText(initial);
1502
+ setBroken(null);
1503
+ }
1504
+ }
1505
+ )
1506
+ ) : null
1346
1507
  ] });
1347
1508
  }
1348
1509
  function OptionControl({ field, value, onChange, disabled, id }) {
1349
1510
  const client = (0, import_react8.useOptionalRecordsClient)();
1350
1511
  const [options, setOptions] = (0, import_react7.useState)(null);
1351
- const [refusal, setRefusal] = (0, import_react7.useState)(null);
1512
+ const [refusal2, setRefusal] = (0, import_react7.useState)(null);
1352
1513
  (0, import_react7.useEffect)(() => {
1353
1514
  if (!client) return;
1354
1515
  let cancelled = false;
1355
1516
  void client.fieldOptions({ field_id: field.id }).then((result) => {
1356
1517
  if (cancelled) return;
1357
1518
  if (result.ok) setOptions(result.data ?? []);
1358
- else setRefusal(refusalLineForAPerson(result.error));
1519
+ else setRefusal(result.error);
1359
1520
  });
1360
1521
  return () => {
1361
1522
  cancelled = true;
1362
1523
  };
1363
1524
  }, [client, field.id]);
1364
1525
  if (!client) {
1365
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-col gap-1", children: [
1366
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1367
- import_design_system3.BasicInput,
1526
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex flex-col gap-1", children: [
1527
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1528
+ import_design_system4.BasicInput,
1368
1529
  {
1369
1530
  id,
1370
1531
  value: value === null || value === void 0 ? "" : String(value),
@@ -1372,20 +1533,21 @@ function OptionControl({ field, value, onChange, disabled, id }) {
1372
1533
  onChange: (e) => onChange(e.target.value === "" ? null : e.target.value)
1373
1534
  }
1374
1535
  ),
1375
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-xs text-muted-foreground", children: "This is normally picked from a list. A page answered without an account cannot read that list, so type your answer and whoever receives it will match it up." })
1536
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-xs text-muted-foreground", children: "This is normally picked from a list. A page answered without an account cannot read that list, so type your answer and whoever receives it will match it up." })
1376
1537
  ] });
1377
1538
  }
1378
- if (refusal) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { id, className: "text-xs text-destructive", children: refusal });
1379
- if (!options) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { id, className: "text-xs text-muted-foreground", children: "Reading this field's choices\u2026" });
1539
+ if (refusal2)
1540
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { id, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RefusalLine, { error: refusal2 }) });
1541
+ if (!options) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { id, className: "text-xs text-muted-foreground", children: "Reading this field's choices\u2026" });
1380
1542
  if (options.length === 0) {
1381
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { id, className: "text-xs text-muted-foreground", children: field.options_table_id ? `${fieldName(field)} takes its choices from a Table that has no records yet \u2014 add the choices there and they appear here.` : `${fieldName(field)} does not name the Table its choices come from yet, so there is nothing to choose. A table admin sets it in the field editor.` });
1543
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { id, className: "text-xs text-muted-foreground", children: field.options_table_id ? `${fieldName(field)} takes its choices from a Table that has no records yet \u2014 add the choices there and they appear here.` : `${fieldName(field)} does not name the Table its choices come from yet, so there is nothing to choose. A table admin sets it in the field editor.` });
1382
1544
  }
1383
1545
  const titleOf = (option) => recordName(option.data, null, "Unnamed choice");
1384
1546
  if (field.multi) {
1385
1547
  const chosen = options.filter((option) => (0, import_records4.choiceValuesOf)(value).some((v) => (0, import_records4.isTheChosen)(option, v)));
1386
1548
  const picked = new Set(chosen.map((option) => (0, import_records4.optionKey)(option)));
1387
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { id, className: "flex flex-wrap gap-1", children: options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1388
- import_design_system3.Button,
1549
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { id, className: "flex flex-wrap gap-1", children: options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1550
+ import_design_system4.Button,
1389
1551
  {
1390
1552
  type: "button",
1391
1553
  size: "sm",
@@ -1403,15 +1565,15 @@ function OptionControl({ field, value, onChange, disabled, id }) {
1403
1565
  )) });
1404
1566
  }
1405
1567
  const current = options.find((option) => (0, import_records4.isTheChosen)(option, value));
1406
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1407
- import_design_system3.Select,
1568
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1569
+ import_design_system4.Select,
1408
1570
  {
1409
1571
  value: current ? (0, import_records4.optionKey)(current) : "",
1410
1572
  disabled: disabled ?? false,
1411
1573
  onValueChange: (next) => onChange(next === "" ? null : next),
1412
1574
  children: [
1413
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system3.SelectTrigger, { id, size: "sm", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system3.SelectValue, { placeholder: "Choose" }) }),
1414
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system3.SelectContent, { children: options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system3.SelectItem, { value: (0, import_records4.optionKey)(option), children: titleOf(option) }, option.id)) })
1575
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system4.SelectTrigger, { id, size: "sm", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system4.SelectValue, { placeholder: "Choose" }) }),
1576
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system4.SelectContent, { children: options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system4.SelectItem, { value: (0, import_records4.optionKey)(option), children: titleOf(option) }, option.id)) })
1415
1577
  ]
1416
1578
  }
1417
1579
  );
@@ -1421,14 +1583,21 @@ function RecordChip({
1421
1583
  onRemove,
1422
1584
  className
1423
1585
  }) {
1424
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system3.Badge, { variant: "secondary", className: (0, import_design_system3.cn)("gap-1 text-[11px]", className), children: [
1425
- title,
1426
- onRemove ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", onClick: onRemove, "aria-label": `Remove ${title}`, className: "opacity-60 hover:opacity-100", children: "\xD7" }) : null
1427
- ] });
1586
+ return (
1587
+ // A chip carries a RECORD'S OWN TITLE, which nobody here chose the length of —
1588
+ // "Marisol Okonkwo — 418 Calle Puerto Vallarta, Camarillo" is an ordinary customer
1589
+ // name. So it caps at its container and ends in an ellipsis with the whole name in
1590
+ // its tooltip, rather than being cut mid-word by whatever box it landed in
1591
+ // (lane FIX-14, 2026-09-22).
1592
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_design_system4.Badge, { variant: "secondary", className: (0, import_design_system4.cn)("max-w-full gap-1 text-[11px]", className), title, children: [
1593
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "min-w-0 truncate", children: title }),
1594
+ onRemove ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", onClick: onRemove, "aria-label": `Remove ${title}`, className: "shrink-0 opacity-60 hover:opacity-100", children: "\xD7" }) : null
1595
+ ] })
1596
+ );
1428
1597
  }
1429
1598
 
1430
1599
  // src/PersonPicker.tsx
1431
- var import_jsx_runtime5 = require("react/jsx-runtime");
1600
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1432
1601
  function personFromRow(row) {
1433
1602
  const data = row.document ?? {};
1434
1603
  const userId = data["user_id"];
@@ -1481,7 +1650,7 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1481
1650
  ]);
1482
1651
  setMembers(roster);
1483
1652
  if (records && !records.ok) {
1484
- setError(refusalLineForAPerson(records.error));
1653
+ setError(records.error);
1485
1654
  return;
1486
1655
  }
1487
1656
  setPeople(records ? records.data.rows.map(personFromRow).filter((p) => p.userId !== null) : []);
@@ -1502,10 +1671,10 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1502
1671
  };
1503
1672
  }, [client, personTable, picked.length, people.length]);
1504
1673
  if (!membersPort || !client) {
1505
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: (0, import_design_system4.cn)("text-xs text-muted-foreground", className), children: NO_MEMBERS_REASON });
1674
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: (0, import_design_system5.cn)("text-xs text-muted-foreground", className), children: NO_MEMBERS_REASON });
1506
1675
  }
1507
1676
  if (!personTable) {
1508
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { className: (0, import_design_system4.cn)("text-xs text-muted-foreground", className), children: [
1677
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: (0, import_design_system5.cn)("text-xs text-muted-foreground", className), children: [
1509
1678
  fieldName(field),
1510
1679
  " is a person field, but it does not say which Table the people are records of yet, so there is nothing to pick from. A table admin sets its target in the field editor."
1511
1680
  ] });
@@ -1523,7 +1692,7 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1523
1692
  const resolved = await personRecordForMember(client, personTable, people, member);
1524
1693
  setBusy(false);
1525
1694
  if (!resolved.ok) {
1526
- setError(refusalLineForAPerson(resolved.error));
1695
+ setError(resolved.error);
1527
1696
  return;
1528
1697
  }
1529
1698
  if (!people.some((p) => p.id === resolved.data)) {
@@ -1549,8 +1718,8 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1549
1718
  const pickedUserIds = new Set(
1550
1719
  picked.map((recordId) => people.find((p) => p.id === recordId)?.userId).filter(Boolean)
1551
1720
  );
1552
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: (0, import_design_system4.cn)("flex flex-wrap items-center gap-1", className), children: [
1553
- picked.map((recordId) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1721
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: (0, import_design_system5.cn)("flex flex-wrap items-center gap-1", className), children: [
1722
+ picked.map((recordId) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1554
1723
  RecordChip,
1555
1724
  {
1556
1725
  title: nameOfRecord(recordId),
@@ -1558,9 +1727,9 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1558
1727
  },
1559
1728
  recordId
1560
1729
  )),
1561
- disabled ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_design_system4.Popover, { open, onOpenChange: setOpen, children: [
1562
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system4.PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1563
- import_design_system4.Button,
1730
+ disabled ? null : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system5.Popover, { open, onOpenChange: setOpen, children: [
1731
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system5.PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1732
+ import_design_system5.Button,
1564
1733
  {
1565
1734
  id,
1566
1735
  type: "button",
@@ -1570,9 +1739,9 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1570
1739
  children: picked.length === 0 ? "Pick a person" : "Change"
1571
1740
  }
1572
1741
  ) }),
1573
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_design_system4.PopoverContent, { className: "w-64 p-1", align: "start", children: [
1574
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1575
- import_design_system4.BasicInput,
1742
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system5.PopoverContent, { sizing: "content", className: "p-1", align: "start", children: [
1743
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1744
+ import_design_system5.BasicInput,
1576
1745
  {
1577
1746
  value: search,
1578
1747
  onChange: (e) => setSearch(e.target.value),
@@ -1580,29 +1749,29 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1580
1749
  className: "mb-1 h-7 text-xs"
1581
1750
  }
1582
1751
  ),
1583
- error ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "p-2 text-xs text-destructive", children: error }) : null,
1584
- members === null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "p-2 text-xs text-muted-foreground", children: "Reading the members\u2026" }) : null,
1585
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_design_system4.ScrollArea, { className: "max-h-56", children: [
1586
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { children: visible.map((member) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1752
+ error ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RefusalLine, { error, className: "p-2" }) : null,
1753
+ members === null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "p-2 text-xs text-muted-foreground", children: "Reading the members\u2026" }) : null,
1754
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system5.ScrollArea, { className: "max-h-56", children: [
1755
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ul", { children: visible.map((member) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1587
1756
  "button",
1588
1757
  {
1589
1758
  type: "button",
1590
1759
  disabled: busy,
1591
1760
  onClick: () => void pick(member),
1592
- className: (0, import_design_system4.cn)(
1761
+ className: (0, import_design_system5.cn)(
1593
1762
  "w-full truncate rounded px-2 py-1 text-left text-xs hover:bg-muted",
1594
1763
  pickedUserIds.has(member.userId) ? "bg-accent text-accent-foreground" : ""
1595
1764
  ),
1596
1765
  children: [
1597
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: memberName(member) }),
1598
- member.email && member.email !== memberName(member) ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "ml-1.5 opacity-60", children: [
1766
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: memberName(member) }),
1767
+ member.email && member.email !== memberName(member) ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "ml-1.5 opacity-60", children: [
1599
1768
  "\xB7 ",
1600
1769
  member.email
1601
1770
  ] }) : null
1602
1771
  ]
1603
1772
  }
1604
1773
  ) }, member.userId)) }),
1605
- members !== null && visible.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "p-2 text-xs text-muted-foreground", children: members.length === 0 ? "This organization has no other members yet, so there is nobody to assign." : "Nobody in this organization matches." }) : null
1774
+ members !== null && visible.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "p-2 text-xs text-muted-foreground", children: members.length === 0 ? "This organization has no other members yet, so there is nobody to assign." : "Nobody in this organization matches." }) : null
1606
1775
  ] })
1607
1776
  ] })
1608
1777
  ] })
@@ -1615,55 +1784,6 @@ var import_core = require("@ai-matrx/records/core");
1615
1784
  var import_react16 = require("@ai-matrx/records/react");
1616
1785
  var import_design_system7 = require("@ai-matrx/design-system");
1617
1786
 
1618
- // src/Refusal.tsx
1619
- var import_design_system5 = require("@ai-matrx/design-system");
1620
- var import_jsx_runtime6 = require("react/jsx-runtime");
1621
- function hintIsMachineIdentity(hint) {
1622
- return isMachineIdentity(hint);
1623
- }
1624
- function hintForAPerson(hint) {
1625
- const said = (hint ?? "").trim();
1626
- if (said === "") return null;
1627
- return isMachineIdentity(said) ? null : said;
1628
- }
1629
- function RefusalNotice({
1630
- error,
1631
- className,
1632
- actions
1633
- }) {
1634
- const plain = refusalForAPerson(error);
1635
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1636
- import_design_system5.Alert,
1637
- {
1638
- variant: "destructive",
1639
- className: (0, import_design_system5.cn)("text-xs", className),
1640
- ...plain.forEngineers ? { title: plain.forEngineers } : {},
1641
- children: [
1642
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system5.AlertTitle, { className: "text-xs font-medium", children: plain.title }),
1643
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system5.AlertDescription, { className: "space-y-1 text-xs", children: [
1644
- plain.sentence.replace(/\.$/, "") === plain.title ? null : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: plain.sentence }),
1645
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "opacity-80", children: plain.remedy }),
1646
- plain.forEngineers ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1647
- "p",
1648
- {
1649
- className: "sr-only",
1650
- "aria-hidden": "true",
1651
- "data-for-engineers": "",
1652
- ...error.sqlstate ? { "data-sqlstate": error.sqlstate } : {},
1653
- children: plain.forEngineers
1654
- }
1655
- ) : null,
1656
- actions
1657
- ] })
1658
- ]
1659
- }
1660
- );
1661
- }
1662
- function RefusalLine({ error, className }) {
1663
- const plain = refusalForAPerson(error);
1664
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: (0, import_design_system5.cn)("text-xs text-destructive", className), title: plain.forEngineers || void 0, children: refusalLineForAPerson(error) });
1665
- }
1666
-
1667
1787
  // src/whoChangedSource.tsx
1668
1788
  var import_react11 = require("react");
1669
1789
  var import_react12 = require("@ai-matrx/records/react");
@@ -2499,7 +2619,7 @@ function GridCell({
2499
2619
  );
2500
2620
  const value = editing ? editing.valueFor(row, field) : row.document?.[field.key];
2501
2621
  const state = editing ? editing.stateOf(row.id, field.key) : null;
2502
- const refusal = editing ? editing.refusalOf(row.id, field.key) : null;
2622
+ const refusal2 = editing ? editing.refusalOf(row.id, field.key) : null;
2503
2623
  const editable = Boolean(editing) && canWrite && fieldIsEditable(field);
2504
2624
  if (open && editing) {
2505
2625
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
@@ -2528,8 +2648,8 @@ function GridCell({
2528
2648
  }
2529
2649
  ),
2530
2650
  answered ? null : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-xs text-muted-foreground", title: "nobody has answered this yet", children: "\u2014" }),
2531
- refusal ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "flex items-center gap-1 rounded border border-destructive/40 px-1", children: [
2532
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(RefusalLine, { error: refusal.error }),
2651
+ refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "flex items-center gap-1 rounded border border-destructive/40 px-1", children: [
2652
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(RefusalLine, { error: refusal2.error }),
2533
2653
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_design_system7.Button, { size: "sm", variant: "outline", onClick: () => editing?.retry(row.id, field.key), children: "Try again" })
2534
2654
  ] }) : null
2535
2655
  ] });
@@ -2604,11 +2724,11 @@ function GridCell({
2604
2724
  ),
2605
2725
  onAskWhoChanged ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(WhoBadge, { field, row, onAskWhoChanged }) : null,
2606
2726
  state === "saving" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "px-1 text-[10px] text-muted-foreground", children: "Saving\u2026" }) : null,
2607
- refusal ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "flex flex-col gap-0.5 rounded border border-destructive/40 p-1", children: [
2608
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(RefusalLine, { error: refusal.error }),
2609
- refusal.conflict ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "text-[11px]", children: [
2727
+ refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "flex flex-col gap-0.5 rounded border border-destructive/40 p-1", children: [
2728
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(RefusalLine, { error: refusal2.error }),
2729
+ refusal2.conflict ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "text-[11px]", children: [
2610
2730
  "Theirs: ",
2611
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("strong", { children: formatTheirs(refusal.conflict, field.key) })
2731
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("strong", { children: formatTheirs(refusal2.conflict, field.key) })
2612
2732
  ] }) : null,
2613
2733
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "flex gap-1", children: [
2614
2734
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_design_system7.Button, { size: "sm", variant: "outline", onClick: () => editing?.retry(row.id, field.key), children: "Try again" }),
@@ -3307,7 +3427,20 @@ function EnrichPanel({ tableId, fieldId, className }) {
3307
3427
  " in one go."
3308
3428
  ] }) : null
3309
3429
  ] }) : null,
3310
- outcome ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: (0, import_design_system8.cn)("text-xs", outcome.ok ? "text-muted-foreground" : "text-destructive"), children: outcome.message }) : null,
3430
+ outcome ? outcome.ok ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "text-xs text-muted-foreground", children: outcome.message }) : (
3431
+ // A run that did not happen is a refusal, with a heading and a remedy,
3432
+ // never the host's words in red (lane REFUSAL-SWEEP).
3433
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3434
+ RefusalLine,
3435
+ {
3436
+ error: refusal(
3437
+ "internal",
3438
+ outcome.message,
3439
+ "Nothing was filled in. Try the run again in a moment."
3440
+ )
3441
+ }
3442
+ )
3443
+ ) : null,
3311
3444
  field ? null : null
3312
3445
  ] }, row.field_id);
3313
3446
  }) });
@@ -3352,15 +3485,15 @@ function PastePreview({
3352
3485
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PlanTable, { plan }),
3353
3486
  plan.refusals.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { "data-matrx-paste-refusals": true, className: "flex flex-col gap-1", children: [
3354
3487
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-xs font-medium", children: plan.refusals.length === 1 ? "One cell will be left alone:" : `${plan.refusals.length} cells will be left alone:` }),
3355
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("ul", { className: "flex flex-col gap-1", children: plan.refusals.slice(0, 12).map((refusal, index) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("li", { className: "text-xs text-muted-foreground", children: [
3488
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("ul", { className: "flex flex-col gap-1", children: plan.refusals.slice(0, 12).map((refusal2, index) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("li", { className: "text-xs text-muted-foreground", children: [
3356
3489
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "font-medium", children: [
3357
3490
  "Row ",
3358
- refusal.fromLine,
3491
+ refusal2.fromLine,
3359
3492
  ", ",
3360
- refusal.column
3493
+ refusal2.column
3361
3494
  ] }),
3362
3495
  " \u2014 ",
3363
- refusal.why
3496
+ refusal2.why
3364
3497
  ] }, index)) }),
3365
3498
  plan.refusals.length > 12 ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
3366
3499
  plan.refusals.length - 12,
@@ -3388,7 +3521,13 @@ function PastePreview({
3388
3521
  setAdding(group.fieldKey);
3389
3522
  void onAddOptions(group.fieldKey, group.words).catch(
3390
3523
  (err) => setAddFailed(
3391
- `Those options were not added: ${err instanceof Error ? err.message : String(err)}`
3524
+ // The error's own text is an engineer's; the person
3525
+ // reads what did not happen and what to do.
3526
+ asRefusal(
3527
+ err,
3528
+ "Those options were not added, so the pasted words still do not match a choice.",
3529
+ "Try adding them again, or add them from the column's settings."
3530
+ )
3392
3531
  )
3393
3532
  ).finally(() => setAdding(null));
3394
3533
  },
@@ -3396,7 +3535,7 @@ function PastePreview({
3396
3535
  }
3397
3536
  )
3398
3537
  ] }, group.fieldKey)),
3399
- addFailed ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-xs text-destructive", children: addFailed }) : null
3538
+ addFailed ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(RefusalLine, { error: addFailed }) : null
3400
3539
  ] }) : null,
3401
3540
  plan.pastRightEdge > 0 ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
3402
3541
  plan.pastRightEdge,
@@ -3576,19 +3715,19 @@ function planPastedBlock(args) {
3576
3715
  accepted[key] = judged.value;
3577
3716
  }
3578
3717
  const predicted = (0, import_core2.predictValueRefusals)(args.fields, accepted);
3579
- for (const refusal of predicted) {
3580
- const key = refusal.field_key;
3718
+ for (const refusal2 of predicted) {
3719
+ const key = refusal2.field_key;
3581
3720
  if (key === void 0) continue;
3582
3721
  const cell = built.find((b) => b.key === key && b.refusal === null);
3583
3722
  if (!cell) continue;
3584
- cell.refusal = refusal.message;
3723
+ cell.refusal = refusal2.message;
3585
3724
  cell.value = null;
3586
3725
  delete accepted[key];
3587
3726
  refusals.push({
3588
3727
  fromLine,
3589
3728
  column: fieldName(byKey.get(key) ?? { key }),
3590
3729
  raw: cell.raw,
3591
- why: refusal.message
3730
+ why: refusal2.message
3592
3731
  });
3593
3732
  }
3594
3733
  cells += Object.keys(accepted).length;
@@ -3691,6 +3830,9 @@ function Grid({
3691
3830
  [client, host, refreshEnrich]
3692
3831
  );
3693
3832
  const [pastePlan, setPastePlan] = (0, import_react20.useState)(null);
3833
+ const [deleting, setDeleting] = (0, import_react20.useState)(null);
3834
+ const [deleteBusy, setDeleteBusy] = (0, import_react20.useState)(false);
3835
+ const [archived, setArchived] = (0, import_react20.useState)(null);
3694
3836
  const choiceOptions = (0, import_react20.useRef)(/* @__PURE__ */ new Map());
3695
3837
  const loadChoiceOptions = (0, import_react20.useCallback)(
3696
3838
  async (fieldList) => {
@@ -3978,6 +4120,21 @@ function Grid({
3978
4120
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "ml-auto flex items-center gap-1", children: toolbarActions })
3979
4121
  ] }),
3980
4122
  records.error ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(RefusalNotice, { error: records.error }) : null,
4123
+ archived ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
4124
+ "p",
4125
+ {
4126
+ "data-records-archived-notice": "",
4127
+ role: "status",
4128
+ className: "flex items-center gap-2 rounded border border-border bg-muted/40 px-2 py-1 text-xs",
4129
+ children: [
4130
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { children: [
4131
+ archived,
4132
+ " is archived. Nothing was destroyed \u2014 it is in this table\u2019s Archived items, where Restore brings it back."
4133
+ ] }),
4134
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_design_system10.Button, { size: "sm", variant: "ghost", className: "ml-auto h-6", onClick: () => setArchived(null), children: "Dismiss" })
4135
+ ]
4136
+ }
4137
+ ) : null,
3981
4138
  editing.rowError ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3982
4139
  RefusalNotice,
3983
4140
  {
@@ -4094,11 +4251,13 @@ function Grid({
4094
4251
  }
4095
4252
  ) : null,
4096
4253
  canWrite && mayWriteRow(row.id) ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4097
- DeleteRecordButton,
4254
+ import_design_system10.Button,
4098
4255
  {
4099
- row,
4100
- retentionDays: table.data?.retention_days ?? null,
4101
- onDelete: () => editing.remove(row.id)
4256
+ size: "sm",
4257
+ variant: "ghost",
4258
+ title: "Delete this record",
4259
+ onClick: () => setDeleting(row),
4260
+ children: "Delete"
4102
4261
  }
4103
4262
  ) : null
4104
4263
  ] })
@@ -4141,7 +4300,32 @@ function Grid({
4141
4300
  onPage: setPage
4142
4301
  }
4143
4302
  )
4144
- ] })
4303
+ ] }),
4304
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4305
+ import_design_system10.ConfirmDialog,
4306
+ {
4307
+ open: deleting !== null,
4308
+ onOpenChange: (next) => {
4309
+ if (!next && !deleteBusy) setDeleting(null);
4310
+ },
4311
+ variant: "destructive",
4312
+ title: `Delete ${deleting ? recordNameIn(table.data, deleting.document ?? {}) : "this record"}?`,
4313
+ description: table.data?.retention_days ? `It leaves the table and waits in Archived items. Nothing is destroyed: Restore brings it back, for ${table.data.retention_days} days.` : "It leaves the table and waits in Archived items. Nothing is destroyed: Restore brings it back.",
4314
+ confirmLabel: "Delete",
4315
+ cancelLabel: "Keep",
4316
+ busy: deleteBusy,
4317
+ onConfirm: async () => {
4318
+ const row = deleting;
4319
+ if (!row) return;
4320
+ const name = recordNameIn(table.data, row.document ?? {});
4321
+ setDeleteBusy(true);
4322
+ const failed = await editing.remove(row.id);
4323
+ setDeleteBusy(false);
4324
+ setDeleting(null);
4325
+ if (!failed) setArchived(name);
4326
+ }
4327
+ }
4328
+ )
4145
4329
  ] }) });
4146
4330
  }
4147
4331
  function columnLabel(fields, columnId) {
@@ -4161,42 +4345,6 @@ function AddFieldButton({ onClick }) {
4161
4345
  }
4162
4346
  );
4163
4347
  }
4164
- function DeleteRecordButton({
4165
- row,
4166
- retentionDays,
4167
- onDelete
4168
- }) {
4169
- const [asking, setAsking] = (0, import_react20.useState)(false);
4170
- if (!asking) {
4171
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4172
- import_design_system10.Button,
4173
- {
4174
- size: "sm",
4175
- variant: "ghost",
4176
- title: "Delete this record",
4177
- onClick: () => setAsking(true),
4178
- children: "Delete"
4179
- }
4180
- );
4181
- }
4182
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "flex items-center gap-1 text-[11px]", children: [
4183
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-muted-foreground", children: retentionDays ? `Removes it from the table. Restorable for ${retentionDays} days.` : "Removes it from the table. It can be restored from the record's history." }),
4184
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4185
- import_design_system10.Button,
4186
- {
4187
- size: "sm",
4188
- variant: "destructive",
4189
- onClick: () => {
4190
- setAsking(false);
4191
- void onDelete();
4192
- },
4193
- children: "Delete"
4194
- }
4195
- ),
4196
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_design_system10.Button, { size: "sm", variant: "ghost", onClick: () => setAsking(false), children: "Keep" }),
4197
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "sr-only", children: row.id })
4198
- ] });
4199
- }
4200
4348
  function Pager({
4201
4349
  page,
4202
4350
  pageSize,
@@ -4343,18 +4491,24 @@ function ExportMenu({ tableId, rows, label, className }) {
4343
4491
  size: "sm",
4344
4492
  variant: "outline",
4345
4493
  onClick: () => {
4494
+ const failed = (thrown) => setFailure(
4495
+ refusalFromThrown(
4496
+ thrown,
4497
+ "The spreadsheet could not be written.",
4498
+ "The CSV beside this button carries the same rows."
4499
+ )
4500
+ );
4501
+ setFailure(null);
4346
4502
  try {
4347
- void (0, import_core3.exportXlsx)(fields.data ?? [], exported(), name).then(
4503
+ (0, import_core3.exportXlsx)(fields.data ?? [], exported(), name).then(
4348
4504
  (bytes) => download(
4349
4505
  `${name}.xlsx`,
4350
4506
  "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
4351
4507
  bytes
4352
4508
  )
4353
- );
4509
+ ).catch(failed);
4354
4510
  } catch (thrown) {
4355
- setFailure(
4356
- `The spreadsheet could not be written (${thrown instanceof Error ? thrown.message : String(thrown)}). The CSV beside this button carries the same rows.`
4357
- );
4511
+ failed(thrown);
4358
4512
  }
4359
4513
  },
4360
4514
  children: "XLSX"
@@ -4370,7 +4524,7 @@ function ExportMenu({ tableId, rows, label, className }) {
4370
4524
  human: () => (0, import_core3.exportCsv)(fields.data ?? [], exported())
4371
4525
  }
4372
4526
  ),
4373
- failure ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-xs text-destructive", children: failure }) : null
4527
+ failure ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(RefusalLine, { error: failure }) : null
4374
4528
  ] });
4375
4529
  }
4376
4530
 
@@ -4471,8 +4625,47 @@ function FilePicker({
4471
4625
  // src/ImportWizard.tsx
4472
4626
  var import_react26 = require("react");
4473
4627
  var import_react27 = require("@ai-matrx/records/react");
4474
- var import_core4 = require("@ai-matrx/records/core");
4628
+ var import_core5 = require("@ai-matrx/records/core");
4475
4629
  var import_design_system13 = require("@ai-matrx/design-system");
4630
+
4631
+ // src/importRuleCheck.ts
4632
+ var import_core4 = require("@ai-matrx/records/core");
4633
+ function checkImportRules(fields, rows, mapping) {
4634
+ const byKey = new Map(fields.map((f) => [f.key, f]));
4635
+ const verdicts = [];
4636
+ for (const [header, key] of Object.entries(mapping)) {
4637
+ const field = byKey.get(key);
4638
+ if (!field) continue;
4639
+ let refused = 0;
4640
+ let judged = 0;
4641
+ let first = null;
4642
+ rows.forEach((row, i) => {
4643
+ const raw = String(row[header] ?? "").trim();
4644
+ if (raw === "") return;
4645
+ judged += 1;
4646
+ const meant = valueFromPastedText(field, raw);
4647
+ let why = null;
4648
+ if ("refusal" in meant) why = meant.refusal;
4649
+ else {
4650
+ const predicted = (0, import_core4.predictValueRefusals)([field], { [key]: meant.value }).filter(
4651
+ (p) => p.field_key === void 0 || p.field_key === key
4652
+ );
4653
+ if (predicted.length > 0) why = predicted[0].message;
4654
+ }
4655
+ if (why !== null) {
4656
+ refused += 1;
4657
+ if (!first) first = { why, line: i + 1, raw };
4658
+ }
4659
+ });
4660
+ if (refused > 0 && first) {
4661
+ const f = first;
4662
+ verdicts.push({ header, field, refused, judged, firstWhy: f.why, firstLine: f.line, firstRaw: f.raw });
4663
+ }
4664
+ }
4665
+ return verdicts;
4666
+ }
4667
+
4668
+ // src/ImportWizard.tsx
4476
4669
  var import_jsx_runtime16 = require("react/jsx-runtime");
4477
4670
  var BATCH = 250;
4478
4671
  var SAMPLES = 40;
@@ -4495,7 +4688,7 @@ async function sha256(bytes) {
4495
4688
  function typeWord(t) {
4496
4689
  return fieldTypeChoice(t)?.label ?? (t === "relation" ? "Points at another table" : t);
4497
4690
  }
4498
- function ImportWizard({ tableId, onDone, className }) {
4691
+ function ImportWizard({ tableId, onDone, onWrote, className }) {
4499
4692
  const client = (0, import_react27.useRecordsClient)();
4500
4693
  const table = (0, import_react27.useTable)(tableId);
4501
4694
  const fields = (0, import_react27.useFields)(tableId);
@@ -4521,6 +4714,50 @@ function ImportWizard({ tableId, onDone, className }) {
4521
4714
  const [columnsAdded, setColumnsAdded] = (0, import_react26.useState)(null);
4522
4715
  const [show, setShow] = (0, import_react26.useState)("all");
4523
4716
  const [ledger, setLedger] = (0, import_react26.useState)(null);
4717
+ const [runs, setRuns] = (0, import_react26.useState)(null);
4718
+ const [openRun, setOpenRun] = (0, import_react26.useState)(null);
4719
+ const [runRows, setRunRows] = (0, import_react26.useState)(null);
4720
+ const [runProblem, setRunProblem] = (0, import_react26.useState)(null);
4721
+ const [runsProblem, setRunsProblem] = (0, import_react26.useState)(null);
4722
+ const loadRuns = (0, import_react26.useCallback)(async () => {
4723
+ const answered = await client.imports({ table_id: tableId, limit: 10 });
4724
+ if (answered.ok) {
4725
+ setRuns(answered.data);
4726
+ setRunsProblem(null);
4727
+ } else setRunsProblem(answered.error);
4728
+ }, [client, tableId]);
4729
+ (0, import_react26.useEffect)(() => {
4730
+ void loadRuns();
4731
+ }, [loadRuns]);
4732
+ const openRefusals = (0, import_react26.useCallback)(
4733
+ async (importId) => {
4734
+ setRunProblem(null);
4735
+ if (openRun === importId) {
4736
+ setOpenRun(null);
4737
+ setRunRows(null);
4738
+ return;
4739
+ }
4740
+ setOpenRun(importId);
4741
+ setRunRows(null);
4742
+ const answered = await client.importReport({
4743
+ import_id: importId,
4744
+ state: "refused",
4745
+ limit: 200
4746
+ });
4747
+ if (!answered.ok) {
4748
+ setRunProblem(answered.error);
4749
+ return;
4750
+ }
4751
+ setRunRows({
4752
+ import_id: answered.data.import_id,
4753
+ rows: answered.data.rows,
4754
+ kept: answered.data.kept,
4755
+ total: answered.data.total,
4756
+ note: answered.data.note
4757
+ });
4758
+ },
4759
+ [client, openRun]
4760
+ );
4524
4761
  const declared = fields.data ?? [];
4525
4762
  const take = (0, import_react26.useCallback)(
4526
4763
  async (file) => {
@@ -4535,7 +4772,7 @@ function ImportWizard({ tableId, onDone, className }) {
4535
4772
  try {
4536
4773
  const xlsx = file.name.toLowerCase().endsWith(".xlsx");
4537
4774
  const bytes = await file.arrayBuffer();
4538
- const result = xlsx ? await (0, import_core4.importXlsx)(bytes) : (0, import_core4.importCsv)(new TextDecoder().decode(bytes));
4775
+ const result = xlsx ? await (0, import_core5.importXlsx)(bytes) : (0, import_core5.importCsv)(new TextDecoder().decode(bytes));
4539
4776
  setFormat(xlsx ? "xlsx" : "csv");
4540
4777
  setFileName(file.name);
4541
4778
  setFileBytes(file.size);
@@ -4549,7 +4786,7 @@ function ImportWizard({ tableId, onDone, className }) {
4549
4786
  }));
4550
4787
  const answered = await client.importPlan({ table_id: tableId, columns });
4551
4788
  if (!answered.ok) {
4552
- setProblem(answered.error.message);
4789
+ setProblem(answered.error);
4553
4790
  setPhase("waiting");
4554
4791
  return;
4555
4792
  }
@@ -4566,7 +4803,14 @@ function ImportWizard({ tableId, onDone, className }) {
4566
4803
  setMapping(next);
4567
4804
  setPhase("ready");
4568
4805
  } catch (thrown) {
4569
- setProblem(`That file could not be read: ${thrown instanceof Error ? thrown.message : String(thrown)}`);
4806
+ setProblem(
4807
+ refusalFromThrown(
4808
+ thrown,
4809
+ "That file could not be read, so nothing was imported.",
4810
+ "Save it again as CSV or Excel and pick it again.",
4811
+ "invalid_argument"
4812
+ )
4813
+ );
4570
4814
  setPhase("waiting");
4571
4815
  }
4572
4816
  },
@@ -4583,7 +4827,7 @@ function ImportWizard({ tableId, onDone, className }) {
4583
4827
  setPhase("declaring");
4584
4828
  const made = await client.importDeclareColumns({ table_id: tableId, rows, mapping });
4585
4829
  if (!made.ok) {
4586
- setProblem(made.error.message);
4830
+ setProblem(made.error);
4587
4831
  setPhase("ready");
4588
4832
  return;
4589
4833
  }
@@ -4602,7 +4846,7 @@ function ImportWizard({ tableId, onDone, className }) {
4602
4846
  policy: { on_duplicate: onDuplicate, unmapped }
4603
4847
  });
4604
4848
  if (!opened.ok) {
4605
- setProblem(opened.error.message);
4849
+ setProblem(opened.error);
4606
4850
  setPhase("ready");
4607
4851
  return;
4608
4852
  }
@@ -4621,7 +4865,7 @@ function ImportWizard({ tableId, onDone, className }) {
4621
4865
  const batch = rows.slice(at, at + take2);
4622
4866
  const answered = await client.importRows({ import_id: importId, rows: batch, mapping });
4623
4867
  if (!answered.ok) {
4624
- setProblem(answered.error.message);
4868
+ setProblem(answered.error);
4625
4869
  setOutcomes([...collected]);
4626
4870
  setLedger({ ...told, final: false });
4627
4871
  setPhase("ready");
@@ -4640,7 +4884,7 @@ function ImportWizard({ tableId, onDone, className }) {
4640
4884
  }
4641
4885
  const finished = await client.importFinish({ import_id: importId, unmapped });
4642
4886
  if (!finished.ok) {
4643
- setProblem(finished.error.message);
4887
+ setProblem(finished.error);
4644
4888
  setPhase("done");
4645
4889
  return;
4646
4890
  }
@@ -4654,8 +4898,9 @@ function ImportWizard({ tableId, onDone, className }) {
4654
4898
  final: true
4655
4899
  });
4656
4900
  setPhase("done");
4657
- onDone?.(finished.data.rows_written);
4658
- }, [client, dedupeKey, fileBytes, fileHash, fileName, format, mapping, onDuplicate, onDone, plan, rows, tableId, unmapped]);
4901
+ onWrote?.(finished.data.rows_written);
4902
+ void loadRuns();
4903
+ }, [client, dedupeKey, fileBytes, fileHash, fileName, format, loadRuns, mapping, onDuplicate, onWrote, plan, rows, tableId, unmapped]);
4659
4904
  const counts = (0, import_react26.useMemo)(() => {
4660
4905
  let landed = 0;
4661
4906
  let duplicate = 0;
@@ -4677,6 +4922,11 @@ function ImportWizard({ tableId, onDone, className }) {
4677
4922
  const titleMissing = Boolean(titleKey) && titleMappedTo === null;
4678
4923
  const titleFieldLabel = (titleKey ? plan?.fields.find((f) => f.key === titleKey)?.label : null) || (titleKey ? declared.find((f) => f.key === titleKey)?.label : null) || "the name column";
4679
4924
  const busy = phase === "reading" || phase === "planning" || phase === "declaring" || phase === "writing";
4925
+ const ruleVerdicts = (0, import_react26.useMemo)(() => checkImportRules(declared, rows, mapping), [declared, rows, mapping]);
4926
+ const ruleVerdictOf = (0, import_react26.useMemo)(() => new Map(ruleVerdicts.map((v) => [v.header, v])), [ruleVerdicts]);
4927
+ const refusedByRules = ruleVerdicts.reduce((n, v) => n + v.refused, 0);
4928
+ const [goAheadAnyway, setGoAheadAnyway] = (0, import_react26.useState)(false);
4929
+ (0, import_react26.useEffect)(() => setGoAheadAnyway(false), [ruleVerdicts]);
4680
4930
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: (0, import_design_system13.cn)("flex min-w-0 flex-col gap-2 text-xs", className), children: [
4681
4931
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center gap-2", children: [
4682
4932
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "font-medium", children: "Import" }),
@@ -4704,7 +4954,7 @@ function ImportWizard({ tableId, onDone, className }) {
4704
4954
  } : {}
4705
4955
  }
4706
4956
  ) : null,
4707
- problem ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-destructive", children: problem }) : null,
4957
+ problem ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(RefusalNotice, { error: problem }) : null,
4708
4958
  phase === "reading" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-muted-foreground", children: "Reading the file\u2026" }) : null,
4709
4959
  phase === "planning" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-muted-foreground", children: "Working out what each column is\u2026" }) : null,
4710
4960
  plan && parsed ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
@@ -4801,7 +5051,28 @@ function ImportWizard({ tableId, onDone, className }) {
4801
5051
  column.unit ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "ml-1 opacity-60", children: column.unit }) : null,
4802
5052
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "mt-0.5 text-muted-foreground", children: column.why }),
4803
5053
  column.ambiguous ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-destructive", children: "Check a date you know before you run this." }) : null,
4804
- column.collides_with ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-destructive", children: "Two columns in this file would make the same column." }) : null
5054
+ column.collides_with ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-destructive", children: "Two columns in this file would make the same column." }) : null,
5055
+ ruleVerdictOf.get(column.header) ? (() => {
5056
+ const v = ruleVerdictOf.get(column.header);
5057
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { "data-import-rule-refusal": column.header, className: "mt-1", children: [
5058
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5059
+ RefusalLine,
5060
+ {
5061
+ error: refusal(
5062
+ "refused_by_rule",
5063
+ `${v.refused} of ${v.judged} row${v.judged === 1 ? "" : "s"} would be refused by ${fieldName(v.field)}'s rules. ${v.firstWhy}`,
5064
+ "Send this column somewhere else, fix those rows in your file, or import the rest and they are listed afterwards."
5065
+ )
5066
+ }
5067
+ ),
5068
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("p", { className: "text-muted-foreground", "data-import-rule-sample": "", children: [
5069
+ "Line ",
5070
+ v.firstLine,
5071
+ " in your file: ",
5072
+ v.firstRaw
5073
+ ] })
5074
+ ] });
5075
+ })() : null
4805
5076
  ] }),
4806
5077
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "truncate pt-1.5 text-muted-foreground", children: (column.samples ?? []).slice(0, 3).map(String).join(" \xB7 ") })
4807
5078
  ]
@@ -4865,12 +5136,34 @@ function ImportWizard({ tableId, onDone, className }) {
4865
5136
  )
4866
5137
  ] })
4867
5138
  ] }),
5139
+ ruleVerdicts.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5140
+ RefusalNotice,
5141
+ {
5142
+ error: refusal(
5143
+ "refused_by_rule",
5144
+ `${refusedByRules} value${refusedByRules === 1 ? "" : "s"} in this file would be refused by ${ruleVerdicts.length === 1 ? `the rules of ${fieldName(ruleVerdicts[0].field)}` : `the rules of ${ruleVerdicts.length} columns`} before anything is written. Nothing has been written yet.`,
5145
+ "Change where those columns go, or fix the file and pick it again. To go ahead, the rest is written and every refused row is listed afterwards."
5146
+ ),
5147
+ actions: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("label", { className: "mt-1 flex items-center gap-1.5", "data-import-go-ahead": "", children: [
5148
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5149
+ "input",
5150
+ {
5151
+ type: "checkbox",
5152
+ checked: goAheadAnyway,
5153
+ disabled: busy,
5154
+ onChange: (e) => setGoAheadAnyway(e.target.checked)
5155
+ }
5156
+ ),
5157
+ "Import the rest anyway"
5158
+ ] })
5159
+ }
5160
+ ) : null,
4868
5161
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center gap-2", children: [
4869
5162
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4870
5163
  import_design_system13.Button,
4871
5164
  {
4872
5165
  size: "sm",
4873
- disabled: busy || !rights.write || rows.length === 0 || titleMissing,
5166
+ disabled: busy || !rights.write || rows.length === 0 || titleMissing || ruleVerdicts.length > 0 && !goAheadAnyway,
4874
5167
  onClick: () => void run(),
4875
5168
  children: phase === "declaring" ? "Adding the columns\u2026" : phase === "writing" ? `Writing\u2026 ${progress} of ${rows.length}` : `Import ${rows.length} row${rows.length === 1 ? "" : "s"}`
4876
5169
  }
@@ -4925,14 +5218,95 @@ function ImportWizard({ tableId, onDone, className }) {
4925
5218
  ] }) }),
4926
5219
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("tbody", { children: interesting.slice(0, 200).map((o) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("tr", { className: "border-t align-top", children: [
4927
5220
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("td", { className: "px-2 py-1", children: o.row }),
4928
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("td", { className: (0, import_design_system13.cn)("px-2 py-1", o.outcome === "refused" && "text-destructive"), children: o.outcome === "refused" ? o.reason : o.outcome === "duplicate" ? o.reason : "written" }),
5221
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("td", { className: "px-2 py-1", children: o.outcome === "refused" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5222
+ RefusalLine,
5223
+ {
5224
+ error: refusal("refused_by_rule", o.reason, "Fix this line in your file and import it again.")
5225
+ }
5226
+ ) : o.outcome === "duplicate" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-muted-foreground", children: o.reason }) : "written" }),
4929
5227
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("td", { className: "max-w-96 px-2 py-1 text-muted-foreground", children: o.outcome === "refused" ? Object.entries(o.source).map(([k, v]) => `${k}: ${String(v ?? "")}`).join(" \xB7 ") : "" })
4930
5228
  ] }, `${o.outcome}-${o.row}`)) })
4931
5229
  ] }) }) : null,
4932
5230
  proposals.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("ul", { className: "flex flex-col gap-0.5", children: proposals.map((p) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("li", { className: "text-muted-foreground", children: [
4933
5231
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "font-medium text-foreground", children: p.column }),
4934
5232
  p.state === "waiting" ? " is waiting in the approvals inbox." : p.state === "accepted" ? " was added as a column." : p.state === "ignored" ? " was left out." : p.state === "refused" ? ` could not be added: ${p.reason ?? ""}` : ` \u2014 ${p.state}`
4935
- ] }, p.column)) }) : null
5233
+ ] }, p.column)) }) : null,
5234
+ phase === "done" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
5235
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system13.Button, { size: "sm", onClick: () => onDone?.(ledger?.written ?? 0), children: "Done" }),
5236
+ (ledger?.refused ?? 0) > 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: "text-muted-foreground", children: [
5237
+ "The ",
5238
+ ledger?.refused,
5239
+ " row",
5240
+ ledger?.refused === 1 ? "" : "s",
5241
+ " that ",
5242
+ ledger?.refused === 1 ? "was" : "were",
5243
+ " refused",
5244
+ " ",
5245
+ "are kept with this import \u2014 open them again from the list of imports below, with the store's reason and the line from your file on each."
5246
+ ] }) : null
5247
+ ] }) : null
5248
+ ] }) : null,
5249
+ runsProblem ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(RefusalNotice, { error: runsProblem }) : null,
5250
+ runs && runs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
5251
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system13.Separator, {}),
5252
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "font-medium", children: "Imports into this table" }),
5253
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("ul", { className: "flex flex-col gap-1", children: runs.map((r) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("li", { className: "flex flex-col gap-1", children: [
5254
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
5255
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "font-medium", children: r.source_name ?? "a file" }),
5256
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: "text-muted-foreground", children: [
5257
+ new Date(r.opened_at).toLocaleString(),
5258
+ " \xB7 ",
5259
+ r.rows_written,
5260
+ " landed \xB7",
5261
+ " ",
5262
+ r.rows_duplicate,
5263
+ " already here \xB7 ",
5264
+ r.rows_refused,
5265
+ " refused, of ",
5266
+ r.rows_seen,
5267
+ " row",
5268
+ r.rows_seen === 1 ? "" : "s",
5269
+ " offered"
5270
+ ] }),
5271
+ r.rows_refused > 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5272
+ "button",
5273
+ {
5274
+ type: "button",
5275
+ className: "underline underline-offset-2",
5276
+ "aria-expanded": openRun === r.import_id,
5277
+ onClick: () => void openRefusals(r.import_id),
5278
+ children: openRun === r.import_id ? "hide the rows it refused" : `see the ${r.rows_refused} row${r.rows_refused === 1 ? "" : "s"} it refused`
5279
+ }
5280
+ ) : null
5281
+ ] }),
5282
+ openRun === r.import_id ? runProblem ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(RefusalNotice, { error: runProblem }) : runRows ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
5283
+ runRows.note ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-muted-foreground", children: runRows.note }) : null,
5284
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "max-h-64 overflow-auto rounded border", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("table", { className: "w-full", children: [
5285
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("thead", { className: "sticky top-0 bg-muted", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("tr", { children: [
5286
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("th", { className: "px-2 py-1 text-left font-medium", children: "Line in your file" }),
5287
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("th", { className: "px-2 py-1 text-left font-medium", children: "What happened" }),
5288
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("th", { className: "px-2 py-1 text-left font-medium", children: "The row in your file" })
5289
+ ] }) }),
5290
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("tbody", { children: runRows.rows.map((row, i) => {
5291
+ const source = row.source ?? {};
5292
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("tr", { className: "border-t align-top", children: [
5293
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("td", { className: "px-2 py-1", children: String(row.row ?? "") }),
5294
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("td", { className: "px-2 py-1", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5295
+ RefusalLine,
5296
+ {
5297
+ error: refusal(
5298
+ "refused_by_rule",
5299
+ String(row.reason ?? "This row was refused."),
5300
+ "Fix this line in your file and import it again."
5301
+ )
5302
+ }
5303
+ ) }),
5304
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("td", { className: "max-w-96 px-2 py-1 text-muted-foreground", children: Object.entries(source).map(([k, v]) => `${k}: ${String(v ?? "")}`).join(" \xB7 ") })
5305
+ ] }, `${r.import_id}-${String(row.row ?? i)}`);
5306
+ }) })
5307
+ ] }) })
5308
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-muted-foreground", children: "Reading the rows this import refused\u2026" }) : null
5309
+ ] }, r.import_id)) })
4936
5310
  ] }) : null,
4937
5311
  table.error ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(RefusalNotice, { error: table.error }) : null
4938
5312
  ] });
@@ -5145,7 +5519,9 @@ function keyFor(label) {
5145
5519
  if (token === "") return "";
5146
5520
  return /^[a-z]/.test(token) ? token : `f_${token}`;
5147
5521
  }
5148
- var COLUMN_DID_NOT_LAND = "The store accepted this column and the table does not have it yet. Nothing was lost \u2014 your record values are untouched \u2014 but the column is not there, so this panel is staying open. Press Save again; if it keeps happening, tell us, because the store and the table disagree and that is ours to fix.";
5522
+ var COLUMN_DID_NOT_LAND_SENTENCE = "The store accepted this column and the table does not have it yet. Nothing was lost \u2014 your record values are untouched \u2014 but the column is not there, so this panel is staying open.";
5523
+ var COLUMN_DID_NOT_LAND_REMEDY = "Press Save again; if it keeps happening, tell us, because the store and the table disagree and that is ours to fix.";
5524
+ var COLUMN_DID_NOT_LAND = `${COLUMN_DID_NOT_LAND_SENTENCE} ${COLUMN_DID_NOT_LAND_REMEDY}`;
5149
5525
  function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }) {
5150
5526
  const table = (0, import_react31.useTable)(tableId);
5151
5527
  const fields = (0, import_react31.useFields)(tableId);
@@ -5445,14 +5821,20 @@ function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }
5445
5821
  ] }, `${rule.kind}-${index}`))
5446
5822
  ] }),
5447
5823
  mutation.error ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(RefusalNotice, { error: mutation.error }) : null,
5448
- missing ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "text-[11px] text-destructive", children: missing }) : null,
5449
- notLanded ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "text-[11px] text-destructive", children: COLUMN_DID_NOT_LAND }) : null,
5824
+ missing ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { id: "field-editor-missing", className: "text-[11px] text-muted-foreground", "data-field-editor-missing": "", children: missing }) : null,
5825
+ notLanded ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5826
+ RefusalNotice,
5827
+ {
5828
+ error: refusal("internal", COLUMN_DID_NOT_LAND_SENTENCE, COLUMN_DID_NOT_LAND_REMEDY)
5829
+ }
5830
+ ) : null,
5450
5831
  /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
5451
5832
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5452
5833
  import_design_system15.Button,
5453
5834
  {
5454
5835
  size: "sm",
5455
5836
  disabled: mutation.saving || missing !== null,
5837
+ ...missing ? { "aria-describedby": "field-editor-missing" } : {},
5456
5838
  onClick: () => void save(),
5457
5839
  children: mutation.saving ? "Saving\u2026" : field ? EDIT_FIELD_SAVE_LABEL : ADD_FIELD_SAVE_LABEL
5458
5840
  }
@@ -6665,7 +7047,7 @@ var import_design_system22 = require("@ai-matrx/design-system");
6665
7047
  // src/RecordForm.tsx
6666
7048
  var import_react41 = require("react");
6667
7049
  var import_react42 = require("@ai-matrx/records/react");
6668
- var import_core5 = require("@ai-matrx/records/core");
7050
+ var import_core6 = require("@ai-matrx/records/core");
6669
7051
  var import_design_system20 = require("@ai-matrx/design-system");
6670
7052
 
6671
7053
  // src/systemTable.ts
@@ -6870,7 +7252,7 @@ function RecordForm({
6870
7252
  return out;
6871
7253
  }, [draft]);
6872
7254
  const predicted = (0, import_react41.useMemo)(
6873
- () => fields.data ? (0, import_core5.predictWriteRefusals)({ fields: fields.data, document: document2, ...recordType ? { recordType } : {} }) : [],
7255
+ () => fields.data ? (0, import_core6.predictWriteRefusals)({ fields: fields.data, document: document2, ...recordType ? { recordType } : {} }) : [],
6874
7256
  [fields.data, document2, recordType]
6875
7257
  );
6876
7258
  if (fields.error) return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(RefusalNotice, { error: fields.error, className });
@@ -6918,7 +7300,13 @@ function RecordForm({
6918
7300
  },
6919
7301
  field.id
6920
7302
  )) }),
6921
- touched ? predicted.filter((p) => !p.field_key).map((p) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { className: "text-xs text-destructive", children: p.message }, p.message)) : null,
7303
+ touched ? predicted.filter((p) => !p.field_key).map((p) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7304
+ RefusalNotice,
7305
+ {
7306
+ error: { code: "refused_by_rule", message: p.message }
7307
+ },
7308
+ p.message
7309
+ )) : null,
6922
7310
  mutation.error ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6923
7311
  RefusalNotice,
6924
7312
  {
@@ -6973,7 +7361,14 @@ function FieldRow({
6973
7361
  return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex min-w-0 flex-col gap-1", children: [
6974
7362
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(FieldLabel, { field, htmlFor: id, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ProvenanceBadge, { document: document2, fieldKey: field.key }) }),
6975
7363
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(FieldControl, { field, value, onChange, id }),
6976
- problems.map((problem) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { className: "text-xs text-destructive", children: problem.message }, problem.message))
7364
+ problems.map((problem) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7365
+ RefusalNotice,
7366
+ {
7367
+ error: { code: "refused_by_rule", message: problem.message },
7368
+ className: "border-0 p-0"
7369
+ },
7370
+ problem.message
7371
+ ))
6977
7372
  ] });
6978
7373
  }
6979
7374
  function asWords(value) {
@@ -6999,12 +7394,16 @@ function ShareControl({
6999
7394
  subjectId,
7000
7395
  name,
7001
7396
  may,
7397
+ initiallyOpen = false,
7002
7398
  size = "sm",
7003
7399
  variant = "ghost",
7004
7400
  className
7005
7401
  }) {
7006
7402
  const host = useRecordsUi();
7007
- const [open, setOpen] = (0, import_react43.useState)(false);
7403
+ const [open, setOpen] = (0, import_react43.useState)(initiallyOpen);
7404
+ (0, import_react43.useEffect)(() => {
7405
+ if (initiallyOpen) setOpen(true);
7406
+ }, [initiallyOpen]);
7008
7407
  const asked = useRecordRights(may === void 0 ? subjectId : null);
7009
7408
  const mayShare = may ?? asked.share;
7010
7409
  if (!host.share) return null;
@@ -7392,8 +7791,8 @@ function PipelineBoard({
7392
7791
  const asked = await client.pipelineTransitionRefusal({ record_id: recordId, to });
7393
7792
  if (!alive.current) return;
7394
7793
  if (!asked.ok) {
7395
- setError(asked.error);
7396
- setPending({ kind: "none" });
7794
+ setPending({ kind: "clash", recordId, to, error: asked.error });
7795
+ onMoved?.();
7397
7796
  return;
7398
7797
  }
7399
7798
  const verdict = asked.data;
@@ -7406,8 +7805,8 @@ function PipelineBoard({
7406
7805
  const moved = await client.pipelineMove({ record_id: recordId, to });
7407
7806
  if (!alive.current) return;
7408
7807
  if (!moved.ok) {
7409
- setPending({ kind: "none" });
7410
- setError(moved.error);
7808
+ setPending({ kind: "clash", recordId, to, error: moved.error });
7809
+ onMoved?.();
7411
7810
  return;
7412
7811
  }
7413
7812
  const result = moved.data;
@@ -7428,21 +7827,22 @@ function PipelineBoard({
7428
7827
  const moved = await client.pipelineMove({ record_id: recordId, to });
7429
7828
  if (!alive.current) return;
7430
7829
  if (!moved.ok) {
7431
- setPending({ kind: "none" });
7432
- setError(moved.error);
7830
+ setPending({ kind: "clash", recordId, to, error: moved.error });
7831
+ onMoved?.();
7433
7832
  return;
7434
7833
  }
7435
7834
  setPending({ kind: "filed", recordId, to, result: moved.data });
7436
7835
  void reload();
7437
7836
  },
7438
- [client, reload]
7837
+ [client, onMoved, reload]
7439
7838
  );
7440
7839
  const moveWith = (0, import_react47.useCallback)(
7441
7840
  async (recordId, to, also) => {
7442
7841
  const moved = await client.pipelineMove({ record_id: recordId, to, also });
7443
7842
  if (!alive.current) return;
7444
7843
  if (!moved.ok) {
7445
- setError(moved.error);
7844
+ setPending({ kind: "clash", recordId, to, error: moved.error });
7845
+ onMoved?.();
7446
7846
  return;
7447
7847
  }
7448
7848
  setPending({ kind: "none" });
@@ -7570,11 +7970,33 @@ function Held({
7570
7970
  if (pending.kind === "asking") {
7571
7971
  return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { className: "px-2 py-1 text-xs text-muted-foreground", children: "Asking\u2026" });
7572
7972
  }
7973
+ if (pending.kind === "clash") {
7974
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
7975
+ RefusalNotice,
7976
+ {
7977
+ className: "mt-1",
7978
+ error: pending.error,
7979
+ actions: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "mt-1", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_design_system23.Button, { size: "sm", variant: "ghost", className: "h-6", onClick: onCancel, children: "Close" }) })
7980
+ }
7981
+ );
7982
+ }
7573
7983
  if (pending.kind === "refused") {
7574
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "mt-1 rounded border border-destructive/50 bg-destructive/5 p-2 text-xs", children: [
7575
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { children: pending.verdict.why }),
7576
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_design_system23.Button, { size: "sm", variant: "ghost", className: "mt-1 h-6", onClick: onCancel, children: "Close" })
7577
- ] });
7984
+ return (
7985
+ // THE STAGE GATE SAID NO: the one refusal surface, heading and remedy both,
7986
+ // never its sentence alone in a red box (lane REFUSAL-SWEEP).
7987
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
7988
+ RefusalNotice,
7989
+ {
7990
+ className: "mt-1",
7991
+ error: refusal(
7992
+ "refused_by_rule",
7993
+ pending.verdict.why,
7994
+ "The card stays where it was. Fill in what this stage asks for, then move it again."
7995
+ ),
7996
+ actions: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_design_system23.Button, { size: "sm", variant: "ghost", className: "mt-1 h-6", onClick: onCancel, children: "Close" })
7997
+ }
7998
+ )
7999
+ );
7578
8000
  }
7579
8001
  if (pending.kind === "approval") {
7580
8002
  return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "mt-1 flex flex-col gap-1 rounded border p-2 text-xs", children: [
@@ -8138,7 +8560,7 @@ function ArchivedView({
8138
8560
  const [loading, setLoading] = (0, import_react51.useState)(true);
8139
8561
  const [error, setError] = (0, import_react51.useState)(null);
8140
8562
  const [restoring, setRestoring] = (0, import_react51.useState)(null);
8141
- const [refusal, setRefusal] = (0, import_react51.useState)(null);
8563
+ const [refusal2, setRefusal] = (0, import_react51.useState)(null);
8142
8564
  (0, import_react51.useEffect)(() => {
8143
8565
  if (laneFromHost) setLane(laneFromHost);
8144
8566
  }, [laneFromHost]);
@@ -8215,7 +8637,7 @@ function ArchivedView({
8215
8637
  /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col", children: [
8216
8638
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "truncate text-sm", children: recordName(row.document, titleKey) }),
8217
8639
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "truncate text-xs text-muted-foreground", "data-testid": "archived-who", children: (0, import_records7.archivedByLine)(row) }),
8218
- refusal && refusal.recordId === row.id ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(RefusalNotice, { error: refusal.error, className: "mt-2" }) : null
8640
+ refusal2 && refusal2.recordId === row.id ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(RefusalNotice, { error: refusal2.error, className: "mt-2" }) : null
8219
8641
  ] }),
8220
8642
  mayRestore ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
8221
8643
  import_design_system25.Button,
@@ -8289,7 +8711,7 @@ function ArchivedPortals({
8289
8711
  const [confirming, setConfirming] = (0, import_react53.useState)(null);
8290
8712
  const [typed, setTyped] = (0, import_react53.useState)("");
8291
8713
  const [restoring, setRestoring] = (0, import_react53.useState)(null);
8292
- const [refusal, setRefusal] = (0, import_react53.useState)(null);
8714
+ const [refusal2, setRefusal] = (0, import_react53.useState)(null);
8293
8715
  const [said, setSaid] = (0, import_react53.useState)(null);
8294
8716
  const read = (0, import_react53.useCallback)(async () => {
8295
8717
  const answered = await client.listPortals({ archived: "archived" });
@@ -8405,7 +8827,7 @@ function ArchivedPortals({
8405
8827
  )
8406
8828
  ] })
8407
8829
  ] }) : null,
8408
- refusal && refusal.portalId === portal.portal_id ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(RefusalNotice, { error: refusal.error }) : null
8830
+ refusal2 && refusal2.portalId === portal.portal_id ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(RefusalNotice, { error: refusal2.error }) : null
8409
8831
  ]
8410
8832
  },
8411
8833
  portal.portal_id
@@ -8970,7 +9392,7 @@ function MyChecklistSteps({ className }) {
8970
9392
  const client = (0, import_react62.useRecordsClient)();
8971
9393
  const { steps, loading, error, refresh } = useMyChecklistSteps();
8972
9394
  const [busy, setBusy] = (0, import_react61.useState)(null);
8973
- const [refusal, setRefusal] = (0, import_react61.useState)(null);
9395
+ const [refusal2, setRefusal] = (0, import_react61.useState)(null);
8974
9396
  const complete = (0, import_react61.useCallback)(
8975
9397
  async (step2, evidence) => {
8976
9398
  setBusy(step2.step_id);
@@ -8993,7 +9415,7 @@ function MyChecklistSteps({ className }) {
8993
9415
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "tabular-nums", children: steps.length })
8994
9416
  ] }),
8995
9417
  error ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(RefusalNotice, { error }) : null,
8996
- refusal ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(RefusalNotice, { error: refusal }) : null,
9418
+ refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(RefusalNotice, { error: refusal2 }) : null,
8997
9419
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("ol", { className: "flex flex-col gap-1.5", children: steps.map((step2) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
8998
9420
  StepRow,
8999
9421
  {
@@ -9170,7 +9592,7 @@ function ChecklistTemplateEditor({
9170
9592
  const client = (0, import_react62.useRecordsClient)();
9171
9593
  const [name, setName] = (0, import_react61.useState)("");
9172
9594
  const [rows, setRows] = (0, import_react61.useState)([{ ...EMPTY_ROW }]);
9173
- const [refusal, setRefusal] = (0, import_react61.useState)(null);
9595
+ const [refusal2, setRefusal] = (0, import_react61.useState)(null);
9174
9596
  const [error, setError] = (0, import_react61.useState)(null);
9175
9597
  const [busy, setBusy] = (0, import_react61.useState)(false);
9176
9598
  const [loading, setLoading] = (0, import_react61.useState)(Boolean(templateId));
@@ -9338,9 +9760,9 @@ function ChecklistTemplateEditor({
9338
9760
  ] }, index)) }),
9339
9761
  /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-center gap-1.5", children: [
9340
9762
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_design_system29.Button, { size: "sm", variant: "outline", onClick: () => setRows((held) => [...held, { ...EMPTY_ROW }]), children: "Add a step" }),
9341
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_design_system29.Button, { size: "sm", disabled: busy || refusal !== null || spec.steps.length === 0, onClick: () => void save(), children: busy ? "\u2026" : "Save" })
9763
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_design_system29.Button, { size: "sm", disabled: busy || refusal2 !== null || spec.steps.length === 0, onClick: () => void save(), children: busy ? "\u2026" : "Save" })
9342
9764
  ] }),
9343
- refusal ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "text-xs text-muted-foreground", "data-testid": "checklist-editor-refusal", children: refusal }) : null
9765
+ refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "text-xs text-muted-foreground", "data-testid": "checklist-editor-refusal", children: refusal2 }) : null
9344
9766
  ] });
9345
9767
  }
9346
9768
  function toStepSpec(row, index) {
@@ -10633,6 +11055,7 @@ function BuildOrAsk({
10633
11055
 
10634
11056
  // src/PortalsPanel.tsx
10635
11057
  var import_jsx_runtime39 = require("react/jsx-runtime");
11058
+ var PORTAL_NOT_HERE_LINE = "The link named a portal this table is not part of \u2014 it may have been archived, or it shows other tables. The portals this table is in are listed here.";
10636
11059
  function revokeConsequence(person, portalTitle) {
10637
11060
  const who = person.client ? `${person.email} (${person.client})` : person.email;
10638
11061
  return `${who} will no longer be able to sign in to \u201C${portalTitle}\u201D, and the records that named their client will stop reaching them. Nothing is deleted \u2014 you can invite them again.`;
@@ -10662,13 +11085,16 @@ function stateWords(person) {
10662
11085
  };
10663
11086
  }
10664
11087
  var PORTAL_SUGGESTION = "Let each of my customers sign in and see their own jobs and invoices, and nothing else.";
10665
- function PortalsPanel({ tableId, className }) {
11088
+ function PortalsPanel({ tableId, activePortalId, className }) {
10666
11089
  const client = (0, import_react74.useRecordsClient)();
10667
11090
  const host = useRecordsUi();
10668
11091
  const [portals, setPortals] = (0, import_react73.useState)(null);
10669
11092
  const [exposures, setExposures] = (0, import_react73.useState)([]);
10670
11093
  const [listError, setListError] = (0, import_react73.useState)(null);
10671
- const [openId, setOpenId] = (0, import_react73.useState)(null);
11094
+ const [openId, setOpenId] = (0, import_react73.useState)(activePortalId ?? null);
11095
+ (0, import_react73.useEffect)(() => {
11096
+ if (activePortalId) setOpenId(activePortalId);
11097
+ }, [activePortalId]);
10672
11098
  const [building, setBuilding] = (0, import_react73.useState)(false);
10673
11099
  const [adding, setAdding] = (0, import_react73.useState)(null);
10674
11100
  const [archiveToken, setArchiveToken] = (0, import_react73.useState)(0);
@@ -10765,59 +11191,73 @@ function PortalsPanel({ tableId, className }) {
10765
11191
  portal.portal_id
10766
11192
  )) })
10767
11193
  ] }) : null,
11194
+ activePortalId && !shown.some((p) => p.portal_id === activePortalId) ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("p", { className: "rounded-md border border-amber-600/40 bg-amber-500/5 px-3 py-2 text-xs leading-relaxed text-amber-700 dark:border-amber-400/40 dark:text-amber-300", children: PORTAL_NOT_HERE_LINE }) : null,
10768
11195
  /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("ul", { className: "flex flex-col gap-2", children: shown.map((portal) => {
10769
11196
  const url = `${origin}${(0, import_records9.portalPath)(portal.slug)}`;
10770
11197
  const open = openId === portal.portal_id;
10771
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("li", { className: "rounded-md border border-border bg-card p-2.5", children: [
10772
- /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex items-center gap-2", children: [
10773
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: portal.title }),
10774
- portal.is_active ? null : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_design_system36.Badge, { variant: "outline", children: "closed" })
10775
- ] }),
10776
- /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("p", { className: "mt-0.5 text-xs text-muted-foreground", children: [
10777
- "Clients come from ",
10778
- portal.client_table,
10779
- portal.tables === 1 ? ", and see 1 Table" : `, and see ${portal.tables} Tables`,
10780
- "."
10781
- ] }),
10782
- /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("p", { className: "mt-1.5 text-xs", children: [
10783
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "font-medium", children: portal.signed_in }),
10784
- " signed in",
10785
- " \xB7 ",
10786
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "font-medium", children: portal.invited - portal.signed_in }),
10787
- " invited and waiting"
10788
- ] }),
10789
- /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "mt-2 flex flex-wrap items-center gap-1.5", children: [
10790
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(CopyLink, { url }),
10791
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
10792
- import_design_system36.Button,
10793
- {
10794
- size: "sm",
10795
- variant: "ghost",
10796
- onClick: () => setOpenId(open ? null : portal.portal_id),
10797
- children: open ? "Close" : "Open it"
10798
- }
11198
+ const linked = portal.portal_id === activePortalId;
11199
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
11200
+ "li",
11201
+ {
11202
+ "data-linked": linked ? "true" : void 0,
11203
+ ref: linked ? (el) => el?.scrollIntoView({ block: "nearest" }) : void 0,
11204
+ className: (0, import_design_system36.cn)(
11205
+ "rounded-md border border-border bg-card p-2.5",
11206
+ linked && "border-primary ring-1 ring-primary"
10799
11207
  ),
10800
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
10801
- ArchivePortalControl,
10802
- {
10803
- portal,
10804
- onArchived: () => {
10805
- setArchiveToken((t) => t + 1);
10806
- void load();
11208
+ children: [
11209
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex items-center gap-2", children: [
11210
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: portal.title }),
11211
+ portal.is_active ? null : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_design_system36.Badge, { variant: "outline", children: "closed" })
11212
+ ] }),
11213
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("p", { className: "mt-0.5 text-xs text-muted-foreground", children: [
11214
+ "Clients come from ",
11215
+ portal.client_table,
11216
+ portal.tables === 1 ? ", and see 1 Table" : `, and see ${portal.tables} Tables`,
11217
+ "."
11218
+ ] }),
11219
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("p", { className: "mt-1.5 text-xs", children: [
11220
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "font-medium", children: portal.signed_in }),
11221
+ " signed in",
11222
+ " \xB7 ",
11223
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "font-medium", children: portal.invited - portal.signed_in }),
11224
+ " invited and waiting"
11225
+ ] }),
11226
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "mt-2 flex flex-wrap items-center gap-1.5", children: [
11227
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(CopyLink, { url }),
11228
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
11229
+ import_design_system36.Button,
11230
+ {
11231
+ size: "sm",
11232
+ variant: "ghost",
11233
+ onClick: () => setOpenId(open ? null : portal.portal_id),
11234
+ children: open ? "Close" : "Open it"
11235
+ }
11236
+ ),
11237
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
11238
+ ArchivePortalControl,
11239
+ {
11240
+ portal,
11241
+ onArchived: () => {
11242
+ setArchiveToken((t) => t + 1);
11243
+ void load();
11244
+ }
11245
+ }
11246
+ )
11247
+ ] }),
11248
+ open ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
11249
+ PortalDetail,
11250
+ {
11251
+ portalId: portal.portal_id,
11252
+ ...tableId ? { tableId } : {},
11253
+ origin,
11254
+ onChanged: () => void load()
10807
11255
  }
10808
- }
10809
- )
10810
- ] }),
10811
- open ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
10812
- PortalDetail,
10813
- {
10814
- portalId: portal.portal_id,
10815
- ...tableId ? { tableId } : {},
10816
- origin,
10817
- onChanged: () => void load()
10818
- }
10819
- ) : null
10820
- ] }, portal.portal_id);
11256
+ ) : null
11257
+ ]
11258
+ },
11259
+ portal.portal_id
11260
+ );
10821
11261
  }) }),
10822
11262
  /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ArchivedPortals, { refreshToken: archiveToken, onRestored: () => void load() })
10823
11263
  ] });
@@ -11575,7 +12015,17 @@ function DigestScheduler({
11575
12015
  preview ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "rounded-md border bg-muted/40 p-2.5 text-xs", children: [
11576
12016
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("p", { className: "font-medium", children: preview.subject }),
11577
12017
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("p", { className: "mt-1 text-muted-foreground", children: preview.body }),
11578
- preview.incomplete ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("p", { className: "mt-1 text-destructive", children: preview.incomplete }) : null,
12018
+ preview.incomplete ? (
12019
+ // The summary could not be finished: a refusal line naming the missing
12020
+ // piece and what to do, never the bare sentence in red (lane REFUSAL-SWEEP).
12021
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
12022
+ RefusalLine,
12023
+ {
12024
+ className: "mt-1",
12025
+ error: refusal("not_supported", preview.incomplete, "Fill in the missing piece above, then preview again.")
12026
+ }
12027
+ )
12028
+ ) : null,
11579
12029
  preview.entered.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("p", { className: "mt-1", children: [
11580
12030
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "font-medium", children: "Arrived:" }),
11581
12031
  " ",
@@ -11597,6 +12047,7 @@ var import_react77 = require("react");
11597
12047
  var import_react78 = require("@ai-matrx/records/react");
11598
12048
  var import_design_system38 = require("@ai-matrx/design-system");
11599
12049
  var import_jsx_runtime41 = require("react/jsx-runtime");
12050
+ var RULE_NOT_HERE_LINE = "The link named a digest that is not one of yours on this table \u2014 it belongs to somebody else, or it was switched off for good. Yours are listed here; its owner can change theirs.";
11600
12051
  function whenItFires(subscription) {
11601
12052
  if (subscription.cadence === "instant") return "as it happens";
11602
12053
  const every = subscription.cadence === "hourly" ? "an hourly summary" : subscription.cadence === "weekly" ? "a weekly summary" : "a daily summary";
@@ -11604,7 +12055,7 @@ function whenItFires(subscription) {
11604
12055
  }
11605
12056
  var CHANNEL_WORDS2 = SUBSCRIPTION_CHANNEL_LABEL;
11606
12057
  var DIGEST_SUGGESTION = "Email me a summary of this every Monday at 8 in the morning.";
11607
- function SubscriptionsPanel({ tableId, className }) {
12058
+ function SubscriptionsPanel({ tableId, activeRuleId, className }) {
11608
12059
  const client = (0, import_react78.useRecordsClient)();
11609
12060
  const [rows, setRows] = (0, import_react77.useState)(null);
11610
12061
  const [error, setError] = (0, import_react77.useState)(null);
@@ -11695,81 +12146,104 @@ function SubscriptionsPanel({ tableId, className }) {
11695
12146
  children: "Nothing is telling you about this table. A summary arrives on a schedule you set and names what arrived, what left and what changed since the last one."
11696
12147
  }
11697
12148
  ) : null,
11698
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("ul", { className: "flex flex-col gap-2", children: rows.map((subscription) => /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("li", { className: "rounded-md border p-2.5", children: [
11699
- /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "flex items-start gap-2", children: [
11700
- /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "min-w-0 flex-1", children: [
11701
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "truncate text-sm font-medium", children: subscription.name }),
11702
- /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("p", { className: "mt-0.5 text-xs text-muted-foreground", children: [
11703
- CHANNEL_WORDS2[subscription.channel] ?? `on the ${subscription.channel} channel`,
11704
- " \xB7 ",
11705
- whenItFires(subscription)
11706
- ] })
11707
- ] }),
11708
- subscription.i_may_mute ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
11709
- import_design_system38.Switch,
11710
- {
11711
- checked: !subscription.muted,
11712
- disabled: busy === subscription.rule_id,
11713
- "aria-label": `Tell me about ${subscription.name}`,
11714
- onCheckedChange: (on) => void flip(subscription, on)
11715
- }
11716
- ) : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_design_system38.Badge, { variant: "secondary", children: "someone else's" })
11717
- ] }),
11718
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "mt-1.5 text-xs text-muted-foreground", children: subscription.muted ? "Off \u2014 it stays here and tells nobody until you switch it back on." : subscription.mine ? "On, and addressed to you." : "On, and addressed to somebody else in this organization." }),
11719
- /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
11720
- subscription.next_digest_at ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { children: [
11721
- "Next summary ",
11722
- new Date(subscription.next_digest_at).toLocaleString()
11723
- ] }) : subscription.cadence === "instant" ? null : subscription.muted ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { children: "No next summary while it is off." }) : null,
11724
- subscription.last_sent_at ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { children: [
11725
- "Last told you ",
11726
- new Date(subscription.last_sent_at).toLocaleString()
11727
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { children: "It has not told you anything yet." }),
11728
- subscription.quiet_hours ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { children: [
11729
- "Not between ",
11730
- subscription.quiet_hours.start,
11731
- " and ",
11732
- subscription.quiet_hours.end,
11733
- " \u2014 a send inside those hours waits until they end."
11734
- ] }) : null,
11735
- subscription.cadence === "instant" ? null : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
11736
- import_design_system38.Button,
11737
- {
11738
- size: "sm",
11739
- variant: "ghost",
11740
- disabled: busy === subscription.rule_id,
11741
- onClick: () => void showOne(subscription),
11742
- children: "Send me a preview now"
11743
- }
11744
- )
11745
- ] }),
11746
- preview && preview.rule_id === subscription.rule_id ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "mt-2 rounded-md border bg-muted/40 p-2.5 text-xs", children: [
11747
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "font-medium", children: preview.subject }),
11748
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "mt-1 text-muted-foreground", children: preview.body }),
11749
- preview.incomplete ? (
11750
- // Absent or honest: a subscription that cannot produce a summary
11751
- // says which piece is missing instead of showing an empty one.
11752
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "mt-1 text-destructive", children: preview.incomplete })
11753
- ) : null,
11754
- preview.entered.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("p", { className: "mt-1", children: [
11755
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "font-medium", children: "Arrived:" }),
11756
- " ",
11757
- preview.entered.map((e) => e.name).join(", ")
11758
- ] }) : null,
11759
- preview.left.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("p", { className: "mt-1", children: [
11760
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "font-medium", children: "Left:" }),
11761
- " ",
11762
- preview.left.map((e) => e.name).join(", ")
11763
- ] }) : null,
11764
- preview.changed.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("p", { className: "mt-1", children: [
11765
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "font-medium", children: "Changed:" }),
11766
- " ",
11767
- preview.changed.map((e) => e.name).join(", ")
11768
- ] }) : null,
11769
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "mt-1.5 text-muted-foreground", children: "Nothing was sent and nothing was recorded \u2014 this is what the next one would say." }),
11770
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_design_system38.Button, { size: "sm", variant: "ghost", className: "mt-1", onClick: () => setPreview(null), children: "Close" })
11771
- ] }) : null
11772
- ] }, subscription.rule_id)) })
12149
+ activeRuleId && !rows.some((r) => r.rule_id === activeRuleId) ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "rounded-md border border-amber-600/40 bg-amber-500/5 px-3 py-2 text-xs leading-relaxed text-amber-700 dark:border-amber-400/40 dark:text-amber-300", children: RULE_NOT_HERE_LINE }) : null,
12150
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("ul", { className: "flex flex-col gap-2", children: rows.map((subscription) => /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
12151
+ "li",
12152
+ {
12153
+ "data-linked": subscription.rule_id === activeRuleId ? "true" : void 0,
12154
+ ref: subscription.rule_id === activeRuleId ? (el) => el?.scrollIntoView({ block: "nearest" }) : void 0,
12155
+ className: (0, import_design_system38.cn)(
12156
+ "rounded-md border p-2.5",
12157
+ subscription.rule_id === activeRuleId && "border-primary ring-1 ring-primary"
12158
+ ),
12159
+ children: [
12160
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "flex items-start gap-2", children: [
12161
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "min-w-0 flex-1", children: [
12162
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "truncate text-sm font-medium", children: subscription.name }),
12163
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("p", { className: "mt-0.5 text-xs text-muted-foreground", children: [
12164
+ CHANNEL_WORDS2[subscription.channel] ?? `on the ${subscription.channel} channel`,
12165
+ " \xB7 ",
12166
+ whenItFires(subscription)
12167
+ ] })
12168
+ ] }),
12169
+ subscription.i_may_mute ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
12170
+ import_design_system38.Switch,
12171
+ {
12172
+ checked: !subscription.muted,
12173
+ disabled: busy === subscription.rule_id,
12174
+ "aria-label": `Tell me about ${subscription.name}`,
12175
+ onCheckedChange: (on) => void flip(subscription, on)
12176
+ }
12177
+ ) : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_design_system38.Badge, { variant: "secondary", children: "someone else's" })
12178
+ ] }),
12179
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "mt-1.5 text-xs text-muted-foreground", children: subscription.muted ? "Off \u2014 it stays here and tells nobody until you switch it back on." : subscription.mine ? "On, and addressed to you." : "On, and addressed to somebody else in this organization." }),
12180
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
12181
+ subscription.next_digest_at ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { children: [
12182
+ "Next summary ",
12183
+ new Date(subscription.next_digest_at).toLocaleString()
12184
+ ] }) : subscription.cadence === "instant" ? null : subscription.muted ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { children: "No next summary while it is off." }) : null,
12185
+ subscription.last_sent_at ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { children: [
12186
+ "Last told you ",
12187
+ new Date(subscription.last_sent_at).toLocaleString()
12188
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { children: "It has not told you anything yet." }),
12189
+ subscription.quiet_hours ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { children: [
12190
+ "Not between ",
12191
+ subscription.quiet_hours.start,
12192
+ " and ",
12193
+ subscription.quiet_hours.end,
12194
+ " \u2014 a send inside those hours waits until they end."
12195
+ ] }) : null,
12196
+ subscription.cadence === "instant" ? null : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
12197
+ import_design_system38.Button,
12198
+ {
12199
+ size: "sm",
12200
+ variant: "ghost",
12201
+ disabled: busy === subscription.rule_id,
12202
+ onClick: () => void showOne(subscription),
12203
+ children: "Send me a preview now"
12204
+ }
12205
+ )
12206
+ ] }),
12207
+ preview && preview.rule_id === subscription.rule_id ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "mt-2 rounded-md border bg-muted/40 p-2.5 text-xs", children: [
12208
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "font-medium", children: preview.subject }),
12209
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "mt-1 text-muted-foreground", children: preview.body }),
12210
+ preview.incomplete ? (
12211
+ // Absent or honest: a subscription that cannot produce a summary
12212
+ // says which piece is missing instead of showing an empty one.
12213
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
12214
+ RefusalLine,
12215
+ {
12216
+ className: "mt-1",
12217
+ error: refusal(
12218
+ "not_supported",
12219
+ preview.incomplete,
12220
+ "Fill in the missing piece, then preview again."
12221
+ )
12222
+ }
12223
+ )
12224
+ ) : null,
12225
+ preview.entered.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("p", { className: "mt-1", children: [
12226
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "font-medium", children: "Arrived:" }),
12227
+ " ",
12228
+ preview.entered.map((e) => e.name).join(", ")
12229
+ ] }) : null,
12230
+ preview.left.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("p", { className: "mt-1", children: [
12231
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "font-medium", children: "Left:" }),
12232
+ " ",
12233
+ preview.left.map((e) => e.name).join(", ")
12234
+ ] }) : null,
12235
+ preview.changed.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("p", { className: "mt-1", children: [
12236
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "font-medium", children: "Changed:" }),
12237
+ " ",
12238
+ preview.changed.map((e) => e.name).join(", ")
12239
+ ] }) : null,
12240
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "mt-1.5 text-muted-foreground", children: "Nothing was sent and nothing was recorded \u2014 this is what the next one would say." }),
12241
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_design_system38.Button, { size: "sm", variant: "ghost", className: "mt-1", onClick: () => setPreview(null), children: "Close" })
12242
+ ] }) : null
12243
+ ]
12244
+ },
12245
+ subscription.rule_id
12246
+ )) })
11773
12247
  ] });
11774
12248
  }
11775
12249
 
@@ -11824,9 +12298,16 @@ function ConnectedFormRunner(props) {
11824
12298
  const submit = (0, import_react81.useCallback)(
11825
12299
  async (values) => {
11826
12300
  if (!client) {
12301
+ const said = "This form cannot send answers from here, so nothing was sent.";
11827
12302
  return {
11828
12303
  ok: false,
11829
- message: "This form has nowhere to send an answer: it is running outside the record store and nobody gave it a submit port. A public form is served with its questions and its submit port by the server; a signed-in one needs <RecordsProvider> above it."
12304
+ message: said,
12305
+ error: refusal(
12306
+ "not_supported",
12307
+ said,
12308
+ "Open the form from its own link and answer it there.",
12309
+ "No submit port: a public form is served with its questions and its submit port by the server; a signed-in one needs <RecordsProvider> above it."
12310
+ )
11830
12311
  };
11831
12312
  }
11832
12313
  const document2 = {
@@ -11839,7 +12320,7 @@ function ConnectedFormRunner(props) {
11839
12320
  })
11840
12321
  };
11841
12322
  const written = await client.recordWrite({ table_id: form.subject, data: document2 });
11842
- if (!written.ok) return { ok: false, message: written.error.message };
12323
+ if (!written.ok) return { ok: false, message: written.error.message, error: written.error };
11843
12324
  return { ok: true, recordId: written.data };
11844
12325
  },
11845
12326
  [client, form]
@@ -11881,7 +12362,7 @@ function FormStage({
11881
12362
  const [answers, setAnswers] = (0, import_react81.useState)({});
11882
12363
  const [at, setAt] = (0, import_react81.useState)(0);
11883
12364
  const [error, setError] = (0, import_react81.useState)(null);
11884
- const [refusal, setRefusal] = (0, import_react81.useState)(null);
12365
+ const [refusal2, setRefusal] = (0, import_react81.useState)(null);
11885
12366
  const [writing, setWriting] = (0, import_react81.useState)(false);
11886
12367
  const [done, setDone] = (0, import_react81.useState)(null);
11887
12368
  const [hidden, setHidden] = (0, import_react81.useState)({});
@@ -11950,12 +12431,21 @@ function FormStage({
11950
12431
  const outcome = await onSubmit(values);
11951
12432
  setWriting(false);
11952
12433
  if (!outcome.ok) {
11953
- setRefusal(outcome.message);
12434
+ const inWords = (text) => questions.reduce(
12435
+ (said, q) => said.replace(new RegExp(`\\b${q.key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "g"), q.ask),
12436
+ text
12437
+ );
12438
+ const told = outcome.error ?? refusal("invalid_argument", outcome.message, "Check your answers, then send again.");
12439
+ setRefusal({
12440
+ ...told,
12441
+ message: inWords(told.message),
12442
+ ...told.hint ? { hint: inWords(told.hint) } : {}
12443
+ });
11954
12444
  return;
11955
12445
  }
11956
12446
  setDone(outcome.message ?? "sent");
11957
12447
  onSubmitted?.(outcome.recordId ?? null);
11958
- }, [answers, form, honeypotKey, live, onSubmit, onSubmitted, preview]);
12448
+ }, [answers, form, honeypotKey, live, onSubmit, onSubmitted, preview, questions]);
11959
12449
  function advance() {
11960
12450
  if (!oneAtATime) return;
11961
12451
  if (index < live.length - 1) setAt(index + 1);
@@ -12014,7 +12504,22 @@ function FormStage({
12014
12504
  ] }) : null,
12015
12505
  form.intro && index === 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "text-sm text-muted-foreground", children: form.intro }) : null,
12016
12506
  error ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(RefusalNotice, { error, className: "text-left" }) : null,
12017
- refusal ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { role: "alert", className: "rounded border border-destructive/40 px-2 py-1 text-left text-xs text-destructive", children: refusal }) : null,
12507
+ refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
12508
+ RefusalNotice,
12509
+ {
12510
+ error: refusal2,
12511
+ className: "text-left",
12512
+ onKeepEditing: () => {
12513
+ setRefusal(null);
12514
+ stage.current?.querySelector("input, textarea, select, button")?.focus();
12515
+ },
12516
+ onDiscard: () => {
12517
+ setRefusal(null);
12518
+ setAnswers({});
12519
+ setAt(0);
12520
+ }
12521
+ }
12522
+ ) : null,
12018
12523
  /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { ref: stage, className: "flex flex-col gap-4 text-left", children: [
12019
12524
  (oneAtATime ? current ? [current] : [] : live).map((q) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
12020
12525
  Question,
@@ -12088,12 +12593,15 @@ function Question({
12088
12593
  void upload?.(file).then((result) => {
12089
12594
  if (!result) return;
12090
12595
  if (result.ok) onChange(result.fileId);
12091
- else setUploadError(result.reason);
12596
+ else
12597
+ setUploadError(
12598
+ refusal("invalid_argument", result.reason, "Pick another file, or try this one again.")
12599
+ );
12092
12600
  });
12093
12601
  }
12094
12602
  }
12095
12603
  ),
12096
- uploadError ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "text-xs text-destructive", children: uploadError }) : null
12604
+ uploadError ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(RefusalLine, { error: uploadError }) : null
12097
12605
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(FieldControl, { field, value, onChange, id })
12098
12606
  ] });
12099
12607
  }
@@ -12880,10 +13388,15 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
12880
13388
  onChange: (e) => setDraftBody(e.target.value)
12881
13389
  }
12882
13390
  ),
12883
- unresolved.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("ul", { className: "flex flex-col gap-0.5 rounded border border-dashed px-2 py-1 text-xs", children: unresolved.map((token) => /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("li", { className: "text-destructive", children: [
12884
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("code", { children: token.raw }),
12885
- " \u2014 ",
12886
- token.why
13391
+ unresolved.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("ul", { className: "flex flex-col gap-0.5 rounded border border-dashed px-2 py-1 text-xs", children: unresolved.map((token) => /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("li", { className: "flex items-baseline gap-1", children: [
13392
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("code", { className: "text-destructive", children: token.raw }),
13393
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { "aria-hidden": "true", children: "\u2014" }),
13394
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
13395
+ RefusalLine,
13396
+ {
13397
+ error: refusal("invalid_argument", token.why, "Change it to a Field this table has, or take it out.")
13398
+ }
13399
+ )
12887
13400
  ] }, token.raw)) }) : draftBody.trim() !== "" ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("p", { className: "text-xs text-muted-foreground", children: "Every token in this body points at a Field this table can answer." }) : null
12888
13401
  ] });
12889
13402
  }
@@ -13088,7 +13601,21 @@ function SignBlock({ tableId, recordId, render, className }) {
13088
13601
  signature.document_hash.slice(0, 12)
13089
13602
  ] })
13090
13603
  ] }),
13091
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("p", { className: (0, import_design_system44.cn)("mt-0.5", intact === false ? "text-destructive" : "text-muted-foreground"), children: intact === true ? "The store says what was signed is still exactly what is there." : intact === false ? typeof verdict === "object" && verdict !== null && "reason" in verdict ? String(verdict.reason) : "The store says what is there no longer matches what was signed." : String(verdict ?? "") })
13604
+ intact === false ? (
13605
+ // THE STORE REFUSED TO CALL IT INTACT: its reason goes through the one
13606
+ // formatter, with what to do next, never raw in red (lane REFUSAL-SWEEP).
13607
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
13608
+ RefusalLine,
13609
+ {
13610
+ className: "mt-0.5",
13611
+ error: refusal(
13612
+ "refused_by_rule",
13613
+ typeof verdict === "object" && verdict !== null && "reason" in verdict ? String(verdict.reason) : "The store says what is there no longer matches what was signed.",
13614
+ "Ask for it to be signed again on the version that is there now."
13615
+ )
13616
+ }
13617
+ )
13618
+ ) : /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("p", { className: "mt-0.5 text-muted-foreground", children: intact === true ? "The store says what was signed is still exactly what is there." : String(verdict ?? "") })
13092
13619
  ] }, signature.id);
13093
13620
  }) }) : null,
13094
13621
  !render ? /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("p", { className: "text-xs text-muted-foreground", children: 'Nothing has been frozen yet, so there is no version to sign. A signature seals one exact document version and its hash \u2014 "the document as it reads today" is not something anyone can agree to.' }) : signable.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
@@ -13349,14 +13876,14 @@ function NotifyRuleEditor({ tableId, seed, className }) {
13349
13876
  var import_react95 = require("react");
13350
13877
  var import_recharts = require("recharts");
13351
13878
  var import_records11 = require("@ai-matrx/records");
13352
- var import_core6 = require("@ai-matrx/records/core");
13879
+ var import_core7 = require("@ai-matrx/records/core");
13353
13880
  var import_design_system46 = require("@ai-matrx/design-system");
13354
13881
  var import_jsx_runtime49 = require("react/jsx-runtime");
13355
13882
  function pretty(n) {
13356
13883
  return Number.isInteger(n) ? n.toLocaleString() : n.toLocaleString(void 0, { maximumFractionDigits: 2 });
13357
13884
  }
13358
- function asRefusal(block) {
13359
- return (0, import_core6.mapPgError)(
13885
+ function asRefusal2(block) {
13886
+ return (0, import_core7.mapPgError)(
13360
13887
  {
13361
13888
  message: block.refused ?? "",
13362
13889
  ...block.sqlstate ? { code: block.sqlstate } : {}
@@ -13416,7 +13943,7 @@ function ChartBlock({ block, subject, className }) {
13416
13943
  " ms"
13417
13944
  ] }) : null
13418
13945
  ] }),
13419
- block.refused ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(RefusalNotice, { error: asRefusal(block) }) : needs ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("p", { className: "px-1 py-3 text-xs text-muted-foreground", children: needs }) : kind === "stuck" ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(StuckList, { rows: block.rows ?? [] }) : points.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("p", { className: "px-1 py-3 text-xs text-muted-foreground", children: "The store answered this question with no groups at all, so there is nothing to draw yet." }) : /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_jsx_runtime49.Fragment, { children: kind === "number" ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(BigNumber, { points, measure: primary, onDrill: drill }) : kind === "table" ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(GroupTable, { points, series, onDrill: drill }) : /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(import_jsx_runtime49.Fragment, { children: [
13946
+ block.refused ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(RefusalNotice, { error: asRefusal2(block) }) : needs ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("p", { className: "px-1 py-3 text-xs text-muted-foreground", children: needs }) : kind === "stuck" ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(StuckList, { rows: block.rows ?? [] }) : points.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("p", { className: "px-1 py-3 text-xs text-muted-foreground", children: "The store answered this question with no groups at all, so there is nothing to draw yet." }) : /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_jsx_runtime49.Fragment, { children: kind === "number" ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(BigNumber, { points, measure: primary, onDrill: drill }) : kind === "table" ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(GroupTable, { points, series, onDrill: drill }) : /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(import_jsx_runtime49.Fragment, { children: [
13420
13947
  /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(Drawing, { kind, points, series, config, onDrill: drill }),
13421
13948
  /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(Values, { points, measure: primary, config, onDrill: drill })
13422
13949
  ] }) })
@@ -13982,13 +14509,14 @@ var import_react99 = require("@ai-matrx/records/react");
13982
14509
  var import_records12 = require("@ai-matrx/records");
13983
14510
  var import_design_system48 = require("@ai-matrx/design-system");
13984
14511
  var import_jsx_runtime51 = require("react/jsx-runtime");
14512
+ var FORM_NOT_HERE_LINE = "The link named a form this table does not have \u2014 it may have been removed, or it belongs to another table. The forms this table does have are listed here.";
13985
14513
  var WHAT_A_FORM_IS = "A form asks for this table's own fields, and its answers land here as ordinary records stamped with the form they came through.";
13986
14514
  var NO_ADMIN = "This table has no forms. Making one needs the admin level on it, because a form decides what people with no account may add here.";
13987
14515
  function formSuggestion(tableName2) {
13988
14516
  const subject = tableName2?.trim() ? tableName2.trim() : "this table";
13989
14517
  return `Make me a form that collects new ${subject} entries and tells me when somebody answers.`;
13990
14518
  }
13991
- function FormsPanel({ tableId, className }) {
14519
+ function FormsPanel({ tableId, activeFormId, className }) {
13992
14520
  const client = (0, import_react99.useRecordsClient)();
13993
14521
  const publishGate = usePublishGate();
13994
14522
  const host = useRecordsUi();
@@ -13999,6 +14527,11 @@ function FormsPanel({ tableId, className }) {
13999
14527
  const [busy, setBusy] = (0, import_react98.useState)(null);
14000
14528
  const [copied, setCopied] = (0, import_react98.useState)(null);
14001
14529
  const [building, setBuilding] = (0, import_react98.useState)(false);
14530
+ const linked = activeFormId && forms !== null ? forms.find((f) => f.form_id === activeFormId) ?? null : null;
14531
+ const linkedMissing = Boolean(activeFormId) && forms !== null && linked === null;
14532
+ (0, import_react98.useEffect)(() => {
14533
+ if (linked && rights.structure) setBuilding(true);
14534
+ }, [linked, rights.structure]);
14002
14535
  const load = (0, import_react98.useCallback)(async () => {
14003
14536
  const answered = await client.forms({ table_id: tableId });
14004
14537
  if (!answered.ok) {
@@ -14047,10 +14580,12 @@ function FormsPanel({ tableId, className }) {
14047
14580
  rights.structure && forms.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(import_design_system48.Button, { size: "sm", variant: building ? "secondary" : "ghost", onClick: () => setBuilding((b) => !b), children: building ? "Done building" : "Build a form" }) : null
14048
14581
  ] }),
14049
14582
  error ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(RefusalNotice, { error }) : null,
14583
+ linkedMissing ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("p", { className: "rounded-md border border-amber-600/40 bg-amber-500/5 px-3 py-2 text-xs leading-relaxed text-amber-700 dark:border-amber-400/40 dark:text-amber-300", children: FORM_NOT_HERE_LINE }) : null,
14050
14584
  building ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
14051
14585
  FormBuilder,
14052
14586
  {
14053
14587
  tableId,
14588
+ activeFormId: linked ? linked.form_id : null,
14054
14589
  onActiveForm: () => {
14055
14590
  void load();
14056
14591
  }
@@ -14076,50 +14611,62 @@ function FormsPanel({ tableId, className }) {
14076
14611
  ) : null,
14077
14612
  /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("ul", { className: "flex flex-col gap-2", children: forms.map((form) => {
14078
14613
  const url = `${origin}${(0, import_records12.publicFormPath)(form.form_id)}`;
14079
- return /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("li", { className: "rounded-md border p-2.5", children: [
14080
- /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("div", { className: "flex items-center gap-2", children: [
14081
- /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: form.title ?? form.slug }),
14082
- /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(import_design_system48.Badge, { variant: form.state === "open" ? "default" : "secondary", children: form.state })
14083
- ] }),
14084
- /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("p", { className: "mt-0.5 text-xs text-muted-foreground", children: FORM_STATE_WORDS[form.state] }),
14085
- /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("p", { className: "mt-1.5 text-xs", children: [
14086
- /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("span", { className: "font-medium", children: form.in_table }),
14087
- " in the table",
14088
- form.held > 0 ? /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(import_jsx_runtime51.Fragment, { children: [
14089
- " \xB7 ",
14090
- /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("span", { className: "font-medium", children: form.held }),
14091
- " waiting for someone"
14092
- ] }) : null,
14093
- form.rejected > 0 ? /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(import_jsx_runtime51.Fragment, { children: [
14094
- " \xB7 ",
14095
- /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("span", { className: "font-medium", children: form.rejected }),
14096
- " turned away"
14097
- ] }) : null,
14098
- form.submission_cap !== null ? /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("span", { className: "text-muted-foreground", children: [
14099
- " \xB7 stops at ",
14100
- form.submission_cap
14101
- ] }) : null
14102
- ] }),
14103
- /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("div", { className: "mt-2 flex flex-wrap items-center gap-1.5", children: [
14104
- form.published_at ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(import_design_system48.Button, { size: "sm", variant: "outline", onClick: () => void copy(url, form.form_id), children: copied === form.form_id ? "Copied" : "Copy link" }) : null,
14105
- rights.structure && !publishGate.blocked ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
14106
- import_design_system48.Button,
14107
- {
14108
- size: "sm",
14109
- variant: "ghost",
14110
- disabled: busy === form.form_id,
14111
- onClick: () => void toggle(form),
14112
- children: busy === form.form_id ? "\u2026" : form.published_at && !form.closed_at ? "Unpublish" : "Publish"
14113
- }
14114
- ) : null
14115
- ] }),
14116
- rights.structure && publishGate.why ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("p", { className: "mt-1.5 text-xs text-muted-foreground", children: publishGate.why }) : null,
14117
- shown && shown === url ? /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("p", { className: "mt-1.5 break-all rounded border border-dashed px-2 py-1 text-xs", children: [
14118
- "This browser would not let the page copy for you, so here it is to copy by hand:",
14119
- " ",
14120
- url
14121
- ] }) : null
14122
- ] }, form.form_id);
14614
+ return /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(
14615
+ "li",
14616
+ {
14617
+ "data-linked": form.form_id === activeFormId ? "true" : void 0,
14618
+ ref: form.form_id === activeFormId ? (el) => el?.scrollIntoView({ block: "nearest" }) : void 0,
14619
+ className: (0, import_design_system48.cn)(
14620
+ "rounded-md border p-2.5",
14621
+ form.form_id === activeFormId && "border-primary ring-1 ring-primary"
14622
+ ),
14623
+ children: [
14624
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("div", { className: "flex items-center gap-2", children: [
14625
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: form.title ?? form.slug }),
14626
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(import_design_system48.Badge, { variant: form.state === "open" ? "default" : "secondary", children: form.state })
14627
+ ] }),
14628
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("p", { className: "mt-0.5 text-xs text-muted-foreground", children: FORM_STATE_WORDS[form.state] }),
14629
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("p", { className: "mt-1.5 text-xs", children: [
14630
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("span", { className: "font-medium", children: form.in_table }),
14631
+ " in the table",
14632
+ form.held > 0 ? /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(import_jsx_runtime51.Fragment, { children: [
14633
+ " \xB7 ",
14634
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("span", { className: "font-medium", children: form.held }),
14635
+ " waiting for someone"
14636
+ ] }) : null,
14637
+ form.rejected > 0 ? /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(import_jsx_runtime51.Fragment, { children: [
14638
+ " \xB7 ",
14639
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("span", { className: "font-medium", children: form.rejected }),
14640
+ " turned away"
14641
+ ] }) : null,
14642
+ form.submission_cap !== null ? /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("span", { className: "text-muted-foreground", children: [
14643
+ " \xB7 stops at ",
14644
+ form.submission_cap
14645
+ ] }) : null
14646
+ ] }),
14647
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("div", { className: "mt-2 flex flex-wrap items-center gap-1.5", children: [
14648
+ form.published_at ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(import_design_system48.Button, { size: "sm", variant: "outline", onClick: () => void copy(url, form.form_id), children: copied === form.form_id ? "Copied" : "Copy link" }) : null,
14649
+ rights.structure && !publishGate.blocked ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
14650
+ import_design_system48.Button,
14651
+ {
14652
+ size: "sm",
14653
+ variant: "ghost",
14654
+ disabled: busy === form.form_id,
14655
+ onClick: () => void toggle(form),
14656
+ children: busy === form.form_id ? "\u2026" : form.published_at && !form.closed_at ? "Unpublish" : "Publish"
14657
+ }
14658
+ ) : null
14659
+ ] }),
14660
+ rights.structure && publishGate.why ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("p", { className: "mt-1.5 text-xs text-muted-foreground", children: publishGate.why }) : null,
14661
+ shown && shown === url ? /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("p", { className: "mt-1.5 break-all rounded border border-dashed px-2 py-1 text-xs", children: [
14662
+ "This browser would not let the page copy for you, so here it is to copy by hand:",
14663
+ " ",
14664
+ url
14665
+ ] }) : null
14666
+ ]
14667
+ },
14668
+ form.form_id
14669
+ );
14123
14670
  }) }),
14124
14671
  !rights.known ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("p", { className: "text-xs text-muted-foreground", children: "Still asking what you may do with this table \u2014 nothing is offered until it answers." }) : null
14125
14672
  ] });
@@ -14776,7 +15323,7 @@ function CaptureRun({ sheetId, face: given, className }) {
14776
15323
  void client.captureOpen({ sheet_id: sheetId }).then((res) => {
14777
15324
  if (cancelled) return;
14778
15325
  if (res.ok) setFace(res.data);
14779
- else setLoadFailed(res.error?.message ?? "This sheet could not be opened.");
15326
+ else setLoadFailed(refusalOr(res.error, "unreachable", "This sheet could not be opened."));
14780
15327
  });
14781
15328
  return () => {
14782
15329
  cancelled = true;
@@ -14823,7 +15370,11 @@ function CaptureRun({ sheetId, face: given, className }) {
14823
15370
  const needed = questions.filter((x) => x.required && !answered(x.field));
14824
15371
  if (needed.length > 0) {
14825
15372
  setMissing(
14826
- `${needed.map((x) => (x.ask ?? x.field).replace(/\?$/, "")).join(", ")} still ${needed.length === 1 ? "needs" : "need"} an answer.`
15373
+ refusal(
15374
+ "invalid_argument",
15375
+ `${needed.map((x) => (x.ask ?? x.field).replace(/\?$/, "")).join(", ")} still ${needed.length === 1 ? "needs" : "need"} an answer.`,
15376
+ "Answer it, then capture again. Nothing has been queued yet."
15377
+ )
14827
15378
  );
14828
15379
  setAt(questions.findIndex((x) => x.field === needed[0].field));
14829
15380
  return;
@@ -14854,7 +15405,7 @@ function CaptureRun({ sheetId, face: given, className }) {
14854
15405
  if (online) await sync();
14855
15406
  }
14856
15407
  if (loadFailed) {
14857
- return /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("section", { className: (0, import_design_system51.cn)("mx-auto w-full max-w-sm p-4", className), children: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("p", { className: "text-sm text-destructive", "data-testid": "capture-refusal", children: loadFailed }) });
15408
+ return /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("section", { className: (0, import_design_system51.cn)("mx-auto w-full max-w-sm p-4", className), children: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("div", { "data-testid": "capture-refusal", children: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(RefusalNotice, { error: loadFailed, className: "text-sm" }) }) });
14858
15409
  }
14859
15410
  if (face === void 0) {
14860
15411
  return /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_design_system51.Skeleton, { className: (0, import_design_system51.cn)("mx-auto h-64 w-full max-w-sm", className) });
@@ -14885,7 +15436,21 @@ function CaptureRun({ sheetId, face: given, className }) {
14885
15436
  ] });
14886
15437
  const queuePanel = items.filter((i) => i.state !== "landed").length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("ul", { className: "flex flex-col gap-1 rounded border p-2", "data-testid": "capture-queue", children: items.filter((i) => i.state !== "landed").map((i) => /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("li", { className: "flex items-start gap-2 text-[11px]", children: [
14887
15438
  /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("span", { className: "tabular-nums text-muted-foreground", children: new Date(i.captured_at).toLocaleTimeString() }),
14888
- /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("span", { className: (0, import_design_system51.cn)("flex-1", i.state === "refused" && "text-destructive"), children: i.state === "refused" ? i.last_error ?? "This one was refused." : i.last_error ?? "Waiting for a signal." }),
15439
+ i.state === "refused" ? (
15440
+ // THE STORE REFUSED THIS ONE: a refusal line, heading and remedy
15441
+ // both — never its raw words in red (lane REFUSAL-SWEEP).
15442
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
15443
+ RefusalLine,
15444
+ {
15445
+ className: "flex-1",
15446
+ error: refusal(
15447
+ "refused_by_rule",
15448
+ i.last_error ?? "This one was refused.",
15449
+ "Throw it away and capture it again with the answer changed."
15450
+ )
15451
+ }
15452
+ )
15453
+ ) : /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("span", { className: "flex-1", children: i.last_error ?? "Waiting for a signal." }),
14889
15454
  i.state === "refused" ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
14890
15455
  import_design_system51.Button,
14891
15456
  {
@@ -15005,7 +15570,7 @@ function CaptureRun({ sheetId, face: given, className }) {
15005
15570
  }
15006
15571
  }
15007
15572
  ),
15008
- missing ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("p", { className: "text-sm text-destructive", "data-testid": "capture-missing", children: missing }) : null,
15573
+ missing ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("div", { "data-testid": "capture-missing", children: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(RefusalNotice, { error: missing, className: "text-sm" }) }) : null,
15009
15574
  /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("div", { className: "flex items-center gap-2", children: [
15010
15575
  at > 0 ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
15011
15576
  import_design_system51.Button,
@@ -15233,13 +15798,13 @@ function AdHocCaptureSheet({
15233
15798
  setUploadError(null);
15234
15799
  void host.upload?.(file).then((result) => {
15235
15800
  if (result.ok) setFileId(result.fileId);
15236
- else setUploadError(result.reason);
15801
+ else setUploadError(refusal("invalid_argument", result.reason, "Pick another file, or try this one again."));
15237
15802
  });
15238
15803
  }
15239
15804
  }
15240
15805
  ),
15241
15806
  fileId ? /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "Attached." }) : null,
15242
- uploadError ? /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("p", { className: "text-xs text-destructive", children: uploadError }) : null
15807
+ uploadError ? /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(RefusalLine, { error: uploadError }) : null
15243
15808
  ] }) : null,
15244
15809
  /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
15245
15810
  import_design_system52.Textarea,
@@ -15702,17 +16267,17 @@ async function declareTable(client, spec) {
15702
16267
  if (!written.ok) return undo(client, table.data, home.data, spec.name, written.error);
15703
16268
  return { ok: true, data: table.data, homeId: home.data };
15704
16269
  }
15705
- async function undo(client, tableId, homeId, name, refusal) {
16270
+ async function undo(client, tableId, homeId, name, refusal2) {
15706
16271
  const removed = await client.recordDelete({ record_id: tableId });
15707
16272
  if (removed.ok) {
15708
16273
  await client.recordDelete({ record_id: homeId });
15709
- return { ok: false, error: refusal };
16274
+ return { ok: false, error: refusal2 };
15710
16275
  }
15711
16276
  return {
15712
16277
  ok: false,
15713
16278
  error: {
15714
- ...refusal,
15715
- hint: `${refusal.hint ? `${refusal.hint} ` : ""}"${name}" was created before this happened and could not be removed again, so it is in your tables with no columns \u2014 open it and add the first column, or delete it from its own Settings.`
16279
+ ...refusal2,
16280
+ hint: `${refusal2.hint ? `${refusal2.hint} ` : ""}"${name}" was created before this happened and could not be removed again, so it is in your tables with no columns \u2014 open it and add the first column, or delete it from its own Settings.`
15716
16281
  }
15717
16282
  };
15718
16283
  }
@@ -15966,9 +16531,43 @@ function chooseSurface(current, pressed) {
15966
16531
  function surfaceChosen(current, asking) {
15967
16532
  return asking.main !== void 0 ? current.main === asking.main : current.rail === asking.rail;
15968
16533
  }
15969
- function openingRail(activeRecordId) {
15970
- return activeRecordId ? { rail: "record", record: activeRecordId } : { rail: "none", record: null };
16534
+ function openingRail(activeRecordId, activeRail) {
16535
+ if (activeRecordId) return { rail: "record", record: activeRecordId };
16536
+ const named = railFromParam(activeRail);
16537
+ if (named !== null && named !== "share") return { rail: named, record: null };
16538
+ return { rail: "none", record: null };
16539
+ }
16540
+ var RAIL_SPELLINGS = {
16541
+ forms: "forms",
16542
+ form: "forms",
16543
+ bookings: "bookings",
16544
+ booking: "bookings",
16545
+ checklists: "checklists",
16546
+ checklist: "checklists",
16547
+ notifications: "notifications",
16548
+ notification: "notifications",
16549
+ digests: "notifications",
16550
+ digest: "notifications",
16551
+ subscriptions: "notifications",
16552
+ subscription: "notifications",
16553
+ portals: "portals",
16554
+ portal: "portals",
16555
+ settings: "settings",
16556
+ import: "import",
16557
+ inbox: "inbox",
16558
+ share: "share",
16559
+ sharing: "share"
16560
+ };
16561
+ function railFromParam(raw) {
16562
+ if (typeof raw !== "string") return null;
16563
+ const token = raw.trim().toLowerCase();
16564
+ if (token === "") return null;
16565
+ return RAIL_SPELLINGS[token] ?? null;
16566
+ }
16567
+ function unknownRailLine(raw) {
16568
+ return `The link asked to open \u201C${raw}\u201D on this table, which is not something a table has, so you are looking at its records. A table opens Forms, Bookings, Checklists, Notifications, Portals, Settings, Import, Inbox and Share \u2014 each is a button above.`;
15971
16569
  }
16570
+ var SHARE_NOT_YOURS_LINE = "The link asked to open this table's sharing, and only someone with Admin on this table can share it, so it did not open. Ask an admin of this table to share it or to give you Admin.";
15972
16571
  function openingView(activeView, activeDashboardId) {
15973
16572
  const named = pageViewFromParam(activeView);
15974
16573
  if (named === null) {
@@ -15996,6 +16595,8 @@ function TablePage({
15996
16595
  activeGroupField,
15997
16596
  cameFrom,
15998
16597
  filter,
16598
+ activeRail,
16599
+ activeItemId,
15999
16600
  className
16000
16601
  }) {
16001
16602
  const client = (0, import_react119.useRecordsClient)();
@@ -16005,8 +16606,13 @@ function TablePage({
16005
16606
  const askedInWords = filter ? filterInWords(filter, pageFields.data ?? []) : null;
16006
16607
  const organizationId = (0, import_react119.useRecordsClient)().config.organizationId;
16007
16608
  const [view, setView] = (0, import_react118.useState)(null);
16008
- const opening = openingRail(activeRecordId);
16609
+ const opening = openingRail(activeRecordId, activeRail);
16009
16610
  const opened = openingView(activeView, activeDashboardId);
16611
+ const railAsked = typeof activeRail === "string" ? activeRail.trim() : "";
16612
+ const railUnknown = railAsked !== "" && railFromParam(railAsked) === null ? railAsked : null;
16613
+ const shareFromLink = railFromParam(activeRail) === "share";
16614
+ const canShare = useCanShare();
16615
+ const itemFor = (which) => railFromParam(activeRail) === which ? activeItemId ?? null : null;
16010
16616
  const [asking, setAsking] = (0, import_react118.useState)(null);
16011
16617
  const [layoutFromLink, setLayoutFromLink] = (0, import_react118.useState)(opened.layout);
16012
16618
  const [surface, setSurface] = (0, import_react118.useState)({
@@ -16031,6 +16637,12 @@ function TablePage({
16031
16637
  setOpenRecord(activeRecordId);
16032
16638
  setSurface((now) => ({ ...now, rail: "record" }));
16033
16639
  }, [activeRecordId]);
16640
+ (0, import_react118.useEffect)(() => {
16641
+ if (activeRecordId) return;
16642
+ const named = railFromParam(activeRail);
16643
+ if (named === null || named === "share") return;
16644
+ setSurface((now) => ({ ...now, rail: named }));
16645
+ }, [activeRail, activeRecordId]);
16034
16646
  (0, import_react118.useEffect)(() => {
16035
16647
  const named = pageViewFromParam(activeView);
16036
16648
  if (named === null) return;
@@ -16195,7 +16807,8 @@ function TablePage({
16195
16807
  organizationId,
16196
16808
  subjectId: tableId,
16197
16809
  name: table.data?.name,
16198
- may: rights.share
16810
+ may: rights.share,
16811
+ initiallyOpen: shareFromLink
16199
16812
  }
16200
16813
  ),
16201
16814
  /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(ExportMenu, { tableId })
@@ -16218,6 +16831,8 @@ function TablePage({
16218
16831
  }
16219
16832
  )
16220
16833
  ) : /* @__PURE__ */ (0, import_jsx_runtime61.jsxs)(import_jsx_runtime61.Fragment, { children: [
16834
+ railUnknown ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("p", { className: "rounded-md border border-amber-600/40 bg-amber-500/5 px-3 py-2 text-xs leading-relaxed text-amber-700 dark:border-amber-400/40 dark:text-amber-300", children: unknownRailLine(railUnknown) }) : null,
16835
+ shareFromLink && rights.known && (!rights.share || !canShare) ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("p", { className: "rounded-md border border-amber-600/40 bg-amber-500/5 px-3 py-2 text-xs leading-relaxed text-amber-700 dark:border-amber-400/40 dark:text-amber-300", children: canShare ? SHARE_NOT_YOURS_LINE : shareUnavailableReason() }) : null,
16221
16836
  opened.unknown ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("p", { className: "rounded-md border border-amber-600/40 bg-amber-500/5 px-3 py-2 text-xs leading-relaxed text-amber-700 dark:border-amber-400/40 dark:text-amber-300", children: unknownViewLine(opened.unknown) }) : null,
16222
16837
  cameFrom ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
16223
16838
  "p",
@@ -16276,7 +16891,7 @@ function TablePage({
16276
16891
  }
16277
16892
  }
16278
16893
  ) : null,
16279
- rail === "forms" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(FormsPanel, { tableId }) : null,
16894
+ rail === "forms" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(FormsPanel, { tableId, activeFormId: itemFor("forms") }) : null,
16280
16895
  rail === "bookings" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(BookingSlots, { tableId }) : null,
16281
16896
  rail === "checklists" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
16282
16897
  ChecklistsPanel,
@@ -16288,8 +16903,8 @@ function TablePage({
16288
16903
  }
16289
16904
  }
16290
16905
  ) : null,
16291
- rail === "notifications" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(SubscriptionsPanel, { tableId }) : null,
16292
- rail === "portals" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(PortalsPanel, { tableId }) : null,
16906
+ rail === "notifications" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(SubscriptionsPanel, { tableId, activeRuleId: itemFor("notifications") }) : null,
16907
+ rail === "portals" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(PortalsPanel, { tableId, activePortalId: itemFor("portals") }) : null,
16293
16908
  rail === "import" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(ImportWizard, { tableId, onDone: () => setRail("none") }) : null,
16294
16909
  rail === "field" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(FieldEditor, { tableId, onSaved: () => setRail("none"), onCancel: () => setRail("none") }) : null,
16295
16910
  rail === "new-record" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(