@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.cjs CHANGED
@@ -40,6 +40,7 @@ __export(src_exports, {
40
40
  ArchivedDisclosure: () => ArchivedDisclosure,
41
41
  ArchivedPortals: () => ArchivedPortals,
42
42
  ArchivedView: () => ArchivedView,
43
+ BOOKING_NOT_HERE_LINE: () => BOOKING_NOT_HERE_LINE,
43
44
  BOOKING_PAGE_STATE_LABEL: () => BOOKING_PAGE_STATE_LABEL,
44
45
  BOOKING_PAGE_STATE_VALUES: () => BOOKING_PAGE_STATE_VALUES,
45
46
  BookingBuilder: () => BookingBuilder,
@@ -256,6 +257,7 @@ __export(src_exports, {
256
257
  mayNotRestoreLine: () => mayNotRestoreLine,
257
258
  memberName: () => memberName,
258
259
  nextInWords: () => nextInWords,
260
+ nextSummaryAt: () => nextSummaryAt,
259
261
  openingRail: () => openingRail,
260
262
  openingView: () => openingView,
261
263
  pageViewFromParam: () => pageViewFromParam,
@@ -560,13 +562,20 @@ function plainSentence(message) {
560
562
  }
561
563
  return { kept: kept.length === 0 ? null : kept.join(" "), dropped };
562
564
  }
565
+ function exampleOf(error) {
566
+ const detail = error.detail;
567
+ if (!detail || typeof detail !== "object") return null;
568
+ const example = detail.example;
569
+ return typeof example === "string" && example.trim() !== "" ? example.trim() : null;
570
+ }
563
571
  function refusalForAPerson(error) {
564
572
  const code = error.code ?? "internal";
565
573
  const message = plainSentence(String(error.message ?? ""));
566
574
  const hint = plainSentence(String(error.hint ?? ""));
567
575
  const title = TITLE[code] ?? "The store refused this";
568
576
  const hintIsForEngineers = isEngineerText(String(error.hint ?? ""));
569
- const remedy = (hintIsForEngineers ? null : hint.kept) ?? REMEDY[code] ?? REMEDY["internal"];
577
+ const example = exampleOf(error);
578
+ const remedy = (example ? `Enter it like ${example}.` : null) ?? (hintIsForEngineers ? null : hint.kept) ?? REMEDY[code] ?? REMEDY["internal"];
570
579
  const messageIsForEngineers = isEngineerText(String(error.message ?? ""));
571
580
  const said = (messageIsForEngineers ? "" : message.kept ?? "").trim();
572
581
  const forEngineers = [
@@ -590,7 +599,9 @@ function refusalForAPerson(error) {
590
599
  }
591
600
  function refusalLineForAPerson(error) {
592
601
  const plain = refusalForAPerson(error);
593
- return `${plain.sentence} ${plain.remedy}`.trim();
602
+ const sentence2 = plain.sentence.trim();
603
+ const ended = sentence2 === "" || /[.!?…:]$/.test(sentence2) ? sentence2 : `${sentence2}.`;
604
+ return `${ended} ${plain.remedy}`.trim();
594
605
  }
595
606
 
596
607
  // src/names.ts
@@ -1060,6 +1071,42 @@ function asRefusal(thrown, sentence2, remedy, code = "internal") {
1060
1071
  return refusalFromThrown(thrown, sentence2, remedy, code);
1061
1072
  }
1062
1073
 
1074
+ // src/tellApart.ts
1075
+ function shown(value) {
1076
+ if (value === null || value === void 0) return null;
1077
+ if (typeof value === "string") return value.trim() === "" ? null : value.trim();
1078
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
1079
+ return null;
1080
+ }
1081
+ function tellApart(rows, nameOf) {
1082
+ const byName = /* @__PURE__ */ new Map();
1083
+ for (const row of rows) {
1084
+ const name = nameOf(row.id).trim().toLowerCase();
1085
+ byName.set(name, [...byName.get(name) ?? [], row]);
1086
+ }
1087
+ const out = /* @__PURE__ */ new Map();
1088
+ for (const [name, group] of byName) {
1089
+ if (group.length < 2) continue;
1090
+ const keys = [
1091
+ ...new Set(
1092
+ group.flatMap(
1093
+ (row) => Object.keys(row.document ?? {}).filter((k) => !k.startsWith("_") && shown(row.document?.[k]) !== null)
1094
+ )
1095
+ )
1096
+ ].filter((k) => group.some((row) => (shown(row.document?.[k]) ?? "").toLowerCase() !== name));
1097
+ const valuesOf = (k) => group.map((row) => shown(row.document?.[k]) ?? "");
1098
+ const differs = keys.filter((k) => new Set(valuesOf(k)).size > 1);
1099
+ const whole = differs.find((k) => new Set(valuesOf(k)).size === group.length);
1100
+ const chosen = whole ? [whole] : differs.slice(0, 2);
1101
+ if (chosen.length === 0) continue;
1102
+ for (const row of group) {
1103
+ const words = chosen.map((k) => shown(row.document?.[k])).filter((w) => w !== null);
1104
+ if (words.length > 0) out.set(row.id, words.join(" \xB7 "));
1105
+ }
1106
+ }
1107
+ return out;
1108
+ }
1109
+
1063
1110
  // src/RelationPicker.tsx
1064
1111
  var import_jsx_runtime4 = (
1065
1112
  // The Field's own name is already printed above this control by
@@ -1201,9 +1248,12 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1201
1248
  setSearch("");
1202
1249
  toggle(createdId);
1203
1250
  };
1204
- const visible = (rows ?? []).filter(
1205
- (row) => search.trim() === "" ? true : titleOf(row.id).toLowerCase().includes(search.trim().toLowerCase())
1206
- );
1251
+ const apart = tellApart(rows ?? [], titleOf);
1252
+ const visible = (rows ?? []).filter((row) => {
1253
+ const q = search.trim().toLowerCase();
1254
+ if (q === "") return true;
1255
+ return `${titleOf(row.id)} ${apart.get(row.id) ?? ""}`.toLowerCase().includes(q);
1256
+ });
1207
1257
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: (0, import_design_system3.cn)("flex flex-wrap items-center gap-1", className), children: [
1208
1258
  picked.map((recordId) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1209
1259
  RecordChip,
@@ -1234,17 +1284,21 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1234
1284
  " at most. Remove one to add another."
1235
1285
  ] }) : null,
1236
1286
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system3.ScrollArea, { className: "max-h-56", children: [
1237
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { children: visible.map((row) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1287
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { children: visible.map((row) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1238
1288
  "button",
1239
1289
  {
1240
1290
  type: "button",
1241
1291
  onClick: () => toggle(row.id),
1242
- title: titleOf(row.id),
1292
+ title: apart.has(row.id) ? `${titleOf(row.id)} \u2014 ${apart.get(row.id)}` : titleOf(row.id),
1293
+ "data-relation-candidate": row.id,
1243
1294
  className: (0, import_design_system3.cn)(
1244
1295
  "w-full truncate rounded px-2 py-1 text-left text-xs hover:bg-muted",
1245
1296
  picked.includes(row.id) ? "bg-accent text-accent-foreground" : ""
1246
1297
  ),
1247
- children: titleOf(row.id)
1298
+ children: [
1299
+ titleOf(row.id),
1300
+ apart.has(row.id) ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "block truncate text-[11px] text-muted-foreground", "data-tell-apart": "", children: apart.get(row.id) }) : null
1301
+ ]
1248
1302
  }
1249
1303
  ) }, row.id)) }),
1250
1304
  rows && visible.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "p-2 text-xs text-muted-foreground", children: "Nothing in that table matches." }) : null
@@ -2176,7 +2230,7 @@ var FIELD_RULE_KIND_VALUES = ["min", "max", "length", "pattern", "equals_field",
2176
2230
  var FIELD_RULE_KIND_LABEL = {
2177
2231
  min: "At least",
2178
2232
  max: "At most",
2179
- length: "No longer than",
2233
+ length: "Number of characters",
2180
2234
  pattern: "Looks like",
2181
2235
  equals_field: "Same as the column",
2182
2236
  differs_from_field: "Different from the column"
@@ -2208,6 +2262,20 @@ function envelopeFor(document2, key) {
2208
2262
  const block = document2?._values;
2209
2263
  return block?.[key];
2210
2264
  }
2265
+ function unresolvedFor(document2, key) {
2266
+ const sources = document2?._sources;
2267
+ if (!sources || typeof sources !== "object") return void 0;
2268
+ for (const source of Object.values(sources)) {
2269
+ const held = source?.unresolved?.[key];
2270
+ if (typeof held === "string" && held) return held;
2271
+ if (Array.isArray(held) && held.length) return held.map(String).join(", ");
2272
+ }
2273
+ return void 0;
2274
+ }
2275
+ function hasFormula(field) {
2276
+ const config = field.config ?? {};
2277
+ return Boolean(config.expr) || Boolean(String(config.formula_text ?? "").trim());
2278
+ }
2211
2279
  var ABSENCE_WORDS = ABSENCE_WORD_LABEL;
2212
2280
  function renderValue(field, value, document2, labels, stillWaitingFor) {
2213
2281
  const envelope = envelopeFor(document2, field.key);
@@ -2215,6 +2283,13 @@ function renderValue(field, value, document2, labels, stillWaitingFor) {
2215
2283
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-xs italic text-muted-foreground", title: `VAL-2: ${envelope.absent}`, children: ABSENCE_WORDS[String(envelope.absent)] ?? String(envelope.absent) });
2216
2284
  }
2217
2285
  if (value === null || value === void 0 || value === "") {
2286
+ const gone = unresolvedFor(document2, field.key);
2287
+ if (gone) {
2288
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-xs italic text-muted-foreground", title: `It pointed at ${gone}`, children: "The record this pointed at no longer exists" });
2289
+ }
2290
+ if (field.type === "formula" && !hasFormula(field)) {
2291
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-xs italic text-muted-foreground", children: "Worked out once its formula is in place" });
2292
+ }
2218
2293
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-muted-foreground", children: "\u2014" });
2219
2294
  }
2220
2295
  const kind = editorKindFor(field);
@@ -3241,7 +3316,7 @@ function EnrichPanel({ tableId, fieldId, className }) {
3241
3316
  (0, import_react17.useEffect)(() => {
3242
3317
  void load();
3243
3318
  }, [load]);
3244
- const shown = (0, import_react17.useMemo)(
3319
+ const shown2 = (0, import_react17.useMemo)(
3245
3320
  () => fieldId ? (rows ?? []).filter((r) => r.field_id === fieldId) : rows ?? [],
3246
3321
  [rows, fieldId]
3247
3322
  );
@@ -3300,7 +3375,7 @@ function EnrichPanel({ tableId, fieldId, className }) {
3300
3375
  );
3301
3376
  if (error) return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(RefusalNotice, { error, className });
3302
3377
  if (rows === null) return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_design_system8.Skeleton, { className: (0, import_design_system8.cn)("h-40 w-full", className) });
3303
- if (shown.length === 0 && fieldId) {
3378
+ if (shown2.length === 0 && fieldId) {
3304
3379
  const target = (fields.data ?? []).find((f) => f.id === fieldId);
3305
3380
  const setup = draft[fieldId] ?? { instruction: "", every: "30" };
3306
3381
  if (!rights.admin) {
@@ -3363,13 +3438,13 @@ function EnrichPanel({ tableId, fieldId, className }) {
3363
3438
  outcome ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "text-xs text-muted-foreground", children: outcome.message }) : null
3364
3439
  ] });
3365
3440
  }
3366
- if (shown.length === 0) {
3441
+ if (shown2.length === 0) {
3367
3442
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: (0, import_design_system8.cn)("space-y-2 p-4 text-sm", className), children: [
3368
3443
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "font-medium", children: "No column here is filled in by a model." }),
3369
3444
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("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." })
3370
3445
  ] });
3371
3446
  }
3372
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: (0, import_design_system8.cn)("space-y-6 p-4", className), children: shown.map((row) => {
3447
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: (0, import_design_system8.cn)("space-y-6 p-4", className), children: shown2.map((row) => {
3373
3448
  const d = draft[row.field_id] ?? {
3374
3449
  instruction: row.enrichment?.instruction ?? "",
3375
3450
  every: row.review_interval_days ? String(row.review_interval_days) : ""
@@ -3658,18 +3733,18 @@ function Outcome({ outcome, onClose }) {
3658
3733
  ] });
3659
3734
  }
3660
3735
  function PlanTable({ plan }) {
3661
- const shown = plan.rows.slice(0, 8);
3662
- const keys = Array.from(new Set(shown.flatMap((row) => row.cells.map((c) => c.key))));
3663
- if (shown.length === 0 || keys.length === 0) return null;
3736
+ const shown2 = plan.rows.slice(0, 8);
3737
+ const keys = Array.from(new Set(shown2.flatMap((row) => row.cells.map((c) => c.key))));
3738
+ if (shown2.length === 0 || keys.length === 0) return null;
3664
3739
  const columnName = /* @__PURE__ */ new Map();
3665
- for (const row of shown) for (const cell of row.cells) if (!columnName.has(cell.key)) columnName.set(cell.key, cell.column);
3740
+ for (const row of shown2) for (const cell of row.cells) if (!columnName.has(cell.key)) columnName.set(cell.key, cell.column);
3666
3741
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "overflow-auto rounded border", children: [
3667
3742
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("table", { className: "w-full text-xs", "data-matrx-paste-plan": true, children: [
3668
3743
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("thead", { className: "bg-muted/50", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
3669
3744
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { className: "px-2 py-1 text-left font-medium", children: "Row" }),
3670
3745
  keys.map((key) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { className: "px-2 py-1 text-left font-medium", children: columnName.get(key) ?? key }, key))
3671
3746
  ] }) }),
3672
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("tbody", { children: shown.map((row) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { className: "border-t", children: [
3747
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("tbody", { children: shown2.map((row) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { className: "border-t", children: [
3673
3748
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("td", { className: "whitespace-nowrap px-2 py-1", children: [
3674
3749
  row.fromLine,
3675
3750
  row.recordId === null ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_design_system9.Badge, { variant: "outline", className: "ml-1", children: "new" }) : null
@@ -3691,9 +3766,9 @@ function PlanTable({ plan }) {
3691
3766
  })
3692
3767
  ] }, row.fromLine)) })
3693
3768
  ] }),
3694
- plan.rows.length > shown.length ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("p", { className: "px-2 py-1 text-xs text-muted-foreground", children: [
3769
+ plan.rows.length > shown2.length ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("p", { className: "px-2 py-1 text-xs text-muted-foreground", children: [
3695
3770
  "\u2026and ",
3696
- plan.rows.length - shown.length,
3771
+ plan.rows.length - shown2.length,
3697
3772
  " more rows the same way."
3698
3773
  ] }) : null
3699
3774
  ] });
@@ -4753,7 +4828,6 @@ function checkImportRules(fields, rows, mapping) {
4753
4828
  var import_jsx_runtime16 = require("react/jsx-runtime");
4754
4829
  var BATCH = 250;
4755
4830
  var SAMPLES = 40;
4756
- var MAPPING_COLUMNS = "minmax(9rem, 11rem) minmax(14rem, 17rem) minmax(13rem, 1fr) minmax(8rem, 10rem)";
4757
4831
  var ABSENT = "__absent__";
4758
4832
  function obviousTitleColumn(columns) {
4759
4833
  const says = /^(title|name|record|customer|client|company|job|item|subject|label|description)\b|(\bname|\btitle|\bnumber|\bid)$/i;
@@ -4770,7 +4844,11 @@ async function sha256(bytes) {
4770
4844
  return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
4771
4845
  }
4772
4846
  function typeWord(t) {
4773
- return fieldTypeChoice(t)?.label ?? (t === "relation" ? "Points at another table" : t);
4847
+ if (t === "range") return fieldTypeChoice("number")?.label ?? "Number";
4848
+ return fieldTypeChoice(t)?.label ?? (t === "relation" ? "Points at another table" : humanize(t));
4849
+ }
4850
+ function lineInYourFile(dataRow) {
4851
+ return dataRow + 1;
4774
4852
  }
4775
4853
  function ImportWizard({ tableId, onDone, onWrote, className }) {
4776
4854
  const client = (0, import_react27.useRecordsClient)();
@@ -5011,6 +5089,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
5011
5089
  const refusedByRules = ruleVerdicts.reduce((n, v) => n + v.refused, 0);
5012
5090
  const [goAheadAnyway, setGoAheadAnyway] = (0, import_react26.useState)(false);
5013
5091
  (0, import_react26.useEffect)(() => setGoAheadAnyway(false), [ruleVerdicts]);
5092
+ const afterWrite = !busy && (phase === "done" || alreadyRan !== null || ledger !== null && ledger.seen > 0);
5014
5093
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: (0, import_design_system13.cn)("flex min-w-0 flex-col gap-2 text-xs", className), children: [
5015
5094
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center gap-2", children: [
5016
5095
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "font-medium", children: "Import" }),
@@ -5041,7 +5120,11 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
5041
5120
  problem ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(RefusalNotice, { error: problem }) : null,
5042
5121
  phase === "reading" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-muted-foreground", children: "Reading the file\u2026" }) : null,
5043
5122
  phase === "planning" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-muted-foreground", children: "Working out what each column is\u2026" }) : null,
5044
- plan && parsed ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
5123
+ plan && parsed && afterWrite ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("p", { className: "text-muted-foreground", "data-import-after-write": "", children: [
5124
+ fileName ? `${fileName} has been sent to this table` : "This file has been sent to this table",
5125
+ 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."
5126
+ ] }) : null,
5127
+ plan && parsed && !afterWrite ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
5045
5128
  titleKey ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex flex-wrap items-center gap-2 rounded border bg-muted/40 px-2 py-1.5", children: [
5046
5129
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "font-medium", children: "What each record is called" }),
5047
5130
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
@@ -5075,30 +5158,19 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
5075
5158
  ),
5076
5159
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("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.` })
5077
5160
  ] }) : null,
5078
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "w-full overflow-x-auto rounded border", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "min-w-[52rem]", children: [
5079
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
5161
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "flex w-full min-w-0 flex-col rounded border", "data-import-mapping": "", children: plan.columns.map((column) => {
5162
+ const to = mapping[column.header] ?? "";
5163
+ const absent = unmapped === "ignore" ? "\u2014 leave it out \u2014" : "\u2014 offer it as a new column \u2014";
5164
+ const matchedField = column.field_id ? declared.find((f) => f.id === column.field_id) : void 0;
5165
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
5080
5166
  "div",
5081
5167
  {
5082
- className: "grid items-center gap-x-3 border-b bg-muted px-2 py-1 font-medium",
5083
- style: { gridTemplateColumns: MAPPING_COLUMNS },
5168
+ "data-import-column": column.header,
5169
+ className: "flex min-w-0 flex-col gap-1 border-t px-2 py-1.5 first:border-t-0",
5084
5170
  children: [
5085
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { children: "Column in the file" }),
5086
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { children: "Goes to" }),
5087
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { children: "Kind" }),
5088
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { children: "First values" })
5089
- ]
5090
- }
5091
- ),
5092
- plan.columns.map((column) => {
5093
- const to = mapping[column.header] ?? "";
5094
- const absent = unmapped === "ignore" ? "\u2014 leave it out \u2014" : "\u2014 offer it as a new column \u2014";
5095
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
5096
- "div",
5097
- {
5098
- className: "grid items-start gap-x-3 border-t px-2 py-1",
5099
- style: { gridTemplateColumns: MAPPING_COLUMNS },
5100
- children: [
5101
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "truncate pt-1.5 font-medium", title: column.header, children: column.header }),
5171
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1", children: [
5172
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "min-w-[8rem] flex-1 break-words font-medium", children: column.header }),
5173
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-muted-foreground", children: "goes to" }),
5102
5174
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
5103
5175
  import_design_system13.Select,
5104
5176
  {
@@ -5119,7 +5191,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
5119
5191
  size: "sm",
5120
5192
  "aria-label": `Where ${column.header} goes`,
5121
5193
  "data-goes-to": column.header,
5122
- className: "w-full min-w-[14rem]",
5194
+ className: "min-w-[14rem] flex-1",
5123
5195
  children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system13.SelectValue, {})
5124
5196
  }
5125
5197
  ),
@@ -5129,42 +5201,53 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
5129
5201
  ] })
5130
5202
  ]
5131
5203
  }
5204
+ )
5205
+ ] }),
5206
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "min-w-0", children: [
5207
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5208
+ import_design_system13.Badge,
5209
+ {
5210
+ variant: column.matched ? "secondary" : "outline",
5211
+ className: "text-[11px] font-normal",
5212
+ "data-import-kind": column.header,
5213
+ children: matchedField ? fieldTypeLabel(matchedField) : typeWord(column.type)
5214
+ }
5132
5215
  ),
5133
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "pt-1", children: [
5134
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system13.Badge, { variant: column.matched ? "secondary" : "outline", className: "text-[11px] font-normal", children: typeWord(column.type) }),
5135
- column.unit ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "ml-1 opacity-60", children: column.unit }) : null,
5136
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "mt-0.5 text-muted-foreground", children: column.why }),
5137
- column.ambiguous ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-destructive", children: "Check a date you know before you run this." }) : null,
5138
- column.collides_with ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-destructive", children: "Two columns in this file would make the same column." }) : null,
5139
- ruleVerdictOf.get(column.header) ? (() => {
5140
- const v = ruleVerdictOf.get(column.header);
5141
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { "data-import-rule-refusal": column.header, className: "mt-1", children: [
5142
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5143
- RefusalLine,
5144
- {
5145
- error: refusal(
5146
- "refused_by_rule",
5147
- `${v.refused} of ${v.judged} row${v.judged === 1 ? "" : "s"} would be refused by ${fieldName(v.field)}'s rules. ${v.firstWhy}`,
5148
- "Send this column somewhere else, fix those rows in your file, or import the rest and they are listed afterwards."
5149
- )
5150
- }
5151
- ),
5152
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("p", { className: "text-muted-foreground", "data-import-rule-sample": "", children: [
5153
- "Line ",
5154
- v.firstLine,
5155
- " in your file: ",
5156
- v.firstRaw
5157
- ] })
5158
- ] });
5159
- })() : null
5160
- ] }),
5161
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "truncate pt-1.5 text-muted-foreground", children: (column.samples ?? []).slice(0, 3).map(String).join(" \xB7 ") })
5162
- ]
5163
- },
5164
- column.header
5165
- );
5166
- })
5167
- ] }) }),
5216
+ column.unit ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "ml-1 opacity-60", children: column.unit }) : null,
5217
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "mt-0.5 break-words text-muted-foreground", children: column.why }),
5218
+ column.ambiguous ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-destructive", children: "Check a date you know before you run this." }) : null,
5219
+ column.collides_with ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-destructive", children: "Two columns in this file would make the same column." }) : null,
5220
+ ruleVerdictOf.get(column.header) ? (() => {
5221
+ const v = ruleVerdictOf.get(column.header);
5222
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { "data-import-rule-refusal": column.header, className: "mt-1", children: [
5223
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5224
+ RefusalLine,
5225
+ {
5226
+ error: refusal(
5227
+ "refused_by_rule",
5228
+ `${v.refused} of ${v.judged} row${v.judged === 1 ? "" : "s"} would be refused by ${fieldName(v.field)}'s rules. ${v.firstWhy}`,
5229
+ "Send this column somewhere else, fix those rows in your file, or import the rest and they are listed afterwards."
5230
+ )
5231
+ }
5232
+ ),
5233
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("p", { className: "text-muted-foreground", "data-import-rule-sample": "", children: [
5234
+ "Line ",
5235
+ lineInYourFile(v.firstLine),
5236
+ " in your file: ",
5237
+ v.firstRaw
5238
+ ] })
5239
+ ] });
5240
+ })() : null
5241
+ ] }),
5242
+ (column.samples ?? []).length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("p", { className: "break-words text-muted-foreground", children: [
5243
+ "First values: ",
5244
+ (column.samples ?? []).slice(0, 3).map(String).join(" \xB7 ")
5245
+ ] }) : null
5246
+ ]
5247
+ },
5248
+ column.header
5249
+ );
5250
+ }) }),
5168
5251
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system13.Separator, {}),
5169
5252
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex flex-wrap items-center gap-3", children: [
5170
5253
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("label", { className: "flex items-center gap-1", children: [
@@ -5301,7 +5384,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
5301
5384
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("th", { className: "px-2 py-1 text-left font-medium", children: "The row in your file" })
5302
5385
  ] }) }),
5303
5386
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("tbody", { children: interesting.slice(0, 200).map((o) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("tr", { className: "border-t align-top", children: [
5304
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("td", { className: "px-2 py-1", children: o.row }),
5387
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("td", { className: "px-2 py-1", children: lineInYourFile(o.row) }),
5305
5388
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("td", { className: "px-2 py-1", children: o.outcome === "refused" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5306
5389
  RefusalLine,
5307
5390
  {
@@ -5325,8 +5408,10 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
5325
5408
  " that ",
5326
5409
  ledger?.refused === 1 ? "was" : "were",
5327
5410
  " refused",
5328
- " ",
5329
- "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."
5411
+ ledger?.refused === 1 ? " is" : " are",
5412
+ " kept with this import \u2014 open ",
5413
+ ledger?.refused === 1 ? "it" : "them",
5414
+ " again from the list of imports below, with the store's reason and the line from your file on each."
5330
5415
  ] }) : null
5331
5416
  ] }) : null
5332
5417
  ] }) : null,
@@ -5374,7 +5459,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
5374
5459
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("tbody", { children: runRows.rows.map((row, i) => {
5375
5460
  const source = row.source ?? {};
5376
5461
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("tr", { className: "border-t align-top", children: [
5377
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("td", { className: "px-2 py-1", children: String(row.row ?? "") }),
5462
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("td", { className: "px-2 py-1", children: typeof row.row === "number" ? lineInYourFile(row.row) : String(row.row ?? "") }),
5378
5463
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("td", { className: "px-2 py-1", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5379
5464
  RefusalLine,
5380
5465
  {
@@ -5620,6 +5705,7 @@ function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }
5620
5705
  const [sensitivity, setSensitivity] = (0, import_react30.useState)(String(field?.sensitivity ?? "internal"));
5621
5706
  const [contextPolicy, setContextPolicy] = (0, import_react30.useState)(String(field?.context_policy ?? "include"));
5622
5707
  const [rules, setRules] = (0, import_react30.useState)(field?.rules ?? []);
5708
+ const [allowOther, setAllowOther] = (0, import_react30.useState)(field?.config?.["allow_other"] === true);
5623
5709
  const [optionText, setOptionText] = (0, import_react30.useState)("");
5624
5710
  const [unit, setUnit] = (0, import_react30.useState)(field?.unit ?? "$");
5625
5711
  const [withTime, setWithTime] = (0, import_react30.useState)(String(field?.config?.["kind"] ?? "date") === "datetime");
@@ -5676,7 +5762,10 @@ function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }
5676
5762
  // REMOVES a spec and the column goes back to the target table's own
5677
5763
  // reference column — a panel that only sent the key when it was set
5678
5764
  // could turn the override on and never off.
5679
- ...field.type === "relation" ? { display } : {}
5765
+ ...field.type === "relation" ? { display } : {},
5766
+ // STORE-RULE-GAPS: a choice list's own setting, sent for every list so
5767
+ // turning it off is as possible as turning it on.
5768
+ ...field.type === "list" ? { allow_other: allowOther } : {}
5680
5769
  };
5681
5770
  const written2 = await mutation.update({ field_id: field.id, patch });
5682
5771
  if (written2 !== null) onSaved?.();
@@ -5703,6 +5792,7 @@ function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }
5703
5792
  context_policy: contextPolicy,
5704
5793
  rules,
5705
5794
  ...type === "select" || type === "multi_select" ? { options } : {},
5795
+ ...(type === "select" || type === "multi_select") && allowOther ? { allow_other: true } : {},
5706
5796
  ...type === "currency" ? { unit } : {},
5707
5797
  ...type === "datetime" ? { kind: withTime ? "datetime" : "date" } : {},
5708
5798
  ...type === "lookup" ? { via, pick } : {},
@@ -5834,7 +5924,18 @@ function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }
5834
5924
  }
5835
5925
  ),
5836
5926
  "Each value has its own date"
5837
- ] })
5927
+ ] }),
5928
+ type === "select" || type === "multi_select" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("label", { className: "flex items-center gap-1.5", children: [
5929
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5930
+ import_design_system15.Checkbox,
5931
+ {
5932
+ checked: allowOther,
5933
+ "aria-label": "A value that is not one of the choices is added to them",
5934
+ onCheckedChange: (v) => setAllowOther(v === true)
5935
+ }
5936
+ ),
5937
+ "A value that is not one of the choices is added to them"
5938
+ ] }) : null
5838
5939
  ] }),
5839
5940
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_design_system15.Separator, {}),
5840
5941
  /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "grid gap-3 sm:grid-cols-2", children: [
@@ -5882,15 +5983,37 @@ function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }
5882
5983
  ]
5883
5984
  }
5884
5985
  ),
5986
+ rule.kind === "length" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5987
+ import_design_system15.BasicInput,
5988
+ {
5989
+ className: "h-8 w-24",
5990
+ inputMode: "numeric",
5991
+ placeholder: "fewest",
5992
+ value: rule.min === void 0 ? "" : String(rule.min),
5993
+ "aria-label": `Check ${index + 1} fewest characters`,
5994
+ onChange: (e) => setRules(rules.map((r, i) => i === index ? withNumber(r, "min", e.target.value) : r))
5995
+ }
5996
+ ) : null,
5885
5997
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5886
5998
  import_design_system15.BasicInput,
5887
5999
  {
5888
6000
  className: "h-8",
6001
+ ...rule.kind === "length" ? { inputMode: "numeric", placeholder: "most" } : {},
5889
6002
  value: String(rule.value ?? ""),
5890
- "aria-label": `Check ${index + 1} value`,
6003
+ "aria-label": rule.kind === "length" ? `Check ${index + 1} most characters` : `Check ${index + 1} value`,
5891
6004
  onChange: (e) => setRules(rules.map((r, i) => i === index ? { ...r, value: e.target.value } : r))
5892
6005
  }
5893
6006
  ),
6007
+ rule.kind === "pattern" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6008
+ import_design_system15.BasicInput,
6009
+ {
6010
+ className: "h-8",
6011
+ placeholder: "for example 949-555-0142",
6012
+ value: rule.example ?? "",
6013
+ "aria-label": `Check ${index + 1} example`,
6014
+ onChange: (e) => setRules(rules.map((r, i) => i === index ? withText(r, "example", e.target.value) : r))
6015
+ }
6016
+ ) : null,
5894
6017
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5895
6018
  import_design_system15.Button,
5896
6019
  {
@@ -6196,6 +6319,17 @@ function ValueOnTheOtherSide({
6196
6319
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "text-[11px] text-muted-foreground", children: help })
6197
6320
  ] });
6198
6321
  }
6322
+ function withNumber(rule, key, typed) {
6323
+ const { [key]: _gone, ...rest } = rule;
6324
+ const trimmed = typed.trim();
6325
+ if (trimmed === "") return rest;
6326
+ const n = Number(trimmed);
6327
+ return Number.isFinite(n) ? { ...rest, [key]: n } : { ...rest, [key]: trimmed };
6328
+ }
6329
+ function withText(rule, key, typed) {
6330
+ const { [key]: _gone, ...rest } = rule;
6331
+ return typed.trim() === "" ? rest : { ...rest, [key]: typed };
6332
+ }
6199
6333
 
6200
6334
  // src/StageRules.tsx
6201
6335
  var import_react33 = require("react");
@@ -6952,9 +7086,9 @@ function TableSettings({
6952
7086
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(StageRulesSection, { tableId }),
6953
7087
  /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-2", children: [
6954
7088
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "font-medium", children: "Proposed" }),
6955
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-muted-foreground", children: proposals?.length ?? 0 })
7089
+ proposals === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-muted-foreground", children: proposals.length })
6956
7090
  ] }),
6957
- proposals === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("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__ */ (0, import_jsx_runtime21.jsx)("p", { className: "text-muted-foreground", children: "No field is waiting for a decision." }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("ul", { className: "divide-y rounded border", children: proposals.map((proposal) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("li", { className: "flex flex-wrap items-center gap-x-2 gap-y-1 px-2 py-1.5", children: [
7091
+ proposals === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("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__ */ (0, import_jsx_runtime21.jsx)("p", { className: "text-muted-foreground", children: "No field is waiting for a decision." }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("ul", { className: "divide-y rounded border", children: proposals.map((proposal) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("li", { className: "flex flex-wrap items-center gap-x-2 gap-y-1 px-2 py-1.5", children: [
6958
7092
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "min-w-[8rem] flex-1 break-words", children: proposal.field.label || proposal.field.key }),
6959
7093
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "min-w-[8rem] flex-1 break-words text-muted-foreground", children: proposal.why }),
6960
7094
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_design_system18.Badge, { variant: "secondary", className: "text-[10px] font-normal", children: proposal.proposed_by }),
@@ -7718,10 +7852,10 @@ function Peek({ tableId, recordId, onClose, className }) {
7718
7852
  }
7719
7853
  function computedInPlainWords(row, field) {
7720
7854
  const name = field ? fieldName(field) : humanize(row.field_key);
7721
- const shown = row.value === null || row.value === void 0 || row.value === "" ? "nothing" : String(row.value);
7855
+ const shown2 = row.value === null || row.value === void 0 || row.value === "" ? "nothing" : String(row.value);
7722
7856
  const when = new Date(row.computed_at);
7723
7857
  const at = Number.isNaN(when.getTime()) ? "" : ` on ${when.toLocaleDateString()}`;
7724
- return `${name} was worked out as ${shown}${at}.`;
7858
+ return `${name} was worked out as ${shown2}${at}.`;
7725
7859
  }
7726
7860
 
7727
7861
  // src/views.ts
@@ -8672,7 +8806,7 @@ function Gallery({
8672
8806
  onOpenRecord,
8673
8807
  note
8674
8808
  }) {
8675
- const shown = fields.slice(0, 4);
8809
+ const shown2 = fields.slice(0, 4);
8676
8810
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex min-h-0 flex-col gap-2", children: [
8677
8811
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Note, { children: note }),
8678
8812
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("ul", { className: "grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-2 overflow-y-auto", children: rows.map((row) => {
@@ -8685,7 +8819,7 @@ function Gallery({
8685
8819
  onClick: () => onOpenRecord?.(row.id),
8686
8820
  children: [
8687
8821
  typeof image === "string" && image !== "" ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("img", { src: image, alt: "", className: "h-28 w-full rounded object-cover" }) : null,
8688
- shown.map((field) => /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { className: "flex min-w-0 items-baseline gap-1 text-xs", children: [
8822
+ shown2.map((field) => /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { className: "flex min-w-0 items-baseline gap-1 text-xs", children: [
8689
8823
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "shrink-0 text-muted-foreground", children: fieldName(field) }),
8690
8824
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "min-w-0 truncate", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(RecordValue, { field, value: (row.document ?? {})[field.key], document: row.document }) })
8691
8825
  ] }, field.key))
@@ -10015,18 +10149,18 @@ function ActionInbox({ tableId, includeSettled = false, onOpenRecord, className
10015
10149
  (0, import_react63.useEffect)(() => {
10016
10150
  void load();
10017
10151
  }, [load]);
10018
- const shown = (0, import_react63.useMemo)(() => {
10152
+ const shown2 = (0, import_react63.useMemo)(() => {
10019
10153
  const all = items ?? [];
10020
10154
  if (!tableId) return all;
10021
10155
  return all.filter((i) => i.kind !== "assignment" || i.subject_kind !== "record" || true);
10022
10156
  }, [items, tableId]);
10023
10157
  (0, import_react63.useEffect)(() => {
10024
- if (cursor >= shown.length) setCursor(Math.max(0, shown.length - 1));
10025
- }, [shown.length, cursor]);
10158
+ if (cursor >= shown2.length) setCursor(Math.max(0, shown2.length - 1));
10159
+ }, [shown2.length, cursor]);
10026
10160
  (0, import_react63.useEffect)(() => {
10027
10161
  const el = listRef.current?.querySelector(`[data-row="${cursor}"]`);
10028
10162
  el?.scrollIntoView({ block: "nearest" });
10029
- }, [cursor, shown.length]);
10163
+ }, [cursor, shown2.length]);
10030
10164
  const decide = (0, import_react63.useCallback)(
10031
10165
  async (item, approve) => {
10032
10166
  if (item.kind === "assignment") return;
@@ -10051,11 +10185,11 @@ function ActionInbox({ tableId, includeSettled = false, onOpenRecord, className
10051
10185
  );
10052
10186
  const onKeyDown = (0, import_react63.useCallback)(
10053
10187
  (event) => {
10054
- const item = shown[cursor];
10188
+ const item = shown2[cursor];
10055
10189
  const key = event.key;
10056
10190
  if (key === "j" || key === "ArrowDown") {
10057
10191
  event.preventDefault();
10058
- setCursor((c) => Math.min(shown.length - 1, c + 1));
10192
+ setCursor((c) => Math.min(shown2.length - 1, c + 1));
10059
10193
  } else if (key === "k" || key === "ArrowUp") {
10060
10194
  event.preventDefault();
10061
10195
  setCursor((c) => Math.max(0, c - 1));
@@ -10070,7 +10204,7 @@ function ActionInbox({ tableId, includeSettled = false, onOpenRecord, className
10070
10204
  void decide(item, key === "a");
10071
10205
  }
10072
10206
  },
10073
- [shown, cursor, decide, open, load]
10207
+ [shown2, cursor, decide, open, load]
10074
10208
  );
10075
10209
  if (items === null) return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_design_system30.Skeleton, { className: (0, import_design_system30.cn)("h-32 w-full", className) });
10076
10210
  return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
@@ -10083,12 +10217,12 @@ function ActionInbox({ tableId, includeSettled = false, onOpenRecord, className
10083
10217
  children: [
10084
10218
  /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "flex items-center gap-2", children: [
10085
10219
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("h3", { className: "text-sm font-medium", children: "Inbox" }),
10086
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "text-xs text-muted-foreground", children: shown.length }),
10220
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "text-xs text-muted-foreground", children: shown2.length }),
10087
10221
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("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" }),
10088
10222
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_design_system30.Button, { size: "sm", variant: "ghost", onClick: () => void load(), children: "Refresh" })
10089
10223
  ] }),
10090
10224
  error ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(RefusalNotice, { error }) : null,
10091
- shown.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { className: "text-xs text-muted-foreground", children: includeSettled ? "Nothing has come through this inbox yet." : "Nothing is waiting on you." }) : /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("ol", { ref: listRef, className: "flex min-h-0 flex-col gap-1 overflow-y-auto", children: shown.map((item, index) => {
10225
+ shown2.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { className: "text-xs text-muted-foreground", children: includeSettled ? "Nothing has come through this inbox yet." : "Nothing is waiting on you." }) : /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("ol", { ref: listRef, className: "flex min-h-0 flex-col gap-1 overflow-y-auto", children: shown2.map((item, index) => {
10092
10226
  const settled = outcome[item.item_id];
10093
10227
  return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
10094
10228
  "li",
@@ -10164,6 +10298,7 @@ function HistoryPanel({ tableId, recordId, onAskAboutField, className }) {
10164
10298
  const [pending, setPending] = (0, import_react65.useState)({ phase: "idle" });
10165
10299
  const [said, setSaid] = (0, import_react65.useState)(null);
10166
10300
  const listRef = (0, import_react65.useRef)(null);
10301
+ const changed = (0, import_react66.useRecordChangeRevision)(recordId);
10167
10302
  const load = (0, import_react65.useCallback)(async () => {
10168
10303
  const answered = await client.recordHistory({ record_id: recordId });
10169
10304
  if (!answered.ok) {
@@ -10172,7 +10307,7 @@ function HistoryPanel({ tableId, recordId, onAskAboutField, className }) {
10172
10307
  }
10173
10308
  setError(null);
10174
10309
  setEntries(answered.data);
10175
- }, [client, recordId]);
10310
+ }, [client, recordId, changed]);
10176
10311
  (0, import_react65.useEffect)(() => {
10177
10312
  void load();
10178
10313
  }, [load]);
@@ -10514,9 +10649,9 @@ function CommentThread({ tableId, recordId, fieldKey, className }) {
10514
10649
  }, [mentionQuery, people, picked]);
10515
10650
  if (error) return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(RefusalNotice, { error, className });
10516
10651
  if (!thread || table.loading || fields.loading) return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_design_system32.Skeleton, { className: (0, import_design_system32.cn)("h-32 w-full", className) });
10517
- const shown = fieldKey ? thread.comments.filter((c) => c.field_key === fieldKey) : thread.comments;
10518
- const roots = shown.filter((c) => !c.parent_comment_id);
10519
- const repliesOf = (id) => shown.filter((c) => c.parent_comment_id === id);
10652
+ const shown2 = fieldKey ? thread.comments.filter((c) => c.field_key === fieldKey) : thread.comments;
10653
+ const roots = shown2.filter((c) => !c.parent_comment_id);
10654
+ const repliesOf = (id) => shown2.filter((c) => c.parent_comment_id === id);
10520
10655
  const fieldLabel = fieldKey ? (fields.data ?? []).find((f) => f.key === fieldKey)?.label || humanize(fieldKey) : null;
10521
10656
  async function post() {
10522
10657
  if (draft.trim() === "") return;
@@ -10559,7 +10694,7 @@ function CommentThread({ tableId, recordId, fieldKey, className }) {
10559
10694
  return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("section", { className: (0, import_design_system32.cn)("flex min-h-0 flex-col gap-2", className), children: [
10560
10695
  /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "flex items-baseline gap-2", children: [
10561
10696
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("h3", { className: "text-sm font-medium", children: fieldLabel ? `Comments on ${fieldLabel}` : "Comments" }),
10562
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "text-xs text-muted-foreground", children: shown.length }),
10697
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "text-xs text-muted-foreground", children: shown2.length }),
10563
10698
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
10564
10699
  import_design_system32.Button,
10565
10700
  {
@@ -11311,16 +11446,16 @@ function PortalsPanel({ tableId, activePortalId, className }) {
11311
11446
  const holdingThis = new Set(
11312
11447
  exposures.filter((e) => e.table_id === tableId).map((e) => e.portal_id)
11313
11448
  );
11314
- const shown = onATable ? portals.filter((p) => holdingThis.has(p.portal_id)) : portals;
11449
+ const shown2 = onATable ? portals.filter((p) => holdingThis.has(p.portal_id)) : portals;
11315
11450
  const couldTakeIt = onATable ? portals.filter((p) => !holdingThis.has(p.portal_id)) : [];
11316
11451
  const elsewhere = couldTakeIt.length;
11317
11452
  const origin = host.publicOrigin ?? (typeof window === "undefined" ? "" : window.location.origin);
11318
11453
  return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("section", { className: (0, import_design_system36.cn)("flex flex-col gap-3", className), children: [
11319
11454
  /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("header", { className: "flex items-center gap-2", children: [
11320
11455
  /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("h3", { className: "text-sm font-medium", children: "Portals" }),
11321
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "text-xs text-muted-foreground", children: shown.length === 0 ? "none for this table" : `${shown.length}` }),
11456
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "text-xs text-muted-foreground", children: shown2.length === 0 ? "none for this table" : `${shown2.length}` }),
11322
11457
  /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "flex-1" }),
11323
- shown.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_design_system36.Button, { size: "sm", variant: building ? "secondary" : "ghost", onClick: () => setBuilding((b) => !b), children: building ? "Done building" : "Build a portal" }) : null
11458
+ shown2.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_design_system36.Button, { size: "sm", variant: building ? "secondary" : "ghost", onClick: () => setBuilding((b) => !b), children: building ? "Done building" : "Build a portal" }) : null
11324
11459
  ] }),
11325
11460
  listError ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(RefusalNotice, { error: listError }) : null,
11326
11461
  building ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
@@ -11345,7 +11480,7 @@ function PortalsPanel({ tableId, activePortalId, className }) {
11345
11480
  onClose: () => setAdding(null)
11346
11481
  }
11347
11482
  ) : null,
11348
- shown.length === 0 && !listError && !building && !adding ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
11483
+ shown2.length === 0 && !listError && !building && !adding ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
11349
11484
  BuildOrAsk,
11350
11485
  {
11351
11486
  mayBuild: true,
@@ -11367,7 +11502,7 @@ function PortalsPanel({ tableId, activePortalId, className }) {
11367
11502
  ]
11368
11503
  }
11369
11504
  ) : null,
11370
- shown.length === 0 && !listError && !building && !adding && elsewhere > 0 ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex flex-col gap-1.5 rounded-md border border-dashed border-border p-2.5", children: [
11505
+ shown2.length === 0 && !listError && !building && !adding && elsewhere > 0 ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex flex-col gap-1.5 rounded-md border border-dashed border-border p-2.5", children: [
11371
11506
  /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("p", { className: "text-xs text-muted-foreground", children: "Or add this table to a portal your clients already sign in to:" }),
11372
11507
  /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "flex flex-wrap gap-1.5", children: couldTakeIt.map((portal) => /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
11373
11508
  import_design_system36.Button,
@@ -11384,8 +11519,8 @@ function PortalsPanel({ tableId, activePortalId, className }) {
11384
11519
  portal.portal_id
11385
11520
  )) })
11386
11521
  ] }) : null,
11387
- activePortalId && !shown.some((p) => p.portal_id === activePortalId) ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("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,
11388
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("ul", { className: "flex flex-col gap-2", children: shown.map((portal) => {
11522
+ activePortalId && !shown2.some((p) => p.portal_id === activePortalId) ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("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,
11523
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("ul", { className: "flex flex-col gap-2", children: shown2.map((portal) => {
11389
11524
  const url = `${origin}${(0, import_records9.portalPath)(portal.slug)}`;
11390
11525
  const open = openId === portal.portal_id;
11391
11526
  const linked = portal.portal_id === activePortalId;
@@ -11532,7 +11667,7 @@ function ArchivePortalControl({
11532
11667
  }
11533
11668
  function CopyLink({ url, what }) {
11534
11669
  const [copied, setCopied] = (0, import_react73.useState)(false);
11535
- const [shown, setShown] = (0, import_react73.useState)(false);
11670
+ const [shown2, setShown] = (0, import_react73.useState)(false);
11536
11671
  return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_jsx_runtime39.Fragment, { children: [
11537
11672
  /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
11538
11673
  import_design_system36.Button,
@@ -11554,7 +11689,7 @@ function CopyLink({ url, what }) {
11554
11689
  children: copied ? "Copied" : what ? `Copy ${what}` : "Copy link"
11555
11690
  }
11556
11691
  ),
11557
- shown ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("p", { className: "mt-1.5 w-full break-all rounded border border-dashed border-border px-2 py-1 text-xs", children: [
11692
+ shown2 ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("p", { className: "mt-1.5 w-full break-all rounded border border-dashed border-border px-2 py-1 text-xs", children: [
11558
11693
  "This browser would not let the page copy for you, so here it is to copy by hand: ",
11559
11694
  url
11560
11695
  ] }) : null
@@ -14142,20 +14277,23 @@ function ChartBlock({ block, subject, className }) {
14142
14277
  label: point.label === "All records" ? block.title : `${block.title} \xB7 ${point.label}`
14143
14278
  });
14144
14279
  } : null;
14145
- return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("figure", { className: (0, import_design_system46.cn)("flex min-w-0 flex-col gap-1.5 rounded border p-2", className), children: [
14146
- /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("figcaption", { className: "flex items-baseline gap-2 text-xs", children: [
14147
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: "truncate font-medium", children: block.title || CHART_KIND_LABEL[kind] }),
14148
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: "ml-auto shrink-0 text-muted-foreground", children: kind === "stuck" ? `${block.days ?? 14} days` : primary.replace("_", " of ") }),
14149
- typeof block.ms === "number" ? /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("span", { className: "shrink-0 tabular-nums text-muted-foreground/70", title: "how long the store took", children: [
14150
- pretty(block.ms),
14151
- " ms"
14152
- ] }) : null
14153
- ] }),
14154
- block.refused ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(RefusalNotice, { error: asRefusal2(block) }) : needs ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("p", { className: "px-1 py-3 text-xs text-muted-foreground", children: needs }) : kind === "stuck" ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(StuckList, { rows: block.rows ?? [] }) : points.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("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__ */ (0, import_jsx_runtime49.jsx)(import_jsx_runtime49.Fragment, { children: kind === "number" ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(BigNumber, { points, measure: primary, onDrill: drill }) : kind === "table" ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(GroupTable, { points, series, onDrill: drill }) : /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(import_jsx_runtime49.Fragment, { children: [
14155
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(Drawing, { kind, points, series, config, onDrill: drill }),
14156
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(Values, { points, measure: primary, config, onDrill: drill })
14157
- ] }) })
14158
- ] });
14280
+ return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(
14281
+ "figure",
14282
+ {
14283
+ className: (0, import_design_system46.cn)("flex min-w-0 flex-col gap-1.5 rounded border p-2", className),
14284
+ ...typeof block.ms === "number" ? { "data-store-ms": pretty(block.ms) } : {},
14285
+ children: [
14286
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("figcaption", { className: "flex items-baseline gap-2 text-xs", children: [
14287
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: "truncate font-medium", children: block.title || CHART_KIND_LABEL[kind] }),
14288
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: "ml-auto shrink-0 text-muted-foreground", children: kind === "stuck" ? `${block.days ?? 14} days` : primary.replace("_", " of ") })
14289
+ ] }),
14290
+ block.refused ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(RefusalNotice, { error: asRefusal2(block) }) : needs ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("p", { className: "px-1 py-3 text-xs text-muted-foreground", children: needs }) : kind === "stuck" ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(StuckList, { rows: block.rows ?? [] }) : points.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("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__ */ (0, import_jsx_runtime49.jsx)(import_jsx_runtime49.Fragment, { children: kind === "number" ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(BigNumber, { points, measure: primary, onDrill: drill }) : kind === "table" ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(GroupTable, { points, series, onDrill: drill }) : /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(import_jsx_runtime49.Fragment, { children: [
14291
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(Drawing, { kind, points, series, config, onDrill: drill }),
14292
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(Values, { points, measure: primary, config, onDrill: drill })
14293
+ ] }) })
14294
+ ]
14295
+ }
14296
+ );
14159
14297
  }
14160
14298
  function BigNumber({
14161
14299
  points,
@@ -14767,7 +14905,7 @@ function FormsPanel({ tableId, activeFormId, className }) {
14767
14905
  },
14768
14906
  [client, load]
14769
14907
  );
14770
- const [shown, setShown] = (0, import_react98.useState)(null);
14908
+ const [shown2, setShown] = (0, import_react98.useState)(null);
14771
14909
  const copy = (0, import_react98.useCallback)(async (url, formId) => {
14772
14910
  try {
14773
14911
  await navigator.clipboard.writeText(url);
@@ -14866,7 +15004,7 @@ function FormsPanel({ tableId, activeFormId, className }) {
14866
15004
  ) : null
14867
15005
  ] }),
14868
15006
  rights.structure && publishGate.why ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("p", { className: "mt-1.5 text-xs text-muted-foreground", children: publishGate.why }) : null,
14869
- shown && shown === url ? /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("p", { className: "mt-1.5 break-all rounded border border-dashed px-2 py-1 text-xs", children: [
15007
+ shown2 && shown2 === url ? /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("p", { className: "mt-1.5 break-all rounded border border-dashed px-2 py-1 text-xs", children: [
14870
15008
  "This browser would not let the page copy for you, so here it is to copy by hand:",
14871
15009
  " ",
14872
15010
  url
@@ -15028,7 +15166,7 @@ function BookingBuilder({ tableId, bookingId, onSaved, onClose, className }) {
15028
15166
  return /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("p", { className: (0, import_design_system49.cn)("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." });
15029
15167
  }
15030
15168
  const url = formId ? `${publicOrigin}${(0, import_records13.bookingPath)(formId)}` : null;
15031
- const shown = offer ?? null;
15169
+ const shown2 = offer ?? null;
15032
15170
  return (
15033
15171
  // THE RAIL IS 384px WIDE AND THE VIEWPORT IS NOT (walk 1, 2026-09-21):
15034
15172
  // every split below is a CONTAINER query, so this screen is the same
@@ -15176,21 +15314,21 @@ function BookingBuilder({ tableId, bookingId, onSaved, onClose, className }) {
15176
15314
  }
15177
15315
  )
15178
15316
  ] }),
15179
- shown ? /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("p", { className: "rounded-md border bg-muted/40 p-2.5 text-xs", children: [
15317
+ shown2 ? /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("p", { className: "rounded-md border bg-muted/40 p-2.5 text-xs", children: [
15180
15318
  "Saved. The page offers ",
15181
- shown.slot_minutes,
15319
+ shown2.slot_minutes,
15182
15320
  "-minute appointments in ",
15183
- shown.timezone,
15321
+ shown2.timezone,
15184
15322
  ", up to",
15185
15323
  " ",
15186
- shown.max_per_day,
15324
+ shown2.max_per_day,
15187
15325
  " a day, ",
15188
- shown.days,
15326
+ shown2.days,
15189
15327
  " days ahead",
15190
- shown.buffer_minutes > 0 ? `, with ${shown.buffer_minutes} minutes between them` : "",
15328
+ shown2.buffer_minutes > 0 ? `, with ${shown2.buffer_minutes} minutes between them` : "",
15191
15329
  ":",
15192
15330
  " ",
15193
- shown.windows.map(
15331
+ shown2.windows.map(
15194
15332
  (w) => `${DAYS.find((d) => d.weekday === w.weekday)?.label ?? w.weekday} ${w.from}\u2013${w.to}`
15195
15333
  ).join(", "),
15196
15334
  "."
@@ -15232,21 +15370,22 @@ var import_react103 = require("@ai-matrx/records/react");
15232
15370
  var import_records14 = require("@ai-matrx/records");
15233
15371
  var import_design_system50 = require("@ai-matrx/design-system");
15234
15372
  var import_jsx_runtime53 = require("react/jsx-runtime");
15373
+ 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.";
15235
15374
  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.";
15236
15375
  function bookingSuggestion(tableName2) {
15237
- const subject = tableName2?.trim() ? tableName2.trim() : "appointments";
15238
- return `Let clients book a 30-minute ${subject} slot on Tuesday and Thursday afternoons.`;
15376
+ const name = tableName2?.trim();
15377
+ 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.";
15239
15378
  }
15240
15379
  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.";
15241
15380
  var STATE_WORDS = BOOKING_PAGE_STATE_LABEL;
15242
- function BookingSlots({ tableId, className }) {
15381
+ function BookingSlots({ tableId, activeBookingId, className }) {
15243
15382
  const client = (0, import_react103.useRecordsClient)();
15244
15383
  const host = useRecordsUi();
15245
15384
  const [pages, setPages] = (0, import_react102.useState)(null);
15246
15385
  const [error, setError] = (0, import_react102.useState)(null);
15247
15386
  const [busy, setBusy] = (0, import_react102.useState)(null);
15248
15387
  const [copied, setCopied] = (0, import_react102.useState)(null);
15249
- const [shown, setShown] = (0, import_react102.useState)(null);
15388
+ const [shown2, setShown] = (0, import_react102.useState)(null);
15250
15389
  const [building, setBuilding] = (0, import_react102.useState)(false);
15251
15390
  const load = (0, import_react102.useCallback)(async () => {
15252
15391
  const answered = await client.bookings(tableId ? { table_id: tableId } : {});
@@ -15312,6 +15451,7 @@ function BookingSlots({ tableId, className }) {
15312
15451
  tableId && pages.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(import_design_system50.Button, { size: "sm", variant: building ? "secondary" : "ghost", onClick: () => setBuilding((b) => !b), children: building ? "Done building" : "Build a booking page" }) : null
15313
15452
  ] }),
15314
15453
  error ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(RefusalNotice, { error }) : null,
15454
+ activeBookingId && !pages.some((p) => p.form_id === activeBookingId) ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("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,
15315
15455
  building && tableId ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
15316
15456
  BookingBuilder,
15317
15457
  {
@@ -15347,53 +15487,66 @@ function BookingSlots({ tableId, className }) {
15347
15487
  const url = `${origin}${(0, import_records14.bookingPath)(page.form_id)}`;
15348
15488
  const mayOpen = levels.data?.[page.table_id] === "admin";
15349
15489
  const open = page.published_at !== null && page.closed_at === null;
15350
- return /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("li", { className: "rounded-md border p-2.5", "data-testid": "booking-page", children: [
15351
- /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("div", { className: "flex items-center gap-2", children: [
15352
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: page.title ?? page.slug }),
15353
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(import_design_system50.Badge, { variant: page.state === "open" ? "default" : "secondary", children: page.state })
15354
- ] }),
15355
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("p", { className: "mt-0.5 text-xs text-muted-foreground", children: STATE_WORDS[page.state] ?? page.state }),
15356
- /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("p", { className: "mt-1.5 text-xs", children: [
15357
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("span", { className: "font-medium", children: page.upcoming }),
15358
- " coming up",
15359
- " \xB7 ",
15360
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("span", { className: "font-medium", children: page.booked }),
15361
- " booked in all",
15362
- page.held > 0 ? /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(import_jsx_runtime53.Fragment, { children: [
15363
- " \xB7 ",
15364
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("span", { className: "font-medium", children: page.held }),
15365
- " being held right now"
15366
- ] }) : null,
15367
- page.cancelled > 0 ? /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(import_jsx_runtime53.Fragment, { children: [
15368
- " \xB7 ",
15369
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("span", { className: "font-medium", children: page.cancelled }),
15370
- " cancelled"
15371
- ] }) : null,
15372
- /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("span", { className: "text-muted-foreground", children: [
15373
- " \xB7 ",
15374
- page.slot_minutes,
15375
- " minutes each"
15376
- ] })
15377
- ] }),
15378
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("p", { className: "mt-1 text-xs text-muted-foreground", children: nextInWords(page) }),
15379
- /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("div", { className: "mt-2 flex flex-wrap items-center gap-1.5", children: [
15380
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(import_design_system50.Button, { size: "sm", variant: "outline", onClick: () => void copy(url, page.form_id), children: copied === page.form_id ? "Copied" : "Copy link" }),
15381
- mayOpen ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
15382
- import_design_system50.Button,
15383
- {
15384
- size: "sm",
15385
- variant: "ghost",
15386
- disabled: busy === page.form_id,
15387
- onClick: () => void toggle(page),
15388
- children: busy === page.form_id ? "\u2026" : open ? "Close" : "Open"
15389
- }
15390
- ) : null
15391
- ] }),
15392
- shown && shown === url ? /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("p", { className: "mt-1.5 break-all rounded border border-dashed px-2 py-1 text-xs", children: [
15393
- "This browser would not let the page copy for you, so here it is to copy by hand: ",
15394
- url
15395
- ] }) : null
15396
- ] }, page.form_id);
15490
+ return /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(
15491
+ "li",
15492
+ {
15493
+ "data-testid": "booking-page",
15494
+ "data-linked": page.form_id === activeBookingId ? "true" : void 0,
15495
+ ref: page.form_id === activeBookingId ? (el) => el?.scrollIntoView({ block: "nearest" }) : void 0,
15496
+ className: (0, import_design_system50.cn)(
15497
+ "rounded-md border p-2.5",
15498
+ page.form_id === activeBookingId && "border-primary ring-1 ring-primary"
15499
+ ),
15500
+ children: [
15501
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("div", { className: "flex items-center gap-2", children: [
15502
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: page.title ?? page.slug }),
15503
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(import_design_system50.Badge, { variant: page.state === "open" ? "default" : "secondary", children: page.state })
15504
+ ] }),
15505
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("p", { className: "mt-0.5 text-xs text-muted-foreground", children: STATE_WORDS[page.state] ?? page.state }),
15506
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("p", { className: "mt-1.5 text-xs", children: [
15507
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("span", { className: "font-medium", children: page.upcoming }),
15508
+ " coming up",
15509
+ " \xB7 ",
15510
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("span", { className: "font-medium", children: page.booked }),
15511
+ " booked in all",
15512
+ page.held > 0 ? /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(import_jsx_runtime53.Fragment, { children: [
15513
+ " \xB7 ",
15514
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("span", { className: "font-medium", children: page.held }),
15515
+ " being held right now"
15516
+ ] }) : null,
15517
+ page.cancelled > 0 ? /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(import_jsx_runtime53.Fragment, { children: [
15518
+ " \xB7 ",
15519
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("span", { className: "font-medium", children: page.cancelled }),
15520
+ " cancelled"
15521
+ ] }) : null,
15522
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("span", { className: "text-muted-foreground", children: [
15523
+ " \xB7 ",
15524
+ page.slot_minutes,
15525
+ " minutes each"
15526
+ ] })
15527
+ ] }),
15528
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("p", { className: "mt-1 text-xs text-muted-foreground", children: nextInWords(page) }),
15529
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("div", { className: "mt-2 flex flex-wrap items-center gap-1.5", children: [
15530
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(import_design_system50.Button, { size: "sm", variant: "outline", onClick: () => void copy(url, page.form_id), children: copied === page.form_id ? "Copied" : "Copy link" }),
15531
+ mayOpen ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
15532
+ import_design_system50.Button,
15533
+ {
15534
+ size: "sm",
15535
+ variant: "ghost",
15536
+ disabled: busy === page.form_id,
15537
+ onClick: () => void toggle(page),
15538
+ children: busy === page.form_id ? "\u2026" : open ? "Close" : "Open"
15539
+ }
15540
+ ) : null
15541
+ ] }),
15542
+ shown2 && shown2 === url ? /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("p", { className: "mt-1.5 break-all rounded border border-dashed px-2 py-1 text-xs", children: [
15543
+ "This browser would not let the page copy for you, so here it is to copy by hand: ",
15544
+ url
15545
+ ] }) : null
15546
+ ]
15547
+ },
15548
+ page.form_id
15549
+ );
15397
15550
  }) }),
15398
15551
  pages.length > 0 && !levels.loading && levels.data && subjectIds.every((id) => levels.data?.[id] !== "admin") ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("p", { className: "text-xs text-muted-foreground", children: NO_ADMIN2 }) : null
15399
15552
  ] });
@@ -16532,8 +16685,15 @@ var LANE_TITLE = {
16532
16685
  };
16533
16686
  var APP_TABLE_PREFIX = "records_ui_";
16534
16687
  var LANE_EMPTY = {
16535
- mine: "Nothing of your own yet. New table makes one that only you can see.",
16536
- organization: "No tables shared across your organization yet. A table you make is yours until you change its visibility to internal.",
16688
+ // 🚨 BOTH SENTENCES USED TO DESCRIBE A PRODUCT THAT DOES NOT EXIST
16689
+ // (VERIFIER-15 M2, VERIFIER-16 M6). "New table makes one that only you can
16690
+ // see" — New table makes one shared with the organization, and it lands under
16691
+ // My organization. "A table you make is yours until you change its visibility
16692
+ // to internal" — it is internal from the start, and no screen here changes a
16693
+ // table's visibility at all. An empty lane says what is true and names only a
16694
+ // control that exists.
16695
+ mine: "Nothing here is visible to you alone. A new table is shared with your organization from the start.",
16696
+ organization: "No tables shared across your organization yet. New table makes one, and everyone here can open it.",
16537
16697
  system: "The platform's own tables are not in this organization's store.",
16538
16698
  community: "Nothing shared beyond your organization. A table published by link or to the world lands here.",
16539
16699
  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."
@@ -17178,7 +17338,7 @@ function TablePage({
17178
17338
  }
17179
17339
  ) : null,
17180
17340
  rail === "forms" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(FormsPanel, { tableId, activeFormId: itemFor("forms") }) : null,
17181
- rail === "bookings" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(BookingSlots, { tableId }) : null,
17341
+ rail === "bookings" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(BookingSlots, { tableId, activeBookingId: itemFor("bookings") }) : null,
17182
17342
  rail === "checklists" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
17183
17343
  ChecklistsPanel,
17184
17344
  {