@ai-matrx/records-ui 0.95.3 → 0.95.10

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/pickers.cjs CHANGED
@@ -46,6 +46,40 @@ var import_react30 = require("react");
46
46
  var import_react31 = require("@ai-matrx/records/react");
47
47
  var import_design_system16 = require("@ai-matrx/design-system");
48
48
 
49
+ // src/askOnce.ts
50
+ var asked = /* @__PURE__ */ new WeakMap();
51
+ function askOnce(client, key, ask, freshMs) {
52
+ let held = asked.get(client);
53
+ if (!held) {
54
+ held = /* @__PURE__ */ new Map();
55
+ asked.set(client, held);
56
+ }
57
+ const now = Date.now();
58
+ const kept = held.get(key);
59
+ if (kept && now - kept.at <= freshMs) return kept.answer;
60
+ const answer = ask().then(
61
+ (result) => {
62
+ if (!result.ok) held.delete(key);
63
+ return result;
64
+ },
65
+ (thrown) => {
66
+ held.delete(key);
67
+ throw thrown;
68
+ }
69
+ );
70
+ held.set(key, { at: now, answer });
71
+ return answer;
72
+ }
73
+ function forgetAsked(client, prefix) {
74
+ const held = asked.get(client);
75
+ if (!held) return;
76
+ for (const key of [...held.keys()]) if (key.startsWith(prefix)) held.delete(key);
77
+ }
78
+ var FIELD_OPTIONS_FRESH_MS = 2e3;
79
+ function fieldOptionsOnce(client, fieldId) {
80
+ return askOnce(client, `field_options:${fieldId}`, () => client.fieldOptions({ field_id: fieldId }), FIELD_OPTIONS_FRESH_MS);
81
+ }
82
+
49
83
  // src/editors.tsx
50
84
  var import_react24 = require("react");
51
85
  var import_records10 = require("@ai-matrx/records");
@@ -717,7 +751,12 @@ var REMEDY = {
717
751
  unreachable: "Nothing was changed. Check your connection and open this again.",
718
752
  needs_approval: "Nothing was changed yet. The people who can approve it have been asked.",
719
753
  stale_read: "Open this again in a moment and it will be up to date.",
720
- timed_out: "Nothing was changed. Try a smaller page, or try again in a moment.",
754
+ // 🚨 ONE REMEDY THAT IS TRUE OF EVERY DOOR (lane CHAIR-STORE-PERF, 2026-10-03). The store cancels the
755
+ // WHOLE statement at its clock, so a timed-out door wrote nothing — a create, a column add and a page
756
+ // read alike. "Try a smaller page" was written for the page read and was printed to a person adding
757
+ // one row to a three-row table (clone walk, 2026-10-03; the clock was a lock another lane's
758
+ // rehearsal held, not the row). A door whose page can shrink says so in its own hint, which wins here.
759
+ timed_out: "Nothing was changed. Try again in a moment.",
721
760
  internal: "Nothing you did caused this. Try again, and tell us if it keeps happening."
722
761
  };
723
762
  var ENGINEER_TEXT = [
@@ -961,7 +1000,7 @@ var import_jsx_runtime4 = (
961
1000
  require("react/jsx-runtime")
962
1001
  );
963
1002
  var NO_STORE_ON_THIS_SCREEN_REASON = "This question points at a record in somebody's own data, and a page answered without an account is never allowed to search that. Leave it blank \u2014 whoever receives your answer will fill it in.";
964
- function RelationPicker({ field, value, onChange, disabled, id, className, allowCreate = true }) {
1003
+ function RelationPicker({ field, value, onChange, disabled, id, className, allowCreate = true, onPicked }) {
965
1004
  const client = (0, import_react4.useOptionalRecordsClient)();
966
1005
  const [rows, setRows] = (0, import_react3.useState)(null);
967
1006
  const [words, setWords] = (0, import_react3.useState)({});
@@ -1049,8 +1088,10 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1049
1088
  const full = field.multi && field.relation_max !== null && picked.length >= field.relation_max;
1050
1089
  const toggle = (recordId) => {
1051
1090
  if (!field.multi) {
1052
- onChange(picked[0] === recordId ? null : recordId);
1091
+ const choosing = picked[0] !== recordId;
1092
+ onChange(choosing ? recordId : null);
1053
1093
  setOpen(false);
1094
+ if (choosing && onPicked) setTimeout(onPicked, 0);
1054
1095
  return;
1055
1096
  }
1056
1097
  const next = new Set(picked);
@@ -1326,31 +1367,31 @@ var FILES = /* @__PURE__ */ new WeakMap();
1326
1367
  function signatureFilesOf(client, recordId) {
1327
1368
  let held = SOURCES.get(client);
1328
1369
  if (!held) SOURCES.set(client, held = /* @__PURE__ */ new Map());
1329
- let asked2 = held.get(recordId);
1330
- if (!asked2) {
1331
- asked2 = client.recordValuesVersioned({ record_id: recordId }).then((answered) => {
1370
+ let asked3 = held.get(recordId);
1371
+ if (!asked3) {
1372
+ asked3 = client.recordValuesVersioned({ record_id: recordId }).then((answered) => {
1332
1373
  if (!answered.ok) return {};
1333
1374
  return Object.fromEntries(answered.data.map((v) => [v.field_key, signatureFileOf(v.source)]));
1334
1375
  });
1335
- held.set(recordId, asked2);
1376
+ held.set(recordId, asked3);
1336
1377
  }
1337
- return asked2;
1378
+ return asked3;
1338
1379
  }
1339
1380
  function drawingOf(client, fileId) {
1340
1381
  let held = FILES.get(client);
1341
1382
  if (!held) FILES.set(client, held = /* @__PURE__ */ new Map());
1342
- let asked2 = held.get(fileId);
1343
- if (!asked2) {
1344
- asked2 = client.recordRead({ record_id: fileId }).then((answered) => {
1383
+ let asked3 = held.get(fileId);
1384
+ if (!asked3) {
1385
+ asked3 = client.recordRead({ record_id: fileId }).then((answered) => {
1345
1386
  if (!answered.ok) return { src: null, hostFileId: null };
1346
1387
  const doc = answered.data.document ?? {};
1347
1388
  const content = drawnSignatureOf(doc["content"]);
1348
1389
  const hostFileId = typeof doc["file_id"] === "string" && doc["file_id"] !== "" ? doc["file_id"] : null;
1349
1390
  return { src: content, hostFileId };
1350
1391
  });
1351
- held.set(fileId, asked2);
1392
+ held.set(fileId, asked3);
1352
1393
  }
1353
- return asked2;
1394
+ return asked3;
1354
1395
  }
1355
1396
  function DrawnImage({ src, inCell }) {
1356
1397
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
@@ -1597,7 +1638,7 @@ function RecordLabelProvider({ children }) {
1597
1638
  const [archivedRefs, setArchivedRefs] = (0, import_react9.useState)({});
1598
1639
  const [settled, setSettled] = (0, import_react9.useState)({});
1599
1640
  const [waitingLines, setWaitingLines] = (0, import_react9.useState)({});
1600
- const asked2 = (0, import_react9.useRef)(/* @__PURE__ */ new Set());
1641
+ const asked3 = (0, import_react9.useRef)(/* @__PURE__ */ new Set());
1601
1642
  const chased = (0, import_react9.useRef)(/* @__PURE__ */ new Set());
1602
1643
  const timers = (0, import_react9.useRef)(/* @__PURE__ */ new Map());
1603
1644
  const startWaitClock = (0, import_react9.useCallback)((field, target) => {
@@ -1635,12 +1676,12 @@ function RecordLabelProvider({ children }) {
1635
1676
  const want = (0, import_react9.useCallback)(
1636
1677
  (field) => {
1637
1678
  if (!pointsAtRecords(field)) return;
1638
- if (asked2.current.has(field.id)) return;
1639
- asked2.current.add(field.id);
1679
+ if (asked3.current.has(field.id)) return;
1680
+ asked3.current.add(field.id);
1640
1681
  startWaitClock(field, null);
1641
1682
  void (async () => {
1642
1683
  if (field.type === "list") {
1643
- const result = await client.fieldOptions({ field_id: field.id });
1684
+ const result = await fieldOptionsOnce(client, field.id);
1644
1685
  if (result.ok) {
1645
1686
  setNames((current) => {
1646
1687
  const next = { ...current };
@@ -1724,7 +1765,7 @@ function RecordLabelProvider({ children }) {
1724
1765
  const once = choiceKeyName(field.id, id);
1725
1766
  if (settled[field.id] && !rereadFor.current.has(once)) {
1726
1767
  rereadFor.current.add(once);
1727
- asked2.current.delete(field.id);
1768
+ asked3.current.delete(field.id);
1728
1769
  want(field);
1729
1770
  }
1730
1771
  return null;
@@ -2954,15 +2995,15 @@ function TypedDateHelper({ kind, text, onText, label }) {
2954
2995
  // src/choiceNudge.ts
2955
2996
  var import_react22 = require("react");
2956
2997
  var import_react23 = require("@ai-matrx/records/react");
2957
- var asked = /* @__PURE__ */ new WeakMap();
2998
+ var asked2 = /* @__PURE__ */ new WeakMap();
2958
2999
  function choiceNudgeOf(client) {
2959
- let answer = asked.get(client);
3000
+ let answer = asked2.get(client);
2960
3001
  if (!answer) {
2961
3002
  answer = Promise.resolve().then(() => client.choiceNudge()).then(
2962
3003
  (result) => result.ok ? result.data : "ask",
2963
3004
  () => "ask"
2964
3005
  );
2965
- asked.set(client, answer);
3006
+ asked2.set(client, answer);
2966
3007
  }
2967
3008
  return answer;
2968
3009
  }
@@ -3110,7 +3151,7 @@ function TypedTextControl({ field, value, onChange, disabled, id, onCommit, onCa
3110
3151
  (0, import_react24.useEffect)(() => {
3111
3152
  if (!needsChoices || !client) return;
3112
3153
  let cancelled = false;
3113
- void client.fieldOptions({ field_id: field.id }).then((result) => {
3154
+ void fieldOptionsOnce(client, field.id).then((result) => {
3114
3155
  if (!cancelled) setChoices(result.ok ? inTheStoresOrder(result.data ?? []) : []);
3115
3156
  });
3116
3157
  return () => {
@@ -3274,7 +3315,7 @@ function FieldControl({ field, value, onChange, disabled, id, autoOpen, onClose,
3274
3315
  }
3275
3316
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(RelationPicker, { field, value, onChange, disabled, id: inputId });
3276
3317
  case "relation":
3277
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(RelationPicker, { field, value, onChange, disabled, id: inputId });
3318
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(RelationPicker, { field, value, onChange, disabled, id: inputId, onPicked: onCommit });
3278
3319
  default:
3279
3320
  if (typeof value === "boolean" || field.config?.["kind"] === "boolean") {
3280
3321
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
@@ -3444,7 +3485,7 @@ function OptionControl({ field, value, onChange, disabled, id, autoOpen, onClose
3444
3485
  (0, import_react24.useEffect)(() => {
3445
3486
  if (!client) return;
3446
3487
  let cancelled = false;
3447
- void client.fieldOptions({ field_id: field.id }).then((result) => {
3488
+ void fieldOptionsOnce(client, field.id).then((result) => {
3448
3489
  if (cancelled) return;
3449
3490
  if (result.ok) setOptions(inTheStoresOrder(result.data ?? []));
3450
3491
  else setRefusal(result.error);
@@ -3507,7 +3548,10 @@ function OptionControl({ field, value, onChange, disabled, id, autoOpen, onClose
3507
3548
  initialSearch,
3508
3549
  addsChoices,
3509
3550
  allowOther: (0, import_records10.allowsOtherValues)(field),
3510
- onChoicesChanged: () => setReread((n) => n + 1)
3551
+ onChoicesChanged: () => {
3552
+ if (client) forgetAsked(client, `field_options:${field.id}`);
3553
+ setReread((n) => n + 1);
3554
+ }
3511
3555
  }
3512
3556
  );
3513
3557
  }
@@ -3933,20 +3977,40 @@ function TablePicker({ dataSource, value, onChange, disabled, tables }) {
3933
3977
  const options = tablePickerEntries(
3934
3978
  list.rows.map((t) => ({ ...t, id: t.table_id, name: t.table_name })),
3935
3979
  { keep: value }
3936
- ).entries.map(({ table: t }) => ({
3937
- value: t.table_id,
3938
- // The organization hint already tells two same-named tables apart across organizations.
3939
- label: t.table_name,
3940
- hint: t.organization_name,
3941
- keywords: t.organization_name
3942
- }));
3980
+ ).entries.map(({ table: t }) => t);
3981
+ const sameName = /* @__PURE__ */ new Map();
3982
+ for (const t of options) {
3983
+ const key = `${t.organization_id}|${t.table_name.trim().toLowerCase()}`;
3984
+ sameName.set(key, (sameName.get(key) ?? 0) + 1);
3985
+ }
3986
+ const pickerOptions = options.map((t) => {
3987
+ const twin = (sameName.get(`${t.organization_id}|${t.table_name.trim().toLowerCase()}`) ?? 0) > 1;
3988
+ const row = t;
3989
+ const second = [
3990
+ row.created_by_name ? `by ${row.created_by_name}` : null,
3991
+ row.updated_at ? `changed ${new Date(row.updated_at).toLocaleDateString()}` : null
3992
+ ].filter(Boolean).join(" \xB7 ");
3993
+ return {
3994
+ value: t.table_id,
3995
+ // The organization hint already tells two same-named tables apart across organizations.
3996
+ label: t.table_name,
3997
+ hint: t.organization_name,
3998
+ keywords: t.organization_name,
3999
+ ...twin && second ? {
4000
+ render: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("span", { className: "flex min-w-0 flex-col", children: [
4001
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "truncate", children: t.table_name }),
4002
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "truncate text-[11px] text-muted-foreground", children: second })
4003
+ ] })
4004
+ } : {}
4005
+ };
4006
+ });
3943
4007
  const missing = Boolean(value) && !list.loading && !list.error && !list.rows.some((t) => t.table_id === value);
3944
4008
  return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex flex-col gap-1", children: [
3945
4009
  /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
3946
4010
  import_design_system16.CreatablePicker,
3947
4011
  {
3948
4012
  value: value || null,
3949
- options,
4013
+ options: pickerOptions,
3950
4014
  onSelect: onChange,
3951
4015
  placeholder: missing ? "Table not available" : "Choose a table",
3952
4016
  searchPlaceholder: "Search tables",
@@ -4468,7 +4532,7 @@ function referencesOf(field, value, kind, labels) {
4468
4532
  }));
4469
4533
  }
4470
4534
  var ARCHIVED_REFERENCE_WORD = "Archived";
4471
- var ARCHIVED_REFERENCE_TIP = "In Trash; restore it to use it again";
4535
+ var ARCHIVED_REFERENCE_TIP = "In the Archived view; restore it to use it again";
4472
4536
  function ReferenceChip({ reference, waiting }) {
4473
4537
  const host = useRecordsUi();
4474
4538
  const words = reference.label ?? (reference.token === "record" ? waiting ?? "Loading\u2026" : WITHHELD_ENTITY_LABEL);