@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.js CHANGED
@@ -538,7 +538,7 @@ import {
538
538
  PopoverContent as PopoverContent2,
539
539
  PopoverTrigger as PopoverTrigger2,
540
540
  ScrollArea as ScrollArea2,
541
- cn as cn4
541
+ cn as cn5
542
542
  } from "@ai-matrx/design-system";
543
543
 
544
544
  // src/editors.tsx
@@ -557,7 +557,7 @@ import {
557
557
  SelectItem as SelectItem2,
558
558
  SelectTrigger as SelectTrigger2,
559
559
  SelectValue as SelectValue2,
560
- cn as cn3
560
+ cn as cn4
561
561
  } from "@ai-matrx/design-system";
562
562
 
563
563
  // src/parity.ts
@@ -671,15 +671,126 @@ import {
671
671
  PopoverContent,
672
672
  PopoverTrigger,
673
673
  ScrollArea,
674
- cn as cn2
674
+ cn as cn3
675
675
  } from "@ai-matrx/design-system";
676
+
677
+ // src/Refusal.tsx
678
+ import { Alert, AlertDescription, AlertTitle, cn as cn2 } from "@ai-matrx/design-system";
676
679
  import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
680
+ function hintIsMachineIdentity(hint) {
681
+ return isMachineIdentity(hint);
682
+ }
683
+ function hintForAPerson(hint) {
684
+ const said = (hint ?? "").trim();
685
+ if (said === "") return null;
686
+ return isMachineIdentity(said) ? null : said;
687
+ }
688
+ function RefusalNotice({
689
+ error,
690
+ className,
691
+ actions,
692
+ onKeepEditing,
693
+ onDiscard
694
+ }) {
695
+ const plain = refusalForAPerson(error);
696
+ const doors = onKeepEditing || onDiscard ? /* @__PURE__ */ jsxs2("div", { className: "flex flex-wrap items-center gap-1 pt-0.5", "data-refusal-doors": "", children: [
697
+ onKeepEditing ? /* @__PURE__ */ jsx3(
698
+ "button",
699
+ {
700
+ type: "button",
701
+ "data-refusal-keep-editing": "",
702
+ className: "rounded border px-2 py-0.5 text-xs hover:bg-muted",
703
+ onClick: onKeepEditing,
704
+ children: "Keep editing"
705
+ }
706
+ ) : null,
707
+ onDiscard ? /* @__PURE__ */ jsx3(
708
+ "button",
709
+ {
710
+ type: "button",
711
+ "data-refusal-discard": "",
712
+ className: "rounded border px-2 py-0.5 text-xs hover:bg-muted",
713
+ onClick: onDiscard,
714
+ children: "Discard"
715
+ }
716
+ ) : null
717
+ ] }) : null;
718
+ return /* @__PURE__ */ jsxs2(
719
+ Alert,
720
+ {
721
+ variant: "destructive",
722
+ role: "alert",
723
+ "data-refusal-notice": "",
724
+ className: cn2("text-xs", className),
725
+ ...plain.forEngineers ? { title: plain.forEngineers } : {},
726
+ children: [
727
+ /* @__PURE__ */ jsx3(AlertTitle, { className: "text-xs font-medium", children: plain.title }),
728
+ /* @__PURE__ */ jsxs2(AlertDescription, { className: "space-y-1 text-xs", children: [
729
+ plain.sentence.replace(/\.$/, "") === plain.title ? null : /* @__PURE__ */ jsx3("p", { children: plain.sentence }),
730
+ /* @__PURE__ */ jsx3("p", { className: "opacity-80", children: plain.remedy }),
731
+ plain.forEngineers ? /* @__PURE__ */ jsx3(
732
+ "p",
733
+ {
734
+ className: "sr-only",
735
+ "aria-hidden": "true",
736
+ "data-for-engineers": "",
737
+ ...error.sqlstate ? { "data-sqlstate": error.sqlstate } : {},
738
+ children: plain.forEngineers
739
+ }
740
+ ) : null,
741
+ actions,
742
+ doors
743
+ ] })
744
+ ]
745
+ }
746
+ );
747
+ }
748
+ function RefusalLine({ error, className }) {
749
+ const plain = refusalForAPerson(error);
750
+ return /* @__PURE__ */ jsx3(
751
+ "p",
752
+ {
753
+ role: "alert",
754
+ "data-refusal-line": "",
755
+ className: cn2("text-xs text-destructive", className),
756
+ title: plain.forEngineers || void 0,
757
+ children: refusalLineForAPerson(error)
758
+ }
759
+ );
760
+ }
761
+
762
+ // src/refusals.ts
763
+ function refusal(code, sentence2, remedy, diagnostic) {
764
+ return {
765
+ code,
766
+ message: sentence2,
767
+ ...remedy ? { hint: remedy } : {},
768
+ ...diagnostic ? { diagnostic } : {}
769
+ };
770
+ }
771
+ function refusalFromThrown(thrown, sentence2, remedy, code = "internal") {
772
+ const said = thrown instanceof Error ? thrown.message : String(thrown ?? "");
773
+ return refusal(code, sentence2, remedy, said ? `Thrown: ${said}` : null);
774
+ }
775
+ function refusalOr(error, code, sentence2) {
776
+ return error ?? refusal(code, sentence2);
777
+ }
778
+ function asRefusal(thrown, sentence2, remedy, code = "internal") {
779
+ const t = thrown;
780
+ if (t && typeof t === "object" && typeof t.code === "string" && typeof t.message === "string" && !(thrown instanceof Error)) {
781
+ return t;
782
+ }
783
+ return refusalFromThrown(thrown, sentence2, remedy, code);
784
+ }
785
+
786
+ // src/RelationPicker.tsx
787
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
677
788
  var NO_STORE_ON_THIS_SCREEN_REASON = "This question points at a record in somebody's own data, and a page answered without an account is never allowed to search that. Leave it blank \u2014 whoever receives your answer will fill it in.";
678
789
  function RelationPicker({ field, value, onChange, disabled, id, className, allowCreate = true }) {
679
790
  const client = useOptionalRecordsClient();
680
791
  const [rows, setRows] = useState2(null);
681
792
  const [words, setWords] = useState2({});
682
- const [refusal, setRefusal] = useState2(null);
793
+ const [refusal2, setRefusal] = useState2(null);
683
794
  const [search, setSearch] = useState2("");
684
795
  const [open, setOpen] = useState2(false);
685
796
  const [target, setTarget] = useState2(null);
@@ -699,7 +810,7 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
699
810
  void client.list({ table_id: target2, limit: 200 }).then((result) => {
700
811
  if (cancelled) return;
701
812
  if (result.ok) setRows(result.data.rows);
702
- else setRefusal(refusalLineForAPerson(result.error));
813
+ else setRefusal(result.error);
703
814
  });
704
815
  return () => {
705
816
  cancelled = true;
@@ -733,14 +844,17 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
733
844
  void client.tableList().then((result) => {
734
845
  if (cancelled) return;
735
846
  if (!result.ok) {
736
- setTargetRefusal(refusalLineForAPerson(result.error));
847
+ setTargetRefusal(result.error);
737
848
  return;
738
849
  }
739
850
  const found = result.data.find((t) => t.id === id2) ?? null;
740
851
  if (found) setTarget(found);
741
852
  else
742
853
  setTargetRefusal(
743
- "That Table is not one you can open, so a new record cannot be added to it from here."
854
+ refusal(
855
+ "door",
856
+ "That Table is not one you can open, so a new record cannot be added to it from here."
857
+ )
744
858
  );
745
859
  });
746
860
  return () => {
@@ -751,11 +865,11 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
751
865
  return (
752
866
  // The Field's own name is already printed above this control by
753
867
  // `FieldLabel`, so the sentence does not say it a second time.
754
- /* @__PURE__ */ jsx3("p", { id, className: cn2("text-xs text-muted-foreground", className), children: NO_STORE_ON_THIS_SCREEN_REASON })
868
+ /* @__PURE__ */ jsx4("p", { id, className: cn3("text-xs text-muted-foreground", className), children: NO_STORE_ON_THIS_SCREEN_REASON })
755
869
  );
756
870
  }
757
871
  if (!field.relation_target) {
758
- return /* @__PURE__ */ jsxs2("p", { className: "text-xs text-muted-foreground", children: [
872
+ return /* @__PURE__ */ jsxs3("p", { className: "text-xs text-muted-foreground", children: [
759
873
  fieldName(field),
760
874
  " 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."
761
875
  ] });
@@ -779,13 +893,17 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
779
893
  const tableId = field.relation_target;
780
894
  if (!tableId || title === "" || busy) return;
781
895
  if (!target) {
782
- setCreateRefusal("Still reading that Table. Try again in a moment.");
896
+ setCreateRefusal(refusal("unreachable", "Still reading that Table.", "Try again in a moment."));
783
897
  return;
784
898
  }
785
899
  const titleField = target.title_field;
786
900
  if (!titleField) {
787
901
  setCreateRefusal(
788
- `${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.`
902
+ refusal(
903
+ "not_supported",
904
+ `${target.name} has no column that names a record yet, so a new one would have nothing to show here.`,
905
+ "A table admin picks its title column in that table's settings."
906
+ )
789
907
  );
790
908
  return;
791
909
  }
@@ -794,7 +912,7 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
794
912
  const written = await client.recordWrite({ table_id: tableId, data: { [titleField]: title } });
795
913
  if (!written.ok) {
796
914
  setBusy(false);
797
- setCreateRefusal(refusalLineForAPerson(written.error));
915
+ setCreateRefusal(written.error);
798
916
  return;
799
917
  }
800
918
  const createdId = String(written.data);
@@ -809,8 +927,8 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
809
927
  const visible = (rows ?? []).filter(
810
928
  (row) => search.trim() === "" ? true : titleOf(row.id).toLowerCase().includes(search.trim().toLowerCase())
811
929
  );
812
- return /* @__PURE__ */ jsxs2("div", { className: cn2("flex flex-wrap items-center gap-1", className), children: [
813
- picked.map((recordId) => /* @__PURE__ */ jsx3(
930
+ return /* @__PURE__ */ jsxs3("div", { className: cn3("flex flex-wrap items-center gap-1", className), children: [
931
+ picked.map((recordId) => /* @__PURE__ */ jsx4(
814
932
  RecordChip,
815
933
  {
816
934
  title: titleOf(recordId),
@@ -818,10 +936,10 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
818
936
  },
819
937
  recordId
820
938
  )),
821
- disabled ? null : /* @__PURE__ */ jsxs2(Popover, { open, onOpenChange: setOpen, children: [
822
- /* @__PURE__ */ jsx3(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx3(Button2, { id, type: "button", size: "sm", variant: "outline", "aria-label": `Pick for ${fieldName(field)}`, children: picked.length === 0 ? "Pick" : "Change" }) }),
823
- /* @__PURE__ */ jsxs2(PopoverContent, { sizing: "content", className: "p-1", align: "start", children: [
824
- /* @__PURE__ */ jsx3(
939
+ disabled ? null : /* @__PURE__ */ jsxs3(Popover, { open, onOpenChange: setOpen, children: [
940
+ /* @__PURE__ */ jsx4(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx4(Button2, { id, type: "button", size: "sm", variant: "outline", "aria-label": `Pick for ${fieldName(field)}`, children: picked.length === 0 ? "Pick" : "Change" }) }),
941
+ /* @__PURE__ */ jsxs3(PopoverContent, { sizing: "content", className: "p-1", align: "start", children: [
942
+ /* @__PURE__ */ jsx4(
825
943
  BasicInput2,
826
944
  {
827
945
  value: search,
@@ -830,33 +948,33 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
830
948
  className: "mb-1 h-7 text-xs"
831
949
  }
832
950
  ),
833
- refusal ? /* @__PURE__ */ jsx3("p", { className: "p-2 text-xs text-destructive", children: refusal }) : null,
834
- !rows && !refusal ? /* @__PURE__ */ jsx3("p", { className: "p-2 text-xs text-muted-foreground", children: "Reading\u2026" }) : null,
835
- full ? /* @__PURE__ */ jsxs2("p", { className: "p-2 text-xs text-muted-foreground", children: [
951
+ refusal2 ? /* @__PURE__ */ jsx4(RefusalLine, { error: refusal2, className: "p-2" }) : null,
952
+ !rows && !refusal2 ? /* @__PURE__ */ jsx4("p", { className: "p-2 text-xs text-muted-foreground", children: "Reading\u2026" }) : null,
953
+ full ? /* @__PURE__ */ jsxs3("p", { className: "p-2 text-xs text-muted-foreground", children: [
836
954
  fieldName(field),
837
955
  " can point at ",
838
956
  field.relation_max,
839
957
  " at most. Remove one to add another."
840
958
  ] }) : null,
841
- /* @__PURE__ */ jsxs2(ScrollArea, { className: "max-h-56", children: [
842
- /* @__PURE__ */ jsx3("ul", { children: visible.map((row) => /* @__PURE__ */ jsx3("li", { children: /* @__PURE__ */ jsx3(
959
+ /* @__PURE__ */ jsxs3(ScrollArea, { className: "max-h-56", children: [
960
+ /* @__PURE__ */ jsx4("ul", { children: visible.map((row) => /* @__PURE__ */ jsx4("li", { children: /* @__PURE__ */ jsx4(
843
961
  "button",
844
962
  {
845
963
  type: "button",
846
964
  onClick: () => toggle(row.id),
847
965
  title: titleOf(row.id),
848
- className: cn2(
966
+ className: cn3(
849
967
  "w-full truncate rounded px-2 py-1 text-left text-xs hover:bg-muted",
850
968
  picked.includes(row.id) ? "bg-accent text-accent-foreground" : ""
851
969
  ),
852
970
  children: titleOf(row.id)
853
971
  }
854
972
  ) }, row.id)) }),
855
- rows && visible.length === 0 ? /* @__PURE__ */ jsx3("p", { className: "p-2 text-xs text-muted-foreground", children: "Nothing in that table matches." }) : null
973
+ rows && visible.length === 0 ? /* @__PURE__ */ jsx4("p", { className: "p-2 text-xs text-muted-foreground", children: "Nothing in that table matches." }) : null
856
974
  ] }),
857
- allowCreate && !full ? /* @__PURE__ */ jsxs2("div", { className: "mt-1 border-t pt-1", children: [
858
- targetRefusal ? /* @__PURE__ */ jsx3("p", { className: "p-2 text-xs text-destructive", children: targetRefusal }) : !target ? /* @__PURE__ */ jsx3("p", { className: "p-2 text-xs text-muted-foreground", children: "Reading that table\u2026" }) : creating ? /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1 p-1", children: [
859
- /* @__PURE__ */ jsx3(
975
+ allowCreate && !full ? /* @__PURE__ */ jsxs3("div", { className: "mt-1 border-t pt-1", children: [
976
+ targetRefusal ? /* @__PURE__ */ jsx4(RefusalLine, { error: targetRefusal, className: "p-2" }) : !target ? /* @__PURE__ */ jsx4("p", { className: "p-2 text-xs text-muted-foreground", children: "Reading that table\u2026" }) : creating ? /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1 p-1", children: [
977
+ /* @__PURE__ */ jsx4(
860
978
  BasicInput2,
861
979
  {
862
980
  autoFocus: true,
@@ -873,7 +991,7 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
873
991
  "aria-label": `Name the new ${targetWord}`
874
992
  }
875
993
  ),
876
- /* @__PURE__ */ jsx3(
994
+ /* @__PURE__ */ jsx4(
877
995
  Button2,
878
996
  {
879
997
  type: "button",
@@ -884,7 +1002,7 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
884
1002
  children: busy ? "Creating\u2026" : "Create"
885
1003
  }
886
1004
  )
887
- ] }) : /* @__PURE__ */ jsx3(
1005
+ ] }) : /* @__PURE__ */ jsx4(
888
1006
  Button2,
889
1007
  {
890
1008
  type: "button",
@@ -900,10 +1018,25 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
900
1018
  setCreating(true);
901
1019
  }
902
1020
  },
903
- children: /* @__PURE__ */ jsx3("span", { className: "block w-full truncate text-left", children: search.trim() !== "" ? `Create \u201C${search.trim()}\u201D` : `New ${targetWord}` })
1021
+ children: /* @__PURE__ */ jsx4("span", { className: "block w-full truncate text-left", children: search.trim() !== "" ? `Create \u201C${search.trim()}\u201D` : `New ${targetWord}` })
904
1022
  }
905
1023
  ),
906
- createRefusal ? /* @__PURE__ */ jsx3("p", { className: "p-2 text-xs text-destructive", children: createRefusal }) : null
1024
+ createRefusal ? (
1025
+ // The typed title is still in the search box: the notice owns the
1026
+ // two doors out (lane REFUSAL-SWEEP).
1027
+ /* @__PURE__ */ jsx4(
1028
+ RefusalNotice,
1029
+ {
1030
+ error: createRefusal,
1031
+ className: "mt-1 max-w-72",
1032
+ onKeepEditing: () => setCreateRefusal(null),
1033
+ onDiscard: () => {
1034
+ setCreateRefusal(null);
1035
+ setSearch("");
1036
+ }
1037
+ }
1038
+ )
1039
+ ) : null
907
1040
  ] }) : null
908
1041
  ] })
909
1042
  ] })
@@ -911,12 +1044,12 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
911
1044
  }
912
1045
 
913
1046
  // src/editors.tsx
914
- import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1047
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
915
1048
  function FieldLabel({ field, htmlFor, children }) {
916
- return /* @__PURE__ */ jsxs3("div", { className: "flex items-baseline gap-1.5", children: [
917
- /* @__PURE__ */ jsx4(Label2, { htmlFor, className: "text-xs font-medium", children: fieldName(field) }),
918
- field.required ? /* @__PURE__ */ jsx4("span", { className: "text-xs text-destructive", children: "required" }) : null,
919
- field.unit ? /* @__PURE__ */ jsx4("span", { className: "text-xs text-muted-foreground", children: field.unit }) : null,
1049
+ return /* @__PURE__ */ jsxs4("div", { className: "flex items-baseline gap-1.5", children: [
1050
+ /* @__PURE__ */ jsx5(Label2, { htmlFor, className: "text-xs font-medium", children: fieldName(field) }),
1051
+ field.required ? /* @__PURE__ */ jsx5("span", { className: "text-xs text-destructive", children: "required" }) : null,
1052
+ field.unit ? /* @__PURE__ */ jsx5("span", { className: "text-xs text-muted-foreground", children: field.unit }) : null,
920
1053
  children
921
1054
  ] });
922
1055
  }
@@ -927,17 +1060,17 @@ function FieldControl({ field, value, onChange, disabled, id }) {
927
1060
  case "formula":
928
1061
  case "lookup":
929
1062
  case "rollup":
930
- return /* @__PURE__ */ jsxs3("div", { id: inputId, className: "rounded border border-dashed px-2 py-1.5 text-xs text-muted-foreground", children: [
1063
+ return /* @__PURE__ */ jsxs4("div", { id: inputId, className: "rounded border border-dashed px-2 py-1.5 text-xs text-muted-foreground", children: [
931
1064
  value === null || value === void 0 ? "\u2014" : String(value),
932
- /* @__PURE__ */ jsxs3("span", { className: "ml-2 italic", children: [
1065
+ /* @__PURE__ */ jsxs4("span", { className: "ml-2 italic", children: [
933
1066
  fieldName(field),
934
1067
  " is worked out by the system, so it cannot be typed in"
935
1068
  ] })
936
1069
  ] });
937
1070
  case "json":
938
- return /* @__PURE__ */ jsx4(JsonControl, { field, value, onChange, disabled, id: inputId });
1071
+ return /* @__PURE__ */ jsx5(JsonControl, { field, value, onChange, disabled, id: inputId });
939
1072
  case "long_text":
940
- return /* @__PURE__ */ jsx4(
1073
+ return /* @__PURE__ */ jsx5(
941
1074
  BasicTextarea,
942
1075
  {
943
1076
  id: inputId,
@@ -950,7 +1083,7 @@ function FieldControl({ field, value, onChange, disabled, id }) {
950
1083
  case "number":
951
1084
  case "currency":
952
1085
  case "percent":
953
- return /* @__PURE__ */ jsx4(
1086
+ return /* @__PURE__ */ jsx5(
954
1087
  BasicInput3,
955
1088
  {
956
1089
  id: inputId,
@@ -963,7 +1096,7 @@ function FieldControl({ field, value, onChange, disabled, id }) {
963
1096
  }
964
1097
  );
965
1098
  case "datetime":
966
- return /* @__PURE__ */ jsx4(
1099
+ return /* @__PURE__ */ jsx5(
967
1100
  BasicInput3,
968
1101
  {
969
1102
  id: inputId,
@@ -976,7 +1109,7 @@ function FieldControl({ field, value, onChange, disabled, id }) {
976
1109
  case "email":
977
1110
  case "phone":
978
1111
  case "url":
979
- return /* @__PURE__ */ jsx4(
1112
+ return /* @__PURE__ */ jsx5(
980
1113
  BasicInput3,
981
1114
  {
982
1115
  id: inputId,
@@ -987,18 +1120,18 @@ function FieldControl({ field, value, onChange, disabled, id }) {
987
1120
  }
988
1121
  );
989
1122
  case "checkbox":
990
- return /* @__PURE__ */ jsx4(CheckboxControl, { field, value, onChange, disabled, id: inputId });
1123
+ return /* @__PURE__ */ jsx5(CheckboxControl, { field, value, onChange, disabled, id: inputId });
991
1124
  case "select":
992
1125
  case "multi_select":
993
- return /* @__PURE__ */ jsx4(OptionControl, { field, value, onChange, disabled, id: inputId });
1126
+ return /* @__PURE__ */ jsx5(OptionControl, { field, value, onChange, disabled, id: inputId });
994
1127
  case "member":
995
- return /* @__PURE__ */ jsx4(PersonPicker, { field, value, onChange, disabled, id: inputId });
1128
+ return /* @__PURE__ */ jsx5(PersonPicker, { field, value, onChange, disabled, id: inputId });
996
1129
  case "relation":
997
1130
  case "attachment":
998
- return /* @__PURE__ */ jsx4(RelationPicker, { field, value, onChange, disabled, id: inputId });
1131
+ return /* @__PURE__ */ jsx5(RelationPicker, { field, value, onChange, disabled, id: inputId });
999
1132
  default:
1000
1133
  if (typeof value === "boolean" || field.config?.["kind"] === "boolean") {
1001
- return /* @__PURE__ */ jsx4(
1134
+ return /* @__PURE__ */ jsx5(
1002
1135
  Checkbox2,
1003
1136
  {
1004
1137
  id: inputId,
@@ -1008,7 +1141,7 @@ function FieldControl({ field, value, onChange, disabled, id }) {
1008
1141
  }
1009
1142
  );
1010
1143
  }
1011
- return /* @__PURE__ */ jsx4(
1144
+ return /* @__PURE__ */ jsx5(
1012
1145
  BasicInput3,
1013
1146
  {
1014
1147
  id: inputId,
@@ -1021,8 +1154,8 @@ function FieldControl({ field, value, onChange, disabled, id }) {
1021
1154
  }
1022
1155
  function CheckboxControl({ field, value, onChange, disabled, id }) {
1023
1156
  const unanswered = value === null || value === void 0;
1024
- return /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
1025
- /* @__PURE__ */ jsx4(
1157
+ return /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
1158
+ /* @__PURE__ */ jsx5(
1026
1159
  Checkbox2,
1027
1160
  {
1028
1161
  id,
@@ -1032,9 +1165,9 @@ function CheckboxControl({ field, value, onChange, disabled, id }) {
1032
1165
  onCheckedChange: (next) => onChange(next === true)
1033
1166
  }
1034
1167
  ),
1035
- unanswered ? /* @__PURE__ */ jsx4("span", { className: "text-xs italic text-muted-foreground", children: "not answered yet" }) : /* @__PURE__ */ jsxs3(Fragment2, { children: [
1036
- /* @__PURE__ */ jsx4("span", { className: "text-xs text-muted-foreground", children: value === true ? "Yes" : "No" }),
1037
- field.required ? null : /* @__PURE__ */ jsx4(
1168
+ unanswered ? /* @__PURE__ */ jsx5("span", { className: "text-xs italic text-muted-foreground", children: "not answered yet" }) : /* @__PURE__ */ jsxs4(Fragment2, { children: [
1169
+ /* @__PURE__ */ jsx5("span", { className: "text-xs text-muted-foreground", children: value === true ? "Yes" : "No" }),
1170
+ field.required ? null : /* @__PURE__ */ jsx5(
1038
1171
  Button3,
1039
1172
  {
1040
1173
  size: "sm",
@@ -1052,8 +1185,8 @@ function JsonControl({ field, value, onChange, disabled, id }) {
1052
1185
  const initial = value === null || value === void 0 ? "" : typeof value === "string" ? value : JSON.stringify(value, null, 2);
1053
1186
  const [text, setText] = useState3(initial);
1054
1187
  const [broken, setBroken] = useState3(null);
1055
- return /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-1", children: [
1056
- /* @__PURE__ */ jsx4(
1188
+ return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-1", children: [
1189
+ /* @__PURE__ */ jsx5(
1057
1190
  BasicTextarea,
1058
1191
  {
1059
1192
  id,
@@ -1073,37 +1206,53 @@ function JsonControl({ field, value, onChange, disabled, id }) {
1073
1206
  onChange(JSON.parse(next));
1074
1207
  setBroken(null);
1075
1208
  } catch (thrown) {
1076
- setBroken(thrown instanceof Error ? thrown.message : String(thrown));
1209
+ setBroken(
1210
+ refusalFromThrown(
1211
+ thrown,
1212
+ `${fieldName(field)} is not readable as structured data yet.`,
1213
+ "Fix the text so it reads as structured data, or discard it to put back what was there.",
1214
+ "invalid_argument"
1215
+ )
1216
+ );
1077
1217
  }
1078
1218
  }
1079
1219
  }
1080
1220
  ),
1081
- broken ? /* @__PURE__ */ jsxs3("p", { className: "text-xs text-destructive", children: [
1082
- fieldName(field),
1083
- " is not readable as structured data yet: ",
1084
- broken
1085
- ] }) : null
1221
+ broken ? (
1222
+ // The typed text is still in the box: the notice owns the two doors out.
1223
+ /* @__PURE__ */ jsx5(
1224
+ RefusalNotice,
1225
+ {
1226
+ error: broken,
1227
+ onKeepEditing: () => id ? document.getElementById(id)?.focus() : void 0,
1228
+ onDiscard: () => {
1229
+ setText(initial);
1230
+ setBroken(null);
1231
+ }
1232
+ }
1233
+ )
1234
+ ) : null
1086
1235
  ] });
1087
1236
  }
1088
1237
  function OptionControl({ field, value, onChange, disabled, id }) {
1089
1238
  const client = useOptionalRecordsClient2();
1090
1239
  const [options, setOptions] = useState3(null);
1091
- const [refusal, setRefusal] = useState3(null);
1240
+ const [refusal2, setRefusal] = useState3(null);
1092
1241
  useEffect2(() => {
1093
1242
  if (!client) return;
1094
1243
  let cancelled = false;
1095
1244
  void client.fieldOptions({ field_id: field.id }).then((result) => {
1096
1245
  if (cancelled) return;
1097
1246
  if (result.ok) setOptions(result.data ?? []);
1098
- else setRefusal(refusalLineForAPerson(result.error));
1247
+ else setRefusal(result.error);
1099
1248
  });
1100
1249
  return () => {
1101
1250
  cancelled = true;
1102
1251
  };
1103
1252
  }, [client, field.id]);
1104
1253
  if (!client) {
1105
- return /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-1", children: [
1106
- /* @__PURE__ */ jsx4(
1254
+ return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-1", children: [
1255
+ /* @__PURE__ */ jsx5(
1107
1256
  BasicInput3,
1108
1257
  {
1109
1258
  id,
@@ -1112,19 +1261,20 @@ function OptionControl({ field, value, onChange, disabled, id }) {
1112
1261
  onChange: (e) => onChange(e.target.value === "" ? null : e.target.value)
1113
1262
  }
1114
1263
  ),
1115
- /* @__PURE__ */ jsx4("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." })
1264
+ /* @__PURE__ */ jsx5("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." })
1116
1265
  ] });
1117
1266
  }
1118
- if (refusal) return /* @__PURE__ */ jsx4("p", { id, className: "text-xs text-destructive", children: refusal });
1119
- if (!options) return /* @__PURE__ */ jsx4("p", { id, className: "text-xs text-muted-foreground", children: "Reading this field's choices\u2026" });
1267
+ if (refusal2)
1268
+ return /* @__PURE__ */ jsx5("div", { id, children: /* @__PURE__ */ jsx5(RefusalLine, { error: refusal2 }) });
1269
+ if (!options) return /* @__PURE__ */ jsx5("p", { id, className: "text-xs text-muted-foreground", children: "Reading this field's choices\u2026" });
1120
1270
  if (options.length === 0) {
1121
- return /* @__PURE__ */ jsx4("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.` });
1271
+ return /* @__PURE__ */ jsx5("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.` });
1122
1272
  }
1123
1273
  const titleOf = (option) => recordName(option.data, null, "Unnamed choice");
1124
1274
  if (field.multi) {
1125
1275
  const chosen = options.filter((option) => choiceValuesOf(value).some((v) => isTheChosen(option, v)));
1126
1276
  const picked = new Set(chosen.map((option) => optionKey(option)));
1127
- return /* @__PURE__ */ jsx4("div", { id, className: "flex flex-wrap gap-1", children: options.map((option) => /* @__PURE__ */ jsx4(
1277
+ return /* @__PURE__ */ jsx5("div", { id, className: "flex flex-wrap gap-1", children: options.map((option) => /* @__PURE__ */ jsx5(
1128
1278
  Button3,
1129
1279
  {
1130
1280
  type: "button",
@@ -1143,15 +1293,15 @@ function OptionControl({ field, value, onChange, disabled, id }) {
1143
1293
  )) });
1144
1294
  }
1145
1295
  const current = options.find((option) => isTheChosen(option, value));
1146
- return /* @__PURE__ */ jsxs3(
1296
+ return /* @__PURE__ */ jsxs4(
1147
1297
  Select2,
1148
1298
  {
1149
1299
  value: current ? optionKey(current) : "",
1150
1300
  disabled: disabled ?? false,
1151
1301
  onValueChange: (next) => onChange(next === "" ? null : next),
1152
1302
  children: [
1153
- /* @__PURE__ */ jsx4(SelectTrigger2, { id, size: "sm", children: /* @__PURE__ */ jsx4(SelectValue2, { placeholder: "Choose" }) }),
1154
- /* @__PURE__ */ jsx4(SelectContent2, { children: options.map((option) => /* @__PURE__ */ jsx4(SelectItem2, { value: optionKey(option), children: titleOf(option) }, option.id)) })
1303
+ /* @__PURE__ */ jsx5(SelectTrigger2, { id, size: "sm", children: /* @__PURE__ */ jsx5(SelectValue2, { placeholder: "Choose" }) }),
1304
+ /* @__PURE__ */ jsx5(SelectContent2, { children: options.map((option) => /* @__PURE__ */ jsx5(SelectItem2, { value: optionKey(option), children: titleOf(option) }, option.id)) })
1155
1305
  ]
1156
1306
  }
1157
1307
  );
@@ -1167,15 +1317,15 @@ function RecordChip({
1167
1317
  // name. So it caps at its container and ends in an ellipsis with the whole name in
1168
1318
  // its tooltip, rather than being cut mid-word by whatever box it landed in
1169
1319
  // (lane FIX-14, 2026-09-22).
1170
- /* @__PURE__ */ jsxs3(Badge, { variant: "secondary", className: cn3("max-w-full gap-1 text-[11px]", className), title, children: [
1171
- /* @__PURE__ */ jsx4("span", { className: "min-w-0 truncate", children: title }),
1172
- onRemove ? /* @__PURE__ */ jsx4("button", { type: "button", onClick: onRemove, "aria-label": `Remove ${title}`, className: "shrink-0 opacity-60 hover:opacity-100", children: "\xD7" }) : null
1320
+ /* @__PURE__ */ jsxs4(Badge, { variant: "secondary", className: cn4("max-w-full gap-1 text-[11px]", className), title, children: [
1321
+ /* @__PURE__ */ jsx5("span", { className: "min-w-0 truncate", children: title }),
1322
+ onRemove ? /* @__PURE__ */ jsx5("button", { type: "button", onClick: onRemove, "aria-label": `Remove ${title}`, className: "shrink-0 opacity-60 hover:opacity-100", children: "\xD7" }) : null
1173
1323
  ] })
1174
1324
  );
1175
1325
  }
1176
1326
 
1177
1327
  // src/PersonPicker.tsx
1178
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1328
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1179
1329
  function personFromRow(row) {
1180
1330
  const data = row.document ?? {};
1181
1331
  const userId = data["user_id"];
@@ -1228,7 +1378,7 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1228
1378
  ]);
1229
1379
  setMembers(roster);
1230
1380
  if (records && !records.ok) {
1231
- setError(refusalLineForAPerson(records.error));
1381
+ setError(records.error);
1232
1382
  return;
1233
1383
  }
1234
1384
  setPeople(records ? records.data.rows.map(personFromRow).filter((p) => p.userId !== null) : []);
@@ -1249,10 +1399,10 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1249
1399
  };
1250
1400
  }, [client, personTable, picked.length, people.length]);
1251
1401
  if (!membersPort || !client) {
1252
- return /* @__PURE__ */ jsx5("p", { className: cn4("text-xs text-muted-foreground", className), children: NO_MEMBERS_REASON });
1402
+ return /* @__PURE__ */ jsx6("p", { className: cn5("text-xs text-muted-foreground", className), children: NO_MEMBERS_REASON });
1253
1403
  }
1254
1404
  if (!personTable) {
1255
- return /* @__PURE__ */ jsxs4("p", { className: cn4("text-xs text-muted-foreground", className), children: [
1405
+ return /* @__PURE__ */ jsxs5("p", { className: cn5("text-xs text-muted-foreground", className), children: [
1256
1406
  fieldName(field),
1257
1407
  " 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."
1258
1408
  ] });
@@ -1270,7 +1420,7 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1270
1420
  const resolved = await personRecordForMember(client, personTable, people, member);
1271
1421
  setBusy(false);
1272
1422
  if (!resolved.ok) {
1273
- setError(refusalLineForAPerson(resolved.error));
1423
+ setError(resolved.error);
1274
1424
  return;
1275
1425
  }
1276
1426
  if (!people.some((p) => p.id === resolved.data)) {
@@ -1296,8 +1446,8 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1296
1446
  const pickedUserIds = new Set(
1297
1447
  picked.map((recordId) => people.find((p) => p.id === recordId)?.userId).filter(Boolean)
1298
1448
  );
1299
- return /* @__PURE__ */ jsxs4("div", { className: cn4("flex flex-wrap items-center gap-1", className), children: [
1300
- picked.map((recordId) => /* @__PURE__ */ jsx5(
1449
+ return /* @__PURE__ */ jsxs5("div", { className: cn5("flex flex-wrap items-center gap-1", className), children: [
1450
+ picked.map((recordId) => /* @__PURE__ */ jsx6(
1301
1451
  RecordChip,
1302
1452
  {
1303
1453
  title: nameOfRecord(recordId),
@@ -1305,8 +1455,8 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1305
1455
  },
1306
1456
  recordId
1307
1457
  )),
1308
- disabled ? null : /* @__PURE__ */ jsxs4(Popover2, { open, onOpenChange: setOpen, children: [
1309
- /* @__PURE__ */ jsx5(PopoverTrigger2, { asChild: true, children: /* @__PURE__ */ jsx5(
1458
+ disabled ? null : /* @__PURE__ */ jsxs5(Popover2, { open, onOpenChange: setOpen, children: [
1459
+ /* @__PURE__ */ jsx6(PopoverTrigger2, { asChild: true, children: /* @__PURE__ */ jsx6(
1310
1460
  Button4,
1311
1461
  {
1312
1462
  id,
@@ -1317,8 +1467,8 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1317
1467
  children: picked.length === 0 ? "Pick a person" : "Change"
1318
1468
  }
1319
1469
  ) }),
1320
- /* @__PURE__ */ jsxs4(PopoverContent2, { sizing: "content", className: "p-1", align: "start", children: [
1321
- /* @__PURE__ */ jsx5(
1470
+ /* @__PURE__ */ jsxs5(PopoverContent2, { sizing: "content", className: "p-1", align: "start", children: [
1471
+ /* @__PURE__ */ jsx6(
1322
1472
  BasicInput4,
1323
1473
  {
1324
1474
  value: search,
@@ -1327,29 +1477,29 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1327
1477
  className: "mb-1 h-7 text-xs"
1328
1478
  }
1329
1479
  ),
1330
- error ? /* @__PURE__ */ jsx5("p", { className: "p-2 text-xs text-destructive", children: error }) : null,
1331
- members === null ? /* @__PURE__ */ jsx5("p", { className: "p-2 text-xs text-muted-foreground", children: "Reading the members\u2026" }) : null,
1332
- /* @__PURE__ */ jsxs4(ScrollArea2, { className: "max-h-56", children: [
1333
- /* @__PURE__ */ jsx5("ul", { children: visible.map((member) => /* @__PURE__ */ jsx5("li", { children: /* @__PURE__ */ jsxs4(
1480
+ error ? /* @__PURE__ */ jsx6(RefusalLine, { error, className: "p-2" }) : null,
1481
+ members === null ? /* @__PURE__ */ jsx6("p", { className: "p-2 text-xs text-muted-foreground", children: "Reading the members\u2026" }) : null,
1482
+ /* @__PURE__ */ jsxs5(ScrollArea2, { className: "max-h-56", children: [
1483
+ /* @__PURE__ */ jsx6("ul", { children: visible.map((member) => /* @__PURE__ */ jsx6("li", { children: /* @__PURE__ */ jsxs5(
1334
1484
  "button",
1335
1485
  {
1336
1486
  type: "button",
1337
1487
  disabled: busy,
1338
1488
  onClick: () => void pick(member),
1339
- className: cn4(
1489
+ className: cn5(
1340
1490
  "w-full truncate rounded px-2 py-1 text-left text-xs hover:bg-muted",
1341
1491
  pickedUserIds.has(member.userId) ? "bg-accent text-accent-foreground" : ""
1342
1492
  ),
1343
1493
  children: [
1344
- /* @__PURE__ */ jsx5("span", { children: memberName(member) }),
1345
- member.email && member.email !== memberName(member) ? /* @__PURE__ */ jsxs4("span", { className: "ml-1.5 opacity-60", children: [
1494
+ /* @__PURE__ */ jsx6("span", { children: memberName(member) }),
1495
+ member.email && member.email !== memberName(member) ? /* @__PURE__ */ jsxs5("span", { className: "ml-1.5 opacity-60", children: [
1346
1496
  "\xB7 ",
1347
1497
  member.email
1348
1498
  ] }) : null
1349
1499
  ]
1350
1500
  }
1351
1501
  ) }, member.userId)) }),
1352
- members !== null && visible.length === 0 ? /* @__PURE__ */ jsx5("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
1502
+ members !== null && visible.length === 0 ? /* @__PURE__ */ jsx6("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
1353
1503
  ] })
1354
1504
  ] })
1355
1505
  ] })
@@ -1362,55 +1512,6 @@ import { asWriteConflict } from "@ai-matrx/records/core";
1362
1512
  import { useRecordsClient as useRecordsClient3 } from "@ai-matrx/records/react";
1363
1513
  import { Button as Button5, Checkbox as Checkbox3, cn as cn7 } from "@ai-matrx/design-system";
1364
1514
 
1365
- // src/Refusal.tsx
1366
- import { Alert, AlertDescription, AlertTitle, cn as cn5 } from "@ai-matrx/design-system";
1367
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1368
- function hintIsMachineIdentity(hint) {
1369
- return isMachineIdentity(hint);
1370
- }
1371
- function hintForAPerson(hint) {
1372
- const said = (hint ?? "").trim();
1373
- if (said === "") return null;
1374
- return isMachineIdentity(said) ? null : said;
1375
- }
1376
- function RefusalNotice({
1377
- error,
1378
- className,
1379
- actions
1380
- }) {
1381
- const plain = refusalForAPerson(error);
1382
- return /* @__PURE__ */ jsxs5(
1383
- Alert,
1384
- {
1385
- variant: "destructive",
1386
- className: cn5("text-xs", className),
1387
- ...plain.forEngineers ? { title: plain.forEngineers } : {},
1388
- children: [
1389
- /* @__PURE__ */ jsx6(AlertTitle, { className: "text-xs font-medium", children: plain.title }),
1390
- /* @__PURE__ */ jsxs5(AlertDescription, { className: "space-y-1 text-xs", children: [
1391
- plain.sentence.replace(/\.$/, "") === plain.title ? null : /* @__PURE__ */ jsx6("p", { children: plain.sentence }),
1392
- /* @__PURE__ */ jsx6("p", { className: "opacity-80", children: plain.remedy }),
1393
- plain.forEngineers ? /* @__PURE__ */ jsx6(
1394
- "p",
1395
- {
1396
- className: "sr-only",
1397
- "aria-hidden": "true",
1398
- "data-for-engineers": "",
1399
- ...error.sqlstate ? { "data-sqlstate": error.sqlstate } : {},
1400
- children: plain.forEngineers
1401
- }
1402
- ) : null,
1403
- actions
1404
- ] })
1405
- ]
1406
- }
1407
- );
1408
- }
1409
- function RefusalLine({ error, className }) {
1410
- const plain = refusalForAPerson(error);
1411
- return /* @__PURE__ */ jsx6("p", { className: cn5("text-xs text-destructive", className), title: plain.forEngineers || void 0, children: refusalLineForAPerson(error) });
1412
- }
1413
-
1414
1515
  // src/whoChangedSource.tsx
1415
1516
  import { createContext as createContext2, useCallback as useCallback2, useContext as useContext2, useEffect as useEffect4, useMemo as useMemo5, useRef, useState as useState5 } from "react";
1416
1517
  import { useRecordsClient } from "@ai-matrx/records/react";
@@ -2013,7 +2114,7 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow }
2013
2114
  startedFrom.current = null;
2014
2115
  }, []);
2015
2116
  const writeCellNow = useCallback4(
2016
- async (rowId, key, value) => {
2117
+ async (rowId, key, value, resent = false) => {
2017
2118
  const id = cellKey(rowId, key);
2018
2119
  setOptimistic((held) => ({ ...held, [id]: value }));
2019
2120
  setSaving((held) => ({ ...held, [id]: true }));
@@ -2056,16 +2157,25 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow }
2056
2157
  delete next[id];
2057
2158
  return next;
2058
2159
  });
2059
- if (written.error.code === "stale_write") versions.current.delete(rowId);
2160
+ const conflict = asWriteConflict(written.error, rowId);
2161
+ if (written.error.code === "stale_write") {
2162
+ if (conflict) rememberVersion(versions.current, rowId, conflict.current_version);
2163
+ else versions.current.delete(rowId);
2164
+ if (conflict && !resent && !Object.prototype.hasOwnProperty.call(conflict.contested_fields, key)) {
2165
+ await writeCellNow(rowId, key, value, true);
2166
+ return;
2167
+ }
2168
+ }
2060
2169
  setRefusals((held) => ({
2061
2170
  ...held,
2062
2171
  [id]: {
2063
2172
  error: written.error,
2064
- conflict: asWriteConflict(written.error, rowId),
2173
+ conflict,
2065
2174
  attempted: value
2066
2175
  }
2067
2176
  }));
2068
2177
  },
2178
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2069
2179
  [client, reload]
2070
2180
  );
2071
2181
  const writeCell = useCallback4(
@@ -2140,6 +2250,33 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow }
2140
2250
  },
2141
2251
  [reload]
2142
2252
  );
2253
+ const keepEditing = useCallback4(
2254
+ (rowId, key) => {
2255
+ const held = refusals[cellKey(rowId, key)];
2256
+ if (!held || !writable(rowId)) return;
2257
+ startedFrom.current = shownValue(rowId, key);
2258
+ typed.current = held.attempted;
2259
+ setDraft(held.attempted);
2260
+ setEditing({ rowId, key });
2261
+ },
2262
+ [refusals, writable, shownValue]
2263
+ );
2264
+ const discard = useCallback4((rowId, key) => {
2265
+ const id = cellKey(rowId, key);
2266
+ setRefusals((held) => {
2267
+ if (!held[id]) return held;
2268
+ const next = { ...held };
2269
+ delete next[id];
2270
+ return next;
2271
+ });
2272
+ setOptimistic((held) => {
2273
+ if (!(id in held)) return held;
2274
+ const next = { ...held };
2275
+ delete next[id];
2276
+ return next;
2277
+ });
2278
+ setEditing((open) => open && open.rowId === rowId && open.key === key ? null : open);
2279
+ }, []);
2143
2280
  const remove = useCallback4(
2144
2281
  async (rowId) => {
2145
2282
  setBusy(true);
@@ -2191,6 +2328,8 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow }
2191
2328
  },
2192
2329
  retry,
2193
2330
  keepTheirs,
2331
+ keepEditing,
2332
+ discard,
2194
2333
  remove,
2195
2334
  addRecord,
2196
2335
  rowError,
@@ -2249,7 +2388,7 @@ function GridCell({
2249
2388
  );
2250
2389
  const value = editing ? editing.valueFor(row, field) : row.document?.[field.key];
2251
2390
  const state = editing ? editing.stateOf(row.id, field.key) : null;
2252
- const refusal = editing ? editing.refusalOf(row.id, field.key) : null;
2391
+ const refusal2 = editing ? editing.refusalOf(row.id, field.key) : null;
2253
2392
  const editable = Boolean(editing) && canWrite && fieldIsEditable(field);
2254
2393
  if (open && editing) {
2255
2394
  return /* @__PURE__ */ jsx10(
@@ -2259,13 +2398,28 @@ function GridCell({
2259
2398
  draft: editing.draft,
2260
2399
  onType: editing.type,
2261
2400
  onCommit: (move) => editing.commit(move),
2262
- onCancel: editing.cancel
2401
+ onCancel: editing.cancel,
2402
+ below: (
2403
+ // KEPT EDITING: the refusal stays beside the value it is about, so the
2404
+ // person reads what to change while changing it. Keep editing is the
2405
+ // state they are in, so only Discard is offered. It is INSIDE the open
2406
+ // cell, so pressing Discard is not "focus left the cell" — which would
2407
+ // commit the refused value one more time on its way out.
2408
+ refusal2 && !refusal2.conflict ? /* @__PURE__ */ jsx10(
2409
+ RefusalNotice,
2410
+ {
2411
+ error: refusal2.error,
2412
+ className: "py-1.5",
2413
+ onDiscard: () => editing.discard(row.id, field.key)
2414
+ }
2415
+ ) : null
2416
+ )
2263
2417
  }
2264
2418
  );
2265
2419
  }
2266
2420
  if (editorKindFor(field) === "checkbox") {
2267
2421
  const answered = value === true || value === false;
2268
- return /* @__PURE__ */ jsxs6("span", { "data-matrx-cell-control": "", className: "flex w-full items-center gap-1.5", children: [
2422
+ return /* @__PURE__ */ jsxs6("span", { "data-matrx-cell-control": "", className: "flex w-full flex-wrap items-center gap-1.5", children: [
2269
2423
  /* @__PURE__ */ jsx10(
2270
2424
  Checkbox3,
2271
2425
  {
@@ -2278,10 +2432,7 @@ function GridCell({
2278
2432
  }
2279
2433
  ),
2280
2434
  answered ? null : /* @__PURE__ */ jsx10("span", { className: "text-xs text-muted-foreground", title: "nobody has answered this yet", children: "\u2014" }),
2281
- refusal ? /* @__PURE__ */ jsxs6("span", { className: "flex items-center gap-1 rounded border border-destructive/40 px-1", children: [
2282
- /* @__PURE__ */ jsx10(RefusalLine, { error: refusal.error }),
2283
- /* @__PURE__ */ jsx10(Button5, { size: "sm", variant: "outline", onClick: () => editing?.retry(row.id, field.key), children: "Try again" })
2284
- ] }) : null
2435
+ refusal2 ? /* @__PURE__ */ jsx10(CellRefusalNotice, { refusal: refusal2, field, row, editing, typed: false }) : null
2285
2436
  ] });
2286
2437
  }
2287
2438
  const openThisCell = (event) => {
@@ -2354,22 +2505,48 @@ function GridCell({
2354
2505
  ),
2355
2506
  onAskWhoChanged ? /* @__PURE__ */ jsx10(WhoBadge, { field, row, onAskWhoChanged }) : null,
2356
2507
  state === "saving" ? /* @__PURE__ */ jsx10("span", { className: "px-1 text-[10px] text-muted-foreground", children: "Saving\u2026" }) : null,
2357
- refusal ? /* @__PURE__ */ jsxs6("span", { className: "flex flex-col gap-0.5 rounded border border-destructive/40 p-1", children: [
2358
- /* @__PURE__ */ jsx10(RefusalLine, { error: refusal.error }),
2359
- refusal.conflict ? /* @__PURE__ */ jsxs6("span", { className: "text-[11px]", children: [
2360
- "Theirs: ",
2361
- /* @__PURE__ */ jsx10("strong", { children: formatTheirs(refusal.conflict, field.key) })
2362
- ] }) : null,
2363
- /* @__PURE__ */ jsxs6("span", { className: "flex gap-1", children: [
2364
- /* @__PURE__ */ jsx10(Button5, { size: "sm", variant: "outline", onClick: () => editing?.retry(row.id, field.key), children: "Try again" }),
2365
- /* @__PURE__ */ jsx10(Button5, { size: "sm", variant: "ghost", onClick: () => editing?.keepTheirs(row.id, field.key), children: "Keep theirs" })
2366
- ] })
2367
- ] }) : null
2508
+ refusal2 ? /* @__PURE__ */ jsx10(CellRefusalNotice, { refusal: refusal2, field, row, editing, typed: true }) : null
2368
2509
  ]
2369
2510
  }
2370
2511
  )
2371
2512
  );
2372
2513
  }
2514
+ function CellRefusalNotice({
2515
+ refusal: refusal2,
2516
+ field,
2517
+ row,
2518
+ editing,
2519
+ typed
2520
+ }) {
2521
+ if (refusal2.conflict) {
2522
+ return /* @__PURE__ */ jsx10(
2523
+ RefusalNotice,
2524
+ {
2525
+ error: refusal2.error,
2526
+ className: "py-1.5",
2527
+ actions: /* @__PURE__ */ jsxs6("span", { className: "flex flex-col gap-1", "data-refusal-conflict": "", children: [
2528
+ /* @__PURE__ */ jsxs6("span", { className: "text-[11px]", children: [
2529
+ "Theirs: ",
2530
+ /* @__PURE__ */ jsx10("strong", { children: formatTheirs(refusal2.conflict, field.key) })
2531
+ ] }),
2532
+ /* @__PURE__ */ jsxs6("span", { className: "flex flex-wrap gap-1", children: [
2533
+ /* @__PURE__ */ jsx10(Button5, { size: "sm", variant: "outline", onClick: () => editing?.retry(row.id, field.key), children: "Try again" }),
2534
+ /* @__PURE__ */ jsx10(Button5, { size: "sm", variant: "ghost", onClick: () => editing?.keepTheirs(row.id, field.key), children: "Keep theirs" })
2535
+ ] })
2536
+ ] })
2537
+ }
2538
+ );
2539
+ }
2540
+ return /* @__PURE__ */ jsx10(
2541
+ RefusalNotice,
2542
+ {
2543
+ error: refusal2.error,
2544
+ className: "py-1.5",
2545
+ ...typed ? { onKeepEditing: () => editing?.keepEditing(row.id, field.key) } : {},
2546
+ onDiscard: () => editing?.discard(row.id, field.key)
2547
+ }
2548
+ );
2549
+ }
2373
2550
  function WhoBadge({
2374
2551
  field,
2375
2552
  row,
@@ -2422,7 +2599,8 @@ function OpenCell({
2422
2599
  draft,
2423
2600
  onType,
2424
2601
  onCommit,
2425
- onCancel
2602
+ onCancel,
2603
+ below
2426
2604
  }) {
2427
2605
  const multiline = takesANewline(field);
2428
2606
  const blurCommits = closesOnBlur(field);
@@ -2451,6 +2629,7 @@ function OpenCell({
2451
2629
  className: "flex w-full min-w-[10rem] flex-col gap-1",
2452
2630
  onDoubleClick: (event) => event.stopPropagation(),
2453
2631
  onKeyDown: (event) => {
2632
+ if (event.target?.closest?.("[data-refusal-notice]")) return;
2454
2633
  if (event.key === "Escape") {
2455
2634
  event.preventDefault();
2456
2635
  event.stopPropagation();
@@ -2476,7 +2655,8 @@ function OpenCell({
2476
2655
  blurCommits ? /* @__PURE__ */ jsx10("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__ */ jsxs6("span", { className: "flex items-center gap-1", children: [
2477
2656
  /* @__PURE__ */ jsx10(Button5, { size: "sm", onClick: () => once(() => onCommit(null)), children: "Save" }),
2478
2657
  /* @__PURE__ */ jsx10(Button5, { size: "sm", variant: "ghost", onClick: () => once(onCancel), children: "Cancel" })
2479
- ] })
2658
+ ] }),
2659
+ below
2480
2660
  ]
2481
2661
  }
2482
2662
  );
@@ -3081,7 +3261,20 @@ function EnrichPanel({ tableId, fieldId, className }) {
3081
3261
  " in one go."
3082
3262
  ] }) : null
3083
3263
  ] }) : null,
3084
- outcome ? /* @__PURE__ */ jsx11("p", { className: cn8("text-xs", outcome.ok ? "text-muted-foreground" : "text-destructive"), children: outcome.message }) : null,
3264
+ outcome ? outcome.ok ? /* @__PURE__ */ jsx11("p", { className: "text-xs text-muted-foreground", children: outcome.message }) : (
3265
+ // A run that did not happen is a refusal, with a heading and a remedy,
3266
+ // never the host's words in red (lane REFUSAL-SWEEP).
3267
+ /* @__PURE__ */ jsx11(
3268
+ RefusalLine,
3269
+ {
3270
+ error: refusal(
3271
+ "internal",
3272
+ outcome.message,
3273
+ "Nothing was filled in. Try the run again in a moment."
3274
+ )
3275
+ }
3276
+ )
3277
+ ) : null,
3085
3278
  field ? null : null
3086
3279
  ] }, row.field_id);
3087
3280
  }) });
@@ -3126,15 +3319,15 @@ function PastePreview({
3126
3319
  /* @__PURE__ */ jsx12(PlanTable, { plan }),
3127
3320
  plan.refusals.length > 0 ? /* @__PURE__ */ jsxs8("div", { "data-matrx-paste-refusals": true, className: "flex flex-col gap-1", children: [
3128
3321
  /* @__PURE__ */ jsx12("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:` }),
3129
- /* @__PURE__ */ jsx12("ul", { className: "flex flex-col gap-1", children: plan.refusals.slice(0, 12).map((refusal, index) => /* @__PURE__ */ jsxs8("li", { className: "text-xs text-muted-foreground", children: [
3322
+ /* @__PURE__ */ jsx12("ul", { className: "flex flex-col gap-1", children: plan.refusals.slice(0, 12).map((refusal2, index) => /* @__PURE__ */ jsxs8("li", { className: "text-xs text-muted-foreground", children: [
3130
3323
  /* @__PURE__ */ jsxs8("span", { className: "font-medium", children: [
3131
3324
  "Row ",
3132
- refusal.fromLine,
3325
+ refusal2.fromLine,
3133
3326
  ", ",
3134
- refusal.column
3327
+ refusal2.column
3135
3328
  ] }),
3136
3329
  " \u2014 ",
3137
- refusal.why
3330
+ refusal2.why
3138
3331
  ] }, index)) }),
3139
3332
  plan.refusals.length > 12 ? /* @__PURE__ */ jsxs8("p", { className: "text-xs text-muted-foreground", children: [
3140
3333
  plan.refusals.length - 12,
@@ -3162,7 +3355,13 @@ function PastePreview({
3162
3355
  setAdding(group.fieldKey);
3163
3356
  void onAddOptions(group.fieldKey, group.words).catch(
3164
3357
  (err) => setAddFailed(
3165
- `Those options were not added: ${err instanceof Error ? err.message : String(err)}`
3358
+ // The error's own text is an engineer's; the person
3359
+ // reads what did not happen and what to do.
3360
+ asRefusal(
3361
+ err,
3362
+ "Those options were not added, so the pasted words still do not match a choice.",
3363
+ "Try adding them again, or add them from the column's settings."
3364
+ )
3166
3365
  )
3167
3366
  ).finally(() => setAdding(null));
3168
3367
  },
@@ -3170,7 +3369,7 @@ function PastePreview({
3170
3369
  }
3171
3370
  )
3172
3371
  ] }, group.fieldKey)),
3173
- addFailed ? /* @__PURE__ */ jsx12("p", { className: "text-xs text-destructive", children: addFailed }) : null
3372
+ addFailed ? /* @__PURE__ */ jsx12(RefusalLine, { error: addFailed }) : null
3174
3373
  ] }) : null,
3175
3374
  plan.pastRightEdge > 0 ? /* @__PURE__ */ jsxs8("p", { className: "text-xs text-muted-foreground", children: [
3176
3375
  plan.pastRightEdge,
@@ -3350,19 +3549,19 @@ function planPastedBlock(args) {
3350
3549
  accepted[key] = judged.value;
3351
3550
  }
3352
3551
  const predicted = predictValueRefusals(args.fields, accepted);
3353
- for (const refusal of predicted) {
3354
- const key = refusal.field_key;
3552
+ for (const refusal2 of predicted) {
3553
+ const key = refusal2.field_key;
3355
3554
  if (key === void 0) continue;
3356
3555
  const cell = built.find((b) => b.key === key && b.refusal === null);
3357
3556
  if (!cell) continue;
3358
- cell.refusal = refusal.message;
3557
+ cell.refusal = refusal2.message;
3359
3558
  cell.value = null;
3360
3559
  delete accepted[key];
3361
3560
  refusals.push({
3362
3561
  fromLine,
3363
3562
  column: fieldName(byKey.get(key) ?? { key }),
3364
3563
  raw: cell.raw,
3365
- why: refusal.message
3564
+ why: refusal2.message
3366
3565
  });
3367
3566
  }
3368
3567
  cells += Object.keys(accepted).length;
@@ -4126,18 +4325,24 @@ function ExportMenu({ tableId, rows, label, className }) {
4126
4325
  size: "sm",
4127
4326
  variant: "outline",
4128
4327
  onClick: () => {
4328
+ const failed = (thrown) => setFailure(
4329
+ refusalFromThrown(
4330
+ thrown,
4331
+ "The spreadsheet could not be written.",
4332
+ "The CSV beside this button carries the same rows."
4333
+ )
4334
+ );
4335
+ setFailure(null);
4129
4336
  try {
4130
- void exportXlsx(fields.data ?? [], exported(), name).then(
4337
+ exportXlsx(fields.data ?? [], exported(), name).then(
4131
4338
  (bytes) => download(
4132
4339
  `${name}.xlsx`,
4133
4340
  "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
4134
4341
  bytes
4135
4342
  )
4136
- );
4343
+ ).catch(failed);
4137
4344
  } catch (thrown) {
4138
- setFailure(
4139
- `The spreadsheet could not be written (${thrown instanceof Error ? thrown.message : String(thrown)}). The CSV beside this button carries the same rows.`
4140
- );
4345
+ failed(thrown);
4141
4346
  }
4142
4347
  },
4143
4348
  children: "XLSX"
@@ -4153,7 +4358,7 @@ function ExportMenu({ tableId, rows, label, className }) {
4153
4358
  human: () => exportCsv(fields.data ?? [], exported())
4154
4359
  }
4155
4360
  ),
4156
- failure ? /* @__PURE__ */ jsx14("p", { className: "text-xs text-destructive", children: failure }) : null
4361
+ failure ? /* @__PURE__ */ jsx14(RefusalLine, { error: failure }) : null
4157
4362
  ] });
4158
4363
  }
4159
4364
 
@@ -4269,6 +4474,45 @@ import {
4269
4474
  Separator as Separator2,
4270
4475
  cn as cn12
4271
4476
  } from "@ai-matrx/design-system";
4477
+
4478
+ // src/importRuleCheck.ts
4479
+ import { predictValueRefusals as predictValueRefusals2 } from "@ai-matrx/records/core";
4480
+ function checkImportRules(fields, rows, mapping) {
4481
+ const byKey = new Map(fields.map((f) => [f.key, f]));
4482
+ const verdicts = [];
4483
+ for (const [header, key] of Object.entries(mapping)) {
4484
+ const field = byKey.get(key);
4485
+ if (!field) continue;
4486
+ let refused = 0;
4487
+ let judged = 0;
4488
+ let first = null;
4489
+ rows.forEach((row, i) => {
4490
+ const raw = String(row[header] ?? "").trim();
4491
+ if (raw === "") return;
4492
+ judged += 1;
4493
+ const meant = valueFromPastedText(field, raw);
4494
+ let why = null;
4495
+ if ("refusal" in meant) why = meant.refusal;
4496
+ else {
4497
+ const predicted = predictValueRefusals2([field], { [key]: meant.value }).filter(
4498
+ (p) => p.field_key === void 0 || p.field_key === key
4499
+ );
4500
+ if (predicted.length > 0) why = predicted[0].message;
4501
+ }
4502
+ if (why !== null) {
4503
+ refused += 1;
4504
+ if (!first) first = { why, line: i + 1, raw };
4505
+ }
4506
+ });
4507
+ if (refused > 0 && first) {
4508
+ const f = first;
4509
+ verdicts.push({ header, field, refused, judged, firstWhy: f.why, firstLine: f.line, firstRaw: f.raw });
4510
+ }
4511
+ }
4512
+ return verdicts;
4513
+ }
4514
+
4515
+ // src/ImportWizard.tsx
4272
4516
  import { Fragment as Fragment7, jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
4273
4517
  var BATCH = 250;
4274
4518
  var SAMPLES = 40;
@@ -4321,9 +4565,13 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4321
4565
  const [openRun, setOpenRun] = useState13(null);
4322
4566
  const [runRows, setRunRows] = useState13(null);
4323
4567
  const [runProblem, setRunProblem] = useState13(null);
4568
+ const [runsProblem, setRunsProblem] = useState13(null);
4324
4569
  const loadRuns = useCallback8(async () => {
4325
4570
  const answered = await client.imports({ table_id: tableId, limit: 10 });
4326
- if (answered.ok) setRuns(answered.data);
4571
+ if (answered.ok) {
4572
+ setRuns(answered.data);
4573
+ setRunsProblem(null);
4574
+ } else setRunsProblem(answered.error);
4327
4575
  }, [client, tableId]);
4328
4576
  useEffect8(() => {
4329
4577
  void loadRuns();
@@ -4344,7 +4592,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4344
4592
  limit: 200
4345
4593
  });
4346
4594
  if (!answered.ok) {
4347
- setRunProblem(answered.error.message);
4595
+ setRunProblem(answered.error);
4348
4596
  return;
4349
4597
  }
4350
4598
  setRunRows({
@@ -4385,7 +4633,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4385
4633
  }));
4386
4634
  const answered = await client.importPlan({ table_id: tableId, columns });
4387
4635
  if (!answered.ok) {
4388
- setProblem(answered.error.message);
4636
+ setProblem(answered.error);
4389
4637
  setPhase("waiting");
4390
4638
  return;
4391
4639
  }
@@ -4402,7 +4650,14 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4402
4650
  setMapping(next);
4403
4651
  setPhase("ready");
4404
4652
  } catch (thrown) {
4405
- setProblem(`That file could not be read: ${thrown instanceof Error ? thrown.message : String(thrown)}`);
4653
+ setProblem(
4654
+ refusalFromThrown(
4655
+ thrown,
4656
+ "That file could not be read, so nothing was imported.",
4657
+ "Save it again as CSV or Excel and pick it again.",
4658
+ "invalid_argument"
4659
+ )
4660
+ );
4406
4661
  setPhase("waiting");
4407
4662
  }
4408
4663
  },
@@ -4419,7 +4674,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4419
4674
  setPhase("declaring");
4420
4675
  const made = await client.importDeclareColumns({ table_id: tableId, rows, mapping });
4421
4676
  if (!made.ok) {
4422
- setProblem(made.error.message);
4677
+ setProblem(made.error);
4423
4678
  setPhase("ready");
4424
4679
  return;
4425
4680
  }
@@ -4438,7 +4693,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4438
4693
  policy: { on_duplicate: onDuplicate, unmapped }
4439
4694
  });
4440
4695
  if (!opened.ok) {
4441
- setProblem(opened.error.message);
4696
+ setProblem(opened.error);
4442
4697
  setPhase("ready");
4443
4698
  return;
4444
4699
  }
@@ -4457,7 +4712,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4457
4712
  const batch = rows.slice(at, at + take2);
4458
4713
  const answered = await client.importRows({ import_id: importId, rows: batch, mapping });
4459
4714
  if (!answered.ok) {
4460
- setProblem(answered.error.message);
4715
+ setProblem(answered.error);
4461
4716
  setOutcomes([...collected]);
4462
4717
  setLedger({ ...told, final: false });
4463
4718
  setPhase("ready");
@@ -4476,7 +4731,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4476
4731
  }
4477
4732
  const finished = await client.importFinish({ import_id: importId, unmapped });
4478
4733
  if (!finished.ok) {
4479
- setProblem(finished.error.message);
4734
+ setProblem(finished.error);
4480
4735
  setPhase("done");
4481
4736
  return;
4482
4737
  }
@@ -4514,6 +4769,11 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4514
4769
  const titleMissing = Boolean(titleKey) && titleMappedTo === null;
4515
4770
  const titleFieldLabel = (titleKey ? plan?.fields.find((f) => f.key === titleKey)?.label : null) || (titleKey ? declared.find((f) => f.key === titleKey)?.label : null) || "the name column";
4516
4771
  const busy = phase === "reading" || phase === "planning" || phase === "declaring" || phase === "writing";
4772
+ const ruleVerdicts = useMemo10(() => checkImportRules(declared, rows, mapping), [declared, rows, mapping]);
4773
+ const ruleVerdictOf = useMemo10(() => new Map(ruleVerdicts.map((v) => [v.header, v])), [ruleVerdicts]);
4774
+ const refusedByRules = ruleVerdicts.reduce((n, v) => n + v.refused, 0);
4775
+ const [goAheadAnyway, setGoAheadAnyway] = useState13(false);
4776
+ useEffect8(() => setGoAheadAnyway(false), [ruleVerdicts]);
4517
4777
  return /* @__PURE__ */ jsxs12("div", { className: cn12("flex min-w-0 flex-col gap-2 text-xs", className), children: [
4518
4778
  /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-2", children: [
4519
4779
  /* @__PURE__ */ jsx16("span", { className: "font-medium", children: "Import" }),
@@ -4541,7 +4801,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4541
4801
  } : {}
4542
4802
  }
4543
4803
  ) : null,
4544
- problem ? /* @__PURE__ */ jsx16("p", { className: "text-destructive", children: problem }) : null,
4804
+ problem ? /* @__PURE__ */ jsx16(RefusalNotice, { error: problem }) : null,
4545
4805
  phase === "reading" ? /* @__PURE__ */ jsx16("p", { className: "text-muted-foreground", children: "Reading the file\u2026" }) : null,
4546
4806
  phase === "planning" ? /* @__PURE__ */ jsx16("p", { className: "text-muted-foreground", children: "Working out what each column is\u2026" }) : null,
4547
4807
  plan && parsed ? /* @__PURE__ */ jsxs12(Fragment7, { children: [
@@ -4638,7 +4898,28 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4638
4898
  column.unit ? /* @__PURE__ */ jsx16("span", { className: "ml-1 opacity-60", children: column.unit }) : null,
4639
4899
  /* @__PURE__ */ jsx16("p", { className: "mt-0.5 text-muted-foreground", children: column.why }),
4640
4900
  column.ambiguous ? /* @__PURE__ */ jsx16("p", { className: "text-destructive", children: "Check a date you know before you run this." }) : null,
4641
- column.collides_with ? /* @__PURE__ */ jsx16("p", { className: "text-destructive", children: "Two columns in this file would make the same column." }) : null
4901
+ column.collides_with ? /* @__PURE__ */ jsx16("p", { className: "text-destructive", children: "Two columns in this file would make the same column." }) : null,
4902
+ ruleVerdictOf.get(column.header) ? (() => {
4903
+ const v = ruleVerdictOf.get(column.header);
4904
+ return /* @__PURE__ */ jsxs12("div", { "data-import-rule-refusal": column.header, className: "mt-1", children: [
4905
+ /* @__PURE__ */ jsx16(
4906
+ RefusalLine,
4907
+ {
4908
+ error: refusal(
4909
+ "refused_by_rule",
4910
+ `${v.refused} of ${v.judged} row${v.judged === 1 ? "" : "s"} would be refused by ${fieldName(v.field)}'s rules. ${v.firstWhy}`,
4911
+ "Send this column somewhere else, fix those rows in your file, or import the rest and they are listed afterwards."
4912
+ )
4913
+ }
4914
+ ),
4915
+ /* @__PURE__ */ jsxs12("p", { className: "text-muted-foreground", "data-import-rule-sample": "", children: [
4916
+ "Line ",
4917
+ v.firstLine,
4918
+ " in your file: ",
4919
+ v.firstRaw
4920
+ ] })
4921
+ ] });
4922
+ })() : null
4642
4923
  ] }),
4643
4924
  /* @__PURE__ */ jsx16("span", { className: "truncate pt-1.5 text-muted-foreground", children: (column.samples ?? []).slice(0, 3).map(String).join(" \xB7 ") })
4644
4925
  ]
@@ -4702,12 +4983,34 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4702
4983
  )
4703
4984
  ] })
4704
4985
  ] }),
4986
+ ruleVerdicts.length > 0 ? /* @__PURE__ */ jsx16(
4987
+ RefusalNotice,
4988
+ {
4989
+ error: refusal(
4990
+ "refused_by_rule",
4991
+ `${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.`,
4992
+ "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."
4993
+ ),
4994
+ actions: /* @__PURE__ */ jsxs12("label", { className: "mt-1 flex items-center gap-1.5", "data-import-go-ahead": "", children: [
4995
+ /* @__PURE__ */ jsx16(
4996
+ "input",
4997
+ {
4998
+ type: "checkbox",
4999
+ checked: goAheadAnyway,
5000
+ disabled: busy,
5001
+ onChange: (e) => setGoAheadAnyway(e.target.checked)
5002
+ }
5003
+ ),
5004
+ "Import the rest anyway"
5005
+ ] })
5006
+ }
5007
+ ) : null,
4705
5008
  /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-2", children: [
4706
5009
  /* @__PURE__ */ jsx16(
4707
5010
  Button11,
4708
5011
  {
4709
5012
  size: "sm",
4710
- disabled: busy || !rights.write || rows.length === 0 || titleMissing,
5013
+ disabled: busy || !rights.write || rows.length === 0 || titleMissing || ruleVerdicts.length > 0 && !goAheadAnyway,
4711
5014
  onClick: () => void run(),
4712
5015
  children: phase === "declaring" ? "Adding the columns\u2026" : phase === "writing" ? `Writing\u2026 ${progress} of ${rows.length}` : `Import ${rows.length} row${rows.length === 1 ? "" : "s"}`
4713
5016
  }
@@ -4762,7 +5065,12 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4762
5065
  ] }) }),
4763
5066
  /* @__PURE__ */ jsx16("tbody", { children: interesting.slice(0, 200).map((o) => /* @__PURE__ */ jsxs12("tr", { className: "border-t align-top", children: [
4764
5067
  /* @__PURE__ */ jsx16("td", { className: "px-2 py-1", children: o.row }),
4765
- /* @__PURE__ */ jsx16("td", { className: cn12("px-2 py-1", o.outcome === "refused" && "text-destructive"), children: o.outcome === "refused" ? o.reason : o.outcome === "duplicate" ? o.reason : "written" }),
5068
+ /* @__PURE__ */ jsx16("td", { className: "px-2 py-1", children: o.outcome === "refused" ? /* @__PURE__ */ jsx16(
5069
+ RefusalLine,
5070
+ {
5071
+ error: refusal("refused_by_rule", o.reason, "Fix this line in your file and import it again.")
5072
+ }
5073
+ ) : o.outcome === "duplicate" ? /* @__PURE__ */ jsx16("span", { className: "text-muted-foreground", children: o.reason }) : "written" }),
4766
5074
  /* @__PURE__ */ jsx16("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 ") : "" })
4767
5075
  ] }, `${o.outcome}-${o.row}`)) })
4768
5076
  ] }) }) : null,
@@ -4785,6 +5093,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4785
5093
  ] }) : null
4786
5094
  ] }) : null
4787
5095
  ] }) : null,
5096
+ runsProblem ? /* @__PURE__ */ jsx16(RefusalNotice, { error: runsProblem }) : null,
4788
5097
  runs && runs.length > 0 ? /* @__PURE__ */ jsxs12(Fragment7, { children: [
4789
5098
  /* @__PURE__ */ jsx16(Separator2, {}),
4790
5099
  /* @__PURE__ */ jsx16("p", { className: "font-medium", children: "Imports into this table" }),
@@ -4817,7 +5126,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4817
5126
  }
4818
5127
  ) : null
4819
5128
  ] }),
4820
- openRun === r.import_id ? runProblem ? /* @__PURE__ */ jsx16("p", { className: "text-destructive", children: runProblem }) : runRows ? /* @__PURE__ */ jsxs12(Fragment7, { children: [
5129
+ openRun === r.import_id ? runProblem ? /* @__PURE__ */ jsx16(RefusalNotice, { error: runProblem }) : runRows ? /* @__PURE__ */ jsxs12(Fragment7, { children: [
4821
5130
  runRows.note ? /* @__PURE__ */ jsx16("p", { className: "text-muted-foreground", children: runRows.note }) : null,
4822
5131
  /* @__PURE__ */ jsx16("div", { className: "max-h-64 overflow-auto rounded border", children: /* @__PURE__ */ jsxs12("table", { className: "w-full", children: [
4823
5132
  /* @__PURE__ */ jsx16("thead", { className: "sticky top-0 bg-muted", children: /* @__PURE__ */ jsxs12("tr", { children: [
@@ -4829,7 +5138,16 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4829
5138
  const source = row.source ?? {};
4830
5139
  return /* @__PURE__ */ jsxs12("tr", { className: "border-t align-top", children: [
4831
5140
  /* @__PURE__ */ jsx16("td", { className: "px-2 py-1", children: String(row.row ?? "") }),
4832
- /* @__PURE__ */ jsx16("td", { className: "px-2 py-1 text-destructive", children: String(row.reason ?? "") }),
5141
+ /* @__PURE__ */ jsx16("td", { className: "px-2 py-1", children: /* @__PURE__ */ jsx16(
5142
+ RefusalLine,
5143
+ {
5144
+ error: refusal(
5145
+ "refused_by_rule",
5146
+ String(row.reason ?? "This row was refused."),
5147
+ "Fix this line in your file and import it again."
5148
+ )
5149
+ }
5150
+ ) }),
4833
5151
  /* @__PURE__ */ jsx16("td", { className: "max-w-96 px-2 py-1 text-muted-foreground", children: Object.entries(source).map(([k, v]) => `${k}: ${String(v ?? "")}`).join(" \xB7 ") })
4834
5152
  ] }, `${r.import_id}-${String(row.row ?? i)}`);
4835
5153
  }) })
@@ -5067,7 +5385,9 @@ function keyFor(label) {
5067
5385
  if (token === "") return "";
5068
5386
  return /^[a-z]/.test(token) ? token : `f_${token}`;
5069
5387
  }
5070
- 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.";
5388
+ 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.";
5389
+ 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.";
5390
+ var COLUMN_DID_NOT_LAND = `${COLUMN_DID_NOT_LAND_SENTENCE} ${COLUMN_DID_NOT_LAND_REMEDY}`;
5071
5391
  function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }) {
5072
5392
  const table = useTable4(tableId);
5073
5393
  const fields = useFields6(tableId);
@@ -5367,14 +5687,20 @@ function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }
5367
5687
  ] }, `${rule.kind}-${index}`))
5368
5688
  ] }),
5369
5689
  mutation.error ? /* @__PURE__ */ jsx18(RefusalNotice, { error: mutation.error }) : null,
5370
- missing ? /* @__PURE__ */ jsx18("p", { className: "text-[11px] text-destructive", children: missing }) : null,
5371
- notLanded ? /* @__PURE__ */ jsx18("p", { className: "text-[11px] text-destructive", children: COLUMN_DID_NOT_LAND }) : null,
5690
+ missing ? /* @__PURE__ */ jsx18("p", { id: "field-editor-missing", className: "text-[11px] text-muted-foreground", "data-field-editor-missing": "", children: missing }) : null,
5691
+ notLanded ? /* @__PURE__ */ jsx18(
5692
+ RefusalNotice,
5693
+ {
5694
+ error: refusal("internal", COLUMN_DID_NOT_LAND_SENTENCE, COLUMN_DID_NOT_LAND_REMEDY)
5695
+ }
5696
+ ) : null,
5372
5697
  /* @__PURE__ */ jsxs14("div", { className: "flex flex-wrap items-center gap-2", children: [
5373
5698
  /* @__PURE__ */ jsx18(
5374
5699
  Button13,
5375
5700
  {
5376
5701
  size: "sm",
5377
5702
  disabled: mutation.saving || missing !== null,
5703
+ ...missing ? { "aria-describedby": "field-editor-missing" } : {},
5378
5704
  onClick: () => void save(),
5379
5705
  children: mutation.saving ? "Saving\u2026" : field ? EDIT_FIELD_SAVE_LABEL : ADD_FIELD_SAVE_LABEL
5380
5706
  }
@@ -6357,8 +6683,8 @@ function TableSettings({
6357
6683
  /* @__PURE__ */ jsx21(Button16, { size: "sm", variant: "ghost", onClick: () => setAskingToRemove(null), children: "Keep it" })
6358
6684
  ] }) : null,
6359
6685
  /* @__PURE__ */ jsxs17("ul", { className: "divide-y rounded border", children: [
6360
- rows.map((field, index) => /* @__PURE__ */ jsxs17("li", { className: "flex items-center gap-2 px-2 py-1.5", children: [
6361
- /* @__PURE__ */ jsx21("span", { className: "min-w-0 flex-1 truncate", children: fieldName(field) }),
6686
+ rows.map((field, index) => /* @__PURE__ */ jsxs17("li", { "data-settings-field": "", className: "flex flex-wrap items-center gap-x-2 gap-y-1 px-2 py-1.5", children: [
6687
+ /* @__PURE__ */ jsx21("span", { "data-settings-field-name": "", className: "min-w-[8rem] flex-1 break-words", children: fieldName(field) }),
6362
6688
  /* @__PURE__ */ jsx21(Badge6, { variant: "outline", className: "text-[10px] font-normal", children: fieldTypeLabel(field) }),
6363
6689
  field.required ? /* @__PURE__ */ jsx21("span", { className: "text-[10px] text-destructive", children: "required" }) : null,
6364
6690
  /* @__PURE__ */ jsx21(
@@ -6415,9 +6741,9 @@ function TableSettings({
6415
6741
  /* @__PURE__ */ jsx21("span", { className: "font-medium", children: "Proposed" }),
6416
6742
  /* @__PURE__ */ jsx21("span", { className: "text-muted-foreground", children: proposals?.length ?? 0 })
6417
6743
  ] }),
6418
- proposals === void 0 ? /* @__PURE__ */ jsx21("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__ */ jsx21("p", { className: "text-muted-foreground", children: "No field is waiting for a decision." }) : /* @__PURE__ */ jsx21("ul", { className: "divide-y rounded border", children: proposals.map((proposal) => /* @__PURE__ */ jsxs17("li", { className: "flex items-center gap-2 px-2 py-1.5", children: [
6419
- /* @__PURE__ */ jsx21("span", { className: "min-w-0 flex-1 truncate", children: proposal.field.label || proposal.field.key }),
6420
- /* @__PURE__ */ jsx21("span", { className: "min-w-0 flex-1 truncate text-muted-foreground", children: proposal.why }),
6744
+ proposals === void 0 ? /* @__PURE__ */ jsx21("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__ */ jsx21("p", { className: "text-muted-foreground", children: "No field is waiting for a decision." }) : /* @__PURE__ */ jsx21("ul", { className: "divide-y rounded border", children: proposals.map((proposal) => /* @__PURE__ */ jsxs17("li", { className: "flex flex-wrap items-center gap-x-2 gap-y-1 px-2 py-1.5", children: [
6745
+ /* @__PURE__ */ jsx21("span", { className: "min-w-[8rem] flex-1 break-words", children: proposal.field.label || proposal.field.key }),
6746
+ /* @__PURE__ */ jsx21("span", { className: "min-w-[8rem] flex-1 break-words text-muted-foreground", children: proposal.why }),
6421
6747
  /* @__PURE__ */ jsx21(Badge6, { variant: "secondary", className: "text-[10px] font-normal", children: proposal.proposed_by }),
6422
6748
  /* @__PURE__ */ jsx21(Button16, { size: "sm", variant: "outline", onClick: () => onAcceptProposal?.(proposal), children: "Accept" }),
6423
6749
  /* @__PURE__ */ jsx21(Button16, { size: "sm", variant: "ghost", onClick: () => onRejectProposal?.(proposal), children: "Reject" })
@@ -6786,6 +7112,8 @@ function RecordForm({
6786
7112
  const mutation = useRecordMutation();
6787
7113
  const [draft, setDraft] = useState21({});
6788
7114
  const [touched, setTouched] = useState21(false);
7115
+ const [changed, setChanged] = useState21({});
7116
+ const [answered, setAnswered] = useState21(null);
6789
7117
  const loaded = useRecordVersion(recordId ?? null);
6790
7118
  const loadedVersion = loaded.version;
6791
7119
  useEffect12(() => {
@@ -6806,8 +7134,38 @@ function RecordForm({
6806
7134
  );
6807
7135
  if (fields.error) return /* @__PURE__ */ jsx23(RefusalNotice, { error: fields.error, className });
6808
7136
  if (existing.error) return /* @__PURE__ */ jsx23(RefusalNotice, { error: existing.error, className });
7137
+ const storeSaid = mutation.error && mutation.error !== answered ? mutation.error : null;
7138
+ const fieldProblems = predicted.filter((p) => p.field_key);
7139
+ const problemsShown = (key) => touched || changed[key] ? fieldProblems.filter((p) => p.field_key === key) : [];
7140
+ const storeRefusalIsOnAField = storeSaid !== null && !mutation.conflict && storeSaid.code === "refused_by_rule" && (fields.data ?? []).some((f) => problemsShown(f.key).length > 0);
7141
+ const stored = existing.data?.document;
7142
+ const focusField = (field) => {
7143
+ if (!field) return;
7144
+ globalThis.document?.getElementById(`field-${field.id}`)?.focus();
7145
+ };
7146
+ const keepEditing = (field) => {
7147
+ if (storeSaid) setAnswered(storeSaid);
7148
+ const first = field ?? (fields.data ?? []).find((f) => problemsShown(f.key).length > 0) ?? (fields.data ?? [])[0];
7149
+ focusField(first);
7150
+ };
7151
+ const discard = (field) => {
7152
+ if (storeSaid) setAnswered(storeSaid);
7153
+ if (field) {
7154
+ setDraft((d) => ({ ...d, [field.key]: stored ? stored[field.key] : void 0 }));
7155
+ setChanged((c) => {
7156
+ const next = { ...c };
7157
+ delete next[field.key];
7158
+ return next;
7159
+ });
7160
+ return;
7161
+ }
7162
+ setDraft(stored ? { ...stored } : {});
7163
+ setChanged({});
7164
+ setTouched(false);
7165
+ };
6809
7166
  const save = async () => {
6810
7167
  setTouched(true);
7168
+ setAnswered(null);
6811
7169
  if (recordId) {
6812
7170
  const version = loadedVersion;
6813
7171
  const moved = await mutation.update({
@@ -6844,16 +7202,27 @@ function RecordForm({
6844
7202
  field,
6845
7203
  value: draft[field.key],
6846
7204
  document: existing.data?.document,
6847
- problems: touched ? predicted.filter((p) => p.field_key === field.key) : [],
6848
- onChange: (value) => setDraft((d) => ({ ...d, [field.key]: value }))
7205
+ problems: problemsShown(field.key),
7206
+ onChange: (value) => {
7207
+ setDraft((d) => ({ ...d, [field.key]: value }));
7208
+ setChanged((c) => c[field.key] ? c : { ...c, [field.key]: true });
7209
+ },
7210
+ onKeepEditing: () => keepEditing(field),
7211
+ onDiscard: () => discard(field)
6849
7212
  },
6850
7213
  field.id
6851
7214
  )) }),
6852
- touched ? predicted.filter((p) => !p.field_key).map((p) => /* @__PURE__ */ jsx23("p", { className: "text-xs text-destructive", children: p.message }, p.message)) : null,
6853
- mutation.error ? /* @__PURE__ */ jsx23(
7215
+ touched ? predicted.filter((p) => !p.field_key).map((p) => /* @__PURE__ */ jsx23(
7216
+ RefusalNotice,
7217
+ {
7218
+ error: { code: "refused_by_rule", message: p.message }
7219
+ },
7220
+ p.message
7221
+ )) : null,
7222
+ storeSaid && !storeRefusalIsOnAField ? /* @__PURE__ */ jsx23(
6854
7223
  RefusalNotice,
6855
7224
  {
6856
- error: mutation.error,
7225
+ error: storeSaid,
6857
7226
  ...mutation.conflict ? {
6858
7227
  actions: /* @__PURE__ */ jsxs19("div", { className: "mt-1 space-y-1", children: [
6859
7228
  Object.entries(mutation.conflict.contested_fields).map(([key, theirs]) => {
@@ -6874,14 +7243,16 @@ function RecordForm({
6874
7243
  size: "sm",
6875
7244
  variant: "outline",
6876
7245
  onClick: () => {
7246
+ if (mutation.conflict) loaded.note(mutation.conflict.current_version);
6877
7247
  existing.reload();
6878
7248
  mutation.clearConflict();
7249
+ setAnswered(storeSaid);
6879
7250
  },
6880
7251
  children: "Take theirs and start again"
6881
7252
  }
6882
7253
  )
6883
7254
  ] })
6884
- } : {}
7255
+ } : { onKeepEditing: () => keepEditing(), onDiscard: () => discard() }
6885
7256
  }
6886
7257
  ) : null,
6887
7258
  /* @__PURE__ */ jsx23(Separator7, {}),
@@ -6898,13 +7269,23 @@ function FieldRow({
6898
7269
  value,
6899
7270
  document: document2,
6900
7271
  problems,
6901
- onChange
7272
+ onChange,
7273
+ onKeepEditing,
7274
+ onDiscard
6902
7275
  }) {
6903
7276
  const id = `field-${field.id}`;
6904
7277
  return /* @__PURE__ */ jsxs19("div", { className: "flex min-w-0 flex-col gap-1", children: [
6905
7278
  /* @__PURE__ */ jsx23(FieldLabel, { field, htmlFor: id, children: /* @__PURE__ */ jsx23(ProvenanceBadge, { document: document2, fieldKey: field.key }) }),
6906
7279
  /* @__PURE__ */ jsx23(FieldControl, { field, value, onChange, id }),
6907
- problems.map((problem) => /* @__PURE__ */ jsx23("p", { className: "text-xs text-destructive", children: problem.message }, problem.message))
7280
+ problems.map((problem, index) => /* @__PURE__ */ jsx23(
7281
+ RefusalNotice,
7282
+ {
7283
+ error: { code: "refused_by_rule", message: problem.message },
7284
+ className: "border-0 p-0",
7285
+ ...index === 0 ? { onKeepEditing, onDiscard } : {}
7286
+ },
7287
+ problem.message
7288
+ ))
6908
7289
  ] });
6909
7290
  }
6910
7291
  function asWords(value) {
@@ -6915,7 +7296,7 @@ function asWords(value) {
6915
7296
  }
6916
7297
 
6917
7298
  // src/ShareControl.tsx
6918
- import { useState as useState22 } from "react";
7299
+ import { useEffect as useEffect13, useState as useState22 } from "react";
6919
7300
  import { Button as Button18 } from "@ai-matrx/design-system";
6920
7301
  import { Fragment as Fragment11, jsx as jsx24, jsxs as jsxs20 } from "react/jsx-runtime";
6921
7302
  function useCanShare() {
@@ -6930,12 +7311,16 @@ function ShareControl({
6930
7311
  subjectId,
6931
7312
  name,
6932
7313
  may,
7314
+ initiallyOpen = false,
6933
7315
  size = "sm",
6934
7316
  variant = "ghost",
6935
7317
  className
6936
7318
  }) {
6937
7319
  const host = useRecordsUi();
6938
- const [open, setOpen] = useState22(false);
7320
+ const [open, setOpen] = useState22(initiallyOpen);
7321
+ useEffect13(() => {
7322
+ if (initiallyOpen) setOpen(true);
7323
+ }, [initiallyOpen]);
6939
7324
  const asked = useRecordRights(may === void 0 ? subjectId : null);
6940
7325
  const mayShare = may ?? asked.share;
6941
7326
  if (!host.share) return null;
@@ -7067,6 +7452,7 @@ function computedInPlainWords(row, field) {
7067
7452
  }
7068
7453
 
7069
7454
  // src/views.ts
7455
+ import { asWriteConflict as asWriteConflict2 } from "@ai-matrx/records/core";
7070
7456
  var VIEW_LAYOUTS = ["grid", "kanban", "calendar", "gallery"];
7071
7457
  var LAYOUT_LABEL = {
7072
7458
  grid: "Grid",
@@ -7190,6 +7576,18 @@ function viewFromRecord(row) {
7190
7576
  presentation: parseGridPresentation(data["presentation"])
7191
7577
  };
7192
7578
  }
7579
+ async function saveViewPatch(client, viewId, document2, expectedVersion) {
7580
+ const send = (expected) => client.recordUpdate({
7581
+ record_id: viewId,
7582
+ patch: document2,
7583
+ ...expected === null ? {} : { expectedVersion: expected }
7584
+ });
7585
+ const first = await send(expectedVersion);
7586
+ if (first.ok) return first;
7587
+ const conflict = asWriteConflict2(first.error, viewId);
7588
+ if (!conflict) return first;
7589
+ return send(conflict.current_version);
7590
+ }
7193
7591
  function viewPatchDocument(patch) {
7194
7592
  const out = {};
7195
7593
  if (patch.name !== void 0) out["name"] = patch.name;
@@ -7217,12 +7615,12 @@ function parseSorts(raw) {
7217
7615
  }
7218
7616
 
7219
7617
  // src/ViewSwitcher.tsx
7220
- import { useEffect as useEffect14, useMemo as useMemo18, useState as useState25 } from "react";
7618
+ import { useEffect as useEffect15, useMemo as useMemo18, useState as useState25 } from "react";
7221
7619
  import { useFields as useFields12, useRecords as useRecords5, useRecordsClient as useRecordsClient13 } from "@ai-matrx/records/react";
7222
7620
  import { Button as Button21, Skeleton as Skeleton7, cn as cn21 } from "@ai-matrx/design-system";
7223
7621
 
7224
7622
  // src/Pipeline.tsx
7225
- import { useCallback as useCallback10, useEffect as useEffect13, useMemo as useMemo17, useRef as useRef6, useState as useState24 } from "react";
7623
+ import { useCallback as useCallback10, useEffect as useEffect14, useMemo as useMemo17, useRef as useRef6, useState as useState24 } from "react";
7226
7624
  import {
7227
7625
  mayDrag,
7228
7626
  useFields as useFields11,
@@ -7271,7 +7669,7 @@ function PipelineBoard({
7271
7669
  const [dragging, setDragging] = useState24(null);
7272
7670
  const [over, setOver] = useState24(null);
7273
7671
  const alive = useRef6(true);
7274
- useEffect13(() => {
7672
+ useEffect14(() => {
7275
7673
  alive.current = true;
7276
7674
  return () => {
7277
7675
  alive.current = false;
@@ -7295,7 +7693,7 @@ function PipelineBoard({
7295
7693
  setWaiting(held.ok ? new Map((held.data ?? []).map((p) => [p.record_id, p])) : /* @__PURE__ */ new Map());
7296
7694
  setColumns(board.ok ? board.data ?? [] : []);
7297
7695
  }, [client, tableId, measure]);
7298
- useEffect13(() => {
7696
+ useEffect14(() => {
7299
7697
  void reload();
7300
7698
  }, [reload]);
7301
7699
  const stageKey = definition?.stage_field ?? null;
@@ -7517,10 +7915,22 @@ function Held({
7517
7915
  );
7518
7916
  }
7519
7917
  if (pending.kind === "refused") {
7520
- return /* @__PURE__ */ jsxs22("div", { className: "mt-1 rounded border border-destructive/50 bg-destructive/5 p-2 text-xs", children: [
7521
- /* @__PURE__ */ jsx26("p", { children: pending.verdict.why }),
7522
- /* @__PURE__ */ jsx26(Button20, { size: "sm", variant: "ghost", className: "mt-1 h-6", onClick: onCancel, children: "Close" })
7523
- ] });
7918
+ return (
7919
+ // THE STAGE GATE SAID NO: the one refusal surface, heading and remedy both,
7920
+ // never its sentence alone in a red box (lane REFUSAL-SWEEP).
7921
+ /* @__PURE__ */ jsx26(
7922
+ RefusalNotice,
7923
+ {
7924
+ className: "mt-1",
7925
+ error: refusal(
7926
+ "refused_by_rule",
7927
+ pending.verdict.why,
7928
+ "The card stays where it was. Fill in what this stage asks for, then move it again."
7929
+ ),
7930
+ actions: /* @__PURE__ */ jsx26(Button20, { size: "sm", variant: "ghost", className: "mt-1 h-6", onClick: onCancel, children: "Close" })
7931
+ }
7932
+ )
7933
+ );
7524
7934
  }
7525
7935
  if (pending.kind === "approval") {
7526
7936
  return /* @__PURE__ */ jsxs22("div", { className: "mt-1 flex flex-col gap-1 rounded border p-2 text-xs", children: [
@@ -7653,7 +8063,7 @@ function useViewRecords(view, pageSize = 200, filter) {
7653
8063
  error: null
7654
8064
  });
7655
8065
  const ruleId = view.ruleId ?? null;
7656
- useEffect14(() => {
8066
+ useEffect15(() => {
7657
8067
  if (!ruleId) return;
7658
8068
  let cancelled = false;
7659
8069
  setRuled({ rows: [], loading: true, error: null });
@@ -7717,7 +8127,7 @@ function ViewSwitcher({
7717
8127
  }) {
7718
8128
  const [layout, setLayout] = useState25(view.layout);
7719
8129
  const [local, setLocal] = useState25({});
7720
- useEffect14(() => {
8130
+ useEffect15(() => {
7721
8131
  setLayout(view.layout);
7722
8132
  setLocal({});
7723
8133
  }, [view.layout, view.name, view.subject]);
@@ -7785,7 +8195,7 @@ function useStageField(tableId) {
7785
8195
  asked: false,
7786
8196
  key: null
7787
8197
  });
7788
- useEffect14(() => {
8198
+ useEffect15(() => {
7789
8199
  let cancelled = false;
7790
8200
  setStage({ asked: false, key: null });
7791
8201
  void client.tableStageField({ table_id: tableId }).then((r) => {
@@ -8054,7 +8464,7 @@ function Card2({
8054
8464
  }
8055
8465
 
8056
8466
  // src/ArchivedView.tsx
8057
- import { useCallback as useCallback11, useEffect as useEffect15, useState as useState26 } from "react";
8467
+ import { useCallback as useCallback11, useEffect as useEffect16, useState as useState26 } from "react";
8058
8468
  import {
8059
8469
  ARCHIVE_LANES,
8060
8470
  ARCHIVE_LANE_LABEL,
@@ -8091,8 +8501,8 @@ function ArchivedView({
8091
8501
  const [loading, setLoading] = useState26(true);
8092
8502
  const [error, setError] = useState26(null);
8093
8503
  const [restoring, setRestoring] = useState26(null);
8094
- const [refusal, setRefusal] = useState26(null);
8095
- useEffect15(() => {
8504
+ const [refusal2, setRefusal] = useState26(null);
8505
+ useEffect16(() => {
8096
8506
  if (laneFromHost) setLane(laneFromHost);
8097
8507
  }, [laneFromHost]);
8098
8508
  const read = useCallback11(async () => {
@@ -8107,7 +8517,7 @@ function ArchivedView({
8107
8517
  }
8108
8518
  setLoading(false);
8109
8519
  }, [client, tableId, lane, pageSize]);
8110
- useEffect15(() => {
8520
+ useEffect16(() => {
8111
8521
  void read();
8112
8522
  }, [read]);
8113
8523
  const pick = (next) => {
@@ -8168,7 +8578,7 @@ function ArchivedView({
8168
8578
  /* @__PURE__ */ jsxs24("div", { className: "flex min-w-0 flex-1 flex-col", children: [
8169
8579
  /* @__PURE__ */ jsx28("span", { className: "truncate text-sm", children: recordName(row.document, titleKey) }),
8170
8580
  /* @__PURE__ */ jsx28("span", { className: "truncate text-xs text-muted-foreground", "data-testid": "archived-who", children: archivedByLine(row) }),
8171
- refusal && refusal.recordId === row.id ? /* @__PURE__ */ jsx28(RefusalNotice, { error: refusal.error, className: "mt-2" }) : null
8581
+ refusal2 && refusal2.recordId === row.id ? /* @__PURE__ */ jsx28(RefusalNotice, { error: refusal2.error, className: "mt-2" }) : null
8172
8582
  ] }),
8173
8583
  mayRestore ? /* @__PURE__ */ jsx28(
8174
8584
  Button22,
@@ -8189,7 +8599,7 @@ function ArchivedView({
8189
8599
  }
8190
8600
 
8191
8601
  // src/ArchivedDisclosure.tsx
8192
- import { useCallback as useCallback12, useEffect as useEffect16, useState as useState27 } from "react";
8602
+ import { useCallback as useCallback12, useEffect as useEffect17, useState as useState27 } from "react";
8193
8603
  import {
8194
8604
  emptyPortalArchiveLine,
8195
8605
  portalConfirmLine,
@@ -8246,7 +8656,7 @@ function ArchivedPortals({
8246
8656
  const [confirming, setConfirming] = useState27(null);
8247
8657
  const [typed, setTyped] = useState27("");
8248
8658
  const [restoring, setRestoring] = useState27(null);
8249
- const [refusal, setRefusal] = useState27(null);
8659
+ const [refusal2, setRefusal] = useState27(null);
8250
8660
  const [said, setSaid] = useState27(null);
8251
8661
  const read = useCallback12(async () => {
8252
8662
  const answered = await client.listPortals({ archived: "archived" });
@@ -8258,7 +8668,7 @@ function ArchivedPortals({
8258
8668
  setError(answered.error);
8259
8669
  }
8260
8670
  }, [client]);
8261
- useEffect16(() => {
8671
+ useEffect17(() => {
8262
8672
  void read();
8263
8673
  }, [read, refreshToken]);
8264
8674
  const restore = async (portal) => {
@@ -8362,7 +8772,7 @@ function ArchivedPortals({
8362
8772
  )
8363
8773
  ] })
8364
8774
  ] }) : null,
8365
- refusal && refusal.portalId === portal.portal_id ? /* @__PURE__ */ jsx29(RefusalNotice, { error: refusal.error }) : null
8775
+ refusal2 && refusal2.portalId === portal.portal_id ? /* @__PURE__ */ jsx29(RefusalNotice, { error: refusal2.error }) : null
8366
8776
  ]
8367
8777
  },
8368
8778
  portal.portal_id
@@ -8374,7 +8784,7 @@ function ArchivedPortals({
8374
8784
  }
8375
8785
 
8376
8786
  // src/ViewBar.tsx
8377
- import { useCallback as useCallback14, useEffect as useEffect17, useState as useState28 } from "react";
8787
+ import { useCallback as useCallback14, useEffect as useEffect18, useState as useState28 } from "react";
8378
8788
  import { useRecordsClient as useRecordsClient16 } from "@ai-matrx/records/react";
8379
8789
  import { Button as Button24, Input as Input2, Skeleton as Skeleton10, cn as cn24 } from "@ai-matrx/design-system";
8380
8790
 
@@ -8437,10 +8847,10 @@ function ViewBar({ tableId, activeViewId, onActiveView, seed, className }) {
8437
8847
  setError(null);
8438
8848
  setViews(mine);
8439
8849
  }, [client, viewTableId, tableId, seed]);
8440
- useEffect17(() => {
8850
+ useEffect18(() => {
8441
8851
  void load();
8442
8852
  }, [load]);
8443
- useEffect17(() => {
8853
+ useEffect18(() => {
8444
8854
  if (!views || views.length === 0) return;
8445
8855
  const chosen = views.find((v) => v.id === (activeViewId ?? active)) ?? views.find((v) => v.isDefault) ?? views[0];
8446
8856
  if (chosen.id !== active) setActive(chosen.id);
@@ -8616,12 +9026,12 @@ function ProposalRow({
8616
9026
  }
8617
9027
 
8618
9028
  // src/ActionInbox.tsx
8619
- import { useCallback as useCallback16, useEffect as useEffect19, useMemo as useMemo20, useRef as useRef8, useState as useState31 } from "react";
9029
+ import { useCallback as useCallback16, useEffect as useEffect20, useMemo as useMemo20, useRef as useRef8, useState as useState31 } from "react";
8620
9030
  import { useRecordsClient as useRecordsClient19 } from "@ai-matrx/records/react";
8621
9031
  import { Badge as Badge9, Button as Button27, Skeleton as Skeleton12, cn as cn27 } from "@ai-matrx/design-system";
8622
9032
 
8623
9033
  // src/ChecklistRunner.tsx
8624
- import { useCallback as useCallback15, useEffect as useEffect18, useMemo as useMemo19, useState as useState30 } from "react";
9034
+ import { useCallback as useCallback15, useEffect as useEffect19, useMemo as useMemo19, useState as useState30 } from "react";
8625
9035
  import { useRecordsClient as useRecordsClient18, useTable as useTable10 } from "@ai-matrx/records/react";
8626
9036
  import { Badge as Badge8, BasicInput as BasicInput9, BasicTextarea as BasicTextarea4, Button as Button26, Skeleton as Skeleton11, cn as cn26 } from "@ai-matrx/design-system";
8627
9037
  import { Fragment as Fragment13, jsx as jsx32, jsxs as jsxs28 } from "react/jsx-runtime";
@@ -8669,10 +9079,10 @@ function ChecklistRunner({
8669
9079
  (held) => held && answered.data.some((r) => r.run_id === held) ? held : answered.data[0]?.run_id ?? null
8670
9080
  );
8671
9081
  }, [client, includeClosed, recordId, runId, tableId]);
8672
- useEffect18(() => {
9082
+ useEffect19(() => {
8673
9083
  void loadRuns();
8674
9084
  }, [loadRuns]);
8675
- useEffect18(() => {
9085
+ useEffect19(() => {
8676
9086
  if (runId) setActiveId(runId);
8677
9087
  }, [runId]);
8678
9088
  const loadSteps = useCallback15(async () => {
@@ -8689,10 +9099,10 @@ function ChecklistRunner({
8689
9099
  setError(null);
8690
9100
  setSteps(answered.data);
8691
9101
  }, [client, activeId]);
8692
- useEffect18(() => {
9102
+ useEffect19(() => {
8693
9103
  void loadSteps();
8694
9104
  }, [loadSteps]);
8695
- useEffect18(() => {
9105
+ useEffect19(() => {
8696
9106
  if (!mayStart || !tableId) return;
8697
9107
  let cancelled = false;
8698
9108
  void client.checklistTemplates({ about_table_id: tableId, limit: 50 }).then((answered) => {
@@ -8918,7 +9328,7 @@ function useMyChecklistSteps(limit = 25) {
8918
9328
  setSteps(held);
8919
9329
  setLoading(false);
8920
9330
  }, [client, limit, me]);
8921
- useEffect18(() => {
9331
+ useEffect19(() => {
8922
9332
  void refresh();
8923
9333
  }, [refresh]);
8924
9334
  return { steps, loading, error, refresh };
@@ -8927,7 +9337,7 @@ function MyChecklistSteps({ className }) {
8927
9337
  const client = useRecordsClient18();
8928
9338
  const { steps, loading, error, refresh } = useMyChecklistSteps();
8929
9339
  const [busy, setBusy] = useState30(null);
8930
- const [refusal, setRefusal] = useState30(null);
9340
+ const [refusal2, setRefusal] = useState30(null);
8931
9341
  const complete = useCallback15(
8932
9342
  async (step2, evidence) => {
8933
9343
  setBusy(step2.step_id);
@@ -8950,7 +9360,7 @@ function MyChecklistSteps({ className }) {
8950
9360
  /* @__PURE__ */ jsx32("span", { className: "tabular-nums", children: steps.length })
8951
9361
  ] }),
8952
9362
  error ? /* @__PURE__ */ jsx32(RefusalNotice, { error }) : null,
8953
- refusal ? /* @__PURE__ */ jsx32(RefusalNotice, { error: refusal }) : null,
9363
+ refusal2 ? /* @__PURE__ */ jsx32(RefusalNotice, { error: refusal2 }) : null,
8954
9364
  /* @__PURE__ */ jsx32("ol", { className: "flex flex-col gap-1.5", children: steps.map((step2) => /* @__PURE__ */ jsx32(
8955
9365
  StepRow,
8956
9366
  {
@@ -8985,7 +9395,7 @@ function ChecklistsPanel({ tableId, onOpenRecord, className }) {
8985
9395
  if (!r.ok) setRuns([]);
8986
9396
  else setRuns(r.data);
8987
9397
  }, [client, tableId]);
8988
- useEffect18(() => {
9398
+ useEffect19(() => {
8989
9399
  void load();
8990
9400
  }, [load]);
8991
9401
  if (templates === null) return /* @__PURE__ */ jsx32(Skeleton11, { className: cn26("h-40 w-full", className) });
@@ -9127,11 +9537,11 @@ function ChecklistTemplateEditor({
9127
9537
  const client = useRecordsClient18();
9128
9538
  const [name, setName] = useState30("");
9129
9539
  const [rows, setRows] = useState30([{ ...EMPTY_ROW }]);
9130
- const [refusal, setRefusal] = useState30(null);
9540
+ const [refusal2, setRefusal] = useState30(null);
9131
9541
  const [error, setError] = useState30(null);
9132
9542
  const [busy, setBusy] = useState30(false);
9133
9543
  const [loading, setLoading] = useState30(Boolean(templateId));
9134
- useEffect18(() => {
9544
+ useEffect19(() => {
9135
9545
  if (!templateId) return;
9136
9546
  let cancelled = false;
9137
9547
  void client.checklistTemplateShape({ template_id: templateId }).then((answered) => {
@@ -9168,7 +9578,7 @@ function ChecklistTemplateEditor({
9168
9578
  }),
9169
9579
  [aboutTableId, name, rows]
9170
9580
  );
9171
- useEffect18(() => {
9581
+ useEffect19(() => {
9172
9582
  if (spec.steps.length === 0 || spec.name.length === 0) {
9173
9583
  setRefusal(null);
9174
9584
  return;
@@ -9295,9 +9705,9 @@ function ChecklistTemplateEditor({
9295
9705
  ] }, index)) }),
9296
9706
  /* @__PURE__ */ jsxs28("div", { className: "flex items-center gap-1.5", children: [
9297
9707
  /* @__PURE__ */ jsx32(Button26, { size: "sm", variant: "outline", onClick: () => setRows((held) => [...held, { ...EMPTY_ROW }]), children: "Add a step" }),
9298
- /* @__PURE__ */ jsx32(Button26, { size: "sm", disabled: busy || refusal !== null || spec.steps.length === 0, onClick: () => void save(), children: busy ? "\u2026" : "Save" })
9708
+ /* @__PURE__ */ jsx32(Button26, { size: "sm", disabled: busy || refusal2 !== null || spec.steps.length === 0, onClick: () => void save(), children: busy ? "\u2026" : "Save" })
9299
9709
  ] }),
9300
- refusal ? /* @__PURE__ */ jsx32("p", { className: "text-xs text-muted-foreground", "data-testid": "checklist-editor-refusal", children: refusal }) : null
9710
+ refusal2 ? /* @__PURE__ */ jsx32("p", { className: "text-xs text-muted-foreground", "data-testid": "checklist-editor-refusal", children: refusal2 }) : null
9301
9711
  ] });
9302
9712
  }
9303
9713
  function toStepSpec(row, index) {
@@ -9354,7 +9764,7 @@ function ActionInbox({ tableId, includeSettled = false, onOpenRecord, className
9354
9764
  setError(null);
9355
9765
  setItems(result.data);
9356
9766
  }, [client, includeSettled]);
9357
- useEffect19(() => {
9767
+ useEffect20(() => {
9358
9768
  void load();
9359
9769
  }, [load]);
9360
9770
  const shown = useMemo20(() => {
@@ -9362,10 +9772,10 @@ function ActionInbox({ tableId, includeSettled = false, onOpenRecord, className
9362
9772
  if (!tableId) return all;
9363
9773
  return all.filter((i) => i.kind !== "assignment" || i.subject_kind !== "record" || true);
9364
9774
  }, [items, tableId]);
9365
- useEffect19(() => {
9775
+ useEffect20(() => {
9366
9776
  if (cursor >= shown.length) setCursor(Math.max(0, shown.length - 1));
9367
9777
  }, [shown.length, cursor]);
9368
- useEffect19(() => {
9778
+ useEffect20(() => {
9369
9779
  const el = listRef.current?.querySelector(`[data-row="${cursor}"]`);
9370
9780
  el?.scrollIntoView({ block: "nearest" });
9371
9781
  }, [cursor, shown.length]);
@@ -9491,7 +9901,7 @@ function ActionInbox({ tableId, includeSettled = false, onOpenRecord, className
9491
9901
  }
9492
9902
 
9493
9903
  // src/HistoryPanel.tsx
9494
- import { useCallback as useCallback17, useEffect as useEffect20, useRef as useRef9, useState as useState32 } from "react";
9904
+ import { useCallback as useCallback17, useEffect as useEffect21, useRef as useRef9, useState as useState32 } from "react";
9495
9905
  import {
9496
9906
  useFields as useFields14,
9497
9907
  useRecordsClient as useRecordsClient20,
@@ -9519,7 +9929,7 @@ function HistoryPanel({ tableId, recordId, onAskAboutField, className }) {
9519
9929
  setError(null);
9520
9930
  setEntries(answered.data);
9521
9931
  }, [client, recordId]);
9522
- useEffect20(() => {
9932
+ useEffect21(() => {
9523
9933
  void load();
9524
9934
  }, [load]);
9525
9935
  if (error) return /* @__PURE__ */ jsx34(RefusalNotice, { error, className });
@@ -9800,7 +10210,7 @@ function say(value, field) {
9800
10210
  }
9801
10211
 
9802
10212
  // src/CommentThread.tsx
9803
- import { useCallback as useCallback18, useEffect as useEffect21, useMemo as useMemo21, useRef as useRef10, useState as useState33 } from "react";
10213
+ import { useCallback as useCallback18, useEffect as useEffect22, useMemo as useMemo21, useRef as useRef10, useState as useState33 } from "react";
9804
10214
  import {
9805
10215
  useFields as useFields15,
9806
10216
  useRecordsClient as useRecordsClient21,
@@ -9837,10 +10247,10 @@ function CommentThread({ tableId, recordId, fieldKey, className }) {
9837
10247
  mayResolve: answered.data.may_resolve
9838
10248
  });
9839
10249
  }, [client, recordId, showResolved]);
9840
- useEffect21(() => {
10250
+ useEffect22(() => {
9841
10251
  void load();
9842
10252
  }, [load]);
9843
- useEffect21(() => {
10253
+ useEffect22(() => {
9844
10254
  let alive = true;
9845
10255
  if (!host.members) return;
9846
10256
  void host.members().then((roster) => {
@@ -10043,7 +10453,7 @@ function Line({
10043
10453
  }
10044
10454
 
10045
10455
  // src/FieldHistoryPanel.tsx
10046
- import { useCallback as useCallback19, useEffect as useEffect22, useState as useState34 } from "react";
10456
+ import { useCallback as useCallback19, useEffect as useEffect23, useState as useState34 } from "react";
10047
10457
  import {
10048
10458
  useFields as useFields16,
10049
10459
  useRecordsClient as useRecordsClient22,
@@ -10078,7 +10488,7 @@ function FieldHistoryPanel({
10078
10488
  setError(null);
10079
10489
  setRows(answered.data);
10080
10490
  }, [client, tableId, fieldKey, recordId]);
10081
- useEffect22(() => {
10491
+ useEffect23(() => {
10082
10492
  void load();
10083
10493
  }, [load]);
10084
10494
  const label = (fields.data ?? []).find((f) => f.key === fieldKey)?.label || humanize(fieldKey);
@@ -10271,7 +10681,7 @@ function submissionStamp(args) {
10271
10681
  }
10272
10682
 
10273
10683
  // src/PortalBuilder.tsx
10274
- import { useCallback as useCallback20, useEffect as useEffect23, useMemo as useMemo22, useState as useState35 } from "react";
10684
+ import { useCallback as useCallback20, useEffect as useEffect24, useMemo as useMemo22, useState as useState35 } from "react";
10275
10685
  import { useRecordsClient as useRecordsClient23, useTables as useTables3 } from "@ai-matrx/records/react";
10276
10686
  import { BasicInput as BasicInput10, Button as Button31, Checkbox as Checkbox5, Label as Label5, Separator as Separator9, Skeleton as Skeleton16, cn as cn31 } from "@ai-matrx/design-system";
10277
10687
  import { Fragment as Fragment15, jsx as jsx37, jsxs as jsxs33 } from "react/jsx-runtime";
@@ -10304,7 +10714,7 @@ function PortalBuilder({ tableId, portalId, onSaved, onClose, className }) {
10304
10714
  },
10305
10715
  [client, fieldsByTable]
10306
10716
  );
10307
- useEffect23(() => {
10717
+ useEffect24(() => {
10308
10718
  if (!portalId) return;
10309
10719
  void (async () => {
10310
10720
  const answered = await client.portalCard({ portal_id: portalId });
@@ -10318,7 +10728,7 @@ function PortalBuilder({ tableId, portalId, onSaved, onClose, className }) {
10318
10728
  setClientTableId(answered.data.client_table_id);
10319
10729
  })();
10320
10730
  }, [client, portalId]);
10321
- useEffect23(() => {
10731
+ useEffect24(() => {
10322
10732
  if (!tableId) return;
10323
10733
  setExposures(
10324
10734
  (prev) => prev[tableId] ? prev : {
@@ -10566,7 +10976,7 @@ function PortalBuilder({ tableId, portalId, onSaved, onClose, className }) {
10566
10976
  }
10567
10977
 
10568
10978
  // src/PortalsPanel.tsx
10569
- import { useCallback as useCallback21, useEffect as useEffect24, useMemo as useMemo23, useState as useState36 } from "react";
10979
+ import { useCallback as useCallback21, useEffect as useEffect25, useMemo as useMemo23, useState as useState36 } from "react";
10570
10980
  import { useRecordsClient as useRecordsClient24 } from "@ai-matrx/records/react";
10571
10981
  import {
10572
10982
  portalArchiveConsequence,
@@ -10607,6 +11017,7 @@ function BuildOrAsk({
10607
11017
 
10608
11018
  // src/PortalsPanel.tsx
10609
11019
  import { Fragment as Fragment16, jsx as jsx39, jsxs as jsxs35 } from "react/jsx-runtime";
11020
+ 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.";
10610
11021
  function revokeConsequence(person, portalTitle) {
10611
11022
  const who = person.client ? `${person.email} (${person.client})` : person.email;
10612
11023
  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.`;
@@ -10636,13 +11047,16 @@ function stateWords(person) {
10636
11047
  };
10637
11048
  }
10638
11049
  var PORTAL_SUGGESTION = "Let each of my customers sign in and see their own jobs and invoices, and nothing else.";
10639
- function PortalsPanel({ tableId, className }) {
11050
+ function PortalsPanel({ tableId, activePortalId, className }) {
10640
11051
  const client = useRecordsClient24();
10641
11052
  const host = useRecordsUi();
10642
11053
  const [portals, setPortals] = useState36(null);
10643
11054
  const [exposures, setExposures] = useState36([]);
10644
11055
  const [listError, setListError] = useState36(null);
10645
- const [openId, setOpenId] = useState36(null);
11056
+ const [openId, setOpenId] = useState36(activePortalId ?? null);
11057
+ useEffect25(() => {
11058
+ if (activePortalId) setOpenId(activePortalId);
11059
+ }, [activePortalId]);
10646
11060
  const [building, setBuilding] = useState36(false);
10647
11061
  const [adding, setAdding] = useState36(null);
10648
11062
  const [archiveToken, setArchiveToken] = useState36(0);
@@ -10658,7 +11072,7 @@ function PortalsPanel({ tableId, className }) {
10658
11072
  setPortals(answered.data);
10659
11073
  setExposures(mapped.ok ? mapped.data : []);
10660
11074
  }, [client]);
10661
- useEffect24(() => {
11075
+ useEffect25(() => {
10662
11076
  void load();
10663
11077
  }, [load]);
10664
11078
  if (portals === null) return /* @__PURE__ */ jsx39(Skeleton17, { className: cn33("h-32 w-full", className) });
@@ -10739,59 +11153,73 @@ function PortalsPanel({ tableId, className }) {
10739
11153
  portal.portal_id
10740
11154
  )) })
10741
11155
  ] }) : null,
11156
+ activePortalId && !shown.some((p) => p.portal_id === activePortalId) ? /* @__PURE__ */ jsx39("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,
10742
11157
  /* @__PURE__ */ jsx39("ul", { className: "flex flex-col gap-2", children: shown.map((portal) => {
10743
11158
  const url = `${origin}${portalPath(portal.slug)}`;
10744
11159
  const open = openId === portal.portal_id;
10745
- return /* @__PURE__ */ jsxs35("li", { className: "rounded-md border border-border bg-card p-2.5", children: [
10746
- /* @__PURE__ */ jsxs35("div", { className: "flex items-center gap-2", children: [
10747
- /* @__PURE__ */ jsx39("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: portal.title }),
10748
- portal.is_active ? null : /* @__PURE__ */ jsx39(Badge13, { variant: "outline", children: "closed" })
10749
- ] }),
10750
- /* @__PURE__ */ jsxs35("p", { className: "mt-0.5 text-xs text-muted-foreground", children: [
10751
- "Clients come from ",
10752
- portal.client_table,
10753
- portal.tables === 1 ? ", and see 1 Table" : `, and see ${portal.tables} Tables`,
10754
- "."
10755
- ] }),
10756
- /* @__PURE__ */ jsxs35("p", { className: "mt-1.5 text-xs", children: [
10757
- /* @__PURE__ */ jsx39("span", { className: "font-medium", children: portal.signed_in }),
10758
- " signed in",
10759
- " \xB7 ",
10760
- /* @__PURE__ */ jsx39("span", { className: "font-medium", children: portal.invited - portal.signed_in }),
10761
- " invited and waiting"
10762
- ] }),
10763
- /* @__PURE__ */ jsxs35("div", { className: "mt-2 flex flex-wrap items-center gap-1.5", children: [
10764
- /* @__PURE__ */ jsx39(CopyLink, { url }),
10765
- /* @__PURE__ */ jsx39(
10766
- Button33,
10767
- {
10768
- size: "sm",
10769
- variant: "ghost",
10770
- onClick: () => setOpenId(open ? null : portal.portal_id),
10771
- children: open ? "Close" : "Open it"
10772
- }
11160
+ const linked = portal.portal_id === activePortalId;
11161
+ return /* @__PURE__ */ jsxs35(
11162
+ "li",
11163
+ {
11164
+ "data-linked": linked ? "true" : void 0,
11165
+ ref: linked ? (el) => el?.scrollIntoView({ block: "nearest" }) : void 0,
11166
+ className: cn33(
11167
+ "rounded-md border border-border bg-card p-2.5",
11168
+ linked && "border-primary ring-1 ring-primary"
10773
11169
  ),
10774
- /* @__PURE__ */ jsx39(
10775
- ArchivePortalControl,
10776
- {
10777
- portal,
10778
- onArchived: () => {
10779
- setArchiveToken((t) => t + 1);
10780
- void load();
11170
+ children: [
11171
+ /* @__PURE__ */ jsxs35("div", { className: "flex items-center gap-2", children: [
11172
+ /* @__PURE__ */ jsx39("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: portal.title }),
11173
+ portal.is_active ? null : /* @__PURE__ */ jsx39(Badge13, { variant: "outline", children: "closed" })
11174
+ ] }),
11175
+ /* @__PURE__ */ jsxs35("p", { className: "mt-0.5 text-xs text-muted-foreground", children: [
11176
+ "Clients come from ",
11177
+ portal.client_table,
11178
+ portal.tables === 1 ? ", and see 1 Table" : `, and see ${portal.tables} Tables`,
11179
+ "."
11180
+ ] }),
11181
+ /* @__PURE__ */ jsxs35("p", { className: "mt-1.5 text-xs", children: [
11182
+ /* @__PURE__ */ jsx39("span", { className: "font-medium", children: portal.signed_in }),
11183
+ " signed in",
11184
+ " \xB7 ",
11185
+ /* @__PURE__ */ jsx39("span", { className: "font-medium", children: portal.invited - portal.signed_in }),
11186
+ " invited and waiting"
11187
+ ] }),
11188
+ /* @__PURE__ */ jsxs35("div", { className: "mt-2 flex flex-wrap items-center gap-1.5", children: [
11189
+ /* @__PURE__ */ jsx39(CopyLink, { url }),
11190
+ /* @__PURE__ */ jsx39(
11191
+ Button33,
11192
+ {
11193
+ size: "sm",
11194
+ variant: "ghost",
11195
+ onClick: () => setOpenId(open ? null : portal.portal_id),
11196
+ children: open ? "Close" : "Open it"
11197
+ }
11198
+ ),
11199
+ /* @__PURE__ */ jsx39(
11200
+ ArchivePortalControl,
11201
+ {
11202
+ portal,
11203
+ onArchived: () => {
11204
+ setArchiveToken((t) => t + 1);
11205
+ void load();
11206
+ }
11207
+ }
11208
+ )
11209
+ ] }),
11210
+ open ? /* @__PURE__ */ jsx39(
11211
+ PortalDetail,
11212
+ {
11213
+ portalId: portal.portal_id,
11214
+ ...tableId ? { tableId } : {},
11215
+ origin,
11216
+ onChanged: () => void load()
10781
11217
  }
10782
- }
10783
- )
10784
- ] }),
10785
- open ? /* @__PURE__ */ jsx39(
10786
- PortalDetail,
10787
- {
10788
- portalId: portal.portal_id,
10789
- ...tableId ? { tableId } : {},
10790
- origin,
10791
- onChanged: () => void load()
10792
- }
10793
- ) : null
10794
- ] }, portal.portal_id);
11218
+ ) : null
11219
+ ]
11220
+ },
11221
+ portal.portal_id
11222
+ );
10795
11223
  }) }),
10796
11224
  /* @__PURE__ */ jsx39(ArchivedPortals, { refreshToken: archiveToken, onRestored: () => void load() })
10797
11225
  ] });
@@ -10920,7 +11348,7 @@ function PortalDetail({
10920
11348
  setError(null);
10921
11349
  setCard(answered.data);
10922
11350
  }, [client, portalId]);
10923
- useEffect24(() => {
11351
+ useEffect25(() => {
10924
11352
  void load();
10925
11353
  }, [load]);
10926
11354
  const revoke = useCallback21(
@@ -11113,7 +11541,7 @@ function Preview({
11113
11541
  const [which, setWhich] = useState36(first?.table_id ?? null);
11114
11542
  const [rows, setRows] = useState36(null);
11115
11543
  const [error, setError] = useState36(null);
11116
- useEffect24(() => {
11544
+ useEffect25(() => {
11117
11545
  if (!which) return;
11118
11546
  let cancelled = false;
11119
11547
  setRows(null);
@@ -11159,7 +11587,7 @@ function Invite({ card, onInvited }) {
11159
11587
  const [busy, setBusy] = useState36(false);
11160
11588
  const [said, setSaid] = useState36(null);
11161
11589
  const [error, setError] = useState36(null);
11162
- useEffect24(() => {
11590
+ useEffect25(() => {
11163
11591
  let cancelled = false;
11164
11592
  void client.list({ table_id: card.client_table_id, limit: 200 }).then((answered) => {
11165
11593
  if (cancelled) return;
@@ -11261,7 +11689,7 @@ function Invite({ card, onInvited }) {
11261
11689
  }
11262
11690
 
11263
11691
  // src/DigestScheduler.tsx
11264
- import { useCallback as useCallback22, useEffect as useEffect25, useMemo as useMemo24, useState as useState37 } from "react";
11692
+ import { useCallback as useCallback22, useEffect as useEffect26, useMemo as useMemo24, useState as useState37 } from "react";
11265
11693
  import { useRecordsClient as useRecordsClient25 } from "@ai-matrx/records/react";
11266
11694
  import { BasicInput as BasicInput12, Button as Button34, Checkbox as Checkbox6, Label as Label6, Separator as Separator11, Skeleton as Skeleton18, cn as cn34 } from "@ai-matrx/design-system";
11267
11695
  import { Fragment as Fragment17, jsx as jsx40, jsxs as jsxs36 } from "react/jsx-runtime";
@@ -11325,7 +11753,7 @@ function DigestScheduler({
11325
11753
  setMembers([]);
11326
11754
  }
11327
11755
  }, [client, tableId, host]);
11328
- useEffect25(() => {
11756
+ useEffect26(() => {
11329
11757
  void load();
11330
11758
  }, [load]);
11331
11759
  const schedule = useMemo24(() => {
@@ -11549,7 +11977,17 @@ function DigestScheduler({
11549
11977
  preview ? /* @__PURE__ */ jsxs36("div", { className: "rounded-md border bg-muted/40 p-2.5 text-xs", children: [
11550
11978
  /* @__PURE__ */ jsx40("p", { className: "font-medium", children: preview.subject }),
11551
11979
  /* @__PURE__ */ jsx40("p", { className: "mt-1 text-muted-foreground", children: preview.body }),
11552
- preview.incomplete ? /* @__PURE__ */ jsx40("p", { className: "mt-1 text-destructive", children: preview.incomplete }) : null,
11980
+ preview.incomplete ? (
11981
+ // The summary could not be finished: a refusal line naming the missing
11982
+ // piece and what to do, never the bare sentence in red (lane REFUSAL-SWEEP).
11983
+ /* @__PURE__ */ jsx40(
11984
+ RefusalLine,
11985
+ {
11986
+ className: "mt-1",
11987
+ error: refusal("not_supported", preview.incomplete, "Fill in the missing piece above, then preview again.")
11988
+ }
11989
+ )
11990
+ ) : null,
11553
11991
  preview.entered.length > 0 ? /* @__PURE__ */ jsxs36("p", { className: "mt-1", children: [
11554
11992
  /* @__PURE__ */ jsx40("span", { className: "font-medium", children: "Arrived:" }),
11555
11993
  " ",
@@ -11567,18 +12005,34 @@ function DigestScheduler({
11567
12005
  }
11568
12006
 
11569
12007
  // src/SubscriptionsPanel.tsx
11570
- import { useCallback as useCallback23, useEffect as useEffect26, useState as useState38 } from "react";
12008
+ import { useCallback as useCallback23, useEffect as useEffect27, useState as useState38 } from "react";
11571
12009
  import { useRecordsClient as useRecordsClient26 } from "@ai-matrx/records/react";
11572
12010
  import { Badge as Badge14, Button as Button35, Skeleton as Skeleton19, Switch as Switch2, cn as cn35 } from "@ai-matrx/design-system";
11573
12011
  import { jsx as jsx41, jsxs as jsxs37 } from "react/jsx-runtime";
12012
+ 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.";
11574
12013
  function whenItFires(subscription) {
11575
12014
  if (subscription.cadence === "instant") return "as it happens";
11576
12015
  const every = subscription.cadence === "hourly" ? "an hourly summary" : subscription.cadence === "weekly" ? "a weekly summary" : "a daily summary";
11577
12016
  return subscription.schedule ? `${every}, ${subscription.schedule}` : every;
11578
12017
  }
12018
+ var PERIOD_MS = {
12019
+ hourly: 60 * 60 * 1e3,
12020
+ daily: 24 * 60 * 60 * 1e3,
12021
+ weekly: 7 * 24 * 60 * 60 * 1e3
12022
+ };
12023
+ function nextSummaryAt(subscription, now) {
12024
+ if (!subscription.next_digest_at) return null;
12025
+ const due = new Date(subscription.next_digest_at);
12026
+ if (Number.isNaN(due.getTime())) return null;
12027
+ if (due.getTime() > now.getTime()) return due;
12028
+ const period = PERIOD_MS[subscription.cadence];
12029
+ if (!period) return due;
12030
+ const behind = now.getTime() - due.getTime();
12031
+ return new Date(due.getTime() + (Math.floor(behind / period) + 1) * period);
12032
+ }
11579
12033
  var CHANNEL_WORDS2 = SUBSCRIPTION_CHANNEL_LABEL;
11580
12034
  var DIGEST_SUGGESTION = "Email me a summary of this every Monday at 8 in the morning.";
11581
- function SubscriptionsPanel({ tableId, className }) {
12035
+ function SubscriptionsPanel({ tableId, activeRuleId, className }) {
11582
12036
  const client = useRecordsClient26();
11583
12037
  const [rows, setRows] = useState38(null);
11584
12038
  const [error, setError] = useState38(null);
@@ -11596,7 +12050,7 @@ function SubscriptionsPanel({ tableId, className }) {
11596
12050
  setError(null);
11597
12051
  setRows(answered.data);
11598
12052
  }, [client, tableId]);
11599
- useEffect26(() => {
12053
+ useEffect27(() => {
11600
12054
  void load();
11601
12055
  }, [load]);
11602
12056
  const flip = useCallback23(
@@ -11669,96 +12123,119 @@ function SubscriptionsPanel({ tableId, className }) {
11669
12123
  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."
11670
12124
  }
11671
12125
  ) : null,
11672
- /* @__PURE__ */ jsx41("ul", { className: "flex flex-col gap-2", children: rows.map((subscription) => /* @__PURE__ */ jsxs37("li", { className: "rounded-md border p-2.5", children: [
11673
- /* @__PURE__ */ jsxs37("div", { className: "flex items-start gap-2", children: [
11674
- /* @__PURE__ */ jsxs37("div", { className: "min-w-0 flex-1", children: [
11675
- /* @__PURE__ */ jsx41("p", { className: "truncate text-sm font-medium", children: subscription.name }),
11676
- /* @__PURE__ */ jsxs37("p", { className: "mt-0.5 text-xs text-muted-foreground", children: [
11677
- CHANNEL_WORDS2[subscription.channel] ?? `on the ${subscription.channel} channel`,
11678
- " \xB7 ",
11679
- whenItFires(subscription)
11680
- ] })
11681
- ] }),
11682
- subscription.i_may_mute ? /* @__PURE__ */ jsx41(
11683
- Switch2,
11684
- {
11685
- checked: !subscription.muted,
11686
- disabled: busy === subscription.rule_id,
11687
- "aria-label": `Tell me about ${subscription.name}`,
11688
- onCheckedChange: (on) => void flip(subscription, on)
11689
- }
11690
- ) : /* @__PURE__ */ jsx41(Badge14, { variant: "secondary", children: "someone else's" })
11691
- ] }),
11692
- /* @__PURE__ */ jsx41("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." }),
11693
- /* @__PURE__ */ jsxs37("div", { className: "mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
11694
- subscription.next_digest_at ? /* @__PURE__ */ jsxs37("span", { children: [
11695
- "Next summary ",
11696
- new Date(subscription.next_digest_at).toLocaleString()
11697
- ] }) : subscription.cadence === "instant" ? null : subscription.muted ? /* @__PURE__ */ jsx41("span", { children: "No next summary while it is off." }) : null,
11698
- subscription.last_sent_at ? /* @__PURE__ */ jsxs37("span", { children: [
11699
- "Last told you ",
11700
- new Date(subscription.last_sent_at).toLocaleString()
11701
- ] }) : /* @__PURE__ */ jsx41("span", { children: "It has not told you anything yet." }),
11702
- subscription.quiet_hours ? /* @__PURE__ */ jsxs37("span", { children: [
11703
- "Not between ",
11704
- subscription.quiet_hours.start,
11705
- " and ",
11706
- subscription.quiet_hours.end,
11707
- " \u2014 a send inside those hours waits until they end."
11708
- ] }) : null,
11709
- subscription.cadence === "instant" ? null : /* @__PURE__ */ jsx41(
11710
- Button35,
11711
- {
11712
- size: "sm",
11713
- variant: "ghost",
11714
- disabled: busy === subscription.rule_id,
11715
- onClick: () => void showOne(subscription),
11716
- children: "Send me a preview now"
11717
- }
11718
- )
11719
- ] }),
11720
- preview && preview.rule_id === subscription.rule_id ? /* @__PURE__ */ jsxs37("div", { className: "mt-2 rounded-md border bg-muted/40 p-2.5 text-xs", children: [
11721
- /* @__PURE__ */ jsx41("p", { className: "font-medium", children: preview.subject }),
11722
- /* @__PURE__ */ jsx41("p", { className: "mt-1 text-muted-foreground", children: preview.body }),
11723
- preview.incomplete ? (
11724
- // Absent or honest: a subscription that cannot produce a summary
11725
- // says which piece is missing instead of showing an empty one.
11726
- /* @__PURE__ */ jsx41("p", { className: "mt-1 text-destructive", children: preview.incomplete })
11727
- ) : null,
11728
- preview.entered.length > 0 ? /* @__PURE__ */ jsxs37("p", { className: "mt-1", children: [
11729
- /* @__PURE__ */ jsx41("span", { className: "font-medium", children: "Arrived:" }),
11730
- " ",
11731
- preview.entered.map((e) => e.name).join(", ")
11732
- ] }) : null,
11733
- preview.left.length > 0 ? /* @__PURE__ */ jsxs37("p", { className: "mt-1", children: [
11734
- /* @__PURE__ */ jsx41("span", { className: "font-medium", children: "Left:" }),
11735
- " ",
11736
- preview.left.map((e) => e.name).join(", ")
11737
- ] }) : null,
11738
- preview.changed.length > 0 ? /* @__PURE__ */ jsxs37("p", { className: "mt-1", children: [
11739
- /* @__PURE__ */ jsx41("span", { className: "font-medium", children: "Changed:" }),
11740
- " ",
11741
- preview.changed.map((e) => e.name).join(", ")
11742
- ] }) : null,
11743
- /* @__PURE__ */ jsx41("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." }),
11744
- /* @__PURE__ */ jsx41(Button35, { size: "sm", variant: "ghost", className: "mt-1", onClick: () => setPreview(null), children: "Close" })
11745
- ] }) : null
11746
- ] }, subscription.rule_id)) })
12126
+ activeRuleId && !rows.some((r) => r.rule_id === activeRuleId) ? /* @__PURE__ */ jsx41("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,
12127
+ /* @__PURE__ */ jsx41("ul", { className: "flex flex-col gap-2", children: rows.map((subscription) => /* @__PURE__ */ jsxs37(
12128
+ "li",
12129
+ {
12130
+ "data-linked": subscription.rule_id === activeRuleId ? "true" : void 0,
12131
+ ref: subscription.rule_id === activeRuleId ? (el) => el?.scrollIntoView({ block: "nearest" }) : void 0,
12132
+ className: cn35(
12133
+ "rounded-md border p-2.5",
12134
+ subscription.rule_id === activeRuleId && "border-primary ring-1 ring-primary"
12135
+ ),
12136
+ children: [
12137
+ /* @__PURE__ */ jsxs37("div", { className: "flex items-start gap-2", children: [
12138
+ /* @__PURE__ */ jsxs37("div", { className: "min-w-0 flex-1", children: [
12139
+ /* @__PURE__ */ jsx41("p", { className: "truncate text-sm font-medium", children: subscription.name }),
12140
+ /* @__PURE__ */ jsxs37("p", { className: "mt-0.5 text-xs text-muted-foreground", children: [
12141
+ CHANNEL_WORDS2[subscription.channel] ?? `on the ${subscription.channel} channel`,
12142
+ " \xB7 ",
12143
+ whenItFires(subscription)
12144
+ ] })
12145
+ ] }),
12146
+ subscription.i_may_mute ? /* @__PURE__ */ jsx41(
12147
+ Switch2,
12148
+ {
12149
+ checked: !subscription.muted,
12150
+ disabled: busy === subscription.rule_id,
12151
+ "aria-label": `Tell me about ${subscription.name}`,
12152
+ onCheckedChange: (on) => void flip(subscription, on)
12153
+ }
12154
+ ) : /* @__PURE__ */ jsx41(Badge14, { variant: "secondary", children: "someone else's" })
12155
+ ] }),
12156
+ /* @__PURE__ */ jsx41("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." }),
12157
+ /* @__PURE__ */ jsxs37("div", { className: "mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
12158
+ subscription.next_digest_at ? /* @__PURE__ */ jsxs37("span", { children: [
12159
+ "Next summary ",
12160
+ nextSummaryAt(subscription, /* @__PURE__ */ new Date())?.toLocaleString()
12161
+ ] }) : subscription.cadence === "instant" ? null : subscription.muted ? /* @__PURE__ */ jsx41("span", { children: "No next summary while it is off." }) : null,
12162
+ subscription.last_sent_at ? /* @__PURE__ */ jsxs37("span", { children: [
12163
+ "Last told you ",
12164
+ new Date(subscription.last_sent_at).toLocaleString()
12165
+ ] }) : /* @__PURE__ */ jsx41("span", { children: "It has not told you anything yet." }),
12166
+ subscription.quiet_hours ? /* @__PURE__ */ jsxs37("span", { children: [
12167
+ "Not between ",
12168
+ subscription.quiet_hours.start,
12169
+ " and ",
12170
+ subscription.quiet_hours.end,
12171
+ " \u2014 a send inside those hours waits until they end."
12172
+ ] }) : null,
12173
+ subscription.cadence === "instant" ? null : /* @__PURE__ */ jsx41(
12174
+ Button35,
12175
+ {
12176
+ size: "sm",
12177
+ variant: "ghost",
12178
+ disabled: busy === subscription.rule_id,
12179
+ onClick: () => void showOne(subscription),
12180
+ children: "Send me a preview now"
12181
+ }
12182
+ )
12183
+ ] }),
12184
+ preview && preview.rule_id === subscription.rule_id ? /* @__PURE__ */ jsxs37("div", { className: "mt-2 rounded-md border bg-muted/40 p-2.5 text-xs", children: [
12185
+ /* @__PURE__ */ jsx41("p", { className: "font-medium", children: preview.subject }),
12186
+ /* @__PURE__ */ jsx41("p", { className: "mt-1 text-muted-foreground", children: preview.body }),
12187
+ preview.incomplete ? (
12188
+ // Absent or honest: a subscription that cannot produce a summary
12189
+ // says which piece is missing instead of showing an empty one.
12190
+ /* @__PURE__ */ jsx41(
12191
+ RefusalLine,
12192
+ {
12193
+ className: "mt-1",
12194
+ error: refusal(
12195
+ "not_supported",
12196
+ preview.incomplete,
12197
+ "Fill in the missing piece, then preview again."
12198
+ )
12199
+ }
12200
+ )
12201
+ ) : null,
12202
+ preview.entered.length > 0 ? /* @__PURE__ */ jsxs37("p", { className: "mt-1", children: [
12203
+ /* @__PURE__ */ jsx41("span", { className: "font-medium", children: "Arrived:" }),
12204
+ " ",
12205
+ preview.entered.map((e) => e.name).join(", ")
12206
+ ] }) : null,
12207
+ preview.left.length > 0 ? /* @__PURE__ */ jsxs37("p", { className: "mt-1", children: [
12208
+ /* @__PURE__ */ jsx41("span", { className: "font-medium", children: "Left:" }),
12209
+ " ",
12210
+ preview.left.map((e) => e.name).join(", ")
12211
+ ] }) : null,
12212
+ preview.changed.length > 0 ? /* @__PURE__ */ jsxs37("p", { className: "mt-1", children: [
12213
+ /* @__PURE__ */ jsx41("span", { className: "font-medium", children: "Changed:" }),
12214
+ " ",
12215
+ preview.changed.map((e) => e.name).join(", ")
12216
+ ] }) : null,
12217
+ /* @__PURE__ */ jsx41("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." }),
12218
+ /* @__PURE__ */ jsx41(Button35, { size: "sm", variant: "ghost", className: "mt-1", onClick: () => setPreview(null), children: "Close" })
12219
+ ] }) : null
12220
+ ]
12221
+ },
12222
+ subscription.rule_id
12223
+ )) })
11747
12224
  ] });
11748
12225
  }
11749
12226
 
11750
12227
  // src/FormBuilder.tsx
11751
- import { useCallback as useCallback25, useEffect as useEffect29, useMemo as useMemo26, useState as useState41 } from "react";
12228
+ import { useCallback as useCallback25, useEffect as useEffect30, useMemo as useMemo26, useState as useState41 } from "react";
11752
12229
  import { useFields as useFields18, useRecordsClient as useRecordsClient28, useTable as useTable14 } from "@ai-matrx/records/react";
11753
12230
 
11754
12231
  // src/publish-gate.ts
11755
- import { useEffect as useEffect27, useState as useState39 } from "react";
12232
+ import { useEffect as useEffect28, useState as useState39 } from "react";
11756
12233
  import { useRecordsClient as useRecordsClient27 } from "@ai-matrx/records/react";
11757
12234
  var PUBLISH_NEEDS_THE_STORE_ON = "This organization has its record store switched off, so publishing would hand you a link that opens nothing. An owner or an administrator turns it on under Database settings, on the unified data screen \u2014 everything you have already built here is kept and starts working the moment they do.";
11758
12235
  function usePublishGate() {
11759
12236
  const client = useRecordsClient27();
11760
12237
  const [storeOpen, setStoreOpen] = useState39(null);
11761
- useEffect27(() => {
12238
+ useEffect28(() => {
11762
12239
  let alive = true;
11763
12240
  void (async () => {
11764
12241
  const answered = await client.storeIsOpen();
@@ -11789,7 +12266,7 @@ import {
11789
12266
  } from "@ai-matrx/design-system";
11790
12267
 
11791
12268
  // src/FormRunner.tsx
11792
- import { useCallback as useCallback24, useEffect as useEffect28, useMemo as useMemo25, useRef as useRef11, useState as useState40 } from "react";
12269
+ import { useCallback as useCallback24, useEffect as useEffect29, useMemo as useMemo25, useRef as useRef11, useState as useState40 } from "react";
11793
12270
  import { useFields as useFields17, useOptionalRecordsClient as useOptionalRecordsClient4 } from "@ai-matrx/records/react";
11794
12271
  import { Button as Button36, Progress, Skeleton as Skeleton20, cn as cn36 } from "@ai-matrx/design-system";
11795
12272
  import { Fragment as Fragment18, jsx as jsx42, jsxs as jsxs38 } from "react/jsx-runtime";
@@ -11806,9 +12283,16 @@ function ConnectedFormRunner(props) {
11806
12283
  const submit = useCallback24(
11807
12284
  async (values) => {
11808
12285
  if (!client) {
12286
+ const said = "This form cannot send answers from here, so nothing was sent.";
11809
12287
  return {
11810
12288
  ok: false,
11811
- 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."
12289
+ message: said,
12290
+ error: refusal(
12291
+ "not_supported",
12292
+ said,
12293
+ "Open the form from its own link and answer it there.",
12294
+ "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."
12295
+ )
11812
12296
  };
11813
12297
  }
11814
12298
  const document2 = {
@@ -11821,7 +12305,7 @@ function ConnectedFormRunner(props) {
11821
12305
  })
11822
12306
  };
11823
12307
  const written = await client.recordWrite({ table_id: form.subject, data: document2 });
11824
- if (!written.ok) return { ok: false, message: written.error.message };
12308
+ if (!written.ok) return { ok: false, message: written.error.message, error: written.error };
11825
12309
  return { ok: true, recordId: written.data };
11826
12310
  },
11827
12311
  [client, form]
@@ -11863,7 +12347,7 @@ function FormStage({
11863
12347
  const [answers, setAnswers] = useState40({});
11864
12348
  const [at, setAt] = useState40(0);
11865
12349
  const [error, setError] = useState40(null);
11866
- const [refusal, setRefusal] = useState40(null);
12350
+ const [refusal2, setRefusal] = useState40(null);
11867
12351
  const [writing, setWriting] = useState40(false);
11868
12352
  const [done, setDone] = useState40(null);
11869
12353
  const [hidden, setHidden] = useState40({});
@@ -11883,7 +12367,7 @@ function FormStage({
11883
12367
  };
11884
12368
  });
11885
12369
  }, [fields, form.questions]);
11886
- useEffect28(() => {
12370
+ useEffect29(() => {
11887
12371
  let cancelled = false;
11888
12372
  const conditional = questions.filter((q) => q.showIf);
11889
12373
  if (conditional.length === 0 || !evaluate) return;
@@ -11932,12 +12416,21 @@ function FormStage({
11932
12416
  const outcome = await onSubmit(values);
11933
12417
  setWriting(false);
11934
12418
  if (!outcome.ok) {
11935
- setRefusal(outcome.message);
12419
+ const inWords = (text) => questions.reduce(
12420
+ (said, q) => said.replace(new RegExp(`\\b${q.key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "g"), q.ask),
12421
+ text
12422
+ );
12423
+ const told = outcome.error ?? refusal("invalid_argument", outcome.message, "Check your answers, then send again.");
12424
+ setRefusal({
12425
+ ...told,
12426
+ message: inWords(told.message),
12427
+ ...told.hint ? { hint: inWords(told.hint) } : {}
12428
+ });
11936
12429
  return;
11937
12430
  }
11938
12431
  setDone(outcome.message ?? "sent");
11939
12432
  onSubmitted?.(outcome.recordId ?? null);
11940
- }, [answers, form, honeypotKey, live, onSubmit, onSubmitted, preview]);
12433
+ }, [answers, form, honeypotKey, live, onSubmit, onSubmitted, preview, questions]);
11941
12434
  function advance() {
11942
12435
  if (!oneAtATime) return;
11943
12436
  if (index < live.length - 1) setAt(index + 1);
@@ -11954,7 +12447,7 @@ function FormStage({
11954
12447
  if (event.shiftKey) retreat();
11955
12448
  else advance();
11956
12449
  }
11957
- useEffect28(() => {
12450
+ useEffect29(() => {
11958
12451
  const input = stage.current?.querySelector(
11959
12452
  "input:not([tabindex='-1']), textarea, select, [role='combobox']"
11960
12453
  );
@@ -11996,7 +12489,22 @@ function FormStage({
11996
12489
  ] }) : null,
11997
12490
  form.intro && index === 0 ? /* @__PURE__ */ jsx42("p", { className: "text-sm text-muted-foreground", children: form.intro }) : null,
11998
12491
  error ? /* @__PURE__ */ jsx42(RefusalNotice, { error, className: "text-left" }) : null,
11999
- refusal ? /* @__PURE__ */ jsx42("p", { role: "alert", className: "rounded border border-destructive/40 px-2 py-1 text-left text-xs text-destructive", children: refusal }) : null,
12492
+ refusal2 ? /* @__PURE__ */ jsx42(
12493
+ RefusalNotice,
12494
+ {
12495
+ error: refusal2,
12496
+ className: "text-left",
12497
+ onKeepEditing: () => {
12498
+ setRefusal(null);
12499
+ stage.current?.querySelector("input, textarea, select, button")?.focus();
12500
+ },
12501
+ onDiscard: () => {
12502
+ setRefusal(null);
12503
+ setAnswers({});
12504
+ setAt(0);
12505
+ }
12506
+ }
12507
+ ) : null,
12000
12508
  /* @__PURE__ */ jsxs38("div", { ref: stage, className: "flex flex-col gap-4 text-left", children: [
12001
12509
  (oneAtATime ? current ? [current] : [] : live).map((q) => /* @__PURE__ */ jsx42(
12002
12510
  Question,
@@ -12070,12 +12578,15 @@ function Question({
12070
12578
  void upload?.(file).then((result) => {
12071
12579
  if (!result) return;
12072
12580
  if (result.ok) onChange(result.fileId);
12073
- else setUploadError(result.reason);
12581
+ else
12582
+ setUploadError(
12583
+ refusal("invalid_argument", result.reason, "Pick another file, or try this one again.")
12584
+ );
12074
12585
  });
12075
12586
  }
12076
12587
  }
12077
12588
  ),
12078
- uploadError ? /* @__PURE__ */ jsx42("p", { className: "text-xs text-destructive", children: uploadError }) : null
12589
+ uploadError ? /* @__PURE__ */ jsx42(RefusalLine, { error: uploadError }) : null
12079
12590
  ] }) : /* @__PURE__ */ jsx42(FieldControl, { field, value, onChange, id })
12080
12591
  ] });
12081
12592
  }
@@ -12149,10 +12660,10 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, className }) {
12149
12660
  setError(null);
12150
12661
  setForms(mine);
12151
12662
  }, [client, tableId, seed, claimSeed]);
12152
- useEffect29(() => {
12663
+ useEffect30(() => {
12153
12664
  void load();
12154
12665
  }, [load]);
12155
- useEffect29(() => {
12666
+ useEffect30(() => {
12156
12667
  if (!forms || forms.length === 0) return;
12157
12668
  const chosen = forms.find((f) => f.id === (activeFormId ?? activeId)) ?? forms[0];
12158
12669
  if (chosen.id !== activeId) setActiveId(chosen.id);
@@ -12563,7 +13074,7 @@ function groupLabel(groups) {
12563
13074
 
12564
13075
  // src/chartFrame.tsx
12565
13076
  import {
12566
- useEffect as useEffect30,
13077
+ useEffect as useEffect31,
12567
13078
  useId as useId2,
12568
13079
  useRef as useRef12,
12569
13080
  useState as useState42
@@ -12573,7 +13084,7 @@ import { jsx as jsx44, jsxs as jsxs40 } from "react/jsx-runtime";
12573
13084
  function useMeasuredWidth(fallback = 480) {
12574
13085
  const ref = useRef12(null);
12575
13086
  const [width, setWidth] = useState42(fallback);
12576
- useEffect30(() => {
13087
+ useEffect31(() => {
12577
13088
  const node = ref.current;
12578
13089
  if (!node) return;
12579
13090
  const apply = () => {
@@ -12697,7 +13208,7 @@ function isSignatureField(field) {
12697
13208
  }
12698
13209
 
12699
13210
  // src/DocTemplate.tsx
12700
- import { useCallback as useCallback26, useEffect as useEffect31, useState as useState43 } from "react";
13211
+ import { useCallback as useCallback26, useEffect as useEffect32, useState as useState43 } from "react";
12701
13212
  import { useFields as useFields19, useRecordsClient as useRecordsClient29, useTable as useTable15 } from "@ai-matrx/records/react";
12702
13213
  import { BasicInput as BasicInput14, BasicTextarea as BasicTextarea6, Button as Button38, Label as Label8, Skeleton as Skeleton22, cn as cn39 } from "@ai-matrx/design-system";
12703
13214
  import { jsx as jsx45, jsxs as jsxs41 } from "react/jsx-runtime";
@@ -12745,10 +13256,10 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
12745
13256
  setError(null);
12746
13257
  setTemplates(rows);
12747
13258
  }, [client, tableId, seed, fields.data]);
12748
- useEffect31(() => {
13259
+ useEffect32(() => {
12749
13260
  void load();
12750
13261
  }, [load]);
12751
- useEffect31(() => {
13262
+ useEffect32(() => {
12752
13263
  if (!templates || templates.length === 0) return;
12753
13264
  const chosen = templates.find((t) => t.id === (activeTemplateId ?? activeId)) ?? templates[0];
12754
13265
  if (chosen.id !== activeId) setActiveId(chosen.id);
@@ -12756,7 +13267,7 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
12756
13267
  setDraftBody(chosen.body);
12757
13268
  onActiveTemplate?.(chosen);
12758
13269
  }, [templates, activeTemplateId]);
12759
- useEffect31(() => {
13270
+ useEffect32(() => {
12760
13271
  let cancelled = false;
12761
13272
  if (draftBody.trim() === "") {
12762
13273
  setUnresolved([]);
@@ -12867,16 +13378,21 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
12867
13378
  onChange: (e) => setDraftBody(e.target.value)
12868
13379
  }
12869
13380
  ),
12870
- unresolved.length > 0 ? /* @__PURE__ */ jsx45("ul", { className: "flex flex-col gap-0.5 rounded border border-dashed px-2 py-1 text-xs", children: unresolved.map((token) => /* @__PURE__ */ jsxs41("li", { className: "text-destructive", children: [
12871
- /* @__PURE__ */ jsx45("code", { children: token.raw }),
12872
- " \u2014 ",
12873
- token.why
13381
+ unresolved.length > 0 ? /* @__PURE__ */ jsx45("ul", { className: "flex flex-col gap-0.5 rounded border border-dashed px-2 py-1 text-xs", children: unresolved.map((token) => /* @__PURE__ */ jsxs41("li", { className: "flex items-baseline gap-1", children: [
13382
+ /* @__PURE__ */ jsx45("code", { className: "text-destructive", children: token.raw }),
13383
+ /* @__PURE__ */ jsx45("span", { "aria-hidden": "true", children: "\u2014" }),
13384
+ /* @__PURE__ */ jsx45(
13385
+ RefusalLine,
13386
+ {
13387
+ error: refusal("invalid_argument", token.why, "Change it to a Field this table has, or take it out.")
13388
+ }
13389
+ )
12874
13390
  ] }, token.raw)) }) : draftBody.trim() !== "" ? /* @__PURE__ */ jsx45("p", { className: "text-xs text-muted-foreground", children: "Every token in this body points at a Field this table can answer." }) : null
12875
13391
  ] });
12876
13392
  }
12877
13393
 
12878
13394
  // src/DocRender.tsx
12879
- import { useCallback as useCallback27, useEffect as useEffect32, useRef as useRef13, useState as useState44 } from "react";
13395
+ import { useCallback as useCallback27, useEffect as useEffect33, useRef as useRef13, useState as useState44 } from "react";
12880
13396
  import { useRecordsClient as useRecordsClient30 } from "@ai-matrx/records/react";
12881
13397
  import { Button as Button39, Skeleton as Skeleton23, cn as cn40 } from "@ai-matrx/design-system";
12882
13398
  import { jsx as jsx46, jsxs as jsxs42 } from "react/jsx-runtime";
@@ -12905,7 +13421,7 @@ function DocRender({ templateId, recordId, filename, onRendered, className }) {
12905
13421
  setPreview(body.data);
12906
13422
  setRenders(held.data.filter((r) => r.template_id === templateId));
12907
13423
  }, [client, templateId, recordId]);
12908
- useEffect32(() => {
13424
+ useEffect33(() => {
12909
13425
  void load();
12910
13426
  }, [load]);
12911
13427
  async function freeze() {
@@ -12989,7 +13505,7 @@ function DocRender({ templateId, recordId, filename, onRendered, className }) {
12989
13505
  }
12990
13506
 
12991
13507
  // src/SignBlock.tsx
12992
- import { useCallback as useCallback28, useEffect as useEffect33, useState as useState45 } from "react";
13508
+ import { useCallback as useCallback28, useEffect as useEffect34, useState as useState45 } from "react";
12993
13509
  import { useFields as useFields20, useRecordsClient as useRecordsClient31 } from "@ai-matrx/records/react";
12994
13510
  import { BasicInput as BasicInput15, Button as Button40, Skeleton as Skeleton24, cn as cn41 } from "@ai-matrx/design-system";
12995
13511
  import { useTable as useTable16 } from "@ai-matrx/records/react";
@@ -13019,7 +13535,7 @@ function SignBlock({ tableId, recordId, render, className }) {
13019
13535
  }
13020
13536
  setVerdicts(answers);
13021
13537
  }, [client, recordId]);
13022
- useEffect33(() => {
13538
+ useEffect34(() => {
13023
13539
  void load();
13024
13540
  }, [load]);
13025
13541
  async function sign(field) {
@@ -13075,7 +13591,21 @@ function SignBlock({ tableId, recordId, render, className }) {
13075
13591
  signature.document_hash.slice(0, 12)
13076
13592
  ] })
13077
13593
  ] }),
13078
- /* @__PURE__ */ jsx47("p", { className: cn41("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 ?? "") })
13594
+ intact === false ? (
13595
+ // THE STORE REFUSED TO CALL IT INTACT: its reason goes through the one
13596
+ // formatter, with what to do next, never raw in red (lane REFUSAL-SWEEP).
13597
+ /* @__PURE__ */ jsx47(
13598
+ RefusalLine,
13599
+ {
13600
+ className: "mt-0.5",
13601
+ error: refusal(
13602
+ "refused_by_rule",
13603
+ typeof verdict === "object" && verdict !== null && "reason" in verdict ? String(verdict.reason) : "The store says what is there no longer matches what was signed.",
13604
+ "Ask for it to be signed again on the version that is there now."
13605
+ )
13606
+ }
13607
+ )
13608
+ ) : /* @__PURE__ */ jsx47("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 ?? "") })
13079
13609
  ] }, signature.id);
13080
13610
  }) }) : null,
13081
13611
  !render ? /* @__PURE__ */ jsx47("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__ */ jsxs43("p", { className: "text-xs text-muted-foreground", children: [
@@ -13126,7 +13656,7 @@ function SignBlock({ tableId, recordId, render, className }) {
13126
13656
  }
13127
13657
 
13128
13658
  // src/NotifyRuleEditor.tsx
13129
- import { useCallback as useCallback29, useEffect as useEffect34, useState as useState46 } from "react";
13659
+ import { useCallback as useCallback29, useEffect as useEffect35, useState as useState46 } from "react";
13130
13660
  import { useRecordsClient as useRecordsClient32, useTable as useTable17 } from "@ai-matrx/records/react";
13131
13661
  import { Button as Button41, Skeleton as Skeleton25, cn as cn42 } from "@ai-matrx/design-system";
13132
13662
  import { jsx as jsx48, jsxs as jsxs44 } from "react/jsx-runtime";
@@ -13162,7 +13692,7 @@ function NotifyRuleEditor({ tableId, seed, className }) {
13162
13692
  if (host.savedViews) setViews(await host.savedViews());
13163
13693
  else setViews(null);
13164
13694
  }, [client, host, tableId]);
13165
- useEffect34(() => {
13695
+ useEffect35(() => {
13166
13696
  void load();
13167
13697
  }, [load]);
13168
13698
  const write = useCallback29(
@@ -13190,7 +13720,7 @@ function NotifyRuleEditor({ tableId, seed, className }) {
13190
13720
  },
13191
13721
  [client, tableId]
13192
13722
  );
13193
- useEffect34(() => {
13723
+ useEffect35(() => {
13194
13724
  if (!subscriptions || !seed || seed.length === 0) return;
13195
13725
  const missing = seed.filter((s) => !subscriptions.some((held) => held.name === s.name));
13196
13726
  if (missing.length === 0) return;
@@ -13357,7 +13887,7 @@ import { Fragment as Fragment20, jsx as jsx49, jsxs as jsxs45 } from "react/jsx-
13357
13887
  function pretty(n) {
13358
13888
  return Number.isInteger(n) ? n.toLocaleString() : n.toLocaleString(void 0, { maximumFractionDigits: 2 });
13359
13889
  }
13360
- function asRefusal(block) {
13890
+ function asRefusal2(block) {
13361
13891
  return mapPgError(
13362
13892
  {
13363
13893
  message: block.refused ?? "",
@@ -13418,7 +13948,7 @@ function ChartBlock({ block, subject, className }) {
13418
13948
  " ms"
13419
13949
  ] }) : null
13420
13950
  ] }),
13421
- block.refused ? /* @__PURE__ */ jsx49(RefusalNotice, { error: asRefusal(block) }) : needs ? /* @__PURE__ */ jsx49("p", { className: "px-1 py-3 text-xs text-muted-foreground", children: needs }) : kind === "stuck" ? /* @__PURE__ */ jsx49(StuckList, { rows: block.rows ?? [] }) : points.length === 0 ? /* @__PURE__ */ jsx49("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__ */ jsx49(Fragment20, { children: kind === "number" ? /* @__PURE__ */ jsx49(BigNumber, { points, measure: primary, onDrill: drill }) : kind === "table" ? /* @__PURE__ */ jsx49(GroupTable, { points, series, onDrill: drill }) : /* @__PURE__ */ jsxs45(Fragment20, { children: [
13951
+ block.refused ? /* @__PURE__ */ jsx49(RefusalNotice, { error: asRefusal2(block) }) : needs ? /* @__PURE__ */ jsx49("p", { className: "px-1 py-3 text-xs text-muted-foreground", children: needs }) : kind === "stuck" ? /* @__PURE__ */ jsx49(StuckList, { rows: block.rows ?? [] }) : points.length === 0 ? /* @__PURE__ */ jsx49("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__ */ jsx49(Fragment20, { children: kind === "number" ? /* @__PURE__ */ jsx49(BigNumber, { points, measure: primary, onDrill: drill }) : kind === "table" ? /* @__PURE__ */ jsx49(GroupTable, { points, series, onDrill: drill }) : /* @__PURE__ */ jsxs45(Fragment20, { children: [
13422
13952
  /* @__PURE__ */ jsx49(Drawing, { kind, points, series, config, onDrill: drill }),
13423
13953
  /* @__PURE__ */ jsx49(Values, { points, measure: primary, config, onDrill: drill })
13424
13954
  ] }) })
@@ -13646,7 +14176,7 @@ function Drawing({
13646
14176
  }
13647
14177
 
13648
14178
  // src/DashboardCanvas.tsx
13649
- import { useCallback as useCallback30, useEffect as useEffect35, useMemo as useMemo28, useState as useState47 } from "react";
14179
+ import { useCallback as useCallback30, useEffect as useEffect36, useMemo as useMemo28, useState as useState47 } from "react";
13650
14180
  import { useFields as useFields21, useRecordsClient as useRecordsClient33, useTable as useTable18 } from "@ai-matrx/records/react";
13651
14181
  import {
13652
14182
  BasicInput as BasicInput16,
@@ -13683,17 +14213,17 @@ function DashboardCanvas({ tableId, activeDashboardId, filter, className }) {
13683
14213
  setError(null);
13684
14214
  setBoards(answered.data.map(dashboardFromSummary));
13685
14215
  }, [client, tableId]);
13686
- useEffect35(() => {
14216
+ useEffect36(() => {
13687
14217
  void load();
13688
14218
  }, [load]);
13689
- useEffect35(() => {
14219
+ useEffect36(() => {
13690
14220
  if (!boards || boards.length === 0) return;
13691
14221
  const chosen = boards.find((d) => d.id === (activeDashboardId ?? activeId)) ?? boards[0];
13692
14222
  if (chosen.id !== activeId) setActiveId(chosen.id);
13693
14223
  }, [boards, activeDashboardId]);
13694
14224
  const board = useMemo28(() => boards?.find((d) => d.id === activeId) ?? null, [boards, activeId]);
13695
14225
  const filterKey = JSON.stringify(filter ?? {});
13696
- useEffect35(() => {
14226
+ useEffect36(() => {
13697
14227
  if (!activeId) {
13698
14228
  setRun(null);
13699
14229
  return;
@@ -13989,18 +14519,19 @@ function GroupingPicker({
13989
14519
  }
13990
14520
 
13991
14521
  // src/FormsPanel.tsx
13992
- import { useCallback as useCallback31, useEffect as useEffect36, useState as useState48 } from "react";
14522
+ import { useCallback as useCallback31, useEffect as useEffect37, useState as useState48 } from "react";
13993
14523
  import { useRecordsClient as useRecordsClient34, useTable as useTable19 } from "@ai-matrx/records/react";
13994
14524
  import { publicFormPath as publicFormPath2 } from "@ai-matrx/records";
13995
14525
  import { Badge as Badge15, Button as Button43, Skeleton as Skeleton27, cn as cn45 } from "@ai-matrx/design-system";
13996
14526
  import { Fragment as Fragment22, jsx as jsx51, jsxs as jsxs47 } from "react/jsx-runtime";
14527
+ 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.";
13997
14528
  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.";
13998
14529
  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.";
13999
14530
  function formSuggestion(tableName2) {
14000
14531
  const subject = tableName2?.trim() ? tableName2.trim() : "this table";
14001
14532
  return `Make me a form that collects new ${subject} entries and tells me when somebody answers.`;
14002
14533
  }
14003
- function FormsPanel({ tableId, className }) {
14534
+ function FormsPanel({ tableId, activeFormId, className }) {
14004
14535
  const client = useRecordsClient34();
14005
14536
  const publishGate = usePublishGate();
14006
14537
  const host = useRecordsUi();
@@ -14011,6 +14542,11 @@ function FormsPanel({ tableId, className }) {
14011
14542
  const [busy, setBusy] = useState48(null);
14012
14543
  const [copied, setCopied] = useState48(null);
14013
14544
  const [building, setBuilding] = useState48(false);
14545
+ const linked = activeFormId && forms !== null ? forms.find((f) => f.form_id === activeFormId) ?? null : null;
14546
+ const linkedMissing = Boolean(activeFormId) && forms !== null && linked === null;
14547
+ useEffect37(() => {
14548
+ if (linked && rights.structure) setBuilding(true);
14549
+ }, [linked, rights.structure]);
14014
14550
  const load = useCallback31(async () => {
14015
14551
  const answered = await client.forms({ table_id: tableId });
14016
14552
  if (!answered.ok) {
@@ -14021,7 +14557,7 @@ function FormsPanel({ tableId, className }) {
14021
14557
  setError(null);
14022
14558
  setForms(answered.data);
14023
14559
  }, [client, tableId]);
14024
- useEffect36(() => {
14560
+ useEffect37(() => {
14025
14561
  void load();
14026
14562
  }, [load]);
14027
14563
  const toggle = useCallback31(
@@ -14059,10 +14595,12 @@ function FormsPanel({ tableId, className }) {
14059
14595
  rights.structure && forms.length > 0 ? /* @__PURE__ */ jsx51(Button43, { size: "sm", variant: building ? "secondary" : "ghost", onClick: () => setBuilding((b) => !b), children: building ? "Done building" : "Build a form" }) : null
14060
14596
  ] }),
14061
14597
  error ? /* @__PURE__ */ jsx51(RefusalNotice, { error }) : null,
14598
+ linkedMissing ? /* @__PURE__ */ jsx51("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,
14062
14599
  building ? /* @__PURE__ */ jsx51(
14063
14600
  FormBuilder,
14064
14601
  {
14065
14602
  tableId,
14603
+ activeFormId: linked ? linked.form_id : null,
14066
14604
  onActiveForm: () => {
14067
14605
  void load();
14068
14606
  }
@@ -14088,57 +14626,69 @@ function FormsPanel({ tableId, className }) {
14088
14626
  ) : null,
14089
14627
  /* @__PURE__ */ jsx51("ul", { className: "flex flex-col gap-2", children: forms.map((form) => {
14090
14628
  const url = `${origin}${publicFormPath2(form.form_id)}`;
14091
- return /* @__PURE__ */ jsxs47("li", { className: "rounded-md border p-2.5", children: [
14092
- /* @__PURE__ */ jsxs47("div", { className: "flex items-center gap-2", children: [
14093
- /* @__PURE__ */ jsx51("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: form.title ?? form.slug }),
14094
- /* @__PURE__ */ jsx51(Badge15, { variant: form.state === "open" ? "default" : "secondary", children: form.state })
14095
- ] }),
14096
- /* @__PURE__ */ jsx51("p", { className: "mt-0.5 text-xs text-muted-foreground", children: FORM_STATE_WORDS[form.state] }),
14097
- /* @__PURE__ */ jsxs47("p", { className: "mt-1.5 text-xs", children: [
14098
- /* @__PURE__ */ jsx51("span", { className: "font-medium", children: form.in_table }),
14099
- " in the table",
14100
- form.held > 0 ? /* @__PURE__ */ jsxs47(Fragment22, { children: [
14101
- " \xB7 ",
14102
- /* @__PURE__ */ jsx51("span", { className: "font-medium", children: form.held }),
14103
- " waiting for someone"
14104
- ] }) : null,
14105
- form.rejected > 0 ? /* @__PURE__ */ jsxs47(Fragment22, { children: [
14106
- " \xB7 ",
14107
- /* @__PURE__ */ jsx51("span", { className: "font-medium", children: form.rejected }),
14108
- " turned away"
14109
- ] }) : null,
14110
- form.submission_cap !== null ? /* @__PURE__ */ jsxs47("span", { className: "text-muted-foreground", children: [
14111
- " \xB7 stops at ",
14112
- form.submission_cap
14113
- ] }) : null
14114
- ] }),
14115
- /* @__PURE__ */ jsxs47("div", { className: "mt-2 flex flex-wrap items-center gap-1.5", children: [
14116
- form.published_at ? /* @__PURE__ */ jsx51(Button43, { size: "sm", variant: "outline", onClick: () => void copy(url, form.form_id), children: copied === form.form_id ? "Copied" : "Copy link" }) : null,
14117
- rights.structure && !publishGate.blocked ? /* @__PURE__ */ jsx51(
14118
- Button43,
14119
- {
14120
- size: "sm",
14121
- variant: "ghost",
14122
- disabled: busy === form.form_id,
14123
- onClick: () => void toggle(form),
14124
- children: busy === form.form_id ? "\u2026" : form.published_at && !form.closed_at ? "Unpublish" : "Publish"
14125
- }
14126
- ) : null
14127
- ] }),
14128
- rights.structure && publishGate.why ? /* @__PURE__ */ jsx51("p", { className: "mt-1.5 text-xs text-muted-foreground", children: publishGate.why }) : null,
14129
- shown && shown === url ? /* @__PURE__ */ jsxs47("p", { className: "mt-1.5 break-all rounded border border-dashed px-2 py-1 text-xs", children: [
14130
- "This browser would not let the page copy for you, so here it is to copy by hand:",
14131
- " ",
14132
- url
14133
- ] }) : null
14134
- ] }, form.form_id);
14629
+ return /* @__PURE__ */ jsxs47(
14630
+ "li",
14631
+ {
14632
+ "data-linked": form.form_id === activeFormId ? "true" : void 0,
14633
+ ref: form.form_id === activeFormId ? (el) => el?.scrollIntoView({ block: "nearest" }) : void 0,
14634
+ className: cn45(
14635
+ "rounded-md border p-2.5",
14636
+ form.form_id === activeFormId && "border-primary ring-1 ring-primary"
14637
+ ),
14638
+ children: [
14639
+ /* @__PURE__ */ jsxs47("div", { className: "flex items-center gap-2", children: [
14640
+ /* @__PURE__ */ jsx51("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: form.title ?? form.slug }),
14641
+ /* @__PURE__ */ jsx51(Badge15, { variant: form.state === "open" ? "default" : "secondary", children: form.state })
14642
+ ] }),
14643
+ /* @__PURE__ */ jsx51("p", { className: "mt-0.5 text-xs text-muted-foreground", children: FORM_STATE_WORDS[form.state] }),
14644
+ /* @__PURE__ */ jsxs47("p", { className: "mt-1.5 text-xs", children: [
14645
+ /* @__PURE__ */ jsx51("span", { className: "font-medium", children: form.in_table }),
14646
+ " in the table",
14647
+ form.held > 0 ? /* @__PURE__ */ jsxs47(Fragment22, { children: [
14648
+ " \xB7 ",
14649
+ /* @__PURE__ */ jsx51("span", { className: "font-medium", children: form.held }),
14650
+ " waiting for someone"
14651
+ ] }) : null,
14652
+ form.rejected > 0 ? /* @__PURE__ */ jsxs47(Fragment22, { children: [
14653
+ " \xB7 ",
14654
+ /* @__PURE__ */ jsx51("span", { className: "font-medium", children: form.rejected }),
14655
+ " turned away"
14656
+ ] }) : null,
14657
+ form.submission_cap !== null ? /* @__PURE__ */ jsxs47("span", { className: "text-muted-foreground", children: [
14658
+ " \xB7 stops at ",
14659
+ form.submission_cap
14660
+ ] }) : null
14661
+ ] }),
14662
+ /* @__PURE__ */ jsxs47("div", { className: "mt-2 flex flex-wrap items-center gap-1.5", children: [
14663
+ form.published_at ? /* @__PURE__ */ jsx51(Button43, { size: "sm", variant: "outline", onClick: () => void copy(url, form.form_id), children: copied === form.form_id ? "Copied" : "Copy link" }) : null,
14664
+ rights.structure && !publishGate.blocked ? /* @__PURE__ */ jsx51(
14665
+ Button43,
14666
+ {
14667
+ size: "sm",
14668
+ variant: "ghost",
14669
+ disabled: busy === form.form_id,
14670
+ onClick: () => void toggle(form),
14671
+ children: busy === form.form_id ? "\u2026" : form.published_at && !form.closed_at ? "Unpublish" : "Publish"
14672
+ }
14673
+ ) : null
14674
+ ] }),
14675
+ rights.structure && publishGate.why ? /* @__PURE__ */ jsx51("p", { className: "mt-1.5 text-xs text-muted-foreground", children: publishGate.why }) : null,
14676
+ shown && shown === url ? /* @__PURE__ */ jsxs47("p", { className: "mt-1.5 break-all rounded border border-dashed px-2 py-1 text-xs", children: [
14677
+ "This browser would not let the page copy for you, so here it is to copy by hand:",
14678
+ " ",
14679
+ url
14680
+ ] }) : null
14681
+ ]
14682
+ },
14683
+ form.form_id
14684
+ );
14135
14685
  }) }),
14136
14686
  !rights.known ? /* @__PURE__ */ jsx51("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
14137
14687
  ] });
14138
14688
  }
14139
14689
 
14140
14690
  // src/BookingBuilder.tsx
14141
- import { useCallback as useCallback32, useEffect as useEffect37, useMemo as useMemo29, useState as useState49 } from "react";
14691
+ import { useCallback as useCallback32, useEffect as useEffect38, useMemo as useMemo29, useState as useState49 } from "react";
14142
14692
  import { useFields as useFields22, useRecordsClient as useRecordsClient35, useTable as useTable20 } from "@ai-matrx/records/react";
14143
14693
  import {
14144
14694
  bookingPath
@@ -14213,18 +14763,18 @@ function BookingBuilder({ tableId, bookingId, onSaved, onClose, className }) {
14213
14763
  setLoaded(true);
14214
14764
  if (mine) setFormId(mine.form_id);
14215
14765
  }, [client, tableId, bookingId]);
14216
- useEffect37(() => {
14766
+ useEffect38(() => {
14217
14767
  void load();
14218
14768
  }, [load]);
14219
- useEffect37(() => {
14769
+ useEffect38(() => {
14220
14770
  if (title !== "" || !table.data) return;
14221
14771
  setTitle(existing?.title ?? `Book a ${minutes}-minute ${table.data.name} appointment`);
14222
14772
  }, [table.data, existing]);
14223
- useEffect37(() => {
14773
+ useEffect38(() => {
14224
14774
  if (!existing) return;
14225
14775
  setMinutes(existing.slot_minutes);
14226
14776
  }, [existing]);
14227
- useEffect37(() => {
14777
+ useEffect38(() => {
14228
14778
  if (!offer) return;
14229
14779
  setWindows(draftWindows(offer));
14230
14780
  setMinutes(offer.slot_minutes);
@@ -14486,7 +15036,7 @@ function BookingBuilder({ tableId, bookingId, onSaved, onClose, className }) {
14486
15036
  }
14487
15037
 
14488
15038
  // src/BookingSlots.tsx
14489
- import { useCallback as useCallback33, useEffect as useEffect38, useMemo as useMemo30, useState as useState50 } from "react";
15039
+ import { useCallback as useCallback33, useEffect as useEffect39, useMemo as useMemo30, useState as useState50 } from "react";
14490
15040
  import { useRecordsClient as useRecordsClient36, useMyLevels as useMyLevels2 } from "@ai-matrx/records/react";
14491
15041
  import { bookingPath as bookingPath2 } from "@ai-matrx/records";
14492
15042
  import { Badge as Badge16, Button as Button45, Skeleton as Skeleton29, cn as cn47 } from "@ai-matrx/design-system";
@@ -14517,7 +15067,7 @@ function BookingSlots({ tableId, className }) {
14517
15067
  setError(null);
14518
15068
  setPages(answered.data);
14519
15069
  }, [client, tableId]);
14520
- useEffect38(() => {
15070
+ useEffect39(() => {
14521
15071
  void load();
14522
15072
  }, [load]);
14523
15073
  const subjectIds = useMemo30(
@@ -14686,12 +15236,12 @@ function nextInWords(page) {
14686
15236
  }
14687
15237
 
14688
15238
  // src/CaptureSheet.tsx
14689
- import { useCallback as useCallback35, useEffect as useEffect40, useRef as useRef15, useState as useState52 } from "react";
15239
+ import { useCallback as useCallback35, useEffect as useEffect41, useRef as useRef15, useState as useState52 } from "react";
14690
15240
  import { useFields as useFields23, useRecordsClient as useRecordsClient38, useTable as useTable21 } from "@ai-matrx/records/react";
14691
15241
  import { Button as Button47, Input as Input4, Skeleton as Skeleton31, Textarea as Textarea3, cn as cn49 } from "@ai-matrx/design-system";
14692
15242
 
14693
15243
  // src/CaptureRun.tsx
14694
- import { useCallback as useCallback34, useEffect as useEffect39, useMemo as useMemo31, useRef as useRef14, useState as useState51 } from "react";
15244
+ import { useCallback as useCallback34, useEffect as useEffect40, useMemo as useMemo31, useRef as useRef14, useState as useState51 } from "react";
14695
15245
  import {
14696
15246
  coerceTypedAnswer as coerceTypedAnswer2,
14697
15247
  coercionLabel,
@@ -14750,7 +15300,7 @@ function CaptureRun({ sheetId, face: given, className }) {
14750
15300
  const [lastSynced, setLastSynced] = useState51(null);
14751
15301
  const [sending, setSending] = useState51(false);
14752
15302
  const queueRef = useRef14(null);
14753
- useEffect39(() => {
15303
+ useEffect40(() => {
14754
15304
  const q2 = openCaptureQueue({
14755
15305
  onChange: (c, all) => {
14756
15306
  setCounts(c);
@@ -14788,13 +15338,13 @@ function CaptureRun({ sheetId, face: given, className }) {
14788
15338
  void q2.sync().then(() => void q2.lastSyncedAt().then(setLastSynced));
14789
15339
  return () => q2.dispose();
14790
15340
  }, [client, host]);
14791
- useEffect39(() => {
15341
+ useEffect40(() => {
14792
15342
  if (given !== void 0) return;
14793
15343
  let cancelled = false;
14794
15344
  void client.captureOpen({ sheet_id: sheetId }).then((res) => {
14795
15345
  if (cancelled) return;
14796
15346
  if (res.ok) setFace(res.data);
14797
- else setLoadFailed(res.error?.message ?? "This sheet could not be opened.");
15347
+ else setLoadFailed(refusalOr(res.error, "unreachable", "This sheet could not be opened."));
14798
15348
  });
14799
15349
  return () => {
14800
15350
  cancelled = true;
@@ -14841,7 +15391,11 @@ function CaptureRun({ sheetId, face: given, className }) {
14841
15391
  const needed = questions.filter((x) => x.required && !answered(x.field));
14842
15392
  if (needed.length > 0) {
14843
15393
  setMissing(
14844
- `${needed.map((x) => (x.ask ?? x.field).replace(/\?$/, "")).join(", ")} still ${needed.length === 1 ? "needs" : "need"} an answer.`
15394
+ refusal(
15395
+ "invalid_argument",
15396
+ `${needed.map((x) => (x.ask ?? x.field).replace(/\?$/, "")).join(", ")} still ${needed.length === 1 ? "needs" : "need"} an answer.`,
15397
+ "Answer it, then capture again. Nothing has been queued yet."
15398
+ )
14845
15399
  );
14846
15400
  setAt(questions.findIndex((x) => x.field === needed[0].field));
14847
15401
  return;
@@ -14872,7 +15426,7 @@ function CaptureRun({ sheetId, face: given, className }) {
14872
15426
  if (online) await sync();
14873
15427
  }
14874
15428
  if (loadFailed) {
14875
- return /* @__PURE__ */ jsx54("section", { className: cn48("mx-auto w-full max-w-sm p-4", className), children: /* @__PURE__ */ jsx54("p", { className: "text-sm text-destructive", "data-testid": "capture-refusal", children: loadFailed }) });
15429
+ return /* @__PURE__ */ jsx54("section", { className: cn48("mx-auto w-full max-w-sm p-4", className), children: /* @__PURE__ */ jsx54("div", { "data-testid": "capture-refusal", children: /* @__PURE__ */ jsx54(RefusalNotice, { error: loadFailed, className: "text-sm" }) }) });
14876
15430
  }
14877
15431
  if (face === void 0) {
14878
15432
  return /* @__PURE__ */ jsx54(Skeleton30, { className: cn48("mx-auto h-64 w-full max-w-sm", className) });
@@ -14903,7 +15457,21 @@ function CaptureRun({ sheetId, face: given, className }) {
14903
15457
  ] });
14904
15458
  const queuePanel = items.filter((i) => i.state !== "landed").length > 0 ? /* @__PURE__ */ jsx54("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__ */ jsxs50("li", { className: "flex items-start gap-2 text-[11px]", children: [
14905
15459
  /* @__PURE__ */ jsx54("span", { className: "tabular-nums text-muted-foreground", children: new Date(i.captured_at).toLocaleTimeString() }),
14906
- /* @__PURE__ */ jsx54("span", { className: cn48("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." }),
15460
+ i.state === "refused" ? (
15461
+ // THE STORE REFUSED THIS ONE: a refusal line, heading and remedy
15462
+ // both — never its raw words in red (lane REFUSAL-SWEEP).
15463
+ /* @__PURE__ */ jsx54(
15464
+ RefusalLine,
15465
+ {
15466
+ className: "flex-1",
15467
+ error: refusal(
15468
+ "refused_by_rule",
15469
+ i.last_error ?? "This one was refused.",
15470
+ "Throw it away and capture it again with the answer changed."
15471
+ )
15472
+ }
15473
+ )
15474
+ ) : /* @__PURE__ */ jsx54("span", { className: "flex-1", children: i.last_error ?? "Waiting for a signal." }),
14907
15475
  i.state === "refused" ? /* @__PURE__ */ jsx54(
14908
15476
  Button46,
14909
15477
  {
@@ -15023,7 +15591,7 @@ function CaptureRun({ sheetId, face: given, className }) {
15023
15591
  }
15024
15592
  }
15025
15593
  ),
15026
- missing ? /* @__PURE__ */ jsx54("p", { className: "text-sm text-destructive", "data-testid": "capture-missing", children: missing }) : null,
15594
+ missing ? /* @__PURE__ */ jsx54("div", { "data-testid": "capture-missing", children: /* @__PURE__ */ jsx54(RefusalNotice, { error: missing, className: "text-sm" }) }) : null,
15027
15595
  /* @__PURE__ */ jsxs50("div", { className: "flex items-center gap-2", children: [
15028
15596
  at > 0 ? /* @__PURE__ */ jsx54(
15029
15597
  Button46,
@@ -15120,7 +15688,7 @@ function AdHocCaptureSheet({
15120
15688
  },
15121
15689
  [host]
15122
15690
  );
15123
- useEffect40(() => {
15691
+ useEffect41(() => {
15124
15692
  let cancelled = false;
15125
15693
  void (async () => {
15126
15694
  const held = host.captureQueue ? await host.captureQueue.load() : [];
@@ -15251,13 +15819,13 @@ function AdHocCaptureSheet({
15251
15819
  setUploadError(null);
15252
15820
  void host.upload?.(file).then((result) => {
15253
15821
  if (result.ok) setFileId(result.fileId);
15254
- else setUploadError(result.reason);
15822
+ else setUploadError(refusal("invalid_argument", result.reason, "Pick another file, or try this one again."));
15255
15823
  });
15256
15824
  }
15257
15825
  }
15258
15826
  ),
15259
15827
  fileId ? /* @__PURE__ */ jsx55("p", { className: "text-[11px] text-muted-foreground", children: "Attached." }) : null,
15260
- uploadError ? /* @__PURE__ */ jsx55("p", { className: "text-xs text-destructive", children: uploadError }) : null
15828
+ uploadError ? /* @__PURE__ */ jsx55(RefusalLine, { error: uploadError }) : null
15261
15829
  ] }) : null,
15262
15830
  /* @__PURE__ */ jsx55(
15263
15831
  Textarea3,
@@ -15299,7 +15867,7 @@ function AdHocCaptureSheet({
15299
15867
  }
15300
15868
 
15301
15869
  // src/PortalShell.tsx
15302
- import { useCallback as useCallback36, useEffect as useEffect41, useState as useState53 } from "react";
15870
+ import { useCallback as useCallback36, useEffect as useEffect42, useState as useState53 } from "react";
15303
15871
  import { useRecordsClient as useRecordsClient39 } from "@ai-matrx/records/react";
15304
15872
  import { Button as Button48, Skeleton as Skeleton32, cn as cn50 } from "@ai-matrx/design-system";
15305
15873
  import { jsx as jsx56, jsxs as jsxs52 } from "react/jsx-runtime";
@@ -15325,7 +15893,7 @@ function PortalShell({ tableId, form, resourceType = "record", className }) {
15325
15893
  }
15326
15894
  setReach(reached.data.map((row) => row.resource_id));
15327
15895
  }, [client, resourceType]);
15328
- useEffect41(() => {
15896
+ useEffect42(() => {
15329
15897
  void load();
15330
15898
  }, [load]);
15331
15899
  if (error) {
@@ -15385,7 +15953,7 @@ function PortalShell({ tableId, form, resourceType = "record", className }) {
15385
15953
  function PortalRow({ tableId, recordId }) {
15386
15954
  const client = useRecordsClient39();
15387
15955
  const [title, setTitle] = useState53(null);
15388
- useEffect41(() => {
15956
+ useEffect42(() => {
15389
15957
  let cancelled = false;
15390
15958
  void client.recordRead({ record_id: recordId }).then((answered) => {
15391
15959
  if (cancelled) return;
@@ -15405,7 +15973,7 @@ function PortalRow({ tableId, recordId }) {
15405
15973
  }
15406
15974
 
15407
15975
  // src/PublicViewPage.tsx
15408
- import { useCallback as useCallback37, useEffect as useEffect42, useState as useState54 } from "react";
15976
+ import { useCallback as useCallback37, useEffect as useEffect43, useState as useState54 } from "react";
15409
15977
  import { useRecordsClient as useRecordsClient40 } from "@ai-matrx/records/react";
15410
15978
  import { Skeleton as Skeleton33, cn as cn51 } from "@ai-matrx/design-system";
15411
15979
  import { jsx as jsx57, jsxs as jsxs53 } from "react/jsx-runtime";
@@ -15449,7 +16017,7 @@ function PublicViewPage({ slug, className }) {
15449
16017
  }
15450
16018
  setRows([{ id: found.resource_id, document: read.data.document, level: "viewer", hidden: read.data.hidden }]);
15451
16019
  }, [client, slug]);
15452
- useEffect42(() => {
16020
+ useEffect43(() => {
15453
16021
  void load();
15454
16022
  }, [load]);
15455
16023
  if (error) return /* @__PURE__ */ jsx57(RefusalNotice, { error, className });
@@ -15486,7 +16054,7 @@ function PublicRow({ row, fields }) {
15486
16054
  }
15487
16055
 
15488
16056
  // src/EmbedFrame.tsx
15489
- import { useCallback as useCallback38, useEffect as useEffect43, useState as useState55 } from "react";
16057
+ import { useCallback as useCallback38, useEffect as useEffect44, useState as useState55 } from "react";
15490
16058
  import { useRecordsClient as useRecordsClient41 } from "@ai-matrx/records/react";
15491
16059
  import { Button as Button49, Input as Input5, Skeleton as Skeleton34, Textarea as Textarea4, cn as cn52 } from "@ai-matrx/design-system";
15492
16060
  import { useTable as useTable22 } from "@ai-matrx/records/react";
@@ -15599,7 +16167,7 @@ function useEmbedHandshake(args) {
15599
16167
  const [loading, setLoading] = useState55(true);
15600
16168
  const origin = args.origin ?? (typeof location === "undefined" ? "" : location.origin);
15601
16169
  const { secret, requiredMode } = args;
15602
- useEffect43(() => {
16170
+ useEffect44(() => {
15603
16171
  let cancelled = false;
15604
16172
  setLoading(true);
15605
16173
  setError(null);
@@ -15654,7 +16222,7 @@ function recordsDataSource(client, fallbackSchema = "custom") {
15654
16222
  }
15655
16223
 
15656
16224
  // src/TablesHome.tsx
15657
- import { useCallback as useCallback39, useEffect as useEffect44, useState as useState56 } from "react";
16225
+ import { useCallback as useCallback39, useEffect as useEffect45, useState as useState56 } from "react";
15658
16226
  import { useRecordsClient as useRecordsClient42, useTables as useTables4 } from "@ai-matrx/records/react";
15659
16227
  import { BasicInput as BasicInput18, Button as Button50, Skeleton as Skeleton35, cn as cn53 } from "@ai-matrx/design-system";
15660
16228
 
@@ -15720,17 +16288,17 @@ async function declareTable(client, spec) {
15720
16288
  if (!written.ok) return undo(client, table.data, home.data, spec.name, written.error);
15721
16289
  return { ok: true, data: table.data, homeId: home.data };
15722
16290
  }
15723
- async function undo(client, tableId, homeId, name, refusal) {
16291
+ async function undo(client, tableId, homeId, name, refusal2) {
15724
16292
  const removed = await client.recordDelete({ record_id: tableId });
15725
16293
  if (removed.ok) {
15726
16294
  await client.recordDelete({ record_id: homeId });
15727
- return { ok: false, error: refusal };
16295
+ return { ok: false, error: refusal2 };
15728
16296
  }
15729
16297
  return {
15730
16298
  ok: false,
15731
16299
  error: {
15732
- ...refusal,
15733
- 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.`
16300
+ ...refusal2,
16301
+ 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.`
15734
16302
  }
15735
16303
  };
15736
16304
  }
@@ -15783,13 +16351,31 @@ var LANE_EMPTY = {
15783
16351
  community: "Nothing shared beyond your organization. A table published by link or to the world lands here.",
15784
16352
  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."
15785
16353
  };
16354
+ function keptByTheApp(table) {
16355
+ return table.kept_by_the_app === true;
16356
+ }
15786
16357
  function laneFor(table) {
15787
16358
  if (table.is_kernel) return "system";
15788
- if ((table.slug ?? "").startsWith(APP_TABLE_PREFIX)) return "app";
16359
+ if ((table.slug ?? "").startsWith(APP_TABLE_PREFIX) || keptByTheApp(table)) return "app";
15789
16360
  if (table.visibility === "public" || table.visibility === "link") return "community";
15790
16361
  if (table.visibility === "personal") return "mine";
15791
16362
  return "organization";
15792
16363
  }
16364
+ var VISIBILITY_LANES = ["mine", "organization", "community", "world"];
16365
+ var VISIBILITY_LANE_TITLE = {
16366
+ mine: "Mine",
16367
+ organization: "My organization",
16368
+ community: "Community",
16369
+ world: "World"
16370
+ };
16371
+ function visibilityLaneFor(table) {
16372
+ if (table.is_kernel) return null;
16373
+ if ((table.slug ?? "").startsWith(APP_TABLE_PREFIX) || keptByTheApp(table)) return null;
16374
+ if (table.visibility === "personal") return "mine";
16375
+ if (table.visibility === "link") return "community";
16376
+ if (table.visibility === "public") return "world";
16377
+ return "organization";
16378
+ }
15793
16379
  function TablesHome({ onOpenTable, className }) {
15794
16380
  const client = useRecordsClient42();
15795
16381
  const tables = useTables4();
@@ -15799,7 +16385,7 @@ function TablesHome({ onOpenTable, className }) {
15799
16385
  const [error, setError] = useState56(null);
15800
16386
  const [importInto, setImportInto] = useState56(null);
15801
16387
  const [boards, setBoards] = useState56(null);
15802
- useEffect44(() => {
16388
+ useEffect45(() => {
15803
16389
  let cancelled = false;
15804
16390
  void client.dashboards({}).then((result) => {
15805
16391
  if (cancelled) return;
@@ -15933,10 +16519,47 @@ function TablesHome({ onOpenTable, className }) {
15933
16519
  }
15934
16520
 
15935
16521
  // src/TablePage.tsx
15936
- import { useCallback as useCallback40, useEffect as useEffect45, useState as useState57 } from "react";
16522
+ import { useCallback as useCallback40, useEffect as useEffect46, useState as useState57 } from "react";
15937
16523
  import { useFields as useFields24, useRecordsClient as useRecordsClient43, useTable as useTable23 } from "@ai-matrx/records/react";
15938
- import { Button as Button51, Separator as Separator12, Skeleton as Skeleton36, cn as cn54 } from "@ai-matrx/design-system";
16524
+ import { BottomSheet, Button as Button51, Separator as Separator12, Skeleton as Skeleton36, cn as cn54, useIsMobile } from "@ai-matrx/design-system";
15939
16525
  import { Fragment as Fragment28, jsx as jsx61, jsxs as jsxs56 } from "react/jsx-runtime";
16526
+ var RAIL_TITLE = {
16527
+ settings: "Table settings",
16528
+ inbox: "Inbox",
16529
+ import: "Import",
16530
+ field: "Add a field",
16531
+ record: "Record",
16532
+ "new-record": "New record",
16533
+ forms: "Forms",
16534
+ bookings: "Bookings",
16535
+ checklists: "Checklists",
16536
+ notifications: "Notifications",
16537
+ portals: "Portals",
16538
+ "who-changed": "Who changed this"
16539
+ };
16540
+ function RailFrame({
16541
+ phone,
16542
+ title,
16543
+ onClose,
16544
+ children
16545
+ }) {
16546
+ if (phone) {
16547
+ return /* @__PURE__ */ jsx61(
16548
+ BottomSheet,
16549
+ {
16550
+ open: true,
16551
+ onOpenChange: (open) => {
16552
+ if (!open) onClose();
16553
+ },
16554
+ title,
16555
+ size: "full",
16556
+ surface: "solid",
16557
+ children: /* @__PURE__ */ jsx61("div", { "data-rail-sheet": "", className: "min-h-0 flex-1 overflow-y-auto px-3 pb-6", children })
16558
+ }
16559
+ );
16560
+ }
16561
+ return /* @__PURE__ */ jsx61("aside", { "data-rail-column": "", className: "w-[26rem] shrink-0 overflow-y-auto rounded-md border p-3", children });
16562
+ }
15940
16563
  function filterInWords(filter, fields) {
15941
16564
  const nameOf = (key) => {
15942
16565
  const field = fields.find((f) => f.key === key);
@@ -15984,9 +16607,43 @@ function chooseSurface(current, pressed) {
15984
16607
  function surfaceChosen(current, asking) {
15985
16608
  return asking.main !== void 0 ? current.main === asking.main : current.rail === asking.rail;
15986
16609
  }
15987
- function openingRail(activeRecordId) {
15988
- return activeRecordId ? { rail: "record", record: activeRecordId } : { rail: "none", record: null };
16610
+ function openingRail(activeRecordId, activeRail) {
16611
+ if (activeRecordId) return { rail: "record", record: activeRecordId };
16612
+ const named = railFromParam(activeRail);
16613
+ if (named !== null && named !== "share") return { rail: named, record: null };
16614
+ return { rail: "none", record: null };
16615
+ }
16616
+ var RAIL_SPELLINGS = {
16617
+ forms: "forms",
16618
+ form: "forms",
16619
+ bookings: "bookings",
16620
+ booking: "bookings",
16621
+ checklists: "checklists",
16622
+ checklist: "checklists",
16623
+ notifications: "notifications",
16624
+ notification: "notifications",
16625
+ digests: "notifications",
16626
+ digest: "notifications",
16627
+ subscriptions: "notifications",
16628
+ subscription: "notifications",
16629
+ portals: "portals",
16630
+ portal: "portals",
16631
+ settings: "settings",
16632
+ import: "import",
16633
+ inbox: "inbox",
16634
+ share: "share",
16635
+ sharing: "share"
16636
+ };
16637
+ function railFromParam(raw) {
16638
+ if (typeof raw !== "string") return null;
16639
+ const token = raw.trim().toLowerCase();
16640
+ if (token === "") return null;
16641
+ return RAIL_SPELLINGS[token] ?? null;
16642
+ }
16643
+ function unknownRailLine(raw) {
16644
+ 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.`;
15989
16645
  }
16646
+ 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.";
15990
16647
  function openingView(activeView, activeDashboardId) {
15991
16648
  const named = pageViewFromParam(activeView);
15992
16649
  if (named === null) {
@@ -16014,6 +16671,8 @@ function TablePage({
16014
16671
  activeGroupField,
16015
16672
  cameFrom,
16016
16673
  filter,
16674
+ activeRail,
16675
+ activeItemId,
16017
16676
  className
16018
16677
  }) {
16019
16678
  const client = useRecordsClient43();
@@ -16023,8 +16682,13 @@ function TablePage({
16023
16682
  const askedInWords = filter ? filterInWords(filter, pageFields.data ?? []) : null;
16024
16683
  const organizationId = useRecordsClient43().config.organizationId;
16025
16684
  const [view, setView] = useState57(null);
16026
- const opening = openingRail(activeRecordId);
16685
+ const opening = openingRail(activeRecordId, activeRail);
16027
16686
  const opened = openingView(activeView, activeDashboardId);
16687
+ const railAsked = typeof activeRail === "string" ? activeRail.trim() : "";
16688
+ const railUnknown = railAsked !== "" && railFromParam(railAsked) === null ? railAsked : null;
16689
+ const shareFromLink = railFromParam(activeRail) === "share";
16690
+ const canShare = useCanShare();
16691
+ const itemFor = (which) => railFromParam(activeRail) === which ? activeItemId ?? null : null;
16028
16692
  const [asking, setAsking] = useState57(null);
16029
16693
  const [layoutFromLink, setLayoutFromLink] = useState57(opened.layout);
16030
16694
  const [surface, setSurface] = useState57({
@@ -16035,6 +16699,7 @@ function TablePage({
16035
16699
  const setRail = (next) => setSurface((now) => ({ ...now, rail: next }));
16036
16700
  const [openRecord, setOpenRecord] = useState57(opening.record);
16037
16701
  const viewVersion = useRecordVersion(view?.id ?? null);
16702
+ const phone = useIsMobile();
16038
16703
  const shownLayout = layoutFromLink ?? view?.layout ?? "grid";
16039
16704
  const press = (pressed) => {
16040
16705
  const next = chooseSurface(surface, pressed);
@@ -16044,18 +16709,25 @@ function TablePage({
16044
16709
  }
16045
16710
  };
16046
16711
  const show = (next) => press({ rail: next });
16047
- useEffect45(() => {
16712
+ useEffect46(() => {
16048
16713
  if (!activeRecordId) return;
16049
16714
  setOpenRecord(activeRecordId);
16050
16715
  setSurface((now) => ({ ...now, rail: "record" }));
16051
16716
  }, [activeRecordId]);
16052
- useEffect45(() => {
16717
+ useEffect46(() => {
16718
+ if (activeRecordId) return;
16719
+ const named = railFromParam(activeRail);
16720
+ if (named === null || named === "share") return;
16721
+ setSurface((now) => ({ ...now, rail: named }));
16722
+ }, [activeRail, activeRecordId]);
16723
+ useEffect46(() => {
16053
16724
  const named = pageViewFromParam(activeView);
16054
16725
  if (named === null) return;
16055
16726
  const isLayout = named !== "dashboards" && named !== "archived";
16056
16727
  setLayoutFromLink(isLayout ? named : null);
16057
16728
  setSurface((now) => ({ ...now, main: isLayout ? "records" : named }));
16058
16729
  }, [activeView]);
16730
+ const [viewSaveRefused, setViewSaveRefused] = useState57(null);
16059
16731
  const patchView = useCallback40(
16060
16732
  async (patch) => {
16061
16733
  const current = view;
@@ -16063,12 +16735,13 @@ function TablePage({
16063
16735
  setView({ ...current, ...patch });
16064
16736
  const document2 = viewPatchDocument(patch);
16065
16737
  if (Object.keys(document2).length === 0) return;
16066
- const written = await client.recordUpdate({
16067
- record_id: current.id,
16068
- patch: document2,
16069
- ...viewVersion.version === null ? {} : { expectedVersion: viewVersion.version }
16070
- });
16071
- if (written.ok) viewVersion.note(written.data);
16738
+ setViewSaveRefused(null);
16739
+ const written = await saveViewPatch(client, current.id, document2, viewVersion.version);
16740
+ if (written.ok) {
16741
+ viewVersion.note(written.data);
16742
+ return;
16743
+ }
16744
+ setViewSaveRefused({ error: written.error, patch, before: current });
16072
16745
  },
16073
16746
  [client, view, viewVersion]
16074
16747
  );
@@ -16213,11 +16886,33 @@ function TablePage({
16213
16886
  organizationId,
16214
16887
  subjectId: tableId,
16215
16888
  name: table.data?.name,
16216
- may: rights.share
16889
+ may: rights.share,
16890
+ initiallyOpen: shareFromLink
16217
16891
  }
16218
16892
  ),
16219
16893
  /* @__PURE__ */ jsx61(ExportMenu, { tableId })
16220
16894
  ] }),
16895
+ viewSaveRefused ? /* @__PURE__ */ jsx61(
16896
+ RefusalNotice,
16897
+ {
16898
+ error: viewSaveRefused.error,
16899
+ actions: /* @__PURE__ */ jsxs56("span", { className: "flex flex-wrap gap-1 pt-0.5", "data-view-save-refused": "", children: [
16900
+ /* @__PURE__ */ jsx61(Button51, { size: "sm", variant: "outline", onClick: () => void patchView(viewSaveRefused.patch), children: "Try again" }),
16901
+ /* @__PURE__ */ jsx61(
16902
+ Button51,
16903
+ {
16904
+ size: "sm",
16905
+ variant: "ghost",
16906
+ onClick: () => {
16907
+ setView(viewSaveRefused.before);
16908
+ setViewSaveRefused(null);
16909
+ },
16910
+ children: "Discard"
16911
+ }
16912
+ )
16913
+ ] })
16914
+ }
16915
+ ) : null,
16221
16916
  main === "dashboards" ? /* @__PURE__ */ jsx61(DashboardCanvas, { tableId, activeDashboardId: activeDashboardId ?? null }) : main === "archived" ? (
16222
16917
  /* THE ROWS THAT LEFT. Restoring one puts it back in the grid, so the
16223
16918
  page returns to the records rather than leaving the person looking
@@ -16236,6 +16931,8 @@ function TablePage({
16236
16931
  }
16237
16932
  )
16238
16933
  ) : /* @__PURE__ */ jsxs56(Fragment28, { children: [
16934
+ railUnknown ? /* @__PURE__ */ jsx61("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,
16935
+ shareFromLink && rights.known && (!rights.share || !canShare) ? /* @__PURE__ */ jsx61("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,
16239
16936
  opened.unknown ? /* @__PURE__ */ jsx61("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,
16240
16937
  cameFrom ? /* @__PURE__ */ jsx61(
16241
16938
  "p",
@@ -16276,7 +16973,7 @@ function TablePage({
16276
16973
  view ? null : /* @__PURE__ */ jsx61("p", { className: "text-xs text-muted-foreground", children: VIEW_NOT_SAVED_YET })
16277
16974
  ] })
16278
16975
  ] }),
16279
- rail === "none" ? null : /* @__PURE__ */ jsxs56("aside", { className: "w-[26rem] shrink-0 overflow-y-auto rounded-md border p-3", children: [
16976
+ rail === "none" ? null : /* @__PURE__ */ jsxs56(RailFrame, { phone, title: RAIL_TITLE[rail], onClose: () => setRail("none"), children: [
16280
16977
  rail === "settings" ? /* @__PURE__ */ jsx61(
16281
16978
  TableSettings,
16282
16979
  {
@@ -16294,7 +16991,7 @@ function TablePage({
16294
16991
  }
16295
16992
  }
16296
16993
  ) : null,
16297
- rail === "forms" ? /* @__PURE__ */ jsx61(FormsPanel, { tableId }) : null,
16994
+ rail === "forms" ? /* @__PURE__ */ jsx61(FormsPanel, { tableId, activeFormId: itemFor("forms") }) : null,
16298
16995
  rail === "bookings" ? /* @__PURE__ */ jsx61(BookingSlots, { tableId }) : null,
16299
16996
  rail === "checklists" ? /* @__PURE__ */ jsx61(
16300
16997
  ChecklistsPanel,
@@ -16306,8 +17003,8 @@ function TablePage({
16306
17003
  }
16307
17004
  }
16308
17005
  ) : null,
16309
- rail === "notifications" ? /* @__PURE__ */ jsx61(SubscriptionsPanel, { tableId }) : null,
16310
- rail === "portals" ? /* @__PURE__ */ jsx61(PortalsPanel, { tableId }) : null,
17006
+ rail === "notifications" ? /* @__PURE__ */ jsx61(SubscriptionsPanel, { tableId, activeRuleId: itemFor("notifications") }) : null,
17007
+ rail === "portals" ? /* @__PURE__ */ jsx61(PortalsPanel, { tableId, activePortalId: itemFor("portals") }) : null,
16311
17008
  rail === "import" ? /* @__PURE__ */ jsx61(ImportWizard, { tableId, onDone: () => setRail("none") }) : null,
16312
17009
  rail === "field" ? /* @__PURE__ */ jsx61(FieldEditor, { tableId, onSaved: () => setRail("none"), onCancel: () => setRail("none") }) : null,
16313
17010
  rail === "new-record" ? /* @__PURE__ */ jsx61(
@@ -16414,6 +17111,7 @@ export {
16414
17111
  FORMULA_OP_LABEL,
16415
17112
  FORMULA_OP_VALUES,
16416
17113
  FORM_FLOWS,
17114
+ FORM_NOT_HERE_LINE,
16417
17115
  FROZEN_COLUMN_WIDTH,
16418
17116
  FieldControl,
16419
17117
  FieldEditor,
@@ -16461,6 +17159,7 @@ export {
16461
17159
  PAGE_VIEW_LABEL,
16462
17160
  PARITY_LABEL,
16463
17161
  PARITY_MADE_OF,
17162
+ PORTAL_NOT_HERE_LINE,
16464
17163
  PROPOSED_CHANGE_ACT_LABEL,
16465
17164
  PROPOSED_CHANGE_ACT_VALUES,
16466
17165
  Peek,
@@ -16475,6 +17174,7 @@ export {
16475
17174
  PublicViewPage,
16476
17175
  ROLLUP_AGG_LABEL,
16477
17176
  ROLLUP_AGG_VALUES,
17177
+ RULE_NOT_HERE_LINE,
16478
17178
  RecordChat,
16479
17179
  RecordChip,
16480
17180
  RecordForm,
@@ -16489,6 +17189,7 @@ export {
16489
17189
  SAVED_VIEWS_UNAVAILABLE,
16490
17190
  SAY_WHAT_YOU_ARE_WAITING_FOR_AFTER_MS,
16491
17191
  SERIES_COLORS,
17192
+ SHARE_NOT_YOURS_LINE,
16492
17193
  SOMEBODY,
16493
17194
  STAGE_RULE_ON_FAIL_LABEL,
16494
17195
  STORE_ANSWERS_THESE,
@@ -16511,6 +17212,8 @@ export {
16511
17212
  VIEW_LAYOUTS,
16512
17213
  VIEW_NOT_SAVED_YET,
16513
17214
  VIEW_TABLE,
17215
+ VISIBILITY_LANES,
17216
+ VISIBILITY_LANE_TITLE,
16514
17217
  ViewBar,
16515
17218
  ViewSwitcher,
16516
17219
  WITHHELD_RECORD_LABEL,
@@ -16522,6 +17225,7 @@ export {
16522
17225
  actorWords,
16523
17226
  addFields,
16524
17227
  archivedRowLine,
17228
+ asRefusal,
16525
17229
  askableFields,
16526
17230
  blockFromSpec,
16527
17231
  bodyForReading,
@@ -16565,6 +17269,7 @@ export {
16565
17269
  isPlainFieldType,
16566
17270
  isRelationFieldType,
16567
17271
  isSignatureField,
17272
+ keptByTheApp,
16568
17273
  keyFor,
16569
17274
  kindsWithNoChoice,
16570
17275
  laneFor,
@@ -16587,15 +17292,20 @@ export {
16587
17292
  previewLine,
16588
17293
  previewWords,
16589
17294
  publiclyAnswerable,
17295
+ railFromParam,
16590
17296
  recordName,
16591
17297
  recordNameIn,
16592
17298
  recordsDataSource,
17299
+ refusal,
16593
17300
  refusalForAPerson,
17301
+ refusalFromThrown,
16594
17302
  refusalLineForAPerson,
17303
+ refusalOr,
16595
17304
  renderValue,
16596
17305
  revokeConsequence,
16597
17306
  rowName,
16598
17307
  rowNameIn,
17308
+ saveViewPatch,
16599
17309
  scalarText,
16600
17310
  shareUnavailableReason,
16601
17311
  specFromBlock,
@@ -16605,6 +17315,7 @@ export {
16605
17315
  tableName,
16606
17316
  tableRightsAt,
16607
17317
  tokenFor,
17318
+ unknownRailLine,
16608
17319
  unknownViewLine,
16609
17320
  useCanShare,
16610
17321
  useEmbedHandshake,
@@ -16624,6 +17335,7 @@ export {
16624
17335
  viewDocument,
16625
17336
  viewFromRecord,
16626
17337
  viewPatchDocument,
17338
+ visibilityLaneFor,
16627
17339
  whatIsMissing,
16628
17340
  whatYouMayDo,
16629
17341
  whatYouMayDoWithTable,