@ai-matrx/records-ui 0.84.0 → 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,
@@ -561,13 +562,20 @@ function plainSentence(message) {
561
562
  }
562
563
  return { kept: kept.length === 0 ? null : kept.join(" "), dropped };
563
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
+ }
564
571
  function refusalForAPerson(error) {
565
572
  const code = error.code ?? "internal";
566
573
  const message = plainSentence(String(error.message ?? ""));
567
574
  const hint = plainSentence(String(error.hint ?? ""));
568
575
  const title = TITLE[code] ?? "The store refused this";
569
576
  const hintIsForEngineers = isEngineerText(String(error.hint ?? ""));
570
- 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"];
571
579
  const messageIsForEngineers = isEngineerText(String(error.message ?? ""));
572
580
  const said = (messageIsForEngineers ? "" : message.kept ?? "").trim();
573
581
  const forEngineers = [
@@ -591,7 +599,9 @@ function refusalForAPerson(error) {
591
599
  }
592
600
  function refusalLineForAPerson(error) {
593
601
  const plain = refusalForAPerson(error);
594
- 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();
595
605
  }
596
606
 
597
607
  // src/names.ts
@@ -1061,6 +1071,42 @@ function asRefusal(thrown, sentence2, remedy, code = "internal") {
1061
1071
  return refusalFromThrown(thrown, sentence2, remedy, code);
1062
1072
  }
1063
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
+
1064
1110
  // src/RelationPicker.tsx
1065
1111
  var import_jsx_runtime4 = (
1066
1112
  // The Field's own name is already printed above this control by
@@ -1202,9 +1248,12 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1202
1248
  setSearch("");
1203
1249
  toggle(createdId);
1204
1250
  };
1205
- const visible = (rows ?? []).filter(
1206
- (row) => search.trim() === "" ? true : titleOf(row.id).toLowerCase().includes(search.trim().toLowerCase())
1207
- );
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
+ });
1208
1257
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: (0, import_design_system3.cn)("flex flex-wrap items-center gap-1", className), children: [
1209
1258
  picked.map((recordId) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1210
1259
  RecordChip,
@@ -1235,17 +1284,21 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1235
1284
  " at most. Remove one to add another."
1236
1285
  ] }) : null,
1237
1286
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system3.ScrollArea, { className: "max-h-56", children: [
1238
- /* @__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)(
1239
1288
  "button",
1240
1289
  {
1241
1290
  type: "button",
1242
1291
  onClick: () => toggle(row.id),
1243
- 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,
1244
1294
  className: (0, import_design_system3.cn)(
1245
1295
  "w-full truncate rounded px-2 py-1 text-left text-xs hover:bg-muted",
1246
1296
  picked.includes(row.id) ? "bg-accent text-accent-foreground" : ""
1247
1297
  ),
1248
- 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
+ ]
1249
1302
  }
1250
1303
  ) }, row.id)) }),
1251
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
@@ -2177,7 +2230,7 @@ var FIELD_RULE_KIND_VALUES = ["min", "max", "length", "pattern", "equals_field",
2177
2230
  var FIELD_RULE_KIND_LABEL = {
2178
2231
  min: "At least",
2179
2232
  max: "At most",
2180
- length: "No longer than",
2233
+ length: "Number of characters",
2181
2234
  pattern: "Looks like",
2182
2235
  equals_field: "Same as the column",
2183
2236
  differs_from_field: "Different from the column"
@@ -2209,6 +2262,20 @@ function envelopeFor(document2, key) {
2209
2262
  const block = document2?._values;
2210
2263
  return block?.[key];
2211
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
+ }
2212
2279
  var ABSENCE_WORDS = ABSENCE_WORD_LABEL;
2213
2280
  function renderValue(field, value, document2, labels, stillWaitingFor) {
2214
2281
  const envelope = envelopeFor(document2, field.key);
@@ -2216,6 +2283,13 @@ function renderValue(field, value, document2, labels, stillWaitingFor) {
2216
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) });
2217
2284
  }
2218
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
+ }
2219
2293
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-muted-foreground", children: "\u2014" });
2220
2294
  }
2221
2295
  const kind = editorKindFor(field);
@@ -3242,7 +3316,7 @@ function EnrichPanel({ tableId, fieldId, className }) {
3242
3316
  (0, import_react17.useEffect)(() => {
3243
3317
  void load();
3244
3318
  }, [load]);
3245
- const shown = (0, import_react17.useMemo)(
3319
+ const shown2 = (0, import_react17.useMemo)(
3246
3320
  () => fieldId ? (rows ?? []).filter((r) => r.field_id === fieldId) : rows ?? [],
3247
3321
  [rows, fieldId]
3248
3322
  );
@@ -3301,7 +3375,7 @@ function EnrichPanel({ tableId, fieldId, className }) {
3301
3375
  );
3302
3376
  if (error) return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(RefusalNotice, { error, className });
3303
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) });
3304
- if (shown.length === 0 && fieldId) {
3378
+ if (shown2.length === 0 && fieldId) {
3305
3379
  const target = (fields.data ?? []).find((f) => f.id === fieldId);
3306
3380
  const setup = draft[fieldId] ?? { instruction: "", every: "30" };
3307
3381
  if (!rights.admin) {
@@ -3364,13 +3438,13 @@ function EnrichPanel({ tableId, fieldId, className }) {
3364
3438
  outcome ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "text-xs text-muted-foreground", children: outcome.message }) : null
3365
3439
  ] });
3366
3440
  }
3367
- if (shown.length === 0) {
3441
+ if (shown2.length === 0) {
3368
3442
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: (0, import_design_system8.cn)("space-y-2 p-4 text-sm", className), children: [
3369
3443
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "font-medium", children: "No column here is filled in by a model." }),
3370
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." })
3371
3445
  ] });
3372
3446
  }
3373
- 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) => {
3374
3448
  const d = draft[row.field_id] ?? {
3375
3449
  instruction: row.enrichment?.instruction ?? "",
3376
3450
  every: row.review_interval_days ? String(row.review_interval_days) : ""
@@ -3659,18 +3733,18 @@ function Outcome({ outcome, onClose }) {
3659
3733
  ] });
3660
3734
  }
3661
3735
  function PlanTable({ plan }) {
3662
- const shown = plan.rows.slice(0, 8);
3663
- const keys = Array.from(new Set(shown.flatMap((row) => row.cells.map((c) => c.key))));
3664
- 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;
3665
3739
  const columnName = /* @__PURE__ */ new Map();
3666
- 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);
3667
3741
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "overflow-auto rounded border", children: [
3668
3742
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("table", { className: "w-full text-xs", "data-matrx-paste-plan": true, children: [
3669
3743
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("thead", { className: "bg-muted/50", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
3670
3744
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { className: "px-2 py-1 text-left font-medium", children: "Row" }),
3671
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))
3672
3746
  ] }) }),
3673
- /* @__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: [
3674
3748
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("td", { className: "whitespace-nowrap px-2 py-1", children: [
3675
3749
  row.fromLine,
3676
3750
  row.recordId === null ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_design_system9.Badge, { variant: "outline", className: "ml-1", children: "new" }) : null
@@ -3692,9 +3766,9 @@ function PlanTable({ plan }) {
3692
3766
  })
3693
3767
  ] }, row.fromLine)) })
3694
3768
  ] }),
3695
- 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: [
3696
3770
  "\u2026and ",
3697
- plan.rows.length - shown.length,
3771
+ plan.rows.length - shown2.length,
3698
3772
  " more rows the same way."
3699
3773
  ] }) : null
3700
3774
  ] });
@@ -4754,7 +4828,6 @@ function checkImportRules(fields, rows, mapping) {
4754
4828
  var import_jsx_runtime16 = require("react/jsx-runtime");
4755
4829
  var BATCH = 250;
4756
4830
  var SAMPLES = 40;
4757
- var MAPPING_COLUMNS = "minmax(9rem, 11rem) minmax(14rem, 17rem) minmax(13rem, 1fr) minmax(8rem, 10rem)";
4758
4831
  var ABSENT = "__absent__";
4759
4832
  function obviousTitleColumn(columns) {
4760
4833
  const says = /^(title|name|record|customer|client|company|job|item|subject|label|description)\b|(\bname|\btitle|\bnumber|\bid)$/i;
@@ -4771,7 +4844,11 @@ async function sha256(bytes) {
4771
4844
  return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
4772
4845
  }
4773
4846
  function typeWord(t) {
4774
- 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;
4775
4852
  }
4776
4853
  function ImportWizard({ tableId, onDone, onWrote, className }) {
4777
4854
  const client = (0, import_react27.useRecordsClient)();
@@ -5012,6 +5089,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
5012
5089
  const refusedByRules = ruleVerdicts.reduce((n, v) => n + v.refused, 0);
5013
5090
  const [goAheadAnyway, setGoAheadAnyway] = (0, import_react26.useState)(false);
5014
5091
  (0, import_react26.useEffect)(() => setGoAheadAnyway(false), [ruleVerdicts]);
5092
+ const afterWrite = !busy && (phase === "done" || alreadyRan !== null || ledger !== null && ledger.seen > 0);
5015
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: [
5016
5094
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center gap-2", children: [
5017
5095
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "font-medium", children: "Import" }),
@@ -5042,7 +5120,11 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
5042
5120
  problem ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(RefusalNotice, { error: problem }) : null,
5043
5121
  phase === "reading" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-muted-foreground", children: "Reading the file\u2026" }) : null,
5044
5122
  phase === "planning" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-muted-foreground", children: "Working out what each column is\u2026" }) : null,
5045
- 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: [
5046
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: [
5047
5129
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "font-medium", children: "What each record is called" }),
5048
5130
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
@@ -5076,30 +5158,19 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
5076
5158
  ),
5077
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.` })
5078
5160
  ] }) : null,
5079
- /* @__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: [
5080
- /* @__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)(
5081
5166
  "div",
5082
5167
  {
5083
- className: "grid items-center gap-x-3 border-b bg-muted px-2 py-1 font-medium",
5084
- 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",
5085
5170
  children: [
5086
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { children: "Column in the file" }),
5087
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { children: "Goes to" }),
5088
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { children: "Kind" }),
5089
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { children: "First values" })
5090
- ]
5091
- }
5092
- ),
5093
- plan.columns.map((column) => {
5094
- const to = mapping[column.header] ?? "";
5095
- const absent = unmapped === "ignore" ? "\u2014 leave it out \u2014" : "\u2014 offer it as a new column \u2014";
5096
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
5097
- "div",
5098
- {
5099
- className: "grid items-start gap-x-3 border-t px-2 py-1",
5100
- style: { gridTemplateColumns: MAPPING_COLUMNS },
5101
- children: [
5102
- /* @__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" }),
5103
5174
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
5104
5175
  import_design_system13.Select,
5105
5176
  {
@@ -5120,7 +5191,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
5120
5191
  size: "sm",
5121
5192
  "aria-label": `Where ${column.header} goes`,
5122
5193
  "data-goes-to": column.header,
5123
- className: "w-full min-w-[14rem]",
5194
+ className: "min-w-[14rem] flex-1",
5124
5195
  children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system13.SelectValue, {})
5125
5196
  }
5126
5197
  ),
@@ -5130,42 +5201,53 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
5130
5201
  ] })
5131
5202
  ]
5132
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
+ }
5133
5215
  ),
5134
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "pt-1", children: [
5135
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system13.Badge, { variant: column.matched ? "secondary" : "outline", className: "text-[11px] font-normal", children: typeWord(column.type) }),
5136
- column.unit ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "ml-1 opacity-60", children: column.unit }) : null,
5137
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "mt-0.5 text-muted-foreground", children: column.why }),
5138
- column.ambiguous ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-destructive", children: "Check a date you know before you run this." }) : null,
5139
- 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,
5140
- ruleVerdictOf.get(column.header) ? (() => {
5141
- const v = ruleVerdictOf.get(column.header);
5142
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { "data-import-rule-refusal": column.header, className: "mt-1", children: [
5143
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5144
- RefusalLine,
5145
- {
5146
- error: refusal(
5147
- "refused_by_rule",
5148
- `${v.refused} of ${v.judged} row${v.judged === 1 ? "" : "s"} would be refused by ${fieldName(v.field)}'s rules. ${v.firstWhy}`,
5149
- "Send this column somewhere else, fix those rows in your file, or import the rest and they are listed afterwards."
5150
- )
5151
- }
5152
- ),
5153
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("p", { className: "text-muted-foreground", "data-import-rule-sample": "", children: [
5154
- "Line ",
5155
- v.firstLine,
5156
- " in your file: ",
5157
- v.firstRaw
5158
- ] })
5159
- ] });
5160
- })() : null
5161
- ] }),
5162
- /* @__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 ") })
5163
- ]
5164
- },
5165
- column.header
5166
- );
5167
- })
5168
- ] }) }),
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
+ }) }),
5169
5251
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system13.Separator, {}),
5170
5252
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex flex-wrap items-center gap-3", children: [
5171
5253
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("label", { className: "flex items-center gap-1", children: [
@@ -5302,7 +5384,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
5302
5384
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("th", { className: "px-2 py-1 text-left font-medium", children: "The row in your file" })
5303
5385
  ] }) }),
5304
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: [
5305
- /* @__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) }),
5306
5388
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("td", { className: "px-2 py-1", children: o.outcome === "refused" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5307
5389
  RefusalLine,
5308
5390
  {
@@ -5326,8 +5408,10 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
5326
5408
  " that ",
5327
5409
  ledger?.refused === 1 ? "was" : "were",
5328
5410
  " refused",
5329
- " ",
5330
- "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."
5331
5415
  ] }) : null
5332
5416
  ] }) : null
5333
5417
  ] }) : null,
@@ -5375,7 +5459,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
5375
5459
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("tbody", { children: runRows.rows.map((row, i) => {
5376
5460
  const source = row.source ?? {};
5377
5461
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("tr", { className: "border-t align-top", children: [
5378
- /* @__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 ?? "") }),
5379
5463
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("td", { className: "px-2 py-1", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5380
5464
  RefusalLine,
5381
5465
  {
@@ -5621,6 +5705,7 @@ function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }
5621
5705
  const [sensitivity, setSensitivity] = (0, import_react30.useState)(String(field?.sensitivity ?? "internal"));
5622
5706
  const [contextPolicy, setContextPolicy] = (0, import_react30.useState)(String(field?.context_policy ?? "include"));
5623
5707
  const [rules, setRules] = (0, import_react30.useState)(field?.rules ?? []);
5708
+ const [allowOther, setAllowOther] = (0, import_react30.useState)(field?.config?.["allow_other"] === true);
5624
5709
  const [optionText, setOptionText] = (0, import_react30.useState)("");
5625
5710
  const [unit, setUnit] = (0, import_react30.useState)(field?.unit ?? "$");
5626
5711
  const [withTime, setWithTime] = (0, import_react30.useState)(String(field?.config?.["kind"] ?? "date") === "datetime");
@@ -5677,7 +5762,10 @@ function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }
5677
5762
  // REMOVES a spec and the column goes back to the target table's own
5678
5763
  // reference column — a panel that only sent the key when it was set
5679
5764
  // could turn the override on and never off.
5680
- ...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 } : {}
5681
5769
  };
5682
5770
  const written2 = await mutation.update({ field_id: field.id, patch });
5683
5771
  if (written2 !== null) onSaved?.();
@@ -5704,6 +5792,7 @@ function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }
5704
5792
  context_policy: contextPolicy,
5705
5793
  rules,
5706
5794
  ...type === "select" || type === "multi_select" ? { options } : {},
5795
+ ...(type === "select" || type === "multi_select") && allowOther ? { allow_other: true } : {},
5707
5796
  ...type === "currency" ? { unit } : {},
5708
5797
  ...type === "datetime" ? { kind: withTime ? "datetime" : "date" } : {},
5709
5798
  ...type === "lookup" ? { via, pick } : {},
@@ -5835,7 +5924,18 @@ function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }
5835
5924
  }
5836
5925
  ),
5837
5926
  "Each value has its own date"
5838
- ] })
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
5839
5939
  ] }),
5840
5940
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_design_system15.Separator, {}),
5841
5941
  /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "grid gap-3 sm:grid-cols-2", children: [
@@ -5883,15 +5983,37 @@ function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }
5883
5983
  ]
5884
5984
  }
5885
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,
5886
5997
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5887
5998
  import_design_system15.BasicInput,
5888
5999
  {
5889
6000
  className: "h-8",
6001
+ ...rule.kind === "length" ? { inputMode: "numeric", placeholder: "most" } : {},
5890
6002
  value: String(rule.value ?? ""),
5891
- "aria-label": `Check ${index + 1} value`,
6003
+ "aria-label": rule.kind === "length" ? `Check ${index + 1} most characters` : `Check ${index + 1} value`,
5892
6004
  onChange: (e) => setRules(rules.map((r, i) => i === index ? { ...r, value: e.target.value } : r))
5893
6005
  }
5894
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,
5895
6017
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5896
6018
  import_design_system15.Button,
5897
6019
  {
@@ -6197,6 +6319,17 @@ function ValueOnTheOtherSide({
6197
6319
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "text-[11px] text-muted-foreground", children: help })
6198
6320
  ] });
6199
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
+ }
6200
6333
 
6201
6334
  // src/StageRules.tsx
6202
6335
  var import_react33 = require("react");
@@ -6953,9 +7086,9 @@ function TableSettings({
6953
7086
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(StageRulesSection, { tableId }),
6954
7087
  /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-2", children: [
6955
7088
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "font-medium", children: "Proposed" }),
6956
- /* @__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 })
6957
7090
  ] }),
6958
- 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: [
6959
7092
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "min-w-[8rem] flex-1 break-words", children: proposal.field.label || proposal.field.key }),
6960
7093
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "min-w-[8rem] flex-1 break-words text-muted-foreground", children: proposal.why }),
6961
7094
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_design_system18.Badge, { variant: "secondary", className: "text-[10px] font-normal", children: proposal.proposed_by }),
@@ -7719,10 +7852,10 @@ function Peek({ tableId, recordId, onClose, className }) {
7719
7852
  }
7720
7853
  function computedInPlainWords(row, field) {
7721
7854
  const name = field ? fieldName(field) : humanize(row.field_key);
7722
- 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);
7723
7856
  const when = new Date(row.computed_at);
7724
7857
  const at = Number.isNaN(when.getTime()) ? "" : ` on ${when.toLocaleDateString()}`;
7725
- return `${name} was worked out as ${shown}${at}.`;
7858
+ return `${name} was worked out as ${shown2}${at}.`;
7726
7859
  }
7727
7860
 
7728
7861
  // src/views.ts
@@ -8673,7 +8806,7 @@ function Gallery({
8673
8806
  onOpenRecord,
8674
8807
  note
8675
8808
  }) {
8676
- const shown = fields.slice(0, 4);
8809
+ const shown2 = fields.slice(0, 4);
8677
8810
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex min-h-0 flex-col gap-2", children: [
8678
8811
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Note, { children: note }),
8679
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) => {
@@ -8686,7 +8819,7 @@ function Gallery({
8686
8819
  onClick: () => onOpenRecord?.(row.id),
8687
8820
  children: [
8688
8821
  typeof image === "string" && image !== "" ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("img", { src: image, alt: "", className: "h-28 w-full rounded object-cover" }) : null,
8689
- 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: [
8690
8823
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "shrink-0 text-muted-foreground", children: fieldName(field) }),
8691
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 }) })
8692
8825
  ] }, field.key))
@@ -10016,18 +10149,18 @@ function ActionInbox({ tableId, includeSettled = false, onOpenRecord, className
10016
10149
  (0, import_react63.useEffect)(() => {
10017
10150
  void load();
10018
10151
  }, [load]);
10019
- const shown = (0, import_react63.useMemo)(() => {
10152
+ const shown2 = (0, import_react63.useMemo)(() => {
10020
10153
  const all = items ?? [];
10021
10154
  if (!tableId) return all;
10022
10155
  return all.filter((i) => i.kind !== "assignment" || i.subject_kind !== "record" || true);
10023
10156
  }, [items, tableId]);
10024
10157
  (0, import_react63.useEffect)(() => {
10025
- if (cursor >= shown.length) setCursor(Math.max(0, shown.length - 1));
10026
- }, [shown.length, cursor]);
10158
+ if (cursor >= shown2.length) setCursor(Math.max(0, shown2.length - 1));
10159
+ }, [shown2.length, cursor]);
10027
10160
  (0, import_react63.useEffect)(() => {
10028
10161
  const el = listRef.current?.querySelector(`[data-row="${cursor}"]`);
10029
10162
  el?.scrollIntoView({ block: "nearest" });
10030
- }, [cursor, shown.length]);
10163
+ }, [cursor, shown2.length]);
10031
10164
  const decide = (0, import_react63.useCallback)(
10032
10165
  async (item, approve) => {
10033
10166
  if (item.kind === "assignment") return;
@@ -10052,11 +10185,11 @@ function ActionInbox({ tableId, includeSettled = false, onOpenRecord, className
10052
10185
  );
10053
10186
  const onKeyDown = (0, import_react63.useCallback)(
10054
10187
  (event) => {
10055
- const item = shown[cursor];
10188
+ const item = shown2[cursor];
10056
10189
  const key = event.key;
10057
10190
  if (key === "j" || key === "ArrowDown") {
10058
10191
  event.preventDefault();
10059
- setCursor((c) => Math.min(shown.length - 1, c + 1));
10192
+ setCursor((c) => Math.min(shown2.length - 1, c + 1));
10060
10193
  } else if (key === "k" || key === "ArrowUp") {
10061
10194
  event.preventDefault();
10062
10195
  setCursor((c) => Math.max(0, c - 1));
@@ -10071,7 +10204,7 @@ function ActionInbox({ tableId, includeSettled = false, onOpenRecord, className
10071
10204
  void decide(item, key === "a");
10072
10205
  }
10073
10206
  },
10074
- [shown, cursor, decide, open, load]
10207
+ [shown2, cursor, decide, open, load]
10075
10208
  );
10076
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) });
10077
10210
  return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
@@ -10084,12 +10217,12 @@ function ActionInbox({ tableId, includeSettled = false, onOpenRecord, className
10084
10217
  children: [
10085
10218
  /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "flex items-center gap-2", children: [
10086
10219
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("h3", { className: "text-sm font-medium", children: "Inbox" }),
10087
- /* @__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 }),
10088
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" }),
10089
10222
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_design_system30.Button, { size: "sm", variant: "ghost", onClick: () => void load(), children: "Refresh" })
10090
10223
  ] }),
10091
10224
  error ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(RefusalNotice, { error }) : null,
10092
- 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) => {
10093
10226
  const settled = outcome[item.item_id];
10094
10227
  return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
10095
10228
  "li",
@@ -10165,6 +10298,7 @@ function HistoryPanel({ tableId, recordId, onAskAboutField, className }) {
10165
10298
  const [pending, setPending] = (0, import_react65.useState)({ phase: "idle" });
10166
10299
  const [said, setSaid] = (0, import_react65.useState)(null);
10167
10300
  const listRef = (0, import_react65.useRef)(null);
10301
+ const changed = (0, import_react66.useRecordChangeRevision)(recordId);
10168
10302
  const load = (0, import_react65.useCallback)(async () => {
10169
10303
  const answered = await client.recordHistory({ record_id: recordId });
10170
10304
  if (!answered.ok) {
@@ -10173,7 +10307,7 @@ function HistoryPanel({ tableId, recordId, onAskAboutField, className }) {
10173
10307
  }
10174
10308
  setError(null);
10175
10309
  setEntries(answered.data);
10176
- }, [client, recordId]);
10310
+ }, [client, recordId, changed]);
10177
10311
  (0, import_react65.useEffect)(() => {
10178
10312
  void load();
10179
10313
  }, [load]);
@@ -10515,9 +10649,9 @@ function CommentThread({ tableId, recordId, fieldKey, className }) {
10515
10649
  }, [mentionQuery, people, picked]);
10516
10650
  if (error) return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(RefusalNotice, { error, className });
10517
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) });
10518
- const shown = fieldKey ? thread.comments.filter((c) => c.field_key === fieldKey) : thread.comments;
10519
- const roots = shown.filter((c) => !c.parent_comment_id);
10520
- 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);
10521
10655
  const fieldLabel = fieldKey ? (fields.data ?? []).find((f) => f.key === fieldKey)?.label || humanize(fieldKey) : null;
10522
10656
  async function post() {
10523
10657
  if (draft.trim() === "") return;
@@ -10560,7 +10694,7 @@ function CommentThread({ tableId, recordId, fieldKey, className }) {
10560
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: [
10561
10695
  /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "flex items-baseline gap-2", children: [
10562
10696
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("h3", { className: "text-sm font-medium", children: fieldLabel ? `Comments on ${fieldLabel}` : "Comments" }),
10563
- /* @__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 }),
10564
10698
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
10565
10699
  import_design_system32.Button,
10566
10700
  {
@@ -11312,16 +11446,16 @@ function PortalsPanel({ tableId, activePortalId, className }) {
11312
11446
  const holdingThis = new Set(
11313
11447
  exposures.filter((e) => e.table_id === tableId).map((e) => e.portal_id)
11314
11448
  );
11315
- 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;
11316
11450
  const couldTakeIt = onATable ? portals.filter((p) => !holdingThis.has(p.portal_id)) : [];
11317
11451
  const elsewhere = couldTakeIt.length;
11318
11452
  const origin = host.publicOrigin ?? (typeof window === "undefined" ? "" : window.location.origin);
11319
11453
  return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("section", { className: (0, import_design_system36.cn)("flex flex-col gap-3", className), children: [
11320
11454
  /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("header", { className: "flex items-center gap-2", children: [
11321
11455
  /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("h3", { className: "text-sm font-medium", children: "Portals" }),
11322
- /* @__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}` }),
11323
11457
  /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "flex-1" }),
11324
- 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
11325
11459
  ] }),
11326
11460
  listError ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(RefusalNotice, { error: listError }) : null,
11327
11461
  building ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
@@ -11346,7 +11480,7 @@ function PortalsPanel({ tableId, activePortalId, className }) {
11346
11480
  onClose: () => setAdding(null)
11347
11481
  }
11348
11482
  ) : null,
11349
- 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)(
11350
11484
  BuildOrAsk,
11351
11485
  {
11352
11486
  mayBuild: true,
@@ -11368,7 +11502,7 @@ function PortalsPanel({ tableId, activePortalId, className }) {
11368
11502
  ]
11369
11503
  }
11370
11504
  ) : null,
11371
- 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: [
11372
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:" }),
11373
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)(
11374
11508
  import_design_system36.Button,
@@ -11385,8 +11519,8 @@ function PortalsPanel({ tableId, activePortalId, className }) {
11385
11519
  portal.portal_id
11386
11520
  )) })
11387
11521
  ] }) : null,
11388
- 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,
11389
- /* @__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) => {
11390
11524
  const url = `${origin}${(0, import_records9.portalPath)(portal.slug)}`;
11391
11525
  const open = openId === portal.portal_id;
11392
11526
  const linked = portal.portal_id === activePortalId;
@@ -11533,7 +11667,7 @@ function ArchivePortalControl({
11533
11667
  }
11534
11668
  function CopyLink({ url, what }) {
11535
11669
  const [copied, setCopied] = (0, import_react73.useState)(false);
11536
- const [shown, setShown] = (0, import_react73.useState)(false);
11670
+ const [shown2, setShown] = (0, import_react73.useState)(false);
11537
11671
  return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_jsx_runtime39.Fragment, { children: [
11538
11672
  /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
11539
11673
  import_design_system36.Button,
@@ -11555,7 +11689,7 @@ function CopyLink({ url, what }) {
11555
11689
  children: copied ? "Copied" : what ? `Copy ${what}` : "Copy link"
11556
11690
  }
11557
11691
  ),
11558
- 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: [
11559
11693
  "This browser would not let the page copy for you, so here it is to copy by hand: ",
11560
11694
  url
11561
11695
  ] }) : null
@@ -14143,20 +14277,23 @@ function ChartBlock({ block, subject, className }) {
14143
14277
  label: point.label === "All records" ? block.title : `${block.title} \xB7 ${point.label}`
14144
14278
  });
14145
14279
  } : null;
14146
- 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: [
14147
- /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("figcaption", { className: "flex items-baseline gap-2 text-xs", children: [
14148
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: "truncate font-medium", children: block.title || CHART_KIND_LABEL[kind] }),
14149
- /* @__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 ") }),
14150
- 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: [
14151
- pretty(block.ms),
14152
- " ms"
14153
- ] }) : null
14154
- ] }),
14155
- 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: [
14156
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(Drawing, { kind, points, series, config, onDrill: drill }),
14157
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(Values, { points, measure: primary, config, onDrill: drill })
14158
- ] }) })
14159
- ] });
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
+ );
14160
14297
  }
14161
14298
  function BigNumber({
14162
14299
  points,
@@ -14768,7 +14905,7 @@ function FormsPanel({ tableId, activeFormId, className }) {
14768
14905
  },
14769
14906
  [client, load]
14770
14907
  );
14771
- const [shown, setShown] = (0, import_react98.useState)(null);
14908
+ const [shown2, setShown] = (0, import_react98.useState)(null);
14772
14909
  const copy = (0, import_react98.useCallback)(async (url, formId) => {
14773
14910
  try {
14774
14911
  await navigator.clipboard.writeText(url);
@@ -14867,7 +15004,7 @@ function FormsPanel({ tableId, activeFormId, className }) {
14867
15004
  ) : null
14868
15005
  ] }),
14869
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,
14870
- 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: [
14871
15008
  "This browser would not let the page copy for you, so here it is to copy by hand:",
14872
15009
  " ",
14873
15010
  url
@@ -15029,7 +15166,7 @@ function BookingBuilder({ tableId, bookingId, onSaved, onClose, className }) {
15029
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." });
15030
15167
  }
15031
15168
  const url = formId ? `${publicOrigin}${(0, import_records13.bookingPath)(formId)}` : null;
15032
- const shown = offer ?? null;
15169
+ const shown2 = offer ?? null;
15033
15170
  return (
15034
15171
  // THE RAIL IS 384px WIDE AND THE VIEWPORT IS NOT (walk 1, 2026-09-21):
15035
15172
  // every split below is a CONTAINER query, so this screen is the same
@@ -15177,21 +15314,21 @@ function BookingBuilder({ tableId, bookingId, onSaved, onClose, className }) {
15177
15314
  }
15178
15315
  )
15179
15316
  ] }),
15180
- 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: [
15181
15318
  "Saved. The page offers ",
15182
- shown.slot_minutes,
15319
+ shown2.slot_minutes,
15183
15320
  "-minute appointments in ",
15184
- shown.timezone,
15321
+ shown2.timezone,
15185
15322
  ", up to",
15186
15323
  " ",
15187
- shown.max_per_day,
15324
+ shown2.max_per_day,
15188
15325
  " a day, ",
15189
- shown.days,
15326
+ shown2.days,
15190
15327
  " days ahead",
15191
- shown.buffer_minutes > 0 ? `, with ${shown.buffer_minutes} minutes between them` : "",
15328
+ shown2.buffer_minutes > 0 ? `, with ${shown2.buffer_minutes} minutes between them` : "",
15192
15329
  ":",
15193
15330
  " ",
15194
- shown.windows.map(
15331
+ shown2.windows.map(
15195
15332
  (w) => `${DAYS.find((d) => d.weekday === w.weekday)?.label ?? w.weekday} ${w.from}\u2013${w.to}`
15196
15333
  ).join(", "),
15197
15334
  "."
@@ -15233,21 +15370,22 @@ var import_react103 = require("@ai-matrx/records/react");
15233
15370
  var import_records14 = require("@ai-matrx/records");
15234
15371
  var import_design_system50 = require("@ai-matrx/design-system");
15235
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.";
15236
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.";
15237
15375
  function bookingSuggestion(tableName2) {
15238
- const subject = tableName2?.trim() ? tableName2.trim() : "appointments";
15239
- 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.";
15240
15378
  }
15241
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.";
15242
15380
  var STATE_WORDS = BOOKING_PAGE_STATE_LABEL;
15243
- function BookingSlots({ tableId, className }) {
15381
+ function BookingSlots({ tableId, activeBookingId, className }) {
15244
15382
  const client = (0, import_react103.useRecordsClient)();
15245
15383
  const host = useRecordsUi();
15246
15384
  const [pages, setPages] = (0, import_react102.useState)(null);
15247
15385
  const [error, setError] = (0, import_react102.useState)(null);
15248
15386
  const [busy, setBusy] = (0, import_react102.useState)(null);
15249
15387
  const [copied, setCopied] = (0, import_react102.useState)(null);
15250
- const [shown, setShown] = (0, import_react102.useState)(null);
15388
+ const [shown2, setShown] = (0, import_react102.useState)(null);
15251
15389
  const [building, setBuilding] = (0, import_react102.useState)(false);
15252
15390
  const load = (0, import_react102.useCallback)(async () => {
15253
15391
  const answered = await client.bookings(tableId ? { table_id: tableId } : {});
@@ -15313,6 +15451,7 @@ function BookingSlots({ tableId, className }) {
15313
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
15314
15452
  ] }),
15315
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,
15316
15455
  building && tableId ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
15317
15456
  BookingBuilder,
15318
15457
  {
@@ -15348,53 +15487,66 @@ function BookingSlots({ tableId, className }) {
15348
15487
  const url = `${origin}${(0, import_records14.bookingPath)(page.form_id)}`;
15349
15488
  const mayOpen = levels.data?.[page.table_id] === "admin";
15350
15489
  const open = page.published_at !== null && page.closed_at === null;
15351
- return /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("li", { className: "rounded-md border p-2.5", "data-testid": "booking-page", children: [
15352
- /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("div", { className: "flex items-center gap-2", children: [
15353
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: page.title ?? page.slug }),
15354
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(import_design_system50.Badge, { variant: page.state === "open" ? "default" : "secondary", children: page.state })
15355
- ] }),
15356
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("p", { className: "mt-0.5 text-xs text-muted-foreground", children: STATE_WORDS[page.state] ?? page.state }),
15357
- /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("p", { className: "mt-1.5 text-xs", children: [
15358
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("span", { className: "font-medium", children: page.upcoming }),
15359
- " coming up",
15360
- " \xB7 ",
15361
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("span", { className: "font-medium", children: page.booked }),
15362
- " booked in all",
15363
- page.held > 0 ? /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(import_jsx_runtime53.Fragment, { children: [
15364
- " \xB7 ",
15365
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("span", { className: "font-medium", children: page.held }),
15366
- " being held right now"
15367
- ] }) : null,
15368
- page.cancelled > 0 ? /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(import_jsx_runtime53.Fragment, { children: [
15369
- " \xB7 ",
15370
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("span", { className: "font-medium", children: page.cancelled }),
15371
- " cancelled"
15372
- ] }) : null,
15373
- /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("span", { className: "text-muted-foreground", children: [
15374
- " \xB7 ",
15375
- page.slot_minutes,
15376
- " minutes each"
15377
- ] })
15378
- ] }),
15379
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("p", { className: "mt-1 text-xs text-muted-foreground", children: nextInWords(page) }),
15380
- /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("div", { className: "mt-2 flex flex-wrap items-center gap-1.5", children: [
15381
- /* @__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" }),
15382
- mayOpen ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
15383
- import_design_system50.Button,
15384
- {
15385
- size: "sm",
15386
- variant: "ghost",
15387
- disabled: busy === page.form_id,
15388
- onClick: () => void toggle(page),
15389
- children: busy === page.form_id ? "\u2026" : open ? "Close" : "Open"
15390
- }
15391
- ) : null
15392
- ] }),
15393
- 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: [
15394
- "This browser would not let the page copy for you, so here it is to copy by hand: ",
15395
- url
15396
- ] }) : null
15397
- ] }, 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
+ );
15398
15550
  }) }),
15399
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
15400
15552
  ] });
@@ -16533,8 +16685,15 @@ var LANE_TITLE = {
16533
16685
  };
16534
16686
  var APP_TABLE_PREFIX = "records_ui_";
16535
16687
  var LANE_EMPTY = {
16536
- mine: "Nothing of your own yet. New table makes one that only you can see.",
16537
- 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.",
16538
16697
  system: "The platform's own tables are not in this organization's store.",
16539
16698
  community: "Nothing shared beyond your organization. A table published by link or to the world lands here.",
16540
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."
@@ -17179,7 +17338,7 @@ function TablePage({
17179
17338
  }
17180
17339
  ) : null,
17181
17340
  rail === "forms" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(FormsPanel, { tableId, activeFormId: itemFor("forms") }) : null,
17182
- 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,
17183
17342
  rail === "checklists" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
17184
17343
  ChecklistsPanel,
17185
17344
  {