@ai-matrx/records-ui 0.81.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, { sizing: "content", className: "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,34 +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
1237
  title: titleOf(row.id),
1110
- className: (0, import_design_system2.cn)(
1238
+ className: (0, import_design_system3.cn)(
1111
1239
  "w-full truncate rounded px-2 py-1 text-left text-xs hover:bg-muted",
1112
1240
  picked.includes(row.id) ? "bg-accent text-accent-foreground" : ""
1113
1241
  ),
1114
1242
  children: titleOf(row.id)
1115
1243
  }
1116
1244
  ) }, row.id)) }),
1117
- 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
1118
1246
  ] }),
1119
- allowCreate && !full ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-1 border-t pt-1", children: [
1120
- 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: [
1121
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1122
- 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,
1123
1251
  {
1124
1252
  autoFocus: true,
1125
1253
  value: newTitle,
@@ -1135,8 +1263,8 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1135
1263
  "aria-label": `Name the new ${targetWord}`
1136
1264
  }
1137
1265
  ),
1138
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1139
- import_design_system2.Button,
1266
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1267
+ import_design_system3.Button,
1140
1268
  {
1141
1269
  type: "button",
1142
1270
  size: "sm",
@@ -1146,8 +1274,8 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1146
1274
  children: busy ? "Creating\u2026" : "Create"
1147
1275
  }
1148
1276
  )
1149
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1150
- import_design_system2.Button,
1277
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1278
+ import_design_system3.Button,
1151
1279
  {
1152
1280
  type: "button",
1153
1281
  size: "sm",
@@ -1162,10 +1290,25 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1162
1290
  setCreating(true);
1163
1291
  }
1164
1292
  },
1165
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "block w-full truncate text-left", 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}` })
1166
1294
  }
1167
1295
  ),
1168
- 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
1169
1312
  ] }) : null
1170
1313
  ] })
1171
1314
  ] })
@@ -1173,12 +1316,12 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1173
1316
  }
1174
1317
 
1175
1318
  // src/editors.tsx
1176
- var import_jsx_runtime4 = require("react/jsx-runtime");
1319
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1177
1320
  function FieldLabel({ field, htmlFor, children }) {
1178
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-baseline gap-1.5", children: [
1179
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system3.Label, { htmlFor, className: "text-xs font-medium", children: fieldName(field) }),
1180
- field.required ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-xs text-destructive", children: "required" }) : null,
1181
- 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,
1182
1325
  children
1183
1326
  ] });
1184
1327
  }
@@ -1189,18 +1332,18 @@ function FieldControl({ field, value, onChange, disabled, id }) {
1189
1332
  case "formula":
1190
1333
  case "lookup":
1191
1334
  case "rollup":
1192
- 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: [
1193
1336
  value === null || value === void 0 ? "\u2014" : String(value),
1194
- /* @__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: [
1195
1338
  fieldName(field),
1196
1339
  " is worked out by the system, so it cannot be typed in"
1197
1340
  ] })
1198
1341
  ] });
1199
1342
  case "json":
1200
- 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 });
1201
1344
  case "long_text":
1202
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1203
- import_design_system3.BasicTextarea,
1345
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1346
+ import_design_system4.BasicTextarea,
1204
1347
  {
1205
1348
  id: inputId,
1206
1349
  rows: 3,
@@ -1212,8 +1355,8 @@ function FieldControl({ field, value, onChange, disabled, id }) {
1212
1355
  case "number":
1213
1356
  case "currency":
1214
1357
  case "percent":
1215
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1216
- import_design_system3.BasicInput,
1358
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1359
+ import_design_system4.BasicInput,
1217
1360
  {
1218
1361
  id: inputId,
1219
1362
  type: "number",
@@ -1225,8 +1368,8 @@ function FieldControl({ field, value, onChange, disabled, id }) {
1225
1368
  }
1226
1369
  );
1227
1370
  case "datetime":
1228
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1229
- import_design_system3.BasicInput,
1371
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1372
+ import_design_system4.BasicInput,
1230
1373
  {
1231
1374
  id: inputId,
1232
1375
  type: field.format === "datetime" ? "datetime-local" : "date",
@@ -1238,8 +1381,8 @@ function FieldControl({ field, value, onChange, disabled, id }) {
1238
1381
  case "email":
1239
1382
  case "phone":
1240
1383
  case "url":
1241
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1242
- import_design_system3.BasicInput,
1384
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1385
+ import_design_system4.BasicInput,
1243
1386
  {
1244
1387
  id: inputId,
1245
1388
  type: kind === "email" ? "email" : kind === "phone" ? "tel" : "url",
@@ -1249,19 +1392,19 @@ function FieldControl({ field, value, onChange, disabled, id }) {
1249
1392
  }
1250
1393
  );
1251
1394
  case "checkbox":
1252
- 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 });
1253
1396
  case "select":
1254
1397
  case "multi_select":
1255
- 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 });
1256
1399
  case "member":
1257
- 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 });
1258
1401
  case "relation":
1259
1402
  case "attachment":
1260
- 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 });
1261
1404
  default:
1262
1405
  if (typeof value === "boolean" || field.config?.["kind"] === "boolean") {
1263
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1264
- import_design_system3.Checkbox,
1406
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1407
+ import_design_system4.Checkbox,
1265
1408
  {
1266
1409
  id: inputId,
1267
1410
  checked: value === true,
@@ -1270,8 +1413,8 @@ function FieldControl({ field, value, onChange, disabled, id }) {
1270
1413
  }
1271
1414
  );
1272
1415
  }
1273
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1274
- import_design_system3.BasicInput,
1416
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1417
+ import_design_system4.BasicInput,
1275
1418
  {
1276
1419
  id: inputId,
1277
1420
  value: value === null || value === void 0 ? "" : String(value),
@@ -1283,9 +1426,9 @@ function FieldControl({ field, value, onChange, disabled, id }) {
1283
1426
  }
1284
1427
  function CheckboxControl({ field, value, onChange, disabled, id }) {
1285
1428
  const unanswered = value === null || value === void 0;
1286
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-center gap-2", children: [
1287
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1288
- 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,
1289
1432
  {
1290
1433
  id,
1291
1434
  checked: value === true,
@@ -1294,10 +1437,10 @@ function CheckboxControl({ field, value, onChange, disabled, id }) {
1294
1437
  onCheckedChange: (next) => onChange(next === true)
1295
1438
  }
1296
1439
  ),
1297
- 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: [
1298
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-xs text-muted-foreground", children: value === true ? "Yes" : "No" }),
1299
- field.required ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1300
- 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,
1301
1444
  {
1302
1445
  size: "sm",
1303
1446
  variant: "ghost",
@@ -1314,9 +1457,9 @@ function JsonControl({ field, value, onChange, disabled, id }) {
1314
1457
  const initial = value === null || value === void 0 ? "" : typeof value === "string" ? value : JSON.stringify(value, null, 2);
1315
1458
  const [text, setText] = (0, import_react7.useState)(initial);
1316
1459
  const [broken, setBroken] = (0, import_react7.useState)(null);
1317
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-col gap-1", children: [
1318
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1319
- 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,
1320
1463
  {
1321
1464
  id,
1322
1465
  rows: 4,
@@ -1335,38 +1478,54 @@ function JsonControl({ field, value, onChange, disabled, id }) {
1335
1478
  onChange(JSON.parse(next));
1336
1479
  setBroken(null);
1337
1480
  } catch (thrown) {
1338
- 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
+ );
1339
1489
  }
1340
1490
  }
1341
1491
  }
1342
1492
  ),
1343
- broken ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "text-xs text-destructive", children: [
1344
- fieldName(field),
1345
- " is not readable as structured data yet: ",
1346
- broken
1347
- ] }) : 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
1348
1507
  ] });
1349
1508
  }
1350
1509
  function OptionControl({ field, value, onChange, disabled, id }) {
1351
1510
  const client = (0, import_react8.useOptionalRecordsClient)();
1352
1511
  const [options, setOptions] = (0, import_react7.useState)(null);
1353
- const [refusal, setRefusal] = (0, import_react7.useState)(null);
1512
+ const [refusal2, setRefusal] = (0, import_react7.useState)(null);
1354
1513
  (0, import_react7.useEffect)(() => {
1355
1514
  if (!client) return;
1356
1515
  let cancelled = false;
1357
1516
  void client.fieldOptions({ field_id: field.id }).then((result) => {
1358
1517
  if (cancelled) return;
1359
1518
  if (result.ok) setOptions(result.data ?? []);
1360
- else setRefusal(refusalLineForAPerson(result.error));
1519
+ else setRefusal(result.error);
1361
1520
  });
1362
1521
  return () => {
1363
1522
  cancelled = true;
1364
1523
  };
1365
1524
  }, [client, field.id]);
1366
1525
  if (!client) {
1367
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-col gap-1", children: [
1368
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1369
- 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,
1370
1529
  {
1371
1530
  id,
1372
1531
  value: value === null || value === void 0 ? "" : String(value),
@@ -1374,20 +1533,21 @@ function OptionControl({ field, value, onChange, disabled, id }) {
1374
1533
  onChange: (e) => onChange(e.target.value === "" ? null : e.target.value)
1375
1534
  }
1376
1535
  ),
1377
- /* @__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." })
1378
1537
  ] });
1379
1538
  }
1380
- if (refusal) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { id, className: "text-xs text-destructive", children: refusal });
1381
- 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" });
1382
1542
  if (options.length === 0) {
1383
- 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.` });
1384
1544
  }
1385
1545
  const titleOf = (option) => recordName(option.data, null, "Unnamed choice");
1386
1546
  if (field.multi) {
1387
1547
  const chosen = options.filter((option) => (0, import_records4.choiceValuesOf)(value).some((v) => (0, import_records4.isTheChosen)(option, v)));
1388
1548
  const picked = new Set(chosen.map((option) => (0, import_records4.optionKey)(option)));
1389
- 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)(
1390
- 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,
1391
1551
  {
1392
1552
  type: "button",
1393
1553
  size: "sm",
@@ -1405,15 +1565,15 @@ function OptionControl({ field, value, onChange, disabled, id }) {
1405
1565
  )) });
1406
1566
  }
1407
1567
  const current = options.find((option) => (0, import_records4.isTheChosen)(option, value));
1408
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1409
- import_design_system3.Select,
1568
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1569
+ import_design_system4.Select,
1410
1570
  {
1411
1571
  value: current ? (0, import_records4.optionKey)(current) : "",
1412
1572
  disabled: disabled ?? false,
1413
1573
  onValueChange: (next) => onChange(next === "" ? null : next),
1414
1574
  children: [
1415
- /* @__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" }) }),
1416
- /* @__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)) })
1417
1577
  ]
1418
1578
  }
1419
1579
  );
@@ -1429,15 +1589,15 @@ function RecordChip({
1429
1589
  // name. So it caps at its container and ends in an ellipsis with the whole name in
1430
1590
  // its tooltip, rather than being cut mid-word by whatever box it landed in
1431
1591
  // (lane FIX-14, 2026-09-22).
1432
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system3.Badge, { variant: "secondary", className: (0, import_design_system3.cn)("max-w-full gap-1 text-[11px]", className), title, children: [
1433
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "min-w-0 truncate", children: title }),
1434
- onRemove ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", onClick: onRemove, "aria-label": `Remove ${title}`, className: "shrink-0 opacity-60 hover:opacity-100", children: "\xD7" }) : null
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
1435
1595
  ] })
1436
1596
  );
1437
1597
  }
1438
1598
 
1439
1599
  // src/PersonPicker.tsx
1440
- var import_jsx_runtime5 = require("react/jsx-runtime");
1600
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1441
1601
  function personFromRow(row) {
1442
1602
  const data = row.document ?? {};
1443
1603
  const userId = data["user_id"];
@@ -1490,7 +1650,7 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1490
1650
  ]);
1491
1651
  setMembers(roster);
1492
1652
  if (records && !records.ok) {
1493
- setError(refusalLineForAPerson(records.error));
1653
+ setError(records.error);
1494
1654
  return;
1495
1655
  }
1496
1656
  setPeople(records ? records.data.rows.map(personFromRow).filter((p) => p.userId !== null) : []);
@@ -1511,10 +1671,10 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1511
1671
  };
1512
1672
  }, [client, personTable, picked.length, people.length]);
1513
1673
  if (!membersPort || !client) {
1514
- 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 });
1515
1675
  }
1516
1676
  if (!personTable) {
1517
- 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: [
1518
1678
  fieldName(field),
1519
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."
1520
1680
  ] });
@@ -1532,7 +1692,7 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1532
1692
  const resolved = await personRecordForMember(client, personTable, people, member);
1533
1693
  setBusy(false);
1534
1694
  if (!resolved.ok) {
1535
- setError(refusalLineForAPerson(resolved.error));
1695
+ setError(resolved.error);
1536
1696
  return;
1537
1697
  }
1538
1698
  if (!people.some((p) => p.id === resolved.data)) {
@@ -1558,8 +1718,8 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1558
1718
  const pickedUserIds = new Set(
1559
1719
  picked.map((recordId) => people.find((p) => p.id === recordId)?.userId).filter(Boolean)
1560
1720
  );
1561
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: (0, import_design_system4.cn)("flex flex-wrap items-center gap-1", className), children: [
1562
- 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)(
1563
1723
  RecordChip,
1564
1724
  {
1565
1725
  title: nameOfRecord(recordId),
@@ -1567,9 +1727,9 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1567
1727
  },
1568
1728
  recordId
1569
1729
  )),
1570
- disabled ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_design_system4.Popover, { open, onOpenChange: setOpen, children: [
1571
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system4.PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1572
- 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,
1573
1733
  {
1574
1734
  id,
1575
1735
  type: "button",
@@ -1579,9 +1739,9 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1579
1739
  children: picked.length === 0 ? "Pick a person" : "Change"
1580
1740
  }
1581
1741
  ) }),
1582
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_design_system4.PopoverContent, { sizing: "content", className: "p-1", align: "start", children: [
1583
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1584
- 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,
1585
1745
  {
1586
1746
  value: search,
1587
1747
  onChange: (e) => setSearch(e.target.value),
@@ -1589,29 +1749,29 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1589
1749
  className: "mb-1 h-7 text-xs"
1590
1750
  }
1591
1751
  ),
1592
- error ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "p-2 text-xs text-destructive", children: error }) : null,
1593
- members === null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "p-2 text-xs text-muted-foreground", children: "Reading the members\u2026" }) : null,
1594
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_design_system4.ScrollArea, { className: "max-h-56", children: [
1595
- /* @__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)(
1596
1756
  "button",
1597
1757
  {
1598
1758
  type: "button",
1599
1759
  disabled: busy,
1600
1760
  onClick: () => void pick(member),
1601
- className: (0, import_design_system4.cn)(
1761
+ className: (0, import_design_system5.cn)(
1602
1762
  "w-full truncate rounded px-2 py-1 text-left text-xs hover:bg-muted",
1603
1763
  pickedUserIds.has(member.userId) ? "bg-accent text-accent-foreground" : ""
1604
1764
  ),
1605
1765
  children: [
1606
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: memberName(member) }),
1607
- 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: [
1608
1768
  "\xB7 ",
1609
1769
  member.email
1610
1770
  ] }) : null
1611
1771
  ]
1612
1772
  }
1613
1773
  ) }, member.userId)) }),
1614
- 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
1615
1775
  ] })
1616
1776
  ] })
1617
1777
  ] })
@@ -1624,55 +1784,6 @@ var import_core = require("@ai-matrx/records/core");
1624
1784
  var import_react16 = require("@ai-matrx/records/react");
1625
1785
  var import_design_system7 = require("@ai-matrx/design-system");
1626
1786
 
1627
- // src/Refusal.tsx
1628
- var import_design_system5 = require("@ai-matrx/design-system");
1629
- var import_jsx_runtime6 = require("react/jsx-runtime");
1630
- function hintIsMachineIdentity(hint) {
1631
- return isMachineIdentity(hint);
1632
- }
1633
- function hintForAPerson(hint) {
1634
- const said = (hint ?? "").trim();
1635
- if (said === "") return null;
1636
- return isMachineIdentity(said) ? null : said;
1637
- }
1638
- function RefusalNotice({
1639
- error,
1640
- className,
1641
- actions
1642
- }) {
1643
- const plain = refusalForAPerson(error);
1644
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1645
- import_design_system5.Alert,
1646
- {
1647
- variant: "destructive",
1648
- className: (0, import_design_system5.cn)("text-xs", className),
1649
- ...plain.forEngineers ? { title: plain.forEngineers } : {},
1650
- children: [
1651
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system5.AlertTitle, { className: "text-xs font-medium", children: plain.title }),
1652
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system5.AlertDescription, { className: "space-y-1 text-xs", children: [
1653
- plain.sentence.replace(/\.$/, "") === plain.title ? null : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: plain.sentence }),
1654
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "opacity-80", children: plain.remedy }),
1655
- plain.forEngineers ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1656
- "p",
1657
- {
1658
- className: "sr-only",
1659
- "aria-hidden": "true",
1660
- "data-for-engineers": "",
1661
- ...error.sqlstate ? { "data-sqlstate": error.sqlstate } : {},
1662
- children: plain.forEngineers
1663
- }
1664
- ) : null,
1665
- actions
1666
- ] })
1667
- ]
1668
- }
1669
- );
1670
- }
1671
- function RefusalLine({ error, className }) {
1672
- const plain = refusalForAPerson(error);
1673
- 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) });
1674
- }
1675
-
1676
1787
  // src/whoChangedSource.tsx
1677
1788
  var import_react11 = require("react");
1678
1789
  var import_react12 = require("@ai-matrx/records/react");
@@ -2508,7 +2619,7 @@ function GridCell({
2508
2619
  );
2509
2620
  const value = editing ? editing.valueFor(row, field) : row.document?.[field.key];
2510
2621
  const state = editing ? editing.stateOf(row.id, field.key) : null;
2511
- const refusal = editing ? editing.refusalOf(row.id, field.key) : null;
2622
+ const refusal2 = editing ? editing.refusalOf(row.id, field.key) : null;
2512
2623
  const editable = Boolean(editing) && canWrite && fieldIsEditable(field);
2513
2624
  if (open && editing) {
2514
2625
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
@@ -2537,8 +2648,8 @@ function GridCell({
2537
2648
  }
2538
2649
  ),
2539
2650
  answered ? null : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-xs text-muted-foreground", title: "nobody has answered this yet", children: "\u2014" }),
2540
- refusal ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "flex items-center gap-1 rounded border border-destructive/40 px-1", children: [
2541
- /* @__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 }),
2542
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" })
2543
2654
  ] }) : null
2544
2655
  ] });
@@ -2613,11 +2724,11 @@ function GridCell({
2613
2724
  ),
2614
2725
  onAskWhoChanged ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(WhoBadge, { field, row, onAskWhoChanged }) : null,
2615
2726
  state === "saving" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "px-1 text-[10px] text-muted-foreground", children: "Saving\u2026" }) : null,
2616
- refusal ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "flex flex-col gap-0.5 rounded border border-destructive/40 p-1", children: [
2617
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(RefusalLine, { error: refusal.error }),
2618
- 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: [
2619
2730
  "Theirs: ",
2620
- /* @__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) })
2621
2732
  ] }) : null,
2622
2733
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "flex gap-1", children: [
2623
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" }),
@@ -3316,7 +3427,20 @@ function EnrichPanel({ tableId, fieldId, className }) {
3316
3427
  " in one go."
3317
3428
  ] }) : null
3318
3429
  ] }) : null,
3319
- 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,
3320
3444
  field ? null : null
3321
3445
  ] }, row.field_id);
3322
3446
  }) });
@@ -3361,15 +3485,15 @@ function PastePreview({
3361
3485
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PlanTable, { plan }),
3362
3486
  plan.refusals.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { "data-matrx-paste-refusals": true, className: "flex flex-col gap-1", children: [
3363
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:` }),
3364
- /* @__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: [
3365
3489
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "font-medium", children: [
3366
3490
  "Row ",
3367
- refusal.fromLine,
3491
+ refusal2.fromLine,
3368
3492
  ", ",
3369
- refusal.column
3493
+ refusal2.column
3370
3494
  ] }),
3371
3495
  " \u2014 ",
3372
- refusal.why
3496
+ refusal2.why
3373
3497
  ] }, index)) }),
3374
3498
  plan.refusals.length > 12 ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
3375
3499
  plan.refusals.length - 12,
@@ -3397,7 +3521,13 @@ function PastePreview({
3397
3521
  setAdding(group.fieldKey);
3398
3522
  void onAddOptions(group.fieldKey, group.words).catch(
3399
3523
  (err) => setAddFailed(
3400
- `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
+ )
3401
3531
  )
3402
3532
  ).finally(() => setAdding(null));
3403
3533
  },
@@ -3405,7 +3535,7 @@ function PastePreview({
3405
3535
  }
3406
3536
  )
3407
3537
  ] }, group.fieldKey)),
3408
- 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
3409
3539
  ] }) : null,
3410
3540
  plan.pastRightEdge > 0 ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
3411
3541
  plan.pastRightEdge,
@@ -3585,19 +3715,19 @@ function planPastedBlock(args) {
3585
3715
  accepted[key] = judged.value;
3586
3716
  }
3587
3717
  const predicted = (0, import_core2.predictValueRefusals)(args.fields, accepted);
3588
- for (const refusal of predicted) {
3589
- const key = refusal.field_key;
3718
+ for (const refusal2 of predicted) {
3719
+ const key = refusal2.field_key;
3590
3720
  if (key === void 0) continue;
3591
3721
  const cell = built.find((b) => b.key === key && b.refusal === null);
3592
3722
  if (!cell) continue;
3593
- cell.refusal = refusal.message;
3723
+ cell.refusal = refusal2.message;
3594
3724
  cell.value = null;
3595
3725
  delete accepted[key];
3596
3726
  refusals.push({
3597
3727
  fromLine,
3598
3728
  column: fieldName(byKey.get(key) ?? { key }),
3599
3729
  raw: cell.raw,
3600
- why: refusal.message
3730
+ why: refusal2.message
3601
3731
  });
3602
3732
  }
3603
3733
  cells += Object.keys(accepted).length;
@@ -4361,18 +4491,24 @@ function ExportMenu({ tableId, rows, label, className }) {
4361
4491
  size: "sm",
4362
4492
  variant: "outline",
4363
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);
4364
4502
  try {
4365
- void (0, import_core3.exportXlsx)(fields.data ?? [], exported(), name).then(
4503
+ (0, import_core3.exportXlsx)(fields.data ?? [], exported(), name).then(
4366
4504
  (bytes) => download(
4367
4505
  `${name}.xlsx`,
4368
4506
  "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
4369
4507
  bytes
4370
4508
  )
4371
- );
4509
+ ).catch(failed);
4372
4510
  } catch (thrown) {
4373
- setFailure(
4374
- `The spreadsheet could not be written (${thrown instanceof Error ? thrown.message : String(thrown)}). The CSV beside this button carries the same rows.`
4375
- );
4511
+ failed(thrown);
4376
4512
  }
4377
4513
  },
4378
4514
  children: "XLSX"
@@ -4388,7 +4524,7 @@ function ExportMenu({ tableId, rows, label, className }) {
4388
4524
  human: () => (0, import_core3.exportCsv)(fields.data ?? [], exported())
4389
4525
  }
4390
4526
  ),
4391
- 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
4392
4528
  ] });
4393
4529
  }
4394
4530
 
@@ -4489,8 +4625,47 @@ function FilePicker({
4489
4625
  // src/ImportWizard.tsx
4490
4626
  var import_react26 = require("react");
4491
4627
  var import_react27 = require("@ai-matrx/records/react");
4492
- var import_core4 = require("@ai-matrx/records/core");
4628
+ var import_core5 = require("@ai-matrx/records/core");
4493
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
4494
4669
  var import_jsx_runtime16 = require("react/jsx-runtime");
4495
4670
  var BATCH = 250;
4496
4671
  var SAMPLES = 40;
@@ -4543,9 +4718,13 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4543
4718
  const [openRun, setOpenRun] = (0, import_react26.useState)(null);
4544
4719
  const [runRows, setRunRows] = (0, import_react26.useState)(null);
4545
4720
  const [runProblem, setRunProblem] = (0, import_react26.useState)(null);
4721
+ const [runsProblem, setRunsProblem] = (0, import_react26.useState)(null);
4546
4722
  const loadRuns = (0, import_react26.useCallback)(async () => {
4547
4723
  const answered = await client.imports({ table_id: tableId, limit: 10 });
4548
- if (answered.ok) setRuns(answered.data);
4724
+ if (answered.ok) {
4725
+ setRuns(answered.data);
4726
+ setRunsProblem(null);
4727
+ } else setRunsProblem(answered.error);
4549
4728
  }, [client, tableId]);
4550
4729
  (0, import_react26.useEffect)(() => {
4551
4730
  void loadRuns();
@@ -4566,7 +4745,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4566
4745
  limit: 200
4567
4746
  });
4568
4747
  if (!answered.ok) {
4569
- setRunProblem(answered.error.message);
4748
+ setRunProblem(answered.error);
4570
4749
  return;
4571
4750
  }
4572
4751
  setRunRows({
@@ -4593,7 +4772,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4593
4772
  try {
4594
4773
  const xlsx = file.name.toLowerCase().endsWith(".xlsx");
4595
4774
  const bytes = await file.arrayBuffer();
4596
- 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));
4597
4776
  setFormat(xlsx ? "xlsx" : "csv");
4598
4777
  setFileName(file.name);
4599
4778
  setFileBytes(file.size);
@@ -4607,7 +4786,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4607
4786
  }));
4608
4787
  const answered = await client.importPlan({ table_id: tableId, columns });
4609
4788
  if (!answered.ok) {
4610
- setProblem(answered.error.message);
4789
+ setProblem(answered.error);
4611
4790
  setPhase("waiting");
4612
4791
  return;
4613
4792
  }
@@ -4624,7 +4803,14 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4624
4803
  setMapping(next);
4625
4804
  setPhase("ready");
4626
4805
  } catch (thrown) {
4627
- 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
+ );
4628
4814
  setPhase("waiting");
4629
4815
  }
4630
4816
  },
@@ -4641,7 +4827,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4641
4827
  setPhase("declaring");
4642
4828
  const made = await client.importDeclareColumns({ table_id: tableId, rows, mapping });
4643
4829
  if (!made.ok) {
4644
- setProblem(made.error.message);
4830
+ setProblem(made.error);
4645
4831
  setPhase("ready");
4646
4832
  return;
4647
4833
  }
@@ -4660,7 +4846,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4660
4846
  policy: { on_duplicate: onDuplicate, unmapped }
4661
4847
  });
4662
4848
  if (!opened.ok) {
4663
- setProblem(opened.error.message);
4849
+ setProblem(opened.error);
4664
4850
  setPhase("ready");
4665
4851
  return;
4666
4852
  }
@@ -4679,7 +4865,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4679
4865
  const batch = rows.slice(at, at + take2);
4680
4866
  const answered = await client.importRows({ import_id: importId, rows: batch, mapping });
4681
4867
  if (!answered.ok) {
4682
- setProblem(answered.error.message);
4868
+ setProblem(answered.error);
4683
4869
  setOutcomes([...collected]);
4684
4870
  setLedger({ ...told, final: false });
4685
4871
  setPhase("ready");
@@ -4698,7 +4884,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4698
4884
  }
4699
4885
  const finished = await client.importFinish({ import_id: importId, unmapped });
4700
4886
  if (!finished.ok) {
4701
- setProblem(finished.error.message);
4887
+ setProblem(finished.error);
4702
4888
  setPhase("done");
4703
4889
  return;
4704
4890
  }
@@ -4736,6 +4922,11 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4736
4922
  const titleMissing = Boolean(titleKey) && titleMappedTo === null;
4737
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";
4738
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]);
4739
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: [
4740
4931
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center gap-2", children: [
4741
4932
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "font-medium", children: "Import" }),
@@ -4763,7 +4954,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4763
4954
  } : {}
4764
4955
  }
4765
4956
  ) : null,
4766
- 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,
4767
4958
  phase === "reading" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-muted-foreground", children: "Reading the file\u2026" }) : null,
4768
4959
  phase === "planning" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-muted-foreground", children: "Working out what each column is\u2026" }) : null,
4769
4960
  plan && parsed ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
@@ -4860,7 +5051,28 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4860
5051
  column.unit ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "ml-1 opacity-60", children: column.unit }) : null,
4861
5052
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "mt-0.5 text-muted-foreground", children: column.why }),
4862
5053
  column.ambiguous ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-destructive", children: "Check a date you know before you run this." }) : null,
4863
- 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
4864
5076
  ] }),
4865
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 ") })
4866
5078
  ]
@@ -4924,12 +5136,34 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4924
5136
  )
4925
5137
  ] })
4926
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,
4927
5161
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center gap-2", children: [
4928
5162
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4929
5163
  import_design_system13.Button,
4930
5164
  {
4931
5165
  size: "sm",
4932
- disabled: busy || !rights.write || rows.length === 0 || titleMissing,
5166
+ disabled: busy || !rights.write || rows.length === 0 || titleMissing || ruleVerdicts.length > 0 && !goAheadAnyway,
4933
5167
  onClick: () => void run(),
4934
5168
  children: phase === "declaring" ? "Adding the columns\u2026" : phase === "writing" ? `Writing\u2026 ${progress} of ${rows.length}` : `Import ${rows.length} row${rows.length === 1 ? "" : "s"}`
4935
5169
  }
@@ -4984,7 +5218,12 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4984
5218
  ] }) }),
4985
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: [
4986
5220
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("td", { className: "px-2 py-1", children: o.row }),
4987
- /* @__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" }),
4988
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 ") : "" })
4989
5228
  ] }, `${o.outcome}-${o.row}`)) })
4990
5229
  ] }) }) : null,
@@ -5007,6 +5246,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
5007
5246
  ] }) : null
5008
5247
  ] }) : null
5009
5248
  ] }) : null,
5249
+ runsProblem ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(RefusalNotice, { error: runsProblem }) : null,
5010
5250
  runs && runs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
5011
5251
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system13.Separator, {}),
5012
5252
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "font-medium", children: "Imports into this table" }),
@@ -5039,7 +5279,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
5039
5279
  }
5040
5280
  ) : null
5041
5281
  ] }),
5042
- openRun === r.import_id ? runProblem ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-destructive", children: runProblem }) : runRows ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
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: [
5043
5283
  runRows.note ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-muted-foreground", children: runRows.note }) : null,
5044
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: [
5045
5285
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("thead", { className: "sticky top-0 bg-muted", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("tr", { children: [
@@ -5051,7 +5291,16 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
5051
5291
  const source = row.source ?? {};
5052
5292
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("tr", { className: "border-t align-top", children: [
5053
5293
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("td", { className: "px-2 py-1", children: String(row.row ?? "") }),
5054
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("td", { className: "px-2 py-1 text-destructive", children: String(row.reason ?? "") }),
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
+ ) }),
5055
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 ") })
5056
5305
  ] }, `${r.import_id}-${String(row.row ?? i)}`);
5057
5306
  }) })
@@ -5270,7 +5519,9 @@ function keyFor(label) {
5270
5519
  if (token === "") return "";
5271
5520
  return /^[a-z]/.test(token) ? token : `f_${token}`;
5272
5521
  }
5273
- 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}`;
5274
5525
  function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }) {
5275
5526
  const table = (0, import_react31.useTable)(tableId);
5276
5527
  const fields = (0, import_react31.useFields)(tableId);
@@ -5570,14 +5821,20 @@ function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }
5570
5821
  ] }, `${rule.kind}-${index}`))
5571
5822
  ] }),
5572
5823
  mutation.error ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(RefusalNotice, { error: mutation.error }) : null,
5573
- missing ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "text-[11px] text-destructive", children: missing }) : null,
5574
- 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,
5575
5831
  /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
5576
5832
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5577
5833
  import_design_system15.Button,
5578
5834
  {
5579
5835
  size: "sm",
5580
5836
  disabled: mutation.saving || missing !== null,
5837
+ ...missing ? { "aria-describedby": "field-editor-missing" } : {},
5581
5838
  onClick: () => void save(),
5582
5839
  children: mutation.saving ? "Saving\u2026" : field ? EDIT_FIELD_SAVE_LABEL : ADD_FIELD_SAVE_LABEL
5583
5840
  }
@@ -6790,7 +7047,7 @@ var import_design_system22 = require("@ai-matrx/design-system");
6790
7047
  // src/RecordForm.tsx
6791
7048
  var import_react41 = require("react");
6792
7049
  var import_react42 = require("@ai-matrx/records/react");
6793
- var import_core5 = require("@ai-matrx/records/core");
7050
+ var import_core6 = require("@ai-matrx/records/core");
6794
7051
  var import_design_system20 = require("@ai-matrx/design-system");
6795
7052
 
6796
7053
  // src/systemTable.ts
@@ -6995,7 +7252,7 @@ function RecordForm({
6995
7252
  return out;
6996
7253
  }, [draft]);
6997
7254
  const predicted = (0, import_react41.useMemo)(
6998
- () => 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 } : {} }) : [],
6999
7256
  [fields.data, document2, recordType]
7000
7257
  );
7001
7258
  if (fields.error) return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(RefusalNotice, { error: fields.error, className });
@@ -7043,7 +7300,13 @@ function RecordForm({
7043
7300
  },
7044
7301
  field.id
7045
7302
  )) }),
7046
- 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,
7047
7310
  mutation.error ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7048
7311
  RefusalNotice,
7049
7312
  {
@@ -7098,7 +7361,14 @@ function FieldRow({
7098
7361
  return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex min-w-0 flex-col gap-1", children: [
7099
7362
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(FieldLabel, { field, htmlFor: id, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ProvenanceBadge, { document: document2, fieldKey: field.key }) }),
7100
7363
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(FieldControl, { field, value, onChange, id }),
7101
- 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
+ ))
7102
7372
  ] });
7103
7373
  }
7104
7374
  function asWords(value) {
@@ -7124,12 +7394,16 @@ function ShareControl({
7124
7394
  subjectId,
7125
7395
  name,
7126
7396
  may,
7397
+ initiallyOpen = false,
7127
7398
  size = "sm",
7128
7399
  variant = "ghost",
7129
7400
  className
7130
7401
  }) {
7131
7402
  const host = useRecordsUi();
7132
- 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]);
7133
7407
  const asked = useRecordRights(may === void 0 ? subjectId : null);
7134
7408
  const mayShare = may ?? asked.share;
7135
7409
  if (!host.share) return null;
@@ -7707,10 +7981,22 @@ function Held({
7707
7981
  );
7708
7982
  }
7709
7983
  if (pending.kind === "refused") {
7710
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "mt-1 rounded border border-destructive/50 bg-destructive/5 p-2 text-xs", children: [
7711
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { children: pending.verdict.why }),
7712
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_design_system23.Button, { size: "sm", variant: "ghost", className: "mt-1 h-6", onClick: onCancel, children: "Close" })
7713
- ] });
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
+ );
7714
8000
  }
7715
8001
  if (pending.kind === "approval") {
7716
8002
  return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "mt-1 flex flex-col gap-1 rounded border p-2 text-xs", children: [
@@ -8274,7 +8560,7 @@ function ArchivedView({
8274
8560
  const [loading, setLoading] = (0, import_react51.useState)(true);
8275
8561
  const [error, setError] = (0, import_react51.useState)(null);
8276
8562
  const [restoring, setRestoring] = (0, import_react51.useState)(null);
8277
- const [refusal, setRefusal] = (0, import_react51.useState)(null);
8563
+ const [refusal2, setRefusal] = (0, import_react51.useState)(null);
8278
8564
  (0, import_react51.useEffect)(() => {
8279
8565
  if (laneFromHost) setLane(laneFromHost);
8280
8566
  }, [laneFromHost]);
@@ -8351,7 +8637,7 @@ function ArchivedView({
8351
8637
  /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col", children: [
8352
8638
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "truncate text-sm", children: recordName(row.document, titleKey) }),
8353
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) }),
8354
- 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
8355
8641
  ] }),
8356
8642
  mayRestore ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
8357
8643
  import_design_system25.Button,
@@ -8425,7 +8711,7 @@ function ArchivedPortals({
8425
8711
  const [confirming, setConfirming] = (0, import_react53.useState)(null);
8426
8712
  const [typed, setTyped] = (0, import_react53.useState)("");
8427
8713
  const [restoring, setRestoring] = (0, import_react53.useState)(null);
8428
- const [refusal, setRefusal] = (0, import_react53.useState)(null);
8714
+ const [refusal2, setRefusal] = (0, import_react53.useState)(null);
8429
8715
  const [said, setSaid] = (0, import_react53.useState)(null);
8430
8716
  const read = (0, import_react53.useCallback)(async () => {
8431
8717
  const answered = await client.listPortals({ archived: "archived" });
@@ -8541,7 +8827,7 @@ function ArchivedPortals({
8541
8827
  )
8542
8828
  ] })
8543
8829
  ] }) : null,
8544
- 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
8545
8831
  ]
8546
8832
  },
8547
8833
  portal.portal_id
@@ -9106,7 +9392,7 @@ function MyChecklistSteps({ className }) {
9106
9392
  const client = (0, import_react62.useRecordsClient)();
9107
9393
  const { steps, loading, error, refresh } = useMyChecklistSteps();
9108
9394
  const [busy, setBusy] = (0, import_react61.useState)(null);
9109
- const [refusal, setRefusal] = (0, import_react61.useState)(null);
9395
+ const [refusal2, setRefusal] = (0, import_react61.useState)(null);
9110
9396
  const complete = (0, import_react61.useCallback)(
9111
9397
  async (step2, evidence) => {
9112
9398
  setBusy(step2.step_id);
@@ -9129,7 +9415,7 @@ function MyChecklistSteps({ className }) {
9129
9415
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "tabular-nums", children: steps.length })
9130
9416
  ] }),
9131
9417
  error ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(RefusalNotice, { error }) : null,
9132
- refusal ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(RefusalNotice, { error: refusal }) : null,
9418
+ refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(RefusalNotice, { error: refusal2 }) : null,
9133
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)(
9134
9420
  StepRow,
9135
9421
  {
@@ -9306,7 +9592,7 @@ function ChecklistTemplateEditor({
9306
9592
  const client = (0, import_react62.useRecordsClient)();
9307
9593
  const [name, setName] = (0, import_react61.useState)("");
9308
9594
  const [rows, setRows] = (0, import_react61.useState)([{ ...EMPTY_ROW }]);
9309
- const [refusal, setRefusal] = (0, import_react61.useState)(null);
9595
+ const [refusal2, setRefusal] = (0, import_react61.useState)(null);
9310
9596
  const [error, setError] = (0, import_react61.useState)(null);
9311
9597
  const [busy, setBusy] = (0, import_react61.useState)(false);
9312
9598
  const [loading, setLoading] = (0, import_react61.useState)(Boolean(templateId));
@@ -9474,9 +9760,9 @@ function ChecklistTemplateEditor({
9474
9760
  ] }, index)) }),
9475
9761
  /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-center gap-1.5", children: [
9476
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" }),
9477
- /* @__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" })
9478
9764
  ] }),
9479
- 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
9480
9766
  ] });
9481
9767
  }
9482
9768
  function toStepSpec(row, index) {
@@ -10769,6 +11055,7 @@ function BuildOrAsk({
10769
11055
 
10770
11056
  // src/PortalsPanel.tsx
10771
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.";
10772
11059
  function revokeConsequence(person, portalTitle) {
10773
11060
  const who = person.client ? `${person.email} (${person.client})` : person.email;
10774
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.`;
@@ -10798,13 +11085,16 @@ function stateWords(person) {
10798
11085
  };
10799
11086
  }
10800
11087
  var PORTAL_SUGGESTION = "Let each of my customers sign in and see their own jobs and invoices, and nothing else.";
10801
- function PortalsPanel({ tableId, className }) {
11088
+ function PortalsPanel({ tableId, activePortalId, className }) {
10802
11089
  const client = (0, import_react74.useRecordsClient)();
10803
11090
  const host = useRecordsUi();
10804
11091
  const [portals, setPortals] = (0, import_react73.useState)(null);
10805
11092
  const [exposures, setExposures] = (0, import_react73.useState)([]);
10806
11093
  const [listError, setListError] = (0, import_react73.useState)(null);
10807
- 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]);
10808
11098
  const [building, setBuilding] = (0, import_react73.useState)(false);
10809
11099
  const [adding, setAdding] = (0, import_react73.useState)(null);
10810
11100
  const [archiveToken, setArchiveToken] = (0, import_react73.useState)(0);
@@ -10901,59 +11191,73 @@ function PortalsPanel({ tableId, className }) {
10901
11191
  portal.portal_id
10902
11192
  )) })
10903
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,
10904
11195
  /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("ul", { className: "flex flex-col gap-2", children: shown.map((portal) => {
10905
11196
  const url = `${origin}${(0, import_records9.portalPath)(portal.slug)}`;
10906
11197
  const open = openId === portal.portal_id;
10907
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("li", { className: "rounded-md border border-border bg-card p-2.5", children: [
10908
- /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex items-center gap-2", children: [
10909
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: portal.title }),
10910
- portal.is_active ? null : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_design_system36.Badge, { variant: "outline", children: "closed" })
10911
- ] }),
10912
- /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("p", { className: "mt-0.5 text-xs text-muted-foreground", children: [
10913
- "Clients come from ",
10914
- portal.client_table,
10915
- portal.tables === 1 ? ", and see 1 Table" : `, and see ${portal.tables} Tables`,
10916
- "."
10917
- ] }),
10918
- /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("p", { className: "mt-1.5 text-xs", children: [
10919
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "font-medium", children: portal.signed_in }),
10920
- " signed in",
10921
- " \xB7 ",
10922
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "font-medium", children: portal.invited - portal.signed_in }),
10923
- " invited and waiting"
10924
- ] }),
10925
- /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "mt-2 flex flex-wrap items-center gap-1.5", children: [
10926
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(CopyLink, { url }),
10927
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
10928
- import_design_system36.Button,
10929
- {
10930
- size: "sm",
10931
- variant: "ghost",
10932
- onClick: () => setOpenId(open ? null : portal.portal_id),
10933
- children: open ? "Close" : "Open it"
10934
- }
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"
10935
11207
  ),
10936
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
10937
- ArchivePortalControl,
10938
- {
10939
- portal,
10940
- onArchived: () => {
10941
- setArchiveToken((t) => t + 1);
10942
- 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()
10943
11255
  }
10944
- }
10945
- )
10946
- ] }),
10947
- open ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
10948
- PortalDetail,
10949
- {
10950
- portalId: portal.portal_id,
10951
- ...tableId ? { tableId } : {},
10952
- origin,
10953
- onChanged: () => void load()
10954
- }
10955
- ) : null
10956
- ] }, portal.portal_id);
11256
+ ) : null
11257
+ ]
11258
+ },
11259
+ portal.portal_id
11260
+ );
10957
11261
  }) }),
10958
11262
  /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ArchivedPortals, { refreshToken: archiveToken, onRestored: () => void load() })
10959
11263
  ] });
@@ -11711,7 +12015,17 @@ function DigestScheduler({
11711
12015
  preview ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "rounded-md border bg-muted/40 p-2.5 text-xs", children: [
11712
12016
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("p", { className: "font-medium", children: preview.subject }),
11713
12017
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("p", { className: "mt-1 text-muted-foreground", children: preview.body }),
11714
- 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,
11715
12029
  preview.entered.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("p", { className: "mt-1", children: [
11716
12030
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "font-medium", children: "Arrived:" }),
11717
12031
  " ",
@@ -11733,6 +12047,7 @@ var import_react77 = require("react");
11733
12047
  var import_react78 = require("@ai-matrx/records/react");
11734
12048
  var import_design_system38 = require("@ai-matrx/design-system");
11735
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.";
11736
12051
  function whenItFires(subscription) {
11737
12052
  if (subscription.cadence === "instant") return "as it happens";
11738
12053
  const every = subscription.cadence === "hourly" ? "an hourly summary" : subscription.cadence === "weekly" ? "a weekly summary" : "a daily summary";
@@ -11740,7 +12055,7 @@ function whenItFires(subscription) {
11740
12055
  }
11741
12056
  var CHANNEL_WORDS2 = SUBSCRIPTION_CHANNEL_LABEL;
11742
12057
  var DIGEST_SUGGESTION = "Email me a summary of this every Monday at 8 in the morning.";
11743
- function SubscriptionsPanel({ tableId, className }) {
12058
+ function SubscriptionsPanel({ tableId, activeRuleId, className }) {
11744
12059
  const client = (0, import_react78.useRecordsClient)();
11745
12060
  const [rows, setRows] = (0, import_react77.useState)(null);
11746
12061
  const [error, setError] = (0, import_react77.useState)(null);
@@ -11831,81 +12146,104 @@ function SubscriptionsPanel({ tableId, className }) {
11831
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."
11832
12147
  }
11833
12148
  ) : null,
11834
- /* @__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: [
11835
- /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "flex items-start gap-2", children: [
11836
- /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "min-w-0 flex-1", children: [
11837
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "truncate text-sm font-medium", children: subscription.name }),
11838
- /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("p", { className: "mt-0.5 text-xs text-muted-foreground", children: [
11839
- CHANNEL_WORDS2[subscription.channel] ?? `on the ${subscription.channel} channel`,
11840
- " \xB7 ",
11841
- whenItFires(subscription)
11842
- ] })
11843
- ] }),
11844
- subscription.i_may_mute ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
11845
- import_design_system38.Switch,
11846
- {
11847
- checked: !subscription.muted,
11848
- disabled: busy === subscription.rule_id,
11849
- "aria-label": `Tell me about ${subscription.name}`,
11850
- onCheckedChange: (on) => void flip(subscription, on)
11851
- }
11852
- ) : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_design_system38.Badge, { variant: "secondary", children: "someone else's" })
11853
- ] }),
11854
- /* @__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." }),
11855
- /* @__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: [
11856
- subscription.next_digest_at ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { children: [
11857
- "Next summary ",
11858
- new Date(subscription.next_digest_at).toLocaleString()
11859
- ] }) : subscription.cadence === "instant" ? null : subscription.muted ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { children: "No next summary while it is off." }) : null,
11860
- subscription.last_sent_at ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { children: [
11861
- "Last told you ",
11862
- new Date(subscription.last_sent_at).toLocaleString()
11863
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { children: "It has not told you anything yet." }),
11864
- subscription.quiet_hours ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { children: [
11865
- "Not between ",
11866
- subscription.quiet_hours.start,
11867
- " and ",
11868
- subscription.quiet_hours.end,
11869
- " \u2014 a send inside those hours waits until they end."
11870
- ] }) : null,
11871
- subscription.cadence === "instant" ? null : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
11872
- import_design_system38.Button,
11873
- {
11874
- size: "sm",
11875
- variant: "ghost",
11876
- disabled: busy === subscription.rule_id,
11877
- onClick: () => void showOne(subscription),
11878
- children: "Send me a preview now"
11879
- }
11880
- )
11881
- ] }),
11882
- 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: [
11883
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "font-medium", children: preview.subject }),
11884
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "mt-1 text-muted-foreground", children: preview.body }),
11885
- preview.incomplete ? (
11886
- // Absent or honest: a subscription that cannot produce a summary
11887
- // says which piece is missing instead of showing an empty one.
11888
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "mt-1 text-destructive", children: preview.incomplete })
11889
- ) : null,
11890
- preview.entered.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("p", { className: "mt-1", children: [
11891
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "font-medium", children: "Arrived:" }),
11892
- " ",
11893
- preview.entered.map((e) => e.name).join(", ")
11894
- ] }) : null,
11895
- preview.left.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("p", { className: "mt-1", children: [
11896
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "font-medium", children: "Left:" }),
11897
- " ",
11898
- preview.left.map((e) => e.name).join(", ")
11899
- ] }) : null,
11900
- preview.changed.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("p", { className: "mt-1", children: [
11901
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "font-medium", children: "Changed:" }),
11902
- " ",
11903
- preview.changed.map((e) => e.name).join(", ")
11904
- ] }) : null,
11905
- /* @__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." }),
11906
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_design_system38.Button, { size: "sm", variant: "ghost", className: "mt-1", onClick: () => setPreview(null), children: "Close" })
11907
- ] }) : null
11908
- ] }, 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
+ )) })
11909
12247
  ] });
11910
12248
  }
11911
12249
 
@@ -11960,9 +12298,16 @@ function ConnectedFormRunner(props) {
11960
12298
  const submit = (0, import_react81.useCallback)(
11961
12299
  async (values) => {
11962
12300
  if (!client) {
12301
+ const said = "This form cannot send answers from here, so nothing was sent.";
11963
12302
  return {
11964
12303
  ok: false,
11965
- 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
+ )
11966
12311
  };
11967
12312
  }
11968
12313
  const document2 = {
@@ -11975,7 +12320,7 @@ function ConnectedFormRunner(props) {
11975
12320
  })
11976
12321
  };
11977
12322
  const written = await client.recordWrite({ table_id: form.subject, data: document2 });
11978
- if (!written.ok) return { ok: false, message: written.error.message };
12323
+ if (!written.ok) return { ok: false, message: written.error.message, error: written.error };
11979
12324
  return { ok: true, recordId: written.data };
11980
12325
  },
11981
12326
  [client, form]
@@ -12017,7 +12362,7 @@ function FormStage({
12017
12362
  const [answers, setAnswers] = (0, import_react81.useState)({});
12018
12363
  const [at, setAt] = (0, import_react81.useState)(0);
12019
12364
  const [error, setError] = (0, import_react81.useState)(null);
12020
- const [refusal, setRefusal] = (0, import_react81.useState)(null);
12365
+ const [refusal2, setRefusal] = (0, import_react81.useState)(null);
12021
12366
  const [writing, setWriting] = (0, import_react81.useState)(false);
12022
12367
  const [done, setDone] = (0, import_react81.useState)(null);
12023
12368
  const [hidden, setHidden] = (0, import_react81.useState)({});
@@ -12086,12 +12431,21 @@ function FormStage({
12086
12431
  const outcome = await onSubmit(values);
12087
12432
  setWriting(false);
12088
12433
  if (!outcome.ok) {
12089
- 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
+ });
12090
12444
  return;
12091
12445
  }
12092
12446
  setDone(outcome.message ?? "sent");
12093
12447
  onSubmitted?.(outcome.recordId ?? null);
12094
- }, [answers, form, honeypotKey, live, onSubmit, onSubmitted, preview]);
12448
+ }, [answers, form, honeypotKey, live, onSubmit, onSubmitted, preview, questions]);
12095
12449
  function advance() {
12096
12450
  if (!oneAtATime) return;
12097
12451
  if (index < live.length - 1) setAt(index + 1);
@@ -12150,7 +12504,22 @@ function FormStage({
12150
12504
  ] }) : null,
12151
12505
  form.intro && index === 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "text-sm text-muted-foreground", children: form.intro }) : null,
12152
12506
  error ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(RefusalNotice, { error, className: "text-left" }) : null,
12153
- 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,
12154
12523
  /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { ref: stage, className: "flex flex-col gap-4 text-left", children: [
12155
12524
  (oneAtATime ? current ? [current] : [] : live).map((q) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
12156
12525
  Question,
@@ -12224,12 +12593,15 @@ function Question({
12224
12593
  void upload?.(file).then((result) => {
12225
12594
  if (!result) return;
12226
12595
  if (result.ok) onChange(result.fileId);
12227
- else setUploadError(result.reason);
12596
+ else
12597
+ setUploadError(
12598
+ refusal("invalid_argument", result.reason, "Pick another file, or try this one again.")
12599
+ );
12228
12600
  });
12229
12601
  }
12230
12602
  }
12231
12603
  ),
12232
- 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
12233
12605
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(FieldControl, { field, value, onChange, id })
12234
12606
  ] });
12235
12607
  }
@@ -13016,10 +13388,15 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
13016
13388
  onChange: (e) => setDraftBody(e.target.value)
13017
13389
  }
13018
13390
  ),
13019
- 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: [
13020
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("code", { children: token.raw }),
13021
- " \u2014 ",
13022
- 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
+ )
13023
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
13024
13401
  ] });
13025
13402
  }
@@ -13224,7 +13601,21 @@ function SignBlock({ tableId, recordId, render, className }) {
13224
13601
  signature.document_hash.slice(0, 12)
13225
13602
  ] })
13226
13603
  ] }),
13227
- /* @__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 ?? "") })
13228
13619
  ] }, signature.id);
13229
13620
  }) }) : null,
13230
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: [
@@ -13485,14 +13876,14 @@ function NotifyRuleEditor({ tableId, seed, className }) {
13485
13876
  var import_react95 = require("react");
13486
13877
  var import_recharts = require("recharts");
13487
13878
  var import_records11 = require("@ai-matrx/records");
13488
- var import_core6 = require("@ai-matrx/records/core");
13879
+ var import_core7 = require("@ai-matrx/records/core");
13489
13880
  var import_design_system46 = require("@ai-matrx/design-system");
13490
13881
  var import_jsx_runtime49 = require("react/jsx-runtime");
13491
13882
  function pretty(n) {
13492
13883
  return Number.isInteger(n) ? n.toLocaleString() : n.toLocaleString(void 0, { maximumFractionDigits: 2 });
13493
13884
  }
13494
- function asRefusal(block) {
13495
- return (0, import_core6.mapPgError)(
13885
+ function asRefusal2(block) {
13886
+ return (0, import_core7.mapPgError)(
13496
13887
  {
13497
13888
  message: block.refused ?? "",
13498
13889
  ...block.sqlstate ? { code: block.sqlstate } : {}
@@ -13552,7 +13943,7 @@ function ChartBlock({ block, subject, className }) {
13552
13943
  " ms"
13553
13944
  ] }) : null
13554
13945
  ] }),
13555
- 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: [
13556
13947
  /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(Drawing, { kind, points, series, config, onDrill: drill }),
13557
13948
  /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(Values, { points, measure: primary, config, onDrill: drill })
13558
13949
  ] }) })
@@ -14118,13 +14509,14 @@ var import_react99 = require("@ai-matrx/records/react");
14118
14509
  var import_records12 = require("@ai-matrx/records");
14119
14510
  var import_design_system48 = require("@ai-matrx/design-system");
14120
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.";
14121
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.";
14122
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.";
14123
14515
  function formSuggestion(tableName2) {
14124
14516
  const subject = tableName2?.trim() ? tableName2.trim() : "this table";
14125
14517
  return `Make me a form that collects new ${subject} entries and tells me when somebody answers.`;
14126
14518
  }
14127
- function FormsPanel({ tableId, className }) {
14519
+ function FormsPanel({ tableId, activeFormId, className }) {
14128
14520
  const client = (0, import_react99.useRecordsClient)();
14129
14521
  const publishGate = usePublishGate();
14130
14522
  const host = useRecordsUi();
@@ -14135,6 +14527,11 @@ function FormsPanel({ tableId, className }) {
14135
14527
  const [busy, setBusy] = (0, import_react98.useState)(null);
14136
14528
  const [copied, setCopied] = (0, import_react98.useState)(null);
14137
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]);
14138
14535
  const load = (0, import_react98.useCallback)(async () => {
14139
14536
  const answered = await client.forms({ table_id: tableId });
14140
14537
  if (!answered.ok) {
@@ -14183,10 +14580,12 @@ function FormsPanel({ tableId, className }) {
14183
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
14184
14581
  ] }),
14185
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,
14186
14584
  building ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
14187
14585
  FormBuilder,
14188
14586
  {
14189
14587
  tableId,
14588
+ activeFormId: linked ? linked.form_id : null,
14190
14589
  onActiveForm: () => {
14191
14590
  void load();
14192
14591
  }
@@ -14212,50 +14611,62 @@ function FormsPanel({ tableId, className }) {
14212
14611
  ) : null,
14213
14612
  /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("ul", { className: "flex flex-col gap-2", children: forms.map((form) => {
14214
14613
  const url = `${origin}${(0, import_records12.publicFormPath)(form.form_id)}`;
14215
- return /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("li", { className: "rounded-md border p-2.5", children: [
14216
- /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("div", { className: "flex items-center gap-2", children: [
14217
- /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: form.title ?? form.slug }),
14218
- /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(import_design_system48.Badge, { variant: form.state === "open" ? "default" : "secondary", children: form.state })
14219
- ] }),
14220
- /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("p", { className: "mt-0.5 text-xs text-muted-foreground", children: FORM_STATE_WORDS[form.state] }),
14221
- /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("p", { className: "mt-1.5 text-xs", children: [
14222
- /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("span", { className: "font-medium", children: form.in_table }),
14223
- " in the table",
14224
- form.held > 0 ? /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(import_jsx_runtime51.Fragment, { children: [
14225
- " \xB7 ",
14226
- /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("span", { className: "font-medium", children: form.held }),
14227
- " waiting for someone"
14228
- ] }) : null,
14229
- form.rejected > 0 ? /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(import_jsx_runtime51.Fragment, { children: [
14230
- " \xB7 ",
14231
- /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("span", { className: "font-medium", children: form.rejected }),
14232
- " turned away"
14233
- ] }) : null,
14234
- form.submission_cap !== null ? /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("span", { className: "text-muted-foreground", children: [
14235
- " \xB7 stops at ",
14236
- form.submission_cap
14237
- ] }) : null
14238
- ] }),
14239
- /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("div", { className: "mt-2 flex flex-wrap items-center gap-1.5", children: [
14240
- 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,
14241
- rights.structure && !publishGate.blocked ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
14242
- import_design_system48.Button,
14243
- {
14244
- size: "sm",
14245
- variant: "ghost",
14246
- disabled: busy === form.form_id,
14247
- onClick: () => void toggle(form),
14248
- children: busy === form.form_id ? "\u2026" : form.published_at && !form.closed_at ? "Unpublish" : "Publish"
14249
- }
14250
- ) : null
14251
- ] }),
14252
- rights.structure && publishGate.why ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("p", { className: "mt-1.5 text-xs text-muted-foreground", children: publishGate.why }) : null,
14253
- 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: [
14254
- "This browser would not let the page copy for you, so here it is to copy by hand:",
14255
- " ",
14256
- url
14257
- ] }) : null
14258
- ] }, 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
+ );
14259
14670
  }) }),
14260
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
14261
14672
  ] });
@@ -14912,7 +15323,7 @@ function CaptureRun({ sheetId, face: given, className }) {
14912
15323
  void client.captureOpen({ sheet_id: sheetId }).then((res) => {
14913
15324
  if (cancelled) return;
14914
15325
  if (res.ok) setFace(res.data);
14915
- else setLoadFailed(res.error?.message ?? "This sheet could not be opened.");
15326
+ else setLoadFailed(refusalOr(res.error, "unreachable", "This sheet could not be opened."));
14916
15327
  });
14917
15328
  return () => {
14918
15329
  cancelled = true;
@@ -14959,7 +15370,11 @@ function CaptureRun({ sheetId, face: given, className }) {
14959
15370
  const needed = questions.filter((x) => x.required && !answered(x.field));
14960
15371
  if (needed.length > 0) {
14961
15372
  setMissing(
14962
- `${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
+ )
14963
15378
  );
14964
15379
  setAt(questions.findIndex((x) => x.field === needed[0].field));
14965
15380
  return;
@@ -14990,7 +15405,7 @@ function CaptureRun({ sheetId, face: given, className }) {
14990
15405
  if (online) await sync();
14991
15406
  }
14992
15407
  if (loadFailed) {
14993
- 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" }) }) });
14994
15409
  }
14995
15410
  if (face === void 0) {
14996
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) });
@@ -15021,7 +15436,21 @@ function CaptureRun({ sheetId, face: given, className }) {
15021
15436
  ] });
15022
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: [
15023
15438
  /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("span", { className: "tabular-nums text-muted-foreground", children: new Date(i.captured_at).toLocaleTimeString() }),
15024
- /* @__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." }),
15025
15454
  i.state === "refused" ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
15026
15455
  import_design_system51.Button,
15027
15456
  {
@@ -15141,7 +15570,7 @@ function CaptureRun({ sheetId, face: given, className }) {
15141
15570
  }
15142
15571
  }
15143
15572
  ),
15144
- 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,
15145
15574
  /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("div", { className: "flex items-center gap-2", children: [
15146
15575
  at > 0 ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
15147
15576
  import_design_system51.Button,
@@ -15369,13 +15798,13 @@ function AdHocCaptureSheet({
15369
15798
  setUploadError(null);
15370
15799
  void host.upload?.(file).then((result) => {
15371
15800
  if (result.ok) setFileId(result.fileId);
15372
- else setUploadError(result.reason);
15801
+ else setUploadError(refusal("invalid_argument", result.reason, "Pick another file, or try this one again."));
15373
15802
  });
15374
15803
  }
15375
15804
  }
15376
15805
  ),
15377
15806
  fileId ? /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "Attached." }) : null,
15378
- 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
15379
15808
  ] }) : null,
15380
15809
  /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
15381
15810
  import_design_system52.Textarea,
@@ -15838,17 +16267,17 @@ async function declareTable(client, spec) {
15838
16267
  if (!written.ok) return undo(client, table.data, home.data, spec.name, written.error);
15839
16268
  return { ok: true, data: table.data, homeId: home.data };
15840
16269
  }
15841
- async function undo(client, tableId, homeId, name, refusal) {
16270
+ async function undo(client, tableId, homeId, name, refusal2) {
15842
16271
  const removed = await client.recordDelete({ record_id: tableId });
15843
16272
  if (removed.ok) {
15844
16273
  await client.recordDelete({ record_id: homeId });
15845
- return { ok: false, error: refusal };
16274
+ return { ok: false, error: refusal2 };
15846
16275
  }
15847
16276
  return {
15848
16277
  ok: false,
15849
16278
  error: {
15850
- ...refusal,
15851
- 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.`
15852
16281
  }
15853
16282
  };
15854
16283
  }
@@ -16102,9 +16531,43 @@ function chooseSurface(current, pressed) {
16102
16531
  function surfaceChosen(current, asking) {
16103
16532
  return asking.main !== void 0 ? current.main === asking.main : current.rail === asking.rail;
16104
16533
  }
16105
- function openingRail(activeRecordId) {
16106
- 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;
16107
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.`;
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.";
16108
16571
  function openingView(activeView, activeDashboardId) {
16109
16572
  const named = pageViewFromParam(activeView);
16110
16573
  if (named === null) {
@@ -16132,6 +16595,8 @@ function TablePage({
16132
16595
  activeGroupField,
16133
16596
  cameFrom,
16134
16597
  filter,
16598
+ activeRail,
16599
+ activeItemId,
16135
16600
  className
16136
16601
  }) {
16137
16602
  const client = (0, import_react119.useRecordsClient)();
@@ -16141,8 +16606,13 @@ function TablePage({
16141
16606
  const askedInWords = filter ? filterInWords(filter, pageFields.data ?? []) : null;
16142
16607
  const organizationId = (0, import_react119.useRecordsClient)().config.organizationId;
16143
16608
  const [view, setView] = (0, import_react118.useState)(null);
16144
- const opening = openingRail(activeRecordId);
16609
+ const opening = openingRail(activeRecordId, activeRail);
16145
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;
16146
16616
  const [asking, setAsking] = (0, import_react118.useState)(null);
16147
16617
  const [layoutFromLink, setLayoutFromLink] = (0, import_react118.useState)(opened.layout);
16148
16618
  const [surface, setSurface] = (0, import_react118.useState)({
@@ -16167,6 +16637,12 @@ function TablePage({
16167
16637
  setOpenRecord(activeRecordId);
16168
16638
  setSurface((now) => ({ ...now, rail: "record" }));
16169
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]);
16170
16646
  (0, import_react118.useEffect)(() => {
16171
16647
  const named = pageViewFromParam(activeView);
16172
16648
  if (named === null) return;
@@ -16331,7 +16807,8 @@ function TablePage({
16331
16807
  organizationId,
16332
16808
  subjectId: tableId,
16333
16809
  name: table.data?.name,
16334
- may: rights.share
16810
+ may: rights.share,
16811
+ initiallyOpen: shareFromLink
16335
16812
  }
16336
16813
  ),
16337
16814
  /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(ExportMenu, { tableId })
@@ -16354,6 +16831,8 @@ function TablePage({
16354
16831
  }
16355
16832
  )
16356
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,
16357
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,
16358
16837
  cameFrom ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
16359
16838
  "p",
@@ -16412,7 +16891,7 @@ function TablePage({
16412
16891
  }
16413
16892
  }
16414
16893
  ) : null,
16415
- 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,
16416
16895
  rail === "bookings" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(BookingSlots, { tableId }) : null,
16417
16896
  rail === "checklists" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
16418
16897
  ChecklistsPanel,
@@ -16424,8 +16903,8 @@ function TablePage({
16424
16903
  }
16425
16904
  }
16426
16905
  ) : null,
16427
- rail === "notifications" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(SubscriptionsPanel, { tableId }) : null,
16428
- 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,
16429
16908
  rail === "import" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(ImportWizard, { tableId, onDone: () => setRail("none") }) : null,
16430
16909
  rail === "field" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(FieldEditor, { tableId, onSaved: () => setRail("none"), onCancel: () => setRail("none") }) : null,
16431
16910
  rail === "new-record" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(