@ai-matrx/records-ui 0.106.0 → 0.107.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -6186,7 +6186,8 @@ function formatControl(args) {
6186
6186
  {
6187
6187
  id,
6188
6188
  type: def.editor,
6189
- ...ownDetails && def.editor !== "url" ? { autoComplete: def.editor === "email" ? "email" : "tel" } : {},
6189
+ inputMode: def.editor,
6190
+ ...ownDetails ? { autoComplete: def.editor } : {},
6190
6191
  value: text2,
6191
6192
  disabled,
6192
6193
  onChange: (e) => commit(e.target.value)
@@ -6677,7 +6678,7 @@ function isPersonsName(field2) {
6677
6678
  const words2 = `${field2.key} ${field2.label ?? ""}`.toLowerCase();
6678
6679
  return /(^|[\s_])(full[\s_]?)?name(\s|$)/.test(words2) && !/(therapist|doctor|physician|company|business|pet|room)/.test(words2);
6679
6680
  }
6680
- function FieldControl({ field: field2, value, onChange, disabled, id, autoOpen, onClose, onCommit, onCancel, addsChoices, initialSearch, asForm }) {
6681
+ function FieldControl({ field: field2, value, onChange, disabled, id, autoOpen, onClose, onCommit, onCancel, addsChoices, initialSearch, asForm, onChosen }) {
6681
6682
  const host = useRecordsUi();
6682
6683
  const kind = editorKindFor(field2);
6683
6684
  const inputId = id ?? `field-${field2.key}`;
@@ -6741,7 +6742,8 @@ function FieldControl({ field: field2, value, onChange, disabled, id, autoOpen,
6741
6742
  {
6742
6743
  id: inputId,
6743
6744
  type: kind === "email" ? "email" : kind === "phone" ? "tel" : "url",
6744
- ...ownDetails && kind !== "url" ? { autoComplete: kind === "email" ? "email" : "tel" } : {},
6745
+ inputMode: kind === "email" ? "email" : kind === "phone" ? "tel" : "url",
6746
+ ...ownDetails ? { autoComplete: kind === "email" ? "email" : kind === "phone" ? "tel" : "url" } : {},
6745
6747
  value: value === null || value === void 0 ? "" : String(value),
6746
6748
  disabled,
6747
6749
  onChange: (e) => onChange(e.target.value === "" ? null : e.target.value)
@@ -6751,7 +6753,7 @@ function FieldControl({ field: field2, value, onChange, disabled, id, autoOpen,
6751
6753
  return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(CheckboxControl, { field: field2, value, onChange, disabled, id: inputId });
6752
6754
  case "select":
6753
6755
  case "multi_select":
6754
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(OptionControl, { field: field2, value, onChange, disabled, id: inputId, autoOpen, onClose, onCancel, addsChoices, initialSearch, asForm });
6756
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(OptionControl, { field: field2, value, onChange, disabled, id: inputId, autoOpen, onClose, onCancel, addsChoices, initialSearch, asForm, onChosen });
6755
6757
  case "member":
6756
6758
  return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(PersonPicker, { field: field2, value, onChange, disabled, id: inputId, autoOpen, onClose, onCancel, initialSearch });
6757
6759
  case "entity_reference":
@@ -6933,7 +6935,7 @@ function inTheStoresOrder(options) {
6933
6935
  const made = (o) => String(o.created_at ?? "");
6934
6936
  return [...options].sort((a, b) => position(a) - position(b) || made(a).localeCompare(made(b)));
6935
6937
  }
6936
- function OptionControl({ field: field2, value, onChange, disabled, id, autoOpen, onClose, onCancel, addsChoices, initialSearch, asForm }) {
6938
+ function OptionControl({ field: field2, value, onChange, disabled, id, autoOpen, onClose, onCancel, addsChoices, initialSearch, asForm, onChosen }) {
6937
6939
  const client = (0, import_react38.useOptionalRecordsClient)();
6938
6940
  const [options, setOptions] = (0, import_react37.useState)(null);
6939
6941
  const [refusal4, setRefusal] = (0, import_react37.useState)(null);
@@ -6952,7 +6954,7 @@ function OptionControl({ field: field2, value, onChange, disabled, id, autoOpen,
6952
6954
  }, [client, field2.id, reread]);
6953
6955
  const handed = publicChoicesOf(field2);
6954
6956
  if (!client && handed.length > 0) {
6955
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ChoiceAnswer, { field: field2, choices: handed, value, onChange, disabled, id });
6957
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ChoiceAnswer, { field: field2, choices: handed, value, onChange, disabled, id, onChosen });
6956
6958
  }
6957
6959
  if (!client) {
6958
6960
  return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex flex-col gap-1", children: [
@@ -6982,6 +6984,7 @@ function OptionControl({ field: field2, value, onChange, disabled, id, autoOpen,
6982
6984
  onChange,
6983
6985
  disabled,
6984
6986
  id,
6987
+ onChosen,
6985
6988
  isChosen: (choice, v) => {
6986
6989
  const option = options.find((o) => (0, import_records15.optionKey)(o) === choice.key);
6987
6990
  return option ? (0, import_records15.isTheChosen)(option, v) : false;
@@ -7252,20 +7255,24 @@ function ChoiceAnswer({
7252
7255
  onChange,
7253
7256
  disabled,
7254
7257
  id,
7255
- isChosen
7258
+ isChosen,
7259
+ onChosen
7256
7260
  }) {
7257
7261
  const [search, setSearch] = (0, import_react37.useState)("");
7258
7262
  const held = field2.multi ? (0, import_records15.choiceValuesOf)(value) : value === null || value === void 0 || value === "" ? [] : [value];
7259
7263
  const on = (choice) => held.some((v) => isChosen ? isChosen(choice, v) : String(v) === choice.key || String(v) === choice.label);
7260
7264
  const press = (choice) => {
7261
7265
  if (!field2.multi) {
7262
- onChange(on(choice) ? null : choice.key);
7266
+ const picking = !on(choice);
7267
+ onChange(picking ? choice.key : null);
7268
+ if (picking) onChosen?.();
7263
7269
  return;
7264
7270
  }
7265
7271
  const keys = choices.filter((c) => c.key === choice.key ? !on(c) : on(c)).map((c) => c.key);
7266
7272
  onChange(keys.length === 0 ? null : keys);
7267
7273
  };
7268
7274
  const lettered = choices.length <= CHOICE_LETTERS.length;
7275
+ const tabStop = choices.find((c) => on(c))?.key ?? choices[0]?.key;
7269
7276
  const option = (choice) => {
7270
7277
  const chosen = on(choice);
7271
7278
  const letter = lettered ? CHOICE_LETTERS[choices.indexOf(choice)] : null;
@@ -7277,6 +7284,7 @@ function ChoiceAnswer({
7277
7284
  "aria-checked": chosen,
7278
7285
  "aria-label": choice.label,
7279
7286
  disabled,
7287
+ tabIndex: field2.multi || choice.key === tabStop ? 0 : -1,
7280
7288
  onClick: () => press(choice),
7281
7289
  "data-records-choice-answer": choice.key,
7282
7290
  className: (0, import_design_system20.cn)(
@@ -7303,8 +7311,25 @@ function ChoiceAnswer({
7303
7311
  );
7304
7312
  };
7305
7313
  const onKeyDown = (event) => {
7306
- if (!lettered || disabled || event.metaKey || event.ctrlKey || event.altKey) return;
7314
+ if (disabled || event.metaKey || event.ctrlKey || event.altKey) return;
7307
7315
  if (event.target.tagName === "INPUT") return;
7316
+ const arrow = { ArrowDown: 1, ArrowRight: 1, ArrowUp: -1, ArrowLeft: -1 }[event.key];
7317
+ if (arrow) {
7318
+ const buttons = Array.from(
7319
+ event.currentTarget.querySelectorAll("[data-records-choice-answer]")
7320
+ );
7321
+ const here = buttons.indexOf(event.target);
7322
+ if (buttons.length === 0) return;
7323
+ event.preventDefault();
7324
+ const next = buttons[(Math.max(here, 0) + arrow + buttons.length) % buttons.length];
7325
+ next.focus();
7326
+ if (!field2.multi) {
7327
+ const choice = choices.find((c) => c.key === next.dataset["recordsChoiceAnswer"]);
7328
+ if (choice && !on(choice)) onChange(choice.key);
7329
+ }
7330
+ return;
7331
+ }
7332
+ if (!lettered) return;
7308
7333
  const at = CHOICE_LETTERS.indexOf(event.key.toUpperCase());
7309
7334
  if (event.key.length !== 1 || at < 0 || at >= choices.length) return;
7310
7335
  event.preventDefault();
@@ -10340,7 +10365,7 @@ function ChoiceValue({ field: field2, value, onChange }) {
10340
10365
  {
10341
10366
  triggerSize: "compact",
10342
10367
  triggerLabel: `Choice for ${field2.label || field2.key}`,
10343
- triggerText: value || "Choose",
10368
+ triggerText: choices.find((c) => c.key === value || c.label === value)?.label ?? (value || "Choose"),
10344
10369
  open,
10345
10370
  onOpenChange: (next) => {
10346
10371
  setOpen(next);
@@ -10352,7 +10377,7 @@ function ChoiceValue({ field: field2, value, onChange }) {
10352
10377
  id: c.key,
10353
10378
  label: c.label,
10354
10379
  chip: choiceChipLookOf(field2, c.key, c.label).className,
10355
- picked: c.label === value,
10380
+ picked: c.label === value || c.key === value,
10356
10381
  attrs: { "data-pick-value": c.label }
10357
10382
  })),
10358
10383
  onPick: (key) => {
@@ -10461,6 +10486,15 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
10461
10486
  attrs: { "data-condition": "field" }
10462
10487
  }
10463
10488
  ),
10489
+ onField && !fields.some((f) => f.id === onField) ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
10490
+ "span",
10491
+ {
10492
+ className: "rounded bg-destructive px-1.5 py-0.5 text-[11px] font-medium text-destructive-foreground",
10493
+ title: "This condition names a question that is no longer on the form.",
10494
+ "data-records-condition-missing": "",
10495
+ children: "Missing question"
10496
+ }
10497
+ ) : null,
10464
10498
  onField ? /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(import_jsx_runtime37.Fragment, { children: [
10465
10499
  /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
10466
10500
  PickOne,
@@ -37920,9 +37954,41 @@ function formPresentation(spec) {
37920
37954
  welcome: welcomeDocument(spec.welcome ?? null),
37921
37955
  endings: (spec.endings ?? []).map(endingDocument),
37922
37956
  hidden_fields: spec.hiddenFields ?? [],
37923
- theme: themeDocument(spec.theme ?? null)
37957
+ theme: themeDocument(spec.theme ?? null),
37958
+ ...typeof spec.showOwnerHeader === "boolean" ? { show_owner_header: spec.showOwnerHeader } : {},
37959
+ ...typeof spec.autoAdvance === "boolean" ? { auto_advance: spec.autoAdvance } : {},
37960
+ ...spec.notify && (spec.notify.webhookUrl || spec.notify.webhookAutomationId) ? { notify: strip({ webhook_url: text(spec.notify.webhookUrl), webhook_automation_id: text(spec.notify.webhookAutomationId) }) } : {}
37924
37961
  };
37925
37962
  }
37963
+ function notifyFromDocument(raw) {
37964
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
37965
+ const d = raw;
37966
+ const n = { webhookUrl: text(d["webhook_url"]), webhookAutomationId: text(d["webhook_automation_id"]) };
37967
+ return n.webhookUrl || n.webhookAutomationId ? n : null;
37968
+ }
37969
+ var bool = (v) => typeof v === "boolean" ? v : null;
37970
+ function answerRefusal(kind, required, value) {
37971
+ const empty = value === void 0 || value === null || value === "" || Array.isArray(value) && value.length === 0;
37972
+ if (empty) return required ? "This field is required" : null;
37973
+ if (typeof value !== "string") return null;
37974
+ const v = value.trim();
37975
+ if (kind === "email" && !/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(v)) return "Enter a valid email";
37976
+ if (kind === "phone" && (v.replace(/\D/g, "").length < 7 || /[^\d\s()+.\-x]/i.test(v))) return "Enter a valid phone number";
37977
+ if (kind === "url" && !/^(https?:\/\/)?[^\s.\/]+(\.[^\s.\/]+)+(\/\S*)?$/i.test(v)) return "Enter a valid link";
37978
+ return null;
37979
+ }
37980
+ function pipeAnswers(text2, answers, questions, labelOf) {
37981
+ if (!text2.includes("{{")) return text2;
37982
+ return text2.replace(/\{\{\s*([^{}]+?)\s*\}\}/g, (_all, name) => {
37983
+ const wanted = name.trim().toLowerCase();
37984
+ const q = questions.find((x) => x.key.toLowerCase() === wanted) ?? questions.find((x) => (x.ask ?? "").trim().toLowerCase() === wanted);
37985
+ if (!q) return "";
37986
+ const value = answers[q.key];
37987
+ if (value === void 0 || value === null) return "";
37988
+ const one = (v) => labelOf?.(q.key, v) ?? String(v);
37989
+ return Array.isArray(value) ? value.map(one).join(", ") : one(value);
37990
+ });
37991
+ }
37926
37992
  var text = (v) => typeof v === "string" && v.trim() !== "" ? v.trim() : null;
37927
37993
  var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : null;
37928
37994
  var strip = (doc) => Object.fromEntries(Object.entries(doc).filter(([, v]) => v !== null && v !== void 0));
@@ -38097,6 +38163,9 @@ function formFromSummary(summary) {
38097
38163
  thankYou: thankYouFromDocument(presentation["thank_you"]),
38098
38164
  look: formLookFromDocument(presentation["look"]),
38099
38165
  submitLabel: presentation["submit_label"] ?? null,
38166
+ showOwnerHeader: bool(presentation["show_owner_header"]),
38167
+ autoAdvance: bool(presentation["auto_advance"]),
38168
+ notify: notifyFromDocument(presentation["notify"]),
38100
38169
  // A form is public when the store says it is OPEN. A screen never decides
38101
38170
  // this from a flag of its own: the link answers or it does not.
38102
38171
  isPublic: summary.state === "open",
@@ -38880,6 +38949,50 @@ function compactTick(value) {
38880
38949
  if (Math.abs(value) < 1e3 && !Number.isInteger(value)) return String(Math.round(value * 10) / 10);
38881
38950
  return (0, import_format10.formatCount)(value, { style: "compact" });
38882
38951
  }
38952
+ function wrapTickWords(label, perLine, maxLines = 3) {
38953
+ const words2 = label.split(/\s+/).filter(Boolean).map((w) => w.length > perLine ? `${w.slice(0, perLine - 1)}\u2026` : w);
38954
+ const lines = [];
38955
+ let line = "";
38956
+ for (const word of words2) {
38957
+ const next = line ? `${line} ${word}` : word;
38958
+ if (next.length <= perLine) line = next;
38959
+ else {
38960
+ lines.push(line);
38961
+ line = word;
38962
+ }
38963
+ }
38964
+ if (line) lines.push(line);
38965
+ if (lines.length <= maxLines) return lines;
38966
+ const kept = lines.slice(0, maxLines);
38967
+ const last = kept[maxLines - 1].replace(/…$/, "");
38968
+ kept[maxLines - 1] = `${last.length >= perLine ? last.slice(0, perLine - 1) : last}\u2026`;
38969
+ return kept;
38970
+ }
38971
+ function WrappedTick(props) {
38972
+ const { x = 0, y = 0, payload, across, perLine } = props;
38973
+ const lines = wrapTickWords(String(payload?.value ?? ""), perLine);
38974
+ const lineHeight = 11;
38975
+ return /* @__PURE__ */ (0, import_jsx_runtime120.jsx)(
38976
+ "text",
38977
+ {
38978
+ x,
38979
+ y,
38980
+ fontSize: 10,
38981
+ fill: "currentColor",
38982
+ textAnchor: across ? "end" : "middle",
38983
+ "data-records-chart-tick": "",
38984
+ children: lines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime120.jsx)(
38985
+ "tspan",
38986
+ {
38987
+ x: across ? x - 4 : x,
38988
+ dy: i === 0 ? across ? -((lines.length - 1) * lineHeight) / 2 + 3 : 10 : lineHeight,
38989
+ children: line
38990
+ },
38991
+ i
38992
+ ))
38993
+ }
38994
+ );
38995
+ }
38883
38996
  function Drawing({
38884
38997
  kind,
38885
38998
  points,
@@ -38985,9 +39098,9 @@ function Drawing({
38985
39098
  /* @__PURE__ */ (0, import_jsx_runtime120.jsx)(import_recharts.CartesianGrid, { vertical: horizontal, horizontal: !horizontal }),
38986
39099
  horizontal ? /* @__PURE__ */ (0, import_jsx_runtime120.jsxs)(import_jsx_runtime120.Fragment, { children: [
38987
39100
  /* @__PURE__ */ (0, import_jsx_runtime120.jsx)(import_recharts.XAxis, { type: "number", ...axis }),
38988
- /* @__PURE__ */ (0, import_jsx_runtime120.jsx)(import_recharts.YAxis, { type: "category", dataKey: "label", width: 88, ...axis })
39101
+ /* @__PURE__ */ (0, import_jsx_runtime120.jsx)(import_recharts.YAxis, { type: "category", dataKey: "label", width: 96, ...axis, tick: /* @__PURE__ */ (0, import_jsx_runtime120.jsx)(WrappedTick, { across: true, perLine: 15 }) })
38989
39102
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime120.jsxs)(import_jsx_runtime120.Fragment, { children: [
38990
- /* @__PURE__ */ (0, import_jsx_runtime120.jsx)(import_recharts.XAxis, { dataKey: "label", ...axis }),
39103
+ /* @__PURE__ */ (0, import_jsx_runtime120.jsx)(import_recharts.XAxis, { dataKey: "label", ...axis, interval: 0, height: 40, tick: /* @__PURE__ */ (0, import_jsx_runtime120.jsx)(WrappedTick, { perLine: Math.max(6, Math.floor(width / Math.max(points.length, 1) / 6)) }) }),
38991
39104
  /* @__PURE__ */ (0, import_jsx_runtime120.jsx)(import_recharts.YAxis, { width: 44, ...axis })
38992
39105
  ] }),
38993
39106
  tooltip,
@@ -40631,7 +40744,7 @@ function FormLookLogo({ view, size = "md" }) {
40631
40744
  }
40632
40745
  );
40633
40746
  }
40634
- function FormLookFrame({ look, title, children, contained = false, className }) {
40747
+ function FormLookFrame({ look, title, children, contained = false, showOwnerHeader = true, className }) {
40635
40748
  const view = formLookView(look, title);
40636
40749
  return /* @__PURE__ */ (0, import_jsx_runtime127.jsxs)(
40637
40750
  "div",
@@ -40648,10 +40761,10 @@ function FormLookFrame({ look, title, children, contained = false, className })
40648
40761
  }
40649
40762
  ) : null,
40650
40763
  view.coverUrl ? /* @__PURE__ */ (0, import_jsx_runtime127.jsx)("div", { "data-records-form-cover": "", className: "aspect-[3/1] w-full overflow-hidden rounded-xl border border-border bg-muted", children: /* @__PURE__ */ (0, import_jsx_runtime127.jsx)("img", { src: view.coverUrl, alt: "", className: "h-full w-full object-cover" }) }) : null,
40651
- /* @__PURE__ */ (0, import_jsx_runtime127.jsxs)("div", { className: "flex items-center gap-3", children: [
40764
+ showOwnerHeader ? /* @__PURE__ */ (0, import_jsx_runtime127.jsxs)("div", { className: "flex items-center gap-3", "data-records-form-owner": "", children: [
40652
40765
  /* @__PURE__ */ (0, import_jsx_runtime127.jsx)(FormLookLogo, { view }),
40653
40766
  /* @__PURE__ */ (0, import_jsx_runtime127.jsx)("p", { className: "min-w-0 truncate text-base font-semibold tracking-tight text-foreground", children: view.name })
40654
- ] }),
40767
+ ] }) : null,
40655
40768
  /* @__PURE__ */ (0, import_jsx_runtime127.jsx)("h1", { className: "text-2xl font-semibold tracking-tight text-foreground", children: title }),
40656
40769
  /* @__PURE__ */ (0, import_jsx_runtime127.jsx)("div", { className: "w-full", children }),
40657
40770
  view.footerLinks.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime127.jsx)("footer", { className: "mt-10 flex flex-wrap items-center justify-center gap-x-5 gap-y-2 pb-6 text-sm text-muted-foreground", children: view.footerLinks.map((link) => /* @__PURE__ */ (0, import_jsx_runtime127.jsx)(
@@ -41473,11 +41586,14 @@ function SubscriptionsPanel({ tableId, activeRuleId, className }) {
41473
41586
 
41474
41587
  // src/FormBuilder.tsx
41475
41588
  var import_clipboard9 = require("@ai-matrx/kit/clipboard");
41589
+ var import_design_system117 = require("@ai-matrx/design-system");
41590
+ var import_react232 = require("react");
41591
+ var import_core22 = require("@dnd-kit/core");
41592
+ var import_sortable = require("@dnd-kit/sortable");
41593
+ var import_utilities = require("@dnd-kit/utilities");
41594
+ var import_react233 = require("@ai-matrx/records/react");
41595
+ var import_records56 = require("@ai-matrx/records");
41476
41596
  var import_design_system118 = require("@ai-matrx/design-system");
41477
- var import_react234 = require("react");
41478
- var import_react235 = require("@ai-matrx/records/react");
41479
- var import_records57 = require("@ai-matrx/records");
41480
- var import_design_system119 = require("@ai-matrx/design-system");
41481
41597
 
41482
41598
  // src/FormFlowEditor.tsx
41483
41599
  var import_react229 = require("react");
@@ -41824,7 +41940,8 @@ function FormStage({
41824
41940
  className,
41825
41941
  connected = false,
41826
41942
  onStart,
41827
- onQuestionShown
41943
+ onQuestionShown,
41944
+ autoAdvance
41828
41945
  }) {
41829
41946
  const host = useRecordsUi();
41830
41947
  const [answers, setAnswers] = (0, import_react230.useState)(() => ({ ...initialAnswers ?? {} }));
@@ -41837,6 +41954,11 @@ function FormStage({
41837
41954
  const [ending, setEnding] = (0, import_react230.useState)(null);
41838
41955
  const [started, setStarted] = (0, import_react230.useState)(() => !form.welcome || resumed);
41839
41956
  const routes = formRoutes(form);
41957
+ const [routeKnown, setRouteKnown] = (0, import_react230.useState)(() => !routes || !whichAsked);
41958
+ const [stopped, setStopped] = (0, import_react230.useState)(null);
41959
+ const answersRef = (0, import_react230.useRef)(answers);
41960
+ answersRef.current = answers;
41961
+ const choose = autoAdvance ?? form.autoAdvance ?? true;
41840
41962
  const decoy = (0, import_react230.useRef)("");
41841
41963
  const stage = (0, import_react230.useRef)(null);
41842
41964
  const questions = (0, import_react230.useMemo)(() => {
@@ -41872,6 +41994,7 @@ function FormStage({
41872
41994
  };
41873
41995
  }
41874
41996
  if (cancelled) return;
41997
+ setRouteKnown(true);
41875
41998
  if (!told2.ok) {
41876
41999
  setHidden({});
41877
42000
  setError(told2.error ?? refusal("not_supported", told2.message, "Answer every question shown; none were skipped."));
@@ -41893,7 +42016,7 @@ function FormStage({
41893
42016
  ) : null
41894
42017
  );
41895
42018
  })();
41896
- }, 250);
42019
+ }, routeKnown ? 250 : 0);
41897
42020
  return () => {
41898
42021
  cancelled = true;
41899
42022
  clearTimeout(timer);
@@ -41928,11 +42051,24 @@ function FormStage({
41928
42051
  const v = answers[q.key];
41929
42052
  return v !== void 0 && v !== null && v !== "";
41930
42053
  }).length;
41931
- const missing = live.filter((q) => {
41932
- const v = answers[q.key];
41933
- return q.required && (v === void 0 || v === null || v === "");
41934
- });
42054
+ const refusalFor = (q, values = answersRef.current) => answerRefusal(q.field ? String(editorKindFor(q.field)) : "text", q.required, values[q.key]);
42055
+ const refuse = (q, say2) => {
42056
+ setStopped({ key: q.key, say: say2 });
42057
+ const i = live.findIndex((x) => x.key === q.key);
42058
+ if (oneAtATime && i >= 0 && i !== index) setAt(i);
42059
+ window.setTimeout(() => {
42060
+ const box = stage.current?.querySelector(`[data-records-form-question="${CSS.escape(q.key)}"]`);
42061
+ box?.querySelector("[role='radio'][aria-checked='true'], input:not([tabindex='-1']), textarea, select, [role='combobox'], [role='radio'], [role='checkbox']")?.focus();
42062
+ }, 0);
42063
+ };
41935
42064
  const submit = (0, import_react230.useCallback)(async () => {
42065
+ for (const q of live) {
42066
+ const say2 = refusalFor(q);
42067
+ if (say2) {
42068
+ refuse(q, say2);
42069
+ return;
42070
+ }
42071
+ }
41936
42072
  if (preview) {
41937
42073
  setDone("preview");
41938
42074
  return;
@@ -41964,20 +42100,28 @@ function FormStage({
41964
42100
  }, [answers, form, honeypotKey, live, onSubmit, onSubmitted, preview, questions]);
41965
42101
  async function advance() {
41966
42102
  if (!oneAtATime) return;
42103
+ if (current) {
42104
+ const say2 = refusalFor(current);
42105
+ if (say2) {
42106
+ refuse(current, say2);
42107
+ return;
42108
+ }
42109
+ }
42110
+ setStopped(null);
41967
42111
  if (!routes || !whichAsked || !current) {
41968
42112
  if (index < live.length - 1) setAt(index + 1);
41969
- else if (missing.length === 0) void submit();
42113
+ else void submit();
41970
42114
  return;
41971
42115
  }
41972
42116
  let told2;
41973
42117
  try {
41974
- told2 = await whichAsked(answers);
42118
+ told2 = await whichAsked(answersRef.current);
41975
42119
  } catch {
41976
42120
  told2 = { ok: false, message: "This form could not work out which question comes next, so the next one is shown." };
41977
42121
  }
41978
42122
  if (!told2.ok) {
41979
42123
  if (index < live.length - 1) setAt(index + 1);
41980
- else if (missing.length === 0) void submit();
42124
+ else void submit();
41981
42125
  return;
41982
42126
  }
41983
42127
  const next = {};
@@ -41990,13 +42134,18 @@ function FormStage({
41990
42134
  setAt(here + 1);
41991
42135
  return;
41992
42136
  }
41993
- const stillMissing = onRoute.filter((q) => {
41994
- const v = answers[q.key];
41995
- return q.required && (v === void 0 || v === null || v === "");
41996
- });
41997
- if (stillMissing.length === 0) void submit();
42137
+ const stillMissing = onRoute.find((q) => refusalFor(q) !== null);
42138
+ if (stillMissing) refuse(stillMissing, refusalFor(stillMissing));
42139
+ else void submit();
41998
42140
  }
42141
+ const advanceRef = (0, import_react230.useRef)(advance);
42142
+ advanceRef.current = advance;
42143
+ const chosen = (0, import_react230.useCallback)(() => {
42144
+ if (!oneAtATime || !choose) return;
42145
+ window.setTimeout(() => void advanceRef.current(), 450);
42146
+ }, [oneAtATime, choose]);
41999
42147
  function retreat() {
42148
+ setStopped(null);
42000
42149
  if (oneAtATime && index > 0) setAt(index - 1);
42001
42150
  }
42002
42151
  function onKeyDown(event) {
@@ -42012,12 +42161,11 @@ function FormStage({
42012
42161
  else advance();
42013
42162
  }
42014
42163
  (0, import_react230.useEffect)(() => {
42015
- const input = stage.current?.querySelector(
42016
- // A choice question has no box: its first option takes the caret, so A / B / C answer it.
42164
+ const input = stage.current?.querySelector("[role='radio'][aria-checked='true']") ?? stage.current?.querySelector(
42017
42165
  "input:not([tabindex='-1']), textarea, select, [role='combobox'], [role='radio'], [role='checkbox']"
42018
42166
  );
42019
42167
  input?.focus();
42020
- }, [index, done]);
42168
+ }, [index, done, started]);
42021
42169
  const startTold = (0, import_react230.useRef)(false);
42022
42170
  (0, import_react230.useEffect)(() => {
42023
42171
  if (startTold.current || !onStart) return;
@@ -42036,6 +42184,10 @@ function FormStage({
42036
42184
  }
42037
42185
  }, [current, live, oneAtATime, onQuestionShown, started, done]);
42038
42186
  const theme = (0, import_design_system116.formThemeClasses)(form.theme ?? null);
42187
+ const pipe = (text2) => pipeAnswers(text2, answers, questions, (key, v) => {
42188
+ const f = questions.find((q) => q.key === key)?.field;
42189
+ return f ? publicChoicesOf(f).find((c) => c.key === String(v))?.label ?? null : null;
42190
+ });
42039
42191
  if (done) {
42040
42192
  return /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(
42041
42193
  ThankYou,
@@ -42044,6 +42196,7 @@ function FormStage({
42044
42196
  done,
42045
42197
  ending: (form.endings ?? []).find((e) => e.id === ending) ?? null,
42046
42198
  buttonClass: theme.buttonClass,
42199
+ pipe,
42047
42200
  className: (0, import_design_system116.cn)("mx-auto max-w-xl py-10", centred && "text-center", className)
42048
42201
  }
42049
42202
  );
@@ -42055,14 +42208,14 @@ function FormStage({
42055
42208
  return /* @__PURE__ */ (0, import_jsx_runtime132.jsxs)(
42056
42209
  "section",
42057
42210
  {
42058
- className: (0, import_design_system116.cn)("mx-auto flex w-full max-w-xl flex-col gap-3 py-8", theme.surface, theme.background !== "plain" && "rounded-lg px-5", centred && "items-center text-center", className),
42211
+ className: (0, import_design_system116.cn)("mx-auto flex min-h-[min(70dvh,640px)] w-full max-w-2xl flex-col justify-center gap-4 py-8", theme.surface, theme.background !== "plain" && "rounded-lg px-5", centred && "items-center text-center", className),
42059
42212
  style: theme.surfaceStyle,
42060
42213
  "data-records-form-welcome": "",
42061
42214
  children: [
42062
42215
  w.pictureUrl ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("img", { src: w.pictureUrl, alt: "", className: "max-h-56 w-full rounded-md object-cover" }) : null,
42063
- w.title ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("h2", { className: "text-xl font-semibold", children: w.title }) : null,
42064
- w.body ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("p", { className: "text-sm text-muted-foreground", children: authored(host, w.body, "form-intro") }) : null,
42065
- /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(import_design_system116.Button, { size: "sm", className: theme.buttonClass, onClick: () => setStarted(true), "data-records-form-start": "", children: w.buttonLabel || "Start" }) })
42216
+ w.title ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("h2", { className: "text-2xl font-semibold sm:text-3xl", children: w.title }) : null,
42217
+ w.body ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("p", { className: "text-base text-muted-foreground", children: authored(host, w.body, "form-intro") }) : null,
42218
+ /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(import_design_system116.Button, { size: "lg", className: theme.buttonClass, onClick: () => setStarted(true), "data-records-form-start": "", children: w.buttonLabel || "Start" }) })
42066
42219
  ]
42067
42220
  }
42068
42221
  );
@@ -42071,7 +42224,15 @@ function FormStage({
42071
42224
  return /* @__PURE__ */ (0, import_jsx_runtime132.jsxs)(
42072
42225
  "section",
42073
42226
  {
42074
- className: (0, import_design_system116.cn)("mx-auto flex w-full max-w-xl flex-col gap-3 py-4", theme.surface, theme.background !== "plain" && "rounded-lg px-5", centred && "text-center", className),
42227
+ className: (0, import_design_system116.cn)(
42228
+ "mx-auto flex w-full max-w-2xl flex-col gap-4 py-4",
42229
+ // TYPEFORM'S SIZE (TYPEFORM-2): one question at a time fills the screen, centred in it.
42230
+ oneAtATime && "min-h-[min(70dvh,640px)]",
42231
+ theme.surface,
42232
+ theme.background !== "plain" && "rounded-lg px-5 sm:px-8",
42233
+ centred && "text-center",
42234
+ className
42235
+ ),
42075
42236
  style: theme.surfaceStyle,
42076
42237
  "data-records-form-font": theme.font,
42077
42238
  onKeyDown,
@@ -42081,20 +42242,41 @@ function FormStage({
42081
42242
  import_design_system116.Progress,
42082
42243
  {
42083
42244
  value: live.length === 0 ? 0 : (oneAtATime ? index + 1 : answered) / live.length * 100,
42084
- className: "h-1 flex-1"
42245
+ className: "h-1 flex-1",
42246
+ "aria-label": "Form progress",
42247
+ ...routeKnown ? { "aria-valuetext": oneAtATime ? `${index + 1} of ${live.length}` : `${answered} of ${live.length}` } : {}
42085
42248
  }
42086
42249
  ),
42087
- /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("span", { className: "tabular-nums", children: oneAtATime ? `${index + 1} of ${live.length}` : `${answered} of ${live.length}` })
42250
+ /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("span", { className: (0, import_design_system116.cn)("min-w-[3.5rem] text-right tabular-nums", !routeKnown && "invisible"), "data-records-form-count": "", children: oneAtATime ? `${index + 1} of ${live.length}` : `${answered} of ${live.length}` })
42088
42251
  ] }),
42089
- signedInPublic ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("p", { className: "rounded border border-dashed px-2 py-1 text-left text-xs text-muted-foreground", children: "You are answering this signed in, so the record will carry your name. A stranger answers the same form at its public link, where the answer arrives through the anonymous door instead." }) : null,
42090
- unanswerable ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("p", { className: "rounded border border-dashed px-2 py-1 text-left text-xs text-muted-foreground", children: "This form has questions that only appear in certain cases. Nothing here can work out which ones, so all of them are being shown rather than any being skipped." }) : null,
42091
- unresolved.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime132.jsxs)("p", { className: "rounded border border-dashed px-2 py-1 text-left text-xs text-destructive", children: [
42092
- unresolved.map((q) => q.key).join(", "),
42093
- " ",
42094
- unresolved.length === 1 ? "is a question" : "are questions",
42095
- " this form asks for and this table has no such Field. Add the Field, or take the question out \u2014 it is shown here rather than quietly dropped, because a dropped question is an answer nobody gave."
42096
- ] }) : null,
42097
- form.intro && index === 0 ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("p", { className: "text-sm text-muted-foreground", children: authored(host, form.intro, "form-intro") }) : null,
42252
+ signedInPublic ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(
42253
+ "p",
42254
+ {
42255
+ className: "w-fit rounded border border-dashed px-2 py-0.5 text-left text-xs text-muted-foreground",
42256
+ title: "The record will carry your name. Strangers answer at the public link.",
42257
+ children: "Signed in \xB7 saved as you"
42258
+ }
42259
+ ) : null,
42260
+ unanswerable ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(
42261
+ "p",
42262
+ {
42263
+ className: "w-fit rounded border border-dashed px-2 py-0.5 text-left text-xs text-muted-foreground",
42264
+ title: "Nothing here can work out which conditional questions apply, so all of them are shown.",
42265
+ children: "Showing every question"
42266
+ }
42267
+ ) : null,
42268
+ unresolved.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime132.jsxs)(
42269
+ "p",
42270
+ {
42271
+ className: "w-fit rounded border border-destructive/40 px-2 py-0.5 text-left text-xs text-destructive",
42272
+ title: "The table has no such field. Add the field, or take the question out.",
42273
+ children: [
42274
+ "Missing field: ",
42275
+ unresolved.map((q) => q.ask).join(", ")
42276
+ ]
42277
+ }
42278
+ ) : null,
42279
+ form.intro && index === 0 ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("p", { className: "text-base text-muted-foreground", children: authored(host, form.intro, "form-intro") }) : null,
42098
42280
  error ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(RefusalNotice, { error, className: "text-left" }) : null,
42099
42281
  refusal4 ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(
42100
42282
  RefusalNotice,
@@ -42116,14 +42298,20 @@ function FormStage({
42116
42298
  (oneAtATime ? current ? [current] : [] : live).map((q) => /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(
42117
42299
  Question,
42118
42300
  {
42119
- question: q,
42301
+ question: { ...q, ask: pipe(q.ask), help: q.help ? pipe(q.help) : null },
42120
42302
  value: answers[q.key],
42121
- onChange: (v) => setAnswers((prev) => ({ ...prev, [q.key]: v })),
42303
+ onChange: (v) => {
42304
+ if (stopped?.key === q.key) setStopped(null);
42305
+ setAnswers((prev) => ({ ...prev, [q.key]: v }));
42306
+ },
42307
+ onChosen: chosen,
42308
+ refused: stopped?.key === q.key ? stopped.say : null,
42309
+ large: oneAtATime,
42122
42310
  upload: host.upload
42123
42311
  },
42124
42312
  q.key
42125
42313
  )),
42126
- live.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("p", { className: "text-xs text-muted-foreground", children: "Every question in this form is hidden by its own condition right now, so there is nothing to answer yet." }) : null
42314
+ live.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("p", { className: "text-xs text-muted-foreground", title: "Every question is hidden by its own condition right now.", children: "Nothing to answer yet" }) : null
42127
42315
  ] }),
42128
42316
  honeypotKey ? /* @__PURE__ */ (0, import_jsx_runtime132.jsxs)("div", { "aria-hidden": true, className: "pointer-events-none absolute left-[-9999px] h-px w-px overflow-hidden", children: [
42129
42317
  /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("label", { htmlFor: `hp-${honeypotKey}`, children: "Leave this field empty" }),
@@ -42142,16 +42330,14 @@ function FormStage({
42142
42330
  }
42143
42331
  )
42144
42332
  ] }) : null,
42145
- /* @__PURE__ */ (0, import_jsx_runtime132.jsxs)("footer", { className: (0, import_design_system116.cn)("flex flex-wrap items-center gap-2", centred && "justify-center"), children: [
42146
- oneAtATime && index > 0 ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(import_design_system116.Button, { size: "sm", variant: "ghost", className: theme.buttonClass, onClick: retreat, children: "Back" }) : null,
42147
- oneAtATime && index < live.length - 1 ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(import_design_system116.Button, { size: "sm", className: theme.buttonClass, onClick: () => void advance(), children: "Next" }) : /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(import_design_system116.Button, { size: "sm", className: theme.buttonClass, disabled: writing || missing.length > 0, onClick: () => void submit(), children: writing ? "Sending\u2026" : form.submitLabel ?? "Submit" }),
42148
- missing.length > 0 && (!oneAtATime || index === live.length - 1) ? /* @__PURE__ */ (0, import_jsx_runtime132.jsxs)("span", { className: "text-xs text-muted-foreground", children: [
42149
- missing.map((q) => q.ask).join(", "),
42150
- " still ",
42151
- missing.length === 1 ? "needs" : "need",
42152
- " an answer."
42333
+ /* @__PURE__ */ (0, import_jsx_runtime132.jsxs)("footer", { className: (0, import_design_system116.cn)("flex flex-wrap items-center gap-3", centred && "justify-center"), children: [
42334
+ oneAtATime && index > 0 ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(import_design_system116.Button, { size: "lg", variant: "ghost", className: theme.buttonClass, onClick: retreat, children: "Back" }) : null,
42335
+ oneAtATime && index < live.length - 1 ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(import_design_system116.Button, { size: "lg", className: theme.buttonClass, onClick: () => void advance(), children: "Next" }) : /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(import_design_system116.Button, { size: "lg", className: theme.buttonClass, disabled: writing, onClick: () => void (oneAtATime ? advance() : submit()), children: writing ? "Sending\u2026" : form.submitLabel ?? "Submit" }),
42336
+ oneAtATime ? /* @__PURE__ */ (0, import_jsx_runtime132.jsxs)("span", { className: "hidden text-xs text-muted-foreground sm:inline", "data-records-form-enter-hint": "", children: [
42337
+ "press ",
42338
+ /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("strong", { className: "font-semibold text-foreground", children: "Enter \u21B5" })
42153
42339
  ] }) : null,
42154
- preview ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("span", { className: "text-xs text-muted-foreground", children: "Preview \u2014 nothing is written." }) : null
42340
+ preview ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("span", { className: "text-xs text-muted-foreground", title: "Nothing is written from a preview.", children: "Preview" }) : null
42155
42341
  ] })
42156
42342
  ]
42157
42343
  }
@@ -42163,12 +42349,15 @@ function ThankYou({
42163
42349
  done,
42164
42350
  className,
42165
42351
  ending,
42166
- buttonClass
42352
+ buttonClass,
42353
+ pipe = (t) => t
42167
42354
  }) {
42168
42355
  const host = useRecordsUi();
42169
42356
  const redirect = (ending ? ending.redirectUrl?.trim() : form.thankYou?.redirectUrl?.trim()) || null;
42170
- const title = ending ? ending.title : form.thankYou?.title;
42171
- const body = ending ? ending.body : form.thankYou?.body;
42357
+ const rawTitle = ending ? ending.title : form.thankYou?.title;
42358
+ const rawBody = ending ? ending.body : form.thankYou?.body;
42359
+ const title = rawTitle ? pipe(rawTitle) : rawTitle;
42360
+ const body = rawBody ? pipe(rawBody) : rawBody;
42172
42361
  const going = redirect !== null && done !== "preview";
42173
42362
  (0, import_react230.useEffect)(() => {
42174
42363
  if (!going || !redirect) return;
@@ -42185,8 +42374,8 @@ function ThankYou({
42185
42374
  }
42186
42375
  ),
42187
42376
  ending?.pictureUrl ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("img", { src: ending.pictureUrl, alt: "", className: "mb-3 max-h-48 w-full rounded-md object-cover" }) : null,
42188
- /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("h2", { className: "text-lg font-medium", "data-records-form-ending": ending?.id ?? "", children: title || "Thank you" }),
42189
- body ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("p", { className: "mt-1 text-sm text-muted-foreground", children: authored(host, body, "form-thank-you") }) : null,
42377
+ /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("h2", { className: "text-2xl font-semibold", "data-records-form-ending": ending?.id ?? "", children: title || "Thank you" }),
42378
+ body ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("p", { className: "mt-2 text-base text-muted-foreground", children: authored(host, body, "form-thank-you") }) : null,
42190
42379
  going ? /* @__PURE__ */ (0, import_jsx_runtime132.jsxs)("p", { className: "mt-4 flex flex-wrap items-center gap-3 text-sm", children: [
42191
42380
  /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(
42192
42381
  "a",
@@ -42199,11 +42388,11 @@ function ThankYou({
42199
42388
  ),
42200
42389
  /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("span", { className: "text-muted-foreground", children: "Taking you there\u2026" })
42201
42390
  ] }) : null,
42202
- /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("p", { className: "mt-3 text-xs text-muted-foreground", children: done === "preview" ? redirect ? `This was a preview, so nothing was written. The live form goes on to ${redirect}.` : "This was a preview, so nothing was written." : done === "sent" ? (
42391
+ /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("p", { className: "mt-3 text-xs text-muted-foreground", children: done === "preview" ? redirect ? `Preview \xB7 then ${redirect}` : "Preview \xB7 nothing saved" : done === "sent" ? (
42203
42392
  // SAID TO THE PERSON WHO ANSWERED (lane HANDOVER, 2026-09-29): "Saved to Form 1, with
42204
42393
  // this form stamped on it" was the owner's bookkeeping read out to a front-desk member
42205
42394
  // and to strangers. Where it landed is the owner's to see on the table.
42206
- "Your answers were received."
42395
+ "Answers received"
42207
42396
  ) : done })
42208
42397
  ] });
42209
42398
  }
@@ -42211,6 +42400,9 @@ function Question({
42211
42400
  question,
42212
42401
  value,
42213
42402
  onChange,
42403
+ onChosen,
42404
+ refused,
42405
+ large,
42214
42406
  upload
42215
42407
  }) {
42216
42408
  const host = useRecordsUi();
@@ -42218,297 +42410,62 @@ function Question({
42218
42410
  const field2 = question.field;
42219
42411
  if (!field2) return null;
42220
42412
  const id = `form-${field2.key}`;
42413
+ const sayId = `${id}-refused`;
42221
42414
  const isAttachment = editorKindFor(field2) === "attachment";
42222
- return /* @__PURE__ */ (0, import_jsx_runtime132.jsxs)("div", { className: "flex flex-col gap-1.5", children: [
42223
- /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(FieldLabel, { field: { ...field2, label: question.ask, required: question.required }, htmlFor: id }),
42224
- question.help ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("p", { className: "text-xs text-muted-foreground", children: authored(host, question.help, "form-help") }) : null,
42225
- isAttachment && !upload ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("p", { className: "rounded border border-dashed px-2 py-1 text-xs text-muted-foreground", children: NO_UPLOAD_REASON }) : isAttachment ? /* @__PURE__ */ (0, import_jsx_runtime132.jsxs)(import_jsx_runtime132.Fragment, { children: [
42226
- /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(
42227
- "input",
42228
- {
42229
- id,
42230
- type: "file",
42231
- className: "text-xs",
42232
- onChange: (e) => {
42233
- const file = e.target.files?.[0];
42234
- if (!file) return;
42235
- setUploadError(null);
42236
- void upload?.(file).then((result) => {
42237
- if (!result) return;
42238
- if (result.ok) onChange(result.fileId);
42239
- else
42240
- setUploadError(
42241
- refusal("invalid_argument", result.reason, "Pick another file, or try this one again.")
42242
- );
42243
- });
42244
- }
42245
- }
42415
+ return /* @__PURE__ */ (0, import_jsx_runtime132.jsxs)(
42416
+ "div",
42417
+ {
42418
+ className: (0, import_design_system116.cn)(
42419
+ "flex flex-col",
42420
+ large ? "gap-3 [&_input]:text-base [&_textarea]:text-base [&_input]:min-h-12" : "gap-1.5 [&_input]:text-base [&_textarea]:text-base"
42246
42421
  ),
42247
- uploadError ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(RefusalLine, { error: uploadError }) : null
42248
- ] }) : (
42249
- // A choice is laid out as pills or a searchable list in its own colours (`ChoiceAnswer`).
42250
- /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(FieldControl, { field: field2, value, onChange, id, asForm: true })
42251
- )
42252
- ] });
42253
- }
42254
-
42255
- // src/NotifyRuleEditor.tsx
42256
- var import_react232 = require("react");
42257
- var import_react233 = require("@ai-matrx/records/react");
42258
- var import_records56 = require("@ai-matrx/records");
42259
- var import_design_system117 = require("@ai-matrx/design-system");
42260
- var import_jsx_runtime133 = require("react/jsx-runtime");
42261
- var CADENCE_WORDS2 = SUBSCRIPTION_CADENCE_LABEL;
42262
- var CHANNEL_WORDS3 = SUBSCRIPTION_CHANNEL_LABEL;
42263
- function NotifyRuleEditor({ tableId, seed, chosenRuleId, onChoose, className }) {
42264
- const client = (0, import_react233.useRecordsClient)();
42265
- const host = useRecordsUi();
42266
- const table = (0, import_react233.useTable)(tableId);
42267
- const rights = useTableRights(table.data);
42268
- const [subscriptions, setSubscriptions] = (0, import_react232.useState)(null);
42269
- const [cadences, setCadences] = (0, import_react232.useState)([]);
42270
- const [views, setViews] = (0, import_react232.useState)(null);
42271
- const [error, setError] = (0, import_react232.useState)(null);
42272
- const [busy, setBusy] = (0, import_react232.useState)(false);
42273
- const [stopping, setStopping] = (0, import_react232.useState)(null);
42274
- const load = (0, import_react232.useCallback)(async () => {
42275
- const [held, offered] = await Promise.all([
42276
- // THE PERSON'S OWN DOOR, not the notifier's. It answers what is addressed
42277
- // to them plus — only where they hold admin on this Table — anyone's over
42278
- // it, and it is narrowed to Tables they can already open.
42279
- client.subscriptions({ table_id: tableId }),
42280
- // The cadences are ASKED of the store, never a list typed into a picker
42281
- // here that could drift from what the digest runner understands.
42282
- client.subscriptionCadences()
42283
- ]);
42284
- if (!held.ok) {
42285
- setError(held.error);
42286
- return;
42287
- }
42288
- setError(null);
42289
- setSubscriptions(held.data);
42290
- if (offered.ok) setCadences(offered.data);
42291
- if (host.savedViews) {
42292
- setViews(await host.savedViews());
42293
- return;
42294
- }
42295
- const listed = await client.views({ table_id: tableId });
42296
- if (!listed.ok) {
42297
- setError(listed.error);
42298
- return;
42299
- }
42300
- setViews(listed.data.map((v) => ({ id: v.view_id, name: v.name })));
42301
- }, [client, host, tableId]);
42302
- (0, import_react232.useEffect)(() => {
42303
- void load();
42304
- }, [load]);
42305
- const write = (0, import_react232.useCallback)(
42306
- async (spec) => {
42307
- const declared2 = await client.subscriptionDeclare({
42308
- table_id: tableId,
42309
- spec: {
42310
- name: spec.name,
42311
- saved_view_id: spec.savedViewId,
42312
- cadence: spec.cadence ?? "instant",
42313
- schedule: spec.schedule ?? null,
42314
- channel: spec.channel ?? "in_app",
42315
- quiet_hours: spec.quietHours ?? null,
42316
- // "Tell me" is the default, and the store refuses a subscription with
42317
- // nobody to tell rather than writing one that fires at nobody.
42318
- recipient_user_id: spec.recipientUserId ?? client.config.actor.user_id ?? null,
42319
- event_key: spec.eventKey ?? "records.changed"
42320
- }
42321
- });
42322
- if (!declared2.ok) {
42323
- setError(declared2.error);
42324
- return null;
42325
- }
42326
- return declared2.data;
42327
- },
42328
- [client, tableId]
42329
- );
42330
- (0, import_react232.useEffect)(() => {
42331
- if (!subscriptions || !seed || seed.length === 0) return;
42332
- const missing = seed.filter((s) => !subscriptions.some((held) => held.name === s.name));
42333
- if (missing.length === 0) return;
42334
- void (async () => {
42335
- for (const spec of missing) {
42336
- if (!await write(spec)) return;
42337
- }
42338
- await load();
42339
- })();
42340
- }, [subscriptions, seed, write, load]);
42341
- async function subscribe(viewId, viewName, cadence, channel, schedule, quiet2) {
42342
- setBusy(true);
42343
- const ok = await write({
42344
- name: `${viewName} \u2014 ${CADENCE_WORDS2[cadence] ?? cadence}`,
42345
- savedViewId: viewId,
42346
- cadence,
42347
- channel,
42348
- schedule: schedule.trim() === "" ? null : schedule.trim(),
42349
- quietHours: quiet2
42350
- });
42351
- setBusy(false);
42352
- if (!ok) return;
42353
- onChoose?.(ok);
42354
- await load();
42355
- }
42356
- async function makeEveryRecordView() {
42357
- setBusy(true);
42358
- const made = await client.viewDeclare({
42359
- table_id: tableId,
42360
- spec: { name: `Every ${table.data?.name ?? "record"}`, filters: null }
42361
- });
42362
- setBusy(false);
42363
- if (!made.ok) {
42364
- setError(made.error);
42365
- return;
42366
- }
42367
- await load();
42368
- }
42369
- async function unsubscribe(ruleId) {
42370
- setStopping(null);
42371
- setBusy(true);
42372
- const deleted = await client.recordDelete({ record_id: ruleId });
42373
- setBusy(false);
42374
- if (!deleted.ok) {
42375
- setError(deleted.error);
42376
- return;
42377
- }
42378
- await load();
42379
- }
42380
- if (error) {
42381
- return /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42382
- RefusalNotice,
42383
- {
42384
- error,
42385
- className,
42386
- actions: /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system117.Button, { size: "sm", variant: "ghost", onClick: () => void load(), children: "Try again" })
42387
- }
42388
- );
42389
- }
42390
- if (subscriptions === null) return /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system117.Skeleton, { className: (0, import_design_system117.cn)("h-40 w-full", className) });
42391
- return /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: (0, import_design_system117.cn)("flex flex-col gap-2", className), children: [
42392
- /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
42393
- /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "font-medium text-foreground", children: "Tell me when" }),
42394
- /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("span", { children: [
42395
- subscriptions.length,
42396
- " subscription",
42397
- subscriptions.length === 1 ? "" : "s"
42398
- ] })
42399
- ] }),
42400
- subscriptions.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("p", { className: "text-xs text-muted-foreground", children: "Nothing is telling anyone about this yet. A subscription is a Rule over a saved view, so what you are told about is exactly what that view holds." }) : /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("ul", { className: "flex flex-col gap-1", children: subscriptions.map((held) => /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("li", { className: "flex items-center gap-2 rounded border px-2 py-1.5 text-xs", children: [
42401
- /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "truncate font-medium", children: held.name }),
42402
- /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "text-muted-foreground", children: CADENCE_WORDS2[held.cadence] ?? held.cadence }),
42403
- /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "text-muted-foreground", children: CHANNEL_WORDS3[held.channel] ?? held.channel }),
42404
- held.schedule ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "text-muted-foreground", children: scheduleWords(held.schedule, held.quiet_hours?.tz) }) : null,
42405
- held.next_digest_at ? /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("span", { className: "text-muted-foreground", children: [
42406
- "next ",
42407
- new Date(held.next_digest_at).toLocaleString()
42408
- ] }) : null,
42409
- (0, import_records56.hasQuietHours)(held.quiet_hours) ? /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("span", { className: "text-muted-foreground", children: [
42410
- "quiet ",
42411
- held.quiet_hours.start,
42412
- "\u2013",
42413
- held.quiet_hours.end
42414
- ] }) : null,
42415
- held.recipient_user_id === null ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "text-destructive", children: "This one has nobody to tell, so it fires at nobody." }) : null,
42416
- held.saved_view_id === null ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "text-destructive", children: "This one names no view, so the store never admits a record to it." }) : null,
42417
- held.muted ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "text-muted-foreground", children: "Switched off \u2014 it tells nobody until somebody switches it back on." }) : null,
42418
- onChoose ? held.rule_id === chosenRuleId ? /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("span", { className: "ml-auto flex items-center gap-1", children: [
42419
- /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "font-medium text-foreground", children: "Used for this form" }),
42420
- /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system117.Button, { size: "sm", variant: "ghost", disabled: busy, onClick: () => onChoose(null), children: "Stop using" })
42421
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42422
- import_design_system117.Button,
42423
- {
42424
- size: "sm",
42425
- variant: "outline",
42426
- className: "ml-auto",
42427
- disabled: busy,
42428
- onClick: () => onChoose(held.rule_id),
42429
- children: "Use for this form"
42430
- }
42431
- ) : null,
42432
- rights.write && !(onChoose && held.rule_id === chosenRuleId) && stopping === held.rule_id ? /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("span", { className: (0, import_design_system117.cn)("flex flex-wrap items-center gap-2", onChoose ? void 0 : "ml-auto"), children: [
42433
- /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "text-xs text-destructive", children: archiveConfirmSentence("this subscription") }),
42434
- /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system117.Button, { size: "sm", variant: "destructive", disabled: busy, onClick: () => void unsubscribe(held.rule_id), children: "Stop telling me" }),
42435
- /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system117.Button, { size: "sm", variant: "ghost", onClick: () => setStopping(null), children: "Keep it" })
42436
- ] }) : rights.write && !(onChoose && held.rule_id === chosenRuleId) ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42437
- import_design_system117.Button,
42438
- {
42439
- size: "sm",
42440
- variant: "ghost",
42441
- className: onChoose ? void 0 : "ml-auto",
42442
- disabled: busy,
42443
- onClick: () => setStopping(held.rule_id),
42444
- children: "Stop telling me"
42445
- }
42446
- ) : null
42447
- ] }, held.rule_id)) }),
42448
- onChoose && chosenRuleId && !subscriptions.some((held) => held.rule_id === chosenRuleId) ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("p", { className: "text-xs text-destructive", children: "This form points at a subscription that is not in this list any more, so nobody is told when it is answered. Choose one below, or make one." }) : null,
42449
- views === null ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("p", { className: "rounded border border-dashed px-2 py-1 text-xs text-muted-foreground", children: NO_SAVED_VIEWS_REASON }) : views.length === 0 ? onChoose && rights.write ? /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "flex flex-wrap items-center gap-2 text-xs text-muted-foreground", children: [
42450
- /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { children: "A subscription watches a saved view, and this table has none yet." }),
42451
- /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system117.Button, { size: "sm", variant: "outline", disabled: busy, onClick: () => void makeEveryRecordView(), children: "Watch every record" })
42452
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("p", { className: "text-xs text-muted-foreground", children: "This organization has no saved views yet, so there is nothing to subscribe to. Save a view first." }) : rights.write ? /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)(
42453
- "form",
42454
- {
42455
- className: "flex flex-wrap items-center gap-1",
42456
- onSubmit: (event) => {
42457
- event.preventDefault();
42458
- const form = event.currentTarget;
42459
- const view = form.elements.namedItem("view").value;
42460
- const cadence = form.elements.namedItem("cadence").value;
42461
- const channel = form.elements.namedItem("channel").value;
42462
- const schedule = form.elements.namedItem("schedule").value;
42463
- const start = form.elements.namedItem("quiet_start").value;
42464
- const end = form.elements.namedItem("quiet_end").value;
42465
- const name = views.find((v) => v.id === view)?.name ?? "View";
42466
- const quiet2 = start && end ? { start, end, tz: Intl.DateTimeFormat().resolvedOptions().timeZone } : null;
42467
- if (view) void subscribe(view, name, cadence, channel, schedule, quiet2);
42468
- },
42469
- children: [
42470
- /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("select", { name: "view", "aria-label": "View to be told about", className: "h-7 rounded border bg-background px-1 text-xs", children: views.map((view) => /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("option", { value: view.id, children: view.name }, view.id)) }),
42471
- /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("select", { name: "cadence", "aria-label": "How often", className: "h-7 rounded border bg-background px-1 text-xs", children: (cadences.length > 0 ? cadences : ["instant"]).map((cadence) => /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("option", { value: cadence, children: CADENCE_WORDS2[cadence] ?? cadence }, cadence)) }),
42472
- /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("select", { name: "channel", "aria-label": "Where it arrives", className: "h-7 rounded border bg-background px-1 text-xs", children: ["in_app", "email", "sms"].map((channel) => /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("option", { value: channel, children: CHANNEL_WORDS3[channel] }, channel)) }),
42473
- /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42474
- "input",
42475
- {
42476
- name: "schedule",
42477
- "aria-label": "When the summary goes out",
42478
- placeholder: "monday 08:00",
42479
- className: "h-7 w-28 rounded border bg-background px-1 text-xs"
42480
- }
42481
- ),
42482
- /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "text-xs text-muted-foreground", children: "not between" }),
42483
- /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42484
- "input",
42485
- {
42486
- name: "quiet_start",
42487
- type: "time",
42488
- "aria-label": "Quiet hours start",
42489
- className: "h-7 rounded border bg-background px-1 text-xs"
42490
- }
42491
- ),
42492
- /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "text-xs text-muted-foreground", children: "and" }),
42493
- /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42422
+ "data-records-form-question": field2.key,
42423
+ "aria-describedby": refused ? sayId : void 0,
42424
+ children: [
42425
+ /* @__PURE__ */ (0, import_jsx_runtime132.jsxs)("div", { className: "flex flex-wrap items-baseline gap-x-2", children: [
42426
+ /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(import_design_system116.Label, { htmlFor: id, className: (0, import_design_system116.cn)("font-medium leading-snug text-foreground", large ? "text-xl sm:text-2xl" : "text-base"), children: question.ask }),
42427
+ question.required ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("span", { className: "text-sm text-foreground/80", children: "required" }) : null,
42428
+ field2.unit ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("span", { className: "text-sm text-foreground/70", children: field2.unit }) : null
42429
+ ] }),
42430
+ question.help ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("p", { className: (0, import_design_system116.cn)("text-muted-foreground", large ? "text-base" : "text-sm"), children: authored(host, question.help, "form-help") }) : null,
42431
+ isAttachment && !upload ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("p", { className: "w-fit rounded border border-dashed px-2 py-0.5 text-xs text-muted-foreground", title: NO_UPLOAD_REASON, children: "File upload unavailable here" }) : isAttachment ? /* @__PURE__ */ (0, import_jsx_runtime132.jsxs)(import_jsx_runtime132.Fragment, { children: [
42432
+ /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(
42494
42433
  "input",
42495
42434
  {
42496
- name: "quiet_end",
42497
- type: "time",
42498
- "aria-label": "Quiet hours end",
42499
- className: "h-7 rounded border bg-background px-1 text-xs"
42435
+ id,
42436
+ type: "file",
42437
+ className: "text-sm",
42438
+ onChange: (e) => {
42439
+ const file = e.target.files?.[0];
42440
+ if (!file) return;
42441
+ setUploadError(null);
42442
+ void upload?.(file).then((result) => {
42443
+ if (!result) return;
42444
+ if (result.ok) onChange(result.fileId);
42445
+ else
42446
+ setUploadError(
42447
+ refusal("invalid_argument", result.reason, "Pick another file, or try this one again.")
42448
+ );
42449
+ });
42450
+ }
42500
42451
  }
42501
42452
  ),
42502
- /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system117.Button, { size: "sm", type: "submit", disabled: busy, children: "Tell me" }),
42503
- /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("p", { className: "w-full text-xs text-muted-foreground", children: "A send inside your quiet hours is held until they end \u2014 it is never dropped. Leave the schedule blank for the store's own default of 08:00." })
42504
- ]
42505
- }
42506
- ) : /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("p", { className: "text-xs text-muted-foreground", children: rights.why("write") })
42507
- ] });
42453
+ uploadError ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(RefusalLine, { error: uploadError }) : null
42454
+ ] }) : (
42455
+ // A choice is laid out as lettered rows in the form's colour (`ChoiceAnswer`).
42456
+ /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(FieldControl, { field: field2, value, onChange, id, asForm: true, onChosen })
42457
+ ),
42458
+ /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("p", { id: sayId, role: "alert", "aria-live": "assertive", className: "min-h-0 empty:hidden", "data-records-form-refused": "", children: refused ? /* @__PURE__ */ (0, import_jsx_runtime132.jsxs)("span", { className: "inline-flex items-center gap-1.5 rounded bg-destructive px-2 py-1 text-sm font-medium text-destructive-foreground", children: [
42459
+ /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("span", { "aria-hidden": "true", children: "!" }),
42460
+ refused
42461
+ ] }) : null })
42462
+ ]
42463
+ }
42464
+ );
42508
42465
  }
42509
42466
 
42510
42467
  // src/FormBuilder.tsx
42511
- var import_jsx_runtime134 = require("react/jsx-runtime");
42468
+ var import_jsx_runtime133 = require("react/jsx-runtime");
42512
42469
  function freshFormName(base, taken) {
42513
42470
  const names = new Set(taken.map((n) => n.trim().toLowerCase()));
42514
42471
  if (!names.has(base.toLowerCase())) return base;
@@ -42525,7 +42482,21 @@ function isLookRefusal(error) {
42525
42482
  return Boolean(error && LOOK_REFUSAL.test(refusalSentence(error)));
42526
42483
  }
42527
42484
  var KEPT_DRAFT_PREFIX = "records-ui:form-draft:";
42528
- var SPEC_KEYS = ["name", "intro", "questions", "flow", "theme", "look", "thankYou", "submitLabel"];
42485
+ var SPEC_KEYS = [
42486
+ "name",
42487
+ "intro",
42488
+ "questions",
42489
+ "flow",
42490
+ "theme",
42491
+ "look",
42492
+ "thankYou",
42493
+ "submitLabel",
42494
+ "welcome",
42495
+ "endings",
42496
+ "hiddenFields",
42497
+ "showOwnerHeader",
42498
+ "autoAdvance"
42499
+ ];
42529
42500
  function readKeptDraft(formId) {
42530
42501
  try {
42531
42502
  const raw = window.localStorage.getItem(KEPT_DRAFT_PREFIX + formId);
@@ -42556,31 +42527,31 @@ function withKept(stored, spec) {
42556
42527
  return out;
42557
42528
  }
42558
42529
  function FormBuilder({ tableId, seed, activeFormId, onActiveForm, startWithOne, createOnMount, className }) {
42559
- const client = (0, import_react235.useRecordsClient)();
42530
+ const client = (0, import_react233.useRecordsClient)();
42560
42531
  const host = useRecordsUi();
42561
42532
  const claimSeed = useSeedGuard();
42562
- const table = (0, import_react235.useTable)(tableId);
42533
+ const table = (0, import_react233.useTable)(tableId);
42563
42534
  const rights = useTableRights(table.data);
42564
- const fields = (0, import_react235.useFields)(tableId);
42565
- const [forms, setForms] = (0, import_react234.useState)(null);
42566
- const [error, setError] = (0, import_react234.useState)(null);
42567
- const [refused, setRefused] = (0, import_react234.useState)(null);
42568
- const [activeId, setActiveId] = (0, import_react234.useState)(activeFormId ?? null);
42569
- const [draft, setDraft] = (0, import_react234.useState)(null);
42570
- const [saving, setSaving] = (0, import_react234.useState)(false);
42571
- const [saved, setSaved] = (0, import_react234.useState)(null);
42572
- const [publishing, setPublishing] = (0, import_react234.useState)(false);
42573
- const [copied, setCopied] = (0, import_react234.useState)(false);
42574
- const [shownUrl, setShownUrl] = (0, import_react234.useState)(null);
42575
- const [lookOptions, setLookOptions] = (0, import_react234.useState)(null);
42576
- const [lookUnread, setLookUnread] = (0, import_react234.useState)(null);
42577
- const [restoredAt, setRestoredAt] = (0, import_react234.useState)(null);
42578
- const [showResults, setShowResults] = (0, import_react234.useState)(false);
42579
- const [adding, setAdding] = (0, import_react234.useState)(false);
42580
- const keysBeforeAdd = (0, import_react234.useRef)(null);
42581
- const [makeOnMount, setMakeOnMount] = (0, import_react234.useState)("waiting");
42535
+ const fields = (0, import_react233.useFields)(tableId);
42536
+ const [forms, setForms] = (0, import_react232.useState)(null);
42537
+ const [error, setError] = (0, import_react232.useState)(null);
42538
+ const [refused, setRefused] = (0, import_react232.useState)(null);
42539
+ const [activeId, setActiveId] = (0, import_react232.useState)(activeFormId ?? null);
42540
+ const [draft, setDraft] = (0, import_react232.useState)(null);
42541
+ const [saving, setSaving] = (0, import_react232.useState)(false);
42542
+ const [saved, setSaved] = (0, import_react232.useState)(null);
42543
+ const [publishing, setPublishing] = (0, import_react232.useState)(false);
42544
+ const [copied, setCopied] = (0, import_react232.useState)(false);
42545
+ const [shownUrl, setShownUrl] = (0, import_react232.useState)(null);
42546
+ const [lookOptions, setLookOptions] = (0, import_react232.useState)(null);
42547
+ const [lookUnread, setLookUnread] = (0, import_react232.useState)(null);
42548
+ const [restoredAt, setRestoredAt] = (0, import_react232.useState)(null);
42549
+ const [showResults, setShowResults] = (0, import_react232.useState)(false);
42550
+ const [adding, setAdding] = (0, import_react232.useState)(false);
42551
+ const keysBeforeAdd = (0, import_react232.useRef)(null);
42552
+ const [makeOnMount, setMakeOnMount] = (0, import_react232.useState)("waiting");
42582
42553
  const publicOrigin = host.publicOrigin ?? (typeof window === "undefined" ? "" : window.location.origin);
42583
- const load = (0, import_react234.useCallback)(async () => {
42554
+ const load = (0, import_react232.useCallback)(async () => {
42584
42555
  const answered = await client.forms({ table_id: tableId });
42585
42556
  if (!answered.ok) {
42586
42557
  setError(answered.error);
@@ -42607,10 +42578,10 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, startWithOne,
42607
42578
  setError(null);
42608
42579
  setForms(mine);
42609
42580
  }, [client, tableId, seed, claimSeed]);
42610
- (0, import_react234.useEffect)(() => {
42581
+ (0, import_react232.useEffect)(() => {
42611
42582
  void load();
42612
42583
  }, [load]);
42613
- (0, import_react234.useEffect)(() => {
42584
+ (0, import_react232.useEffect)(() => {
42614
42585
  if (!forms || forms.length === 0) return;
42615
42586
  if (createOnMount && makeOnMount !== "made") return;
42616
42587
  const chosen = forms.find((f) => f.id === (activeFormId ?? activeId)) ?? forms[0];
@@ -42627,14 +42598,14 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, startWithOne,
42627
42598
  }
42628
42599
  onActiveForm?.(chosen);
42629
42600
  }, [forms, activeFormId, makeOnMount]);
42630
- const byKey = (0, import_react234.useMemo)(() => new Map((fields.data ?? []).map((f) => [f.key, f])), [fields.data]);
42631
- const conditionFields = (0, import_react234.useMemo)(
42601
+ const byKey = (0, import_react232.useMemo)(() => new Map((fields.data ?? []).map((f) => [f.key, f])), [fields.data]);
42602
+ const conditionFields = (0, import_react232.useMemo)(
42632
42603
  () => (fields.data ?? []).map((f) => ({ id: String(f.id), key: f.key, label: f.label || f.key, field: f })),
42633
42604
  [fields.data]
42634
42605
  );
42635
- const stored = (0, import_react234.useMemo)(() => draft ? (forms ?? []).find((f) => f.id === draft.id) ?? null : null, [draft, forms]);
42606
+ const stored = (0, import_react232.useMemo)(() => draft ? (forms ?? []).find((f) => f.id === draft.id) ?? null : null, [draft, forms]);
42636
42607
  const dirty = Boolean(draft && stored && !sameForm(draft, stored));
42637
- (0, import_react234.useEffect)(() => {
42608
+ (0, import_react232.useEffect)(() => {
42638
42609
  if (!draft || !stored) return;
42639
42610
  const timer = window.setTimeout(() => {
42640
42611
  if (sameForm(draft, stored)) dropKeptDraft(draft.id);
@@ -42643,7 +42614,7 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, startWithOne,
42643
42614
  return () => window.clearTimeout(timer);
42644
42615
  }, [draft, stored]);
42645
42616
  const lookKey = draft ? JSON.stringify(formLookDocument(draft.look ?? null)) : null;
42646
- (0, import_react234.useEffect)(() => {
42617
+ (0, import_react232.useEffect)(() => {
42647
42618
  if (lookKey === null) return;
42648
42619
  let cancelled = false;
42649
42620
  const timer = window.setTimeout(() => {
@@ -42660,7 +42631,7 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, startWithOne,
42660
42631
  window.clearTimeout(timer);
42661
42632
  };
42662
42633
  }, [client, lookKey]);
42663
- (0, import_react234.useEffect)(() => {
42634
+ (0, import_react232.useEffect)(() => {
42664
42635
  const before = keysBeforeAdd.current;
42665
42636
  if (!before || !fields.data) return;
42666
42637
  const made = askableFields(fields.data).filter((f) => !before.has(f.key));
@@ -42699,7 +42670,7 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, startWithOne,
42699
42670
  }
42700
42671
  dropKeptDraft(draft.id);
42701
42672
  setRestoredAt(null);
42702
- setSaved("Saved.");
42673
+ setSaved("Saved");
42703
42674
  await load();
42704
42675
  return true;
42705
42676
  }
@@ -42738,7 +42709,22 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, startWithOne,
42738
42709
  setRefused(written.error);
42739
42710
  return;
42740
42711
  }
42741
- setSaved(ruleId ? "Saved \u2014 you will be told when somebody answers." : "Saved \u2014 nobody is told now.");
42712
+ setSaved("Saved");
42713
+ await load();
42714
+ }
42715
+ async function chooseWebhook(notify) {
42716
+ if (!draft) return;
42717
+ const next = { ...draft, notify };
42718
+ setDraft(next);
42719
+ setSaving(true);
42720
+ setRefused(null);
42721
+ const written = await client.formDeclare(formDeclareArgs(next));
42722
+ setSaving(false);
42723
+ if (!written.ok) {
42724
+ setRefused(written.error);
42725
+ return;
42726
+ }
42727
+ setSaved("Saved");
42742
42728
  await load();
42743
42729
  }
42744
42730
  async function create(name) {
@@ -42755,15 +42741,15 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, startWithOne,
42755
42741
  await load();
42756
42742
  return true;
42757
42743
  }
42758
- const [started, setStarted] = (0, import_react234.useState)(false);
42759
- (0, import_react234.useEffect)(() => {
42744
+ const [started, setStarted] = (0, import_react232.useState)(false);
42745
+ (0, import_react232.useEffect)(() => {
42760
42746
  if (!startWithOne || started || forms === null || forms.length > 0) return;
42761
42747
  if (askableFields(fields.data ?? []).length === 0) return;
42762
42748
  if (!table.data && !table.error) return;
42763
42749
  setStarted(true);
42764
42750
  void create(table.data?.name?.trim() ? `${table.data.name.trim()} form` : "Form 1");
42765
42751
  }, [startWithOne, started, forms, fields.data, create, table.data, table.error]);
42766
- (0, import_react234.useEffect)(() => {
42752
+ (0, import_react232.useEffect)(() => {
42767
42753
  if (!createOnMount || makeOnMount !== "waiting" || forms === null) return;
42768
42754
  if (!table.data && !table.error) return;
42769
42755
  if (askableFields(fields.data ?? []).length === 0) {
@@ -42776,396 +42762,320 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, startWithOne,
42776
42762
  (made) => setMakeOnMount(made ? "made" : "not-made")
42777
42763
  );
42778
42764
  }, [createOnMount, makeOnMount, forms, fields.data, create, table.data, table.error]);
42779
- if (fields.error) return /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(RefusalNotice, { error: fields.error, className });
42765
+ if (fields.error) return /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(RefusalNotice, { error: fields.error, className });
42780
42766
  if (error) {
42781
- return /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
42767
+ return /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42782
42768
  RefusalNotice,
42783
42769
  {
42784
42770
  error,
42785
42771
  className,
42786
- actions: /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(import_design_system119.Button, { size: "sm", variant: "ghost", onClick: () => void load(), children: "Try again" })
42772
+ actions: /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.Button, { size: "sm", variant: "ghost", onClick: () => void load(), children: "Try again" })
42787
42773
  }
42788
42774
  );
42789
42775
  }
42790
- if (fields.loading || forms === null) return /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(import_design_system119.Skeleton, { className: (0, import_design_system119.cn)("h-64 w-full", className) });
42776
+ if (fields.loading || forms === null) return /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.Skeleton, { className: (0, import_design_system118.cn)("h-64 w-full", className) });
42791
42777
  if (createOnMount && (makeOnMount === "waiting" || makeOnMount === "making") && !refused) {
42792
- return /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(import_design_system119.Skeleton, { className: (0, import_design_system119.cn)("h-64 w-full", className) });
42778
+ return /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.Skeleton, { className: (0, import_design_system118.cn)("h-64 w-full", className) });
42793
42779
  }
42794
42780
  if (!rights.admin) {
42795
- return /* @__PURE__ */ (0, import_jsx_runtime134.jsx)("p", { className: (0, import_design_system119.cn)("text-xs text-muted-foreground", className), children: rights.why("structure") });
42781
+ return /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("p", { className: (0, import_design_system118.cn)("text-xs text-muted-foreground", className), children: rights.why("structure") });
42796
42782
  }
42797
- return /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("div", { className: (0, import_design_system119.cn)("@container flex min-h-0 flex-col gap-3 @3xl:flex-row", className), children: [
42798
- /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col gap-2 overflow-auto", children: [
42799
- /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("div", { className: "flex items-center gap-1 overflow-x-auto", role: "group", "aria-label": "Forms", children: [
42800
- forms.map((form) => /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
42801
- import_design_system119.Button,
42802
- {
42803
- size: "sm",
42804
- variant: form.id === activeId ? "secondary" : "ghost",
42805
- "aria-pressed": form.id === activeId,
42806
- onClick: () => {
42807
- setActiveId(form.id);
42808
- setDraft(form);
42783
+ return (
42784
+ // THE CONTAINER IS THE PARENT (TYPEFORM-2): a container query asks an ANCESTOR, so `@3xl:` on the
42785
+ // element that is itself `@container` never fired and the preview sat 3,000 px below the editor.
42786
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("div", { className: (0, import_design_system118.cn)("@container min-h-0", className), children: /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "flex min-h-0 flex-col gap-3 @3xl:flex-row @3xl:items-start", children: [
42787
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col gap-2", children: [
42788
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "flex items-center gap-1 overflow-x-auto", role: "group", "aria-label": "Forms", children: [
42789
+ forms.map((form) => /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42790
+ import_design_system118.Button,
42791
+ {
42792
+ size: "sm",
42793
+ variant: form.id === activeId ? "secondary" : "ghost",
42794
+ "aria-pressed": form.id === activeId,
42795
+ onClick: () => {
42796
+ setActiveId(form.id);
42797
+ setDraft(form);
42798
+ },
42799
+ children: form.name
42809
42800
  },
42810
- children: form.name
42811
- },
42812
- form.id
42813
- )),
42814
- (fields.data ?? []).length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
42815
- import_design_system119.Button,
42816
- {
42817
- size: "sm",
42818
- variant: "ghost",
42819
- onClick: () => void create(
42820
- freshFormName(
42821
- table.data?.name?.trim() ? `${table.data.name.trim()} form` : "Form",
42822
- forms.map((f) => f.name)
42823
- )
42824
- ),
42825
- children: "New form"
42826
- }
42827
- ) : null,
42828
- /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("span", { className: "ml-auto flex items-center gap-2", children: [
42829
- saved ? /* @__PURE__ */ (0, import_jsx_runtime134.jsx)("span", { className: "text-xs text-muted-foreground", children: saved }) : null,
42830
- stored ? /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(import_design_system119.Button, { size: "sm", variant: showResults ? "default" : "ghost", onClick: () => setShowResults((v) => !v), "data-records-form-results-toggle": "", children: "Results" }) : null,
42831
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(import_design_system119.Button, { size: "sm", disabled: saving || !draft, onClick: () => void save(), children: saving ? "Saving\u2026" : "Save" })
42832
- ] })
42833
- ] }),
42834
- refused && !isLookRefusal(refused) ? /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
42835
- RefusalNotice,
42836
- {
42837
- error: refused,
42838
- onKeepEditing: () => setRefused(null),
42839
- onDiscard: () => {
42840
- const kept = forms.find((f) => f.id === activeId) ?? null;
42841
- if (kept) dropKeptDraft(kept.id);
42842
- setDraft(kept);
42843
- setRestoredAt(null);
42844
- setSaved(null);
42845
- setRefused(null);
42846
- }
42847
- }
42848
- ) : null,
42849
- restoredAt && dirty && stored ? /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("p", { className: "flex items-center gap-2 text-xs text-muted-foreground", "data-records-form-restored": "", children: [
42850
- /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("span", { children: [
42851
- "Unsaved changes from",
42852
- " ",
42853
- new Date(restoredAt).toLocaleString(void 0, { dateStyle: "medium", timeStyle: "short" }),
42854
- " restored"
42801
+ form.id
42802
+ )),
42803
+ (fields.data ?? []).length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42804
+ import_design_system118.Button,
42805
+ {
42806
+ size: "sm",
42807
+ variant: "ghost",
42808
+ onClick: () => void create(
42809
+ freshFormName(
42810
+ table.data?.name?.trim() ? `${table.data.name.trim()} form` : "Form",
42811
+ forms.map((f) => f.name)
42812
+ )
42813
+ ),
42814
+ children: "New form"
42815
+ }
42816
+ ) : null,
42817
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("span", { className: "ml-auto flex items-center gap-2", children: [
42818
+ saved ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "text-xs text-muted-foreground", children: saved }) : null,
42819
+ stored ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.Button, { size: "sm", variant: showResults ? "default" : "ghost", onClick: () => setShowResults((v) => !v), "data-records-form-results-toggle": "", children: "Results" }) : null,
42820
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.Button, { size: "sm", disabled: saving || !draft, onClick: () => void save(), children: saving ? "Saving\u2026" : "Save" })
42821
+ ] })
42855
42822
  ] }),
42856
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
42857
- import_design_system119.Button,
42823
+ refused && !isLookRefusal(refused) ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42824
+ RefusalNotice,
42858
42825
  {
42859
- size: "sm",
42860
- variant: "ghost",
42861
- onClick: () => {
42862
- dropKeptDraft(stored.id);
42863
- setDraft(stored);
42826
+ error: refused,
42827
+ onKeepEditing: () => setRefused(null),
42828
+ onDiscard: () => {
42829
+ const kept = forms.find((f) => f.id === activeId) ?? null;
42830
+ if (kept) dropKeptDraft(kept.id);
42831
+ setDraft(kept);
42864
42832
  setRestoredAt(null);
42865
- },
42866
- children: "Discard"
42867
- }
42868
- )
42869
- ] }) : null,
42870
- !draft ? /* @__PURE__ */ (0, import_jsx_runtime134.jsx)("p", { className: "text-xs text-muted-foreground", children: (fields.data ?? []).length === 0 ? "This table has no fields yet, so there is nothing a form could ask for. Add a field first \u2014 every question is one of this table's own fields." : 'No form collects into this table yet. "New form" makes one, or you ask an agent for the whole thing in a sentence.' }) : /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)(import_jsx_runtime134.Fragment, { children: [
42871
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
42872
- PublishRow,
42873
- {
42874
- url: `${publicOrigin}${(0, import_records57.publicFormPath)(draft.id)}`,
42875
- state: draft.state,
42876
- mayPublish: rights.structure,
42877
- why: rights.why("structure"),
42878
- busy: publishing,
42879
- copied,
42880
- shownUrl,
42881
- onPublish: (wanted) => void publish(wanted),
42882
- onCopy: (url) => void copyLink(url),
42883
- unsaved: dirty
42833
+ setSaved(null);
42834
+ setRefused(null);
42835
+ }
42884
42836
  }
42885
- ),
42886
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(EmbedSnippets, { url: `${publicOrigin}${(0, import_records57.publicFormPath)(draft.id)}`, label: draft.name }),
42887
- /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("section", { className: "flex flex-col gap-1 rounded border px-2.5 py-2", "aria-label": "When somebody answers", children: [
42888
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)("p", { className: "text-xs text-muted-foreground", children: draft.notifyRuleId ? "When somebody answers, you are told through the subscription marked below." : "Nobody is told when this form is answered yet. Pick a subscription below, or make one \u2014 email, text or in the app." }),
42889
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
42890
- NotifyRuleEditor,
42837
+ ) : null,
42838
+ restoredAt && dirty && stored ? /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("p", { className: "flex items-center gap-2 text-xs text-muted-foreground", "data-records-form-restored": "", children: [
42839
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("span", { children: [
42840
+ "Unsaved changes from",
42841
+ " ",
42842
+ new Date(restoredAt).toLocaleString(void 0, { dateStyle: "medium", timeStyle: "short" }),
42843
+ " restored"
42844
+ ] }),
42845
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42846
+ import_design_system118.Button,
42891
42847
  {
42892
- tableId,
42893
- chosenRuleId: draft.notifyRuleId,
42894
- onChoose: (ruleId) => void chooseNotify(ruleId)
42848
+ size: "sm",
42849
+ variant: "ghost",
42850
+ onClick: () => {
42851
+ dropKeptDraft(stored.id);
42852
+ setDraft(stored);
42853
+ setRestoredAt(null);
42854
+ },
42855
+ children: "Discard"
42895
42856
  }
42896
42857
  )
42897
- ] }),
42898
- /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("section", { className: "flex flex-col gap-2 rounded border px-2.5 py-2", "aria-label": "After somebody sends it", children: [
42899
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)("p", { className: "text-xs text-muted-foreground", children: draft.thankYou?.redirectUrl ? "After sending, people see this message for a moment and then go on to the page below." : "After sending, people see this message. Add a page to send them on to, if there is a next step somewhere else." }),
42900
- /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("div", { className: "grid grid-cols-2 gap-2", children: [
42901
- /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("label", { className: "flex flex-col gap-1", children: [
42902
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(import_design_system119.Label, { className: "text-xs font-medium", children: "Thank-you title" }),
42903
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
42904
- import_design_system119.BasicInput,
42905
- {
42906
- value: draft.thankYou?.title ?? "",
42907
- placeholder: "Thank you",
42908
- onChange: (e) => patch({ thankYou: { ...draft.thankYou, title: e.target.value || null } })
42909
- }
42910
- )
42911
- ] }),
42912
- /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("label", { className: "flex flex-col gap-1", children: [
42913
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(import_design_system119.Label, { className: "text-xs font-medium", children: "Then go to (optional)" }),
42914
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
42915
- import_design_system119.BasicInput,
42858
+ ] }) : null,
42859
+ !draft ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("p", { className: "text-xs text-muted-foreground", children: (fields.data ?? []).length === 0 ? "No fields yet" : "No form yet" }) : /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)(import_jsx_runtime133.Fragment, { children: [
42860
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42861
+ PublishRow,
42862
+ {
42863
+ url: `${publicOrigin}${(0, import_records56.publicFormPath)(draft.id)}`,
42864
+ state: draft.state,
42865
+ mayPublish: rights.structure,
42866
+ why: rights.why("structure"),
42867
+ busy: publishing,
42868
+ copied,
42869
+ shownUrl,
42870
+ onPublish: (wanted) => void publish(wanted),
42871
+ onCopy: (url) => void copyLink(url),
42872
+ unsaved: dirty
42873
+ }
42874
+ ),
42875
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(EmbedSnippets, { url: `${publicOrigin}${(0, import_records56.publicFormPath)(draft.id)}`, label: draft.name }),
42876
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42877
+ NotifyOnAnswer,
42878
+ {
42879
+ tableId,
42880
+ tableName: table.data?.name ?? "record",
42881
+ formName: draft.name,
42882
+ ruleId: draft.notifyRuleId,
42883
+ notify: draft.notify ?? null,
42884
+ onRule: (ruleId) => void chooseNotify(ruleId),
42885
+ onNotify: (notify) => void chooseWebhook(notify)
42886
+ }
42887
+ ),
42888
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("section", { className: "flex flex-wrap items-center gap-x-4 gap-y-1 rounded border px-2.5 py-2 text-xs", "aria-label": "Options", children: [
42889
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("label", { className: "flex items-center gap-2", title: "Off hides your logo and name \u2014 for a client's form.", children: [
42890
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42891
+ import_design_system118.Checkbox,
42916
42892
  {
42917
- type: "url",
42918
- inputMode: "url",
42919
- value: draft.thankYou?.redirectUrl ?? "",
42920
- placeholder: "https://",
42921
- onChange: (e) => patch({ thankYou: { ...draft.thankYou, redirectUrl: e.target.value || null } })
42893
+ checked: draft.showOwnerHeader ?? true,
42894
+ onCheckedChange: (checked) => patch({ showOwnerHeader: checked === true }),
42895
+ "aria-label": "Show our logo and name"
42922
42896
  }
42923
- )
42897
+ ),
42898
+ "Show our logo and name"
42924
42899
  ] }),
42925
- /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("label", { className: "col-span-2 flex flex-col gap-1", children: [
42926
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(import_design_system119.Label, { className: "text-xs font-medium", children: "Thank-you message" }),
42927
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
42928
- import_design_system119.BasicTextarea,
42900
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("label", { className: "flex items-center gap-2", title: "A single choice goes on to the next question when picked.", children: [
42901
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42902
+ import_design_system118.Checkbox,
42929
42903
  {
42930
- rows: 2,
42931
- value: draft.thankYou?.body ?? "",
42932
- placeholder: "What happens next, in your words.",
42933
- onChange: (e) => patch({ thankYou: { ...draft.thankYou, body: e.target.value || null } })
42904
+ checked: draft.autoAdvance ?? true,
42905
+ onCheckedChange: (checked) => patch({ autoAdvance: checked === true }),
42906
+ "aria-label": "Next after a choice"
42934
42907
  }
42935
- )
42908
+ ),
42909
+ "Next after a choice"
42936
42910
  ] })
42937
- ] })
42938
- ] }),
42939
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(WelcomeEditor, { value: draft.welcome, pictures: lookOptions?.pictures ?? null, onChange: (welcome) => patch({ welcome }) }),
42940
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
42941
- EndingsEditor,
42942
- {
42943
- endings: draft.endings ?? [],
42944
- pictures: lookOptions?.pictures ?? null,
42945
- fieldKeys: conditionFields,
42946
- scored: draft.questions.some((q) => q.points),
42947
- onChange: (endings) => patch({ endings })
42948
- }
42949
- ),
42950
- /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("div", { className: "grid grid-cols-2 gap-2", children: [
42951
- /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("label", { className: "flex flex-col gap-1", children: [
42952
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(import_design_system119.Label, { className: "text-xs font-medium", children: "Name" }),
42953
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(import_design_system119.BasicInput, { value: draft.name, onChange: (e) => patch({ name: e.target.value }) })
42954
- ] }),
42955
- /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("label", { className: "flex flex-col gap-1", children: [
42956
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(import_design_system119.Label, { className: "text-xs font-medium", children: "Flow" }),
42957
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
42958
- "select",
42959
- {
42960
- className: "h-9 rounded border bg-background px-2 text-sm",
42961
- value: draft.flow ?? "one-at-a-time",
42962
- onChange: (e) => patch({ flow: e.target.value }),
42963
- children: FORM_FLOWS.map((flow) => /* @__PURE__ */ (0, import_jsx_runtime134.jsx)("option", { value: flow, children: flow === "one-at-a-time" ? "One question at a time" : "All on one page" }, flow))
42964
- }
42965
- )
42966
42911
  ] }),
42967
- /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("label", { className: "col-span-2 flex flex-col gap-1", children: [
42968
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(import_design_system119.Label, { className: "text-xs font-medium", children: "Intro" }),
42969
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
42970
- import_design_system119.BasicTextarea,
42971
- {
42972
- rows: 2,
42973
- value: draft.intro ?? "",
42974
- onChange: (e) => patch({ intro: e.target.value || null })
42975
- }
42976
- )
42977
- ] }),
42978
- /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("label", { className: "flex flex-col gap-1", children: [
42979
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(import_design_system119.Label, { className: "text-xs font-medium", children: "Submit button" }),
42980
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
42981
- import_design_system119.BasicInput,
42982
- {
42983
- value: draft.submitLabel ?? "",
42984
- placeholder: "Submit",
42985
- onChange: (e) => patch({ submitLabel: e.target.value || null })
42986
- }
42987
- )
42912
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("section", { className: "flex flex-col gap-2 rounded border px-2.5 py-2", "aria-label": "After somebody sends it", children: [
42913
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "text-xs font-medium", title: "What people see after sending, and an optional page they go on to.", children: "After sending" }),
42914
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "grid grid-cols-2 gap-2", children: [
42915
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("label", { className: "flex flex-col gap-1", children: [
42916
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.Label, { className: "text-xs font-medium", children: "Thank-you title" }),
42917
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42918
+ import_design_system118.BasicInput,
42919
+ {
42920
+ value: draft.thankYou?.title ?? "",
42921
+ placeholder: "Thank you",
42922
+ onChange: (e) => patch({ thankYou: { ...draft.thankYou, title: e.target.value || null } })
42923
+ }
42924
+ )
42925
+ ] }),
42926
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("label", { className: "flex flex-col gap-1", children: [
42927
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.Label, { className: "text-xs font-medium", children: "Then go to (optional)" }),
42928
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42929
+ import_design_system118.BasicInput,
42930
+ {
42931
+ type: "url",
42932
+ inputMode: "url",
42933
+ value: draft.thankYou?.redirectUrl ?? "",
42934
+ placeholder: "https://",
42935
+ onChange: (e) => patch({ thankYou: { ...draft.thankYou, redirectUrl: e.target.value || null } })
42936
+ }
42937
+ )
42938
+ ] }),
42939
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("label", { className: "col-span-2 flex flex-col gap-1", children: [
42940
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.Label, { className: "text-xs font-medium", children: "Thank-you message" }),
42941
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42942
+ import_design_system118.BasicTextarea,
42943
+ {
42944
+ rows: 2,
42945
+ value: draft.thankYou?.body ?? "",
42946
+ placeholder: "What happens next, in your words.",
42947
+ onChange: (e) => patch({ thankYou: { ...draft.thankYou, body: e.target.value || null } })
42948
+ }
42949
+ )
42950
+ ] })
42951
+ ] })
42988
42952
  ] }),
42989
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(HiddenFieldsEditor, { value: draft.hiddenFields ?? [], onChange: (hiddenFields) => patch({ hiddenFields }) })
42990
- ] }),
42991
- /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("div", { className: "flex flex-col gap-1.5", "data-records-form-look-editor": "", children: [
42992
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(import_design_system119.Label, { className: "text-xs font-medium", children: "Look" }),
42993
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
42994
- AccentSwatches,
42953
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(WelcomeEditor, { value: draft.welcome, pictures: lookOptions?.pictures ?? null, onChange: (welcome) => patch({ welcome }) }),
42954
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42955
+ EndingsEditor,
42995
42956
  {
42996
- value: draft.look?.accent ?? "",
42997
- noneLabel: "Organization colour",
42998
- onChange: (accent) => patchLook({ accent: accent || null })
42957
+ endings: draft.endings ?? [],
42958
+ pictures: lookOptions?.pictures ?? null,
42959
+ fieldKeys: conditionFields,
42960
+ scored: draft.questions.some((q) => q.points),
42961
+ onChange: (endings) => patch({ endings })
42999
42962
  }
43000
42963
  ),
43001
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(ThemeEditor, { theme: draft.theme, pictures: lookOptions?.pictures ?? null, onChange: (theme) => patch({ theme }) }),
43002
- lookOptions ? /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("div", { className: "flex flex-col gap-2", children: [
43003
- /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("div", { className: "flex flex-col gap-1", children: [
43004
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)("span", { className: "text-xs", children: "Logo" }),
43005
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
43006
- PublicPicturePicker,
42964
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "grid grid-cols-2 gap-2", children: [
42965
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("label", { className: "flex flex-col gap-1", children: [
42966
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.Label, { className: "text-xs font-medium", children: "Name" }),
42967
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.BasicInput, { value: draft.name, onChange: (e) => patch({ name: e.target.value }) })
42968
+ ] }),
42969
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("label", { className: "flex flex-col gap-1", children: [
42970
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.Label, { className: "text-xs font-medium", children: "Flow" }),
42971
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42972
+ "select",
43007
42973
  {
43008
- label: "Logo",
43009
- pictures: lookOptions.pictures,
43010
- value: draft.look?.logoFileId ?? "",
43011
- noneLabel: "Organization logo",
43012
- onChange: (id) => patchLook({ logoFileId: id || null })
42974
+ className: "h-9 rounded border bg-background px-2 text-sm",
42975
+ value: draft.flow ?? "one-at-a-time",
42976
+ onChange: (e) => patch({ flow: e.target.value }),
42977
+ children: FORM_FLOWS.map((flow) => /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("option", { value: flow, children: flow === "one-at-a-time" ? "One question at a time" : "All on one page" }, flow))
43013
42978
  }
43014
42979
  )
43015
42980
  ] }),
43016
- /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("div", { className: "flex flex-col gap-1", children: [
43017
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)("span", { className: "text-xs", children: "Cover" }),
43018
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
43019
- PublicPicturePicker,
42981
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("label", { className: "col-span-2 flex flex-col gap-1", children: [
42982
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.Label, { className: "text-xs font-medium", children: "Intro" }),
42983
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42984
+ import_design_system118.BasicTextarea,
43020
42985
  {
43021
- label: "Cover",
43022
- pictures: lookOptions.pictures,
43023
- value: draft.look?.coverFileId ?? "",
43024
- noneLabel: "No cover",
43025
- onChange: (id) => patchLook({ coverFileId: id || null })
42986
+ rows: 2,
42987
+ value: draft.intro ?? "",
42988
+ onChange: (e) => patch({ intro: e.target.value || null })
43026
42989
  }
43027
42990
  )
43028
- ] })
43029
- ] }) : lookUnread ? null : /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(import_design_system119.Skeleton, { className: "h-12 w-full" }),
43030
- lookUnread ? /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(RefusalLine, { error: lookUnread }) : null,
43031
- refused && isLookRefusal(refused) ? /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(RefusalLine, { error: refused }) : null
43032
- ] }),
43033
- /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("div", { className: "flex flex-col gap-1", children: [
43034
- /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
43035
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)("span", { className: "font-medium text-foreground", children: "Questions" }),
43036
- /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("span", { children: [
43037
- draft.questions.length,
43038
- " of this table's ",
43039
- fields.data?.length ?? 0,
43040
- " fields"
43041
- ] })
42991
+ ] }),
42992
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("label", { className: "flex flex-col gap-1", children: [
42993
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.Label, { className: "text-xs font-medium", children: "Submit button" }),
42994
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42995
+ import_design_system118.BasicInput,
42996
+ {
42997
+ value: draft.submitLabel ?? "",
42998
+ placeholder: "Submit",
42999
+ onChange: (e) => patch({ submitLabel: e.target.value || null })
43000
+ }
43001
+ )
43002
+ ] }),
43003
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(HiddenFieldsEditor, { value: draft.hiddenFields ?? [], onChange: (hiddenFields) => patch({ hiddenFields }) })
43042
43004
  ] }),
43043
- askableFields(fields.data ?? []).map((field2) => {
43044
- const index = draft.questions.findIndex((q) => q.field === field2.key);
43045
- const asked3 = index >= 0;
43046
- const question = asked3 ? draft.questions[index] : null;
43047
- return /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("div", { className: "rounded border px-2 py-1.5", children: [
43048
- /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("div", { className: "flex items-center gap-2", children: [
43049
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
43050
- import_design_system119.Checkbox,
43005
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "flex flex-col gap-1.5", "data-records-form-look-editor": "", children: [
43006
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.Label, { className: "text-xs font-medium", children: "Look" }),
43007
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43008
+ AccentSwatches,
43009
+ {
43010
+ value: draft.look?.accent ?? "",
43011
+ noneLabel: "Organization colour",
43012
+ onChange: (accent) => patchLook({ accent: accent || null })
43013
+ }
43014
+ ),
43015
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(ThemeEditor, { theme: draft.theme, pictures: lookOptions?.pictures ?? null, onChange: (theme) => patch({ theme }) }),
43016
+ lookOptions ? /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "flex flex-col gap-2", children: [
43017
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "flex flex-col gap-1", children: [
43018
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "text-xs", children: "Logo" }),
43019
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43020
+ PublicPicturePicker,
43051
43021
  {
43052
- checked: asked3,
43053
- "aria-label": `Ask for ${fieldName(field2)}`,
43054
- onCheckedChange: (checked) => patch({
43055
- questions: checked ? [...draft.questions, { field: field2.key }] : draft.questions.filter((q) => q.field !== field2.key)
43056
- })
43022
+ label: "Logo",
43023
+ pictures: lookOptions.pictures,
43024
+ value: draft.look?.logoFileId ?? "",
43025
+ noneLabel: "Organization logo",
43026
+ onChange: (id) => patchLook({ logoFileId: id || null })
43057
43027
  }
43058
- ),
43059
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)("span", { className: "text-xs font-medium", children: fieldName(field2) }),
43060
- field2.required ? /* @__PURE__ */ (0, import_jsx_runtime134.jsx)("span", { className: "text-xs text-destructive", children: "required" }) : null
43028
+ )
43061
43029
  ] }),
43062
- asked3 && question ? /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("div", { className: "mt-1.5 grid grid-cols-2 gap-2 pl-6", children: [
43063
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
43064
- import_design_system119.BasicInput,
43065
- {
43066
- className: "h-8 text-xs",
43067
- placeholder: fieldName(field2),
43068
- "aria-label": `How to ask for ${fieldName(field2)}`,
43069
- value: question.ask ?? "",
43070
- onChange: (e) => patchQuestion(index, { ask: e.target.value || null })
43071
- }
43072
- ),
43073
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
43074
- import_design_system119.BasicInput,
43075
- {
43076
- className: "h-8 text-xs",
43077
- placeholder: "Help text",
43078
- "aria-label": `Help text for ${fieldName(field2)}`,
43079
- value: question.help ?? "",
43080
- onChange: (e) => patchQuestion(index, { help: e.target.value || null })
43081
- }
43082
- ),
43083
- /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("label", { className: "col-span-2 flex items-center gap-2 text-xs", children: [
43084
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
43085
- import_design_system119.Checkbox,
43086
- {
43087
- checked: field2.required ? true : question.required === true,
43088
- disabled: field2.required === true,
43089
- "aria-label": `${fieldName(field2)} is required`,
43090
- onCheckedChange: (checked) => patchQuestion(index, { required: checked === true ? true : null })
43091
- }
43092
- ),
43093
- "Required"
43094
- ] }),
43095
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
43096
- Condition,
43030
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "flex flex-col gap-1", children: [
43031
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "text-xs", children: "Cover" }),
43032
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43033
+ PublicPicturePicker,
43097
43034
  {
43098
- question,
43099
- fieldKeys: (fields.data ?? []).map((f) => ({
43100
- id: String(f.id),
43101
- key: f.key,
43102
- label: f.label || f.key,
43103
- field: f
43104
- })),
43105
- onChange: (showIf) => patchQuestion(index, { showIf })
43106
- }
43107
- ),
43108
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
43109
- QuestionLogic,
43110
- {
43111
- question,
43112
- index,
43113
- questions: draft.questions,
43114
- endings: draft.endings ?? [],
43115
- field: field2,
43116
- fieldKeys: conditionFields,
43117
- label: (key) => {
43118
- const f = byKey.get(key);
43119
- return draft.questions.find((q) => q.field === key)?.ask || (f ? fieldName(f) : key);
43120
- },
43121
- onChange: (part) => patchQuestion(index, part)
43035
+ label: "Cover",
43036
+ pictures: lookOptions.pictures,
43037
+ value: draft.look?.coverFileId ?? "",
43038
+ noneLabel: "No cover",
43039
+ onChange: (id) => patchLook({ coverFileId: id || null })
43122
43040
  }
43123
43041
  )
43124
- ] }) : null
43125
- ] }, field2.id);
43126
- }),
43127
- whyNotAskable(fields.data ?? []) ? /* @__PURE__ */ (0, import_jsx_runtime134.jsx)("p", { className: "text-xs text-muted-foreground", children: whyNotAskable(fields.data ?? []) }) : null,
43128
- requiredLeftUnasked(fields.data ?? [], draft.questions.map((q) => q.field)) ? /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
43129
- import_design_system118.ErrorBox,
43130
- {
43131
- className: "rounded border border-destructive/40 bg-destructive/5 px-2 py-1.5 text-xs text-destructive",
43132
- message: requiredLeftUnasked(fields.data ?? [], draft.questions.map((q) => q.field)) ?? "",
43133
- operation: "Build a form",
43134
- origin: "records-ui/FormBuilder"
43135
- }
43136
- ) : null,
43137
- draft.questions.filter((q) => !byKey.has(q.field)).map((q) => /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("p", { className: "text-xs text-destructive", children: [
43138
- 'This form asks for "',
43139
- q.field,
43140
- '" and this table has no such Field. It is shown here rather than quietly dropped.'
43141
- ] }, q.field)),
43142
- adding ? /* @__PURE__ */ (0, import_jsx_runtime134.jsx)("div", { className: "rounded border p-2", "data-records-form-add-question": "", children: /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
43143
- FieldEditor,
43042
+ ] })
43043
+ ] }) : lookUnread ? null : /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.Skeleton, { className: "h-12 w-full" }),
43044
+ lookUnread ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(RefusalLine, { error: lookUnread }) : null,
43045
+ refused && isLookRefusal(refused) ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(RefusalLine, { error: refused }) : null
43046
+ ] }),
43047
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43048
+ QuestionList,
43144
43049
  {
43145
43050
  tableId,
43146
- onCancel: () => {
43147
- keysBeforeAdd.current = null;
43148
- setAdding(false);
43051
+ draft,
43052
+ fields: fields.data ?? [],
43053
+ byKey,
43054
+ adding,
43055
+ onAdding: (on) => {
43056
+ keysBeforeAdd.current = on ? new Set((fields.data ?? []).map((f) => f.key)) : null;
43057
+ setAdding(on);
43149
43058
  },
43150
- onSaved: () => setAdding(false)
43059
+ onAdded: () => setAdding(false),
43060
+ onQuestions: (questions) => patch({ questions }),
43061
+ onQuestion: patchQuestion
43151
43062
  }
43152
- ) }) : /* @__PURE__ */ (0, import_jsx_runtime134.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
43153
- import_design_system119.Button,
43154
- {
43155
- size: "sm",
43156
- variant: "outline",
43157
- onClick: () => {
43158
- keysBeforeAdd.current = new Set((fields.data ?? []).map((f) => f.key));
43159
- setAdding(true);
43160
- },
43161
- children: "Add a question"
43162
- }
43163
- ) })
43063
+ )
43164
43064
  ] })
43165
- ] })
43166
- ] }),
43167
- draft ? /* @__PURE__ */ (0, import_jsx_runtime134.jsx)("aside", { className: "w-full shrink-0 overflow-auto rounded border p-2 @3xl:w-96", children: showResults && stored ? /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(FormResultsPanel, { form: stored }) : /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(FormLookFrame, { look: lookOptions?.look ?? null, title: draft.name, contained: true, children: /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(FormRunner, { form: draft, preview: true }) }) }) : null
43168
- ] });
43065
+ ] }),
43066
+ draft ? (
43067
+ // BESIDE THE EDITOR, ALWAYS IN VIEW (TYPEFORM-2): it follows the scroll and updates as you type.
43068
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43069
+ "aside",
43070
+ {
43071
+ className: "w-full shrink-0 overflow-auto rounded border p-2 @3xl:sticky @3xl:top-2 @3xl:max-h-[calc(100dvh-6rem)] @3xl:w-[min(45%,520px)]",
43072
+ "data-records-form-preview": "",
43073
+ children: showResults && stored ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(FormResultsPanel, { form: stored }) : /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(FormLookFrame, { look: lookOptions?.look ?? null, title: draft.name, contained: true, showOwnerHeader: draft.showOwnerHeader ?? true, children: /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(FormRunner, { form: draft, preview: true }) })
43074
+ }
43075
+ )
43076
+ ) : null
43077
+ ] }) })
43078
+ );
43169
43079
  }
43170
43080
  function PublishRow({
43171
43081
  url,
@@ -43180,10 +43090,10 @@ function PublishRow({
43180
43090
  unsaved = false
43181
43091
  }) {
43182
43092
  const open = state === "open";
43183
- return /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("div", { className: "flex flex-col gap-1.5 rounded border px-2.5 py-2", children: [
43184
- /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
43185
- mayPublish ? /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
43186
- import_design_system119.Button,
43093
+ return /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "flex flex-col gap-1.5 rounded border px-2.5 py-2", children: [
43094
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
43095
+ mayPublish ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43096
+ import_design_system118.Button,
43187
43097
  {
43188
43098
  size: "sm",
43189
43099
  variant: open ? "ghost" : "default",
@@ -43192,8 +43102,8 @@ function PublishRow({
43192
43102
  children: busy ? "\u2026" : open ? "Unpublish" : unsaved ? "Save and publish" : "Publish"
43193
43103
  }
43194
43104
  ) : null,
43195
- state !== "draft" ? /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)(import_jsx_runtime134.Fragment, { children: [
43196
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
43105
+ state !== "draft" ? /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)(import_jsx_runtime133.Fragment, { children: [
43106
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43197
43107
  "a",
43198
43108
  {
43199
43109
  href: url,
@@ -43203,14 +43113,14 @@ function PublishRow({
43203
43113
  children: url
43204
43114
  }
43205
43115
  ),
43206
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(import_design_system119.Button, { size: "sm", variant: "outline", onClick: () => onCopy(url), children: copied ? "Copied" : "Copy link" })
43116
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.Button, { size: "sm", variant: "outline", onClick: () => onCopy(url), children: copied ? "Copied" : "Copy link" })
43207
43117
  ] }) : null
43208
43118
  ] }),
43209
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)("p", { className: "text-xs text-muted-foreground", children: FORM_STATE_WORDS[state] }),
43210
- open && unsaved ? /* @__PURE__ */ (0, import_jsx_runtime134.jsx)("p", { className: "text-xs text-muted-foreground", "data-records-form-unsaved": "", children: "Unsaved changes \u2014 the live form shows your last save" }) : null,
43211
- !mayPublish ? /* @__PURE__ */ (0, import_jsx_runtime134.jsx)("p", { className: "text-xs text-muted-foreground", children: why }) : null,
43212
- shownUrl ? /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("p", { className: "break-all rounded border border-dashed px-2 py-1 text-xs", children: [
43213
- "This browser would not let the page copy for you, so here it is to copy by hand: ",
43119
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("p", { className: "w-fit text-xs text-muted-foreground", title: FORM_STATE_WORDS[state], "data-records-form-state": state, children: FORM_STATE_LABELS[state] }),
43120
+ open && unsaved ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("p", { className: "text-xs text-muted-foreground", "data-records-form-unsaved": "", children: "Unsaved changes \u2014 the live form shows your last save" }) : null,
43121
+ !mayPublish ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("p", { className: "w-fit text-xs text-muted-foreground", title: why, children: "Publishing needs table admin" }) : null,
43122
+ shownUrl ? /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("p", { className: "break-all rounded border border-dashed px-2 py-1 text-xs", title: "This browser would not copy for you.", children: [
43123
+ "Copy by hand: ",
43214
43124
  shownUrl
43215
43125
  ] }) : null
43216
43126
  ] });
@@ -43220,7 +43130,7 @@ function Condition({
43220
43130
  fieldKeys,
43221
43131
  onChange
43222
43132
  }) {
43223
- return /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
43133
+ return /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43224
43134
  ConditionGroup,
43225
43135
  {
43226
43136
  lead: "Ask only when",
@@ -43231,26 +43141,326 @@ function Condition({
43231
43141
  }
43232
43142
  );
43233
43143
  }
43144
+ var FORM_STATE_LABELS = {
43145
+ draft: "Not published",
43146
+ open: "Open",
43147
+ closed: "Closed",
43148
+ full: "Full"
43149
+ };
43150
+ function QuestionList({
43151
+ tableId,
43152
+ draft,
43153
+ fields,
43154
+ byKey,
43155
+ adding,
43156
+ onAdding,
43157
+ onAdded,
43158
+ onQuestions,
43159
+ onQuestion
43160
+ }) {
43161
+ const sensors = (0, import_core22.useSensors)(
43162
+ (0, import_core22.useSensor)(import_core22.PointerSensor, { activationConstraint: { distance: 4 } }),
43163
+ (0, import_core22.useSensor)(import_core22.KeyboardSensor, { coordinateGetter: import_sortable.sortableKeyboardCoordinates })
43164
+ );
43165
+ const askable = askableFields([...fields]);
43166
+ const unasked = askable.filter((f) => !draft.questions.some((q) => q.field === f.key));
43167
+ const asked3 = draft.questions.map((q) => byKey.get(q.field)).filter((f) => Boolean(f)).map((f) => ({ id: String(f.id), key: f.key, label: draft.questions.find((q) => q.field === f.key)?.ask || fieldName(f), field: f }));
43168
+ const label = (key) => {
43169
+ const f = byKey.get(key);
43170
+ return draft.questions.find((q) => q.field === key)?.ask || (f ? fieldName(f) : key);
43171
+ };
43172
+ function dragEnd(event) {
43173
+ const from = draft.questions.findIndex((q) => q.field === event.active.id);
43174
+ const to = event.over ? draft.questions.findIndex((q) => q.field === event.over.id) : -1;
43175
+ if (from < 0 || to < 0 || from === to) return;
43176
+ onQuestions((0, import_sortable.arrayMove)(draft.questions, from, to));
43177
+ }
43178
+ const notAskable = whyNotAskable([...fields]);
43179
+ const leftUnasked = requiredLeftUnasked([...fields], draft.questions.map((q) => q.field));
43180
+ return /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "flex flex-col gap-1.5", "data-records-form-questions": "", children: [
43181
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
43182
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "font-medium text-foreground", children: "Questions" }),
43183
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "tabular-nums", children: draft.questions.length })
43184
+ ] }),
43185
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_core22.DndContext, { sensors, collisionDetection: import_core22.closestCenter, onDragEnd: dragEnd, children: /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_sortable.SortableContext, { items: draft.questions.map((q) => q.field), strategy: import_sortable.verticalListSortingStrategy, children: draft.questions.map((question, index) => /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43186
+ QuestionRow,
43187
+ {
43188
+ index,
43189
+ question,
43190
+ field: byKey.get(question.field) ?? null,
43191
+ draft,
43192
+ conditionFields: asked3.filter((f) => f.key !== question.field),
43193
+ label,
43194
+ onChange: (change) => onQuestion(index, change),
43195
+ onRemove: () => onQuestions(draft.questions.filter((q) => q.field !== question.field))
43196
+ },
43197
+ question.field
43198
+ )) }) }),
43199
+ unasked.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("div", { className: "flex flex-wrap items-center gap-1", "data-records-form-unasked": "", children: unasked.map((f) => /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)(
43200
+ import_design_system118.Button,
43201
+ {
43202
+ size: "sm",
43203
+ variant: "outline",
43204
+ "aria-label": `Ask for ${fieldName(f)}`,
43205
+ onClick: () => onQuestions([...draft.questions, { field: f.key }]),
43206
+ children: [
43207
+ "+ ",
43208
+ fieldName(f)
43209
+ ]
43210
+ },
43211
+ f.id
43212
+ )) }) : null,
43213
+ notAskable ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("p", { className: "w-fit text-xs text-muted-foreground", title: notAskable, children: "Some fields cannot be asked publicly" }) : null,
43214
+ leftUnasked ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43215
+ import_design_system117.ErrorBox,
43216
+ {
43217
+ className: "rounded border border-destructive/40 bg-destructive/5 px-2 py-1.5 text-xs text-destructive",
43218
+ message: leftUnasked,
43219
+ operation: "Build a form",
43220
+ origin: "records-ui/FormBuilder"
43221
+ }
43222
+ ) : null,
43223
+ adding ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("div", { className: "rounded border p-2", "data-records-form-add-question": "", children: /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(FieldEditor, { tableId, onCancel: () => onAdding(false), onSaved: onAdded }) }) : /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.Button, { size: "sm", variant: "outline", onClick: () => onAdding(true), children: "Add a question" }) })
43224
+ ] });
43225
+ }
43226
+ function QuestionRow({
43227
+ index,
43228
+ question,
43229
+ field: field2,
43230
+ draft,
43231
+ conditionFields,
43232
+ label,
43233
+ onChange,
43234
+ onRemove
43235
+ }) {
43236
+ const sortable = (0, import_sortable.useSortable)({ id: question.field });
43237
+ const [open, setOpen] = (0, import_react232.useState)(true);
43238
+ const name = field2 ? fieldName(field2) : question.field;
43239
+ return /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)(
43240
+ "div",
43241
+ {
43242
+ ref: sortable.setNodeRef,
43243
+ style: { transform: import_utilities.CSS.Transform.toString(sortable.transform), transition: sortable.transition },
43244
+ className: (0, import_design_system118.cn)("rounded border bg-background px-2 py-1.5", sortable.isDragging && "z-10 shadow-md"),
43245
+ "data-records-form-question-row": question.field,
43246
+ children: [
43247
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "flex items-center gap-2", children: [
43248
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43249
+ "button",
43250
+ {
43251
+ type: "button",
43252
+ className: "cursor-grab touch-none rounded px-1 text-muted-foreground hover:bg-muted active:cursor-grabbing",
43253
+ "aria-label": `Move ${name}`,
43254
+ title: "Drag to reorder",
43255
+ "data-records-form-drag": "",
43256
+ ...sortable.attributes,
43257
+ ...sortable.listeners,
43258
+ children: "\u22EE\u22EE"
43259
+ }
43260
+ ),
43261
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "w-5 text-right text-xs tabular-nums text-muted-foreground", children: index + 1 }),
43262
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("button", { type: "button", className: "min-w-0 flex-1 truncate text-left text-xs font-medium", onClick: () => setOpen((v) => !v), "aria-expanded": open, children: question.ask?.trim() || name }),
43263
+ field2?.required || question.required ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "text-xs text-foreground/80", children: "required" }) : null,
43264
+ !field2 ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "rounded bg-destructive px-1.5 text-[11px] text-destructive-foreground", title: "The table has no such field.", children: "Missing field" }) : null,
43265
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.Button, { size: "sm", variant: "ghost", onClick: onRemove, "aria-label": `Stop asking ${name}`, children: "Remove" })
43266
+ ] }),
43267
+ open && field2 ? /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "mt-1.5 grid grid-cols-2 gap-2 pl-7", children: [
43268
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43269
+ import_design_system118.BasicInput,
43270
+ {
43271
+ className: "h-8 text-xs",
43272
+ placeholder: name,
43273
+ "aria-label": `How to ask for ${name}`,
43274
+ title: "Use {{question}} to recall an earlier answer",
43275
+ value: question.ask ?? "",
43276
+ onChange: (e) => onChange({ ask: e.target.value || null })
43277
+ }
43278
+ ),
43279
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43280
+ import_design_system118.BasicInput,
43281
+ {
43282
+ className: "h-8 text-xs",
43283
+ placeholder: "Help text",
43284
+ "aria-label": `Help text for ${name}`,
43285
+ value: question.help ?? "",
43286
+ onChange: (e) => onChange({ help: e.target.value || null })
43287
+ }
43288
+ ),
43289
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("label", { className: "col-span-2 flex items-center gap-2 text-xs", children: [
43290
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43291
+ import_design_system118.Checkbox,
43292
+ {
43293
+ checked: field2.required ? true : question.required === true,
43294
+ disabled: field2.required === true,
43295
+ "aria-label": `${name} is required`,
43296
+ onCheckedChange: (checked) => onChange({ required: checked === true ? true : null })
43297
+ }
43298
+ ),
43299
+ "Required"
43300
+ ] }),
43301
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(Condition, { question, fieldKeys: conditionFields, onChange: (showIf) => onChange({ showIf }) }),
43302
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43303
+ QuestionLogic,
43304
+ {
43305
+ question,
43306
+ index,
43307
+ questions: draft.questions,
43308
+ endings: draft.endings ?? [],
43309
+ field: field2,
43310
+ fieldKeys: conditionFields,
43311
+ label,
43312
+ onChange
43313
+ }
43314
+ )
43315
+ ] }) : null
43316
+ ]
43317
+ }
43318
+ );
43319
+ }
43320
+ function NotifyOnAnswer({
43321
+ tableId,
43322
+ tableName: tableName2,
43323
+ formName,
43324
+ ruleId,
43325
+ notify,
43326
+ onRule,
43327
+ onNotify
43328
+ }) {
43329
+ const client = (0, import_react233.useRecordsClient)();
43330
+ const [subs, setSubs] = (0, import_react232.useState)(null);
43331
+ const [busy, setBusy] = (0, import_react232.useState)(false);
43332
+ const [error, setError] = (0, import_react232.useState)(null);
43333
+ const [said2, setSaid] = (0, import_react232.useState)(null);
43334
+ const [url, setUrl] = (0, import_react232.useState)(notify?.webhookUrl ?? "");
43335
+ (0, import_react232.useEffect)(() => setUrl(notify?.webhookUrl ?? ""), [notify?.webhookUrl]);
43336
+ (0, import_react232.useEffect)(() => {
43337
+ let cancelled = false;
43338
+ void client.subscriptions({ table_id: tableId }).then((r) => {
43339
+ if (!cancelled && r.ok) setSubs(r.data);
43340
+ });
43341
+ return () => {
43342
+ cancelled = true;
43343
+ };
43344
+ }, [client, tableId, ruleId]);
43345
+ const chosen = subs?.find((s) => s.rule_id === ruleId) ?? null;
43346
+ async function emailMe(on) {
43347
+ setBusy(true);
43348
+ setError(null);
43349
+ try {
43350
+ if (!on) {
43351
+ onRule(null);
43352
+ if (ruleId) await client.recordDelete({ record_id: ruleId });
43353
+ return;
43354
+ }
43355
+ const listed = await client.views({ table_id: tableId });
43356
+ if (!listed.ok) return setError(listed.error);
43357
+ let viewId = listed.data.find((v) => !v.filters || Object.keys(v.filters).length === 0)?.view_id ?? null;
43358
+ if (!viewId) {
43359
+ const made = await client.viewDeclare({ table_id: tableId, spec: { name: `Every ${tableName2}`, filters: null } });
43360
+ if (!made.ok) return setError(made.error);
43361
+ viewId = made.data;
43362
+ }
43363
+ const declared2 = await client.subscriptionDeclare({
43364
+ table_id: tableId,
43365
+ spec: {
43366
+ name: `${formName} \u2014 new answers`,
43367
+ saved_view_id: viewId,
43368
+ cadence: "instant",
43369
+ schedule: null,
43370
+ channel: "email",
43371
+ quiet_hours: null,
43372
+ recipient_user_id: client.config.actor.user_id ?? null,
43373
+ event_key: "records.changed"
43374
+ }
43375
+ });
43376
+ if (!declared2.ok) return setError(declared2.error);
43377
+ onRule(declared2.data);
43378
+ } finally {
43379
+ setBusy(false);
43380
+ }
43381
+ }
43382
+ async function saveWebhook() {
43383
+ const next = url.trim();
43384
+ if (next === (notify?.webhookUrl ?? "")) return;
43385
+ setBusy(true);
43386
+ setError(null);
43387
+ setSaid(null);
43388
+ try {
43389
+ if (next === "") {
43390
+ if (notify?.webhookAutomationId) await client.automationArchive({ automation_id: notify.webhookAutomationId });
43391
+ onNotify(null);
43392
+ return;
43393
+ }
43394
+ const declared2 = await client.automationDeclare({
43395
+ table_id: tableId,
43396
+ automation_id: notify?.webhookAutomationId ?? null,
43397
+ spec: { name: `${formName} \u2014 webhook`, trigger: { on: "form_answered" }, actions: [{ do: "webhook", url: next }] }
43398
+ });
43399
+ if (!declared2.ok) return setError(declared2.error);
43400
+ if (!declared2.data.ok) {
43401
+ setSaid(Object.values(declared2.data._errors)[0] ?? "Not saved");
43402
+ return;
43403
+ }
43404
+ onNotify({ webhookUrl: next, webhookAutomationId: declared2.data.automation_id });
43405
+ } finally {
43406
+ setBusy(false);
43407
+ }
43408
+ }
43409
+ return /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("section", { className: "flex flex-col gap-2 rounded border px-2.5 py-2 text-xs", "aria-label": "Notify on answer", "data-records-form-notify": "", children: [
43410
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "font-medium", children: "Notify on answer" }),
43411
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("div", { className: "flex flex-wrap items-center gap-x-4 gap-y-1", children: /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("label", { className: "flex items-center gap-2", title: "An email to you each time somebody answers.", children: [
43412
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.Checkbox, { checked: Boolean(ruleId), disabled: busy, onCheckedChange: (c) => void emailMe(c === true), "aria-label": "Email me" }),
43413
+ "Email me",
43414
+ chosen && chosen.channel !== "email" ? /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("span", { className: "text-muted-foreground", children: [
43415
+ "(",
43416
+ chosen.channel === "sms" ? "text" : "in app",
43417
+ ")"
43418
+ ] }) : null
43419
+ ] }) }),
43420
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("label", { className: "flex items-center gap-2", children: [
43421
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "shrink-0", children: "Webhook" }),
43422
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43423
+ import_design_system118.BasicInput,
43424
+ {
43425
+ type: "url",
43426
+ inputMode: "url",
43427
+ className: "h-8 text-xs",
43428
+ placeholder: "https://",
43429
+ "aria-label": "Webhook URL",
43430
+ value: url,
43431
+ disabled: busy,
43432
+ onChange: (e) => setUrl(e.target.value),
43433
+ onBlur: () => void saveWebhook(),
43434
+ onKeyDown: (e) => {
43435
+ if (e.key === "Enter") void saveWebhook();
43436
+ }
43437
+ }
43438
+ )
43439
+ ] }),
43440
+ said2 ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("p", { className: "text-destructive", children: said2 }) : null,
43441
+ error ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(RefusalLine, { error }) : null
43442
+ ] });
43443
+ }
43234
43444
 
43235
43445
  // src/DocTemplate.tsx
43236
- var import_react236 = require("react");
43237
- var import_react237 = require("@ai-matrx/records/react");
43238
- var import_design_system120 = require("@ai-matrx/design-system");
43239
- var import_jsx_runtime135 = require("react/jsx-runtime");
43446
+ var import_react234 = require("react");
43447
+ var import_react235 = require("@ai-matrx/records/react");
43448
+ var import_design_system119 = require("@ai-matrx/design-system");
43449
+ var import_jsx_runtime134 = require("react/jsx-runtime");
43240
43450
  function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, className }) {
43241
- const client = (0, import_react237.useRecordsClient)();
43451
+ const client = (0, import_react235.useRecordsClient)();
43242
43452
  const claimSeed = useSeedGuard();
43243
- const table = (0, import_react237.useTable)(tableId);
43453
+ const table = (0, import_react235.useTable)(tableId);
43244
43454
  const rights = useTableRights(table.data);
43245
- const fields = (0, import_react237.useFields)(tableId);
43246
- const [templates, setTemplates] = (0, import_react236.useState)(null);
43247
- const [error, setError] = (0, import_react236.useState)(null);
43248
- const [activeId, setActiveId] = (0, import_react236.useState)(activeTemplateId ?? null);
43249
- const [draftName, setDraftName] = (0, import_react236.useState)("");
43250
- const [draftBody, setDraftBody] = (0, import_react236.useState)("");
43251
- const [unresolved, setUnresolved] = (0, import_react236.useState)([]);
43252
- const [saving, setSaving] = (0, import_react236.useState)(false);
43253
- const load = (0, import_react236.useCallback)(async () => {
43455
+ const fields = (0, import_react235.useFields)(tableId);
43456
+ const [templates, setTemplates] = (0, import_react234.useState)(null);
43457
+ const [error, setError] = (0, import_react234.useState)(null);
43458
+ const [activeId, setActiveId] = (0, import_react234.useState)(activeTemplateId ?? null);
43459
+ const [draftName, setDraftName] = (0, import_react234.useState)("");
43460
+ const [draftBody, setDraftBody] = (0, import_react234.useState)("");
43461
+ const [unresolved, setUnresolved] = (0, import_react234.useState)([]);
43462
+ const [saving, setSaving] = (0, import_react234.useState)(false);
43463
+ const load = (0, import_react234.useCallback)(async () => {
43254
43464
  const held = await client.docTemplates({ table_id: tableId });
43255
43465
  if (!held.ok) {
43256
43466
  setError(held.error);
@@ -43281,10 +43491,10 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
43281
43491
  setError(null);
43282
43492
  setTemplates(rows);
43283
43493
  }, [client, tableId, seed, fields.data]);
43284
- (0, import_react236.useEffect)(() => {
43494
+ (0, import_react234.useEffect)(() => {
43285
43495
  void load();
43286
43496
  }, [load]);
43287
- (0, import_react236.useEffect)(() => {
43497
+ (0, import_react234.useEffect)(() => {
43288
43498
  if (!templates || templates.length === 0) return;
43289
43499
  const chosen = templates.find((t) => t.id === (activeTemplateId ?? activeId)) ?? templates[0];
43290
43500
  if (chosen.id !== activeId) setActiveId(chosen.id);
@@ -43292,7 +43502,7 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
43292
43502
  setDraftBody(chosen.body);
43293
43503
  onActiveTemplate?.(chosen);
43294
43504
  }, [templates, activeTemplateId]);
43295
- (0, import_react236.useEffect)(() => {
43505
+ (0, import_react234.useEffect)(() => {
43296
43506
  let cancelled = false;
43297
43507
  if (draftBody.trim() === "") {
43298
43508
  setUnresolved([]);
@@ -43322,25 +43532,25 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
43322
43532
  setActiveId(saved.data);
43323
43533
  await load();
43324
43534
  }
43325
- if (fields.error) return /* @__PURE__ */ (0, import_jsx_runtime135.jsx)(RefusalNotice, { error: fields.error, className });
43535
+ if (fields.error) return /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(RefusalNotice, { error: fields.error, className });
43326
43536
  if (error) {
43327
- return /* @__PURE__ */ (0, import_jsx_runtime135.jsx)(
43537
+ return /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
43328
43538
  RefusalNotice,
43329
43539
  {
43330
43540
  error,
43331
43541
  className,
43332
- actions: /* @__PURE__ */ (0, import_jsx_runtime135.jsx)(import_design_system120.Button, { size: "sm", variant: "ghost", onClick: () => void load(), children: "Try again" })
43542
+ actions: /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(import_design_system119.Button, { size: "sm", variant: "ghost", onClick: () => void load(), children: "Try again" })
43333
43543
  }
43334
43544
  );
43335
43545
  }
43336
- if (templates === null || fields.loading && !fields.data) return /* @__PURE__ */ (0, import_jsx_runtime135.jsx)(import_design_system120.Skeleton, { className: (0, import_design_system120.cn)("h-64 w-full", className) });
43546
+ if (templates === null || fields.loading && !fields.data) return /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(import_design_system119.Skeleton, { className: (0, import_design_system119.cn)("h-64 w-full", className) });
43337
43547
  if (!rights.admin) {
43338
- return /* @__PURE__ */ (0, import_jsx_runtime135.jsx)("p", { className: (0, import_design_system120.cn)("text-xs text-muted-foreground", className), children: rights.why("structure") });
43548
+ return /* @__PURE__ */ (0, import_jsx_runtime134.jsx)("p", { className: (0, import_design_system119.cn)("text-xs text-muted-foreground", className), children: rights.why("structure") });
43339
43549
  }
43340
- return /* @__PURE__ */ (0, import_jsx_runtime135.jsxs)("div", { className: (0, import_design_system120.cn)("flex flex-col gap-2", className), children: [
43341
- /* @__PURE__ */ (0, import_jsx_runtime135.jsxs)("div", { className: "flex items-center gap-1 overflow-x-auto", role: "group", "aria-label": "Templates", children: [
43342
- templates.map((t) => /* @__PURE__ */ (0, import_jsx_runtime135.jsxs)(
43343
- import_design_system120.Button,
43550
+ return /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("div", { className: (0, import_design_system119.cn)("flex flex-col gap-2", className), children: [
43551
+ /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("div", { className: "flex items-center gap-1 overflow-x-auto", role: "group", "aria-label": "Templates", children: [
43552
+ templates.map((t) => /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)(
43553
+ import_design_system119.Button,
43344
43554
  {
43345
43555
  size: "sm",
43346
43556
  variant: t.id === activeId ? "secondary" : "ghost",
@@ -43352,7 +43562,7 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
43352
43562
  },
43353
43563
  children: [
43354
43564
  t.name,
43355
- /* @__PURE__ */ (0, import_jsx_runtime135.jsxs)("span", { className: "ml-1 text-[10px] opacity-70", children: [
43565
+ /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("span", { className: "ml-1 text-[10px] opacity-70", children: [
43356
43566
  "v",
43357
43567
  t.template_version
43358
43568
  ] })
@@ -43360,8 +43570,8 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
43360
43570
  },
43361
43571
  t.id
43362
43572
  )),
43363
- /* @__PURE__ */ (0, import_jsx_runtime135.jsx)(
43364
- import_design_system120.Button,
43573
+ /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
43574
+ import_design_system119.Button,
43365
43575
  {
43366
43576
  size: "sm",
43367
43577
  variant: "ghost",
@@ -43373,16 +43583,16 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
43373
43583
  children: "New template"
43374
43584
  }
43375
43585
  ),
43376
- /* @__PURE__ */ (0, import_jsx_runtime135.jsx)(import_design_system120.Button, { size: "sm", className: "ml-auto", disabled: saving, onClick: () => void save(), children: saving ? "Saving\u2026" : "Save" })
43586
+ /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(import_design_system119.Button, { size: "sm", className: "ml-auto", disabled: saving, onClick: () => void save(), children: saving ? "Saving\u2026" : "Save" })
43377
43587
  ] }),
43378
- /* @__PURE__ */ (0, import_jsx_runtime135.jsxs)("label", { className: "flex flex-col gap-1", children: [
43379
- /* @__PURE__ */ (0, import_jsx_runtime135.jsx)(import_design_system120.Label, { className: "text-xs font-medium", children: "Name" }),
43380
- /* @__PURE__ */ (0, import_jsx_runtime135.jsx)(import_design_system120.BasicInput, { value: draftName, onChange: (e) => setDraftName(e.target.value), placeholder: "Agreement" })
43588
+ /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("label", { className: "flex flex-col gap-1", children: [
43589
+ /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(import_design_system119.Label, { className: "text-xs font-medium", children: "Name" }),
43590
+ /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(import_design_system119.BasicInput, { value: draftName, onChange: (e) => setDraftName(e.target.value), placeholder: "Agreement" })
43381
43591
  ] }),
43382
- /* @__PURE__ */ (0, import_jsx_runtime135.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
43383
- /* @__PURE__ */ (0, import_jsx_runtime135.jsx)("span", { className: "text-xs text-muted-foreground", children: "Insert" }),
43384
- (fields.data ?? []).map((field2) => /* @__PURE__ */ (0, import_jsx_runtime135.jsx)(
43385
- import_design_system120.Button,
43592
+ /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
43593
+ /* @__PURE__ */ (0, import_jsx_runtime134.jsx)("span", { className: "text-xs text-muted-foreground", children: "Insert" }),
43594
+ (fields.data ?? []).map((field2) => /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
43595
+ import_design_system119.Button,
43386
43596
  {
43387
43597
  size: "sm",
43388
43598
  variant: "ghost",
@@ -43393,8 +43603,8 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
43393
43603
  field2.id
43394
43604
  ))
43395
43605
  ] }),
43396
- /* @__PURE__ */ (0, import_jsx_runtime135.jsx)(
43397
- import_design_system120.BasicTextarea,
43606
+ /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
43607
+ import_design_system119.BasicTextarea,
43398
43608
  {
43399
43609
  rows: 10,
43400
43610
  "aria-label": "Template body",
@@ -43403,33 +43613,33 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
43403
43613
  onChange: (e) => setDraftBody(e.target.value)
43404
43614
  }
43405
43615
  ),
43406
- unresolved.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime135.jsx)("ul", { className: "flex flex-col gap-0.5 rounded border border-dashed px-2 py-1 text-xs", children: unresolved.map((token) => /* @__PURE__ */ (0, import_jsx_runtime135.jsxs)("li", { className: "flex items-baseline gap-1", children: [
43407
- /* @__PURE__ */ (0, import_jsx_runtime135.jsx)("code", { className: "text-destructive", children: token.raw }),
43408
- /* @__PURE__ */ (0, import_jsx_runtime135.jsx)("span", { "aria-hidden": "true", children: "\u2014" }),
43409
- /* @__PURE__ */ (0, import_jsx_runtime135.jsx)(
43616
+ unresolved.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime134.jsx)("ul", { className: "flex flex-col gap-0.5 rounded border border-dashed px-2 py-1 text-xs", children: unresolved.map((token) => /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("li", { className: "flex items-baseline gap-1", children: [
43617
+ /* @__PURE__ */ (0, import_jsx_runtime134.jsx)("code", { className: "text-destructive", children: token.raw }),
43618
+ /* @__PURE__ */ (0, import_jsx_runtime134.jsx)("span", { "aria-hidden": "true", children: "\u2014" }),
43619
+ /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
43410
43620
  RefusalLine,
43411
43621
  {
43412
43622
  error: refusal("invalid_argument", token.why, "Change it to a Field this table has, or take it out.")
43413
43623
  }
43414
43624
  )
43415
- ] }, token.raw)) }) : draftBody.trim() !== "" ? /* @__PURE__ */ (0, import_jsx_runtime135.jsx)("p", { className: "text-xs text-muted-foreground", children: "Every token in this body points at a Field this table can answer." }) : null
43625
+ ] }, token.raw)) }) : draftBody.trim() !== "" ? /* @__PURE__ */ (0, import_jsx_runtime134.jsx)("p", { className: "text-xs text-muted-foreground", children: "Every token in this body points at a Field this table can answer." }) : null
43416
43626
  ] });
43417
43627
  }
43418
43628
 
43419
43629
  // src/DocRender.tsx
43420
- var import_react238 = require("react");
43421
- var import_react239 = require("@ai-matrx/records/react");
43422
- var import_design_system121 = require("@ai-matrx/design-system");
43423
- var import_jsx_runtime136 = require("react/jsx-runtime");
43630
+ var import_react236 = require("react");
43631
+ var import_react237 = require("@ai-matrx/records/react");
43632
+ var import_design_system120 = require("@ai-matrx/design-system");
43633
+ var import_jsx_runtime135 = require("react/jsx-runtime");
43424
43634
  function DocRender({ templateId, recordId, filename, onRendered, className }) {
43425
- const client = (0, import_react239.useRecordsClient)();
43426
- const [preview, setPreview] = (0, import_react238.useState)(null);
43427
- const [renders, setRenders] = (0, import_react238.useState)(null);
43428
- const [showing, setShowing] = (0, import_react238.useState)(null);
43429
- const [error, setError] = (0, import_react238.useState)(null);
43430
- const [busy, setBusy] = (0, import_react238.useState)(null);
43431
- const paper = (0, import_react238.useRef)(null);
43432
- const load = (0, import_react238.useCallback)(async () => {
43635
+ const client = (0, import_react237.useRecordsClient)();
43636
+ const [preview, setPreview] = (0, import_react236.useState)(null);
43637
+ const [renders, setRenders] = (0, import_react236.useState)(null);
43638
+ const [showing, setShowing] = (0, import_react236.useState)(null);
43639
+ const [error, setError] = (0, import_react236.useState)(null);
43640
+ const [busy, setBusy] = (0, import_react236.useState)(null);
43641
+ const paper = (0, import_react236.useRef)(null);
43642
+ const load = (0, import_react236.useCallback)(async () => {
43433
43643
  const [body, held] = await Promise.all([
43434
43644
  client.docRenderBody({ template_id: templateId, record_id: recordId }),
43435
43645
  client.docRenders({ record_id: recordId })
@@ -43446,7 +43656,7 @@ function DocRender({ templateId, recordId, filename, onRendered, className }) {
43446
43656
  setPreview(body.data);
43447
43657
  setRenders(held.data.filter((r) => r.template_id === templateId));
43448
43658
  }, [client, templateId, recordId]);
43449
- (0, import_react238.useEffect)(() => {
43659
+ (0, import_react236.useEffect)(() => {
43450
43660
  void load();
43451
43661
  }, [load]);
43452
43662
  async function freeze() {
@@ -43486,22 +43696,22 @@ function DocRender({ templateId, recordId, filename, onRendered, className }) {
43486
43696
  }
43487
43697
  }
43488
43698
  if (error) {
43489
- return /* @__PURE__ */ (0, import_jsx_runtime136.jsx)(
43699
+ return /* @__PURE__ */ (0, import_jsx_runtime135.jsx)(
43490
43700
  RefusalNotice,
43491
43701
  {
43492
43702
  error,
43493
43703
  className,
43494
- actions: /* @__PURE__ */ (0, import_jsx_runtime136.jsx)(import_design_system121.Button, { size: "sm", variant: "ghost", onClick: () => void load(), children: "Try again" })
43704
+ actions: /* @__PURE__ */ (0, import_jsx_runtime135.jsx)(import_design_system120.Button, { size: "sm", variant: "ghost", onClick: () => void load(), children: "Try again" })
43495
43705
  }
43496
43706
  );
43497
43707
  }
43498
- if (preview === null || renders === null) return /* @__PURE__ */ (0, import_jsx_runtime136.jsx)(import_design_system121.Skeleton, { className: (0, import_design_system121.cn)("h-64 w-full", className) });
43708
+ if (preview === null || renders === null) return /* @__PURE__ */ (0, import_jsx_runtime135.jsx)(import_design_system120.Skeleton, { className: (0, import_design_system120.cn)("h-64 w-full", className) });
43499
43709
  const frozen = showing ? renders.find((r) => r.id === showing) ?? null : null;
43500
43710
  const text2 = frozen ? frozen.body : preview;
43501
- return /* @__PURE__ */ (0, import_jsx_runtime136.jsxs)("div", { className: (0, import_design_system121.cn)("flex flex-col gap-2", className), children: [
43502
- /* @__PURE__ */ (0, import_jsx_runtime136.jsxs)("div", { className: "flex items-center gap-1 text-xs", children: [
43503
- /* @__PURE__ */ (0, import_jsx_runtime136.jsx)(
43504
- import_design_system121.Button,
43711
+ return /* @__PURE__ */ (0, import_jsx_runtime135.jsxs)("div", { className: (0, import_design_system120.cn)("flex flex-col gap-2", className), children: [
43712
+ /* @__PURE__ */ (0, import_jsx_runtime135.jsxs)("div", { className: "flex items-center gap-1 text-xs", children: [
43713
+ /* @__PURE__ */ (0, import_jsx_runtime135.jsx)(
43714
+ import_design_system120.Button,
43505
43715
  {
43506
43716
  size: "sm",
43507
43717
  variant: frozen ? "ghost" : "secondary",
@@ -43510,8 +43720,8 @@ function DocRender({ templateId, recordId, filename, onRendered, className }) {
43510
43720
  children: "As it reads now"
43511
43721
  }
43512
43722
  ),
43513
- renders.map((render) => /* @__PURE__ */ (0, import_jsx_runtime136.jsx)(
43514
- import_design_system121.Button,
43723
+ renders.map((render) => /* @__PURE__ */ (0, import_jsx_runtime135.jsx)(
43724
+ import_design_system120.Button,
43515
43725
  {
43516
43726
  size: "sm",
43517
43727
  variant: render.id === showing ? "secondary" : "ghost",
@@ -43522,33 +43732,33 @@ function DocRender({ templateId, recordId, filename, onRendered, className }) {
43522
43732
  },
43523
43733
  render.id
43524
43734
  )),
43525
- /* @__PURE__ */ (0, import_jsx_runtime136.jsxs)("span", { className: "ml-auto flex items-center gap-1", children: [
43526
- /* @__PURE__ */ (0, import_jsx_runtime136.jsx)(import_design_system121.Button, { size: "sm", disabled: busy !== null, onClick: () => void freeze(), children: busy === "freeze" ? "Freezing\u2026" : "Freeze this version" }),
43527
- /* @__PURE__ */ (0, import_jsx_runtime136.jsx)(import_design_system121.Button, { size: "sm", variant: "ghost", disabled: busy !== null, onClick: () => void toPdf(), children: busy === "pdf" ? "Making the PDF\u2026" : "PDF" })
43735
+ /* @__PURE__ */ (0, import_jsx_runtime135.jsxs)("span", { className: "ml-auto flex items-center gap-1", children: [
43736
+ /* @__PURE__ */ (0, import_jsx_runtime135.jsx)(import_design_system120.Button, { size: "sm", disabled: busy !== null, onClick: () => void freeze(), children: busy === "freeze" ? "Freezing\u2026" : "Freeze this version" }),
43737
+ /* @__PURE__ */ (0, import_jsx_runtime135.jsx)(import_design_system120.Button, { size: "sm", variant: "ghost", disabled: busy !== null, onClick: () => void toPdf(), children: busy === "pdf" ? "Making the PDF\u2026" : "PDF" })
43528
43738
  ] })
43529
43739
  ] }),
43530
- /* @__PURE__ */ (0, import_jsx_runtime136.jsx)("div", { ref: paper, className: "whitespace-pre-wrap rounded border bg-background p-4 text-sm text-foreground", children: text2 }),
43531
- /* @__PURE__ */ (0, import_jsx_runtime136.jsx)("p", { className: "text-xs text-muted-foreground", children: frozen ? `Frozen ${new Date(frozen.rendered_at).toLocaleString()} from template v${frozen.template_version}. Its text and its SHA-256 (${frozen.content_hash.slice(0, 12)}\u2026) are what any signature seals.` : "This is how the template reads against this record right now. Nothing has been frozen, so there is no version for anyone to sign yet." })
43740
+ /* @__PURE__ */ (0, import_jsx_runtime135.jsx)("div", { ref: paper, className: "whitespace-pre-wrap rounded border bg-background p-4 text-sm text-foreground", children: text2 }),
43741
+ /* @__PURE__ */ (0, import_jsx_runtime135.jsx)("p", { className: "text-xs text-muted-foreground", children: frozen ? `Frozen ${new Date(frozen.rendered_at).toLocaleString()} from template v${frozen.template_version}. Its text and its SHA-256 (${frozen.content_hash.slice(0, 12)}\u2026) are what any signature seals.` : "This is how the template reads against this record right now. Nothing has been frozen, so there is no version for anyone to sign yet." })
43532
43742
  ] });
43533
43743
  }
43534
43744
 
43535
43745
  // src/SignBlock.tsx
43536
- var import_react240 = require("react");
43537
- var import_react241 = require("@ai-matrx/records/react");
43538
- var import_design_system122 = require("@ai-matrx/design-system");
43539
- var import_react242 = require("@ai-matrx/records/react");
43540
- var import_jsx_runtime137 = require("react/jsx-runtime");
43746
+ var import_react238 = require("react");
43747
+ var import_react239 = require("@ai-matrx/records/react");
43748
+ var import_design_system121 = require("@ai-matrx/design-system");
43749
+ var import_react240 = require("@ai-matrx/records/react");
43750
+ var import_jsx_runtime136 = require("react/jsx-runtime");
43541
43751
  function SignBlock({ tableId, recordId, render, className }) {
43542
- const client = (0, import_react241.useRecordsClient)();
43543
- const table = (0, import_react242.useTable)(tableId);
43752
+ const client = (0, import_react239.useRecordsClient)();
43753
+ const table = (0, import_react240.useTable)(tableId);
43544
43754
  const rights = useTableRights(table.data);
43545
- const fields = (0, import_react241.useFields)(tableId);
43546
- const [signatures, setSignatures] = (0, import_react240.useState)(null);
43547
- const [verdicts, setVerdicts] = (0, import_react240.useState)({});
43548
- const [error, setError] = (0, import_react240.useState)(null);
43549
- const [name, setName] = (0, import_react240.useState)("");
43550
- const [busy, setBusy] = (0, import_react240.useState)(false);
43551
- const load = (0, import_react240.useCallback)(async () => {
43755
+ const fields = (0, import_react239.useFields)(tableId);
43756
+ const [signatures, setSignatures] = (0, import_react238.useState)(null);
43757
+ const [verdicts, setVerdicts] = (0, import_react238.useState)({});
43758
+ const [error, setError] = (0, import_react238.useState)(null);
43759
+ const [name, setName] = (0, import_react238.useState)("");
43760
+ const [busy, setBusy] = (0, import_react238.useState)(false);
43761
+ const load = (0, import_react238.useCallback)(async () => {
43552
43762
  const held = await client.docSignatures({ record_id: recordId });
43553
43763
  if (!held.ok) {
43554
43764
  setError(held.error);
@@ -43563,7 +43773,7 @@ function SignBlock({ tableId, recordId, render, className }) {
43563
43773
  }
43564
43774
  setVerdicts(answers);
43565
43775
  }, [client, recordId]);
43566
- (0, import_react240.useEffect)(() => {
43776
+ (0, import_react238.useEffect)(() => {
43567
43777
  void load();
43568
43778
  }, [load]);
43569
43779
  async function sign(field2) {
@@ -43583,36 +43793,36 @@ function SignBlock({ tableId, recordId, render, className }) {
43583
43793
  setName("");
43584
43794
  await load();
43585
43795
  }
43586
- if (fields.error) return /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(RefusalNotice, { error: fields.error, className });
43796
+ if (fields.error) return /* @__PURE__ */ (0, import_jsx_runtime136.jsx)(RefusalNotice, { error: fields.error, className });
43587
43797
  if (error) {
43588
- return /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(
43798
+ return /* @__PURE__ */ (0, import_jsx_runtime136.jsx)(
43589
43799
  RefusalNotice,
43590
43800
  {
43591
43801
  error,
43592
43802
  className,
43593
- actions: /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(import_design_system122.Button, { size: "sm", variant: "ghost", onClick: () => void load(), children: "Try again" })
43803
+ actions: /* @__PURE__ */ (0, import_jsx_runtime136.jsx)(import_design_system121.Button, { size: "sm", variant: "ghost", onClick: () => void load(), children: "Try again" })
43594
43804
  }
43595
43805
  );
43596
43806
  }
43597
- if (fields.loading || signatures === null) return /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(import_design_system122.Skeleton, { className: (0, import_design_system122.cn)("h-32 w-full", className) });
43807
+ if (fields.loading || signatures === null) return /* @__PURE__ */ (0, import_jsx_runtime136.jsx)(import_design_system121.Skeleton, { className: (0, import_design_system121.cn)("h-32 w-full", className) });
43598
43808
  const signable = (fields.data ?? []).filter(isSignatureField);
43599
- return /* @__PURE__ */ (0, import_jsx_runtime137.jsxs)("div", { className: (0, import_design_system122.cn)("flex flex-col gap-2", className), children: [
43600
- /* @__PURE__ */ (0, import_jsx_runtime137.jsxs)("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
43601
- /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("span", { className: "font-medium text-foreground", children: "Signatures" }),
43602
- /* @__PURE__ */ (0, import_jsx_runtime137.jsxs)("span", { children: [
43809
+ return /* @__PURE__ */ (0, import_jsx_runtime136.jsxs)("div", { className: (0, import_design_system121.cn)("flex flex-col gap-2", className), children: [
43810
+ /* @__PURE__ */ (0, import_jsx_runtime136.jsxs)("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
43811
+ /* @__PURE__ */ (0, import_jsx_runtime136.jsx)("span", { className: "font-medium text-foreground", children: "Signatures" }),
43812
+ /* @__PURE__ */ (0, import_jsx_runtime136.jsxs)("span", { children: [
43603
43813
  signatures.length,
43604
43814
  " on this record"
43605
43815
  ] })
43606
43816
  ] }),
43607
- signatures.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("ul", { className: "flex flex-col gap-1", children: signatures.map((signature) => {
43817
+ signatures.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime136.jsx)("ul", { className: "flex flex-col gap-1", children: signatures.map((signature) => {
43608
43818
  const verdict = verdicts[signature.id];
43609
43819
  const intact = typeof verdict === "object" && verdict !== null ? verdict.intact : void 0;
43610
- return /* @__PURE__ */ (0, import_jsx_runtime137.jsxs)("li", { className: "rounded border px-2 py-1.5 text-xs", children: [
43611
- /* @__PURE__ */ (0, import_jsx_runtime137.jsxs)("div", { className: "flex items-center gap-2", children: [
43612
- /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("span", { className: "font-medium", children: signature.signer_name }),
43613
- /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("span", { className: "text-muted-foreground", children: signature.field_key }),
43614
- /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("span", { className: "text-muted-foreground", children: new Date(signature.signed_at).toLocaleString() }),
43615
- /* @__PURE__ */ (0, import_jsx_runtime137.jsxs)("span", { className: "ml-auto font-mono text-[10px] text-muted-foreground", children: [
43820
+ return /* @__PURE__ */ (0, import_jsx_runtime136.jsxs)("li", { className: "rounded border px-2 py-1.5 text-xs", children: [
43821
+ /* @__PURE__ */ (0, import_jsx_runtime136.jsxs)("div", { className: "flex items-center gap-2", children: [
43822
+ /* @__PURE__ */ (0, import_jsx_runtime136.jsx)("span", { className: "font-medium", children: signature.signer_name }),
43823
+ /* @__PURE__ */ (0, import_jsx_runtime136.jsx)("span", { className: "text-muted-foreground", children: signature.field_key }),
43824
+ /* @__PURE__ */ (0, import_jsx_runtime136.jsx)("span", { className: "text-muted-foreground", children: new Date(signature.signed_at).toLocaleString() }),
43825
+ /* @__PURE__ */ (0, import_jsx_runtime136.jsxs)("span", { className: "ml-auto font-mono text-[10px] text-muted-foreground", children: [
43616
43826
  "v",
43617
43827
  signature.document_version,
43618
43828
  " \xB7 ",
@@ -43622,7 +43832,7 @@ function SignBlock({ tableId, recordId, render, className }) {
43622
43832
  intact === false ? (
43623
43833
  // THE STORE REFUSED TO CALL IT INTACT: its reason goes through the one
43624
43834
  // formatter, with what to do next, never raw in red (lane REFUSAL-SWEEP).
43625
- /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(
43835
+ /* @__PURE__ */ (0, import_jsx_runtime136.jsx)(
43626
43836
  RefusalLine,
43627
43837
  {
43628
43838
  className: "mt-0.5",
@@ -43633,22 +43843,22 @@ function SignBlock({ tableId, recordId, render, className }) {
43633
43843
  )
43634
43844
  }
43635
43845
  )
43636
- ) : /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("p", { className: "mt-0.5 text-muted-foreground", children: intact === true ? "The store says what was signed is still exactly what is there." : String(verdict ?? "") })
43846
+ ) : /* @__PURE__ */ (0, import_jsx_runtime136.jsx)("p", { className: "mt-0.5 text-muted-foreground", children: intact === true ? "The store says what was signed is still exactly what is there." : String(verdict ?? "") })
43637
43847
  ] }, signature.id);
43638
43848
  }) }) : null,
43639
- !render ? /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("p", { className: "text-xs text-muted-foreground", children: 'Nothing has been frozen yet, so there is no version to sign. A signature seals one exact document version and its hash \u2014 "the document as it reads today" is not something anyone can agree to.' }) : signable.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime137.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
43849
+ !render ? /* @__PURE__ */ (0, import_jsx_runtime136.jsx)("p", { className: "text-xs text-muted-foreground", children: 'Nothing has been frozen yet, so there is no version to sign. A signature seals one exact document version and its hash \u2014 "the document as it reads today" is not something anyone can agree to.' }) : signable.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime136.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
43640
43850
  "This table has no signature Field, so there is nowhere to write a signature. A signature is a Value on a text Field whose format is ",
43641
- /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("code", { children: "signature" }),
43851
+ /* @__PURE__ */ (0, import_jsx_runtime136.jsx)("code", { children: "signature" }),
43642
43852
  " \u2014 add one and the signing appears."
43643
- ] }) : !rights.write ? /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("p", { className: "text-xs text-muted-foreground", children: rights.why("write") }) : /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("div", { className: "flex flex-col gap-1", children: signable.map((field2) => {
43853
+ ] }) : !rights.write ? /* @__PURE__ */ (0, import_jsx_runtime136.jsx)("p", { className: "text-xs text-muted-foreground", children: rights.why("write") }) : /* @__PURE__ */ (0, import_jsx_runtime136.jsx)("div", { className: "flex flex-col gap-1", children: signable.map((field2) => {
43644
43854
  const already = signatures.find((s) => s.field_key === field2.key);
43645
43855
  if (already) {
43646
- return /* @__PURE__ */ (0, import_jsx_runtime137.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
43856
+ return /* @__PURE__ */ (0, import_jsx_runtime136.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
43647
43857
  fieldName(field2),
43648
43858
  " is signed, and a signature is immutable once made. A further agreement is a further Field with its own signature, or a further version with its own seal."
43649
43859
  ] }, field2.id);
43650
43860
  }
43651
- return /* @__PURE__ */ (0, import_jsx_runtime137.jsxs)(
43861
+ return /* @__PURE__ */ (0, import_jsx_runtime136.jsxs)(
43652
43862
  "form",
43653
43863
  {
43654
43864
  className: "flex items-center gap-1",
@@ -43657,8 +43867,8 @@ function SignBlock({ tableId, recordId, render, className }) {
43657
43867
  if (name.trim() !== "") void sign(field2);
43658
43868
  },
43659
43869
  children: [
43660
- /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(
43661
- import_design_system122.BasicInput,
43870
+ /* @__PURE__ */ (0, import_jsx_runtime136.jsx)(
43871
+ import_design_system121.BasicInput,
43662
43872
  {
43663
43873
  className: "h-8 w-56 text-xs",
43664
43874
  "aria-label": `Sign ${fieldName(field2)}`,
@@ -43667,8 +43877,8 @@ function SignBlock({ tableId, recordId, render, className }) {
43667
43877
  onChange: (e) => setName(e.target.value)
43668
43878
  }
43669
43879
  ),
43670
- /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(import_design_system122.Button, { size: "sm", type: "submit", disabled: busy || name.trim() === "", children: "Sign" }),
43671
- /* @__PURE__ */ (0, import_jsx_runtime137.jsxs)("span", { className: "text-xs text-muted-foreground", children: [
43880
+ /* @__PURE__ */ (0, import_jsx_runtime136.jsx)(import_design_system121.Button, { size: "sm", type: "submit", disabled: busy || name.trim() === "", children: "Sign" }),
43881
+ /* @__PURE__ */ (0, import_jsx_runtime136.jsxs)("span", { className: "text-xs text-muted-foreground", children: [
43672
43882
  "seals version ",
43673
43883
  render.template_version,
43674
43884
  " \xB7 ",
@@ -43683,15 +43893,270 @@ function SignBlock({ tableId, recordId, render, className }) {
43683
43893
  ] });
43684
43894
  }
43685
43895
 
43896
+ // src/NotifyRuleEditor.tsx
43897
+ var import_react241 = require("react");
43898
+ var import_react242 = require("@ai-matrx/records/react");
43899
+ var import_records57 = require("@ai-matrx/records");
43900
+ var import_design_system122 = require("@ai-matrx/design-system");
43901
+ var import_jsx_runtime137 = require("react/jsx-runtime");
43902
+ var CADENCE_WORDS2 = SUBSCRIPTION_CADENCE_LABEL;
43903
+ var CHANNEL_WORDS3 = SUBSCRIPTION_CHANNEL_LABEL;
43904
+ function NotifyRuleEditor({ tableId, seed, chosenRuleId, onChoose, className }) {
43905
+ const client = (0, import_react242.useRecordsClient)();
43906
+ const host = useRecordsUi();
43907
+ const table = (0, import_react242.useTable)(tableId);
43908
+ const rights = useTableRights(table.data);
43909
+ const [subscriptions, setSubscriptions] = (0, import_react241.useState)(null);
43910
+ const [cadences, setCadences] = (0, import_react241.useState)([]);
43911
+ const [views, setViews] = (0, import_react241.useState)(null);
43912
+ const [error, setError] = (0, import_react241.useState)(null);
43913
+ const [busy, setBusy] = (0, import_react241.useState)(false);
43914
+ const [stopping, setStopping] = (0, import_react241.useState)(null);
43915
+ const load = (0, import_react241.useCallback)(async () => {
43916
+ const [held, offered] = await Promise.all([
43917
+ // THE PERSON'S OWN DOOR, not the notifier's. It answers what is addressed
43918
+ // to them plus — only where they hold admin on this Table — anyone's over
43919
+ // it, and it is narrowed to Tables they can already open.
43920
+ client.subscriptions({ table_id: tableId }),
43921
+ // The cadences are ASKED of the store, never a list typed into a picker
43922
+ // here that could drift from what the digest runner understands.
43923
+ client.subscriptionCadences()
43924
+ ]);
43925
+ if (!held.ok) {
43926
+ setError(held.error);
43927
+ return;
43928
+ }
43929
+ setError(null);
43930
+ setSubscriptions(held.data);
43931
+ if (offered.ok) setCadences(offered.data);
43932
+ if (host.savedViews) {
43933
+ setViews(await host.savedViews());
43934
+ return;
43935
+ }
43936
+ const listed = await client.views({ table_id: tableId });
43937
+ if (!listed.ok) {
43938
+ setError(listed.error);
43939
+ return;
43940
+ }
43941
+ setViews(listed.data.map((v) => ({ id: v.view_id, name: v.name })));
43942
+ }, [client, host, tableId]);
43943
+ (0, import_react241.useEffect)(() => {
43944
+ void load();
43945
+ }, [load]);
43946
+ const write = (0, import_react241.useCallback)(
43947
+ async (spec) => {
43948
+ const declared2 = await client.subscriptionDeclare({
43949
+ table_id: tableId,
43950
+ spec: {
43951
+ name: spec.name,
43952
+ saved_view_id: spec.savedViewId,
43953
+ cadence: spec.cadence ?? "instant",
43954
+ schedule: spec.schedule ?? null,
43955
+ channel: spec.channel ?? "in_app",
43956
+ quiet_hours: spec.quietHours ?? null,
43957
+ // "Tell me" is the default, and the store refuses a subscription with
43958
+ // nobody to tell rather than writing one that fires at nobody.
43959
+ recipient_user_id: spec.recipientUserId ?? client.config.actor.user_id ?? null,
43960
+ event_key: spec.eventKey ?? "records.changed"
43961
+ }
43962
+ });
43963
+ if (!declared2.ok) {
43964
+ setError(declared2.error);
43965
+ return null;
43966
+ }
43967
+ return declared2.data;
43968
+ },
43969
+ [client, tableId]
43970
+ );
43971
+ (0, import_react241.useEffect)(() => {
43972
+ if (!subscriptions || !seed || seed.length === 0) return;
43973
+ const missing = seed.filter((s) => !subscriptions.some((held) => held.name === s.name));
43974
+ if (missing.length === 0) return;
43975
+ void (async () => {
43976
+ for (const spec of missing) {
43977
+ if (!await write(spec)) return;
43978
+ }
43979
+ await load();
43980
+ })();
43981
+ }, [subscriptions, seed, write, load]);
43982
+ async function subscribe(viewId, viewName, cadence, channel, schedule, quiet2) {
43983
+ setBusy(true);
43984
+ const ok = await write({
43985
+ name: `${viewName} \u2014 ${CADENCE_WORDS2[cadence] ?? cadence}`,
43986
+ savedViewId: viewId,
43987
+ cadence,
43988
+ channel,
43989
+ schedule: schedule.trim() === "" ? null : schedule.trim(),
43990
+ quietHours: quiet2
43991
+ });
43992
+ setBusy(false);
43993
+ if (!ok) return;
43994
+ onChoose?.(ok);
43995
+ await load();
43996
+ }
43997
+ async function makeEveryRecordView() {
43998
+ setBusy(true);
43999
+ const made = await client.viewDeclare({
44000
+ table_id: tableId,
44001
+ spec: { name: `Every ${table.data?.name ?? "record"}`, filters: null }
44002
+ });
44003
+ setBusy(false);
44004
+ if (!made.ok) {
44005
+ setError(made.error);
44006
+ return;
44007
+ }
44008
+ await load();
44009
+ }
44010
+ async function unsubscribe(ruleId) {
44011
+ setStopping(null);
44012
+ setBusy(true);
44013
+ const deleted = await client.recordDelete({ record_id: ruleId });
44014
+ setBusy(false);
44015
+ if (!deleted.ok) {
44016
+ setError(deleted.error);
44017
+ return;
44018
+ }
44019
+ await load();
44020
+ }
44021
+ if (error) {
44022
+ return /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(
44023
+ RefusalNotice,
44024
+ {
44025
+ error,
44026
+ className,
44027
+ actions: /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(import_design_system122.Button, { size: "sm", variant: "ghost", onClick: () => void load(), children: "Try again" })
44028
+ }
44029
+ );
44030
+ }
44031
+ if (subscriptions === null) return /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(import_design_system122.Skeleton, { className: (0, import_design_system122.cn)("h-40 w-full", className) });
44032
+ return /* @__PURE__ */ (0, import_jsx_runtime137.jsxs)("div", { className: (0, import_design_system122.cn)("flex flex-col gap-2", className), children: [
44033
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsxs)("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
44034
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("span", { className: "font-medium text-foreground", children: "Tell me when" }),
44035
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsxs)("span", { children: [
44036
+ subscriptions.length,
44037
+ " subscription",
44038
+ subscriptions.length === 1 ? "" : "s"
44039
+ ] })
44040
+ ] }),
44041
+ subscriptions.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("p", { className: "text-xs text-muted-foreground", children: "Nothing is telling anyone about this yet. A subscription is a Rule over a saved view, so what you are told about is exactly what that view holds." }) : /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("ul", { className: "flex flex-col gap-1", children: subscriptions.map((held) => /* @__PURE__ */ (0, import_jsx_runtime137.jsxs)("li", { className: "flex items-center gap-2 rounded border px-2 py-1.5 text-xs", children: [
44042
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("span", { className: "truncate font-medium", children: held.name }),
44043
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("span", { className: "text-muted-foreground", children: CADENCE_WORDS2[held.cadence] ?? held.cadence }),
44044
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("span", { className: "text-muted-foreground", children: CHANNEL_WORDS3[held.channel] ?? held.channel }),
44045
+ held.schedule ? /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("span", { className: "text-muted-foreground", children: scheduleWords(held.schedule, held.quiet_hours?.tz) }) : null,
44046
+ held.next_digest_at ? /* @__PURE__ */ (0, import_jsx_runtime137.jsxs)("span", { className: "text-muted-foreground", children: [
44047
+ "next ",
44048
+ new Date(held.next_digest_at).toLocaleString()
44049
+ ] }) : null,
44050
+ (0, import_records57.hasQuietHours)(held.quiet_hours) ? /* @__PURE__ */ (0, import_jsx_runtime137.jsxs)("span", { className: "text-muted-foreground", children: [
44051
+ "quiet ",
44052
+ held.quiet_hours.start,
44053
+ "\u2013",
44054
+ held.quiet_hours.end
44055
+ ] }) : null,
44056
+ held.recipient_user_id === null ? /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("span", { className: "text-destructive", children: "This one has nobody to tell, so it fires at nobody." }) : null,
44057
+ held.saved_view_id === null ? /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("span", { className: "text-destructive", children: "This one names no view, so the store never admits a record to it." }) : null,
44058
+ held.muted ? /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("span", { className: "text-muted-foreground", children: "Switched off \u2014 it tells nobody until somebody switches it back on." }) : null,
44059
+ onChoose ? held.rule_id === chosenRuleId ? /* @__PURE__ */ (0, import_jsx_runtime137.jsxs)("span", { className: "ml-auto flex items-center gap-1", children: [
44060
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("span", { className: "font-medium text-foreground", children: "Used for this form" }),
44061
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(import_design_system122.Button, { size: "sm", variant: "ghost", disabled: busy, onClick: () => onChoose(null), children: "Stop using" })
44062
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(
44063
+ import_design_system122.Button,
44064
+ {
44065
+ size: "sm",
44066
+ variant: "outline",
44067
+ className: "ml-auto",
44068
+ disabled: busy,
44069
+ onClick: () => onChoose(held.rule_id),
44070
+ children: "Use for this form"
44071
+ }
44072
+ ) : null,
44073
+ rights.write && !(onChoose && held.rule_id === chosenRuleId) && stopping === held.rule_id ? /* @__PURE__ */ (0, import_jsx_runtime137.jsxs)("span", { className: (0, import_design_system122.cn)("flex flex-wrap items-center gap-2", onChoose ? void 0 : "ml-auto"), children: [
44074
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("span", { className: "text-xs text-destructive", children: archiveConfirmSentence("this subscription") }),
44075
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(import_design_system122.Button, { size: "sm", variant: "destructive", disabled: busy, onClick: () => void unsubscribe(held.rule_id), children: "Stop telling me" }),
44076
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(import_design_system122.Button, { size: "sm", variant: "ghost", onClick: () => setStopping(null), children: "Keep it" })
44077
+ ] }) : rights.write && !(onChoose && held.rule_id === chosenRuleId) ? /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(
44078
+ import_design_system122.Button,
44079
+ {
44080
+ size: "sm",
44081
+ variant: "ghost",
44082
+ className: onChoose ? void 0 : "ml-auto",
44083
+ disabled: busy,
44084
+ onClick: () => setStopping(held.rule_id),
44085
+ children: "Stop telling me"
44086
+ }
44087
+ ) : null
44088
+ ] }, held.rule_id)) }),
44089
+ onChoose && chosenRuleId && !subscriptions.some((held) => held.rule_id === chosenRuleId) ? /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("p", { className: "text-xs text-destructive", children: "This form points at a subscription that is not in this list any more, so nobody is told when it is answered. Choose one below, or make one." }) : null,
44090
+ views === null ? /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("p", { className: "rounded border border-dashed px-2 py-1 text-xs text-muted-foreground", children: NO_SAVED_VIEWS_REASON }) : views.length === 0 ? onChoose && rights.write ? /* @__PURE__ */ (0, import_jsx_runtime137.jsxs)("div", { className: "flex flex-wrap items-center gap-2 text-xs text-muted-foreground", children: [
44091
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("span", { children: "A subscription watches a saved view, and this table has none yet." }),
44092
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(import_design_system122.Button, { size: "sm", variant: "outline", disabled: busy, onClick: () => void makeEveryRecordView(), children: "Watch every record" })
44093
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("p", { className: "text-xs text-muted-foreground", children: "This organization has no saved views yet, so there is nothing to subscribe to. Save a view first." }) : rights.write ? /* @__PURE__ */ (0, import_jsx_runtime137.jsxs)(
44094
+ "form",
44095
+ {
44096
+ className: "flex flex-wrap items-center gap-1",
44097
+ onSubmit: (event) => {
44098
+ event.preventDefault();
44099
+ const form = event.currentTarget;
44100
+ const view = form.elements.namedItem("view").value;
44101
+ const cadence = form.elements.namedItem("cadence").value;
44102
+ const channel = form.elements.namedItem("channel").value;
44103
+ const schedule = form.elements.namedItem("schedule").value;
44104
+ const start = form.elements.namedItem("quiet_start").value;
44105
+ const end = form.elements.namedItem("quiet_end").value;
44106
+ const name = views.find((v) => v.id === view)?.name ?? "View";
44107
+ const quiet2 = start && end ? { start, end, tz: Intl.DateTimeFormat().resolvedOptions().timeZone } : null;
44108
+ if (view) void subscribe(view, name, cadence, channel, schedule, quiet2);
44109
+ },
44110
+ children: [
44111
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("select", { name: "view", "aria-label": "View to be told about", className: "h-7 rounded border bg-background px-1 text-xs", children: views.map((view) => /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("option", { value: view.id, children: view.name }, view.id)) }),
44112
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("select", { name: "cadence", "aria-label": "How often", className: "h-7 rounded border bg-background px-1 text-xs", children: (cadences.length > 0 ? cadences : ["instant"]).map((cadence) => /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("option", { value: cadence, children: CADENCE_WORDS2[cadence] ?? cadence }, cadence)) }),
44113
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("select", { name: "channel", "aria-label": "Where it arrives", className: "h-7 rounded border bg-background px-1 text-xs", children: ["in_app", "email", "sms"].map((channel) => /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("option", { value: channel, children: CHANNEL_WORDS3[channel] }, channel)) }),
44114
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(
44115
+ "input",
44116
+ {
44117
+ name: "schedule",
44118
+ "aria-label": "When the summary goes out",
44119
+ placeholder: "monday 08:00",
44120
+ className: "h-7 w-28 rounded border bg-background px-1 text-xs"
44121
+ }
44122
+ ),
44123
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("span", { className: "text-xs text-muted-foreground", children: "not between" }),
44124
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(
44125
+ "input",
44126
+ {
44127
+ name: "quiet_start",
44128
+ type: "time",
44129
+ "aria-label": "Quiet hours start",
44130
+ className: "h-7 rounded border bg-background px-1 text-xs"
44131
+ }
44132
+ ),
44133
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("span", { className: "text-xs text-muted-foreground", children: "and" }),
44134
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(
44135
+ "input",
44136
+ {
44137
+ name: "quiet_end",
44138
+ type: "time",
44139
+ "aria-label": "Quiet hours end",
44140
+ className: "h-7 rounded border bg-background px-1 text-xs"
44141
+ }
44142
+ ),
44143
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(import_design_system122.Button, { size: "sm", type: "submit", disabled: busy, children: "Tell me" }),
44144
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("p", { className: "w-full text-xs text-muted-foreground", children: "A send inside your quiet hours is held until they end \u2014 it is never dropped. Leave the schedule blank for the store's own default of 08:00." })
44145
+ ]
44146
+ }
44147
+ ) : /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("p", { className: "text-xs text-muted-foreground", children: rights.why("write") })
44148
+ ] });
44149
+ }
44150
+
43686
44151
  // src/DashboardCanvas.tsx
43687
44152
  var import_react249 = require("react");
43688
44153
  var import_react250 = require("@ai-matrx/records/react");
43689
44154
  var import_design_system126 = require("@ai-matrx/design-system");
43690
44155
 
43691
44156
  // src/DashboardGrid.tsx
43692
- var import_core22 = require("@dnd-kit/core");
43693
- var import_sortable = require("@dnd-kit/sortable");
43694
- var import_utilities = require("@dnd-kit/utilities");
44157
+ var import_core23 = require("@dnd-kit/core");
44158
+ var import_sortable2 = require("@dnd-kit/sortable");
44159
+ var import_utilities2 = require("@dnd-kit/utilities");
43695
44160
  var import_design_system123 = require("@ai-matrx/design-system");
43696
44161
 
43697
44162
  // src/dashboardLayout.ts
@@ -43745,7 +44210,7 @@ function withBlockIds(blocks) {
43745
44210
  // src/DashboardGrid.tsx
43746
44211
  var import_jsx_runtime138 = require("react/jsx-runtime");
43747
44212
  var dashboardCollision = (args) => {
43748
- const under = (0, import_core22.pointerWithin)(args);
44213
+ const under = (0, import_core23.pointerWithin)(args);
43749
44214
  if (under.length > 0) return under;
43750
44215
  const at = args.collisionRect;
43751
44216
  return args.droppableContainers.flatMap((container) => {
@@ -43755,9 +44220,9 @@ var dashboardCollision = (args) => {
43755
44220
  }).sort((a, b) => a.data.value - b.data.value);
43756
44221
  };
43757
44222
  function DashboardGrid({ items, arranging, onMove, onSpan }) {
43758
- const sensors = (0, import_core22.useSensors)(
43759
- (0, import_core22.useSensor)(import_core22.PointerSensor, { activationConstraint: { distance: 4 } }),
43760
- (0, import_core22.useSensor)(import_core22.KeyboardSensor, { coordinateGetter: import_sortable.sortableKeyboardCoordinates })
44223
+ const sensors = (0, import_core23.useSensors)(
44224
+ (0, import_core23.useSensor)(import_core23.PointerSensor, { activationConstraint: { distance: 4 } }),
44225
+ (0, import_core23.useSensor)(import_core23.KeyboardSensor, { coordinateGetter: import_sortable2.sortableKeyboardCoordinates })
43761
44226
  );
43762
44227
  const ids = items.map((item) => item.id);
43763
44228
  function dragEnd(event) {
@@ -43767,7 +44232,7 @@ function DashboardGrid({ items, arranging, onMove, onSpan }) {
43767
44232
  }
43768
44233
  const grid = /* @__PURE__ */ (0, import_jsx_runtime138.jsx)("div", { className: "grid grid-cols-1 gap-2 sm:grid-cols-12", children: items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime138.jsx)(GridCell2, { item, index, arranging, onSpan }, item.id)) });
43769
44234
  if (!arranging) return grid;
43770
- return /* @__PURE__ */ (0, import_jsx_runtime138.jsx)(import_core22.DndContext, { sensors, collisionDetection: dashboardCollision, onDragEnd: dragEnd, children: /* @__PURE__ */ (0, import_jsx_runtime138.jsx)(import_sortable.SortableContext, { items: ids, strategy: import_sortable.rectSortingStrategy, children: grid }) });
44235
+ return /* @__PURE__ */ (0, import_jsx_runtime138.jsx)(import_core23.DndContext, { sensors, collisionDetection: dashboardCollision, onDragEnd: dragEnd, children: /* @__PURE__ */ (0, import_jsx_runtime138.jsx)(import_sortable2.SortableContext, { items: ids, strategy: import_sortable2.rectSortingStrategy, children: grid }) });
43771
44236
  }
43772
44237
  function GridCell2({
43773
44238
  item,
@@ -43775,8 +44240,8 @@ function GridCell2({
43775
44240
  arranging,
43776
44241
  onSpan
43777
44242
  }) {
43778
- const sortable = (0, import_sortable.useSortable)({ id: item.id, disabled: !arranging });
43779
- const style = arranging ? { transform: import_utilities.CSS.Transform.toString(sortable.transform), transition: sortable.transition } : void 0;
44243
+ const sortable = (0, import_sortable2.useSortable)({ id: item.id, disabled: !arranging });
44244
+ const style = arranging ? { transform: import_utilities2.CSS.Transform.toString(sortable.transform), transition: sortable.transition } : void 0;
43780
44245
  return /* @__PURE__ */ (0, import_jsx_runtime138.jsxs)(
43781
44246
  "div",
43782
44247
  {
@@ -44980,7 +45445,7 @@ var import_react252 = require("@ai-matrx/records/react");
44980
45445
 
44981
45446
  // src/question.ts
44982
45447
  var import_records59 = require("@ai-matrx/records");
44983
- var import_core23 = require("@ai-matrx/records/core");
45448
+ var import_core24 = require("@ai-matrx/records/core");
44984
45449
  var QUESTION_OPS = ["count", "sum", "avg", "min", "max"];
44985
45450
  var QUESTION_LIMIT_DEFAULT = 25;
44986
45451
  var QUESTION_GROUP_CEILING = 200;
@@ -45232,7 +45697,7 @@ async function askAsOf(client, tableId, spec, fields, measures, keys) {
45232
45697
  },
45233
45698
  { schema: config.schema ?? "custom" }
45234
45699
  );
45235
- if (response.error) return { ok: false, error: (0, import_core23.mapPgError)(response.error, "askQuestion.asOf") };
45700
+ if (response.error) return { ok: false, error: (0, import_core24.mapPgError)(response.error, "askQuestion.asOf") };
45236
45701
  const rows2 = Array.isArray(response.data) ? response.data : [];
45237
45702
  const bad = rows2.find((r) => Number(r.not_numbers ?? 0) > 0);
45238
45703
  if (bad && m.op !== "count") {
@@ -45303,7 +45768,7 @@ function savedOf(row) {
45303
45768
  }
45304
45769
  async function questionsOf(client, tableId) {
45305
45770
  const response = await client.config.dataSource.schema("platform").from("saved_view").select("id, name, definition, visibility, version, created_by").eq("surface_key", QUESTION_SURFACE).eq("subject_id", tableId).is("deleted_at", null).order("name", { ascending: true });
45306
- if (response.error) return { ok: false, error: (0, import_core23.mapPgError)(response.error, "questionsOf") };
45771
+ if (response.error) return { ok: false, error: (0, import_core24.mapPgError)(response.error, "questionsOf") };
45307
45772
  const me = client.config.actor?.user_id ?? null;
45308
45773
  return {
45309
45774
  ok: true,
@@ -45332,7 +45797,7 @@ async function questionSave(client, args) {
45332
45797
  if (response.error.code === "23505") {
45333
45798
  return { ok: false, error: { code: "already_exists", message: `You already have a question called \u201C${name}\u201D.` } };
45334
45799
  }
45335
- return { ok: false, error: (0, import_core23.mapPgError)(response.error, "questionSave") };
45800
+ return { ok: false, error: (0, import_core24.mapPgError)(response.error, "questionSave") };
45336
45801
  }
45337
45802
  return { ok: true, data: response.data ? savedOf(response.data) : null };
45338
45803
  }
@@ -45342,7 +45807,7 @@ async function questionArchive(client, id, version) {
45342
45807
  { p_surface_key: QUESTION_SURFACE, p_id: id, ...version !== void 0 ? { p_expected_version: version } : {} },
45343
45808
  { schema: "public" }
45344
45809
  );
45345
- if (response.error) return { ok: false, error: (0, import_core23.mapPgError)(response.error, "questionArchive") };
45810
+ if (response.error) return { ok: false, error: (0, import_core24.mapPgError)(response.error, "questionArchive") };
45346
45811
  return { ok: true, data: response.data !== null };
45347
45812
  }
45348
45813
 
@@ -46201,7 +46666,7 @@ function TableAsk({ tableId, onOpenInQuestions }) {
46201
46666
  // src/PageScreen.tsx
46202
46667
  var import_react255 = require("react");
46203
46668
  var import_react256 = require("@ai-matrx/records/react");
46204
- var import_core24 = require("@ai-matrx/records/core");
46669
+ var import_core25 = require("@ai-matrx/records/core");
46205
46670
  var import_design_system129 = require("@ai-matrx/design-system");
46206
46671
  var import_jsx_runtime145 = require("react/jsx-runtime");
46207
46672
  var EDIT_LEVELS = /* @__PURE__ */ new Set(["edit_content", "editor", "admin", "owner"]);
@@ -46617,7 +47082,7 @@ function ListBlockView({
46617
47082
  ...sort.length > 0 ? { sort } : {}
46618
47083
  });
46619
47084
  const reload = page.reload;
46620
- (0, import_react255.useEffect)(() => (0, import_core24.onRecordChanged)(() => reload()), [reload]);
47085
+ (0, import_react255.useEffect)(() => (0, import_core25.onRecordChanged)(() => reload()), [reload]);
46621
47086
  const rows = nobodyIsMe ? [] : page.data?.rows ?? [];
46622
47087
  const columns = block.columns && block.columns.length > 0 ? block.columns.map((k) => all.find((f) => f.key === k)).filter((f) => !!f) : [...all].sort((a, b) => (a.sort ?? 100) - (b.sort ?? 100)).slice(0, DEFAULT_COLUMNS);
46623
47088
  const firstId = rows[0]?.id ?? null;
@@ -48152,7 +48617,7 @@ var import_design_system142 = require("@ai-matrx/design-system");
48152
48617
  var import_text_case19 = require("@ai-matrx/kit/text-case");
48153
48618
 
48154
48619
  // src/viewShare.ts
48155
- var import_core25 = require("@ai-matrx/records/core");
48620
+ var import_core26 = require("@ai-matrx/records/core");
48156
48621
  var NO_VIEW_REASON = "No saved view yet";
48157
48622
  function viewLink(tableId, viewId, origin) {
48158
48623
  return `${tableLink(tableId, origin)}?view=${encodeURIComponent(viewId)}`;
@@ -48188,7 +48653,7 @@ async function downloadViewCsv(args) {
48188
48653
  const columns = viewColumns(fields, view.presentation);
48189
48654
  const rows = await rowsForFile({ client, host, fields: columns, records: read.rows, labels });
48190
48655
  const name = fileNameOf(args.tableName) || "records";
48191
- download(`${name}.csv`, "text/csv;charset=utf-8", (0, import_core25.exportCsv)(columns, rows));
48656
+ download(`${name}.csv`, "text/csv;charset=utf-8", (0, import_core26.exportCsv)(columns, rows));
48192
48657
  host.notify?.success(read.cut ? cutWords(read.rows.length) : "CSV downloaded");
48193
48658
  }
48194
48659