@ai-matrx/records-ui 0.82.0 → 0.83.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2114,7 +2114,7 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow }
2114
2114
  startedFrom.current = null;
2115
2115
  }, []);
2116
2116
  const writeCellNow = useCallback4(
2117
- async (rowId, key, value) => {
2117
+ async (rowId, key, value, base, resent = false) => {
2118
2118
  const id = cellKey(rowId, key);
2119
2119
  setOptimistic((held) => ({ ...held, [id]: value }));
2120
2120
  setSaving((held) => ({ ...held, [id]: true }));
@@ -2157,22 +2157,31 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow }
2157
2157
  delete next[id];
2158
2158
  return next;
2159
2159
  });
2160
- if (written.error.code === "stale_write") versions.current.delete(rowId);
2160
+ const conflict = asWriteConflict(written.error, rowId);
2161
+ if (written.error.code === "stale_write") {
2162
+ if (conflict) rememberVersion(versions.current, rowId, conflict.current_version);
2163
+ else versions.current.delete(rowId);
2164
+ if (conflict && !resent && sameValue(conflict.contested_fields[key], base)) {
2165
+ await writeCellNow(rowId, key, value, base, true);
2166
+ return;
2167
+ }
2168
+ }
2161
2169
  setRefusals((held) => ({
2162
2170
  ...held,
2163
2171
  [id]: {
2164
2172
  error: written.error,
2165
- conflict: asWriteConflict(written.error, rowId),
2173
+ conflict,
2166
2174
  attempted: value
2167
2175
  }
2168
2176
  }));
2169
2177
  },
2178
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2170
2179
  [client, reload]
2171
2180
  );
2172
2181
  const writeCell = useCallback4(
2173
- (rowId, key, value) => {
2182
+ (rowId, key, value, base) => {
2174
2183
  const behind = queues.current.get(rowId) ?? Promise.resolve();
2175
- const next = behind.then(() => writeCellNow(rowId, key, value));
2184
+ const next = behind.then(() => writeCellNow(rowId, key, value, base));
2176
2185
  queues.current.set(
2177
2186
  rowId,
2178
2187
  next.then(
@@ -2190,7 +2199,7 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow }
2190
2199
  const open = editing;
2191
2200
  if (!open) return;
2192
2201
  const value = typed.current;
2193
- if (!sameValue(startedFrom.current, value)) void writeCell(open.rowId, open.key, value);
2202
+ if (!sameValue(startedFrom.current, value)) void writeCell(open.rowId, open.key, value, startedFrom.current);
2194
2203
  if (!move) {
2195
2204
  setEditing(null);
2196
2205
  setDraft(null);
@@ -2219,7 +2228,7 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow }
2219
2228
  (rowId, key) => {
2220
2229
  const held = refusals[cellKey(rowId, key)];
2221
2230
  if (!held) return;
2222
- void writeCell(rowId, key, held.attempted);
2231
+ void writeCell(rowId, key, held.attempted, held.conflict?.contested_fields[key]);
2223
2232
  },
2224
2233
  [refusals, writeCell]
2225
2234
  );
@@ -2241,6 +2250,33 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow }
2241
2250
  },
2242
2251
  [reload]
2243
2252
  );
2253
+ const keepEditing = useCallback4(
2254
+ (rowId, key) => {
2255
+ const held = refusals[cellKey(rowId, key)];
2256
+ if (!held || !writable(rowId)) return;
2257
+ startedFrom.current = shownValue(rowId, key);
2258
+ typed.current = held.attempted;
2259
+ setDraft(held.attempted);
2260
+ setEditing({ rowId, key });
2261
+ },
2262
+ [refusals, writable, shownValue]
2263
+ );
2264
+ const discard = useCallback4((rowId, key) => {
2265
+ const id = cellKey(rowId, key);
2266
+ setRefusals((held) => {
2267
+ if (!held[id]) return held;
2268
+ const next = { ...held };
2269
+ delete next[id];
2270
+ return next;
2271
+ });
2272
+ setOptimistic((held) => {
2273
+ if (!(id in held)) return held;
2274
+ const next = { ...held };
2275
+ delete next[id];
2276
+ return next;
2277
+ });
2278
+ setEditing((open) => open && open.rowId === rowId && open.key === key ? null : open);
2279
+ }, []);
2244
2280
  const remove = useCallback4(
2245
2281
  async (rowId) => {
2246
2282
  setBusy(true);
@@ -2288,10 +2324,12 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow }
2288
2324
  commit,
2289
2325
  setCell: (rowId, key, value) => {
2290
2326
  if (!writable(rowId)) return;
2291
- void writeCell(rowId, key, value);
2327
+ void writeCell(rowId, key, value, shownValue(rowId, key));
2292
2328
  },
2293
2329
  retry,
2294
2330
  keepTheirs,
2331
+ keepEditing,
2332
+ discard,
2295
2333
  remove,
2296
2334
  addRecord,
2297
2335
  rowError,
@@ -2360,13 +2398,28 @@ function GridCell({
2360
2398
  draft: editing.draft,
2361
2399
  onType: editing.type,
2362
2400
  onCommit: (move) => editing.commit(move),
2363
- onCancel: editing.cancel
2401
+ onCancel: editing.cancel,
2402
+ below: (
2403
+ // KEPT EDITING: the refusal stays beside the value it is about, so the
2404
+ // person reads what to change while changing it. Keep editing is the
2405
+ // state they are in, so only Discard is offered. It is INSIDE the open
2406
+ // cell, so pressing Discard is not "focus left the cell" — which would
2407
+ // commit the refused value one more time on its way out.
2408
+ refusal2 && !refusal2.conflict ? /* @__PURE__ */ jsx10(
2409
+ RefusalNotice,
2410
+ {
2411
+ error: refusal2.error,
2412
+ className: "py-1.5 text-left",
2413
+ onDiscard: () => editing.discard(row.id, field.key)
2414
+ }
2415
+ ) : null
2416
+ )
2364
2417
  }
2365
2418
  );
2366
2419
  }
2367
2420
  if (editorKindFor(field) === "checkbox") {
2368
2421
  const answered = value === true || value === false;
2369
- return /* @__PURE__ */ jsxs6("span", { "data-matrx-cell-control": "", className: "flex w-full items-center gap-1.5", children: [
2422
+ return /* @__PURE__ */ jsxs6("span", { "data-matrx-cell-control": "", className: "flex w-full flex-wrap items-center gap-1.5", children: [
2370
2423
  /* @__PURE__ */ jsx10(
2371
2424
  Checkbox3,
2372
2425
  {
@@ -2379,10 +2432,7 @@ function GridCell({
2379
2432
  }
2380
2433
  ),
2381
2434
  answered ? null : /* @__PURE__ */ jsx10("span", { className: "text-xs text-muted-foreground", title: "nobody has answered this yet", children: "\u2014" }),
2382
- refusal2 ? /* @__PURE__ */ jsxs6("span", { className: "flex items-center gap-1 rounded border border-destructive/40 px-1", children: [
2383
- /* @__PURE__ */ jsx10(RefusalLine, { error: refusal2.error }),
2384
- /* @__PURE__ */ jsx10(Button5, { size: "sm", variant: "outline", onClick: () => editing?.retry(row.id, field.key), children: "Try again" })
2385
- ] }) : null
2435
+ refusal2 ? /* @__PURE__ */ jsx10(CellRefusalNotice, { refusal: refusal2, field, row, editing, typed: false }) : null
2386
2436
  ] });
2387
2437
  }
2388
2438
  const openThisCell = (event) => {
@@ -2455,22 +2505,48 @@ function GridCell({
2455
2505
  ),
2456
2506
  onAskWhoChanged ? /* @__PURE__ */ jsx10(WhoBadge, { field, row, onAskWhoChanged }) : null,
2457
2507
  state === "saving" ? /* @__PURE__ */ jsx10("span", { className: "px-1 text-[10px] text-muted-foreground", children: "Saving\u2026" }) : null,
2458
- refusal2 ? /* @__PURE__ */ jsxs6("span", { className: "flex flex-col gap-0.5 rounded border border-destructive/40 p-1", children: [
2459
- /* @__PURE__ */ jsx10(RefusalLine, { error: refusal2.error }),
2460
- refusal2.conflict ? /* @__PURE__ */ jsxs6("span", { className: "text-[11px]", children: [
2461
- "Theirs: ",
2462
- /* @__PURE__ */ jsx10("strong", { children: formatTheirs(refusal2.conflict, field.key) })
2463
- ] }) : null,
2464
- /* @__PURE__ */ jsxs6("span", { className: "flex gap-1", children: [
2465
- /* @__PURE__ */ jsx10(Button5, { size: "sm", variant: "outline", onClick: () => editing?.retry(row.id, field.key), children: "Try again" }),
2466
- /* @__PURE__ */ jsx10(Button5, { size: "sm", variant: "ghost", onClick: () => editing?.keepTheirs(row.id, field.key), children: "Keep theirs" })
2467
- ] })
2468
- ] }) : null
2508
+ refusal2 ? /* @__PURE__ */ jsx10(CellRefusalNotice, { refusal: refusal2, field, row, editing, typed: true }) : null
2469
2509
  ]
2470
2510
  }
2471
2511
  )
2472
2512
  );
2473
2513
  }
2514
+ function CellRefusalNotice({
2515
+ refusal: refusal2,
2516
+ field,
2517
+ row,
2518
+ editing,
2519
+ typed
2520
+ }) {
2521
+ if (refusal2.conflict) {
2522
+ return /* @__PURE__ */ jsx10(
2523
+ RefusalNotice,
2524
+ {
2525
+ error: refusal2.error,
2526
+ className: "py-1.5 text-left",
2527
+ actions: /* @__PURE__ */ jsxs6("span", { className: "flex flex-col gap-1", "data-refusal-conflict": "", children: [
2528
+ /* @__PURE__ */ jsxs6("span", { className: "text-[11px]", children: [
2529
+ "Theirs: ",
2530
+ /* @__PURE__ */ jsx10("strong", { children: formatTheirs(refusal2.conflict, field.key) })
2531
+ ] }),
2532
+ /* @__PURE__ */ jsxs6("span", { className: "flex flex-wrap gap-1", children: [
2533
+ /* @__PURE__ */ jsx10(Button5, { size: "sm", variant: "outline", onClick: () => editing?.retry(row.id, field.key), children: "Try again" }),
2534
+ /* @__PURE__ */ jsx10(Button5, { size: "sm", variant: "ghost", onClick: () => editing?.keepTheirs(row.id, field.key), children: "Keep theirs" })
2535
+ ] })
2536
+ ] })
2537
+ }
2538
+ );
2539
+ }
2540
+ return /* @__PURE__ */ jsx10(
2541
+ RefusalNotice,
2542
+ {
2543
+ error: refusal2.error,
2544
+ className: "py-1.5 text-left",
2545
+ ...typed ? { onKeepEditing: () => editing?.keepEditing(row.id, field.key) } : {},
2546
+ onDiscard: () => editing?.discard(row.id, field.key)
2547
+ }
2548
+ );
2549
+ }
2474
2550
  function WhoBadge({
2475
2551
  field,
2476
2552
  row,
@@ -2523,7 +2599,8 @@ function OpenCell({
2523
2599
  draft,
2524
2600
  onType,
2525
2601
  onCommit,
2526
- onCancel
2602
+ onCancel,
2603
+ below
2527
2604
  }) {
2528
2605
  const multiline = takesANewline(field);
2529
2606
  const blurCommits = closesOnBlur(field);
@@ -2552,6 +2629,7 @@ function OpenCell({
2552
2629
  className: "flex w-full min-w-[10rem] flex-col gap-1",
2553
2630
  onDoubleClick: (event) => event.stopPropagation(),
2554
2631
  onKeyDown: (event) => {
2632
+ if (event.target?.closest?.("[data-refusal-notice]")) return;
2555
2633
  if (event.key === "Escape") {
2556
2634
  event.preventDefault();
2557
2635
  event.stopPropagation();
@@ -2577,7 +2655,8 @@ function OpenCell({
2577
2655
  blurCommits ? /* @__PURE__ */ jsx10("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__ */ jsxs6("span", { className: "flex items-center gap-1", children: [
2578
2656
  /* @__PURE__ */ jsx10(Button5, { size: "sm", onClick: () => once(() => onCommit(null)), children: "Save" }),
2579
2657
  /* @__PURE__ */ jsx10(Button5, { size: "sm", variant: "ghost", onClick: () => once(onCancel), children: "Cancel" })
2580
- ] })
2658
+ ] }),
2659
+ below
2581
2660
  ]
2582
2661
  }
2583
2662
  );
@@ -6604,8 +6683,8 @@ function TableSettings({
6604
6683
  /* @__PURE__ */ jsx21(Button16, { size: "sm", variant: "ghost", onClick: () => setAskingToRemove(null), children: "Keep it" })
6605
6684
  ] }) : null,
6606
6685
  /* @__PURE__ */ jsxs17("ul", { className: "divide-y rounded border", children: [
6607
- rows.map((field, index) => /* @__PURE__ */ jsxs17("li", { className: "flex items-center gap-2 px-2 py-1.5", children: [
6608
- /* @__PURE__ */ jsx21("span", { className: "min-w-0 flex-1 truncate", children: fieldName(field) }),
6686
+ rows.map((field, index) => /* @__PURE__ */ jsxs17("li", { "data-settings-field": "", className: "flex flex-wrap items-center gap-x-2 gap-y-1 px-2 py-1.5", children: [
6687
+ /* @__PURE__ */ jsx21("span", { "data-settings-field-name": "", className: "min-w-[8rem] flex-1 break-words", children: fieldName(field) }),
6609
6688
  /* @__PURE__ */ jsx21(Badge6, { variant: "outline", className: "text-[10px] font-normal", children: fieldTypeLabel(field) }),
6610
6689
  field.required ? /* @__PURE__ */ jsx21("span", { className: "text-[10px] text-destructive", children: "required" }) : null,
6611
6690
  /* @__PURE__ */ jsx21(
@@ -6662,9 +6741,9 @@ function TableSettings({
6662
6741
  /* @__PURE__ */ jsx21("span", { className: "font-medium", children: "Proposed" }),
6663
6742
  /* @__PURE__ */ jsx21("span", { className: "text-muted-foreground", children: proposals?.length ?? 0 })
6664
6743
  ] }),
6665
- proposals === void 0 ? /* @__PURE__ */ jsx21("p", { className: "text-muted-foreground", children: "Nothing is listing proposals here yet: the store has no `custom.field_propose` door, so this panel would be inventing an empty queue. When the door lands, its rows appear in this list with the same accept and reject." }) : proposals.length === 0 ? /* @__PURE__ */ jsx21("p", { className: "text-muted-foreground", children: "No field is waiting for a decision." }) : /* @__PURE__ */ jsx21("ul", { className: "divide-y rounded border", children: proposals.map((proposal) => /* @__PURE__ */ jsxs17("li", { className: "flex items-center gap-2 px-2 py-1.5", children: [
6666
- /* @__PURE__ */ jsx21("span", { className: "min-w-0 flex-1 truncate", children: proposal.field.label || proposal.field.key }),
6667
- /* @__PURE__ */ jsx21("span", { className: "min-w-0 flex-1 truncate text-muted-foreground", children: proposal.why }),
6744
+ proposals === void 0 ? /* @__PURE__ */ jsx21("p", { className: "text-muted-foreground", children: "Nothing is listing proposals here yet: the store has no `custom.field_propose` door, so this panel would be inventing an empty queue. When the door lands, its rows appear in this list with the same accept and reject." }) : proposals.length === 0 ? /* @__PURE__ */ jsx21("p", { className: "text-muted-foreground", children: "No field is waiting for a decision." }) : /* @__PURE__ */ jsx21("ul", { className: "divide-y rounded border", children: proposals.map((proposal) => /* @__PURE__ */ jsxs17("li", { className: "flex flex-wrap items-center gap-x-2 gap-y-1 px-2 py-1.5", children: [
6745
+ /* @__PURE__ */ jsx21("span", { className: "min-w-[8rem] flex-1 break-words", children: proposal.field.label || proposal.field.key }),
6746
+ /* @__PURE__ */ jsx21("span", { className: "min-w-[8rem] flex-1 break-words text-muted-foreground", children: proposal.why }),
6668
6747
  /* @__PURE__ */ jsx21(Badge6, { variant: "secondary", className: "text-[10px] font-normal", children: proposal.proposed_by }),
6669
6748
  /* @__PURE__ */ jsx21(Button16, { size: "sm", variant: "outline", onClick: () => onAcceptProposal?.(proposal), children: "Accept" }),
6670
6749
  /* @__PURE__ */ jsx21(Button16, { size: "sm", variant: "ghost", onClick: () => onRejectProposal?.(proposal), children: "Reject" })
@@ -6706,7 +6785,7 @@ function TableSettings({
6706
6785
 
6707
6786
  // src/Peek.tsx
6708
6787
  import { useState as useState23 } from "react";
6709
- import { useFields as useFields10, useRecord as useRecord2, useRecordsClient as useRecordsClient11, useTable as useTable7 } from "@ai-matrx/records/react";
6788
+ import { useFields as useFields10, useRecord as useRecord2, useRecordsClient as useRecordsClient12, useTable as useTable7 } from "@ai-matrx/records/react";
6710
6789
  import { AlchemyMenu as AlchemyMenu2 } from "@ai-matrx/alchemy/react";
6711
6790
 
6712
6791
  // src/RecordChat.tsx
@@ -6837,13 +6916,46 @@ function entriesFor(scope) {
6837
6916
  import { Button as Button19, Separator as Separator8, Skeleton as Skeleton5, cn as cn19 } from "@ai-matrx/design-system";
6838
6917
 
6839
6918
  // src/RecordForm.tsx
6840
- import { useEffect as useEffect12, useMemo as useMemo16, useState as useState21 } from "react";
6919
+ import { useEffect as useEffect12, useMemo as useMemo16, useRef as useRef6, useState as useState21 } from "react";
6841
6920
  import {
6842
6921
  useFields as useFields9,
6843
6922
  useRecord,
6844
- useRecordMutation
6923
+ useRecordMutation,
6924
+ useRecordsClient as useRecordsClient11
6845
6925
  } from "@ai-matrx/records/react";
6846
- import { predictWriteRefusals } from "@ai-matrx/records/core";
6926
+ import { asWriteConflict as asWriteConflict3, predictWriteRefusals } from "@ai-matrx/records/core";
6927
+
6928
+ // src/writeOnTheWinningVersion.ts
6929
+ import { asWriteConflict as asWriteConflict2 } from "@ai-matrx/records/core";
6930
+ function sameStoredValue(left, right) {
6931
+ if (left === right) return true;
6932
+ const empty = (v) => v === null || v === void 0 || v === "";
6933
+ if (empty(left) || empty(right)) return empty(left) && empty(right);
6934
+ if (typeof left === "object" || typeof right === "object") {
6935
+ try {
6936
+ return JSON.stringify(left) === JSON.stringify(right);
6937
+ } catch {
6938
+ return false;
6939
+ }
6940
+ }
6941
+ return false;
6942
+ }
6943
+ async function updateOnTheWinningVersion(client, recordId, patch, expectedVersion, base) {
6944
+ const send = (expected) => client.recordUpdate({
6945
+ record_id: recordId,
6946
+ patch,
6947
+ ...expected === null ? {} : { expectedVersion: expected }
6948
+ });
6949
+ const first = await send(expectedVersion);
6950
+ if (first.ok) return first;
6951
+ const conflict = asWriteConflict2(first.error, recordId);
6952
+ if (!conflict) return first;
6953
+ const untouched = base === "always" || Object.keys(patch).every((key) => sameStoredValue(conflict.contested_fields[key], base[key]));
6954
+ if (!untouched) return first;
6955
+ return send(conflict.current_version);
6956
+ }
6957
+
6958
+ // src/RecordForm.tsx
6847
6959
  import { Button as Button17, Separator as Separator7, cn as cn18 } from "@ai-matrx/design-system";
6848
6960
 
6849
6961
  // src/systemTable.ts
@@ -7031,13 +7143,38 @@ function RecordForm({
7031
7143
  const fields = useFields9(tableId, recordType);
7032
7144
  const existing = useRecord(recordId ?? null);
7033
7145
  const mutation = useRecordMutation();
7146
+ const client = useRecordsClient11();
7147
+ const [edit, setEdit] = useState21({
7148
+ saving: false,
7149
+ error: null,
7150
+ conflict: null
7151
+ });
7152
+ const said = {
7153
+ saving: mutation.saving || edit.saving,
7154
+ error: recordId ? edit.error : mutation.error,
7155
+ conflict: recordId ? edit.conflict : mutation.conflict,
7156
+ clearConflict: () => {
7157
+ mutation.clearConflict();
7158
+ setEdit((e) => ({ ...e, conflict: null }));
7159
+ }
7160
+ };
7034
7161
  const [draft, setDraft] = useState21({});
7035
7162
  const [touched, setTouched] = useState21(false);
7163
+ const [changed, setChanged] = useState21({});
7164
+ const [answered, setAnswered] = useState21(null);
7036
7165
  const loaded = useRecordVersion(recordId ?? null);
7037
7166
  const loadedVersion = loaded.version;
7167
+ const changedRef = useRef6({});
7168
+ changedRef.current = changed;
7038
7169
  useEffect12(() => {
7039
7170
  const document3 = existing.data?.document;
7040
- if (document3) setDraft({ ...document3 });
7171
+ if (!document3) return;
7172
+ const mine = changedRef.current;
7173
+ setDraft((held) => {
7174
+ const next = { ...document3 };
7175
+ for (const key of Object.keys(mine)) next[key] = held[key];
7176
+ return next;
7177
+ });
7041
7178
  }, [existing.data?.record_id, loadedVersion]);
7042
7179
  const document2 = useMemo16(() => {
7043
7180
  const out = {};
@@ -7053,17 +7190,56 @@ function RecordForm({
7053
7190
  );
7054
7191
  if (fields.error) return /* @__PURE__ */ jsx23(RefusalNotice, { error: fields.error, className });
7055
7192
  if (existing.error) return /* @__PURE__ */ jsx23(RefusalNotice, { error: existing.error, className });
7193
+ const storeSaid = said.error && said.error !== answered ? said.error : null;
7194
+ const fieldProblems = predicted.filter((p) => p.field_key);
7195
+ const problemsShown = (key) => touched || changed[key] ? fieldProblems.filter((p) => p.field_key === key) : [];
7196
+ const storeRefusalIsOnAField = storeSaid !== null && !said.conflict && storeSaid.code === "refused_by_rule" && (fields.data ?? []).some((f) => problemsShown(f.key).length > 0);
7197
+ const stored = existing.data?.document;
7198
+ const focusField = (field) => {
7199
+ if (!field) return;
7200
+ globalThis.document?.getElementById(`field-${field.id}`)?.focus();
7201
+ };
7202
+ const keepEditing = (field) => {
7203
+ if (storeSaid) setAnswered(storeSaid);
7204
+ const first = field ?? (fields.data ?? []).find((f) => problemsShown(f.key).length > 0) ?? (fields.data ?? [])[0];
7205
+ focusField(first);
7206
+ };
7207
+ const discard = (field) => {
7208
+ if (storeSaid) setAnswered(storeSaid);
7209
+ if (field) {
7210
+ setDraft((d) => ({ ...d, [field.key]: stored ? stored[field.key] : void 0 }));
7211
+ setChanged((c) => {
7212
+ const next = { ...c };
7213
+ delete next[field.key];
7214
+ return next;
7215
+ });
7216
+ return;
7217
+ }
7218
+ setDraft(stored ? { ...stored } : {});
7219
+ setChanged({});
7220
+ setTouched(false);
7221
+ };
7056
7222
  const save = async () => {
7057
7223
  setTouched(true);
7224
+ setAnswered(null);
7058
7225
  if (recordId) {
7059
7226
  const version = loadedVersion;
7060
- const moved = await mutation.update({
7061
- record_id: recordId,
7062
- patch: document2,
7063
- ...version === null ? {} : { expectedVersion: version }
7227
+ if (Object.keys(changed).length === 0) {
7228
+ onSaved?.(recordId);
7229
+ return;
7230
+ }
7231
+ const patch = Object.fromEntries(Object.entries(document2).filter(([key]) => changed[key]));
7232
+ setEdit({ saving: true, error: null, conflict: null });
7233
+ const answered2 = await updateOnTheWinningVersion(client, recordId, patch, version, stored ?? {});
7234
+ setEdit({
7235
+ saving: false,
7236
+ error: answered2.ok ? null : answered2.error,
7237
+ conflict: answered2.ok ? null : asWriteConflict3(answered2.error, recordId)
7064
7238
  });
7239
+ const moved = answered2.ok ? answered2.data : null;
7065
7240
  if (moved !== null) {
7066
7241
  loaded.note(moved);
7242
+ setChanged({});
7067
7243
  host.notify?.success("Saved");
7068
7244
  existing.reload();
7069
7245
  onSaved?.(recordId);
@@ -7091,8 +7267,13 @@ function RecordForm({
7091
7267
  field,
7092
7268
  value: draft[field.key],
7093
7269
  document: existing.data?.document,
7094
- problems: touched ? predicted.filter((p) => p.field_key === field.key) : [],
7095
- onChange: (value) => setDraft((d) => ({ ...d, [field.key]: value }))
7270
+ problems: problemsShown(field.key),
7271
+ onChange: (value) => {
7272
+ setDraft((d) => ({ ...d, [field.key]: value }));
7273
+ setChanged((c) => c[field.key] ? c : { ...c, [field.key]: true });
7274
+ },
7275
+ onKeepEditing: () => keepEditing(field),
7276
+ onDiscard: () => discard(field)
7096
7277
  },
7097
7278
  field.id
7098
7279
  )) }),
@@ -7103,13 +7284,13 @@ function RecordForm({
7103
7284
  },
7104
7285
  p.message
7105
7286
  )) : null,
7106
- mutation.error ? /* @__PURE__ */ jsx23(
7287
+ storeSaid && !storeRefusalIsOnAField ? /* @__PURE__ */ jsx23(
7107
7288
  RefusalNotice,
7108
7289
  {
7109
- error: mutation.error,
7110
- ...mutation.conflict ? {
7290
+ error: storeSaid,
7291
+ ...said.conflict ? {
7111
7292
  actions: /* @__PURE__ */ jsxs19("div", { className: "mt-1 space-y-1", children: [
7112
- Object.entries(mutation.conflict.contested_fields).map(([key, theirs]) => {
7293
+ Object.entries(said.conflict.contested_fields).map(([key, theirs]) => {
7113
7294
  const column = (fields.data ?? []).find((f) => f.key === key);
7114
7295
  return /* @__PURE__ */ jsxs19("p", { className: "text-[11px]", children: [
7115
7296
  column ? fieldName(column) : humanize(key),
@@ -7127,19 +7308,21 @@ function RecordForm({
7127
7308
  size: "sm",
7128
7309
  variant: "outline",
7129
7310
  onClick: () => {
7311
+ if (said.conflict) loaded.note(said.conflict.current_version);
7130
7312
  existing.reload();
7131
- mutation.clearConflict();
7313
+ said.clearConflict();
7314
+ setAnswered(storeSaid);
7132
7315
  },
7133
7316
  children: "Take theirs and start again"
7134
7317
  }
7135
7318
  )
7136
7319
  ] })
7137
- } : {}
7320
+ } : { onKeepEditing: () => keepEditing(), onDiscard: () => discard() }
7138
7321
  }
7139
7322
  ) : null,
7140
7323
  /* @__PURE__ */ jsx23(Separator7, {}),
7141
7324
  /* @__PURE__ */ jsxs19("div", { className: "flex items-center gap-2", children: [
7142
- /* @__PURE__ */ jsx23(Button17, { type: "submit", size: "sm", disabled: mutation.saving, children: mutation.saving ? "Saving\u2026" : recordId ? "Save" : "Create" }),
7325
+ /* @__PURE__ */ jsx23(Button17, { type: "submit", size: "sm", disabled: said.saving, children: said.saving ? "Saving\u2026" : recordId ? "Save" : "Create" }),
7143
7326
  onCancel ? /* @__PURE__ */ jsx23(Button17, { type: "button", size: "sm", variant: "ghost", onClick: onCancel, children: "Cancel" }) : null
7144
7327
  ] })
7145
7328
  ]
@@ -7151,17 +7334,20 @@ function FieldRow({
7151
7334
  value,
7152
7335
  document: document2,
7153
7336
  problems,
7154
- onChange
7337
+ onChange,
7338
+ onKeepEditing,
7339
+ onDiscard
7155
7340
  }) {
7156
7341
  const id = `field-${field.id}`;
7157
7342
  return /* @__PURE__ */ jsxs19("div", { className: "flex min-w-0 flex-col gap-1", children: [
7158
7343
  /* @__PURE__ */ jsx23(FieldLabel, { field, htmlFor: id, children: /* @__PURE__ */ jsx23(ProvenanceBadge, { document: document2, fieldKey: field.key }) }),
7159
7344
  /* @__PURE__ */ jsx23(FieldControl, { field, value, onChange, id }),
7160
- problems.map((problem) => /* @__PURE__ */ jsx23(
7345
+ problems.map((problem, index) => /* @__PURE__ */ jsx23(
7161
7346
  RefusalNotice,
7162
7347
  {
7163
7348
  error: { code: "refused_by_rule", message: problem.message },
7164
- className: "border-0 p-0"
7349
+ className: "border-0 p-0",
7350
+ ...index === 0 ? { onKeepEditing, onDiscard } : {}
7165
7351
  },
7166
7352
  problem.message
7167
7353
  ))
@@ -7236,7 +7422,7 @@ function Peek({ tableId, recordId, onClose, className }) {
7236
7422
  const record = useRecord2(recordId, true);
7237
7423
  const may = useRecordRights(recordId);
7238
7424
  const host = useRecordsUi();
7239
- const organizationId = useRecordsClient11().config.organizationId;
7425
+ const organizationId = useRecordsClient12().config.organizationId;
7240
7426
  const [editing, setEditing] = useState23(false);
7241
7427
  const [talking, setTalking] = useState23(false);
7242
7428
  if (record.error) return /* @__PURE__ */ jsx25(RefusalNotice, { error: record.error, className });
@@ -7454,6 +7640,9 @@ function viewFromRecord(row) {
7454
7640
  presentation: parseGridPresentation(data["presentation"])
7455
7641
  };
7456
7642
  }
7643
+ async function saveViewPatch(client, viewId, document2, expectedVersion) {
7644
+ return updateOnTheWinningVersion(client, viewId, document2, expectedVersion, "always");
7645
+ }
7457
7646
  function viewPatchDocument(patch) {
7458
7647
  const out = {};
7459
7648
  if (patch.name !== void 0) out["name"] = patch.name;
@@ -7482,15 +7671,15 @@ function parseSorts(raw) {
7482
7671
 
7483
7672
  // src/ViewSwitcher.tsx
7484
7673
  import { useEffect as useEffect15, useMemo as useMemo18, useState as useState25 } from "react";
7485
- import { useFields as useFields12, useRecords as useRecords5, useRecordsClient as useRecordsClient13 } from "@ai-matrx/records/react";
7674
+ import { useFields as useFields12, useRecords as useRecords5, useRecordsClient as useRecordsClient14 } from "@ai-matrx/records/react";
7486
7675
  import { Button as Button21, Skeleton as Skeleton7, cn as cn21 } from "@ai-matrx/design-system";
7487
7676
 
7488
7677
  // src/Pipeline.tsx
7489
- import { useCallback as useCallback10, useEffect as useEffect14, useMemo as useMemo17, useRef as useRef6, useState as useState24 } from "react";
7678
+ import { useCallback as useCallback10, useEffect as useEffect14, useMemo as useMemo17, useRef as useRef7, useState as useState24 } from "react";
7490
7679
  import {
7491
7680
  mayDrag,
7492
7681
  useFields as useFields11,
7493
- useRecordsClient as useRecordsClient12
7682
+ useRecordsClient as useRecordsClient13
7494
7683
  } from "@ai-matrx/records/react";
7495
7684
  import { Button as Button20, Skeleton as Skeleton6, cn as cn20 } from "@ai-matrx/design-system";
7496
7685
  import { jsx as jsx26, jsxs as jsxs22 } from "react/jsx-runtime";
@@ -7524,7 +7713,7 @@ function PipelineBoard({
7524
7713
  onMoved,
7525
7714
  className
7526
7715
  }) {
7527
- const client = useRecordsClient12();
7716
+ const client = useRecordsClient13();
7528
7717
  const fields = useFields11(tableId);
7529
7718
  const [definition, setDefinition] = useState24(null);
7530
7719
  const [columns, setColumns] = useState24([]);
@@ -7534,7 +7723,7 @@ function PipelineBoard({
7534
7723
  const [waiting, setWaiting] = useState24(/* @__PURE__ */ new Map());
7535
7724
  const [dragging, setDragging] = useState24(null);
7536
7725
  const [over, setOver] = useState24(null);
7537
- const alive = useRef6(true);
7726
+ const alive = useRef7(true);
7538
7727
  useEffect14(() => {
7539
7728
  alive.current = true;
7540
7729
  return () => {
@@ -7918,7 +8107,7 @@ function unplacedName(record, field, labels) {
7918
8107
  import { jsx as jsx27, jsxs as jsxs23 } from "react/jsx-runtime";
7919
8108
  var NO_FIELDS = [];
7920
8109
  function useViewRecords(view, pageSize = 200, filter) {
7921
- const client = useRecordsClient13();
8110
+ const client = useRecordsClient14();
7922
8111
  const table = useRecords5(view.ruleId ? null : view.subject, {
7923
8112
  pageSize,
7924
8113
  ...filter ? { filter } : {}
@@ -8056,7 +8245,7 @@ function offerableFields(all, want) {
8056
8245
  });
8057
8246
  }
8058
8247
  function useStageField(tableId) {
8059
- const client = useRecordsClient13();
8248
+ const client = useRecordsClient14();
8060
8249
  const [stage, setStage] = useState25({
8061
8250
  asked: false,
8062
8251
  key: null
@@ -8339,7 +8528,7 @@ import {
8339
8528
  archivedByLine,
8340
8529
  emptyArchiveLine
8341
8530
  } from "@ai-matrx/records";
8342
- import { useFields as useFields13, useRecordsClient as useRecordsClient14, useTable as useTable8 } from "@ai-matrx/records/react";
8531
+ import { useFields as useFields13, useRecordsClient as useRecordsClient15, useTable as useTable8 } from "@ai-matrx/records/react";
8343
8532
  import { Button as Button22, Skeleton as Skeleton8, cn as cn22 } from "@ai-matrx/design-system";
8344
8533
  import { jsx as jsx28, jsxs as jsxs24 } from "react/jsx-runtime";
8345
8534
  var ARCHIVE_MEANS = "Archiving never destroys anything. A record you remove from the grid waits here until somebody brings it back.";
@@ -8358,7 +8547,7 @@ function ArchivedView({
8358
8547
  onRestored,
8359
8548
  className
8360
8549
  }) {
8361
- const client = useRecordsClient14();
8550
+ const client = useRecordsClient15();
8362
8551
  const table = useTable8(tableId);
8363
8552
  const rights = useTableRights(table.data);
8364
8553
  const fields = useFields13(tableId);
@@ -8471,7 +8660,7 @@ import {
8471
8660
  portalConfirmLine,
8472
8661
  portalTitleTypedBack
8473
8662
  } from "@ai-matrx/records";
8474
- import { useRecordsClient as useRecordsClient15 } from "@ai-matrx/records/react";
8663
+ import { useRecordsClient as useRecordsClient16 } from "@ai-matrx/records/react";
8475
8664
  import { BasicInput as BasicInput8, Button as Button23, Skeleton as Skeleton9, cn as cn23 } from "@ai-matrx/design-system";
8476
8665
  import { jsx as jsx29, jsxs as jsxs25 } from "react/jsx-runtime";
8477
8666
  var ARCHIVED_NEVER_DESTROYED = "Archiving never destroys anything. What you put away waits here until somebody brings it back.";
@@ -8516,7 +8705,7 @@ function ArchivedPortals({
8516
8705
  defaultOpen,
8517
8706
  className
8518
8707
  }) {
8519
- const client = useRecordsClient15();
8708
+ const client = useRecordsClient16();
8520
8709
  const [rows, setRows] = useState27(null);
8521
8710
  const [error, setError] = useState27(null);
8522
8711
  const [confirming, setConfirming] = useState27(null);
@@ -8651,13 +8840,13 @@ function ArchivedPortals({
8651
8840
 
8652
8841
  // src/ViewBar.tsx
8653
8842
  import { useCallback as useCallback14, useEffect as useEffect18, useState as useState28 } from "react";
8654
- import { useRecordsClient as useRecordsClient16 } from "@ai-matrx/records/react";
8843
+ import { useRecordsClient as useRecordsClient17 } from "@ai-matrx/records/react";
8655
8844
  import { Button as Button24, Input as Input2, Skeleton as Skeleton10, cn as cn24 } from "@ai-matrx/design-system";
8656
8845
 
8657
8846
  // src/seedOnce.ts
8658
- import { useCallback as useCallback13, useRef as useRef7 } from "react";
8847
+ import { useCallback as useCallback13, useRef as useRef8 } from "react";
8659
8848
  function useSeedGuard() {
8660
- const claimed = useRef7(/* @__PURE__ */ new Set());
8849
+ const claimed = useRef8(/* @__PURE__ */ new Set());
8661
8850
  return useCallback13((name) => {
8662
8851
  if (claimed.current.has(name)) return false;
8663
8852
  claimed.current.add(name);
@@ -8670,7 +8859,7 @@ import { useTable as useTable9 } from "@ai-matrx/records/react";
8670
8859
  import { jsx as jsx30, jsxs as jsxs26 } from "react/jsx-runtime";
8671
8860
  var SAVED_VIEWS_UNAVAILABLE = "Saved views are not available in this organization right now, so the layout you pick here is not being kept. Everything else on this table works as usual.";
8672
8861
  function ViewBar({ tableId, activeViewId, onActiveView, seed, className }) {
8673
- const client = useRecordsClient16();
8862
+ const client = useRecordsClient17();
8674
8863
  const claimSeed = useSeedGuard();
8675
8864
  const table = useTable9(tableId);
8676
8865
  const rights = useTableRights(table.data);
@@ -8791,7 +8980,7 @@ function ViewBar({ tableId, activeViewId, onActiveView, seed, className }) {
8791
8980
 
8792
8981
  // src/ProposalRow.tsx
8793
8982
  import { useState as useState29 } from "react";
8794
- import { useRecordsClient as useRecordsClient17 } from "@ai-matrx/records/react";
8983
+ import { useRecordsClient as useRecordsClient18 } from "@ai-matrx/records/react";
8795
8984
  import { Badge as Badge7, Button as Button25, cn as cn25 } from "@ai-matrx/design-system";
8796
8985
  import { jsx as jsx31, jsxs as jsxs27 } from "react/jsx-runtime";
8797
8986
  var ACT_WORD = PROPOSED_CHANGE_ACT_LABEL;
@@ -8804,7 +8993,7 @@ function ProposalRow({
8804
8993
  onSettled,
8805
8994
  className
8806
8995
  }) {
8807
- const client = useRecordsClient17();
8996
+ const client = useRecordsClient18();
8808
8997
  const [settled, setSettled] = useState29(outcome ?? null);
8809
8998
  const [busy, setBusy] = useState29(false);
8810
8999
  const [confirming, setConfirming] = useState29(false);
@@ -8892,13 +9081,13 @@ function ProposalRow({
8892
9081
  }
8893
9082
 
8894
9083
  // src/ActionInbox.tsx
8895
- import { useCallback as useCallback16, useEffect as useEffect20, useMemo as useMemo20, useRef as useRef8, useState as useState31 } from "react";
8896
- import { useRecordsClient as useRecordsClient19 } from "@ai-matrx/records/react";
9084
+ import { useCallback as useCallback16, useEffect as useEffect20, useMemo as useMemo20, useRef as useRef9, useState as useState31 } from "react";
9085
+ import { useRecordsClient as useRecordsClient20 } from "@ai-matrx/records/react";
8897
9086
  import { Badge as Badge9, Button as Button27, Skeleton as Skeleton12, cn as cn27 } from "@ai-matrx/design-system";
8898
9087
 
8899
9088
  // src/ChecklistRunner.tsx
8900
9089
  import { useCallback as useCallback15, useEffect as useEffect19, useMemo as useMemo19, useState as useState30 } from "react";
8901
- import { useRecordsClient as useRecordsClient18, useTable as useTable10 } from "@ai-matrx/records/react";
9090
+ import { useRecordsClient as useRecordsClient19, useTable as useTable10 } from "@ai-matrx/records/react";
8902
9091
  import { Badge as Badge8, BasicInput as BasicInput9, BasicTextarea as BasicTextarea4, Button as Button26, Skeleton as Skeleton11, cn as cn26 } from "@ai-matrx/design-system";
8903
9092
  import { Fragment as Fragment13, jsx as jsx32, jsxs as jsxs28 } from "react/jsx-runtime";
8904
9093
  var DUE_WORD = DUE_STATE_LABEL;
@@ -8912,7 +9101,7 @@ function ChecklistRunner({
8912
9101
  offerToStart,
8913
9102
  className
8914
9103
  }) {
8915
- const client = useRecordsClient18();
9104
+ const client = useRecordsClient19();
8916
9105
  const table = useTable10(tableId ?? null);
8917
9106
  const rights = useTableRights(table.data);
8918
9107
  const [runs, setRuns] = useState30(null);
@@ -9161,7 +9350,7 @@ function StepRow({
9161
9350
  );
9162
9351
  }
9163
9352
  function useMyChecklistSteps(limit = 25) {
9164
- const client = useRecordsClient18();
9353
+ const client = useRecordsClient19();
9165
9354
  const me = client.config.actor.user_id ?? null;
9166
9355
  const [steps, setSteps] = useState30([]);
9167
9356
  const [loading, setLoading] = useState30(true);
@@ -9200,7 +9389,7 @@ function useMyChecklistSteps(limit = 25) {
9200
9389
  return { steps, loading, error, refresh };
9201
9390
  }
9202
9391
  function MyChecklistSteps({ className }) {
9203
- const client = useRecordsClient18();
9392
+ const client = useRecordsClient19();
9204
9393
  const { steps, loading, error, refresh } = useMyChecklistSteps();
9205
9394
  const [busy, setBusy] = useState30(null);
9206
9395
  const [refusal2, setRefusal] = useState30(null);
@@ -9239,7 +9428,7 @@ function MyChecklistSteps({ className }) {
9239
9428
  ] });
9240
9429
  }
9241
9430
  function ChecklistsPanel({ tableId, onOpenRecord, className }) {
9242
- const client = useRecordsClient18();
9431
+ const client = useRecordsClient19();
9243
9432
  const table = useTable10(tableId);
9244
9433
  const rights = useTableRights(table.data);
9245
9434
  const [templates, setTemplates] = useState30(null);
@@ -9400,7 +9589,7 @@ function ChecklistTemplateEditor({
9400
9589
  onDone,
9401
9590
  className
9402
9591
  }) {
9403
- const client = useRecordsClient18();
9592
+ const client = useRecordsClient19();
9404
9593
  const [name, setName] = useState30("");
9405
9594
  const [rows, setRows] = useState30([{ ...EMPTY_ROW }]);
9406
9595
  const [refusal2, setRefusal] = useState30(null);
@@ -9613,13 +9802,13 @@ var ACTION_KINDS = ["approval", "assignment", "proposal"];
9613
9802
  var KIND_WORD = WORK_INBOX_KIND_LABEL;
9614
9803
  var DUE_WORD2 = DUE_STATE_LABEL;
9615
9804
  function ActionInbox({ tableId, includeSettled = false, onOpenRecord, className }) {
9616
- const client = useRecordsClient19();
9805
+ const client = useRecordsClient20();
9617
9806
  const [items, setItems] = useState31(null);
9618
9807
  const [error, setError] = useState31(null);
9619
9808
  const [outcome, setOutcome] = useState31({});
9620
9809
  const [busy, setBusy] = useState31(null);
9621
9810
  const [cursor, setCursor] = useState31(0);
9622
- const listRef = useRef8(null);
9811
+ const listRef = useRef9(null);
9623
9812
  const load = useCallback16(async () => {
9624
9813
  const result = await client.workInbox({ limit: 200, includeDecided: includeSettled });
9625
9814
  if (!result.ok) {
@@ -9767,16 +9956,16 @@ function ActionInbox({ tableId, includeSettled = false, onOpenRecord, className
9767
9956
  }
9768
9957
 
9769
9958
  // src/HistoryPanel.tsx
9770
- import { useCallback as useCallback17, useEffect as useEffect21, useRef as useRef9, useState as useState32 } from "react";
9959
+ import { useCallback as useCallback17, useEffect as useEffect21, useRef as useRef10, useState as useState32 } from "react";
9771
9960
  import {
9772
9961
  useFields as useFields14,
9773
- useRecordsClient as useRecordsClient20,
9962
+ useRecordsClient as useRecordsClient21,
9774
9963
  useTable as useTable11
9775
9964
  } from "@ai-matrx/records/react";
9776
9965
  import { Badge as Badge10, Button as Button28, Skeleton as Skeleton13, cn as cn28 } from "@ai-matrx/design-system";
9777
9966
  import { jsx as jsx34, jsxs as jsxs30 } from "react/jsx-runtime";
9778
9967
  function HistoryPanel({ tableId, recordId, onAskAboutField, className }) {
9779
- const client = useRecordsClient20();
9968
+ const client = useRecordsClient21();
9780
9969
  const table = useTable11(tableId);
9781
9970
  const fields = useFields14(tableId);
9782
9971
  const rights = useTableRights(table.data);
@@ -9785,7 +9974,7 @@ function HistoryPanel({ tableId, recordId, onAskAboutField, className }) {
9785
9974
  const [open, setOpen] = useState32(null);
9786
9975
  const [pending, setPending] = useState32({ phase: "idle" });
9787
9976
  const [said, setSaid] = useState32(null);
9788
- const listRef = useRef9(null);
9977
+ const listRef = useRef10(null);
9789
9978
  const load = useCallback17(async () => {
9790
9979
  const answered = await client.recordHistory({ record_id: recordId });
9791
9980
  if (!answered.ok) {
@@ -10076,16 +10265,16 @@ function say(value, field) {
10076
10265
  }
10077
10266
 
10078
10267
  // src/CommentThread.tsx
10079
- import { useCallback as useCallback18, useEffect as useEffect22, useMemo as useMemo21, useRef as useRef10, useState as useState33 } from "react";
10268
+ import { useCallback as useCallback18, useEffect as useEffect22, useMemo as useMemo21, useRef as useRef11, useState as useState33 } from "react";
10080
10269
  import {
10081
10270
  useFields as useFields15,
10082
- useRecordsClient as useRecordsClient21,
10271
+ useRecordsClient as useRecordsClient22,
10083
10272
  useTable as useTable12
10084
10273
  } from "@ai-matrx/records/react";
10085
10274
  import { Badge as Badge11, Button as Button29, Skeleton as Skeleton14, Textarea, cn as cn29 } from "@ai-matrx/design-system";
10086
10275
  import { jsx as jsx35, jsxs as jsxs31 } from "react/jsx-runtime";
10087
10276
  function CommentThread({ tableId, recordId, fieldKey, className }) {
10088
- const client = useRecordsClient21();
10277
+ const client = useRecordsClient22();
10089
10278
  const table = useTable12(tableId);
10090
10279
  const fields = useFields15(tableId);
10091
10280
  const host = useRecordsUi();
@@ -10099,7 +10288,7 @@ function CommentThread({ tableId, recordId, fieldKey, className }) {
10099
10288
  const [people, setPeople] = useState33([]);
10100
10289
  const [mentionQuery, setMentionQuery] = useState33(null);
10101
10290
  const [picked, setPicked] = useState33([]);
10102
- const box = useRef10(null);
10291
+ const box = useRef11(null);
10103
10292
  const load = useCallback18(async () => {
10104
10293
  const answered = await client.commentThread({ record_id: recordId, include_resolved: showResolved });
10105
10294
  if (!answered.ok) {
@@ -10322,7 +10511,7 @@ function Line({
10322
10511
  import { useCallback as useCallback19, useEffect as useEffect23, useState as useState34 } from "react";
10323
10512
  import {
10324
10513
  useFields as useFields16,
10325
- useRecordsClient as useRecordsClient22,
10514
+ useRecordsClient as useRecordsClient23,
10326
10515
  useTable as useTable13
10327
10516
  } from "@ai-matrx/records/react";
10328
10517
  import { Badge as Badge12, Button as Button30, Skeleton as Skeleton15, cn as cn30 } from "@ai-matrx/design-system";
@@ -10335,7 +10524,7 @@ function FieldHistoryPanel({
10335
10524
  onOpenRecord,
10336
10525
  className
10337
10526
  }) {
10338
- const client = useRecordsClient22();
10527
+ const client = useRecordsClient23();
10339
10528
  const table = useTable13(tableId);
10340
10529
  const fields = useFields16(tableId);
10341
10530
  const rights = useTableRights(table.data);
@@ -10548,12 +10737,12 @@ function submissionStamp(args) {
10548
10737
 
10549
10738
  // src/PortalBuilder.tsx
10550
10739
  import { useCallback as useCallback20, useEffect as useEffect24, useMemo as useMemo22, useState as useState35 } from "react";
10551
- import { useRecordsClient as useRecordsClient23, useTables as useTables3 } from "@ai-matrx/records/react";
10740
+ import { useRecordsClient as useRecordsClient24, useTables as useTables3 } from "@ai-matrx/records/react";
10552
10741
  import { BasicInput as BasicInput10, Button as Button31, Checkbox as Checkbox5, Label as Label5, Separator as Separator9, Skeleton as Skeleton16, cn as cn31 } from "@ai-matrx/design-system";
10553
10742
  import { Fragment as Fragment15, jsx as jsx37, jsxs as jsxs33 } from "react/jsx-runtime";
10554
10743
  var RESTATING_REPLACES = "Saving replaces what this portal shows: a table you untick stops being visible to every client the moment you save. Who is invited is not touched.";
10555
10744
  function PortalBuilder({ tableId, portalId, onSaved, onClose, className }) {
10556
- const client = useRecordsClient23();
10745
+ const client = useRecordsClient24();
10557
10746
  const tables = useTables3();
10558
10747
  const [title, setTitle] = useState35("");
10559
10748
  const [clientTableId, setClientTableId] = useState35(null);
@@ -10843,7 +11032,7 @@ function PortalBuilder({ tableId, portalId, onSaved, onClose, className }) {
10843
11032
 
10844
11033
  // src/PortalsPanel.tsx
10845
11034
  import { useCallback as useCallback21, useEffect as useEffect25, useMemo as useMemo23, useState as useState36 } from "react";
10846
- import { useRecordsClient as useRecordsClient24 } from "@ai-matrx/records/react";
11035
+ import { useRecordsClient as useRecordsClient25 } from "@ai-matrx/records/react";
10847
11036
  import {
10848
11037
  portalArchiveConsequence,
10849
11038
  portalConfirmLine as portalConfirmLine2,
@@ -10914,7 +11103,7 @@ function stateWords(person) {
10914
11103
  }
10915
11104
  var PORTAL_SUGGESTION = "Let each of my customers sign in and see their own jobs and invoices, and nothing else.";
10916
11105
  function PortalsPanel({ tableId, activePortalId, className }) {
10917
- const client = useRecordsClient24();
11106
+ const client = useRecordsClient25();
10918
11107
  const host = useRecordsUi();
10919
11108
  const [portals, setPortals] = useState36(null);
10920
11109
  const [exposures, setExposures] = useState36([]);
@@ -11094,7 +11283,7 @@ function ArchivePortalControl({
11094
11283
  portal,
11095
11284
  onArchived
11096
11285
  }) {
11097
- const client = useRecordsClient24();
11286
+ const client = useRecordsClient25();
11098
11287
  const [asking, setAsking] = useState36(false);
11099
11288
  const [typed, setTyped] = useState36("");
11100
11289
  const [busy, setBusy] = useState36(false);
@@ -11201,7 +11390,7 @@ function PortalDetail({
11201
11390
  origin,
11202
11391
  onChanged
11203
11392
  }) {
11204
- const client = useRecordsClient24();
11393
+ const client = useRecordsClient25();
11205
11394
  const [card, setCard] = useState36(null);
11206
11395
  const [error, setError] = useState36(null);
11207
11396
  const load = useCallback21(async () => {
@@ -11444,7 +11633,7 @@ function Preview({
11444
11633
  ] });
11445
11634
  }
11446
11635
  function Invite({ card, onInvited }) {
11447
- const client = useRecordsClient24();
11636
+ const client = useRecordsClient25();
11448
11637
  const [rows, setRows] = useState36(null);
11449
11638
  const [clientTable, setClientTable] = useState36(null);
11450
11639
  const [search, setSearch] = useState36("");
@@ -11556,7 +11745,7 @@ function Invite({ card, onInvited }) {
11556
11745
 
11557
11746
  // src/DigestScheduler.tsx
11558
11747
  import { useCallback as useCallback22, useEffect as useEffect26, useMemo as useMemo24, useState as useState37 } from "react";
11559
- import { useRecordsClient as useRecordsClient25 } from "@ai-matrx/records/react";
11748
+ import { useRecordsClient as useRecordsClient26 } from "@ai-matrx/records/react";
11560
11749
  import { BasicInput as BasicInput12, Button as Button34, Checkbox as Checkbox6, Label as Label6, Separator as Separator11, Skeleton as Skeleton18, cn as cn34 } from "@ai-matrx/design-system";
11561
11750
  import { Fragment as Fragment17, jsx as jsx40, jsxs as jsxs36 } from "react/jsx-runtime";
11562
11751
  var WEEKDAYS = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"];
@@ -11580,7 +11769,7 @@ function DigestScheduler({
11580
11769
  onClose,
11581
11770
  className
11582
11771
  }) {
11583
- const client = useRecordsClient25();
11772
+ const client = useRecordsClient26();
11584
11773
  const host = useRecordsUi();
11585
11774
  const [views, setViews] = useState37(null);
11586
11775
  const [cadences, setCadences] = useState37([]);
@@ -11872,7 +12061,7 @@ function DigestScheduler({
11872
12061
 
11873
12062
  // src/SubscriptionsPanel.tsx
11874
12063
  import { useCallback as useCallback23, useEffect as useEffect27, useState as useState38 } from "react";
11875
- import { useRecordsClient as useRecordsClient26 } from "@ai-matrx/records/react";
12064
+ import { useRecordsClient as useRecordsClient27 } from "@ai-matrx/records/react";
11876
12065
  import { Badge as Badge14, Button as Button35, Skeleton as Skeleton19, Switch as Switch2, cn as cn35 } from "@ai-matrx/design-system";
11877
12066
  import { jsx as jsx41, jsxs as jsxs37 } from "react/jsx-runtime";
11878
12067
  var RULE_NOT_HERE_LINE = "The link named a digest that is not one of yours on this table \u2014 it belongs to somebody else, or it was switched off for good. Yours are listed here; its owner can change theirs.";
@@ -11881,10 +12070,25 @@ function whenItFires(subscription) {
11881
12070
  const every = subscription.cadence === "hourly" ? "an hourly summary" : subscription.cadence === "weekly" ? "a weekly summary" : "a daily summary";
11882
12071
  return subscription.schedule ? `${every}, ${subscription.schedule}` : every;
11883
12072
  }
12073
+ var PERIOD_MS = {
12074
+ hourly: 60 * 60 * 1e3,
12075
+ daily: 24 * 60 * 60 * 1e3,
12076
+ weekly: 7 * 24 * 60 * 60 * 1e3
12077
+ };
12078
+ function nextSummaryAt(subscription, now) {
12079
+ if (!subscription.next_digest_at) return null;
12080
+ const due = new Date(subscription.next_digest_at);
12081
+ if (Number.isNaN(due.getTime())) return null;
12082
+ if (due.getTime() > now.getTime()) return due;
12083
+ const period = PERIOD_MS[subscription.cadence];
12084
+ if (!period) return due;
12085
+ const behind = now.getTime() - due.getTime();
12086
+ return new Date(due.getTime() + (Math.floor(behind / period) + 1) * period);
12087
+ }
11884
12088
  var CHANNEL_WORDS2 = SUBSCRIPTION_CHANNEL_LABEL;
11885
12089
  var DIGEST_SUGGESTION = "Email me a summary of this every Monday at 8 in the morning.";
11886
12090
  function SubscriptionsPanel({ tableId, activeRuleId, className }) {
11887
- const client = useRecordsClient26();
12091
+ const client = useRecordsClient27();
11888
12092
  const [rows, setRows] = useState38(null);
11889
12093
  const [error, setError] = useState38(null);
11890
12094
  const [busy, setBusy] = useState38(null);
@@ -12008,7 +12212,7 @@ function SubscriptionsPanel({ tableId, activeRuleId, className }) {
12008
12212
  /* @__PURE__ */ jsxs37("div", { className: "mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
12009
12213
  subscription.next_digest_at ? /* @__PURE__ */ jsxs37("span", { children: [
12010
12214
  "Next summary ",
12011
- new Date(subscription.next_digest_at).toLocaleString()
12215
+ nextSummaryAt(subscription, /* @__PURE__ */ new Date())?.toLocaleString()
12012
12216
  ] }) : subscription.cadence === "instant" ? null : subscription.muted ? /* @__PURE__ */ jsx41("span", { children: "No next summary while it is off." }) : null,
12013
12217
  subscription.last_sent_at ? /* @__PURE__ */ jsxs37("span", { children: [
12014
12218
  "Last told you ",
@@ -12077,14 +12281,14 @@ function SubscriptionsPanel({ tableId, activeRuleId, className }) {
12077
12281
 
12078
12282
  // src/FormBuilder.tsx
12079
12283
  import { useCallback as useCallback25, useEffect as useEffect30, useMemo as useMemo26, useState as useState41 } from "react";
12080
- import { useFields as useFields18, useRecordsClient as useRecordsClient28, useTable as useTable14 } from "@ai-matrx/records/react";
12284
+ import { useFields as useFields18, useRecordsClient as useRecordsClient29, useTable as useTable14 } from "@ai-matrx/records/react";
12081
12285
 
12082
12286
  // src/publish-gate.ts
12083
12287
  import { useEffect as useEffect28, useState as useState39 } from "react";
12084
- import { useRecordsClient as useRecordsClient27 } from "@ai-matrx/records/react";
12288
+ import { useRecordsClient as useRecordsClient28 } from "@ai-matrx/records/react";
12085
12289
  var PUBLISH_NEEDS_THE_STORE_ON = "This organization has its record store switched off, so publishing would hand you a link that opens nothing. An owner or an administrator turns it on under Database settings, on the unified data screen \u2014 everything you have already built here is kept and starts working the moment they do.";
12086
12290
  function usePublishGate() {
12087
- const client = useRecordsClient27();
12291
+ const client = useRecordsClient28();
12088
12292
  const [storeOpen, setStoreOpen] = useState39(null);
12089
12293
  useEffect28(() => {
12090
12294
  let alive = true;
@@ -12117,7 +12321,7 @@ import {
12117
12321
  } from "@ai-matrx/design-system";
12118
12322
 
12119
12323
  // src/FormRunner.tsx
12120
- import { useCallback as useCallback24, useEffect as useEffect29, useMemo as useMemo25, useRef as useRef11, useState as useState40 } from "react";
12324
+ import { useCallback as useCallback24, useEffect as useEffect29, useMemo as useMemo25, useRef as useRef12, useState as useState40 } from "react";
12121
12325
  import { useFields as useFields17, useOptionalRecordsClient as useOptionalRecordsClient4 } from "@ai-matrx/records/react";
12122
12326
  import { Button as Button36, Progress, Skeleton as Skeleton20, cn as cn36 } from "@ai-matrx/design-system";
12123
12327
  import { Fragment as Fragment18, jsx as jsx42, jsxs as jsxs38 } from "react/jsx-runtime";
@@ -12202,8 +12406,8 @@ function FormStage({
12202
12406
  const [writing, setWriting] = useState40(false);
12203
12407
  const [done, setDone] = useState40(null);
12204
12408
  const [hidden, setHidden] = useState40({});
12205
- const decoy = useRef11("");
12206
- const stage = useRef11(null);
12409
+ const decoy = useRef12("");
12410
+ const stage = useRef12(null);
12207
12411
  const questions = useMemo25(() => {
12208
12412
  const byKey = new Map((fields ?? []).map((f) => [f.key, f]));
12209
12413
  return (form.questions ?? []).map((q) => {
@@ -12467,7 +12671,7 @@ function whyNotAskable(fields, hidden = []) {
12467
12671
  // src/FormBuilder.tsx
12468
12672
  import { Fragment as Fragment19, jsx as jsx43, jsxs as jsxs39 } from "react/jsx-runtime";
12469
12673
  function FormBuilder({ tableId, seed, activeFormId, onActiveForm, className }) {
12470
- const client = useRecordsClient28();
12674
+ const client = useRecordsClient29();
12471
12675
  const publishGate = usePublishGate();
12472
12676
  const host = useRecordsUi();
12473
12677
  const claimSeed = useSeedGuard();
@@ -12927,13 +13131,13 @@ function groupLabel(groups) {
12927
13131
  import {
12928
13132
  useEffect as useEffect31,
12929
13133
  useId as useId2,
12930
- useRef as useRef12,
13134
+ useRef as useRef13,
12931
13135
  useState as useState42
12932
13136
  } from "react";
12933
13137
  import { cn as cn38 } from "@ai-matrx/design-system";
12934
13138
  import { jsx as jsx44, jsxs as jsxs40 } from "react/jsx-runtime";
12935
13139
  function useMeasuredWidth(fallback = 480) {
12936
- const ref = useRef12(null);
13140
+ const ref = useRef13(null);
12937
13141
  const [width, setWidth] = useState42(fallback);
12938
13142
  useEffect31(() => {
12939
13143
  const node = ref.current;
@@ -13060,11 +13264,11 @@ function isSignatureField(field) {
13060
13264
 
13061
13265
  // src/DocTemplate.tsx
13062
13266
  import { useCallback as useCallback26, useEffect as useEffect32, useState as useState43 } from "react";
13063
- import { useFields as useFields19, useRecordsClient as useRecordsClient29, useTable as useTable15 } from "@ai-matrx/records/react";
13267
+ import { useFields as useFields19, useRecordsClient as useRecordsClient30, useTable as useTable15 } from "@ai-matrx/records/react";
13064
13268
  import { BasicInput as BasicInput14, BasicTextarea as BasicTextarea6, Button as Button38, Label as Label8, Skeleton as Skeleton22, cn as cn39 } from "@ai-matrx/design-system";
13065
13269
  import { jsx as jsx45, jsxs as jsxs41 } from "react/jsx-runtime";
13066
13270
  function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, className }) {
13067
- const client = useRecordsClient29();
13271
+ const client = useRecordsClient30();
13068
13272
  const claimSeed = useSeedGuard();
13069
13273
  const table = useTable15(tableId);
13070
13274
  const rights = useTableRights(table.data);
@@ -13243,18 +13447,18 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
13243
13447
  }
13244
13448
 
13245
13449
  // src/DocRender.tsx
13246
- import { useCallback as useCallback27, useEffect as useEffect33, useRef as useRef13, useState as useState44 } from "react";
13247
- import { useRecordsClient as useRecordsClient30 } from "@ai-matrx/records/react";
13450
+ import { useCallback as useCallback27, useEffect as useEffect33, useRef as useRef14, useState as useState44 } from "react";
13451
+ import { useRecordsClient as useRecordsClient31 } from "@ai-matrx/records/react";
13248
13452
  import { Button as Button39, Skeleton as Skeleton23, cn as cn40 } from "@ai-matrx/design-system";
13249
13453
  import { jsx as jsx46, jsxs as jsxs42 } from "react/jsx-runtime";
13250
13454
  function DocRender({ templateId, recordId, filename, onRendered, className }) {
13251
- const client = useRecordsClient30();
13455
+ const client = useRecordsClient31();
13252
13456
  const [preview, setPreview] = useState44(null);
13253
13457
  const [renders, setRenders] = useState44(null);
13254
13458
  const [showing, setShowing] = useState44(null);
13255
13459
  const [error, setError] = useState44(null);
13256
13460
  const [busy, setBusy] = useState44(null);
13257
- const paper = useRef13(null);
13461
+ const paper = useRef14(null);
13258
13462
  const load = useCallback27(async () => {
13259
13463
  const [body, held] = await Promise.all([
13260
13464
  client.docRenderBody({ template_id: templateId, record_id: recordId }),
@@ -13357,12 +13561,12 @@ function DocRender({ templateId, recordId, filename, onRendered, className }) {
13357
13561
 
13358
13562
  // src/SignBlock.tsx
13359
13563
  import { useCallback as useCallback28, useEffect as useEffect34, useState as useState45 } from "react";
13360
- import { useFields as useFields20, useRecordsClient as useRecordsClient31 } from "@ai-matrx/records/react";
13564
+ import { useFields as useFields20, useRecordsClient as useRecordsClient32 } from "@ai-matrx/records/react";
13361
13565
  import { BasicInput as BasicInput15, Button as Button40, Skeleton as Skeleton24, cn as cn41 } from "@ai-matrx/design-system";
13362
13566
  import { useTable as useTable16 } from "@ai-matrx/records/react";
13363
13567
  import { jsx as jsx47, jsxs as jsxs43 } from "react/jsx-runtime";
13364
13568
  function SignBlock({ tableId, recordId, render, className }) {
13365
- const client = useRecordsClient31();
13569
+ const client = useRecordsClient32();
13366
13570
  const table = useTable16(tableId);
13367
13571
  const rights = useTableRights(table.data);
13368
13572
  const fields = useFields20(tableId);
@@ -13508,13 +13712,13 @@ function SignBlock({ tableId, recordId, render, className }) {
13508
13712
 
13509
13713
  // src/NotifyRuleEditor.tsx
13510
13714
  import { useCallback as useCallback29, useEffect as useEffect35, useState as useState46 } from "react";
13511
- import { useRecordsClient as useRecordsClient32, useTable as useTable17 } from "@ai-matrx/records/react";
13715
+ import { useRecordsClient as useRecordsClient33, useTable as useTable17 } from "@ai-matrx/records/react";
13512
13716
  import { Button as Button41, Skeleton as Skeleton25, cn as cn42 } from "@ai-matrx/design-system";
13513
13717
  import { jsx as jsx48, jsxs as jsxs44 } from "react/jsx-runtime";
13514
13718
  var CADENCE_WORDS2 = SUBSCRIPTION_CADENCE_LABEL;
13515
13719
  var CHANNEL_WORDS3 = SUBSCRIPTION_CHANNEL_LABEL;
13516
13720
  function NotifyRuleEditor({ tableId, seed, className }) {
13517
- const client = useRecordsClient32();
13721
+ const client = useRecordsClient33();
13518
13722
  const host = useRecordsUi();
13519
13723
  const table = useTable17(tableId);
13520
13724
  const rights = useTableRights(table.data);
@@ -14028,7 +14232,7 @@ function Drawing({
14028
14232
 
14029
14233
  // src/DashboardCanvas.tsx
14030
14234
  import { useCallback as useCallback30, useEffect as useEffect36, useMemo as useMemo28, useState as useState47 } from "react";
14031
- import { useFields as useFields21, useRecordsClient as useRecordsClient33, useTable as useTable18 } from "@ai-matrx/records/react";
14235
+ import { useFields as useFields21, useRecordsClient as useRecordsClient34, useTable as useTable18 } from "@ai-matrx/records/react";
14032
14236
  import {
14033
14237
  BasicInput as BasicInput16,
14034
14238
  Button as Button42,
@@ -14042,7 +14246,7 @@ import {
14042
14246
  } from "@ai-matrx/design-system";
14043
14247
  import { Fragment as Fragment21, jsx as jsx50, jsxs as jsxs46 } from "react/jsx-runtime";
14044
14248
  function DashboardCanvas({ tableId, activeDashboardId, filter, className }) {
14045
- const client = useRecordsClient33();
14249
+ const client = useRecordsClient34();
14046
14250
  const host = useRecordsUi();
14047
14251
  const table = useTable18(tableId);
14048
14252
  const rights = useTableRights(table.data);
@@ -14371,7 +14575,7 @@ function GroupingPicker({
14371
14575
 
14372
14576
  // src/FormsPanel.tsx
14373
14577
  import { useCallback as useCallback31, useEffect as useEffect37, useState as useState48 } from "react";
14374
- import { useRecordsClient as useRecordsClient34, useTable as useTable19 } from "@ai-matrx/records/react";
14578
+ import { useRecordsClient as useRecordsClient35, useTable as useTable19 } from "@ai-matrx/records/react";
14375
14579
  import { publicFormPath as publicFormPath2 } from "@ai-matrx/records";
14376
14580
  import { Badge as Badge15, Button as Button43, Skeleton as Skeleton27, cn as cn45 } from "@ai-matrx/design-system";
14377
14581
  import { Fragment as Fragment22, jsx as jsx51, jsxs as jsxs47 } from "react/jsx-runtime";
@@ -14383,7 +14587,7 @@ function formSuggestion(tableName2) {
14383
14587
  return `Make me a form that collects new ${subject} entries and tells me when somebody answers.`;
14384
14588
  }
14385
14589
  function FormsPanel({ tableId, activeFormId, className }) {
14386
- const client = useRecordsClient34();
14590
+ const client = useRecordsClient35();
14387
14591
  const publishGate = usePublishGate();
14388
14592
  const host = useRecordsUi();
14389
14593
  const table = useTable19(tableId);
@@ -14540,7 +14744,7 @@ function FormsPanel({ tableId, activeFormId, className }) {
14540
14744
 
14541
14745
  // src/BookingBuilder.tsx
14542
14746
  import { useCallback as useCallback32, useEffect as useEffect38, useMemo as useMemo29, useState as useState49 } from "react";
14543
- import { useFields as useFields22, useRecordsClient as useRecordsClient35, useTable as useTable20 } from "@ai-matrx/records/react";
14747
+ import { useFields as useFields22, useRecordsClient as useRecordsClient36, useTable as useTable20 } from "@ai-matrx/records/react";
14544
14748
  import {
14545
14749
  bookingPath
14546
14750
  } from "@ai-matrx/records";
@@ -14570,7 +14774,7 @@ function draftWindows(availability) {
14570
14774
  });
14571
14775
  }
14572
14776
  function BookingBuilder({ tableId, bookingId, onSaved, onClose, className }) {
14573
- const client = useRecordsClient35();
14777
+ const client = useRecordsClient36();
14574
14778
  const publishGate = usePublishGate();
14575
14779
  const host = useRecordsUi();
14576
14780
  const table = useTable20(tableId);
@@ -14888,7 +15092,7 @@ function BookingBuilder({ tableId, bookingId, onSaved, onClose, className }) {
14888
15092
 
14889
15093
  // src/BookingSlots.tsx
14890
15094
  import { useCallback as useCallback33, useEffect as useEffect39, useMemo as useMemo30, useState as useState50 } from "react";
14891
- import { useRecordsClient as useRecordsClient36, useMyLevels as useMyLevels2 } from "@ai-matrx/records/react";
15095
+ import { useRecordsClient as useRecordsClient37, useMyLevels as useMyLevels2 } from "@ai-matrx/records/react";
14892
15096
  import { bookingPath as bookingPath2 } from "@ai-matrx/records";
14893
15097
  import { Badge as Badge16, Button as Button45, Skeleton as Skeleton29, cn as cn47 } from "@ai-matrx/design-system";
14894
15098
  import { Fragment as Fragment24, jsx as jsx53, jsxs as jsxs49 } from "react/jsx-runtime";
@@ -14900,7 +15104,7 @@ function bookingSuggestion(tableName2) {
14900
15104
  var NO_ADMIN2 = "Opening a booking page lets people with no account take time in your calendar, so it needs the admin level on the table the appointments land in.";
14901
15105
  var STATE_WORDS = BOOKING_PAGE_STATE_LABEL;
14902
15106
  function BookingSlots({ tableId, className }) {
14903
- const client = useRecordsClient36();
15107
+ const client = useRecordsClient37();
14904
15108
  const host = useRecordsUi();
14905
15109
  const [pages, setPages] = useState50(null);
14906
15110
  const [error, setError] = useState50(null);
@@ -15087,18 +15291,18 @@ function nextInWords(page) {
15087
15291
  }
15088
15292
 
15089
15293
  // src/CaptureSheet.tsx
15090
- import { useCallback as useCallback35, useEffect as useEffect41, useRef as useRef15, useState as useState52 } from "react";
15091
- import { useFields as useFields23, useRecordsClient as useRecordsClient38, useTable as useTable21 } from "@ai-matrx/records/react";
15294
+ import { useCallback as useCallback35, useEffect as useEffect41, useRef as useRef16, useState as useState52 } from "react";
15295
+ import { useFields as useFields23, useRecordsClient as useRecordsClient39, useTable as useTable21 } from "@ai-matrx/records/react";
15092
15296
  import { Button as Button47, Input as Input4, Skeleton as Skeleton31, Textarea as Textarea3, cn as cn49 } from "@ai-matrx/design-system";
15093
15297
 
15094
15298
  // src/CaptureRun.tsx
15095
- import { useCallback as useCallback34, useEffect as useEffect40, useMemo as useMemo31, useRef as useRef14, useState as useState51 } from "react";
15299
+ import { useCallback as useCallback34, useEffect as useEffect40, useMemo as useMemo31, useRef as useRef15, useState as useState51 } from "react";
15096
15300
  import {
15097
15301
  coerceTypedAnswer as coerceTypedAnswer2,
15098
15302
  coercionLabel,
15099
15303
  openCaptureQueue
15100
15304
  } from "@ai-matrx/records";
15101
- import { useRecordsClient as useRecordsClient37 } from "@ai-matrx/records/react";
15305
+ import { useRecordsClient as useRecordsClient38 } from "@ai-matrx/records/react";
15102
15306
  import { Button as Button46, Input as Input3, Skeleton as Skeleton30, Textarea as Textarea2, cn as cn48 } from "@ai-matrx/design-system";
15103
15307
  import { jsx as jsx54, jsxs as jsxs50 } from "react/jsx-runtime";
15104
15308
  function controlFor(field) {
@@ -15137,7 +15341,7 @@ function whereWeAre(timeoutMs = 4e3) {
15137
15341
  });
15138
15342
  }
15139
15343
  function CaptureRun({ sheetId, face: given, className }) {
15140
- const client = useRecordsClient37();
15344
+ const client = useRecordsClient38();
15141
15345
  const host = useRecordsUi();
15142
15346
  const [face, setFace] = useState51(given);
15143
15347
  const [loadFailed, setLoadFailed] = useState51(null);
@@ -15150,7 +15354,7 @@ function CaptureRun({ sheetId, face: given, className }) {
15150
15354
  const [items, setItems] = useState51([]);
15151
15355
  const [lastSynced, setLastSynced] = useState51(null);
15152
15356
  const [sending, setSending] = useState51(false);
15153
- const queueRef = useRef14(null);
15357
+ const queueRef = useRef15(null);
15154
15358
  useEffect40(() => {
15155
15359
  const q2 = openCaptureQueue({
15156
15360
  onChange: (c, all) => {
@@ -15517,7 +15721,7 @@ function AdHocCaptureSheet({
15517
15721
  attachmentField,
15518
15722
  className
15519
15723
  }) {
15520
- const client = useRecordsClient38();
15724
+ const client = useRecordsClient39();
15521
15725
  const host = useRecordsUi();
15522
15726
  const table = useTable21(tableId);
15523
15727
  const fields = useFields23(tableId);
@@ -15530,7 +15734,7 @@ function AdHocCaptureSheet({
15530
15734
  const [error, setError] = useState52(null);
15531
15735
  const [uploadError, setUploadError] = useState52(null);
15532
15736
  const [flushing, setFlushing] = useState52(false);
15533
- const queue = useRef15([]);
15737
+ const queue = useRef16([]);
15534
15738
  const keep = useCallback35(
15535
15739
  async (next) => {
15536
15740
  queue.current = next;
@@ -15719,11 +15923,11 @@ function AdHocCaptureSheet({
15719
15923
 
15720
15924
  // src/PortalShell.tsx
15721
15925
  import { useCallback as useCallback36, useEffect as useEffect42, useState as useState53 } from "react";
15722
- import { useRecordsClient as useRecordsClient39 } from "@ai-matrx/records/react";
15926
+ import { useRecordsClient as useRecordsClient40 } from "@ai-matrx/records/react";
15723
15927
  import { Button as Button48, Skeleton as Skeleton32, cn as cn50 } from "@ai-matrx/design-system";
15724
15928
  import { jsx as jsx56, jsxs as jsxs52 } from "react/jsx-runtime";
15725
15929
  function PortalShell({ tableId, form, resourceType = "record", className }) {
15726
- const client = useRecordsClient39();
15930
+ const client = useRecordsClient40();
15727
15931
  const [card, setCard] = useState53(null);
15728
15932
  const [reach, setReach] = useState53(null);
15729
15933
  const [error, setError] = useState53(null);
@@ -15802,7 +16006,7 @@ function PortalShell({ tableId, form, resourceType = "record", className }) {
15802
16006
  ] });
15803
16007
  }
15804
16008
  function PortalRow({ tableId, recordId }) {
15805
- const client = useRecordsClient39();
16009
+ const client = useRecordsClient40();
15806
16010
  const [title, setTitle] = useState53(null);
15807
16011
  useEffect42(() => {
15808
16012
  let cancelled = false;
@@ -15825,11 +16029,11 @@ function PortalRow({ tableId, recordId }) {
15825
16029
 
15826
16030
  // src/PublicViewPage.tsx
15827
16031
  import { useCallback as useCallback37, useEffect as useEffect43, useState as useState54 } from "react";
15828
- import { useRecordsClient as useRecordsClient40 } from "@ai-matrx/records/react";
16032
+ import { useRecordsClient as useRecordsClient41 } from "@ai-matrx/records/react";
15829
16033
  import { Skeleton as Skeleton33, cn as cn51 } from "@ai-matrx/design-system";
15830
16034
  import { jsx as jsx57, jsxs as jsxs53 } from "react/jsx-runtime";
15831
16035
  function PublicViewPage({ slug, className }) {
15832
- const client = useRecordsClient40();
16036
+ const client = useRecordsClient41();
15833
16037
  const [binding, setBinding] = useState54(null);
15834
16038
  const [rows, setRows] = useState54(null);
15835
16039
  const [fields, setFields] = useState54(null);
@@ -15906,7 +16110,7 @@ function PublicRow({ row, fields }) {
15906
16110
 
15907
16111
  // src/EmbedFrame.tsx
15908
16112
  import { useCallback as useCallback38, useEffect as useEffect44, useState as useState55 } from "react";
15909
- import { useRecordsClient as useRecordsClient41 } from "@ai-matrx/records/react";
16113
+ import { useRecordsClient as useRecordsClient42 } from "@ai-matrx/records/react";
15910
16114
  import { Button as Button49, Input as Input5, Skeleton as Skeleton34, Textarea as Textarea4, cn as cn52 } from "@ai-matrx/design-system";
15911
16115
  import { useTable as useTable22 } from "@ai-matrx/records/react";
15912
16116
  import { Fragment as Fragment26, jsx as jsx58, jsxs as jsxs54 } from "react/jsx-runtime";
@@ -15918,7 +16122,7 @@ function EmbedFrame({
15918
16122
  embedUrl,
15919
16123
  className
15920
16124
  }) {
15921
- const client = useRecordsClient41();
16125
+ const client = useRecordsClient42();
15922
16126
  const host = useRecordsUi();
15923
16127
  const table = useTable22(tableId);
15924
16128
  const rights = useTableRights(table.data);
@@ -16012,7 +16216,7 @@ function EmbedFrame({
16012
16216
  ] });
16013
16217
  }
16014
16218
  function useEmbedHandshake(args) {
16015
- const client = useRecordsClient41();
16219
+ const client = useRecordsClient42();
16016
16220
  const [binding, setBinding] = useState55(null);
16017
16221
  const [error, setError] = useState55(null);
16018
16222
  const [loading, setLoading] = useState55(true);
@@ -16074,7 +16278,7 @@ function recordsDataSource(client, fallbackSchema = "custom") {
16074
16278
 
16075
16279
  // src/TablesHome.tsx
16076
16280
  import { useCallback as useCallback39, useEffect as useEffect45, useState as useState56 } from "react";
16077
- import { useRecordsClient as useRecordsClient42, useTables as useTables4 } from "@ai-matrx/records/react";
16281
+ import { useRecordsClient as useRecordsClient43, useTables as useTables4 } from "@ai-matrx/records/react";
16078
16282
  import { BasicInput as BasicInput18, Button as Button50, Skeleton as Skeleton35, cn as cn53 } from "@ai-matrx/design-system";
16079
16283
 
16080
16284
  // src/createTable.ts
@@ -16202,15 +16406,32 @@ var LANE_EMPTY = {
16202
16406
  community: "Nothing shared beyond your organization. A table published by link or to the world lands here.",
16203
16407
  app: "The app has not needed a table of its own in this organization yet. Saving a view, writing a comment or building a form makes one, and it appears here rather than among your own data."
16204
16408
  };
16409
+ function keptByTheApp(table) {
16410
+ return table.kept_by_the_app === true;
16411
+ }
16205
16412
  function laneFor(table) {
16206
16413
  if (table.is_kernel) return "system";
16207
- if ((table.slug ?? "").startsWith(APP_TABLE_PREFIX)) return "app";
16414
+ if ((table.slug ?? "").startsWith(APP_TABLE_PREFIX) || keptByTheApp(table)) return "app";
16208
16415
  if (table.visibility === "public" || table.visibility === "link") return "community";
16209
16416
  if (table.visibility === "personal") return "mine";
16210
16417
  return "organization";
16211
16418
  }
16419
+ var VISIBILITY_LANES = ["mine", "organization", "community", "world"];
16420
+ var VISIBILITY_LANE_TITLE = {
16421
+ mine: "Mine",
16422
+ organization: "My organization",
16423
+ community: "Community",
16424
+ world: "World"
16425
+ };
16426
+ function visibilityLaneFor(table) {
16427
+ if (table.is_kernel) return null;
16428
+ if ((table.slug ?? "").startsWith(APP_TABLE_PREFIX) || keptByTheApp(table)) return null;
16429
+ if (table.visibility === "personal") return "mine";
16430
+ if (table.visibility === "link" || table.visibility === "public") return "world";
16431
+ return "organization";
16432
+ }
16212
16433
  function TablesHome({ onOpenTable, className }) {
16213
- const client = useRecordsClient42();
16434
+ const client = useRecordsClient43();
16214
16435
  const tables = useTables4();
16215
16436
  const [creating, setCreating] = useState56(false);
16216
16437
  const [name, setName] = useState56("");
@@ -16353,9 +16574,46 @@ function TablesHome({ onOpenTable, className }) {
16353
16574
 
16354
16575
  // src/TablePage.tsx
16355
16576
  import { useCallback as useCallback40, useEffect as useEffect46, useState as useState57 } from "react";
16356
- import { useFields as useFields24, useRecordsClient as useRecordsClient43, useTable as useTable23 } from "@ai-matrx/records/react";
16357
- import { Button as Button51, Separator as Separator12, Skeleton as Skeleton36, cn as cn54 } from "@ai-matrx/design-system";
16577
+ import { useFields as useFields24, useRecordsClient as useRecordsClient44, useTable as useTable23 } from "@ai-matrx/records/react";
16578
+ import { BottomSheet, Button as Button51, Separator as Separator12, Skeleton as Skeleton36, cn as cn54, useIsMobile } from "@ai-matrx/design-system";
16358
16579
  import { Fragment as Fragment28, jsx as jsx61, jsxs as jsxs56 } from "react/jsx-runtime";
16580
+ var RAIL_TITLE = {
16581
+ settings: "Table settings",
16582
+ inbox: "Inbox",
16583
+ import: "Import",
16584
+ field: "Add a field",
16585
+ record: "Record",
16586
+ "new-record": "New record",
16587
+ forms: "Forms",
16588
+ bookings: "Bookings",
16589
+ checklists: "Checklists",
16590
+ notifications: "Notifications",
16591
+ portals: "Portals",
16592
+ "who-changed": "Who changed this"
16593
+ };
16594
+ function RailFrame({
16595
+ phone,
16596
+ title,
16597
+ onClose,
16598
+ children
16599
+ }) {
16600
+ if (phone) {
16601
+ return /* @__PURE__ */ jsx61(
16602
+ BottomSheet,
16603
+ {
16604
+ open: true,
16605
+ onOpenChange: (open) => {
16606
+ if (!open) onClose();
16607
+ },
16608
+ title,
16609
+ size: "full",
16610
+ surface: "solid",
16611
+ children: /* @__PURE__ */ jsx61("div", { "data-rail-sheet": "", className: "min-h-0 flex-1 overflow-y-auto px-3 pb-6", children })
16612
+ }
16613
+ );
16614
+ }
16615
+ return /* @__PURE__ */ jsx61("aside", { "data-rail-column": "", className: "w-[26rem] shrink-0 overflow-y-auto rounded-md border p-3", children });
16616
+ }
16359
16617
  function filterInWords(filter, fields) {
16360
16618
  const nameOf = (key) => {
16361
16619
  const field = fields.find((f) => f.key === key);
@@ -16471,12 +16729,12 @@ function TablePage({
16471
16729
  activeItemId,
16472
16730
  className
16473
16731
  }) {
16474
- const client = useRecordsClient43();
16732
+ const client = useRecordsClient44();
16475
16733
  const table = useTable23(tableId);
16476
16734
  const rights = useTableRights(table.data);
16477
16735
  const pageFields = useFields24(filter ? tableId : null);
16478
16736
  const askedInWords = filter ? filterInWords(filter, pageFields.data ?? []) : null;
16479
- const organizationId = useRecordsClient43().config.organizationId;
16737
+ const organizationId = useRecordsClient44().config.organizationId;
16480
16738
  const [view, setView] = useState57(null);
16481
16739
  const opening = openingRail(activeRecordId, activeRail);
16482
16740
  const opened = openingView(activeView, activeDashboardId);
@@ -16495,6 +16753,7 @@ function TablePage({
16495
16753
  const setRail = (next) => setSurface((now) => ({ ...now, rail: next }));
16496
16754
  const [openRecord, setOpenRecord] = useState57(opening.record);
16497
16755
  const viewVersion = useRecordVersion(view?.id ?? null);
16756
+ const phone = useIsMobile();
16498
16757
  const shownLayout = layoutFromLink ?? view?.layout ?? "grid";
16499
16758
  const press = (pressed) => {
16500
16759
  const next = chooseSurface(surface, pressed);
@@ -16522,6 +16781,7 @@ function TablePage({
16522
16781
  setLayoutFromLink(isLayout ? named : null);
16523
16782
  setSurface((now) => ({ ...now, main: isLayout ? "records" : named }));
16524
16783
  }, [activeView]);
16784
+ const [viewSaveRefused, setViewSaveRefused] = useState57(null);
16525
16785
  const patchView = useCallback40(
16526
16786
  async (patch) => {
16527
16787
  const current = view;
@@ -16529,12 +16789,13 @@ function TablePage({
16529
16789
  setView({ ...current, ...patch });
16530
16790
  const document2 = viewPatchDocument(patch);
16531
16791
  if (Object.keys(document2).length === 0) return;
16532
- const written = await client.recordUpdate({
16533
- record_id: current.id,
16534
- patch: document2,
16535
- ...viewVersion.version === null ? {} : { expectedVersion: viewVersion.version }
16536
- });
16537
- if (written.ok) viewVersion.note(written.data);
16792
+ setViewSaveRefused(null);
16793
+ const written = await saveViewPatch(client, current.id, document2, viewVersion.version);
16794
+ if (written.ok) {
16795
+ viewVersion.note(written.data);
16796
+ return;
16797
+ }
16798
+ setViewSaveRefused({ error: written.error, patch, before: current });
16538
16799
  },
16539
16800
  [client, view, viewVersion]
16540
16801
  );
@@ -16685,6 +16946,27 @@ function TablePage({
16685
16946
  ),
16686
16947
  /* @__PURE__ */ jsx61(ExportMenu, { tableId })
16687
16948
  ] }),
16949
+ viewSaveRefused ? /* @__PURE__ */ jsx61(
16950
+ RefusalNotice,
16951
+ {
16952
+ error: viewSaveRefused.error,
16953
+ actions: /* @__PURE__ */ jsxs56("span", { className: "flex flex-wrap gap-1 pt-0.5", "data-view-save-refused": "", children: [
16954
+ /* @__PURE__ */ jsx61(Button51, { size: "sm", variant: "outline", onClick: () => void patchView(viewSaveRefused.patch), children: "Try again" }),
16955
+ /* @__PURE__ */ jsx61(
16956
+ Button51,
16957
+ {
16958
+ size: "sm",
16959
+ variant: "ghost",
16960
+ onClick: () => {
16961
+ setView(viewSaveRefused.before);
16962
+ setViewSaveRefused(null);
16963
+ },
16964
+ children: "Discard"
16965
+ }
16966
+ )
16967
+ ] })
16968
+ }
16969
+ ) : null,
16688
16970
  main === "dashboards" ? /* @__PURE__ */ jsx61(DashboardCanvas, { tableId, activeDashboardId: activeDashboardId ?? null }) : main === "archived" ? (
16689
16971
  /* THE ROWS THAT LEFT. Restoring one puts it back in the grid, so the
16690
16972
  page returns to the records rather than leaving the person looking
@@ -16745,7 +17027,7 @@ function TablePage({
16745
17027
  view ? null : /* @__PURE__ */ jsx61("p", { className: "text-xs text-muted-foreground", children: VIEW_NOT_SAVED_YET })
16746
17028
  ] })
16747
17029
  ] }),
16748
- rail === "none" ? null : /* @__PURE__ */ jsxs56("aside", { className: "w-[26rem] shrink-0 overflow-y-auto rounded-md border p-3", children: [
17030
+ rail === "none" ? null : /* @__PURE__ */ jsxs56(RailFrame, { phone, title: RAIL_TITLE[rail], onClose: () => setRail("none"), children: [
16749
17031
  rail === "settings" ? /* @__PURE__ */ jsx61(
16750
17032
  TableSettings,
16751
17033
  {
@@ -16984,6 +17266,8 @@ export {
16984
17266
  VIEW_LAYOUTS,
16985
17267
  VIEW_NOT_SAVED_YET,
16986
17268
  VIEW_TABLE,
17269
+ VISIBILITY_LANES,
17270
+ VISIBILITY_LANE_TITLE,
16987
17271
  ViewBar,
16988
17272
  ViewSwitcher,
16989
17273
  WITHHELD_RECORD_LABEL,
@@ -17039,6 +17323,7 @@ export {
17039
17323
  isPlainFieldType,
17040
17324
  isRelationFieldType,
17041
17325
  isSignatureField,
17326
+ keptByTheApp,
17042
17327
  keyFor,
17043
17328
  kindsWithNoChoice,
17044
17329
  laneFor,
@@ -17074,6 +17359,7 @@ export {
17074
17359
  revokeConsequence,
17075
17360
  rowName,
17076
17361
  rowNameIn,
17362
+ saveViewPatch,
17077
17363
  scalarText,
17078
17364
  shareUnavailableReason,
17079
17365
  specFromBlock,
@@ -17103,6 +17389,7 @@ export {
17103
17389
  viewDocument,
17104
17390
  viewFromRecord,
17105
17391
  viewPatchDocument,
17392
+ visibilityLaneFor,
17106
17393
  whatIsMissing,
17107
17394
  whatYouMayDo,
17108
17395
  whatYouMayDoWithTable,