@ai-matrx/records-ui 0.81.0 → 0.83.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,
@@ -185,6 +189,8 @@ __export(src_exports, {
185
189
  VIEW_LAYOUTS: () => VIEW_LAYOUTS,
186
190
  VIEW_NOT_SAVED_YET: () => VIEW_NOT_SAVED_YET,
187
191
  VIEW_TABLE: () => VIEW_TABLE,
192
+ VISIBILITY_LANES: () => VISIBILITY_LANES,
193
+ VISIBILITY_LANE_TITLE: () => VISIBILITY_LANE_TITLE,
188
194
  ViewBar: () => ViewBar,
189
195
  ViewSwitcher: () => ViewSwitcher,
190
196
  WITHHELD_RECORD_LABEL: () => WITHHELD_RECORD_LABEL,
@@ -196,6 +202,7 @@ __export(src_exports, {
196
202
  actorWords: () => actorWords,
197
203
  addFields: () => addFields,
198
204
  archivedRowLine: () => archivedRowLine,
205
+ asRefusal: () => asRefusal,
199
206
  askableFields: () => askableFields,
200
207
  blockFromSpec: () => blockFromSpec,
201
208
  bodyForReading: () => bodyForReading,
@@ -239,6 +246,7 @@ __export(src_exports, {
239
246
  isPlainFieldType: () => isPlainFieldType,
240
247
  isRelationFieldType: () => isRelationFieldType,
241
248
  isSignatureField: () => isSignatureField,
249
+ keptByTheApp: () => keptByTheApp,
242
250
  keyFor: () => keyFor,
243
251
  kindsWithNoChoice: () => kindsWithNoChoice,
244
252
  laneFor: () => laneFor,
@@ -261,15 +269,20 @@ __export(src_exports, {
261
269
  previewLine: () => previewLine,
262
270
  previewWords: () => previewWords,
263
271
  publiclyAnswerable: () => publiclyAnswerable,
272
+ railFromParam: () => railFromParam,
264
273
  recordName: () => recordName,
265
274
  recordNameIn: () => recordNameIn,
266
275
  recordsDataSource: () => recordsDataSource,
276
+ refusal: () => refusal,
267
277
  refusalForAPerson: () => refusalForAPerson,
278
+ refusalFromThrown: () => refusalFromThrown,
268
279
  refusalLineForAPerson: () => refusalLineForAPerson,
280
+ refusalOr: () => refusalOr,
269
281
  renderValue: () => renderValue,
270
282
  revokeConsequence: () => revokeConsequence,
271
283
  rowName: () => rowName,
272
284
  rowNameIn: () => rowNameIn,
285
+ saveViewPatch: () => saveViewPatch,
273
286
  scalarText: () => scalarText,
274
287
  shareUnavailableReason: () => shareUnavailableReason,
275
288
  specFromBlock: () => specFromBlock,
@@ -279,6 +292,7 @@ __export(src_exports, {
279
292
  tableName: () => tableName,
280
293
  tableRightsAt: () => tableRightsAt,
281
294
  tokenFor: () => tokenFor,
295
+ unknownRailLine: () => unknownRailLine,
282
296
  unknownViewLine: () => unknownViewLine,
283
297
  useCanShare: () => useCanShare,
284
298
  useEmbedHandshake: () => useEmbedHandshake,
@@ -298,6 +312,7 @@ __export(src_exports, {
298
312
  viewDocument: () => viewDocument,
299
313
  viewFromRecord: () => viewFromRecord,
300
314
  viewPatchDocument: () => viewPatchDocument,
315
+ visibilityLaneFor: () => visibilityLaneFor,
301
316
  whatIsMissing: () => whatIsMissing,
302
317
  whatYouMayDo: () => whatYouMayDo,
303
318
  whatYouMayDoWithTable: () => whatYouMayDoWithTable,
@@ -826,13 +841,13 @@ function ReferenceBuilder({
826
841
  // src/PersonPicker.tsx
827
842
  var import_react9 = require("react");
828
843
  var import_react10 = require("@ai-matrx/records/react");
829
- var import_design_system4 = require("@ai-matrx/design-system");
844
+ var import_design_system5 = require("@ai-matrx/design-system");
830
845
 
831
846
  // src/editors.tsx
832
847
  var import_react7 = require("react");
833
848
  var import_records4 = require("@ai-matrx/records");
834
849
  var import_react8 = require("@ai-matrx/records/react");
835
- var import_design_system3 = require("@ai-matrx/design-system");
850
+ var import_design_system4 = require("@ai-matrx/design-system");
836
851
 
837
852
  // src/parity.ts
838
853
  var import_records3 = require("@ai-matrx/records");
@@ -934,8 +949,119 @@ function fieldTypeLabel(field) {
934
949
  // src/RelationPicker.tsx
935
950
  var import_react5 = require("react");
936
951
  var import_react6 = require("@ai-matrx/records/react");
952
+ var import_design_system3 = require("@ai-matrx/design-system");
953
+
954
+ // src/Refusal.tsx
937
955
  var import_design_system2 = require("@ai-matrx/design-system");
938
- var import_jsx_runtime3 = (
956
+ var import_jsx_runtime3 = require("react/jsx-runtime");
957
+ function hintIsMachineIdentity(hint) {
958
+ return isMachineIdentity(hint);
959
+ }
960
+ function hintForAPerson(hint) {
961
+ const said = (hint ?? "").trim();
962
+ if (said === "") return null;
963
+ return isMachineIdentity(said) ? null : said;
964
+ }
965
+ function RefusalNotice({
966
+ error,
967
+ className,
968
+ actions,
969
+ onKeepEditing,
970
+ onDiscard
971
+ }) {
972
+ const plain = refusalForAPerson(error);
973
+ 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: [
974
+ onKeepEditing ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
975
+ "button",
976
+ {
977
+ type: "button",
978
+ "data-refusal-keep-editing": "",
979
+ className: "rounded border px-2 py-0.5 text-xs hover:bg-muted",
980
+ onClick: onKeepEditing,
981
+ children: "Keep editing"
982
+ }
983
+ ) : null,
984
+ onDiscard ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
985
+ "button",
986
+ {
987
+ type: "button",
988
+ "data-refusal-discard": "",
989
+ className: "rounded border px-2 py-0.5 text-xs hover:bg-muted",
990
+ onClick: onDiscard,
991
+ children: "Discard"
992
+ }
993
+ ) : null
994
+ ] }) : null;
995
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
996
+ import_design_system2.Alert,
997
+ {
998
+ variant: "destructive",
999
+ role: "alert",
1000
+ "data-refusal-notice": "",
1001
+ className: (0, import_design_system2.cn)("text-xs", className),
1002
+ ...plain.forEngineers ? { title: plain.forEngineers } : {},
1003
+ children: [
1004
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_design_system2.AlertTitle, { className: "text-xs font-medium", children: plain.title }),
1005
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_design_system2.AlertDescription, { className: "space-y-1 text-xs", children: [
1006
+ plain.sentence.replace(/\.$/, "") === plain.title ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: plain.sentence }),
1007
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "opacity-80", children: plain.remedy }),
1008
+ plain.forEngineers ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1009
+ "p",
1010
+ {
1011
+ className: "sr-only",
1012
+ "aria-hidden": "true",
1013
+ "data-for-engineers": "",
1014
+ ...error.sqlstate ? { "data-sqlstate": error.sqlstate } : {},
1015
+ children: plain.forEngineers
1016
+ }
1017
+ ) : null,
1018
+ actions,
1019
+ doors
1020
+ ] })
1021
+ ]
1022
+ }
1023
+ );
1024
+ }
1025
+ function RefusalLine({ error, className }) {
1026
+ const plain = refusalForAPerson(error);
1027
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1028
+ "p",
1029
+ {
1030
+ role: "alert",
1031
+ "data-refusal-line": "",
1032
+ className: (0, import_design_system2.cn)("text-xs text-destructive", className),
1033
+ title: plain.forEngineers || void 0,
1034
+ children: refusalLineForAPerson(error)
1035
+ }
1036
+ );
1037
+ }
1038
+
1039
+ // src/refusals.ts
1040
+ function refusal(code, sentence2, remedy, diagnostic) {
1041
+ return {
1042
+ code,
1043
+ message: sentence2,
1044
+ ...remedy ? { hint: remedy } : {},
1045
+ ...diagnostic ? { diagnostic } : {}
1046
+ };
1047
+ }
1048
+ function refusalFromThrown(thrown, sentence2, remedy, code = "internal") {
1049
+ const said = thrown instanceof Error ? thrown.message : String(thrown ?? "");
1050
+ return refusal(code, sentence2, remedy, said ? `Thrown: ${said}` : null);
1051
+ }
1052
+ function refusalOr(error, code, sentence2) {
1053
+ return error ?? refusal(code, sentence2);
1054
+ }
1055
+ function asRefusal(thrown, sentence2, remedy, code = "internal") {
1056
+ const t = thrown;
1057
+ if (t && typeof t === "object" && typeof t.code === "string" && typeof t.message === "string" && !(thrown instanceof Error)) {
1058
+ return t;
1059
+ }
1060
+ return refusalFromThrown(thrown, sentence2, remedy, code);
1061
+ }
1062
+
1063
+ // src/RelationPicker.tsx
1064
+ var import_jsx_runtime4 = (
939
1065
  // The Field's own name is already printed above this control by
940
1066
  // `FieldLabel`, so the sentence does not say it a second time.
941
1067
  require("react/jsx-runtime")
@@ -945,7 +1071,7 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
945
1071
  const client = (0, import_react6.useOptionalRecordsClient)();
946
1072
  const [rows, setRows] = (0, import_react5.useState)(null);
947
1073
  const [words, setWords] = (0, import_react5.useState)({});
948
- const [refusal, setRefusal] = (0, import_react5.useState)(null);
1074
+ const [refusal2, setRefusal] = (0, import_react5.useState)(null);
949
1075
  const [search, setSearch] = (0, import_react5.useState)("");
950
1076
  const [open, setOpen] = (0, import_react5.useState)(false);
951
1077
  const [target, setTarget] = (0, import_react5.useState)(null);
@@ -965,7 +1091,7 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
965
1091
  void client.list({ table_id: target2, limit: 200 }).then((result) => {
966
1092
  if (cancelled) return;
967
1093
  if (result.ok) setRows(result.data.rows);
968
- else setRefusal(refusalLineForAPerson(result.error));
1094
+ else setRefusal(result.error);
969
1095
  });
970
1096
  return () => {
971
1097
  cancelled = true;
@@ -999,14 +1125,17 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
999
1125
  void client.tableList().then((result) => {
1000
1126
  if (cancelled) return;
1001
1127
  if (!result.ok) {
1002
- setTargetRefusal(refusalLineForAPerson(result.error));
1128
+ setTargetRefusal(result.error);
1003
1129
  return;
1004
1130
  }
1005
1131
  const found = result.data.find((t) => t.id === id2) ?? null;
1006
1132
  if (found) setTarget(found);
1007
1133
  else
1008
1134
  setTargetRefusal(
1009
- "That Table is not one you can open, so a new record cannot be added to it from here."
1135
+ refusal(
1136
+ "door",
1137
+ "That Table is not one you can open, so a new record cannot be added to it from here."
1138
+ )
1010
1139
  );
1011
1140
  });
1012
1141
  return () => {
@@ -1014,10 +1143,10 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1014
1143
  };
1015
1144
  }, [client, open, allowCreate, target, targetRefusal, field.relation_target]);
1016
1145
  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 });
1146
+ 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
1147
  }
1019
1148
  if (!field.relation_target) {
1020
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
1149
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
1021
1150
  fieldName(field),
1022
1151
  " 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
1152
  ] });
@@ -1041,13 +1170,17 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1041
1170
  const tableId = field.relation_target;
1042
1171
  if (!tableId || title === "" || busy) return;
1043
1172
  if (!target) {
1044
- setCreateRefusal("Still reading that Table. Try again in a moment.");
1173
+ setCreateRefusal(refusal("unreachable", "Still reading that Table.", "Try again in a moment."));
1045
1174
  return;
1046
1175
  }
1047
1176
  const titleField = target.title_field;
1048
1177
  if (!titleField) {
1049
1178
  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.`
1179
+ refusal(
1180
+ "not_supported",
1181
+ `${target.name} has no column that names a record yet, so a new one would have nothing to show here.`,
1182
+ "A table admin picks its title column in that table's settings."
1183
+ )
1051
1184
  );
1052
1185
  return;
1053
1186
  }
@@ -1056,7 +1189,7 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1056
1189
  const written = await client.recordWrite({ table_id: tableId, data: { [titleField]: title } });
1057
1190
  if (!written.ok) {
1058
1191
  setBusy(false);
1059
- setCreateRefusal(refusalLineForAPerson(written.error));
1192
+ setCreateRefusal(written.error);
1060
1193
  return;
1061
1194
  }
1062
1195
  const createdId = String(written.data);
@@ -1071,8 +1204,8 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1071
1204
  const visible = (rows ?? []).filter(
1072
1205
  (row) => search.trim() === "" ? true : titleOf(row.id).toLowerCase().includes(search.trim().toLowerCase())
1073
1206
  );
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)(
1207
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: (0, import_design_system3.cn)("flex flex-wrap items-center gap-1", className), children: [
1208
+ picked.map((recordId) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1076
1209
  RecordChip,
1077
1210
  {
1078
1211
  title: titleOf(recordId),
@@ -1080,11 +1213,11 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1080
1213
  },
1081
1214
  recordId
1082
1215
  )),
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,
1216
+ disabled ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system3.Popover, { open, onOpenChange: setOpen, children: [
1217
+ /* @__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" }) }),
1218
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system3.PopoverContent, { sizing: "content", className: "p-1", align: "start", children: [
1219
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1220
+ import_design_system3.BasicInput,
1088
1221
  {
1089
1222
  value: search,
1090
1223
  onChange: (e) => setSearch(e.target.value),
@@ -1092,34 +1225,34 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1092
1225
  className: "mb-1 h-7 text-xs"
1093
1226
  }
1094
1227
  ),
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: [
1228
+ refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RefusalLine, { error: refusal2, className: "p-2" }) : null,
1229
+ !rows && !refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "p-2 text-xs text-muted-foreground", children: "Reading\u2026" }) : null,
1230
+ full ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "p-2 text-xs text-muted-foreground", children: [
1098
1231
  fieldName(field),
1099
1232
  " can point at ",
1100
1233
  field.relation_max,
1101
1234
  " at most. Remove one to add another."
1102
1235
  ] }) : 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)(
1236
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system3.ScrollArea, { className: "max-h-56", children: [
1237
+ /* @__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
1238
  "button",
1106
1239
  {
1107
1240
  type: "button",
1108
1241
  onClick: () => toggle(row.id),
1109
1242
  title: titleOf(row.id),
1110
- className: (0, import_design_system2.cn)(
1243
+ className: (0, import_design_system3.cn)(
1111
1244
  "w-full truncate rounded px-2 py-1 text-left text-xs hover:bg-muted",
1112
1245
  picked.includes(row.id) ? "bg-accent text-accent-foreground" : ""
1113
1246
  ),
1114
1247
  children: titleOf(row.id)
1115
1248
  }
1116
1249
  ) }, 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
1250
+ 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
1251
  ] }),
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,
1252
+ allowCreate && !full ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mt-1 border-t pt-1", children: [
1253
+ 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: [
1254
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1255
+ import_design_system3.BasicInput,
1123
1256
  {
1124
1257
  autoFocus: true,
1125
1258
  value: newTitle,
@@ -1135,8 +1268,8 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1135
1268
  "aria-label": `Name the new ${targetWord}`
1136
1269
  }
1137
1270
  ),
1138
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1139
- import_design_system2.Button,
1271
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1272
+ import_design_system3.Button,
1140
1273
  {
1141
1274
  type: "button",
1142
1275
  size: "sm",
@@ -1146,8 +1279,8 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1146
1279
  children: busy ? "Creating\u2026" : "Create"
1147
1280
  }
1148
1281
  )
1149
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1150
- import_design_system2.Button,
1282
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1283
+ import_design_system3.Button,
1151
1284
  {
1152
1285
  type: "button",
1153
1286
  size: "sm",
@@ -1162,10 +1295,25 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1162
1295
  setCreating(true);
1163
1296
  }
1164
1297
  },
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}` })
1298
+ 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
1299
  }
1167
1300
  ),
1168
- createRefusal ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "p-2 text-xs text-destructive", children: createRefusal }) : null
1301
+ createRefusal ? (
1302
+ // The typed title is still in the search box: the notice owns the
1303
+ // two doors out (lane REFUSAL-SWEEP).
1304
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1305
+ RefusalNotice,
1306
+ {
1307
+ error: createRefusal,
1308
+ className: "mt-1 max-w-72",
1309
+ onKeepEditing: () => setCreateRefusal(null),
1310
+ onDiscard: () => {
1311
+ setCreateRefusal(null);
1312
+ setSearch("");
1313
+ }
1314
+ }
1315
+ )
1316
+ ) : null
1169
1317
  ] }) : null
1170
1318
  ] })
1171
1319
  ] })
@@ -1173,12 +1321,12 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1173
1321
  }
1174
1322
 
1175
1323
  // src/editors.tsx
1176
- var import_jsx_runtime4 = require("react/jsx-runtime");
1324
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1177
1325
  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,
1326
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-baseline gap-1.5", children: [
1327
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system4.Label, { htmlFor, className: "text-xs font-medium", children: fieldName(field) }),
1328
+ field.required ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-xs text-destructive", children: "required" }) : null,
1329
+ field.unit ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-xs text-muted-foreground", children: field.unit }) : null,
1182
1330
  children
1183
1331
  ] });
1184
1332
  }
@@ -1189,18 +1337,18 @@ function FieldControl({ field, value, onChange, disabled, id }) {
1189
1337
  case "formula":
1190
1338
  case "lookup":
1191
1339
  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: [
1340
+ 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
1341
  value === null || value === void 0 ? "\u2014" : String(value),
1194
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "ml-2 italic", children: [
1342
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "ml-2 italic", children: [
1195
1343
  fieldName(field),
1196
1344
  " is worked out by the system, so it cannot be typed in"
1197
1345
  ] })
1198
1346
  ] });
1199
1347
  case "json":
1200
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(JsonControl, { field, value, onChange, disabled, id: inputId });
1348
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(JsonControl, { field, value, onChange, disabled, id: inputId });
1201
1349
  case "long_text":
1202
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1203
- import_design_system3.BasicTextarea,
1350
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1351
+ import_design_system4.BasicTextarea,
1204
1352
  {
1205
1353
  id: inputId,
1206
1354
  rows: 3,
@@ -1212,8 +1360,8 @@ function FieldControl({ field, value, onChange, disabled, id }) {
1212
1360
  case "number":
1213
1361
  case "currency":
1214
1362
  case "percent":
1215
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1216
- import_design_system3.BasicInput,
1363
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1364
+ import_design_system4.BasicInput,
1217
1365
  {
1218
1366
  id: inputId,
1219
1367
  type: "number",
@@ -1225,8 +1373,8 @@ function FieldControl({ field, value, onChange, disabled, id }) {
1225
1373
  }
1226
1374
  );
1227
1375
  case "datetime":
1228
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1229
- import_design_system3.BasicInput,
1376
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1377
+ import_design_system4.BasicInput,
1230
1378
  {
1231
1379
  id: inputId,
1232
1380
  type: field.format === "datetime" ? "datetime-local" : "date",
@@ -1238,8 +1386,8 @@ function FieldControl({ field, value, onChange, disabled, id }) {
1238
1386
  case "email":
1239
1387
  case "phone":
1240
1388
  case "url":
1241
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1242
- import_design_system3.BasicInput,
1389
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1390
+ import_design_system4.BasicInput,
1243
1391
  {
1244
1392
  id: inputId,
1245
1393
  type: kind === "email" ? "email" : kind === "phone" ? "tel" : "url",
@@ -1249,19 +1397,19 @@ function FieldControl({ field, value, onChange, disabled, id }) {
1249
1397
  }
1250
1398
  );
1251
1399
  case "checkbox":
1252
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CheckboxControl, { field, value, onChange, disabled, id: inputId });
1400
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CheckboxControl, { field, value, onChange, disabled, id: inputId });
1253
1401
  case "select":
1254
1402
  case "multi_select":
1255
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(OptionControl, { field, value, onChange, disabled, id: inputId });
1403
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(OptionControl, { field, value, onChange, disabled, id: inputId });
1256
1404
  case "member":
1257
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PersonPicker, { field, value, onChange, disabled, id: inputId });
1405
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PersonPicker, { field, value, onChange, disabled, id: inputId });
1258
1406
  case "relation":
1259
1407
  case "attachment":
1260
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RelationPicker, { field, value, onChange, disabled, id: inputId });
1408
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RelationPicker, { field, value, onChange, disabled, id: inputId });
1261
1409
  default:
1262
1410
  if (typeof value === "boolean" || field.config?.["kind"] === "boolean") {
1263
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1264
- import_design_system3.Checkbox,
1411
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1412
+ import_design_system4.Checkbox,
1265
1413
  {
1266
1414
  id: inputId,
1267
1415
  checked: value === true,
@@ -1270,8 +1418,8 @@ function FieldControl({ field, value, onChange, disabled, id }) {
1270
1418
  }
1271
1419
  );
1272
1420
  }
1273
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1274
- import_design_system3.BasicInput,
1421
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1422
+ import_design_system4.BasicInput,
1275
1423
  {
1276
1424
  id: inputId,
1277
1425
  value: value === null || value === void 0 ? "" : String(value),
@@ -1283,9 +1431,9 @@ function FieldControl({ field, value, onChange, disabled, id }) {
1283
1431
  }
1284
1432
  function CheckboxControl({ field, value, onChange, disabled, id }) {
1285
1433
  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,
1434
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-2", children: [
1435
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1436
+ import_design_system4.Checkbox,
1289
1437
  {
1290
1438
  id,
1291
1439
  checked: value === true,
@@ -1294,10 +1442,10 @@ function CheckboxControl({ field, value, onChange, disabled, id }) {
1294
1442
  onCheckedChange: (next) => onChange(next === true)
1295
1443
  }
1296
1444
  ),
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,
1445
+ 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: [
1446
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-xs text-muted-foreground", children: value === true ? "Yes" : "No" }),
1447
+ field.required ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1448
+ import_design_system4.Button,
1301
1449
  {
1302
1450
  size: "sm",
1303
1451
  variant: "ghost",
@@ -1314,9 +1462,9 @@ function JsonControl({ field, value, onChange, disabled, id }) {
1314
1462
  const initial = value === null || value === void 0 ? "" : typeof value === "string" ? value : JSON.stringify(value, null, 2);
1315
1463
  const [text, setText] = (0, import_react7.useState)(initial);
1316
1464
  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,
1465
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex flex-col gap-1", children: [
1466
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1467
+ import_design_system4.BasicTextarea,
1320
1468
  {
1321
1469
  id,
1322
1470
  rows: 4,
@@ -1335,38 +1483,54 @@ function JsonControl({ field, value, onChange, disabled, id }) {
1335
1483
  onChange(JSON.parse(next));
1336
1484
  setBroken(null);
1337
1485
  } catch (thrown) {
1338
- setBroken(thrown instanceof Error ? thrown.message : String(thrown));
1486
+ setBroken(
1487
+ refusalFromThrown(
1488
+ thrown,
1489
+ `${fieldName(field)} is not readable as structured data yet.`,
1490
+ "Fix the text so it reads as structured data, or discard it to put back what was there.",
1491
+ "invalid_argument"
1492
+ )
1493
+ );
1339
1494
  }
1340
1495
  }
1341
1496
  }
1342
1497
  ),
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
1498
+ broken ? (
1499
+ // The typed text is still in the box: the notice owns the two doors out.
1500
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1501
+ RefusalNotice,
1502
+ {
1503
+ error: broken,
1504
+ onKeepEditing: () => id ? document.getElementById(id)?.focus() : void 0,
1505
+ onDiscard: () => {
1506
+ setText(initial);
1507
+ setBroken(null);
1508
+ }
1509
+ }
1510
+ )
1511
+ ) : null
1348
1512
  ] });
1349
1513
  }
1350
1514
  function OptionControl({ field, value, onChange, disabled, id }) {
1351
1515
  const client = (0, import_react8.useOptionalRecordsClient)();
1352
1516
  const [options, setOptions] = (0, import_react7.useState)(null);
1353
- const [refusal, setRefusal] = (0, import_react7.useState)(null);
1517
+ const [refusal2, setRefusal] = (0, import_react7.useState)(null);
1354
1518
  (0, import_react7.useEffect)(() => {
1355
1519
  if (!client) return;
1356
1520
  let cancelled = false;
1357
1521
  void client.fieldOptions({ field_id: field.id }).then((result) => {
1358
1522
  if (cancelled) return;
1359
1523
  if (result.ok) setOptions(result.data ?? []);
1360
- else setRefusal(refusalLineForAPerson(result.error));
1524
+ else setRefusal(result.error);
1361
1525
  });
1362
1526
  return () => {
1363
1527
  cancelled = true;
1364
1528
  };
1365
1529
  }, [client, field.id]);
1366
1530
  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,
1531
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex flex-col gap-1", children: [
1532
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1533
+ import_design_system4.BasicInput,
1370
1534
  {
1371
1535
  id,
1372
1536
  value: value === null || value === void 0 ? "" : String(value),
@@ -1374,20 +1538,21 @@ function OptionControl({ field, value, onChange, disabled, id }) {
1374
1538
  onChange: (e) => onChange(e.target.value === "" ? null : e.target.value)
1375
1539
  }
1376
1540
  ),
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." })
1541
+ /* @__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
1542
  ] });
1379
1543
  }
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" });
1544
+ if (refusal2)
1545
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { id, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RefusalLine, { error: refusal2 }) });
1546
+ 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
1547
  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.` });
1548
+ 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
1549
  }
1385
1550
  const titleOf = (option) => recordName(option.data, null, "Unnamed choice");
1386
1551
  if (field.multi) {
1387
1552
  const chosen = options.filter((option) => (0, import_records4.choiceValuesOf)(value).some((v) => (0, import_records4.isTheChosen)(option, v)));
1388
1553
  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,
1554
+ 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)(
1555
+ import_design_system4.Button,
1391
1556
  {
1392
1557
  type: "button",
1393
1558
  size: "sm",
@@ -1405,15 +1570,15 @@ function OptionControl({ field, value, onChange, disabled, id }) {
1405
1570
  )) });
1406
1571
  }
1407
1572
  const current = options.find((option) => (0, import_records4.isTheChosen)(option, value));
1408
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1409
- import_design_system3.Select,
1573
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1574
+ import_design_system4.Select,
1410
1575
  {
1411
1576
  value: current ? (0, import_records4.optionKey)(current) : "",
1412
1577
  disabled: disabled ?? false,
1413
1578
  onValueChange: (next) => onChange(next === "" ? null : next),
1414
1579
  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)) })
1580
+ /* @__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" }) }),
1581
+ /* @__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
1582
  ]
1418
1583
  }
1419
1584
  );
@@ -1429,15 +1594,15 @@ function RecordChip({
1429
1594
  // name. So it caps at its container and ends in an ellipsis with the whole name in
1430
1595
  // its tooltip, rather than being cut mid-word by whatever box it landed in
1431
1596
  // (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
1597
+ /* @__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: [
1598
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "min-w-0 truncate", children: title }),
1599
+ 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
1600
  ] })
1436
1601
  );
1437
1602
  }
1438
1603
 
1439
1604
  // src/PersonPicker.tsx
1440
- var import_jsx_runtime5 = require("react/jsx-runtime");
1605
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1441
1606
  function personFromRow(row) {
1442
1607
  const data = row.document ?? {};
1443
1608
  const userId = data["user_id"];
@@ -1490,7 +1655,7 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1490
1655
  ]);
1491
1656
  setMembers(roster);
1492
1657
  if (records && !records.ok) {
1493
- setError(refusalLineForAPerson(records.error));
1658
+ setError(records.error);
1494
1659
  return;
1495
1660
  }
1496
1661
  setPeople(records ? records.data.rows.map(personFromRow).filter((p) => p.userId !== null) : []);
@@ -1511,10 +1676,10 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1511
1676
  };
1512
1677
  }, [client, personTable, picked.length, people.length]);
1513
1678
  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 });
1679
+ 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
1680
  }
1516
1681
  if (!personTable) {
1517
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { className: (0, import_design_system4.cn)("text-xs text-muted-foreground", className), children: [
1682
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: (0, import_design_system5.cn)("text-xs text-muted-foreground", className), children: [
1518
1683
  fieldName(field),
1519
1684
  " 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
1685
  ] });
@@ -1532,7 +1697,7 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1532
1697
  const resolved = await personRecordForMember(client, personTable, people, member);
1533
1698
  setBusy(false);
1534
1699
  if (!resolved.ok) {
1535
- setError(refusalLineForAPerson(resolved.error));
1700
+ setError(resolved.error);
1536
1701
  return;
1537
1702
  }
1538
1703
  if (!people.some((p) => p.id === resolved.data)) {
@@ -1558,8 +1723,8 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1558
1723
  const pickedUserIds = new Set(
1559
1724
  picked.map((recordId) => people.find((p) => p.id === recordId)?.userId).filter(Boolean)
1560
1725
  );
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)(
1726
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: (0, import_design_system5.cn)("flex flex-wrap items-center gap-1", className), children: [
1727
+ picked.map((recordId) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1563
1728
  RecordChip,
1564
1729
  {
1565
1730
  title: nameOfRecord(recordId),
@@ -1567,9 +1732,9 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1567
1732
  },
1568
1733
  recordId
1569
1734
  )),
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,
1735
+ disabled ? null : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system5.Popover, { open, onOpenChange: setOpen, children: [
1736
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system5.PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1737
+ import_design_system5.Button,
1573
1738
  {
1574
1739
  id,
1575
1740
  type: "button",
@@ -1579,9 +1744,9 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1579
1744
  children: picked.length === 0 ? "Pick a person" : "Change"
1580
1745
  }
1581
1746
  ) }),
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,
1747
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system5.PopoverContent, { sizing: "content", className: "p-1", align: "start", children: [
1748
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1749
+ import_design_system5.BasicInput,
1585
1750
  {
1586
1751
  value: search,
1587
1752
  onChange: (e) => setSearch(e.target.value),
@@ -1589,29 +1754,29 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1589
1754
  className: "mb-1 h-7 text-xs"
1590
1755
  }
1591
1756
  ),
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)(
1757
+ error ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RefusalLine, { error, className: "p-2" }) : null,
1758
+ members === null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "p-2 text-xs text-muted-foreground", children: "Reading the members\u2026" }) : null,
1759
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system5.ScrollArea, { className: "max-h-56", children: [
1760
+ /* @__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
1761
  "button",
1597
1762
  {
1598
1763
  type: "button",
1599
1764
  disabled: busy,
1600
1765
  onClick: () => void pick(member),
1601
- className: (0, import_design_system4.cn)(
1766
+ className: (0, import_design_system5.cn)(
1602
1767
  "w-full truncate rounded px-2 py-1 text-left text-xs hover:bg-muted",
1603
1768
  pickedUserIds.has(member.userId) ? "bg-accent text-accent-foreground" : ""
1604
1769
  ),
1605
1770
  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: [
1771
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: memberName(member) }),
1772
+ member.email && member.email !== memberName(member) ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "ml-1.5 opacity-60", children: [
1608
1773
  "\xB7 ",
1609
1774
  member.email
1610
1775
  ] }) : null
1611
1776
  ]
1612
1777
  }
1613
1778
  ) }, 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
1779
+ 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
1780
  ] })
1616
1781
  ] })
1617
1782
  ] })
@@ -1624,55 +1789,6 @@ var import_core = require("@ai-matrx/records/core");
1624
1789
  var import_react16 = require("@ai-matrx/records/react");
1625
1790
  var import_design_system7 = require("@ai-matrx/design-system");
1626
1791
 
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
1792
  // src/whoChangedSource.tsx
1677
1793
  var import_react11 = require("react");
1678
1794
  var import_react12 = require("@ai-matrx/records/react");
@@ -2272,7 +2388,7 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow }
2272
2388
  startedFrom.current = null;
2273
2389
  }, []);
2274
2390
  const writeCellNow = (0, import_react15.useCallback)(
2275
- async (rowId, key, value) => {
2391
+ async (rowId, key, value, resent = false) => {
2276
2392
  const id = cellKey(rowId, key);
2277
2393
  setOptimistic((held) => ({ ...held, [id]: value }));
2278
2394
  setSaving((held) => ({ ...held, [id]: true }));
@@ -2315,16 +2431,25 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow }
2315
2431
  delete next[id];
2316
2432
  return next;
2317
2433
  });
2318
- if (written.error.code === "stale_write") versions.current.delete(rowId);
2434
+ const conflict = (0, import_core.asWriteConflict)(written.error, rowId);
2435
+ if (written.error.code === "stale_write") {
2436
+ if (conflict) rememberVersion(versions.current, rowId, conflict.current_version);
2437
+ else versions.current.delete(rowId);
2438
+ if (conflict && !resent && !Object.prototype.hasOwnProperty.call(conflict.contested_fields, key)) {
2439
+ await writeCellNow(rowId, key, value, true);
2440
+ return;
2441
+ }
2442
+ }
2319
2443
  setRefusals((held) => ({
2320
2444
  ...held,
2321
2445
  [id]: {
2322
2446
  error: written.error,
2323
- conflict: (0, import_core.asWriteConflict)(written.error, rowId),
2447
+ conflict,
2324
2448
  attempted: value
2325
2449
  }
2326
2450
  }));
2327
2451
  },
2452
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2328
2453
  [client, reload]
2329
2454
  );
2330
2455
  const writeCell = (0, import_react15.useCallback)(
@@ -2399,6 +2524,33 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow }
2399
2524
  },
2400
2525
  [reload]
2401
2526
  );
2527
+ const keepEditing = (0, import_react15.useCallback)(
2528
+ (rowId, key) => {
2529
+ const held = refusals[cellKey(rowId, key)];
2530
+ if (!held || !writable(rowId)) return;
2531
+ startedFrom.current = shownValue(rowId, key);
2532
+ typed.current = held.attempted;
2533
+ setDraft(held.attempted);
2534
+ setEditing({ rowId, key });
2535
+ },
2536
+ [refusals, writable, shownValue]
2537
+ );
2538
+ const discard = (0, import_react15.useCallback)((rowId, key) => {
2539
+ const id = cellKey(rowId, key);
2540
+ setRefusals((held) => {
2541
+ if (!held[id]) return held;
2542
+ const next = { ...held };
2543
+ delete next[id];
2544
+ return next;
2545
+ });
2546
+ setOptimistic((held) => {
2547
+ if (!(id in held)) return held;
2548
+ const next = { ...held };
2549
+ delete next[id];
2550
+ return next;
2551
+ });
2552
+ setEditing((open) => open && open.rowId === rowId && open.key === key ? null : open);
2553
+ }, []);
2402
2554
  const remove = (0, import_react15.useCallback)(
2403
2555
  async (rowId) => {
2404
2556
  setBusy(true);
@@ -2450,6 +2602,8 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow }
2450
2602
  },
2451
2603
  retry,
2452
2604
  keepTheirs,
2605
+ keepEditing,
2606
+ discard,
2453
2607
  remove,
2454
2608
  addRecord,
2455
2609
  rowError,
@@ -2508,7 +2662,7 @@ function GridCell({
2508
2662
  );
2509
2663
  const value = editing ? editing.valueFor(row, field) : row.document?.[field.key];
2510
2664
  const state = editing ? editing.stateOf(row.id, field.key) : null;
2511
- const refusal = editing ? editing.refusalOf(row.id, field.key) : null;
2665
+ const refusal2 = editing ? editing.refusalOf(row.id, field.key) : null;
2512
2666
  const editable = Boolean(editing) && canWrite && fieldIsEditable(field);
2513
2667
  if (open && editing) {
2514
2668
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
@@ -2518,13 +2672,28 @@ function GridCell({
2518
2672
  draft: editing.draft,
2519
2673
  onType: editing.type,
2520
2674
  onCommit: (move) => editing.commit(move),
2521
- onCancel: editing.cancel
2675
+ onCancel: editing.cancel,
2676
+ below: (
2677
+ // KEPT EDITING: the refusal stays beside the value it is about, so the
2678
+ // person reads what to change while changing it. Keep editing is the
2679
+ // state they are in, so only Discard is offered. It is INSIDE the open
2680
+ // cell, so pressing Discard is not "focus left the cell" — which would
2681
+ // commit the refused value one more time on its way out.
2682
+ refusal2 && !refusal2.conflict ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2683
+ RefusalNotice,
2684
+ {
2685
+ error: refusal2.error,
2686
+ className: "py-1.5",
2687
+ onDiscard: () => editing.discard(row.id, field.key)
2688
+ }
2689
+ ) : null
2690
+ )
2522
2691
  }
2523
2692
  );
2524
2693
  }
2525
2694
  if (editorKindFor(field) === "checkbox") {
2526
2695
  const answered = value === true || value === false;
2527
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { "data-matrx-cell-control": "", className: "flex w-full items-center gap-1.5", children: [
2696
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { "data-matrx-cell-control": "", className: "flex w-full flex-wrap items-center gap-1.5", children: [
2528
2697
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2529
2698
  import_design_system7.Checkbox,
2530
2699
  {
@@ -2537,10 +2706,7 @@ function GridCell({
2537
2706
  }
2538
2707
  ),
2539
2708
  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 }),
2542
- /* @__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
- ] }) : null
2709
+ refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(CellRefusalNotice, { refusal: refusal2, field, row, editing, typed: false }) : null
2544
2710
  ] });
2545
2711
  }
2546
2712
  const openThisCell = (event) => {
@@ -2613,22 +2779,48 @@ function GridCell({
2613
2779
  ),
2614
2780
  onAskWhoChanged ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(WhoBadge, { field, row, onAskWhoChanged }) : null,
2615
2781
  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: [
2619
- "Theirs: ",
2620
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("strong", { children: formatTheirs(refusal.conflict, field.key) })
2621
- ] }) : null,
2622
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "flex gap-1", children: [
2623
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_design_system7.Button, { size: "sm", variant: "outline", onClick: () => editing?.retry(row.id, field.key), children: "Try again" }),
2624
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_design_system7.Button, { size: "sm", variant: "ghost", onClick: () => editing?.keepTheirs(row.id, field.key), children: "Keep theirs" })
2625
- ] })
2626
- ] }) : null
2782
+ refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(CellRefusalNotice, { refusal: refusal2, field, row, editing, typed: true }) : null
2627
2783
  ]
2628
2784
  }
2629
2785
  )
2630
2786
  );
2631
2787
  }
2788
+ function CellRefusalNotice({
2789
+ refusal: refusal2,
2790
+ field,
2791
+ row,
2792
+ editing,
2793
+ typed
2794
+ }) {
2795
+ if (refusal2.conflict) {
2796
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2797
+ RefusalNotice,
2798
+ {
2799
+ error: refusal2.error,
2800
+ className: "py-1.5",
2801
+ actions: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "flex flex-col gap-1", "data-refusal-conflict": "", children: [
2802
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "text-[11px]", children: [
2803
+ "Theirs: ",
2804
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("strong", { children: formatTheirs(refusal2.conflict, field.key) })
2805
+ ] }),
2806
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "flex flex-wrap gap-1", children: [
2807
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_design_system7.Button, { size: "sm", variant: "outline", onClick: () => editing?.retry(row.id, field.key), children: "Try again" }),
2808
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_design_system7.Button, { size: "sm", variant: "ghost", onClick: () => editing?.keepTheirs(row.id, field.key), children: "Keep theirs" })
2809
+ ] })
2810
+ ] })
2811
+ }
2812
+ );
2813
+ }
2814
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2815
+ RefusalNotice,
2816
+ {
2817
+ error: refusal2.error,
2818
+ className: "py-1.5",
2819
+ ...typed ? { onKeepEditing: () => editing?.keepEditing(row.id, field.key) } : {},
2820
+ onDiscard: () => editing?.discard(row.id, field.key)
2821
+ }
2822
+ );
2823
+ }
2632
2824
  function WhoBadge({
2633
2825
  field,
2634
2826
  row,
@@ -2681,7 +2873,8 @@ function OpenCell({
2681
2873
  draft,
2682
2874
  onType,
2683
2875
  onCommit,
2684
- onCancel
2876
+ onCancel,
2877
+ below
2685
2878
  }) {
2686
2879
  const multiline = takesANewline(field);
2687
2880
  const blurCommits = closesOnBlur(field);
@@ -2710,6 +2903,7 @@ function OpenCell({
2710
2903
  className: "flex w-full min-w-[10rem] flex-col gap-1",
2711
2904
  onDoubleClick: (event) => event.stopPropagation(),
2712
2905
  onKeyDown: (event) => {
2906
+ if (event.target?.closest?.("[data-refusal-notice]")) return;
2713
2907
  if (event.key === "Escape") {
2714
2908
  event.preventDefault();
2715
2909
  event.stopPropagation();
@@ -2735,7 +2929,8 @@ function OpenCell({
2735
2929
  blurCommits ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-[10px] text-muted-foreground", children: multiline ? "\u23CE saves \xB7 \u21E7\u23CE new line \xB7 Esc cancels \xB7 Tab next" : "\u23CE saves \xB7 Esc cancels \xB7 Tab next" }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "flex items-center gap-1", children: [
2736
2930
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_design_system7.Button, { size: "sm", onClick: () => once(() => onCommit(null)), children: "Save" }),
2737
2931
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_design_system7.Button, { size: "sm", variant: "ghost", onClick: () => once(onCancel), children: "Cancel" })
2738
- ] })
2932
+ ] }),
2933
+ below
2739
2934
  ]
2740
2935
  }
2741
2936
  );
@@ -3316,7 +3511,20 @@ function EnrichPanel({ tableId, fieldId, className }) {
3316
3511
  " in one go."
3317
3512
  ] }) : null
3318
3513
  ] }) : 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,
3514
+ outcome ? outcome.ok ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "text-xs text-muted-foreground", children: outcome.message }) : (
3515
+ // A run that did not happen is a refusal, with a heading and a remedy,
3516
+ // never the host's words in red (lane REFUSAL-SWEEP).
3517
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3518
+ RefusalLine,
3519
+ {
3520
+ error: refusal(
3521
+ "internal",
3522
+ outcome.message,
3523
+ "Nothing was filled in. Try the run again in a moment."
3524
+ )
3525
+ }
3526
+ )
3527
+ ) : null,
3320
3528
  field ? null : null
3321
3529
  ] }, row.field_id);
3322
3530
  }) });
@@ -3361,15 +3569,15 @@ function PastePreview({
3361
3569
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PlanTable, { plan }),
3362
3570
  plan.refusals.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { "data-matrx-paste-refusals": true, className: "flex flex-col gap-1", children: [
3363
3571
  /* @__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: [
3572
+ /* @__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
3573
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "font-medium", children: [
3366
3574
  "Row ",
3367
- refusal.fromLine,
3575
+ refusal2.fromLine,
3368
3576
  ", ",
3369
- refusal.column
3577
+ refusal2.column
3370
3578
  ] }),
3371
3579
  " \u2014 ",
3372
- refusal.why
3580
+ refusal2.why
3373
3581
  ] }, index)) }),
3374
3582
  plan.refusals.length > 12 ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
3375
3583
  plan.refusals.length - 12,
@@ -3397,7 +3605,13 @@ function PastePreview({
3397
3605
  setAdding(group.fieldKey);
3398
3606
  void onAddOptions(group.fieldKey, group.words).catch(
3399
3607
  (err) => setAddFailed(
3400
- `Those options were not added: ${err instanceof Error ? err.message : String(err)}`
3608
+ // The error's own text is an engineer's; the person
3609
+ // reads what did not happen and what to do.
3610
+ asRefusal(
3611
+ err,
3612
+ "Those options were not added, so the pasted words still do not match a choice.",
3613
+ "Try adding them again, or add them from the column's settings."
3614
+ )
3401
3615
  )
3402
3616
  ).finally(() => setAdding(null));
3403
3617
  },
@@ -3405,7 +3619,7 @@ function PastePreview({
3405
3619
  }
3406
3620
  )
3407
3621
  ] }, group.fieldKey)),
3408
- addFailed ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-xs text-destructive", children: addFailed }) : null
3622
+ addFailed ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(RefusalLine, { error: addFailed }) : null
3409
3623
  ] }) : null,
3410
3624
  plan.pastRightEdge > 0 ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
3411
3625
  plan.pastRightEdge,
@@ -3585,19 +3799,19 @@ function planPastedBlock(args) {
3585
3799
  accepted[key] = judged.value;
3586
3800
  }
3587
3801
  const predicted = (0, import_core2.predictValueRefusals)(args.fields, accepted);
3588
- for (const refusal of predicted) {
3589
- const key = refusal.field_key;
3802
+ for (const refusal2 of predicted) {
3803
+ const key = refusal2.field_key;
3590
3804
  if (key === void 0) continue;
3591
3805
  const cell = built.find((b) => b.key === key && b.refusal === null);
3592
3806
  if (!cell) continue;
3593
- cell.refusal = refusal.message;
3807
+ cell.refusal = refusal2.message;
3594
3808
  cell.value = null;
3595
3809
  delete accepted[key];
3596
3810
  refusals.push({
3597
3811
  fromLine,
3598
3812
  column: fieldName(byKey.get(key) ?? { key }),
3599
3813
  raw: cell.raw,
3600
- why: refusal.message
3814
+ why: refusal2.message
3601
3815
  });
3602
3816
  }
3603
3817
  cells += Object.keys(accepted).length;
@@ -4361,18 +4575,24 @@ function ExportMenu({ tableId, rows, label, className }) {
4361
4575
  size: "sm",
4362
4576
  variant: "outline",
4363
4577
  onClick: () => {
4578
+ const failed = (thrown) => setFailure(
4579
+ refusalFromThrown(
4580
+ thrown,
4581
+ "The spreadsheet could not be written.",
4582
+ "The CSV beside this button carries the same rows."
4583
+ )
4584
+ );
4585
+ setFailure(null);
4364
4586
  try {
4365
- void (0, import_core3.exportXlsx)(fields.data ?? [], exported(), name).then(
4587
+ (0, import_core3.exportXlsx)(fields.data ?? [], exported(), name).then(
4366
4588
  (bytes) => download(
4367
4589
  `${name}.xlsx`,
4368
4590
  "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
4369
4591
  bytes
4370
4592
  )
4371
- );
4593
+ ).catch(failed);
4372
4594
  } 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
- );
4595
+ failed(thrown);
4376
4596
  }
4377
4597
  },
4378
4598
  children: "XLSX"
@@ -4388,7 +4608,7 @@ function ExportMenu({ tableId, rows, label, className }) {
4388
4608
  human: () => (0, import_core3.exportCsv)(fields.data ?? [], exported())
4389
4609
  }
4390
4610
  ),
4391
- failure ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-xs text-destructive", children: failure }) : null
4611
+ failure ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(RefusalLine, { error: failure }) : null
4392
4612
  ] });
4393
4613
  }
4394
4614
 
@@ -4489,8 +4709,47 @@ function FilePicker({
4489
4709
  // src/ImportWizard.tsx
4490
4710
  var import_react26 = require("react");
4491
4711
  var import_react27 = require("@ai-matrx/records/react");
4492
- var import_core4 = require("@ai-matrx/records/core");
4712
+ var import_core5 = require("@ai-matrx/records/core");
4493
4713
  var import_design_system13 = require("@ai-matrx/design-system");
4714
+
4715
+ // src/importRuleCheck.ts
4716
+ var import_core4 = require("@ai-matrx/records/core");
4717
+ function checkImportRules(fields, rows, mapping) {
4718
+ const byKey = new Map(fields.map((f) => [f.key, f]));
4719
+ const verdicts = [];
4720
+ for (const [header, key] of Object.entries(mapping)) {
4721
+ const field = byKey.get(key);
4722
+ if (!field) continue;
4723
+ let refused = 0;
4724
+ let judged = 0;
4725
+ let first = null;
4726
+ rows.forEach((row, i) => {
4727
+ const raw = String(row[header] ?? "").trim();
4728
+ if (raw === "") return;
4729
+ judged += 1;
4730
+ const meant = valueFromPastedText(field, raw);
4731
+ let why = null;
4732
+ if ("refusal" in meant) why = meant.refusal;
4733
+ else {
4734
+ const predicted = (0, import_core4.predictValueRefusals)([field], { [key]: meant.value }).filter(
4735
+ (p) => p.field_key === void 0 || p.field_key === key
4736
+ );
4737
+ if (predicted.length > 0) why = predicted[0].message;
4738
+ }
4739
+ if (why !== null) {
4740
+ refused += 1;
4741
+ if (!first) first = { why, line: i + 1, raw };
4742
+ }
4743
+ });
4744
+ if (refused > 0 && first) {
4745
+ const f = first;
4746
+ verdicts.push({ header, field, refused, judged, firstWhy: f.why, firstLine: f.line, firstRaw: f.raw });
4747
+ }
4748
+ }
4749
+ return verdicts;
4750
+ }
4751
+
4752
+ // src/ImportWizard.tsx
4494
4753
  var import_jsx_runtime16 = require("react/jsx-runtime");
4495
4754
  var BATCH = 250;
4496
4755
  var SAMPLES = 40;
@@ -4543,9 +4802,13 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4543
4802
  const [openRun, setOpenRun] = (0, import_react26.useState)(null);
4544
4803
  const [runRows, setRunRows] = (0, import_react26.useState)(null);
4545
4804
  const [runProblem, setRunProblem] = (0, import_react26.useState)(null);
4805
+ const [runsProblem, setRunsProblem] = (0, import_react26.useState)(null);
4546
4806
  const loadRuns = (0, import_react26.useCallback)(async () => {
4547
4807
  const answered = await client.imports({ table_id: tableId, limit: 10 });
4548
- if (answered.ok) setRuns(answered.data);
4808
+ if (answered.ok) {
4809
+ setRuns(answered.data);
4810
+ setRunsProblem(null);
4811
+ } else setRunsProblem(answered.error);
4549
4812
  }, [client, tableId]);
4550
4813
  (0, import_react26.useEffect)(() => {
4551
4814
  void loadRuns();
@@ -4566,7 +4829,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4566
4829
  limit: 200
4567
4830
  });
4568
4831
  if (!answered.ok) {
4569
- setRunProblem(answered.error.message);
4832
+ setRunProblem(answered.error);
4570
4833
  return;
4571
4834
  }
4572
4835
  setRunRows({
@@ -4593,7 +4856,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4593
4856
  try {
4594
4857
  const xlsx = file.name.toLowerCase().endsWith(".xlsx");
4595
4858
  const bytes = await file.arrayBuffer();
4596
- const result = xlsx ? await (0, import_core4.importXlsx)(bytes) : (0, import_core4.importCsv)(new TextDecoder().decode(bytes));
4859
+ const result = xlsx ? await (0, import_core5.importXlsx)(bytes) : (0, import_core5.importCsv)(new TextDecoder().decode(bytes));
4597
4860
  setFormat(xlsx ? "xlsx" : "csv");
4598
4861
  setFileName(file.name);
4599
4862
  setFileBytes(file.size);
@@ -4607,7 +4870,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4607
4870
  }));
4608
4871
  const answered = await client.importPlan({ table_id: tableId, columns });
4609
4872
  if (!answered.ok) {
4610
- setProblem(answered.error.message);
4873
+ setProblem(answered.error);
4611
4874
  setPhase("waiting");
4612
4875
  return;
4613
4876
  }
@@ -4624,7 +4887,14 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4624
4887
  setMapping(next);
4625
4888
  setPhase("ready");
4626
4889
  } catch (thrown) {
4627
- setProblem(`That file could not be read: ${thrown instanceof Error ? thrown.message : String(thrown)}`);
4890
+ setProblem(
4891
+ refusalFromThrown(
4892
+ thrown,
4893
+ "That file could not be read, so nothing was imported.",
4894
+ "Save it again as CSV or Excel and pick it again.",
4895
+ "invalid_argument"
4896
+ )
4897
+ );
4628
4898
  setPhase("waiting");
4629
4899
  }
4630
4900
  },
@@ -4641,7 +4911,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4641
4911
  setPhase("declaring");
4642
4912
  const made = await client.importDeclareColumns({ table_id: tableId, rows, mapping });
4643
4913
  if (!made.ok) {
4644
- setProblem(made.error.message);
4914
+ setProblem(made.error);
4645
4915
  setPhase("ready");
4646
4916
  return;
4647
4917
  }
@@ -4660,7 +4930,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4660
4930
  policy: { on_duplicate: onDuplicate, unmapped }
4661
4931
  });
4662
4932
  if (!opened.ok) {
4663
- setProblem(opened.error.message);
4933
+ setProblem(opened.error);
4664
4934
  setPhase("ready");
4665
4935
  return;
4666
4936
  }
@@ -4679,7 +4949,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4679
4949
  const batch = rows.slice(at, at + take2);
4680
4950
  const answered = await client.importRows({ import_id: importId, rows: batch, mapping });
4681
4951
  if (!answered.ok) {
4682
- setProblem(answered.error.message);
4952
+ setProblem(answered.error);
4683
4953
  setOutcomes([...collected]);
4684
4954
  setLedger({ ...told, final: false });
4685
4955
  setPhase("ready");
@@ -4698,7 +4968,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4698
4968
  }
4699
4969
  const finished = await client.importFinish({ import_id: importId, unmapped });
4700
4970
  if (!finished.ok) {
4701
- setProblem(finished.error.message);
4971
+ setProblem(finished.error);
4702
4972
  setPhase("done");
4703
4973
  return;
4704
4974
  }
@@ -4736,6 +5006,11 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4736
5006
  const titleMissing = Boolean(titleKey) && titleMappedTo === null;
4737
5007
  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
5008
  const busy = phase === "reading" || phase === "planning" || phase === "declaring" || phase === "writing";
5009
+ const ruleVerdicts = (0, import_react26.useMemo)(() => checkImportRules(declared, rows, mapping), [declared, rows, mapping]);
5010
+ const ruleVerdictOf = (0, import_react26.useMemo)(() => new Map(ruleVerdicts.map((v) => [v.header, v])), [ruleVerdicts]);
5011
+ const refusedByRules = ruleVerdicts.reduce((n, v) => n + v.refused, 0);
5012
+ const [goAheadAnyway, setGoAheadAnyway] = (0, import_react26.useState)(false);
5013
+ (0, import_react26.useEffect)(() => setGoAheadAnyway(false), [ruleVerdicts]);
4739
5014
  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
5015
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center gap-2", children: [
4741
5016
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "font-medium", children: "Import" }),
@@ -4763,7 +5038,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4763
5038
  } : {}
4764
5039
  }
4765
5040
  ) : null,
4766
- problem ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-destructive", children: problem }) : null,
5041
+ problem ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(RefusalNotice, { error: problem }) : null,
4767
5042
  phase === "reading" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-muted-foreground", children: "Reading the file\u2026" }) : null,
4768
5043
  phase === "planning" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-muted-foreground", children: "Working out what each column is\u2026" }) : null,
4769
5044
  plan && parsed ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
@@ -4860,7 +5135,28 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4860
5135
  column.unit ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "ml-1 opacity-60", children: column.unit }) : null,
4861
5136
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "mt-0.5 text-muted-foreground", children: column.why }),
4862
5137
  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
5138
+ 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,
5139
+ ruleVerdictOf.get(column.header) ? (() => {
5140
+ const v = ruleVerdictOf.get(column.header);
5141
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { "data-import-rule-refusal": column.header, className: "mt-1", children: [
5142
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5143
+ RefusalLine,
5144
+ {
5145
+ error: refusal(
5146
+ "refused_by_rule",
5147
+ `${v.refused} of ${v.judged} row${v.judged === 1 ? "" : "s"} would be refused by ${fieldName(v.field)}'s rules. ${v.firstWhy}`,
5148
+ "Send this column somewhere else, fix those rows in your file, or import the rest and they are listed afterwards."
5149
+ )
5150
+ }
5151
+ ),
5152
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("p", { className: "text-muted-foreground", "data-import-rule-sample": "", children: [
5153
+ "Line ",
5154
+ v.firstLine,
5155
+ " in your file: ",
5156
+ v.firstRaw
5157
+ ] })
5158
+ ] });
5159
+ })() : null
4864
5160
  ] }),
4865
5161
  /* @__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
5162
  ]
@@ -4924,12 +5220,34 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4924
5220
  )
4925
5221
  ] })
4926
5222
  ] }),
5223
+ ruleVerdicts.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5224
+ RefusalNotice,
5225
+ {
5226
+ error: refusal(
5227
+ "refused_by_rule",
5228
+ `${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.`,
5229
+ "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."
5230
+ ),
5231
+ actions: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("label", { className: "mt-1 flex items-center gap-1.5", "data-import-go-ahead": "", children: [
5232
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5233
+ "input",
5234
+ {
5235
+ type: "checkbox",
5236
+ checked: goAheadAnyway,
5237
+ disabled: busy,
5238
+ onChange: (e) => setGoAheadAnyway(e.target.checked)
5239
+ }
5240
+ ),
5241
+ "Import the rest anyway"
5242
+ ] })
5243
+ }
5244
+ ) : null,
4927
5245
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center gap-2", children: [
4928
5246
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4929
5247
  import_design_system13.Button,
4930
5248
  {
4931
5249
  size: "sm",
4932
- disabled: busy || !rights.write || rows.length === 0 || titleMissing,
5250
+ disabled: busy || !rights.write || rows.length === 0 || titleMissing || ruleVerdicts.length > 0 && !goAheadAnyway,
4933
5251
  onClick: () => void run(),
4934
5252
  children: phase === "declaring" ? "Adding the columns\u2026" : phase === "writing" ? `Writing\u2026 ${progress} of ${rows.length}` : `Import ${rows.length} row${rows.length === 1 ? "" : "s"}`
4935
5253
  }
@@ -4984,7 +5302,12 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4984
5302
  ] }) }),
4985
5303
  /* @__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
5304
  /* @__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" }),
5305
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("td", { className: "px-2 py-1", children: o.outcome === "refused" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5306
+ RefusalLine,
5307
+ {
5308
+ error: refusal("refused_by_rule", o.reason, "Fix this line in your file and import it again.")
5309
+ }
5310
+ ) : o.outcome === "duplicate" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-muted-foreground", children: o.reason }) : "written" }),
4988
5311
  /* @__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
5312
  ] }, `${o.outcome}-${o.row}`)) })
4990
5313
  ] }) }) : null,
@@ -5007,6 +5330,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
5007
5330
  ] }) : null
5008
5331
  ] }) : null
5009
5332
  ] }) : null,
5333
+ runsProblem ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(RefusalNotice, { error: runsProblem }) : null,
5010
5334
  runs && runs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
5011
5335
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system13.Separator, {}),
5012
5336
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "font-medium", children: "Imports into this table" }),
@@ -5039,7 +5363,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
5039
5363
  }
5040
5364
  ) : null
5041
5365
  ] }),
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: [
5366
+ 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
5367
  runRows.note ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-muted-foreground", children: runRows.note }) : null,
5044
5368
  /* @__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
5369
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("thead", { className: "sticky top-0 bg-muted", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("tr", { children: [
@@ -5051,7 +5375,16 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
5051
5375
  const source = row.source ?? {};
5052
5376
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("tr", { className: "border-t align-top", children: [
5053
5377
  /* @__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 ?? "") }),
5378
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("td", { className: "px-2 py-1", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5379
+ RefusalLine,
5380
+ {
5381
+ error: refusal(
5382
+ "refused_by_rule",
5383
+ String(row.reason ?? "This row was refused."),
5384
+ "Fix this line in your file and import it again."
5385
+ )
5386
+ }
5387
+ ) }),
5055
5388
  /* @__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
5389
  ] }, `${r.import_id}-${String(row.row ?? i)}`);
5057
5390
  }) })
@@ -5270,7 +5603,9 @@ function keyFor(label) {
5270
5603
  if (token === "") return "";
5271
5604
  return /^[a-z]/.test(token) ? token : `f_${token}`;
5272
5605
  }
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.";
5606
+ 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.";
5607
+ 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.";
5608
+ var COLUMN_DID_NOT_LAND = `${COLUMN_DID_NOT_LAND_SENTENCE} ${COLUMN_DID_NOT_LAND_REMEDY}`;
5274
5609
  function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }) {
5275
5610
  const table = (0, import_react31.useTable)(tableId);
5276
5611
  const fields = (0, import_react31.useFields)(tableId);
@@ -5570,14 +5905,20 @@ function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }
5570
5905
  ] }, `${rule.kind}-${index}`))
5571
5906
  ] }),
5572
5907
  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,
5908
+ 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,
5909
+ notLanded ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5910
+ RefusalNotice,
5911
+ {
5912
+ error: refusal("internal", COLUMN_DID_NOT_LAND_SENTENCE, COLUMN_DID_NOT_LAND_REMEDY)
5913
+ }
5914
+ ) : null,
5575
5915
  /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
5576
5916
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5577
5917
  import_design_system15.Button,
5578
5918
  {
5579
5919
  size: "sm",
5580
5920
  disabled: mutation.saving || missing !== null,
5921
+ ...missing ? { "aria-describedby": "field-editor-missing" } : {},
5581
5922
  onClick: () => void save(),
5582
5923
  children: mutation.saving ? "Saving\u2026" : field ? EDIT_FIELD_SAVE_LABEL : ADD_FIELD_SAVE_LABEL
5583
5924
  }
@@ -6555,8 +6896,8 @@ function TableSettings({
6555
6896
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_design_system18.Button, { size: "sm", variant: "ghost", onClick: () => setAskingToRemove(null), children: "Keep it" })
6556
6897
  ] }) : null,
6557
6898
  /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("ul", { className: "divide-y rounded border", children: [
6558
- rows.map((field, index) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("li", { className: "flex items-center gap-2 px-2 py-1.5", children: [
6559
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "min-w-0 flex-1 truncate", children: fieldName(field) }),
6899
+ rows.map((field, index) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("li", { "data-settings-field": "", className: "flex flex-wrap items-center gap-x-2 gap-y-1 px-2 py-1.5", children: [
6900
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { "data-settings-field-name": "", className: "min-w-[8rem] flex-1 break-words", children: fieldName(field) }),
6560
6901
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_design_system18.Badge, { variant: "outline", className: "text-[10px] font-normal", children: fieldTypeLabel(field) }),
6561
6902
  field.required ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-[10px] text-destructive", children: "required" }) : null,
6562
6903
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
@@ -6613,9 +6954,9 @@ function TableSettings({
6613
6954
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "font-medium", children: "Proposed" }),
6614
6955
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-muted-foreground", children: proposals?.length ?? 0 })
6615
6956
  ] }),
6616
- proposals === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "text-muted-foreground", children: "Nothing is listing proposals here yet: the store has no `custom.field_propose` door, so this panel would be inventing an empty queue. When the door lands, its rows appear in this list with the same accept and reject." }) : proposals.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "text-muted-foreground", children: "No field is waiting for a decision." }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("ul", { className: "divide-y rounded border", children: proposals.map((proposal) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("li", { className: "flex items-center gap-2 px-2 py-1.5", children: [
6617
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "min-w-0 flex-1 truncate", children: proposal.field.label || proposal.field.key }),
6618
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "min-w-0 flex-1 truncate text-muted-foreground", children: proposal.why }),
6957
+ proposals === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "text-muted-foreground", children: "Nothing is listing proposals here yet: the store has no `custom.field_propose` door, so this panel would be inventing an empty queue. When the door lands, its rows appear in this list with the same accept and reject." }) : proposals.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "text-muted-foreground", children: "No field is waiting for a decision." }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("ul", { className: "divide-y rounded border", children: proposals.map((proposal) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("li", { className: "flex flex-wrap items-center gap-x-2 gap-y-1 px-2 py-1.5", children: [
6958
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "min-w-[8rem] flex-1 break-words", children: proposal.field.label || proposal.field.key }),
6959
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "min-w-[8rem] flex-1 break-words text-muted-foreground", children: proposal.why }),
6619
6960
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_design_system18.Badge, { variant: "secondary", className: "text-[10px] font-normal", children: proposal.proposed_by }),
6620
6961
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_design_system18.Button, { size: "sm", variant: "outline", onClick: () => onAcceptProposal?.(proposal), children: "Accept" }),
6621
6962
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_design_system18.Button, { size: "sm", variant: "ghost", onClick: () => onRejectProposal?.(proposal), children: "Reject" })
@@ -6790,7 +7131,7 @@ var import_design_system22 = require("@ai-matrx/design-system");
6790
7131
  // src/RecordForm.tsx
6791
7132
  var import_react41 = require("react");
6792
7133
  var import_react42 = require("@ai-matrx/records/react");
6793
- var import_core5 = require("@ai-matrx/records/core");
7134
+ var import_core6 = require("@ai-matrx/records/core");
6794
7135
  var import_design_system20 = require("@ai-matrx/design-system");
6795
7136
 
6796
7137
  // src/systemTable.ts
@@ -6980,6 +7321,8 @@ function RecordForm({
6980
7321
  const mutation = (0, import_react42.useRecordMutation)();
6981
7322
  const [draft, setDraft] = (0, import_react41.useState)({});
6982
7323
  const [touched, setTouched] = (0, import_react41.useState)(false);
7324
+ const [changed, setChanged] = (0, import_react41.useState)({});
7325
+ const [answered, setAnswered] = (0, import_react41.useState)(null);
6983
7326
  const loaded = useRecordVersion(recordId ?? null);
6984
7327
  const loadedVersion = loaded.version;
6985
7328
  (0, import_react41.useEffect)(() => {
@@ -6995,13 +7338,43 @@ function RecordForm({
6995
7338
  return out;
6996
7339
  }, [draft]);
6997
7340
  const predicted = (0, import_react41.useMemo)(
6998
- () => fields.data ? (0, import_core5.predictWriteRefusals)({ fields: fields.data, document: document2, ...recordType ? { recordType } : {} }) : [],
7341
+ () => fields.data ? (0, import_core6.predictWriteRefusals)({ fields: fields.data, document: document2, ...recordType ? { recordType } : {} }) : [],
6999
7342
  [fields.data, document2, recordType]
7000
7343
  );
7001
7344
  if (fields.error) return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(RefusalNotice, { error: fields.error, className });
7002
7345
  if (existing.error) return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(RefusalNotice, { error: existing.error, className });
7346
+ const storeSaid = mutation.error && mutation.error !== answered ? mutation.error : null;
7347
+ const fieldProblems = predicted.filter((p) => p.field_key);
7348
+ const problemsShown = (key) => touched || changed[key] ? fieldProblems.filter((p) => p.field_key === key) : [];
7349
+ const storeRefusalIsOnAField = storeSaid !== null && !mutation.conflict && storeSaid.code === "refused_by_rule" && (fields.data ?? []).some((f) => problemsShown(f.key).length > 0);
7350
+ const stored = existing.data?.document;
7351
+ const focusField = (field) => {
7352
+ if (!field) return;
7353
+ globalThis.document?.getElementById(`field-${field.id}`)?.focus();
7354
+ };
7355
+ const keepEditing = (field) => {
7356
+ if (storeSaid) setAnswered(storeSaid);
7357
+ const first = field ?? (fields.data ?? []).find((f) => problemsShown(f.key).length > 0) ?? (fields.data ?? [])[0];
7358
+ focusField(first);
7359
+ };
7360
+ const discard = (field) => {
7361
+ if (storeSaid) setAnswered(storeSaid);
7362
+ if (field) {
7363
+ setDraft((d) => ({ ...d, [field.key]: stored ? stored[field.key] : void 0 }));
7364
+ setChanged((c) => {
7365
+ const next = { ...c };
7366
+ delete next[field.key];
7367
+ return next;
7368
+ });
7369
+ return;
7370
+ }
7371
+ setDraft(stored ? { ...stored } : {});
7372
+ setChanged({});
7373
+ setTouched(false);
7374
+ };
7003
7375
  const save = async () => {
7004
7376
  setTouched(true);
7377
+ setAnswered(null);
7005
7378
  if (recordId) {
7006
7379
  const version = loadedVersion;
7007
7380
  const moved = await mutation.update({
@@ -7038,16 +7411,27 @@ function RecordForm({
7038
7411
  field,
7039
7412
  value: draft[field.key],
7040
7413
  document: existing.data?.document,
7041
- problems: touched ? predicted.filter((p) => p.field_key === field.key) : [],
7042
- onChange: (value) => setDraft((d) => ({ ...d, [field.key]: value }))
7414
+ problems: problemsShown(field.key),
7415
+ onChange: (value) => {
7416
+ setDraft((d) => ({ ...d, [field.key]: value }));
7417
+ setChanged((c) => c[field.key] ? c : { ...c, [field.key]: true });
7418
+ },
7419
+ onKeepEditing: () => keepEditing(field),
7420
+ onDiscard: () => discard(field)
7043
7421
  },
7044
7422
  field.id
7045
7423
  )) }),
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,
7047
- mutation.error ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7424
+ touched ? predicted.filter((p) => !p.field_key).map((p) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7425
+ RefusalNotice,
7426
+ {
7427
+ error: { code: "refused_by_rule", message: p.message }
7428
+ },
7429
+ p.message
7430
+ )) : null,
7431
+ storeSaid && !storeRefusalIsOnAField ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7048
7432
  RefusalNotice,
7049
7433
  {
7050
- error: mutation.error,
7434
+ error: storeSaid,
7051
7435
  ...mutation.conflict ? {
7052
7436
  actions: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "mt-1 space-y-1", children: [
7053
7437
  Object.entries(mutation.conflict.contested_fields).map(([key, theirs]) => {
@@ -7068,14 +7452,16 @@ function RecordForm({
7068
7452
  size: "sm",
7069
7453
  variant: "outline",
7070
7454
  onClick: () => {
7455
+ if (mutation.conflict) loaded.note(mutation.conflict.current_version);
7071
7456
  existing.reload();
7072
7457
  mutation.clearConflict();
7458
+ setAnswered(storeSaid);
7073
7459
  },
7074
7460
  children: "Take theirs and start again"
7075
7461
  }
7076
7462
  )
7077
7463
  ] })
7078
- } : {}
7464
+ } : { onKeepEditing: () => keepEditing(), onDiscard: () => discard() }
7079
7465
  }
7080
7466
  ) : null,
7081
7467
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_design_system20.Separator, {}),
@@ -7092,13 +7478,23 @@ function FieldRow({
7092
7478
  value,
7093
7479
  document: document2,
7094
7480
  problems,
7095
- onChange
7481
+ onChange,
7482
+ onKeepEditing,
7483
+ onDiscard
7096
7484
  }) {
7097
7485
  const id = `field-${field.id}`;
7098
7486
  return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex min-w-0 flex-col gap-1", children: [
7099
7487
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(FieldLabel, { field, htmlFor: id, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ProvenanceBadge, { document: document2, fieldKey: field.key }) }),
7100
7488
  /* @__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))
7489
+ problems.map((problem, index) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7490
+ RefusalNotice,
7491
+ {
7492
+ error: { code: "refused_by_rule", message: problem.message },
7493
+ className: "border-0 p-0",
7494
+ ...index === 0 ? { onKeepEditing, onDiscard } : {}
7495
+ },
7496
+ problem.message
7497
+ ))
7102
7498
  ] });
7103
7499
  }
7104
7500
  function asWords(value) {
@@ -7124,12 +7520,16 @@ function ShareControl({
7124
7520
  subjectId,
7125
7521
  name,
7126
7522
  may,
7523
+ initiallyOpen = false,
7127
7524
  size = "sm",
7128
7525
  variant = "ghost",
7129
7526
  className
7130
7527
  }) {
7131
7528
  const host = useRecordsUi();
7132
- const [open, setOpen] = (0, import_react43.useState)(false);
7529
+ const [open, setOpen] = (0, import_react43.useState)(initiallyOpen);
7530
+ (0, import_react43.useEffect)(() => {
7531
+ if (initiallyOpen) setOpen(true);
7532
+ }, [initiallyOpen]);
7133
7533
  const asked = useRecordRights(may === void 0 ? subjectId : null);
7134
7534
  const mayShare = may ?? asked.share;
7135
7535
  if (!host.share) return null;
@@ -7261,6 +7661,7 @@ function computedInPlainWords(row, field) {
7261
7661
  }
7262
7662
 
7263
7663
  // src/views.ts
7664
+ var import_core7 = require("@ai-matrx/records/core");
7264
7665
  var VIEW_LAYOUTS = ["grid", "kanban", "calendar", "gallery"];
7265
7666
  var LAYOUT_LABEL = {
7266
7667
  grid: "Grid",
@@ -7384,6 +7785,18 @@ function viewFromRecord(row) {
7384
7785
  presentation: parseGridPresentation(data["presentation"])
7385
7786
  };
7386
7787
  }
7788
+ async function saveViewPatch(client, viewId, document2, expectedVersion) {
7789
+ const send = (expected) => client.recordUpdate({
7790
+ record_id: viewId,
7791
+ patch: document2,
7792
+ ...expected === null ? {} : { expectedVersion: expected }
7793
+ });
7794
+ const first = await send(expectedVersion);
7795
+ if (first.ok) return first;
7796
+ const conflict = (0, import_core7.asWriteConflict)(first.error, viewId);
7797
+ if (!conflict) return first;
7798
+ return send(conflict.current_version);
7799
+ }
7387
7800
  function viewPatchDocument(patch) {
7388
7801
  const out = {};
7389
7802
  if (patch.name !== void 0) out["name"] = patch.name;
@@ -7707,10 +8120,22 @@ function Held({
7707
8120
  );
7708
8121
  }
7709
8122
  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
- ] });
8123
+ return (
8124
+ // THE STAGE GATE SAID NO: the one refusal surface, heading and remedy both,
8125
+ // never its sentence alone in a red box (lane REFUSAL-SWEEP).
8126
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
8127
+ RefusalNotice,
8128
+ {
8129
+ className: "mt-1",
8130
+ error: refusal(
8131
+ "refused_by_rule",
8132
+ pending.verdict.why,
8133
+ "The card stays where it was. Fill in what this stage asks for, then move it again."
8134
+ ),
8135
+ actions: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_design_system23.Button, { size: "sm", variant: "ghost", className: "mt-1 h-6", onClick: onCancel, children: "Close" })
8136
+ }
8137
+ )
8138
+ );
7714
8139
  }
7715
8140
  if (pending.kind === "approval") {
7716
8141
  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 +8699,7 @@ function ArchivedView({
8274
8699
  const [loading, setLoading] = (0, import_react51.useState)(true);
8275
8700
  const [error, setError] = (0, import_react51.useState)(null);
8276
8701
  const [restoring, setRestoring] = (0, import_react51.useState)(null);
8277
- const [refusal, setRefusal] = (0, import_react51.useState)(null);
8702
+ const [refusal2, setRefusal] = (0, import_react51.useState)(null);
8278
8703
  (0, import_react51.useEffect)(() => {
8279
8704
  if (laneFromHost) setLane(laneFromHost);
8280
8705
  }, [laneFromHost]);
@@ -8351,7 +8776,7 @@ function ArchivedView({
8351
8776
  /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col", children: [
8352
8777
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "truncate text-sm", children: recordName(row.document, titleKey) }),
8353
8778
  /* @__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
8779
+ refusal2 && refusal2.recordId === row.id ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(RefusalNotice, { error: refusal2.error, className: "mt-2" }) : null
8355
8780
  ] }),
8356
8781
  mayRestore ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
8357
8782
  import_design_system25.Button,
@@ -8425,7 +8850,7 @@ function ArchivedPortals({
8425
8850
  const [confirming, setConfirming] = (0, import_react53.useState)(null);
8426
8851
  const [typed, setTyped] = (0, import_react53.useState)("");
8427
8852
  const [restoring, setRestoring] = (0, import_react53.useState)(null);
8428
- const [refusal, setRefusal] = (0, import_react53.useState)(null);
8853
+ const [refusal2, setRefusal] = (0, import_react53.useState)(null);
8429
8854
  const [said, setSaid] = (0, import_react53.useState)(null);
8430
8855
  const read = (0, import_react53.useCallback)(async () => {
8431
8856
  const answered = await client.listPortals({ archived: "archived" });
@@ -8541,7 +8966,7 @@ function ArchivedPortals({
8541
8966
  )
8542
8967
  ] })
8543
8968
  ] }) : null,
8544
- refusal && refusal.portalId === portal.portal_id ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(RefusalNotice, { error: refusal.error }) : null
8969
+ refusal2 && refusal2.portalId === portal.portal_id ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(RefusalNotice, { error: refusal2.error }) : null
8545
8970
  ]
8546
8971
  },
8547
8972
  portal.portal_id
@@ -9106,7 +9531,7 @@ function MyChecklistSteps({ className }) {
9106
9531
  const client = (0, import_react62.useRecordsClient)();
9107
9532
  const { steps, loading, error, refresh } = useMyChecklistSteps();
9108
9533
  const [busy, setBusy] = (0, import_react61.useState)(null);
9109
- const [refusal, setRefusal] = (0, import_react61.useState)(null);
9534
+ const [refusal2, setRefusal] = (0, import_react61.useState)(null);
9110
9535
  const complete = (0, import_react61.useCallback)(
9111
9536
  async (step2, evidence) => {
9112
9537
  setBusy(step2.step_id);
@@ -9129,7 +9554,7 @@ function MyChecklistSteps({ className }) {
9129
9554
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "tabular-nums", children: steps.length })
9130
9555
  ] }),
9131
9556
  error ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(RefusalNotice, { error }) : null,
9132
- refusal ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(RefusalNotice, { error: refusal }) : null,
9557
+ refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(RefusalNotice, { error: refusal2 }) : null,
9133
9558
  /* @__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
9559
  StepRow,
9135
9560
  {
@@ -9306,7 +9731,7 @@ function ChecklistTemplateEditor({
9306
9731
  const client = (0, import_react62.useRecordsClient)();
9307
9732
  const [name, setName] = (0, import_react61.useState)("");
9308
9733
  const [rows, setRows] = (0, import_react61.useState)([{ ...EMPTY_ROW }]);
9309
- const [refusal, setRefusal] = (0, import_react61.useState)(null);
9734
+ const [refusal2, setRefusal] = (0, import_react61.useState)(null);
9310
9735
  const [error, setError] = (0, import_react61.useState)(null);
9311
9736
  const [busy, setBusy] = (0, import_react61.useState)(false);
9312
9737
  const [loading, setLoading] = (0, import_react61.useState)(Boolean(templateId));
@@ -9474,9 +9899,9 @@ function ChecklistTemplateEditor({
9474
9899
  ] }, index)) }),
9475
9900
  /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-center gap-1.5", children: [
9476
9901
  /* @__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" })
9902
+ /* @__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
9903
  ] }),
9479
- refusal ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "text-xs text-muted-foreground", "data-testid": "checklist-editor-refusal", children: refusal }) : null
9904
+ refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "text-xs text-muted-foreground", "data-testid": "checklist-editor-refusal", children: refusal2 }) : null
9480
9905
  ] });
9481
9906
  }
9482
9907
  function toStepSpec(row, index) {
@@ -10769,6 +11194,7 @@ function BuildOrAsk({
10769
11194
 
10770
11195
  // src/PortalsPanel.tsx
10771
11196
  var import_jsx_runtime39 = require("react/jsx-runtime");
11197
+ 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
11198
  function revokeConsequence(person, portalTitle) {
10773
11199
  const who = person.client ? `${person.email} (${person.client})` : person.email;
10774
11200
  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 +11224,16 @@ function stateWords(person) {
10798
11224
  };
10799
11225
  }
10800
11226
  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 }) {
11227
+ function PortalsPanel({ tableId, activePortalId, className }) {
10802
11228
  const client = (0, import_react74.useRecordsClient)();
10803
11229
  const host = useRecordsUi();
10804
11230
  const [portals, setPortals] = (0, import_react73.useState)(null);
10805
11231
  const [exposures, setExposures] = (0, import_react73.useState)([]);
10806
11232
  const [listError, setListError] = (0, import_react73.useState)(null);
10807
- const [openId, setOpenId] = (0, import_react73.useState)(null);
11233
+ const [openId, setOpenId] = (0, import_react73.useState)(activePortalId ?? null);
11234
+ (0, import_react73.useEffect)(() => {
11235
+ if (activePortalId) setOpenId(activePortalId);
11236
+ }, [activePortalId]);
10808
11237
  const [building, setBuilding] = (0, import_react73.useState)(false);
10809
11238
  const [adding, setAdding] = (0, import_react73.useState)(null);
10810
11239
  const [archiveToken, setArchiveToken] = (0, import_react73.useState)(0);
@@ -10901,59 +11330,73 @@ function PortalsPanel({ tableId, className }) {
10901
11330
  portal.portal_id
10902
11331
  )) })
10903
11332
  ] }) : null,
11333
+ 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
11334
  /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("ul", { className: "flex flex-col gap-2", children: shown.map((portal) => {
10905
11335
  const url = `${origin}${(0, import_records9.portalPath)(portal.slug)}`;
10906
11336
  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
- }
11337
+ const linked = portal.portal_id === activePortalId;
11338
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
11339
+ "li",
11340
+ {
11341
+ "data-linked": linked ? "true" : void 0,
11342
+ ref: linked ? (el) => el?.scrollIntoView({ block: "nearest" }) : void 0,
11343
+ className: (0, import_design_system36.cn)(
11344
+ "rounded-md border border-border bg-card p-2.5",
11345
+ linked && "border-primary ring-1 ring-primary"
10935
11346
  ),
10936
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
10937
- ArchivePortalControl,
10938
- {
10939
- portal,
10940
- onArchived: () => {
10941
- setArchiveToken((t) => t + 1);
10942
- void load();
11347
+ children: [
11348
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex items-center gap-2", children: [
11349
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: portal.title }),
11350
+ portal.is_active ? null : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_design_system36.Badge, { variant: "outline", children: "closed" })
11351
+ ] }),
11352
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("p", { className: "mt-0.5 text-xs text-muted-foreground", children: [
11353
+ "Clients come from ",
11354
+ portal.client_table,
11355
+ portal.tables === 1 ? ", and see 1 Table" : `, and see ${portal.tables} Tables`,
11356
+ "."
11357
+ ] }),
11358
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("p", { className: "mt-1.5 text-xs", children: [
11359
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "font-medium", children: portal.signed_in }),
11360
+ " signed in",
11361
+ " \xB7 ",
11362
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "font-medium", children: portal.invited - portal.signed_in }),
11363
+ " invited and waiting"
11364
+ ] }),
11365
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "mt-2 flex flex-wrap items-center gap-1.5", children: [
11366
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(CopyLink, { url }),
11367
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
11368
+ import_design_system36.Button,
11369
+ {
11370
+ size: "sm",
11371
+ variant: "ghost",
11372
+ onClick: () => setOpenId(open ? null : portal.portal_id),
11373
+ children: open ? "Close" : "Open it"
11374
+ }
11375
+ ),
11376
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
11377
+ ArchivePortalControl,
11378
+ {
11379
+ portal,
11380
+ onArchived: () => {
11381
+ setArchiveToken((t) => t + 1);
11382
+ void load();
11383
+ }
11384
+ }
11385
+ )
11386
+ ] }),
11387
+ open ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
11388
+ PortalDetail,
11389
+ {
11390
+ portalId: portal.portal_id,
11391
+ ...tableId ? { tableId } : {},
11392
+ origin,
11393
+ onChanged: () => void load()
10943
11394
  }
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);
11395
+ ) : null
11396
+ ]
11397
+ },
11398
+ portal.portal_id
11399
+ );
10957
11400
  }) }),
10958
11401
  /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ArchivedPortals, { refreshToken: archiveToken, onRestored: () => void load() })
10959
11402
  ] });
@@ -11711,7 +12154,17 @@ function DigestScheduler({
11711
12154
  preview ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "rounded-md border bg-muted/40 p-2.5 text-xs", children: [
11712
12155
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("p", { className: "font-medium", children: preview.subject }),
11713
12156
  /* @__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,
12157
+ preview.incomplete ? (
12158
+ // The summary could not be finished: a refusal line naming the missing
12159
+ // piece and what to do, never the bare sentence in red (lane REFUSAL-SWEEP).
12160
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
12161
+ RefusalLine,
12162
+ {
12163
+ className: "mt-1",
12164
+ error: refusal("not_supported", preview.incomplete, "Fill in the missing piece above, then preview again.")
12165
+ }
12166
+ )
12167
+ ) : null,
11715
12168
  preview.entered.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("p", { className: "mt-1", children: [
11716
12169
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "font-medium", children: "Arrived:" }),
11717
12170
  " ",
@@ -11733,14 +12186,30 @@ var import_react77 = require("react");
11733
12186
  var import_react78 = require("@ai-matrx/records/react");
11734
12187
  var import_design_system38 = require("@ai-matrx/design-system");
11735
12188
  var import_jsx_runtime41 = require("react/jsx-runtime");
12189
+ 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
12190
  function whenItFires(subscription) {
11737
12191
  if (subscription.cadence === "instant") return "as it happens";
11738
12192
  const every = subscription.cadence === "hourly" ? "an hourly summary" : subscription.cadence === "weekly" ? "a weekly summary" : "a daily summary";
11739
12193
  return subscription.schedule ? `${every}, ${subscription.schedule}` : every;
11740
12194
  }
12195
+ var PERIOD_MS = {
12196
+ hourly: 60 * 60 * 1e3,
12197
+ daily: 24 * 60 * 60 * 1e3,
12198
+ weekly: 7 * 24 * 60 * 60 * 1e3
12199
+ };
12200
+ function nextSummaryAt(subscription, now) {
12201
+ if (!subscription.next_digest_at) return null;
12202
+ const due = new Date(subscription.next_digest_at);
12203
+ if (Number.isNaN(due.getTime())) return null;
12204
+ if (due.getTime() > now.getTime()) return due;
12205
+ const period = PERIOD_MS[subscription.cadence];
12206
+ if (!period) return due;
12207
+ const behind = now.getTime() - due.getTime();
12208
+ return new Date(due.getTime() + (Math.floor(behind / period) + 1) * period);
12209
+ }
11741
12210
  var CHANNEL_WORDS2 = SUBSCRIPTION_CHANNEL_LABEL;
11742
12211
  var DIGEST_SUGGESTION = "Email me a summary of this every Monday at 8 in the morning.";
11743
- function SubscriptionsPanel({ tableId, className }) {
12212
+ function SubscriptionsPanel({ tableId, activeRuleId, className }) {
11744
12213
  const client = (0, import_react78.useRecordsClient)();
11745
12214
  const [rows, setRows] = (0, import_react77.useState)(null);
11746
12215
  const [error, setError] = (0, import_react77.useState)(null);
@@ -11831,81 +12300,104 @@ function SubscriptionsPanel({ tableId, className }) {
11831
12300
  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
12301
  }
11833
12302
  ) : 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)) })
12303
+ 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,
12304
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("ul", { className: "flex flex-col gap-2", children: rows.map((subscription) => /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
12305
+ "li",
12306
+ {
12307
+ "data-linked": subscription.rule_id === activeRuleId ? "true" : void 0,
12308
+ ref: subscription.rule_id === activeRuleId ? (el) => el?.scrollIntoView({ block: "nearest" }) : void 0,
12309
+ className: (0, import_design_system38.cn)(
12310
+ "rounded-md border p-2.5",
12311
+ subscription.rule_id === activeRuleId && "border-primary ring-1 ring-primary"
12312
+ ),
12313
+ children: [
12314
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "flex items-start gap-2", children: [
12315
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "min-w-0 flex-1", children: [
12316
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "truncate text-sm font-medium", children: subscription.name }),
12317
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("p", { className: "mt-0.5 text-xs text-muted-foreground", children: [
12318
+ CHANNEL_WORDS2[subscription.channel] ?? `on the ${subscription.channel} channel`,
12319
+ " \xB7 ",
12320
+ whenItFires(subscription)
12321
+ ] })
12322
+ ] }),
12323
+ subscription.i_may_mute ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
12324
+ import_design_system38.Switch,
12325
+ {
12326
+ checked: !subscription.muted,
12327
+ disabled: busy === subscription.rule_id,
12328
+ "aria-label": `Tell me about ${subscription.name}`,
12329
+ onCheckedChange: (on) => void flip(subscription, on)
12330
+ }
12331
+ ) : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_design_system38.Badge, { variant: "secondary", children: "someone else's" })
12332
+ ] }),
12333
+ /* @__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." }),
12334
+ /* @__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: [
12335
+ subscription.next_digest_at ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { children: [
12336
+ "Next summary ",
12337
+ nextSummaryAt(subscription, /* @__PURE__ */ new Date())?.toLocaleString()
12338
+ ] }) : subscription.cadence === "instant" ? null : subscription.muted ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { children: "No next summary while it is off." }) : null,
12339
+ subscription.last_sent_at ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { children: [
12340
+ "Last told you ",
12341
+ new Date(subscription.last_sent_at).toLocaleString()
12342
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { children: "It has not told you anything yet." }),
12343
+ subscription.quiet_hours ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { children: [
12344
+ "Not between ",
12345
+ subscription.quiet_hours.start,
12346
+ " and ",
12347
+ subscription.quiet_hours.end,
12348
+ " \u2014 a send inside those hours waits until they end."
12349
+ ] }) : null,
12350
+ subscription.cadence === "instant" ? null : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
12351
+ import_design_system38.Button,
12352
+ {
12353
+ size: "sm",
12354
+ variant: "ghost",
12355
+ disabled: busy === subscription.rule_id,
12356
+ onClick: () => void showOne(subscription),
12357
+ children: "Send me a preview now"
12358
+ }
12359
+ )
12360
+ ] }),
12361
+ 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: [
12362
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "font-medium", children: preview.subject }),
12363
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "mt-1 text-muted-foreground", children: preview.body }),
12364
+ preview.incomplete ? (
12365
+ // Absent or honest: a subscription that cannot produce a summary
12366
+ // says which piece is missing instead of showing an empty one.
12367
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
12368
+ RefusalLine,
12369
+ {
12370
+ className: "mt-1",
12371
+ error: refusal(
12372
+ "not_supported",
12373
+ preview.incomplete,
12374
+ "Fill in the missing piece, then preview again."
12375
+ )
12376
+ }
12377
+ )
12378
+ ) : null,
12379
+ preview.entered.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("p", { className: "mt-1", children: [
12380
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "font-medium", children: "Arrived:" }),
12381
+ " ",
12382
+ preview.entered.map((e) => e.name).join(", ")
12383
+ ] }) : null,
12384
+ preview.left.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("p", { className: "mt-1", children: [
12385
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "font-medium", children: "Left:" }),
12386
+ " ",
12387
+ preview.left.map((e) => e.name).join(", ")
12388
+ ] }) : null,
12389
+ preview.changed.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("p", { className: "mt-1", children: [
12390
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "font-medium", children: "Changed:" }),
12391
+ " ",
12392
+ preview.changed.map((e) => e.name).join(", ")
12393
+ ] }) : null,
12394
+ /* @__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." }),
12395
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_design_system38.Button, { size: "sm", variant: "ghost", className: "mt-1", onClick: () => setPreview(null), children: "Close" })
12396
+ ] }) : null
12397
+ ]
12398
+ },
12399
+ subscription.rule_id
12400
+ )) })
11909
12401
  ] });
11910
12402
  }
11911
12403
 
@@ -11960,9 +12452,16 @@ function ConnectedFormRunner(props) {
11960
12452
  const submit = (0, import_react81.useCallback)(
11961
12453
  async (values) => {
11962
12454
  if (!client) {
12455
+ const said = "This form cannot send answers from here, so nothing was sent.";
11963
12456
  return {
11964
12457
  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."
12458
+ message: said,
12459
+ error: refusal(
12460
+ "not_supported",
12461
+ said,
12462
+ "Open the form from its own link and answer it there.",
12463
+ "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."
12464
+ )
11966
12465
  };
11967
12466
  }
11968
12467
  const document2 = {
@@ -11975,7 +12474,7 @@ function ConnectedFormRunner(props) {
11975
12474
  })
11976
12475
  };
11977
12476
  const written = await client.recordWrite({ table_id: form.subject, data: document2 });
11978
- if (!written.ok) return { ok: false, message: written.error.message };
12477
+ if (!written.ok) return { ok: false, message: written.error.message, error: written.error };
11979
12478
  return { ok: true, recordId: written.data };
11980
12479
  },
11981
12480
  [client, form]
@@ -12017,7 +12516,7 @@ function FormStage({
12017
12516
  const [answers, setAnswers] = (0, import_react81.useState)({});
12018
12517
  const [at, setAt] = (0, import_react81.useState)(0);
12019
12518
  const [error, setError] = (0, import_react81.useState)(null);
12020
- const [refusal, setRefusal] = (0, import_react81.useState)(null);
12519
+ const [refusal2, setRefusal] = (0, import_react81.useState)(null);
12021
12520
  const [writing, setWriting] = (0, import_react81.useState)(false);
12022
12521
  const [done, setDone] = (0, import_react81.useState)(null);
12023
12522
  const [hidden, setHidden] = (0, import_react81.useState)({});
@@ -12086,12 +12585,21 @@ function FormStage({
12086
12585
  const outcome = await onSubmit(values);
12087
12586
  setWriting(false);
12088
12587
  if (!outcome.ok) {
12089
- setRefusal(outcome.message);
12588
+ const inWords = (text) => questions.reduce(
12589
+ (said, q) => said.replace(new RegExp(`\\b${q.key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "g"), q.ask),
12590
+ text
12591
+ );
12592
+ const told = outcome.error ?? refusal("invalid_argument", outcome.message, "Check your answers, then send again.");
12593
+ setRefusal({
12594
+ ...told,
12595
+ message: inWords(told.message),
12596
+ ...told.hint ? { hint: inWords(told.hint) } : {}
12597
+ });
12090
12598
  return;
12091
12599
  }
12092
12600
  setDone(outcome.message ?? "sent");
12093
12601
  onSubmitted?.(outcome.recordId ?? null);
12094
- }, [answers, form, honeypotKey, live, onSubmit, onSubmitted, preview]);
12602
+ }, [answers, form, honeypotKey, live, onSubmit, onSubmitted, preview, questions]);
12095
12603
  function advance() {
12096
12604
  if (!oneAtATime) return;
12097
12605
  if (index < live.length - 1) setAt(index + 1);
@@ -12150,7 +12658,22 @@ function FormStage({
12150
12658
  ] }) : null,
12151
12659
  form.intro && index === 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "text-sm text-muted-foreground", children: form.intro }) : null,
12152
12660
  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,
12661
+ refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
12662
+ RefusalNotice,
12663
+ {
12664
+ error: refusal2,
12665
+ className: "text-left",
12666
+ onKeepEditing: () => {
12667
+ setRefusal(null);
12668
+ stage.current?.querySelector("input, textarea, select, button")?.focus();
12669
+ },
12670
+ onDiscard: () => {
12671
+ setRefusal(null);
12672
+ setAnswers({});
12673
+ setAt(0);
12674
+ }
12675
+ }
12676
+ ) : null,
12154
12677
  /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { ref: stage, className: "flex flex-col gap-4 text-left", children: [
12155
12678
  (oneAtATime ? current ? [current] : [] : live).map((q) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
12156
12679
  Question,
@@ -12224,12 +12747,15 @@ function Question({
12224
12747
  void upload?.(file).then((result) => {
12225
12748
  if (!result) return;
12226
12749
  if (result.ok) onChange(result.fileId);
12227
- else setUploadError(result.reason);
12750
+ else
12751
+ setUploadError(
12752
+ refusal("invalid_argument", result.reason, "Pick another file, or try this one again.")
12753
+ );
12228
12754
  });
12229
12755
  }
12230
12756
  }
12231
12757
  ),
12232
- uploadError ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "text-xs text-destructive", children: uploadError }) : null
12758
+ uploadError ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(RefusalLine, { error: uploadError }) : null
12233
12759
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(FieldControl, { field, value, onChange, id })
12234
12760
  ] });
12235
12761
  }
@@ -13016,10 +13542,15 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
13016
13542
  onChange: (e) => setDraftBody(e.target.value)
13017
13543
  }
13018
13544
  ),
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
13545
+ 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: [
13546
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("code", { className: "text-destructive", children: token.raw }),
13547
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { "aria-hidden": "true", children: "\u2014" }),
13548
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
13549
+ RefusalLine,
13550
+ {
13551
+ error: refusal("invalid_argument", token.why, "Change it to a Field this table has, or take it out.")
13552
+ }
13553
+ )
13023
13554
  ] }, 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
13555
  ] });
13025
13556
  }
@@ -13224,7 +13755,21 @@ function SignBlock({ tableId, recordId, render, className }) {
13224
13755
  signature.document_hash.slice(0, 12)
13225
13756
  ] })
13226
13757
  ] }),
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 ?? "") })
13758
+ intact === false ? (
13759
+ // THE STORE REFUSED TO CALL IT INTACT: its reason goes through the one
13760
+ // formatter, with what to do next, never raw in red (lane REFUSAL-SWEEP).
13761
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
13762
+ RefusalLine,
13763
+ {
13764
+ className: "mt-0.5",
13765
+ error: refusal(
13766
+ "refused_by_rule",
13767
+ typeof verdict === "object" && verdict !== null && "reason" in verdict ? String(verdict.reason) : "The store says what is there no longer matches what was signed.",
13768
+ "Ask for it to be signed again on the version that is there now."
13769
+ )
13770
+ }
13771
+ )
13772
+ ) : /* @__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
13773
  ] }, signature.id);
13229
13774
  }) }) : null,
13230
13775
  !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 +14030,14 @@ function NotifyRuleEditor({ tableId, seed, className }) {
13485
14030
  var import_react95 = require("react");
13486
14031
  var import_recharts = require("recharts");
13487
14032
  var import_records11 = require("@ai-matrx/records");
13488
- var import_core6 = require("@ai-matrx/records/core");
14033
+ var import_core8 = require("@ai-matrx/records/core");
13489
14034
  var import_design_system46 = require("@ai-matrx/design-system");
13490
14035
  var import_jsx_runtime49 = require("react/jsx-runtime");
13491
14036
  function pretty(n) {
13492
14037
  return Number.isInteger(n) ? n.toLocaleString() : n.toLocaleString(void 0, { maximumFractionDigits: 2 });
13493
14038
  }
13494
- function asRefusal(block) {
13495
- return (0, import_core6.mapPgError)(
14039
+ function asRefusal2(block) {
14040
+ return (0, import_core8.mapPgError)(
13496
14041
  {
13497
14042
  message: block.refused ?? "",
13498
14043
  ...block.sqlstate ? { code: block.sqlstate } : {}
@@ -13552,7 +14097,7 @@ function ChartBlock({ block, subject, className }) {
13552
14097
  " ms"
13553
14098
  ] }) : null
13554
14099
  ] }),
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: [
14100
+ 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
14101
  /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(Drawing, { kind, points, series, config, onDrill: drill }),
13557
14102
  /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(Values, { points, measure: primary, config, onDrill: drill })
13558
14103
  ] }) })
@@ -14118,13 +14663,14 @@ var import_react99 = require("@ai-matrx/records/react");
14118
14663
  var import_records12 = require("@ai-matrx/records");
14119
14664
  var import_design_system48 = require("@ai-matrx/design-system");
14120
14665
  var import_jsx_runtime51 = require("react/jsx-runtime");
14666
+ 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
14667
  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
14668
  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
14669
  function formSuggestion(tableName2) {
14124
14670
  const subject = tableName2?.trim() ? tableName2.trim() : "this table";
14125
14671
  return `Make me a form that collects new ${subject} entries and tells me when somebody answers.`;
14126
14672
  }
14127
- function FormsPanel({ tableId, className }) {
14673
+ function FormsPanel({ tableId, activeFormId, className }) {
14128
14674
  const client = (0, import_react99.useRecordsClient)();
14129
14675
  const publishGate = usePublishGate();
14130
14676
  const host = useRecordsUi();
@@ -14135,6 +14681,11 @@ function FormsPanel({ tableId, className }) {
14135
14681
  const [busy, setBusy] = (0, import_react98.useState)(null);
14136
14682
  const [copied, setCopied] = (0, import_react98.useState)(null);
14137
14683
  const [building, setBuilding] = (0, import_react98.useState)(false);
14684
+ const linked = activeFormId && forms !== null ? forms.find((f) => f.form_id === activeFormId) ?? null : null;
14685
+ const linkedMissing = Boolean(activeFormId) && forms !== null && linked === null;
14686
+ (0, import_react98.useEffect)(() => {
14687
+ if (linked && rights.structure) setBuilding(true);
14688
+ }, [linked, rights.structure]);
14138
14689
  const load = (0, import_react98.useCallback)(async () => {
14139
14690
  const answered = await client.forms({ table_id: tableId });
14140
14691
  if (!answered.ok) {
@@ -14183,10 +14734,12 @@ function FormsPanel({ tableId, className }) {
14183
14734
  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
14735
  ] }),
14185
14736
  error ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(RefusalNotice, { error }) : null,
14737
+ 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
14738
  building ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
14187
14739
  FormBuilder,
14188
14740
  {
14189
14741
  tableId,
14742
+ activeFormId: linked ? linked.form_id : null,
14190
14743
  onActiveForm: () => {
14191
14744
  void load();
14192
14745
  }
@@ -14212,50 +14765,62 @@ function FormsPanel({ tableId, className }) {
14212
14765
  ) : null,
14213
14766
  /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("ul", { className: "flex flex-col gap-2", children: forms.map((form) => {
14214
14767
  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);
14768
+ return /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(
14769
+ "li",
14770
+ {
14771
+ "data-linked": form.form_id === activeFormId ? "true" : void 0,
14772
+ ref: form.form_id === activeFormId ? (el) => el?.scrollIntoView({ block: "nearest" }) : void 0,
14773
+ className: (0, import_design_system48.cn)(
14774
+ "rounded-md border p-2.5",
14775
+ form.form_id === activeFormId && "border-primary ring-1 ring-primary"
14776
+ ),
14777
+ children: [
14778
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("div", { className: "flex items-center gap-2", children: [
14779
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: form.title ?? form.slug }),
14780
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(import_design_system48.Badge, { variant: form.state === "open" ? "default" : "secondary", children: form.state })
14781
+ ] }),
14782
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("p", { className: "mt-0.5 text-xs text-muted-foreground", children: FORM_STATE_WORDS[form.state] }),
14783
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("p", { className: "mt-1.5 text-xs", children: [
14784
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("span", { className: "font-medium", children: form.in_table }),
14785
+ " in the table",
14786
+ form.held > 0 ? /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(import_jsx_runtime51.Fragment, { children: [
14787
+ " \xB7 ",
14788
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("span", { className: "font-medium", children: form.held }),
14789
+ " waiting for someone"
14790
+ ] }) : null,
14791
+ form.rejected > 0 ? /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(import_jsx_runtime51.Fragment, { children: [
14792
+ " \xB7 ",
14793
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("span", { className: "font-medium", children: form.rejected }),
14794
+ " turned away"
14795
+ ] }) : null,
14796
+ form.submission_cap !== null ? /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("span", { className: "text-muted-foreground", children: [
14797
+ " \xB7 stops at ",
14798
+ form.submission_cap
14799
+ ] }) : null
14800
+ ] }),
14801
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("div", { className: "mt-2 flex flex-wrap items-center gap-1.5", children: [
14802
+ 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,
14803
+ rights.structure && !publishGate.blocked ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
14804
+ import_design_system48.Button,
14805
+ {
14806
+ size: "sm",
14807
+ variant: "ghost",
14808
+ disabled: busy === form.form_id,
14809
+ onClick: () => void toggle(form),
14810
+ children: busy === form.form_id ? "\u2026" : form.published_at && !form.closed_at ? "Unpublish" : "Publish"
14811
+ }
14812
+ ) : null
14813
+ ] }),
14814
+ rights.structure && publishGate.why ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("p", { className: "mt-1.5 text-xs text-muted-foreground", children: publishGate.why }) : null,
14815
+ 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: [
14816
+ "This browser would not let the page copy for you, so here it is to copy by hand:",
14817
+ " ",
14818
+ url
14819
+ ] }) : null
14820
+ ]
14821
+ },
14822
+ form.form_id
14823
+ );
14259
14824
  }) }),
14260
14825
  !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
14826
  ] });
@@ -14912,7 +15477,7 @@ function CaptureRun({ sheetId, face: given, className }) {
14912
15477
  void client.captureOpen({ sheet_id: sheetId }).then((res) => {
14913
15478
  if (cancelled) return;
14914
15479
  if (res.ok) setFace(res.data);
14915
- else setLoadFailed(res.error?.message ?? "This sheet could not be opened.");
15480
+ else setLoadFailed(refusalOr(res.error, "unreachable", "This sheet could not be opened."));
14916
15481
  });
14917
15482
  return () => {
14918
15483
  cancelled = true;
@@ -14959,7 +15524,11 @@ function CaptureRun({ sheetId, face: given, className }) {
14959
15524
  const needed = questions.filter((x) => x.required && !answered(x.field));
14960
15525
  if (needed.length > 0) {
14961
15526
  setMissing(
14962
- `${needed.map((x) => (x.ask ?? x.field).replace(/\?$/, "")).join(", ")} still ${needed.length === 1 ? "needs" : "need"} an answer.`
15527
+ refusal(
15528
+ "invalid_argument",
15529
+ `${needed.map((x) => (x.ask ?? x.field).replace(/\?$/, "")).join(", ")} still ${needed.length === 1 ? "needs" : "need"} an answer.`,
15530
+ "Answer it, then capture again. Nothing has been queued yet."
15531
+ )
14963
15532
  );
14964
15533
  setAt(questions.findIndex((x) => x.field === needed[0].field));
14965
15534
  return;
@@ -14990,7 +15559,7 @@ function CaptureRun({ sheetId, face: given, className }) {
14990
15559
  if (online) await sync();
14991
15560
  }
14992
15561
  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 }) });
15562
+ 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
15563
  }
14995
15564
  if (face === void 0) {
14996
15565
  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 +15590,21 @@ function CaptureRun({ sheetId, face: given, className }) {
15021
15590
  ] });
15022
15591
  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
15592
  /* @__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." }),
15593
+ i.state === "refused" ? (
15594
+ // THE STORE REFUSED THIS ONE: a refusal line, heading and remedy
15595
+ // both — never its raw words in red (lane REFUSAL-SWEEP).
15596
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
15597
+ RefusalLine,
15598
+ {
15599
+ className: "flex-1",
15600
+ error: refusal(
15601
+ "refused_by_rule",
15602
+ i.last_error ?? "This one was refused.",
15603
+ "Throw it away and capture it again with the answer changed."
15604
+ )
15605
+ }
15606
+ )
15607
+ ) : /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("span", { className: "flex-1", children: i.last_error ?? "Waiting for a signal." }),
15025
15608
  i.state === "refused" ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
15026
15609
  import_design_system51.Button,
15027
15610
  {
@@ -15141,7 +15724,7 @@ function CaptureRun({ sheetId, face: given, className }) {
15141
15724
  }
15142
15725
  }
15143
15726
  ),
15144
- missing ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("p", { className: "text-sm text-destructive", "data-testid": "capture-missing", children: missing }) : null,
15727
+ 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
15728
  /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("div", { className: "flex items-center gap-2", children: [
15146
15729
  at > 0 ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
15147
15730
  import_design_system51.Button,
@@ -15369,13 +15952,13 @@ function AdHocCaptureSheet({
15369
15952
  setUploadError(null);
15370
15953
  void host.upload?.(file).then((result) => {
15371
15954
  if (result.ok) setFileId(result.fileId);
15372
- else setUploadError(result.reason);
15955
+ else setUploadError(refusal("invalid_argument", result.reason, "Pick another file, or try this one again."));
15373
15956
  });
15374
15957
  }
15375
15958
  }
15376
15959
  ),
15377
15960
  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
15961
+ uploadError ? /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(RefusalLine, { error: uploadError }) : null
15379
15962
  ] }) : null,
15380
15963
  /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
15381
15964
  import_design_system52.Textarea,
@@ -15838,17 +16421,17 @@ async function declareTable(client, spec) {
15838
16421
  if (!written.ok) return undo(client, table.data, home.data, spec.name, written.error);
15839
16422
  return { ok: true, data: table.data, homeId: home.data };
15840
16423
  }
15841
- async function undo(client, tableId, homeId, name, refusal) {
16424
+ async function undo(client, tableId, homeId, name, refusal2) {
15842
16425
  const removed = await client.recordDelete({ record_id: tableId });
15843
16426
  if (removed.ok) {
15844
16427
  await client.recordDelete({ record_id: homeId });
15845
- return { ok: false, error: refusal };
16428
+ return { ok: false, error: refusal2 };
15846
16429
  }
15847
16430
  return {
15848
16431
  ok: false,
15849
16432
  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.`
16433
+ ...refusal2,
16434
+ 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
16435
  }
15853
16436
  };
15854
16437
  }
@@ -15901,13 +16484,31 @@ var LANE_EMPTY = {
15901
16484
  community: "Nothing shared beyond your organization. A table published by link or to the world lands here.",
15902
16485
  app: "The app has not needed a table of its own in this organization yet. Saving a view, writing a comment or building a form makes one, and it appears here rather than among your own data."
15903
16486
  };
16487
+ function keptByTheApp(table) {
16488
+ return table.kept_by_the_app === true;
16489
+ }
15904
16490
  function laneFor(table) {
15905
16491
  if (table.is_kernel) return "system";
15906
- if ((table.slug ?? "").startsWith(APP_TABLE_PREFIX)) return "app";
16492
+ if ((table.slug ?? "").startsWith(APP_TABLE_PREFIX) || keptByTheApp(table)) return "app";
15907
16493
  if (table.visibility === "public" || table.visibility === "link") return "community";
15908
16494
  if (table.visibility === "personal") return "mine";
15909
16495
  return "organization";
15910
16496
  }
16497
+ var VISIBILITY_LANES = ["mine", "organization", "community", "world"];
16498
+ var VISIBILITY_LANE_TITLE = {
16499
+ mine: "Mine",
16500
+ organization: "My organization",
16501
+ community: "Community",
16502
+ world: "World"
16503
+ };
16504
+ function visibilityLaneFor(table) {
16505
+ if (table.is_kernel) return null;
16506
+ if ((table.slug ?? "").startsWith(APP_TABLE_PREFIX) || keptByTheApp(table)) return null;
16507
+ if (table.visibility === "personal") return "mine";
16508
+ if (table.visibility === "link") return "community";
16509
+ if (table.visibility === "public") return "world";
16510
+ return "organization";
16511
+ }
15911
16512
  function TablesHome({ onOpenTable, className }) {
15912
16513
  const client = (0, import_react117.useRecordsClient)();
15913
16514
  const tables = (0, import_react117.useTables)();
@@ -16055,6 +16656,43 @@ var import_react118 = require("react");
16055
16656
  var import_react119 = require("@ai-matrx/records/react");
16056
16657
  var import_design_system57 = require("@ai-matrx/design-system");
16057
16658
  var import_jsx_runtime61 = require("react/jsx-runtime");
16659
+ var RAIL_TITLE = {
16660
+ settings: "Table settings",
16661
+ inbox: "Inbox",
16662
+ import: "Import",
16663
+ field: "Add a field",
16664
+ record: "Record",
16665
+ "new-record": "New record",
16666
+ forms: "Forms",
16667
+ bookings: "Bookings",
16668
+ checklists: "Checklists",
16669
+ notifications: "Notifications",
16670
+ portals: "Portals",
16671
+ "who-changed": "Who changed this"
16672
+ };
16673
+ function RailFrame({
16674
+ phone,
16675
+ title,
16676
+ onClose,
16677
+ children
16678
+ }) {
16679
+ if (phone) {
16680
+ return /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
16681
+ import_design_system57.BottomSheet,
16682
+ {
16683
+ open: true,
16684
+ onOpenChange: (open) => {
16685
+ if (!open) onClose();
16686
+ },
16687
+ title,
16688
+ size: "full",
16689
+ surface: "solid",
16690
+ children: /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("div", { "data-rail-sheet": "", className: "min-h-0 flex-1 overflow-y-auto px-3 pb-6", children })
16691
+ }
16692
+ );
16693
+ }
16694
+ return /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("aside", { "data-rail-column": "", className: "w-[26rem] shrink-0 overflow-y-auto rounded-md border p-3", children });
16695
+ }
16058
16696
  function filterInWords(filter, fields) {
16059
16697
  const nameOf = (key) => {
16060
16698
  const field = fields.find((f) => f.key === key);
@@ -16102,9 +16740,43 @@ function chooseSurface(current, pressed) {
16102
16740
  function surfaceChosen(current, asking) {
16103
16741
  return asking.main !== void 0 ? current.main === asking.main : current.rail === asking.rail;
16104
16742
  }
16105
- function openingRail(activeRecordId) {
16106
- return activeRecordId ? { rail: "record", record: activeRecordId } : { rail: "none", record: null };
16743
+ function openingRail(activeRecordId, activeRail) {
16744
+ if (activeRecordId) return { rail: "record", record: activeRecordId };
16745
+ const named = railFromParam(activeRail);
16746
+ if (named !== null && named !== "share") return { rail: named, record: null };
16747
+ return { rail: "none", record: null };
16748
+ }
16749
+ var RAIL_SPELLINGS = {
16750
+ forms: "forms",
16751
+ form: "forms",
16752
+ bookings: "bookings",
16753
+ booking: "bookings",
16754
+ checklists: "checklists",
16755
+ checklist: "checklists",
16756
+ notifications: "notifications",
16757
+ notification: "notifications",
16758
+ digests: "notifications",
16759
+ digest: "notifications",
16760
+ subscriptions: "notifications",
16761
+ subscription: "notifications",
16762
+ portals: "portals",
16763
+ portal: "portals",
16764
+ settings: "settings",
16765
+ import: "import",
16766
+ inbox: "inbox",
16767
+ share: "share",
16768
+ sharing: "share"
16769
+ };
16770
+ function railFromParam(raw) {
16771
+ if (typeof raw !== "string") return null;
16772
+ const token = raw.trim().toLowerCase();
16773
+ if (token === "") return null;
16774
+ return RAIL_SPELLINGS[token] ?? null;
16775
+ }
16776
+ function unknownRailLine(raw) {
16777
+ 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.`;
16107
16778
  }
16779
+ 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
16780
  function openingView(activeView, activeDashboardId) {
16109
16781
  const named = pageViewFromParam(activeView);
16110
16782
  if (named === null) {
@@ -16132,6 +16804,8 @@ function TablePage({
16132
16804
  activeGroupField,
16133
16805
  cameFrom,
16134
16806
  filter,
16807
+ activeRail,
16808
+ activeItemId,
16135
16809
  className
16136
16810
  }) {
16137
16811
  const client = (0, import_react119.useRecordsClient)();
@@ -16141,8 +16815,13 @@ function TablePage({
16141
16815
  const askedInWords = filter ? filterInWords(filter, pageFields.data ?? []) : null;
16142
16816
  const organizationId = (0, import_react119.useRecordsClient)().config.organizationId;
16143
16817
  const [view, setView] = (0, import_react118.useState)(null);
16144
- const opening = openingRail(activeRecordId);
16818
+ const opening = openingRail(activeRecordId, activeRail);
16145
16819
  const opened = openingView(activeView, activeDashboardId);
16820
+ const railAsked = typeof activeRail === "string" ? activeRail.trim() : "";
16821
+ const railUnknown = railAsked !== "" && railFromParam(railAsked) === null ? railAsked : null;
16822
+ const shareFromLink = railFromParam(activeRail) === "share";
16823
+ const canShare = useCanShare();
16824
+ const itemFor = (which) => railFromParam(activeRail) === which ? activeItemId ?? null : null;
16146
16825
  const [asking, setAsking] = (0, import_react118.useState)(null);
16147
16826
  const [layoutFromLink, setLayoutFromLink] = (0, import_react118.useState)(opened.layout);
16148
16827
  const [surface, setSurface] = (0, import_react118.useState)({
@@ -16153,6 +16832,7 @@ function TablePage({
16153
16832
  const setRail = (next) => setSurface((now) => ({ ...now, rail: next }));
16154
16833
  const [openRecord, setOpenRecord] = (0, import_react118.useState)(opening.record);
16155
16834
  const viewVersion = useRecordVersion(view?.id ?? null);
16835
+ const phone = (0, import_design_system57.useIsMobile)();
16156
16836
  const shownLayout = layoutFromLink ?? view?.layout ?? "grid";
16157
16837
  const press = (pressed) => {
16158
16838
  const next = chooseSurface(surface, pressed);
@@ -16167,6 +16847,12 @@ function TablePage({
16167
16847
  setOpenRecord(activeRecordId);
16168
16848
  setSurface((now) => ({ ...now, rail: "record" }));
16169
16849
  }, [activeRecordId]);
16850
+ (0, import_react118.useEffect)(() => {
16851
+ if (activeRecordId) return;
16852
+ const named = railFromParam(activeRail);
16853
+ if (named === null || named === "share") return;
16854
+ setSurface((now) => ({ ...now, rail: named }));
16855
+ }, [activeRail, activeRecordId]);
16170
16856
  (0, import_react118.useEffect)(() => {
16171
16857
  const named = pageViewFromParam(activeView);
16172
16858
  if (named === null) return;
@@ -16174,6 +16860,7 @@ function TablePage({
16174
16860
  setLayoutFromLink(isLayout ? named : null);
16175
16861
  setSurface((now) => ({ ...now, main: isLayout ? "records" : named }));
16176
16862
  }, [activeView]);
16863
+ const [viewSaveRefused, setViewSaveRefused] = (0, import_react118.useState)(null);
16177
16864
  const patchView = (0, import_react118.useCallback)(
16178
16865
  async (patch) => {
16179
16866
  const current = view;
@@ -16181,12 +16868,13 @@ function TablePage({
16181
16868
  setView({ ...current, ...patch });
16182
16869
  const document2 = viewPatchDocument(patch);
16183
16870
  if (Object.keys(document2).length === 0) return;
16184
- const written = await client.recordUpdate({
16185
- record_id: current.id,
16186
- patch: document2,
16187
- ...viewVersion.version === null ? {} : { expectedVersion: viewVersion.version }
16188
- });
16189
- if (written.ok) viewVersion.note(written.data);
16871
+ setViewSaveRefused(null);
16872
+ const written = await saveViewPatch(client, current.id, document2, viewVersion.version);
16873
+ if (written.ok) {
16874
+ viewVersion.note(written.data);
16875
+ return;
16876
+ }
16877
+ setViewSaveRefused({ error: written.error, patch, before: current });
16190
16878
  },
16191
16879
  [client, view, viewVersion]
16192
16880
  );
@@ -16331,11 +17019,33 @@ function TablePage({
16331
17019
  organizationId,
16332
17020
  subjectId: tableId,
16333
17021
  name: table.data?.name,
16334
- may: rights.share
17022
+ may: rights.share,
17023
+ initiallyOpen: shareFromLink
16335
17024
  }
16336
17025
  ),
16337
17026
  /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(ExportMenu, { tableId })
16338
17027
  ] }),
17028
+ viewSaveRefused ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
17029
+ RefusalNotice,
17030
+ {
17031
+ error: viewSaveRefused.error,
17032
+ actions: /* @__PURE__ */ (0, import_jsx_runtime61.jsxs)("span", { className: "flex flex-wrap gap-1 pt-0.5", "data-view-save-refused": "", children: [
17033
+ /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(import_design_system57.Button, { size: "sm", variant: "outline", onClick: () => void patchView(viewSaveRefused.patch), children: "Try again" }),
17034
+ /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
17035
+ import_design_system57.Button,
17036
+ {
17037
+ size: "sm",
17038
+ variant: "ghost",
17039
+ onClick: () => {
17040
+ setView(viewSaveRefused.before);
17041
+ setViewSaveRefused(null);
17042
+ },
17043
+ children: "Discard"
17044
+ }
17045
+ )
17046
+ ] })
17047
+ }
17048
+ ) : null,
16339
17049
  main === "dashboards" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(DashboardCanvas, { tableId, activeDashboardId: activeDashboardId ?? null }) : main === "archived" ? (
16340
17050
  /* THE ROWS THAT LEFT. Restoring one puts it back in the grid, so the
16341
17051
  page returns to the records rather than leaving the person looking
@@ -16354,6 +17064,8 @@ function TablePage({
16354
17064
  }
16355
17065
  )
16356
17066
  ) : /* @__PURE__ */ (0, import_jsx_runtime61.jsxs)(import_jsx_runtime61.Fragment, { children: [
17067
+ 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,
17068
+ 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
17069
  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
17070
  cameFrom ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
16359
17071
  "p",
@@ -16394,7 +17106,7 @@ function TablePage({
16394
17106
  view ? null : /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("p", { className: "text-xs text-muted-foreground", children: VIEW_NOT_SAVED_YET })
16395
17107
  ] })
16396
17108
  ] }),
16397
- rail === "none" ? null : /* @__PURE__ */ (0, import_jsx_runtime61.jsxs)("aside", { className: "w-[26rem] shrink-0 overflow-y-auto rounded-md border p-3", children: [
17109
+ rail === "none" ? null : /* @__PURE__ */ (0, import_jsx_runtime61.jsxs)(RailFrame, { phone, title: RAIL_TITLE[rail], onClose: () => setRail("none"), children: [
16398
17110
  rail === "settings" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
16399
17111
  TableSettings,
16400
17112
  {
@@ -16412,7 +17124,7 @@ function TablePage({
16412
17124
  }
16413
17125
  }
16414
17126
  ) : null,
16415
- rail === "forms" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(FormsPanel, { tableId }) : null,
17127
+ rail === "forms" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(FormsPanel, { tableId, activeFormId: itemFor("forms") }) : null,
16416
17128
  rail === "bookings" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(BookingSlots, { tableId }) : null,
16417
17129
  rail === "checklists" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
16418
17130
  ChecklistsPanel,
@@ -16424,8 +17136,8 @@ function TablePage({
16424
17136
  }
16425
17137
  }
16426
17138
  ) : 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,
17139
+ rail === "notifications" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(SubscriptionsPanel, { tableId, activeRuleId: itemFor("notifications") }) : null,
17140
+ rail === "portals" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(PortalsPanel, { tableId, activePortalId: itemFor("portals") }) : null,
16429
17141
  rail === "import" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(ImportWizard, { tableId, onDone: () => setRail("none") }) : null,
16430
17142
  rail === "field" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(FieldEditor, { tableId, onSaved: () => setRail("none"), onCancel: () => setRail("none") }) : null,
16431
17143
  rail === "new-record" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(