@ai-matrx/records-ui 0.79.0 → 0.82.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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, { className: "w-64 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,32 +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
- className: cn2(
965
+ title: titleOf(row.id),
966
+ className: cn3(
848
967
  "w-full truncate rounded px-2 py-1 text-left text-xs hover:bg-muted",
849
968
  picked.includes(row.id) ? "bg-accent text-accent-foreground" : ""
850
969
  ),
851
970
  children: titleOf(row.id)
852
971
  }
853
972
  ) }, row.id)) }),
854
- 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
855
974
  ] }),
856
- allowCreate && !full ? /* @__PURE__ */ jsxs2("div", { className: "mt-1 border-t pt-1", children: [
857
- 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: [
858
- /* @__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(
859
978
  BasicInput2,
860
979
  {
861
980
  autoFocus: true,
@@ -872,7 +991,7 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
872
991
  "aria-label": `Name the new ${targetWord}`
873
992
  }
874
993
  ),
875
- /* @__PURE__ */ jsx3(
994
+ /* @__PURE__ */ jsx4(
876
995
  Button2,
877
996
  {
878
997
  type: "button",
@@ -883,12 +1002,13 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
883
1002
  children: busy ? "Creating\u2026" : "Create"
884
1003
  }
885
1004
  )
886
- ] }) : /* @__PURE__ */ jsx3(
1005
+ ] }) : /* @__PURE__ */ jsx4(
887
1006
  Button2,
888
1007
  {
889
1008
  type: "button",
890
1009
  size: "sm",
891
1010
  variant: "ghost",
1011
+ title: search.trim() !== "" ? `Create \u201C${search.trim()}\u201D` : `New ${targetWord}`,
892
1012
  className: "w-full justify-start text-xs",
893
1013
  disabled: busy,
894
1014
  onClick: () => {
@@ -898,10 +1018,25 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
898
1018
  setCreating(true);
899
1019
  }
900
1020
  },
901
- 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}` })
902
1022
  }
903
1023
  ),
904
- 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
905
1040
  ] }) : null
906
1041
  ] })
907
1042
  ] })
@@ -909,12 +1044,12 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
909
1044
  }
910
1045
 
911
1046
  // src/editors.tsx
912
- 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";
913
1048
  function FieldLabel({ field, htmlFor, children }) {
914
- return /* @__PURE__ */ jsxs3("div", { className: "flex items-baseline gap-1.5", children: [
915
- /* @__PURE__ */ jsx4(Label2, { htmlFor, className: "text-xs font-medium", children: fieldName(field) }),
916
- field.required ? /* @__PURE__ */ jsx4("span", { className: "text-xs text-destructive", children: "required" }) : null,
917
- 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,
918
1053
  children
919
1054
  ] });
920
1055
  }
@@ -925,17 +1060,17 @@ function FieldControl({ field, value, onChange, disabled, id }) {
925
1060
  case "formula":
926
1061
  case "lookup":
927
1062
  case "rollup":
928
- 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: [
929
1064
  value === null || value === void 0 ? "\u2014" : String(value),
930
- /* @__PURE__ */ jsxs3("span", { className: "ml-2 italic", children: [
1065
+ /* @__PURE__ */ jsxs4("span", { className: "ml-2 italic", children: [
931
1066
  fieldName(field),
932
1067
  " is worked out by the system, so it cannot be typed in"
933
1068
  ] })
934
1069
  ] });
935
1070
  case "json":
936
- return /* @__PURE__ */ jsx4(JsonControl, { field, value, onChange, disabled, id: inputId });
1071
+ return /* @__PURE__ */ jsx5(JsonControl, { field, value, onChange, disabled, id: inputId });
937
1072
  case "long_text":
938
- return /* @__PURE__ */ jsx4(
1073
+ return /* @__PURE__ */ jsx5(
939
1074
  BasicTextarea,
940
1075
  {
941
1076
  id: inputId,
@@ -948,7 +1083,7 @@ function FieldControl({ field, value, onChange, disabled, id }) {
948
1083
  case "number":
949
1084
  case "currency":
950
1085
  case "percent":
951
- return /* @__PURE__ */ jsx4(
1086
+ return /* @__PURE__ */ jsx5(
952
1087
  BasicInput3,
953
1088
  {
954
1089
  id: inputId,
@@ -961,7 +1096,7 @@ function FieldControl({ field, value, onChange, disabled, id }) {
961
1096
  }
962
1097
  );
963
1098
  case "datetime":
964
- return /* @__PURE__ */ jsx4(
1099
+ return /* @__PURE__ */ jsx5(
965
1100
  BasicInput3,
966
1101
  {
967
1102
  id: inputId,
@@ -974,7 +1109,7 @@ function FieldControl({ field, value, onChange, disabled, id }) {
974
1109
  case "email":
975
1110
  case "phone":
976
1111
  case "url":
977
- return /* @__PURE__ */ jsx4(
1112
+ return /* @__PURE__ */ jsx5(
978
1113
  BasicInput3,
979
1114
  {
980
1115
  id: inputId,
@@ -985,18 +1120,18 @@ function FieldControl({ field, value, onChange, disabled, id }) {
985
1120
  }
986
1121
  );
987
1122
  case "checkbox":
988
- return /* @__PURE__ */ jsx4(CheckboxControl, { field, value, onChange, disabled, id: inputId });
1123
+ return /* @__PURE__ */ jsx5(CheckboxControl, { field, value, onChange, disabled, id: inputId });
989
1124
  case "select":
990
1125
  case "multi_select":
991
- return /* @__PURE__ */ jsx4(OptionControl, { field, value, onChange, disabled, id: inputId });
1126
+ return /* @__PURE__ */ jsx5(OptionControl, { field, value, onChange, disabled, id: inputId });
992
1127
  case "member":
993
- return /* @__PURE__ */ jsx4(PersonPicker, { field, value, onChange, disabled, id: inputId });
1128
+ return /* @__PURE__ */ jsx5(PersonPicker, { field, value, onChange, disabled, id: inputId });
994
1129
  case "relation":
995
1130
  case "attachment":
996
- return /* @__PURE__ */ jsx4(RelationPicker, { field, value, onChange, disabled, id: inputId });
1131
+ return /* @__PURE__ */ jsx5(RelationPicker, { field, value, onChange, disabled, id: inputId });
997
1132
  default:
998
1133
  if (typeof value === "boolean" || field.config?.["kind"] === "boolean") {
999
- return /* @__PURE__ */ jsx4(
1134
+ return /* @__PURE__ */ jsx5(
1000
1135
  Checkbox2,
1001
1136
  {
1002
1137
  id: inputId,
@@ -1006,7 +1141,7 @@ function FieldControl({ field, value, onChange, disabled, id }) {
1006
1141
  }
1007
1142
  );
1008
1143
  }
1009
- return /* @__PURE__ */ jsx4(
1144
+ return /* @__PURE__ */ jsx5(
1010
1145
  BasicInput3,
1011
1146
  {
1012
1147
  id: inputId,
@@ -1019,8 +1154,8 @@ function FieldControl({ field, value, onChange, disabled, id }) {
1019
1154
  }
1020
1155
  function CheckboxControl({ field, value, onChange, disabled, id }) {
1021
1156
  const unanswered = value === null || value === void 0;
1022
- return /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
1023
- /* @__PURE__ */ jsx4(
1157
+ return /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
1158
+ /* @__PURE__ */ jsx5(
1024
1159
  Checkbox2,
1025
1160
  {
1026
1161
  id,
@@ -1030,9 +1165,9 @@ function CheckboxControl({ field, value, onChange, disabled, id }) {
1030
1165
  onCheckedChange: (next) => onChange(next === true)
1031
1166
  }
1032
1167
  ),
1033
- unanswered ? /* @__PURE__ */ jsx4("span", { className: "text-xs italic text-muted-foreground", children: "not answered yet" }) : /* @__PURE__ */ jsxs3(Fragment2, { children: [
1034
- /* @__PURE__ */ jsx4("span", { className: "text-xs text-muted-foreground", children: value === true ? "Yes" : "No" }),
1035
- 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(
1036
1171
  Button3,
1037
1172
  {
1038
1173
  size: "sm",
@@ -1050,8 +1185,8 @@ function JsonControl({ field, value, onChange, disabled, id }) {
1050
1185
  const initial = value === null || value === void 0 ? "" : typeof value === "string" ? value : JSON.stringify(value, null, 2);
1051
1186
  const [text, setText] = useState3(initial);
1052
1187
  const [broken, setBroken] = useState3(null);
1053
- return /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-1", children: [
1054
- /* @__PURE__ */ jsx4(
1188
+ return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-1", children: [
1189
+ /* @__PURE__ */ jsx5(
1055
1190
  BasicTextarea,
1056
1191
  {
1057
1192
  id,
@@ -1071,37 +1206,53 @@ function JsonControl({ field, value, onChange, disabled, id }) {
1071
1206
  onChange(JSON.parse(next));
1072
1207
  setBroken(null);
1073
1208
  } catch (thrown) {
1074
- 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
+ );
1075
1217
  }
1076
1218
  }
1077
1219
  }
1078
1220
  ),
1079
- broken ? /* @__PURE__ */ jsxs3("p", { className: "text-xs text-destructive", children: [
1080
- fieldName(field),
1081
- " is not readable as structured data yet: ",
1082
- broken
1083
- ] }) : 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
1084
1235
  ] });
1085
1236
  }
1086
1237
  function OptionControl({ field, value, onChange, disabled, id }) {
1087
1238
  const client = useOptionalRecordsClient2();
1088
1239
  const [options, setOptions] = useState3(null);
1089
- const [refusal, setRefusal] = useState3(null);
1240
+ const [refusal2, setRefusal] = useState3(null);
1090
1241
  useEffect2(() => {
1091
1242
  if (!client) return;
1092
1243
  let cancelled = false;
1093
1244
  void client.fieldOptions({ field_id: field.id }).then((result) => {
1094
1245
  if (cancelled) return;
1095
1246
  if (result.ok) setOptions(result.data ?? []);
1096
- else setRefusal(refusalLineForAPerson(result.error));
1247
+ else setRefusal(result.error);
1097
1248
  });
1098
1249
  return () => {
1099
1250
  cancelled = true;
1100
1251
  };
1101
1252
  }, [client, field.id]);
1102
1253
  if (!client) {
1103
- return /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-1", children: [
1104
- /* @__PURE__ */ jsx4(
1254
+ return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-1", children: [
1255
+ /* @__PURE__ */ jsx5(
1105
1256
  BasicInput3,
1106
1257
  {
1107
1258
  id,
@@ -1110,19 +1261,20 @@ function OptionControl({ field, value, onChange, disabled, id }) {
1110
1261
  onChange: (e) => onChange(e.target.value === "" ? null : e.target.value)
1111
1262
  }
1112
1263
  ),
1113
- /* @__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." })
1114
1265
  ] });
1115
1266
  }
1116
- if (refusal) return /* @__PURE__ */ jsx4("p", { id, className: "text-xs text-destructive", children: refusal });
1117
- 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" });
1118
1270
  if (options.length === 0) {
1119
- 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.` });
1120
1272
  }
1121
1273
  const titleOf = (option) => recordName(option.data, null, "Unnamed choice");
1122
1274
  if (field.multi) {
1123
1275
  const chosen = options.filter((option) => choiceValuesOf(value).some((v) => isTheChosen(option, v)));
1124
1276
  const picked = new Set(chosen.map((option) => optionKey(option)));
1125
- 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(
1126
1278
  Button3,
1127
1279
  {
1128
1280
  type: "button",
@@ -1141,15 +1293,15 @@ function OptionControl({ field, value, onChange, disabled, id }) {
1141
1293
  )) });
1142
1294
  }
1143
1295
  const current = options.find((option) => isTheChosen(option, value));
1144
- return /* @__PURE__ */ jsxs3(
1296
+ return /* @__PURE__ */ jsxs4(
1145
1297
  Select2,
1146
1298
  {
1147
1299
  value: current ? optionKey(current) : "",
1148
1300
  disabled: disabled ?? false,
1149
1301
  onValueChange: (next) => onChange(next === "" ? null : next),
1150
1302
  children: [
1151
- /* @__PURE__ */ jsx4(SelectTrigger2, { id, size: "sm", children: /* @__PURE__ */ jsx4(SelectValue2, { placeholder: "Choose" }) }),
1152
- /* @__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)) })
1153
1305
  ]
1154
1306
  }
1155
1307
  );
@@ -1159,14 +1311,21 @@ function RecordChip({
1159
1311
  onRemove,
1160
1312
  className
1161
1313
  }) {
1162
- return /* @__PURE__ */ jsxs3(Badge, { variant: "secondary", className: cn3("gap-1 text-[11px]", className), children: [
1163
- title,
1164
- onRemove ? /* @__PURE__ */ jsx4("button", { type: "button", onClick: onRemove, "aria-label": `Remove ${title}`, className: "opacity-60 hover:opacity-100", children: "\xD7" }) : null
1165
- ] });
1314
+ return (
1315
+ // A chip carries a RECORD'S OWN TITLE, which nobody here chose the length of —
1316
+ // "Marisol Okonkwo — 418 Calle Puerto Vallarta, Camarillo" is an ordinary customer
1317
+ // name. So it caps at its container and ends in an ellipsis with the whole name in
1318
+ // its tooltip, rather than being cut mid-word by whatever box it landed in
1319
+ // (lane FIX-14, 2026-09-22).
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
1323
+ ] })
1324
+ );
1166
1325
  }
1167
1326
 
1168
1327
  // src/PersonPicker.tsx
1169
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1328
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1170
1329
  function personFromRow(row) {
1171
1330
  const data = row.document ?? {};
1172
1331
  const userId = data["user_id"];
@@ -1219,7 +1378,7 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1219
1378
  ]);
1220
1379
  setMembers(roster);
1221
1380
  if (records && !records.ok) {
1222
- setError(refusalLineForAPerson(records.error));
1381
+ setError(records.error);
1223
1382
  return;
1224
1383
  }
1225
1384
  setPeople(records ? records.data.rows.map(personFromRow).filter((p) => p.userId !== null) : []);
@@ -1240,10 +1399,10 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1240
1399
  };
1241
1400
  }, [client, personTable, picked.length, people.length]);
1242
1401
  if (!membersPort || !client) {
1243
- 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 });
1244
1403
  }
1245
1404
  if (!personTable) {
1246
- 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: [
1247
1406
  fieldName(field),
1248
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."
1249
1408
  ] });
@@ -1261,7 +1420,7 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1261
1420
  const resolved = await personRecordForMember(client, personTable, people, member);
1262
1421
  setBusy(false);
1263
1422
  if (!resolved.ok) {
1264
- setError(refusalLineForAPerson(resolved.error));
1423
+ setError(resolved.error);
1265
1424
  return;
1266
1425
  }
1267
1426
  if (!people.some((p) => p.id === resolved.data)) {
@@ -1287,8 +1446,8 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1287
1446
  const pickedUserIds = new Set(
1288
1447
  picked.map((recordId) => people.find((p) => p.id === recordId)?.userId).filter(Boolean)
1289
1448
  );
1290
- return /* @__PURE__ */ jsxs4("div", { className: cn4("flex flex-wrap items-center gap-1", className), children: [
1291
- 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(
1292
1451
  RecordChip,
1293
1452
  {
1294
1453
  title: nameOfRecord(recordId),
@@ -1296,8 +1455,8 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1296
1455
  },
1297
1456
  recordId
1298
1457
  )),
1299
- disabled ? null : /* @__PURE__ */ jsxs4(Popover2, { open, onOpenChange: setOpen, children: [
1300
- /* @__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(
1301
1460
  Button4,
1302
1461
  {
1303
1462
  id,
@@ -1308,8 +1467,8 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1308
1467
  children: picked.length === 0 ? "Pick a person" : "Change"
1309
1468
  }
1310
1469
  ) }),
1311
- /* @__PURE__ */ jsxs4(PopoverContent2, { className: "w-64 p-1", align: "start", children: [
1312
- /* @__PURE__ */ jsx5(
1470
+ /* @__PURE__ */ jsxs5(PopoverContent2, { sizing: "content", className: "p-1", align: "start", children: [
1471
+ /* @__PURE__ */ jsx6(
1313
1472
  BasicInput4,
1314
1473
  {
1315
1474
  value: search,
@@ -1318,29 +1477,29 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1318
1477
  className: "mb-1 h-7 text-xs"
1319
1478
  }
1320
1479
  ),
1321
- error ? /* @__PURE__ */ jsx5("p", { className: "p-2 text-xs text-destructive", children: error }) : null,
1322
- members === null ? /* @__PURE__ */ jsx5("p", { className: "p-2 text-xs text-muted-foreground", children: "Reading the members\u2026" }) : null,
1323
- /* @__PURE__ */ jsxs4(ScrollArea2, { className: "max-h-56", children: [
1324
- /* @__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(
1325
1484
  "button",
1326
1485
  {
1327
1486
  type: "button",
1328
1487
  disabled: busy,
1329
1488
  onClick: () => void pick(member),
1330
- className: cn4(
1489
+ className: cn5(
1331
1490
  "w-full truncate rounded px-2 py-1 text-left text-xs hover:bg-muted",
1332
1491
  pickedUserIds.has(member.userId) ? "bg-accent text-accent-foreground" : ""
1333
1492
  ),
1334
1493
  children: [
1335
- /* @__PURE__ */ jsx5("span", { children: memberName(member) }),
1336
- 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: [
1337
1496
  "\xB7 ",
1338
1497
  member.email
1339
1498
  ] }) : null
1340
1499
  ]
1341
1500
  }
1342
1501
  ) }, member.userId)) }),
1343
- 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
1344
1503
  ] })
1345
1504
  ] })
1346
1505
  ] })
@@ -1353,55 +1512,6 @@ import { asWriteConflict } from "@ai-matrx/records/core";
1353
1512
  import { useRecordsClient as useRecordsClient3 } from "@ai-matrx/records/react";
1354
1513
  import { Button as Button5, Checkbox as Checkbox3, cn as cn7 } from "@ai-matrx/design-system";
1355
1514
 
1356
- // src/Refusal.tsx
1357
- import { Alert, AlertDescription, AlertTitle, cn as cn5 } from "@ai-matrx/design-system";
1358
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1359
- function hintIsMachineIdentity(hint) {
1360
- return isMachineIdentity(hint);
1361
- }
1362
- function hintForAPerson(hint) {
1363
- const said = (hint ?? "").trim();
1364
- if (said === "") return null;
1365
- return isMachineIdentity(said) ? null : said;
1366
- }
1367
- function RefusalNotice({
1368
- error,
1369
- className,
1370
- actions
1371
- }) {
1372
- const plain = refusalForAPerson(error);
1373
- return /* @__PURE__ */ jsxs5(
1374
- Alert,
1375
- {
1376
- variant: "destructive",
1377
- className: cn5("text-xs", className),
1378
- ...plain.forEngineers ? { title: plain.forEngineers } : {},
1379
- children: [
1380
- /* @__PURE__ */ jsx6(AlertTitle, { className: "text-xs font-medium", children: plain.title }),
1381
- /* @__PURE__ */ jsxs5(AlertDescription, { className: "space-y-1 text-xs", children: [
1382
- plain.sentence.replace(/\.$/, "") === plain.title ? null : /* @__PURE__ */ jsx6("p", { children: plain.sentence }),
1383
- /* @__PURE__ */ jsx6("p", { className: "opacity-80", children: plain.remedy }),
1384
- plain.forEngineers ? /* @__PURE__ */ jsx6(
1385
- "p",
1386
- {
1387
- className: "sr-only",
1388
- "aria-hidden": "true",
1389
- "data-for-engineers": "",
1390
- ...error.sqlstate ? { "data-sqlstate": error.sqlstate } : {},
1391
- children: plain.forEngineers
1392
- }
1393
- ) : null,
1394
- actions
1395
- ] })
1396
- ]
1397
- }
1398
- );
1399
- }
1400
- function RefusalLine({ error, className }) {
1401
- const plain = refusalForAPerson(error);
1402
- return /* @__PURE__ */ jsx6("p", { className: cn5("text-xs text-destructive", className), title: plain.forEngineers || void 0, children: refusalLineForAPerson(error) });
1403
- }
1404
-
1405
1515
  // src/whoChangedSource.tsx
1406
1516
  import { createContext as createContext2, useCallback as useCallback2, useContext as useContext2, useEffect as useEffect4, useMemo as useMemo5, useRef, useState as useState5 } from "react";
1407
1517
  import { useRecordsClient } from "@ai-matrx/records/react";
@@ -2240,7 +2350,7 @@ function GridCell({
2240
2350
  );
2241
2351
  const value = editing ? editing.valueFor(row, field) : row.document?.[field.key];
2242
2352
  const state = editing ? editing.stateOf(row.id, field.key) : null;
2243
- const refusal = editing ? editing.refusalOf(row.id, field.key) : null;
2353
+ const refusal2 = editing ? editing.refusalOf(row.id, field.key) : null;
2244
2354
  const editable = Boolean(editing) && canWrite && fieldIsEditable(field);
2245
2355
  if (open && editing) {
2246
2356
  return /* @__PURE__ */ jsx10(
@@ -2269,8 +2379,8 @@ function GridCell({
2269
2379
  }
2270
2380
  ),
2271
2381
  answered ? null : /* @__PURE__ */ jsx10("span", { className: "text-xs text-muted-foreground", title: "nobody has answered this yet", children: "\u2014" }),
2272
- refusal ? /* @__PURE__ */ jsxs6("span", { className: "flex items-center gap-1 rounded border border-destructive/40 px-1", children: [
2273
- /* @__PURE__ */ jsx10(RefusalLine, { error: refusal.error }),
2382
+ refusal2 ? /* @__PURE__ */ jsxs6("span", { className: "flex items-center gap-1 rounded border border-destructive/40 px-1", children: [
2383
+ /* @__PURE__ */ jsx10(RefusalLine, { error: refusal2.error }),
2274
2384
  /* @__PURE__ */ jsx10(Button5, { size: "sm", variant: "outline", onClick: () => editing?.retry(row.id, field.key), children: "Try again" })
2275
2385
  ] }) : null
2276
2386
  ] });
@@ -2345,11 +2455,11 @@ function GridCell({
2345
2455
  ),
2346
2456
  onAskWhoChanged ? /* @__PURE__ */ jsx10(WhoBadge, { field, row, onAskWhoChanged }) : null,
2347
2457
  state === "saving" ? /* @__PURE__ */ jsx10("span", { className: "px-1 text-[10px] text-muted-foreground", children: "Saving\u2026" }) : null,
2348
- refusal ? /* @__PURE__ */ jsxs6("span", { className: "flex flex-col gap-0.5 rounded border border-destructive/40 p-1", children: [
2349
- /* @__PURE__ */ jsx10(RefusalLine, { error: refusal.error }),
2350
- refusal.conflict ? /* @__PURE__ */ jsxs6("span", { className: "text-[11px]", children: [
2458
+ refusal2 ? /* @__PURE__ */ jsxs6("span", { className: "flex flex-col gap-0.5 rounded border border-destructive/40 p-1", children: [
2459
+ /* @__PURE__ */ jsx10(RefusalLine, { error: refusal2.error }),
2460
+ refusal2.conflict ? /* @__PURE__ */ jsxs6("span", { className: "text-[11px]", children: [
2351
2461
  "Theirs: ",
2352
- /* @__PURE__ */ jsx10("strong", { children: formatTheirs(refusal.conflict, field.key) })
2462
+ /* @__PURE__ */ jsx10("strong", { children: formatTheirs(refusal2.conflict, field.key) })
2353
2463
  ] }) : null,
2354
2464
  /* @__PURE__ */ jsxs6("span", { className: "flex gap-1", children: [
2355
2465
  /* @__PURE__ */ jsx10(Button5, { size: "sm", variant: "outline", onClick: () => editing?.retry(row.id, field.key), children: "Try again" }),
@@ -2650,7 +2760,7 @@ var colorFromTheValue = (_fieldKey, value) => {
2650
2760
  };
2651
2761
 
2652
2762
  // src/Grid.tsx
2653
- import { Button as Button8, Skeleton as Skeleton2, cn as cn9 } from "@ai-matrx/design-system";
2763
+ import { Button as Button8, ConfirmDialog, Skeleton as Skeleton2, cn as cn9 } from "@ai-matrx/design-system";
2654
2764
 
2655
2765
  // src/Enrich.tsx
2656
2766
  import { useCallback as useCallback5, useEffect as useEffect7, useMemo as useMemo8, useState as useState8 } from "react";
@@ -3072,7 +3182,20 @@ function EnrichPanel({ tableId, fieldId, className }) {
3072
3182
  " in one go."
3073
3183
  ] }) : null
3074
3184
  ] }) : null,
3075
- outcome ? /* @__PURE__ */ jsx11("p", { className: cn8("text-xs", outcome.ok ? "text-muted-foreground" : "text-destructive"), children: outcome.message }) : null,
3185
+ outcome ? outcome.ok ? /* @__PURE__ */ jsx11("p", { className: "text-xs text-muted-foreground", children: outcome.message }) : (
3186
+ // A run that did not happen is a refusal, with a heading and a remedy,
3187
+ // never the host's words in red (lane REFUSAL-SWEEP).
3188
+ /* @__PURE__ */ jsx11(
3189
+ RefusalLine,
3190
+ {
3191
+ error: refusal(
3192
+ "internal",
3193
+ outcome.message,
3194
+ "Nothing was filled in. Try the run again in a moment."
3195
+ )
3196
+ }
3197
+ )
3198
+ ) : null,
3076
3199
  field ? null : null
3077
3200
  ] }, row.field_id);
3078
3201
  }) });
@@ -3117,15 +3240,15 @@ function PastePreview({
3117
3240
  /* @__PURE__ */ jsx12(PlanTable, { plan }),
3118
3241
  plan.refusals.length > 0 ? /* @__PURE__ */ jsxs8("div", { "data-matrx-paste-refusals": true, className: "flex flex-col gap-1", children: [
3119
3242
  /* @__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:` }),
3120
- /* @__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: [
3243
+ /* @__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: [
3121
3244
  /* @__PURE__ */ jsxs8("span", { className: "font-medium", children: [
3122
3245
  "Row ",
3123
- refusal.fromLine,
3246
+ refusal2.fromLine,
3124
3247
  ", ",
3125
- refusal.column
3248
+ refusal2.column
3126
3249
  ] }),
3127
3250
  " \u2014 ",
3128
- refusal.why
3251
+ refusal2.why
3129
3252
  ] }, index)) }),
3130
3253
  plan.refusals.length > 12 ? /* @__PURE__ */ jsxs8("p", { className: "text-xs text-muted-foreground", children: [
3131
3254
  plan.refusals.length - 12,
@@ -3153,7 +3276,13 @@ function PastePreview({
3153
3276
  setAdding(group.fieldKey);
3154
3277
  void onAddOptions(group.fieldKey, group.words).catch(
3155
3278
  (err) => setAddFailed(
3156
- `Those options were not added: ${err instanceof Error ? err.message : String(err)}`
3279
+ // The error's own text is an engineer's; the person
3280
+ // reads what did not happen and what to do.
3281
+ asRefusal(
3282
+ err,
3283
+ "Those options were not added, so the pasted words still do not match a choice.",
3284
+ "Try adding them again, or add them from the column's settings."
3285
+ )
3157
3286
  )
3158
3287
  ).finally(() => setAdding(null));
3159
3288
  },
@@ -3161,7 +3290,7 @@ function PastePreview({
3161
3290
  }
3162
3291
  )
3163
3292
  ] }, group.fieldKey)),
3164
- addFailed ? /* @__PURE__ */ jsx12("p", { className: "text-xs text-destructive", children: addFailed }) : null
3293
+ addFailed ? /* @__PURE__ */ jsx12(RefusalLine, { error: addFailed }) : null
3165
3294
  ] }) : null,
3166
3295
  plan.pastRightEdge > 0 ? /* @__PURE__ */ jsxs8("p", { className: "text-xs text-muted-foreground", children: [
3167
3296
  plan.pastRightEdge,
@@ -3341,19 +3470,19 @@ function planPastedBlock(args) {
3341
3470
  accepted[key] = judged.value;
3342
3471
  }
3343
3472
  const predicted = predictValueRefusals(args.fields, accepted);
3344
- for (const refusal of predicted) {
3345
- const key = refusal.field_key;
3473
+ for (const refusal2 of predicted) {
3474
+ const key = refusal2.field_key;
3346
3475
  if (key === void 0) continue;
3347
3476
  const cell = built.find((b) => b.key === key && b.refusal === null);
3348
3477
  if (!cell) continue;
3349
- cell.refusal = refusal.message;
3478
+ cell.refusal = refusal2.message;
3350
3479
  cell.value = null;
3351
3480
  delete accepted[key];
3352
3481
  refusals.push({
3353
3482
  fromLine,
3354
3483
  column: fieldName(byKey.get(key) ?? { key }),
3355
3484
  raw: cell.raw,
3356
- why: refusal.message
3485
+ why: refusal2.message
3357
3486
  });
3358
3487
  }
3359
3488
  cells += Object.keys(accepted).length;
@@ -3456,6 +3585,9 @@ function Grid({
3456
3585
  [client, host, refreshEnrich]
3457
3586
  );
3458
3587
  const [pastePlan, setPastePlan] = useState10(null);
3588
+ const [deleting, setDeleting] = useState10(null);
3589
+ const [deleteBusy, setDeleteBusy] = useState10(false);
3590
+ const [archived, setArchived] = useState10(null);
3459
3591
  const choiceOptions = useRef4(/* @__PURE__ */ new Map());
3460
3592
  const loadChoiceOptions = useCallback6(
3461
3593
  async (fieldList) => {
@@ -3743,6 +3875,21 @@ function Grid({
3743
3875
  /* @__PURE__ */ jsx13("div", { className: "ml-auto flex items-center gap-1", children: toolbarActions })
3744
3876
  ] }),
3745
3877
  records.error ? /* @__PURE__ */ jsx13(RefusalNotice, { error: records.error }) : null,
3878
+ archived ? /* @__PURE__ */ jsxs9(
3879
+ "p",
3880
+ {
3881
+ "data-records-archived-notice": "",
3882
+ role: "status",
3883
+ className: "flex items-center gap-2 rounded border border-border bg-muted/40 px-2 py-1 text-xs",
3884
+ children: [
3885
+ /* @__PURE__ */ jsxs9("span", { children: [
3886
+ archived,
3887
+ " is archived. Nothing was destroyed \u2014 it is in this table\u2019s Archived items, where Restore brings it back."
3888
+ ] }),
3889
+ /* @__PURE__ */ jsx13(Button8, { size: "sm", variant: "ghost", className: "ml-auto h-6", onClick: () => setArchived(null), children: "Dismiss" })
3890
+ ]
3891
+ }
3892
+ ) : null,
3746
3893
  editing.rowError ? /* @__PURE__ */ jsx13(
3747
3894
  RefusalNotice,
3748
3895
  {
@@ -3859,11 +4006,13 @@ function Grid({
3859
4006
  }
3860
4007
  ) : null,
3861
4008
  canWrite && mayWriteRow(row.id) ? /* @__PURE__ */ jsx13(
3862
- DeleteRecordButton,
4009
+ Button8,
3863
4010
  {
3864
- row,
3865
- retentionDays: table.data?.retention_days ?? null,
3866
- onDelete: () => editing.remove(row.id)
4011
+ size: "sm",
4012
+ variant: "ghost",
4013
+ title: "Delete this record",
4014
+ onClick: () => setDeleting(row),
4015
+ children: "Delete"
3867
4016
  }
3868
4017
  ) : null
3869
4018
  ] })
@@ -3906,7 +4055,32 @@ function Grid({
3906
4055
  onPage: setPage
3907
4056
  }
3908
4057
  )
3909
- ] })
4058
+ ] }),
4059
+ /* @__PURE__ */ jsx13(
4060
+ ConfirmDialog,
4061
+ {
4062
+ open: deleting !== null,
4063
+ onOpenChange: (next) => {
4064
+ if (!next && !deleteBusy) setDeleting(null);
4065
+ },
4066
+ variant: "destructive",
4067
+ title: `Delete ${deleting ? recordNameIn(table.data, deleting.document ?? {}) : "this record"}?`,
4068
+ description: table.data?.retention_days ? `It leaves the table and waits in Archived items. Nothing is destroyed: Restore brings it back, for ${table.data.retention_days} days.` : "It leaves the table and waits in Archived items. Nothing is destroyed: Restore brings it back.",
4069
+ confirmLabel: "Delete",
4070
+ cancelLabel: "Keep",
4071
+ busy: deleteBusy,
4072
+ onConfirm: async () => {
4073
+ const row = deleting;
4074
+ if (!row) return;
4075
+ const name = recordNameIn(table.data, row.document ?? {});
4076
+ setDeleteBusy(true);
4077
+ const failed = await editing.remove(row.id);
4078
+ setDeleteBusy(false);
4079
+ setDeleting(null);
4080
+ if (!failed) setArchived(name);
4081
+ }
4082
+ }
4083
+ )
3910
4084
  ] }) });
3911
4085
  }
3912
4086
  function columnLabel(fields, columnId) {
@@ -3926,42 +4100,6 @@ function AddFieldButton({ onClick }) {
3926
4100
  }
3927
4101
  );
3928
4102
  }
3929
- function DeleteRecordButton({
3930
- row,
3931
- retentionDays,
3932
- onDelete
3933
- }) {
3934
- const [asking, setAsking] = useState10(false);
3935
- if (!asking) {
3936
- return /* @__PURE__ */ jsx13(
3937
- Button8,
3938
- {
3939
- size: "sm",
3940
- variant: "ghost",
3941
- title: "Delete this record",
3942
- onClick: () => setAsking(true),
3943
- children: "Delete"
3944
- }
3945
- );
3946
- }
3947
- return /* @__PURE__ */ jsxs9("span", { className: "flex items-center gap-1 text-[11px]", children: [
3948
- /* @__PURE__ */ jsx13("span", { className: "text-muted-foreground", children: retentionDays ? `Removes it from the table. Restorable for ${retentionDays} days.` : "Removes it from the table. It can be restored from the record's history." }),
3949
- /* @__PURE__ */ jsx13(
3950
- Button8,
3951
- {
3952
- size: "sm",
3953
- variant: "destructive",
3954
- onClick: () => {
3955
- setAsking(false);
3956
- void onDelete();
3957
- },
3958
- children: "Delete"
3959
- }
3960
- ),
3961
- /* @__PURE__ */ jsx13(Button8, { size: "sm", variant: "ghost", onClick: () => setAsking(false), children: "Keep" }),
3962
- /* @__PURE__ */ jsx13("span", { className: "sr-only", children: row.id })
3963
- ] });
3964
- }
3965
4103
  function Pager({
3966
4104
  page,
3967
4105
  pageSize,
@@ -4108,18 +4246,24 @@ function ExportMenu({ tableId, rows, label, className }) {
4108
4246
  size: "sm",
4109
4247
  variant: "outline",
4110
4248
  onClick: () => {
4249
+ const failed = (thrown) => setFailure(
4250
+ refusalFromThrown(
4251
+ thrown,
4252
+ "The spreadsheet could not be written.",
4253
+ "The CSV beside this button carries the same rows."
4254
+ )
4255
+ );
4256
+ setFailure(null);
4111
4257
  try {
4112
- void exportXlsx(fields.data ?? [], exported(), name).then(
4258
+ exportXlsx(fields.data ?? [], exported(), name).then(
4113
4259
  (bytes) => download(
4114
4260
  `${name}.xlsx`,
4115
4261
  "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
4116
4262
  bytes
4117
4263
  )
4118
- );
4264
+ ).catch(failed);
4119
4265
  } catch (thrown) {
4120
- setFailure(
4121
- `The spreadsheet could not be written (${thrown instanceof Error ? thrown.message : String(thrown)}). The CSV beside this button carries the same rows.`
4122
- );
4266
+ failed(thrown);
4123
4267
  }
4124
4268
  },
4125
4269
  children: "XLSX"
@@ -4135,7 +4279,7 @@ function ExportMenu({ tableId, rows, label, className }) {
4135
4279
  human: () => exportCsv(fields.data ?? [], exported())
4136
4280
  }
4137
4281
  ),
4138
- failure ? /* @__PURE__ */ jsx14("p", { className: "text-xs text-destructive", children: failure }) : null
4282
+ failure ? /* @__PURE__ */ jsx14(RefusalLine, { error: failure }) : null
4139
4283
  ] });
4140
4284
  }
4141
4285
 
@@ -4234,7 +4378,7 @@ function FilePicker({
4234
4378
  }
4235
4379
 
4236
4380
  // src/ImportWizard.tsx
4237
- import { useCallback as useCallback8, useMemo as useMemo10, useState as useState13 } from "react";
4381
+ import { useCallback as useCallback8, useEffect as useEffect8, useMemo as useMemo10, useState as useState13 } from "react";
4238
4382
  import { useFields as useFields5, useRecordsClient as useRecordsClient6, useTable as useTable3 } from "@ai-matrx/records/react";
4239
4383
  import {
4240
4384
  importCsv,
@@ -4251,6 +4395,45 @@ import {
4251
4395
  Separator as Separator2,
4252
4396
  cn as cn12
4253
4397
  } from "@ai-matrx/design-system";
4398
+
4399
+ // src/importRuleCheck.ts
4400
+ import { predictValueRefusals as predictValueRefusals2 } from "@ai-matrx/records/core";
4401
+ function checkImportRules(fields, rows, mapping) {
4402
+ const byKey = new Map(fields.map((f) => [f.key, f]));
4403
+ const verdicts = [];
4404
+ for (const [header, key] of Object.entries(mapping)) {
4405
+ const field = byKey.get(key);
4406
+ if (!field) continue;
4407
+ let refused = 0;
4408
+ let judged = 0;
4409
+ let first = null;
4410
+ rows.forEach((row, i) => {
4411
+ const raw = String(row[header] ?? "").trim();
4412
+ if (raw === "") return;
4413
+ judged += 1;
4414
+ const meant = valueFromPastedText(field, raw);
4415
+ let why = null;
4416
+ if ("refusal" in meant) why = meant.refusal;
4417
+ else {
4418
+ const predicted = predictValueRefusals2([field], { [key]: meant.value }).filter(
4419
+ (p) => p.field_key === void 0 || p.field_key === key
4420
+ );
4421
+ if (predicted.length > 0) why = predicted[0].message;
4422
+ }
4423
+ if (why !== null) {
4424
+ refused += 1;
4425
+ if (!first) first = { why, line: i + 1, raw };
4426
+ }
4427
+ });
4428
+ if (refused > 0 && first) {
4429
+ const f = first;
4430
+ verdicts.push({ header, field, refused, judged, firstWhy: f.why, firstLine: f.line, firstRaw: f.raw });
4431
+ }
4432
+ }
4433
+ return verdicts;
4434
+ }
4435
+
4436
+ // src/ImportWizard.tsx
4254
4437
  import { Fragment as Fragment7, jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
4255
4438
  var BATCH = 250;
4256
4439
  var SAMPLES = 40;
@@ -4273,7 +4456,7 @@ async function sha256(bytes) {
4273
4456
  function typeWord(t) {
4274
4457
  return fieldTypeChoice(t)?.label ?? (t === "relation" ? "Points at another table" : t);
4275
4458
  }
4276
- function ImportWizard({ tableId, onDone, className }) {
4459
+ function ImportWizard({ tableId, onDone, onWrote, className }) {
4277
4460
  const client = useRecordsClient6();
4278
4461
  const table = useTable3(tableId);
4279
4462
  const fields = useFields5(tableId);
@@ -4299,6 +4482,50 @@ function ImportWizard({ tableId, onDone, className }) {
4299
4482
  const [columnsAdded, setColumnsAdded] = useState13(null);
4300
4483
  const [show, setShow] = useState13("all");
4301
4484
  const [ledger, setLedger] = useState13(null);
4485
+ const [runs, setRuns] = useState13(null);
4486
+ const [openRun, setOpenRun] = useState13(null);
4487
+ const [runRows, setRunRows] = useState13(null);
4488
+ const [runProblem, setRunProblem] = useState13(null);
4489
+ const [runsProblem, setRunsProblem] = useState13(null);
4490
+ const loadRuns = useCallback8(async () => {
4491
+ const answered = await client.imports({ table_id: tableId, limit: 10 });
4492
+ if (answered.ok) {
4493
+ setRuns(answered.data);
4494
+ setRunsProblem(null);
4495
+ } else setRunsProblem(answered.error);
4496
+ }, [client, tableId]);
4497
+ useEffect8(() => {
4498
+ void loadRuns();
4499
+ }, [loadRuns]);
4500
+ const openRefusals = useCallback8(
4501
+ async (importId) => {
4502
+ setRunProblem(null);
4503
+ if (openRun === importId) {
4504
+ setOpenRun(null);
4505
+ setRunRows(null);
4506
+ return;
4507
+ }
4508
+ setOpenRun(importId);
4509
+ setRunRows(null);
4510
+ const answered = await client.importReport({
4511
+ import_id: importId,
4512
+ state: "refused",
4513
+ limit: 200
4514
+ });
4515
+ if (!answered.ok) {
4516
+ setRunProblem(answered.error);
4517
+ return;
4518
+ }
4519
+ setRunRows({
4520
+ import_id: answered.data.import_id,
4521
+ rows: answered.data.rows,
4522
+ kept: answered.data.kept,
4523
+ total: answered.data.total,
4524
+ note: answered.data.note
4525
+ });
4526
+ },
4527
+ [client, openRun]
4528
+ );
4302
4529
  const declared = fields.data ?? [];
4303
4530
  const take = useCallback8(
4304
4531
  async (file) => {
@@ -4327,7 +4554,7 @@ function ImportWizard({ tableId, onDone, className }) {
4327
4554
  }));
4328
4555
  const answered = await client.importPlan({ table_id: tableId, columns });
4329
4556
  if (!answered.ok) {
4330
- setProblem(answered.error.message);
4557
+ setProblem(answered.error);
4331
4558
  setPhase("waiting");
4332
4559
  return;
4333
4560
  }
@@ -4344,7 +4571,14 @@ function ImportWizard({ tableId, onDone, className }) {
4344
4571
  setMapping(next);
4345
4572
  setPhase("ready");
4346
4573
  } catch (thrown) {
4347
- setProblem(`That file could not be read: ${thrown instanceof Error ? thrown.message : String(thrown)}`);
4574
+ setProblem(
4575
+ refusalFromThrown(
4576
+ thrown,
4577
+ "That file could not be read, so nothing was imported.",
4578
+ "Save it again as CSV or Excel and pick it again.",
4579
+ "invalid_argument"
4580
+ )
4581
+ );
4348
4582
  setPhase("waiting");
4349
4583
  }
4350
4584
  },
@@ -4361,7 +4595,7 @@ function ImportWizard({ tableId, onDone, className }) {
4361
4595
  setPhase("declaring");
4362
4596
  const made = await client.importDeclareColumns({ table_id: tableId, rows, mapping });
4363
4597
  if (!made.ok) {
4364
- setProblem(made.error.message);
4598
+ setProblem(made.error);
4365
4599
  setPhase("ready");
4366
4600
  return;
4367
4601
  }
@@ -4380,7 +4614,7 @@ function ImportWizard({ tableId, onDone, className }) {
4380
4614
  policy: { on_duplicate: onDuplicate, unmapped }
4381
4615
  });
4382
4616
  if (!opened.ok) {
4383
- setProblem(opened.error.message);
4617
+ setProblem(opened.error);
4384
4618
  setPhase("ready");
4385
4619
  return;
4386
4620
  }
@@ -4399,7 +4633,7 @@ function ImportWizard({ tableId, onDone, className }) {
4399
4633
  const batch = rows.slice(at, at + take2);
4400
4634
  const answered = await client.importRows({ import_id: importId, rows: batch, mapping });
4401
4635
  if (!answered.ok) {
4402
- setProblem(answered.error.message);
4636
+ setProblem(answered.error);
4403
4637
  setOutcomes([...collected]);
4404
4638
  setLedger({ ...told, final: false });
4405
4639
  setPhase("ready");
@@ -4418,7 +4652,7 @@ function ImportWizard({ tableId, onDone, className }) {
4418
4652
  }
4419
4653
  const finished = await client.importFinish({ import_id: importId, unmapped });
4420
4654
  if (!finished.ok) {
4421
- setProblem(finished.error.message);
4655
+ setProblem(finished.error);
4422
4656
  setPhase("done");
4423
4657
  return;
4424
4658
  }
@@ -4432,8 +4666,9 @@ function ImportWizard({ tableId, onDone, className }) {
4432
4666
  final: true
4433
4667
  });
4434
4668
  setPhase("done");
4435
- onDone?.(finished.data.rows_written);
4436
- }, [client, dedupeKey, fileBytes, fileHash, fileName, format, mapping, onDuplicate, onDone, plan, rows, tableId, unmapped]);
4669
+ onWrote?.(finished.data.rows_written);
4670
+ void loadRuns();
4671
+ }, [client, dedupeKey, fileBytes, fileHash, fileName, format, loadRuns, mapping, onDuplicate, onWrote, plan, rows, tableId, unmapped]);
4437
4672
  const counts = useMemo10(() => {
4438
4673
  let landed = 0;
4439
4674
  let duplicate = 0;
@@ -4455,6 +4690,11 @@ function ImportWizard({ tableId, onDone, className }) {
4455
4690
  const titleMissing = Boolean(titleKey) && titleMappedTo === null;
4456
4691
  const titleFieldLabel = (titleKey ? plan?.fields.find((f) => f.key === titleKey)?.label : null) || (titleKey ? declared.find((f) => f.key === titleKey)?.label : null) || "the name column";
4457
4692
  const busy = phase === "reading" || phase === "planning" || phase === "declaring" || phase === "writing";
4693
+ const ruleVerdicts = useMemo10(() => checkImportRules(declared, rows, mapping), [declared, rows, mapping]);
4694
+ const ruleVerdictOf = useMemo10(() => new Map(ruleVerdicts.map((v) => [v.header, v])), [ruleVerdicts]);
4695
+ const refusedByRules = ruleVerdicts.reduce((n, v) => n + v.refused, 0);
4696
+ const [goAheadAnyway, setGoAheadAnyway] = useState13(false);
4697
+ useEffect8(() => setGoAheadAnyway(false), [ruleVerdicts]);
4458
4698
  return /* @__PURE__ */ jsxs12("div", { className: cn12("flex min-w-0 flex-col gap-2 text-xs", className), children: [
4459
4699
  /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-2", children: [
4460
4700
  /* @__PURE__ */ jsx16("span", { className: "font-medium", children: "Import" }),
@@ -4482,7 +4722,7 @@ function ImportWizard({ tableId, onDone, className }) {
4482
4722
  } : {}
4483
4723
  }
4484
4724
  ) : null,
4485
- problem ? /* @__PURE__ */ jsx16("p", { className: "text-destructive", children: problem }) : null,
4725
+ problem ? /* @__PURE__ */ jsx16(RefusalNotice, { error: problem }) : null,
4486
4726
  phase === "reading" ? /* @__PURE__ */ jsx16("p", { className: "text-muted-foreground", children: "Reading the file\u2026" }) : null,
4487
4727
  phase === "planning" ? /* @__PURE__ */ jsx16("p", { className: "text-muted-foreground", children: "Working out what each column is\u2026" }) : null,
4488
4728
  plan && parsed ? /* @__PURE__ */ jsxs12(Fragment7, { children: [
@@ -4579,7 +4819,28 @@ function ImportWizard({ tableId, onDone, className }) {
4579
4819
  column.unit ? /* @__PURE__ */ jsx16("span", { className: "ml-1 opacity-60", children: column.unit }) : null,
4580
4820
  /* @__PURE__ */ jsx16("p", { className: "mt-0.5 text-muted-foreground", children: column.why }),
4581
4821
  column.ambiguous ? /* @__PURE__ */ jsx16("p", { className: "text-destructive", children: "Check a date you know before you run this." }) : null,
4582
- column.collides_with ? /* @__PURE__ */ jsx16("p", { className: "text-destructive", children: "Two columns in this file would make the same column." }) : null
4822
+ column.collides_with ? /* @__PURE__ */ jsx16("p", { className: "text-destructive", children: "Two columns in this file would make the same column." }) : null,
4823
+ ruleVerdictOf.get(column.header) ? (() => {
4824
+ const v = ruleVerdictOf.get(column.header);
4825
+ return /* @__PURE__ */ jsxs12("div", { "data-import-rule-refusal": column.header, className: "mt-1", children: [
4826
+ /* @__PURE__ */ jsx16(
4827
+ RefusalLine,
4828
+ {
4829
+ error: refusal(
4830
+ "refused_by_rule",
4831
+ `${v.refused} of ${v.judged} row${v.judged === 1 ? "" : "s"} would be refused by ${fieldName(v.field)}'s rules. ${v.firstWhy}`,
4832
+ "Send this column somewhere else, fix those rows in your file, or import the rest and they are listed afterwards."
4833
+ )
4834
+ }
4835
+ ),
4836
+ /* @__PURE__ */ jsxs12("p", { className: "text-muted-foreground", "data-import-rule-sample": "", children: [
4837
+ "Line ",
4838
+ v.firstLine,
4839
+ " in your file: ",
4840
+ v.firstRaw
4841
+ ] })
4842
+ ] });
4843
+ })() : null
4583
4844
  ] }),
4584
4845
  /* @__PURE__ */ jsx16("span", { className: "truncate pt-1.5 text-muted-foreground", children: (column.samples ?? []).slice(0, 3).map(String).join(" \xB7 ") })
4585
4846
  ]
@@ -4643,12 +4904,34 @@ function ImportWizard({ tableId, onDone, className }) {
4643
4904
  )
4644
4905
  ] })
4645
4906
  ] }),
4907
+ ruleVerdicts.length > 0 ? /* @__PURE__ */ jsx16(
4908
+ RefusalNotice,
4909
+ {
4910
+ error: refusal(
4911
+ "refused_by_rule",
4912
+ `${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.`,
4913
+ "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."
4914
+ ),
4915
+ actions: /* @__PURE__ */ jsxs12("label", { className: "mt-1 flex items-center gap-1.5", "data-import-go-ahead": "", children: [
4916
+ /* @__PURE__ */ jsx16(
4917
+ "input",
4918
+ {
4919
+ type: "checkbox",
4920
+ checked: goAheadAnyway,
4921
+ disabled: busy,
4922
+ onChange: (e) => setGoAheadAnyway(e.target.checked)
4923
+ }
4924
+ ),
4925
+ "Import the rest anyway"
4926
+ ] })
4927
+ }
4928
+ ) : null,
4646
4929
  /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-2", children: [
4647
4930
  /* @__PURE__ */ jsx16(
4648
4931
  Button11,
4649
4932
  {
4650
4933
  size: "sm",
4651
- disabled: busy || !rights.write || rows.length === 0 || titleMissing,
4934
+ disabled: busy || !rights.write || rows.length === 0 || titleMissing || ruleVerdicts.length > 0 && !goAheadAnyway,
4652
4935
  onClick: () => void run(),
4653
4936
  children: phase === "declaring" ? "Adding the columns\u2026" : phase === "writing" ? `Writing\u2026 ${progress} of ${rows.length}` : `Import ${rows.length} row${rows.length === 1 ? "" : "s"}`
4654
4937
  }
@@ -4703,14 +4986,95 @@ function ImportWizard({ tableId, onDone, className }) {
4703
4986
  ] }) }),
4704
4987
  /* @__PURE__ */ jsx16("tbody", { children: interesting.slice(0, 200).map((o) => /* @__PURE__ */ jsxs12("tr", { className: "border-t align-top", children: [
4705
4988
  /* @__PURE__ */ jsx16("td", { className: "px-2 py-1", children: o.row }),
4706
- /* @__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" }),
4989
+ /* @__PURE__ */ jsx16("td", { className: "px-2 py-1", children: o.outcome === "refused" ? /* @__PURE__ */ jsx16(
4990
+ RefusalLine,
4991
+ {
4992
+ error: refusal("refused_by_rule", o.reason, "Fix this line in your file and import it again.")
4993
+ }
4994
+ ) : o.outcome === "duplicate" ? /* @__PURE__ */ jsx16("span", { className: "text-muted-foreground", children: o.reason }) : "written" }),
4707
4995
  /* @__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 ") : "" })
4708
4996
  ] }, `${o.outcome}-${o.row}`)) })
4709
4997
  ] }) }) : null,
4710
4998
  proposals.length > 0 ? /* @__PURE__ */ jsx16("ul", { className: "flex flex-col gap-0.5", children: proposals.map((p) => /* @__PURE__ */ jsxs12("li", { className: "text-muted-foreground", children: [
4711
4999
  /* @__PURE__ */ jsx16("span", { className: "font-medium text-foreground", children: p.column }),
4712
5000
  p.state === "waiting" ? " is waiting in the approvals inbox." : p.state === "accepted" ? " was added as a column." : p.state === "ignored" ? " was left out." : p.state === "refused" ? ` could not be added: ${p.reason ?? ""}` : ` \u2014 ${p.state}`
4713
- ] }, p.column)) }) : null
5001
+ ] }, p.column)) }) : null,
5002
+ phase === "done" ? /* @__PURE__ */ jsxs12("div", { className: "flex flex-wrap items-center gap-2", children: [
5003
+ /* @__PURE__ */ jsx16(Button11, { size: "sm", onClick: () => onDone?.(ledger?.written ?? 0), children: "Done" }),
5004
+ (ledger?.refused ?? 0) > 0 ? /* @__PURE__ */ jsxs12("span", { className: "text-muted-foreground", children: [
5005
+ "The ",
5006
+ ledger?.refused,
5007
+ " row",
5008
+ ledger?.refused === 1 ? "" : "s",
5009
+ " that ",
5010
+ ledger?.refused === 1 ? "was" : "were",
5011
+ " refused",
5012
+ " ",
5013
+ "are kept with this import \u2014 open them again from the list of imports below, with the store's reason and the line from your file on each."
5014
+ ] }) : null
5015
+ ] }) : null
5016
+ ] }) : null,
5017
+ runsProblem ? /* @__PURE__ */ jsx16(RefusalNotice, { error: runsProblem }) : null,
5018
+ runs && runs.length > 0 ? /* @__PURE__ */ jsxs12(Fragment7, { children: [
5019
+ /* @__PURE__ */ jsx16(Separator2, {}),
5020
+ /* @__PURE__ */ jsx16("p", { className: "font-medium", children: "Imports into this table" }),
5021
+ /* @__PURE__ */ jsx16("ul", { className: "flex flex-col gap-1", children: runs.map((r) => /* @__PURE__ */ jsxs12("li", { className: "flex flex-col gap-1", children: [
5022
+ /* @__PURE__ */ jsxs12("div", { className: "flex flex-wrap items-center gap-2", children: [
5023
+ /* @__PURE__ */ jsx16("span", { className: "font-medium", children: r.source_name ?? "a file" }),
5024
+ /* @__PURE__ */ jsxs12("span", { className: "text-muted-foreground", children: [
5025
+ new Date(r.opened_at).toLocaleString(),
5026
+ " \xB7 ",
5027
+ r.rows_written,
5028
+ " landed \xB7",
5029
+ " ",
5030
+ r.rows_duplicate,
5031
+ " already here \xB7 ",
5032
+ r.rows_refused,
5033
+ " refused, of ",
5034
+ r.rows_seen,
5035
+ " row",
5036
+ r.rows_seen === 1 ? "" : "s",
5037
+ " offered"
5038
+ ] }),
5039
+ r.rows_refused > 0 ? /* @__PURE__ */ jsx16(
5040
+ "button",
5041
+ {
5042
+ type: "button",
5043
+ className: "underline underline-offset-2",
5044
+ "aria-expanded": openRun === r.import_id,
5045
+ onClick: () => void openRefusals(r.import_id),
5046
+ children: openRun === r.import_id ? "hide the rows it refused" : `see the ${r.rows_refused} row${r.rows_refused === 1 ? "" : "s"} it refused`
5047
+ }
5048
+ ) : null
5049
+ ] }),
5050
+ openRun === r.import_id ? runProblem ? /* @__PURE__ */ jsx16(RefusalNotice, { error: runProblem }) : runRows ? /* @__PURE__ */ jsxs12(Fragment7, { children: [
5051
+ runRows.note ? /* @__PURE__ */ jsx16("p", { className: "text-muted-foreground", children: runRows.note }) : null,
5052
+ /* @__PURE__ */ jsx16("div", { className: "max-h-64 overflow-auto rounded border", children: /* @__PURE__ */ jsxs12("table", { className: "w-full", children: [
5053
+ /* @__PURE__ */ jsx16("thead", { className: "sticky top-0 bg-muted", children: /* @__PURE__ */ jsxs12("tr", { children: [
5054
+ /* @__PURE__ */ jsx16("th", { className: "px-2 py-1 text-left font-medium", children: "Line in your file" }),
5055
+ /* @__PURE__ */ jsx16("th", { className: "px-2 py-1 text-left font-medium", children: "What happened" }),
5056
+ /* @__PURE__ */ jsx16("th", { className: "px-2 py-1 text-left font-medium", children: "The row in your file" })
5057
+ ] }) }),
5058
+ /* @__PURE__ */ jsx16("tbody", { children: runRows.rows.map((row, i) => {
5059
+ const source = row.source ?? {};
5060
+ return /* @__PURE__ */ jsxs12("tr", { className: "border-t align-top", children: [
5061
+ /* @__PURE__ */ jsx16("td", { className: "px-2 py-1", children: String(row.row ?? "") }),
5062
+ /* @__PURE__ */ jsx16("td", { className: "px-2 py-1", children: /* @__PURE__ */ jsx16(
5063
+ RefusalLine,
5064
+ {
5065
+ error: refusal(
5066
+ "refused_by_rule",
5067
+ String(row.reason ?? "This row was refused."),
5068
+ "Fix this line in your file and import it again."
5069
+ )
5070
+ }
5071
+ ) }),
5072
+ /* @__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 ") })
5073
+ ] }, `${r.import_id}-${String(row.row ?? i)}`);
5074
+ }) })
5075
+ ] }) })
5076
+ ] }) : /* @__PURE__ */ jsx16("p", { className: "text-muted-foreground", children: "Reading the rows this import refused\u2026" }) : null
5077
+ ] }, r.import_id)) })
4714
5078
  ] }) : null,
4715
5079
  table.error ? /* @__PURE__ */ jsx16(RefusalNotice, { error: table.error }) : null
4716
5080
  ] });
@@ -4942,7 +5306,9 @@ function keyFor(label) {
4942
5306
  if (token === "") return "";
4943
5307
  return /^[a-z]/.test(token) ? token : `f_${token}`;
4944
5308
  }
4945
- 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.";
5309
+ 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.";
5310
+ 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.";
5311
+ var COLUMN_DID_NOT_LAND = `${COLUMN_DID_NOT_LAND_SENTENCE} ${COLUMN_DID_NOT_LAND_REMEDY}`;
4946
5312
  function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }) {
4947
5313
  const table = useTable4(tableId);
4948
5314
  const fields = useFields6(tableId);
@@ -5242,14 +5608,20 @@ function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }
5242
5608
  ] }, `${rule.kind}-${index}`))
5243
5609
  ] }),
5244
5610
  mutation.error ? /* @__PURE__ */ jsx18(RefusalNotice, { error: mutation.error }) : null,
5245
- missing ? /* @__PURE__ */ jsx18("p", { className: "text-[11px] text-destructive", children: missing }) : null,
5246
- notLanded ? /* @__PURE__ */ jsx18("p", { className: "text-[11px] text-destructive", children: COLUMN_DID_NOT_LAND }) : null,
5611
+ missing ? /* @__PURE__ */ jsx18("p", { id: "field-editor-missing", className: "text-[11px] text-muted-foreground", "data-field-editor-missing": "", children: missing }) : null,
5612
+ notLanded ? /* @__PURE__ */ jsx18(
5613
+ RefusalNotice,
5614
+ {
5615
+ error: refusal("internal", COLUMN_DID_NOT_LAND_SENTENCE, COLUMN_DID_NOT_LAND_REMEDY)
5616
+ }
5617
+ ) : null,
5247
5618
  /* @__PURE__ */ jsxs14("div", { className: "flex flex-wrap items-center gap-2", children: [
5248
5619
  /* @__PURE__ */ jsx18(
5249
5620
  Button13,
5250
5621
  {
5251
5622
  size: "sm",
5252
5623
  disabled: mutation.saving || missing !== null,
5624
+ ...missing ? { "aria-describedby": "field-editor-missing" } : {},
5253
5625
  onClick: () => void save(),
5254
5626
  children: mutation.saving ? "Saving\u2026" : field ? EDIT_FIELD_SAVE_LABEL : ADD_FIELD_SAVE_LABEL
5255
5627
  }
@@ -5529,7 +5901,7 @@ function ValueOnTheOtherSide({
5529
5901
  }
5530
5902
 
5531
5903
  // src/StageRules.tsx
5532
- import { useCallback as useCallback9, useEffect as useEffect8, useMemo as useMemo13, useState as useState17 } from "react";
5904
+ import { useCallback as useCallback9, useEffect as useEffect9, useMemo as useMemo13, useState as useState17 } from "react";
5533
5905
  import { useFields as useFields7, useRecordsClient as useRecordsClient8, useTable as useTable5 } from "@ai-matrx/records/react";
5534
5906
  import { BasicInput as BasicInput7, BasicTextarea as BasicTextarea3, Button as Button15, Separator as Separator5, Skeleton as Skeleton3, cn as cn15 } from "@ai-matrx/design-system";
5535
5907
 
@@ -5888,7 +6260,7 @@ function StageRulesSection({ tableId, stage, className }) {
5888
6260
  if (mode.ok) setEnforcement(mode.data);
5889
6261
  setAsked(true);
5890
6262
  }, [client, tableId]);
5891
- useEffect8(() => {
6263
+ useEffect9(() => {
5892
6264
  void load();
5893
6265
  }, [load]);
5894
6266
  const stages = pipeline?.stages ?? [];
@@ -5899,7 +6271,7 @@ function StageRulesSection({ tableId, stage, className }) {
5899
6271
  () => (fields.data ?? []).map((f) => ({ id: String(f.id), key: f.key, label: fieldName(f), field: f })),
5900
6272
  [fields.data]
5901
6273
  );
5902
- useEffect8(() => {
6274
+ useEffect9(() => {
5903
6275
  if (!draft || !stageKey || !draft.demands || Object.keys(draft.demands).length === 0) {
5904
6276
  setPreview(null);
5905
6277
  setPreviewError(null);
@@ -6338,7 +6710,7 @@ import { useFields as useFields10, useRecord as useRecord2, useRecordsClient as
6338
6710
  import { AlchemyMenu as AlchemyMenu2 } from "@ai-matrx/alchemy/react";
6339
6711
 
6340
6712
  // src/RecordChat.tsx
6341
- import { useEffect as useEffect9, useMemo as useMemo14, useState as useState19 } from "react";
6713
+ import { useEffect as useEffect10, useMemo as useMemo14, useState as useState19 } from "react";
6342
6714
  import { useRecordsClient as useRecordsClient9 } from "@ai-matrx/records/react";
6343
6715
  import { Skeleton as Skeleton4, cn as cn17 } from "@ai-matrx/design-system";
6344
6716
  import { jsx as jsx22, jsxs as jsxs18 } from "react/jsx-runtime";
@@ -6348,7 +6720,7 @@ function RecordChat({ tableId, recordId, className }) {
6348
6720
  const host = useRecordsUi();
6349
6721
  const [scope, setScope] = useState19(null);
6350
6722
  const [error, setError] = useState19(null);
6351
- useEffect9(() => {
6723
+ useEffect10(() => {
6352
6724
  let cancelled = false;
6353
6725
  setScope(null);
6354
6726
  setError(null);
@@ -6465,7 +6837,7 @@ function entriesFor(scope) {
6465
6837
  import { Button as Button19, Separator as Separator8, Skeleton as Skeleton5, cn as cn19 } from "@ai-matrx/design-system";
6466
6838
 
6467
6839
  // src/RecordForm.tsx
6468
- import { useEffect as useEffect11, useMemo as useMemo16, useState as useState21 } from "react";
6840
+ import { useEffect as useEffect12, useMemo as useMemo16, useState as useState21 } from "react";
6469
6841
  import {
6470
6842
  useFields as useFields9,
6471
6843
  useRecord,
@@ -6475,7 +6847,7 @@ import { predictWriteRefusals } from "@ai-matrx/records/core";
6475
6847
  import { Button as Button17, Separator as Separator7, cn as cn18 } from "@ai-matrx/design-system";
6476
6848
 
6477
6849
  // src/systemTable.ts
6478
- import { useEffect as useEffect10, useMemo as useMemo15, useState as useState20 } from "react";
6850
+ import { useEffect as useEffect11, useMemo as useMemo15, useState as useState20 } from "react";
6479
6851
  import { useRecordsClient as useRecordsClient10 } from "@ai-matrx/records/react";
6480
6852
  var inFlight = /* @__PURE__ */ new Map();
6481
6853
  async function ensureSystemTable(client, spec) {
@@ -6608,7 +6980,7 @@ function useSystemTable(spec) {
6608
6980
  const [state, setState] = useState20({ tableId: null, loading: true, error: null });
6609
6981
  const slug = spec.slug;
6610
6982
  const stable = useMemo15(() => spec, [slug]);
6611
- useEffect10(() => {
6983
+ useEffect11(() => {
6612
6984
  let cancelled = false;
6613
6985
  setState({ tableId: null, loading: true, error: null });
6614
6986
  void ensureSystemTable(client, stable).then((result) => {
@@ -6626,7 +6998,7 @@ function useSystemTable(spec) {
6626
6998
  function useRecordVersion(recordId) {
6627
6999
  const client = useRecordsClient10();
6628
7000
  const [version, setVersion] = useState20(null);
6629
- useEffect10(() => {
7001
+ useEffect11(() => {
6630
7002
  let cancelled = false;
6631
7003
  setVersion(null);
6632
7004
  if (!recordId) return;
@@ -6663,7 +7035,7 @@ function RecordForm({
6663
7035
  const [touched, setTouched] = useState21(false);
6664
7036
  const loaded = useRecordVersion(recordId ?? null);
6665
7037
  const loadedVersion = loaded.version;
6666
- useEffect11(() => {
7038
+ useEffect12(() => {
6667
7039
  const document3 = existing.data?.document;
6668
7040
  if (document3) setDraft({ ...document3 });
6669
7041
  }, [existing.data?.record_id, loadedVersion]);
@@ -6724,7 +7096,13 @@ function RecordForm({
6724
7096
  },
6725
7097
  field.id
6726
7098
  )) }),
6727
- touched ? predicted.filter((p) => !p.field_key).map((p) => /* @__PURE__ */ jsx23("p", { className: "text-xs text-destructive", children: p.message }, p.message)) : null,
7099
+ touched ? predicted.filter((p) => !p.field_key).map((p) => /* @__PURE__ */ jsx23(
7100
+ RefusalNotice,
7101
+ {
7102
+ error: { code: "refused_by_rule", message: p.message }
7103
+ },
7104
+ p.message
7105
+ )) : null,
6728
7106
  mutation.error ? /* @__PURE__ */ jsx23(
6729
7107
  RefusalNotice,
6730
7108
  {
@@ -6779,7 +7157,14 @@ function FieldRow({
6779
7157
  return /* @__PURE__ */ jsxs19("div", { className: "flex min-w-0 flex-col gap-1", children: [
6780
7158
  /* @__PURE__ */ jsx23(FieldLabel, { field, htmlFor: id, children: /* @__PURE__ */ jsx23(ProvenanceBadge, { document: document2, fieldKey: field.key }) }),
6781
7159
  /* @__PURE__ */ jsx23(FieldControl, { field, value, onChange, id }),
6782
- problems.map((problem) => /* @__PURE__ */ jsx23("p", { className: "text-xs text-destructive", children: problem.message }, problem.message))
7160
+ problems.map((problem) => /* @__PURE__ */ jsx23(
7161
+ RefusalNotice,
7162
+ {
7163
+ error: { code: "refused_by_rule", message: problem.message },
7164
+ className: "border-0 p-0"
7165
+ },
7166
+ problem.message
7167
+ ))
6783
7168
  ] });
6784
7169
  }
6785
7170
  function asWords(value) {
@@ -6790,7 +7175,7 @@ function asWords(value) {
6790
7175
  }
6791
7176
 
6792
7177
  // src/ShareControl.tsx
6793
- import { useState as useState22 } from "react";
7178
+ import { useEffect as useEffect13, useState as useState22 } from "react";
6794
7179
  import { Button as Button18 } from "@ai-matrx/design-system";
6795
7180
  import { Fragment as Fragment11, jsx as jsx24, jsxs as jsxs20 } from "react/jsx-runtime";
6796
7181
  function useCanShare() {
@@ -6805,12 +7190,16 @@ function ShareControl({
6805
7190
  subjectId,
6806
7191
  name,
6807
7192
  may,
7193
+ initiallyOpen = false,
6808
7194
  size = "sm",
6809
7195
  variant = "ghost",
6810
7196
  className
6811
7197
  }) {
6812
7198
  const host = useRecordsUi();
6813
- const [open, setOpen] = useState22(false);
7199
+ const [open, setOpen] = useState22(initiallyOpen);
7200
+ useEffect13(() => {
7201
+ if (initiallyOpen) setOpen(true);
7202
+ }, [initiallyOpen]);
6814
7203
  const asked = useRecordRights(may === void 0 ? subjectId : null);
6815
7204
  const mayShare = may ?? asked.share;
6816
7205
  if (!host.share) return null;
@@ -7092,12 +7481,12 @@ function parseSorts(raw) {
7092
7481
  }
7093
7482
 
7094
7483
  // src/ViewSwitcher.tsx
7095
- import { useEffect as useEffect13, useMemo as useMemo18, useState as useState25 } from "react";
7484
+ import { useEffect as useEffect15, useMemo as useMemo18, useState as useState25 } from "react";
7096
7485
  import { useFields as useFields12, useRecords as useRecords5, useRecordsClient as useRecordsClient13 } from "@ai-matrx/records/react";
7097
7486
  import { Button as Button21, Skeleton as Skeleton7, cn as cn21 } from "@ai-matrx/design-system";
7098
7487
 
7099
7488
  // src/Pipeline.tsx
7100
- import { useCallback as useCallback10, useEffect as useEffect12, useMemo as useMemo17, useRef as useRef6, useState as useState24 } from "react";
7489
+ import { useCallback as useCallback10, useEffect as useEffect14, useMemo as useMemo17, useRef as useRef6, useState as useState24 } from "react";
7101
7490
  import {
7102
7491
  mayDrag,
7103
7492
  useFields as useFields11,
@@ -7146,7 +7535,7 @@ function PipelineBoard({
7146
7535
  const [dragging, setDragging] = useState24(null);
7147
7536
  const [over, setOver] = useState24(null);
7148
7537
  const alive = useRef6(true);
7149
- useEffect12(() => {
7538
+ useEffect14(() => {
7150
7539
  alive.current = true;
7151
7540
  return () => {
7152
7541
  alive.current = false;
@@ -7170,7 +7559,7 @@ function PipelineBoard({
7170
7559
  setWaiting(held.ok ? new Map((held.data ?? []).map((p) => [p.record_id, p])) : /* @__PURE__ */ new Map());
7171
7560
  setColumns(board.ok ? board.data ?? [] : []);
7172
7561
  }, [client, tableId, measure]);
7173
- useEffect12(() => {
7562
+ useEffect14(() => {
7174
7563
  void reload();
7175
7564
  }, [reload]);
7176
7565
  const stageKey = definition?.stage_field ?? null;
@@ -7202,8 +7591,8 @@ function PipelineBoard({
7202
7591
  const asked = await client.pipelineTransitionRefusal({ record_id: recordId, to });
7203
7592
  if (!alive.current) return;
7204
7593
  if (!asked.ok) {
7205
- setError(asked.error);
7206
- setPending({ kind: "none" });
7594
+ setPending({ kind: "clash", recordId, to, error: asked.error });
7595
+ onMoved?.();
7207
7596
  return;
7208
7597
  }
7209
7598
  const verdict = asked.data;
@@ -7216,8 +7605,8 @@ function PipelineBoard({
7216
7605
  const moved = await client.pipelineMove({ record_id: recordId, to });
7217
7606
  if (!alive.current) return;
7218
7607
  if (!moved.ok) {
7219
- setPending({ kind: "none" });
7220
- setError(moved.error);
7608
+ setPending({ kind: "clash", recordId, to, error: moved.error });
7609
+ onMoved?.();
7221
7610
  return;
7222
7611
  }
7223
7612
  const result = moved.data;
@@ -7238,21 +7627,22 @@ function PipelineBoard({
7238
7627
  const moved = await client.pipelineMove({ record_id: recordId, to });
7239
7628
  if (!alive.current) return;
7240
7629
  if (!moved.ok) {
7241
- setPending({ kind: "none" });
7242
- setError(moved.error);
7630
+ setPending({ kind: "clash", recordId, to, error: moved.error });
7631
+ onMoved?.();
7243
7632
  return;
7244
7633
  }
7245
7634
  setPending({ kind: "filed", recordId, to, result: moved.data });
7246
7635
  void reload();
7247
7636
  },
7248
- [client, reload]
7637
+ [client, onMoved, reload]
7249
7638
  );
7250
7639
  const moveWith = useCallback10(
7251
7640
  async (recordId, to, also) => {
7252
7641
  const moved = await client.pipelineMove({ record_id: recordId, to, also });
7253
7642
  if (!alive.current) return;
7254
7643
  if (!moved.ok) {
7255
- setError(moved.error);
7644
+ setPending({ kind: "clash", recordId, to, error: moved.error });
7645
+ onMoved?.();
7256
7646
  return;
7257
7647
  }
7258
7648
  setPending({ kind: "none" });
@@ -7380,11 +7770,33 @@ function Held({
7380
7770
  if (pending.kind === "asking") {
7381
7771
  return /* @__PURE__ */ jsx26("p", { className: "px-2 py-1 text-xs text-muted-foreground", children: "Asking\u2026" });
7382
7772
  }
7773
+ if (pending.kind === "clash") {
7774
+ return /* @__PURE__ */ jsx26(
7775
+ RefusalNotice,
7776
+ {
7777
+ className: "mt-1",
7778
+ error: pending.error,
7779
+ actions: /* @__PURE__ */ jsx26("div", { className: "mt-1", children: /* @__PURE__ */ jsx26(Button20, { size: "sm", variant: "ghost", className: "h-6", onClick: onCancel, children: "Close" }) })
7780
+ }
7781
+ );
7782
+ }
7383
7783
  if (pending.kind === "refused") {
7384
- return /* @__PURE__ */ jsxs22("div", { className: "mt-1 rounded border border-destructive/50 bg-destructive/5 p-2 text-xs", children: [
7385
- /* @__PURE__ */ jsx26("p", { children: pending.verdict.why }),
7386
- /* @__PURE__ */ jsx26(Button20, { size: "sm", variant: "ghost", className: "mt-1 h-6", onClick: onCancel, children: "Close" })
7387
- ] });
7784
+ return (
7785
+ // THE STAGE GATE SAID NO: the one refusal surface, heading and remedy both,
7786
+ // never its sentence alone in a red box (lane REFUSAL-SWEEP).
7787
+ /* @__PURE__ */ jsx26(
7788
+ RefusalNotice,
7789
+ {
7790
+ className: "mt-1",
7791
+ error: refusal(
7792
+ "refused_by_rule",
7793
+ pending.verdict.why,
7794
+ "The card stays where it was. Fill in what this stage asks for, then move it again."
7795
+ ),
7796
+ actions: /* @__PURE__ */ jsx26(Button20, { size: "sm", variant: "ghost", className: "mt-1 h-6", onClick: onCancel, children: "Close" })
7797
+ }
7798
+ )
7799
+ );
7388
7800
  }
7389
7801
  if (pending.kind === "approval") {
7390
7802
  return /* @__PURE__ */ jsxs22("div", { className: "mt-1 flex flex-col gap-1 rounded border p-2 text-xs", children: [
@@ -7517,7 +7929,7 @@ function useViewRecords(view, pageSize = 200, filter) {
7517
7929
  error: null
7518
7930
  });
7519
7931
  const ruleId = view.ruleId ?? null;
7520
- useEffect13(() => {
7932
+ useEffect15(() => {
7521
7933
  if (!ruleId) return;
7522
7934
  let cancelled = false;
7523
7935
  setRuled({ rows: [], loading: true, error: null });
@@ -7581,7 +7993,7 @@ function ViewSwitcher({
7581
7993
  }) {
7582
7994
  const [layout, setLayout] = useState25(view.layout);
7583
7995
  const [local, setLocal] = useState25({});
7584
- useEffect13(() => {
7996
+ useEffect15(() => {
7585
7997
  setLayout(view.layout);
7586
7998
  setLocal({});
7587
7999
  }, [view.layout, view.name, view.subject]);
@@ -7649,7 +8061,7 @@ function useStageField(tableId) {
7649
8061
  asked: false,
7650
8062
  key: null
7651
8063
  });
7652
- useEffect13(() => {
8064
+ useEffect15(() => {
7653
8065
  let cancelled = false;
7654
8066
  setStage({ asked: false, key: null });
7655
8067
  void client.tableStageField({ table_id: tableId }).then((r) => {
@@ -7918,7 +8330,7 @@ function Card2({
7918
8330
  }
7919
8331
 
7920
8332
  // src/ArchivedView.tsx
7921
- import { useCallback as useCallback11, useEffect as useEffect14, useState as useState26 } from "react";
8333
+ import { useCallback as useCallback11, useEffect as useEffect16, useState as useState26 } from "react";
7922
8334
  import {
7923
8335
  ARCHIVE_LANES,
7924
8336
  ARCHIVE_LANE_LABEL,
@@ -7955,8 +8367,8 @@ function ArchivedView({
7955
8367
  const [loading, setLoading] = useState26(true);
7956
8368
  const [error, setError] = useState26(null);
7957
8369
  const [restoring, setRestoring] = useState26(null);
7958
- const [refusal, setRefusal] = useState26(null);
7959
- useEffect14(() => {
8370
+ const [refusal2, setRefusal] = useState26(null);
8371
+ useEffect16(() => {
7960
8372
  if (laneFromHost) setLane(laneFromHost);
7961
8373
  }, [laneFromHost]);
7962
8374
  const read = useCallback11(async () => {
@@ -7971,7 +8383,7 @@ function ArchivedView({
7971
8383
  }
7972
8384
  setLoading(false);
7973
8385
  }, [client, tableId, lane, pageSize]);
7974
- useEffect14(() => {
8386
+ useEffect16(() => {
7975
8387
  void read();
7976
8388
  }, [read]);
7977
8389
  const pick = (next) => {
@@ -8032,7 +8444,7 @@ function ArchivedView({
8032
8444
  /* @__PURE__ */ jsxs24("div", { className: "flex min-w-0 flex-1 flex-col", children: [
8033
8445
  /* @__PURE__ */ jsx28("span", { className: "truncate text-sm", children: recordName(row.document, titleKey) }),
8034
8446
  /* @__PURE__ */ jsx28("span", { className: "truncate text-xs text-muted-foreground", "data-testid": "archived-who", children: archivedByLine(row) }),
8035
- refusal && refusal.recordId === row.id ? /* @__PURE__ */ jsx28(RefusalNotice, { error: refusal.error, className: "mt-2" }) : null
8447
+ refusal2 && refusal2.recordId === row.id ? /* @__PURE__ */ jsx28(RefusalNotice, { error: refusal2.error, className: "mt-2" }) : null
8036
8448
  ] }),
8037
8449
  mayRestore ? /* @__PURE__ */ jsx28(
8038
8450
  Button22,
@@ -8053,7 +8465,7 @@ function ArchivedView({
8053
8465
  }
8054
8466
 
8055
8467
  // src/ArchivedDisclosure.tsx
8056
- import { useCallback as useCallback12, useEffect as useEffect15, useState as useState27 } from "react";
8468
+ import { useCallback as useCallback12, useEffect as useEffect17, useState as useState27 } from "react";
8057
8469
  import {
8058
8470
  emptyPortalArchiveLine,
8059
8471
  portalConfirmLine,
@@ -8110,7 +8522,7 @@ function ArchivedPortals({
8110
8522
  const [confirming, setConfirming] = useState27(null);
8111
8523
  const [typed, setTyped] = useState27("");
8112
8524
  const [restoring, setRestoring] = useState27(null);
8113
- const [refusal, setRefusal] = useState27(null);
8525
+ const [refusal2, setRefusal] = useState27(null);
8114
8526
  const [said, setSaid] = useState27(null);
8115
8527
  const read = useCallback12(async () => {
8116
8528
  const answered = await client.listPortals({ archived: "archived" });
@@ -8122,7 +8534,7 @@ function ArchivedPortals({
8122
8534
  setError(answered.error);
8123
8535
  }
8124
8536
  }, [client]);
8125
- useEffect15(() => {
8537
+ useEffect17(() => {
8126
8538
  void read();
8127
8539
  }, [read, refreshToken]);
8128
8540
  const restore = async (portal) => {
@@ -8226,7 +8638,7 @@ function ArchivedPortals({
8226
8638
  )
8227
8639
  ] })
8228
8640
  ] }) : null,
8229
- refusal && refusal.portalId === portal.portal_id ? /* @__PURE__ */ jsx29(RefusalNotice, { error: refusal.error }) : null
8641
+ refusal2 && refusal2.portalId === portal.portal_id ? /* @__PURE__ */ jsx29(RefusalNotice, { error: refusal2.error }) : null
8230
8642
  ]
8231
8643
  },
8232
8644
  portal.portal_id
@@ -8238,7 +8650,7 @@ function ArchivedPortals({
8238
8650
  }
8239
8651
 
8240
8652
  // src/ViewBar.tsx
8241
- import { useCallback as useCallback14, useEffect as useEffect16, useState as useState28 } from "react";
8653
+ import { useCallback as useCallback14, useEffect as useEffect18, useState as useState28 } from "react";
8242
8654
  import { useRecordsClient as useRecordsClient16 } from "@ai-matrx/records/react";
8243
8655
  import { Button as Button24, Input as Input2, Skeleton as Skeleton10, cn as cn24 } from "@ai-matrx/design-system";
8244
8656
 
@@ -8301,10 +8713,10 @@ function ViewBar({ tableId, activeViewId, onActiveView, seed, className }) {
8301
8713
  setError(null);
8302
8714
  setViews(mine);
8303
8715
  }, [client, viewTableId, tableId, seed]);
8304
- useEffect16(() => {
8716
+ useEffect18(() => {
8305
8717
  void load();
8306
8718
  }, [load]);
8307
- useEffect16(() => {
8719
+ useEffect18(() => {
8308
8720
  if (!views || views.length === 0) return;
8309
8721
  const chosen = views.find((v) => v.id === (activeViewId ?? active)) ?? views.find((v) => v.isDefault) ?? views[0];
8310
8722
  if (chosen.id !== active) setActive(chosen.id);
@@ -8480,12 +8892,12 @@ function ProposalRow({
8480
8892
  }
8481
8893
 
8482
8894
  // src/ActionInbox.tsx
8483
- import { useCallback as useCallback16, useEffect as useEffect18, useMemo as useMemo20, useRef as useRef8, useState as useState31 } from "react";
8895
+ import { useCallback as useCallback16, useEffect as useEffect20, useMemo as useMemo20, useRef as useRef8, useState as useState31 } from "react";
8484
8896
  import { useRecordsClient as useRecordsClient19 } from "@ai-matrx/records/react";
8485
8897
  import { Badge as Badge9, Button as Button27, Skeleton as Skeleton12, cn as cn27 } from "@ai-matrx/design-system";
8486
8898
 
8487
8899
  // src/ChecklistRunner.tsx
8488
- import { useCallback as useCallback15, useEffect as useEffect17, useMemo as useMemo19, useState as useState30 } from "react";
8900
+ import { useCallback as useCallback15, useEffect as useEffect19, useMemo as useMemo19, useState as useState30 } from "react";
8489
8901
  import { useRecordsClient as useRecordsClient18, useTable as useTable10 } from "@ai-matrx/records/react";
8490
8902
  import { Badge as Badge8, BasicInput as BasicInput9, BasicTextarea as BasicTextarea4, Button as Button26, Skeleton as Skeleton11, cn as cn26 } from "@ai-matrx/design-system";
8491
8903
  import { Fragment as Fragment13, jsx as jsx32, jsxs as jsxs28 } from "react/jsx-runtime";
@@ -8533,10 +8945,10 @@ function ChecklistRunner({
8533
8945
  (held) => held && answered.data.some((r) => r.run_id === held) ? held : answered.data[0]?.run_id ?? null
8534
8946
  );
8535
8947
  }, [client, includeClosed, recordId, runId, tableId]);
8536
- useEffect17(() => {
8948
+ useEffect19(() => {
8537
8949
  void loadRuns();
8538
8950
  }, [loadRuns]);
8539
- useEffect17(() => {
8951
+ useEffect19(() => {
8540
8952
  if (runId) setActiveId(runId);
8541
8953
  }, [runId]);
8542
8954
  const loadSteps = useCallback15(async () => {
@@ -8553,10 +8965,10 @@ function ChecklistRunner({
8553
8965
  setError(null);
8554
8966
  setSteps(answered.data);
8555
8967
  }, [client, activeId]);
8556
- useEffect17(() => {
8968
+ useEffect19(() => {
8557
8969
  void loadSteps();
8558
8970
  }, [loadSteps]);
8559
- useEffect17(() => {
8971
+ useEffect19(() => {
8560
8972
  if (!mayStart || !tableId) return;
8561
8973
  let cancelled = false;
8562
8974
  void client.checklistTemplates({ about_table_id: tableId, limit: 50 }).then((answered) => {
@@ -8782,7 +9194,7 @@ function useMyChecklistSteps(limit = 25) {
8782
9194
  setSteps(held);
8783
9195
  setLoading(false);
8784
9196
  }, [client, limit, me]);
8785
- useEffect17(() => {
9197
+ useEffect19(() => {
8786
9198
  void refresh();
8787
9199
  }, [refresh]);
8788
9200
  return { steps, loading, error, refresh };
@@ -8791,7 +9203,7 @@ function MyChecklistSteps({ className }) {
8791
9203
  const client = useRecordsClient18();
8792
9204
  const { steps, loading, error, refresh } = useMyChecklistSteps();
8793
9205
  const [busy, setBusy] = useState30(null);
8794
- const [refusal, setRefusal] = useState30(null);
9206
+ const [refusal2, setRefusal] = useState30(null);
8795
9207
  const complete = useCallback15(
8796
9208
  async (step2, evidence) => {
8797
9209
  setBusy(step2.step_id);
@@ -8814,7 +9226,7 @@ function MyChecklistSteps({ className }) {
8814
9226
  /* @__PURE__ */ jsx32("span", { className: "tabular-nums", children: steps.length })
8815
9227
  ] }),
8816
9228
  error ? /* @__PURE__ */ jsx32(RefusalNotice, { error }) : null,
8817
- refusal ? /* @__PURE__ */ jsx32(RefusalNotice, { error: refusal }) : null,
9229
+ refusal2 ? /* @__PURE__ */ jsx32(RefusalNotice, { error: refusal2 }) : null,
8818
9230
  /* @__PURE__ */ jsx32("ol", { className: "flex flex-col gap-1.5", children: steps.map((step2) => /* @__PURE__ */ jsx32(
8819
9231
  StepRow,
8820
9232
  {
@@ -8849,7 +9261,7 @@ function ChecklistsPanel({ tableId, onOpenRecord, className }) {
8849
9261
  if (!r.ok) setRuns([]);
8850
9262
  else setRuns(r.data);
8851
9263
  }, [client, tableId]);
8852
- useEffect17(() => {
9264
+ useEffect19(() => {
8853
9265
  void load();
8854
9266
  }, [load]);
8855
9267
  if (templates === null) return /* @__PURE__ */ jsx32(Skeleton11, { className: cn26("h-40 w-full", className) });
@@ -8991,11 +9403,11 @@ function ChecklistTemplateEditor({
8991
9403
  const client = useRecordsClient18();
8992
9404
  const [name, setName] = useState30("");
8993
9405
  const [rows, setRows] = useState30([{ ...EMPTY_ROW }]);
8994
- const [refusal, setRefusal] = useState30(null);
9406
+ const [refusal2, setRefusal] = useState30(null);
8995
9407
  const [error, setError] = useState30(null);
8996
9408
  const [busy, setBusy] = useState30(false);
8997
9409
  const [loading, setLoading] = useState30(Boolean(templateId));
8998
- useEffect17(() => {
9410
+ useEffect19(() => {
8999
9411
  if (!templateId) return;
9000
9412
  let cancelled = false;
9001
9413
  void client.checklistTemplateShape({ template_id: templateId }).then((answered) => {
@@ -9032,7 +9444,7 @@ function ChecklistTemplateEditor({
9032
9444
  }),
9033
9445
  [aboutTableId, name, rows]
9034
9446
  );
9035
- useEffect17(() => {
9447
+ useEffect19(() => {
9036
9448
  if (spec.steps.length === 0 || spec.name.length === 0) {
9037
9449
  setRefusal(null);
9038
9450
  return;
@@ -9159,9 +9571,9 @@ function ChecklistTemplateEditor({
9159
9571
  ] }, index)) }),
9160
9572
  /* @__PURE__ */ jsxs28("div", { className: "flex items-center gap-1.5", children: [
9161
9573
  /* @__PURE__ */ jsx32(Button26, { size: "sm", variant: "outline", onClick: () => setRows((held) => [...held, { ...EMPTY_ROW }]), children: "Add a step" }),
9162
- /* @__PURE__ */ jsx32(Button26, { size: "sm", disabled: busy || refusal !== null || spec.steps.length === 0, onClick: () => void save(), children: busy ? "\u2026" : "Save" })
9574
+ /* @__PURE__ */ jsx32(Button26, { size: "sm", disabled: busy || refusal2 !== null || spec.steps.length === 0, onClick: () => void save(), children: busy ? "\u2026" : "Save" })
9163
9575
  ] }),
9164
- refusal ? /* @__PURE__ */ jsx32("p", { className: "text-xs text-muted-foreground", "data-testid": "checklist-editor-refusal", children: refusal }) : null
9576
+ refusal2 ? /* @__PURE__ */ jsx32("p", { className: "text-xs text-muted-foreground", "data-testid": "checklist-editor-refusal", children: refusal2 }) : null
9165
9577
  ] });
9166
9578
  }
9167
9579
  function toStepSpec(row, index) {
@@ -9218,7 +9630,7 @@ function ActionInbox({ tableId, includeSettled = false, onOpenRecord, className
9218
9630
  setError(null);
9219
9631
  setItems(result.data);
9220
9632
  }, [client, includeSettled]);
9221
- useEffect18(() => {
9633
+ useEffect20(() => {
9222
9634
  void load();
9223
9635
  }, [load]);
9224
9636
  const shown = useMemo20(() => {
@@ -9226,10 +9638,10 @@ function ActionInbox({ tableId, includeSettled = false, onOpenRecord, className
9226
9638
  if (!tableId) return all;
9227
9639
  return all.filter((i) => i.kind !== "assignment" || i.subject_kind !== "record" || true);
9228
9640
  }, [items, tableId]);
9229
- useEffect18(() => {
9641
+ useEffect20(() => {
9230
9642
  if (cursor >= shown.length) setCursor(Math.max(0, shown.length - 1));
9231
9643
  }, [shown.length, cursor]);
9232
- useEffect18(() => {
9644
+ useEffect20(() => {
9233
9645
  const el = listRef.current?.querySelector(`[data-row="${cursor}"]`);
9234
9646
  el?.scrollIntoView({ block: "nearest" });
9235
9647
  }, [cursor, shown.length]);
@@ -9355,7 +9767,7 @@ function ActionInbox({ tableId, includeSettled = false, onOpenRecord, className
9355
9767
  }
9356
9768
 
9357
9769
  // src/HistoryPanel.tsx
9358
- import { useCallback as useCallback17, useEffect as useEffect19, useRef as useRef9, useState as useState32 } from "react";
9770
+ import { useCallback as useCallback17, useEffect as useEffect21, useRef as useRef9, useState as useState32 } from "react";
9359
9771
  import {
9360
9772
  useFields as useFields14,
9361
9773
  useRecordsClient as useRecordsClient20,
@@ -9383,7 +9795,7 @@ function HistoryPanel({ tableId, recordId, onAskAboutField, className }) {
9383
9795
  setError(null);
9384
9796
  setEntries(answered.data);
9385
9797
  }, [client, recordId]);
9386
- useEffect19(() => {
9798
+ useEffect21(() => {
9387
9799
  void load();
9388
9800
  }, [load]);
9389
9801
  if (error) return /* @__PURE__ */ jsx34(RefusalNotice, { error, className });
@@ -9664,7 +10076,7 @@ function say(value, field) {
9664
10076
  }
9665
10077
 
9666
10078
  // src/CommentThread.tsx
9667
- import { useCallback as useCallback18, useEffect as useEffect20, useMemo as useMemo21, useRef as useRef10, useState as useState33 } from "react";
10079
+ import { useCallback as useCallback18, useEffect as useEffect22, useMemo as useMemo21, useRef as useRef10, useState as useState33 } from "react";
9668
10080
  import {
9669
10081
  useFields as useFields15,
9670
10082
  useRecordsClient as useRecordsClient21,
@@ -9701,10 +10113,10 @@ function CommentThread({ tableId, recordId, fieldKey, className }) {
9701
10113
  mayResolve: answered.data.may_resolve
9702
10114
  });
9703
10115
  }, [client, recordId, showResolved]);
9704
- useEffect20(() => {
10116
+ useEffect22(() => {
9705
10117
  void load();
9706
10118
  }, [load]);
9707
- useEffect20(() => {
10119
+ useEffect22(() => {
9708
10120
  let alive = true;
9709
10121
  if (!host.members) return;
9710
10122
  void host.members().then((roster) => {
@@ -9907,7 +10319,7 @@ function Line({
9907
10319
  }
9908
10320
 
9909
10321
  // src/FieldHistoryPanel.tsx
9910
- import { useCallback as useCallback19, useEffect as useEffect21, useState as useState34 } from "react";
10322
+ import { useCallback as useCallback19, useEffect as useEffect23, useState as useState34 } from "react";
9911
10323
  import {
9912
10324
  useFields as useFields16,
9913
10325
  useRecordsClient as useRecordsClient22,
@@ -9942,7 +10354,7 @@ function FieldHistoryPanel({
9942
10354
  setError(null);
9943
10355
  setRows(answered.data);
9944
10356
  }, [client, tableId, fieldKey, recordId]);
9945
- useEffect21(() => {
10357
+ useEffect23(() => {
9946
10358
  void load();
9947
10359
  }, [load]);
9948
10360
  const label = (fields.data ?? []).find((f) => f.key === fieldKey)?.label || humanize(fieldKey);
@@ -10135,7 +10547,7 @@ function submissionStamp(args) {
10135
10547
  }
10136
10548
 
10137
10549
  // src/PortalBuilder.tsx
10138
- import { useCallback as useCallback20, useEffect as useEffect22, useMemo as useMemo22, useState as useState35 } from "react";
10550
+ import { useCallback as useCallback20, useEffect as useEffect24, useMemo as useMemo22, useState as useState35 } from "react";
10139
10551
  import { useRecordsClient as useRecordsClient23, useTables as useTables3 } from "@ai-matrx/records/react";
10140
10552
  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";
10141
10553
  import { Fragment as Fragment15, jsx as jsx37, jsxs as jsxs33 } from "react/jsx-runtime";
@@ -10168,7 +10580,7 @@ function PortalBuilder({ tableId, portalId, onSaved, onClose, className }) {
10168
10580
  },
10169
10581
  [client, fieldsByTable]
10170
10582
  );
10171
- useEffect22(() => {
10583
+ useEffect24(() => {
10172
10584
  if (!portalId) return;
10173
10585
  void (async () => {
10174
10586
  const answered = await client.portalCard({ portal_id: portalId });
@@ -10182,7 +10594,7 @@ function PortalBuilder({ tableId, portalId, onSaved, onClose, className }) {
10182
10594
  setClientTableId(answered.data.client_table_id);
10183
10595
  })();
10184
10596
  }, [client, portalId]);
10185
- useEffect22(() => {
10597
+ useEffect24(() => {
10186
10598
  if (!tableId) return;
10187
10599
  setExposures(
10188
10600
  (prev) => prev[tableId] ? prev : {
@@ -10430,7 +10842,7 @@ function PortalBuilder({ tableId, portalId, onSaved, onClose, className }) {
10430
10842
  }
10431
10843
 
10432
10844
  // src/PortalsPanel.tsx
10433
- import { useCallback as useCallback21, useEffect as useEffect23, useMemo as useMemo23, useState as useState36 } from "react";
10845
+ import { useCallback as useCallback21, useEffect as useEffect25, useMemo as useMemo23, useState as useState36 } from "react";
10434
10846
  import { useRecordsClient as useRecordsClient24 } from "@ai-matrx/records/react";
10435
10847
  import {
10436
10848
  portalArchiveConsequence,
@@ -10471,6 +10883,7 @@ function BuildOrAsk({
10471
10883
 
10472
10884
  // src/PortalsPanel.tsx
10473
10885
  import { Fragment as Fragment16, jsx as jsx39, jsxs as jsxs35 } from "react/jsx-runtime";
10886
+ 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.";
10474
10887
  function revokeConsequence(person, portalTitle) {
10475
10888
  const who = person.client ? `${person.email} (${person.client})` : person.email;
10476
10889
  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.`;
@@ -10500,13 +10913,16 @@ function stateWords(person) {
10500
10913
  };
10501
10914
  }
10502
10915
  var PORTAL_SUGGESTION = "Let each of my customers sign in and see their own jobs and invoices, and nothing else.";
10503
- function PortalsPanel({ tableId, className }) {
10916
+ function PortalsPanel({ tableId, activePortalId, className }) {
10504
10917
  const client = useRecordsClient24();
10505
10918
  const host = useRecordsUi();
10506
10919
  const [portals, setPortals] = useState36(null);
10507
10920
  const [exposures, setExposures] = useState36([]);
10508
10921
  const [listError, setListError] = useState36(null);
10509
- const [openId, setOpenId] = useState36(null);
10922
+ const [openId, setOpenId] = useState36(activePortalId ?? null);
10923
+ useEffect25(() => {
10924
+ if (activePortalId) setOpenId(activePortalId);
10925
+ }, [activePortalId]);
10510
10926
  const [building, setBuilding] = useState36(false);
10511
10927
  const [adding, setAdding] = useState36(null);
10512
10928
  const [archiveToken, setArchiveToken] = useState36(0);
@@ -10522,7 +10938,7 @@ function PortalsPanel({ tableId, className }) {
10522
10938
  setPortals(answered.data);
10523
10939
  setExposures(mapped.ok ? mapped.data : []);
10524
10940
  }, [client]);
10525
- useEffect23(() => {
10941
+ useEffect25(() => {
10526
10942
  void load();
10527
10943
  }, [load]);
10528
10944
  if (portals === null) return /* @__PURE__ */ jsx39(Skeleton17, { className: cn33("h-32 w-full", className) });
@@ -10603,59 +11019,73 @@ function PortalsPanel({ tableId, className }) {
10603
11019
  portal.portal_id
10604
11020
  )) })
10605
11021
  ] }) : null,
11022
+ 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,
10606
11023
  /* @__PURE__ */ jsx39("ul", { className: "flex flex-col gap-2", children: shown.map((portal) => {
10607
11024
  const url = `${origin}${portalPath(portal.slug)}`;
10608
11025
  const open = openId === portal.portal_id;
10609
- return /* @__PURE__ */ jsxs35("li", { className: "rounded-md border border-border bg-card p-2.5", children: [
10610
- /* @__PURE__ */ jsxs35("div", { className: "flex items-center gap-2", children: [
10611
- /* @__PURE__ */ jsx39("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: portal.title }),
10612
- portal.is_active ? null : /* @__PURE__ */ jsx39(Badge13, { variant: "outline", children: "closed" })
10613
- ] }),
10614
- /* @__PURE__ */ jsxs35("p", { className: "mt-0.5 text-xs text-muted-foreground", children: [
10615
- "Clients come from ",
10616
- portal.client_table,
10617
- portal.tables === 1 ? ", and see 1 Table" : `, and see ${portal.tables} Tables`,
10618
- "."
10619
- ] }),
10620
- /* @__PURE__ */ jsxs35("p", { className: "mt-1.5 text-xs", children: [
10621
- /* @__PURE__ */ jsx39("span", { className: "font-medium", children: portal.signed_in }),
10622
- " signed in",
10623
- " \xB7 ",
10624
- /* @__PURE__ */ jsx39("span", { className: "font-medium", children: portal.invited - portal.signed_in }),
10625
- " invited and waiting"
10626
- ] }),
10627
- /* @__PURE__ */ jsxs35("div", { className: "mt-2 flex flex-wrap items-center gap-1.5", children: [
10628
- /* @__PURE__ */ jsx39(CopyLink, { url }),
10629
- /* @__PURE__ */ jsx39(
10630
- Button33,
10631
- {
10632
- size: "sm",
10633
- variant: "ghost",
10634
- onClick: () => setOpenId(open ? null : portal.portal_id),
10635
- children: open ? "Close" : "Open it"
10636
- }
11026
+ const linked = portal.portal_id === activePortalId;
11027
+ return /* @__PURE__ */ jsxs35(
11028
+ "li",
11029
+ {
11030
+ "data-linked": linked ? "true" : void 0,
11031
+ ref: linked ? (el) => el?.scrollIntoView({ block: "nearest" }) : void 0,
11032
+ className: cn33(
11033
+ "rounded-md border border-border bg-card p-2.5",
11034
+ linked && "border-primary ring-1 ring-primary"
10637
11035
  ),
10638
- /* @__PURE__ */ jsx39(
10639
- ArchivePortalControl,
10640
- {
10641
- portal,
10642
- onArchived: () => {
10643
- setArchiveToken((t) => t + 1);
10644
- void load();
11036
+ children: [
11037
+ /* @__PURE__ */ jsxs35("div", { className: "flex items-center gap-2", children: [
11038
+ /* @__PURE__ */ jsx39("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: portal.title }),
11039
+ portal.is_active ? null : /* @__PURE__ */ jsx39(Badge13, { variant: "outline", children: "closed" })
11040
+ ] }),
11041
+ /* @__PURE__ */ jsxs35("p", { className: "mt-0.5 text-xs text-muted-foreground", children: [
11042
+ "Clients come from ",
11043
+ portal.client_table,
11044
+ portal.tables === 1 ? ", and see 1 Table" : `, and see ${portal.tables} Tables`,
11045
+ "."
11046
+ ] }),
11047
+ /* @__PURE__ */ jsxs35("p", { className: "mt-1.5 text-xs", children: [
11048
+ /* @__PURE__ */ jsx39("span", { className: "font-medium", children: portal.signed_in }),
11049
+ " signed in",
11050
+ " \xB7 ",
11051
+ /* @__PURE__ */ jsx39("span", { className: "font-medium", children: portal.invited - portal.signed_in }),
11052
+ " invited and waiting"
11053
+ ] }),
11054
+ /* @__PURE__ */ jsxs35("div", { className: "mt-2 flex flex-wrap items-center gap-1.5", children: [
11055
+ /* @__PURE__ */ jsx39(CopyLink, { url }),
11056
+ /* @__PURE__ */ jsx39(
11057
+ Button33,
11058
+ {
11059
+ size: "sm",
11060
+ variant: "ghost",
11061
+ onClick: () => setOpenId(open ? null : portal.portal_id),
11062
+ children: open ? "Close" : "Open it"
11063
+ }
11064
+ ),
11065
+ /* @__PURE__ */ jsx39(
11066
+ ArchivePortalControl,
11067
+ {
11068
+ portal,
11069
+ onArchived: () => {
11070
+ setArchiveToken((t) => t + 1);
11071
+ void load();
11072
+ }
11073
+ }
11074
+ )
11075
+ ] }),
11076
+ open ? /* @__PURE__ */ jsx39(
11077
+ PortalDetail,
11078
+ {
11079
+ portalId: portal.portal_id,
11080
+ ...tableId ? { tableId } : {},
11081
+ origin,
11082
+ onChanged: () => void load()
10645
11083
  }
10646
- }
10647
- )
10648
- ] }),
10649
- open ? /* @__PURE__ */ jsx39(
10650
- PortalDetail,
10651
- {
10652
- portalId: portal.portal_id,
10653
- ...tableId ? { tableId } : {},
10654
- origin,
10655
- onChanged: () => void load()
10656
- }
10657
- ) : null
10658
- ] }, portal.portal_id);
11084
+ ) : null
11085
+ ]
11086
+ },
11087
+ portal.portal_id
11088
+ );
10659
11089
  }) }),
10660
11090
  /* @__PURE__ */ jsx39(ArchivedPortals, { refreshToken: archiveToken, onRestored: () => void load() })
10661
11091
  ] });
@@ -10784,7 +11214,7 @@ function PortalDetail({
10784
11214
  setError(null);
10785
11215
  setCard(answered.data);
10786
11216
  }, [client, portalId]);
10787
- useEffect23(() => {
11217
+ useEffect25(() => {
10788
11218
  void load();
10789
11219
  }, [load]);
10790
11220
  const revoke = useCallback21(
@@ -10977,7 +11407,7 @@ function Preview({
10977
11407
  const [which, setWhich] = useState36(first?.table_id ?? null);
10978
11408
  const [rows, setRows] = useState36(null);
10979
11409
  const [error, setError] = useState36(null);
10980
- useEffect23(() => {
11410
+ useEffect25(() => {
10981
11411
  if (!which) return;
10982
11412
  let cancelled = false;
10983
11413
  setRows(null);
@@ -11023,7 +11453,7 @@ function Invite({ card, onInvited }) {
11023
11453
  const [busy, setBusy] = useState36(false);
11024
11454
  const [said, setSaid] = useState36(null);
11025
11455
  const [error, setError] = useState36(null);
11026
- useEffect23(() => {
11456
+ useEffect25(() => {
11027
11457
  let cancelled = false;
11028
11458
  void client.list({ table_id: card.client_table_id, limit: 200 }).then((answered) => {
11029
11459
  if (cancelled) return;
@@ -11125,7 +11555,7 @@ function Invite({ card, onInvited }) {
11125
11555
  }
11126
11556
 
11127
11557
  // src/DigestScheduler.tsx
11128
- import { useCallback as useCallback22, useEffect as useEffect24, useMemo as useMemo24, useState as useState37 } from "react";
11558
+ import { useCallback as useCallback22, useEffect as useEffect26, useMemo as useMemo24, useState as useState37 } from "react";
11129
11559
  import { useRecordsClient as useRecordsClient25 } from "@ai-matrx/records/react";
11130
11560
  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";
11131
11561
  import { Fragment as Fragment17, jsx as jsx40, jsxs as jsxs36 } from "react/jsx-runtime";
@@ -11189,7 +11619,7 @@ function DigestScheduler({
11189
11619
  setMembers([]);
11190
11620
  }
11191
11621
  }, [client, tableId, host]);
11192
- useEffect24(() => {
11622
+ useEffect26(() => {
11193
11623
  void load();
11194
11624
  }, [load]);
11195
11625
  const schedule = useMemo24(() => {
@@ -11413,7 +11843,17 @@ function DigestScheduler({
11413
11843
  preview ? /* @__PURE__ */ jsxs36("div", { className: "rounded-md border bg-muted/40 p-2.5 text-xs", children: [
11414
11844
  /* @__PURE__ */ jsx40("p", { className: "font-medium", children: preview.subject }),
11415
11845
  /* @__PURE__ */ jsx40("p", { className: "mt-1 text-muted-foreground", children: preview.body }),
11416
- preview.incomplete ? /* @__PURE__ */ jsx40("p", { className: "mt-1 text-destructive", children: preview.incomplete }) : null,
11846
+ preview.incomplete ? (
11847
+ // The summary could not be finished: a refusal line naming the missing
11848
+ // piece and what to do, never the bare sentence in red (lane REFUSAL-SWEEP).
11849
+ /* @__PURE__ */ jsx40(
11850
+ RefusalLine,
11851
+ {
11852
+ className: "mt-1",
11853
+ error: refusal("not_supported", preview.incomplete, "Fill in the missing piece above, then preview again.")
11854
+ }
11855
+ )
11856
+ ) : null,
11417
11857
  preview.entered.length > 0 ? /* @__PURE__ */ jsxs36("p", { className: "mt-1", children: [
11418
11858
  /* @__PURE__ */ jsx40("span", { className: "font-medium", children: "Arrived:" }),
11419
11859
  " ",
@@ -11431,10 +11871,11 @@ function DigestScheduler({
11431
11871
  }
11432
11872
 
11433
11873
  // src/SubscriptionsPanel.tsx
11434
- import { useCallback as useCallback23, useEffect as useEffect25, useState as useState38 } from "react";
11874
+ import { useCallback as useCallback23, useEffect as useEffect27, useState as useState38 } from "react";
11435
11875
  import { useRecordsClient as useRecordsClient26 } from "@ai-matrx/records/react";
11436
11876
  import { Badge as Badge14, Button as Button35, Skeleton as Skeleton19, Switch as Switch2, cn as cn35 } from "@ai-matrx/design-system";
11437
11877
  import { jsx as jsx41, jsxs as jsxs37 } from "react/jsx-runtime";
11878
+ 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.";
11438
11879
  function whenItFires(subscription) {
11439
11880
  if (subscription.cadence === "instant") return "as it happens";
11440
11881
  const every = subscription.cadence === "hourly" ? "an hourly summary" : subscription.cadence === "weekly" ? "a weekly summary" : "a daily summary";
@@ -11442,7 +11883,7 @@ function whenItFires(subscription) {
11442
11883
  }
11443
11884
  var CHANNEL_WORDS2 = SUBSCRIPTION_CHANNEL_LABEL;
11444
11885
  var DIGEST_SUGGESTION = "Email me a summary of this every Monday at 8 in the morning.";
11445
- function SubscriptionsPanel({ tableId, className }) {
11886
+ function SubscriptionsPanel({ tableId, activeRuleId, className }) {
11446
11887
  const client = useRecordsClient26();
11447
11888
  const [rows, setRows] = useState38(null);
11448
11889
  const [error, setError] = useState38(null);
@@ -11460,7 +11901,7 @@ function SubscriptionsPanel({ tableId, className }) {
11460
11901
  setError(null);
11461
11902
  setRows(answered.data);
11462
11903
  }, [client, tableId]);
11463
- useEffect25(() => {
11904
+ useEffect27(() => {
11464
11905
  void load();
11465
11906
  }, [load]);
11466
11907
  const flip = useCallback23(
@@ -11533,96 +11974,119 @@ function SubscriptionsPanel({ tableId, className }) {
11533
11974
  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."
11534
11975
  }
11535
11976
  ) : null,
11536
- /* @__PURE__ */ jsx41("ul", { className: "flex flex-col gap-2", children: rows.map((subscription) => /* @__PURE__ */ jsxs37("li", { className: "rounded-md border p-2.5", children: [
11537
- /* @__PURE__ */ jsxs37("div", { className: "flex items-start gap-2", children: [
11538
- /* @__PURE__ */ jsxs37("div", { className: "min-w-0 flex-1", children: [
11539
- /* @__PURE__ */ jsx41("p", { className: "truncate text-sm font-medium", children: subscription.name }),
11540
- /* @__PURE__ */ jsxs37("p", { className: "mt-0.5 text-xs text-muted-foreground", children: [
11541
- CHANNEL_WORDS2[subscription.channel] ?? `on the ${subscription.channel} channel`,
11542
- " \xB7 ",
11543
- whenItFires(subscription)
11544
- ] })
11545
- ] }),
11546
- subscription.i_may_mute ? /* @__PURE__ */ jsx41(
11547
- Switch2,
11548
- {
11549
- checked: !subscription.muted,
11550
- disabled: busy === subscription.rule_id,
11551
- "aria-label": `Tell me about ${subscription.name}`,
11552
- onCheckedChange: (on) => void flip(subscription, on)
11553
- }
11554
- ) : /* @__PURE__ */ jsx41(Badge14, { variant: "secondary", children: "someone else's" })
11555
- ] }),
11556
- /* @__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." }),
11557
- /* @__PURE__ */ jsxs37("div", { className: "mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
11558
- subscription.next_digest_at ? /* @__PURE__ */ jsxs37("span", { children: [
11559
- "Next summary ",
11560
- new Date(subscription.next_digest_at).toLocaleString()
11561
- ] }) : subscription.cadence === "instant" ? null : subscription.muted ? /* @__PURE__ */ jsx41("span", { children: "No next summary while it is off." }) : null,
11562
- subscription.last_sent_at ? /* @__PURE__ */ jsxs37("span", { children: [
11563
- "Last told you ",
11564
- new Date(subscription.last_sent_at).toLocaleString()
11565
- ] }) : /* @__PURE__ */ jsx41("span", { children: "It has not told you anything yet." }),
11566
- subscription.quiet_hours ? /* @__PURE__ */ jsxs37("span", { children: [
11567
- "Not between ",
11568
- subscription.quiet_hours.start,
11569
- " and ",
11570
- subscription.quiet_hours.end,
11571
- " \u2014 a send inside those hours waits until they end."
11572
- ] }) : null,
11573
- subscription.cadence === "instant" ? null : /* @__PURE__ */ jsx41(
11574
- Button35,
11575
- {
11576
- size: "sm",
11577
- variant: "ghost",
11578
- disabled: busy === subscription.rule_id,
11579
- onClick: () => void showOne(subscription),
11580
- children: "Send me a preview now"
11581
- }
11582
- )
11583
- ] }),
11584
- 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: [
11585
- /* @__PURE__ */ jsx41("p", { className: "font-medium", children: preview.subject }),
11586
- /* @__PURE__ */ jsx41("p", { className: "mt-1 text-muted-foreground", children: preview.body }),
11587
- preview.incomplete ? (
11588
- // Absent or honest: a subscription that cannot produce a summary
11589
- // says which piece is missing instead of showing an empty one.
11590
- /* @__PURE__ */ jsx41("p", { className: "mt-1 text-destructive", children: preview.incomplete })
11591
- ) : null,
11592
- preview.entered.length > 0 ? /* @__PURE__ */ jsxs37("p", { className: "mt-1", children: [
11593
- /* @__PURE__ */ jsx41("span", { className: "font-medium", children: "Arrived:" }),
11594
- " ",
11595
- preview.entered.map((e) => e.name).join(", ")
11596
- ] }) : null,
11597
- preview.left.length > 0 ? /* @__PURE__ */ jsxs37("p", { className: "mt-1", children: [
11598
- /* @__PURE__ */ jsx41("span", { className: "font-medium", children: "Left:" }),
11599
- " ",
11600
- preview.left.map((e) => e.name).join(", ")
11601
- ] }) : null,
11602
- preview.changed.length > 0 ? /* @__PURE__ */ jsxs37("p", { className: "mt-1", children: [
11603
- /* @__PURE__ */ jsx41("span", { className: "font-medium", children: "Changed:" }),
11604
- " ",
11605
- preview.changed.map((e) => e.name).join(", ")
11606
- ] }) : null,
11607
- /* @__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." }),
11608
- /* @__PURE__ */ jsx41(Button35, { size: "sm", variant: "ghost", className: "mt-1", onClick: () => setPreview(null), children: "Close" })
11609
- ] }) : null
11610
- ] }, subscription.rule_id)) })
11977
+ 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,
11978
+ /* @__PURE__ */ jsx41("ul", { className: "flex flex-col gap-2", children: rows.map((subscription) => /* @__PURE__ */ jsxs37(
11979
+ "li",
11980
+ {
11981
+ "data-linked": subscription.rule_id === activeRuleId ? "true" : void 0,
11982
+ ref: subscription.rule_id === activeRuleId ? (el) => el?.scrollIntoView({ block: "nearest" }) : void 0,
11983
+ className: cn35(
11984
+ "rounded-md border p-2.5",
11985
+ subscription.rule_id === activeRuleId && "border-primary ring-1 ring-primary"
11986
+ ),
11987
+ children: [
11988
+ /* @__PURE__ */ jsxs37("div", { className: "flex items-start gap-2", children: [
11989
+ /* @__PURE__ */ jsxs37("div", { className: "min-w-0 flex-1", children: [
11990
+ /* @__PURE__ */ jsx41("p", { className: "truncate text-sm font-medium", children: subscription.name }),
11991
+ /* @__PURE__ */ jsxs37("p", { className: "mt-0.5 text-xs text-muted-foreground", children: [
11992
+ CHANNEL_WORDS2[subscription.channel] ?? `on the ${subscription.channel} channel`,
11993
+ " \xB7 ",
11994
+ whenItFires(subscription)
11995
+ ] })
11996
+ ] }),
11997
+ subscription.i_may_mute ? /* @__PURE__ */ jsx41(
11998
+ Switch2,
11999
+ {
12000
+ checked: !subscription.muted,
12001
+ disabled: busy === subscription.rule_id,
12002
+ "aria-label": `Tell me about ${subscription.name}`,
12003
+ onCheckedChange: (on) => void flip(subscription, on)
12004
+ }
12005
+ ) : /* @__PURE__ */ jsx41(Badge14, { variant: "secondary", children: "someone else's" })
12006
+ ] }),
12007
+ /* @__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." }),
12008
+ /* @__PURE__ */ jsxs37("div", { className: "mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
12009
+ subscription.next_digest_at ? /* @__PURE__ */ jsxs37("span", { children: [
12010
+ "Next summary ",
12011
+ new Date(subscription.next_digest_at).toLocaleString()
12012
+ ] }) : subscription.cadence === "instant" ? null : subscription.muted ? /* @__PURE__ */ jsx41("span", { children: "No next summary while it is off." }) : null,
12013
+ subscription.last_sent_at ? /* @__PURE__ */ jsxs37("span", { children: [
12014
+ "Last told you ",
12015
+ new Date(subscription.last_sent_at).toLocaleString()
12016
+ ] }) : /* @__PURE__ */ jsx41("span", { children: "It has not told you anything yet." }),
12017
+ subscription.quiet_hours ? /* @__PURE__ */ jsxs37("span", { children: [
12018
+ "Not between ",
12019
+ subscription.quiet_hours.start,
12020
+ " and ",
12021
+ subscription.quiet_hours.end,
12022
+ " \u2014 a send inside those hours waits until they end."
12023
+ ] }) : null,
12024
+ subscription.cadence === "instant" ? null : /* @__PURE__ */ jsx41(
12025
+ Button35,
12026
+ {
12027
+ size: "sm",
12028
+ variant: "ghost",
12029
+ disabled: busy === subscription.rule_id,
12030
+ onClick: () => void showOne(subscription),
12031
+ children: "Send me a preview now"
12032
+ }
12033
+ )
12034
+ ] }),
12035
+ 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: [
12036
+ /* @__PURE__ */ jsx41("p", { className: "font-medium", children: preview.subject }),
12037
+ /* @__PURE__ */ jsx41("p", { className: "mt-1 text-muted-foreground", children: preview.body }),
12038
+ preview.incomplete ? (
12039
+ // Absent or honest: a subscription that cannot produce a summary
12040
+ // says which piece is missing instead of showing an empty one.
12041
+ /* @__PURE__ */ jsx41(
12042
+ RefusalLine,
12043
+ {
12044
+ className: "mt-1",
12045
+ error: refusal(
12046
+ "not_supported",
12047
+ preview.incomplete,
12048
+ "Fill in the missing piece, then preview again."
12049
+ )
12050
+ }
12051
+ )
12052
+ ) : null,
12053
+ preview.entered.length > 0 ? /* @__PURE__ */ jsxs37("p", { className: "mt-1", children: [
12054
+ /* @__PURE__ */ jsx41("span", { className: "font-medium", children: "Arrived:" }),
12055
+ " ",
12056
+ preview.entered.map((e) => e.name).join(", ")
12057
+ ] }) : null,
12058
+ preview.left.length > 0 ? /* @__PURE__ */ jsxs37("p", { className: "mt-1", children: [
12059
+ /* @__PURE__ */ jsx41("span", { className: "font-medium", children: "Left:" }),
12060
+ " ",
12061
+ preview.left.map((e) => e.name).join(", ")
12062
+ ] }) : null,
12063
+ preview.changed.length > 0 ? /* @__PURE__ */ jsxs37("p", { className: "mt-1", children: [
12064
+ /* @__PURE__ */ jsx41("span", { className: "font-medium", children: "Changed:" }),
12065
+ " ",
12066
+ preview.changed.map((e) => e.name).join(", ")
12067
+ ] }) : null,
12068
+ /* @__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." }),
12069
+ /* @__PURE__ */ jsx41(Button35, { size: "sm", variant: "ghost", className: "mt-1", onClick: () => setPreview(null), children: "Close" })
12070
+ ] }) : null
12071
+ ]
12072
+ },
12073
+ subscription.rule_id
12074
+ )) })
11611
12075
  ] });
11612
12076
  }
11613
12077
 
11614
12078
  // src/FormBuilder.tsx
11615
- import { useCallback as useCallback25, useEffect as useEffect28, useMemo as useMemo26, useState as useState41 } from "react";
12079
+ import { useCallback as useCallback25, useEffect as useEffect30, useMemo as useMemo26, useState as useState41 } from "react";
11616
12080
  import { useFields as useFields18, useRecordsClient as useRecordsClient28, useTable as useTable14 } from "@ai-matrx/records/react";
11617
12081
 
11618
12082
  // src/publish-gate.ts
11619
- import { useEffect as useEffect26, useState as useState39 } from "react";
12083
+ import { useEffect as useEffect28, useState as useState39 } from "react";
11620
12084
  import { useRecordsClient as useRecordsClient27 } from "@ai-matrx/records/react";
11621
12085
  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.";
11622
12086
  function usePublishGate() {
11623
12087
  const client = useRecordsClient27();
11624
12088
  const [storeOpen, setStoreOpen] = useState39(null);
11625
- useEffect26(() => {
12089
+ useEffect28(() => {
11626
12090
  let alive = true;
11627
12091
  void (async () => {
11628
12092
  const answered = await client.storeIsOpen();
@@ -11653,7 +12117,7 @@ import {
11653
12117
  } from "@ai-matrx/design-system";
11654
12118
 
11655
12119
  // src/FormRunner.tsx
11656
- import { useCallback as useCallback24, useEffect as useEffect27, useMemo as useMemo25, useRef as useRef11, useState as useState40 } from "react";
12120
+ import { useCallback as useCallback24, useEffect as useEffect29, useMemo as useMemo25, useRef as useRef11, useState as useState40 } from "react";
11657
12121
  import { useFields as useFields17, useOptionalRecordsClient as useOptionalRecordsClient4 } from "@ai-matrx/records/react";
11658
12122
  import { Button as Button36, Progress, Skeleton as Skeleton20, cn as cn36 } from "@ai-matrx/design-system";
11659
12123
  import { Fragment as Fragment18, jsx as jsx42, jsxs as jsxs38 } from "react/jsx-runtime";
@@ -11670,9 +12134,16 @@ function ConnectedFormRunner(props) {
11670
12134
  const submit = useCallback24(
11671
12135
  async (values) => {
11672
12136
  if (!client) {
12137
+ const said = "This form cannot send answers from here, so nothing was sent.";
11673
12138
  return {
11674
12139
  ok: false,
11675
- 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."
12140
+ message: said,
12141
+ error: refusal(
12142
+ "not_supported",
12143
+ said,
12144
+ "Open the form from its own link and answer it there.",
12145
+ "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."
12146
+ )
11676
12147
  };
11677
12148
  }
11678
12149
  const document2 = {
@@ -11685,7 +12156,7 @@ function ConnectedFormRunner(props) {
11685
12156
  })
11686
12157
  };
11687
12158
  const written = await client.recordWrite({ table_id: form.subject, data: document2 });
11688
- if (!written.ok) return { ok: false, message: written.error.message };
12159
+ if (!written.ok) return { ok: false, message: written.error.message, error: written.error };
11689
12160
  return { ok: true, recordId: written.data };
11690
12161
  },
11691
12162
  [client, form]
@@ -11727,7 +12198,7 @@ function FormStage({
11727
12198
  const [answers, setAnswers] = useState40({});
11728
12199
  const [at, setAt] = useState40(0);
11729
12200
  const [error, setError] = useState40(null);
11730
- const [refusal, setRefusal] = useState40(null);
12201
+ const [refusal2, setRefusal] = useState40(null);
11731
12202
  const [writing, setWriting] = useState40(false);
11732
12203
  const [done, setDone] = useState40(null);
11733
12204
  const [hidden, setHidden] = useState40({});
@@ -11747,7 +12218,7 @@ function FormStage({
11747
12218
  };
11748
12219
  });
11749
12220
  }, [fields, form.questions]);
11750
- useEffect27(() => {
12221
+ useEffect29(() => {
11751
12222
  let cancelled = false;
11752
12223
  const conditional = questions.filter((q) => q.showIf);
11753
12224
  if (conditional.length === 0 || !evaluate) return;
@@ -11796,12 +12267,21 @@ function FormStage({
11796
12267
  const outcome = await onSubmit(values);
11797
12268
  setWriting(false);
11798
12269
  if (!outcome.ok) {
11799
- setRefusal(outcome.message);
12270
+ const inWords = (text) => questions.reduce(
12271
+ (said, q) => said.replace(new RegExp(`\\b${q.key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "g"), q.ask),
12272
+ text
12273
+ );
12274
+ const told = outcome.error ?? refusal("invalid_argument", outcome.message, "Check your answers, then send again.");
12275
+ setRefusal({
12276
+ ...told,
12277
+ message: inWords(told.message),
12278
+ ...told.hint ? { hint: inWords(told.hint) } : {}
12279
+ });
11800
12280
  return;
11801
12281
  }
11802
12282
  setDone(outcome.message ?? "sent");
11803
12283
  onSubmitted?.(outcome.recordId ?? null);
11804
- }, [answers, form, honeypotKey, live, onSubmit, onSubmitted, preview]);
12284
+ }, [answers, form, honeypotKey, live, onSubmit, onSubmitted, preview, questions]);
11805
12285
  function advance() {
11806
12286
  if (!oneAtATime) return;
11807
12287
  if (index < live.length - 1) setAt(index + 1);
@@ -11818,7 +12298,7 @@ function FormStage({
11818
12298
  if (event.shiftKey) retreat();
11819
12299
  else advance();
11820
12300
  }
11821
- useEffect27(() => {
12301
+ useEffect29(() => {
11822
12302
  const input = stage.current?.querySelector(
11823
12303
  "input:not([tabindex='-1']), textarea, select, [role='combobox']"
11824
12304
  );
@@ -11860,7 +12340,22 @@ function FormStage({
11860
12340
  ] }) : null,
11861
12341
  form.intro && index === 0 ? /* @__PURE__ */ jsx42("p", { className: "text-sm text-muted-foreground", children: form.intro }) : null,
11862
12342
  error ? /* @__PURE__ */ jsx42(RefusalNotice, { error, className: "text-left" }) : null,
11863
- 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,
12343
+ refusal2 ? /* @__PURE__ */ jsx42(
12344
+ RefusalNotice,
12345
+ {
12346
+ error: refusal2,
12347
+ className: "text-left",
12348
+ onKeepEditing: () => {
12349
+ setRefusal(null);
12350
+ stage.current?.querySelector("input, textarea, select, button")?.focus();
12351
+ },
12352
+ onDiscard: () => {
12353
+ setRefusal(null);
12354
+ setAnswers({});
12355
+ setAt(0);
12356
+ }
12357
+ }
12358
+ ) : null,
11864
12359
  /* @__PURE__ */ jsxs38("div", { ref: stage, className: "flex flex-col gap-4 text-left", children: [
11865
12360
  (oneAtATime ? current ? [current] : [] : live).map((q) => /* @__PURE__ */ jsx42(
11866
12361
  Question,
@@ -11934,12 +12429,15 @@ function Question({
11934
12429
  void upload?.(file).then((result) => {
11935
12430
  if (!result) return;
11936
12431
  if (result.ok) onChange(result.fileId);
11937
- else setUploadError(result.reason);
12432
+ else
12433
+ setUploadError(
12434
+ refusal("invalid_argument", result.reason, "Pick another file, or try this one again.")
12435
+ );
11938
12436
  });
11939
12437
  }
11940
12438
  }
11941
12439
  ),
11942
- uploadError ? /* @__PURE__ */ jsx42("p", { className: "text-xs text-destructive", children: uploadError }) : null
12440
+ uploadError ? /* @__PURE__ */ jsx42(RefusalLine, { error: uploadError }) : null
11943
12441
  ] }) : /* @__PURE__ */ jsx42(FieldControl, { field, value, onChange, id })
11944
12442
  ] });
11945
12443
  }
@@ -12013,10 +12511,10 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, className }) {
12013
12511
  setError(null);
12014
12512
  setForms(mine);
12015
12513
  }, [client, tableId, seed, claimSeed]);
12016
- useEffect28(() => {
12514
+ useEffect30(() => {
12017
12515
  void load();
12018
12516
  }, [load]);
12019
- useEffect28(() => {
12517
+ useEffect30(() => {
12020
12518
  if (!forms || forms.length === 0) return;
12021
12519
  const chosen = forms.find((f) => f.id === (activeFormId ?? activeId)) ?? forms[0];
12022
12520
  if (chosen.id !== activeId) setActiveId(chosen.id);
@@ -12427,7 +12925,7 @@ function groupLabel(groups) {
12427
12925
 
12428
12926
  // src/chartFrame.tsx
12429
12927
  import {
12430
- useEffect as useEffect29,
12928
+ useEffect as useEffect31,
12431
12929
  useId as useId2,
12432
12930
  useRef as useRef12,
12433
12931
  useState as useState42
@@ -12437,7 +12935,7 @@ import { jsx as jsx44, jsxs as jsxs40 } from "react/jsx-runtime";
12437
12935
  function useMeasuredWidth(fallback = 480) {
12438
12936
  const ref = useRef12(null);
12439
12937
  const [width, setWidth] = useState42(fallback);
12440
- useEffect29(() => {
12938
+ useEffect31(() => {
12441
12939
  const node = ref.current;
12442
12940
  if (!node) return;
12443
12941
  const apply = () => {
@@ -12561,7 +13059,7 @@ function isSignatureField(field) {
12561
13059
  }
12562
13060
 
12563
13061
  // src/DocTemplate.tsx
12564
- import { useCallback as useCallback26, useEffect as useEffect30, useState as useState43 } from "react";
13062
+ import { useCallback as useCallback26, useEffect as useEffect32, useState as useState43 } from "react";
12565
13063
  import { useFields as useFields19, useRecordsClient as useRecordsClient29, useTable as useTable15 } from "@ai-matrx/records/react";
12566
13064
  import { BasicInput as BasicInput14, BasicTextarea as BasicTextarea6, Button as Button38, Label as Label8, Skeleton as Skeleton22, cn as cn39 } from "@ai-matrx/design-system";
12567
13065
  import { jsx as jsx45, jsxs as jsxs41 } from "react/jsx-runtime";
@@ -12609,10 +13107,10 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
12609
13107
  setError(null);
12610
13108
  setTemplates(rows);
12611
13109
  }, [client, tableId, seed, fields.data]);
12612
- useEffect30(() => {
13110
+ useEffect32(() => {
12613
13111
  void load();
12614
13112
  }, [load]);
12615
- useEffect30(() => {
13113
+ useEffect32(() => {
12616
13114
  if (!templates || templates.length === 0) return;
12617
13115
  const chosen = templates.find((t) => t.id === (activeTemplateId ?? activeId)) ?? templates[0];
12618
13116
  if (chosen.id !== activeId) setActiveId(chosen.id);
@@ -12620,7 +13118,7 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
12620
13118
  setDraftBody(chosen.body);
12621
13119
  onActiveTemplate?.(chosen);
12622
13120
  }, [templates, activeTemplateId]);
12623
- useEffect30(() => {
13121
+ useEffect32(() => {
12624
13122
  let cancelled = false;
12625
13123
  if (draftBody.trim() === "") {
12626
13124
  setUnresolved([]);
@@ -12731,16 +13229,21 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
12731
13229
  onChange: (e) => setDraftBody(e.target.value)
12732
13230
  }
12733
13231
  ),
12734
- 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: [
12735
- /* @__PURE__ */ jsx45("code", { children: token.raw }),
12736
- " \u2014 ",
12737
- token.why
13232
+ 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: [
13233
+ /* @__PURE__ */ jsx45("code", { className: "text-destructive", children: token.raw }),
13234
+ /* @__PURE__ */ jsx45("span", { "aria-hidden": "true", children: "\u2014" }),
13235
+ /* @__PURE__ */ jsx45(
13236
+ RefusalLine,
13237
+ {
13238
+ error: refusal("invalid_argument", token.why, "Change it to a Field this table has, or take it out.")
13239
+ }
13240
+ )
12738
13241
  ] }, 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
12739
13242
  ] });
12740
13243
  }
12741
13244
 
12742
13245
  // src/DocRender.tsx
12743
- import { useCallback as useCallback27, useEffect as useEffect31, useRef as useRef13, useState as useState44 } from "react";
13246
+ import { useCallback as useCallback27, useEffect as useEffect33, useRef as useRef13, useState as useState44 } from "react";
12744
13247
  import { useRecordsClient as useRecordsClient30 } from "@ai-matrx/records/react";
12745
13248
  import { Button as Button39, Skeleton as Skeleton23, cn as cn40 } from "@ai-matrx/design-system";
12746
13249
  import { jsx as jsx46, jsxs as jsxs42 } from "react/jsx-runtime";
@@ -12769,7 +13272,7 @@ function DocRender({ templateId, recordId, filename, onRendered, className }) {
12769
13272
  setPreview(body.data);
12770
13273
  setRenders(held.data.filter((r) => r.template_id === templateId));
12771
13274
  }, [client, templateId, recordId]);
12772
- useEffect31(() => {
13275
+ useEffect33(() => {
12773
13276
  void load();
12774
13277
  }, [load]);
12775
13278
  async function freeze() {
@@ -12853,7 +13356,7 @@ function DocRender({ templateId, recordId, filename, onRendered, className }) {
12853
13356
  }
12854
13357
 
12855
13358
  // src/SignBlock.tsx
12856
- import { useCallback as useCallback28, useEffect as useEffect32, useState as useState45 } from "react";
13359
+ import { useCallback as useCallback28, useEffect as useEffect34, useState as useState45 } from "react";
12857
13360
  import { useFields as useFields20, useRecordsClient as useRecordsClient31 } from "@ai-matrx/records/react";
12858
13361
  import { BasicInput as BasicInput15, Button as Button40, Skeleton as Skeleton24, cn as cn41 } from "@ai-matrx/design-system";
12859
13362
  import { useTable as useTable16 } from "@ai-matrx/records/react";
@@ -12883,7 +13386,7 @@ function SignBlock({ tableId, recordId, render, className }) {
12883
13386
  }
12884
13387
  setVerdicts(answers);
12885
13388
  }, [client, recordId]);
12886
- useEffect32(() => {
13389
+ useEffect34(() => {
12887
13390
  void load();
12888
13391
  }, [load]);
12889
13392
  async function sign(field) {
@@ -12939,7 +13442,21 @@ function SignBlock({ tableId, recordId, render, className }) {
12939
13442
  signature.document_hash.slice(0, 12)
12940
13443
  ] })
12941
13444
  ] }),
12942
- /* @__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 ?? "") })
13445
+ intact === false ? (
13446
+ // THE STORE REFUSED TO CALL IT INTACT: its reason goes through the one
13447
+ // formatter, with what to do next, never raw in red (lane REFUSAL-SWEEP).
13448
+ /* @__PURE__ */ jsx47(
13449
+ RefusalLine,
13450
+ {
13451
+ className: "mt-0.5",
13452
+ error: refusal(
13453
+ "refused_by_rule",
13454
+ typeof verdict === "object" && verdict !== null && "reason" in verdict ? String(verdict.reason) : "The store says what is there no longer matches what was signed.",
13455
+ "Ask for it to be signed again on the version that is there now."
13456
+ )
13457
+ }
13458
+ )
13459
+ ) : /* @__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 ?? "") })
12943
13460
  ] }, signature.id);
12944
13461
  }) }) : null,
12945
13462
  !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: [
@@ -12990,7 +13507,7 @@ function SignBlock({ tableId, recordId, render, className }) {
12990
13507
  }
12991
13508
 
12992
13509
  // src/NotifyRuleEditor.tsx
12993
- import { useCallback as useCallback29, useEffect as useEffect33, useState as useState46 } from "react";
13510
+ import { useCallback as useCallback29, useEffect as useEffect35, useState as useState46 } from "react";
12994
13511
  import { useRecordsClient as useRecordsClient32, useTable as useTable17 } from "@ai-matrx/records/react";
12995
13512
  import { Button as Button41, Skeleton as Skeleton25, cn as cn42 } from "@ai-matrx/design-system";
12996
13513
  import { jsx as jsx48, jsxs as jsxs44 } from "react/jsx-runtime";
@@ -13026,7 +13543,7 @@ function NotifyRuleEditor({ tableId, seed, className }) {
13026
13543
  if (host.savedViews) setViews(await host.savedViews());
13027
13544
  else setViews(null);
13028
13545
  }, [client, host, tableId]);
13029
- useEffect33(() => {
13546
+ useEffect35(() => {
13030
13547
  void load();
13031
13548
  }, [load]);
13032
13549
  const write = useCallback29(
@@ -13054,7 +13571,7 @@ function NotifyRuleEditor({ tableId, seed, className }) {
13054
13571
  },
13055
13572
  [client, tableId]
13056
13573
  );
13057
- useEffect33(() => {
13574
+ useEffect35(() => {
13058
13575
  if (!subscriptions || !seed || seed.length === 0) return;
13059
13576
  const missing = seed.filter((s) => !subscriptions.some((held) => held.name === s.name));
13060
13577
  if (missing.length === 0) return;
@@ -13221,7 +13738,7 @@ import { Fragment as Fragment20, jsx as jsx49, jsxs as jsxs45 } from "react/jsx-
13221
13738
  function pretty(n) {
13222
13739
  return Number.isInteger(n) ? n.toLocaleString() : n.toLocaleString(void 0, { maximumFractionDigits: 2 });
13223
13740
  }
13224
- function asRefusal(block) {
13741
+ function asRefusal2(block) {
13225
13742
  return mapPgError(
13226
13743
  {
13227
13744
  message: block.refused ?? "",
@@ -13282,7 +13799,7 @@ function ChartBlock({ block, subject, className }) {
13282
13799
  " ms"
13283
13800
  ] }) : null
13284
13801
  ] }),
13285
- 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: [
13802
+ 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: [
13286
13803
  /* @__PURE__ */ jsx49(Drawing, { kind, points, series, config, onDrill: drill }),
13287
13804
  /* @__PURE__ */ jsx49(Values, { points, measure: primary, config, onDrill: drill })
13288
13805
  ] }) })
@@ -13510,7 +14027,7 @@ function Drawing({
13510
14027
  }
13511
14028
 
13512
14029
  // src/DashboardCanvas.tsx
13513
- import { useCallback as useCallback30, useEffect as useEffect34, useMemo as useMemo28, useState as useState47 } from "react";
14030
+ import { useCallback as useCallback30, useEffect as useEffect36, useMemo as useMemo28, useState as useState47 } from "react";
13514
14031
  import { useFields as useFields21, useRecordsClient as useRecordsClient33, useTable as useTable18 } from "@ai-matrx/records/react";
13515
14032
  import {
13516
14033
  BasicInput as BasicInput16,
@@ -13547,17 +14064,17 @@ function DashboardCanvas({ tableId, activeDashboardId, filter, className }) {
13547
14064
  setError(null);
13548
14065
  setBoards(answered.data.map(dashboardFromSummary));
13549
14066
  }, [client, tableId]);
13550
- useEffect34(() => {
14067
+ useEffect36(() => {
13551
14068
  void load();
13552
14069
  }, [load]);
13553
- useEffect34(() => {
14070
+ useEffect36(() => {
13554
14071
  if (!boards || boards.length === 0) return;
13555
14072
  const chosen = boards.find((d) => d.id === (activeDashboardId ?? activeId)) ?? boards[0];
13556
14073
  if (chosen.id !== activeId) setActiveId(chosen.id);
13557
14074
  }, [boards, activeDashboardId]);
13558
14075
  const board = useMemo28(() => boards?.find((d) => d.id === activeId) ?? null, [boards, activeId]);
13559
14076
  const filterKey = JSON.stringify(filter ?? {});
13560
- useEffect34(() => {
14077
+ useEffect36(() => {
13561
14078
  if (!activeId) {
13562
14079
  setRun(null);
13563
14080
  return;
@@ -13853,18 +14370,19 @@ function GroupingPicker({
13853
14370
  }
13854
14371
 
13855
14372
  // src/FormsPanel.tsx
13856
- import { useCallback as useCallback31, useEffect as useEffect35, useState as useState48 } from "react";
14373
+ import { useCallback as useCallback31, useEffect as useEffect37, useState as useState48 } from "react";
13857
14374
  import { useRecordsClient as useRecordsClient34, useTable as useTable19 } from "@ai-matrx/records/react";
13858
14375
  import { publicFormPath as publicFormPath2 } from "@ai-matrx/records";
13859
14376
  import { Badge as Badge15, Button as Button43, Skeleton as Skeleton27, cn as cn45 } from "@ai-matrx/design-system";
13860
14377
  import { Fragment as Fragment22, jsx as jsx51, jsxs as jsxs47 } from "react/jsx-runtime";
14378
+ 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.";
13861
14379
  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.";
13862
14380
  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.";
13863
14381
  function formSuggestion(tableName2) {
13864
14382
  const subject = tableName2?.trim() ? tableName2.trim() : "this table";
13865
14383
  return `Make me a form that collects new ${subject} entries and tells me when somebody answers.`;
13866
14384
  }
13867
- function FormsPanel({ tableId, className }) {
14385
+ function FormsPanel({ tableId, activeFormId, className }) {
13868
14386
  const client = useRecordsClient34();
13869
14387
  const publishGate = usePublishGate();
13870
14388
  const host = useRecordsUi();
@@ -13875,6 +14393,11 @@ function FormsPanel({ tableId, className }) {
13875
14393
  const [busy, setBusy] = useState48(null);
13876
14394
  const [copied, setCopied] = useState48(null);
13877
14395
  const [building, setBuilding] = useState48(false);
14396
+ const linked = activeFormId && forms !== null ? forms.find((f) => f.form_id === activeFormId) ?? null : null;
14397
+ const linkedMissing = Boolean(activeFormId) && forms !== null && linked === null;
14398
+ useEffect37(() => {
14399
+ if (linked && rights.structure) setBuilding(true);
14400
+ }, [linked, rights.structure]);
13878
14401
  const load = useCallback31(async () => {
13879
14402
  const answered = await client.forms({ table_id: tableId });
13880
14403
  if (!answered.ok) {
@@ -13885,7 +14408,7 @@ function FormsPanel({ tableId, className }) {
13885
14408
  setError(null);
13886
14409
  setForms(answered.data);
13887
14410
  }, [client, tableId]);
13888
- useEffect35(() => {
14411
+ useEffect37(() => {
13889
14412
  void load();
13890
14413
  }, [load]);
13891
14414
  const toggle = useCallback31(
@@ -13923,10 +14446,12 @@ function FormsPanel({ tableId, className }) {
13923
14446
  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
13924
14447
  ] }),
13925
14448
  error ? /* @__PURE__ */ jsx51(RefusalNotice, { error }) : null,
14449
+ 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,
13926
14450
  building ? /* @__PURE__ */ jsx51(
13927
14451
  FormBuilder,
13928
14452
  {
13929
14453
  tableId,
14454
+ activeFormId: linked ? linked.form_id : null,
13930
14455
  onActiveForm: () => {
13931
14456
  void load();
13932
14457
  }
@@ -13952,57 +14477,69 @@ function FormsPanel({ tableId, className }) {
13952
14477
  ) : null,
13953
14478
  /* @__PURE__ */ jsx51("ul", { className: "flex flex-col gap-2", children: forms.map((form) => {
13954
14479
  const url = `${origin}${publicFormPath2(form.form_id)}`;
13955
- return /* @__PURE__ */ jsxs47("li", { className: "rounded-md border p-2.5", children: [
13956
- /* @__PURE__ */ jsxs47("div", { className: "flex items-center gap-2", children: [
13957
- /* @__PURE__ */ jsx51("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: form.title ?? form.slug }),
13958
- /* @__PURE__ */ jsx51(Badge15, { variant: form.state === "open" ? "default" : "secondary", children: form.state })
13959
- ] }),
13960
- /* @__PURE__ */ jsx51("p", { className: "mt-0.5 text-xs text-muted-foreground", children: FORM_STATE_WORDS[form.state] }),
13961
- /* @__PURE__ */ jsxs47("p", { className: "mt-1.5 text-xs", children: [
13962
- /* @__PURE__ */ jsx51("span", { className: "font-medium", children: form.in_table }),
13963
- " in the table",
13964
- form.held > 0 ? /* @__PURE__ */ jsxs47(Fragment22, { children: [
13965
- " \xB7 ",
13966
- /* @__PURE__ */ jsx51("span", { className: "font-medium", children: form.held }),
13967
- " waiting for someone"
13968
- ] }) : null,
13969
- form.rejected > 0 ? /* @__PURE__ */ jsxs47(Fragment22, { children: [
13970
- " \xB7 ",
13971
- /* @__PURE__ */ jsx51("span", { className: "font-medium", children: form.rejected }),
13972
- " turned away"
13973
- ] }) : null,
13974
- form.submission_cap !== null ? /* @__PURE__ */ jsxs47("span", { className: "text-muted-foreground", children: [
13975
- " \xB7 stops at ",
13976
- form.submission_cap
13977
- ] }) : null
13978
- ] }),
13979
- /* @__PURE__ */ jsxs47("div", { className: "mt-2 flex flex-wrap items-center gap-1.5", children: [
13980
- 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,
13981
- rights.structure && !publishGate.blocked ? /* @__PURE__ */ jsx51(
13982
- Button43,
13983
- {
13984
- size: "sm",
13985
- variant: "ghost",
13986
- disabled: busy === form.form_id,
13987
- onClick: () => void toggle(form),
13988
- children: busy === form.form_id ? "\u2026" : form.published_at && !form.closed_at ? "Unpublish" : "Publish"
13989
- }
13990
- ) : null
13991
- ] }),
13992
- rights.structure && publishGate.why ? /* @__PURE__ */ jsx51("p", { className: "mt-1.5 text-xs text-muted-foreground", children: publishGate.why }) : null,
13993
- shown && shown === url ? /* @__PURE__ */ jsxs47("p", { className: "mt-1.5 break-all rounded border border-dashed px-2 py-1 text-xs", children: [
13994
- "This browser would not let the page copy for you, so here it is to copy by hand:",
13995
- " ",
13996
- url
13997
- ] }) : null
13998
- ] }, form.form_id);
14480
+ return /* @__PURE__ */ jsxs47(
14481
+ "li",
14482
+ {
14483
+ "data-linked": form.form_id === activeFormId ? "true" : void 0,
14484
+ ref: form.form_id === activeFormId ? (el) => el?.scrollIntoView({ block: "nearest" }) : void 0,
14485
+ className: cn45(
14486
+ "rounded-md border p-2.5",
14487
+ form.form_id === activeFormId && "border-primary ring-1 ring-primary"
14488
+ ),
14489
+ children: [
14490
+ /* @__PURE__ */ jsxs47("div", { className: "flex items-center gap-2", children: [
14491
+ /* @__PURE__ */ jsx51("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: form.title ?? form.slug }),
14492
+ /* @__PURE__ */ jsx51(Badge15, { variant: form.state === "open" ? "default" : "secondary", children: form.state })
14493
+ ] }),
14494
+ /* @__PURE__ */ jsx51("p", { className: "mt-0.5 text-xs text-muted-foreground", children: FORM_STATE_WORDS[form.state] }),
14495
+ /* @__PURE__ */ jsxs47("p", { className: "mt-1.5 text-xs", children: [
14496
+ /* @__PURE__ */ jsx51("span", { className: "font-medium", children: form.in_table }),
14497
+ " in the table",
14498
+ form.held > 0 ? /* @__PURE__ */ jsxs47(Fragment22, { children: [
14499
+ " \xB7 ",
14500
+ /* @__PURE__ */ jsx51("span", { className: "font-medium", children: form.held }),
14501
+ " waiting for someone"
14502
+ ] }) : null,
14503
+ form.rejected > 0 ? /* @__PURE__ */ jsxs47(Fragment22, { children: [
14504
+ " \xB7 ",
14505
+ /* @__PURE__ */ jsx51("span", { className: "font-medium", children: form.rejected }),
14506
+ " turned away"
14507
+ ] }) : null,
14508
+ form.submission_cap !== null ? /* @__PURE__ */ jsxs47("span", { className: "text-muted-foreground", children: [
14509
+ " \xB7 stops at ",
14510
+ form.submission_cap
14511
+ ] }) : null
14512
+ ] }),
14513
+ /* @__PURE__ */ jsxs47("div", { className: "mt-2 flex flex-wrap items-center gap-1.5", children: [
14514
+ 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,
14515
+ rights.structure && !publishGate.blocked ? /* @__PURE__ */ jsx51(
14516
+ Button43,
14517
+ {
14518
+ size: "sm",
14519
+ variant: "ghost",
14520
+ disabled: busy === form.form_id,
14521
+ onClick: () => void toggle(form),
14522
+ children: busy === form.form_id ? "\u2026" : form.published_at && !form.closed_at ? "Unpublish" : "Publish"
14523
+ }
14524
+ ) : null
14525
+ ] }),
14526
+ rights.structure && publishGate.why ? /* @__PURE__ */ jsx51("p", { className: "mt-1.5 text-xs text-muted-foreground", children: publishGate.why }) : null,
14527
+ shown && shown === url ? /* @__PURE__ */ jsxs47("p", { className: "mt-1.5 break-all rounded border border-dashed px-2 py-1 text-xs", children: [
14528
+ "This browser would not let the page copy for you, so here it is to copy by hand:",
14529
+ " ",
14530
+ url
14531
+ ] }) : null
14532
+ ]
14533
+ },
14534
+ form.form_id
14535
+ );
13999
14536
  }) }),
14000
14537
  !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
14001
14538
  ] });
14002
14539
  }
14003
14540
 
14004
14541
  // src/BookingBuilder.tsx
14005
- import { useCallback as useCallback32, useEffect as useEffect36, useMemo as useMemo29, useState as useState49 } from "react";
14542
+ import { useCallback as useCallback32, useEffect as useEffect38, useMemo as useMemo29, useState as useState49 } from "react";
14006
14543
  import { useFields as useFields22, useRecordsClient as useRecordsClient35, useTable as useTable20 } from "@ai-matrx/records/react";
14007
14544
  import {
14008
14545
  bookingPath
@@ -14077,18 +14614,18 @@ function BookingBuilder({ tableId, bookingId, onSaved, onClose, className }) {
14077
14614
  setLoaded(true);
14078
14615
  if (mine) setFormId(mine.form_id);
14079
14616
  }, [client, tableId, bookingId]);
14080
- useEffect36(() => {
14617
+ useEffect38(() => {
14081
14618
  void load();
14082
14619
  }, [load]);
14083
- useEffect36(() => {
14620
+ useEffect38(() => {
14084
14621
  if (title !== "" || !table.data) return;
14085
14622
  setTitle(existing?.title ?? `Book a ${minutes}-minute ${table.data.name} appointment`);
14086
14623
  }, [table.data, existing]);
14087
- useEffect36(() => {
14624
+ useEffect38(() => {
14088
14625
  if (!existing) return;
14089
14626
  setMinutes(existing.slot_minutes);
14090
14627
  }, [existing]);
14091
- useEffect36(() => {
14628
+ useEffect38(() => {
14092
14629
  if (!offer) return;
14093
14630
  setWindows(draftWindows(offer));
14094
14631
  setMinutes(offer.slot_minutes);
@@ -14350,7 +14887,7 @@ function BookingBuilder({ tableId, bookingId, onSaved, onClose, className }) {
14350
14887
  }
14351
14888
 
14352
14889
  // src/BookingSlots.tsx
14353
- import { useCallback as useCallback33, useEffect as useEffect37, useMemo as useMemo30, useState as useState50 } from "react";
14890
+ import { useCallback as useCallback33, useEffect as useEffect39, useMemo as useMemo30, useState as useState50 } from "react";
14354
14891
  import { useRecordsClient as useRecordsClient36, useMyLevels as useMyLevels2 } from "@ai-matrx/records/react";
14355
14892
  import { bookingPath as bookingPath2 } from "@ai-matrx/records";
14356
14893
  import { Badge as Badge16, Button as Button45, Skeleton as Skeleton29, cn as cn47 } from "@ai-matrx/design-system";
@@ -14381,7 +14918,7 @@ function BookingSlots({ tableId, className }) {
14381
14918
  setError(null);
14382
14919
  setPages(answered.data);
14383
14920
  }, [client, tableId]);
14384
- useEffect37(() => {
14921
+ useEffect39(() => {
14385
14922
  void load();
14386
14923
  }, [load]);
14387
14924
  const subjectIds = useMemo30(
@@ -14550,12 +15087,12 @@ function nextInWords(page) {
14550
15087
  }
14551
15088
 
14552
15089
  // src/CaptureSheet.tsx
14553
- import { useCallback as useCallback35, useEffect as useEffect39, useRef as useRef15, useState as useState52 } from "react";
15090
+ import { useCallback as useCallback35, useEffect as useEffect41, useRef as useRef15, useState as useState52 } from "react";
14554
15091
  import { useFields as useFields23, useRecordsClient as useRecordsClient38, useTable as useTable21 } from "@ai-matrx/records/react";
14555
15092
  import { Button as Button47, Input as Input4, Skeleton as Skeleton31, Textarea as Textarea3, cn as cn49 } from "@ai-matrx/design-system";
14556
15093
 
14557
15094
  // src/CaptureRun.tsx
14558
- import { useCallback as useCallback34, useEffect as useEffect38, useMemo as useMemo31, useRef as useRef14, useState as useState51 } from "react";
15095
+ import { useCallback as useCallback34, useEffect as useEffect40, useMemo as useMemo31, useRef as useRef14, useState as useState51 } from "react";
14559
15096
  import {
14560
15097
  coerceTypedAnswer as coerceTypedAnswer2,
14561
15098
  coercionLabel,
@@ -14614,7 +15151,7 @@ function CaptureRun({ sheetId, face: given, className }) {
14614
15151
  const [lastSynced, setLastSynced] = useState51(null);
14615
15152
  const [sending, setSending] = useState51(false);
14616
15153
  const queueRef = useRef14(null);
14617
- useEffect38(() => {
15154
+ useEffect40(() => {
14618
15155
  const q2 = openCaptureQueue({
14619
15156
  onChange: (c, all) => {
14620
15157
  setCounts(c);
@@ -14652,13 +15189,13 @@ function CaptureRun({ sheetId, face: given, className }) {
14652
15189
  void q2.sync().then(() => void q2.lastSyncedAt().then(setLastSynced));
14653
15190
  return () => q2.dispose();
14654
15191
  }, [client, host]);
14655
- useEffect38(() => {
15192
+ useEffect40(() => {
14656
15193
  if (given !== void 0) return;
14657
15194
  let cancelled = false;
14658
15195
  void client.captureOpen({ sheet_id: sheetId }).then((res) => {
14659
15196
  if (cancelled) return;
14660
15197
  if (res.ok) setFace(res.data);
14661
- else setLoadFailed(res.error?.message ?? "This sheet could not be opened.");
15198
+ else setLoadFailed(refusalOr(res.error, "unreachable", "This sheet could not be opened."));
14662
15199
  });
14663
15200
  return () => {
14664
15201
  cancelled = true;
@@ -14705,7 +15242,11 @@ function CaptureRun({ sheetId, face: given, className }) {
14705
15242
  const needed = questions.filter((x) => x.required && !answered(x.field));
14706
15243
  if (needed.length > 0) {
14707
15244
  setMissing(
14708
- `${needed.map((x) => (x.ask ?? x.field).replace(/\?$/, "")).join(", ")} still ${needed.length === 1 ? "needs" : "need"} an answer.`
15245
+ refusal(
15246
+ "invalid_argument",
15247
+ `${needed.map((x) => (x.ask ?? x.field).replace(/\?$/, "")).join(", ")} still ${needed.length === 1 ? "needs" : "need"} an answer.`,
15248
+ "Answer it, then capture again. Nothing has been queued yet."
15249
+ )
14709
15250
  );
14710
15251
  setAt(questions.findIndex((x) => x.field === needed[0].field));
14711
15252
  return;
@@ -14736,7 +15277,7 @@ function CaptureRun({ sheetId, face: given, className }) {
14736
15277
  if (online) await sync();
14737
15278
  }
14738
15279
  if (loadFailed) {
14739
- 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 }) });
15280
+ 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" }) }) });
14740
15281
  }
14741
15282
  if (face === void 0) {
14742
15283
  return /* @__PURE__ */ jsx54(Skeleton30, { className: cn48("mx-auto h-64 w-full max-w-sm", className) });
@@ -14767,7 +15308,21 @@ function CaptureRun({ sheetId, face: given, className }) {
14767
15308
  ] });
14768
15309
  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: [
14769
15310
  /* @__PURE__ */ jsx54("span", { className: "tabular-nums text-muted-foreground", children: new Date(i.captured_at).toLocaleTimeString() }),
14770
- /* @__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." }),
15311
+ i.state === "refused" ? (
15312
+ // THE STORE REFUSED THIS ONE: a refusal line, heading and remedy
15313
+ // both — never its raw words in red (lane REFUSAL-SWEEP).
15314
+ /* @__PURE__ */ jsx54(
15315
+ RefusalLine,
15316
+ {
15317
+ className: "flex-1",
15318
+ error: refusal(
15319
+ "refused_by_rule",
15320
+ i.last_error ?? "This one was refused.",
15321
+ "Throw it away and capture it again with the answer changed."
15322
+ )
15323
+ }
15324
+ )
15325
+ ) : /* @__PURE__ */ jsx54("span", { className: "flex-1", children: i.last_error ?? "Waiting for a signal." }),
14771
15326
  i.state === "refused" ? /* @__PURE__ */ jsx54(
14772
15327
  Button46,
14773
15328
  {
@@ -14887,7 +15442,7 @@ function CaptureRun({ sheetId, face: given, className }) {
14887
15442
  }
14888
15443
  }
14889
15444
  ),
14890
- missing ? /* @__PURE__ */ jsx54("p", { className: "text-sm text-destructive", "data-testid": "capture-missing", children: missing }) : null,
15445
+ missing ? /* @__PURE__ */ jsx54("div", { "data-testid": "capture-missing", children: /* @__PURE__ */ jsx54(RefusalNotice, { error: missing, className: "text-sm" }) }) : null,
14891
15446
  /* @__PURE__ */ jsxs50("div", { className: "flex items-center gap-2", children: [
14892
15447
  at > 0 ? /* @__PURE__ */ jsx54(
14893
15448
  Button46,
@@ -14984,7 +15539,7 @@ function AdHocCaptureSheet({
14984
15539
  },
14985
15540
  [host]
14986
15541
  );
14987
- useEffect39(() => {
15542
+ useEffect41(() => {
14988
15543
  let cancelled = false;
14989
15544
  void (async () => {
14990
15545
  const held = host.captureQueue ? await host.captureQueue.load() : [];
@@ -15115,13 +15670,13 @@ function AdHocCaptureSheet({
15115
15670
  setUploadError(null);
15116
15671
  void host.upload?.(file).then((result) => {
15117
15672
  if (result.ok) setFileId(result.fileId);
15118
- else setUploadError(result.reason);
15673
+ else setUploadError(refusal("invalid_argument", result.reason, "Pick another file, or try this one again."));
15119
15674
  });
15120
15675
  }
15121
15676
  }
15122
15677
  ),
15123
15678
  fileId ? /* @__PURE__ */ jsx55("p", { className: "text-[11px] text-muted-foreground", children: "Attached." }) : null,
15124
- uploadError ? /* @__PURE__ */ jsx55("p", { className: "text-xs text-destructive", children: uploadError }) : null
15679
+ uploadError ? /* @__PURE__ */ jsx55(RefusalLine, { error: uploadError }) : null
15125
15680
  ] }) : null,
15126
15681
  /* @__PURE__ */ jsx55(
15127
15682
  Textarea3,
@@ -15163,7 +15718,7 @@ function AdHocCaptureSheet({
15163
15718
  }
15164
15719
 
15165
15720
  // src/PortalShell.tsx
15166
- import { useCallback as useCallback36, useEffect as useEffect40, useState as useState53 } from "react";
15721
+ import { useCallback as useCallback36, useEffect as useEffect42, useState as useState53 } from "react";
15167
15722
  import { useRecordsClient as useRecordsClient39 } from "@ai-matrx/records/react";
15168
15723
  import { Button as Button48, Skeleton as Skeleton32, cn as cn50 } from "@ai-matrx/design-system";
15169
15724
  import { jsx as jsx56, jsxs as jsxs52 } from "react/jsx-runtime";
@@ -15189,7 +15744,7 @@ function PortalShell({ tableId, form, resourceType = "record", className }) {
15189
15744
  }
15190
15745
  setReach(reached.data.map((row) => row.resource_id));
15191
15746
  }, [client, resourceType]);
15192
- useEffect40(() => {
15747
+ useEffect42(() => {
15193
15748
  void load();
15194
15749
  }, [load]);
15195
15750
  if (error) {
@@ -15249,7 +15804,7 @@ function PortalShell({ tableId, form, resourceType = "record", className }) {
15249
15804
  function PortalRow({ tableId, recordId }) {
15250
15805
  const client = useRecordsClient39();
15251
15806
  const [title, setTitle] = useState53(null);
15252
- useEffect40(() => {
15807
+ useEffect42(() => {
15253
15808
  let cancelled = false;
15254
15809
  void client.recordRead({ record_id: recordId }).then((answered) => {
15255
15810
  if (cancelled) return;
@@ -15269,7 +15824,7 @@ function PortalRow({ tableId, recordId }) {
15269
15824
  }
15270
15825
 
15271
15826
  // src/PublicViewPage.tsx
15272
- import { useCallback as useCallback37, useEffect as useEffect41, useState as useState54 } from "react";
15827
+ import { useCallback as useCallback37, useEffect as useEffect43, useState as useState54 } from "react";
15273
15828
  import { useRecordsClient as useRecordsClient40 } from "@ai-matrx/records/react";
15274
15829
  import { Skeleton as Skeleton33, cn as cn51 } from "@ai-matrx/design-system";
15275
15830
  import { jsx as jsx57, jsxs as jsxs53 } from "react/jsx-runtime";
@@ -15313,7 +15868,7 @@ function PublicViewPage({ slug, className }) {
15313
15868
  }
15314
15869
  setRows([{ id: found.resource_id, document: read.data.document, level: "viewer", hidden: read.data.hidden }]);
15315
15870
  }, [client, slug]);
15316
- useEffect41(() => {
15871
+ useEffect43(() => {
15317
15872
  void load();
15318
15873
  }, [load]);
15319
15874
  if (error) return /* @__PURE__ */ jsx57(RefusalNotice, { error, className });
@@ -15350,7 +15905,7 @@ function PublicRow({ row, fields }) {
15350
15905
  }
15351
15906
 
15352
15907
  // src/EmbedFrame.tsx
15353
- import { useCallback as useCallback38, useEffect as useEffect42, useState as useState55 } from "react";
15908
+ import { useCallback as useCallback38, useEffect as useEffect44, useState as useState55 } from "react";
15354
15909
  import { useRecordsClient as useRecordsClient41 } from "@ai-matrx/records/react";
15355
15910
  import { Button as Button49, Input as Input5, Skeleton as Skeleton34, Textarea as Textarea4, cn as cn52 } from "@ai-matrx/design-system";
15356
15911
  import { useTable as useTable22 } from "@ai-matrx/records/react";
@@ -15463,7 +16018,7 @@ function useEmbedHandshake(args) {
15463
16018
  const [loading, setLoading] = useState55(true);
15464
16019
  const origin = args.origin ?? (typeof location === "undefined" ? "" : location.origin);
15465
16020
  const { secret, requiredMode } = args;
15466
- useEffect42(() => {
16021
+ useEffect44(() => {
15467
16022
  let cancelled = false;
15468
16023
  setLoading(true);
15469
16024
  setError(null);
@@ -15518,7 +16073,7 @@ function recordsDataSource(client, fallbackSchema = "custom") {
15518
16073
  }
15519
16074
 
15520
16075
  // src/TablesHome.tsx
15521
- import { useCallback as useCallback39, useEffect as useEffect43, useState as useState56 } from "react";
16076
+ import { useCallback as useCallback39, useEffect as useEffect45, useState as useState56 } from "react";
15522
16077
  import { useRecordsClient as useRecordsClient42, useTables as useTables4 } from "@ai-matrx/records/react";
15523
16078
  import { BasicInput as BasicInput18, Button as Button50, Skeleton as Skeleton35, cn as cn53 } from "@ai-matrx/design-system";
15524
16079
 
@@ -15584,17 +16139,17 @@ async function declareTable(client, spec) {
15584
16139
  if (!written.ok) return undo(client, table.data, home.data, spec.name, written.error);
15585
16140
  return { ok: true, data: table.data, homeId: home.data };
15586
16141
  }
15587
- async function undo(client, tableId, homeId, name, refusal) {
16142
+ async function undo(client, tableId, homeId, name, refusal2) {
15588
16143
  const removed = await client.recordDelete({ record_id: tableId });
15589
16144
  if (removed.ok) {
15590
16145
  await client.recordDelete({ record_id: homeId });
15591
- return { ok: false, error: refusal };
16146
+ return { ok: false, error: refusal2 };
15592
16147
  }
15593
16148
  return {
15594
16149
  ok: false,
15595
16150
  error: {
15596
- ...refusal,
15597
- 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.`
16151
+ ...refusal2,
16152
+ 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.`
15598
16153
  }
15599
16154
  };
15600
16155
  }
@@ -15663,7 +16218,7 @@ function TablesHome({ onOpenTable, className }) {
15663
16218
  const [error, setError] = useState56(null);
15664
16219
  const [importInto, setImportInto] = useState56(null);
15665
16220
  const [boards, setBoards] = useState56(null);
15666
- useEffect43(() => {
16221
+ useEffect45(() => {
15667
16222
  let cancelled = false;
15668
16223
  void client.dashboards({}).then((result) => {
15669
16224
  if (cancelled) return;
@@ -15797,7 +16352,7 @@ function TablesHome({ onOpenTable, className }) {
15797
16352
  }
15798
16353
 
15799
16354
  // src/TablePage.tsx
15800
- import { useCallback as useCallback40, useEffect as useEffect44, useState as useState57 } from "react";
16355
+ import { useCallback as useCallback40, useEffect as useEffect46, useState as useState57 } from "react";
15801
16356
  import { useFields as useFields24, useRecordsClient as useRecordsClient43, useTable as useTable23 } from "@ai-matrx/records/react";
15802
16357
  import { Button as Button51, Separator as Separator12, Skeleton as Skeleton36, cn as cn54 } from "@ai-matrx/design-system";
15803
16358
  import { Fragment as Fragment28, jsx as jsx61, jsxs as jsxs56 } from "react/jsx-runtime";
@@ -15848,9 +16403,43 @@ function chooseSurface(current, pressed) {
15848
16403
  function surfaceChosen(current, asking) {
15849
16404
  return asking.main !== void 0 ? current.main === asking.main : current.rail === asking.rail;
15850
16405
  }
15851
- function openingRail(activeRecordId) {
15852
- return activeRecordId ? { rail: "record", record: activeRecordId } : { rail: "none", record: null };
16406
+ function openingRail(activeRecordId, activeRail) {
16407
+ if (activeRecordId) return { rail: "record", record: activeRecordId };
16408
+ const named = railFromParam(activeRail);
16409
+ if (named !== null && named !== "share") return { rail: named, record: null };
16410
+ return { rail: "none", record: null };
16411
+ }
16412
+ var RAIL_SPELLINGS = {
16413
+ forms: "forms",
16414
+ form: "forms",
16415
+ bookings: "bookings",
16416
+ booking: "bookings",
16417
+ checklists: "checklists",
16418
+ checklist: "checklists",
16419
+ notifications: "notifications",
16420
+ notification: "notifications",
16421
+ digests: "notifications",
16422
+ digest: "notifications",
16423
+ subscriptions: "notifications",
16424
+ subscription: "notifications",
16425
+ portals: "portals",
16426
+ portal: "portals",
16427
+ settings: "settings",
16428
+ import: "import",
16429
+ inbox: "inbox",
16430
+ share: "share",
16431
+ sharing: "share"
16432
+ };
16433
+ function railFromParam(raw) {
16434
+ if (typeof raw !== "string") return null;
16435
+ const token = raw.trim().toLowerCase();
16436
+ if (token === "") return null;
16437
+ return RAIL_SPELLINGS[token] ?? null;
16438
+ }
16439
+ function unknownRailLine(raw) {
16440
+ 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.`;
15853
16441
  }
16442
+ 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.";
15854
16443
  function openingView(activeView, activeDashboardId) {
15855
16444
  const named = pageViewFromParam(activeView);
15856
16445
  if (named === null) {
@@ -15878,6 +16467,8 @@ function TablePage({
15878
16467
  activeGroupField,
15879
16468
  cameFrom,
15880
16469
  filter,
16470
+ activeRail,
16471
+ activeItemId,
15881
16472
  className
15882
16473
  }) {
15883
16474
  const client = useRecordsClient43();
@@ -15887,8 +16478,13 @@ function TablePage({
15887
16478
  const askedInWords = filter ? filterInWords(filter, pageFields.data ?? []) : null;
15888
16479
  const organizationId = useRecordsClient43().config.organizationId;
15889
16480
  const [view, setView] = useState57(null);
15890
- const opening = openingRail(activeRecordId);
16481
+ const opening = openingRail(activeRecordId, activeRail);
15891
16482
  const opened = openingView(activeView, activeDashboardId);
16483
+ const railAsked = typeof activeRail === "string" ? activeRail.trim() : "";
16484
+ const railUnknown = railAsked !== "" && railFromParam(railAsked) === null ? railAsked : null;
16485
+ const shareFromLink = railFromParam(activeRail) === "share";
16486
+ const canShare = useCanShare();
16487
+ const itemFor = (which) => railFromParam(activeRail) === which ? activeItemId ?? null : null;
15892
16488
  const [asking, setAsking] = useState57(null);
15893
16489
  const [layoutFromLink, setLayoutFromLink] = useState57(opened.layout);
15894
16490
  const [surface, setSurface] = useState57({
@@ -15908,12 +16504,18 @@ function TablePage({
15908
16504
  }
15909
16505
  };
15910
16506
  const show = (next) => press({ rail: next });
15911
- useEffect44(() => {
16507
+ useEffect46(() => {
15912
16508
  if (!activeRecordId) return;
15913
16509
  setOpenRecord(activeRecordId);
15914
16510
  setSurface((now) => ({ ...now, rail: "record" }));
15915
16511
  }, [activeRecordId]);
15916
- useEffect44(() => {
16512
+ useEffect46(() => {
16513
+ if (activeRecordId) return;
16514
+ const named = railFromParam(activeRail);
16515
+ if (named === null || named === "share") return;
16516
+ setSurface((now) => ({ ...now, rail: named }));
16517
+ }, [activeRail, activeRecordId]);
16518
+ useEffect46(() => {
15917
16519
  const named = pageViewFromParam(activeView);
15918
16520
  if (named === null) return;
15919
16521
  const isLayout = named !== "dashboards" && named !== "archived";
@@ -16077,7 +16679,8 @@ function TablePage({
16077
16679
  organizationId,
16078
16680
  subjectId: tableId,
16079
16681
  name: table.data?.name,
16080
- may: rights.share
16682
+ may: rights.share,
16683
+ initiallyOpen: shareFromLink
16081
16684
  }
16082
16685
  ),
16083
16686
  /* @__PURE__ */ jsx61(ExportMenu, { tableId })
@@ -16100,6 +16703,8 @@ function TablePage({
16100
16703
  }
16101
16704
  )
16102
16705
  ) : /* @__PURE__ */ jsxs56(Fragment28, { children: [
16706
+ 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,
16707
+ 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,
16103
16708
  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,
16104
16709
  cameFrom ? /* @__PURE__ */ jsx61(
16105
16710
  "p",
@@ -16158,7 +16763,7 @@ function TablePage({
16158
16763
  }
16159
16764
  }
16160
16765
  ) : null,
16161
- rail === "forms" ? /* @__PURE__ */ jsx61(FormsPanel, { tableId }) : null,
16766
+ rail === "forms" ? /* @__PURE__ */ jsx61(FormsPanel, { tableId, activeFormId: itemFor("forms") }) : null,
16162
16767
  rail === "bookings" ? /* @__PURE__ */ jsx61(BookingSlots, { tableId }) : null,
16163
16768
  rail === "checklists" ? /* @__PURE__ */ jsx61(
16164
16769
  ChecklistsPanel,
@@ -16170,8 +16775,8 @@ function TablePage({
16170
16775
  }
16171
16776
  }
16172
16777
  ) : null,
16173
- rail === "notifications" ? /* @__PURE__ */ jsx61(SubscriptionsPanel, { tableId }) : null,
16174
- rail === "portals" ? /* @__PURE__ */ jsx61(PortalsPanel, { tableId }) : null,
16778
+ rail === "notifications" ? /* @__PURE__ */ jsx61(SubscriptionsPanel, { tableId, activeRuleId: itemFor("notifications") }) : null,
16779
+ rail === "portals" ? /* @__PURE__ */ jsx61(PortalsPanel, { tableId, activePortalId: itemFor("portals") }) : null,
16175
16780
  rail === "import" ? /* @__PURE__ */ jsx61(ImportWizard, { tableId, onDone: () => setRail("none") }) : null,
16176
16781
  rail === "field" ? /* @__PURE__ */ jsx61(FieldEditor, { tableId, onSaved: () => setRail("none"), onCancel: () => setRail("none") }) : null,
16177
16782
  rail === "new-record" ? /* @__PURE__ */ jsx61(
@@ -16278,6 +16883,7 @@ export {
16278
16883
  FORMULA_OP_LABEL,
16279
16884
  FORMULA_OP_VALUES,
16280
16885
  FORM_FLOWS,
16886
+ FORM_NOT_HERE_LINE,
16281
16887
  FROZEN_COLUMN_WIDTH,
16282
16888
  FieldControl,
16283
16889
  FieldEditor,
@@ -16325,6 +16931,7 @@ export {
16325
16931
  PAGE_VIEW_LABEL,
16326
16932
  PARITY_LABEL,
16327
16933
  PARITY_MADE_OF,
16934
+ PORTAL_NOT_HERE_LINE,
16328
16935
  PROPOSED_CHANGE_ACT_LABEL,
16329
16936
  PROPOSED_CHANGE_ACT_VALUES,
16330
16937
  Peek,
@@ -16339,6 +16946,7 @@ export {
16339
16946
  PublicViewPage,
16340
16947
  ROLLUP_AGG_LABEL,
16341
16948
  ROLLUP_AGG_VALUES,
16949
+ RULE_NOT_HERE_LINE,
16342
16950
  RecordChat,
16343
16951
  RecordChip,
16344
16952
  RecordForm,
@@ -16353,6 +16961,7 @@ export {
16353
16961
  SAVED_VIEWS_UNAVAILABLE,
16354
16962
  SAY_WHAT_YOU_ARE_WAITING_FOR_AFTER_MS,
16355
16963
  SERIES_COLORS,
16964
+ SHARE_NOT_YOURS_LINE,
16356
16965
  SOMEBODY,
16357
16966
  STAGE_RULE_ON_FAIL_LABEL,
16358
16967
  STORE_ANSWERS_THESE,
@@ -16386,6 +16995,7 @@ export {
16386
16995
  actorWords,
16387
16996
  addFields,
16388
16997
  archivedRowLine,
16998
+ asRefusal,
16389
16999
  askableFields,
16390
17000
  blockFromSpec,
16391
17001
  bodyForReading,
@@ -16451,11 +17061,15 @@ export {
16451
17061
  previewLine,
16452
17062
  previewWords,
16453
17063
  publiclyAnswerable,
17064
+ railFromParam,
16454
17065
  recordName,
16455
17066
  recordNameIn,
16456
17067
  recordsDataSource,
17068
+ refusal,
16457
17069
  refusalForAPerson,
17070
+ refusalFromThrown,
16458
17071
  refusalLineForAPerson,
17072
+ refusalOr,
16459
17073
  renderValue,
16460
17074
  revokeConsequence,
16461
17075
  rowName,
@@ -16469,6 +17083,7 @@ export {
16469
17083
  tableName,
16470
17084
  tableRightsAt,
16471
17085
  tokenFor,
17086
+ unknownRailLine,
16472
17087
  unknownViewLine,
16473
17088
  useCanShare,
16474
17089
  useEmbedHandshake,