@ai-matrx/records-ui 0.90.2 → 0.92.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -67,7 +67,7 @@ __export(src_exports, {
67
67
  ConditionGroup: () => ConditionGroup,
68
68
  ConditionRow: () => ConditionRow,
69
69
  CustomFieldsSection: () => CustomFieldsSection,
70
- DEFAULT_FIELDS: () => import_core12.DEFAULT_FIELDS,
70
+ DEFAULT_FIELDS: () => import_core15.DEFAULT_FIELDS,
71
71
  DEFAULT_FOOTER: () => DEFAULT_FOOTER,
72
72
  DEFAULT_VIEW_NAME: () => DEFAULT_VIEW_NAME,
73
73
  DUE_STATE_LABEL: () => DUE_STATE_LABEL,
@@ -221,7 +221,7 @@ __export(src_exports, {
221
221
  WithheldValue: () => WithheldValue,
222
222
  actorBadge: () => actorBadge,
223
223
  actorWords: () => actorWords,
224
- addFields: () => import_core12.addFields,
224
+ addFields: () => import_core15.addFields,
225
225
  applyLook: () => applyLook,
226
226
  archiveConfirmSentence: () => archiveConfirmSentence,
227
227
  archivedRowLine: () => archivedRowLine,
@@ -250,7 +250,7 @@ __export(src_exports, {
250
250
  dashboardFromSummary: () => dashboardFromSummary,
251
251
  dateFormatFor: () => dateFormatFor,
252
252
  dateText: () => dateText,
253
- declareTable: () => import_core12.declareTable,
253
+ declareTable: () => import_core15.declareTable,
254
254
  declareView: () => declareView,
255
255
  defaultViewOf: () => defaultViewOf,
256
256
  designatedLayoutOf: () => designatedLayoutOf,
@@ -258,7 +258,7 @@ __export(src_exports, {
258
258
  ensureSystemTable: () => ensureSystemTable,
259
259
  entityReferenceSpec: () => entityReferenceSpec,
260
260
  envelopeFor: () => envelopeFor,
261
- fieldDeclarationFor: () => import_core12.fieldDeclarationFor,
261
+ fieldDeclarationFor: () => import_core15.fieldDeclarationFor,
262
262
  fieldIsEditable: () => fieldIsEditable,
263
263
  fieldName: () => fieldName,
264
264
  fieldToken: () => fieldToken,
@@ -346,7 +346,7 @@ __export(src_exports, {
346
346
  tableName: () => tableName,
347
347
  tableOpening: () => tableOpening,
348
348
  tableRightsAt: () => tableRightsAt,
349
- tokenFor: () => import_core12.tokenFor,
349
+ tokenFor: () => import_core15.tokenFor,
350
350
  unknownRailLine: () => unknownRailLine,
351
351
  unknownViewLine: () => unknownViewLine,
352
352
  useCanShare: () => useCanShare,
@@ -677,6 +677,7 @@ function humanize(token) {
677
677
  }
678
678
  function fieldName(field2) {
679
679
  const label = (field2.label ?? "").trim();
680
+ if (label !== "" && label === field2.key && /^[a-z0-9]+(?:[_.-][a-z0-9]+)+$/.test(label)) return humanize(label);
680
681
  if (label !== "") return label;
681
682
  const name = (field2.name ?? "").trim();
682
683
  if (name !== "") return name;
@@ -1090,9 +1091,9 @@ function parityTypesWithNoExplanation() {
1090
1091
  function kindsWithNoChoice() {
1091
1092
  return import_records2.DECLARABLE_FIELD_KINDS.map((row) => row.kind).filter((kind) => !BY_ID.has(kind));
1092
1093
  }
1093
- var ADD_FIELD_OPEN_LABEL = "Add field";
1094
- var ADD_FIELD_SAVE_LABEL = "Create field";
1095
- var EDIT_FIELD_SAVE_LABEL = "Save field";
1094
+ var ADD_FIELD_OPEN_LABEL = "Add column";
1095
+ var ADD_FIELD_SAVE_LABEL = "Create column";
1096
+ var EDIT_FIELD_SAVE_LABEL = "Save column";
1096
1097
 
1097
1098
  // src/parity.ts
1098
1099
  var kernelId = (name) => import_records3.KERNEL_TABLES.find((t) => t.name === name)?.id ?? "";
@@ -2016,7 +2017,7 @@ function rich(id, text, raw, format) {
2016
2017
  case "url":
2017
2018
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("a", { href: text.includes("://") ? text : `https://${text}`, target: "_blank", rel: "noopener noreferrer", className: LINK, onClick: own, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "truncate", children: text }) });
2018
2019
  case "phone":
2019
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("a", { href: `tel:${text.replace(/[^\d+]/g, "")}`, className: LINK, onClick: own, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "truncate", children: text }) });
2020
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("a", { href: `tel:${text.replace(/[^\d+]/g, "")}`, className: LINK, onClick: own, title: text, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "truncate tabular-nums", children: formatPhone(text) }) });
2020
2021
  case "address":
2021
2022
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2022
2023
  "a",
@@ -2081,6 +2082,26 @@ function FormattedValue({
2081
2082
  if (!result.ok || result.empty || !def?.rich) return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_data_table2.FormattedCell, { result });
2082
2083
  return rich(def.id, result.text, value, format) ?? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_data_table2.FormattedCell, { result });
2083
2084
  }
2085
+ function formatPhone(raw) {
2086
+ const text = raw.trim();
2087
+ const digits = text.replace(/\D/g, "");
2088
+ if (/[a-z]/i.test(text.replace(/ext\.?|x/gi, ""))) return text;
2089
+ if (digits.length === 10 && !text.startsWith("+")) return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6)}`;
2090
+ if (digits.length === 11 && digits.startsWith("1") && (text.startsWith("+1") || text.startsWith("1") || !text.startsWith("+"))) {
2091
+ return `+1 (${digits.slice(1, 4)}) ${digits.slice(4, 7)}-${digits.slice(7)}`;
2092
+ }
2093
+ return text;
2094
+ }
2095
+ function linkChipText(raw) {
2096
+ try {
2097
+ const url = new URL(raw.includes("://") ? raw : `https://${raw}`);
2098
+ const host = url.hostname.replace(/^www\./, "");
2099
+ const last = url.pathname.split("/").filter(Boolean).pop();
2100
+ return last ? `${host} \xB7 ${decodeURIComponent(last)}` : host;
2101
+ } catch {
2102
+ return raw;
2103
+ }
2104
+ }
2084
2105
 
2085
2106
  // src/formatControls.tsx
2086
2107
  var import_jsx_runtime8 = require("react/jsx-runtime");
@@ -2175,6 +2196,7 @@ function formatControl(args) {
2175
2196
  }
2176
2197
 
2177
2198
  // src/editors.tsx
2199
+ var import_field_formats5 = require("@ai-matrx/design-system/field-formats");
2178
2200
  var import_jsx_runtime9 = require("react/jsx-runtime");
2179
2201
  var CHOICE_EDITOR_ADDS_WHAT_IS_TYPED = true;
2180
2202
  var FORMAT_BLIND_KINDS = /* @__PURE__ */ new Set(["formula", "lookup", "rollup", "json", "checkbox", "select", "multi_select", "member", "entity_reference", "relation", "attachment", "datetime"]);
@@ -2186,9 +2208,123 @@ function FieldLabel({ field: field2, htmlFor, children }) {
2186
2208
  children
2187
2209
  ] });
2188
2210
  }
2189
- function FieldControl({ field: field2, value, onChange, disabled, id }) {
2211
+ function isTypedDraft(value) {
2212
+ return typeof value === "object" && value !== null && value.typedDraft === true;
2213
+ }
2214
+ var TYPED_AS_WORDS = /* @__PURE__ */ new Set(["text", "long_text", "email", "url", "phone"]);
2215
+ var TYPED_AND_READ = /* @__PURE__ */ new Set(["number", "currency", "percent", "datetime", "select", "multi_select"]);
2216
+ function typingOpens(field2) {
2217
+ const kind = String(editorKindFor(field2));
2218
+ if (kind === "formula" || kind === "lookup" || kind === "rollup") return "none";
2219
+ const format = formatForField(field2);
2220
+ if (format && format.id === "time") return "read";
2221
+ if (format && format.id === "rating") return "read";
2222
+ if (TYPED_AS_WORDS.has(kind)) return "words";
2223
+ if (TYPED_AND_READ.has(kind)) return "read";
2224
+ return "none";
2225
+ }
2226
+ function judgeTyped(field2, text, choices) {
2227
+ const trimmed = text.trim();
2228
+ const kind = String(editorKindFor(field2));
2229
+ const format = formatForField(field2);
2230
+ if (trimmed === "") return { value: null };
2231
+ if (format && format.id === "time") {
2232
+ const kept = (0, import_field_formats5.parseFieldInput)(trimmed, format);
2233
+ return typeof kept === "string" && /^\d{2}:\d{2}(:\d{2})?$/.test(kept) ? { value: kept } : { refusal: `${fieldName(field2)} holds a time of day, and \u201C${text}\u201D is not one. Write it as 14:30 or 2:30 PM.` };
2234
+ }
2235
+ if (kind === "select" || kind === "multi_select") {
2236
+ if (!choices) return null;
2237
+ const title = (option) => recordName(option.data, null, "").toLowerCase();
2238
+ const words2 = field2.multi || kind === "multi_select" ? trimmed.split(/\s*[,;\n]\s*/).filter(Boolean) : [trimmed];
2239
+ const keys = [];
2240
+ const unknown = [];
2241
+ for (const word of words2) {
2242
+ const low = word.toLowerCase();
2243
+ const option = choices.find((o) => title(o) === low || (0, import_records5.optionKey)(o).toLowerCase() === low);
2244
+ if (option) keys.push((0, import_records5.optionKey)(option));
2245
+ else if ((0, import_records5.allowsOtherValues)(field2)) keys.push(word);
2246
+ else unknown.push(word);
2247
+ }
2248
+ if (unknown.length > 0) {
2249
+ return {
2250
+ refusal: `${fieldName(field2)} has no choice called \u201C${unknown.join("\u201D, \u201C")}\u201D. Type one of its choices, or press Escape and pick from the list.`
2251
+ };
2252
+ }
2253
+ return { value: field2.multi || kind === "multi_select" ? keys : keys[0] ?? null };
2254
+ }
2255
+ if (format && format.id === "rating") {
2256
+ const n = Number(trimmed);
2257
+ const max = format.options?.ratingMax ?? 5;
2258
+ return Number.isInteger(n) && n >= 0 && n <= max ? { value: n === 0 ? null : n } : { refusal: `${fieldName(field2)} is a rating from 1 to ${max}, and \u201C${text}\u201D is not one.` };
2259
+ }
2260
+ const read = (0, import_records5.coerceTypedAnswer)(field2, text, { label: fieldName(field2) });
2261
+ if ("refusal" in read) return { refusal: read.refusal };
2262
+ if (typeof read.value === "number" && format && (format.id === "percent" || format.id === "progress") && format.options?.percentScale === "fraction") {
2263
+ return { value: Math.round(read.value / 100 * 1e9) / 1e9 };
2264
+ }
2265
+ return { value: read.value };
2266
+ }
2267
+ function draftFromTyping(field2, seed) {
2268
+ const opens = typingOpens(field2);
2269
+ if (opens === "none") return void 0;
2270
+ if (opens === "words") return seed;
2271
+ return { typedDraft: true, text: seed, judged: judgeTyped(field2, seed) };
2272
+ }
2273
+ function TypedTextControl({ field: field2, value, onChange, disabled, id }) {
2274
+ const client = (0, import_react11.useOptionalRecordsClient)();
2275
+ const kind = String(editorKindFor(field2));
2276
+ const needsChoices = kind === "select" || kind === "multi_select";
2277
+ const [choices, setChoices] = (0, import_react10.useState)(null);
2278
+ (0, import_react10.useEffect)(() => {
2279
+ if (!needsChoices || !client) return;
2280
+ let cancelled = false;
2281
+ void client.fieldOptions({ field_id: field2.id }).then((result) => {
2282
+ if (!cancelled) setChoices(result.ok ? inTheStoresOrder(result.data ?? []) : []);
2283
+ });
2284
+ return () => {
2285
+ cancelled = true;
2286
+ };
2287
+ }, [client, field2.id, needsChoices]);
2288
+ (0, import_react10.useEffect)(() => {
2289
+ if (choices && value.judged === null) onChange({ ...value, judged: judgeTyped(field2, value.text, choices) });
2290
+ }, [choices, value, field2, onChange]);
2291
+ const judged = value.judged;
2292
+ const format = formatForField(field2);
2293
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "flex flex-col gap-0.5", children: [
2294
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2295
+ import_design_system8.BasicInput,
2296
+ {
2297
+ id,
2298
+ value: value.text,
2299
+ disabled,
2300
+ autoComplete: "off",
2301
+ "aria-invalid": judged !== null && "refusal" in judged ? true : void 0,
2302
+ onChange: (e) => onChange({ typedDraft: true, text: e.target.value, judged: judgeTyped(field2, e.target.value, needsChoices ? choices : void 0) })
2303
+ }
2304
+ ),
2305
+ judged === null ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "text-[10px] text-muted-foreground", children: [
2306
+ "Reading ",
2307
+ fieldName(field2),
2308
+ "'s choices\u2026"
2309
+ ] }) : "refusal" in judged ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { "data-records-typed-refusal": "", role: "status", className: "text-[11px] leading-snug text-amber-700 dark:text-amber-400", children: judged.refusal }) : judged.value === null || judged.value === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { "data-records-typed-reads-as": "", className: "text-[10px] text-muted-foreground", children: [
2310
+ "Reads as",
2311
+ " ",
2312
+ needsChoices ? String(
2313
+ (Array.isArray(judged.value) ? judged.value : [judged.value]).map((k) => {
2314
+ const option = choices?.find((o) => (0, import_records5.optionKey)(o) === k);
2315
+ return option ? recordName(option.data, null, String(k)) : String(k);
2316
+ }).join(", ")
2317
+ ) : (() => {
2318
+ const shown3 = format ?? (kind === "datetime" ? { id: carriesTimeOfDay(field2, judged.value) ? "datetime" : "date" } : void 0);
2319
+ return shown3 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(FormattedValue, { value: judged.value, format: shown3 }) : String(judged.value);
2320
+ })()
2321
+ ] })
2322
+ ] });
2323
+ }
2324
+ function FieldControl({ field: field2, value, onChange, disabled, id, autoOpen, onClose }) {
2190
2325
  const kind = editorKindFor(field2);
2191
2326
  const inputId = id ?? `field-${field2.key}`;
2327
+ if (isTypedDraft(value)) return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(TypedTextControl, { field: field2, value, onChange, disabled, id: inputId });
2192
2328
  const format = formatForField(field2);
2193
2329
  if (format && !FORMAT_BLIND_KINDS.has(String(kind))) {
2194
2330
  const own2 = formatControl({ format, value, onChange, disabled, id: inputId, label: fieldName(field2) });
@@ -2261,9 +2397,9 @@ function FieldControl({ field: field2, value, onChange, disabled, id }) {
2261
2397
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(CheckboxControl, { field: field2, value, onChange, disabled, id: inputId });
2262
2398
  case "select":
2263
2399
  case "multi_select":
2264
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(OptionControl, { field: field2, value, onChange, disabled, id: inputId });
2400
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(OptionControl, { field: field2, value, onChange, disabled, id: inputId, autoOpen, onClose });
2265
2401
  case "member":
2266
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PersonPicker, { field: field2, value, onChange, disabled, id: inputId });
2402
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PersonPicker, { field: field2, value, onChange, disabled, id: inputId, autoOpen, onClose });
2267
2403
  case "entity_reference":
2268
2404
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(EntityReferenceControl, { field: field2, value, onChange, disabled, id: inputId });
2269
2405
  case "relation":
@@ -2383,7 +2519,7 @@ function inTheStoresOrder(options) {
2383
2519
  const made = (o) => String(o.created_at ?? "");
2384
2520
  return [...options].sort((a, b) => position(a) - position(b) || made(a).localeCompare(made(b)));
2385
2521
  }
2386
- function OptionControl({ field: field2, value, onChange, disabled, id }) {
2522
+ function OptionControl({ field: field2, value, onChange, disabled, id, autoOpen, onClose }) {
2387
2523
  const client = (0, import_react11.useOptionalRecordsClient)();
2388
2524
  const [options, setOptions] = (0, import_react10.useState)(null);
2389
2525
  const [refusal2, setRefusal] = (0, import_react10.useState)(null);
@@ -2426,7 +2562,7 @@ function OptionControl({ field: field2, value, onChange, disabled, id }) {
2426
2562
  if (field2.multi) {
2427
2563
  const chosen = options.filter((option) => (0, import_records5.choiceValuesOf)(value).some((v) => (0, import_records5.isTheChosen)(option, v)));
2428
2564
  const picked = new Set(chosen.map((option) => (0, import_records5.optionKey)(option)));
2429
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { id, className: "flex flex-wrap gap-1", children: options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2565
+ const toggles = /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { id: autoOpen ? void 0 : id, className: "flex flex-wrap gap-1", children: options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2430
2566
  import_design_system8.Button,
2431
2567
  {
2432
2568
  type: "button",
@@ -2443,6 +2579,20 @@ function OptionControl({ field: field2, value, onChange, disabled, id }) {
2443
2579
  },
2444
2580
  option.id
2445
2581
  )) });
2582
+ if (!autoOpen) return toggles;
2583
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2584
+ import_design_system8.Popover,
2585
+ {
2586
+ defaultOpen: true,
2587
+ onOpenChange: (open) => {
2588
+ if (!open) onClose?.();
2589
+ },
2590
+ children: [
2591
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_design_system8.PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_design_system8.Button, { id, type: "button", size: "sm", variant: "outline", className: "h-6 max-w-full truncate", children: chosen.length === 0 ? "Choose" : chosen.map(titleOf).join(", ") }) }),
2592
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_design_system8.PopoverContent, { sizing: "content", className: "max-w-[20rem] p-2", align: "start", children: toggles })
2593
+ ]
2594
+ }
2595
+ );
2446
2596
  }
2447
2597
  const current = options.find((option) => (0, import_records5.isTheChosen)(option, value));
2448
2598
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
@@ -2451,6 +2601,13 @@ function OptionControl({ field: field2, value, onChange, disabled, id }) {
2451
2601
  value: current ? (0, import_records5.optionKey)(current) : "",
2452
2602
  disabled: disabled ?? false,
2453
2603
  onValueChange: (next) => onChange(next === "" ? null : next),
2604
+ ...autoOpen ? {
2605
+ // IN A GRID CELL the list opens at once and a pick is the commit (Airtable).
2606
+ defaultOpen: true,
2607
+ onOpenChange: (open) => {
2608
+ if (!open) onClose?.();
2609
+ }
2610
+ } : {},
2454
2611
  children: [
2455
2612
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_design_system8.SelectTrigger, { id, size: "sm", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_design_system8.SelectValue, { placeholder: "Choose" }) }),
2456
2613
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_design_system8.SelectContent, { children: options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_design_system8.SelectItem, { value: (0, import_records5.optionKey)(option), children: titleOf(option) }, option.id)) })
@@ -2570,14 +2727,20 @@ async function personRecordForMember(client, personTableId, people, member) {
2570
2727
  });
2571
2728
  return written;
2572
2729
  }
2573
- function PersonPicker({ field: field2, value, onChange, disabled, id, className }) {
2730
+ function PersonPicker({ field: field2, value, onChange, disabled, id, className, autoOpen, onClose }) {
2574
2731
  const client = (0, import_react13.useOptionalRecordsClient)();
2575
2732
  const host = useRecordsUi();
2576
2733
  const [members, setMembers] = (0, import_react12.useState)(null);
2577
2734
  const [people, setPeople] = (0, import_react12.useState)([]);
2578
2735
  const [error, setError] = (0, import_react12.useState)(null);
2579
2736
  const [search, setSearch] = (0, import_react12.useState)("");
2580
- const [open, setOpen] = (0, import_react12.useState)(false);
2737
+ const [open, setOpenState] = (0, import_react12.useState)(autoOpen === true);
2738
+ const onCloseRef = (0, import_react12.useRef)(onClose);
2739
+ onCloseRef.current = onClose;
2740
+ const setOpen = (0, import_react12.useCallback)((next) => {
2741
+ setOpenState(next);
2742
+ if (!next) onCloseRef.current?.();
2743
+ }, []);
2581
2744
  const [busy, setBusy] = (0, import_react12.useState)(false);
2582
2745
  const membersPort = host.members;
2583
2746
  const personTable = field2.relation_target;
@@ -2726,6 +2889,7 @@ var import_react19 = require("react");
2726
2889
  var import_core3 = require("@ai-matrx/records/core");
2727
2890
  var import_react20 = require("@ai-matrx/records/react");
2728
2891
  var import_design_system12 = require("@ai-matrx/design-system");
2892
+ var import_records6 = require("@ai-matrx/records");
2729
2893
 
2730
2894
  // src/whoChangedSource.tsx
2731
2895
  var import_react14 = require("react");
@@ -2843,7 +3007,7 @@ function useWhoChanged(fieldKey, recordId) {
2843
3007
 
2844
3008
  // src/values.tsx
2845
3009
  var import_data_table4 = require("@ai-matrx/design-system/data-table");
2846
- var import_field_formats5 = require("@ai-matrx/design-system/field-formats");
3010
+ var import_field_formats6 = require("@ai-matrx/design-system/field-formats");
2847
3011
 
2848
3012
  // src/withheld.tsx
2849
3013
  var import_jsx_runtime12 = require("react/jsx-runtime");
@@ -3373,32 +3537,61 @@ function renderValue(field2, value, document2, labels, stillWaitingFor) {
3373
3537
  if (whole) return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(WholeValueCell, { words: value, pointer: whole });
3374
3538
  const kind = editorKindFor(field2);
3375
3539
  const chosen = kind === "select" || kind === "multi_select";
3376
- const tintOf = (item) => {
3377
- if (!chosen) return void 0;
3540
+ const chipOf = (item) => {
3378
3541
  const color = storedChoiceColor(field2, item) ?? storedChoiceColor(field2, scalarText(field2, item, labels));
3379
- return color ? import_data_table4.CELL_TINT_CLASS[color] : void 0;
3542
+ return color ? { className: import_data_table4.CHOICE_CHIP_CLASS[color], tinted: true } : { className: import_data_table4.NEUTRAL_CHIP_CLASS, tinted: false };
3380
3543
  };
3381
- if (Array.isArray(value)) {
3382
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "flex flex-wrap gap-1", children: value.map((item, i) => {
3383
- const tint2 = tintOf(item);
3544
+ if (chosen) {
3545
+ const items = Array.isArray(value) ? value : [value];
3546
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "flex flex-wrap gap-1", "data-records-choice-chips": "", children: items.map((item, i) => {
3547
+ const chip = chipOf(item);
3384
3548
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3385
3549
  import_design_system11.Badge,
3386
3550
  {
3387
- variant: "secondary",
3388
- className: (0, import_design_system11.cn)("text-[11px]", tint2),
3389
- ...tint2 ? { "data-records-choice-tint": "" } : {},
3390
- children: scalarText(field2, item, labels)
3551
+ variant: "outline",
3552
+ className: (0, import_design_system11.cn)("text-[11px]", chip.className),
3553
+ "data-records-choice-chip": "",
3554
+ ...chip.tinted ? { "data-records-choice-tint": "" } : {},
3555
+ children: scalarText(field2, item, labels, Array.isArray(value) ? void 0 : stillWaitingFor)
3391
3556
  },
3392
3557
  `${String(item)}-${i}`
3393
3558
  );
3394
3559
  }) });
3395
3560
  }
3396
- const tint = tintOf(value);
3397
- if (tint) {
3398
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_design_system11.Badge, { variant: "secondary", className: (0, import_design_system11.cn)("text-[11px]", tint), "data-records-choice-tint": "", children: scalarText(field2, value, labels, stillWaitingFor) });
3561
+ if (Array.isArray(value)) {
3562
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "flex flex-wrap gap-1", children: value.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_design_system11.Badge, { variant: "secondary", className: "text-[11px]", children: scalarText(field2, item, labels) }, `${String(item)}-${i}`)) });
3399
3563
  }
3400
3564
  if (kind === "url" && typeof value === "string") {
3401
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("a", { className: "truncate text-primary underline-offset-2 hover:underline", href: value, target: "_blank", rel: "noreferrer", children: value });
3565
+ const href = value.includes("://") ? value : `https://${value}`;
3566
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
3567
+ "a",
3568
+ {
3569
+ className: "inline-flex min-w-0 max-w-full items-center gap-1 rounded-md border border-border bg-muted/60 px-1.5 py-0.5 text-[11px] text-foreground hover:bg-accent",
3570
+ href,
3571
+ target: "_blank",
3572
+ rel: "noreferrer",
3573
+ title: value,
3574
+ "data-records-link-chip": "",
3575
+ onClick: (event) => event.stopPropagation(),
3576
+ children: [
3577
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { "aria-hidden": "true", className: "text-muted-foreground", children: "\u2197" }),
3578
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "truncate", children: linkChipText(value) })
3579
+ ]
3580
+ }
3581
+ );
3582
+ }
3583
+ if (kind === "phone" && typeof value === "string" && value.trim() !== "") {
3584
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3585
+ "a",
3586
+ {
3587
+ className: "truncate tabular-nums text-primary underline-offset-2 hover:underline",
3588
+ href: `tel:${value.replace(/[^\d+]/g, "")}`,
3589
+ title: value,
3590
+ "data-records-phone": "",
3591
+ onClick: (event) => event.stopPropagation(),
3592
+ children: formatPhone(value)
3593
+ }
3594
+ );
3402
3595
  }
3403
3596
  if (kind === "email" && typeof value === "string") {
3404
3597
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("a", { className: "truncate text-primary underline-offset-2 hover:underline", href: `mailto:${value}`, children: value });
@@ -3417,7 +3610,7 @@ function scalarText(field2, value, labels, stillWaitingFor) {
3417
3610
  }
3418
3611
  const shownAs = shownAsOf(field2);
3419
3612
  if (shownAs) {
3420
- const said = (0, import_field_formats5.formatFieldValue)(value, shownAs);
3613
+ const said = (0, import_field_formats6.formatFieldValue)(value, shownAs);
3421
3614
  if (said.ok && !said.empty) return said.text;
3422
3615
  }
3423
3616
  if (typeof value === "number") {
@@ -3478,7 +3671,6 @@ function cardWords(record, field2, labels) {
3478
3671
 
3479
3672
  // src/gridEditing.tsx
3480
3673
  var import_jsx_runtime16 = require("react/jsx-runtime");
3481
- var TYPED_KINDS = /* @__PURE__ */ new Set(["text", "long_text", "email", "url", "phone", "number", "currency", "percent"]);
3482
3674
  function asksWhoChanged(event) {
3483
3675
  return event.altKey && event.shiftKey && !event.metaKey && !event.ctrlKey && (event.code === "KeyH" || event.key.toLowerCase() === "h");
3484
3676
  }
@@ -3515,7 +3707,9 @@ function rememberVersion(cache, recordId, version) {
3515
3707
  const held = cache.get(recordId);
3516
3708
  if (held === void 0 || version > held) cache.set(recordId, version);
3517
3709
  }
3518
- function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow, onWritten }) {
3710
+ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow, onWritten, onEnded }) {
3711
+ const endedRef = (0, import_react19.useRef)(onEnded);
3712
+ endedRef.current = onEnded;
3519
3713
  const client = (0, import_react20.useRecordsClient)();
3520
3714
  const [editing, setEditing] = (0, import_react19.useState)(null);
3521
3715
  const [draft, setDraft] = (0, import_react19.useState)(null);
@@ -3561,6 +3755,24 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow,
3561
3755
  (rowId, key) => refusals[cellKey(rowId, key)] ?? null,
3562
3756
  [refusals]
3563
3757
  );
3758
+ const settling = (0, import_react19.useRef)(/* @__PURE__ */ new Map());
3759
+ (0, import_react19.useEffect)(() => {
3760
+ if (settling.current.size === 0) return;
3761
+ const released = [];
3762
+ for (const [id, before] of settling.current) {
3763
+ const [rowId, key] = id.split("::");
3764
+ const row = rows.find((r) => r.id === rowId);
3765
+ if (!row) continue;
3766
+ if (!sameValue(row.document?.[key], before)) released.push(id);
3767
+ }
3768
+ if (released.length === 0) return;
3769
+ for (const id of released) settling.current.delete(id);
3770
+ setOptimistic((held) => {
3771
+ const next = { ...held };
3772
+ for (const id of released) delete next[id];
3773
+ return next;
3774
+ });
3775
+ }, [rows]);
3564
3776
  const shownValue2 = (0, import_react19.useCallback)(
3565
3777
  (rowId, key) => {
3566
3778
  const held = optimistic[cellKey(rowId, key)];
@@ -3579,9 +3791,8 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow,
3579
3791
  if (editing && editing.rowId === address.rowId && editing.key === address.key) return;
3580
3792
  const current = shownValue2(address.rowId, address.key);
3581
3793
  const seedField = typeof seed === "string" && seed !== "" ? fields.find((f) => f.key === address.key) : void 0;
3582
- const seedKind = seedField ? String(editorKindFor(seedField)) : "";
3583
- const numeric = seedKind === "number" || seedKind === "currency" || seedKind === "percent";
3584
- const seeded = seedField && TYPED_KINDS.has(seedKind) ? numeric ? Number.isFinite(Number(seed)) ? Number(seed) : void 0 : seed : void 0;
3794
+ const seeded = seedField ? draftFromTyping(seedField, seed) : void 0;
3795
+ if (seedField && seeded === void 0) return;
3585
3796
  if (seeded !== void 0) {
3586
3797
  startedFrom.current = current;
3587
3798
  startedStored.current = current;
@@ -3632,7 +3843,11 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow,
3632
3843
  },
3633
3844
  [writable, client, editing, shownValue2, optimistic, rows, host, fields]
3634
3845
  );
3635
- const cancel = (0, import_react19.useCallback)(() => {
3846
+ const openRef = (0, import_react19.useRef)(null);
3847
+ openRef.current = editing;
3848
+ const cancel = (0, import_react19.useCallback)((via = "key") => {
3849
+ const open = openRef.current;
3850
+ if (open && via !== "blur") endedRef.current?.("stay", open, via === "key" ? "keyboard" : "pointer");
3636
3851
  setEditing(null);
3637
3852
  setDraft(null);
3638
3853
  typed.current = null;
@@ -3674,6 +3889,7 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow,
3674
3889
  });
3675
3890
  if (written.ok) {
3676
3891
  rememberVersion(versions.current, rowId, written.data);
3892
+ settling.current.set(id, base);
3677
3893
  writtenRef.current?.({ rowId, key, before: base, after: value });
3678
3894
  reload();
3679
3895
  return;
@@ -3721,11 +3937,25 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow,
3721
3937
  [client, reload]
3722
3938
  );
3723
3939
  const commit = (0, import_react19.useCallback)(
3724
- (move = null) => {
3940
+ (move = null, via = "key") => {
3725
3941
  const open = editing;
3726
3942
  if (!open) return;
3727
- const value = typed.current;
3943
+ let value = typed.current;
3944
+ if (isTypedDraft(value)) {
3945
+ if (value.judged === null || "refusal" in value.judged) return false;
3946
+ value = value.judged.value;
3947
+ }
3728
3948
  if (!sameValue(startedFrom.current, value)) void writeCell(open.rowId, open.key, value, startedStored.current);
3949
+ const ended = endedRef.current;
3950
+ if (ended && via !== "blur") {
3951
+ setEditing(null);
3952
+ setDraft(null);
3953
+ typed.current = null;
3954
+ startedFrom.current = null;
3955
+ const exit = move === "next" ? "right" : move === "previous" ? "left" : via === "pick" ? "stay" : "down";
3956
+ ended(exit, open, via === "pick" ? "pointer" : "keyboard");
3957
+ return;
3958
+ }
3729
3959
  if (!move) {
3730
3960
  setEditing(null);
3731
3961
  setDraft(null);
@@ -3937,6 +4167,7 @@ function GridCell({
3937
4167
  const open = Boolean(
3938
4168
  editing && editing.editing?.rowId === row.id && editing.editing.key === field2.key
3939
4169
  );
4170
+ const wasActive = (0, import_react19.useRef)(false);
3940
4171
  const value = editing ? editing.valueFor(row, field2) : row.document?.[field2.key];
3941
4172
  const state = editing ? editing.stateOf(row.id, field2.key) : null;
3942
4173
  const refusal2 = editing ? editing.refusalOf(row.id, field2.key) : null;
@@ -3948,8 +4179,8 @@ function GridCell({
3948
4179
  field: field2,
3949
4180
  draft: editing.draft,
3950
4181
  onType: editing.type,
3951
- onCommit: (move) => editing.commit(move),
3952
- onCancel: editing.cancel,
4182
+ onCommit: (move, via) => editing.commit(move, via),
4183
+ onCancel: (via) => editing.cancel(via),
3953
4184
  below: (
3954
4185
  // KEPT EDITING: the refusal stays beside the value it is about, so the
3955
4186
  // person reads what to change while changing it. Keep editing is the
@@ -3998,6 +4229,17 @@ function GridCell({
3998
4229
  event.stopPropagation();
3999
4230
  editing?.begin({ rowId: row.id, key: field2.key });
4000
4231
  };
4232
+ const onPointerDown = (event) => {
4233
+ wasActive.current = event.currentTarget.closest("td")?.hasAttribute("data-matrx-cell-active") === true;
4234
+ };
4235
+ const onCellClick = (event) => {
4236
+ if (!editable) return;
4237
+ if (event.detail === 0) {
4238
+ openThisCell(event);
4239
+ return;
4240
+ }
4241
+ if (wasActive.current && opensAsAList(field2)) openThisCell(event);
4242
+ };
4001
4243
  return (
4002
4244
  // BOTH MARKERS, ON PURPOSE. `data-matrx-cell-control` is the data table's
4003
4245
  // opt-out from whole-row click, so opening a cell never also opens the
@@ -4012,62 +4254,65 @@ function GridCell({
4012
4254
  "data-matrx-cell-fill": "",
4013
4255
  className: "group/cell relative flex w-full min-w-0 flex-col gap-0.5",
4014
4256
  children: [
4015
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4016
- "span",
4017
- {
4018
- role: editable ? "button" : void 0,
4019
- "aria-label": editable ? `Edit ${fieldName(field2)}` : void 0,
4020
- title: editable ? `${fieldName(field2)} \u2014 click or press Enter to edit` : fieldIsEditable(field2) ? (
4021
- // NOT A DEAD CONTROL AND NOT A SILENT ONE: the cell is plain
4022
- // text, and hovering it says exactly why, in the level's own
4023
- // words. The editor was never offered, so nothing is refused
4024
- // after it has been typed into.
4025
- `${fieldName(field2)} \u2014 ${whyNot ?? "you cannot change this record"}`
4026
- ) : `${fieldName(field2)} is worked out by the system, so it cannot be typed in`,
4027
- className: (0, import_design_system12.cn)(
4028
- // `w-full` so the whole cell is the control. A gap beside it is bare
4029
- // table cell, and a click there is a row click.
4030
- "block w-full min-w-0 truncate rounded px-1 py-0.5",
4031
- editable ? "cursor-text hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" : "",
4032
- state === "saving" ? "opacity-60" : "",
4033
- state === "refused" ? "ring-1 ring-destructive" : ""
4034
- ),
4035
- onClick: editable ? openThisCell : void 0,
4036
- onDoubleClick: (event) => {
4037
- event.preventDefault();
4038
- event.stopPropagation();
4039
- if (editable) editing?.begin({ rowId: row.id, key: field2.key });
4040
- },
4041
- onKeyDown: (event) => {
4042
- if (editable && (event.key === "Enter" || event.key === "F2")) {
4043
- event.preventDefault();
4044
- editing?.begin({ rowId: row.id, key: field2.key });
4045
- return;
4046
- }
4047
- if (onAskWhoChanged && asksWhoChanged(event)) {
4048
- event.preventDefault();
4049
- onAskWhoChanged(field2.key, row.id);
4050
- return;
4051
- }
4052
- if (editable && isTypedCharacter(event)) {
4257
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: "flex w-full min-w-0 items-center gap-1", children: [
4258
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4259
+ "span",
4260
+ {
4261
+ role: editable ? "button" : void 0,
4262
+ "aria-label": editable ? `Edit ${fieldName(field2)}` : void 0,
4263
+ title: editable ? `${fieldName(field2)} \u2014 double-click or press Enter to edit` : fieldIsEditable(field2) ? (
4264
+ // NOT A DEAD CONTROL AND NOT A SILENT ONE: the cell is plain
4265
+ // text, and hovering it says exactly why, in the level's own
4266
+ // words. The editor was never offered, so nothing is refused
4267
+ // after it has been typed into.
4268
+ `${fieldName(field2)} \u2014 ${whyNot ?? "you cannot change this record"}`
4269
+ ) : `${fieldName(field2)} is worked out by the system, so it cannot be typed in`,
4270
+ className: (0, import_design_system12.cn)(
4271
+ // `w-full` so the whole cell is the control. A gap beside it is bare
4272
+ // table cell, and a click there is a row click.
4273
+ "block w-full min-w-0 flex-1 truncate rounded px-1 py-0.5",
4274
+ editable ? "cursor-text hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" : "",
4275
+ state === "saving" ? "opacity-60" : "",
4276
+ state === "refused" ? "ring-1 ring-destructive" : ""
4277
+ ),
4278
+ onMouseDown: onPointerDown,
4279
+ onClick: editable ? onCellClick : void 0,
4280
+ onDoubleClick: (event) => {
4053
4281
  event.preventDefault();
4054
- editing?.begin({ rowId: row.id, key: field2.key }, event.key);
4055
- }
4056
- },
4057
- tabIndex: editable || onAskWhoChanged ? 0 : void 0,
4058
- children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4059
- RecordValue,
4060
- {
4061
- field: field2,
4062
- value,
4063
- document: row.document,
4064
- withheld: withheldNoticeOf(row.hidden, field2.key),
4065
- ...format ? { format } : {}
4066
- }
4067
- )
4068
- }
4069
- ),
4070
- onAskWhoChanged ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(WhoBadge, { field: field2, row, onAskWhoChanged }) : null,
4282
+ event.stopPropagation();
4283
+ if (editable) editing?.begin({ rowId: row.id, key: field2.key });
4284
+ },
4285
+ onKeyDown: (event) => {
4286
+ if (editable && (event.key === "Enter" || event.key === "F2")) {
4287
+ event.preventDefault();
4288
+ editing?.begin({ rowId: row.id, key: field2.key });
4289
+ return;
4290
+ }
4291
+ if (onAskWhoChanged && asksWhoChanged(event)) {
4292
+ event.preventDefault();
4293
+ onAskWhoChanged(field2.key, row.id);
4294
+ return;
4295
+ }
4296
+ if (editable && isTypedCharacter(event) && typingOpens(field2) !== "none") {
4297
+ event.preventDefault();
4298
+ editing?.begin({ rowId: row.id, key: field2.key }, event.key);
4299
+ }
4300
+ },
4301
+ tabIndex: editable || onAskWhoChanged ? 0 : void 0,
4302
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4303
+ RecordValue,
4304
+ {
4305
+ field: field2,
4306
+ value,
4307
+ document: row.document,
4308
+ withheld: withheldNoticeOf(row.hidden, field2.key),
4309
+ ...format ? { format } : {}
4310
+ }
4311
+ )
4312
+ }
4313
+ ),
4314
+ onAskWhoChanged ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(WhoBadge, { field: field2, row, onAskWhoChanged }) : null
4315
+ ] }),
4071
4316
  state === "saving" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "px-1 text-[10px] text-muted-foreground", children: "Saving\u2026" }) : null,
4072
4317
  refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(CellRefusalNotice, { refusal: refusal2, field: field2, row, editing, typed: true }) : null
4073
4318
  ]
@@ -4122,9 +4367,10 @@ function WhoBadge({
4122
4367
  "button",
4123
4368
  {
4124
4369
  type: "button",
4370
+ "data-records-who-badge": "",
4125
4371
  "aria-label": `Who changed ${fieldName(field2)}?`,
4126
4372
  title: who.sentence ?? `Who changed ${fieldName(field2)}? (press H)`,
4127
- className: "pointer-events-auto absolute right-0 top-0 max-w-[9rem] truncate rounded px-1 text-[10px] text-muted-foreground opacity-0 transition-opacity hover:bg-muted focus-visible:opacity-100 group-focus-within/cell:opacity-100 group-hover/cell:opacity-100",
4373
+ className: "hidden max-w-[9rem] shrink-0 select-none truncate rounded px-1 text-[10px] leading-4 text-muted-foreground hover:bg-muted focus-visible:inline-flex in-data-[matrx-cell-active=true]:inline-flex",
4128
4374
  onMouseEnter: ask,
4129
4375
  onFocus: ask,
4130
4376
  onClick: (event) => {
@@ -4155,6 +4401,12 @@ function closesOnBlur(field2) {
4155
4401
  return true;
4156
4402
  }
4157
4403
  }
4404
+ function opensAsAList(field2) {
4405
+ const kind = editorKindFor(field2);
4406
+ if (kind === "member") return true;
4407
+ if (kind === "select" || kind === "multi_select") return !(CHOICE_EDITOR_ADDS_WHAT_IS_TYPED && (0, import_records6.allowsOtherValues)(field2));
4408
+ return false;
4409
+ }
4158
4410
  function takesANewline(field2) {
4159
4411
  const kind = editorKindFor(field2);
4160
4412
  return kind === "long_text" || kind === "json";
@@ -4168,12 +4420,13 @@ function OpenCell({
4168
4420
  below
4169
4421
  }) {
4170
4422
  const multiline = takesANewline(field2);
4171
- const blurCommits = closesOnBlur(field2);
4423
+ const picker = opensAsAList(field2);
4424
+ const blurCommits = !picker && closesOnBlur(field2);
4172
4425
  const done = (0, import_react19.useRef)(false);
4173
4426
  const once = (run) => {
4174
4427
  if (done.current) return;
4175
4428
  done.current = true;
4176
- run();
4429
+ if (run() === false) done.current = false;
4177
4430
  };
4178
4431
  const host = (0, import_react19.useRef)(null);
4179
4432
  (0, import_react19.useEffect)(() => {
@@ -4190,7 +4443,7 @@ function OpenCell({
4190
4443
  const blur = blurCommits ? {
4191
4444
  onBlur: (event) => {
4192
4445
  if (event.currentTarget.contains(event.relatedTarget)) return;
4193
- once(() => onCommit(null));
4446
+ once(() => onCommit(null, "blur"));
4194
4447
  }
4195
4448
  } : {};
4196
4449
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
@@ -4205,28 +4458,37 @@ function OpenCell({
4205
4458
  if (event.key === "Escape") {
4206
4459
  event.preventDefault();
4207
4460
  event.stopPropagation();
4208
- once(onCancel);
4461
+ once(() => onCancel("key"));
4209
4462
  return;
4210
4463
  }
4211
4464
  if (event.key === "Tab") {
4212
4465
  event.preventDefault();
4213
4466
  event.stopPropagation();
4214
- once(() => onCommit(event.shiftKey ? "previous" : "next"));
4467
+ once(() => onCommit(event.shiftKey ? "previous" : "next", "key"));
4215
4468
  return;
4216
4469
  }
4217
4470
  if (event.key === "Enter") {
4218
4471
  if (multiline && event.shiftKey && !event.metaKey && !event.ctrlKey) return;
4219
4472
  event.preventDefault();
4220
4473
  event.stopPropagation();
4221
- once(() => onCommit(null));
4474
+ once(() => onCommit(null, "key"));
4222
4475
  }
4223
4476
  },
4224
4477
  ...blur,
4225
4478
  children: [
4226
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(FieldControl, { field: field2, value: draft, onChange: onType, id: `cell-${field2.id}` }),
4227
- blurCommits ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-[10px] text-muted-foreground", children: multiline ? "\u23CE saves \xB7 \u21E7\u23CE new line \xB7 Esc cancels \xB7 Tab next" : "\u23CE saves \xB7 Esc cancels \xB7 Tab next" }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: "flex items-center gap-1", children: [
4228
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system12.Button, { size: "sm", onClick: () => once(() => onCommit(null)), children: "Save" }),
4229
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system12.Button, { size: "sm", variant: "ghost", onClick: () => once(onCancel), children: "Cancel" })
4479
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4480
+ FieldControl,
4481
+ {
4482
+ field: field2,
4483
+ value: draft,
4484
+ onChange: onType,
4485
+ id: `cell-${field2.id}`,
4486
+ ...picker ? { autoOpen: true, onClose: () => once(() => onCommit(null, "pick")) } : {}
4487
+ }
4488
+ ),
4489
+ picker ? null : blurCommits ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-[10px] text-muted-foreground", children: multiline ? "\u23CE saves \xB7 \u21E7\u23CE new line \xB7 Esc cancels \xB7 Tab next" : "\u23CE saves \xB7 Esc cancels \xB7 Tab next" }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: "flex items-center gap-1", children: [
4490
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system12.Button, { size: "sm", onClick: () => once(() => onCommit(null, "pick")), children: "Save" }),
4491
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system12.Button, { size: "sm", variant: "ghost", onClick: () => once(() => onCancel("pick")), children: "Cancel" })
4230
4492
  ] }),
4231
4493
  below
4232
4494
  ]
@@ -4237,8 +4499,9 @@ function OpenCell({
4237
4499
  // src/Grid.tsx
4238
4500
  var import_react57 = require("react");
4239
4501
  var import_react58 = require("@ai-matrx/records/react");
4240
- var import_records14 = require("@ai-matrx/records");
4502
+ var import_records15 = require("@ai-matrx/records");
4241
4503
  var import_data_table9 = require("@ai-matrx/design-system/data-table");
4504
+ var import_core8 = require("@ai-matrx/records/core");
4242
4505
 
4243
4506
  // src/archiveWords.ts
4244
4507
  function archiveConfirmSentence(what) {
@@ -4284,10 +4547,10 @@ var import_design_system17 = require("@ai-matrx/design-system");
4284
4547
  // src/FormulaTextEditor.tsx
4285
4548
  var import_react23 = require("react");
4286
4549
  var import_react24 = require("@ai-matrx/records/react");
4287
- var import_records6 = require("@ai-matrx/records");
4550
+ var import_records7 = require("@ai-matrx/records");
4288
4551
  var import_design_system13 = require("@ai-matrx/design-system");
4289
4552
  var import_jsx_runtime17 = require("react/jsx-runtime");
4290
- var FORMULA_FUNCTIONS = import_records6.RULE_NODE_KINDS.map((row) => row.node).filter((node) => node.startsWith("fx.")).map((node) => node.slice(3).toUpperCase()).filter((name) => !["ADD", "SUB", "MUL", "DIV", "EQ", "NEQ", "GT", "GTE", "LT", "LTE", "NEG", "FIELD", "CONST"].includes(name)).sort();
4553
+ var FORMULA_FUNCTIONS = import_records7.RULE_NODE_KINDS.map((row) => row.node).filter((node) => node.startsWith("fx.")).map((node) => node.slice(3).toUpperCase()).filter((name) => !["ADD", "SUB", "MUL", "DIV", "EQ", "NEQ", "GT", "GTE", "LT", "LTE", "NEG", "FIELD", "CONST"].includes(name)).sort();
4291
4554
  var RESULT_WORD = {
4292
4555
  number: "a number",
4293
4556
  text: "text",
@@ -4620,7 +4883,7 @@ function ChoiceColorsEditor({
4620
4883
 
4621
4884
  // src/FieldFormatPicker.tsx
4622
4885
  var import_design_system16 = require("@ai-matrx/design-system");
4623
- var import_field_formats6 = require("@ai-matrx/design-system/field-formats");
4886
+ var import_field_formats7 = require("@ai-matrx/design-system/field-formats");
4624
4887
  var import_jsx_runtime20 = require("react/jsx-runtime");
4625
4888
  var KIND_WORD = {
4626
4889
  choice: "select",
@@ -4683,7 +4946,7 @@ function formatMenu(field2) {
4683
4946
  if (base === null) return { groups: [], otherKinds: [] };
4684
4947
  if (base === "formula") {
4685
4948
  const groups2 = [];
4686
- for (const def of import_field_formats6.FIELD_FORMAT_LIST) {
4949
+ for (const def of import_field_formats7.FIELD_FORMAT_LIST) {
4687
4950
  if (neverALook(def) || KIND_WORD[def.id] || def.id === "boolean") continue;
4688
4951
  const bucket = groups2.find((g) => g.group === def.group);
4689
4952
  if (bucket) bucket.formats.push(def);
@@ -4691,10 +4954,10 @@ function formatMenu(field2) {
4691
4954
  }
4692
4955
  return { groups: groups2, otherKinds: [] };
4693
4956
  }
4694
- const groups = (0, import_field_formats6.groupedFormatsForBase)(base).map((g) => ({ group: g.group, formats: g.formats.filter((d) => !neverALook(d) && !KIND_WORD[d.id]) })).filter((g) => g.formats.length > 0);
4957
+ const groups = (0, import_field_formats7.groupedFormatsForBase)(base).map((g) => ({ group: g.group, formats: g.formats.filter((d) => !neverALook(d) && !KIND_WORD[d.id]) })).filter((g) => g.formats.length > 0);
4695
4958
  const own2 = new Set(groups.flatMap((g) => g.formats.map((d) => d.id)));
4696
4959
  const otherKinds = [];
4697
- for (const def of import_field_formats6.FIELD_FORMAT_LIST) {
4960
+ for (const def of import_field_formats7.FIELD_FORMAT_LIST) {
4698
4961
  if (own2.has(def.id) || neverALook(def)) continue;
4699
4962
  const word = KIND_WORD[def.id] ?? PLAIN_WORD[def.base];
4700
4963
  if (word) otherKinds.push({ def, word });
@@ -4703,7 +4966,7 @@ function formatMenu(field2) {
4703
4966
  }
4704
4967
  function startingChoice(field2) {
4705
4968
  const held = displayFormatOf(field2);
4706
- if (!held || !(0, import_field_formats6.getFieldFormat)(held.id) || held.id === "formula") return { format: null, retype: null };
4969
+ if (!held || !(0, import_field_formats7.getFieldFormat)(held.id) || held.id === "formula") return { format: null, retype: null };
4707
4970
  return {
4708
4971
  format: Object.keys(held.options).length > 0 ? { id: held.id, options: held.options } : { id: held.id },
4709
4972
  retype: null
@@ -4733,7 +4996,7 @@ function FieldFormatPicker({
4733
4996
  }) {
4734
4997
  const { groups, otherKinds } = formatMenu(field2);
4735
4998
  if (groups.length === 0 && otherKinds.length === 0) return null;
4736
- const active = value.format ? (0, import_field_formats6.getFieldFormat)(value.format.id) : null;
4999
+ const active = value.format ? (0, import_field_formats7.getFieldFormat)(value.format.id) : null;
4737
5000
  const options = value.format?.options ?? {};
4738
5001
  const optionKeys = active?.optionKeys ?? [];
4739
5002
  const retypedTo = value.retype ? otherKinds.find((k) => k.def.id === value.format?.id) : void 0;
@@ -4897,125 +5160,474 @@ function FieldFormatPicker({
4897
5160
  ] });
4898
5161
  }
4899
5162
 
4900
- // src/FieldEditor.tsx
4901
- var import_records7 = require("@ai-matrx/records");
4902
- var import_jsx_runtime21 = require("react/jsx-runtime");
4903
- var RULE_KINDS = FIELD_RULE_KIND_VALUES.map((kind) => ({ kind, label: FIELD_RULE_KIND_LABEL[kind] }));
4904
- var SENSITIVITIES = import_records7.FIELD_SENSITIVITIES.map((value) => ({ value, label: FIELD_SENSITIVITY_LABEL[value] }));
4905
- var AGENT_VISIBILITY = import_records7.CONTEXT_POLICIES.map((value) => ({ value, label: CONTEXT_POLICY_LABEL[value] }));
4906
- var ROLLUP_OPERATIONS = ROLLUP_AGG_VALUES.map((value) => ({
4907
- value,
4908
- label: ROLLUP_AGG_LABEL[value]
4909
- }));
4910
- var FORMULA_SEPARATOR_LABEL = "Joined by";
4911
- var EDITING_A_WORKED_OUT_COLUMN = "This column works out its answer from the columns picked when it was made. To change what it works out, write it as a formula below \u2014 the store checks it as you type, and a rename alone leaves it exactly as it is.";
4912
- var FORMULA_DEFAULT_SEPARATOR = " \u2014 ";
4913
- var FORMULA_OPERATIONS = FORMULA_OP_VALUES.map((value) => ({
4914
- value,
4915
- label: FORMULA_OP_LABEL[value],
4916
- joins: FORMULA_OP_JOINS[value]
4917
- }));
4918
- function keyFor(label) {
4919
- const token = label.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 48);
4920
- if (token === "") return "";
4921
- return /^[a-z]/.test(token) ? token : `f_${token}`;
5163
+ // src/gridPage.ts
5164
+ var import_records8 = require("@ai-matrx/records");
5165
+ var import_data_table6 = require("@ai-matrx/design-system/data-table");
5166
+ function sortRefusal(f) {
5167
+ if (!computedOnRead(f)) return null;
5168
+ return `${fieldName(f)} is worked out each time it is read, so the store keeps no value to sort the whole table by. Sort by a column it is worked out from.`;
4922
5169
  }
4923
- var COLUMN_DID_NOT_LAND_SENTENCE = "The store accepted this column and the table does not have it yet. Nothing was lost \u2014 your record values are untouched \u2014 but the column is not there, so this panel is staying open.";
4924
- var COLUMN_DID_NOT_LAND_REMEDY = "Press Save again; if it keeps happening, tell us, because the store and the table disagree and that is ours to fix.";
4925
- var COLUMN_DID_NOT_LAND = `${COLUMN_DID_NOT_LAND_SENTENCE} ${COLUMN_DID_NOT_LAND_REMEDY}`;
4926
- function FieldEditor({ tableId, field: field2, onSaved, onCancel, onRemoved, className }) {
4927
- const table = (0, import_react30.useTable)(tableId);
4928
- const fields = (0, import_react30.useFields)(tableId);
4929
- const rights = useTableRights(table.data);
4930
- const mutation = (0, import_react30.useFieldMutation)();
4931
- const client = (0, import_react30.useRecordsClient)();
4932
- const [label, setLabel] = (0, import_react29.useState)(field2?.label ?? "");
4933
- const [type, setType] = (0, import_react29.useState)(() => startingType(field2));
4934
- const [required, setRequired] = (0, import_react29.useState)(field2?.required ?? false);
4935
- const [multi, setMulti] = (0, import_react29.useState)(field2?.multi ?? false);
4936
- const [dated, setDated] = (0, import_react29.useState)(field2?.dated ?? false);
4937
- const [sensitivity, setSensitivity] = (0, import_react29.useState)(String(field2?.sensitivity ?? "internal"));
4938
- const [contextPolicy, setContextPolicy] = (0, import_react29.useState)(String(field2?.context_policy ?? "include"));
4939
- const [rules, setRules] = (0, import_react29.useState)(field2?.rules ?? []);
4940
- const [allowOther, setAllowOther] = (0, import_react29.useState)(field2?.config?.["allow_other"] === true);
4941
- const [optionText, setOptionText] = (0, import_react29.useState)("");
4942
- const [unit, setUnit] = (0, import_react29.useState)(field2?.unit ?? "$");
4943
- const [withTime, setWithTime] = (0, import_react29.useState)(String(field2?.config?.["kind"] ?? "date") === "datetime");
4944
- const [via, setVia] = (0, import_react29.useState)(String(field2?.config?.["via"] ?? ""));
4945
- const [pick, setPick] = (0, import_react29.useState)(String(field2?.config?.["pick"] ?? ""));
4946
- const [agg, setAgg] = (0, import_react29.useState)(
4947
- field2?.config?.["agg"] ?? "count"
4948
- );
4949
- const [of, setOf] = (0, import_react29.useState)(String(field2?.config?.["of"] ?? ""));
4950
- const [relationTarget, setRelationTarget] = (0, import_react29.useState)(String(field2?.relation_target ?? ""));
4951
- const [display, setDisplay] = (0, import_react29.useState)(field2?.display ?? null);
4952
- const [formulaOp, setFormulaOp] = (0, import_react29.useState)(() => formulaOpOf(field2));
4953
- const [formulaFields, setFormulaFields] = (0, import_react29.useState)(() => formulaFieldIdsOf(field2));
4954
- const [formulaSeparator, setFormulaSeparator] = (0, import_react29.useState)(() => formulaSeparatorOf(field2));
4955
- const [allowedTypes, setAllowedTypes] = (0, import_react29.useState)(() => entityReferenceKindsOf(field2));
4956
- const startingFormulaText = String(field2?.config?.["formula_text"] ?? "");
4957
- const [formulaText, setFormulaText] = (0, import_react29.useState)(startingFormulaText);
4958
- const [formulaWritten, setFormulaWritten] = (0, import_react29.useState)(() => startingFormulaText.trim() !== "" || Boolean(field2));
4959
- const [formulaOk, setFormulaOk] = (0, import_react29.useState)(null);
4960
- const [askingToRemove, setAskingToRemove] = (0, import_react29.useState)(false);
4961
- const [choiceRows, setChoiceRows] = (0, import_react29.useState)(null);
4962
- const [showsAs, setShowsAs] = (0, import_react29.useState)(() => field2 ? startingChoice(field2) : { format: null, retype: null });
4963
- const [backfillError, setBackfillError] = (0, import_react29.useState)(null);
4964
- const [notLanded, setNotLanded] = (0, import_react29.useState)(false);
4965
- const others = (0, import_react29.useMemo)(
4966
- () => (fields.data ?? []).filter((f) => f.id !== field2?.id),
4967
- [fields.data, field2?.id]
4968
- );
4969
- const relations = (0, import_react29.useMemo)(() => others.filter((f) => f.type === "relation"), [others]);
4970
- const choice = fieldTypeChoice(type);
4971
- const editing = Boolean(field2);
4972
- if (!rights.structure) {
4973
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: (0, import_design_system17.cn)("text-xs text-muted-foreground", className), children: rights.why("structure") });
4974
- }
4975
- const options = optionText.split("\n").map((word) => word.trim()).filter((word) => word !== "");
4976
- const formulaChanged = formulaText.trim() !== startingFormulaText.trim();
4977
- const formulaMissing = type !== "formula" || !formulaWritten ? null : editing && !formulaChanged ? null : formulaText.trim() === "" ? "Write the formula this column works out." : formulaOk === false ? "The formula has a mistake \u2014 it is marked above." : formulaOk === null ? "Checking the formula\u2026" : null;
4978
- const missing = editing ? formulaMissing : formulaMissing ?? (type === "formula" && formulaWritten ? null : whatIsMissing({ type, label, options, via, pick, agg, of, relations, formulaFields, relationTarget, allowedTypes }));
4979
- const save = async () => {
4980
- if (missing) return;
4981
- if (editing && field2) {
4982
- const chooses = type === "select" || type === "multi_select";
4983
- const look = chooses ? choiceRows ? choiceDisplayPatch(field2, choiceRows) : null : null;
4984
- const shownAs = chooses ? void 0 : formatPatch(field2, showsAs);
4985
- const patch = {
4986
- // The column's look travels in the same settings patch (`display_format`), so a save is
4987
- // still one store call. `FieldPatch` does not declare the key yet (store gap, reported);
4988
- // the door has carried it since the Sheet's seam started writing it.
4989
- ...chooses ? look ? { display_format: look } : {} : shownAs ?? {},
4990
- label,
4991
- required,
4992
- dated,
4993
- sensitivity,
4994
- context_policy: contextPolicy,
4995
- rules,
4996
- // FLD-2. THE SHAPE OF THE VALUE, WHICH IS A SETTING LIKE ANY OTHER. This
4997
- // patch never carried it, so the tick beside "Can hold more than one"
4998
- // was decoration on an existing column — and the store's own roll-up
4999
- // refusal tells a person to use exactly that tick. It is sent ALONE:
5000
- // `custom.field_update` honours a `multi`-only patch since FIX-7B-FIELD's
5001
- // migration (before it, the door read `multi` only inside its behaviour
5002
- // arm, answered "saved" and changed nothing), so there is no behaviour
5003
- // word to send beside it and sending one would retype a column nobody
5004
- // asked to retype.
5005
- ...multiApplies(type) ? { multi } : {},
5006
- ...unitApplies(type) ? { unit } : {},
5007
- // An empty box means "leave the choices alone"; a list replaces them.
5008
- ...optionText.trim() === "" ? {} : { options },
5009
- // REL-DISP. Which of the other record's columns this one shows. Sent for
5010
- // every relation column, `null` included, because `null` is how the door
5011
- // REMOVES a spec and the column goes back to the target table's own
5012
- // reference column — a panel that only sent the key when it was set
5013
- // could turn the override on and never off.
5014
- ...field2.type === "relation" ? { display } : {},
5015
- // STORE-RULE-GAPS: a choice list's own setting, sent for every list so
5016
- // turning it off is as possible as turning it on.
5017
- ...field2.type === "list" ? { allow_other: allowOther } : {},
5018
- // K2: a re-aimed formula goes WITH its type word, the arm `field_update` reads it in.
5170
+ function filterRefusal(f) {
5171
+ if (!computedOnRead(f)) return null;
5172
+ return `${fieldName(f)} is worked out each time it is read, so the store keeps no value to filter the whole table by. Filter by a column it is worked out from.`;
5173
+ }
5174
+ function computedOnRead(f) {
5175
+ const kind = String(editorKindFor(f));
5176
+ return (f.type === "formula" || kind === "formula" || kind === "lookup" || kind === "rollup") && String(f.compute_on ?? "read") === "read";
5177
+ }
5178
+ function sortAsFor(field2) {
5179
+ const kind = String(editorKindFor(field2));
5180
+ if (kind === "number" || kind === "currency" || kind === "percent") return "number";
5181
+ if (kind === "datetime") return "date";
5182
+ return "text";
5183
+ }
5184
+ function pageSortFor(fields, sort) {
5185
+ if (!sort) return [];
5186
+ const field2 = fields.find((f) => f.key === sort.id);
5187
+ if (!field2) return [];
5188
+ return [{ field: field2.key, direction: sort.direction === "desc" ? "desc" : "asc", as: sortAsFor(field2) }];
5189
+ }
5190
+ function containsPattern(text) {
5191
+ return `(?i)${text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`;
5192
+ }
5193
+ var field = (f) => ({ field: f.id });
5194
+ var constant = (value) => ({ const: value });
5195
+ var op = (name, ...args) => ({ op: name, args });
5196
+ function clauseFor(f, value, now = /* @__PURE__ */ new Date()) {
5197
+ const name = fieldName(f);
5198
+ const refused = filterRefusal(f);
5199
+ if (refused) return refused;
5200
+ const not = (rule) => op("not", rule);
5201
+ switch (value.kind) {
5202
+ case "text": {
5203
+ const mode = value.mode ?? "contains";
5204
+ if (mode === "empty" || mode === "null") return value.negated ? { rule: op("present", field(f)) } : { rule: not(op("present", field(f))) };
5205
+ if (mode === "not_empty" || mode === "not_null") return value.negated ? { rule: not(op("present", field(f))) } : { rule: op("present", field(f)) };
5206
+ const text = value.value.trim();
5207
+ if (text === "") return null;
5208
+ const kind = String(editorKindFor(f));
5209
+ if (kind === "number" || kind === "currency" || kind === "percent" || kind === "checkbox") {
5210
+ return `${name} holds numbers, so it is filtered by a range, not by words.`;
5211
+ }
5212
+ const matches2 = op("matches", field(f), constant(containsPattern(text)));
5213
+ return { rule: value.negated ? not(matches2) : matches2 };
5214
+ }
5215
+ case "select": {
5216
+ const words2 = value.values ?? (value.value === "" ? [] : [value.value]);
5217
+ if (words2.length === 0) return value.negated ? null : { rule: constant(false) };
5218
+ if (words2.length === 1 && !value.negated) return { flat: { [f.key]: words2[0] } };
5219
+ const any = words2.length === 1 ? op("eq", field(f), constant(words2[0])) : op("or", ...words2.map((w) => op("eq", field(f), constant(w))));
5220
+ return { rule: value.negated ? not(any) : any };
5221
+ }
5222
+ case "boolean":
5223
+ return value.negated ? { rule: op("ne", field(f), constant(value.value)) } : { flat: { [f.key]: value.value } };
5224
+ case "number": {
5225
+ if (value.op === "empty") return { rule: not(op("present", field(f))) };
5226
+ if (value.op === "not_empty") return { rule: op("present", field(f)) };
5227
+ if (value.op === "eq" || value.op === "ne") {
5228
+ if (typeof value.min !== "number") return null;
5229
+ return { rule: op(value.op, field(f), constant(value.min)) };
5230
+ }
5231
+ if (value.op === "lt") return typeof value.max === "number" ? { rule: op("lt", field(f), constant(value.max)) } : null;
5232
+ if (value.op === "gt") return typeof value.min === "number" ? { rule: op("gt", field(f), constant(value.min)) } : null;
5233
+ const parts = [];
5234
+ if (typeof value.min === "number") parts.push(op("gte", field(f), constant(value.min)));
5235
+ if (typeof value.max === "number") parts.push(op("lte", field(f), constant(value.max)));
5236
+ if (parts.length === 0) return null;
5237
+ const range = parts.length === 1 ? parts[0] : op("and", ...parts);
5238
+ return { rule: value.negated ? not(range) : range };
5239
+ }
5240
+ case "date": {
5241
+ const bounds = (0, import_data_table6.dateFilterBounds)(value, now);
5242
+ if (bounds.since === void 0 && bounds.until === void 0) return null;
5243
+ return {
5244
+ flat: {
5245
+ [f.key]: {
5246
+ ...bounds.since === void 0 ? {} : { from: bounds.since },
5247
+ ...bounds.until === void 0 ? {} : { to: bounds.until }
5248
+ }
5249
+ }
5250
+ };
5251
+ }
5252
+ default:
5253
+ return `${name} cannot be filtered that way by the store.`;
5254
+ }
5255
+ }
5256
+ function storeQuestion(fields, base, columnFilters, now = /* @__PURE__ */ new Date()) {
5257
+ const refused = [];
5258
+ const flat = {};
5259
+ const rules = [];
5260
+ if (base && (0, import_records8.isRuleFilter)(base)) rules.push(base);
5261
+ else if (base) Object.assign(flat, base);
5262
+ for (const [key, value] of Object.entries(columnFilters)) {
5263
+ if (!value) continue;
5264
+ const f = fields.find((candidate) => candidate.key === key);
5265
+ if (!f) continue;
5266
+ const clause = clauseFor(f, value, now);
5267
+ if (clause === null) continue;
5268
+ if (typeof clause === "string") {
5269
+ refused.push(clause);
5270
+ continue;
5271
+ }
5272
+ if ("flat" in clause) Object.assign(flat, clause.flat);
5273
+ else rules.push(clause.rule);
5274
+ }
5275
+ const flatKeys = Object.keys(flat);
5276
+ if (rules.length === 0) return { filter: flatKeys.length > 0 ? flat : void 0, refused };
5277
+ for (const key of flatKeys) {
5278
+ const f = fields.find((candidate) => candidate.key === key);
5279
+ const value = flat[key];
5280
+ if (!f) continue;
5281
+ if (value !== null && typeof value === "object") {
5282
+ refused.push(`${fieldName(f)}'s date range cannot be asked together with the other filters yet, so it is left off.`);
5283
+ continue;
5284
+ }
5285
+ rules.push(value === null ? op("not", op("present", field(f))) : op("eq", field(f), constant(value)));
5286
+ }
5287
+ return { filter: rules.length === 1 ? rules[0] : op("and", ...rules), refused };
5288
+ }
5289
+ function inColumnWords(error, fields) {
5290
+ const say2 = (text) => {
5291
+ if (!text) return text;
5292
+ let out = text;
5293
+ for (const f of [...fields].sort((a, b) => b.key.length - a.key.length)) {
5294
+ const key = f.key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5295
+ out = out.replace(new RegExp(`(["\u201C'])${key}(["\u201D'])`, "g"), `$1${fieldName(f)}$2`);
5296
+ }
5297
+ return out;
5298
+ };
5299
+ return { ...error, message: say2(error.message) ?? error.message, ...error.hint ? { hint: say2(error.hint) ?? error.hint } : {} };
5300
+ }
5301
+
5302
+ // src/gridMenus.ts
5303
+ function fieldOf(ctx, columnId) {
5304
+ return ctx.fields.find((f) => f.key === columnId);
5305
+ }
5306
+ function columnWords(ctx, columnId) {
5307
+ const field2 = fieldOf(ctx, columnId);
5308
+ return field2 ? fieldName(field2) : columnId;
5309
+ }
5310
+ function rowLabelRefusal(field2) {
5311
+ if (!field2) return "This column is not one of the table's own.";
5312
+ const kind = String(field2.type);
5313
+ if (kind === "formula" || kind === "lookup" || kind === "rollup") {
5314
+ return `${fieldName(field2)} is worked out by the table, and a record is named by a column people fill in.`;
5315
+ }
5316
+ if (kind === "relation") return `${fieldName(field2)} points at other records; a record is named by its own words.`;
5317
+ return void 0;
5318
+ }
5319
+ function headerMenuItems(columnId, ctx) {
5320
+ const field2 = fieldOf(ctx, columnId);
5321
+ const shape = ctx.mayStructure ? void 0 : ctx.whyNotStructure;
5322
+ const isLabel = ctx.titleField === columnId;
5323
+ const labelRefusal = shape ?? (isLabel ? "Records are already named by this column." : rowLabelRefusal(field2));
5324
+ const a = ctx.actions;
5325
+ return [
5326
+ { id: "col-rename", label: "Rename column", icon: "pencil", disabledReason: shape, onSelect: () => a.renameColumn(columnId) },
5327
+ { id: "col-settings", label: "Column settings\u2026", icon: "settings", disabledReason: shape, onSelect: () => a.columnSettings(columnId) },
5328
+ { id: "col-insert-left", label: "Insert column left\u2026", icon: "panel-left", disabledReason: shape, onSelect: () => a.insertColumn(columnId, "left") },
5329
+ { id: "col-insert-right", label: "Insert column right\u2026", icon: "panel-right", disabledReason: shape, onSelect: () => a.insertColumn(columnId, "right") },
5330
+ {
5331
+ id: "col-row-label",
5332
+ label: isLabel ? "Names each record" : "Use as row label",
5333
+ icon: "tag",
5334
+ disabledReason: labelRefusal,
5335
+ onSelect: () => a.useAsRowLabel(columnId)
5336
+ },
5337
+ ...ctx.extras?.header?.(columnId) ?? [],
5338
+ { id: "col-hide", label: "Hide column", icon: "eye-off", onSelect: () => a.hideColumn(columnId) },
5339
+ {
5340
+ id: "col-delete",
5341
+ label: "Delete column\u2026",
5342
+ icon: "trash",
5343
+ destructive: true,
5344
+ disabledReason: shape ?? (isLabel ? "Records are named by this column; pick another row label first." : void 0),
5345
+ onSelect: () => a.deleteColumn(columnId)
5346
+ }
5347
+ ];
5348
+ }
5349
+ function cellSection(payload, ctx) {
5350
+ const a = ctx.actions;
5351
+ const { rowId, columnId } = payload;
5352
+ const mayWrite = ctx.mayWriteRow(rowId);
5353
+ const typeable = ctx.typeable(columnId);
5354
+ const why = !mayWrite ? ctx.whyNotWrite(rowId) : !typeable ? `${columnWords(ctx, columnId)} is worked out by the table; nobody types it.` : void 0;
5355
+ const items = [
5356
+ { id: "cell-copy", label: "Copy cell", icon: "copy", shortcut: "\u2318C", onSelect: () => a.copyText(payload.text, "The cell") },
5357
+ { id: "cell-paste", label: "Paste", icon: "clipboard-paste", shortcut: "\u2318V", disabledReason: why, onSelect: () => a.pasteInto(rowId, columnId) },
5358
+ { id: "cell-clear", label: "Clear cell", icon: "eraser", destructive: true, disabledReason: why, onSelect: () => a.clearCells([{ rowId, columnId }]) }
5359
+ ];
5360
+ if (a.askWhoChanged) {
5361
+ items.push({ id: "cell-history", label: "Who changed this?", icon: "history", shortcut: "\u2325\u21E7H", onSelect: () => a.askWhoChanged?.(columnId, rowId) });
5362
+ }
5363
+ const empty = payload.value === null || payload.value === void 0 || payload.value === "";
5364
+ items.push({
5365
+ id: "cell-filter",
5366
+ label: empty ? "Show rows where this is empty" : "Show rows with this value",
5367
+ icon: "filter",
5368
+ onSelect: () => a.filterBy(columnId, payload.value)
5369
+ });
5370
+ items.push(...ctx.extras?.cell?.(rowId, columnId) ?? []);
5371
+ return { id: "cell", title: payload.columnLabel || "Cell", items };
5372
+ }
5373
+ function rowSection(rowId, ctx, rowText) {
5374
+ const a = ctx.actions;
5375
+ const mayWrite = ctx.mayWriteRow(rowId);
5376
+ const items = [];
5377
+ if (a.openRecord) {
5378
+ items.push({ id: "row-open", label: "Open record", icon: "panel-right", shortcut: "Space", onSelect: () => a.openRecord?.(rowId) });
5379
+ items.push({ id: "row-history", label: "Row history", icon: "history", onSelect: () => a.openRecord?.(rowId) });
5380
+ }
5381
+ if (rowText !== null) {
5382
+ items.push({ id: "row-copy", label: "Copy row", icon: "copy", onSelect: () => a.copyText(rowText, "The row") });
5383
+ }
5384
+ const noPlace = "This view is sorted, so a new record lands where its sort puts it. Reorder places rows by hand; New record adds one.";
5385
+ const insertWhy = !ctx.mayAddRecord ? ctx.whyNotAdd : a.insertRow ? void 0 : noPlace;
5386
+ items.push(
5387
+ { id: "row-insert-above", label: "Insert row above", icon: "plus", disabledReason: insertWhy, onSelect: () => a.insertRow?.(rowId, "above") },
5388
+ { id: "row-insert-below", label: "Insert row below", icon: "plus", disabledReason: insertWhy, onSelect: () => a.insertRow?.(rowId, "below") },
5389
+ {
5390
+ id: "row-duplicate",
5391
+ label: "Duplicate row",
5392
+ icon: "copy-plus",
5393
+ disabledReason: ctx.mayAddRecord ? void 0 : ctx.whyNotAdd,
5394
+ onSelect: () => a.duplicateRow(rowId)
5395
+ },
5396
+ {
5397
+ id: "row-archive",
5398
+ label: "Delete row\u2026",
5399
+ icon: "trash",
5400
+ destructive: true,
5401
+ disabledReason: mayWrite ? void 0 : ctx.whyNotWrite(rowId),
5402
+ onSelect: () => a.archiveRow(rowId)
5403
+ }
5404
+ );
5405
+ items.push(...ctx.extras?.row?.(rowId) ?? []);
5406
+ return { id: "row", title: ctx.extras?.rowTitle?.(rowId) || "Row", items };
5407
+ }
5408
+ function columnSection(columnId, columnLabel2, ctx) {
5409
+ const a = ctx.actions;
5410
+ const sorted = ctx.sortedBy?.id === columnId ? ctx.sortedBy.direction : null;
5411
+ const field2 = fieldOf(ctx, columnId);
5412
+ const refused = field2 ? sortRefusal(field2) ?? void 0 : void 0;
5413
+ return {
5414
+ id: "column",
5415
+ title: columnLabel2 || columnWords(ctx, columnId),
5416
+ items: [
5417
+ { id: "col-sort-asc", label: "Sort A\u2192Z", icon: "arrow-up-a-z", disabledReason: refused ?? (sorted === "asc" ? "Already sorted A\u2192Z." : void 0), onSelect: () => a.sortBy(columnId, "asc") },
5418
+ { id: "col-sort-desc", label: "Sort Z\u2192A", icon: "arrow-down-a-z", disabledReason: refused ?? (sorted === "desc" ? "Already sorted Z\u2192A." : void 0), onSelect: () => a.sortBy(columnId, "desc") },
5419
+ ...sorted ? [{ id: "col-sort-clear", label: "Clear sort", icon: "arrow-up-down", onSelect: () => a.sortBy(columnId, null) }] : [],
5420
+ ...headerMenuItems(columnId, ctx)
5421
+ ]
5422
+ };
5423
+ }
5424
+ function tableSection(ctx) {
5425
+ const a = ctx.actions;
5426
+ const items = [
5427
+ { id: "table-new-record", label: "New record", icon: "plus", disabledReason: ctx.mayAddRecord ? void 0 : ctx.whyNotAdd, onSelect: () => a.newRecord() }
5428
+ ];
5429
+ if (a.addColumn) {
5430
+ items.push({
5431
+ id: "table-add-column",
5432
+ label: "Add column\u2026",
5433
+ icon: "plus",
5434
+ disabledReason: ctx.mayStructure ? void 0 : ctx.whyNotStructure,
5435
+ onSelect: () => a.addColumn?.()
5436
+ });
5437
+ }
5438
+ items.push(...ctx.extras?.table?.() ?? []);
5439
+ return { id: "table", title: "Table", items };
5440
+ }
5441
+ function gridMenuSections(payload, ctx, rowText = () => null) {
5442
+ switch (payload.level) {
5443
+ case "cell":
5444
+ return [
5445
+ { ...cellSection(payload, ctx), primary: true },
5446
+ rowSection(payload.rowId, ctx, rowText(payload.rowId)),
5447
+ columnSection(payload.columnId, payload.columnLabel, ctx),
5448
+ tableSection(ctx)
5449
+ ];
5450
+ case "row":
5451
+ return [{ ...rowSection(payload.rowId, ctx, rowText(payload.rowId)), primary: true }, tableSection(ctx)];
5452
+ case "column":
5453
+ return [{ ...columnSection(payload.columnId, payload.columnLabel, ctx), primary: true }, tableSection(ctx)];
5454
+ case "selection": {
5455
+ const n = payload.cellCount;
5456
+ const blocked = payload.rowIds.find((id) => !ctx.mayWriteRow(id));
5457
+ const untypeable = payload.columnIds.find((id) => !ctx.typeable(id));
5458
+ const why = blocked ? ctx.whyNotWrite(blocked) : untypeable ? `${columnWords(ctx, untypeable)} is worked out by the table; nobody types it.` : void 0;
5459
+ const cells = payload.rowIds.flatMap((rowId) => payload.columnIds.map((columnId) => ({ rowId, columnId })));
5460
+ return [
5461
+ {
5462
+ id: "selection",
5463
+ title: `${n} ${n === 1 ? "cell" : "cells"}`,
5464
+ primary: true,
5465
+ items: [
5466
+ { id: "sel-copy", label: `Copy ${n} cells`, icon: "copy", shortcut: "\u2318C", onSelect: () => ctx.actions.copyText(payload.tsv, `${n} cells`) },
5467
+ { id: "sel-clear", label: `Clear ${n} cells`, icon: "eraser", destructive: true, disabledReason: why, onSelect: () => ctx.actions.clearCells(cells) },
5468
+ {
5469
+ id: "sel-fill-down",
5470
+ label: "Fill down",
5471
+ icon: "arrow-down-to-line",
5472
+ shortcut: "\u2318D",
5473
+ disabledReason: why ?? (payload.rowIds.length < 2 ? "Select at least two rows to fill down." : void 0),
5474
+ onSelect: () => ctx.actions.fillDown(payload.rowIds, payload.columnIds)
5475
+ }
5476
+ ]
5477
+ },
5478
+ tableSection(ctx)
5479
+ ];
5480
+ }
5481
+ case "table":
5482
+ return [{ ...tableSection(ctx), primary: true }];
5483
+ }
5484
+ }
5485
+ function placeColumn(order, newKey, anchor, side) {
5486
+ const rest = order.filter((k) => k !== newKey);
5487
+ const at = rest.indexOf(anchor);
5488
+ if (at < 0) return [...rest, newKey];
5489
+ const index = side === "left" ? at : at + 1;
5490
+ return [...rest.slice(0, index), newKey, ...rest.slice(index)];
5491
+ }
5492
+ function columnsInKeptOrder(fieldKeys, kept) {
5493
+ if (!kept || kept.length === 0) return [...fieldKeys];
5494
+ const known = new Set(fieldKeys);
5495
+ const named = kept.filter((k, i) => known.has(k) && kept.indexOf(k) === i);
5496
+ const namedSet = new Set(named);
5497
+ return [...named, ...fieldKeys.filter((k) => !namedSet.has(k))];
5498
+ }
5499
+ function compareFieldOrder(a, b) {
5500
+ const bySort = (a.sort ?? 100) - (b.sort ?? 100);
5501
+ if (bySort !== 0) return bySort;
5502
+ const made = String(a.created_at ?? "").localeCompare(String(b.created_at ?? ""));
5503
+ if (made !== 0) return made;
5504
+ return a.key.localeCompare(b.key);
5505
+ }
5506
+ function nextFieldSort(fields) {
5507
+ let last = 0;
5508
+ for (const f of fields ?? []) last = Math.max(last, f.sort ?? 100);
5509
+ return last + 1;
5510
+ }
5511
+
5512
+ // src/FieldEditor.tsx
5513
+ var import_records9 = require("@ai-matrx/records");
5514
+ var import_jsx_runtime21 = require("react/jsx-runtime");
5515
+ var RULE_KINDS = FIELD_RULE_KIND_VALUES.map((kind) => ({ kind, label: FIELD_RULE_KIND_LABEL[kind] }));
5516
+ var SENSITIVITIES = import_records9.FIELD_SENSITIVITIES.map((value) => ({ value, label: FIELD_SENSITIVITY_LABEL[value] }));
5517
+ var AGENT_VISIBILITY = import_records9.CONTEXT_POLICIES.map((value) => ({ value, label: CONTEXT_POLICY_LABEL[value] }));
5518
+ var ROLLUP_OPERATIONS = ROLLUP_AGG_VALUES.map((value) => ({
5519
+ value,
5520
+ label: ROLLUP_AGG_LABEL[value]
5521
+ }));
5522
+ var FORMULA_SEPARATOR_LABEL = "Joined by";
5523
+ var EDITING_A_WORKED_OUT_COLUMN = "This column works out its answer from the columns picked when it was made. To change what it works out, write it as a formula below \u2014 the store checks it as you type, and a rename alone leaves it exactly as it is.";
5524
+ var FORMULA_DEFAULT_SEPARATOR = " \u2014 ";
5525
+ var FORMULA_OPERATIONS = FORMULA_OP_VALUES.map((value) => ({
5526
+ value,
5527
+ label: FORMULA_OP_LABEL[value],
5528
+ joins: FORMULA_OP_JOINS[value]
5529
+ }));
5530
+ function keyFor(label) {
5531
+ const token = label.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 48);
5532
+ if (token === "") return "";
5533
+ return /^[a-z]/.test(token) ? token : `f_${token}`;
5534
+ }
5535
+ var COLUMN_DID_NOT_LAND_SENTENCE = "The store accepted this column and the table does not have it yet. Nothing was lost \u2014 your record values are untouched \u2014 but the column is not there, so this panel is staying open.";
5536
+ var COLUMN_DID_NOT_LAND_REMEDY = "Press Save again; if it keeps happening, tell us, because the store and the table disagree and that is ours to fix.";
5537
+ var COLUMN_DID_NOT_LAND = `${COLUMN_DID_NOT_LAND_SENTENCE} ${COLUMN_DID_NOT_LAND_REMEDY}`;
5538
+ function FieldEditor({ tableId, field: field2, onSaved, onCancel, onRemoved, className }) {
5539
+ const table = (0, import_react30.useTable)(tableId);
5540
+ const fields = (0, import_react30.useFields)(tableId);
5541
+ const rights = useTableRights(table.data);
5542
+ const mutation = (0, import_react30.useFieldMutation)();
5543
+ const client = (0, import_react30.useRecordsClient)();
5544
+ const [label, setLabel] = (0, import_react29.useState)(field2?.label ?? "");
5545
+ const [type, setType] = (0, import_react29.useState)(() => startingType(field2));
5546
+ const [required, setRequired] = (0, import_react29.useState)(field2?.required ?? false);
5547
+ const [multi, setMulti] = (0, import_react29.useState)(field2?.multi ?? false);
5548
+ const [dated, setDated] = (0, import_react29.useState)(field2?.dated ?? false);
5549
+ const [sensitivity, setSensitivity] = (0, import_react29.useState)(String(field2?.sensitivity ?? "internal"));
5550
+ const [contextPolicy, setContextPolicy] = (0, import_react29.useState)(String(field2?.context_policy ?? "include"));
5551
+ const [rules, setRules] = (0, import_react29.useState)(field2?.rules ?? []);
5552
+ const [allowOther, setAllowOther] = (0, import_react29.useState)(field2?.config?.["allow_other"] === true);
5553
+ const [optionText, setOptionText] = (0, import_react29.useState)("");
5554
+ const [unit, setUnit] = (0, import_react29.useState)(field2?.unit ?? "$");
5555
+ const [withTime, setWithTime] = (0, import_react29.useState)(String(field2?.config?.["kind"] ?? "date") === "datetime");
5556
+ const [via, setVia] = (0, import_react29.useState)(String(field2?.config?.["via"] ?? ""));
5557
+ const [pick, setPick] = (0, import_react29.useState)(String(field2?.config?.["pick"] ?? ""));
5558
+ const [agg, setAgg] = (0, import_react29.useState)(
5559
+ field2?.config?.["agg"] ?? "count"
5560
+ );
5561
+ const [of, setOf] = (0, import_react29.useState)(String(field2?.config?.["of"] ?? ""));
5562
+ const [relationTarget, setRelationTarget] = (0, import_react29.useState)(String(field2?.relation_target ?? ""));
5563
+ const [display, setDisplay] = (0, import_react29.useState)(field2?.display ?? null);
5564
+ const [formulaOp, setFormulaOp] = (0, import_react29.useState)(() => formulaOpOf(field2));
5565
+ const [formulaFields, setFormulaFields] = (0, import_react29.useState)(() => formulaFieldIdsOf(field2));
5566
+ const [formulaSeparator, setFormulaSeparator] = (0, import_react29.useState)(() => formulaSeparatorOf(field2));
5567
+ const [allowedTypes, setAllowedTypes] = (0, import_react29.useState)(() => entityReferenceKindsOf(field2));
5568
+ const startingFormulaText = String(field2?.config?.["formula_text"] ?? "");
5569
+ const [formulaText, setFormulaText] = (0, import_react29.useState)(startingFormulaText);
5570
+ const [formulaWritten, setFormulaWritten] = (0, import_react29.useState)(() => startingFormulaText.trim() !== "" || Boolean(field2));
5571
+ const [formulaOk, setFormulaOk] = (0, import_react29.useState)(null);
5572
+ const [askingToRemove, setAskingToRemove] = (0, import_react29.useState)(false);
5573
+ const [choiceRows, setChoiceRows] = (0, import_react29.useState)(null);
5574
+ const [showsAs, setShowsAs] = (0, import_react29.useState)(() => field2 ? startingChoice(field2) : { format: null, retype: null });
5575
+ const [backfillError, setBackfillError] = (0, import_react29.useState)(null);
5576
+ const [notLanded, setNotLanded] = (0, import_react29.useState)(false);
5577
+ const others = (0, import_react29.useMemo)(
5578
+ () => (fields.data ?? []).filter((f) => f.id !== field2?.id),
5579
+ [fields.data, field2?.id]
5580
+ );
5581
+ const relations = (0, import_react29.useMemo)(() => others.filter((f) => f.type === "relation"), [others]);
5582
+ const choice = fieldTypeChoice(type);
5583
+ const editing = Boolean(field2);
5584
+ if (!rights.structure) {
5585
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: (0, import_design_system17.cn)("text-xs text-muted-foreground", className), children: rights.why("structure") });
5586
+ }
5587
+ const options = optionText.split("\n").map((word) => word.trim()).filter((word) => word !== "");
5588
+ const formulaChanged = formulaText.trim() !== startingFormulaText.trim();
5589
+ const formulaMissing = type !== "formula" || !formulaWritten ? null : editing && !formulaChanged ? null : formulaText.trim() === "" ? "Write the formula this column works out." : formulaOk === false ? "The formula has a mistake \u2014 it is marked above." : formulaOk === null ? "Checking the formula\u2026" : null;
5590
+ const missing = editing ? formulaMissing : formulaMissing ?? (type === "formula" && formulaWritten ? null : whatIsMissing({ type, label, options, via, pick, agg, of, relations, formulaFields, relationTarget, allowedTypes }));
5591
+ const save = async () => {
5592
+ if (missing) return;
5593
+ if (editing && field2) {
5594
+ const chooses = type === "select" || type === "multi_select";
5595
+ const look = chooses ? choiceRows ? choiceDisplayPatch(field2, choiceRows) : null : null;
5596
+ const shownAs = chooses ? void 0 : formatPatch(field2, showsAs);
5597
+ const patch = {
5598
+ // The column's look travels in the same settings patch (`display_format`), so a save is
5599
+ // still one store call. `FieldPatch` does not declare the key yet (store gap, reported);
5600
+ // the door has carried it since the Sheet's seam started writing it.
5601
+ ...chooses ? look ? { display_format: look } : {} : shownAs ?? {},
5602
+ label,
5603
+ required,
5604
+ dated,
5605
+ sensitivity,
5606
+ context_policy: contextPolicy,
5607
+ rules,
5608
+ // FLD-2. THE SHAPE OF THE VALUE, WHICH IS A SETTING LIKE ANY OTHER. This
5609
+ // patch never carried it, so the tick beside "Can hold more than one"
5610
+ // was decoration on an existing column — and the store's own roll-up
5611
+ // refusal tells a person to use exactly that tick. It is sent ALONE:
5612
+ // `custom.field_update` honours a `multi`-only patch since FIX-7B-FIELD's
5613
+ // migration (before it, the door read `multi` only inside its behaviour
5614
+ // arm, answered "saved" and changed nothing), so there is no behaviour
5615
+ // word to send beside it and sending one would retype a column nobody
5616
+ // asked to retype.
5617
+ ...multiApplies(type) ? { multi } : {},
5618
+ ...unitApplies(type) ? { unit } : {},
5619
+ // An empty box means "leave the choices alone"; a list replaces them.
5620
+ ...optionText.trim() === "" ? {} : { options },
5621
+ // REL-DISP. Which of the other record's columns this one shows. Sent for
5622
+ // every relation column, `null` included, because `null` is how the door
5623
+ // REMOVES a spec and the column goes back to the target table's own
5624
+ // reference column — a panel that only sent the key when it was set
5625
+ // could turn the override on and never off.
5626
+ ...field2.type === "relation" ? { display } : {},
5627
+ // STORE-RULE-GAPS: a choice list's own setting, sent for every list so
5628
+ // turning it off is as possible as turning it on.
5629
+ ...field2.type === "list" ? { allow_other: allowOther } : {},
5630
+ // K2: a re-aimed formula goes WITH its type word, the arm `field_update` reads it in.
5019
5631
  ...type === "formula" && formulaWritten && formulaChanged && formulaText.trim() !== "" ? { type: "formula", formula_text: formulaText.trim() } : {}
5020
5632
  };
5021
5633
  const written2 = await mutation.update({ field_id: field2.id, patch });
@@ -5024,6 +5636,10 @@ function FieldEditor({ tableId, field: field2, onSaved, onCancel, onRemoved, cla
5024
5636
  }
5025
5637
  const spec = {
5026
5638
  label,
5639
+ // A NEW COLUMN LANDS AT THE END (lane C, review 2026-09-26 C5): declared one past the last
5640
+ // column's `sort`, so every view and every client draws it last — never in the alphabetical
5641
+ // slot of its internal key.
5642
+ sort: nextFieldSort(fields.data),
5027
5643
  ...isRelationFieldType(type) ? {
5028
5644
  type: "relation",
5029
5645
  relation_target: relationTarget,
@@ -5642,10 +6258,10 @@ function entityReferenceKindsOf(field2) {
5642
6258
 
5643
6259
  // src/gridColors.tsx
5644
6260
  var import_react31 = require("react");
5645
- var import_records8 = require("@ai-matrx/records");
6261
+ var import_records10 = require("@ai-matrx/records");
5646
6262
  var import_react32 = require("@ai-matrx/records/react");
5647
6263
  var import_design_system18 = require("@ai-matrx/design-system");
5648
- var import_data_table6 = require("@ai-matrx/design-system/data-table");
6264
+ var import_data_table7 = require("@ai-matrx/design-system/data-table");
5649
6265
  var import_jsx_runtime22 = require("react/jsx-runtime");
5650
6266
  function isChoiceField(field2) {
5651
6267
  const kind = String(editorKindFor(field2));
@@ -5701,7 +6317,7 @@ function useChoiceOptions(fields, keys) {
5701
6317
  return index;
5702
6318
  }
5703
6319
  function lookOf(option) {
5704
- return { id: String(option.id), key: (0, import_records8.optionKey)(option), label: (0, import_records8.optionLabel)(option) };
6320
+ return { id: String(option.id), key: (0, import_records10.optionKey)(option), label: (0, import_records10.optionLabel)(option) };
5705
6321
  }
5706
6322
  function paintByOption(style, fields, index, lookup) {
5707
6323
  if (index.size === 0) return { style, documentOf: (doc) => doc, choiceColorFor: lookup };
@@ -5761,7 +6377,7 @@ function rulesLeaf(drafts, fields) {
5761
6377
  ...d.id ? { id: d.id } : {},
5762
6378
  field: field2.id,
5763
6379
  op: d.op,
5764
- ...import_data_table6.OPS_WITH_VALUE.includes(d.op) ? { value: d.value } : {},
6380
+ ...import_data_table7.OPS_WITH_VALUE.includes(d.op) ? { value: d.value } : {},
5765
6381
  color: d.color,
5766
6382
  target: d.target
5767
6383
  });
@@ -5770,9 +6386,9 @@ function rulesLeaf(drafts, fields) {
5770
6386
  }
5771
6387
  function swatches(prefix, current, write) {
5772
6388
  return [
5773
- ...import_data_table6.STYLE_COLORS.map((color) => ({
6389
+ ...import_data_table7.STYLE_COLORS.map((color) => ({
5774
6390
  id: `${prefix}-${color}`,
5775
- label: color === current ? `${import_data_table6.STYLE_COLOR_LABELS[color]} (now)` : import_data_table6.STYLE_COLOR_LABELS[color],
6391
+ label: color === current ? `${import_data_table7.STYLE_COLOR_LABELS[color]} (now)` : import_data_table7.STYLE_COLOR_LABELS[color],
5776
6392
  onSelect: () => write(color)
5777
6393
  })),
5778
6394
  ...current ? [{ id: `${prefix}-none`, label: "No highlight", onSelect: () => write(null) }] : []
@@ -5838,11 +6454,11 @@ function useGridColours(args) {
5838
6454
  },
5839
6455
  {
5840
6456
  id: "col-highlight",
5841
- label: column ? `Highlight column (${import_data_table6.STYLE_COLOR_LABELS[column]})` : "Highlight column",
6457
+ label: column ? `Highlight column (${import_data_table7.STYLE_COLOR_LABELS[column]})` : "Highlight column",
5842
6458
  submenu: swatches(
5843
6459
  "col-highlight",
5844
6460
  column,
5845
- (color) => void write(["columns", field2.id], color, color ? `${name} is highlighted ${import_data_table6.STYLE_COLOR_LABELS[color].toLowerCase()}.` : `${name} is no longer highlighted.`)
6461
+ (color) => void write(["columns", field2.id], color, color ? `${name} is highlighted ${import_data_table7.STYLE_COLOR_LABELS[color].toLowerCase()}.` : `${name} is no longer highlighted.`)
5846
6462
  )
5847
6463
  }
5848
6464
  ]
@@ -5907,7 +6523,7 @@ function useGridColours(args) {
5907
6523
  }
5908
6524
  var SELECT2 = "h-8 rounded border bg-background px-1 text-xs";
5909
6525
  var CHOICE_OPS = ["is", "is_not", "is_empty", "not_empty"].filter(
5910
- (op2) => import_data_table6.COLOR_RULE_OPS.includes(op2)
6526
+ (op2) => import_data_table7.COLOR_RULE_OPS.includes(op2)
5911
6527
  );
5912
6528
  function ColourRulesDialog({
5913
6529
  open,
@@ -5957,10 +6573,10 @@ function ColourRulesDialog({
5957
6573
  className: SELECT2,
5958
6574
  value: rule.op,
5959
6575
  onChange: (e) => set(i, { op: e.target.value }),
5960
- children: (choicesOf(rule.field) ? import_data_table6.COLOR_RULE_OPS.filter((op2) => CHOICE_OPS.includes(op2)) : import_data_table6.COLOR_RULE_OPS).map((op2) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("option", { value: op2, children: import_data_table6.COLOR_RULE_OP_LABELS[op2] }, op2))
6576
+ children: (choicesOf(rule.field) ? import_data_table7.COLOR_RULE_OPS.filter((op2) => CHOICE_OPS.includes(op2)) : import_data_table7.COLOR_RULE_OPS).map((op2) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("option", { value: op2, children: import_data_table7.COLOR_RULE_OP_LABELS[op2] }, op2))
5961
6577
  }
5962
6578
  ),
5963
- import_data_table6.OPS_WITH_VALUE.includes(rule.op) && choicesOf(rule.field) ? /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
6579
+ import_data_table7.OPS_WITH_VALUE.includes(rule.op) && choicesOf(rule.field) ? /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
5964
6580
  "select",
5965
6581
  {
5966
6582
  "aria-label": "Choice",
@@ -5973,7 +6589,7 @@ function ColourRulesDialog({
5973
6589
  choicesOf(rule.field).map((option) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("option", { value: option.id, children: option.label }, option.id))
5974
6590
  ]
5975
6591
  }
5976
- ) : import_data_table6.OPS_WITH_VALUE.includes(rule.op) ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
6592
+ ) : import_data_table7.OPS_WITH_VALUE.includes(rule.op) ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
5977
6593
  "input",
5978
6594
  {
5979
6595
  "aria-label": "Value",
@@ -5996,7 +6612,7 @@ function ColourRulesDialog({
5996
6612
  className: SELECT2,
5997
6613
  value: rule.color,
5998
6614
  onChange: (e) => set(i, { color: e.target.value }),
5999
- children: import_data_table6.STYLE_COLORS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("option", { value: c, children: import_data_table6.STYLE_COLOR_LABELS[c] }, c))
6615
+ children: import_data_table7.STYLE_COLORS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("option", { value: c, children: import_data_table7.STYLE_COLOR_LABELS[c] }, c))
6000
6616
  }
6001
6617
  ),
6002
6618
  /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_design_system18.Button, { type: "button", size: "sm", variant: "ghost", onClick: () => setDrafts(rows.filter((_, j) => j !== i)), children: "Remove" })
@@ -6039,207 +6655,9 @@ function ColourRulesDialog({
6039
6655
  var import_react33 = require("react");
6040
6656
  var import_react34 = require("@ai-matrx/records/react");
6041
6657
  var import_design_system19 = require("@ai-matrx/design-system");
6042
-
6043
- // src/gridMenus.ts
6044
- function fieldOf(ctx, columnId) {
6045
- return ctx.fields.find((f) => f.key === columnId);
6046
- }
6047
- function columnWords(ctx, columnId) {
6048
- const field2 = fieldOf(ctx, columnId);
6049
- return field2 ? fieldName(field2) : columnId;
6050
- }
6051
- function rowLabelRefusal(field2) {
6052
- if (!field2) return "This column is not one of the table's own.";
6053
- const kind = String(field2.type);
6054
- if (kind === "formula" || kind === "lookup" || kind === "rollup") {
6055
- return `${fieldName(field2)} is worked out by the table, and a record is named by a column people fill in.`;
6056
- }
6057
- if (kind === "relation") return `${fieldName(field2)} points at other records; a record is named by its own words.`;
6058
- return void 0;
6059
- }
6060
- function headerMenuItems(columnId, ctx) {
6061
- const field2 = fieldOf(ctx, columnId);
6062
- const shape = ctx.mayStructure ? void 0 : ctx.whyNotStructure;
6063
- const isLabel = ctx.titleField === columnId;
6064
- const labelRefusal = shape ?? (isLabel ? "Records are already named by this column." : rowLabelRefusal(field2));
6065
- const a = ctx.actions;
6066
- return [
6067
- { id: "col-rename", label: "Rename column", icon: "pencil", disabledReason: shape, onSelect: () => a.renameColumn(columnId) },
6068
- { id: "col-settings", label: "Column settings\u2026", icon: "settings", disabledReason: shape, onSelect: () => a.columnSettings(columnId) },
6069
- { id: "col-insert-left", label: "Insert column left\u2026", icon: "panel-left", disabledReason: shape, onSelect: () => a.insertColumn(columnId, "left") },
6070
- { id: "col-insert-right", label: "Insert column right\u2026", icon: "panel-right", disabledReason: shape, onSelect: () => a.insertColumn(columnId, "right") },
6071
- {
6072
- id: "col-row-label",
6073
- label: isLabel ? "Names each record" : "Use as row label",
6074
- icon: "tag",
6075
- disabledReason: labelRefusal,
6076
- onSelect: () => a.useAsRowLabel(columnId)
6077
- },
6078
- ...ctx.extras?.header?.(columnId) ?? [],
6079
- { id: "col-hide", label: "Hide column", icon: "eye-off", onSelect: () => a.hideColumn(columnId) },
6080
- {
6081
- id: "col-delete",
6082
- label: "Delete column\u2026",
6083
- icon: "trash",
6084
- destructive: true,
6085
- disabledReason: shape ?? (isLabel ? "Records are named by this column; pick another row label first." : void 0),
6086
- onSelect: () => a.deleteColumn(columnId)
6087
- }
6088
- ];
6089
- }
6090
- function cellSection(payload, ctx) {
6091
- const a = ctx.actions;
6092
- const { rowId, columnId } = payload;
6093
- const mayWrite = ctx.mayWriteRow(rowId);
6094
- const typeable = ctx.typeable(columnId);
6095
- const why = !mayWrite ? ctx.whyNotWrite(rowId) : !typeable ? `${columnWords(ctx, columnId)} is worked out by the table; nobody types it.` : void 0;
6096
- const items = [
6097
- { id: "cell-copy", label: "Copy cell", icon: "copy", shortcut: "\u2318C", onSelect: () => a.copyText(payload.text, "The cell") },
6098
- { id: "cell-paste", label: "Paste", icon: "clipboard-paste", shortcut: "\u2318V", disabledReason: why, onSelect: () => a.pasteInto(rowId, columnId) },
6099
- { id: "cell-clear", label: "Clear cell", icon: "eraser", destructive: true, disabledReason: why, onSelect: () => a.clearCells([{ rowId, columnId }]) }
6100
- ];
6101
- if (a.askWhoChanged) {
6102
- items.push({ id: "cell-history", label: "Who changed this?", icon: "history", shortcut: "\u2325\u21E7H", onSelect: () => a.askWhoChanged?.(columnId, rowId) });
6103
- }
6104
- const empty = payload.value === null || payload.value === void 0 || payload.value === "";
6105
- items.push({
6106
- id: "cell-filter",
6107
- label: empty ? "Show rows where this is empty" : "Show rows with this value",
6108
- icon: "filter",
6109
- onSelect: () => a.filterBy(columnId, payload.value)
6110
- });
6111
- items.push(...ctx.extras?.cell?.(rowId, columnId) ?? []);
6112
- return { id: "cell", title: payload.columnLabel || "Cell", items };
6113
- }
6114
- function rowSection(rowId, ctx, rowText) {
6115
- const a = ctx.actions;
6116
- const mayWrite = ctx.mayWriteRow(rowId);
6117
- const items = [];
6118
- if (a.openRecord) {
6119
- items.push({ id: "row-open", label: "Open record", icon: "panel-right", shortcut: "Space", onSelect: () => a.openRecord?.(rowId) });
6120
- items.push({ id: "row-history", label: "Row history", icon: "history", onSelect: () => a.openRecord?.(rowId) });
6121
- }
6122
- if (rowText !== null) {
6123
- items.push({ id: "row-copy", label: "Copy row", icon: "copy", onSelect: () => a.copyText(rowText, "The row") });
6124
- }
6125
- const noPlace = "This view is sorted, so a new record lands where its sort puts it. Reorder places rows by hand; New record adds one.";
6126
- const insertWhy = !ctx.mayAddRecord ? ctx.whyNotAdd : a.insertRow ? void 0 : noPlace;
6127
- items.push(
6128
- { id: "row-insert-above", label: "Insert row above", icon: "plus", disabledReason: insertWhy, onSelect: () => a.insertRow?.(rowId, "above") },
6129
- { id: "row-insert-below", label: "Insert row below", icon: "plus", disabledReason: insertWhy, onSelect: () => a.insertRow?.(rowId, "below") },
6130
- {
6131
- id: "row-duplicate",
6132
- label: "Duplicate row",
6133
- icon: "copy-plus",
6134
- disabledReason: ctx.mayAddRecord ? void 0 : ctx.whyNotAdd,
6135
- onSelect: () => a.duplicateRow(rowId)
6136
- },
6137
- {
6138
- id: "row-archive",
6139
- label: "Delete row\u2026",
6140
- icon: "trash",
6141
- destructive: true,
6142
- disabledReason: mayWrite ? void 0 : ctx.whyNotWrite(rowId),
6143
- onSelect: () => a.archiveRow(rowId)
6144
- }
6145
- );
6146
- items.push(...ctx.extras?.row?.(rowId) ?? []);
6147
- return { id: "row", title: ctx.extras?.rowTitle?.(rowId) || "Row", items };
6148
- }
6149
- function columnSection(columnId, columnLabel2, ctx) {
6150
- const a = ctx.actions;
6151
- const sorted = ctx.sortedBy?.id === columnId ? ctx.sortedBy.direction : null;
6152
- return {
6153
- id: "column",
6154
- title: columnLabel2 || columnWords(ctx, columnId),
6155
- items: [
6156
- { id: "col-sort-asc", label: "Sort A\u2192Z", icon: "arrow-up-a-z", disabledReason: sorted === "asc" ? "Already sorted A\u2192Z." : void 0, onSelect: () => a.sortBy(columnId, "asc") },
6157
- { id: "col-sort-desc", label: "Sort Z\u2192A", icon: "arrow-down-a-z", disabledReason: sorted === "desc" ? "Already sorted Z\u2192A." : void 0, onSelect: () => a.sortBy(columnId, "desc") },
6158
- ...sorted ? [{ id: "col-sort-clear", label: "Clear sort", icon: "arrow-up-down", onSelect: () => a.sortBy(columnId, null) }] : [],
6159
- ...headerMenuItems(columnId, ctx)
6160
- ]
6161
- };
6162
- }
6163
- function tableSection(ctx) {
6164
- const a = ctx.actions;
6165
- const items = [
6166
- { id: "table-new-record", label: "New record", icon: "plus", disabledReason: ctx.mayAddRecord ? void 0 : ctx.whyNotAdd, onSelect: () => a.newRecord() }
6167
- ];
6168
- if (a.addColumn) {
6169
- items.push({
6170
- id: "table-add-column",
6171
- label: "Add column\u2026",
6172
- icon: "plus",
6173
- disabledReason: ctx.mayStructure ? void 0 : ctx.whyNotStructure,
6174
- onSelect: () => a.addColumn?.()
6175
- });
6176
- }
6177
- items.push(...ctx.extras?.table?.() ?? []);
6178
- return { id: "table", title: "Table", items };
6179
- }
6180
- function gridMenuSections(payload, ctx, rowText = () => null) {
6181
- switch (payload.level) {
6182
- case "cell":
6183
- return [
6184
- { ...cellSection(payload, ctx), primary: true },
6185
- rowSection(payload.rowId, ctx, rowText(payload.rowId)),
6186
- columnSection(payload.columnId, payload.columnLabel, ctx),
6187
- tableSection(ctx)
6188
- ];
6189
- case "row":
6190
- return [{ ...rowSection(payload.rowId, ctx, rowText(payload.rowId)), primary: true }, tableSection(ctx)];
6191
- case "column":
6192
- return [{ ...columnSection(payload.columnId, payload.columnLabel, ctx), primary: true }, tableSection(ctx)];
6193
- case "selection": {
6194
- const n = payload.cellCount;
6195
- const blocked = payload.rowIds.find((id) => !ctx.mayWriteRow(id));
6196
- const untypeable = payload.columnIds.find((id) => !ctx.typeable(id));
6197
- const why = blocked ? ctx.whyNotWrite(blocked) : untypeable ? `${columnWords(ctx, untypeable)} is worked out by the table; nobody types it.` : void 0;
6198
- const cells = payload.rowIds.flatMap((rowId) => payload.columnIds.map((columnId) => ({ rowId, columnId })));
6199
- return [
6200
- {
6201
- id: "selection",
6202
- title: `${n} ${n === 1 ? "cell" : "cells"}`,
6203
- primary: true,
6204
- items: [
6205
- { id: "sel-copy", label: `Copy ${n} cells`, icon: "copy", shortcut: "\u2318C", onSelect: () => ctx.actions.copyText(payload.tsv, `${n} cells`) },
6206
- { id: "sel-clear", label: `Clear ${n} cells`, icon: "eraser", destructive: true, disabledReason: why, onSelect: () => ctx.actions.clearCells(cells) },
6207
- {
6208
- id: "sel-fill-down",
6209
- label: "Fill down",
6210
- icon: "arrow-down-to-line",
6211
- shortcut: "\u2318D",
6212
- disabledReason: why ?? (payload.rowIds.length < 2 ? "Select at least two rows to fill down." : void 0),
6213
- onSelect: () => ctx.actions.fillDown(payload.rowIds, payload.columnIds)
6214
- }
6215
- ]
6216
- },
6217
- tableSection(ctx)
6218
- ];
6219
- }
6220
- case "table":
6221
- return [{ ...tableSection(ctx), primary: true }];
6222
- }
6223
- }
6224
- function placeColumn(order, newKey, anchor, side) {
6225
- const rest = order.filter((k) => k !== newKey);
6226
- const at = rest.indexOf(anchor);
6227
- if (at < 0) return [...rest, newKey];
6228
- const index = side === "left" ? at : at + 1;
6229
- return [...rest.slice(0, index), newKey, ...rest.slice(index)];
6230
- }
6231
- function columnsInKeptOrder(fieldKeys, kept) {
6232
- if (!kept || kept.length === 0) return [...fieldKeys];
6233
- const known = new Set(fieldKeys);
6234
- const named = kept.filter((k, i) => known.has(k) && kept.indexOf(k) === i);
6235
- const namedSet = new Set(named);
6236
- return [...named, ...fieldKeys.filter((k) => !namedSet.has(k))];
6237
- }
6238
-
6239
- // src/gridRowLabel.tsx
6240
6658
  var import_jsx_runtime23 = require("react/jsx-runtime");
6241
6659
  function labelColumns(fields) {
6242
- return [...fields].filter((f) => rowLabelRefusal(f) === void 0).sort((a, b) => (a.sort ?? 100) - (b.sort ?? 100) || a.key.localeCompare(b.key));
6660
+ return [...fields].filter((f) => rowLabelRefusal(f) === void 0).sort(compareFieldOrder);
6243
6661
  }
6244
6662
  var JOINED_LABEL_LIMIT = "The record store names each record by the words one column holds, so a name joined from several columns (First and Last) is not something it keeps yet. Add a column that holds the whole name and pick it here, or pick the one column that names a record best.";
6245
6663
  function RowLabelPicker({
@@ -6333,7 +6751,7 @@ function RowLabelPicker({
6333
6751
  // src/gridSummaries.tsx
6334
6752
  var import_react35 = require("react");
6335
6753
  var import_react36 = require("@ai-matrx/records/react");
6336
- var import_records9 = require("@ai-matrx/records");
6754
+ var import_records11 = require("@ai-matrx/records");
6337
6755
  var import_design_system20 = require("@ai-matrx/design-system");
6338
6756
 
6339
6757
  // src/gridLook.ts
@@ -6533,7 +6951,7 @@ function useColumnSummaries(args) {
6533
6951
  const out = {};
6534
6952
  for (const [key, op2] of entries) {
6535
6953
  const measure = op2 === "count" ? { op: "count" } : { op: op2, key };
6536
- out[key] = row?.measures?.[(0, import_records9.measureKey)(measure)] ?? null;
6954
+ out[key] = row?.measures?.[(0, import_records11.measureKey)(measure)] ?? null;
6537
6955
  }
6538
6956
  setValues(out);
6539
6957
  setRowCount(row?.row_count ?? 0);
@@ -6584,7 +7002,10 @@ function SummaryCell({
6584
7002
  "data-records-summary": field2.key,
6585
7003
  className: (0, import_design_system20.cn)(
6586
7004
  "h-6 max-w-full justify-start gap-1 px-1 text-xs font-normal",
6587
- chosen ? "text-foreground" : "text-muted-foreground"
7005
+ // AN UNCHOSEN SUMMARY IS A HOVER, NOT A WORD IN EVERY FOOTER CELL (lane C, review
7006
+ // 2026-09-26 E1 — Airtable's footer): it shows under the pointer, on keyboard focus and
7007
+ // while its menu is open; the header menu's "Summarize column" is the other door.
7008
+ chosen ? "text-foreground" : "text-muted-foreground opacity-0 hover:opacity-100 focus-visible:opacity-100 data-[state=open]:opacity-100"
6588
7009
  ),
6589
7010
  title: chosen && rowCount !== null ? `${label} of ${fieldName(field2)}, worked out by the store over all ${rowCount} record${rowCount === 1 ? "" : "s"} \u2014 not only this page.` : `Summarize ${fieldName(field2)}`,
6590
7011
  children: chosen ? /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
@@ -6633,7 +7054,7 @@ function summaryMenuItem(field2, firstColumn, chosen, operations, onChoose) {
6633
7054
 
6634
7055
  // src/pasteValues.ts
6635
7056
  var import_core4 = require("@ai-matrx/records/core");
6636
- var import_records10 = require("@ai-matrx/records");
7057
+ var import_records12 = require("@ai-matrx/records");
6637
7058
  function wordForStoredChoice(options, value) {
6638
7059
  if (options === void 0 || typeof value !== "string" || value === "") return null;
6639
7060
  for (const label of options.labels) {
@@ -6646,7 +7067,7 @@ function choiceOptionsOf(options) {
6646
7067
  const labels = [];
6647
7068
  for (const option of options) {
6648
7069
  const id = option.id;
6649
- const key = (0, import_records10.optionKey)(option);
7070
+ const key = (0, import_records12.optionKey)(option);
6650
7071
  const label = recordName(option.data, null, "");
6651
7072
  if (label) {
6652
7073
  labels.push(label);
@@ -6658,7 +7079,7 @@ function choiceOptionsOf(options) {
6658
7079
  return { byWord, labels };
6659
7080
  }
6660
7081
  function valueFromPastedText(field2, raw, options) {
6661
- return (0, import_records10.coerceTypedAnswer)(field2, raw, {
7082
+ return (0, import_records12.coerceTypedAnswer)(field2, raw, {
6662
7083
  label: fieldName(field2),
6663
7084
  ...options ? { options } : {}
6664
7085
  });
@@ -6849,8 +7270,31 @@ function useMergedGridControls(args) {
6849
7270
  if (!made) return;
6850
7271
  pendingInsert.current = null;
6851
7272
  if (pending.anchor) keepColumns({ order: placeColumn(order, made, pending.anchor, pending.side), hidden });
6852
- }, [fieldKeys, order, hidden, keepColumns]);
7273
+ else if (keptOrder && keptOrder.length > 0) keepColumns({ order: [...order.filter((k) => k !== made), made], hidden });
7274
+ }, [fieldKeys, order, hidden, keepColumns, keptOrder]);
6853
7275
  const fieldOf2 = (0, import_react37.useCallback)((key) => fields.find((f) => f.key === key), [fields]);
7276
+ const choiceKeys = (0, import_react37.useMemo)(() => enabled ? fields.filter(isChoiceField).map((f) => f.key) : [], [enabled, fields]);
7277
+ const choiceOptions = useChoiceOptions(fields, choiceKeys);
7278
+ const filterFor = (0, import_react37.useCallback)(
7279
+ (key) => {
7280
+ if (!enabled) return void 0;
7281
+ const field2 = fieldOf2(key);
7282
+ if (!field2) return void 0;
7283
+ const kind = String(editorKindFor(field2));
7284
+ if (kind === "number" || kind === "currency" || kind === "percent" || kind === "rating") return { filter: "number" };
7285
+ if (kind === "datetime" || kind === "date") return { filter: "date" };
7286
+ if (kind === "checkbox") return { filter: "boolean" };
7287
+ if (!isChoiceField(field2)) return void 0;
7288
+ const options = choiceOptions.get(key);
7289
+ if (!options || options.length === 0) return { filter: "select" };
7290
+ const holdsKeys = rows.some((row) => {
7291
+ const v = (row.document ?? {})[key];
7292
+ return (Array.isArray(v) ? v : [v]).some((x) => options.some((o) => o.key === x && o.id !== x));
7293
+ });
7294
+ return { filter: "select", filterOptions: options.map((o) => ({ value: holdsKeys ? o.key : o.id, label: o.label })) };
7295
+ },
7296
+ [enabled, fieldOf2, choiceOptions, rows]
7297
+ );
6854
7298
  const copyText = (0, import_react37.useCallback)(
6855
7299
  (text, what) => {
6856
7300
  const clip = typeof navigator !== "undefined" ? navigator.clipboard : void 0;
@@ -7136,7 +7580,7 @@ function useMergedGridControls(args) {
7136
7580
  },
7137
7581
  children: /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_design_system21.DialogContent, { className: "max-h-[85vh] overflow-y-auto sm:max-w-2xl", children: [
7138
7582
  /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_design_system21.DialogHeader, { children: [
7139
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_design_system21.DialogTitle, { children: dialog?.kind === "settings" ? `Column settings \xB7 ${fieldName(dialog.field)}` : dialog?.kind === "insert" && dialog.anchor ? `New column ${dialog.side === "left" ? "left" : "right"} of ${fieldOf2(dialog.anchor) ? fieldName(fieldOf2(dialog.anchor)) : dialog.anchor}` : "New column" }),
7583
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_design_system21.DialogTitle, { children: dialog?.kind === "settings" ? `Column settings \xB7 ${fieldName(dialog.field)}` : dialog?.kind === "insert" && dialog.anchor ? `Add column ${dialog.side === "left" ? "left" : "right"} of ${fieldOf2(dialog.anchor) ? fieldName(fieldOf2(dialog.anchor)) : dialog.anchor}` : ADD_FIELD_OPEN_LABEL }),
7140
7584
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_design_system21.DialogDescription, { className: "sr-only", children: "The column\u2019s name, what it holds and how it looks." })
7141
7585
  ] }),
7142
7586
  dialog?.kind === "settings" ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
@@ -7198,6 +7642,7 @@ function useMergedGridControls(args) {
7198
7642
  ] });
7199
7643
  return {
7200
7644
  headerMenuFor,
7645
+ filterFor,
7201
7646
  contextMenu,
7202
7647
  columns: enabled ? { order, hidden, onChange: keepColumns } : void 0,
7203
7648
  dialogs,
@@ -7225,9 +7670,11 @@ function useAddedRows(args) {
7225
7670
  const { tableId, enabled, pageRows, pageKey } = args;
7226
7671
  const client = (0, import_react40.useRecordsClient)();
7227
7672
  const [added, setAdded] = (0, import_react39.useState)([]);
7673
+ const [anchors, setAnchors] = (0, import_react39.useState)(() => /* @__PURE__ */ new Map());
7228
7674
  const [busy, setBusy] = (0, import_react39.useState)(false);
7229
7675
  (0, import_react39.useEffect)(() => {
7230
7676
  setAdded([]);
7677
+ setAnchors(/* @__PURE__ */ new Map());
7231
7678
  }, [pageKey, tableId]);
7232
7679
  const pinnedKey = added.map((r) => r.id).join(",");
7233
7680
  const asked = (0, import_react39.useRef)(0);
@@ -7249,11 +7696,11 @@ function useAddedRows(args) {
7249
7696
  );
7250
7697
  });
7251
7698
  }, [enabled, pageRows, pinnedKey, client, tableId]);
7252
- const rows = (0, import_react39.useMemo)(() => {
7253
- if (!enabled || added.length === 0) return pageRows;
7254
- const onPage = new Set(pageRows.map((r) => r.id));
7255
- return [...pageRows, ...added.filter((r) => !onPage.has(r.id))];
7256
- }, [enabled, pageRows, added]);
7699
+ const rows = (0, import_react39.useMemo)(() => enabled ? placePinnedRows(pageRows, added, anchors) : pageRows, [enabled, pageRows, added, anchors]);
7700
+ const pin = (0, import_react39.useCallback)((row, after) => {
7701
+ setAdded((held) => held.some((r) => r.id === row.id) ? held : [...held, row]);
7702
+ if (after) setAnchors((held) => new Map(held).set(row.id, after));
7703
+ }, []);
7257
7704
  const add = (0, import_react39.useCallback)(async () => {
7258
7705
  setBusy(true);
7259
7706
  const written = await client.recordWrite({ table_id: tableId, data: {} });
@@ -7263,7 +7710,29 @@ function useAddedRows(args) {
7263
7710
  setAdded((held) => held.some((r) => r.id === id) ? held : [...held, { id, document: {}, level: "editor", hidden: {} }]);
7264
7711
  return { id };
7265
7712
  }, [client, tableId]);
7266
- return { rows, add, busy };
7713
+ return { rows, add, pin, busy };
7714
+ }
7715
+ function placePinnedRows(pageRows, pinned, anchors) {
7716
+ if (pinned.length === 0) return [...pageRows];
7717
+ const pageIds = new Set(pageRows.map((r) => r.id));
7718
+ const fresh = new Map(pageRows.map((r) => [r.id, r]));
7719
+ const below = /* @__PURE__ */ new Map();
7720
+ const placed = /* @__PURE__ */ new Set();
7721
+ for (const row of pinned) {
7722
+ const anchor = anchors.get(row.id);
7723
+ if (anchor && anchor !== row.id && pageIds.has(anchor)) {
7724
+ below.set(anchor, [...below.get(anchor) ?? [], fresh.get(row.id) ?? row]);
7725
+ placed.add(row.id);
7726
+ }
7727
+ }
7728
+ const out = [];
7729
+ const walk = (row) => {
7730
+ out.push(row);
7731
+ for (const child of below.get(row.id) ?? []) walk(child);
7732
+ };
7733
+ for (const row of pageRows) if (!placed.has(row.id)) walk(row);
7734
+ for (const row of pinned) if (!pageIds.has(row.id) && !placed.has(row.id)) out.push(row);
7735
+ return out;
7267
7736
  }
7268
7737
  function withAddRowCells(columns, line) {
7269
7738
  if (line === null) return columns;
@@ -7785,6 +8254,10 @@ function reorderItems(rows, sort, label) {
7785
8254
  }
7786
8255
  return list.map((row) => ({ id: row.id, label: label(row) }));
7787
8256
  }
8257
+ function fieldColumnIds(ids, fields) {
8258
+ const known = new Set(fields.map((f) => f.key));
8259
+ return ids.filter((id) => known.has(id));
8260
+ }
7788
8261
  function columnLabel(fields, columnId) {
7789
8262
  const field2 = fields.find((f) => f.key === columnId);
7790
8263
  return field2 ? fieldName(field2) : humanize(columnId);
@@ -7796,8 +8269,8 @@ function AddFieldButton({ onClick }) {
7796
8269
  size: "sm",
7797
8270
  variant: "outline",
7798
8271
  onClick,
7799
- "aria-label": "Add a field",
7800
- title: "Add a field to this table",
8272
+ "aria-label": "Add a column",
8273
+ title: "Add a column to this table",
7801
8274
  children: "+"
7802
8275
  }
7803
8276
  );
@@ -7864,7 +8337,8 @@ function columnForField(field2, editing, mayWriteRow, whyNotRow, look, onAskWhoC
7864
8337
  ] }) : name,
7865
8338
  label: name,
7866
8339
  accessorFn: (row) => row.document?.[field2.key],
7867
- sortable: true,
8340
+ // A column the store keeps no value for is not offered a sort it would refuse (`sortRefusal`).
8341
+ sortable: sortRefusal(field2) === null,
7868
8342
  ...format ? { format } : {},
7869
8343
  /*
7870
8344
  * A frozen column MUST declare a width or the table declines to freeze at
@@ -7872,7 +8346,7 @@ function columnForField(field2, editing, mayWriteRow, whyNotRow, look, onAskWhoC
7872
8346
  * frozen column gets one here.
7873
8347
  */
7874
8348
  ...look?.frozen ? { frozen: true, width: FROZEN_COLUMN_WIDTH } : {},
7875
- filter: editorKindFor(field2) === "select" || editorKindFor(field2) === "multi_select" ? "select" : "auto",
8349
+ filter: filterRefusal(field2) !== null ? false : editorKindFor(field2) === "select" || editorKindFor(field2) === "multi_select" ? "select" : "auto",
7876
8350
  cell: (row) => (
7877
8351
  /*
7878
8352
  * 🚨 A READING QUESTION IS NOT AN EDITING RIGHT. This used to read
@@ -8115,7 +8589,7 @@ function confirmLabel(plan, noun, nounPlural) {
8115
8589
  // src/gridActions.tsx
8116
8590
  var import_react44 = require("react");
8117
8591
  var import_react45 = require("@ai-matrx/records/react");
8118
- var import_records11 = require("@ai-matrx/records");
8592
+ var import_records13 = require("@ai-matrx/records");
8119
8593
  var import_design_system26 = require("@ai-matrx/design-system");
8120
8594
  var import_jsx_runtime30 = require("react/jsx-runtime");
8121
8595
  var actionListeners = /* @__PURE__ */ new Map();
@@ -8208,7 +8682,7 @@ function useActionRunner(args) {
8208
8682
  const answered = await client.actionRun({ action_id: action.id, record_ids: ids });
8209
8683
  setBusy(null);
8210
8684
  if (!answered.ok) {
8211
- const refusals = (0, import_records11.actionRefusals)(answered.error);
8685
+ const refusals = (0, import_records13.actionRefusals)(answered.error);
8212
8686
  setOutcome({
8213
8687
  kind: "refused",
8214
8688
  action,
@@ -8458,6 +8932,14 @@ function InTableToolbar({ children }) {
8458
8932
  if (slot === null) return null;
8459
8933
  return (0, import_react_dom.createPortal)(children, slot);
8460
8934
  }
8935
+ var TableToolbarFoldContext = (0, import_react46.createContext)(0);
8936
+ function useTableToolbarFold() {
8937
+ return (0, import_react46.useContext)(TableToolbarFoldContext);
8938
+ }
8939
+ var TableToolbarSheetExtrasContext = (0, import_react46.createContext)(void 0);
8940
+ function useTableToolbarSheetExtras() {
8941
+ return (0, import_react46.useContext)(TableToolbarSheetExtrasContext);
8942
+ }
8461
8943
 
8462
8944
  // src/GridLayoutMenu.tsx
8463
8945
  var import_react47 = require("react");
@@ -8567,114 +9049,10 @@ function GridLayoutMenu({
8567
9049
  ] });
8568
9050
  }
8569
9051
 
8570
- // src/gridPage.ts
8571
- var import_records12 = require("@ai-matrx/records");
8572
- var import_data_table7 = require("@ai-matrx/design-system/data-table");
8573
- function sortAsFor(field2) {
8574
- const kind = String(editorKindFor(field2));
8575
- if (kind === "number" || kind === "currency" || kind === "percent") return "number";
8576
- if (kind === "datetime") return "date";
8577
- return "text";
8578
- }
8579
- function pageSortFor(fields, sort) {
8580
- if (!sort) return [];
8581
- const field2 = fields.find((f) => f.key === sort.id);
8582
- if (!field2) return [];
8583
- return [{ field: field2.key, direction: sort.direction === "desc" ? "desc" : "asc", as: sortAsFor(field2) }];
8584
- }
8585
- function containsPattern(text) {
8586
- return `(?i)${text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`;
8587
- }
8588
- var field = (f) => ({ field: f.id });
8589
- var constant = (value) => ({ const: value });
8590
- var op = (name, ...args) => ({ op: name, args });
8591
- function clauseFor(f, value, now = /* @__PURE__ */ new Date()) {
8592
- const name = fieldName(f);
8593
- const not = (rule) => op("not", rule);
8594
- switch (value.kind) {
8595
- case "text": {
8596
- const mode = value.mode ?? "contains";
8597
- if (mode === "empty" || mode === "null") return value.negated ? { rule: op("present", field(f)) } : { rule: not(op("present", field(f))) };
8598
- if (mode === "not_empty" || mode === "not_null") return value.negated ? { rule: not(op("present", field(f))) } : { rule: op("present", field(f)) };
8599
- const text = value.value.trim();
8600
- if (text === "") return null;
8601
- const kind = String(editorKindFor(f));
8602
- if (kind === "number" || kind === "currency" || kind === "percent" || kind === "checkbox") {
8603
- return `${name} holds numbers, so it is filtered by a range, not by words.`;
8604
- }
8605
- const matches2 = op("matches", field(f), constant(containsPattern(text)));
8606
- return { rule: value.negated ? not(matches2) : matches2 };
8607
- }
8608
- case "select": {
8609
- const words2 = value.values ?? (value.value === "" ? [] : [value.value]);
8610
- if (words2.length === 0) return value.negated ? null : { rule: constant(false) };
8611
- if (words2.length === 1 && !value.negated) return { flat: { [f.key]: words2[0] } };
8612
- const any = words2.length === 1 ? op("eq", field(f), constant(words2[0])) : op("or", ...words2.map((w) => op("eq", field(f), constant(w))));
8613
- return { rule: value.negated ? not(any) : any };
8614
- }
8615
- case "boolean":
8616
- return value.negated ? { rule: op("ne", field(f), constant(value.value)) } : { flat: { [f.key]: value.value } };
8617
- case "number": {
8618
- const parts = [];
8619
- if (typeof value.min === "number") parts.push(op("gte", field(f), constant(value.min)));
8620
- if (typeof value.max === "number") parts.push(op("lte", field(f), constant(value.max)));
8621
- if (parts.length === 0) return null;
8622
- const range = parts.length === 1 ? parts[0] : op("and", ...parts);
8623
- return { rule: value.negated ? not(range) : range };
8624
- }
8625
- case "date": {
8626
- const bounds = (0, import_data_table7.dateFilterBounds)(value, now);
8627
- if (bounds.since === void 0 && bounds.until === void 0) return null;
8628
- return {
8629
- flat: {
8630
- [f.key]: {
8631
- ...bounds.since === void 0 ? {} : { from: bounds.since },
8632
- ...bounds.until === void 0 ? {} : { to: bounds.until }
8633
- }
8634
- }
8635
- };
8636
- }
8637
- default:
8638
- return `${name} cannot be filtered that way by the store.`;
8639
- }
8640
- }
8641
- function storeQuestion(fields, base, columnFilters, now = /* @__PURE__ */ new Date()) {
8642
- const refused = [];
8643
- const flat = {};
8644
- const rules = [];
8645
- if (base && (0, import_records12.isRuleFilter)(base)) rules.push(base);
8646
- else if (base) Object.assign(flat, base);
8647
- for (const [key, value] of Object.entries(columnFilters)) {
8648
- if (!value) continue;
8649
- const f = fields.find((candidate) => candidate.key === key);
8650
- if (!f) continue;
8651
- const clause = clauseFor(f, value, now);
8652
- if (clause === null) continue;
8653
- if (typeof clause === "string") {
8654
- refused.push(clause);
8655
- continue;
8656
- }
8657
- if ("flat" in clause) Object.assign(flat, clause.flat);
8658
- else rules.push(clause.rule);
8659
- }
8660
- const flatKeys = Object.keys(flat);
8661
- if (rules.length === 0) return { filter: flatKeys.length > 0 ? flat : void 0, refused };
8662
- for (const key of flatKeys) {
8663
- const f = fields.find((candidate) => candidate.key === key);
8664
- const value = flat[key];
8665
- if (!f) continue;
8666
- if (value !== null && typeof value === "object") {
8667
- refused.push(`${fieldName(f)}'s date range cannot be asked together with the other filters yet, so it is left off.`);
8668
- continue;
8669
- }
8670
- rules.push(value === null ? op("not", op("present", field(f))) : op("eq", field(f), constant(value)));
8671
- }
8672
- return { filter: rules.length === 1 ? rules[0] : op("and", ...rules), refused };
8673
- }
8674
-
8675
9052
  // src/gridUndo.tsx
8676
9053
  var import_react49 = require("react");
8677
9054
  var import_react50 = require("@ai-matrx/records/react");
9055
+ var import_core5 = require("@ai-matrx/records/core");
8678
9056
  var import_design_system28 = require("@ai-matrx/design-system");
8679
9057
  var import_data_table8 = require("@ai-matrx/design-system/data-table");
8680
9058
  var import_jsx_runtime34 = require("react/jsx-runtime");
@@ -8701,6 +9079,7 @@ function useGridUndo(args) {
8701
9079
  const [busy, setBusy] = (0, import_react49.useState)(false);
8702
9080
  const [notice, setNotice] = (0, import_react49.useState)(null);
8703
9081
  const counter = (0, import_react49.useRef)(0);
9082
+ const dismiss = (0, import_react49.useCallback)(() => setNotice(null), []);
8704
9083
  const push = (0, import_react49.useCallback)((step2) => {
8705
9084
  if (step2.cells.length === 0 && !step2.created?.length && !step2.archived?.length) return;
8706
9085
  counter.current += 1;
@@ -8740,6 +9119,9 @@ function useGridUndo(args) {
8740
9119
  return;
8741
9120
  }
8742
9121
  forget(step2.cells.map((c) => ({ rowId: c.rowId, key: c.key })));
9122
+ for (const id of /* @__PURE__ */ new Set([...step2.cells.map((c) => c.rowId), ...step2.created ?? [], ...step2.archived ?? []])) {
9123
+ (0, import_core5.announceRecordChanged)(id);
9124
+ }
8743
9125
  setHeld(() => {
8744
9126
  let stack = taken.stack;
8745
9127
  if (direction === "undo" && ((step2.created?.length ?? 0) > 0 || step2.noRedo)) {
@@ -8768,49 +9150,64 @@ function useGridUndo(args) {
8768
9150
  depth: { back: held.stack.past.length, forward: held.stack.future.length },
8769
9151
  busy,
8770
9152
  notice,
8771
- dismiss: () => setNotice(null)
9153
+ dismiss,
9154
+ say: (says, sticky = false) => setNotice({ says, refusal: sticky ? { code: "internal", message: says } : null, canUndo: false })
8772
9155
  }),
8773
- [push, undo, redo, held, busy, notice]
9156
+ [push, undo, redo, held, busy, notice, dismiss]
8774
9157
  );
8775
9158
  }
8776
9159
  function UndoRedoButtons({ undo }) {
8777
9160
  if (undo.depth.back === 0 && undo.depth.forward === 0) return null;
8778
9161
  return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
8779
- undo.depth.back > 0 ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
9162
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
8780
9163
  import_design_system28.Button,
8781
9164
  {
8782
9165
  size: "sm",
8783
9166
  variant: "ghost",
8784
9167
  "data-records-undo": "",
8785
- title: `Undo (Cmd-Z) \xB7 ${undo.depth.back} ${undo.depth.back === 1 ? "step" : "steps"} back`,
8786
- disabled: undo.busy,
9168
+ title: undo.depth.back > 0 ? `Undo (Cmd-Z) \xB7 ${undo.depth.back} ${undo.depth.back === 1 ? "step" : "steps"} back` : "Nothing to undo",
9169
+ "aria-label": `Undo, ${undo.depth.back} ${undo.depth.back === 1 ? "step" : "steps"}`,
9170
+ disabled: undo.busy || undo.depth.back === 0,
8787
9171
  onClick: () => void undo.undo(),
8788
- children: "Undo"
9172
+ children: [
9173
+ "Undo ",
9174
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "ml-1 tabular-nums text-muted-foreground", "data-records-undo-count": "", children: undo.depth.back })
9175
+ ]
8789
9176
  }
8790
- ) : null,
8791
- undo.depth.forward > 0 ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
9177
+ ),
9178
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
8792
9179
  import_design_system28.Button,
8793
9180
  {
8794
9181
  size: "sm",
8795
9182
  variant: "ghost",
8796
9183
  "data-records-redo": "",
8797
- title: `Redo (Shift-Cmd-Z) \xB7 ${undo.depth.forward} ${undo.depth.forward === 1 ? "step" : "steps"} forward`,
8798
- disabled: undo.busy,
9184
+ title: undo.depth.forward > 0 ? `Redo (Shift-Cmd-Z) \xB7 ${undo.depth.forward} ${undo.depth.forward === 1 ? "step" : "steps"} forward` : "Nothing to redo",
9185
+ "aria-label": `Redo, ${undo.depth.forward} ${undo.depth.forward === 1 ? "step" : "steps"}`,
9186
+ disabled: undo.busy || undo.depth.forward === 0,
8799
9187
  onClick: () => void undo.redo(),
8800
- children: "Redo"
9188
+ children: [
9189
+ "Redo ",
9190
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "ml-1 tabular-nums text-muted-foreground", "data-records-redo-count": "", children: undo.depth.forward })
9191
+ ]
8801
9192
  }
8802
- ) : null
9193
+ )
8803
9194
  ] });
8804
9195
  }
8805
9196
  function UndoNotice({ undo }) {
8806
9197
  const notice = undo.notice;
9198
+ const dismiss = undo.dismiss;
9199
+ (0, import_react49.useEffect)(() => {
9200
+ if (!notice || notice.refusal) return;
9201
+ const timer = setTimeout(dismiss, 8e3);
9202
+ return () => clearTimeout(timer);
9203
+ }, [notice, dismiss]);
8807
9204
  if (!notice) return null;
8808
9205
  return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
8809
9206
  "p",
8810
9207
  {
8811
9208
  role: "status",
8812
9209
  "data-records-undo-notice": notice.refusal ? "refused" : "done",
8813
- className: "flex items-center gap-2 rounded border border-border bg-muted/40 px-2 py-1 text-xs",
9210
+ className: "pointer-events-auto absolute bottom-3 left-1/2 z-30 flex max-w-[min(36rem,calc(100%-2rem))] -translate-x-1/2 items-center gap-2 rounded-md border border-border bg-popover px-3 py-1.5 text-xs text-popover-foreground shadow-lg",
8814
9211
  children: [
8815
9212
  /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { children: notice.says }),
8816
9213
  notice.canUndo && undo.canUndo ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_design_system28.Button, { size: "sm", variant: "outline", className: "ml-auto h-6", "data-records-undo-now": "", onClick: () => void undo.undo(), children: "Undo" }) : null,
@@ -8832,7 +9229,7 @@ function undoKey(event) {
8832
9229
 
8833
9230
  // src/gridAssist.tsx
8834
9231
  var import_react52 = require("react");
8835
- var import_core5 = require("@ai-matrx/records/core");
9232
+ var import_core6 = require("@ai-matrx/records/core");
8836
9233
  var import_content_transfer = require("@ai-matrx/design-system/content-transfer");
8837
9234
 
8838
9235
  // src/gridAgentContext.ts
@@ -9102,7 +9499,7 @@ function useGridAssist(args) {
9102
9499
  if (!row) return;
9103
9500
  const document2 = row.document ?? {};
9104
9501
  const value = editing.valueFor(row, fieldOf2(key));
9105
- const pointer = typeof document2[key] === "string" ? (0, import_core5.wholeValueOf)(document2, key) : null;
9502
+ const pointer = typeof document2[key] === "string" ? (0, import_core6.wholeValueOf)(document2, key) : null;
9106
9503
  setWhole({ rowId, key, rowTitle: titleOf(row), value, pointer });
9107
9504
  },
9108
9505
  [rowOf, fieldOf2, titleOf, editing]
@@ -9195,6 +9592,7 @@ function useGridAssist(args) {
9195
9592
  // src/gridBulk.tsx
9196
9593
  var import_react53 = require("react");
9197
9594
  var import_react54 = require("@ai-matrx/records/react");
9595
+ var import_core7 = require("@ai-matrx/records/core");
9198
9596
  var import_design_system30 = require("@ai-matrx/design-system");
9199
9597
  var import_jsx_runtime37 = require("react/jsx-runtime");
9200
9598
  function countOf(n, one, many) {
@@ -9251,6 +9649,7 @@ function useGridBulk(args) {
9251
9649
  setRefusal(done.error);
9252
9650
  return null;
9253
9651
  }
9652
+ for (const change of changes) if ("record_id" in change && change.record_id) (0, import_core7.announceRecordChanged)(change.record_id);
9254
9653
  return done.data;
9255
9654
  },
9256
9655
  [client, tableId]
@@ -9411,7 +9810,7 @@ function BulkActions({
9411
9810
  // src/handOrder.tsx
9412
9811
  var import_react55 = require("react");
9413
9812
  var import_react56 = require("@ai-matrx/records/react");
9414
- var import_records13 = require("@ai-matrx/records");
9813
+ var import_records14 = require("@ai-matrx/records");
9415
9814
  var import_design_system31 = require("@ai-matrx/design-system");
9416
9815
 
9417
9816
  // src/viewKeys.ts
@@ -9628,8 +10027,8 @@ function unknownViewLine(raw) {
9628
10027
  }
9629
10028
  var LAYOUT_NO_FIELD = {
9630
10029
  grid: "",
9631
- kanban: "This table has no choice field yet, so there is nothing to make columns from. Add a field that holds one of a set of choices \u2014 Add field, then Select \u2014 and it appears here.",
9632
- calendar: "This table has no date field yet, so there is nothing to place records on. Add a field that holds a date \u2014 Add field, then Date \u2014 and it appears here.",
10030
+ kanban: "This table has no choice field yet, so there is nothing to make columns from. Add a field that holds one of a set of choices \u2014 Add column, then Select \u2014 and it appears here.",
10031
+ calendar: "This table has no date field yet, so there is nothing to place records on. Add a field that holds a date \u2014 Add column, then Date \u2014 and it appears here.",
9633
10032
  gallery: ""
9634
10033
  };
9635
10034
  function viewDocument(spec) {
@@ -9839,7 +10238,7 @@ function useHandOrderRows(args) {
9839
10238
  const client = (0, import_react56.useRecordsClient)();
9840
10239
  const { viewId, active, page, pageSize } = args;
9841
10240
  const filter = args.filter && Object.keys(args.filter).length > 0 ? args.filter : null;
9842
- const filterKey = filter ? (0, import_records13.stableFilterKey)(filter) : "";
10241
+ const filterKey = filter ? (0, import_records14.stableFilterKey)(filter) : "";
9843
10242
  const filterNow = (0, import_react55.useRef)(filter);
9844
10243
  filterNow.current = filter;
9845
10244
  const [answer, setAnswer] = (0, import_react55.useState)(null);
@@ -9966,6 +10365,7 @@ function Grid({
9966
10365
  pageSize = 50,
9967
10366
  filter,
9968
10367
  onOpenRecord,
10368
+ openRecordId = null,
9969
10369
  onAddField,
9970
10370
  onNewRecordForm,
9971
10371
  toolbarActions,
@@ -9995,7 +10395,8 @@ function Grid({
9995
10395
  );
9996
10396
  const merged = host.grid === "merged";
9997
10397
  const phone = (0, import_design_system32.useIsMobile)();
9998
- const [toolsOpen, setToolsOpen] = (0, import_react57.useState)(false);
10398
+ const toolbarFold2 = useTableToolbarFold();
10399
+ const sheetExtras = useTableToolbarSheetExtras();
9999
10400
  const rights = useTableRights(table.data);
10000
10401
  const [heldOrder, setHeldOrder] = (0, import_react57.useState)(null);
10001
10402
  const sortsKey = JSON.stringify(viewOrder?.sorts ?? []);
@@ -10080,6 +10481,7 @@ function Grid({
10080
10481
  );
10081
10482
  const canWrite = Boolean(editable) && (rights.write || askPerRow && rowIds.some(mayWriteRow));
10082
10483
  const pushWritten = (0, import_react57.useRef)(null);
10484
+ const sheetController = (0, import_react57.useRef)(null);
10083
10485
  const editing = useGridEditing({
10084
10486
  tableId,
10085
10487
  fields: fields.data ?? [],
@@ -10087,7 +10489,15 @@ function Grid({
10087
10489
  reload: records.reload,
10088
10490
  canWrite,
10089
10491
  mayWriteRow,
10090
- ...merged ? { onWritten: (write) => pushWritten.current?.(write) } : {}
10492
+ ...merged ? { onWritten: (write) => pushWritten.current?.(write) } : {},
10493
+ /*
10494
+ * THE KEYBOARD COMES BACK (merged-grid review 2026-09-26, A1): Enter moves the cursor down, Tab
10495
+ * right, Escape stays, and the grid — not the page body — has the keys, so the next arrow,
10496
+ * Enter or Cmd-Z works without a click. Sheets and Airtable.
10497
+ */
10498
+ ...merged ? {
10499
+ onEnded: (exit, from, how) => sheetController.current?.endEdit(exit, { rowId: from.rowId, columnId: from.key }, how)
10500
+ } : {}
10091
10501
  });
10092
10502
  const undo = useGridUndo({ tableId, reload: records.reload, forget: editing.forget });
10093
10503
  pushWritten.current = (write) => {
@@ -10102,7 +10512,7 @@ function Grid({
10102
10512
  const [localLook, setLocalLook] = (0, import_react57.useState)(null);
10103
10513
  const [localSummaries, setLocalSummaries] = (0, import_react57.useState)(null);
10104
10514
  const sortedKeys = (0, import_react57.useMemo)(
10105
- () => [...fields.data ?? []].sort((a, b) => (a.sort ?? 100) - (b.sort ?? 100) || a.key.localeCompare(b.key)).map((f) => f.key),
10515
+ () => [...fields.data ?? []].sort(compareFieldOrder).map((f) => f.key),
10106
10516
  [fields.data]
10107
10517
  );
10108
10518
  const look = (0, import_react57.useMemo)(() => {
@@ -10119,16 +10529,16 @@ function Grid({
10119
10529
  const frozenList = look.frozen;
10120
10530
  const frozenKeys = (0, import_react57.useMemo)(() => new Set(frozenList), [frozenList]);
10121
10531
  const ordered = (0, import_react57.useMemo)(() => {
10122
- const all = [...fields.data ?? []].sort(
10123
- (a, b) => (a.sort ?? 100) - (b.sort ?? 100) || a.key.localeCompare(b.key)
10124
- );
10532
+ const all = [...fields.data ?? []].sort(compareFieldOrder);
10125
10533
  if (frozenKeys.size === 0) return all;
10126
10534
  const pinned = frozenList.map((key) => all.find((f) => f.key === key)).filter((f) => f !== void 0);
10127
10535
  return [...pinned, ...all.filter((f) => !frozenKeys.has(f.key))];
10128
10536
  }, [fields.data, frozenKeys, frozenList]);
10129
10537
  const tableActions = useTableActions(tableId, canWrite || Boolean(host.runAgentAction));
10130
- const noun = (table.data?.label_singular ?? "record").toLowerCase();
10131
- const nounPlural = pluralNoun(table.data ?? null).toLowerCase();
10538
+ const ownWord = (table.data?.label_singular ?? "").trim();
10539
+ const sameAsName = ownWord === "" || (table.data ? ownWord.toLowerCase() === tableName(table.data).toLowerCase() : true);
10540
+ const noun = merged && sameAsName ? "row" : (table.data?.label_singular ?? "record").toLowerCase();
10541
+ const nounPlural = merged && sameAsName ? "rows" : pluralNoun(table.data ?? null).toLowerCase();
10132
10542
  const runner = useActionRunner({
10133
10543
  rows,
10134
10544
  fields: fields.data ?? [],
@@ -10176,10 +10586,29 @@ function Grid({
10176
10586
  setDuplicated(`The copy was not made: ${made.error.message}`);
10177
10587
  return;
10178
10588
  }
10179
- setDuplicated(`A copy of ${recordNameIn(table.data, document2)} is in the table.`);
10589
+ const copyId = made.data;
10590
+ if (merged) added.pin({ id: copyId, document: data, level: row.level, hidden: {} }, row.id);
10591
+ if (manual && viewId) {
10592
+ const ids = rows.map((r) => r.id).filter((id) => id !== copyId);
10593
+ const at = ids.indexOf(row.id);
10594
+ const next = [...ids.slice(0, at + 1), copyId, ...ids.slice(at + 1)];
10595
+ const placed = await client.viewRecordOrderSet({ view_id: viewId, record_ids: next });
10596
+ if (!placed.ok) {
10597
+ setDuplicated(`A copy of ${recordNameIn(table.data, document2)} was made and could not be placed beside it: ${placed.error.message}`);
10598
+ records.reload();
10599
+ return;
10600
+ }
10601
+ viewOrder?.onPlaced?.();
10602
+ hand.reload();
10603
+ }
10604
+ if (merged) {
10605
+ undo.push({ label: `Duplicated ${recordNameIn(table.data, document2)}`, cells: [], created: [copyId] });
10606
+ } else {
10607
+ setDuplicated(`A copy of ${recordNameIn(table.data, document2)} is in the table, right below it.`);
10608
+ }
10180
10609
  records.reload();
10181
10610
  },
10182
- [client, fields.data, host, records, table.data, tableId]
10611
+ [client, fields.data, host, records, table.data, tableId, merged, added, manual, viewId, rows, viewOrder, hand, undo]
10183
10612
  );
10184
10613
  const recordIds = (0, import_react57.useMemo)(() => rows.map((r) => r.id), [rows]);
10185
10614
  const { cells: enrichCells, refresh: refreshEnrich } = useEnrichCells(tableId, recordIds);
@@ -10271,9 +10700,20 @@ function Grid({
10271
10700
  why
10272
10701
  });
10273
10702
  }
10703
+ if (merged && built.cells > 0 && built.creates === 0 && built.refusals.length === 0 && built.pastRightEdge === 0 && built.unmatchedOptions.length === 0) {
10704
+ const outcome = await pasteAtOnce.current(built);
10705
+ if (outcome.refusals.length > 0 || outcome.missing > 0) {
10706
+ undo.say(
10707
+ outcome.refusals.length > 0 ? outcome.refusals.join(" ") : `${outcome.missing} pasted ${outcome.missing === 1 ? "value was" : "values were"} not found in the table when it was read back.`,
10708
+ true
10709
+ );
10710
+ }
10711
+ sheetController.current?.focusGrid();
10712
+ return;
10713
+ }
10274
10714
  setPastePlan({ plan: built });
10275
10715
  },
10276
- [fields.data, loadChoiceOptions, rows]
10716
+ [fields.data, loadChoiceOptions, rows, merged, undo]
10277
10717
  );
10278
10718
  const addPastedOptions = (0, import_react57.useCallback)(
10279
10719
  async (fieldKey, words2) => {
@@ -10329,6 +10769,7 @@ function Grid({
10329
10769
  }))
10330
10770
  );
10331
10771
  editing.forget(cells);
10772
+ for (const u of updates) (0, import_core8.announceRecordChanged)(u.row.recordId);
10332
10773
  const lines = updates.length + creates.length;
10333
10774
  undo.push({ label: `Pasted ${lines} ${lines === 1 ? "row" : "rows"}`, cells, created });
10334
10775
  const expected = [
@@ -10353,6 +10794,8 @@ function Grid({
10353
10794
  },
10354
10795
  [rows, client, tableId, editing, undo, records]
10355
10796
  );
10797
+ const pasteAtOnce = (0, import_react57.useRef)(applyPasteAsOne);
10798
+ pasteAtOnce.current = applyPasteAsOne;
10356
10799
  const writeRange = (0, import_react57.useCallback)(
10357
10800
  async (patches, intent) => {
10358
10801
  if (patches.length === 0) return;
@@ -10377,7 +10820,14 @@ function Grid({
10377
10820
  const applyPaste = (0, import_react57.useCallback)(async () => {
10378
10821
  const held = pastePlan;
10379
10822
  if (!held) return { written: 0, readBack: 0, missing: 0, refusals: [] };
10380
- if (merged) return applyPasteAsOne(held.plan);
10823
+ if (merged) {
10824
+ const outcome = await applyPasteAsOne(held.plan);
10825
+ if (outcome.missing === 0 && outcome.refusals.length === 0) {
10826
+ setPastePlan(null);
10827
+ sheetController.current?.focusGrid();
10828
+ }
10829
+ return outcome;
10830
+ }
10381
10831
  const refusals = [];
10382
10832
  let written = 0;
10383
10833
  const expected = [];
@@ -10472,7 +10922,7 @@ function Grid({
10472
10922
  tableName: table.data ? tableName(table.data) : "",
10473
10923
  fields: fields.data ?? [],
10474
10924
  document: row.document ?? {},
10475
- level: (0, import_records14.asPermissionLevel)(row.level)
10925
+ level: (0, import_records15.asPermissionLevel)(row.level)
10476
10926
  }),
10477
10927
  [host, tableId, table.data, fields.data]
10478
10928
  );
@@ -10500,6 +10950,18 @@ function Grid({
10500
10950
  total: typeof records.data?.total === "number" ? records.data.total : null,
10501
10951
  search: searchTerm
10502
10952
  });
10953
+ const followRef = (0, import_react57.useRef)({ openRecordId, onOpenRecord });
10954
+ followRef.current = { openRecordId, onOpenRecord };
10955
+ const followSelection = (0, import_react57.useCallback)(
10956
+ (selection) => {
10957
+ assist.onSelectionChange(selection);
10958
+ const { openRecordId: open, onOpenRecord: openIt } = followRef.current;
10959
+ if (!open || !openIt || !selection) return;
10960
+ const rowId = selection.active.rowId;
10961
+ if (rowId !== open && rowId !== ADD_ROW.id && rowId !== SUMMARY_ROW.id) openIt(rowId);
10962
+ },
10963
+ [assist.onSelectionChange]
10964
+ );
10503
10965
  const addRowRef = (0, import_react57.useRef)(() => void 0);
10504
10966
  const controls = useMergedGridControls({
10505
10967
  enabled: merged,
@@ -10614,10 +11076,10 @@ function Grid({
10614
11076
  }
10615
11077
  );
10616
11078
  }
10617
- ), mayAddRow ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(AddRowLine, { onAdd: () => void addRowHere(), busy: added.busy }) : null), controls.headerMenuFor);
11079
+ ), mayAddRow ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(AddRowLine, { onAdd: () => void addRowHere(), busy: added.busy }) : null), controls.headerMenuFor, controls.filterFor);
10618
11080
  return merged && rights.structure && editable ? [...drawn, addColumnColumn(controls.addColumn)] : drawn;
10619
11081
  },
10620
- [fieldColumns, onOpenRecord, table.data, ordered, chosenSummaries, summaries, chooseSummary, controls.headerMenuFor, mayAddRow, addRowHere, added.busy, merged, rights.structure, editable, controls.addColumn]
11082
+ [fieldColumns, onOpenRecord, table.data, ordered, chosenSummaries, summaries, chooseSummary, controls.headerMenuFor, controls.filterFor, mayAddRow, addRowHere, added.busy, merged, rights.structure, editable, controls.addColumn]
10621
11083
  );
10622
11084
  const [localColumns, setLocalColumns] = (0, import_react57.useState)(
10623
11085
  null
@@ -10640,7 +11102,11 @@ function Grid({
10640
11102
  () => ({
10641
11103
  order: controls.columns?.order ?? localColumns?.order ?? columnIds,
10642
11104
  hidden: controls.columns?.hidden ?? localColumns?.hidden ?? [],
10643
- onChange: (next) => controls.columns ? controls.columns.onChange(next) : setLocalColumns(next),
11105
+ onChange: (drawn) => {
11106
+ const next = { order: fieldColumnIds(drawn.order, fields.data ?? []), hidden: fieldColumnIds(drawn.hidden, fields.data ?? []) };
11107
+ if (controls.columns) controls.columns.onChange(next);
11108
+ else setLocalColumns(next);
11109
+ },
10644
11110
  ...widths ? { widths } : {},
10645
11111
  // A drag that changed nothing would be the silent half of a control that
10646
11112
  // looks alive, so the width always lands somewhere — the session at
@@ -10650,13 +11116,12 @@ function Grid({
10650
11116
  writeLook({ ...presentation, widths: next });
10651
11117
  }
10652
11118
  }),
10653
- [columnIds, localColumns, widths, presentation, writeLook, controls.columns]
11119
+ [columnIds, localColumns, widths, presentation, writeLook, controls.columns, fields.data]
10654
11120
  );
10655
11121
  const groupingConfig = (0, import_react57.useMemo)(() => {
10656
11122
  const view = presentation.grouping;
10657
11123
  const field2 = view?.field ?? null;
10658
11124
  if (field2 === null && !onPresentationChange) return null;
10659
- if (field2 === null && merged && phone) return null;
10660
11125
  const aggregates = view && Object.keys(view.aggregates ?? {}).length > 0 ? view.aggregates : field2 ? { [field2]: { kind: "count" } } : void 0;
10661
11126
  return {
10662
11127
  columnId: field2,
@@ -10703,7 +11168,7 @@ function Grid({
10703
11168
  return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(WhoChangedSource, { tableId, labelOf: labelOfField, children: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
10704
11169
  "div",
10705
11170
  {
10706
- className: (0, import_design_system32.cn)("flex min-h-0 flex-col gap-2", fills && "flex-1", className),
11171
+ className: (0, import_design_system32.cn)("relative flex min-h-0 flex-col gap-2", fills && "flex-1", className),
10707
11172
  "data-records-grid-footer": footer ?? "natural",
10708
11173
  ...merged ? {
10709
11174
  // ONE Cmd-Z (tranche 6d): the grid's own undo, never a text box's.
@@ -10718,7 +11183,7 @@ function Grid({
10718
11183
  } : {},
10719
11184
  children: [
10720
11185
  toolbarActions ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "flex items-center gap-2", "data-records-grid-actions": "", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "ml-auto flex items-center gap-1", children: toolbarActions }) }) : null,
10721
- records.error ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(RefusalNotice, { error: records.error }) : null,
11186
+ records.error ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(RefusalNotice, { error: inColumnWords(records.error, fields.data ?? []) }) : null,
10722
11187
  manual && hand.error ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(RefusalNotice, { error: hand.error }) : null,
10723
11188
  archived ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
10724
11189
  "p",
@@ -10930,7 +11395,7 @@ function Grid({
10930
11395
  editing.setCell(patch.rowId, patch.columnId, patch.value);
10931
11396
  }
10932
11397
  },
10933
- ...merged ? { undoDepth: 0, onSelectionChange: assist.onSelectionChange } : {},
11398
+ ...merged ? { undoDepth: 0, onSelectionChange: followSelection, controllerRef: sheetController } : {},
10934
11399
  columnRefusal: (columnId) => {
10935
11400
  const field2 = (fields.data ?? []).find((f) => f.key === columnId);
10936
11401
  if (!field2) return `This table has no column called ${columnId}.`;
@@ -10952,6 +11417,7 @@ function Grid({
10952
11417
  onEditRequest: (address, seed) => {
10953
11418
  const field2 = (fields.data ?? []).find((f) => f.key === address.columnId);
10954
11419
  if (!field2 || !fieldIsEditable(field2) || !mayWriteRow(address.rowId)) return false;
11420
+ if (seed !== null && seed !== "" && typingOpens(field2) === "none") return false;
10955
11421
  editing.begin({ rowId: address.rowId, key: field2.key }, seed);
10956
11422
  return true;
10957
11423
  },
@@ -10968,7 +11434,7 @@ function Grid({
10968
11434
  spreadsheet: {
10969
11435
  enabled: true,
10970
11436
  readOnly: true,
10971
- ...merged ? { onSelectionChange: assist.onSelectionChange } : {},
11437
+ ...merged ? { onSelectionChange: followSelection, controllerRef: sheetController } : {},
10972
11438
  readOnlyReason: !editable || !rights.known || rights.write ? "Editing is off here, so this table can be copied but not changed." : rights.why("write")
10973
11439
  }
10974
11440
  },
@@ -11061,7 +11527,12 @@ function Grid({
11061
11527
  ) : null
11062
11528
  ] })
11063
11529
  } : {},
11064
- emptyState: {
11530
+ emptyState: records.error ? {
11531
+ // A REFUSED READ IS NOT AN EMPTY TABLE (merged-grid review 2026-09-26): the store's own
11532
+ // sentence is above the grid, and this never says "No records yet" over it.
11533
+ title: "These records could not be read",
11534
+ description: inColumnWords(records.error, fields.data ?? []).message
11535
+ } : {
11065
11536
  ...mayAddRow ? { action: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(AddFirstRowButton, { onAdd: () => void addRowHere(), busy: added.busy }) } : {},
11066
11537
  title: "No records yet",
11067
11538
  description: rights.write ? "Add the first one and it appears here." : rights.known ? `Nothing has been written to this table. ${rights.why("write")}` : "Nothing has been written to this table."
@@ -11074,35 +11545,21 @@ function Grid({
11074
11545
  * the row, so Tools is never scrolled out of reach; the search is the same query.
11075
11546
  */
11076
11547
  ...merged && phone ? {
11077
- customSearch: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex min-w-0 flex-1 items-center gap-1.5", "data-records-grid-phone-search": "", children: [
11078
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
11079
- "input",
11080
- {
11081
- type: "search",
11082
- value: query.search,
11083
- placeholder: "Search",
11084
- "aria-label": "Search records",
11085
- onChange: (event) => {
11086
- const search = event.target.value;
11087
- setQuery((held) => ({ ...held, search, page: 1 }));
11088
- },
11089
- className: "h-8 w-full min-w-0 rounded-md border border-input bg-background px-2 text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
11090
- style: { fontSize: "16px" }
11091
- }
11092
- ),
11093
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
11094
- import_design_system32.Button,
11095
- {
11096
- size: "sm",
11097
- variant: "outline",
11098
- className: "h-8 shrink-0",
11099
- "data-records-grid-tools": "",
11100
- "aria-haspopup": "dialog",
11101
- onClick: () => setToolsOpen(true),
11102
- children: "Tools"
11103
- }
11104
- )
11105
- ] })
11548
+ customSearch: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "flex min-w-0 flex-1 items-center gap-1.5", "data-records-grid-phone-search": "", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
11549
+ "input",
11550
+ {
11551
+ type: "search",
11552
+ value: query.search,
11553
+ placeholder: "Search",
11554
+ "aria-label": "Search records",
11555
+ onChange: (event) => {
11556
+ const search = event.target.value;
11557
+ setQuery((held) => ({ ...held, search, page: 1 }));
11558
+ },
11559
+ className: "h-8 w-full min-w-0 rounded-md border border-input bg-background px-2 text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
11560
+ style: { fontSize: "16px" }
11561
+ }
11562
+ ) })
11106
11563
  } : {},
11107
11564
  /* ONE ROW (lane TABLE-PAGE-CHROME): on a table page the grid's search, columns and
11108
11565
  sort are drawn INTO the page's toolbar row, never a second bar under it. */
@@ -11110,63 +11567,110 @@ function Grid({
11110
11567
  // SCR-2 / SCR-4: PRESENT for an admin, ABSENT for everyone else.
11111
11568
  // There is no third state: this package ships no disabled variant of
11112
11569
  // these controls, so a member cannot be shown a dead one by mistake.
11113
- actions: (() => {
11114
- const all = /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_jsx_runtime39.Fragment, { children: [
11115
- manual ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
11116
- SortStateControl,
11117
- {
11118
- ...handOrderSortState({
11119
- sortName: columnSort ? columnLabel(fields.data ?? [], columnSort.id) : null,
11120
- direction: columnSort?.direction ?? null,
11121
- onBack: () => setQuery((held) => ({ ...held, sort: null, page: 1 })),
11122
- ...viewOrder?.onUseSort && columnSort ? {
11123
- onUseSort: () => {
11124
- const sort = columnSort;
11125
- const chosen = { field: sort.id, direction: sort.direction };
11126
- setHeldOrder({ order: "sorted", sorts: [chosen] });
11127
- viewOrder.onUseSort?.(chosen);
11128
- host.notify?.success(
11129
- `Sorted by ${columnLabel(fields.data ?? [], sort.id)} ${sortArrow(sort.direction)}. ${MANUAL_SORT.replacedToast}`
11130
- );
11131
- }
11132
- } : {}
11133
- })
11134
- }
11135
- ) : null,
11136
- merged ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(UndoRedoButtons, { undo }) : null,
11137
- viewId && viewOrder?.onPlaced && rows.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_design_system32.Button, { size: "sm", variant: "ghost", onClick: () => setReordering(true), "data-records-reorder-open": "", children: "Reorder" }) : null,
11138
- Boolean(editable) && rights.write ? merged ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_design_system32.Button, { size: "sm", disabled: added.busy, onClick: () => void addRowHere(), children: added.busy ? "Adding\u2026" : "New record" }) : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_design_system32.Button, { size: "sm", disabled: editing.busy, onClick: () => void editing.addRecord(), children: editing.busy ? "Adding\u2026" : "New record" }) : null,
11139
- rights.structure && onAddField ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(AddFieldButton, { onClick: onAddField }) : null,
11140
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
11141
- GridLayoutMenu,
11142
- {
11143
- look,
11144
- firstColumn: sortedKeys[0] ? {
11145
- key: sortedKeys[0],
11146
- name: (() => {
11147
- const first = (fields.data ?? []).find((f) => f.key === sortedKeys[0]);
11148
- return first ? fieldName(first) : humanize(sortedKeys[0]);
11149
- })()
11150
- } : null,
11151
- onChange: changeLayout,
11152
- kept: Boolean(onPresentationChange)
11153
- }
11154
- )
11155
- ] });
11156
- if (!merged || !phone) return all;
11157
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_jsx_runtime39.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_design_system32.BottomSheet, { open: toolsOpen, onOpenChange: setToolsOpen, title: "Table tools", surface: "solid", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
11158
- "div",
11570
+ /*
11571
+ * WHERE THE GRID'S CONTROLS GO (lane C, review 2026-09-26 D6). Inline when the row has
11572
+ * room; behind the row's "…" once the page row is folded that far (Undo / Redo and New
11573
+ * record stay beside it — the primary action is never folded); on a phone EVERY control
11574
+ * — the table's own (clear, copy/export, group, columns), these, the search, the sort and
11575
+ * the page's filter, layout and share — in ONE "Tools" sheet. Never dropped.
11576
+ */
11577
+ actions: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_jsx_runtime39.Fragment, { children: [
11578
+ manual ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
11579
+ SortStateControl,
11159
11580
  {
11160
- "data-records-grid-tools-sheet": "",
11161
- className: "flex flex-col items-stretch gap-1 px-3 pb-6 [&>button]:justify-start [&_button]:min-h-10",
11162
- onClickCapture: (event) => {
11163
- const target = event.target.closest("button");
11164
- if (target && !target.hasAttribute("aria-haspopup")) setToolsOpen(false);
11165
- },
11166
- children: all
11581
+ ...handOrderSortState({
11582
+ sortName: columnSort ? columnLabel(fields.data ?? [], columnSort.id) : null,
11583
+ direction: columnSort?.direction ?? null,
11584
+ onBack: () => setQuery((held) => ({ ...held, sort: null, page: 1 })),
11585
+ ...viewOrder?.onUseSort && columnSort ? {
11586
+ onUseSort: () => {
11587
+ const sort = columnSort;
11588
+ const chosen = { field: sort.id, direction: sort.direction };
11589
+ setHeldOrder({ order: "sorted", sorts: [chosen] });
11590
+ viewOrder.onUseSort?.(chosen);
11591
+ host.notify?.success(
11592
+ `Sorted by ${columnLabel(fields.data ?? [], sort.id)} ${sortArrow(sort.direction)}. ${MANUAL_SORT.replacedToast}`
11593
+ );
11594
+ }
11595
+ } : {}
11596
+ })
11597
+ }
11598
+ ) : null,
11599
+ viewId && viewOrder?.onPlaced && rows.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_design_system32.Button, { size: "sm", variant: "ghost", onClick: () => setReordering(true), "data-records-reorder-open": "", children: "Reorder" }) : null,
11600
+ !merged && Boolean(editable) && rights.write ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_design_system32.Button, { size: "sm", disabled: editing.busy, onClick: () => void editing.addRecord(), children: editing.busy ? "Adding\u2026" : "New record" }) : null,
11601
+ rights.structure && onAddField ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(AddFieldButton, { onClick: onAddField }) : null,
11602
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
11603
+ GridLayoutMenu,
11604
+ {
11605
+ look,
11606
+ firstColumn: sortedKeys[0] ? {
11607
+ key: sortedKeys[0],
11608
+ name: (() => {
11609
+ const first = (fields.data ?? []).find((f) => f.key === sortedKeys[0]);
11610
+ return first ? fieldName(first) : humanize(sortedKeys[0]);
11611
+ })()
11612
+ } : null,
11613
+ onChange: changeLayout,
11614
+ kept: Boolean(onPresentationChange)
11167
11615
  }
11168
- ) }) });
11169
- })()
11616
+ )
11617
+ ] }),
11618
+ ...merged ? {
11619
+ overflow: {
11620
+ mode: phone ? "sheet" : toolbarFold2 >= 3 ? "menu" : "inline",
11621
+ pinned: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_jsx_runtime39.Fragment, { children: [
11622
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(UndoRedoButtons, { undo }),
11623
+ Boolean(editable) && rights.write ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_design_system32.Button, { size: "sm", className: "shrink-0", disabled: added.busy, onClick: () => void addRowHere(), children: added.busy ? "Adding\u2026" : "New record" }) : null
11624
+ ] }),
11625
+ ...phone ? {
11626
+ sheetTop: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex flex-col gap-1.5 pb-1", "data-records-grid-sheet-top": "", "data-matrx-table-tools-keep-open": "", children: [
11627
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
11628
+ "input",
11629
+ {
11630
+ type: "search",
11631
+ value: query.search,
11632
+ placeholder: "Search",
11633
+ "aria-label": "Search records",
11634
+ onChange: (event) => {
11635
+ const search = event.target.value;
11636
+ setQuery((held) => ({ ...held, search, page: 1 }));
11637
+ },
11638
+ className: "h-10 w-full min-w-0 rounded-md border border-input bg-background px-2 text-foreground placeholder:text-muted-foreground",
11639
+ style: { fontSize: "16px" }
11640
+ }
11641
+ ),
11642
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("label", { className: "flex items-center gap-2 text-xs text-muted-foreground", "data-records-grid-sheet-sort": "", children: [
11643
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "shrink-0", children: "Sort by" }),
11644
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
11645
+ "select",
11646
+ {
11647
+ "aria-label": "Sort by",
11648
+ value: query.sort ? `${query.sort.id}:${query.sort.direction}` : "",
11649
+ onChange: (event) => {
11650
+ const [id, direction] = event.target.value.split(":");
11651
+ setQuery((held) => ({
11652
+ ...held,
11653
+ page: 1,
11654
+ sort: id ? { id, direction: direction === "desc" ? "desc" : "asc" } : null
11655
+ }));
11656
+ },
11657
+ className: "h-10 min-w-0 flex-1 rounded-md border border-input bg-background px-2 text-foreground",
11658
+ style: { fontSize: "16px" },
11659
+ children: [
11660
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("option", { value: "", children: "No sort" }),
11661
+ ordered.flatMap((field2) => [
11662
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("option", { value: `${field2.key}:asc`, children: `${fieldName(field2)} \u2191` }, `${field2.key}:asc`),
11663
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("option", { value: `${field2.key}:desc`, children: `${fieldName(field2)} \u2193` }, `${field2.key}:desc`)
11664
+ ])
11665
+ ]
11666
+ }
11667
+ )
11668
+ ] })
11669
+ ] }),
11670
+ ...sheetExtras ? { sheetExtras } : {}
11671
+ } : {}
11672
+ }
11673
+ } : {}
11170
11674
  }
11171
11675
  },
11172
11676
  `records-grid-${tableId}:${openingKey}`
@@ -11178,11 +11682,14 @@ function Grid({
11178
11682
  PastePreview,
11179
11683
  {
11180
11684
  plan: pastePlan.plan,
11181
- noun: table.data?.label_singular ?? "record",
11182
- nounPlural: pluralNoun(table.data ?? null),
11685
+ noun: merged ? noun : table.data?.label_singular ?? "record",
11686
+ nounPlural: merged ? nounPlural : pluralNoun(table.data ?? null),
11183
11687
  onConfirm: applyPaste,
11184
11688
  onAddOptions: addPastedOptions,
11185
- onCancel: () => setPastePlan(null)
11689
+ onCancel: () => {
11690
+ setPastePlan(null);
11691
+ sheetController.current?.focusGrid();
11692
+ }
11186
11693
  }
11187
11694
  ) : null,
11188
11695
  merged ? null : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
@@ -11248,7 +11755,17 @@ function Grid({
11248
11755
  const failed = await editing.remove(row.id);
11249
11756
  setDeleteBusy(false);
11250
11757
  setDeleting(null);
11251
- if (!failed) setArchived(name);
11758
+ if (failed) return;
11759
+ if (merged) {
11760
+ undo.push({
11761
+ label: `Deleted ${name} \u2014 it is in this table's Archived items`,
11762
+ cells: [],
11763
+ archived: [row.id]
11764
+ });
11765
+ sheetController.current?.focusGrid();
11766
+ } else {
11767
+ setArchived(name);
11768
+ }
11252
11769
  }
11253
11770
  }
11254
11771
  )
@@ -11256,17 +11773,19 @@ function Grid({
11256
11773
  }
11257
11774
  ) });
11258
11775
  }
11259
- function withHeaderMenus(columns, menuFor) {
11776
+ function withHeaderMenus(columns, menuFor, filterFor) {
11260
11777
  return columns.map((column) => {
11261
11778
  const items = menuFor(String(column.id));
11262
- return items && items.length > 0 ? { ...column, headerMenu: items } : column;
11779
+ const filter = filterFor?.(String(column.id));
11780
+ const withMenu = items && items.length > 0 ? { ...column, headerMenu: items } : column;
11781
+ return filter ? { ...withMenu, ...filter } : withMenu;
11263
11782
  });
11264
11783
  }
11265
11784
 
11266
11785
  // src/ExportMenu.tsx
11267
11786
  var import_react59 = require("react");
11268
11787
  var import_react60 = require("@ai-matrx/records/react");
11269
- var import_core6 = require("@ai-matrx/records/core");
11788
+ var import_core9 = require("@ai-matrx/records/core");
11270
11789
  var import_react61 = require("@ai-matrx/alchemy/react");
11271
11790
  var import_design_system33 = require("@ai-matrx/design-system");
11272
11791
  var import_jsx_runtime40 = require("react/jsx-runtime");
@@ -11285,7 +11804,7 @@ function toRows(fields, records, labels) {
11285
11804
  absent_reason: envelope?.absent ?? null,
11286
11805
  // A VALUE KEPT AS A FILE carries its pointer, so the export writes the whole text
11287
11806
  // (`withWholeValues`), never the first words passed off as the value.
11288
- source: (0, import_core6.wholeValueOf)(document2, field2.key)
11807
+ source: (0, import_core9.wholeValueOf)(document2, field2.key)
11289
11808
  };
11290
11809
  }
11291
11810
  return row;
@@ -11339,7 +11858,7 @@ function ExportMenu({ tableId, rows, label, className }) {
11339
11858
  );
11340
11859
  const lookup = (field2, id) => labels(field2, id) ?? learned.get(`${field2.id}:${id}`) ?? null;
11341
11860
  const read = host.readFileText;
11342
- return (0, import_core6.withWholeValues)(toRows(all, records, lookup), read ? (fileId) => read({ fileId }) : null);
11861
+ return (0, import_core9.withWholeValues)(toRows(all, records, lookup), read ? (fileId) => read({ fileId }) : null);
11343
11862
  };
11344
11863
  return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: (0, import_design_system33.cn)("flex items-center gap-1", className), children: [
11345
11864
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
@@ -11347,7 +11866,7 @@ function ExportMenu({ tableId, rows, label, className }) {
11347
11866
  {
11348
11867
  size: "sm",
11349
11868
  variant: "outline",
11350
- onClick: () => void exported().then((rows2) => download(`${name}.csv`, "text/csv;charset=utf-8", (0, import_core6.exportCsv)(fields.data ?? [], rows2))).catch(
11869
+ onClick: () => void exported().then((rows2) => download(`${name}.csv`, "text/csv;charset=utf-8", (0, import_core9.exportCsv)(fields.data ?? [], rows2))).catch(
11351
11870
  (thrown) => setFailure(refusalFromThrown(thrown, "The CSV could not be written.", "Try again in a moment."))
11352
11871
  ),
11353
11872
  children: "CSV"
@@ -11368,7 +11887,7 @@ function ExportMenu({ tableId, rows, label, className }) {
11368
11887
  );
11369
11888
  setFailure(null);
11370
11889
  try {
11371
- exported().then((rows2) => (0, import_core6.exportXlsx)(fields.data ?? [], rows2, name)).then(
11890
+ exported().then((rows2) => (0, import_core9.exportXlsx)(fields.data ?? [], rows2, name)).then(
11372
11891
  (bytes) => download(
11373
11892
  `${name}.xlsx`,
11374
11893
  "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
@@ -11389,7 +11908,7 @@ function ExportMenu({ tableId, rows, label, className }) {
11389
11908
  sourceId: `records:${tableId}`,
11390
11909
  size: "xs",
11391
11910
  json: () => records.map((r) => r.document),
11392
- human: () => (0, import_core6.exportCsv)(fields.data ?? [], toRows(fields.data ?? [], records, labels))
11911
+ human: () => (0, import_core9.exportCsv)(fields.data ?? [], toRows(fields.data ?? [], records, labels))
11393
11912
  }
11394
11913
  ),
11395
11914
  failure ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(RefusalLine, { error: failure }) : null
@@ -11493,11 +12012,11 @@ function FilePicker({
11493
12012
  // src/ImportWizard.tsx
11494
12013
  var import_react63 = require("react");
11495
12014
  var import_react64 = require("@ai-matrx/records/react");
11496
- var import_core8 = require("@ai-matrx/records/core");
12015
+ var import_core11 = require("@ai-matrx/records/core");
11497
12016
  var import_design_system35 = require("@ai-matrx/design-system");
11498
12017
 
11499
12018
  // src/importRuleCheck.ts
11500
- var import_core7 = require("@ai-matrx/records/core");
12019
+ var import_core10 = require("@ai-matrx/records/core");
11501
12020
  function checkImportRules(fields, rows, mapping) {
11502
12021
  const byKey = new Map(fields.map((f) => [f.key, f]));
11503
12022
  const verdicts = [];
@@ -11515,7 +12034,7 @@ function checkImportRules(fields, rows, mapping) {
11515
12034
  let why = null;
11516
12035
  if ("refusal" in meant) why = meant.refusal;
11517
12036
  else {
11518
- const predicted = (0, import_core7.predictValueRefusals)([field2], { [key]: meant.value }).filter(
12037
+ const predicted = (0, import_core10.predictValueRefusals)([field2], { [key]: meant.value }).filter(
11519
12038
  (p) => p.field_key === void 0 || p.field_key === key
11520
12039
  );
11521
12040
  if (predicted.length > 0) why = predicted[0].message;
@@ -11643,7 +12162,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
11643
12162
  try {
11644
12163
  const xlsx = file.name.toLowerCase().endsWith(".xlsx");
11645
12164
  const bytes = await file.arrayBuffer();
11646
- const result = xlsx ? await (0, import_core8.importXlsx)(bytes) : (0, import_core8.importCsv)(new TextDecoder().decode(bytes));
12165
+ const result = xlsx ? await (0, import_core11.importXlsx)(bytes) : (0, import_core11.importCsv)(new TextDecoder().decode(bytes));
11647
12166
  setFormat(xlsx ? "xlsx" : "csv");
11648
12167
  setFileName(file.name);
11649
12168
  setFileBytes(file.size);
@@ -14003,10 +14522,10 @@ var import_design_system45 = require("@ai-matrx/design-system");
14003
14522
  // src/RecordForm.tsx
14004
14523
  var import_react80 = require("react");
14005
14524
  var import_react81 = require("@ai-matrx/records/react");
14006
- var import_core10 = require("@ai-matrx/records/core");
14525
+ var import_core13 = require("@ai-matrx/records/core");
14007
14526
 
14008
14527
  // src/writeOnTheWinningVersion.ts
14009
- var import_core9 = require("@ai-matrx/records/core");
14528
+ var import_core12 = require("@ai-matrx/records/core");
14010
14529
  function sameStoredValue(left, right) {
14011
14530
  if (left === right) return true;
14012
14531
  const empty = (v) => v === null || v === void 0 || v === "";
@@ -14028,7 +14547,7 @@ async function updateOnTheWinningVersion(client, recordId, patch, expectedVersio
14028
14547
  });
14029
14548
  const first = await send(expectedVersion);
14030
14549
  if (first.ok) return first;
14031
- const conflict = (0, import_core9.asWriteConflict)(first.error, recordId);
14550
+ const conflict = (0, import_core12.asWriteConflict)(first.error, recordId);
14032
14551
  if (!conflict) return first;
14033
14552
  const untouched = base === "always" || Object.keys(patch).every((key) => sameStoredValue(conflict.contested_fields[key], base[key]));
14034
14553
  if (!untouched) return first;
@@ -14265,7 +14784,7 @@ function RecordForm({
14265
14784
  return out;
14266
14785
  }, [draft]);
14267
14786
  const predicted = (0, import_react80.useMemo)(
14268
- () => fields.data ? (0, import_core10.predictWriteRefusals)({ fields: fields.data, document: document2, ...recordType ? { recordType } : {} }) : [],
14787
+ () => fields.data ? (0, import_core13.predictWriteRefusals)({ fields: fields.data, document: document2, ...recordType ? { recordType } : {} }) : [],
14269
14788
  [fields.data, document2, recordType]
14270
14789
  );
14271
14790
  if (fields.error) return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(RefusalNotice, { error: fields.error, className });
@@ -14314,7 +14833,7 @@ function RecordForm({
14314
14833
  setEdit({
14315
14834
  saving: false,
14316
14835
  error: answered2.ok ? null : answered2.error,
14317
- conflict: answered2.ok ? null : (0, import_core10.asWriteConflict)(answered2.error, recordId)
14836
+ conflict: answered2.ok ? null : (0, import_core13.asWriteConflict)(answered2.error, recordId)
14318
14837
  });
14319
14838
  const moved = answered2.ok ? answered2.data : null;
14320
14839
  if (moved !== null) {
@@ -14420,7 +14939,7 @@ function FieldRow({
14420
14939
  }) {
14421
14940
  const id = `field-${field2.id}`;
14422
14941
  const storedWords = document2?.[field2.key];
14423
- const whole = typeof storedWords === "string" ? (0, import_core10.wholeValueOf)(document2, field2.key) : null;
14942
+ const whole = typeof storedWords === "string" ? (0, import_core13.wholeValueOf)(document2, field2.key) : null;
14424
14943
  return /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("div", { className: (0, import_design_system43.cn)("flex min-w-0 flex-col gap-1", whole && "sm:col-span-2"), children: [
14425
14944
  /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(FieldLabel, { field: field2, htmlFor: id, children: /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(ProvenanceBadge, { document: document2, fieldKey: field2.key }) }),
14426
14945
  whole ? (
@@ -14517,8 +15036,8 @@ function Peek({ tableId, recordId, onClose, className }) {
14517
15036
  const title = document2 ? recordNameIn(table.data, document2) : "";
14518
15037
  const href = host.hrefForRecord && table.data ? host.hrefForRecord({ table: table.data, recordId }) : void 0;
14519
15038
  return /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("div", { className: (0, import_design_system45.cn)("flex min-w-0 flex-col gap-2 text-xs", className), children: [
14520
- /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("div", { className: "flex items-center gap-2", children: [
14521
- /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("h3", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: record.loading && !document2 ? /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(import_design_system45.Skeleton, { className: "h-4 w-40" }) : title }),
15039
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("h3", { className: "break-words text-sm font-medium leading-snug", "data-records-peek-title": "", title, children: record.loading && !document2 ? /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(import_design_system45.Skeleton, { className: "h-4 w-40" }) : title }),
15040
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
14522
15041
  /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
14523
15042
  import_react85.AlchemyMenu,
14524
15043
  {
@@ -14556,7 +15075,7 @@ function Peek({ tableId, recordId, onClose, className }) {
14556
15075
  // it would take and who can give it.
14557
15076
  /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("span", { className: "max-w-[18rem] text-[11px] text-muted-foreground", children: may.why("write") })
14558
15077
  ) : null,
14559
- onClose ? /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(import_design_system45.Button, { size: "sm", variant: "ghost", onClick: onClose, "aria-label": "Close", children: "\xD7" }) : null
15078
+ onClose ? /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(import_design_system45.Button, { size: "sm", variant: "ghost", className: "ml-auto", onClick: onClose, "aria-label": "Close", children: "\xD7" }) : null
14560
15079
  ] }),
14561
15080
  /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(import_design_system45.Separator, {}),
14562
15081
  talking && host.chat ? /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(import_jsx_runtime52.Fragment, { children: [
@@ -14687,14 +15206,14 @@ var LOOK_WORDS = {
14687
15206
  // src/ViewSwitcher.tsx
14688
15207
  var import_react88 = require("react");
14689
15208
  var import_react89 = require("@ai-matrx/records/react");
14690
- var import_records16 = require("@ai-matrx/records");
15209
+ var import_records17 = require("@ai-matrx/records");
14691
15210
  var import_design_system47 = require("@ai-matrx/design-system");
14692
15211
  var import_data_table11 = require("@ai-matrx/design-system/data-table");
14693
15212
 
14694
15213
  // src/Pipeline.tsx
14695
15214
  var import_react86 = require("react");
14696
15215
  var import_react87 = require("@ai-matrx/records/react");
14697
- var import_records15 = require("@ai-matrx/records");
15216
+ var import_records16 = require("@ai-matrx/records");
14698
15217
  var import_design_system46 = require("@ai-matrx/design-system");
14699
15218
  var import_data_table10 = require("@ai-matrx/design-system/data-table");
14700
15219
 
@@ -14801,7 +15320,7 @@ function PipelineBoard({
14801
15320
  alive.current = false;
14802
15321
  };
14803
15322
  }, []);
14804
- const filterKey = (0, import_records15.stableFilterKey)(filter);
15323
+ const filterKey = (0, import_records16.stableFilterKey)(filter);
14805
15324
  const reload = (0, import_react86.useCallback)(async () => {
14806
15325
  setLoading(true);
14807
15326
  const [read, board, held] = await Promise.all([
@@ -15448,8 +15967,8 @@ function useViewRecords(view, pageSize = 200, filter, viewId) {
15448
15967
  cancelled = true;
15449
15968
  };
15450
15969
  }, [client, ruleId, pageSize]);
15451
- const asked = (0, import_records16.stableFilterKey)(filter ?? null);
15452
- const fromView = asked !== "{}" && (asked === (0, import_records16.stableFilterKey)(view.where ?? null) || asked === (0, import_records16.stableFilterKey)(view.filters ?? null));
15970
+ const asked = (0, import_records17.stableFilterKey)(filter ?? null);
15971
+ const fromView = asked !== "{}" && (asked === (0, import_records17.stableFilterKey)(view.where ?? null) || asked === (0, import_records17.stableFilterKey)(view.filters ?? null));
15453
15972
  const tableRows = (0, import_react88.useMemo)(() => table.data?.rows ?? [], [table.data]);
15454
15973
  const manual = view.order === "manual" && Boolean(viewId) && !ruleId;
15455
15974
  const hand = useHandOrderRows({
@@ -15499,6 +16018,7 @@ function ViewSwitcher({
15499
16018
  onLayoutChange,
15500
16019
  onViewChange,
15501
16020
  onOpenRecord,
16021
+ openRecordId,
15502
16022
  onMoved,
15503
16023
  onNewRecordForm,
15504
16024
  onAskWhoChanged,
@@ -15577,6 +16097,7 @@ function ViewSwitcher({
15577
16097
  ...local.presentation ?? view.presentation ? { presentation: local.presentation ?? view.presentation } : {},
15578
16098
  ...onViewChange ? { onPresentationChange: (next) => change({ presentation: next }) } : {},
15579
16099
  ...onOpenRecord ? { onOpenRecord } : {},
16100
+ ...openRecordId ? { openRecordId } : {},
15580
16101
  ...onNewRecordForm ? { onNewRecordForm } : {},
15581
16102
  ...onAskWhoChanged ? { onAskWhoChanged } : {},
15582
16103
  ...viewId ? {
@@ -16025,7 +16546,7 @@ function Card2({
16025
16546
 
16026
16547
  // src/ArchivedView.tsx
16027
16548
  var import_react90 = require("react");
16028
- var import_records17 = require("@ai-matrx/records");
16549
+ var import_records18 = require("@ai-matrx/records");
16029
16550
  var import_react91 = require("@ai-matrx/records/react");
16030
16551
  var import_design_system48 = require("@ai-matrx/design-system");
16031
16552
  var import_jsx_runtime56 = require("react/jsx-runtime");
@@ -16035,7 +16556,7 @@ function mayNotRestoreLine(why) {
16035
16556
  return said === "" ? "Bringing a record back needs permission to edit this table. Ask somebody who has it." : said;
16036
16557
  }
16037
16558
  function archivedRowLine(row, titleKey) {
16038
- return `${recordName(row.document, titleKey)} \u2014 ${(0, import_records17.archivedByLine)(row)}`;
16559
+ return `${recordName(row.document, titleKey)} \u2014 ${(0, import_records18.archivedByLine)(row)}`;
16039
16560
  }
16040
16561
  function ArchivedView({
16041
16562
  tableId,
@@ -16049,7 +16570,7 @@ function ArchivedView({
16049
16570
  const table = (0, import_react91.useTable)(tableId);
16050
16571
  const rights = useTableRights(table.data);
16051
16572
  const fields = (0, import_react91.useFields)(tableId);
16052
- const [lane, setLane] = (0, import_react90.useState)(laneFromHost ?? import_records17.DEFAULT_ARCHIVE_LANE);
16573
+ const [lane, setLane] = (0, import_react90.useState)(laneFromHost ?? import_records18.DEFAULT_ARCHIVE_LANE);
16053
16574
  const [rows, setRows] = (0, import_react90.useState)([]);
16054
16575
  const [loading, setLoading] = (0, import_react90.useState)(true);
16055
16576
  const [error, setError] = (0, import_react90.useState)(null);
@@ -16096,15 +16617,15 @@ function ArchivedView({
16096
16617
  return /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: (0, import_design_system48.cn)("flex min-h-0 flex-col gap-2", className), "data-testid": "archived-view", children: [
16097
16618
  /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
16098
16619
  /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("span", { className: "text-sm font-medium", children: "Archived" }),
16099
- /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("div", { className: "ml-auto flex items-center gap-0.5", role: "group", "aria-label": "Archived by", children: import_records17.ARCHIVE_LANES.map((option) => /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
16620
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("div", { className: "ml-auto flex items-center gap-0.5", role: "group", "aria-label": "Archived by", children: import_records18.ARCHIVE_LANES.map((option) => /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
16100
16621
  import_design_system48.Button,
16101
16622
  {
16102
16623
  size: "sm",
16103
16624
  variant: option === lane ? "secondary" : "ghost",
16104
16625
  "aria-pressed": option === lane,
16105
- title: import_records17.ARCHIVE_LANE_MEANS[option],
16626
+ title: import_records18.ARCHIVE_LANE_MEANS[option],
16106
16627
  onClick: () => pick(option),
16107
- children: import_records17.ARCHIVE_LANE_LABEL[option]
16628
+ children: import_records18.ARCHIVE_LANE_LABEL[option]
16108
16629
  },
16109
16630
  option
16110
16631
  )) })
@@ -16120,7 +16641,7 @@ function ArchivedView({
16120
16641
  ),
16121
16642
  error ? /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(RefusalNotice, { error }) : null,
16122
16643
  loading && !error ? /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(import_design_system48.Skeleton, { className: "h-24 w-full" }) : null,
16123
- !loading && !error && rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("p", { "data-testid": "archived-empty", className: "rounded-md border border-dashed px-3 py-6 text-xs text-muted-foreground", children: (0, import_records17.emptyArchiveLine)(lane) }) : null,
16644
+ !loading && !error && rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("p", { "data-testid": "archived-empty", className: "rounded-md border border-dashed px-3 py-6 text-xs text-muted-foreground", children: (0, import_records18.emptyArchiveLine)(lane) }) : null,
16124
16645
  !loading && !error && rows.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("ul", { className: "flex flex-col gap-1", "data-testid": "archived-rows", children: rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)(
16125
16646
  "li",
16126
16647
  {
@@ -16130,7 +16651,7 @@ function ArchivedView({
16130
16651
  children: [
16131
16652
  /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col", children: [
16132
16653
  /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("span", { className: "truncate text-sm", children: recordName(row.document, titleKey) }),
16133
- /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("span", { className: "truncate text-xs text-muted-foreground", "data-testid": "archived-who", children: (0, import_records17.archivedByLine)(row) }),
16654
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("span", { className: "truncate text-xs text-muted-foreground", "data-testid": "archived-who", children: (0, import_records18.archivedByLine)(row) }),
16134
16655
  refusal2 && refusal2.recordId === row.id ? /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(RefusalNotice, { error: refusal2.error, className: "mt-2" }) : null
16135
16656
  ] }),
16136
16657
  mayRestore ? /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
@@ -16153,7 +16674,7 @@ function ArchivedView({
16153
16674
 
16154
16675
  // src/ArchivedDisclosure.tsx
16155
16676
  var import_react92 = require("react");
16156
- var import_records18 = require("@ai-matrx/records");
16677
+ var import_records19 = require("@ai-matrx/records");
16157
16678
  var import_react93 = require("@ai-matrx/records/react");
16158
16679
  var import_design_system49 = require("@ai-matrx/design-system");
16159
16680
  var import_jsx_runtime57 = require("react/jsx-runtime");
@@ -16253,10 +16774,10 @@ function ArchivedPortals({
16253
16774
  error ? /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(RefusalNotice, { error }) : null,
16254
16775
  rows === null && !error ? /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(import_design_system49.Skeleton, { className: "h-16 w-full" }) : null,
16255
16776
  said ? /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("p", { className: "rounded-md border border-border px-3 py-2 text-xs", "data-testid": "archived-portal-said", children: said }) : null,
16256
- rows !== null && !error && rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("p", { "data-testid": "archived-portals-empty", className: "text-xs text-muted-foreground", children: (0, import_records18.emptyPortalArchiveLine)() }) : null,
16777
+ rows !== null && !error && rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("p", { "data-testid": "archived-portals-empty", className: "text-xs text-muted-foreground", children: (0, import_records19.emptyPortalArchiveLine)() }) : null,
16257
16778
  rows !== null && rows.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("ul", { className: "flex flex-col gap-1.5", "data-testid": "archived-portals", children: rows.map((portal) => {
16258
16779
  const asking = confirming === portal.portal_id;
16259
- const matches2 = (0, import_records18.portalTitleTypedBack)(typed, portal.title);
16780
+ const matches2 = (0, import_records19.portalTitleTypedBack)(typed, portal.title);
16260
16781
  return /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(
16261
16782
  "li",
16262
16783
  {
@@ -16295,7 +16816,7 @@ function ArchivedPortals({
16295
16816
  ] }),
16296
16817
  asking ? /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)("div", { className: "flex flex-col gap-1.5", children: [
16297
16818
  /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
16298
- (0, import_records18.portalConfirmLine)(portal.title),
16819
+ (0, import_records19.portalConfirmLine)(portal.title),
16299
16820
  " Restoring it puts it back exactly as it was: everyone who was invited can sign in again, and it shows the same Tables."
16300
16821
  ] }),
16301
16822
  /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)("div", { className: "flex flex-wrap items-center gap-1.5", children: [
@@ -19253,7 +19774,7 @@ function PortalBuilder({ tableId, portalId, onSaved, onClose, className }) {
19253
19774
  // src/PortalsPanel.tsx
19254
19775
  var import_react114 = require("react");
19255
19776
  var import_react115 = require("@ai-matrx/records/react");
19256
- var import_records19 = require("@ai-matrx/records");
19777
+ var import_records20 = require("@ai-matrx/records");
19257
19778
  var import_design_system60 = require("@ai-matrx/design-system");
19258
19779
 
19259
19780
  // src/BuildOrAsk.tsx
@@ -19717,7 +20238,7 @@ function PortalsPanel({ tableId, activePortalId, className }) {
19717
20238
  ] }) : null,
19718
20239
  activePortalId && !shown3.some((p) => p.portal_id === activePortalId) ? /* @__PURE__ */ (0, import_jsx_runtime68.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,
19719
20240
  /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("ul", { className: "flex flex-col gap-2", children: shown3.map((portal) => {
19720
- const url = `${origin}${(0, import_records19.portalPath)(portal.slug)}`;
20241
+ const url = `${origin}${(0, import_records20.portalPath)(portal.slug)}`;
19721
20242
  const open = openId === portal.portal_id;
19722
20243
  const linked = portal.portal_id === activePortalId;
19723
20244
  return /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)(
@@ -19832,8 +20353,8 @@ function ArchivePortalControl({
19832
20353
  }
19833
20354
  ),
19834
20355
  asking ? /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("div", { className: "mt-1.5 flex w-full flex-col gap-1.5 rounded-md border border-dashed border-border p-2.5", children: [
19835
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("p", { className: "text-xs text-muted-foreground", "data-testid": "portal-archive-consequence", children: (0, import_records19.portalArchiveConsequence)(portal.title, portal.signed_in, portal.invited) }),
19836
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("p", { className: "text-xs text-muted-foreground", children: (0, import_records19.portalConfirmLine)(portal.title) }),
20356
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("p", { className: "text-xs text-muted-foreground", "data-testid": "portal-archive-consequence", children: (0, import_records20.portalArchiveConsequence)(portal.title, portal.signed_in, portal.invited) }),
20357
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("p", { className: "text-xs text-muted-foreground", children: (0, import_records20.portalConfirmLine)(portal.title) }),
19837
20358
  /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("div", { className: "flex flex-wrap items-center gap-1.5", children: [
19838
20359
  /* @__PURE__ */ (0, import_jsx_runtime68.jsx)(
19839
20360
  import_design_system60.BasicInput,
@@ -19851,7 +20372,7 @@ function ArchivePortalControl({
19851
20372
  size: "sm",
19852
20373
  variant: "destructive",
19853
20374
  "data-testid": "portal-archive-do",
19854
- disabled: busy || !(0, import_records19.portalTitleTypedBack)(typed, portal.title),
20375
+ disabled: busy || !(0, import_records20.portalTitleTypedBack)(typed, portal.title),
19855
20376
  onClick: () => void go(),
19856
20377
  children: busy ? "Archiving\u2026" : "Archive it"
19857
20378
  }
@@ -21010,7 +21531,7 @@ function usePublishGate() {
21010
21531
  }
21011
21532
 
21012
21533
  // src/FormBuilder.tsx
21013
- var import_records20 = require("@ai-matrx/records");
21534
+ var import_records21 = require("@ai-matrx/records");
21014
21535
  var import_design_system66 = require("@ai-matrx/design-system");
21015
21536
 
21016
21537
  // src/FormRunner.tsx
@@ -21886,7 +22407,7 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, className }) {
21886
22407
  /* @__PURE__ */ (0, import_jsx_runtime74.jsx)(
21887
22408
  PublishRow,
21888
22409
  {
21889
- url: `${publicOrigin}${(0, import_records20.publicFormPath)(draft.id)}`,
22410
+ url: `${publicOrigin}${(0, import_records21.publicFormPath)(draft.id)}`,
21890
22411
  state: draft.state,
21891
22412
  mayPublish: rights.structure && !publishGate.blocked,
21892
22413
  why: publishGate.why ?? rights.why("structure"),
@@ -22904,8 +23425,8 @@ function SignBlock({ tableId, recordId, render, className }) {
22904
23425
  // src/ChartBlock.tsx
22905
23426
  var import_react138 = require("react");
22906
23427
  var import_recharts = require("recharts");
22907
- var import_records21 = require("@ai-matrx/records");
22908
- var import_core11 = require("@ai-matrx/records/core");
23428
+ var import_records22 = require("@ai-matrx/records");
23429
+ var import_core14 = require("@ai-matrx/records/core");
22909
23430
  var import_design_system71 = require("@ai-matrx/design-system");
22910
23431
  var import_jsx_runtime79 = require("react/jsx-runtime");
22911
23432
  var priorKey = (key) => `prior__${key}`;
@@ -22962,7 +23483,7 @@ function pretty(n) {
22962
23483
  return Number.isInteger(n) ? n.toLocaleString() : n.toLocaleString(void 0, { maximumFractionDigits: 2 });
22963
23484
  }
22964
23485
  function asRefusal2(block) {
22965
- return (0, import_core11.mapPgError)(
23486
+ return (0, import_core14.mapPgError)(
22966
23487
  {
22967
23488
  message: block.refused ?? "",
22968
23489
  ...block.sqlstate ? { code: block.sqlstate } : {}
@@ -22975,7 +23496,7 @@ function ChartBlock({ block, subject, className }) {
22975
23496
  const kind = block.kind;
22976
23497
  const tableId = block.table_id ?? subject;
22977
23498
  const measures = block.measures && block.measures.length > 0 ? block.measures : [{ op: "count" }];
22978
- const primary = (0, import_records21.measureKey)(measures[0]);
23499
+ const primary = (0, import_records22.measureKey)(measures[0]);
22979
23500
  const windows = kind === "stuck" ? null : block.compare ?? null;
22980
23501
  const compared = !!windows;
22981
23502
  const grain = block.bucket?.by ?? null;
@@ -23907,7 +24428,7 @@ function TargetControl({
23907
24428
  // src/FormsPanel.tsx
23908
24429
  var import_react141 = require("react");
23909
24430
  var import_react142 = require("@ai-matrx/records/react");
23910
- var import_records22 = require("@ai-matrx/records");
24431
+ var import_records23 = require("@ai-matrx/records");
23911
24432
  var import_design_system73 = require("@ai-matrx/design-system");
23912
24433
  var import_jsx_runtime81 = require("react/jsx-runtime");
23913
24434
  var FORM_NOT_HERE_LINE = "The link named a form this table does not have \u2014 it may have been removed, or it belongs to another table. The forms this table does have are listed here.";
@@ -24011,7 +24532,7 @@ function FormsPanel({ tableId, activeFormId, className }) {
24011
24532
  }
24012
24533
  ) : null,
24013
24534
  /* @__PURE__ */ (0, import_jsx_runtime81.jsx)("ul", { className: "flex flex-col gap-2", children: forms.map((form) => {
24014
- const url = `${origin}${(0, import_records22.publicFormPath)(form.form_id)}`;
24535
+ const url = `${origin}${(0, import_records23.publicFormPath)(form.form_id)}`;
24015
24536
  return /* @__PURE__ */ (0, import_jsx_runtime81.jsxs)(
24016
24537
  "li",
24017
24538
  {
@@ -24076,7 +24597,7 @@ function FormsPanel({ tableId, activeFormId, className }) {
24076
24597
  // src/BookingBuilder.tsx
24077
24598
  var import_react143 = require("react");
24078
24599
  var import_react144 = require("@ai-matrx/records/react");
24079
- var import_records23 = require("@ai-matrx/records");
24600
+ var import_records24 = require("@ai-matrx/records");
24080
24601
  var import_design_system74 = require("@ai-matrx/design-system");
24081
24602
  var import_jsx_runtime82 = require("react/jsx-runtime");
24082
24603
  var STORE_ANSWERS_THESE = ["slot", "status", "booked_with"];
@@ -24220,7 +24741,7 @@ function BookingBuilder({ tableId, bookingId, onSaved, onClose, className }) {
24220
24741
  if (!rights.structure) {
24221
24742
  return /* @__PURE__ */ (0, import_jsx_runtime82.jsx)("p", { className: (0, import_design_system74.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." });
24222
24743
  }
24223
- const url = formId ? `${publicOrigin}${(0, import_records23.bookingPath)(formId)}` : null;
24744
+ const url = formId ? `${publicOrigin}${(0, import_records24.bookingPath)(formId)}` : null;
24224
24745
  const shown3 = offer ?? null;
24225
24746
  return (
24226
24747
  // THE RAIL IS 384px WIDE AND THE VIEWPORT IS NOT (walk 1, 2026-09-21):
@@ -24422,7 +24943,7 @@ function BookingBuilder({ tableId, bookingId, onSaved, onClose, className }) {
24422
24943
  // src/BookingSlots.tsx
24423
24944
  var import_react145 = require("react");
24424
24945
  var import_react146 = require("@ai-matrx/records/react");
24425
- var import_records24 = require("@ai-matrx/records");
24946
+ var import_records25 = require("@ai-matrx/records");
24426
24947
  var import_design_system75 = require("@ai-matrx/design-system");
24427
24948
  var import_jsx_runtime83 = require("react/jsx-runtime");
24428
24949
  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.";
@@ -24539,7 +25060,7 @@ function BookingSlots({ tableId, activeBookingId, className }) {
24539
25060
  " Open the table the appointments should land in to make one."
24540
25061
  ] }) : null,
24541
25062
  /* @__PURE__ */ (0, import_jsx_runtime83.jsx)("ul", { className: "flex flex-col gap-2", children: pages.map((page) => {
24542
- const url = `${origin}${(0, import_records24.bookingPath)(page.form_id)}`;
25063
+ const url = `${origin}${(0, import_records25.bookingPath)(page.form_id)}`;
24543
25064
  const mayOpen = levels.data?.[page.table_id] === "admin";
24544
25065
  const open = page.published_at !== null && page.closed_at === null;
24545
25066
  return /* @__PURE__ */ (0, import_jsx_runtime83.jsxs)(
@@ -24641,7 +25162,7 @@ var import_design_system77 = require("@ai-matrx/design-system");
24641
25162
 
24642
25163
  // src/CaptureRun.tsx
24643
25164
  var import_react147 = require("react");
24644
- var import_records25 = require("@ai-matrx/records");
25165
+ var import_records26 = require("@ai-matrx/records");
24645
25166
  var import_react148 = require("@ai-matrx/records/react");
24646
25167
  var import_design_system76 = require("@ai-matrx/design-system");
24647
25168
  var import_jsx_runtime84 = require("react/jsx-runtime");
@@ -24696,7 +25217,7 @@ function CaptureRun({ sheetId, face: given, className }) {
24696
25217
  const [sending, setSending] = (0, import_react147.useState)(false);
24697
25218
  const queueRef = (0, import_react147.useRef)(null);
24698
25219
  (0, import_react147.useEffect)(() => {
24699
- const q2 = (0, import_records25.openCaptureQueue)({
25220
+ const q2 = (0, import_records26.openCaptureQueue)({
24700
25221
  onChange: (c, all) => {
24701
25222
  setCounts(c);
24702
25223
  setItems(all);
@@ -24963,7 +25484,7 @@ function CaptureRun({ sheetId, face: given, className }) {
24963
25484
  value: String(answers[q.field] ?? ""),
24964
25485
  onChange: (e) => {
24965
25486
  const raw = e.target.value;
24966
- const answered2 = field2 ? (0, import_records25.coerceTypedAnswer)(field2, raw, { label: q.ask ?? (0, import_records25.coercionLabel)(field2) }) : { value: raw };
25487
+ const answered2 = field2 ? (0, import_records26.coerceTypedAnswer)(field2, raw, { label: q.ask ?? (0, import_records26.coercionLabel)(field2) }) : { value: raw };
24967
25488
  setAnswers((held) => ({
24968
25489
  ...held,
24969
25490
  [q.field]: raw === "" ? "" : "refusal" in answered2 ? raw : answered2.value
@@ -25619,8 +26140,8 @@ function recordsDataSource(client, fallbackSchema = "custom") {
25619
26140
  // src/TablesHome.tsx
25620
26141
  var import_react163 = require("react");
25621
26142
  var import_react164 = require("@ai-matrx/records/react");
25622
- var import_core13 = require("@ai-matrx/records/core");
25623
- var import_records27 = require("@ai-matrx/records");
26143
+ var import_core16 = require("@ai-matrx/records/core");
26144
+ var import_records28 = require("@ai-matrx/records");
25624
26145
  var import_design_system83 = require("@ai-matrx/design-system");
25625
26146
 
25626
26147
  // src/ExampleTables.tsx
@@ -25714,11 +26235,11 @@ function ExampleTables({
25714
26235
  }
25715
26236
 
25716
26237
  // src/createTable.ts
25717
- var import_core12 = require("@ai-matrx/records/core");
26238
+ var import_core15 = require("@ai-matrx/records/core");
25718
26239
 
25719
26240
  // src/WhereItLives.tsx
25720
26241
  var import_react161 = require("react");
25721
- var import_records26 = require("@ai-matrx/records");
26242
+ var import_records27 = require("@ai-matrx/records");
25722
26243
  var import_react162 = require("@ai-matrx/records/react");
25723
26244
  var import_design_system82 = require("@ai-matrx/design-system");
25724
26245
  var import_jsx_runtime91 = require("react/jsx-runtime");
@@ -25844,7 +26365,7 @@ function WhereItLives({ tableId, knownOrganizationName, onMoved, variant = "head
25844
26365
  /* @__PURE__ */ (0, import_jsx_runtime91.jsx)(SharedNameAddress, { to: choosing, all: answer.home.destinations }),
25845
26366
  "?"
25846
26367
  ] }),
25847
- /* @__PURE__ */ (0, import_jsx_runtime91.jsx)("p", { className: "text-muted-foreground", children: (0, import_records26.moveConsequence)(answer.home, choosing) }),
26368
+ /* @__PURE__ */ (0, import_jsx_runtime91.jsx)("p", { className: "text-muted-foreground", children: (0, import_records27.moveConsequence)(answer.home, choosing) }),
25848
26369
  said?.tone === "refused" ? /* @__PURE__ */ (0, import_jsx_runtime91.jsx)(
25849
26370
  import_design_system82.ErrorBox,
25850
26371
  {
@@ -25992,8 +26513,8 @@ function TablesHome({ onOpenTable, organizationName, makingOnly = false, classNa
25992
26513
  alive = false;
25993
26514
  };
25994
26515
  }, [client]);
25995
- const listed = facts.phase === "read" ? (0, import_records27.withTablePlacement)(
25996
- (0, import_core13.withTableFacts)(tables.data ?? [], facts.rows, me),
26516
+ const listed = facts.phase === "read" ? (0, import_records28.withTablePlacement)(
26517
+ (0, import_core16.withTableFacts)(tables.data ?? [], facts.rows, me),
25997
26518
  facts.rows
25998
26519
  ) : tables.data ?? [];
25999
26520
  const [showEverything, setShowEverything] = (0, import_react163.useState)(false);
@@ -26037,7 +26558,7 @@ function TablesHome({ onOpenTable, organizationName, makingOnly = false, classNa
26037
26558
  if (!trimmed) return;
26038
26559
  setBusy(true);
26039
26560
  setError(null);
26040
- const result = await (0, import_core12.declareTable)(client, { name: trimmed, slug: (0, import_core12.tokenFor)(trimmed) });
26561
+ const result = await (0, import_core15.declareTable)(client, { name: trimmed, slug: (0, import_core15.tokenFor)(trimmed) });
26041
26562
  setBusy(false);
26042
26563
  if (!result.ok) {
26043
26564
  setError(result.error);
@@ -26197,7 +26718,7 @@ function TablesHome({ onOpenTable, organizationName, makingOnly = false, classNa
26197
26718
 
26198
26719
  // src/TablePage.tsx
26199
26720
  var import_react168 = require("react");
26200
- var import_records28 = require("@ai-matrx/records");
26721
+ var import_records29 = require("@ai-matrx/records");
26201
26722
  var import_react169 = require("@ai-matrx/records/react");
26202
26723
  var import_design_system85 = require("@ai-matrx/design-system");
26203
26724
 
@@ -26304,25 +26825,27 @@ function ViewFilter({ tableId, view, onApply, className }) {
26304
26825
 
26305
26826
  // src/toolbarFit.ts
26306
26827
  var import_react167 = require("react");
26307
- var FOLDED_LOOK_GUESS = 104;
26828
+ var FOLDED_GUESS = { look: 104, chooser: 96, tools: 120 };
26308
26829
  function toolbarFold(input) {
26309
- const { available, needsNow, gap, folded, look, chooser } = input;
26310
- const lookAt = (fold) => {
26311
- if (look.now === 0 && look.full === null) return 0;
26312
- if (fold >= 1) return look.folded ?? (folded >= 1 ? look.now : FOLDED_LOOK_GUESS);
26313
- return look.full ?? look.now;
26314
- };
26315
- const chooserAt = (fold) => {
26316
- if (fold >= 2) return 0;
26317
- return chooser.full ?? chooser.now;
26830
+ const { available, needsNow, gap, folded } = input;
26831
+ const pieces = [
26832
+ { piece: input.look, stage: 1, guess: FOLDED_GUESS.look },
26833
+ { piece: input.chooser, stage: 2, guess: FOLDED_GUESS.chooser },
26834
+ ...input.tools ? [{ piece: input.tools, stage: 3, guess: FOLDED_GUESS.tools }] : []
26835
+ ];
26836
+ const widthAt = ({ piece, stage, guess }, fold) => {
26837
+ if (piece.now === 0 && piece.full === null) return 0;
26838
+ if (fold >= stage) return piece.folded ?? (folded >= stage ? piece.now : Math.min(guess, piece.full ?? piece.now));
26839
+ return piece.full ?? piece.now;
26318
26840
  };
26319
26841
  const withGap = (width) => width > 0 ? width + gap : 0;
26320
- const base = needsNow - withGap(look.now) - withGap(chooser.now);
26321
- const needs = [0, 1, 2].map((fold) => base + withGap(lookAt(fold)) + withGap(chooserAt(fold)));
26842
+ const base = pieces.reduce((rest, p) => rest - withGap(p.piece.now), needsNow);
26843
+ const stages = input.tools ? [0, 1, 2, 3] : [0, 1, 2];
26844
+ const needs = stages.map((fold) => pieces.reduce((sum, p) => sum + withGap(widthAt(p, fold)), base));
26322
26845
  const fits = needs.findIndex((need) => need <= available + 0.5);
26323
- if (fits >= 0) return fits;
26846
+ if (fits >= 0) return stages[fits];
26324
26847
  const least = Math.min(...needs);
26325
- return needs.findIndex((need) => need <= least + 0.5);
26848
+ return stages[needs.findIndex((need) => need <= least + 0.5)];
26326
26849
  }
26327
26850
  function naturalWidth(el) {
26328
26851
  const html = el;
@@ -26355,7 +26878,12 @@ function useToolbarFit(row) {
26355
26878
  const [fold, setFold] = (0, import_react167.useState)(0);
26356
26879
  (0, import_react167.useEffect)(() => {
26357
26880
  if (!row) return;
26358
- const widths = { lookFull: null, lookFolded: null, chooserFull: null };
26881
+ const widths = {
26882
+ look: { full: null, folded: null },
26883
+ chooser: { full: null, folded: null },
26884
+ tools: { full: null, folded: null }
26885
+ };
26886
+ const stageOf = { look: 1, chooser: 2, tools: 3 };
26359
26887
  let current = 0;
26360
26888
  let pending = 0;
26361
26889
  let stopped = false;
@@ -26364,23 +26892,22 @@ function useToolbarFit(row) {
26364
26892
  if (stopped) return;
26365
26893
  const available = row.clientWidth;
26366
26894
  if (available <= 0) return;
26367
- const look = row.querySelector(':scope > [data-toolbar-fit="look"]');
26368
- const chooser = row.querySelector(':scope > [data-toolbar-fit="chooser"]');
26369
- const lookNow = look ? naturalWidth(look) : 0;
26370
- const chooserNow = chooser ? naturalWidth(chooser) : 0;
26371
- if (look && current === 0) widths.lookFull = lookNow;
26372
- if (look && current >= 1) widths.lookFolded = lookNow;
26373
- if (!look && current === 0) widths.lookFull = null;
26374
- if (chooser && current < 2) widths.chooserFull = chooserNow;
26895
+ const read = (name) => {
26896
+ const el = name === "tools" ? row.querySelector(":scope > [data-toolbar-fit-slot]") : row.querySelector(`:scope > [data-toolbar-fit="${name}"]`);
26897
+ const now = !el ? 0 : name === "tools" ? slotNeeds(el) : naturalWidth(el);
26898
+ const w = widths[name];
26899
+ if (el && now > 0) {
26900
+ if (current >= stageOf[name]) w.folded = now;
26901
+ else w.full = now;
26902
+ }
26903
+ if (!el && current < stageOf[name]) w.full = null;
26904
+ return { now, full: w.full, folded: w.folded };
26905
+ };
26906
+ const look = read("look");
26907
+ const chooser = read("chooser");
26908
+ const tools = read("tools");
26375
26909
  const { needs, gap } = rowNeeds(row);
26376
- const next = toolbarFold({
26377
- available,
26378
- needsNow: needs,
26379
- gap,
26380
- folded: current,
26381
- look: { now: lookNow, full: widths.lookFull, folded: widths.lookFolded },
26382
- chooser: { now: chooserNow, full: widths.chooserFull }
26383
- });
26910
+ const next = toolbarFold({ available, needsNow: needs, gap, folded: current, look, chooser, tools });
26384
26911
  if (next !== current) {
26385
26912
  current = next;
26386
26913
  setFold(next);
@@ -26417,6 +26944,12 @@ function useToolbarFit(row) {
26417
26944
  }, [row]);
26418
26945
  return fold;
26419
26946
  }
26947
+ function slotNeeds(slot) {
26948
+ const inner = shownChildren(slot);
26949
+ const style = typeof getComputedStyle === "undefined" ? null : getComputedStyle(slot);
26950
+ const gap = style ? parseFloat(style.columnGap || "0") || 0 : 0;
26951
+ return inner.reduce((sum, c) => sum + naturalWidth(c), 0) + gap * Math.max(0, inner.length - 1);
26952
+ }
26420
26953
  function clippedEnds(scrollLeft, clientWidth, scrollWidth) {
26421
26954
  const start = scrollLeft > 1;
26422
26955
  const end = scrollLeft + clientWidth < scrollWidth - 1;
@@ -26560,7 +27093,7 @@ function TableNotHere({
26560
27093
  return /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)("div", { className: (0, import_design_system85.cn)("flex flex-col items-start gap-2 rounded-md border border-dashed p-6", className), children: [
26561
27094
  /* @__PURE__ */ (0, import_jsx_runtime94.jsx)("p", { className: "text-sm font-medium", children: "This table is archived" }),
26562
27095
  /* @__PURE__ */ (0, import_jsx_runtime94.jsx)("p", { className: "max-w-prose text-xs text-muted-foreground", children: TABLE_IS_ARCHIVED }),
26563
- /* @__PURE__ */ (0, import_jsx_runtime94.jsx)("p", { className: "text-xs text-muted-foreground", children: (0, import_records28.archivedByLine)(row) }),
27096
+ /* @__PURE__ */ (0, import_jsx_runtime94.jsx)("p", { className: "text-xs text-muted-foreground", children: (0, import_records29.archivedByLine)(row) }),
26564
27097
  refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(RefusalNotice, { error: refusal2 }) : null,
26565
27098
  /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)("div", { className: "flex gap-2", children: [
26566
27099
  /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(
@@ -26776,6 +27309,8 @@ function TablePage({
26776
27309
  const face = tableOpening(view, hostLayouts.map((h) => h.id));
26777
27310
  const designated = face.kind === "designated";
26778
27311
  const chooserInline = !designated && !phone && toolbarFold2 < 2;
27312
+ const chooserCompact = !designated && !phone && toolbarFold2 >= 2;
27313
+ const controlsInSheet = phone && !designated;
26779
27314
  (0, import_react168.useEffect)(() => {
26780
27315
  if (face.kind !== "designated") return;
26781
27316
  if (hostFromLink || opened.layout || layoutChosen) return;
@@ -26955,6 +27490,50 @@ function TablePage({
26955
27490
  };
26956
27491
  const footer = footerOf(shown3?.presentation);
26957
27492
  const setFooter = (next) => void patchView({ presentation: { ...shown3?.presentation ?? {}, footer: next } });
27493
+ const layoutRadio = /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(
27494
+ import_design_system85.DropdownMenuRadioGroup,
27495
+ {
27496
+ value: layoutShowing ?? "",
27497
+ onValueChange: (value) => {
27498
+ if (VIEW_LAYOUTS.includes(value)) pickLayout(value);
27499
+ else pickHostLayout(value);
27500
+ },
27501
+ children: [
27502
+ hostLayouts.map((option) => /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.DropdownMenuRadioItem, { value: option.id, children: option.label }, `host:${option.id}`)),
27503
+ VIEW_LAYOUTS.map((option) => /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.DropdownMenuRadioItem, { value: option, children: LAYOUT_LABEL[option] }, option))
27504
+ ]
27505
+ }
27506
+ );
27507
+ const sheetExtras = controlsInSheet && main === "records" && !hostLayout && shownLayout === "grid" ? /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(import_jsx_runtime94.Fragment, { children: [
27508
+ /* @__PURE__ */ (0, import_jsx_runtime94.jsx)("div", { className: "flex items-center gap-2 text-xs", "data-table-sheet-filter": "", "data-matrx-table-tools-keep-open": "", children: /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(ViewFilter, { tableId, view: shown3, onApply: (where) => void patchView({ where }) }) }),
27509
+ /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)("div", { role: "group", "aria-label": "Layout", className: "flex flex-wrap gap-1 pt-1", "data-table-sheet-layout": "", children: [
27510
+ VIEW_LAYOUTS.map((option) => /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(
27511
+ import_design_system85.Button,
27512
+ {
27513
+ size: "sm",
27514
+ variant: layoutShowing === option ? "secondary" : "ghost",
27515
+ "aria-pressed": layoutShowing === option,
27516
+ className: "h-8 !w-auto",
27517
+ onClick: () => pickLayout(option),
27518
+ children: LAYOUT_LABEL[option]
27519
+ },
27520
+ option
27521
+ )),
27522
+ hostLayouts.map((option) => /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(
27523
+ import_design_system85.Button,
27524
+ {
27525
+ size: "sm",
27526
+ variant: layoutShowing === option.id ? "secondary" : "ghost",
27527
+ "aria-pressed": layoutShowing === option.id,
27528
+ className: "h-8 !w-auto",
27529
+ onClick: () => pickHostLayout(option.id),
27530
+ children: option.label
27531
+ },
27532
+ `host:${option.id}`
27533
+ ))
27534
+ ] }),
27535
+ rights.share && canShare ? /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.Button, { size: "sm", variant: "ghost", className: "h-8", onClick: () => setShareAsked((n) => n + 1), "data-table-sheet-share": "", children: "Share\u2026" }) : null
27536
+ ] }) : void 0;
26958
27537
  const pageActions = /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(import_jsx_runtime94.Fragment, { children: [
26959
27538
  /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(
26960
27539
  ShareControl,
@@ -26976,22 +27555,9 @@ function TablePage({
26976
27555
  /* @__PURE__ */ (0, import_jsx_runtime94.jsx)("circle", { cx: "19", cy: "12", r: "1.75" })
26977
27556
  ] }) }) }),
26978
27557
  /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(import_design_system85.DropdownMenuContent, { align: "end", className: "w-56", "data-table-menu-content": "", children: [
26979
- !chooserInline ? /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(import_jsx_runtime94.Fragment, { children: [
27558
+ !chooserInline && !chooserCompact ? /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(import_jsx_runtime94.Fragment, { children: [
26980
27559
  /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.DropdownMenuLabel, { className: "text-xs text-muted-foreground", children: "Layout" }),
26981
- /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(
26982
- import_design_system85.DropdownMenuRadioGroup,
26983
- {
26984
- value: layoutShowing ?? "",
26985
- onValueChange: (value) => {
26986
- if (VIEW_LAYOUTS.includes(value)) pickLayout(value);
26987
- else pickHostLayout(value);
26988
- },
26989
- children: [
26990
- hostLayouts.map((option) => /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.DropdownMenuRadioItem, { value: option.id, children: option.label }, `host:${option.id}`)),
26991
- VIEW_LAYOUTS.map((option) => /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.DropdownMenuRadioItem, { value: option, children: LAYOUT_LABEL[option] }, option))
26992
- ]
26993
- }
26994
- ),
27560
+ layoutRadio,
26995
27561
  /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.DropdownMenuSeparator, {})
26996
27562
  ] }) : null,
26997
27563
  MENU.filter((entry) => entry.shown(rights)).map((entry, index, shownEntries) => /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(
@@ -27111,11 +27677,26 @@ function TablePage({
27111
27677
  `host:${option.id}`
27112
27678
  ))
27113
27679
  ] }) : null,
27114
- main === "records" && !hostLayout ? /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(ViewFilter, { tableId, view: shown3, onApply: (where) => void patchView({ where }) }) : null,
27680
+ chooserCompact ? (
27681
+ /* THE ROW HAD NO ROOM (lane C, review 2026-09-26 D6: the switcher vanished at 1280 px and
27682
+ with the record panel open): the chooser folds to ONE button naming the layout that is
27683
+ showing — it never leaves the row. */
27684
+ /* @__PURE__ */ (0, import_jsx_runtime94.jsx)("span", { className: "flex shrink-0 items-center", "data-toolbar-fit": "chooser", "data-layout-chooser-compact": "", children: /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(import_design_system85.DropdownMenu, { children: [
27685
+ /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(import_design_system85.Button, { size: "sm", variant: "outline", className: "h-8 gap-1 px-2 text-xs", "aria-label": `Layout: ${layoutShowingLabel ?? "none"}`, children: [
27686
+ layoutShowingLabel ?? "Layout",
27687
+ /* @__PURE__ */ (0, import_jsx_runtime94.jsx)("svg", { viewBox: "0 0 24 24", className: "h-3.5 w-3.5 opacity-70", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime94.jsx)("path", { d: "m6 9 6 6 6-6" }) })
27688
+ ] }) }),
27689
+ /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(import_design_system85.DropdownMenuContent, { align: "start", className: "w-44", "data-layout-chooser-menu": "", children: [
27690
+ /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.DropdownMenuLabel, { className: "text-xs text-muted-foreground", children: "Layout" }),
27691
+ layoutRadio
27692
+ ] })
27693
+ ] }) })
27694
+ ) : null,
27695
+ main === "records" && !hostLayout && !(controlsInSheet && shownLayout === "grid") ? /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(ViewFilter, { tableId, view: shown3, onApply: (where) => void patchView({ where }) }) : null,
27115
27696
  lookShowing && main === "records" && toolbarFold2 >= 1 && !namingLook ? (
27116
27697
  /* FOLDED: the row did not fit, so the three doors are one menu (same doors, same words). */
27117
27698
  /* @__PURE__ */ (0, import_jsx_runtime94.jsx)("span", { className: "flex shrink-0 items-center", "data-view-look": "", "data-view-look-folded": "", "data-toolbar-fit": "look", children: /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(import_design_system85.DropdownMenu, { children: [
27118
- /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(import_design_system85.Button, { size: "sm", variant: "outline", className: "h-7 gap-1.5 px-2 text-xs", title: LOOK_WORDS.yours, "data-view-look-menu": "", children: [
27699
+ /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(import_design_system85.Button, { size: "sm", variant: "outline", className: "h-8 gap-1.5 px-2 text-xs", title: LOOK_WORDS.yours, "data-view-look-menu": "", children: [
27119
27700
  /* @__PURE__ */ (0, import_jsx_runtime94.jsx)("span", { "aria-hidden": "true", className: "h-1.5 w-1.5 shrink-0 rounded-full bg-primary" }),
27120
27701
  LOOK_WORDS.folded,
27121
27702
  /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)("span", { className: "sr-only", children: [
@@ -27133,7 +27714,7 @@ function TablePage({
27133
27714
  ) : lookShowing && main === "records" ? /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)("span", { className: "flex shrink-0 items-center gap-1", "data-view-look": "", "data-toolbar-fit": "look", title: LOOK_WORDS.yours, children: [
27134
27715
  /* @__PURE__ */ (0, import_jsx_runtime94.jsx)("span", { className: "sr-only", children: LOOK_WORDS.yours }),
27135
27716
  /* @__PURE__ */ (0, import_jsx_runtime94.jsx)("span", { "aria-hidden": "true", className: "h-1.5 w-1.5 shrink-0 rounded-full bg-primary" }),
27136
- lookSavable ? /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.Button, { size: "sm", variant: "outline", className: "h-7 px-2 text-xs", onClick: () => void saveLookToView(), "data-view-look-save": "", children: LOOK_WORDS.saveToView }) : null,
27717
+ lookSavable ? /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.Button, { size: "sm", variant: "outline", className: "h-8 px-2 text-xs", onClick: () => void saveLookToView(), "data-view-look-save": "", children: LOOK_WORDS.saveToView }) : null,
27137
27718
  rights.write ? namingLook ? /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(
27138
27719
  "form",
27139
27720
  {
@@ -27150,15 +27731,15 @@ function TablePage({
27150
27731
  "aria-label": LOOK_WORDS.newName,
27151
27732
  value: lookName,
27152
27733
  onChange: (e) => setLookName(e.target.value),
27153
- className: "h-7 w-36 text-xs",
27734
+ className: "h-8 w-36 text-xs",
27154
27735
  placeholder: "View name"
27155
27736
  }
27156
27737
  ),
27157
- /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.Button, { size: "sm", type: "submit", className: "h-7 px-2 text-xs", disabled: lookName.trim() === "", children: "Save" })
27738
+ /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.Button, { size: "sm", type: "submit", className: "h-8 px-2 text-xs", disabled: lookName.trim() === "", children: "Save" })
27158
27739
  ]
27159
27740
  }
27160
- ) : /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.Button, { size: "sm", variant: "ghost", className: "h-7 px-2 text-xs", onClick: () => setNamingLook(true), "data-view-look-save-new": "", children: LOOK_WORDS.saveAsNew }) : null,
27161
- /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.Button, { size: "sm", variant: "ghost", className: "h-7 px-2 text-xs", onClick: () => void resetLook(), "data-view-look-reset": "", children: LOOK_WORDS.reset })
27741
+ ) : /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.Button, { size: "sm", variant: "ghost", className: "h-8 px-2 text-xs", onClick: () => setNamingLook(true), "data-view-look-save-new": "", children: LOOK_WORDS.saveAsNew }) : null,
27742
+ /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.Button, { size: "sm", variant: "ghost", className: "h-8 px-2 text-xs", onClick: () => void resetLook(), "data-view-look-reset": "", children: LOOK_WORDS.reset })
27162
27743
  ] }) : null,
27163
27744
  /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(
27164
27745
  "div",
@@ -27239,7 +27820,7 @@ function TablePage({
27239
27820
  }
27240
27821
  }
27241
27822
  )
27242
- ) : /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(TableToolbarSlotContext.Provider, { value: toolbarSlot, children: /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(
27823
+ ) : /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(TableToolbarSlotContext.Provider, { value: toolbarSlot, children: /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(TableToolbarFoldContext.Provider, { value: toolbarFold2, children: /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(TableToolbarSheetExtrasContext.Provider, { value: sheetExtras, children: /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(
27243
27824
  "div",
27244
27825
  {
27245
27826
  className: (0, import_design_system85.cn)(
@@ -27301,6 +27882,7 @@ function TablePage({
27301
27882
  setOpenRecord(recordId);
27302
27883
  setRail("record");
27303
27884
  },
27885
+ openRecordId: rail === "record" ? openRecord : null,
27304
27886
  onAskWhoChanged: (fieldKey, recordId) => {
27305
27887
  setAsking({ fieldKey, recordId });
27306
27888
  setRail("who-changed");
@@ -27310,7 +27892,7 @@ function TablePage({
27310
27892
  view || faceUnknown ? null : /* @__PURE__ */ (0, import_jsx_runtime94.jsx)("p", { className: "text-xs text-muted-foreground", children: VIEW_NOT_SAVED_YET })
27311
27893
  ]
27312
27894
  }
27313
- ) })
27895
+ ) }) }) })
27314
27896
  ] }),
27315
27897
  rail === "none" ? null : /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(RailFrame, { phone, title: RAIL_TITLE[rail], onClose: () => setRail("none"), children: [
27316
27898
  rail === "settings" ? /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(