@ai-matrx/records-ui 0.106.0 → 0.107.1

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,53 @@ 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 || spec.notify.emailRespondent) ? {
37961
+ notify: strip({
37962
+ webhook_url: text(spec.notify.webhookUrl),
37963
+ webhook_automation_id: text(spec.notify.webhookAutomationId),
37964
+ email_respondent: spec.notify.emailRespondent ? true : null,
37965
+ respondent_field: spec.notify.emailRespondent ? text(spec.notify.respondentField) : null
37966
+ })
37967
+ } : {}
37924
37968
  };
37925
37969
  }
37970
+ function notifyFromDocument(raw) {
37971
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
37972
+ const d = raw;
37973
+ const n = {
37974
+ webhookUrl: text(d["webhook_url"]),
37975
+ webhookAutomationId: text(d["webhook_automation_id"]),
37976
+ emailRespondent: d["email_respondent"] === true ? true : null,
37977
+ respondentField: text(d["respondent_field"])
37978
+ };
37979
+ return n.webhookUrl || n.webhookAutomationId || n.emailRespondent ? n : null;
37980
+ }
37981
+ var bool = (v) => typeof v === "boolean" ? v : null;
37982
+ function answerRefusal(kind, required, value) {
37983
+ const empty = value === void 0 || value === null || value === "" || Array.isArray(value) && value.length === 0;
37984
+ if (empty) return required ? "This field is required" : null;
37985
+ if (typeof value !== "string") return null;
37986
+ const v = value.trim();
37987
+ if (kind === "email" && !/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(v)) return "Enter a valid email";
37988
+ if (kind === "phone" && (v.replace(/\D/g, "").length < 7 || /[^\d\s()+.\-x]/i.test(v))) return "Enter a valid phone number";
37989
+ if (kind === "url" && !/^(https?:\/\/)?[^\s.\/]+(\.[^\s.\/]+)+(\/\S*)?$/i.test(v)) return "Enter a valid link";
37990
+ return null;
37991
+ }
37992
+ function pipeAnswers(text2, answers, questions, labelOf) {
37993
+ if (!text2.includes("{{")) return text2;
37994
+ return text2.replace(/\{\{\s*([^{}]+?)\s*\}\}/g, (_all, name) => {
37995
+ const wanted = name.trim().toLowerCase();
37996
+ const q = questions.find((x) => x.key.toLowerCase() === wanted) ?? questions.find((x) => (x.ask ?? "").trim().toLowerCase() === wanted);
37997
+ if (!q) return "";
37998
+ const value = answers[q.key];
37999
+ if (value === void 0 || value === null) return "";
38000
+ const one = (v) => labelOf?.(q.key, v) ?? String(v);
38001
+ return Array.isArray(value) ? value.map(one).join(", ") : one(value);
38002
+ });
38003
+ }
37926
38004
  var text = (v) => typeof v === "string" && v.trim() !== "" ? v.trim() : null;
37927
38005
  var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : null;
37928
38006
  var strip = (doc) => Object.fromEntries(Object.entries(doc).filter(([, v]) => v !== null && v !== void 0));
@@ -38097,6 +38175,9 @@ function formFromSummary(summary) {
38097
38175
  thankYou: thankYouFromDocument(presentation["thank_you"]),
38098
38176
  look: formLookFromDocument(presentation["look"]),
38099
38177
  submitLabel: presentation["submit_label"] ?? null,
38178
+ showOwnerHeader: bool(presentation["show_owner_header"]),
38179
+ autoAdvance: bool(presentation["auto_advance"]),
38180
+ notify: notifyFromDocument(presentation["notify"]),
38100
38181
  // A form is public when the store says it is OPEN. A screen never decides
38101
38182
  // this from a flag of its own: the link answers or it does not.
38102
38183
  isPublic: summary.state === "open",
@@ -38880,6 +38961,50 @@ function compactTick(value) {
38880
38961
  if (Math.abs(value) < 1e3 && !Number.isInteger(value)) return String(Math.round(value * 10) / 10);
38881
38962
  return (0, import_format10.formatCount)(value, { style: "compact" });
38882
38963
  }
38964
+ function wrapTickWords(label, perLine, maxLines = 3) {
38965
+ const words2 = label.split(/\s+/).filter(Boolean).map((w) => w.length > perLine ? `${w.slice(0, perLine - 1)}\u2026` : w);
38966
+ const lines = [];
38967
+ let line = "";
38968
+ for (const word of words2) {
38969
+ const next = line ? `${line} ${word}` : word;
38970
+ if (next.length <= perLine) line = next;
38971
+ else {
38972
+ lines.push(line);
38973
+ line = word;
38974
+ }
38975
+ }
38976
+ if (line) lines.push(line);
38977
+ if (lines.length <= maxLines) return lines;
38978
+ const kept = lines.slice(0, maxLines);
38979
+ const last = kept[maxLines - 1].replace(/…$/, "");
38980
+ kept[maxLines - 1] = `${last.length >= perLine ? last.slice(0, perLine - 1) : last}\u2026`;
38981
+ return kept;
38982
+ }
38983
+ function WrappedTick(props) {
38984
+ const { x = 0, y = 0, payload, across, perLine } = props;
38985
+ const lines = wrapTickWords(String(payload?.value ?? ""), perLine);
38986
+ const lineHeight = 11;
38987
+ return /* @__PURE__ */ (0, import_jsx_runtime120.jsx)(
38988
+ "text",
38989
+ {
38990
+ x,
38991
+ y,
38992
+ fontSize: 10,
38993
+ fill: "currentColor",
38994
+ textAnchor: across ? "end" : "middle",
38995
+ "data-records-chart-tick": "",
38996
+ children: lines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime120.jsx)(
38997
+ "tspan",
38998
+ {
38999
+ x: across ? x - 4 : x,
39000
+ dy: i === 0 ? across ? -((lines.length - 1) * lineHeight) / 2 + 3 : 10 : lineHeight,
39001
+ children: line
39002
+ },
39003
+ i
39004
+ ))
39005
+ }
39006
+ );
39007
+ }
38883
39008
  function Drawing({
38884
39009
  kind,
38885
39010
  points,
@@ -38985,9 +39110,9 @@ function Drawing({
38985
39110
  /* @__PURE__ */ (0, import_jsx_runtime120.jsx)(import_recharts.CartesianGrid, { vertical: horizontal, horizontal: !horizontal }),
38986
39111
  horizontal ? /* @__PURE__ */ (0, import_jsx_runtime120.jsxs)(import_jsx_runtime120.Fragment, { children: [
38987
39112
  /* @__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 })
39113
+ /* @__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
39114
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime120.jsxs)(import_jsx_runtime120.Fragment, { children: [
38990
- /* @__PURE__ */ (0, import_jsx_runtime120.jsx)(import_recharts.XAxis, { dataKey: "label", ...axis }),
39115
+ /* @__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
39116
  /* @__PURE__ */ (0, import_jsx_runtime120.jsx)(import_recharts.YAxis, { width: 44, ...axis })
38992
39117
  ] }),
38993
39118
  tooltip,
@@ -40631,7 +40756,7 @@ function FormLookLogo({ view, size = "md" }) {
40631
40756
  }
40632
40757
  );
40633
40758
  }
40634
- function FormLookFrame({ look, title, children, contained = false, className }) {
40759
+ function FormLookFrame({ look, title, children, contained = false, showOwnerHeader = true, className }) {
40635
40760
  const view = formLookView(look, title);
40636
40761
  return /* @__PURE__ */ (0, import_jsx_runtime127.jsxs)(
40637
40762
  "div",
@@ -40648,10 +40773,10 @@ function FormLookFrame({ look, title, children, contained = false, className })
40648
40773
  }
40649
40774
  ) : null,
40650
40775
  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: [
40776
+ showOwnerHeader ? /* @__PURE__ */ (0, import_jsx_runtime127.jsxs)("div", { className: "flex items-center gap-3", "data-records-form-owner": "", children: [
40652
40777
  /* @__PURE__ */ (0, import_jsx_runtime127.jsx)(FormLookLogo, { view }),
40653
40778
  /* @__PURE__ */ (0, import_jsx_runtime127.jsx)("p", { className: "min-w-0 truncate text-base font-semibold tracking-tight text-foreground", children: view.name })
40654
- ] }),
40779
+ ] }) : null,
40655
40780
  /* @__PURE__ */ (0, import_jsx_runtime127.jsx)("h1", { className: "text-2xl font-semibold tracking-tight text-foreground", children: title }),
40656
40781
  /* @__PURE__ */ (0, import_jsx_runtime127.jsx)("div", { className: "w-full", children }),
40657
40782
  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 +41598,14 @@ function SubscriptionsPanel({ tableId, activeRuleId, className }) {
41473
41598
 
41474
41599
  // src/FormBuilder.tsx
41475
41600
  var import_clipboard9 = require("@ai-matrx/kit/clipboard");
41601
+ var import_design_system117 = require("@ai-matrx/design-system");
41602
+ var import_react232 = require("react");
41603
+ var import_core22 = require("@dnd-kit/core");
41604
+ var import_sortable = require("@dnd-kit/sortable");
41605
+ var import_utilities = require("@dnd-kit/utilities");
41606
+ var import_react233 = require("@ai-matrx/records/react");
41607
+ var import_records56 = require("@ai-matrx/records");
41476
41608
  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
41609
 
41482
41610
  // src/FormFlowEditor.tsx
41483
41611
  var import_react229 = require("react");
@@ -41824,7 +41952,8 @@ function FormStage({
41824
41952
  className,
41825
41953
  connected = false,
41826
41954
  onStart,
41827
- onQuestionShown
41955
+ onQuestionShown,
41956
+ autoAdvance
41828
41957
  }) {
41829
41958
  const host = useRecordsUi();
41830
41959
  const [answers, setAnswers] = (0, import_react230.useState)(() => ({ ...initialAnswers ?? {} }));
@@ -41837,6 +41966,11 @@ function FormStage({
41837
41966
  const [ending, setEnding] = (0, import_react230.useState)(null);
41838
41967
  const [started, setStarted] = (0, import_react230.useState)(() => !form.welcome || resumed);
41839
41968
  const routes = formRoutes(form);
41969
+ const [routeKnown, setRouteKnown] = (0, import_react230.useState)(() => !routes || !whichAsked);
41970
+ const [stopped, setStopped] = (0, import_react230.useState)(null);
41971
+ const answersRef = (0, import_react230.useRef)(answers);
41972
+ answersRef.current = answers;
41973
+ const choose = autoAdvance ?? form.autoAdvance ?? true;
41840
41974
  const decoy = (0, import_react230.useRef)("");
41841
41975
  const stage = (0, import_react230.useRef)(null);
41842
41976
  const questions = (0, import_react230.useMemo)(() => {
@@ -41872,6 +42006,7 @@ function FormStage({
41872
42006
  };
41873
42007
  }
41874
42008
  if (cancelled) return;
42009
+ setRouteKnown(true);
41875
42010
  if (!told2.ok) {
41876
42011
  setHidden({});
41877
42012
  setError(told2.error ?? refusal("not_supported", told2.message, "Answer every question shown; none were skipped."));
@@ -41893,7 +42028,7 @@ function FormStage({
41893
42028
  ) : null
41894
42029
  );
41895
42030
  })();
41896
- }, 250);
42031
+ }, routeKnown ? 250 : 0);
41897
42032
  return () => {
41898
42033
  cancelled = true;
41899
42034
  clearTimeout(timer);
@@ -41928,11 +42063,24 @@ function FormStage({
41928
42063
  const v = answers[q.key];
41929
42064
  return v !== void 0 && v !== null && v !== "";
41930
42065
  }).length;
41931
- const missing = live.filter((q) => {
41932
- const v = answers[q.key];
41933
- return q.required && (v === void 0 || v === null || v === "");
41934
- });
42066
+ const refusalFor = (q, values = answersRef.current) => answerRefusal(q.field ? String(editorKindFor(q.field)) : "text", q.required, values[q.key]);
42067
+ const refuse = (q, say2) => {
42068
+ setStopped({ key: q.key, say: say2 });
42069
+ const i = live.findIndex((x) => x.key === q.key);
42070
+ if (oneAtATime && i >= 0 && i !== index) setAt(i);
42071
+ window.setTimeout(() => {
42072
+ const box = stage.current?.querySelector(`[data-records-form-question="${CSS.escape(q.key)}"]`);
42073
+ box?.querySelector("[role='radio'][aria-checked='true'], input:not([tabindex='-1']), textarea, select, [role='combobox'], [role='radio'], [role='checkbox']")?.focus();
42074
+ }, 0);
42075
+ };
41935
42076
  const submit = (0, import_react230.useCallback)(async () => {
42077
+ for (const q of live) {
42078
+ const say2 = refusalFor(q);
42079
+ if (say2) {
42080
+ refuse(q, say2);
42081
+ return;
42082
+ }
42083
+ }
41936
42084
  if (preview) {
41937
42085
  setDone("preview");
41938
42086
  return;
@@ -41964,20 +42112,28 @@ function FormStage({
41964
42112
  }, [answers, form, honeypotKey, live, onSubmit, onSubmitted, preview, questions]);
41965
42113
  async function advance() {
41966
42114
  if (!oneAtATime) return;
42115
+ if (current) {
42116
+ const say2 = refusalFor(current);
42117
+ if (say2) {
42118
+ refuse(current, say2);
42119
+ return;
42120
+ }
42121
+ }
42122
+ setStopped(null);
41967
42123
  if (!routes || !whichAsked || !current) {
41968
42124
  if (index < live.length - 1) setAt(index + 1);
41969
- else if (missing.length === 0) void submit();
42125
+ else void submit();
41970
42126
  return;
41971
42127
  }
41972
42128
  let told2;
41973
42129
  try {
41974
- told2 = await whichAsked(answers);
42130
+ told2 = await whichAsked(answersRef.current);
41975
42131
  } catch {
41976
42132
  told2 = { ok: false, message: "This form could not work out which question comes next, so the next one is shown." };
41977
42133
  }
41978
42134
  if (!told2.ok) {
41979
42135
  if (index < live.length - 1) setAt(index + 1);
41980
- else if (missing.length === 0) void submit();
42136
+ else void submit();
41981
42137
  return;
41982
42138
  }
41983
42139
  const next = {};
@@ -41990,13 +42146,18 @@ function FormStage({
41990
42146
  setAt(here + 1);
41991
42147
  return;
41992
42148
  }
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();
42149
+ const stillMissing = onRoute.find((q) => refusalFor(q) !== null);
42150
+ if (stillMissing) refuse(stillMissing, refusalFor(stillMissing));
42151
+ else void submit();
41998
42152
  }
42153
+ const advanceRef = (0, import_react230.useRef)(advance);
42154
+ advanceRef.current = advance;
42155
+ const chosen = (0, import_react230.useCallback)(() => {
42156
+ if (!oneAtATime || !choose) return;
42157
+ window.setTimeout(() => void advanceRef.current(), 450);
42158
+ }, [oneAtATime, choose]);
41999
42159
  function retreat() {
42160
+ setStopped(null);
42000
42161
  if (oneAtATime && index > 0) setAt(index - 1);
42001
42162
  }
42002
42163
  function onKeyDown(event) {
@@ -42012,12 +42173,11 @@ function FormStage({
42012
42173
  else advance();
42013
42174
  }
42014
42175
  (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.
42176
+ const input = stage.current?.querySelector("[role='radio'][aria-checked='true']") ?? stage.current?.querySelector(
42017
42177
  "input:not([tabindex='-1']), textarea, select, [role='combobox'], [role='radio'], [role='checkbox']"
42018
42178
  );
42019
42179
  input?.focus();
42020
- }, [index, done]);
42180
+ }, [index, done, started]);
42021
42181
  const startTold = (0, import_react230.useRef)(false);
42022
42182
  (0, import_react230.useEffect)(() => {
42023
42183
  if (startTold.current || !onStart) return;
@@ -42036,6 +42196,10 @@ function FormStage({
42036
42196
  }
42037
42197
  }, [current, live, oneAtATime, onQuestionShown, started, done]);
42038
42198
  const theme = (0, import_design_system116.formThemeClasses)(form.theme ?? null);
42199
+ const pipe = (text2) => pipeAnswers(text2, answers, questions, (key, v) => {
42200
+ const f = questions.find((q) => q.key === key)?.field;
42201
+ return f ? publicChoicesOf(f).find((c) => c.key === String(v))?.label ?? null : null;
42202
+ });
42039
42203
  if (done) {
42040
42204
  return /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(
42041
42205
  ThankYou,
@@ -42044,6 +42208,7 @@ function FormStage({
42044
42208
  done,
42045
42209
  ending: (form.endings ?? []).find((e) => e.id === ending) ?? null,
42046
42210
  buttonClass: theme.buttonClass,
42211
+ pipe,
42047
42212
  className: (0, import_design_system116.cn)("mx-auto max-w-xl py-10", centred && "text-center", className)
42048
42213
  }
42049
42214
  );
@@ -42055,14 +42220,14 @@ function FormStage({
42055
42220
  return /* @__PURE__ */ (0, import_jsx_runtime132.jsxs)(
42056
42221
  "section",
42057
42222
  {
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),
42223
+ 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
42224
  style: theme.surfaceStyle,
42060
42225
  "data-records-form-welcome": "",
42061
42226
  children: [
42062
42227
  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" }) })
42228
+ w.title ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("h2", { className: "text-2xl font-semibold sm:text-3xl", children: w.title }) : null,
42229
+ w.body ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("p", { className: "text-base text-muted-foreground", children: authored(host, w.body, "form-intro") }) : null,
42230
+ /* @__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
42231
  ]
42067
42232
  }
42068
42233
  );
@@ -42071,7 +42236,15 @@ function FormStage({
42071
42236
  return /* @__PURE__ */ (0, import_jsx_runtime132.jsxs)(
42072
42237
  "section",
42073
42238
  {
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),
42239
+ className: (0, import_design_system116.cn)(
42240
+ "mx-auto flex w-full max-w-2xl flex-col gap-4 py-4",
42241
+ // TYPEFORM'S SIZE (TYPEFORM-2): one question at a time fills the screen, centred in it.
42242
+ oneAtATime && "min-h-[min(70dvh,640px)]",
42243
+ theme.surface,
42244
+ theme.background !== "plain" && "rounded-lg px-5 sm:px-8",
42245
+ centred && "text-center",
42246
+ className
42247
+ ),
42075
42248
  style: theme.surfaceStyle,
42076
42249
  "data-records-form-font": theme.font,
42077
42250
  onKeyDown,
@@ -42081,20 +42254,41 @@ function FormStage({
42081
42254
  import_design_system116.Progress,
42082
42255
  {
42083
42256
  value: live.length === 0 ? 0 : (oneAtATime ? index + 1 : answered) / live.length * 100,
42084
- className: "h-1 flex-1"
42257
+ className: "h-1 flex-1",
42258
+ "aria-label": "Form progress",
42259
+ ...routeKnown ? { "aria-valuetext": oneAtATime ? `${index + 1} of ${live.length}` : `${answered} of ${live.length}` } : {}
42085
42260
  }
42086
42261
  ),
42087
- /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("span", { className: "tabular-nums", children: oneAtATime ? `${index + 1} of ${live.length}` : `${answered} of ${live.length}` })
42262
+ /* @__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
42263
  ] }),
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,
42264
+ signedInPublic ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(
42265
+ "p",
42266
+ {
42267
+ className: "w-fit rounded border border-dashed px-2 py-0.5 text-left text-xs text-muted-foreground",
42268
+ title: "The record will carry your name. Strangers answer at the public link.",
42269
+ children: "Signed in \xB7 saved as you"
42270
+ }
42271
+ ) : null,
42272
+ unanswerable ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(
42273
+ "p",
42274
+ {
42275
+ className: "w-fit rounded border border-dashed px-2 py-0.5 text-left text-xs text-muted-foreground",
42276
+ title: "Nothing here can work out which conditional questions apply, so all of them are shown.",
42277
+ children: "Showing every question"
42278
+ }
42279
+ ) : null,
42280
+ unresolved.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime132.jsxs)(
42281
+ "p",
42282
+ {
42283
+ className: "w-fit rounded border border-destructive/40 px-2 py-0.5 text-left text-xs text-destructive",
42284
+ title: "The table has no such field. Add the field, or take the question out.",
42285
+ children: [
42286
+ "Missing field: ",
42287
+ unresolved.map((q) => q.ask).join(", ")
42288
+ ]
42289
+ }
42290
+ ) : null,
42291
+ 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
42292
  error ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(RefusalNotice, { error, className: "text-left" }) : null,
42099
42293
  refusal4 ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(
42100
42294
  RefusalNotice,
@@ -42116,14 +42310,20 @@ function FormStage({
42116
42310
  (oneAtATime ? current ? [current] : [] : live).map((q) => /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(
42117
42311
  Question,
42118
42312
  {
42119
- question: q,
42313
+ question: { ...q, ask: pipe(q.ask), help: q.help ? pipe(q.help) : null },
42120
42314
  value: answers[q.key],
42121
- onChange: (v) => setAnswers((prev) => ({ ...prev, [q.key]: v })),
42315
+ onChange: (v) => {
42316
+ if (stopped?.key === q.key) setStopped(null);
42317
+ setAnswers((prev) => ({ ...prev, [q.key]: v }));
42318
+ },
42319
+ onChosen: chosen,
42320
+ refused: stopped?.key === q.key ? stopped.say : null,
42321
+ large: oneAtATime,
42122
42322
  upload: host.upload
42123
42323
  },
42124
42324
  q.key
42125
42325
  )),
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
42326
+ 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
42327
  ] }),
42128
42328
  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
42329
  /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("label", { htmlFor: `hp-${honeypotKey}`, children: "Leave this field empty" }),
@@ -42142,16 +42342,14 @@ function FormStage({
42142
42342
  }
42143
42343
  )
42144
42344
  ] }) : 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."
42345
+ /* @__PURE__ */ (0, import_jsx_runtime132.jsxs)("footer", { className: (0, import_design_system116.cn)("flex flex-wrap items-center gap-3", centred && "justify-center"), children: [
42346
+ 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,
42347
+ 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" }),
42348
+ oneAtATime ? /* @__PURE__ */ (0, import_jsx_runtime132.jsxs)("span", { className: "hidden text-xs text-muted-foreground sm:inline", "data-records-form-enter-hint": "", children: [
42349
+ "press ",
42350
+ /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("strong", { className: "font-semibold text-foreground", children: "Enter \u21B5" })
42153
42351
  ] }) : null,
42154
- preview ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("span", { className: "text-xs text-muted-foreground", children: "Preview \u2014 nothing is written." }) : null
42352
+ 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
42353
  ] })
42156
42354
  ]
42157
42355
  }
@@ -42163,12 +42361,15 @@ function ThankYou({
42163
42361
  done,
42164
42362
  className,
42165
42363
  ending,
42166
- buttonClass
42364
+ buttonClass,
42365
+ pipe = (t) => t
42167
42366
  }) {
42168
42367
  const host = useRecordsUi();
42169
42368
  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;
42369
+ const rawTitle = ending ? ending.title : form.thankYou?.title;
42370
+ const rawBody = ending ? ending.body : form.thankYou?.body;
42371
+ const title = rawTitle ? pipe(rawTitle) : rawTitle;
42372
+ const body = rawBody ? pipe(rawBody) : rawBody;
42172
42373
  const going = redirect !== null && done !== "preview";
42173
42374
  (0, import_react230.useEffect)(() => {
42174
42375
  if (!going || !redirect) return;
@@ -42185,8 +42386,8 @@ function ThankYou({
42185
42386
  }
42186
42387
  ),
42187
42388
  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,
42389
+ /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("h2", { className: "text-2xl font-semibold", "data-records-form-ending": ending?.id ?? "", children: title || "Thank you" }),
42390
+ 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
42391
  going ? /* @__PURE__ */ (0, import_jsx_runtime132.jsxs)("p", { className: "mt-4 flex flex-wrap items-center gap-3 text-sm", children: [
42191
42392
  /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(
42192
42393
  "a",
@@ -42199,11 +42400,11 @@ function ThankYou({
42199
42400
  ),
42200
42401
  /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("span", { className: "text-muted-foreground", children: "Taking you there\u2026" })
42201
42402
  ] }) : 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" ? (
42403
+ /* @__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
42404
  // SAID TO THE PERSON WHO ANSWERED (lane HANDOVER, 2026-09-29): "Saved to Form 1, with
42204
42405
  // this form stamped on it" was the owner's bookkeeping read out to a front-desk member
42205
42406
  // and to strangers. Where it landed is the owner's to see on the table.
42206
- "Your answers were received."
42407
+ "Answers received"
42207
42408
  ) : done })
42208
42409
  ] });
42209
42410
  }
@@ -42211,6 +42412,9 @@ function Question({
42211
42412
  question,
42212
42413
  value,
42213
42414
  onChange,
42415
+ onChosen,
42416
+ refused,
42417
+ large,
42214
42418
  upload
42215
42419
  }) {
42216
42420
  const host = useRecordsUi();
@@ -42218,297 +42422,62 @@ function Question({
42218
42422
  const field2 = question.field;
42219
42423
  if (!field2) return null;
42220
42424
  const id = `form-${field2.key}`;
42425
+ const sayId = `${id}-refused`;
42221
42426
  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
- }
42427
+ return /* @__PURE__ */ (0, import_jsx_runtime132.jsxs)(
42428
+ "div",
42429
+ {
42430
+ className: (0, import_design_system116.cn)(
42431
+ "flex flex-col",
42432
+ large ? "gap-3 [&_input]:text-base [&_textarea]:text-base [&_input]:min-h-12" : "gap-1.5 [&_input]:text-base [&_textarea]:text-base"
42246
42433
  ),
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)(
42434
+ "data-records-form-question": field2.key,
42435
+ "aria-describedby": refused ? sayId : void 0,
42436
+ children: [
42437
+ /* @__PURE__ */ (0, import_jsx_runtime132.jsxs)("div", { className: "flex flex-wrap items-baseline gap-x-2", children: [
42438
+ /* @__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 }),
42439
+ question.required ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("span", { className: "text-sm text-foreground/80", children: "required" }) : null,
42440
+ field2.unit ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("span", { className: "text-sm text-foreground/70", children: field2.unit }) : null
42441
+ ] }),
42442
+ 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,
42443
+ 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: [
42444
+ /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(
42494
42445
  "input",
42495
42446
  {
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"
42447
+ id,
42448
+ type: "file",
42449
+ className: "text-sm",
42450
+ onChange: (e) => {
42451
+ const file = e.target.files?.[0];
42452
+ if (!file) return;
42453
+ setUploadError(null);
42454
+ void upload?.(file).then((result) => {
42455
+ if (!result) return;
42456
+ if (result.ok) onChange(result.fileId);
42457
+ else
42458
+ setUploadError(
42459
+ refusal("invalid_argument", result.reason, "Pick another file, or try this one again.")
42460
+ );
42461
+ });
42462
+ }
42500
42463
  }
42501
42464
  ),
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
- ] });
42465
+ uploadError ? /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(RefusalLine, { error: uploadError }) : null
42466
+ ] }) : (
42467
+ // A choice is laid out as lettered rows in the form's colour (`ChoiceAnswer`).
42468
+ /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(FieldControl, { field: field2, value, onChange, id, asForm: true, onChosen })
42469
+ ),
42470
+ /* @__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: [
42471
+ /* @__PURE__ */ (0, import_jsx_runtime132.jsx)("span", { "aria-hidden": "true", children: "!" }),
42472
+ refused
42473
+ ] }) : null })
42474
+ ]
42475
+ }
42476
+ );
42508
42477
  }
42509
42478
 
42510
42479
  // src/FormBuilder.tsx
42511
- var import_jsx_runtime134 = require("react/jsx-runtime");
42480
+ var import_jsx_runtime133 = require("react/jsx-runtime");
42512
42481
  function freshFormName(base, taken) {
42513
42482
  const names = new Set(taken.map((n) => n.trim().toLowerCase()));
42514
42483
  if (!names.has(base.toLowerCase())) return base;
@@ -42525,7 +42494,21 @@ function isLookRefusal(error) {
42525
42494
  return Boolean(error && LOOK_REFUSAL.test(refusalSentence(error)));
42526
42495
  }
42527
42496
  var KEPT_DRAFT_PREFIX = "records-ui:form-draft:";
42528
- var SPEC_KEYS = ["name", "intro", "questions", "flow", "theme", "look", "thankYou", "submitLabel"];
42497
+ var SPEC_KEYS = [
42498
+ "name",
42499
+ "intro",
42500
+ "questions",
42501
+ "flow",
42502
+ "theme",
42503
+ "look",
42504
+ "thankYou",
42505
+ "submitLabel",
42506
+ "welcome",
42507
+ "endings",
42508
+ "hiddenFields",
42509
+ "showOwnerHeader",
42510
+ "autoAdvance"
42511
+ ];
42529
42512
  function readKeptDraft(formId) {
42530
42513
  try {
42531
42514
  const raw = window.localStorage.getItem(KEPT_DRAFT_PREFIX + formId);
@@ -42556,31 +42539,32 @@ function withKept(stored, spec) {
42556
42539
  return out;
42557
42540
  }
42558
42541
  function FormBuilder({ tableId, seed, activeFormId, onActiveForm, startWithOne, createOnMount, className }) {
42559
- const client = (0, import_react235.useRecordsClient)();
42542
+ const client = (0, import_react233.useRecordsClient)();
42560
42543
  const host = useRecordsUi();
42561
42544
  const claimSeed = useSeedGuard();
42562
- const table = (0, import_react235.useTable)(tableId);
42545
+ const table = (0, import_react233.useTable)(tableId);
42563
42546
  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");
42547
+ const fields = (0, import_react233.useFields)(tableId);
42548
+ const [forms, setForms] = (0, import_react232.useState)(null);
42549
+ const [error, setError] = (0, import_react232.useState)(null);
42550
+ const [refused, setRefused] = (0, import_react232.useState)(null);
42551
+ const [activeId, setActiveId] = (0, import_react232.useState)(activeFormId ?? null);
42552
+ const [draft, setDraft] = (0, import_react232.useState)(null);
42553
+ const [saving, setSaving] = (0, import_react232.useState)(false);
42554
+ const [saved, setSaved] = (0, import_react232.useState)(null);
42555
+ const [publishing, setPublishing] = (0, import_react232.useState)(false);
42556
+ const [copied, setCopied] = (0, import_react232.useState)(false);
42557
+ const [shownUrl, setShownUrl] = (0, import_react232.useState)(null);
42558
+ const [lookOptions, setLookOptions] = (0, import_react232.useState)(null);
42559
+ const [lookUnread, setLookUnread] = (0, import_react232.useState)(null);
42560
+ const [orgKnobs, setOrgKnobs] = (0, import_react232.useState)(null);
42561
+ const [restoredAt, setRestoredAt] = (0, import_react232.useState)(null);
42562
+ const [showResults, setShowResults] = (0, import_react232.useState)(false);
42563
+ const [adding, setAdding] = (0, import_react232.useState)(false);
42564
+ const keysBeforeAdd = (0, import_react232.useRef)(null);
42565
+ const [makeOnMount, setMakeOnMount] = (0, import_react232.useState)("waiting");
42582
42566
  const publicOrigin = host.publicOrigin ?? (typeof window === "undefined" ? "" : window.location.origin);
42583
- const load = (0, import_react234.useCallback)(async () => {
42567
+ const load = (0, import_react232.useCallback)(async () => {
42584
42568
  const answered = await client.forms({ table_id: tableId });
42585
42569
  if (!answered.ok) {
42586
42570
  setError(answered.error);
@@ -42607,10 +42591,19 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, startWithOne,
42607
42591
  setError(null);
42608
42592
  setForms(mine);
42609
42593
  }, [client, tableId, seed, claimSeed]);
42610
- (0, import_react234.useEffect)(() => {
42594
+ (0, import_react232.useEffect)(() => {
42611
42595
  void load();
42612
42596
  }, [load]);
42613
- (0, import_react234.useEffect)(() => {
42597
+ (0, import_react232.useEffect)(() => {
42598
+ let cancelled = false;
42599
+ Promise.resolve().then(() => client.formKnobs()).then((r) => {
42600
+ if (!cancelled && r.ok) setOrgKnobs(r.data);
42601
+ }).catch(() => void 0);
42602
+ return () => {
42603
+ cancelled = true;
42604
+ };
42605
+ }, [client]);
42606
+ (0, import_react232.useEffect)(() => {
42614
42607
  if (!forms || forms.length === 0) return;
42615
42608
  if (createOnMount && makeOnMount !== "made") return;
42616
42609
  const chosen = forms.find((f) => f.id === (activeFormId ?? activeId)) ?? forms[0];
@@ -42627,14 +42620,14 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, startWithOne,
42627
42620
  }
42628
42621
  onActiveForm?.(chosen);
42629
42622
  }, [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)(
42623
+ const byKey = (0, import_react232.useMemo)(() => new Map((fields.data ?? []).map((f) => [f.key, f])), [fields.data]);
42624
+ const conditionFields = (0, import_react232.useMemo)(
42632
42625
  () => (fields.data ?? []).map((f) => ({ id: String(f.id), key: f.key, label: f.label || f.key, field: f })),
42633
42626
  [fields.data]
42634
42627
  );
42635
- const stored = (0, import_react234.useMemo)(() => draft ? (forms ?? []).find((f) => f.id === draft.id) ?? null : null, [draft, forms]);
42628
+ const stored = (0, import_react232.useMemo)(() => draft ? (forms ?? []).find((f) => f.id === draft.id) ?? null : null, [draft, forms]);
42636
42629
  const dirty = Boolean(draft && stored && !sameForm(draft, stored));
42637
- (0, import_react234.useEffect)(() => {
42630
+ (0, import_react232.useEffect)(() => {
42638
42631
  if (!draft || !stored) return;
42639
42632
  const timer = window.setTimeout(() => {
42640
42633
  if (sameForm(draft, stored)) dropKeptDraft(draft.id);
@@ -42643,7 +42636,7 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, startWithOne,
42643
42636
  return () => window.clearTimeout(timer);
42644
42637
  }, [draft, stored]);
42645
42638
  const lookKey = draft ? JSON.stringify(formLookDocument(draft.look ?? null)) : null;
42646
- (0, import_react234.useEffect)(() => {
42639
+ (0, import_react232.useEffect)(() => {
42647
42640
  if (lookKey === null) return;
42648
42641
  let cancelled = false;
42649
42642
  const timer = window.setTimeout(() => {
@@ -42660,7 +42653,7 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, startWithOne,
42660
42653
  window.clearTimeout(timer);
42661
42654
  };
42662
42655
  }, [client, lookKey]);
42663
- (0, import_react234.useEffect)(() => {
42656
+ (0, import_react232.useEffect)(() => {
42664
42657
  const before = keysBeforeAdd.current;
42665
42658
  if (!before || !fields.data) return;
42666
42659
  const made = askableFields(fields.data).filter((f) => !before.has(f.key));
@@ -42699,7 +42692,7 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, startWithOne,
42699
42692
  }
42700
42693
  dropKeptDraft(draft.id);
42701
42694
  setRestoredAt(null);
42702
- setSaved("Saved.");
42695
+ setSaved("Saved");
42703
42696
  await load();
42704
42697
  return true;
42705
42698
  }
@@ -42738,7 +42731,22 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, startWithOne,
42738
42731
  setRefused(written.error);
42739
42732
  return;
42740
42733
  }
42741
- setSaved(ruleId ? "Saved \u2014 you will be told when somebody answers." : "Saved \u2014 nobody is told now.");
42734
+ setSaved("Saved");
42735
+ await load();
42736
+ }
42737
+ async function chooseWebhook(notify) {
42738
+ if (!draft) return;
42739
+ const next = { ...draft, notify };
42740
+ setDraft(next);
42741
+ setSaving(true);
42742
+ setRefused(null);
42743
+ const written = await client.formDeclare(formDeclareArgs(next));
42744
+ setSaving(false);
42745
+ if (!written.ok) {
42746
+ setRefused(written.error);
42747
+ return;
42748
+ }
42749
+ setSaved("Saved");
42742
42750
  await load();
42743
42751
  }
42744
42752
  async function create(name) {
@@ -42755,15 +42763,15 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, startWithOne,
42755
42763
  await load();
42756
42764
  return true;
42757
42765
  }
42758
- const [started, setStarted] = (0, import_react234.useState)(false);
42759
- (0, import_react234.useEffect)(() => {
42766
+ const [started, setStarted] = (0, import_react232.useState)(false);
42767
+ (0, import_react232.useEffect)(() => {
42760
42768
  if (!startWithOne || started || forms === null || forms.length > 0) return;
42761
42769
  if (askableFields(fields.data ?? []).length === 0) return;
42762
42770
  if (!table.data && !table.error) return;
42763
42771
  setStarted(true);
42764
42772
  void create(table.data?.name?.trim() ? `${table.data.name.trim()} form` : "Form 1");
42765
42773
  }, [startWithOne, started, forms, fields.data, create, table.data, table.error]);
42766
- (0, import_react234.useEffect)(() => {
42774
+ (0, import_react232.useEffect)(() => {
42767
42775
  if (!createOnMount || makeOnMount !== "waiting" || forms === null) return;
42768
42776
  if (!table.data && !table.error) return;
42769
42777
  if (askableFields(fields.data ?? []).length === 0) {
@@ -42776,396 +42784,325 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, startWithOne,
42776
42784
  (made) => setMakeOnMount(made ? "made" : "not-made")
42777
42785
  );
42778
42786
  }, [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 });
42787
+ if (fields.error) return /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(RefusalNotice, { error: fields.error, className });
42780
42788
  if (error) {
42781
- return /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
42789
+ return /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42782
42790
  RefusalNotice,
42783
42791
  {
42784
42792
  error,
42785
42793
  className,
42786
- actions: /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(import_design_system119.Button, { size: "sm", variant: "ghost", onClick: () => void load(), children: "Try again" })
42794
+ actions: /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.Button, { size: "sm", variant: "ghost", onClick: () => void load(), children: "Try again" })
42787
42795
  }
42788
42796
  );
42789
42797
  }
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) });
42798
+ 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
42799
  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) });
42800
+ return /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.Skeleton, { className: (0, import_design_system118.cn)("h-64 w-full", className) });
42793
42801
  }
42794
42802
  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") });
42803
+ 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
42804
  }
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);
42805
+ return (
42806
+ // THE CONTAINER IS THE PARENT (TYPEFORM-2): a container query asks an ANCESTOR, so `@3xl:` on the
42807
+ // element that is itself `@container` never fired and the preview sat 3,000 px below the editor.
42808
+ /* @__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: [
42809
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col gap-2", children: [
42810
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "flex items-center gap-1 overflow-x-auto", role: "group", "aria-label": "Forms", children: [
42811
+ forms.map((form) => /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42812
+ import_design_system118.Button,
42813
+ {
42814
+ size: "sm",
42815
+ variant: form.id === activeId ? "secondary" : "ghost",
42816
+ "aria-pressed": form.id === activeId,
42817
+ onClick: () => {
42818
+ setActiveId(form.id);
42819
+ setDraft(form);
42820
+ },
42821
+ children: form.name
42809
42822
  },
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"
42823
+ form.id
42824
+ )),
42825
+ (fields.data ?? []).length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42826
+ import_design_system118.Button,
42827
+ {
42828
+ size: "sm",
42829
+ variant: "ghost",
42830
+ onClick: () => void create(
42831
+ freshFormName(
42832
+ table.data?.name?.trim() ? `${table.data.name.trim()} form` : "Form",
42833
+ forms.map((f) => f.name)
42834
+ )
42835
+ ),
42836
+ children: "New form"
42837
+ }
42838
+ ) : null,
42839
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("span", { className: "ml-auto flex items-center gap-2", children: [
42840
+ saved ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "text-xs text-muted-foreground", children: saved }) : null,
42841
+ 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,
42842
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.Button, { size: "sm", disabled: saving || !draft, onClick: () => void save(), children: saving ? "Saving\u2026" : "Save" })
42843
+ ] })
42855
42844
  ] }),
42856
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
42857
- import_design_system119.Button,
42845
+ refused && !isLookRefusal(refused) ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42846
+ RefusalNotice,
42858
42847
  {
42859
- size: "sm",
42860
- variant: "ghost",
42861
- onClick: () => {
42862
- dropKeptDraft(stored.id);
42863
- setDraft(stored);
42848
+ error: refused,
42849
+ onKeepEditing: () => setRefused(null),
42850
+ onDiscard: () => {
42851
+ const kept = forms.find((f) => f.id === activeId) ?? null;
42852
+ if (kept) dropKeptDraft(kept.id);
42853
+ setDraft(kept);
42864
42854
  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
42855
+ setSaved(null);
42856
+ setRefused(null);
42857
+ }
42884
42858
  }
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,
42859
+ ) : null,
42860
+ 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: [
42861
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("span", { children: [
42862
+ "Unsaved changes from",
42863
+ " ",
42864
+ new Date(restoredAt).toLocaleString(void 0, { dateStyle: "medium", timeStyle: "short" }),
42865
+ " restored"
42866
+ ] }),
42867
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42868
+ import_design_system118.Button,
42891
42869
  {
42892
- tableId,
42893
- chosenRuleId: draft.notifyRuleId,
42894
- onChoose: (ruleId) => void chooseNotify(ruleId)
42870
+ size: "sm",
42871
+ variant: "ghost",
42872
+ onClick: () => {
42873
+ dropKeptDraft(stored.id);
42874
+ setDraft(stored);
42875
+ setRestoredAt(null);
42876
+ },
42877
+ children: "Discard"
42895
42878
  }
42896
42879
  )
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,
42880
+ ] }) : null,
42881
+ !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: [
42882
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42883
+ PublishRow,
42884
+ {
42885
+ url: `${publicOrigin}${(0, import_records56.publicFormPath)(draft.id)}`,
42886
+ state: draft.state,
42887
+ mayPublish: rights.structure,
42888
+ why: rights.why("structure"),
42889
+ busy: publishing,
42890
+ copied,
42891
+ shownUrl,
42892
+ onPublish: (wanted) => void publish(wanted),
42893
+ onCopy: (url) => void copyLink(url),
42894
+ unsaved: dirty
42895
+ }
42896
+ ),
42897
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(EmbedSnippets, { url: `${publicOrigin}${(0, import_records56.publicFormPath)(draft.id)}`, label: draft.name }),
42898
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42899
+ NotifyOnAnswer,
42900
+ {
42901
+ tableId,
42902
+ tableName: table.data?.name ?? "record",
42903
+ formName: draft.name,
42904
+ ruleId: draft.notifyRuleId,
42905
+ notify: draft.notify ?? null,
42906
+ emailQuestion: (
42907
+ // An email field first; else a question whose field is named for an email (an imported
42908
+ // "Contact email" kept as text). The store's resolver refuses an answer that is not one.
42909
+ (draft.questions.map((q) => byKey.get(q.field)).find((f) => f && String(editorKindFor(f)) === "email") ?? draft.questions.map((q) => byKey.get(q.field)).find((f) => f && /e-?mail/i.test(`${f.key} ${f.label ?? ""}`)))?.key ?? null
42910
+ ),
42911
+ onRule: (ruleId) => void chooseNotify(ruleId),
42912
+ onNotify: (notify) => void chooseWebhook(notify)
42913
+ }
42914
+ ),
42915
+ /* @__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: [
42916
+ /* @__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: [
42917
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42918
+ import_design_system118.Checkbox,
42916
42919
  {
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 } })
42920
+ checked: draft.showOwnerHeader ?? orgKnobs?.show_owner_header ?? true,
42921
+ onCheckedChange: (checked) => patch({ showOwnerHeader: checked === true }),
42922
+ "aria-label": "Show our logo and name"
42922
42923
  }
42923
- )
42924
+ ),
42925
+ "Show our logo and name"
42924
42926
  ] }),
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,
42927
+ /* @__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: [
42928
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42929
+ import_design_system118.Checkbox,
42929
42930
  {
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 } })
42931
+ checked: draft.autoAdvance ?? orgKnobs?.choice_auto_advance ?? true,
42932
+ onCheckedChange: (checked) => patch({ autoAdvance: checked === true }),
42933
+ "aria-label": "Next after a choice"
42934
42934
  }
42935
- )
42935
+ ),
42936
+ "Next after a choice"
42936
42937
  ] })
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
42938
  ] }),
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
- )
42939
+ /* @__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: [
42940
+ /* @__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" }),
42941
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "grid grid-cols-2 gap-2", children: [
42942
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("label", { className: "flex flex-col gap-1", children: [
42943
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.Label, { className: "text-xs font-medium", children: "Thank-you title" }),
42944
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42945
+ import_design_system118.BasicInput,
42946
+ {
42947
+ value: draft.thankYou?.title ?? "",
42948
+ placeholder: "Thank you",
42949
+ onChange: (e) => patch({ thankYou: { ...draft.thankYou, title: e.target.value || null } })
42950
+ }
42951
+ )
42952
+ ] }),
42953
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("label", { className: "flex flex-col gap-1", children: [
42954
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.Label, { className: "text-xs font-medium", children: "Then go to (optional)" }),
42955
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42956
+ import_design_system118.BasicInput,
42957
+ {
42958
+ type: "url",
42959
+ inputMode: "url",
42960
+ value: draft.thankYou?.redirectUrl ?? "",
42961
+ placeholder: "https://",
42962
+ onChange: (e) => patch({ thankYou: { ...draft.thankYou, redirectUrl: e.target.value || null } })
42963
+ }
42964
+ )
42965
+ ] }),
42966
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("label", { className: "col-span-2 flex flex-col gap-1", children: [
42967
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.Label, { className: "text-xs font-medium", children: "Thank-you message" }),
42968
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42969
+ import_design_system118.BasicTextarea,
42970
+ {
42971
+ rows: 2,
42972
+ value: draft.thankYou?.body ?? "",
42973
+ placeholder: "What happens next, in your words.",
42974
+ onChange: (e) => patch({ thankYou: { ...draft.thankYou, body: e.target.value || null } })
42975
+ }
42976
+ )
42977
+ ] })
42978
+ ] })
42988
42979
  ] }),
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,
42980
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(WelcomeEditor, { value: draft.welcome, pictures: lookOptions?.pictures ?? null, onChange: (welcome) => patch({ welcome }) }),
42981
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42982
+ EndingsEditor,
42995
42983
  {
42996
- value: draft.look?.accent ?? "",
42997
- noneLabel: "Organization colour",
42998
- onChange: (accent) => patchLook({ accent: accent || null })
42984
+ endings: draft.endings ?? [],
42985
+ pictures: lookOptions?.pictures ?? null,
42986
+ fieldKeys: conditionFields,
42987
+ scored: draft.questions.some((q) => q.points),
42988
+ onChange: (endings) => patch({ endings })
42999
42989
  }
43000
42990
  ),
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,
42991
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "grid grid-cols-2 gap-2", children: [
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: "Name" }),
42994
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.BasicInput, { value: draft.name, onChange: (e) => patch({ name: e.target.value }) })
42995
+ ] }),
42996
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("label", { className: "flex flex-col gap-1", children: [
42997
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.Label, { className: "text-xs font-medium", children: "Flow" }),
42998
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
42999
+ "select",
43007
43000
  {
43008
- label: "Logo",
43009
- pictures: lookOptions.pictures,
43010
- value: draft.look?.logoFileId ?? "",
43011
- noneLabel: "Organization logo",
43012
- onChange: (id) => patchLook({ logoFileId: id || null })
43001
+ className: "h-9 rounded border bg-background px-2 text-sm",
43002
+ value: draft.flow ?? "one-at-a-time",
43003
+ onChange: (e) => patch({ flow: e.target.value }),
43004
+ 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
43005
  }
43014
43006
  )
43015
43007
  ] }),
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,
43008
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("label", { className: "col-span-2 flex flex-col gap-1", children: [
43009
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.Label, { className: "text-xs font-medium", children: "Intro" }),
43010
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43011
+ import_design_system118.BasicTextarea,
43020
43012
  {
43021
- label: "Cover",
43022
- pictures: lookOptions.pictures,
43023
- value: draft.look?.coverFileId ?? "",
43024
- noneLabel: "No cover",
43025
- onChange: (id) => patchLook({ coverFileId: id || null })
43013
+ rows: 2,
43014
+ value: draft.intro ?? "",
43015
+ onChange: (e) => patch({ intro: e.target.value || null })
43026
43016
  }
43027
43017
  )
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
- ] })
43018
+ ] }),
43019
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("label", { className: "flex flex-col gap-1", children: [
43020
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.Label, { className: "text-xs font-medium", children: "Submit button" }),
43021
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43022
+ import_design_system118.BasicInput,
43023
+ {
43024
+ value: draft.submitLabel ?? "",
43025
+ placeholder: "Submit",
43026
+ onChange: (e) => patch({ submitLabel: e.target.value || null })
43027
+ }
43028
+ )
43029
+ ] }),
43030
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(HiddenFieldsEditor, { value: draft.hiddenFields ?? [], onChange: (hiddenFields) => patch({ hiddenFields }) })
43042
43031
  ] }),
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,
43032
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "flex flex-col gap-1.5", "data-records-form-look-editor": "", children: [
43033
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.Label, { className: "text-xs font-medium", children: "Look" }),
43034
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43035
+ AccentSwatches,
43036
+ {
43037
+ value: draft.look?.accent ?? "",
43038
+ noneLabel: "Organization colour",
43039
+ onChange: (accent) => patchLook({ accent: accent || null })
43040
+ }
43041
+ ),
43042
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(ThemeEditor, { theme: draft.theme, pictures: lookOptions?.pictures ?? null, onChange: (theme) => patch({ theme }) }),
43043
+ lookOptions ? /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "flex flex-col gap-2", children: [
43044
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "flex flex-col gap-1", children: [
43045
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "text-xs", children: "Logo" }),
43046
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43047
+ PublicPicturePicker,
43051
43048
  {
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
- })
43049
+ label: "Logo",
43050
+ pictures: lookOptions.pictures,
43051
+ value: draft.look?.logoFileId ?? "",
43052
+ noneLabel: "Organization logo",
43053
+ onChange: (id) => patchLook({ logoFileId: id || null })
43057
43054
  }
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
43055
+ )
43061
43056
  ] }),
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,
43057
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "flex flex-col gap-1", children: [
43058
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "text-xs", children: "Cover" }),
43059
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43060
+ PublicPicturePicker,
43075
43061
  {
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,
43097
- {
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)
43062
+ label: "Cover",
43063
+ pictures: lookOptions.pictures,
43064
+ value: draft.look?.coverFileId ?? "",
43065
+ noneLabel: "No cover",
43066
+ onChange: (id) => patchLook({ coverFileId: id || null })
43122
43067
  }
43123
43068
  )
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,
43069
+ ] })
43070
+ ] }) : lookUnread ? null : /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.Skeleton, { className: "h-12 w-full" }),
43071
+ lookUnread ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(RefusalLine, { error: lookUnread }) : null,
43072
+ refused && isLookRefusal(refused) ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(RefusalLine, { error: refused }) : null
43073
+ ] }),
43074
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43075
+ QuestionList,
43144
43076
  {
43145
43077
  tableId,
43146
- onCancel: () => {
43147
- keysBeforeAdd.current = null;
43148
- setAdding(false);
43149
- },
43150
- onSaved: () => setAdding(false)
43151
- }
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);
43078
+ draft,
43079
+ fields: fields.data ?? [],
43080
+ byKey,
43081
+ adding,
43082
+ onAdding: (on) => {
43083
+ keysBeforeAdd.current = on ? new Set((fields.data ?? []).map((f) => f.key)) : null;
43084
+ setAdding(on);
43160
43085
  },
43161
- children: "Add a question"
43086
+ onAdded: () => setAdding(false),
43087
+ onQuestions: (questions) => patch({ questions }),
43088
+ onQuestion: patchQuestion
43162
43089
  }
43163
- ) })
43090
+ )
43164
43091
  ] })
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
- ] });
43092
+ ] }),
43093
+ draft ? (
43094
+ // BESIDE THE EDITOR, ALWAYS IN VIEW (TYPEFORM-2): it follows the scroll and updates as you type.
43095
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43096
+ "aside",
43097
+ {
43098
+ 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)]",
43099
+ "data-records-form-preview": "",
43100
+ 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 ?? orgKnobs?.show_owner_header ?? true, children: /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(FormRunner, { form: draft, preview: true, autoAdvance: draft.autoAdvance ?? orgKnobs?.choice_auto_advance ?? true }) })
43101
+ }
43102
+ )
43103
+ ) : null
43104
+ ] }) })
43105
+ );
43169
43106
  }
43170
43107
  function PublishRow({
43171
43108
  url,
@@ -43180,10 +43117,10 @@ function PublishRow({
43180
43117
  unsaved = false
43181
43118
  }) {
43182
43119
  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,
43120
+ return /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "flex flex-col gap-1.5 rounded border px-2.5 py-2", children: [
43121
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
43122
+ mayPublish ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43123
+ import_design_system118.Button,
43187
43124
  {
43188
43125
  size: "sm",
43189
43126
  variant: open ? "ghost" : "default",
@@ -43192,8 +43129,8 @@ function PublishRow({
43192
43129
  children: busy ? "\u2026" : open ? "Unpublish" : unsaved ? "Save and publish" : "Publish"
43193
43130
  }
43194
43131
  ) : null,
43195
- state !== "draft" ? /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)(import_jsx_runtime134.Fragment, { children: [
43196
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
43132
+ state !== "draft" ? /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)(import_jsx_runtime133.Fragment, { children: [
43133
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43197
43134
  "a",
43198
43135
  {
43199
43136
  href: url,
@@ -43203,14 +43140,14 @@ function PublishRow({
43203
43140
  children: url
43204
43141
  }
43205
43142
  ),
43206
- /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(import_design_system119.Button, { size: "sm", variant: "outline", onClick: () => onCopy(url), children: copied ? "Copied" : "Copy link" })
43143
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.Button, { size: "sm", variant: "outline", onClick: () => onCopy(url), children: copied ? "Copied" : "Copy link" })
43207
43144
  ] }) : null
43208
43145
  ] }),
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: ",
43146
+ /* @__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] }),
43147
+ 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,
43148
+ !mayPublish ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("p", { className: "w-fit text-xs text-muted-foreground", title: why, children: "Publishing needs table admin" }) : null,
43149
+ 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: [
43150
+ "Copy by hand: ",
43214
43151
  shownUrl
43215
43152
  ] }) : null
43216
43153
  ] });
@@ -43220,7 +43157,7 @@ function Condition({
43220
43157
  fieldKeys,
43221
43158
  onChange
43222
43159
  }) {
43223
- return /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
43160
+ return /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43224
43161
  ConditionGroup,
43225
43162
  {
43226
43163
  lead: "Ask only when",
@@ -43231,26 +43168,348 @@ function Condition({
43231
43168
  }
43232
43169
  );
43233
43170
  }
43171
+ var FORM_STATE_LABELS = {
43172
+ draft: "Not published",
43173
+ open: "Open",
43174
+ closed: "Closed",
43175
+ full: "Full"
43176
+ };
43177
+ function QuestionList({
43178
+ tableId,
43179
+ draft,
43180
+ fields,
43181
+ byKey,
43182
+ adding,
43183
+ onAdding,
43184
+ onAdded,
43185
+ onQuestions,
43186
+ onQuestion
43187
+ }) {
43188
+ const sensors = (0, import_core22.useSensors)(
43189
+ (0, import_core22.useSensor)(import_core22.PointerSensor, { activationConstraint: { distance: 4 } }),
43190
+ (0, import_core22.useSensor)(import_core22.KeyboardSensor, { coordinateGetter: import_sortable.sortableKeyboardCoordinates })
43191
+ );
43192
+ const askable = askableFields([...fields]);
43193
+ const unasked = askable.filter((f) => !draft.questions.some((q) => q.field === f.key));
43194
+ 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 }));
43195
+ const label = (key) => {
43196
+ const f = byKey.get(key);
43197
+ return draft.questions.find((q) => q.field === key)?.ask || (f ? fieldName(f) : key);
43198
+ };
43199
+ function dragEnd(event) {
43200
+ const from = draft.questions.findIndex((q) => q.field === event.active.id);
43201
+ const to = event.over ? draft.questions.findIndex((q) => q.field === event.over.id) : -1;
43202
+ if (from < 0 || to < 0 || from === to) return;
43203
+ onQuestions((0, import_sortable.arrayMove)(draft.questions, from, to));
43204
+ }
43205
+ const notAskable = whyNotAskable([...fields]);
43206
+ const leftUnasked = requiredLeftUnasked([...fields], draft.questions.map((q) => q.field));
43207
+ return /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "flex flex-col gap-1.5", "data-records-form-questions": "", children: [
43208
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
43209
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "font-medium text-foreground", children: "Questions" }),
43210
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "tabular-nums", children: draft.questions.length })
43211
+ ] }),
43212
+ /* @__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)(
43213
+ QuestionRow,
43214
+ {
43215
+ index,
43216
+ question,
43217
+ field: byKey.get(question.field) ?? null,
43218
+ draft,
43219
+ conditionFields: asked3.filter((f) => f.key !== question.field),
43220
+ label,
43221
+ onChange: (change) => onQuestion(index, change),
43222
+ onRemove: () => onQuestions(draft.questions.filter((q) => q.field !== question.field))
43223
+ },
43224
+ question.field
43225
+ )) }) }),
43226
+ 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)(
43227
+ import_design_system118.Button,
43228
+ {
43229
+ size: "sm",
43230
+ variant: "outline",
43231
+ "aria-label": `Ask for ${fieldName(f)}`,
43232
+ onClick: () => onQuestions([...draft.questions, { field: f.key }]),
43233
+ children: [
43234
+ "+ ",
43235
+ fieldName(f)
43236
+ ]
43237
+ },
43238
+ f.id
43239
+ )) }) : null,
43240
+ 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,
43241
+ leftUnasked ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43242
+ import_design_system117.ErrorBox,
43243
+ {
43244
+ className: "rounded border border-destructive/40 bg-destructive/5 px-2 py-1.5 text-xs text-destructive",
43245
+ message: leftUnasked,
43246
+ operation: "Build a form",
43247
+ origin: "records-ui/FormBuilder"
43248
+ }
43249
+ ) : null,
43250
+ 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" }) })
43251
+ ] });
43252
+ }
43253
+ function QuestionRow({
43254
+ index,
43255
+ question,
43256
+ field: field2,
43257
+ draft,
43258
+ conditionFields,
43259
+ label,
43260
+ onChange,
43261
+ onRemove
43262
+ }) {
43263
+ const sortable = (0, import_sortable.useSortable)({ id: question.field });
43264
+ const [open, setOpen] = (0, import_react232.useState)(true);
43265
+ const name = field2 ? fieldName(field2) : question.field;
43266
+ return /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)(
43267
+ "div",
43268
+ {
43269
+ ref: sortable.setNodeRef,
43270
+ style: { transform: import_utilities.CSS.Transform.toString(sortable.transform), transition: sortable.transition },
43271
+ className: (0, import_design_system118.cn)("rounded border bg-background px-2 py-1.5", sortable.isDragging && "z-10 shadow-md"),
43272
+ "data-records-form-question-row": question.field,
43273
+ children: [
43274
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "flex items-center gap-2", children: [
43275
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43276
+ "button",
43277
+ {
43278
+ type: "button",
43279
+ className: "cursor-grab touch-none rounded px-1 text-muted-foreground hover:bg-muted active:cursor-grabbing",
43280
+ "aria-label": `Move ${name}`,
43281
+ title: "Drag to reorder",
43282
+ "data-records-form-drag": "",
43283
+ ...sortable.attributes,
43284
+ ...sortable.listeners,
43285
+ children: "\u22EE\u22EE"
43286
+ }
43287
+ ),
43288
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "w-5 text-right text-xs tabular-nums text-muted-foreground", children: index + 1 }),
43289
+ /* @__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 }),
43290
+ field2?.required || question.required ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "text-xs text-foreground/80", children: "required" }) : null,
43291
+ !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,
43292
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_design_system118.Button, { size: "sm", variant: "ghost", onClick: onRemove, "aria-label": `Stop asking ${name}`, children: "Remove" })
43293
+ ] }),
43294
+ open && field2 ? /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "mt-1.5 grid grid-cols-2 gap-2 pl-7", children: [
43295
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43296
+ import_design_system118.BasicInput,
43297
+ {
43298
+ className: "h-8 text-xs",
43299
+ placeholder: name,
43300
+ "aria-label": `How to ask for ${name}`,
43301
+ title: "Use {{question}} to recall an earlier answer",
43302
+ value: question.ask ?? "",
43303
+ onChange: (e) => onChange({ ask: e.target.value || null })
43304
+ }
43305
+ ),
43306
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43307
+ import_design_system118.BasicInput,
43308
+ {
43309
+ className: "h-8 text-xs",
43310
+ placeholder: "Help text",
43311
+ "aria-label": `Help text for ${name}`,
43312
+ value: question.help ?? "",
43313
+ onChange: (e) => onChange({ help: e.target.value || null })
43314
+ }
43315
+ ),
43316
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("label", { className: "col-span-2 flex items-center gap-2 text-xs", children: [
43317
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43318
+ import_design_system118.Checkbox,
43319
+ {
43320
+ checked: field2.required ? true : question.required === true,
43321
+ disabled: field2.required === true,
43322
+ "aria-label": `${name} is required`,
43323
+ onCheckedChange: (checked) => onChange({ required: checked === true ? true : null })
43324
+ }
43325
+ ),
43326
+ "Required"
43327
+ ] }),
43328
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(Condition, { question, fieldKeys: conditionFields, onChange: (showIf) => onChange({ showIf }) }),
43329
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43330
+ QuestionLogic,
43331
+ {
43332
+ question,
43333
+ index,
43334
+ questions: draft.questions,
43335
+ endings: draft.endings ?? [],
43336
+ field: field2,
43337
+ fieldKeys: conditionFields,
43338
+ label,
43339
+ onChange
43340
+ }
43341
+ )
43342
+ ] }) : null
43343
+ ]
43344
+ }
43345
+ );
43346
+ }
43347
+ function NotifyOnAnswer({
43348
+ tableId,
43349
+ tableName: tableName2,
43350
+ formName,
43351
+ ruleId,
43352
+ notify,
43353
+ emailQuestion,
43354
+ onRule,
43355
+ onNotify
43356
+ }) {
43357
+ const client = (0, import_react233.useRecordsClient)();
43358
+ const [subs, setSubs] = (0, import_react232.useState)(null);
43359
+ const [busy, setBusy] = (0, import_react232.useState)(false);
43360
+ const [error, setError] = (0, import_react232.useState)(null);
43361
+ const [said2, setSaid] = (0, import_react232.useState)(null);
43362
+ const [url, setUrl] = (0, import_react232.useState)(notify?.webhookUrl ?? "");
43363
+ (0, import_react232.useEffect)(() => setUrl(notify?.webhookUrl ?? ""), [notify?.webhookUrl]);
43364
+ (0, import_react232.useEffect)(() => {
43365
+ let cancelled = false;
43366
+ void client.subscriptions({ table_id: tableId }).then((r) => {
43367
+ if (!cancelled && r.ok) setSubs(r.data);
43368
+ });
43369
+ return () => {
43370
+ cancelled = true;
43371
+ };
43372
+ }, [client, tableId, ruleId]);
43373
+ const chosen = subs?.find((s) => s.rule_id === ruleId) ?? null;
43374
+ async function emailMe(on) {
43375
+ setBusy(true);
43376
+ setError(null);
43377
+ try {
43378
+ if (!on) {
43379
+ onRule(null);
43380
+ if (ruleId) await client.recordDelete({ record_id: ruleId });
43381
+ return;
43382
+ }
43383
+ const listed = await client.views({ table_id: tableId });
43384
+ if (!listed.ok) return setError(listed.error);
43385
+ let viewId = listed.data.find((v) => !v.filters || Object.keys(v.filters).length === 0)?.view_id ?? null;
43386
+ if (!viewId) {
43387
+ const made = await client.viewDeclare({ table_id: tableId, spec: { name: `Every ${tableName2}`, filters: null } });
43388
+ if (!made.ok) return setError(made.error);
43389
+ viewId = made.data;
43390
+ }
43391
+ const declared2 = await client.subscriptionDeclare({
43392
+ table_id: tableId,
43393
+ spec: {
43394
+ name: `${formName} \u2014 new answers`,
43395
+ saved_view_id: viewId,
43396
+ cadence: "instant",
43397
+ schedule: null,
43398
+ channel: "email",
43399
+ quiet_hours: null,
43400
+ recipient_user_id: client.config.actor.user_id ?? null,
43401
+ event_key: "records.changed"
43402
+ }
43403
+ });
43404
+ if (!declared2.ok) return setError(declared2.error);
43405
+ onRule(declared2.data);
43406
+ } finally {
43407
+ setBusy(false);
43408
+ }
43409
+ }
43410
+ async function saveWebhook() {
43411
+ const next = url.trim();
43412
+ if (next === (notify?.webhookUrl ?? "")) return;
43413
+ setBusy(true);
43414
+ setError(null);
43415
+ setSaid(null);
43416
+ try {
43417
+ if (next === "") {
43418
+ if (notify?.webhookAutomationId) await client.automationArchive({ automation_id: notify.webhookAutomationId });
43419
+ onNotify(notify?.emailRespondent ? { ...notify, webhookUrl: null, webhookAutomationId: null } : null);
43420
+ return;
43421
+ }
43422
+ const declared2 = await client.automationDeclare({
43423
+ table_id: tableId,
43424
+ automation_id: notify?.webhookAutomationId ?? null,
43425
+ spec: { name: `${formName} \u2014 webhook`, trigger: { on: "form_answered" }, actions: [{ do: "webhook", url: next }] }
43426
+ });
43427
+ if (!declared2.ok) return setError(declared2.error);
43428
+ if (!declared2.data.ok) {
43429
+ setSaid(Object.values(declared2.data._errors)[0] ?? "Not saved");
43430
+ return;
43431
+ }
43432
+ onNotify({ ...notify ?? {}, webhookUrl: next, webhookAutomationId: declared2.data.automation_id });
43433
+ } finally {
43434
+ setBusy(false);
43435
+ }
43436
+ }
43437
+ 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: [
43438
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "font-medium", children: "Notify on answer" }),
43439
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("div", { className: "flex flex-wrap items-center gap-x-4 gap-y-1", children: [
43440
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("label", { className: "flex items-center gap-2", title: "An email to you each time somebody answers.", children: [
43441
+ /* @__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" }),
43442
+ "Email me",
43443
+ chosen && chosen.channel !== "email" ? /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("span", { className: "text-muted-foreground", children: [
43444
+ "(",
43445
+ chosen.channel === "sms" ? "text" : "in app",
43446
+ ")"
43447
+ ] }) : null
43448
+ ] }),
43449
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)(
43450
+ "label",
43451
+ {
43452
+ className: (0, import_design_system118.cn)("flex items-center gap-2", !emailQuestion && "text-muted-foreground"),
43453
+ title: emailQuestion ? "An email to the person who answered, in your ending's words." : "Add an email question first.",
43454
+ children: [
43455
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43456
+ import_design_system118.Checkbox,
43457
+ {
43458
+ checked: Boolean(notify?.emailRespondent) && Boolean(emailQuestion),
43459
+ disabled: busy || !emailQuestion,
43460
+ onCheckedChange: (c) => onNotify({ ...notify ?? {}, emailRespondent: c === true ? true : null, respondentField: c === true ? emailQuestion : null }),
43461
+ "aria-label": "Email the respondent"
43462
+ }
43463
+ ),
43464
+ "Email the respondent"
43465
+ ]
43466
+ }
43467
+ )
43468
+ ] }),
43469
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsxs)("label", { className: "flex items-center gap-2", children: [
43470
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("span", { className: "shrink-0", children: "Webhook" }),
43471
+ /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(
43472
+ import_design_system118.BasicInput,
43473
+ {
43474
+ type: "url",
43475
+ inputMode: "url",
43476
+ className: "h-8 text-xs",
43477
+ placeholder: "https://",
43478
+ "aria-label": "Webhook URL",
43479
+ value: url,
43480
+ disabled: busy,
43481
+ onChange: (e) => setUrl(e.target.value),
43482
+ onBlur: () => void saveWebhook(),
43483
+ onKeyDown: (e) => {
43484
+ if (e.key === "Enter") void saveWebhook();
43485
+ }
43486
+ }
43487
+ )
43488
+ ] }),
43489
+ said2 ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)("p", { className: "text-destructive", children: said2 }) : null,
43490
+ error ? /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(RefusalLine, { error }) : null
43491
+ ] });
43492
+ }
43234
43493
 
43235
43494
  // 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");
43495
+ var import_react234 = require("react");
43496
+ var import_react235 = require("@ai-matrx/records/react");
43497
+ var import_design_system119 = require("@ai-matrx/design-system");
43498
+ var import_jsx_runtime134 = require("react/jsx-runtime");
43240
43499
  function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, className }) {
43241
- const client = (0, import_react237.useRecordsClient)();
43500
+ const client = (0, import_react235.useRecordsClient)();
43242
43501
  const claimSeed = useSeedGuard();
43243
- const table = (0, import_react237.useTable)(tableId);
43502
+ const table = (0, import_react235.useTable)(tableId);
43244
43503
  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 () => {
43504
+ const fields = (0, import_react235.useFields)(tableId);
43505
+ const [templates, setTemplates] = (0, import_react234.useState)(null);
43506
+ const [error, setError] = (0, import_react234.useState)(null);
43507
+ const [activeId, setActiveId] = (0, import_react234.useState)(activeTemplateId ?? null);
43508
+ const [draftName, setDraftName] = (0, import_react234.useState)("");
43509
+ const [draftBody, setDraftBody] = (0, import_react234.useState)("");
43510
+ const [unresolved, setUnresolved] = (0, import_react234.useState)([]);
43511
+ const [saving, setSaving] = (0, import_react234.useState)(false);
43512
+ const load = (0, import_react234.useCallback)(async () => {
43254
43513
  const held = await client.docTemplates({ table_id: tableId });
43255
43514
  if (!held.ok) {
43256
43515
  setError(held.error);
@@ -43281,10 +43540,10 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
43281
43540
  setError(null);
43282
43541
  setTemplates(rows);
43283
43542
  }, [client, tableId, seed, fields.data]);
43284
- (0, import_react236.useEffect)(() => {
43543
+ (0, import_react234.useEffect)(() => {
43285
43544
  void load();
43286
43545
  }, [load]);
43287
- (0, import_react236.useEffect)(() => {
43546
+ (0, import_react234.useEffect)(() => {
43288
43547
  if (!templates || templates.length === 0) return;
43289
43548
  const chosen = templates.find((t) => t.id === (activeTemplateId ?? activeId)) ?? templates[0];
43290
43549
  if (chosen.id !== activeId) setActiveId(chosen.id);
@@ -43292,7 +43551,7 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
43292
43551
  setDraftBody(chosen.body);
43293
43552
  onActiveTemplate?.(chosen);
43294
43553
  }, [templates, activeTemplateId]);
43295
- (0, import_react236.useEffect)(() => {
43554
+ (0, import_react234.useEffect)(() => {
43296
43555
  let cancelled = false;
43297
43556
  if (draftBody.trim() === "") {
43298
43557
  setUnresolved([]);
@@ -43322,25 +43581,25 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
43322
43581
  setActiveId(saved.data);
43323
43582
  await load();
43324
43583
  }
43325
- if (fields.error) return /* @__PURE__ */ (0, import_jsx_runtime135.jsx)(RefusalNotice, { error: fields.error, className });
43584
+ if (fields.error) return /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(RefusalNotice, { error: fields.error, className });
43326
43585
  if (error) {
43327
- return /* @__PURE__ */ (0, import_jsx_runtime135.jsx)(
43586
+ return /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
43328
43587
  RefusalNotice,
43329
43588
  {
43330
43589
  error,
43331
43590
  className,
43332
- actions: /* @__PURE__ */ (0, import_jsx_runtime135.jsx)(import_design_system120.Button, { size: "sm", variant: "ghost", onClick: () => void load(), children: "Try again" })
43591
+ actions: /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(import_design_system119.Button, { size: "sm", variant: "ghost", onClick: () => void load(), children: "Try again" })
43333
43592
  }
43334
43593
  );
43335
43594
  }
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) });
43595
+ 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
43596
  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") });
43597
+ 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
43598
  }
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,
43599
+ return /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("div", { className: (0, import_design_system119.cn)("flex flex-col gap-2", className), children: [
43600
+ /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("div", { className: "flex items-center gap-1 overflow-x-auto", role: "group", "aria-label": "Templates", children: [
43601
+ templates.map((t) => /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)(
43602
+ import_design_system119.Button,
43344
43603
  {
43345
43604
  size: "sm",
43346
43605
  variant: t.id === activeId ? "secondary" : "ghost",
@@ -43352,7 +43611,7 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
43352
43611
  },
43353
43612
  children: [
43354
43613
  t.name,
43355
- /* @__PURE__ */ (0, import_jsx_runtime135.jsxs)("span", { className: "ml-1 text-[10px] opacity-70", children: [
43614
+ /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("span", { className: "ml-1 text-[10px] opacity-70", children: [
43356
43615
  "v",
43357
43616
  t.template_version
43358
43617
  ] })
@@ -43360,8 +43619,8 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
43360
43619
  },
43361
43620
  t.id
43362
43621
  )),
43363
- /* @__PURE__ */ (0, import_jsx_runtime135.jsx)(
43364
- import_design_system120.Button,
43622
+ /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
43623
+ import_design_system119.Button,
43365
43624
  {
43366
43625
  size: "sm",
43367
43626
  variant: "ghost",
@@ -43373,16 +43632,16 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
43373
43632
  children: "New template"
43374
43633
  }
43375
43634
  ),
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" })
43635
+ /* @__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
43636
  ] }),
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" })
43637
+ /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("label", { className: "flex flex-col gap-1", children: [
43638
+ /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(import_design_system119.Label, { className: "text-xs font-medium", children: "Name" }),
43639
+ /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(import_design_system119.BasicInput, { value: draftName, onChange: (e) => setDraftName(e.target.value), placeholder: "Agreement" })
43381
43640
  ] }),
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,
43641
+ /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
43642
+ /* @__PURE__ */ (0, import_jsx_runtime134.jsx)("span", { className: "text-xs text-muted-foreground", children: "Insert" }),
43643
+ (fields.data ?? []).map((field2) => /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
43644
+ import_design_system119.Button,
43386
43645
  {
43387
43646
  size: "sm",
43388
43647
  variant: "ghost",
@@ -43393,8 +43652,8 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
43393
43652
  field2.id
43394
43653
  ))
43395
43654
  ] }),
43396
- /* @__PURE__ */ (0, import_jsx_runtime135.jsx)(
43397
- import_design_system120.BasicTextarea,
43655
+ /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
43656
+ import_design_system119.BasicTextarea,
43398
43657
  {
43399
43658
  rows: 10,
43400
43659
  "aria-label": "Template body",
@@ -43403,33 +43662,33 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
43403
43662
  onChange: (e) => setDraftBody(e.target.value)
43404
43663
  }
43405
43664
  ),
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)(
43665
+ 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: [
43666
+ /* @__PURE__ */ (0, import_jsx_runtime134.jsx)("code", { className: "text-destructive", children: token.raw }),
43667
+ /* @__PURE__ */ (0, import_jsx_runtime134.jsx)("span", { "aria-hidden": "true", children: "\u2014" }),
43668
+ /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(
43410
43669
  RefusalLine,
43411
43670
  {
43412
43671
  error: refusal("invalid_argument", token.why, "Change it to a Field this table has, or take it out.")
43413
43672
  }
43414
43673
  )
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
43674
+ ] }, 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
43675
  ] });
43417
43676
  }
43418
43677
 
43419
43678
  // 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");
43679
+ var import_react236 = require("react");
43680
+ var import_react237 = require("@ai-matrx/records/react");
43681
+ var import_design_system120 = require("@ai-matrx/design-system");
43682
+ var import_jsx_runtime135 = require("react/jsx-runtime");
43424
43683
  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 () => {
43684
+ const client = (0, import_react237.useRecordsClient)();
43685
+ const [preview, setPreview] = (0, import_react236.useState)(null);
43686
+ const [renders, setRenders] = (0, import_react236.useState)(null);
43687
+ const [showing, setShowing] = (0, import_react236.useState)(null);
43688
+ const [error, setError] = (0, import_react236.useState)(null);
43689
+ const [busy, setBusy] = (0, import_react236.useState)(null);
43690
+ const paper = (0, import_react236.useRef)(null);
43691
+ const load = (0, import_react236.useCallback)(async () => {
43433
43692
  const [body, held] = await Promise.all([
43434
43693
  client.docRenderBody({ template_id: templateId, record_id: recordId }),
43435
43694
  client.docRenders({ record_id: recordId })
@@ -43446,7 +43705,7 @@ function DocRender({ templateId, recordId, filename, onRendered, className }) {
43446
43705
  setPreview(body.data);
43447
43706
  setRenders(held.data.filter((r) => r.template_id === templateId));
43448
43707
  }, [client, templateId, recordId]);
43449
- (0, import_react238.useEffect)(() => {
43708
+ (0, import_react236.useEffect)(() => {
43450
43709
  void load();
43451
43710
  }, [load]);
43452
43711
  async function freeze() {
@@ -43486,22 +43745,22 @@ function DocRender({ templateId, recordId, filename, onRendered, className }) {
43486
43745
  }
43487
43746
  }
43488
43747
  if (error) {
43489
- return /* @__PURE__ */ (0, import_jsx_runtime136.jsx)(
43748
+ return /* @__PURE__ */ (0, import_jsx_runtime135.jsx)(
43490
43749
  RefusalNotice,
43491
43750
  {
43492
43751
  error,
43493
43752
  className,
43494
- actions: /* @__PURE__ */ (0, import_jsx_runtime136.jsx)(import_design_system121.Button, { size: "sm", variant: "ghost", onClick: () => void load(), children: "Try again" })
43753
+ actions: /* @__PURE__ */ (0, import_jsx_runtime135.jsx)(import_design_system120.Button, { size: "sm", variant: "ghost", onClick: () => void load(), children: "Try again" })
43495
43754
  }
43496
43755
  );
43497
43756
  }
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) });
43757
+ 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
43758
  const frozen = showing ? renders.find((r) => r.id === showing) ?? null : null;
43500
43759
  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,
43760
+ return /* @__PURE__ */ (0, import_jsx_runtime135.jsxs)("div", { className: (0, import_design_system120.cn)("flex flex-col gap-2", className), children: [
43761
+ /* @__PURE__ */ (0, import_jsx_runtime135.jsxs)("div", { className: "flex items-center gap-1 text-xs", children: [
43762
+ /* @__PURE__ */ (0, import_jsx_runtime135.jsx)(
43763
+ import_design_system120.Button,
43505
43764
  {
43506
43765
  size: "sm",
43507
43766
  variant: frozen ? "ghost" : "secondary",
@@ -43510,8 +43769,8 @@ function DocRender({ templateId, recordId, filename, onRendered, className }) {
43510
43769
  children: "As it reads now"
43511
43770
  }
43512
43771
  ),
43513
- renders.map((render) => /* @__PURE__ */ (0, import_jsx_runtime136.jsx)(
43514
- import_design_system121.Button,
43772
+ renders.map((render) => /* @__PURE__ */ (0, import_jsx_runtime135.jsx)(
43773
+ import_design_system120.Button,
43515
43774
  {
43516
43775
  size: "sm",
43517
43776
  variant: render.id === showing ? "secondary" : "ghost",
@@ -43522,33 +43781,33 @@ function DocRender({ templateId, recordId, filename, onRendered, className }) {
43522
43781
  },
43523
43782
  render.id
43524
43783
  )),
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" })
43784
+ /* @__PURE__ */ (0, import_jsx_runtime135.jsxs)("span", { className: "ml-auto flex items-center gap-1", children: [
43785
+ /* @__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" }),
43786
+ /* @__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
43787
  ] })
43529
43788
  ] }),
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." })
43789
+ /* @__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 }),
43790
+ /* @__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
43791
  ] });
43533
43792
  }
43534
43793
 
43535
43794
  // 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");
43795
+ var import_react238 = require("react");
43796
+ var import_react239 = require("@ai-matrx/records/react");
43797
+ var import_design_system121 = require("@ai-matrx/design-system");
43798
+ var import_react240 = require("@ai-matrx/records/react");
43799
+ var import_jsx_runtime136 = require("react/jsx-runtime");
43541
43800
  function SignBlock({ tableId, recordId, render, className }) {
43542
- const client = (0, import_react241.useRecordsClient)();
43543
- const table = (0, import_react242.useTable)(tableId);
43801
+ const client = (0, import_react239.useRecordsClient)();
43802
+ const table = (0, import_react240.useTable)(tableId);
43544
43803
  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 () => {
43804
+ const fields = (0, import_react239.useFields)(tableId);
43805
+ const [signatures, setSignatures] = (0, import_react238.useState)(null);
43806
+ const [verdicts, setVerdicts] = (0, import_react238.useState)({});
43807
+ const [error, setError] = (0, import_react238.useState)(null);
43808
+ const [name, setName] = (0, import_react238.useState)("");
43809
+ const [busy, setBusy] = (0, import_react238.useState)(false);
43810
+ const load = (0, import_react238.useCallback)(async () => {
43552
43811
  const held = await client.docSignatures({ record_id: recordId });
43553
43812
  if (!held.ok) {
43554
43813
  setError(held.error);
@@ -43563,7 +43822,7 @@ function SignBlock({ tableId, recordId, render, className }) {
43563
43822
  }
43564
43823
  setVerdicts(answers);
43565
43824
  }, [client, recordId]);
43566
- (0, import_react240.useEffect)(() => {
43825
+ (0, import_react238.useEffect)(() => {
43567
43826
  void load();
43568
43827
  }, [load]);
43569
43828
  async function sign(field2) {
@@ -43583,36 +43842,36 @@ function SignBlock({ tableId, recordId, render, className }) {
43583
43842
  setName("");
43584
43843
  await load();
43585
43844
  }
43586
- if (fields.error) return /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(RefusalNotice, { error: fields.error, className });
43845
+ if (fields.error) return /* @__PURE__ */ (0, import_jsx_runtime136.jsx)(RefusalNotice, { error: fields.error, className });
43587
43846
  if (error) {
43588
- return /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(
43847
+ return /* @__PURE__ */ (0, import_jsx_runtime136.jsx)(
43589
43848
  RefusalNotice,
43590
43849
  {
43591
43850
  error,
43592
43851
  className,
43593
- actions: /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(import_design_system122.Button, { size: "sm", variant: "ghost", onClick: () => void load(), children: "Try again" })
43852
+ actions: /* @__PURE__ */ (0, import_jsx_runtime136.jsx)(import_design_system121.Button, { size: "sm", variant: "ghost", onClick: () => void load(), children: "Try again" })
43594
43853
  }
43595
43854
  );
43596
43855
  }
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) });
43856
+ 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
43857
  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: [
43858
+ return /* @__PURE__ */ (0, import_jsx_runtime136.jsxs)("div", { className: (0, import_design_system121.cn)("flex flex-col gap-2", className), children: [
43859
+ /* @__PURE__ */ (0, import_jsx_runtime136.jsxs)("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
43860
+ /* @__PURE__ */ (0, import_jsx_runtime136.jsx)("span", { className: "font-medium text-foreground", children: "Signatures" }),
43861
+ /* @__PURE__ */ (0, import_jsx_runtime136.jsxs)("span", { children: [
43603
43862
  signatures.length,
43604
43863
  " on this record"
43605
43864
  ] })
43606
43865
  ] }),
43607
- signatures.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("ul", { className: "flex flex-col gap-1", children: signatures.map((signature) => {
43866
+ signatures.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime136.jsx)("ul", { className: "flex flex-col gap-1", children: signatures.map((signature) => {
43608
43867
  const verdict = verdicts[signature.id];
43609
43868
  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: [
43869
+ return /* @__PURE__ */ (0, import_jsx_runtime136.jsxs)("li", { className: "rounded border px-2 py-1.5 text-xs", children: [
43870
+ /* @__PURE__ */ (0, import_jsx_runtime136.jsxs)("div", { className: "flex items-center gap-2", children: [
43871
+ /* @__PURE__ */ (0, import_jsx_runtime136.jsx)("span", { className: "font-medium", children: signature.signer_name }),
43872
+ /* @__PURE__ */ (0, import_jsx_runtime136.jsx)("span", { className: "text-muted-foreground", children: signature.field_key }),
43873
+ /* @__PURE__ */ (0, import_jsx_runtime136.jsx)("span", { className: "text-muted-foreground", children: new Date(signature.signed_at).toLocaleString() }),
43874
+ /* @__PURE__ */ (0, import_jsx_runtime136.jsxs)("span", { className: "ml-auto font-mono text-[10px] text-muted-foreground", children: [
43616
43875
  "v",
43617
43876
  signature.document_version,
43618
43877
  " \xB7 ",
@@ -43622,7 +43881,7 @@ function SignBlock({ tableId, recordId, render, className }) {
43622
43881
  intact === false ? (
43623
43882
  // THE STORE REFUSED TO CALL IT INTACT: its reason goes through the one
43624
43883
  // formatter, with what to do next, never raw in red (lane REFUSAL-SWEEP).
43625
- /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(
43884
+ /* @__PURE__ */ (0, import_jsx_runtime136.jsx)(
43626
43885
  RefusalLine,
43627
43886
  {
43628
43887
  className: "mt-0.5",
@@ -43633,22 +43892,22 @@ function SignBlock({ tableId, recordId, render, className }) {
43633
43892
  )
43634
43893
  }
43635
43894
  )
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 ?? "") })
43895
+ ) : /* @__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
43896
  ] }, signature.id);
43638
43897
  }) }) : 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: [
43898
+ !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
43899
  "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" }),
43900
+ /* @__PURE__ */ (0, import_jsx_runtime136.jsx)("code", { children: "signature" }),
43642
43901
  " \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) => {
43902
+ ] }) : !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
43903
  const already = signatures.find((s) => s.field_key === field2.key);
43645
43904
  if (already) {
43646
- return /* @__PURE__ */ (0, import_jsx_runtime137.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
43905
+ return /* @__PURE__ */ (0, import_jsx_runtime136.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
43647
43906
  fieldName(field2),
43648
43907
  " 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
43908
  ] }, field2.id);
43650
43909
  }
43651
- return /* @__PURE__ */ (0, import_jsx_runtime137.jsxs)(
43910
+ return /* @__PURE__ */ (0, import_jsx_runtime136.jsxs)(
43652
43911
  "form",
43653
43912
  {
43654
43913
  className: "flex items-center gap-1",
@@ -43657,8 +43916,8 @@ function SignBlock({ tableId, recordId, render, className }) {
43657
43916
  if (name.trim() !== "") void sign(field2);
43658
43917
  },
43659
43918
  children: [
43660
- /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(
43661
- import_design_system122.BasicInput,
43919
+ /* @__PURE__ */ (0, import_jsx_runtime136.jsx)(
43920
+ import_design_system121.BasicInput,
43662
43921
  {
43663
43922
  className: "h-8 w-56 text-xs",
43664
43923
  "aria-label": `Sign ${fieldName(field2)}`,
@@ -43667,8 +43926,8 @@ function SignBlock({ tableId, recordId, render, className }) {
43667
43926
  onChange: (e) => setName(e.target.value)
43668
43927
  }
43669
43928
  ),
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: [
43929
+ /* @__PURE__ */ (0, import_jsx_runtime136.jsx)(import_design_system121.Button, { size: "sm", type: "submit", disabled: busy || name.trim() === "", children: "Sign" }),
43930
+ /* @__PURE__ */ (0, import_jsx_runtime136.jsxs)("span", { className: "text-xs text-muted-foreground", children: [
43672
43931
  "seals version ",
43673
43932
  render.template_version,
43674
43933
  " \xB7 ",
@@ -43683,15 +43942,270 @@ function SignBlock({ tableId, recordId, render, className }) {
43683
43942
  ] });
43684
43943
  }
43685
43944
 
43945
+ // src/NotifyRuleEditor.tsx
43946
+ var import_react241 = require("react");
43947
+ var import_react242 = require("@ai-matrx/records/react");
43948
+ var import_records57 = require("@ai-matrx/records");
43949
+ var import_design_system122 = require("@ai-matrx/design-system");
43950
+ var import_jsx_runtime137 = require("react/jsx-runtime");
43951
+ var CADENCE_WORDS2 = SUBSCRIPTION_CADENCE_LABEL;
43952
+ var CHANNEL_WORDS3 = SUBSCRIPTION_CHANNEL_LABEL;
43953
+ function NotifyRuleEditor({ tableId, seed, chosenRuleId, onChoose, className }) {
43954
+ const client = (0, import_react242.useRecordsClient)();
43955
+ const host = useRecordsUi();
43956
+ const table = (0, import_react242.useTable)(tableId);
43957
+ const rights = useTableRights(table.data);
43958
+ const [subscriptions, setSubscriptions] = (0, import_react241.useState)(null);
43959
+ const [cadences, setCadences] = (0, import_react241.useState)([]);
43960
+ const [views, setViews] = (0, import_react241.useState)(null);
43961
+ const [error, setError] = (0, import_react241.useState)(null);
43962
+ const [busy, setBusy] = (0, import_react241.useState)(false);
43963
+ const [stopping, setStopping] = (0, import_react241.useState)(null);
43964
+ const load = (0, import_react241.useCallback)(async () => {
43965
+ const [held, offered] = await Promise.all([
43966
+ // THE PERSON'S OWN DOOR, not the notifier's. It answers what is addressed
43967
+ // to them plus — only where they hold admin on this Table — anyone's over
43968
+ // it, and it is narrowed to Tables they can already open.
43969
+ client.subscriptions({ table_id: tableId }),
43970
+ // The cadences are ASKED of the store, never a list typed into a picker
43971
+ // here that could drift from what the digest runner understands.
43972
+ client.subscriptionCadences()
43973
+ ]);
43974
+ if (!held.ok) {
43975
+ setError(held.error);
43976
+ return;
43977
+ }
43978
+ setError(null);
43979
+ setSubscriptions(held.data);
43980
+ if (offered.ok) setCadences(offered.data);
43981
+ if (host.savedViews) {
43982
+ setViews(await host.savedViews());
43983
+ return;
43984
+ }
43985
+ const listed = await client.views({ table_id: tableId });
43986
+ if (!listed.ok) {
43987
+ setError(listed.error);
43988
+ return;
43989
+ }
43990
+ setViews(listed.data.map((v) => ({ id: v.view_id, name: v.name })));
43991
+ }, [client, host, tableId]);
43992
+ (0, import_react241.useEffect)(() => {
43993
+ void load();
43994
+ }, [load]);
43995
+ const write = (0, import_react241.useCallback)(
43996
+ async (spec) => {
43997
+ const declared2 = await client.subscriptionDeclare({
43998
+ table_id: tableId,
43999
+ spec: {
44000
+ name: spec.name,
44001
+ saved_view_id: spec.savedViewId,
44002
+ cadence: spec.cadence ?? "instant",
44003
+ schedule: spec.schedule ?? null,
44004
+ channel: spec.channel ?? "in_app",
44005
+ quiet_hours: spec.quietHours ?? null,
44006
+ // "Tell me" is the default, and the store refuses a subscription with
44007
+ // nobody to tell rather than writing one that fires at nobody.
44008
+ recipient_user_id: spec.recipientUserId ?? client.config.actor.user_id ?? null,
44009
+ event_key: spec.eventKey ?? "records.changed"
44010
+ }
44011
+ });
44012
+ if (!declared2.ok) {
44013
+ setError(declared2.error);
44014
+ return null;
44015
+ }
44016
+ return declared2.data;
44017
+ },
44018
+ [client, tableId]
44019
+ );
44020
+ (0, import_react241.useEffect)(() => {
44021
+ if (!subscriptions || !seed || seed.length === 0) return;
44022
+ const missing = seed.filter((s) => !subscriptions.some((held) => held.name === s.name));
44023
+ if (missing.length === 0) return;
44024
+ void (async () => {
44025
+ for (const spec of missing) {
44026
+ if (!await write(spec)) return;
44027
+ }
44028
+ await load();
44029
+ })();
44030
+ }, [subscriptions, seed, write, load]);
44031
+ async function subscribe(viewId, viewName, cadence, channel, schedule, quiet2) {
44032
+ setBusy(true);
44033
+ const ok = await write({
44034
+ name: `${viewName} \u2014 ${CADENCE_WORDS2[cadence] ?? cadence}`,
44035
+ savedViewId: viewId,
44036
+ cadence,
44037
+ channel,
44038
+ schedule: schedule.trim() === "" ? null : schedule.trim(),
44039
+ quietHours: quiet2
44040
+ });
44041
+ setBusy(false);
44042
+ if (!ok) return;
44043
+ onChoose?.(ok);
44044
+ await load();
44045
+ }
44046
+ async function makeEveryRecordView() {
44047
+ setBusy(true);
44048
+ const made = await client.viewDeclare({
44049
+ table_id: tableId,
44050
+ spec: { name: `Every ${table.data?.name ?? "record"}`, filters: null }
44051
+ });
44052
+ setBusy(false);
44053
+ if (!made.ok) {
44054
+ setError(made.error);
44055
+ return;
44056
+ }
44057
+ await load();
44058
+ }
44059
+ async function unsubscribe(ruleId) {
44060
+ setStopping(null);
44061
+ setBusy(true);
44062
+ const deleted = await client.recordDelete({ record_id: ruleId });
44063
+ setBusy(false);
44064
+ if (!deleted.ok) {
44065
+ setError(deleted.error);
44066
+ return;
44067
+ }
44068
+ await load();
44069
+ }
44070
+ if (error) {
44071
+ return /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(
44072
+ RefusalNotice,
44073
+ {
44074
+ error,
44075
+ className,
44076
+ actions: /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(import_design_system122.Button, { size: "sm", variant: "ghost", onClick: () => void load(), children: "Try again" })
44077
+ }
44078
+ );
44079
+ }
44080
+ 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) });
44081
+ return /* @__PURE__ */ (0, import_jsx_runtime137.jsxs)("div", { className: (0, import_design_system122.cn)("flex flex-col gap-2", className), children: [
44082
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsxs)("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
44083
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("span", { className: "font-medium text-foreground", children: "Tell me when" }),
44084
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsxs)("span", { children: [
44085
+ subscriptions.length,
44086
+ " subscription",
44087
+ subscriptions.length === 1 ? "" : "s"
44088
+ ] })
44089
+ ] }),
44090
+ 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: [
44091
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("span", { className: "truncate font-medium", children: held.name }),
44092
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("span", { className: "text-muted-foreground", children: CADENCE_WORDS2[held.cadence] ?? held.cadence }),
44093
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("span", { className: "text-muted-foreground", children: CHANNEL_WORDS3[held.channel] ?? held.channel }),
44094
+ held.schedule ? /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("span", { className: "text-muted-foreground", children: scheduleWords(held.schedule, held.quiet_hours?.tz) }) : null,
44095
+ held.next_digest_at ? /* @__PURE__ */ (0, import_jsx_runtime137.jsxs)("span", { className: "text-muted-foreground", children: [
44096
+ "next ",
44097
+ new Date(held.next_digest_at).toLocaleString()
44098
+ ] }) : null,
44099
+ (0, import_records57.hasQuietHours)(held.quiet_hours) ? /* @__PURE__ */ (0, import_jsx_runtime137.jsxs)("span", { className: "text-muted-foreground", children: [
44100
+ "quiet ",
44101
+ held.quiet_hours.start,
44102
+ "\u2013",
44103
+ held.quiet_hours.end
44104
+ ] }) : null,
44105
+ 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,
44106
+ 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,
44107
+ 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,
44108
+ onChoose ? held.rule_id === chosenRuleId ? /* @__PURE__ */ (0, import_jsx_runtime137.jsxs)("span", { className: "ml-auto flex items-center gap-1", children: [
44109
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("span", { className: "font-medium text-foreground", children: "Used for this form" }),
44110
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(import_design_system122.Button, { size: "sm", variant: "ghost", disabled: busy, onClick: () => onChoose(null), children: "Stop using" })
44111
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(
44112
+ import_design_system122.Button,
44113
+ {
44114
+ size: "sm",
44115
+ variant: "outline",
44116
+ className: "ml-auto",
44117
+ disabled: busy,
44118
+ onClick: () => onChoose(held.rule_id),
44119
+ children: "Use for this form"
44120
+ }
44121
+ ) : null,
44122
+ 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: [
44123
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("span", { className: "text-xs text-destructive", children: archiveConfirmSentence("this subscription") }),
44124
+ /* @__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" }),
44125
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(import_design_system122.Button, { size: "sm", variant: "ghost", onClick: () => setStopping(null), children: "Keep it" })
44126
+ ] }) : rights.write && !(onChoose && held.rule_id === chosenRuleId) ? /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(
44127
+ import_design_system122.Button,
44128
+ {
44129
+ size: "sm",
44130
+ variant: "ghost",
44131
+ className: onChoose ? void 0 : "ml-auto",
44132
+ disabled: busy,
44133
+ onClick: () => setStopping(held.rule_id),
44134
+ children: "Stop telling me"
44135
+ }
44136
+ ) : null
44137
+ ] }, held.rule_id)) }),
44138
+ 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,
44139
+ 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: [
44140
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("span", { children: "A subscription watches a saved view, and this table has none yet." }),
44141
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(import_design_system122.Button, { size: "sm", variant: "outline", disabled: busy, onClick: () => void makeEveryRecordView(), children: "Watch every record" })
44142
+ ] }) : /* @__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)(
44143
+ "form",
44144
+ {
44145
+ className: "flex flex-wrap items-center gap-1",
44146
+ onSubmit: (event) => {
44147
+ event.preventDefault();
44148
+ const form = event.currentTarget;
44149
+ const view = form.elements.namedItem("view").value;
44150
+ const cadence = form.elements.namedItem("cadence").value;
44151
+ const channel = form.elements.namedItem("channel").value;
44152
+ const schedule = form.elements.namedItem("schedule").value;
44153
+ const start = form.elements.namedItem("quiet_start").value;
44154
+ const end = form.elements.namedItem("quiet_end").value;
44155
+ const name = views.find((v) => v.id === view)?.name ?? "View";
44156
+ const quiet2 = start && end ? { start, end, tz: Intl.DateTimeFormat().resolvedOptions().timeZone } : null;
44157
+ if (view) void subscribe(view, name, cadence, channel, schedule, quiet2);
44158
+ },
44159
+ children: [
44160
+ /* @__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)) }),
44161
+ /* @__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)) }),
44162
+ /* @__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)) }),
44163
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(
44164
+ "input",
44165
+ {
44166
+ name: "schedule",
44167
+ "aria-label": "When the summary goes out",
44168
+ placeholder: "monday 08:00",
44169
+ className: "h-7 w-28 rounded border bg-background px-1 text-xs"
44170
+ }
44171
+ ),
44172
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("span", { className: "text-xs text-muted-foreground", children: "not between" }),
44173
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(
44174
+ "input",
44175
+ {
44176
+ name: "quiet_start",
44177
+ type: "time",
44178
+ "aria-label": "Quiet hours start",
44179
+ className: "h-7 rounded border bg-background px-1 text-xs"
44180
+ }
44181
+ ),
44182
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("span", { className: "text-xs text-muted-foreground", children: "and" }),
44183
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(
44184
+ "input",
44185
+ {
44186
+ name: "quiet_end",
44187
+ type: "time",
44188
+ "aria-label": "Quiet hours end",
44189
+ className: "h-7 rounded border bg-background px-1 text-xs"
44190
+ }
44191
+ ),
44192
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(import_design_system122.Button, { size: "sm", type: "submit", disabled: busy, children: "Tell me" }),
44193
+ /* @__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." })
44194
+ ]
44195
+ }
44196
+ ) : /* @__PURE__ */ (0, import_jsx_runtime137.jsx)("p", { className: "text-xs text-muted-foreground", children: rights.why("write") })
44197
+ ] });
44198
+ }
44199
+
43686
44200
  // src/DashboardCanvas.tsx
43687
44201
  var import_react249 = require("react");
43688
44202
  var import_react250 = require("@ai-matrx/records/react");
43689
44203
  var import_design_system126 = require("@ai-matrx/design-system");
43690
44204
 
43691
44205
  // 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");
44206
+ var import_core23 = require("@dnd-kit/core");
44207
+ var import_sortable2 = require("@dnd-kit/sortable");
44208
+ var import_utilities2 = require("@dnd-kit/utilities");
43695
44209
  var import_design_system123 = require("@ai-matrx/design-system");
43696
44210
 
43697
44211
  // src/dashboardLayout.ts
@@ -43745,7 +44259,7 @@ function withBlockIds(blocks) {
43745
44259
  // src/DashboardGrid.tsx
43746
44260
  var import_jsx_runtime138 = require("react/jsx-runtime");
43747
44261
  var dashboardCollision = (args) => {
43748
- const under = (0, import_core22.pointerWithin)(args);
44262
+ const under = (0, import_core23.pointerWithin)(args);
43749
44263
  if (under.length > 0) return under;
43750
44264
  const at = args.collisionRect;
43751
44265
  return args.droppableContainers.flatMap((container) => {
@@ -43755,9 +44269,9 @@ var dashboardCollision = (args) => {
43755
44269
  }).sort((a, b) => a.data.value - b.data.value);
43756
44270
  };
43757
44271
  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 })
44272
+ const sensors = (0, import_core23.useSensors)(
44273
+ (0, import_core23.useSensor)(import_core23.PointerSensor, { activationConstraint: { distance: 4 } }),
44274
+ (0, import_core23.useSensor)(import_core23.KeyboardSensor, { coordinateGetter: import_sortable2.sortableKeyboardCoordinates })
43761
44275
  );
43762
44276
  const ids = items.map((item) => item.id);
43763
44277
  function dragEnd(event) {
@@ -43767,7 +44281,7 @@ function DashboardGrid({ items, arranging, onMove, onSpan }) {
43767
44281
  }
43768
44282
  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
44283
  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 }) });
44284
+ 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
44285
  }
43772
44286
  function GridCell2({
43773
44287
  item,
@@ -43775,8 +44289,8 @@ function GridCell2({
43775
44289
  arranging,
43776
44290
  onSpan
43777
44291
  }) {
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;
44292
+ const sortable = (0, import_sortable2.useSortable)({ id: item.id, disabled: !arranging });
44293
+ const style = arranging ? { transform: import_utilities2.CSS.Transform.toString(sortable.transform), transition: sortable.transition } : void 0;
43780
44294
  return /* @__PURE__ */ (0, import_jsx_runtime138.jsxs)(
43781
44295
  "div",
43782
44296
  {
@@ -44980,7 +45494,7 @@ var import_react252 = require("@ai-matrx/records/react");
44980
45494
 
44981
45495
  // src/question.ts
44982
45496
  var import_records59 = require("@ai-matrx/records");
44983
- var import_core23 = require("@ai-matrx/records/core");
45497
+ var import_core24 = require("@ai-matrx/records/core");
44984
45498
  var QUESTION_OPS = ["count", "sum", "avg", "min", "max"];
44985
45499
  var QUESTION_LIMIT_DEFAULT = 25;
44986
45500
  var QUESTION_GROUP_CEILING = 200;
@@ -45232,7 +45746,7 @@ async function askAsOf(client, tableId, spec, fields, measures, keys) {
45232
45746
  },
45233
45747
  { schema: config.schema ?? "custom" }
45234
45748
  );
45235
- if (response.error) return { ok: false, error: (0, import_core23.mapPgError)(response.error, "askQuestion.asOf") };
45749
+ if (response.error) return { ok: false, error: (0, import_core24.mapPgError)(response.error, "askQuestion.asOf") };
45236
45750
  const rows2 = Array.isArray(response.data) ? response.data : [];
45237
45751
  const bad = rows2.find((r) => Number(r.not_numbers ?? 0) > 0);
45238
45752
  if (bad && m.op !== "count") {
@@ -45303,7 +45817,7 @@ function savedOf(row) {
45303
45817
  }
45304
45818
  async function questionsOf(client, tableId) {
45305
45819
  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") };
45820
+ if (response.error) return { ok: false, error: (0, import_core24.mapPgError)(response.error, "questionsOf") };
45307
45821
  const me = client.config.actor?.user_id ?? null;
45308
45822
  return {
45309
45823
  ok: true,
@@ -45332,7 +45846,7 @@ async function questionSave(client, args) {
45332
45846
  if (response.error.code === "23505") {
45333
45847
  return { ok: false, error: { code: "already_exists", message: `You already have a question called \u201C${name}\u201D.` } };
45334
45848
  }
45335
- return { ok: false, error: (0, import_core23.mapPgError)(response.error, "questionSave") };
45849
+ return { ok: false, error: (0, import_core24.mapPgError)(response.error, "questionSave") };
45336
45850
  }
45337
45851
  return { ok: true, data: response.data ? savedOf(response.data) : null };
45338
45852
  }
@@ -45342,7 +45856,7 @@ async function questionArchive(client, id, version) {
45342
45856
  { p_surface_key: QUESTION_SURFACE, p_id: id, ...version !== void 0 ? { p_expected_version: version } : {} },
45343
45857
  { schema: "public" }
45344
45858
  );
45345
- if (response.error) return { ok: false, error: (0, import_core23.mapPgError)(response.error, "questionArchive") };
45859
+ if (response.error) return { ok: false, error: (0, import_core24.mapPgError)(response.error, "questionArchive") };
45346
45860
  return { ok: true, data: response.data !== null };
45347
45861
  }
45348
45862
 
@@ -46201,7 +46715,7 @@ function TableAsk({ tableId, onOpenInQuestions }) {
46201
46715
  // src/PageScreen.tsx
46202
46716
  var import_react255 = require("react");
46203
46717
  var import_react256 = require("@ai-matrx/records/react");
46204
- var import_core24 = require("@ai-matrx/records/core");
46718
+ var import_core25 = require("@ai-matrx/records/core");
46205
46719
  var import_design_system129 = require("@ai-matrx/design-system");
46206
46720
  var import_jsx_runtime145 = require("react/jsx-runtime");
46207
46721
  var EDIT_LEVELS = /* @__PURE__ */ new Set(["edit_content", "editor", "admin", "owner"]);
@@ -46617,7 +47131,7 @@ function ListBlockView({
46617
47131
  ...sort.length > 0 ? { sort } : {}
46618
47132
  });
46619
47133
  const reload = page.reload;
46620
- (0, import_react255.useEffect)(() => (0, import_core24.onRecordChanged)(() => reload()), [reload]);
47134
+ (0, import_react255.useEffect)(() => (0, import_core25.onRecordChanged)(() => reload()), [reload]);
46621
47135
  const rows = nobodyIsMe ? [] : page.data?.rows ?? [];
46622
47136
  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
47137
  const firstId = rows[0]?.id ?? null;
@@ -48152,7 +48666,7 @@ var import_design_system142 = require("@ai-matrx/design-system");
48152
48666
  var import_text_case19 = require("@ai-matrx/kit/text-case");
48153
48667
 
48154
48668
  // src/viewShare.ts
48155
- var import_core25 = require("@ai-matrx/records/core");
48669
+ var import_core26 = require("@ai-matrx/records/core");
48156
48670
  var NO_VIEW_REASON = "No saved view yet";
48157
48671
  function viewLink(tableId, viewId, origin) {
48158
48672
  return `${tableLink(tableId, origin)}?view=${encodeURIComponent(viewId)}`;
@@ -48188,7 +48702,7 @@ async function downloadViewCsv(args) {
48188
48702
  const columns = viewColumns(fields, view.presentation);
48189
48703
  const rows = await rowsForFile({ client, host, fields: columns, records: read.rows, labels });
48190
48704
  const name = fileNameOf(args.tableName) || "records";
48191
- download(`${name}.csv`, "text/csv;charset=utf-8", (0, import_core25.exportCsv)(columns, rows));
48705
+ download(`${name}.csv`, "text/csv;charset=utf-8", (0, import_core26.exportCsv)(columns, rows));
48192
48706
  host.notify?.success(read.cut ? cutWords(read.rows.length) : "CSV downloaded");
48193
48707
  }
48194
48708