@ai-matrx/records-ui 0.105.1 → 0.107.0

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