@ai-matrx/records-ui 0.83.1 → 0.84.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -239,13 +239,20 @@ function plainSentence(message) {
239
239
  }
240
240
  return { kept: kept.length === 0 ? null : kept.join(" "), dropped };
241
241
  }
242
+ function exampleOf(error) {
243
+ const detail = error.detail;
244
+ if (!detail || typeof detail !== "object") return null;
245
+ const example = detail.example;
246
+ return typeof example === "string" && example.trim() !== "" ? example.trim() : null;
247
+ }
242
248
  function refusalForAPerson(error) {
243
249
  const code = error.code ?? "internal";
244
250
  const message = plainSentence(String(error.message ?? ""));
245
251
  const hint = plainSentence(String(error.hint ?? ""));
246
252
  const title = TITLE[code] ?? "The store refused this";
247
253
  const hintIsForEngineers = isEngineerText(String(error.hint ?? ""));
248
- const remedy = (hintIsForEngineers ? null : hint.kept) ?? REMEDY[code] ?? REMEDY["internal"];
254
+ const example = exampleOf(error);
255
+ const remedy = (example ? `Enter it like ${example}.` : null) ?? (hintIsForEngineers ? null : hint.kept) ?? REMEDY[code] ?? REMEDY["internal"];
249
256
  const messageIsForEngineers = isEngineerText(String(error.message ?? ""));
250
257
  const said = (messageIsForEngineers ? "" : message.kept ?? "").trim();
251
258
  const forEngineers = [
@@ -269,7 +276,9 @@ function refusalForAPerson(error) {
269
276
  }
270
277
  function refusalLineForAPerson(error) {
271
278
  const plain = refusalForAPerson(error);
272
- return `${plain.sentence} ${plain.remedy}`.trim();
279
+ const sentence2 = plain.sentence.trim();
280
+ const ended = sentence2 === "" || /[.!?…:]$/.test(sentence2) ? sentence2 : `${sentence2}.`;
281
+ return `${ended} ${plain.remedy}`.trim();
273
282
  }
274
283
 
275
284
  // src/names.ts
@@ -783,6 +792,42 @@ function asRefusal(thrown, sentence2, remedy, code = "internal") {
783
792
  return refusalFromThrown(thrown, sentence2, remedy, code);
784
793
  }
785
794
 
795
+ // src/tellApart.ts
796
+ function shown(value) {
797
+ if (value === null || value === void 0) return null;
798
+ if (typeof value === "string") return value.trim() === "" ? null : value.trim();
799
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
800
+ return null;
801
+ }
802
+ function tellApart(rows, nameOf) {
803
+ const byName = /* @__PURE__ */ new Map();
804
+ for (const row of rows) {
805
+ const name = nameOf(row.id).trim().toLowerCase();
806
+ byName.set(name, [...byName.get(name) ?? [], row]);
807
+ }
808
+ const out = /* @__PURE__ */ new Map();
809
+ for (const [name, group] of byName) {
810
+ if (group.length < 2) continue;
811
+ const keys = [
812
+ ...new Set(
813
+ group.flatMap(
814
+ (row) => Object.keys(row.document ?? {}).filter((k) => !k.startsWith("_") && shown(row.document?.[k]) !== null)
815
+ )
816
+ )
817
+ ].filter((k) => group.some((row) => (shown(row.document?.[k]) ?? "").toLowerCase() !== name));
818
+ const valuesOf = (k) => group.map((row) => shown(row.document?.[k]) ?? "");
819
+ const differs = keys.filter((k) => new Set(valuesOf(k)).size > 1);
820
+ const whole = differs.find((k) => new Set(valuesOf(k)).size === group.length);
821
+ const chosen = whole ? [whole] : differs.slice(0, 2);
822
+ if (chosen.length === 0) continue;
823
+ for (const row of group) {
824
+ const words = chosen.map((k) => shown(row.document?.[k])).filter((w) => w !== null);
825
+ if (words.length > 0) out.set(row.id, words.join(" \xB7 "));
826
+ }
827
+ }
828
+ return out;
829
+ }
830
+
786
831
  // src/RelationPicker.tsx
787
832
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
788
833
  var NO_STORE_ON_THIS_SCREEN_REASON = "This question points at a record in somebody's own data, and a page answered without an account is never allowed to search that. Leave it blank \u2014 whoever receives your answer will fill it in.";
@@ -924,9 +969,12 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
924
969
  setSearch("");
925
970
  toggle(createdId);
926
971
  };
927
- const visible = (rows ?? []).filter(
928
- (row) => search.trim() === "" ? true : titleOf(row.id).toLowerCase().includes(search.trim().toLowerCase())
929
- );
972
+ const apart = tellApart(rows ?? [], titleOf);
973
+ const visible = (rows ?? []).filter((row) => {
974
+ const q = search.trim().toLowerCase();
975
+ if (q === "") return true;
976
+ return `${titleOf(row.id)} ${apart.get(row.id) ?? ""}`.toLowerCase().includes(q);
977
+ });
930
978
  return /* @__PURE__ */ jsxs3("div", { className: cn3("flex flex-wrap items-center gap-1", className), children: [
931
979
  picked.map((recordId) => /* @__PURE__ */ jsx4(
932
980
  RecordChip,
@@ -957,17 +1005,21 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
957
1005
  " at most. Remove one to add another."
958
1006
  ] }) : null,
959
1007
  /* @__PURE__ */ jsxs3(ScrollArea, { className: "max-h-56", children: [
960
- /* @__PURE__ */ jsx4("ul", { children: visible.map((row) => /* @__PURE__ */ jsx4("li", { children: /* @__PURE__ */ jsx4(
1008
+ /* @__PURE__ */ jsx4("ul", { children: visible.map((row) => /* @__PURE__ */ jsx4("li", { children: /* @__PURE__ */ jsxs3(
961
1009
  "button",
962
1010
  {
963
1011
  type: "button",
964
1012
  onClick: () => toggle(row.id),
965
- title: titleOf(row.id),
1013
+ title: apart.has(row.id) ? `${titleOf(row.id)} \u2014 ${apart.get(row.id)}` : titleOf(row.id),
1014
+ "data-relation-candidate": row.id,
966
1015
  className: cn3(
967
1016
  "w-full truncate rounded px-2 py-1 text-left text-xs hover:bg-muted",
968
1017
  picked.includes(row.id) ? "bg-accent text-accent-foreground" : ""
969
1018
  ),
970
- children: titleOf(row.id)
1019
+ children: [
1020
+ titleOf(row.id),
1021
+ apart.has(row.id) ? /* @__PURE__ */ jsx4("span", { className: "block truncate text-[11px] text-muted-foreground", "data-tell-apart": "", children: apart.get(row.id) }) : null
1022
+ ]
971
1023
  }
972
1024
  ) }, row.id)) }),
973
1025
  rows && visible.length === 0 ? /* @__PURE__ */ jsx4("p", { className: "p-2 text-xs text-muted-foreground", children: "Nothing in that table matches." }) : null
@@ -1902,7 +1954,7 @@ var FIELD_RULE_KIND_VALUES = ["min", "max", "length", "pattern", "equals_field",
1902
1954
  var FIELD_RULE_KIND_LABEL = {
1903
1955
  min: "At least",
1904
1956
  max: "At most",
1905
- length: "No longer than",
1957
+ length: "Number of characters",
1906
1958
  pattern: "Looks like",
1907
1959
  equals_field: "Same as the column",
1908
1960
  differs_from_field: "Different from the column"
@@ -1934,6 +1986,20 @@ function envelopeFor(document2, key) {
1934
1986
  const block = document2?._values;
1935
1987
  return block?.[key];
1936
1988
  }
1989
+ function unresolvedFor(document2, key) {
1990
+ const sources = document2?._sources;
1991
+ if (!sources || typeof sources !== "object") return void 0;
1992
+ for (const source of Object.values(sources)) {
1993
+ const held = source?.unresolved?.[key];
1994
+ if (typeof held === "string" && held) return held;
1995
+ if (Array.isArray(held) && held.length) return held.map(String).join(", ");
1996
+ }
1997
+ return void 0;
1998
+ }
1999
+ function hasFormula(field) {
2000
+ const config = field.config ?? {};
2001
+ return Boolean(config.expr) || Boolean(String(config.formula_text ?? "").trim());
2002
+ }
1937
2003
  var ABSENCE_WORDS = ABSENCE_WORD_LABEL;
1938
2004
  function renderValue(field, value, document2, labels, stillWaitingFor) {
1939
2005
  const envelope = envelopeFor(document2, field.key);
@@ -1941,6 +2007,13 @@ function renderValue(field, value, document2, labels, stillWaitingFor) {
1941
2007
  return /* @__PURE__ */ jsx9("span", { className: "text-xs italic text-muted-foreground", title: `VAL-2: ${envelope.absent}`, children: ABSENCE_WORDS[String(envelope.absent)] ?? String(envelope.absent) });
1942
2008
  }
1943
2009
  if (value === null || value === void 0 || value === "") {
2010
+ const gone = unresolvedFor(document2, field.key);
2011
+ if (gone) {
2012
+ return /* @__PURE__ */ jsx9("span", { className: "text-xs italic text-muted-foreground", title: `It pointed at ${gone}`, children: "The record this pointed at no longer exists" });
2013
+ }
2014
+ if (field.type === "formula" && !hasFormula(field)) {
2015
+ return /* @__PURE__ */ jsx9("span", { className: "text-xs italic text-muted-foreground", children: "Worked out once its formula is in place" });
2016
+ }
1944
2017
  return /* @__PURE__ */ jsx9("span", { className: "text-muted-foreground", children: "\u2014" });
1945
2018
  }
1946
2019
  const kind = editorKindFor(field);
@@ -2991,7 +3064,7 @@ function EnrichPanel({ tableId, fieldId, className }) {
2991
3064
  useEffect7(() => {
2992
3065
  void load();
2993
3066
  }, [load]);
2994
- const shown = useMemo8(
3067
+ const shown2 = useMemo8(
2995
3068
  () => fieldId ? (rows ?? []).filter((r) => r.field_id === fieldId) : rows ?? [],
2996
3069
  [rows, fieldId]
2997
3070
  );
@@ -3050,7 +3123,7 @@ function EnrichPanel({ tableId, fieldId, className }) {
3050
3123
  );
3051
3124
  if (error) return /* @__PURE__ */ jsx11(RefusalNotice, { error, className });
3052
3125
  if (rows === null) return /* @__PURE__ */ jsx11(Skeleton, { className: cn8("h-40 w-full", className) });
3053
- if (shown.length === 0 && fieldId) {
3126
+ if (shown2.length === 0 && fieldId) {
3054
3127
  const target = (fields.data ?? []).find((f) => f.id === fieldId);
3055
3128
  const setup = draft[fieldId] ?? { instruction: "", every: "30" };
3056
3129
  if (!rights.admin) {
@@ -3113,13 +3186,13 @@ function EnrichPanel({ tableId, fieldId, className }) {
3113
3186
  outcome ? /* @__PURE__ */ jsx11("p", { className: "text-xs text-muted-foreground", children: outcome.message }) : null
3114
3187
  ] });
3115
3188
  }
3116
- if (shown.length === 0) {
3189
+ if (shown2.length === 0) {
3117
3190
  return /* @__PURE__ */ jsxs7("div", { className: cn8("space-y-2 p-4 text-sm", className), children: [
3118
3191
  /* @__PURE__ */ jsx11("p", { className: "font-medium", children: "No column here is filled in by a model." }),
3119
3192
  /* @__PURE__ */ jsx11("p", { className: "text-muted-foreground", children: rights.admin ? "Open a column's menu and choose \u201CLet AI fill\u201D to set one up \u2014 or just ask for it in plain words and the agent will." : "An admin on this table sets that up." })
3120
3193
  ] });
3121
3194
  }
3122
- return /* @__PURE__ */ jsx11("div", { className: cn8("space-y-6 p-4", className), children: shown.map((row) => {
3195
+ return /* @__PURE__ */ jsx11("div", { className: cn8("space-y-6 p-4", className), children: shown2.map((row) => {
3123
3196
  const d = draft[row.field_id] ?? {
3124
3197
  instruction: row.enrichment?.instruction ?? "",
3125
3198
  every: row.review_interval_days ? String(row.review_interval_days) : ""
@@ -3408,18 +3481,18 @@ function Outcome({ outcome, onClose }) {
3408
3481
  ] });
3409
3482
  }
3410
3483
  function PlanTable({ plan }) {
3411
- const shown = plan.rows.slice(0, 8);
3412
- const keys = Array.from(new Set(shown.flatMap((row) => row.cells.map((c) => c.key))));
3413
- if (shown.length === 0 || keys.length === 0) return null;
3484
+ const shown2 = plan.rows.slice(0, 8);
3485
+ const keys = Array.from(new Set(shown2.flatMap((row) => row.cells.map((c) => c.key))));
3486
+ if (shown2.length === 0 || keys.length === 0) return null;
3414
3487
  const columnName = /* @__PURE__ */ new Map();
3415
- for (const row of shown) for (const cell of row.cells) if (!columnName.has(cell.key)) columnName.set(cell.key, cell.column);
3488
+ for (const row of shown2) for (const cell of row.cells) if (!columnName.has(cell.key)) columnName.set(cell.key, cell.column);
3416
3489
  return /* @__PURE__ */ jsxs8("div", { className: "overflow-auto rounded border", children: [
3417
3490
  /* @__PURE__ */ jsxs8("table", { className: "w-full text-xs", "data-matrx-paste-plan": true, children: [
3418
3491
  /* @__PURE__ */ jsx12("thead", { className: "bg-muted/50", children: /* @__PURE__ */ jsxs8("tr", { children: [
3419
3492
  /* @__PURE__ */ jsx12("th", { className: "px-2 py-1 text-left font-medium", children: "Row" }),
3420
3493
  keys.map((key) => /* @__PURE__ */ jsx12("th", { className: "px-2 py-1 text-left font-medium", children: columnName.get(key) ?? key }, key))
3421
3494
  ] }) }),
3422
- /* @__PURE__ */ jsx12("tbody", { children: shown.map((row) => /* @__PURE__ */ jsxs8("tr", { className: "border-t", children: [
3495
+ /* @__PURE__ */ jsx12("tbody", { children: shown2.map((row) => /* @__PURE__ */ jsxs8("tr", { className: "border-t", children: [
3423
3496
  /* @__PURE__ */ jsxs8("td", { className: "whitespace-nowrap px-2 py-1", children: [
3424
3497
  row.fromLine,
3425
3498
  row.recordId === null ? /* @__PURE__ */ jsx12(Badge4, { variant: "outline", className: "ml-1", children: "new" }) : null
@@ -3441,9 +3514,9 @@ function PlanTable({ plan }) {
3441
3514
  })
3442
3515
  ] }, row.fromLine)) })
3443
3516
  ] }),
3444
- plan.rows.length > shown.length ? /* @__PURE__ */ jsxs8("p", { className: "px-2 py-1 text-xs text-muted-foreground", children: [
3517
+ plan.rows.length > shown2.length ? /* @__PURE__ */ jsxs8("p", { className: "px-2 py-1 text-xs text-muted-foreground", children: [
3445
3518
  "\u2026and ",
3446
- plan.rows.length - shown.length,
3519
+ plan.rows.length - shown2.length,
3447
3520
  " more rows the same way."
3448
3521
  ] }) : null
3449
3522
  ] });
@@ -4516,7 +4589,6 @@ function checkImportRules(fields, rows, mapping) {
4516
4589
  import { Fragment as Fragment7, jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
4517
4590
  var BATCH = 250;
4518
4591
  var SAMPLES = 40;
4519
- var MAPPING_COLUMNS = "minmax(9rem, 11rem) minmax(14rem, 17rem) minmax(13rem, 1fr) minmax(8rem, 10rem)";
4520
4592
  var ABSENT = "__absent__";
4521
4593
  function obviousTitleColumn(columns) {
4522
4594
  const says = /^(title|name|record|customer|client|company|job|item|subject|label|description)\b|(\bname|\btitle|\bnumber|\bid)$/i;
@@ -4533,7 +4605,11 @@ async function sha256(bytes) {
4533
4605
  return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
4534
4606
  }
4535
4607
  function typeWord(t) {
4536
- return fieldTypeChoice(t)?.label ?? (t === "relation" ? "Points at another table" : t);
4608
+ if (t === "range") return fieldTypeChoice("number")?.label ?? "Number";
4609
+ return fieldTypeChoice(t)?.label ?? (t === "relation" ? "Points at another table" : humanize(t));
4610
+ }
4611
+ function lineInYourFile(dataRow) {
4612
+ return dataRow + 1;
4537
4613
  }
4538
4614
  function ImportWizard({ tableId, onDone, onWrote, className }) {
4539
4615
  const client = useRecordsClient6();
@@ -4774,6 +4850,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4774
4850
  const refusedByRules = ruleVerdicts.reduce((n, v) => n + v.refused, 0);
4775
4851
  const [goAheadAnyway, setGoAheadAnyway] = useState13(false);
4776
4852
  useEffect8(() => setGoAheadAnyway(false), [ruleVerdicts]);
4853
+ const afterWrite = !busy && (phase === "done" || alreadyRan !== null || ledger !== null && ledger.seen > 0);
4777
4854
  return /* @__PURE__ */ jsxs12("div", { className: cn12("flex min-w-0 flex-col gap-2 text-xs", className), children: [
4778
4855
  /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-2", children: [
4779
4856
  /* @__PURE__ */ jsx16("span", { className: "font-medium", children: "Import" }),
@@ -4804,7 +4881,11 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4804
4881
  problem ? /* @__PURE__ */ jsx16(RefusalNotice, { error: problem }) : null,
4805
4882
  phase === "reading" ? /* @__PURE__ */ jsx16("p", { className: "text-muted-foreground", children: "Reading the file\u2026" }) : null,
4806
4883
  phase === "planning" ? /* @__PURE__ */ jsx16("p", { className: "text-muted-foreground", children: "Working out what each column is\u2026" }) : null,
4807
- plan && parsed ? /* @__PURE__ */ jsxs12(Fragment7, { children: [
4884
+ plan && parsed && afterWrite ? /* @__PURE__ */ jsxs12("p", { className: "text-muted-foreground", "data-import-after-write": "", children: [
4885
+ fileName ? `${fileName} has been sent to this table` : "This file has been sent to this table",
4886
+ ledger && !ledger.final ? ", but the run stopped part-way. Importing it again would write the rows that already landed a second time, so fix the file (or keep only the rows below the ones that landed) and choose it again." : ". What landed and what was refused is below. To import another file, choose it above."
4887
+ ] }) : null,
4888
+ plan && parsed && !afterWrite ? /* @__PURE__ */ jsxs12(Fragment7, { children: [
4808
4889
  titleKey ? /* @__PURE__ */ jsxs12("div", { className: "flex flex-wrap items-center gap-2 rounded border bg-muted/40 px-2 py-1.5", children: [
4809
4890
  /* @__PURE__ */ jsx16("span", { className: "font-medium", children: "What each record is called" }),
4810
4891
  /* @__PURE__ */ jsxs12(
@@ -4838,30 +4919,19 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4838
4919
  ),
4839
4920
  /* @__PURE__ */ jsx16("span", { className: "text-muted-foreground", children: titleMappedTo ? `goes into ${titleFieldLabel}, so every record this file makes has a name you can read in the grid and on a chip.` : `Nothing goes into ${titleFieldLabel} yet, so every record this file makes would land with no name at all. Pick the column that says what each row IS.` })
4840
4921
  ] }) : null,
4841
- /* @__PURE__ */ jsx16("div", { className: "w-full overflow-x-auto rounded border", children: /* @__PURE__ */ jsxs12("div", { className: "min-w-[52rem]", children: [
4842
- /* @__PURE__ */ jsxs12(
4922
+ /* @__PURE__ */ jsx16("div", { className: "flex w-full min-w-0 flex-col rounded border", "data-import-mapping": "", children: plan.columns.map((column) => {
4923
+ const to = mapping[column.header] ?? "";
4924
+ const absent = unmapped === "ignore" ? "\u2014 leave it out \u2014" : "\u2014 offer it as a new column \u2014";
4925
+ const matchedField = column.field_id ? declared.find((f) => f.id === column.field_id) : void 0;
4926
+ return /* @__PURE__ */ jsxs12(
4843
4927
  "div",
4844
4928
  {
4845
- className: "grid items-center gap-x-3 border-b bg-muted px-2 py-1 font-medium",
4846
- style: { gridTemplateColumns: MAPPING_COLUMNS },
4929
+ "data-import-column": column.header,
4930
+ className: "flex min-w-0 flex-col gap-1 border-t px-2 py-1.5 first:border-t-0",
4847
4931
  children: [
4848
- /* @__PURE__ */ jsx16("span", { children: "Column in the file" }),
4849
- /* @__PURE__ */ jsx16("span", { children: "Goes to" }),
4850
- /* @__PURE__ */ jsx16("span", { children: "Kind" }),
4851
- /* @__PURE__ */ jsx16("span", { children: "First values" })
4852
- ]
4853
- }
4854
- ),
4855
- plan.columns.map((column) => {
4856
- const to = mapping[column.header] ?? "";
4857
- const absent = unmapped === "ignore" ? "\u2014 leave it out \u2014" : "\u2014 offer it as a new column \u2014";
4858
- return /* @__PURE__ */ jsxs12(
4859
- "div",
4860
- {
4861
- className: "grid items-start gap-x-3 border-t px-2 py-1",
4862
- style: { gridTemplateColumns: MAPPING_COLUMNS },
4863
- children: [
4864
- /* @__PURE__ */ jsx16("span", { className: "truncate pt-1.5 font-medium", title: column.header, children: column.header }),
4932
+ /* @__PURE__ */ jsxs12("div", { className: "flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1", children: [
4933
+ /* @__PURE__ */ jsx16("span", { className: "min-w-[8rem] flex-1 break-words font-medium", children: column.header }),
4934
+ /* @__PURE__ */ jsx16("span", { className: "text-muted-foreground", children: "goes to" }),
4865
4935
  /* @__PURE__ */ jsxs12(
4866
4936
  Select3,
4867
4937
  {
@@ -4882,7 +4952,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4882
4952
  size: "sm",
4883
4953
  "aria-label": `Where ${column.header} goes`,
4884
4954
  "data-goes-to": column.header,
4885
- className: "w-full min-w-[14rem]",
4955
+ className: "min-w-[14rem] flex-1",
4886
4956
  children: /* @__PURE__ */ jsx16(SelectValue3, {})
4887
4957
  }
4888
4958
  ),
@@ -4892,42 +4962,53 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
4892
4962
  ] })
4893
4963
  ]
4894
4964
  }
4965
+ )
4966
+ ] }),
4967
+ /* @__PURE__ */ jsxs12("div", { className: "min-w-0", children: [
4968
+ /* @__PURE__ */ jsx16(
4969
+ Badge5,
4970
+ {
4971
+ variant: column.matched ? "secondary" : "outline",
4972
+ className: "text-[11px] font-normal",
4973
+ "data-import-kind": column.header,
4974
+ children: matchedField ? fieldTypeLabel(matchedField) : typeWord(column.type)
4975
+ }
4895
4976
  ),
4896
- /* @__PURE__ */ jsxs12("div", { className: "pt-1", children: [
4897
- /* @__PURE__ */ jsx16(Badge5, { variant: column.matched ? "secondary" : "outline", className: "text-[11px] font-normal", children: typeWord(column.type) }),
4898
- column.unit ? /* @__PURE__ */ jsx16("span", { className: "ml-1 opacity-60", children: column.unit }) : null,
4899
- /* @__PURE__ */ jsx16("p", { className: "mt-0.5 text-muted-foreground", children: column.why }),
4900
- column.ambiguous ? /* @__PURE__ */ jsx16("p", { className: "text-destructive", children: "Check a date you know before you run this." }) : null,
4901
- column.collides_with ? /* @__PURE__ */ jsx16("p", { className: "text-destructive", children: "Two columns in this file would make the same column." }) : null,
4902
- ruleVerdictOf.get(column.header) ? (() => {
4903
- const v = ruleVerdictOf.get(column.header);
4904
- return /* @__PURE__ */ jsxs12("div", { "data-import-rule-refusal": column.header, className: "mt-1", children: [
4905
- /* @__PURE__ */ jsx16(
4906
- RefusalLine,
4907
- {
4908
- error: refusal(
4909
- "refused_by_rule",
4910
- `${v.refused} of ${v.judged} row${v.judged === 1 ? "" : "s"} would be refused by ${fieldName(v.field)}'s rules. ${v.firstWhy}`,
4911
- "Send this column somewhere else, fix those rows in your file, or import the rest and they are listed afterwards."
4912
- )
4913
- }
4914
- ),
4915
- /* @__PURE__ */ jsxs12("p", { className: "text-muted-foreground", "data-import-rule-sample": "", children: [
4916
- "Line ",
4917
- v.firstLine,
4918
- " in your file: ",
4919
- v.firstRaw
4920
- ] })
4921
- ] });
4922
- })() : null
4923
- ] }),
4924
- /* @__PURE__ */ jsx16("span", { className: "truncate pt-1.5 text-muted-foreground", children: (column.samples ?? []).slice(0, 3).map(String).join(" \xB7 ") })
4925
- ]
4926
- },
4927
- column.header
4928
- );
4929
- })
4930
- ] }) }),
4977
+ column.unit ? /* @__PURE__ */ jsx16("span", { className: "ml-1 opacity-60", children: column.unit }) : null,
4978
+ /* @__PURE__ */ jsx16("p", { className: "mt-0.5 break-words text-muted-foreground", children: column.why }),
4979
+ column.ambiguous ? /* @__PURE__ */ jsx16("p", { className: "text-destructive", children: "Check a date you know before you run this." }) : null,
4980
+ column.collides_with ? /* @__PURE__ */ jsx16("p", { className: "text-destructive", children: "Two columns in this file would make the same column." }) : null,
4981
+ ruleVerdictOf.get(column.header) ? (() => {
4982
+ const v = ruleVerdictOf.get(column.header);
4983
+ return /* @__PURE__ */ jsxs12("div", { "data-import-rule-refusal": column.header, className: "mt-1", children: [
4984
+ /* @__PURE__ */ jsx16(
4985
+ RefusalLine,
4986
+ {
4987
+ error: refusal(
4988
+ "refused_by_rule",
4989
+ `${v.refused} of ${v.judged} row${v.judged === 1 ? "" : "s"} would be refused by ${fieldName(v.field)}'s rules. ${v.firstWhy}`,
4990
+ "Send this column somewhere else, fix those rows in your file, or import the rest and they are listed afterwards."
4991
+ )
4992
+ }
4993
+ ),
4994
+ /* @__PURE__ */ jsxs12("p", { className: "text-muted-foreground", "data-import-rule-sample": "", children: [
4995
+ "Line ",
4996
+ lineInYourFile(v.firstLine),
4997
+ " in your file: ",
4998
+ v.firstRaw
4999
+ ] })
5000
+ ] });
5001
+ })() : null
5002
+ ] }),
5003
+ (column.samples ?? []).length > 0 ? /* @__PURE__ */ jsxs12("p", { className: "break-words text-muted-foreground", children: [
5004
+ "First values: ",
5005
+ (column.samples ?? []).slice(0, 3).map(String).join(" \xB7 ")
5006
+ ] }) : null
5007
+ ]
5008
+ },
5009
+ column.header
5010
+ );
5011
+ }) }),
4931
5012
  /* @__PURE__ */ jsx16(Separator2, {}),
4932
5013
  /* @__PURE__ */ jsxs12("div", { className: "flex flex-wrap items-center gap-3", children: [
4933
5014
  /* @__PURE__ */ jsxs12("label", { className: "flex items-center gap-1", children: [
@@ -5064,7 +5145,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
5064
5145
  /* @__PURE__ */ jsx16("th", { className: "px-2 py-1 text-left font-medium", children: "The row in your file" })
5065
5146
  ] }) }),
5066
5147
  /* @__PURE__ */ jsx16("tbody", { children: interesting.slice(0, 200).map((o) => /* @__PURE__ */ jsxs12("tr", { className: "border-t align-top", children: [
5067
- /* @__PURE__ */ jsx16("td", { className: "px-2 py-1", children: o.row }),
5148
+ /* @__PURE__ */ jsx16("td", { className: "px-2 py-1", children: lineInYourFile(o.row) }),
5068
5149
  /* @__PURE__ */ jsx16("td", { className: "px-2 py-1", children: o.outcome === "refused" ? /* @__PURE__ */ jsx16(
5069
5150
  RefusalLine,
5070
5151
  {
@@ -5088,8 +5169,10 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
5088
5169
  " that ",
5089
5170
  ledger?.refused === 1 ? "was" : "were",
5090
5171
  " refused",
5091
- " ",
5092
- "are kept with this import \u2014 open them again from the list of imports below, with the store's reason and the line from your file on each."
5172
+ ledger?.refused === 1 ? " is" : " are",
5173
+ " kept with this import \u2014 open ",
5174
+ ledger?.refused === 1 ? "it" : "them",
5175
+ " again from the list of imports below, with the store's reason and the line from your file on each."
5093
5176
  ] }) : null
5094
5177
  ] }) : null
5095
5178
  ] }) : null,
@@ -5137,7 +5220,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
5137
5220
  /* @__PURE__ */ jsx16("tbody", { children: runRows.rows.map((row, i) => {
5138
5221
  const source = row.source ?? {};
5139
5222
  return /* @__PURE__ */ jsxs12("tr", { className: "border-t align-top", children: [
5140
- /* @__PURE__ */ jsx16("td", { className: "px-2 py-1", children: String(row.row ?? "") }),
5223
+ /* @__PURE__ */ jsx16("td", { className: "px-2 py-1", children: typeof row.row === "number" ? lineInYourFile(row.row) : String(row.row ?? "") }),
5141
5224
  /* @__PURE__ */ jsx16("td", { className: "px-2 py-1", children: /* @__PURE__ */ jsx16(
5142
5225
  RefusalLine,
5143
5226
  {
@@ -5402,6 +5485,7 @@ function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }
5402
5485
  const [sensitivity, setSensitivity] = useState15(String(field?.sensitivity ?? "internal"));
5403
5486
  const [contextPolicy, setContextPolicy] = useState15(String(field?.context_policy ?? "include"));
5404
5487
  const [rules, setRules] = useState15(field?.rules ?? []);
5488
+ const [allowOther, setAllowOther] = useState15(field?.config?.["allow_other"] === true);
5405
5489
  const [optionText, setOptionText] = useState15("");
5406
5490
  const [unit, setUnit] = useState15(field?.unit ?? "$");
5407
5491
  const [withTime, setWithTime] = useState15(String(field?.config?.["kind"] ?? "date") === "datetime");
@@ -5458,7 +5542,10 @@ function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }
5458
5542
  // REMOVES a spec and the column goes back to the target table's own
5459
5543
  // reference column — a panel that only sent the key when it was set
5460
5544
  // could turn the override on and never off.
5461
- ...field.type === "relation" ? { display } : {}
5545
+ ...field.type === "relation" ? { display } : {},
5546
+ // STORE-RULE-GAPS: a choice list's own setting, sent for every list so
5547
+ // turning it off is as possible as turning it on.
5548
+ ...field.type === "list" ? { allow_other: allowOther } : {}
5462
5549
  };
5463
5550
  const written2 = await mutation.update({ field_id: field.id, patch });
5464
5551
  if (written2 !== null) onSaved?.();
@@ -5485,6 +5572,7 @@ function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }
5485
5572
  context_policy: contextPolicy,
5486
5573
  rules,
5487
5574
  ...type === "select" || type === "multi_select" ? { options } : {},
5575
+ ...(type === "select" || type === "multi_select") && allowOther ? { allow_other: true } : {},
5488
5576
  ...type === "currency" ? { unit } : {},
5489
5577
  ...type === "datetime" ? { kind: withTime ? "datetime" : "date" } : {},
5490
5578
  ...type === "lookup" ? { via, pick } : {},
@@ -5616,7 +5704,18 @@ function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }
5616
5704
  }
5617
5705
  ),
5618
5706
  "Each value has its own date"
5619
- ] })
5707
+ ] }),
5708
+ type === "select" || type === "multi_select" ? /* @__PURE__ */ jsxs14("label", { className: "flex items-center gap-1.5", children: [
5709
+ /* @__PURE__ */ jsx18(
5710
+ Checkbox4,
5711
+ {
5712
+ checked: allowOther,
5713
+ "aria-label": "A value that is not one of the choices is added to them",
5714
+ onCheckedChange: (v) => setAllowOther(v === true)
5715
+ }
5716
+ ),
5717
+ "A value that is not one of the choices is added to them"
5718
+ ] }) : null
5620
5719
  ] }),
5621
5720
  /* @__PURE__ */ jsx18(Separator4, {}),
5622
5721
  /* @__PURE__ */ jsxs14("div", { className: "grid gap-3 sm:grid-cols-2", children: [
@@ -5664,15 +5763,37 @@ function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }
5664
5763
  ]
5665
5764
  }
5666
5765
  ),
5766
+ rule.kind === "length" ? /* @__PURE__ */ jsx18(
5767
+ BasicInput5,
5768
+ {
5769
+ className: "h-8 w-24",
5770
+ inputMode: "numeric",
5771
+ placeholder: "fewest",
5772
+ value: rule.min === void 0 ? "" : String(rule.min),
5773
+ "aria-label": `Check ${index + 1} fewest characters`,
5774
+ onChange: (e) => setRules(rules.map((r, i) => i === index ? withNumber(r, "min", e.target.value) : r))
5775
+ }
5776
+ ) : null,
5667
5777
  /* @__PURE__ */ jsx18(
5668
5778
  BasicInput5,
5669
5779
  {
5670
5780
  className: "h-8",
5781
+ ...rule.kind === "length" ? { inputMode: "numeric", placeholder: "most" } : {},
5671
5782
  value: String(rule.value ?? ""),
5672
- "aria-label": `Check ${index + 1} value`,
5783
+ "aria-label": rule.kind === "length" ? `Check ${index + 1} most characters` : `Check ${index + 1} value`,
5673
5784
  onChange: (e) => setRules(rules.map((r, i) => i === index ? { ...r, value: e.target.value } : r))
5674
5785
  }
5675
5786
  ),
5787
+ rule.kind === "pattern" ? /* @__PURE__ */ jsx18(
5788
+ BasicInput5,
5789
+ {
5790
+ className: "h-8",
5791
+ placeholder: "for example 949-555-0142",
5792
+ value: rule.example ?? "",
5793
+ "aria-label": `Check ${index + 1} example`,
5794
+ onChange: (e) => setRules(rules.map((r, i) => i === index ? withText(r, "example", e.target.value) : r))
5795
+ }
5796
+ ) : null,
5676
5797
  /* @__PURE__ */ jsx18(
5677
5798
  Button13,
5678
5799
  {
@@ -5978,6 +6099,17 @@ function ValueOnTheOtherSide({
5978
6099
  /* @__PURE__ */ jsx18("p", { className: "text-[11px] text-muted-foreground", children: help })
5979
6100
  ] });
5980
6101
  }
6102
+ function withNumber(rule, key, typed) {
6103
+ const { [key]: _gone, ...rest } = rule;
6104
+ const trimmed = typed.trim();
6105
+ if (trimmed === "") return rest;
6106
+ const n = Number(trimmed);
6107
+ return Number.isFinite(n) ? { ...rest, [key]: n } : { ...rest, [key]: trimmed };
6108
+ }
6109
+ function withText(rule, key, typed) {
6110
+ const { [key]: _gone, ...rest } = rule;
6111
+ return typed.trim() === "" ? rest : { ...rest, [key]: typed };
6112
+ }
5981
6113
 
5982
6114
  // src/StageRules.tsx
5983
6115
  import { useCallback as useCallback9, useEffect as useEffect9, useMemo as useMemo13, useState as useState17 } from "react";
@@ -6739,9 +6871,9 @@ function TableSettings({
6739
6871
  /* @__PURE__ */ jsx21(StageRulesSection, { tableId }),
6740
6872
  /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-2", children: [
6741
6873
  /* @__PURE__ */ jsx21("span", { className: "font-medium", children: "Proposed" }),
6742
- /* @__PURE__ */ jsx21("span", { className: "text-muted-foreground", children: proposals?.length ?? 0 })
6874
+ proposals === void 0 ? null : /* @__PURE__ */ jsx21("span", { className: "text-muted-foreground", children: proposals.length })
6743
6875
  ] }),
6744
- proposals === void 0 ? /* @__PURE__ */ jsx21("p", { className: "text-muted-foreground", children: "Nothing is listing proposals here yet: the store has no `custom.field_propose` door, so this panel would be inventing an empty queue. When the door lands, its rows appear in this list with the same accept and reject." }) : proposals.length === 0 ? /* @__PURE__ */ jsx21("p", { className: "text-muted-foreground", children: "No field is waiting for a decision." }) : /* @__PURE__ */ jsx21("ul", { className: "divide-y rounded border", children: proposals.map((proposal) => /* @__PURE__ */ jsxs17("li", { className: "flex flex-wrap items-center gap-x-2 gap-y-1 px-2 py-1.5", children: [
6876
+ proposals === void 0 ? /* @__PURE__ */ jsx21("p", { className: "text-muted-foreground", children: "Columns proposed for this table \u2014 by an import or by an agent \u2014 wait in this table's Inbox, where each one is accepted or turned down." }) : proposals.length === 0 ? /* @__PURE__ */ jsx21("p", { className: "text-muted-foreground", children: "No field is waiting for a decision." }) : /* @__PURE__ */ jsx21("ul", { className: "divide-y rounded border", children: proposals.map((proposal) => /* @__PURE__ */ jsxs17("li", { className: "flex flex-wrap items-center gap-x-2 gap-y-1 px-2 py-1.5", children: [
6745
6877
  /* @__PURE__ */ jsx21("span", { className: "min-w-[8rem] flex-1 break-words", children: proposal.field.label || proposal.field.key }),
6746
6878
  /* @__PURE__ */ jsx21("span", { className: "min-w-[8rem] flex-1 break-words text-muted-foreground", children: proposal.why }),
6747
6879
  /* @__PURE__ */ jsx21(Badge6, { variant: "secondary", className: "text-[10px] font-normal", children: proposal.proposed_by }),
@@ -7510,10 +7642,10 @@ function Peek({ tableId, recordId, onClose, className }) {
7510
7642
  }
7511
7643
  function computedInPlainWords(row, field) {
7512
7644
  const name = field ? fieldName(field) : humanize(row.field_key);
7513
- const shown = row.value === null || row.value === void 0 || row.value === "" ? "nothing" : String(row.value);
7645
+ const shown2 = row.value === null || row.value === void 0 || row.value === "" ? "nothing" : String(row.value);
7514
7646
  const when = new Date(row.computed_at);
7515
7647
  const at = Number.isNaN(when.getTime()) ? "" : ` on ${when.toLocaleDateString()}`;
7516
- return `${name} was worked out as ${shown}${at}.`;
7648
+ return `${name} was worked out as ${shown2}${at}.`;
7517
7649
  }
7518
7650
 
7519
7651
  // src/views.ts
@@ -8468,7 +8600,7 @@ function Gallery({
8468
8600
  onOpenRecord,
8469
8601
  note
8470
8602
  }) {
8471
- const shown = fields.slice(0, 4);
8603
+ const shown2 = fields.slice(0, 4);
8472
8604
  return /* @__PURE__ */ jsxs23("div", { className: "flex min-h-0 flex-col gap-2", children: [
8473
8605
  /* @__PURE__ */ jsx27(Note, { children: note }),
8474
8606
  /* @__PURE__ */ jsx27("ul", { className: "grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-2 overflow-y-auto", children: rows.map((row) => {
@@ -8481,7 +8613,7 @@ function Gallery({
8481
8613
  onClick: () => onOpenRecord?.(row.id),
8482
8614
  children: [
8483
8615
  typeof image === "string" && image !== "" ? /* @__PURE__ */ jsx27("img", { src: image, alt: "", className: "h-28 w-full rounded object-cover" }) : null,
8484
- shown.map((field) => /* @__PURE__ */ jsxs23("span", { className: "flex min-w-0 items-baseline gap-1 text-xs", children: [
8616
+ shown2.map((field) => /* @__PURE__ */ jsxs23("span", { className: "flex min-w-0 items-baseline gap-1 text-xs", children: [
8485
8617
  /* @__PURE__ */ jsx27("span", { className: "shrink-0 text-muted-foreground", children: fieldName(field) }),
8486
8618
  /* @__PURE__ */ jsx27("span", { className: "min-w-0 truncate", children: /* @__PURE__ */ jsx27(RecordValue, { field, value: (row.document ?? {})[field.key], document: row.document }) })
8487
8619
  ] }, field.key))
@@ -9822,18 +9954,18 @@ function ActionInbox({ tableId, includeSettled = false, onOpenRecord, className
9822
9954
  useEffect20(() => {
9823
9955
  void load();
9824
9956
  }, [load]);
9825
- const shown = useMemo20(() => {
9957
+ const shown2 = useMemo20(() => {
9826
9958
  const all = items ?? [];
9827
9959
  if (!tableId) return all;
9828
9960
  return all.filter((i) => i.kind !== "assignment" || i.subject_kind !== "record" || true);
9829
9961
  }, [items, tableId]);
9830
9962
  useEffect20(() => {
9831
- if (cursor >= shown.length) setCursor(Math.max(0, shown.length - 1));
9832
- }, [shown.length, cursor]);
9963
+ if (cursor >= shown2.length) setCursor(Math.max(0, shown2.length - 1));
9964
+ }, [shown2.length, cursor]);
9833
9965
  useEffect20(() => {
9834
9966
  const el = listRef.current?.querySelector(`[data-row="${cursor}"]`);
9835
9967
  el?.scrollIntoView({ block: "nearest" });
9836
- }, [cursor, shown.length]);
9968
+ }, [cursor, shown2.length]);
9837
9969
  const decide = useCallback16(
9838
9970
  async (item, approve) => {
9839
9971
  if (item.kind === "assignment") return;
@@ -9858,11 +9990,11 @@ function ActionInbox({ tableId, includeSettled = false, onOpenRecord, className
9858
9990
  );
9859
9991
  const onKeyDown = useCallback16(
9860
9992
  (event) => {
9861
- const item = shown[cursor];
9993
+ const item = shown2[cursor];
9862
9994
  const key = event.key;
9863
9995
  if (key === "j" || key === "ArrowDown") {
9864
9996
  event.preventDefault();
9865
- setCursor((c) => Math.min(shown.length - 1, c + 1));
9997
+ setCursor((c) => Math.min(shown2.length - 1, c + 1));
9866
9998
  } else if (key === "k" || key === "ArrowUp") {
9867
9999
  event.preventDefault();
9868
10000
  setCursor((c) => Math.max(0, c - 1));
@@ -9877,7 +10009,7 @@ function ActionInbox({ tableId, includeSettled = false, onOpenRecord, className
9877
10009
  void decide(item, key === "a");
9878
10010
  }
9879
10011
  },
9880
- [shown, cursor, decide, open, load]
10012
+ [shown2, cursor, decide, open, load]
9881
10013
  );
9882
10014
  if (items === null) return /* @__PURE__ */ jsx33(Skeleton12, { className: cn27("h-32 w-full", className) });
9883
10015
  return /* @__PURE__ */ jsxs29(
@@ -9890,12 +10022,12 @@ function ActionInbox({ tableId, includeSettled = false, onOpenRecord, className
9890
10022
  children: [
9891
10023
  /* @__PURE__ */ jsxs29("div", { className: "flex items-center gap-2", children: [
9892
10024
  /* @__PURE__ */ jsx33("h3", { className: "text-sm font-medium", children: "Inbox" }),
9893
- /* @__PURE__ */ jsx33("span", { className: "text-xs text-muted-foreground", children: shown.length }),
10025
+ /* @__PURE__ */ jsx33("span", { className: "text-xs text-muted-foreground", children: shown2.length }),
9894
10026
  /* @__PURE__ */ jsx33("span", { className: "ml-auto hidden text-[10px] text-muted-foreground sm:inline", children: "j/k move \xB7 a approve \xB7 d decline \xB7 o open \xB7 r refresh" }),
9895
10027
  /* @__PURE__ */ jsx33(Button27, { size: "sm", variant: "ghost", onClick: () => void load(), children: "Refresh" })
9896
10028
  ] }),
9897
10029
  error ? /* @__PURE__ */ jsx33(RefusalNotice, { error }) : null,
9898
- shown.length === 0 ? /* @__PURE__ */ jsx33("p", { className: "text-xs text-muted-foreground", children: includeSettled ? "Nothing has come through this inbox yet." : "Nothing is waiting on you." }) : /* @__PURE__ */ jsx33("ol", { ref: listRef, className: "flex min-h-0 flex-col gap-1 overflow-y-auto", children: shown.map((item, index) => {
10030
+ shown2.length === 0 ? /* @__PURE__ */ jsx33("p", { className: "text-xs text-muted-foreground", children: includeSettled ? "Nothing has come through this inbox yet." : "Nothing is waiting on you." }) : /* @__PURE__ */ jsx33("ol", { ref: listRef, className: "flex min-h-0 flex-col gap-1 overflow-y-auto", children: shown2.map((item, index) => {
9899
10031
  const settled = outcome[item.item_id];
9900
10032
  return /* @__PURE__ */ jsxs29(
9901
10033
  "li",
@@ -9959,6 +10091,7 @@ function ActionInbox({ tableId, includeSettled = false, onOpenRecord, className
9959
10091
  import { useCallback as useCallback17, useEffect as useEffect21, useRef as useRef10, useState as useState32 } from "react";
9960
10092
  import {
9961
10093
  useFields as useFields14,
10094
+ useRecordChangeRevision,
9962
10095
  useRecordsClient as useRecordsClient21,
9963
10096
  useTable as useTable11
9964
10097
  } from "@ai-matrx/records/react";
@@ -9975,6 +10108,7 @@ function HistoryPanel({ tableId, recordId, onAskAboutField, className }) {
9975
10108
  const [pending, setPending] = useState32({ phase: "idle" });
9976
10109
  const [said, setSaid] = useState32(null);
9977
10110
  const listRef = useRef10(null);
10111
+ const changed = useRecordChangeRevision(recordId);
9978
10112
  const load = useCallback17(async () => {
9979
10113
  const answered = await client.recordHistory({ record_id: recordId });
9980
10114
  if (!answered.ok) {
@@ -9983,7 +10117,7 @@ function HistoryPanel({ tableId, recordId, onAskAboutField, className }) {
9983
10117
  }
9984
10118
  setError(null);
9985
10119
  setEntries(answered.data);
9986
- }, [client, recordId]);
10120
+ }, [client, recordId, changed]);
9987
10121
  useEffect21(() => {
9988
10122
  void load();
9989
10123
  }, [load]);
@@ -10329,9 +10463,9 @@ function CommentThread({ tableId, recordId, fieldKey, className }) {
10329
10463
  }, [mentionQuery, people, picked]);
10330
10464
  if (error) return /* @__PURE__ */ jsx35(RefusalNotice, { error, className });
10331
10465
  if (!thread || table.loading || fields.loading) return /* @__PURE__ */ jsx35(Skeleton14, { className: cn29("h-32 w-full", className) });
10332
- const shown = fieldKey ? thread.comments.filter((c) => c.field_key === fieldKey) : thread.comments;
10333
- const roots = shown.filter((c) => !c.parent_comment_id);
10334
- const repliesOf = (id) => shown.filter((c) => c.parent_comment_id === id);
10466
+ const shown2 = fieldKey ? thread.comments.filter((c) => c.field_key === fieldKey) : thread.comments;
10467
+ const roots = shown2.filter((c) => !c.parent_comment_id);
10468
+ const repliesOf = (id) => shown2.filter((c) => c.parent_comment_id === id);
10335
10469
  const fieldLabel = fieldKey ? (fields.data ?? []).find((f) => f.key === fieldKey)?.label || humanize(fieldKey) : null;
10336
10470
  async function post() {
10337
10471
  if (draft.trim() === "") return;
@@ -10374,7 +10508,7 @@ function CommentThread({ tableId, recordId, fieldKey, className }) {
10374
10508
  return /* @__PURE__ */ jsxs31("section", { className: cn29("flex min-h-0 flex-col gap-2", className), children: [
10375
10509
  /* @__PURE__ */ jsxs31("div", { className: "flex items-baseline gap-2", children: [
10376
10510
  /* @__PURE__ */ jsx35("h3", { className: "text-sm font-medium", children: fieldLabel ? `Comments on ${fieldLabel}` : "Comments" }),
10377
- /* @__PURE__ */ jsx35("span", { className: "text-xs text-muted-foreground", children: shown.length }),
10511
+ /* @__PURE__ */ jsx35("span", { className: "text-xs text-muted-foreground", children: shown2.length }),
10378
10512
  /* @__PURE__ */ jsx35(
10379
10513
  Button29,
10380
10514
  {
@@ -11135,16 +11269,16 @@ function PortalsPanel({ tableId, activePortalId, className }) {
11135
11269
  const holdingThis = new Set(
11136
11270
  exposures.filter((e) => e.table_id === tableId).map((e) => e.portal_id)
11137
11271
  );
11138
- const shown = onATable ? portals.filter((p) => holdingThis.has(p.portal_id)) : portals;
11272
+ const shown2 = onATable ? portals.filter((p) => holdingThis.has(p.portal_id)) : portals;
11139
11273
  const couldTakeIt = onATable ? portals.filter((p) => !holdingThis.has(p.portal_id)) : [];
11140
11274
  const elsewhere = couldTakeIt.length;
11141
11275
  const origin = host.publicOrigin ?? (typeof window === "undefined" ? "" : window.location.origin);
11142
11276
  return /* @__PURE__ */ jsxs35("section", { className: cn33("flex flex-col gap-3", className), children: [
11143
11277
  /* @__PURE__ */ jsxs35("header", { className: "flex items-center gap-2", children: [
11144
11278
  /* @__PURE__ */ jsx39("h3", { className: "text-sm font-medium", children: "Portals" }),
11145
- /* @__PURE__ */ jsx39("span", { className: "text-xs text-muted-foreground", children: shown.length === 0 ? "none for this table" : `${shown.length}` }),
11279
+ /* @__PURE__ */ jsx39("span", { className: "text-xs text-muted-foreground", children: shown2.length === 0 ? "none for this table" : `${shown2.length}` }),
11146
11280
  /* @__PURE__ */ jsx39("div", { className: "flex-1" }),
11147
- shown.length > 0 ? /* @__PURE__ */ jsx39(Button33, { size: "sm", variant: building ? "secondary" : "ghost", onClick: () => setBuilding((b) => !b), children: building ? "Done building" : "Build a portal" }) : null
11281
+ shown2.length > 0 ? /* @__PURE__ */ jsx39(Button33, { size: "sm", variant: building ? "secondary" : "ghost", onClick: () => setBuilding((b) => !b), children: building ? "Done building" : "Build a portal" }) : null
11148
11282
  ] }),
11149
11283
  listError ? /* @__PURE__ */ jsx39(RefusalNotice, { error: listError }) : null,
11150
11284
  building ? /* @__PURE__ */ jsx39(
@@ -11169,7 +11303,7 @@ function PortalsPanel({ tableId, activePortalId, className }) {
11169
11303
  onClose: () => setAdding(null)
11170
11304
  }
11171
11305
  ) : null,
11172
- shown.length === 0 && !listError && !building && !adding ? /* @__PURE__ */ jsxs35(
11306
+ shown2.length === 0 && !listError && !building && !adding ? /* @__PURE__ */ jsxs35(
11173
11307
  BuildOrAsk,
11174
11308
  {
11175
11309
  mayBuild: true,
@@ -11191,7 +11325,7 @@ function PortalsPanel({ tableId, activePortalId, className }) {
11191
11325
  ]
11192
11326
  }
11193
11327
  ) : null,
11194
- shown.length === 0 && !listError && !building && !adding && elsewhere > 0 ? /* @__PURE__ */ jsxs35("div", { className: "flex flex-col gap-1.5 rounded-md border border-dashed border-border p-2.5", children: [
11328
+ shown2.length === 0 && !listError && !building && !adding && elsewhere > 0 ? /* @__PURE__ */ jsxs35("div", { className: "flex flex-col gap-1.5 rounded-md border border-dashed border-border p-2.5", children: [
11195
11329
  /* @__PURE__ */ jsx39("p", { className: "text-xs text-muted-foreground", children: "Or add this table to a portal your clients already sign in to:" }),
11196
11330
  /* @__PURE__ */ jsx39("div", { className: "flex flex-wrap gap-1.5", children: couldTakeIt.map((portal) => /* @__PURE__ */ jsxs35(
11197
11331
  Button33,
@@ -11208,8 +11342,8 @@ function PortalsPanel({ tableId, activePortalId, className }) {
11208
11342
  portal.portal_id
11209
11343
  )) })
11210
11344
  ] }) : null,
11211
- activePortalId && !shown.some((p) => p.portal_id === activePortalId) ? /* @__PURE__ */ jsx39("p", { className: "rounded-md border border-amber-600/40 bg-amber-500/5 px-3 py-2 text-xs leading-relaxed text-amber-700 dark:border-amber-400/40 dark:text-amber-300", children: PORTAL_NOT_HERE_LINE }) : null,
11212
- /* @__PURE__ */ jsx39("ul", { className: "flex flex-col gap-2", children: shown.map((portal) => {
11345
+ activePortalId && !shown2.some((p) => p.portal_id === activePortalId) ? /* @__PURE__ */ jsx39("p", { className: "rounded-md border border-amber-600/40 bg-amber-500/5 px-3 py-2 text-xs leading-relaxed text-amber-700 dark:border-amber-400/40 dark:text-amber-300", children: PORTAL_NOT_HERE_LINE }) : null,
11346
+ /* @__PURE__ */ jsx39("ul", { className: "flex flex-col gap-2", children: shown2.map((portal) => {
11213
11347
  const url = `${origin}${portalPath(portal.slug)}`;
11214
11348
  const open = openId === portal.portal_id;
11215
11349
  const linked = portal.portal_id === activePortalId;
@@ -11356,7 +11490,7 @@ function ArchivePortalControl({
11356
11490
  }
11357
11491
  function CopyLink({ url, what }) {
11358
11492
  const [copied, setCopied] = useState36(false);
11359
- const [shown, setShown] = useState36(false);
11493
+ const [shown2, setShown] = useState36(false);
11360
11494
  return /* @__PURE__ */ jsxs35(Fragment16, { children: [
11361
11495
  /* @__PURE__ */ jsx39(
11362
11496
  Button33,
@@ -11378,7 +11512,7 @@ function CopyLink({ url, what }) {
11378
11512
  children: copied ? "Copied" : what ? `Copy ${what}` : "Copy link"
11379
11513
  }
11380
11514
  ),
11381
- shown ? /* @__PURE__ */ jsxs35("p", { className: "mt-1.5 w-full break-all rounded border border-dashed border-border px-2 py-1 text-xs", children: [
11515
+ shown2 ? /* @__PURE__ */ jsxs35("p", { className: "mt-1.5 w-full break-all rounded border border-dashed border-border px-2 py-1 text-xs", children: [
11382
11516
  "This browser would not let the page copy for you, so here it is to copy by hand: ",
11383
11517
  url
11384
11518
  ] }) : null
@@ -13994,20 +14128,23 @@ function ChartBlock({ block, subject, className }) {
13994
14128
  label: point.label === "All records" ? block.title : `${block.title} \xB7 ${point.label}`
13995
14129
  });
13996
14130
  } : null;
13997
- return /* @__PURE__ */ jsxs45("figure", { className: cn43("flex min-w-0 flex-col gap-1.5 rounded border p-2", className), children: [
13998
- /* @__PURE__ */ jsxs45("figcaption", { className: "flex items-baseline gap-2 text-xs", children: [
13999
- /* @__PURE__ */ jsx49("span", { className: "truncate font-medium", children: block.title || CHART_KIND_LABEL[kind] }),
14000
- /* @__PURE__ */ jsx49("span", { className: "ml-auto shrink-0 text-muted-foreground", children: kind === "stuck" ? `${block.days ?? 14} days` : primary.replace("_", " of ") }),
14001
- typeof block.ms === "number" ? /* @__PURE__ */ jsxs45("span", { className: "shrink-0 tabular-nums text-muted-foreground/70", title: "how long the store took", children: [
14002
- pretty(block.ms),
14003
- " ms"
14004
- ] }) : null
14005
- ] }),
14006
- block.refused ? /* @__PURE__ */ jsx49(RefusalNotice, { error: asRefusal2(block) }) : needs ? /* @__PURE__ */ jsx49("p", { className: "px-1 py-3 text-xs text-muted-foreground", children: needs }) : kind === "stuck" ? /* @__PURE__ */ jsx49(StuckList, { rows: block.rows ?? [] }) : points.length === 0 ? /* @__PURE__ */ jsx49("p", { className: "px-1 py-3 text-xs text-muted-foreground", children: "The store answered this question with no groups at all, so there is nothing to draw yet." }) : /* @__PURE__ */ jsx49(Fragment20, { children: kind === "number" ? /* @__PURE__ */ jsx49(BigNumber, { points, measure: primary, onDrill: drill }) : kind === "table" ? /* @__PURE__ */ jsx49(GroupTable, { points, series, onDrill: drill }) : /* @__PURE__ */ jsxs45(Fragment20, { children: [
14007
- /* @__PURE__ */ jsx49(Drawing, { kind, points, series, config, onDrill: drill }),
14008
- /* @__PURE__ */ jsx49(Values, { points, measure: primary, config, onDrill: drill })
14009
- ] }) })
14010
- ] });
14131
+ return /* @__PURE__ */ jsxs45(
14132
+ "figure",
14133
+ {
14134
+ className: cn43("flex min-w-0 flex-col gap-1.5 rounded border p-2", className),
14135
+ ...typeof block.ms === "number" ? { "data-store-ms": pretty(block.ms) } : {},
14136
+ children: [
14137
+ /* @__PURE__ */ jsxs45("figcaption", { className: "flex items-baseline gap-2 text-xs", children: [
14138
+ /* @__PURE__ */ jsx49("span", { className: "truncate font-medium", children: block.title || CHART_KIND_LABEL[kind] }),
14139
+ /* @__PURE__ */ jsx49("span", { className: "ml-auto shrink-0 text-muted-foreground", children: kind === "stuck" ? `${block.days ?? 14} days` : primary.replace("_", " of ") })
14140
+ ] }),
14141
+ block.refused ? /* @__PURE__ */ jsx49(RefusalNotice, { error: asRefusal2(block) }) : needs ? /* @__PURE__ */ jsx49("p", { className: "px-1 py-3 text-xs text-muted-foreground", children: needs }) : kind === "stuck" ? /* @__PURE__ */ jsx49(StuckList, { rows: block.rows ?? [] }) : points.length === 0 ? /* @__PURE__ */ jsx49("p", { className: "px-1 py-3 text-xs text-muted-foreground", children: "The store answered this question with no groups at all, so there is nothing to draw yet." }) : /* @__PURE__ */ jsx49(Fragment20, { children: kind === "number" ? /* @__PURE__ */ jsx49(BigNumber, { points, measure: primary, onDrill: drill }) : kind === "table" ? /* @__PURE__ */ jsx49(GroupTable, { points, series, onDrill: drill }) : /* @__PURE__ */ jsxs45(Fragment20, { children: [
14142
+ /* @__PURE__ */ jsx49(Drawing, { kind, points, series, config, onDrill: drill }),
14143
+ /* @__PURE__ */ jsx49(Values, { points, measure: primary, config, onDrill: drill })
14144
+ ] }) })
14145
+ ]
14146
+ }
14147
+ );
14011
14148
  }
14012
14149
  function BigNumber({
14013
14150
  points,
@@ -14629,7 +14766,7 @@ function FormsPanel({ tableId, activeFormId, className }) {
14629
14766
  },
14630
14767
  [client, load]
14631
14768
  );
14632
- const [shown, setShown] = useState48(null);
14769
+ const [shown2, setShown] = useState48(null);
14633
14770
  const copy = useCallback31(async (url, formId) => {
14634
14771
  try {
14635
14772
  await navigator.clipboard.writeText(url);
@@ -14728,7 +14865,7 @@ function FormsPanel({ tableId, activeFormId, className }) {
14728
14865
  ) : null
14729
14866
  ] }),
14730
14867
  rights.structure && publishGate.why ? /* @__PURE__ */ jsx51("p", { className: "mt-1.5 text-xs text-muted-foreground", children: publishGate.why }) : null,
14731
- shown && shown === url ? /* @__PURE__ */ jsxs47("p", { className: "mt-1.5 break-all rounded border border-dashed px-2 py-1 text-xs", children: [
14868
+ shown2 && shown2 === url ? /* @__PURE__ */ jsxs47("p", { className: "mt-1.5 break-all rounded border border-dashed px-2 py-1 text-xs", children: [
14732
14869
  "This browser would not let the page copy for you, so here it is to copy by hand:",
14733
14870
  " ",
14734
14871
  url
@@ -14892,7 +15029,7 @@ function BookingBuilder({ tableId, bookingId, onSaved, onClose, className }) {
14892
15029
  return /* @__PURE__ */ jsx52("p", { className: cn46("text-xs text-muted-foreground", className), children: rights.why("structure") ?? "Making a booking page needs the admin level on this table, because it lets people with no account take time in your calendar." });
14893
15030
  }
14894
15031
  const url = formId ? `${publicOrigin}${bookingPath(formId)}` : null;
14895
- const shown = offer ?? null;
15032
+ const shown2 = offer ?? null;
14896
15033
  return (
14897
15034
  // THE RAIL IS 384px WIDE AND THE VIEWPORT IS NOT (walk 1, 2026-09-21):
14898
15035
  // every split below is a CONTAINER query, so this screen is the same
@@ -15040,21 +15177,21 @@ function BookingBuilder({ tableId, bookingId, onSaved, onClose, className }) {
15040
15177
  }
15041
15178
  )
15042
15179
  ] }),
15043
- shown ? /* @__PURE__ */ jsxs48("p", { className: "rounded-md border bg-muted/40 p-2.5 text-xs", children: [
15180
+ shown2 ? /* @__PURE__ */ jsxs48("p", { className: "rounded-md border bg-muted/40 p-2.5 text-xs", children: [
15044
15181
  "Saved. The page offers ",
15045
- shown.slot_minutes,
15182
+ shown2.slot_minutes,
15046
15183
  "-minute appointments in ",
15047
- shown.timezone,
15184
+ shown2.timezone,
15048
15185
  ", up to",
15049
15186
  " ",
15050
- shown.max_per_day,
15187
+ shown2.max_per_day,
15051
15188
  " a day, ",
15052
- shown.days,
15189
+ shown2.days,
15053
15190
  " days ahead",
15054
- shown.buffer_minutes > 0 ? `, with ${shown.buffer_minutes} minutes between them` : "",
15191
+ shown2.buffer_minutes > 0 ? `, with ${shown2.buffer_minutes} minutes between them` : "",
15055
15192
  ":",
15056
15193
  " ",
15057
- shown.windows.map(
15194
+ shown2.windows.map(
15058
15195
  (w) => `${DAYS.find((d) => d.weekday === w.weekday)?.label ?? w.weekday} ${w.from}\u2013${w.to}`
15059
15196
  ).join(", "),
15060
15197
  "."
@@ -15096,21 +15233,22 @@ import { useRecordsClient as useRecordsClient37, useMyLevels as useMyLevels2 } f
15096
15233
  import { bookingPath as bookingPath2 } from "@ai-matrx/records";
15097
15234
  import { Badge as Badge16, Button as Button45, Skeleton as Skeleton29, cn as cn47 } from "@ai-matrx/design-system";
15098
15235
  import { Fragment as Fragment24, jsx as jsx53, jsxs as jsxs49 } from "react/jsx-runtime";
15236
+ var BOOKING_NOT_HERE_LINE = "The link named a booking page this table does not have \u2014 it may have been closed for good, or it writes into another table. The booking pages this table does have are listed here.";
15099
15237
  var WHAT_A_BOOKING_PAGE_IS = "A booking page offers times you are free and writes each appointment into this table as an ordinary record.";
15100
15238
  function bookingSuggestion(tableName2) {
15101
- const subject = tableName2?.trim() ? tableName2.trim() : "appointments";
15102
- return `Let clients book a 30-minute ${subject} slot on Tuesday and Thursday afternoons.`;
15239
+ const name = tableName2?.trim();
15240
+ return name ? `Let clients book a 30-minute appointment on Tuesday and Thursday afternoons, and put each one in ${name}.` : "Let clients book a 30-minute appointment on Tuesday and Thursday afternoons.";
15103
15241
  }
15104
15242
  var NO_ADMIN2 = "Opening a booking page lets people with no account take time in your calendar, so it needs the admin level on the table the appointments land in.";
15105
15243
  var STATE_WORDS = BOOKING_PAGE_STATE_LABEL;
15106
- function BookingSlots({ tableId, className }) {
15244
+ function BookingSlots({ tableId, activeBookingId, className }) {
15107
15245
  const client = useRecordsClient37();
15108
15246
  const host = useRecordsUi();
15109
15247
  const [pages, setPages] = useState50(null);
15110
15248
  const [error, setError] = useState50(null);
15111
15249
  const [busy, setBusy] = useState50(null);
15112
15250
  const [copied, setCopied] = useState50(null);
15113
- const [shown, setShown] = useState50(null);
15251
+ const [shown2, setShown] = useState50(null);
15114
15252
  const [building, setBuilding] = useState50(false);
15115
15253
  const load = useCallback33(async () => {
15116
15254
  const answered = await client.bookings(tableId ? { table_id: tableId } : {});
@@ -15176,6 +15314,7 @@ function BookingSlots({ tableId, className }) {
15176
15314
  tableId && pages.length > 0 ? /* @__PURE__ */ jsx53(Button45, { size: "sm", variant: building ? "secondary" : "ghost", onClick: () => setBuilding((b) => !b), children: building ? "Done building" : "Build a booking page" }) : null
15177
15315
  ] }),
15178
15316
  error ? /* @__PURE__ */ jsx53(RefusalNotice, { error }) : null,
15317
+ activeBookingId && !pages.some((p) => p.form_id === activeBookingId) ? /* @__PURE__ */ jsx53("p", { className: "rounded-md border border-amber-600/40 bg-amber-500/5 px-3 py-2 text-xs leading-relaxed text-amber-700 dark:border-amber-400/40 dark:text-amber-300", children: BOOKING_NOT_HERE_LINE }) : null,
15179
15318
  building && tableId ? /* @__PURE__ */ jsx53(
15180
15319
  BookingBuilder,
15181
15320
  {
@@ -15211,53 +15350,66 @@ function BookingSlots({ tableId, className }) {
15211
15350
  const url = `${origin}${bookingPath2(page.form_id)}`;
15212
15351
  const mayOpen = levels.data?.[page.table_id] === "admin";
15213
15352
  const open = page.published_at !== null && page.closed_at === null;
15214
- return /* @__PURE__ */ jsxs49("li", { className: "rounded-md border p-2.5", "data-testid": "booking-page", children: [
15215
- /* @__PURE__ */ jsxs49("div", { className: "flex items-center gap-2", children: [
15216
- /* @__PURE__ */ jsx53("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: page.title ?? page.slug }),
15217
- /* @__PURE__ */ jsx53(Badge16, { variant: page.state === "open" ? "default" : "secondary", children: page.state })
15218
- ] }),
15219
- /* @__PURE__ */ jsx53("p", { className: "mt-0.5 text-xs text-muted-foreground", children: STATE_WORDS[page.state] ?? page.state }),
15220
- /* @__PURE__ */ jsxs49("p", { className: "mt-1.5 text-xs", children: [
15221
- /* @__PURE__ */ jsx53("span", { className: "font-medium", children: page.upcoming }),
15222
- " coming up",
15223
- " \xB7 ",
15224
- /* @__PURE__ */ jsx53("span", { className: "font-medium", children: page.booked }),
15225
- " booked in all",
15226
- page.held > 0 ? /* @__PURE__ */ jsxs49(Fragment24, { children: [
15227
- " \xB7 ",
15228
- /* @__PURE__ */ jsx53("span", { className: "font-medium", children: page.held }),
15229
- " being held right now"
15230
- ] }) : null,
15231
- page.cancelled > 0 ? /* @__PURE__ */ jsxs49(Fragment24, { children: [
15232
- " \xB7 ",
15233
- /* @__PURE__ */ jsx53("span", { className: "font-medium", children: page.cancelled }),
15234
- " cancelled"
15235
- ] }) : null,
15236
- /* @__PURE__ */ jsxs49("span", { className: "text-muted-foreground", children: [
15237
- " \xB7 ",
15238
- page.slot_minutes,
15239
- " minutes each"
15240
- ] })
15241
- ] }),
15242
- /* @__PURE__ */ jsx53("p", { className: "mt-1 text-xs text-muted-foreground", children: nextInWords(page) }),
15243
- /* @__PURE__ */ jsxs49("div", { className: "mt-2 flex flex-wrap items-center gap-1.5", children: [
15244
- /* @__PURE__ */ jsx53(Button45, { size: "sm", variant: "outline", onClick: () => void copy(url, page.form_id), children: copied === page.form_id ? "Copied" : "Copy link" }),
15245
- mayOpen ? /* @__PURE__ */ jsx53(
15246
- Button45,
15247
- {
15248
- size: "sm",
15249
- variant: "ghost",
15250
- disabled: busy === page.form_id,
15251
- onClick: () => void toggle(page),
15252
- children: busy === page.form_id ? "\u2026" : open ? "Close" : "Open"
15253
- }
15254
- ) : null
15255
- ] }),
15256
- shown && shown === url ? /* @__PURE__ */ jsxs49("p", { className: "mt-1.5 break-all rounded border border-dashed px-2 py-1 text-xs", children: [
15257
- "This browser would not let the page copy for you, so here it is to copy by hand: ",
15258
- url
15259
- ] }) : null
15260
- ] }, page.form_id);
15353
+ return /* @__PURE__ */ jsxs49(
15354
+ "li",
15355
+ {
15356
+ "data-testid": "booking-page",
15357
+ "data-linked": page.form_id === activeBookingId ? "true" : void 0,
15358
+ ref: page.form_id === activeBookingId ? (el) => el?.scrollIntoView({ block: "nearest" }) : void 0,
15359
+ className: cn47(
15360
+ "rounded-md border p-2.5",
15361
+ page.form_id === activeBookingId && "border-primary ring-1 ring-primary"
15362
+ ),
15363
+ children: [
15364
+ /* @__PURE__ */ jsxs49("div", { className: "flex items-center gap-2", children: [
15365
+ /* @__PURE__ */ jsx53("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: page.title ?? page.slug }),
15366
+ /* @__PURE__ */ jsx53(Badge16, { variant: page.state === "open" ? "default" : "secondary", children: page.state })
15367
+ ] }),
15368
+ /* @__PURE__ */ jsx53("p", { className: "mt-0.5 text-xs text-muted-foreground", children: STATE_WORDS[page.state] ?? page.state }),
15369
+ /* @__PURE__ */ jsxs49("p", { className: "mt-1.5 text-xs", children: [
15370
+ /* @__PURE__ */ jsx53("span", { className: "font-medium", children: page.upcoming }),
15371
+ " coming up",
15372
+ " \xB7 ",
15373
+ /* @__PURE__ */ jsx53("span", { className: "font-medium", children: page.booked }),
15374
+ " booked in all",
15375
+ page.held > 0 ? /* @__PURE__ */ jsxs49(Fragment24, { children: [
15376
+ " \xB7 ",
15377
+ /* @__PURE__ */ jsx53("span", { className: "font-medium", children: page.held }),
15378
+ " being held right now"
15379
+ ] }) : null,
15380
+ page.cancelled > 0 ? /* @__PURE__ */ jsxs49(Fragment24, { children: [
15381
+ " \xB7 ",
15382
+ /* @__PURE__ */ jsx53("span", { className: "font-medium", children: page.cancelled }),
15383
+ " cancelled"
15384
+ ] }) : null,
15385
+ /* @__PURE__ */ jsxs49("span", { className: "text-muted-foreground", children: [
15386
+ " \xB7 ",
15387
+ page.slot_minutes,
15388
+ " minutes each"
15389
+ ] })
15390
+ ] }),
15391
+ /* @__PURE__ */ jsx53("p", { className: "mt-1 text-xs text-muted-foreground", children: nextInWords(page) }),
15392
+ /* @__PURE__ */ jsxs49("div", { className: "mt-2 flex flex-wrap items-center gap-1.5", children: [
15393
+ /* @__PURE__ */ jsx53(Button45, { size: "sm", variant: "outline", onClick: () => void copy(url, page.form_id), children: copied === page.form_id ? "Copied" : "Copy link" }),
15394
+ mayOpen ? /* @__PURE__ */ jsx53(
15395
+ Button45,
15396
+ {
15397
+ size: "sm",
15398
+ variant: "ghost",
15399
+ disabled: busy === page.form_id,
15400
+ onClick: () => void toggle(page),
15401
+ children: busy === page.form_id ? "\u2026" : open ? "Close" : "Open"
15402
+ }
15403
+ ) : null
15404
+ ] }),
15405
+ shown2 && shown2 === url ? /* @__PURE__ */ jsxs49("p", { className: "mt-1.5 break-all rounded border border-dashed px-2 py-1 text-xs", children: [
15406
+ "This browser would not let the page copy for you, so here it is to copy by hand: ",
15407
+ url
15408
+ ] }) : null
15409
+ ]
15410
+ },
15411
+ page.form_id
15412
+ );
15261
15413
  }) }),
15262
15414
  pages.length > 0 && !levels.loading && levels.data && subjectIds.every((id) => levels.data?.[id] !== "admin") ? /* @__PURE__ */ jsx53("p", { className: "text-xs text-muted-foreground", children: NO_ADMIN2 }) : null
15263
15415
  ] });
@@ -16400,8 +16552,15 @@ var LANE_TITLE = {
16400
16552
  };
16401
16553
  var APP_TABLE_PREFIX = "records_ui_";
16402
16554
  var LANE_EMPTY = {
16403
- mine: "Nothing of your own yet. New table makes one that only you can see.",
16404
- organization: "No tables shared across your organization yet. A table you make is yours until you change its visibility to internal.",
16555
+ // 🚨 BOTH SENTENCES USED TO DESCRIBE A PRODUCT THAT DOES NOT EXIST
16556
+ // (VERIFIER-15 M2, VERIFIER-16 M6). "New table makes one that only you can
16557
+ // see" — New table makes one shared with the organization, and it lands under
16558
+ // My organization. "A table you make is yours until you change its visibility
16559
+ // to internal" — it is internal from the start, and no screen here changes a
16560
+ // table's visibility at all. An empty lane says what is true and names only a
16561
+ // control that exists.
16562
+ mine: "Nothing here is visible to you alone. A new table is shared with your organization from the start.",
16563
+ organization: "No tables shared across your organization yet. New table makes one, and everyone here can open it.",
16405
16564
  system: "The platform's own tables are not in this organization's store.",
16406
16565
  community: "Nothing shared beyond your organization. A table published by link or to the world lands here.",
16407
16566
  app: "The app has not needed a table of its own in this organization yet. Saving a view, writing a comment or building a form makes one, and it appears here rather than among your own data."
@@ -17046,7 +17205,7 @@ function TablePage({
17046
17205
  }
17047
17206
  ) : null,
17048
17207
  rail === "forms" ? /* @__PURE__ */ jsx61(FormsPanel, { tableId, activeFormId: itemFor("forms") }) : null,
17049
- rail === "bookings" ? /* @__PURE__ */ jsx61(BookingSlots, { tableId }) : null,
17208
+ rail === "bookings" ? /* @__PURE__ */ jsx61(BookingSlots, { tableId, activeBookingId: itemFor("bookings") }) : null,
17050
17209
  rail === "checklists" ? /* @__PURE__ */ jsx61(
17051
17210
  ChecklistsPanel,
17052
17211
  {
@@ -17117,6 +17276,7 @@ export {
17117
17276
  ArchivedDisclosure,
17118
17277
  ArchivedPortals,
17119
17278
  ArchivedView,
17279
+ BOOKING_NOT_HERE_LINE,
17120
17280
  BOOKING_PAGE_STATE_LABEL,
17121
17281
  BOOKING_PAGE_STATE_VALUES,
17122
17282
  BookingBuilder,
@@ -17333,6 +17493,7 @@ export {
17333
17493
  mayNotRestoreLine,
17334
17494
  memberName,
17335
17495
  nextInWords,
17496
+ nextSummaryAt,
17336
17497
  openingRail,
17337
17498
  openingView,
17338
17499
  pageViewFromParam,