@ai-matrx/records-ui 0.81.0 → 0.82.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -538,7 +538,7 @@ import {
538
538
  PopoverContent as PopoverContent2,
539
539
  PopoverTrigger as PopoverTrigger2,
540
540
  ScrollArea as ScrollArea2,
541
- cn as cn4
541
+ cn as cn5
542
542
  } from "@ai-matrx/design-system";
543
543
 
544
544
  // src/editors.tsx
@@ -557,7 +557,7 @@ import {
557
557
  SelectItem as SelectItem2,
558
558
  SelectTrigger as SelectTrigger2,
559
559
  SelectValue as SelectValue2,
560
- cn as cn3
560
+ cn as cn4
561
561
  } from "@ai-matrx/design-system";
562
562
 
563
563
  // src/parity.ts
@@ -671,15 +671,126 @@ import {
671
671
  PopoverContent,
672
672
  PopoverTrigger,
673
673
  ScrollArea,
674
- cn as cn2
674
+ cn as cn3
675
675
  } from "@ai-matrx/design-system";
676
+
677
+ // src/Refusal.tsx
678
+ import { Alert, AlertDescription, AlertTitle, cn as cn2 } from "@ai-matrx/design-system";
676
679
  import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
680
+ function hintIsMachineIdentity(hint) {
681
+ return isMachineIdentity(hint);
682
+ }
683
+ function hintForAPerson(hint) {
684
+ const said = (hint ?? "").trim();
685
+ if (said === "") return null;
686
+ return isMachineIdentity(said) ? null : said;
687
+ }
688
+ function RefusalNotice({
689
+ error,
690
+ className,
691
+ actions,
692
+ onKeepEditing,
693
+ onDiscard
694
+ }) {
695
+ const plain = refusalForAPerson(error);
696
+ const doors = onKeepEditing || onDiscard ? /* @__PURE__ */ jsxs2("div", { className: "flex flex-wrap items-center gap-1 pt-0.5", "data-refusal-doors": "", children: [
697
+ onKeepEditing ? /* @__PURE__ */ jsx3(
698
+ "button",
699
+ {
700
+ type: "button",
701
+ "data-refusal-keep-editing": "",
702
+ className: "rounded border px-2 py-0.5 text-xs hover:bg-muted",
703
+ onClick: onKeepEditing,
704
+ children: "Keep editing"
705
+ }
706
+ ) : null,
707
+ onDiscard ? /* @__PURE__ */ jsx3(
708
+ "button",
709
+ {
710
+ type: "button",
711
+ "data-refusal-discard": "",
712
+ className: "rounded border px-2 py-0.5 text-xs hover:bg-muted",
713
+ onClick: onDiscard,
714
+ children: "Discard"
715
+ }
716
+ ) : null
717
+ ] }) : null;
718
+ return /* @__PURE__ */ jsxs2(
719
+ Alert,
720
+ {
721
+ variant: "destructive",
722
+ role: "alert",
723
+ "data-refusal-notice": "",
724
+ className: cn2("text-xs", className),
725
+ ...plain.forEngineers ? { title: plain.forEngineers } : {},
726
+ children: [
727
+ /* @__PURE__ */ jsx3(AlertTitle, { className: "text-xs font-medium", children: plain.title }),
728
+ /* @__PURE__ */ jsxs2(AlertDescription, { className: "space-y-1 text-xs", children: [
729
+ plain.sentence.replace(/\.$/, "") === plain.title ? null : /* @__PURE__ */ jsx3("p", { children: plain.sentence }),
730
+ /* @__PURE__ */ jsx3("p", { className: "opacity-80", children: plain.remedy }),
731
+ plain.forEngineers ? /* @__PURE__ */ jsx3(
732
+ "p",
733
+ {
734
+ className: "sr-only",
735
+ "aria-hidden": "true",
736
+ "data-for-engineers": "",
737
+ ...error.sqlstate ? { "data-sqlstate": error.sqlstate } : {},
738
+ children: plain.forEngineers
739
+ }
740
+ ) : null,
741
+ actions,
742
+ doors
743
+ ] })
744
+ ]
745
+ }
746
+ );
747
+ }
748
+ function RefusalLine({ error, className }) {
749
+ const plain = refusalForAPerson(error);
750
+ return /* @__PURE__ */ jsx3(
751
+ "p",
752
+ {
753
+ role: "alert",
754
+ "data-refusal-line": "",
755
+ className: cn2("text-xs text-destructive", className),
756
+ title: plain.forEngineers || void 0,
757
+ children: refusalLineForAPerson(error)
758
+ }
759
+ );
760
+ }
761
+
762
+ // src/refusals.ts
763
+ function refusal(code, sentence2, remedy, diagnostic) {
764
+ return {
765
+ code,
766
+ message: sentence2,
767
+ ...remedy ? { hint: remedy } : {},
768
+ ...diagnostic ? { diagnostic } : {}
769
+ };
770
+ }
771
+ function refusalFromThrown(thrown, sentence2, remedy, code = "internal") {
772
+ const said = thrown instanceof Error ? thrown.message : String(thrown ?? "");
773
+ return refusal(code, sentence2, remedy, said ? `Thrown: ${said}` : null);
774
+ }
775
+ function refusalOr(error, code, sentence2) {
776
+ return error ?? refusal(code, sentence2);
777
+ }
778
+ function asRefusal(thrown, sentence2, remedy, code = "internal") {
779
+ const t = thrown;
780
+ if (t && typeof t === "object" && typeof t.code === "string" && typeof t.message === "string" && !(thrown instanceof Error)) {
781
+ return t;
782
+ }
783
+ return refusalFromThrown(thrown, sentence2, remedy, code);
784
+ }
785
+
786
+ // src/RelationPicker.tsx
787
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
677
788
  var NO_STORE_ON_THIS_SCREEN_REASON = "This question points at a record in somebody's own data, and a page answered without an account is never allowed to search that. Leave it blank \u2014 whoever receives your answer will fill it in.";
678
789
  function RelationPicker({ field, value, onChange, disabled, id, className, allowCreate = true }) {
679
790
  const client = useOptionalRecordsClient();
680
791
  const [rows, setRows] = useState2(null);
681
792
  const [words, setWords] = useState2({});
682
- const [refusal, setRefusal] = useState2(null);
793
+ const [refusal2, setRefusal] = useState2(null);
683
794
  const [search, setSearch] = useState2("");
684
795
  const [open, setOpen] = useState2(false);
685
796
  const [target, setTarget] = useState2(null);
@@ -699,7 +810,7 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
699
810
  void client.list({ table_id: target2, limit: 200 }).then((result) => {
700
811
  if (cancelled) return;
701
812
  if (result.ok) setRows(result.data.rows);
702
- else setRefusal(refusalLineForAPerson(result.error));
813
+ else setRefusal(result.error);
703
814
  });
704
815
  return () => {
705
816
  cancelled = true;
@@ -733,14 +844,17 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
733
844
  void client.tableList().then((result) => {
734
845
  if (cancelled) return;
735
846
  if (!result.ok) {
736
- setTargetRefusal(refusalLineForAPerson(result.error));
847
+ setTargetRefusal(result.error);
737
848
  return;
738
849
  }
739
850
  const found = result.data.find((t) => t.id === id2) ?? null;
740
851
  if (found) setTarget(found);
741
852
  else
742
853
  setTargetRefusal(
743
- "That Table is not one you can open, so a new record cannot be added to it from here."
854
+ refusal(
855
+ "door",
856
+ "That Table is not one you can open, so a new record cannot be added to it from here."
857
+ )
744
858
  );
745
859
  });
746
860
  return () => {
@@ -751,11 +865,11 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
751
865
  return (
752
866
  // The Field's own name is already printed above this control by
753
867
  // `FieldLabel`, so the sentence does not say it a second time.
754
- /* @__PURE__ */ jsx3("p", { id, className: cn2("text-xs text-muted-foreground", className), children: NO_STORE_ON_THIS_SCREEN_REASON })
868
+ /* @__PURE__ */ jsx4("p", { id, className: cn3("text-xs text-muted-foreground", className), children: NO_STORE_ON_THIS_SCREEN_REASON })
755
869
  );
756
870
  }
757
871
  if (!field.relation_target) {
758
- return /* @__PURE__ */ jsxs2("p", { className: "text-xs text-muted-foreground", children: [
872
+ return /* @__PURE__ */ jsxs3("p", { className: "text-xs text-muted-foreground", children: [
759
873
  fieldName(field),
760
874
  " points at records, but it does not say which Table yet, so there is nothing to pick from. A table admin sets its target in the field editor."
761
875
  ] });
@@ -779,13 +893,17 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
779
893
  const tableId = field.relation_target;
780
894
  if (!tableId || title === "" || busy) return;
781
895
  if (!target) {
782
- setCreateRefusal("Still reading that Table. Try again in a moment.");
896
+ setCreateRefusal(refusal("unreachable", "Still reading that Table.", "Try again in a moment."));
783
897
  return;
784
898
  }
785
899
  const titleField = target.title_field;
786
900
  if (!titleField) {
787
901
  setCreateRefusal(
788
- `${target.name} has no column that names a record yet, so a new one would have nothing to show here. A table admin picks its title column in that table's settings.`
902
+ refusal(
903
+ "not_supported",
904
+ `${target.name} has no column that names a record yet, so a new one would have nothing to show here.`,
905
+ "A table admin picks its title column in that table's settings."
906
+ )
789
907
  );
790
908
  return;
791
909
  }
@@ -794,7 +912,7 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
794
912
  const written = await client.recordWrite({ table_id: tableId, data: { [titleField]: title } });
795
913
  if (!written.ok) {
796
914
  setBusy(false);
797
- setCreateRefusal(refusalLineForAPerson(written.error));
915
+ setCreateRefusal(written.error);
798
916
  return;
799
917
  }
800
918
  const createdId = String(written.data);
@@ -809,8 +927,8 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
809
927
  const visible = (rows ?? []).filter(
810
928
  (row) => search.trim() === "" ? true : titleOf(row.id).toLowerCase().includes(search.trim().toLowerCase())
811
929
  );
812
- return /* @__PURE__ */ jsxs2("div", { className: cn2("flex flex-wrap items-center gap-1", className), children: [
813
- picked.map((recordId) => /* @__PURE__ */ jsx3(
930
+ return /* @__PURE__ */ jsxs3("div", { className: cn3("flex flex-wrap items-center gap-1", className), children: [
931
+ picked.map((recordId) => /* @__PURE__ */ jsx4(
814
932
  RecordChip,
815
933
  {
816
934
  title: titleOf(recordId),
@@ -818,10 +936,10 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
818
936
  },
819
937
  recordId
820
938
  )),
821
- disabled ? null : /* @__PURE__ */ jsxs2(Popover, { open, onOpenChange: setOpen, children: [
822
- /* @__PURE__ */ jsx3(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx3(Button2, { id, type: "button", size: "sm", variant: "outline", "aria-label": `Pick for ${fieldName(field)}`, children: picked.length === 0 ? "Pick" : "Change" }) }),
823
- /* @__PURE__ */ jsxs2(PopoverContent, { sizing: "content", className: "p-1", align: "start", children: [
824
- /* @__PURE__ */ jsx3(
939
+ disabled ? null : /* @__PURE__ */ jsxs3(Popover, { open, onOpenChange: setOpen, children: [
940
+ /* @__PURE__ */ jsx4(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx4(Button2, { id, type: "button", size: "sm", variant: "outline", "aria-label": `Pick for ${fieldName(field)}`, children: picked.length === 0 ? "Pick" : "Change" }) }),
941
+ /* @__PURE__ */ jsxs3(PopoverContent, { sizing: "content", className: "p-1", align: "start", children: [
942
+ /* @__PURE__ */ jsx4(
825
943
  BasicInput2,
826
944
  {
827
945
  value: search,
@@ -830,33 +948,33 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
830
948
  className: "mb-1 h-7 text-xs"
831
949
  }
832
950
  ),
833
- refusal ? /* @__PURE__ */ jsx3("p", { className: "p-2 text-xs text-destructive", children: refusal }) : null,
834
- !rows && !refusal ? /* @__PURE__ */ jsx3("p", { className: "p-2 text-xs text-muted-foreground", children: "Reading\u2026" }) : null,
835
- full ? /* @__PURE__ */ jsxs2("p", { className: "p-2 text-xs text-muted-foreground", children: [
951
+ refusal2 ? /* @__PURE__ */ jsx4(RefusalLine, { error: refusal2, className: "p-2" }) : null,
952
+ !rows && !refusal2 ? /* @__PURE__ */ jsx4("p", { className: "p-2 text-xs text-muted-foreground", children: "Reading\u2026" }) : null,
953
+ full ? /* @__PURE__ */ jsxs3("p", { className: "p-2 text-xs text-muted-foreground", children: [
836
954
  fieldName(field),
837
955
  " can point at ",
838
956
  field.relation_max,
839
957
  " at most. Remove one to add another."
840
958
  ] }) : null,
841
- /* @__PURE__ */ jsxs2(ScrollArea, { className: "max-h-56", children: [
842
- /* @__PURE__ */ jsx3("ul", { children: visible.map((row) => /* @__PURE__ */ jsx3("li", { children: /* @__PURE__ */ jsx3(
959
+ /* @__PURE__ */ jsxs3(ScrollArea, { className: "max-h-56", children: [
960
+ /* @__PURE__ */ jsx4("ul", { children: visible.map((row) => /* @__PURE__ */ jsx4("li", { children: /* @__PURE__ */ jsx4(
843
961
  "button",
844
962
  {
845
963
  type: "button",
846
964
  onClick: () => toggle(row.id),
847
965
  title: titleOf(row.id),
848
- className: cn2(
966
+ className: cn3(
849
967
  "w-full truncate rounded px-2 py-1 text-left text-xs hover:bg-muted",
850
968
  picked.includes(row.id) ? "bg-accent text-accent-foreground" : ""
851
969
  ),
852
970
  children: titleOf(row.id)
853
971
  }
854
972
  ) }, row.id)) }),
855
- rows && visible.length === 0 ? /* @__PURE__ */ jsx3("p", { className: "p-2 text-xs text-muted-foreground", children: "Nothing in that table matches." }) : null
973
+ rows && visible.length === 0 ? /* @__PURE__ */ jsx4("p", { className: "p-2 text-xs text-muted-foreground", children: "Nothing in that table matches." }) : null
856
974
  ] }),
857
- allowCreate && !full ? /* @__PURE__ */ jsxs2("div", { className: "mt-1 border-t pt-1", children: [
858
- targetRefusal ? /* @__PURE__ */ jsx3("p", { className: "p-2 text-xs text-destructive", children: targetRefusal }) : !target ? /* @__PURE__ */ jsx3("p", { className: "p-2 text-xs text-muted-foreground", children: "Reading that table\u2026" }) : creating ? /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1 p-1", children: [
859
- /* @__PURE__ */ jsx3(
975
+ allowCreate && !full ? /* @__PURE__ */ jsxs3("div", { className: "mt-1 border-t pt-1", children: [
976
+ targetRefusal ? /* @__PURE__ */ jsx4(RefusalLine, { error: targetRefusal, className: "p-2" }) : !target ? /* @__PURE__ */ jsx4("p", { className: "p-2 text-xs text-muted-foreground", children: "Reading that table\u2026" }) : creating ? /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1 p-1", children: [
977
+ /* @__PURE__ */ jsx4(
860
978
  BasicInput2,
861
979
  {
862
980
  autoFocus: true,
@@ -873,7 +991,7 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
873
991
  "aria-label": `Name the new ${targetWord}`
874
992
  }
875
993
  ),
876
- /* @__PURE__ */ jsx3(
994
+ /* @__PURE__ */ jsx4(
877
995
  Button2,
878
996
  {
879
997
  type: "button",
@@ -884,7 +1002,7 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
884
1002
  children: busy ? "Creating\u2026" : "Create"
885
1003
  }
886
1004
  )
887
- ] }) : /* @__PURE__ */ jsx3(
1005
+ ] }) : /* @__PURE__ */ jsx4(
888
1006
  Button2,
889
1007
  {
890
1008
  type: "button",
@@ -900,10 +1018,25 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
900
1018
  setCreating(true);
901
1019
  }
902
1020
  },
903
- children: /* @__PURE__ */ jsx3("span", { className: "block w-full truncate text-left", children: search.trim() !== "" ? `Create \u201C${search.trim()}\u201D` : `New ${targetWord}` })
1021
+ children: /* @__PURE__ */ jsx4("span", { className: "block w-full truncate text-left", children: search.trim() !== "" ? `Create \u201C${search.trim()}\u201D` : `New ${targetWord}` })
904
1022
  }
905
1023
  ),
906
- createRefusal ? /* @__PURE__ */ jsx3("p", { className: "p-2 text-xs text-destructive", children: createRefusal }) : null
1024
+ createRefusal ? (
1025
+ // The typed title is still in the search box: the notice owns the
1026
+ // two doors out (lane REFUSAL-SWEEP).
1027
+ /* @__PURE__ */ jsx4(
1028
+ RefusalNotice,
1029
+ {
1030
+ error: createRefusal,
1031
+ className: "mt-1 max-w-72",
1032
+ onKeepEditing: () => setCreateRefusal(null),
1033
+ onDiscard: () => {
1034
+ setCreateRefusal(null);
1035
+ setSearch("");
1036
+ }
1037
+ }
1038
+ )
1039
+ ) : null
907
1040
  ] }) : null
908
1041
  ] })
909
1042
  ] })
@@ -911,12 +1044,12 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
911
1044
  }
912
1045
 
913
1046
  // src/editors.tsx
914
- import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1047
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
915
1048
  function FieldLabel({ field, htmlFor, children }) {
916
- return /* @__PURE__ */ jsxs3("div", { className: "flex items-baseline gap-1.5", children: [
917
- /* @__PURE__ */ jsx4(Label2, { htmlFor, className: "text-xs font-medium", children: fieldName(field) }),
918
- field.required ? /* @__PURE__ */ jsx4("span", { className: "text-xs text-destructive", children: "required" }) : null,
919
- field.unit ? /* @__PURE__ */ jsx4("span", { className: "text-xs text-muted-foreground", children: field.unit }) : null,
1049
+ return /* @__PURE__ */ jsxs4("div", { className: "flex items-baseline gap-1.5", children: [
1050
+ /* @__PURE__ */ jsx5(Label2, { htmlFor, className: "text-xs font-medium", children: fieldName(field) }),
1051
+ field.required ? /* @__PURE__ */ jsx5("span", { className: "text-xs text-destructive", children: "required" }) : null,
1052
+ field.unit ? /* @__PURE__ */ jsx5("span", { className: "text-xs text-muted-foreground", children: field.unit }) : null,
920
1053
  children
921
1054
  ] });
922
1055
  }
@@ -927,17 +1060,17 @@ function FieldControl({ field, value, onChange, disabled, id }) {
927
1060
  case "formula":
928
1061
  case "lookup":
929
1062
  case "rollup":
930
- return /* @__PURE__ */ jsxs3("div", { id: inputId, className: "rounded border border-dashed px-2 py-1.5 text-xs text-muted-foreground", children: [
1063
+ return /* @__PURE__ */ jsxs4("div", { id: inputId, className: "rounded border border-dashed px-2 py-1.5 text-xs text-muted-foreground", children: [
931
1064
  value === null || value === void 0 ? "\u2014" : String(value),
932
- /* @__PURE__ */ jsxs3("span", { className: "ml-2 italic", children: [
1065
+ /* @__PURE__ */ jsxs4("span", { className: "ml-2 italic", children: [
933
1066
  fieldName(field),
934
1067
  " is worked out by the system, so it cannot be typed in"
935
1068
  ] })
936
1069
  ] });
937
1070
  case "json":
938
- return /* @__PURE__ */ jsx4(JsonControl, { field, value, onChange, disabled, id: inputId });
1071
+ return /* @__PURE__ */ jsx5(JsonControl, { field, value, onChange, disabled, id: inputId });
939
1072
  case "long_text":
940
- return /* @__PURE__ */ jsx4(
1073
+ return /* @__PURE__ */ jsx5(
941
1074
  BasicTextarea,
942
1075
  {
943
1076
  id: inputId,
@@ -950,7 +1083,7 @@ function FieldControl({ field, value, onChange, disabled, id }) {
950
1083
  case "number":
951
1084
  case "currency":
952
1085
  case "percent":
953
- return /* @__PURE__ */ jsx4(
1086
+ return /* @__PURE__ */ jsx5(
954
1087
  BasicInput3,
955
1088
  {
956
1089
  id: inputId,
@@ -963,7 +1096,7 @@ function FieldControl({ field, value, onChange, disabled, id }) {
963
1096
  }
964
1097
  );
965
1098
  case "datetime":
966
- return /* @__PURE__ */ jsx4(
1099
+ return /* @__PURE__ */ jsx5(
967
1100
  BasicInput3,
968
1101
  {
969
1102
  id: inputId,
@@ -976,7 +1109,7 @@ function FieldControl({ field, value, onChange, disabled, id }) {
976
1109
  case "email":
977
1110
  case "phone":
978
1111
  case "url":
979
- return /* @__PURE__ */ jsx4(
1112
+ return /* @__PURE__ */ jsx5(
980
1113
  BasicInput3,
981
1114
  {
982
1115
  id: inputId,
@@ -987,18 +1120,18 @@ function FieldControl({ field, value, onChange, disabled, id }) {
987
1120
  }
988
1121
  );
989
1122
  case "checkbox":
990
- return /* @__PURE__ */ jsx4(CheckboxControl, { field, value, onChange, disabled, id: inputId });
1123
+ return /* @__PURE__ */ jsx5(CheckboxControl, { field, value, onChange, disabled, id: inputId });
991
1124
  case "select":
992
1125
  case "multi_select":
993
- return /* @__PURE__ */ jsx4(OptionControl, { field, value, onChange, disabled, id: inputId });
1126
+ return /* @__PURE__ */ jsx5(OptionControl, { field, value, onChange, disabled, id: inputId });
994
1127
  case "member":
995
- return /* @__PURE__ */ jsx4(PersonPicker, { field, value, onChange, disabled, id: inputId });
1128
+ return /* @__PURE__ */ jsx5(PersonPicker, { field, value, onChange, disabled, id: inputId });
996
1129
  case "relation":
997
1130
  case "attachment":
998
- return /* @__PURE__ */ jsx4(RelationPicker, { field, value, onChange, disabled, id: inputId });
1131
+ return /* @__PURE__ */ jsx5(RelationPicker, { field, value, onChange, disabled, id: inputId });
999
1132
  default:
1000
1133
  if (typeof value === "boolean" || field.config?.["kind"] === "boolean") {
1001
- return /* @__PURE__ */ jsx4(
1134
+ return /* @__PURE__ */ jsx5(
1002
1135
  Checkbox2,
1003
1136
  {
1004
1137
  id: inputId,
@@ -1008,7 +1141,7 @@ function FieldControl({ field, value, onChange, disabled, id }) {
1008
1141
  }
1009
1142
  );
1010
1143
  }
1011
- return /* @__PURE__ */ jsx4(
1144
+ return /* @__PURE__ */ jsx5(
1012
1145
  BasicInput3,
1013
1146
  {
1014
1147
  id: inputId,
@@ -1021,8 +1154,8 @@ function FieldControl({ field, value, onChange, disabled, id }) {
1021
1154
  }
1022
1155
  function CheckboxControl({ field, value, onChange, disabled, id }) {
1023
1156
  const unanswered = value === null || value === void 0;
1024
- return /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
1025
- /* @__PURE__ */ jsx4(
1157
+ return /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
1158
+ /* @__PURE__ */ jsx5(
1026
1159
  Checkbox2,
1027
1160
  {
1028
1161
  id,
@@ -1032,9 +1165,9 @@ function CheckboxControl({ field, value, onChange, disabled, id }) {
1032
1165
  onCheckedChange: (next) => onChange(next === true)
1033
1166
  }
1034
1167
  ),
1035
- unanswered ? /* @__PURE__ */ jsx4("span", { className: "text-xs italic text-muted-foreground", children: "not answered yet" }) : /* @__PURE__ */ jsxs3(Fragment2, { children: [
1036
- /* @__PURE__ */ jsx4("span", { className: "text-xs text-muted-foreground", children: value === true ? "Yes" : "No" }),
1037
- field.required ? null : /* @__PURE__ */ jsx4(
1168
+ unanswered ? /* @__PURE__ */ jsx5("span", { className: "text-xs italic text-muted-foreground", children: "not answered yet" }) : /* @__PURE__ */ jsxs4(Fragment2, { children: [
1169
+ /* @__PURE__ */ jsx5("span", { className: "text-xs text-muted-foreground", children: value === true ? "Yes" : "No" }),
1170
+ field.required ? null : /* @__PURE__ */ jsx5(
1038
1171
  Button3,
1039
1172
  {
1040
1173
  size: "sm",
@@ -1052,8 +1185,8 @@ function JsonControl({ field, value, onChange, disabled, id }) {
1052
1185
  const initial = value === null || value === void 0 ? "" : typeof value === "string" ? value : JSON.stringify(value, null, 2);
1053
1186
  const [text, setText] = useState3(initial);
1054
1187
  const [broken, setBroken] = useState3(null);
1055
- return /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-1", children: [
1056
- /* @__PURE__ */ jsx4(
1188
+ return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-1", children: [
1189
+ /* @__PURE__ */ jsx5(
1057
1190
  BasicTextarea,
1058
1191
  {
1059
1192
  id,
@@ -1073,37 +1206,53 @@ function JsonControl({ field, value, onChange, disabled, id }) {
1073
1206
  onChange(JSON.parse(next));
1074
1207
  setBroken(null);
1075
1208
  } catch (thrown) {
1076
- setBroken(thrown instanceof Error ? thrown.message : String(thrown));
1209
+ setBroken(
1210
+ refusalFromThrown(
1211
+ thrown,
1212
+ `${fieldName(field)} is not readable as structured data yet.`,
1213
+ "Fix the text so it reads as structured data, or discard it to put back what was there.",
1214
+ "invalid_argument"
1215
+ )
1216
+ );
1077
1217
  }
1078
1218
  }
1079
1219
  }
1080
1220
  ),
1081
- broken ? /* @__PURE__ */ jsxs3("p", { className: "text-xs text-destructive", children: [
1082
- fieldName(field),
1083
- " is not readable as structured data yet: ",
1084
- broken
1085
- ] }) : null
1221
+ broken ? (
1222
+ // The typed text is still in the box: the notice owns the two doors out.
1223
+ /* @__PURE__ */ jsx5(
1224
+ RefusalNotice,
1225
+ {
1226
+ error: broken,
1227
+ onKeepEditing: () => id ? document.getElementById(id)?.focus() : void 0,
1228
+ onDiscard: () => {
1229
+ setText(initial);
1230
+ setBroken(null);
1231
+ }
1232
+ }
1233
+ )
1234
+ ) : null
1086
1235
  ] });
1087
1236
  }
1088
1237
  function OptionControl({ field, value, onChange, disabled, id }) {
1089
1238
  const client = useOptionalRecordsClient2();
1090
1239
  const [options, setOptions] = useState3(null);
1091
- const [refusal, setRefusal] = useState3(null);
1240
+ const [refusal2, setRefusal] = useState3(null);
1092
1241
  useEffect2(() => {
1093
1242
  if (!client) return;
1094
1243
  let cancelled = false;
1095
1244
  void client.fieldOptions({ field_id: field.id }).then((result) => {
1096
1245
  if (cancelled) return;
1097
1246
  if (result.ok) setOptions(result.data ?? []);
1098
- else setRefusal(refusalLineForAPerson(result.error));
1247
+ else setRefusal(result.error);
1099
1248
  });
1100
1249
  return () => {
1101
1250
  cancelled = true;
1102
1251
  };
1103
1252
  }, [client, field.id]);
1104
1253
  if (!client) {
1105
- return /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-1", children: [
1106
- /* @__PURE__ */ jsx4(
1254
+ return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-1", children: [
1255
+ /* @__PURE__ */ jsx5(
1107
1256
  BasicInput3,
1108
1257
  {
1109
1258
  id,
@@ -1112,19 +1261,20 @@ function OptionControl({ field, value, onChange, disabled, id }) {
1112
1261
  onChange: (e) => onChange(e.target.value === "" ? null : e.target.value)
1113
1262
  }
1114
1263
  ),
1115
- /* @__PURE__ */ jsx4("p", { className: "text-xs text-muted-foreground", children: "This is normally picked from a list. A page answered without an account cannot read that list, so type your answer and whoever receives it will match it up." })
1264
+ /* @__PURE__ */ jsx5("p", { className: "text-xs text-muted-foreground", children: "This is normally picked from a list. A page answered without an account cannot read that list, so type your answer and whoever receives it will match it up." })
1116
1265
  ] });
1117
1266
  }
1118
- if (refusal) return /* @__PURE__ */ jsx4("p", { id, className: "text-xs text-destructive", children: refusal });
1119
- if (!options) return /* @__PURE__ */ jsx4("p", { id, className: "text-xs text-muted-foreground", children: "Reading this field's choices\u2026" });
1267
+ if (refusal2)
1268
+ return /* @__PURE__ */ jsx5("div", { id, children: /* @__PURE__ */ jsx5(RefusalLine, { error: refusal2 }) });
1269
+ if (!options) return /* @__PURE__ */ jsx5("p", { id, className: "text-xs text-muted-foreground", children: "Reading this field's choices\u2026" });
1120
1270
  if (options.length === 0) {
1121
- return /* @__PURE__ */ jsx4("p", { id, className: "text-xs text-muted-foreground", children: field.options_table_id ? `${fieldName(field)} takes its choices from a Table that has no records yet \u2014 add the choices there and they appear here.` : `${fieldName(field)} does not name the Table its choices come from yet, so there is nothing to choose. A table admin sets it in the field editor.` });
1271
+ return /* @__PURE__ */ jsx5("p", { id, className: "text-xs text-muted-foreground", children: field.options_table_id ? `${fieldName(field)} takes its choices from a Table that has no records yet \u2014 add the choices there and they appear here.` : `${fieldName(field)} does not name the Table its choices come from yet, so there is nothing to choose. A table admin sets it in the field editor.` });
1122
1272
  }
1123
1273
  const titleOf = (option) => recordName(option.data, null, "Unnamed choice");
1124
1274
  if (field.multi) {
1125
1275
  const chosen = options.filter((option) => choiceValuesOf(value).some((v) => isTheChosen(option, v)));
1126
1276
  const picked = new Set(chosen.map((option) => optionKey(option)));
1127
- return /* @__PURE__ */ jsx4("div", { id, className: "flex flex-wrap gap-1", children: options.map((option) => /* @__PURE__ */ jsx4(
1277
+ return /* @__PURE__ */ jsx5("div", { id, className: "flex flex-wrap gap-1", children: options.map((option) => /* @__PURE__ */ jsx5(
1128
1278
  Button3,
1129
1279
  {
1130
1280
  type: "button",
@@ -1143,15 +1293,15 @@ function OptionControl({ field, value, onChange, disabled, id }) {
1143
1293
  )) });
1144
1294
  }
1145
1295
  const current = options.find((option) => isTheChosen(option, value));
1146
- return /* @__PURE__ */ jsxs3(
1296
+ return /* @__PURE__ */ jsxs4(
1147
1297
  Select2,
1148
1298
  {
1149
1299
  value: current ? optionKey(current) : "",
1150
1300
  disabled: disabled ?? false,
1151
1301
  onValueChange: (next) => onChange(next === "" ? null : next),
1152
1302
  children: [
1153
- /* @__PURE__ */ jsx4(SelectTrigger2, { id, size: "sm", children: /* @__PURE__ */ jsx4(SelectValue2, { placeholder: "Choose" }) }),
1154
- /* @__PURE__ */ jsx4(SelectContent2, { children: options.map((option) => /* @__PURE__ */ jsx4(SelectItem2, { value: optionKey(option), children: titleOf(option) }, option.id)) })
1303
+ /* @__PURE__ */ jsx5(SelectTrigger2, { id, size: "sm", children: /* @__PURE__ */ jsx5(SelectValue2, { placeholder: "Choose" }) }),
1304
+ /* @__PURE__ */ jsx5(SelectContent2, { children: options.map((option) => /* @__PURE__ */ jsx5(SelectItem2, { value: optionKey(option), children: titleOf(option) }, option.id)) })
1155
1305
  ]
1156
1306
  }
1157
1307
  );
@@ -1167,15 +1317,15 @@ function RecordChip({
1167
1317
  // name. So it caps at its container and ends in an ellipsis with the whole name in
1168
1318
  // its tooltip, rather than being cut mid-word by whatever box it landed in
1169
1319
  // (lane FIX-14, 2026-09-22).
1170
- /* @__PURE__ */ jsxs3(Badge, { variant: "secondary", className: cn3("max-w-full gap-1 text-[11px]", className), title, children: [
1171
- /* @__PURE__ */ jsx4("span", { className: "min-w-0 truncate", children: title }),
1172
- onRemove ? /* @__PURE__ */ jsx4("button", { type: "button", onClick: onRemove, "aria-label": `Remove ${title}`, className: "shrink-0 opacity-60 hover:opacity-100", children: "\xD7" }) : null
1320
+ /* @__PURE__ */ jsxs4(Badge, { variant: "secondary", className: cn4("max-w-full gap-1 text-[11px]", className), title, children: [
1321
+ /* @__PURE__ */ jsx5("span", { className: "min-w-0 truncate", children: title }),
1322
+ onRemove ? /* @__PURE__ */ jsx5("button", { type: "button", onClick: onRemove, "aria-label": `Remove ${title}`, className: "shrink-0 opacity-60 hover:opacity-100", children: "\xD7" }) : null
1173
1323
  ] })
1174
1324
  );
1175
1325
  }
1176
1326
 
1177
1327
  // src/PersonPicker.tsx
1178
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1328
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1179
1329
  function personFromRow(row) {
1180
1330
  const data = row.document ?? {};
1181
1331
  const userId = data["user_id"];
@@ -1228,7 +1378,7 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1228
1378
  ]);
1229
1379
  setMembers(roster);
1230
1380
  if (records && !records.ok) {
1231
- setError(refusalLineForAPerson(records.error));
1381
+ setError(records.error);
1232
1382
  return;
1233
1383
  }
1234
1384
  setPeople(records ? records.data.rows.map(personFromRow).filter((p) => p.userId !== null) : []);
@@ -1249,10 +1399,10 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1249
1399
  };
1250
1400
  }, [client, personTable, picked.length, people.length]);
1251
1401
  if (!membersPort || !client) {
1252
- return /* @__PURE__ */ jsx5("p", { className: cn4("text-xs text-muted-foreground", className), children: NO_MEMBERS_REASON });
1402
+ return /* @__PURE__ */ jsx6("p", { className: cn5("text-xs text-muted-foreground", className), children: NO_MEMBERS_REASON });
1253
1403
  }
1254
1404
  if (!personTable) {
1255
- return /* @__PURE__ */ jsxs4("p", { className: cn4("text-xs text-muted-foreground", className), children: [
1405
+ return /* @__PURE__ */ jsxs5("p", { className: cn5("text-xs text-muted-foreground", className), children: [
1256
1406
  fieldName(field),
1257
1407
  " is a person field, but it does not say which Table the people are records of yet, so there is nothing to pick from. A table admin sets its target in the field editor."
1258
1408
  ] });
@@ -1270,7 +1420,7 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1270
1420
  const resolved = await personRecordForMember(client, personTable, people, member);
1271
1421
  setBusy(false);
1272
1422
  if (!resolved.ok) {
1273
- setError(refusalLineForAPerson(resolved.error));
1423
+ setError(resolved.error);
1274
1424
  return;
1275
1425
  }
1276
1426
  if (!people.some((p) => p.id === resolved.data)) {
@@ -1296,8 +1446,8 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1296
1446
  const pickedUserIds = new Set(
1297
1447
  picked.map((recordId) => people.find((p) => p.id === recordId)?.userId).filter(Boolean)
1298
1448
  );
1299
- return /* @__PURE__ */ jsxs4("div", { className: cn4("flex flex-wrap items-center gap-1", className), children: [
1300
- picked.map((recordId) => /* @__PURE__ */ jsx5(
1449
+ return /* @__PURE__ */ jsxs5("div", { className: cn5("flex flex-wrap items-center gap-1", className), children: [
1450
+ picked.map((recordId) => /* @__PURE__ */ jsx6(
1301
1451
  RecordChip,
1302
1452
  {
1303
1453
  title: nameOfRecord(recordId),
@@ -1305,8 +1455,8 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1305
1455
  },
1306
1456
  recordId
1307
1457
  )),
1308
- disabled ? null : /* @__PURE__ */ jsxs4(Popover2, { open, onOpenChange: setOpen, children: [
1309
- /* @__PURE__ */ jsx5(PopoverTrigger2, { asChild: true, children: /* @__PURE__ */ jsx5(
1458
+ disabled ? null : /* @__PURE__ */ jsxs5(Popover2, { open, onOpenChange: setOpen, children: [
1459
+ /* @__PURE__ */ jsx6(PopoverTrigger2, { asChild: true, children: /* @__PURE__ */ jsx6(
1310
1460
  Button4,
1311
1461
  {
1312
1462
  id,
@@ -1317,8 +1467,8 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1317
1467
  children: picked.length === 0 ? "Pick a person" : "Change"
1318
1468
  }
1319
1469
  ) }),
1320
- /* @__PURE__ */ jsxs4(PopoverContent2, { sizing: "content", className: "p-1", align: "start", children: [
1321
- /* @__PURE__ */ jsx5(
1470
+ /* @__PURE__ */ jsxs5(PopoverContent2, { sizing: "content", className: "p-1", align: "start", children: [
1471
+ /* @__PURE__ */ jsx6(
1322
1472
  BasicInput4,
1323
1473
  {
1324
1474
  value: search,
@@ -1327,29 +1477,29 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1327
1477
  className: "mb-1 h-7 text-xs"
1328
1478
  }
1329
1479
  ),
1330
- error ? /* @__PURE__ */ jsx5("p", { className: "p-2 text-xs text-destructive", children: error }) : null,
1331
- members === null ? /* @__PURE__ */ jsx5("p", { className: "p-2 text-xs text-muted-foreground", children: "Reading the members\u2026" }) : null,
1332
- /* @__PURE__ */ jsxs4(ScrollArea2, { className: "max-h-56", children: [
1333
- /* @__PURE__ */ jsx5("ul", { children: visible.map((member) => /* @__PURE__ */ jsx5("li", { children: /* @__PURE__ */ jsxs4(
1480
+ error ? /* @__PURE__ */ jsx6(RefusalLine, { error, className: "p-2" }) : null,
1481
+ members === null ? /* @__PURE__ */ jsx6("p", { className: "p-2 text-xs text-muted-foreground", children: "Reading the members\u2026" }) : null,
1482
+ /* @__PURE__ */ jsxs5(ScrollArea2, { className: "max-h-56", children: [
1483
+ /* @__PURE__ */ jsx6("ul", { children: visible.map((member) => /* @__PURE__ */ jsx6("li", { children: /* @__PURE__ */ jsxs5(
1334
1484
  "button",
1335
1485
  {
1336
1486
  type: "button",
1337
1487
  disabled: busy,
1338
1488
  onClick: () => void pick(member),
1339
- className: cn4(
1489
+ className: cn5(
1340
1490
  "w-full truncate rounded px-2 py-1 text-left text-xs hover:bg-muted",
1341
1491
  pickedUserIds.has(member.userId) ? "bg-accent text-accent-foreground" : ""
1342
1492
  ),
1343
1493
  children: [
1344
- /* @__PURE__ */ jsx5("span", { children: memberName(member) }),
1345
- member.email && member.email !== memberName(member) ? /* @__PURE__ */ jsxs4("span", { className: "ml-1.5 opacity-60", children: [
1494
+ /* @__PURE__ */ jsx6("span", { children: memberName(member) }),
1495
+ member.email && member.email !== memberName(member) ? /* @__PURE__ */ jsxs5("span", { className: "ml-1.5 opacity-60", children: [
1346
1496
  "\xB7 ",
1347
1497
  member.email
1348
1498
  ] }) : null
1349
1499
  ]
1350
1500
  }
1351
1501
  ) }, member.userId)) }),
1352
- members !== null && visible.length === 0 ? /* @__PURE__ */ jsx5("p", { className: "p-2 text-xs text-muted-foreground", children: members.length === 0 ? "This organization has no other members yet, so there is nobody to assign." : "Nobody in this organization matches." }) : null
1502
+ members !== null && visible.length === 0 ? /* @__PURE__ */ jsx6("p", { className: "p-2 text-xs text-muted-foreground", children: members.length === 0 ? "This organization has no other members yet, so there is nobody to assign." : "Nobody in this organization matches." }) : null
1353
1503
  ] })
1354
1504
  ] })
1355
1505
  ] })
@@ -1362,55 +1512,6 @@ import { asWriteConflict } from "@ai-matrx/records/core";
1362
1512
  import { useRecordsClient as useRecordsClient3 } from "@ai-matrx/records/react";
1363
1513
  import { Button as Button5, Checkbox as Checkbox3, cn as cn7 } from "@ai-matrx/design-system";
1364
1514
 
1365
- // src/Refusal.tsx
1366
- import { Alert, AlertDescription, AlertTitle, cn as cn5 } from "@ai-matrx/design-system";
1367
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1368
- function hintIsMachineIdentity(hint) {
1369
- return isMachineIdentity(hint);
1370
- }
1371
- function hintForAPerson(hint) {
1372
- const said = (hint ?? "").trim();
1373
- if (said === "") return null;
1374
- return isMachineIdentity(said) ? null : said;
1375
- }
1376
- function RefusalNotice({
1377
- error,
1378
- className,
1379
- actions
1380
- }) {
1381
- const plain = refusalForAPerson(error);
1382
- return /* @__PURE__ */ jsxs5(
1383
- Alert,
1384
- {
1385
- variant: "destructive",
1386
- className: cn5("text-xs", className),
1387
- ...plain.forEngineers ? { title: plain.forEngineers } : {},
1388
- children: [
1389
- /* @__PURE__ */ jsx6(AlertTitle, { className: "text-xs font-medium", children: plain.title }),
1390
- /* @__PURE__ */ jsxs5(AlertDescription, { className: "space-y-1 text-xs", children: [
1391
- plain.sentence.replace(/\.$/, "") === plain.title ? null : /* @__PURE__ */ jsx6("p", { children: plain.sentence }),
1392
- /* @__PURE__ */ jsx6("p", { className: "opacity-80", children: plain.remedy }),
1393
- plain.forEngineers ? /* @__PURE__ */ jsx6(
1394
- "p",
1395
- {
1396
- className: "sr-only",
1397
- "aria-hidden": "true",
1398
- "data-for-engineers": "",
1399
- ...error.sqlstate ? { "data-sqlstate": error.sqlstate } : {},
1400
- children: plain.forEngineers
1401
- }
1402
- ) : null,
1403
- actions
1404
- ] })
1405
- ]
1406
- }
1407
- );
1408
- }
1409
- function RefusalLine({ error, className }) {
1410
- const plain = refusalForAPerson(error);
1411
- return /* @__PURE__ */ jsx6("p", { className: cn5("text-xs text-destructive", className), title: plain.forEngineers || void 0, children: refusalLineForAPerson(error) });
1412
- }
1413
-
1414
1515
  // src/whoChangedSource.tsx
1415
1516
  import { createContext as createContext2, useCallback as useCallback2, useContext as useContext2, useEffect as useEffect4, useMemo as useMemo5, useRef, useState as useState5 } from "react";
1416
1517
  import { useRecordsClient } from "@ai-matrx/records/react";
@@ -2249,7 +2350,7 @@ function GridCell({
2249
2350
  );
2250
2351
  const value = editing ? editing.valueFor(row, field) : row.document?.[field.key];
2251
2352
  const state = editing ? editing.stateOf(row.id, field.key) : null;
2252
- const refusal = editing ? editing.refusalOf(row.id, field.key) : null;
2353
+ const refusal2 = editing ? editing.refusalOf(row.id, field.key) : null;
2253
2354
  const editable = Boolean(editing) && canWrite && fieldIsEditable(field);
2254
2355
  if (open && editing) {
2255
2356
  return /* @__PURE__ */ jsx10(
@@ -2278,8 +2379,8 @@ function GridCell({
2278
2379
  }
2279
2380
  ),
2280
2381
  answered ? null : /* @__PURE__ */ jsx10("span", { className: "text-xs text-muted-foreground", title: "nobody has answered this yet", children: "\u2014" }),
2281
- refusal ? /* @__PURE__ */ jsxs6("span", { className: "flex items-center gap-1 rounded border border-destructive/40 px-1", children: [
2282
- /* @__PURE__ */ jsx10(RefusalLine, { error: refusal.error }),
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 }),
2283
2384
  /* @__PURE__ */ jsx10(Button5, { size: "sm", variant: "outline", onClick: () => editing?.retry(row.id, field.key), children: "Try again" })
2284
2385
  ] }) : null
2285
2386
  ] });
@@ -2354,11 +2455,11 @@ function GridCell({
2354
2455
  ),
2355
2456
  onAskWhoChanged ? /* @__PURE__ */ jsx10(WhoBadge, { field, row, onAskWhoChanged }) : null,
2356
2457
  state === "saving" ? /* @__PURE__ */ jsx10("span", { className: "px-1 text-[10px] text-muted-foreground", children: "Saving\u2026" }) : null,
2357
- refusal ? /* @__PURE__ */ jsxs6("span", { className: "flex flex-col gap-0.5 rounded border border-destructive/40 p-1", children: [
2358
- /* @__PURE__ */ jsx10(RefusalLine, { error: refusal.error }),
2359
- refusal.conflict ? /* @__PURE__ */ jsxs6("span", { className: "text-[11px]", children: [
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: [
2360
2461
  "Theirs: ",
2361
- /* @__PURE__ */ jsx10("strong", { children: formatTheirs(refusal.conflict, field.key) })
2462
+ /* @__PURE__ */ jsx10("strong", { children: formatTheirs(refusal2.conflict, field.key) })
2362
2463
  ] }) : null,
2363
2464
  /* @__PURE__ */ jsxs6("span", { className: "flex gap-1", children: [
2364
2465
  /* @__PURE__ */ jsx10(Button5, { size: "sm", variant: "outline", onClick: () => editing?.retry(row.id, field.key), children: "Try again" }),
@@ -3081,7 +3182,20 @@ function EnrichPanel({ tableId, fieldId, className }) {
3081
3182
  " in one go."
3082
3183
  ] }) : null
3083
3184
  ] }) : null,
3084
- 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,
3085
3199
  field ? null : null
3086
3200
  ] }, row.field_id);
3087
3201
  }) });
@@ -3126,15 +3240,15 @@ function PastePreview({
3126
3240
  /* @__PURE__ */ jsx12(PlanTable, { plan }),
3127
3241
  plan.refusals.length > 0 ? /* @__PURE__ */ jsxs8("div", { "data-matrx-paste-refusals": true, className: "flex flex-col gap-1", children: [
3128
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:` }),
3129
- /* @__PURE__ */ jsx12("ul", { className: "flex flex-col gap-1", children: plan.refusals.slice(0, 12).map((refusal, index) => /* @__PURE__ */ jsxs8("li", { className: "text-xs text-muted-foreground", children: [
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: [
3130
3244
  /* @__PURE__ */ jsxs8("span", { className: "font-medium", children: [
3131
3245
  "Row ",
3132
- refusal.fromLine,
3246
+ refusal2.fromLine,
3133
3247
  ", ",
3134
- refusal.column
3248
+ refusal2.column
3135
3249
  ] }),
3136
3250
  " \u2014 ",
3137
- refusal.why
3251
+ refusal2.why
3138
3252
  ] }, index)) }),
3139
3253
  plan.refusals.length > 12 ? /* @__PURE__ */ jsxs8("p", { className: "text-xs text-muted-foreground", children: [
3140
3254
  plan.refusals.length - 12,
@@ -3162,7 +3276,13 @@ function PastePreview({
3162
3276
  setAdding(group.fieldKey);
3163
3277
  void onAddOptions(group.fieldKey, group.words).catch(
3164
3278
  (err) => setAddFailed(
3165
- `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
+ )
3166
3286
  )
3167
3287
  ).finally(() => setAdding(null));
3168
3288
  },
@@ -3170,7 +3290,7 @@ function PastePreview({
3170
3290
  }
3171
3291
  )
3172
3292
  ] }, group.fieldKey)),
3173
- addFailed ? /* @__PURE__ */ jsx12("p", { className: "text-xs text-destructive", children: addFailed }) : null
3293
+ addFailed ? /* @__PURE__ */ jsx12(RefusalLine, { error: addFailed }) : null
3174
3294
  ] }) : null,
3175
3295
  plan.pastRightEdge > 0 ? /* @__PURE__ */ jsxs8("p", { className: "text-xs text-muted-foreground", children: [
3176
3296
  plan.pastRightEdge,
@@ -3350,19 +3470,19 @@ function planPastedBlock(args) {
3350
3470
  accepted[key] = judged.value;
3351
3471
  }
3352
3472
  const predicted = predictValueRefusals(args.fields, accepted);
3353
- for (const refusal of predicted) {
3354
- const key = refusal.field_key;
3473
+ for (const refusal2 of predicted) {
3474
+ const key = refusal2.field_key;
3355
3475
  if (key === void 0) continue;
3356
3476
  const cell = built.find((b) => b.key === key && b.refusal === null);
3357
3477
  if (!cell) continue;
3358
- cell.refusal = refusal.message;
3478
+ cell.refusal = refusal2.message;
3359
3479
  cell.value = null;
3360
3480
  delete accepted[key];
3361
3481
  refusals.push({
3362
3482
  fromLine,
3363
3483
  column: fieldName(byKey.get(key) ?? { key }),
3364
3484
  raw: cell.raw,
3365
- why: refusal.message
3485
+ why: refusal2.message
3366
3486
  });
3367
3487
  }
3368
3488
  cells += Object.keys(accepted).length;
@@ -4126,18 +4246,24 @@ function ExportMenu({ tableId, rows, label, className }) {
4126
4246
  size: "sm",
4127
4247
  variant: "outline",
4128
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);
4129
4257
  try {
4130
- void exportXlsx(fields.data ?? [], exported(), name).then(
4258
+ exportXlsx(fields.data ?? [], exported(), name).then(
4131
4259
  (bytes) => download(
4132
4260
  `${name}.xlsx`,
4133
4261
  "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
4134
4262
  bytes
4135
4263
  )
4136
- );
4264
+ ).catch(failed);
4137
4265
  } catch (thrown) {
4138
- setFailure(
4139
- `The spreadsheet could not be written (${thrown instanceof Error ? thrown.message : String(thrown)}). The CSV beside this button carries the same rows.`
4140
- );
4266
+ failed(thrown);
4141
4267
  }
4142
4268
  },
4143
4269
  children: "XLSX"
@@ -4153,7 +4279,7 @@ function ExportMenu({ tableId, rows, label, className }) {
4153
4279
  human: () => exportCsv(fields.data ?? [], exported())
4154
4280
  }
4155
4281
  ),
4156
- failure ? /* @__PURE__ */ jsx14("p", { className: "text-xs text-destructive", children: failure }) : null
4282
+ failure ? /* @__PURE__ */ jsx14(RefusalLine, { error: failure }) : null
4157
4283
  ] });
4158
4284
  }
4159
4285
 
@@ -4269,6 +4395,45 @@ import {
4269
4395
  Separator as Separator2,
4270
4396
  cn as cn12
4271
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
4272
4437
  import { Fragment as Fragment7, jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
4273
4438
  var BATCH = 250;
4274
4439
  var SAMPLES = 40;
@@ -4321,9 +4486,13 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4321
4486
  const [openRun, setOpenRun] = useState13(null);
4322
4487
  const [runRows, setRunRows] = useState13(null);
4323
4488
  const [runProblem, setRunProblem] = useState13(null);
4489
+ const [runsProblem, setRunsProblem] = useState13(null);
4324
4490
  const loadRuns = useCallback8(async () => {
4325
4491
  const answered = await client.imports({ table_id: tableId, limit: 10 });
4326
- if (answered.ok) setRuns(answered.data);
4492
+ if (answered.ok) {
4493
+ setRuns(answered.data);
4494
+ setRunsProblem(null);
4495
+ } else setRunsProblem(answered.error);
4327
4496
  }, [client, tableId]);
4328
4497
  useEffect8(() => {
4329
4498
  void loadRuns();
@@ -4344,7 +4513,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4344
4513
  limit: 200
4345
4514
  });
4346
4515
  if (!answered.ok) {
4347
- setRunProblem(answered.error.message);
4516
+ setRunProblem(answered.error);
4348
4517
  return;
4349
4518
  }
4350
4519
  setRunRows({
@@ -4385,7 +4554,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4385
4554
  }));
4386
4555
  const answered = await client.importPlan({ table_id: tableId, columns });
4387
4556
  if (!answered.ok) {
4388
- setProblem(answered.error.message);
4557
+ setProblem(answered.error);
4389
4558
  setPhase("waiting");
4390
4559
  return;
4391
4560
  }
@@ -4402,7 +4571,14 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4402
4571
  setMapping(next);
4403
4572
  setPhase("ready");
4404
4573
  } catch (thrown) {
4405
- 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
+ );
4406
4582
  setPhase("waiting");
4407
4583
  }
4408
4584
  },
@@ -4419,7 +4595,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4419
4595
  setPhase("declaring");
4420
4596
  const made = await client.importDeclareColumns({ table_id: tableId, rows, mapping });
4421
4597
  if (!made.ok) {
4422
- setProblem(made.error.message);
4598
+ setProblem(made.error);
4423
4599
  setPhase("ready");
4424
4600
  return;
4425
4601
  }
@@ -4438,7 +4614,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4438
4614
  policy: { on_duplicate: onDuplicate, unmapped }
4439
4615
  });
4440
4616
  if (!opened.ok) {
4441
- setProblem(opened.error.message);
4617
+ setProblem(opened.error);
4442
4618
  setPhase("ready");
4443
4619
  return;
4444
4620
  }
@@ -4457,7 +4633,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4457
4633
  const batch = rows.slice(at, at + take2);
4458
4634
  const answered = await client.importRows({ import_id: importId, rows: batch, mapping });
4459
4635
  if (!answered.ok) {
4460
- setProblem(answered.error.message);
4636
+ setProblem(answered.error);
4461
4637
  setOutcomes([...collected]);
4462
4638
  setLedger({ ...told, final: false });
4463
4639
  setPhase("ready");
@@ -4476,7 +4652,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4476
4652
  }
4477
4653
  const finished = await client.importFinish({ import_id: importId, unmapped });
4478
4654
  if (!finished.ok) {
4479
- setProblem(finished.error.message);
4655
+ setProblem(finished.error);
4480
4656
  setPhase("done");
4481
4657
  return;
4482
4658
  }
@@ -4514,6 +4690,11 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4514
4690
  const titleMissing = Boolean(titleKey) && titleMappedTo === null;
4515
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";
4516
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]);
4517
4698
  return /* @__PURE__ */ jsxs12("div", { className: cn12("flex min-w-0 flex-col gap-2 text-xs", className), children: [
4518
4699
  /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-2", children: [
4519
4700
  /* @__PURE__ */ jsx16("span", { className: "font-medium", children: "Import" }),
@@ -4541,7 +4722,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4541
4722
  } : {}
4542
4723
  }
4543
4724
  ) : null,
4544
- problem ? /* @__PURE__ */ jsx16("p", { className: "text-destructive", children: problem }) : null,
4725
+ problem ? /* @__PURE__ */ jsx16(RefusalNotice, { error: problem }) : null,
4545
4726
  phase === "reading" ? /* @__PURE__ */ jsx16("p", { className: "text-muted-foreground", children: "Reading the file\u2026" }) : null,
4546
4727
  phase === "planning" ? /* @__PURE__ */ jsx16("p", { className: "text-muted-foreground", children: "Working out what each column is\u2026" }) : null,
4547
4728
  plan && parsed ? /* @__PURE__ */ jsxs12(Fragment7, { children: [
@@ -4638,7 +4819,28 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4638
4819
  column.unit ? /* @__PURE__ */ jsx16("span", { className: "ml-1 opacity-60", children: column.unit }) : null,
4639
4820
  /* @__PURE__ */ jsx16("p", { className: "mt-0.5 text-muted-foreground", children: column.why }),
4640
4821
  column.ambiguous ? /* @__PURE__ */ jsx16("p", { className: "text-destructive", children: "Check a date you know before you run this." }) : null,
4641
- column.collides_with ? /* @__PURE__ */ jsx16("p", { className: "text-destructive", children: "Two columns in this file would make the same column." }) : null
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
4642
4844
  ] }),
4643
4845
  /* @__PURE__ */ jsx16("span", { className: "truncate pt-1.5 text-muted-foreground", children: (column.samples ?? []).slice(0, 3).map(String).join(" \xB7 ") })
4644
4846
  ]
@@ -4702,12 +4904,34 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4702
4904
  )
4703
4905
  ] })
4704
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,
4705
4929
  /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-2", children: [
4706
4930
  /* @__PURE__ */ jsx16(
4707
4931
  Button11,
4708
4932
  {
4709
4933
  size: "sm",
4710
- disabled: busy || !rights.write || rows.length === 0 || titleMissing,
4934
+ disabled: busy || !rights.write || rows.length === 0 || titleMissing || ruleVerdicts.length > 0 && !goAheadAnyway,
4711
4935
  onClick: () => void run(),
4712
4936
  children: phase === "declaring" ? "Adding the columns\u2026" : phase === "writing" ? `Writing\u2026 ${progress} of ${rows.length}` : `Import ${rows.length} row${rows.length === 1 ? "" : "s"}`
4713
4937
  }
@@ -4762,7 +4986,12 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4762
4986
  ] }) }),
4763
4987
  /* @__PURE__ */ jsx16("tbody", { children: interesting.slice(0, 200).map((o) => /* @__PURE__ */ jsxs12("tr", { className: "border-t align-top", children: [
4764
4988
  /* @__PURE__ */ jsx16("td", { className: "px-2 py-1", children: o.row }),
4765
- /* @__PURE__ */ jsx16("td", { className: cn12("px-2 py-1", o.outcome === "refused" && "text-destructive"), children: o.outcome === "refused" ? o.reason : o.outcome === "duplicate" ? o.reason : "written" }),
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" }),
4766
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 ") : "" })
4767
4996
  ] }, `${o.outcome}-${o.row}`)) })
4768
4997
  ] }) }) : null,
@@ -4785,6 +5014,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4785
5014
  ] }) : null
4786
5015
  ] }) : null
4787
5016
  ] }) : null,
5017
+ runsProblem ? /* @__PURE__ */ jsx16(RefusalNotice, { error: runsProblem }) : null,
4788
5018
  runs && runs.length > 0 ? /* @__PURE__ */ jsxs12(Fragment7, { children: [
4789
5019
  /* @__PURE__ */ jsx16(Separator2, {}),
4790
5020
  /* @__PURE__ */ jsx16("p", { className: "font-medium", children: "Imports into this table" }),
@@ -4817,7 +5047,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4817
5047
  }
4818
5048
  ) : null
4819
5049
  ] }),
4820
- openRun === r.import_id ? runProblem ? /* @__PURE__ */ jsx16("p", { className: "text-destructive", children: runProblem }) : runRows ? /* @__PURE__ */ jsxs12(Fragment7, { children: [
5050
+ openRun === r.import_id ? runProblem ? /* @__PURE__ */ jsx16(RefusalNotice, { error: runProblem }) : runRows ? /* @__PURE__ */ jsxs12(Fragment7, { children: [
4821
5051
  runRows.note ? /* @__PURE__ */ jsx16("p", { className: "text-muted-foreground", children: runRows.note }) : null,
4822
5052
  /* @__PURE__ */ jsx16("div", { className: "max-h-64 overflow-auto rounded border", children: /* @__PURE__ */ jsxs12("table", { className: "w-full", children: [
4823
5053
  /* @__PURE__ */ jsx16("thead", { className: "sticky top-0 bg-muted", children: /* @__PURE__ */ jsxs12("tr", { children: [
@@ -4829,7 +5059,16 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4829
5059
  const source = row.source ?? {};
4830
5060
  return /* @__PURE__ */ jsxs12("tr", { className: "border-t align-top", children: [
4831
5061
  /* @__PURE__ */ jsx16("td", { className: "px-2 py-1", children: String(row.row ?? "") }),
4832
- /* @__PURE__ */ jsx16("td", { className: "px-2 py-1 text-destructive", children: String(row.reason ?? "") }),
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
+ ) }),
4833
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 ") })
4834
5073
  ] }, `${r.import_id}-${String(row.row ?? i)}`);
4835
5074
  }) })
@@ -5067,7 +5306,9 @@ function keyFor(label) {
5067
5306
  if (token === "") return "";
5068
5307
  return /^[a-z]/.test(token) ? token : `f_${token}`;
5069
5308
  }
5070
- var COLUMN_DID_NOT_LAND = "The store accepted this column and the table does not have it yet. Nothing was lost \u2014 your record values are untouched \u2014 but the column is not there, so this panel is staying open. Press Save again; if it keeps happening, tell us, because the store and the table disagree and that is ours to fix.";
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}`;
5071
5312
  function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }) {
5072
5313
  const table = useTable4(tableId);
5073
5314
  const fields = useFields6(tableId);
@@ -5367,14 +5608,20 @@ function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }
5367
5608
  ] }, `${rule.kind}-${index}`))
5368
5609
  ] }),
5369
5610
  mutation.error ? /* @__PURE__ */ jsx18(RefusalNotice, { error: mutation.error }) : null,
5370
- missing ? /* @__PURE__ */ jsx18("p", { className: "text-[11px] text-destructive", children: missing }) : null,
5371
- notLanded ? /* @__PURE__ */ jsx18("p", { className: "text-[11px] text-destructive", children: COLUMN_DID_NOT_LAND }) : null,
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,
5372
5618
  /* @__PURE__ */ jsxs14("div", { className: "flex flex-wrap items-center gap-2", children: [
5373
5619
  /* @__PURE__ */ jsx18(
5374
5620
  Button13,
5375
5621
  {
5376
5622
  size: "sm",
5377
5623
  disabled: mutation.saving || missing !== null,
5624
+ ...missing ? { "aria-describedby": "field-editor-missing" } : {},
5378
5625
  onClick: () => void save(),
5379
5626
  children: mutation.saving ? "Saving\u2026" : field ? EDIT_FIELD_SAVE_LABEL : ADD_FIELD_SAVE_LABEL
5380
5627
  }
@@ -6849,7 +7096,13 @@ function RecordForm({
6849
7096
  },
6850
7097
  field.id
6851
7098
  )) }),
6852
- 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,
6853
7106
  mutation.error ? /* @__PURE__ */ jsx23(
6854
7107
  RefusalNotice,
6855
7108
  {
@@ -6904,7 +7157,14 @@ function FieldRow({
6904
7157
  return /* @__PURE__ */ jsxs19("div", { className: "flex min-w-0 flex-col gap-1", children: [
6905
7158
  /* @__PURE__ */ jsx23(FieldLabel, { field, htmlFor: id, children: /* @__PURE__ */ jsx23(ProvenanceBadge, { document: document2, fieldKey: field.key }) }),
6906
7159
  /* @__PURE__ */ jsx23(FieldControl, { field, value, onChange, id }),
6907
- 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
+ ))
6908
7168
  ] });
6909
7169
  }
6910
7170
  function asWords(value) {
@@ -6915,7 +7175,7 @@ function asWords(value) {
6915
7175
  }
6916
7176
 
6917
7177
  // src/ShareControl.tsx
6918
- import { useState as useState22 } from "react";
7178
+ import { useEffect as useEffect13, useState as useState22 } from "react";
6919
7179
  import { Button as Button18 } from "@ai-matrx/design-system";
6920
7180
  import { Fragment as Fragment11, jsx as jsx24, jsxs as jsxs20 } from "react/jsx-runtime";
6921
7181
  function useCanShare() {
@@ -6930,12 +7190,16 @@ function ShareControl({
6930
7190
  subjectId,
6931
7191
  name,
6932
7192
  may,
7193
+ initiallyOpen = false,
6933
7194
  size = "sm",
6934
7195
  variant = "ghost",
6935
7196
  className
6936
7197
  }) {
6937
7198
  const host = useRecordsUi();
6938
- const [open, setOpen] = useState22(false);
7199
+ const [open, setOpen] = useState22(initiallyOpen);
7200
+ useEffect13(() => {
7201
+ if (initiallyOpen) setOpen(true);
7202
+ }, [initiallyOpen]);
6939
7203
  const asked = useRecordRights(may === void 0 ? subjectId : null);
6940
7204
  const mayShare = may ?? asked.share;
6941
7205
  if (!host.share) return null;
@@ -7217,12 +7481,12 @@ function parseSorts(raw) {
7217
7481
  }
7218
7482
 
7219
7483
  // src/ViewSwitcher.tsx
7220
- import { useEffect as useEffect14, useMemo as useMemo18, useState as useState25 } from "react";
7484
+ import { useEffect as useEffect15, useMemo as useMemo18, useState as useState25 } from "react";
7221
7485
  import { useFields as useFields12, useRecords as useRecords5, useRecordsClient as useRecordsClient13 } from "@ai-matrx/records/react";
7222
7486
  import { Button as Button21, Skeleton as Skeleton7, cn as cn21 } from "@ai-matrx/design-system";
7223
7487
 
7224
7488
  // src/Pipeline.tsx
7225
- import { useCallback as useCallback10, useEffect as useEffect13, 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";
7226
7490
  import {
7227
7491
  mayDrag,
7228
7492
  useFields as useFields11,
@@ -7271,7 +7535,7 @@ function PipelineBoard({
7271
7535
  const [dragging, setDragging] = useState24(null);
7272
7536
  const [over, setOver] = useState24(null);
7273
7537
  const alive = useRef6(true);
7274
- useEffect13(() => {
7538
+ useEffect14(() => {
7275
7539
  alive.current = true;
7276
7540
  return () => {
7277
7541
  alive.current = false;
@@ -7295,7 +7559,7 @@ function PipelineBoard({
7295
7559
  setWaiting(held.ok ? new Map((held.data ?? []).map((p) => [p.record_id, p])) : /* @__PURE__ */ new Map());
7296
7560
  setColumns(board.ok ? board.data ?? [] : []);
7297
7561
  }, [client, tableId, measure]);
7298
- useEffect13(() => {
7562
+ useEffect14(() => {
7299
7563
  void reload();
7300
7564
  }, [reload]);
7301
7565
  const stageKey = definition?.stage_field ?? null;
@@ -7517,10 +7781,22 @@ function Held({
7517
7781
  );
7518
7782
  }
7519
7783
  if (pending.kind === "refused") {
7520
- return /* @__PURE__ */ jsxs22("div", { className: "mt-1 rounded border border-destructive/50 bg-destructive/5 p-2 text-xs", children: [
7521
- /* @__PURE__ */ jsx26("p", { children: pending.verdict.why }),
7522
- /* @__PURE__ */ jsx26(Button20, { size: "sm", variant: "ghost", className: "mt-1 h-6", onClick: onCancel, children: "Close" })
7523
- ] });
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
+ );
7524
7800
  }
7525
7801
  if (pending.kind === "approval") {
7526
7802
  return /* @__PURE__ */ jsxs22("div", { className: "mt-1 flex flex-col gap-1 rounded border p-2 text-xs", children: [
@@ -7653,7 +7929,7 @@ function useViewRecords(view, pageSize = 200, filter) {
7653
7929
  error: null
7654
7930
  });
7655
7931
  const ruleId = view.ruleId ?? null;
7656
- useEffect14(() => {
7932
+ useEffect15(() => {
7657
7933
  if (!ruleId) return;
7658
7934
  let cancelled = false;
7659
7935
  setRuled({ rows: [], loading: true, error: null });
@@ -7717,7 +7993,7 @@ function ViewSwitcher({
7717
7993
  }) {
7718
7994
  const [layout, setLayout] = useState25(view.layout);
7719
7995
  const [local, setLocal] = useState25({});
7720
- useEffect14(() => {
7996
+ useEffect15(() => {
7721
7997
  setLayout(view.layout);
7722
7998
  setLocal({});
7723
7999
  }, [view.layout, view.name, view.subject]);
@@ -7785,7 +8061,7 @@ function useStageField(tableId) {
7785
8061
  asked: false,
7786
8062
  key: null
7787
8063
  });
7788
- useEffect14(() => {
8064
+ useEffect15(() => {
7789
8065
  let cancelled = false;
7790
8066
  setStage({ asked: false, key: null });
7791
8067
  void client.tableStageField({ table_id: tableId }).then((r) => {
@@ -8054,7 +8330,7 @@ function Card2({
8054
8330
  }
8055
8331
 
8056
8332
  // src/ArchivedView.tsx
8057
- import { useCallback as useCallback11, useEffect as useEffect15, useState as useState26 } from "react";
8333
+ import { useCallback as useCallback11, useEffect as useEffect16, useState as useState26 } from "react";
8058
8334
  import {
8059
8335
  ARCHIVE_LANES,
8060
8336
  ARCHIVE_LANE_LABEL,
@@ -8091,8 +8367,8 @@ function ArchivedView({
8091
8367
  const [loading, setLoading] = useState26(true);
8092
8368
  const [error, setError] = useState26(null);
8093
8369
  const [restoring, setRestoring] = useState26(null);
8094
- const [refusal, setRefusal] = useState26(null);
8095
- useEffect15(() => {
8370
+ const [refusal2, setRefusal] = useState26(null);
8371
+ useEffect16(() => {
8096
8372
  if (laneFromHost) setLane(laneFromHost);
8097
8373
  }, [laneFromHost]);
8098
8374
  const read = useCallback11(async () => {
@@ -8107,7 +8383,7 @@ function ArchivedView({
8107
8383
  }
8108
8384
  setLoading(false);
8109
8385
  }, [client, tableId, lane, pageSize]);
8110
- useEffect15(() => {
8386
+ useEffect16(() => {
8111
8387
  void read();
8112
8388
  }, [read]);
8113
8389
  const pick = (next) => {
@@ -8168,7 +8444,7 @@ function ArchivedView({
8168
8444
  /* @__PURE__ */ jsxs24("div", { className: "flex min-w-0 flex-1 flex-col", children: [
8169
8445
  /* @__PURE__ */ jsx28("span", { className: "truncate text-sm", children: recordName(row.document, titleKey) }),
8170
8446
  /* @__PURE__ */ jsx28("span", { className: "truncate text-xs text-muted-foreground", "data-testid": "archived-who", children: archivedByLine(row) }),
8171
- refusal && refusal.recordId === row.id ? /* @__PURE__ */ jsx28(RefusalNotice, { error: refusal.error, className: "mt-2" }) : null
8447
+ refusal2 && refusal2.recordId === row.id ? /* @__PURE__ */ jsx28(RefusalNotice, { error: refusal2.error, className: "mt-2" }) : null
8172
8448
  ] }),
8173
8449
  mayRestore ? /* @__PURE__ */ jsx28(
8174
8450
  Button22,
@@ -8189,7 +8465,7 @@ function ArchivedView({
8189
8465
  }
8190
8466
 
8191
8467
  // src/ArchivedDisclosure.tsx
8192
- import { useCallback as useCallback12, useEffect as useEffect16, useState as useState27 } from "react";
8468
+ import { useCallback as useCallback12, useEffect as useEffect17, useState as useState27 } from "react";
8193
8469
  import {
8194
8470
  emptyPortalArchiveLine,
8195
8471
  portalConfirmLine,
@@ -8246,7 +8522,7 @@ function ArchivedPortals({
8246
8522
  const [confirming, setConfirming] = useState27(null);
8247
8523
  const [typed, setTyped] = useState27("");
8248
8524
  const [restoring, setRestoring] = useState27(null);
8249
- const [refusal, setRefusal] = useState27(null);
8525
+ const [refusal2, setRefusal] = useState27(null);
8250
8526
  const [said, setSaid] = useState27(null);
8251
8527
  const read = useCallback12(async () => {
8252
8528
  const answered = await client.listPortals({ archived: "archived" });
@@ -8258,7 +8534,7 @@ function ArchivedPortals({
8258
8534
  setError(answered.error);
8259
8535
  }
8260
8536
  }, [client]);
8261
- useEffect16(() => {
8537
+ useEffect17(() => {
8262
8538
  void read();
8263
8539
  }, [read, refreshToken]);
8264
8540
  const restore = async (portal) => {
@@ -8362,7 +8638,7 @@ function ArchivedPortals({
8362
8638
  )
8363
8639
  ] })
8364
8640
  ] }) : null,
8365
- 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
8366
8642
  ]
8367
8643
  },
8368
8644
  portal.portal_id
@@ -8374,7 +8650,7 @@ function ArchivedPortals({
8374
8650
  }
8375
8651
 
8376
8652
  // src/ViewBar.tsx
8377
- import { useCallback as useCallback14, useEffect as useEffect17, useState as useState28 } from "react";
8653
+ import { useCallback as useCallback14, useEffect as useEffect18, useState as useState28 } from "react";
8378
8654
  import { useRecordsClient as useRecordsClient16 } from "@ai-matrx/records/react";
8379
8655
  import { Button as Button24, Input as Input2, Skeleton as Skeleton10, cn as cn24 } from "@ai-matrx/design-system";
8380
8656
 
@@ -8437,10 +8713,10 @@ function ViewBar({ tableId, activeViewId, onActiveView, seed, className }) {
8437
8713
  setError(null);
8438
8714
  setViews(mine);
8439
8715
  }, [client, viewTableId, tableId, seed]);
8440
- useEffect17(() => {
8716
+ useEffect18(() => {
8441
8717
  void load();
8442
8718
  }, [load]);
8443
- useEffect17(() => {
8719
+ useEffect18(() => {
8444
8720
  if (!views || views.length === 0) return;
8445
8721
  const chosen = views.find((v) => v.id === (activeViewId ?? active)) ?? views.find((v) => v.isDefault) ?? views[0];
8446
8722
  if (chosen.id !== active) setActive(chosen.id);
@@ -8616,12 +8892,12 @@ function ProposalRow({
8616
8892
  }
8617
8893
 
8618
8894
  // src/ActionInbox.tsx
8619
- import { useCallback as useCallback16, useEffect as useEffect19, 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";
8620
8896
  import { useRecordsClient as useRecordsClient19 } from "@ai-matrx/records/react";
8621
8897
  import { Badge as Badge9, Button as Button27, Skeleton as Skeleton12, cn as cn27 } from "@ai-matrx/design-system";
8622
8898
 
8623
8899
  // src/ChecklistRunner.tsx
8624
- import { useCallback as useCallback15, useEffect as useEffect18, useMemo as useMemo19, useState as useState30 } from "react";
8900
+ import { useCallback as useCallback15, useEffect as useEffect19, useMemo as useMemo19, useState as useState30 } from "react";
8625
8901
  import { useRecordsClient as useRecordsClient18, useTable as useTable10 } from "@ai-matrx/records/react";
8626
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";
8627
8903
  import { Fragment as Fragment13, jsx as jsx32, jsxs as jsxs28 } from "react/jsx-runtime";
@@ -8669,10 +8945,10 @@ function ChecklistRunner({
8669
8945
  (held) => held && answered.data.some((r) => r.run_id === held) ? held : answered.data[0]?.run_id ?? null
8670
8946
  );
8671
8947
  }, [client, includeClosed, recordId, runId, tableId]);
8672
- useEffect18(() => {
8948
+ useEffect19(() => {
8673
8949
  void loadRuns();
8674
8950
  }, [loadRuns]);
8675
- useEffect18(() => {
8951
+ useEffect19(() => {
8676
8952
  if (runId) setActiveId(runId);
8677
8953
  }, [runId]);
8678
8954
  const loadSteps = useCallback15(async () => {
@@ -8689,10 +8965,10 @@ function ChecklistRunner({
8689
8965
  setError(null);
8690
8966
  setSteps(answered.data);
8691
8967
  }, [client, activeId]);
8692
- useEffect18(() => {
8968
+ useEffect19(() => {
8693
8969
  void loadSteps();
8694
8970
  }, [loadSteps]);
8695
- useEffect18(() => {
8971
+ useEffect19(() => {
8696
8972
  if (!mayStart || !tableId) return;
8697
8973
  let cancelled = false;
8698
8974
  void client.checklistTemplates({ about_table_id: tableId, limit: 50 }).then((answered) => {
@@ -8918,7 +9194,7 @@ function useMyChecklistSteps(limit = 25) {
8918
9194
  setSteps(held);
8919
9195
  setLoading(false);
8920
9196
  }, [client, limit, me]);
8921
- useEffect18(() => {
9197
+ useEffect19(() => {
8922
9198
  void refresh();
8923
9199
  }, [refresh]);
8924
9200
  return { steps, loading, error, refresh };
@@ -8927,7 +9203,7 @@ function MyChecklistSteps({ className }) {
8927
9203
  const client = useRecordsClient18();
8928
9204
  const { steps, loading, error, refresh } = useMyChecklistSteps();
8929
9205
  const [busy, setBusy] = useState30(null);
8930
- const [refusal, setRefusal] = useState30(null);
9206
+ const [refusal2, setRefusal] = useState30(null);
8931
9207
  const complete = useCallback15(
8932
9208
  async (step2, evidence) => {
8933
9209
  setBusy(step2.step_id);
@@ -8950,7 +9226,7 @@ function MyChecklistSteps({ className }) {
8950
9226
  /* @__PURE__ */ jsx32("span", { className: "tabular-nums", children: steps.length })
8951
9227
  ] }),
8952
9228
  error ? /* @__PURE__ */ jsx32(RefusalNotice, { error }) : null,
8953
- refusal ? /* @__PURE__ */ jsx32(RefusalNotice, { error: refusal }) : null,
9229
+ refusal2 ? /* @__PURE__ */ jsx32(RefusalNotice, { error: refusal2 }) : null,
8954
9230
  /* @__PURE__ */ jsx32("ol", { className: "flex flex-col gap-1.5", children: steps.map((step2) => /* @__PURE__ */ jsx32(
8955
9231
  StepRow,
8956
9232
  {
@@ -8985,7 +9261,7 @@ function ChecklistsPanel({ tableId, onOpenRecord, className }) {
8985
9261
  if (!r.ok) setRuns([]);
8986
9262
  else setRuns(r.data);
8987
9263
  }, [client, tableId]);
8988
- useEffect18(() => {
9264
+ useEffect19(() => {
8989
9265
  void load();
8990
9266
  }, [load]);
8991
9267
  if (templates === null) return /* @__PURE__ */ jsx32(Skeleton11, { className: cn26("h-40 w-full", className) });
@@ -9127,11 +9403,11 @@ function ChecklistTemplateEditor({
9127
9403
  const client = useRecordsClient18();
9128
9404
  const [name, setName] = useState30("");
9129
9405
  const [rows, setRows] = useState30([{ ...EMPTY_ROW }]);
9130
- const [refusal, setRefusal] = useState30(null);
9406
+ const [refusal2, setRefusal] = useState30(null);
9131
9407
  const [error, setError] = useState30(null);
9132
9408
  const [busy, setBusy] = useState30(false);
9133
9409
  const [loading, setLoading] = useState30(Boolean(templateId));
9134
- useEffect18(() => {
9410
+ useEffect19(() => {
9135
9411
  if (!templateId) return;
9136
9412
  let cancelled = false;
9137
9413
  void client.checklistTemplateShape({ template_id: templateId }).then((answered) => {
@@ -9168,7 +9444,7 @@ function ChecklistTemplateEditor({
9168
9444
  }),
9169
9445
  [aboutTableId, name, rows]
9170
9446
  );
9171
- useEffect18(() => {
9447
+ useEffect19(() => {
9172
9448
  if (spec.steps.length === 0 || spec.name.length === 0) {
9173
9449
  setRefusal(null);
9174
9450
  return;
@@ -9295,9 +9571,9 @@ function ChecklistTemplateEditor({
9295
9571
  ] }, index)) }),
9296
9572
  /* @__PURE__ */ jsxs28("div", { className: "flex items-center gap-1.5", children: [
9297
9573
  /* @__PURE__ */ jsx32(Button26, { size: "sm", variant: "outline", onClick: () => setRows((held) => [...held, { ...EMPTY_ROW }]), children: "Add a step" }),
9298
- /* @__PURE__ */ jsx32(Button26, { size: "sm", disabled: busy || refusal !== null || spec.steps.length === 0, onClick: () => void save(), children: busy ? "\u2026" : "Save" })
9574
+ /* @__PURE__ */ jsx32(Button26, { size: "sm", disabled: busy || refusal2 !== null || spec.steps.length === 0, onClick: () => void save(), children: busy ? "\u2026" : "Save" })
9299
9575
  ] }),
9300
- 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
9301
9577
  ] });
9302
9578
  }
9303
9579
  function toStepSpec(row, index) {
@@ -9354,7 +9630,7 @@ function ActionInbox({ tableId, includeSettled = false, onOpenRecord, className
9354
9630
  setError(null);
9355
9631
  setItems(result.data);
9356
9632
  }, [client, includeSettled]);
9357
- useEffect19(() => {
9633
+ useEffect20(() => {
9358
9634
  void load();
9359
9635
  }, [load]);
9360
9636
  const shown = useMemo20(() => {
@@ -9362,10 +9638,10 @@ function ActionInbox({ tableId, includeSettled = false, onOpenRecord, className
9362
9638
  if (!tableId) return all;
9363
9639
  return all.filter((i) => i.kind !== "assignment" || i.subject_kind !== "record" || true);
9364
9640
  }, [items, tableId]);
9365
- useEffect19(() => {
9641
+ useEffect20(() => {
9366
9642
  if (cursor >= shown.length) setCursor(Math.max(0, shown.length - 1));
9367
9643
  }, [shown.length, cursor]);
9368
- useEffect19(() => {
9644
+ useEffect20(() => {
9369
9645
  const el = listRef.current?.querySelector(`[data-row="${cursor}"]`);
9370
9646
  el?.scrollIntoView({ block: "nearest" });
9371
9647
  }, [cursor, shown.length]);
@@ -9491,7 +9767,7 @@ function ActionInbox({ tableId, includeSettled = false, onOpenRecord, className
9491
9767
  }
9492
9768
 
9493
9769
  // src/HistoryPanel.tsx
9494
- import { useCallback as useCallback17, useEffect as useEffect20, useRef as useRef9, useState as useState32 } from "react";
9770
+ import { useCallback as useCallback17, useEffect as useEffect21, useRef as useRef9, useState as useState32 } from "react";
9495
9771
  import {
9496
9772
  useFields as useFields14,
9497
9773
  useRecordsClient as useRecordsClient20,
@@ -9519,7 +9795,7 @@ function HistoryPanel({ tableId, recordId, onAskAboutField, className }) {
9519
9795
  setError(null);
9520
9796
  setEntries(answered.data);
9521
9797
  }, [client, recordId]);
9522
- useEffect20(() => {
9798
+ useEffect21(() => {
9523
9799
  void load();
9524
9800
  }, [load]);
9525
9801
  if (error) return /* @__PURE__ */ jsx34(RefusalNotice, { error, className });
@@ -9800,7 +10076,7 @@ function say(value, field) {
9800
10076
  }
9801
10077
 
9802
10078
  // src/CommentThread.tsx
9803
- import { useCallback as useCallback18, useEffect as useEffect21, 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";
9804
10080
  import {
9805
10081
  useFields as useFields15,
9806
10082
  useRecordsClient as useRecordsClient21,
@@ -9837,10 +10113,10 @@ function CommentThread({ tableId, recordId, fieldKey, className }) {
9837
10113
  mayResolve: answered.data.may_resolve
9838
10114
  });
9839
10115
  }, [client, recordId, showResolved]);
9840
- useEffect21(() => {
10116
+ useEffect22(() => {
9841
10117
  void load();
9842
10118
  }, [load]);
9843
- useEffect21(() => {
10119
+ useEffect22(() => {
9844
10120
  let alive = true;
9845
10121
  if (!host.members) return;
9846
10122
  void host.members().then((roster) => {
@@ -10043,7 +10319,7 @@ function Line({
10043
10319
  }
10044
10320
 
10045
10321
  // src/FieldHistoryPanel.tsx
10046
- import { useCallback as useCallback19, useEffect as useEffect22, useState as useState34 } from "react";
10322
+ import { useCallback as useCallback19, useEffect as useEffect23, useState as useState34 } from "react";
10047
10323
  import {
10048
10324
  useFields as useFields16,
10049
10325
  useRecordsClient as useRecordsClient22,
@@ -10078,7 +10354,7 @@ function FieldHistoryPanel({
10078
10354
  setError(null);
10079
10355
  setRows(answered.data);
10080
10356
  }, [client, tableId, fieldKey, recordId]);
10081
- useEffect22(() => {
10357
+ useEffect23(() => {
10082
10358
  void load();
10083
10359
  }, [load]);
10084
10360
  const label = (fields.data ?? []).find((f) => f.key === fieldKey)?.label || humanize(fieldKey);
@@ -10271,7 +10547,7 @@ function submissionStamp(args) {
10271
10547
  }
10272
10548
 
10273
10549
  // src/PortalBuilder.tsx
10274
- import { useCallback as useCallback20, useEffect as useEffect23, useMemo as useMemo22, useState as useState35 } from "react";
10550
+ import { useCallback as useCallback20, useEffect as useEffect24, useMemo as useMemo22, useState as useState35 } from "react";
10275
10551
  import { useRecordsClient as useRecordsClient23, useTables as useTables3 } from "@ai-matrx/records/react";
10276
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";
10277
10553
  import { Fragment as Fragment15, jsx as jsx37, jsxs as jsxs33 } from "react/jsx-runtime";
@@ -10304,7 +10580,7 @@ function PortalBuilder({ tableId, portalId, onSaved, onClose, className }) {
10304
10580
  },
10305
10581
  [client, fieldsByTable]
10306
10582
  );
10307
- useEffect23(() => {
10583
+ useEffect24(() => {
10308
10584
  if (!portalId) return;
10309
10585
  void (async () => {
10310
10586
  const answered = await client.portalCard({ portal_id: portalId });
@@ -10318,7 +10594,7 @@ function PortalBuilder({ tableId, portalId, onSaved, onClose, className }) {
10318
10594
  setClientTableId(answered.data.client_table_id);
10319
10595
  })();
10320
10596
  }, [client, portalId]);
10321
- useEffect23(() => {
10597
+ useEffect24(() => {
10322
10598
  if (!tableId) return;
10323
10599
  setExposures(
10324
10600
  (prev) => prev[tableId] ? prev : {
@@ -10566,7 +10842,7 @@ function PortalBuilder({ tableId, portalId, onSaved, onClose, className }) {
10566
10842
  }
10567
10843
 
10568
10844
  // src/PortalsPanel.tsx
10569
- import { useCallback as useCallback21, useEffect as useEffect24, useMemo as useMemo23, useState as useState36 } from "react";
10845
+ import { useCallback as useCallback21, useEffect as useEffect25, useMemo as useMemo23, useState as useState36 } from "react";
10570
10846
  import { useRecordsClient as useRecordsClient24 } from "@ai-matrx/records/react";
10571
10847
  import {
10572
10848
  portalArchiveConsequence,
@@ -10607,6 +10883,7 @@ function BuildOrAsk({
10607
10883
 
10608
10884
  // src/PortalsPanel.tsx
10609
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.";
10610
10887
  function revokeConsequence(person, portalTitle) {
10611
10888
  const who = person.client ? `${person.email} (${person.client})` : person.email;
10612
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.`;
@@ -10636,13 +10913,16 @@ function stateWords(person) {
10636
10913
  };
10637
10914
  }
10638
10915
  var PORTAL_SUGGESTION = "Let each of my customers sign in and see their own jobs and invoices, and nothing else.";
10639
- function PortalsPanel({ tableId, className }) {
10916
+ function PortalsPanel({ tableId, activePortalId, className }) {
10640
10917
  const client = useRecordsClient24();
10641
10918
  const host = useRecordsUi();
10642
10919
  const [portals, setPortals] = useState36(null);
10643
10920
  const [exposures, setExposures] = useState36([]);
10644
10921
  const [listError, setListError] = useState36(null);
10645
- const [openId, setOpenId] = useState36(null);
10922
+ const [openId, setOpenId] = useState36(activePortalId ?? null);
10923
+ useEffect25(() => {
10924
+ if (activePortalId) setOpenId(activePortalId);
10925
+ }, [activePortalId]);
10646
10926
  const [building, setBuilding] = useState36(false);
10647
10927
  const [adding, setAdding] = useState36(null);
10648
10928
  const [archiveToken, setArchiveToken] = useState36(0);
@@ -10658,7 +10938,7 @@ function PortalsPanel({ tableId, className }) {
10658
10938
  setPortals(answered.data);
10659
10939
  setExposures(mapped.ok ? mapped.data : []);
10660
10940
  }, [client]);
10661
- useEffect24(() => {
10941
+ useEffect25(() => {
10662
10942
  void load();
10663
10943
  }, [load]);
10664
10944
  if (portals === null) return /* @__PURE__ */ jsx39(Skeleton17, { className: cn33("h-32 w-full", className) });
@@ -10739,59 +11019,73 @@ function PortalsPanel({ tableId, className }) {
10739
11019
  portal.portal_id
10740
11020
  )) })
10741
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,
10742
11023
  /* @__PURE__ */ jsx39("ul", { className: "flex flex-col gap-2", children: shown.map((portal) => {
10743
11024
  const url = `${origin}${portalPath(portal.slug)}`;
10744
11025
  const open = openId === portal.portal_id;
10745
- return /* @__PURE__ */ jsxs35("li", { className: "rounded-md border border-border bg-card p-2.5", children: [
10746
- /* @__PURE__ */ jsxs35("div", { className: "flex items-center gap-2", children: [
10747
- /* @__PURE__ */ jsx39("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: portal.title }),
10748
- portal.is_active ? null : /* @__PURE__ */ jsx39(Badge13, { variant: "outline", children: "closed" })
10749
- ] }),
10750
- /* @__PURE__ */ jsxs35("p", { className: "mt-0.5 text-xs text-muted-foreground", children: [
10751
- "Clients come from ",
10752
- portal.client_table,
10753
- portal.tables === 1 ? ", and see 1 Table" : `, and see ${portal.tables} Tables`,
10754
- "."
10755
- ] }),
10756
- /* @__PURE__ */ jsxs35("p", { className: "mt-1.5 text-xs", children: [
10757
- /* @__PURE__ */ jsx39("span", { className: "font-medium", children: portal.signed_in }),
10758
- " signed in",
10759
- " \xB7 ",
10760
- /* @__PURE__ */ jsx39("span", { className: "font-medium", children: portal.invited - portal.signed_in }),
10761
- " invited and waiting"
10762
- ] }),
10763
- /* @__PURE__ */ jsxs35("div", { className: "mt-2 flex flex-wrap items-center gap-1.5", children: [
10764
- /* @__PURE__ */ jsx39(CopyLink, { url }),
10765
- /* @__PURE__ */ jsx39(
10766
- Button33,
10767
- {
10768
- size: "sm",
10769
- variant: "ghost",
10770
- onClick: () => setOpenId(open ? null : portal.portal_id),
10771
- children: open ? "Close" : "Open it"
10772
- }
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"
10773
11035
  ),
10774
- /* @__PURE__ */ jsx39(
10775
- ArchivePortalControl,
10776
- {
10777
- portal,
10778
- onArchived: () => {
10779
- setArchiveToken((t) => t + 1);
10780
- 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()
10781
11083
  }
10782
- }
10783
- )
10784
- ] }),
10785
- open ? /* @__PURE__ */ jsx39(
10786
- PortalDetail,
10787
- {
10788
- portalId: portal.portal_id,
10789
- ...tableId ? { tableId } : {},
10790
- origin,
10791
- onChanged: () => void load()
10792
- }
10793
- ) : null
10794
- ] }, portal.portal_id);
11084
+ ) : null
11085
+ ]
11086
+ },
11087
+ portal.portal_id
11088
+ );
10795
11089
  }) }),
10796
11090
  /* @__PURE__ */ jsx39(ArchivedPortals, { refreshToken: archiveToken, onRestored: () => void load() })
10797
11091
  ] });
@@ -10920,7 +11214,7 @@ function PortalDetail({
10920
11214
  setError(null);
10921
11215
  setCard(answered.data);
10922
11216
  }, [client, portalId]);
10923
- useEffect24(() => {
11217
+ useEffect25(() => {
10924
11218
  void load();
10925
11219
  }, [load]);
10926
11220
  const revoke = useCallback21(
@@ -11113,7 +11407,7 @@ function Preview({
11113
11407
  const [which, setWhich] = useState36(first?.table_id ?? null);
11114
11408
  const [rows, setRows] = useState36(null);
11115
11409
  const [error, setError] = useState36(null);
11116
- useEffect24(() => {
11410
+ useEffect25(() => {
11117
11411
  if (!which) return;
11118
11412
  let cancelled = false;
11119
11413
  setRows(null);
@@ -11159,7 +11453,7 @@ function Invite({ card, onInvited }) {
11159
11453
  const [busy, setBusy] = useState36(false);
11160
11454
  const [said, setSaid] = useState36(null);
11161
11455
  const [error, setError] = useState36(null);
11162
- useEffect24(() => {
11456
+ useEffect25(() => {
11163
11457
  let cancelled = false;
11164
11458
  void client.list({ table_id: card.client_table_id, limit: 200 }).then((answered) => {
11165
11459
  if (cancelled) return;
@@ -11261,7 +11555,7 @@ function Invite({ card, onInvited }) {
11261
11555
  }
11262
11556
 
11263
11557
  // src/DigestScheduler.tsx
11264
- import { useCallback as useCallback22, useEffect as useEffect25, useMemo as useMemo24, useState as useState37 } from "react";
11558
+ import { useCallback as useCallback22, useEffect as useEffect26, useMemo as useMemo24, useState as useState37 } from "react";
11265
11559
  import { useRecordsClient as useRecordsClient25 } from "@ai-matrx/records/react";
11266
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";
11267
11561
  import { Fragment as Fragment17, jsx as jsx40, jsxs as jsxs36 } from "react/jsx-runtime";
@@ -11325,7 +11619,7 @@ function DigestScheduler({
11325
11619
  setMembers([]);
11326
11620
  }
11327
11621
  }, [client, tableId, host]);
11328
- useEffect25(() => {
11622
+ useEffect26(() => {
11329
11623
  void load();
11330
11624
  }, [load]);
11331
11625
  const schedule = useMemo24(() => {
@@ -11549,7 +11843,17 @@ function DigestScheduler({
11549
11843
  preview ? /* @__PURE__ */ jsxs36("div", { className: "rounded-md border bg-muted/40 p-2.5 text-xs", children: [
11550
11844
  /* @__PURE__ */ jsx40("p", { className: "font-medium", children: preview.subject }),
11551
11845
  /* @__PURE__ */ jsx40("p", { className: "mt-1 text-muted-foreground", children: preview.body }),
11552
- preview.incomplete ? /* @__PURE__ */ jsx40("p", { className: "mt-1 text-destructive", children: preview.incomplete }) : null,
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,
11553
11857
  preview.entered.length > 0 ? /* @__PURE__ */ jsxs36("p", { className: "mt-1", children: [
11554
11858
  /* @__PURE__ */ jsx40("span", { className: "font-medium", children: "Arrived:" }),
11555
11859
  " ",
@@ -11567,10 +11871,11 @@ function DigestScheduler({
11567
11871
  }
11568
11872
 
11569
11873
  // src/SubscriptionsPanel.tsx
11570
- import { useCallback as useCallback23, useEffect as useEffect26, useState as useState38 } from "react";
11874
+ import { useCallback as useCallback23, useEffect as useEffect27, useState as useState38 } from "react";
11571
11875
  import { useRecordsClient as useRecordsClient26 } from "@ai-matrx/records/react";
11572
11876
  import { Badge as Badge14, Button as Button35, Skeleton as Skeleton19, Switch as Switch2, cn as cn35 } from "@ai-matrx/design-system";
11573
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.";
11574
11879
  function whenItFires(subscription) {
11575
11880
  if (subscription.cadence === "instant") return "as it happens";
11576
11881
  const every = subscription.cadence === "hourly" ? "an hourly summary" : subscription.cadence === "weekly" ? "a weekly summary" : "a daily summary";
@@ -11578,7 +11883,7 @@ function whenItFires(subscription) {
11578
11883
  }
11579
11884
  var CHANNEL_WORDS2 = SUBSCRIPTION_CHANNEL_LABEL;
11580
11885
  var DIGEST_SUGGESTION = "Email me a summary of this every Monday at 8 in the morning.";
11581
- function SubscriptionsPanel({ tableId, className }) {
11886
+ function SubscriptionsPanel({ tableId, activeRuleId, className }) {
11582
11887
  const client = useRecordsClient26();
11583
11888
  const [rows, setRows] = useState38(null);
11584
11889
  const [error, setError] = useState38(null);
@@ -11596,7 +11901,7 @@ function SubscriptionsPanel({ tableId, className }) {
11596
11901
  setError(null);
11597
11902
  setRows(answered.data);
11598
11903
  }, [client, tableId]);
11599
- useEffect26(() => {
11904
+ useEffect27(() => {
11600
11905
  void load();
11601
11906
  }, [load]);
11602
11907
  const flip = useCallback23(
@@ -11669,96 +11974,119 @@ function SubscriptionsPanel({ tableId, className }) {
11669
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."
11670
11975
  }
11671
11976
  ) : null,
11672
- /* @__PURE__ */ jsx41("ul", { className: "flex flex-col gap-2", children: rows.map((subscription) => /* @__PURE__ */ jsxs37("li", { className: "rounded-md border p-2.5", children: [
11673
- /* @__PURE__ */ jsxs37("div", { className: "flex items-start gap-2", children: [
11674
- /* @__PURE__ */ jsxs37("div", { className: "min-w-0 flex-1", children: [
11675
- /* @__PURE__ */ jsx41("p", { className: "truncate text-sm font-medium", children: subscription.name }),
11676
- /* @__PURE__ */ jsxs37("p", { className: "mt-0.5 text-xs text-muted-foreground", children: [
11677
- CHANNEL_WORDS2[subscription.channel] ?? `on the ${subscription.channel} channel`,
11678
- " \xB7 ",
11679
- whenItFires(subscription)
11680
- ] })
11681
- ] }),
11682
- subscription.i_may_mute ? /* @__PURE__ */ jsx41(
11683
- Switch2,
11684
- {
11685
- checked: !subscription.muted,
11686
- disabled: busy === subscription.rule_id,
11687
- "aria-label": `Tell me about ${subscription.name}`,
11688
- onCheckedChange: (on) => void flip(subscription, on)
11689
- }
11690
- ) : /* @__PURE__ */ jsx41(Badge14, { variant: "secondary", children: "someone else's" })
11691
- ] }),
11692
- /* @__PURE__ */ jsx41("p", { className: "mt-1.5 text-xs text-muted-foreground", children: subscription.muted ? "Off \u2014 it stays here and tells nobody until you switch it back on." : subscription.mine ? "On, and addressed to you." : "On, and addressed to somebody else in this organization." }),
11693
- /* @__PURE__ */ jsxs37("div", { className: "mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
11694
- subscription.next_digest_at ? /* @__PURE__ */ jsxs37("span", { children: [
11695
- "Next summary ",
11696
- new Date(subscription.next_digest_at).toLocaleString()
11697
- ] }) : subscription.cadence === "instant" ? null : subscription.muted ? /* @__PURE__ */ jsx41("span", { children: "No next summary while it is off." }) : null,
11698
- subscription.last_sent_at ? /* @__PURE__ */ jsxs37("span", { children: [
11699
- "Last told you ",
11700
- new Date(subscription.last_sent_at).toLocaleString()
11701
- ] }) : /* @__PURE__ */ jsx41("span", { children: "It has not told you anything yet." }),
11702
- subscription.quiet_hours ? /* @__PURE__ */ jsxs37("span", { children: [
11703
- "Not between ",
11704
- subscription.quiet_hours.start,
11705
- " and ",
11706
- subscription.quiet_hours.end,
11707
- " \u2014 a send inside those hours waits until they end."
11708
- ] }) : null,
11709
- subscription.cadence === "instant" ? null : /* @__PURE__ */ jsx41(
11710
- Button35,
11711
- {
11712
- size: "sm",
11713
- variant: "ghost",
11714
- disabled: busy === subscription.rule_id,
11715
- onClick: () => void showOne(subscription),
11716
- children: "Send me a preview now"
11717
- }
11718
- )
11719
- ] }),
11720
- preview && preview.rule_id === subscription.rule_id ? /* @__PURE__ */ jsxs37("div", { className: "mt-2 rounded-md border bg-muted/40 p-2.5 text-xs", children: [
11721
- /* @__PURE__ */ jsx41("p", { className: "font-medium", children: preview.subject }),
11722
- /* @__PURE__ */ jsx41("p", { className: "mt-1 text-muted-foreground", children: preview.body }),
11723
- preview.incomplete ? (
11724
- // Absent or honest: a subscription that cannot produce a summary
11725
- // says which piece is missing instead of showing an empty one.
11726
- /* @__PURE__ */ jsx41("p", { className: "mt-1 text-destructive", children: preview.incomplete })
11727
- ) : null,
11728
- preview.entered.length > 0 ? /* @__PURE__ */ jsxs37("p", { className: "mt-1", children: [
11729
- /* @__PURE__ */ jsx41("span", { className: "font-medium", children: "Arrived:" }),
11730
- " ",
11731
- preview.entered.map((e) => e.name).join(", ")
11732
- ] }) : null,
11733
- preview.left.length > 0 ? /* @__PURE__ */ jsxs37("p", { className: "mt-1", children: [
11734
- /* @__PURE__ */ jsx41("span", { className: "font-medium", children: "Left:" }),
11735
- " ",
11736
- preview.left.map((e) => e.name).join(", ")
11737
- ] }) : null,
11738
- preview.changed.length > 0 ? /* @__PURE__ */ jsxs37("p", { className: "mt-1", children: [
11739
- /* @__PURE__ */ jsx41("span", { className: "font-medium", children: "Changed:" }),
11740
- " ",
11741
- preview.changed.map((e) => e.name).join(", ")
11742
- ] }) : null,
11743
- /* @__PURE__ */ jsx41("p", { className: "mt-1.5 text-muted-foreground", children: "Nothing was sent and nothing was recorded \u2014 this is what the next one would say." }),
11744
- /* @__PURE__ */ jsx41(Button35, { size: "sm", variant: "ghost", className: "mt-1", onClick: () => setPreview(null), children: "Close" })
11745
- ] }) : null
11746
- ] }, subscription.rule_id)) })
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
+ )) })
11747
12075
  ] });
11748
12076
  }
11749
12077
 
11750
12078
  // src/FormBuilder.tsx
11751
- import { useCallback as useCallback25, useEffect as useEffect29, useMemo as useMemo26, useState as useState41 } from "react";
12079
+ import { useCallback as useCallback25, useEffect as useEffect30, useMemo as useMemo26, useState as useState41 } from "react";
11752
12080
  import { useFields as useFields18, useRecordsClient as useRecordsClient28, useTable as useTable14 } from "@ai-matrx/records/react";
11753
12081
 
11754
12082
  // src/publish-gate.ts
11755
- import { useEffect as useEffect27, useState as useState39 } from "react";
12083
+ import { useEffect as useEffect28, useState as useState39 } from "react";
11756
12084
  import { useRecordsClient as useRecordsClient27 } from "@ai-matrx/records/react";
11757
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.";
11758
12086
  function usePublishGate() {
11759
12087
  const client = useRecordsClient27();
11760
12088
  const [storeOpen, setStoreOpen] = useState39(null);
11761
- useEffect27(() => {
12089
+ useEffect28(() => {
11762
12090
  let alive = true;
11763
12091
  void (async () => {
11764
12092
  const answered = await client.storeIsOpen();
@@ -11789,7 +12117,7 @@ import {
11789
12117
  } from "@ai-matrx/design-system";
11790
12118
 
11791
12119
  // src/FormRunner.tsx
11792
- import { useCallback as useCallback24, useEffect as useEffect28, 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";
11793
12121
  import { useFields as useFields17, useOptionalRecordsClient as useOptionalRecordsClient4 } from "@ai-matrx/records/react";
11794
12122
  import { Button as Button36, Progress, Skeleton as Skeleton20, cn as cn36 } from "@ai-matrx/design-system";
11795
12123
  import { Fragment as Fragment18, jsx as jsx42, jsxs as jsxs38 } from "react/jsx-runtime";
@@ -11806,9 +12134,16 @@ function ConnectedFormRunner(props) {
11806
12134
  const submit = useCallback24(
11807
12135
  async (values) => {
11808
12136
  if (!client) {
12137
+ const said = "This form cannot send answers from here, so nothing was sent.";
11809
12138
  return {
11810
12139
  ok: false,
11811
- message: "This form has nowhere to send an answer: it is running outside the record store and nobody gave it a submit port. A public form is served with its questions and its submit port by the server; a signed-in one needs <RecordsProvider> above it."
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
+ )
11812
12147
  };
11813
12148
  }
11814
12149
  const document2 = {
@@ -11821,7 +12156,7 @@ function ConnectedFormRunner(props) {
11821
12156
  })
11822
12157
  };
11823
12158
  const written = await client.recordWrite({ table_id: form.subject, data: document2 });
11824
- if (!written.ok) return { ok: false, message: written.error.message };
12159
+ if (!written.ok) return { ok: false, message: written.error.message, error: written.error };
11825
12160
  return { ok: true, recordId: written.data };
11826
12161
  },
11827
12162
  [client, form]
@@ -11863,7 +12198,7 @@ function FormStage({
11863
12198
  const [answers, setAnswers] = useState40({});
11864
12199
  const [at, setAt] = useState40(0);
11865
12200
  const [error, setError] = useState40(null);
11866
- const [refusal, setRefusal] = useState40(null);
12201
+ const [refusal2, setRefusal] = useState40(null);
11867
12202
  const [writing, setWriting] = useState40(false);
11868
12203
  const [done, setDone] = useState40(null);
11869
12204
  const [hidden, setHidden] = useState40({});
@@ -11883,7 +12218,7 @@ function FormStage({
11883
12218
  };
11884
12219
  });
11885
12220
  }, [fields, form.questions]);
11886
- useEffect28(() => {
12221
+ useEffect29(() => {
11887
12222
  let cancelled = false;
11888
12223
  const conditional = questions.filter((q) => q.showIf);
11889
12224
  if (conditional.length === 0 || !evaluate) return;
@@ -11932,12 +12267,21 @@ function FormStage({
11932
12267
  const outcome = await onSubmit(values);
11933
12268
  setWriting(false);
11934
12269
  if (!outcome.ok) {
11935
- 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
+ });
11936
12280
  return;
11937
12281
  }
11938
12282
  setDone(outcome.message ?? "sent");
11939
12283
  onSubmitted?.(outcome.recordId ?? null);
11940
- }, [answers, form, honeypotKey, live, onSubmit, onSubmitted, preview]);
12284
+ }, [answers, form, honeypotKey, live, onSubmit, onSubmitted, preview, questions]);
11941
12285
  function advance() {
11942
12286
  if (!oneAtATime) return;
11943
12287
  if (index < live.length - 1) setAt(index + 1);
@@ -11954,7 +12298,7 @@ function FormStage({
11954
12298
  if (event.shiftKey) retreat();
11955
12299
  else advance();
11956
12300
  }
11957
- useEffect28(() => {
12301
+ useEffect29(() => {
11958
12302
  const input = stage.current?.querySelector(
11959
12303
  "input:not([tabindex='-1']), textarea, select, [role='combobox']"
11960
12304
  );
@@ -11996,7 +12340,22 @@ function FormStage({
11996
12340
  ] }) : null,
11997
12341
  form.intro && index === 0 ? /* @__PURE__ */ jsx42("p", { className: "text-sm text-muted-foreground", children: form.intro }) : null,
11998
12342
  error ? /* @__PURE__ */ jsx42(RefusalNotice, { error, className: "text-left" }) : null,
11999
- refusal ? /* @__PURE__ */ jsx42("p", { role: "alert", className: "rounded border border-destructive/40 px-2 py-1 text-left text-xs text-destructive", children: refusal }) : null,
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,
12000
12359
  /* @__PURE__ */ jsxs38("div", { ref: stage, className: "flex flex-col gap-4 text-left", children: [
12001
12360
  (oneAtATime ? current ? [current] : [] : live).map((q) => /* @__PURE__ */ jsx42(
12002
12361
  Question,
@@ -12070,12 +12429,15 @@ function Question({
12070
12429
  void upload?.(file).then((result) => {
12071
12430
  if (!result) return;
12072
12431
  if (result.ok) onChange(result.fileId);
12073
- else setUploadError(result.reason);
12432
+ else
12433
+ setUploadError(
12434
+ refusal("invalid_argument", result.reason, "Pick another file, or try this one again.")
12435
+ );
12074
12436
  });
12075
12437
  }
12076
12438
  }
12077
12439
  ),
12078
- uploadError ? /* @__PURE__ */ jsx42("p", { className: "text-xs text-destructive", children: uploadError }) : null
12440
+ uploadError ? /* @__PURE__ */ jsx42(RefusalLine, { error: uploadError }) : null
12079
12441
  ] }) : /* @__PURE__ */ jsx42(FieldControl, { field, value, onChange, id })
12080
12442
  ] });
12081
12443
  }
@@ -12149,10 +12511,10 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, className }) {
12149
12511
  setError(null);
12150
12512
  setForms(mine);
12151
12513
  }, [client, tableId, seed, claimSeed]);
12152
- useEffect29(() => {
12514
+ useEffect30(() => {
12153
12515
  void load();
12154
12516
  }, [load]);
12155
- useEffect29(() => {
12517
+ useEffect30(() => {
12156
12518
  if (!forms || forms.length === 0) return;
12157
12519
  const chosen = forms.find((f) => f.id === (activeFormId ?? activeId)) ?? forms[0];
12158
12520
  if (chosen.id !== activeId) setActiveId(chosen.id);
@@ -12563,7 +12925,7 @@ function groupLabel(groups) {
12563
12925
 
12564
12926
  // src/chartFrame.tsx
12565
12927
  import {
12566
- useEffect as useEffect30,
12928
+ useEffect as useEffect31,
12567
12929
  useId as useId2,
12568
12930
  useRef as useRef12,
12569
12931
  useState as useState42
@@ -12573,7 +12935,7 @@ import { jsx as jsx44, jsxs as jsxs40 } from "react/jsx-runtime";
12573
12935
  function useMeasuredWidth(fallback = 480) {
12574
12936
  const ref = useRef12(null);
12575
12937
  const [width, setWidth] = useState42(fallback);
12576
- useEffect30(() => {
12938
+ useEffect31(() => {
12577
12939
  const node = ref.current;
12578
12940
  if (!node) return;
12579
12941
  const apply = () => {
@@ -12697,7 +13059,7 @@ function isSignatureField(field) {
12697
13059
  }
12698
13060
 
12699
13061
  // src/DocTemplate.tsx
12700
- import { useCallback as useCallback26, useEffect as useEffect31, useState as useState43 } from "react";
13062
+ import { useCallback as useCallback26, useEffect as useEffect32, useState as useState43 } from "react";
12701
13063
  import { useFields as useFields19, useRecordsClient as useRecordsClient29, useTable as useTable15 } from "@ai-matrx/records/react";
12702
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";
12703
13065
  import { jsx as jsx45, jsxs as jsxs41 } from "react/jsx-runtime";
@@ -12745,10 +13107,10 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
12745
13107
  setError(null);
12746
13108
  setTemplates(rows);
12747
13109
  }, [client, tableId, seed, fields.data]);
12748
- useEffect31(() => {
13110
+ useEffect32(() => {
12749
13111
  void load();
12750
13112
  }, [load]);
12751
- useEffect31(() => {
13113
+ useEffect32(() => {
12752
13114
  if (!templates || templates.length === 0) return;
12753
13115
  const chosen = templates.find((t) => t.id === (activeTemplateId ?? activeId)) ?? templates[0];
12754
13116
  if (chosen.id !== activeId) setActiveId(chosen.id);
@@ -12756,7 +13118,7 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
12756
13118
  setDraftBody(chosen.body);
12757
13119
  onActiveTemplate?.(chosen);
12758
13120
  }, [templates, activeTemplateId]);
12759
- useEffect31(() => {
13121
+ useEffect32(() => {
12760
13122
  let cancelled = false;
12761
13123
  if (draftBody.trim() === "") {
12762
13124
  setUnresolved([]);
@@ -12867,16 +13229,21 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
12867
13229
  onChange: (e) => setDraftBody(e.target.value)
12868
13230
  }
12869
13231
  ),
12870
- unresolved.length > 0 ? /* @__PURE__ */ jsx45("ul", { className: "flex flex-col gap-0.5 rounded border border-dashed px-2 py-1 text-xs", children: unresolved.map((token) => /* @__PURE__ */ jsxs41("li", { className: "text-destructive", children: [
12871
- /* @__PURE__ */ jsx45("code", { children: token.raw }),
12872
- " \u2014 ",
12873
- token.why
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
+ )
12874
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
12875
13242
  ] });
12876
13243
  }
12877
13244
 
12878
13245
  // src/DocRender.tsx
12879
- import { useCallback as useCallback27, useEffect as useEffect32, useRef as useRef13, useState as useState44 } from "react";
13246
+ import { useCallback as useCallback27, useEffect as useEffect33, useRef as useRef13, useState as useState44 } from "react";
12880
13247
  import { useRecordsClient as useRecordsClient30 } from "@ai-matrx/records/react";
12881
13248
  import { Button as Button39, Skeleton as Skeleton23, cn as cn40 } from "@ai-matrx/design-system";
12882
13249
  import { jsx as jsx46, jsxs as jsxs42 } from "react/jsx-runtime";
@@ -12905,7 +13272,7 @@ function DocRender({ templateId, recordId, filename, onRendered, className }) {
12905
13272
  setPreview(body.data);
12906
13273
  setRenders(held.data.filter((r) => r.template_id === templateId));
12907
13274
  }, [client, templateId, recordId]);
12908
- useEffect32(() => {
13275
+ useEffect33(() => {
12909
13276
  void load();
12910
13277
  }, [load]);
12911
13278
  async function freeze() {
@@ -12989,7 +13356,7 @@ function DocRender({ templateId, recordId, filename, onRendered, className }) {
12989
13356
  }
12990
13357
 
12991
13358
  // src/SignBlock.tsx
12992
- import { useCallback as useCallback28, useEffect as useEffect33, useState as useState45 } from "react";
13359
+ import { useCallback as useCallback28, useEffect as useEffect34, useState as useState45 } from "react";
12993
13360
  import { useFields as useFields20, useRecordsClient as useRecordsClient31 } from "@ai-matrx/records/react";
12994
13361
  import { BasicInput as BasicInput15, Button as Button40, Skeleton as Skeleton24, cn as cn41 } from "@ai-matrx/design-system";
12995
13362
  import { useTable as useTable16 } from "@ai-matrx/records/react";
@@ -13019,7 +13386,7 @@ function SignBlock({ tableId, recordId, render, className }) {
13019
13386
  }
13020
13387
  setVerdicts(answers);
13021
13388
  }, [client, recordId]);
13022
- useEffect33(() => {
13389
+ useEffect34(() => {
13023
13390
  void load();
13024
13391
  }, [load]);
13025
13392
  async function sign(field) {
@@ -13075,7 +13442,21 @@ function SignBlock({ tableId, recordId, render, className }) {
13075
13442
  signature.document_hash.slice(0, 12)
13076
13443
  ] })
13077
13444
  ] }),
13078
- /* @__PURE__ */ jsx47("p", { className: cn41("mt-0.5", intact === false ? "text-destructive" : "text-muted-foreground"), children: intact === true ? "The store says what was signed is still exactly what is there." : intact === false ? typeof verdict === "object" && verdict !== null && "reason" in verdict ? String(verdict.reason) : "The store says what is there no longer matches what was signed." : String(verdict ?? "") })
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 ?? "") })
13079
13460
  ] }, signature.id);
13080
13461
  }) }) : null,
13081
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: [
@@ -13126,7 +13507,7 @@ function SignBlock({ tableId, recordId, render, className }) {
13126
13507
  }
13127
13508
 
13128
13509
  // src/NotifyRuleEditor.tsx
13129
- import { useCallback as useCallback29, useEffect as useEffect34, useState as useState46 } from "react";
13510
+ import { useCallback as useCallback29, useEffect as useEffect35, useState as useState46 } from "react";
13130
13511
  import { useRecordsClient as useRecordsClient32, useTable as useTable17 } from "@ai-matrx/records/react";
13131
13512
  import { Button as Button41, Skeleton as Skeleton25, cn as cn42 } from "@ai-matrx/design-system";
13132
13513
  import { jsx as jsx48, jsxs as jsxs44 } from "react/jsx-runtime";
@@ -13162,7 +13543,7 @@ function NotifyRuleEditor({ tableId, seed, className }) {
13162
13543
  if (host.savedViews) setViews(await host.savedViews());
13163
13544
  else setViews(null);
13164
13545
  }, [client, host, tableId]);
13165
- useEffect34(() => {
13546
+ useEffect35(() => {
13166
13547
  void load();
13167
13548
  }, [load]);
13168
13549
  const write = useCallback29(
@@ -13190,7 +13571,7 @@ function NotifyRuleEditor({ tableId, seed, className }) {
13190
13571
  },
13191
13572
  [client, tableId]
13192
13573
  );
13193
- useEffect34(() => {
13574
+ useEffect35(() => {
13194
13575
  if (!subscriptions || !seed || seed.length === 0) return;
13195
13576
  const missing = seed.filter((s) => !subscriptions.some((held) => held.name === s.name));
13196
13577
  if (missing.length === 0) return;
@@ -13357,7 +13738,7 @@ import { Fragment as Fragment20, jsx as jsx49, jsxs as jsxs45 } from "react/jsx-
13357
13738
  function pretty(n) {
13358
13739
  return Number.isInteger(n) ? n.toLocaleString() : n.toLocaleString(void 0, { maximumFractionDigits: 2 });
13359
13740
  }
13360
- function asRefusal(block) {
13741
+ function asRefusal2(block) {
13361
13742
  return mapPgError(
13362
13743
  {
13363
13744
  message: block.refused ?? "",
@@ -13418,7 +13799,7 @@ function ChartBlock({ block, subject, className }) {
13418
13799
  " ms"
13419
13800
  ] }) : null
13420
13801
  ] }),
13421
- block.refused ? /* @__PURE__ */ jsx49(RefusalNotice, { error: asRefusal(block) }) : needs ? /* @__PURE__ */ jsx49("p", { className: "px-1 py-3 text-xs text-muted-foreground", children: needs }) : kind === "stuck" ? /* @__PURE__ */ jsx49(StuckList, { rows: block.rows ?? [] }) : points.length === 0 ? /* @__PURE__ */ jsx49("p", { className: "px-1 py-3 text-xs text-muted-foreground", children: "The store answered this question with no groups at all, so there is nothing to draw yet." }) : /* @__PURE__ */ jsx49(Fragment20, { children: kind === "number" ? /* @__PURE__ */ jsx49(BigNumber, { points, measure: primary, onDrill: drill }) : kind === "table" ? /* @__PURE__ */ jsx49(GroupTable, { points, series, onDrill: drill }) : /* @__PURE__ */ jsxs45(Fragment20, { children: [
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: [
13422
13803
  /* @__PURE__ */ jsx49(Drawing, { kind, points, series, config, onDrill: drill }),
13423
13804
  /* @__PURE__ */ jsx49(Values, { points, measure: primary, config, onDrill: drill })
13424
13805
  ] }) })
@@ -13646,7 +14027,7 @@ function Drawing({
13646
14027
  }
13647
14028
 
13648
14029
  // src/DashboardCanvas.tsx
13649
- import { useCallback as useCallback30, useEffect as useEffect35, useMemo as useMemo28, useState as useState47 } from "react";
14030
+ import { useCallback as useCallback30, useEffect as useEffect36, useMemo as useMemo28, useState as useState47 } from "react";
13650
14031
  import { useFields as useFields21, useRecordsClient as useRecordsClient33, useTable as useTable18 } from "@ai-matrx/records/react";
13651
14032
  import {
13652
14033
  BasicInput as BasicInput16,
@@ -13683,17 +14064,17 @@ function DashboardCanvas({ tableId, activeDashboardId, filter, className }) {
13683
14064
  setError(null);
13684
14065
  setBoards(answered.data.map(dashboardFromSummary));
13685
14066
  }, [client, tableId]);
13686
- useEffect35(() => {
14067
+ useEffect36(() => {
13687
14068
  void load();
13688
14069
  }, [load]);
13689
- useEffect35(() => {
14070
+ useEffect36(() => {
13690
14071
  if (!boards || boards.length === 0) return;
13691
14072
  const chosen = boards.find((d) => d.id === (activeDashboardId ?? activeId)) ?? boards[0];
13692
14073
  if (chosen.id !== activeId) setActiveId(chosen.id);
13693
14074
  }, [boards, activeDashboardId]);
13694
14075
  const board = useMemo28(() => boards?.find((d) => d.id === activeId) ?? null, [boards, activeId]);
13695
14076
  const filterKey = JSON.stringify(filter ?? {});
13696
- useEffect35(() => {
14077
+ useEffect36(() => {
13697
14078
  if (!activeId) {
13698
14079
  setRun(null);
13699
14080
  return;
@@ -13989,18 +14370,19 @@ function GroupingPicker({
13989
14370
  }
13990
14371
 
13991
14372
  // src/FormsPanel.tsx
13992
- import { useCallback as useCallback31, useEffect as useEffect36, useState as useState48 } from "react";
14373
+ import { useCallback as useCallback31, useEffect as useEffect37, useState as useState48 } from "react";
13993
14374
  import { useRecordsClient as useRecordsClient34, useTable as useTable19 } from "@ai-matrx/records/react";
13994
14375
  import { publicFormPath as publicFormPath2 } from "@ai-matrx/records";
13995
14376
  import { Badge as Badge15, Button as Button43, Skeleton as Skeleton27, cn as cn45 } from "@ai-matrx/design-system";
13996
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.";
13997
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.";
13998
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.";
13999
14381
  function formSuggestion(tableName2) {
14000
14382
  const subject = tableName2?.trim() ? tableName2.trim() : "this table";
14001
14383
  return `Make me a form that collects new ${subject} entries and tells me when somebody answers.`;
14002
14384
  }
14003
- function FormsPanel({ tableId, className }) {
14385
+ function FormsPanel({ tableId, activeFormId, className }) {
14004
14386
  const client = useRecordsClient34();
14005
14387
  const publishGate = usePublishGate();
14006
14388
  const host = useRecordsUi();
@@ -14011,6 +14393,11 @@ function FormsPanel({ tableId, className }) {
14011
14393
  const [busy, setBusy] = useState48(null);
14012
14394
  const [copied, setCopied] = useState48(null);
14013
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]);
14014
14401
  const load = useCallback31(async () => {
14015
14402
  const answered = await client.forms({ table_id: tableId });
14016
14403
  if (!answered.ok) {
@@ -14021,7 +14408,7 @@ function FormsPanel({ tableId, className }) {
14021
14408
  setError(null);
14022
14409
  setForms(answered.data);
14023
14410
  }, [client, tableId]);
14024
- useEffect36(() => {
14411
+ useEffect37(() => {
14025
14412
  void load();
14026
14413
  }, [load]);
14027
14414
  const toggle = useCallback31(
@@ -14059,10 +14446,12 @@ function FormsPanel({ tableId, className }) {
14059
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
14060
14447
  ] }),
14061
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,
14062
14450
  building ? /* @__PURE__ */ jsx51(
14063
14451
  FormBuilder,
14064
14452
  {
14065
14453
  tableId,
14454
+ activeFormId: linked ? linked.form_id : null,
14066
14455
  onActiveForm: () => {
14067
14456
  void load();
14068
14457
  }
@@ -14088,57 +14477,69 @@ function FormsPanel({ tableId, className }) {
14088
14477
  ) : null,
14089
14478
  /* @__PURE__ */ jsx51("ul", { className: "flex flex-col gap-2", children: forms.map((form) => {
14090
14479
  const url = `${origin}${publicFormPath2(form.form_id)}`;
14091
- return /* @__PURE__ */ jsxs47("li", { className: "rounded-md border p-2.5", children: [
14092
- /* @__PURE__ */ jsxs47("div", { className: "flex items-center gap-2", children: [
14093
- /* @__PURE__ */ jsx51("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: form.title ?? form.slug }),
14094
- /* @__PURE__ */ jsx51(Badge15, { variant: form.state === "open" ? "default" : "secondary", children: form.state })
14095
- ] }),
14096
- /* @__PURE__ */ jsx51("p", { className: "mt-0.5 text-xs text-muted-foreground", children: FORM_STATE_WORDS[form.state] }),
14097
- /* @__PURE__ */ jsxs47("p", { className: "mt-1.5 text-xs", children: [
14098
- /* @__PURE__ */ jsx51("span", { className: "font-medium", children: form.in_table }),
14099
- " in the table",
14100
- form.held > 0 ? /* @__PURE__ */ jsxs47(Fragment22, { children: [
14101
- " \xB7 ",
14102
- /* @__PURE__ */ jsx51("span", { className: "font-medium", children: form.held }),
14103
- " waiting for someone"
14104
- ] }) : null,
14105
- form.rejected > 0 ? /* @__PURE__ */ jsxs47(Fragment22, { children: [
14106
- " \xB7 ",
14107
- /* @__PURE__ */ jsx51("span", { className: "font-medium", children: form.rejected }),
14108
- " turned away"
14109
- ] }) : null,
14110
- form.submission_cap !== null ? /* @__PURE__ */ jsxs47("span", { className: "text-muted-foreground", children: [
14111
- " \xB7 stops at ",
14112
- form.submission_cap
14113
- ] }) : null
14114
- ] }),
14115
- /* @__PURE__ */ jsxs47("div", { className: "mt-2 flex flex-wrap items-center gap-1.5", children: [
14116
- form.published_at ? /* @__PURE__ */ jsx51(Button43, { size: "sm", variant: "outline", onClick: () => void copy(url, form.form_id), children: copied === form.form_id ? "Copied" : "Copy link" }) : null,
14117
- rights.structure && !publishGate.blocked ? /* @__PURE__ */ jsx51(
14118
- Button43,
14119
- {
14120
- size: "sm",
14121
- variant: "ghost",
14122
- disabled: busy === form.form_id,
14123
- onClick: () => void toggle(form),
14124
- children: busy === form.form_id ? "\u2026" : form.published_at && !form.closed_at ? "Unpublish" : "Publish"
14125
- }
14126
- ) : null
14127
- ] }),
14128
- rights.structure && publishGate.why ? /* @__PURE__ */ jsx51("p", { className: "mt-1.5 text-xs text-muted-foreground", children: publishGate.why }) : null,
14129
- shown && shown === url ? /* @__PURE__ */ jsxs47("p", { className: "mt-1.5 break-all rounded border border-dashed px-2 py-1 text-xs", children: [
14130
- "This browser would not let the page copy for you, so here it is to copy by hand:",
14131
- " ",
14132
- url
14133
- ] }) : null
14134
- ] }, form.form_id);
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
+ );
14135
14536
  }) }),
14136
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
14137
14538
  ] });
14138
14539
  }
14139
14540
 
14140
14541
  // src/BookingBuilder.tsx
14141
- import { useCallback as useCallback32, useEffect as useEffect37, useMemo as useMemo29, useState as useState49 } from "react";
14542
+ import { useCallback as useCallback32, useEffect as useEffect38, useMemo as useMemo29, useState as useState49 } from "react";
14142
14543
  import { useFields as useFields22, useRecordsClient as useRecordsClient35, useTable as useTable20 } from "@ai-matrx/records/react";
14143
14544
  import {
14144
14545
  bookingPath
@@ -14213,18 +14614,18 @@ function BookingBuilder({ tableId, bookingId, onSaved, onClose, className }) {
14213
14614
  setLoaded(true);
14214
14615
  if (mine) setFormId(mine.form_id);
14215
14616
  }, [client, tableId, bookingId]);
14216
- useEffect37(() => {
14617
+ useEffect38(() => {
14217
14618
  void load();
14218
14619
  }, [load]);
14219
- useEffect37(() => {
14620
+ useEffect38(() => {
14220
14621
  if (title !== "" || !table.data) return;
14221
14622
  setTitle(existing?.title ?? `Book a ${minutes}-minute ${table.data.name} appointment`);
14222
14623
  }, [table.data, existing]);
14223
- useEffect37(() => {
14624
+ useEffect38(() => {
14224
14625
  if (!existing) return;
14225
14626
  setMinutes(existing.slot_minutes);
14226
14627
  }, [existing]);
14227
- useEffect37(() => {
14628
+ useEffect38(() => {
14228
14629
  if (!offer) return;
14229
14630
  setWindows(draftWindows(offer));
14230
14631
  setMinutes(offer.slot_minutes);
@@ -14486,7 +14887,7 @@ function BookingBuilder({ tableId, bookingId, onSaved, onClose, className }) {
14486
14887
  }
14487
14888
 
14488
14889
  // src/BookingSlots.tsx
14489
- import { useCallback as useCallback33, useEffect as useEffect38, useMemo as useMemo30, useState as useState50 } from "react";
14890
+ import { useCallback as useCallback33, useEffect as useEffect39, useMemo as useMemo30, useState as useState50 } from "react";
14490
14891
  import { useRecordsClient as useRecordsClient36, useMyLevels as useMyLevels2 } from "@ai-matrx/records/react";
14491
14892
  import { bookingPath as bookingPath2 } from "@ai-matrx/records";
14492
14893
  import { Badge as Badge16, Button as Button45, Skeleton as Skeleton29, cn as cn47 } from "@ai-matrx/design-system";
@@ -14517,7 +14918,7 @@ function BookingSlots({ tableId, className }) {
14517
14918
  setError(null);
14518
14919
  setPages(answered.data);
14519
14920
  }, [client, tableId]);
14520
- useEffect38(() => {
14921
+ useEffect39(() => {
14521
14922
  void load();
14522
14923
  }, [load]);
14523
14924
  const subjectIds = useMemo30(
@@ -14686,12 +15087,12 @@ function nextInWords(page) {
14686
15087
  }
14687
15088
 
14688
15089
  // src/CaptureSheet.tsx
14689
- import { useCallback as useCallback35, useEffect as useEffect40, useRef as useRef15, useState as useState52 } from "react";
15090
+ import { useCallback as useCallback35, useEffect as useEffect41, useRef as useRef15, useState as useState52 } from "react";
14690
15091
  import { useFields as useFields23, useRecordsClient as useRecordsClient38, useTable as useTable21 } from "@ai-matrx/records/react";
14691
15092
  import { Button as Button47, Input as Input4, Skeleton as Skeleton31, Textarea as Textarea3, cn as cn49 } from "@ai-matrx/design-system";
14692
15093
 
14693
15094
  // src/CaptureRun.tsx
14694
- import { useCallback as useCallback34, useEffect as useEffect39, 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";
14695
15096
  import {
14696
15097
  coerceTypedAnswer as coerceTypedAnswer2,
14697
15098
  coercionLabel,
@@ -14750,7 +15151,7 @@ function CaptureRun({ sheetId, face: given, className }) {
14750
15151
  const [lastSynced, setLastSynced] = useState51(null);
14751
15152
  const [sending, setSending] = useState51(false);
14752
15153
  const queueRef = useRef14(null);
14753
- useEffect39(() => {
15154
+ useEffect40(() => {
14754
15155
  const q2 = openCaptureQueue({
14755
15156
  onChange: (c, all) => {
14756
15157
  setCounts(c);
@@ -14788,13 +15189,13 @@ function CaptureRun({ sheetId, face: given, className }) {
14788
15189
  void q2.sync().then(() => void q2.lastSyncedAt().then(setLastSynced));
14789
15190
  return () => q2.dispose();
14790
15191
  }, [client, host]);
14791
- useEffect39(() => {
15192
+ useEffect40(() => {
14792
15193
  if (given !== void 0) return;
14793
15194
  let cancelled = false;
14794
15195
  void client.captureOpen({ sheet_id: sheetId }).then((res) => {
14795
15196
  if (cancelled) return;
14796
15197
  if (res.ok) setFace(res.data);
14797
- else setLoadFailed(res.error?.message ?? "This sheet could not be opened.");
15198
+ else setLoadFailed(refusalOr(res.error, "unreachable", "This sheet could not be opened."));
14798
15199
  });
14799
15200
  return () => {
14800
15201
  cancelled = true;
@@ -14841,7 +15242,11 @@ function CaptureRun({ sheetId, face: given, className }) {
14841
15242
  const needed = questions.filter((x) => x.required && !answered(x.field));
14842
15243
  if (needed.length > 0) {
14843
15244
  setMissing(
14844
- `${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
+ )
14845
15250
  );
14846
15251
  setAt(questions.findIndex((x) => x.field === needed[0].field));
14847
15252
  return;
@@ -14872,7 +15277,7 @@ function CaptureRun({ sheetId, face: given, className }) {
14872
15277
  if (online) await sync();
14873
15278
  }
14874
15279
  if (loadFailed) {
14875
- return /* @__PURE__ */ jsx54("section", { className: cn48("mx-auto w-full max-w-sm p-4", className), children: /* @__PURE__ */ jsx54("p", { className: "text-sm text-destructive", "data-testid": "capture-refusal", children: loadFailed }) });
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" }) }) });
14876
15281
  }
14877
15282
  if (face === void 0) {
14878
15283
  return /* @__PURE__ */ jsx54(Skeleton30, { className: cn48("mx-auto h-64 w-full max-w-sm", className) });
@@ -14903,7 +15308,21 @@ function CaptureRun({ sheetId, face: given, className }) {
14903
15308
  ] });
14904
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: [
14905
15310
  /* @__PURE__ */ jsx54("span", { className: "tabular-nums text-muted-foreground", children: new Date(i.captured_at).toLocaleTimeString() }),
14906
- /* @__PURE__ */ jsx54("span", { className: cn48("flex-1", i.state === "refused" && "text-destructive"), children: i.state === "refused" ? i.last_error ?? "This one was refused." : i.last_error ?? "Waiting for a signal." }),
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." }),
14907
15326
  i.state === "refused" ? /* @__PURE__ */ jsx54(
14908
15327
  Button46,
14909
15328
  {
@@ -15023,7 +15442,7 @@ function CaptureRun({ sheetId, face: given, className }) {
15023
15442
  }
15024
15443
  }
15025
15444
  ),
15026
- 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,
15027
15446
  /* @__PURE__ */ jsxs50("div", { className: "flex items-center gap-2", children: [
15028
15447
  at > 0 ? /* @__PURE__ */ jsx54(
15029
15448
  Button46,
@@ -15120,7 +15539,7 @@ function AdHocCaptureSheet({
15120
15539
  },
15121
15540
  [host]
15122
15541
  );
15123
- useEffect40(() => {
15542
+ useEffect41(() => {
15124
15543
  let cancelled = false;
15125
15544
  void (async () => {
15126
15545
  const held = host.captureQueue ? await host.captureQueue.load() : [];
@@ -15251,13 +15670,13 @@ function AdHocCaptureSheet({
15251
15670
  setUploadError(null);
15252
15671
  void host.upload?.(file).then((result) => {
15253
15672
  if (result.ok) setFileId(result.fileId);
15254
- else setUploadError(result.reason);
15673
+ else setUploadError(refusal("invalid_argument", result.reason, "Pick another file, or try this one again."));
15255
15674
  });
15256
15675
  }
15257
15676
  }
15258
15677
  ),
15259
15678
  fileId ? /* @__PURE__ */ jsx55("p", { className: "text-[11px] text-muted-foreground", children: "Attached." }) : null,
15260
- uploadError ? /* @__PURE__ */ jsx55("p", { className: "text-xs text-destructive", children: uploadError }) : null
15679
+ uploadError ? /* @__PURE__ */ jsx55(RefusalLine, { error: uploadError }) : null
15261
15680
  ] }) : null,
15262
15681
  /* @__PURE__ */ jsx55(
15263
15682
  Textarea3,
@@ -15299,7 +15718,7 @@ function AdHocCaptureSheet({
15299
15718
  }
15300
15719
 
15301
15720
  // src/PortalShell.tsx
15302
- import { useCallback as useCallback36, useEffect as useEffect41, useState as useState53 } from "react";
15721
+ import { useCallback as useCallback36, useEffect as useEffect42, useState as useState53 } from "react";
15303
15722
  import { useRecordsClient as useRecordsClient39 } from "@ai-matrx/records/react";
15304
15723
  import { Button as Button48, Skeleton as Skeleton32, cn as cn50 } from "@ai-matrx/design-system";
15305
15724
  import { jsx as jsx56, jsxs as jsxs52 } from "react/jsx-runtime";
@@ -15325,7 +15744,7 @@ function PortalShell({ tableId, form, resourceType = "record", className }) {
15325
15744
  }
15326
15745
  setReach(reached.data.map((row) => row.resource_id));
15327
15746
  }, [client, resourceType]);
15328
- useEffect41(() => {
15747
+ useEffect42(() => {
15329
15748
  void load();
15330
15749
  }, [load]);
15331
15750
  if (error) {
@@ -15385,7 +15804,7 @@ function PortalShell({ tableId, form, resourceType = "record", className }) {
15385
15804
  function PortalRow({ tableId, recordId }) {
15386
15805
  const client = useRecordsClient39();
15387
15806
  const [title, setTitle] = useState53(null);
15388
- useEffect41(() => {
15807
+ useEffect42(() => {
15389
15808
  let cancelled = false;
15390
15809
  void client.recordRead({ record_id: recordId }).then((answered) => {
15391
15810
  if (cancelled) return;
@@ -15405,7 +15824,7 @@ function PortalRow({ tableId, recordId }) {
15405
15824
  }
15406
15825
 
15407
15826
  // src/PublicViewPage.tsx
15408
- import { useCallback as useCallback37, useEffect as useEffect42, useState as useState54 } from "react";
15827
+ import { useCallback as useCallback37, useEffect as useEffect43, useState as useState54 } from "react";
15409
15828
  import { useRecordsClient as useRecordsClient40 } from "@ai-matrx/records/react";
15410
15829
  import { Skeleton as Skeleton33, cn as cn51 } from "@ai-matrx/design-system";
15411
15830
  import { jsx as jsx57, jsxs as jsxs53 } from "react/jsx-runtime";
@@ -15449,7 +15868,7 @@ function PublicViewPage({ slug, className }) {
15449
15868
  }
15450
15869
  setRows([{ id: found.resource_id, document: read.data.document, level: "viewer", hidden: read.data.hidden }]);
15451
15870
  }, [client, slug]);
15452
- useEffect42(() => {
15871
+ useEffect43(() => {
15453
15872
  void load();
15454
15873
  }, [load]);
15455
15874
  if (error) return /* @__PURE__ */ jsx57(RefusalNotice, { error, className });
@@ -15486,7 +15905,7 @@ function PublicRow({ row, fields }) {
15486
15905
  }
15487
15906
 
15488
15907
  // src/EmbedFrame.tsx
15489
- import { useCallback as useCallback38, useEffect as useEffect43, useState as useState55 } from "react";
15908
+ import { useCallback as useCallback38, useEffect as useEffect44, useState as useState55 } from "react";
15490
15909
  import { useRecordsClient as useRecordsClient41 } from "@ai-matrx/records/react";
15491
15910
  import { Button as Button49, Input as Input5, Skeleton as Skeleton34, Textarea as Textarea4, cn as cn52 } from "@ai-matrx/design-system";
15492
15911
  import { useTable as useTable22 } from "@ai-matrx/records/react";
@@ -15599,7 +16018,7 @@ function useEmbedHandshake(args) {
15599
16018
  const [loading, setLoading] = useState55(true);
15600
16019
  const origin = args.origin ?? (typeof location === "undefined" ? "" : location.origin);
15601
16020
  const { secret, requiredMode } = args;
15602
- useEffect43(() => {
16021
+ useEffect44(() => {
15603
16022
  let cancelled = false;
15604
16023
  setLoading(true);
15605
16024
  setError(null);
@@ -15654,7 +16073,7 @@ function recordsDataSource(client, fallbackSchema = "custom") {
15654
16073
  }
15655
16074
 
15656
16075
  // src/TablesHome.tsx
15657
- import { useCallback as useCallback39, useEffect as useEffect44, useState as useState56 } from "react";
16076
+ import { useCallback as useCallback39, useEffect as useEffect45, useState as useState56 } from "react";
15658
16077
  import { useRecordsClient as useRecordsClient42, useTables as useTables4 } from "@ai-matrx/records/react";
15659
16078
  import { BasicInput as BasicInput18, Button as Button50, Skeleton as Skeleton35, cn as cn53 } from "@ai-matrx/design-system";
15660
16079
 
@@ -15720,17 +16139,17 @@ async function declareTable(client, spec) {
15720
16139
  if (!written.ok) return undo(client, table.data, home.data, spec.name, written.error);
15721
16140
  return { ok: true, data: table.data, homeId: home.data };
15722
16141
  }
15723
- async function undo(client, tableId, homeId, name, refusal) {
16142
+ async function undo(client, tableId, homeId, name, refusal2) {
15724
16143
  const removed = await client.recordDelete({ record_id: tableId });
15725
16144
  if (removed.ok) {
15726
16145
  await client.recordDelete({ record_id: homeId });
15727
- return { ok: false, error: refusal };
16146
+ return { ok: false, error: refusal2 };
15728
16147
  }
15729
16148
  return {
15730
16149
  ok: false,
15731
16150
  error: {
15732
- ...refusal,
15733
- hint: `${refusal.hint ? `${refusal.hint} ` : ""}"${name}" was created before this happened and could not be removed again, so it is in your tables with no columns \u2014 open it and add the first column, or delete it from its own Settings.`
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.`
15734
16153
  }
15735
16154
  };
15736
16155
  }
@@ -15799,7 +16218,7 @@ function TablesHome({ onOpenTable, className }) {
15799
16218
  const [error, setError] = useState56(null);
15800
16219
  const [importInto, setImportInto] = useState56(null);
15801
16220
  const [boards, setBoards] = useState56(null);
15802
- useEffect44(() => {
16221
+ useEffect45(() => {
15803
16222
  let cancelled = false;
15804
16223
  void client.dashboards({}).then((result) => {
15805
16224
  if (cancelled) return;
@@ -15933,7 +16352,7 @@ function TablesHome({ onOpenTable, className }) {
15933
16352
  }
15934
16353
 
15935
16354
  // src/TablePage.tsx
15936
- import { useCallback as useCallback40, useEffect as useEffect45, useState as useState57 } from "react";
16355
+ import { useCallback as useCallback40, useEffect as useEffect46, useState as useState57 } from "react";
15937
16356
  import { useFields as useFields24, useRecordsClient as useRecordsClient43, useTable as useTable23 } from "@ai-matrx/records/react";
15938
16357
  import { Button as Button51, Separator as Separator12, Skeleton as Skeleton36, cn as cn54 } from "@ai-matrx/design-system";
15939
16358
  import { Fragment as Fragment28, jsx as jsx61, jsxs as jsxs56 } from "react/jsx-runtime";
@@ -15984,9 +16403,43 @@ function chooseSurface(current, pressed) {
15984
16403
  function surfaceChosen(current, asking) {
15985
16404
  return asking.main !== void 0 ? current.main === asking.main : current.rail === asking.rail;
15986
16405
  }
15987
- function openingRail(activeRecordId) {
15988
- 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;
15989
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.`;
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.";
15990
16443
  function openingView(activeView, activeDashboardId) {
15991
16444
  const named = pageViewFromParam(activeView);
15992
16445
  if (named === null) {
@@ -16014,6 +16467,8 @@ function TablePage({
16014
16467
  activeGroupField,
16015
16468
  cameFrom,
16016
16469
  filter,
16470
+ activeRail,
16471
+ activeItemId,
16017
16472
  className
16018
16473
  }) {
16019
16474
  const client = useRecordsClient43();
@@ -16023,8 +16478,13 @@ function TablePage({
16023
16478
  const askedInWords = filter ? filterInWords(filter, pageFields.data ?? []) : null;
16024
16479
  const organizationId = useRecordsClient43().config.organizationId;
16025
16480
  const [view, setView] = useState57(null);
16026
- const opening = openingRail(activeRecordId);
16481
+ const opening = openingRail(activeRecordId, activeRail);
16027
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;
16028
16488
  const [asking, setAsking] = useState57(null);
16029
16489
  const [layoutFromLink, setLayoutFromLink] = useState57(opened.layout);
16030
16490
  const [surface, setSurface] = useState57({
@@ -16044,12 +16504,18 @@ function TablePage({
16044
16504
  }
16045
16505
  };
16046
16506
  const show = (next) => press({ rail: next });
16047
- useEffect45(() => {
16507
+ useEffect46(() => {
16048
16508
  if (!activeRecordId) return;
16049
16509
  setOpenRecord(activeRecordId);
16050
16510
  setSurface((now) => ({ ...now, rail: "record" }));
16051
16511
  }, [activeRecordId]);
16052
- useEffect45(() => {
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(() => {
16053
16519
  const named = pageViewFromParam(activeView);
16054
16520
  if (named === null) return;
16055
16521
  const isLayout = named !== "dashboards" && named !== "archived";
@@ -16213,7 +16679,8 @@ function TablePage({
16213
16679
  organizationId,
16214
16680
  subjectId: tableId,
16215
16681
  name: table.data?.name,
16216
- may: rights.share
16682
+ may: rights.share,
16683
+ initiallyOpen: shareFromLink
16217
16684
  }
16218
16685
  ),
16219
16686
  /* @__PURE__ */ jsx61(ExportMenu, { tableId })
@@ -16236,6 +16703,8 @@ function TablePage({
16236
16703
  }
16237
16704
  )
16238
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,
16239
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,
16240
16709
  cameFrom ? /* @__PURE__ */ jsx61(
16241
16710
  "p",
@@ -16294,7 +16763,7 @@ function TablePage({
16294
16763
  }
16295
16764
  }
16296
16765
  ) : null,
16297
- rail === "forms" ? /* @__PURE__ */ jsx61(FormsPanel, { tableId }) : null,
16766
+ rail === "forms" ? /* @__PURE__ */ jsx61(FormsPanel, { tableId, activeFormId: itemFor("forms") }) : null,
16298
16767
  rail === "bookings" ? /* @__PURE__ */ jsx61(BookingSlots, { tableId }) : null,
16299
16768
  rail === "checklists" ? /* @__PURE__ */ jsx61(
16300
16769
  ChecklistsPanel,
@@ -16306,8 +16775,8 @@ function TablePage({
16306
16775
  }
16307
16776
  }
16308
16777
  ) : null,
16309
- rail === "notifications" ? /* @__PURE__ */ jsx61(SubscriptionsPanel, { tableId }) : null,
16310
- 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,
16311
16780
  rail === "import" ? /* @__PURE__ */ jsx61(ImportWizard, { tableId, onDone: () => setRail("none") }) : null,
16312
16781
  rail === "field" ? /* @__PURE__ */ jsx61(FieldEditor, { tableId, onSaved: () => setRail("none"), onCancel: () => setRail("none") }) : null,
16313
16782
  rail === "new-record" ? /* @__PURE__ */ jsx61(
@@ -16414,6 +16883,7 @@ export {
16414
16883
  FORMULA_OP_LABEL,
16415
16884
  FORMULA_OP_VALUES,
16416
16885
  FORM_FLOWS,
16886
+ FORM_NOT_HERE_LINE,
16417
16887
  FROZEN_COLUMN_WIDTH,
16418
16888
  FieldControl,
16419
16889
  FieldEditor,
@@ -16461,6 +16931,7 @@ export {
16461
16931
  PAGE_VIEW_LABEL,
16462
16932
  PARITY_LABEL,
16463
16933
  PARITY_MADE_OF,
16934
+ PORTAL_NOT_HERE_LINE,
16464
16935
  PROPOSED_CHANGE_ACT_LABEL,
16465
16936
  PROPOSED_CHANGE_ACT_VALUES,
16466
16937
  Peek,
@@ -16475,6 +16946,7 @@ export {
16475
16946
  PublicViewPage,
16476
16947
  ROLLUP_AGG_LABEL,
16477
16948
  ROLLUP_AGG_VALUES,
16949
+ RULE_NOT_HERE_LINE,
16478
16950
  RecordChat,
16479
16951
  RecordChip,
16480
16952
  RecordForm,
@@ -16489,6 +16961,7 @@ export {
16489
16961
  SAVED_VIEWS_UNAVAILABLE,
16490
16962
  SAY_WHAT_YOU_ARE_WAITING_FOR_AFTER_MS,
16491
16963
  SERIES_COLORS,
16964
+ SHARE_NOT_YOURS_LINE,
16492
16965
  SOMEBODY,
16493
16966
  STAGE_RULE_ON_FAIL_LABEL,
16494
16967
  STORE_ANSWERS_THESE,
@@ -16522,6 +16995,7 @@ export {
16522
16995
  actorWords,
16523
16996
  addFields,
16524
16997
  archivedRowLine,
16998
+ asRefusal,
16525
16999
  askableFields,
16526
17000
  blockFromSpec,
16527
17001
  bodyForReading,
@@ -16587,11 +17061,15 @@ export {
16587
17061
  previewLine,
16588
17062
  previewWords,
16589
17063
  publiclyAnswerable,
17064
+ railFromParam,
16590
17065
  recordName,
16591
17066
  recordNameIn,
16592
17067
  recordsDataSource,
17068
+ refusal,
16593
17069
  refusalForAPerson,
17070
+ refusalFromThrown,
16594
17071
  refusalLineForAPerson,
17072
+ refusalOr,
16595
17073
  renderValue,
16596
17074
  revokeConsequence,
16597
17075
  rowName,
@@ -16605,6 +17083,7 @@ export {
16605
17083
  tableName,
16606
17084
  tableRightsAt,
16607
17085
  tokenFor,
17086
+ unknownRailLine,
16608
17087
  unknownViewLine,
16609
17088
  useCanShare,
16610
17089
  useEmbedHandshake,