@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.
@@ -10,6 +10,10 @@ interface TableAnywhere {
10
10
  organization_name: string;
11
11
  kind: string | null;
12
12
  kept_by_the_app: boolean;
13
+ /** When the table last changed (the door's own column) — what tells two same-named tables apart. */
14
+ updated_at?: string | null;
15
+ /** Who made it, when the door says. */
16
+ created_by_name?: string | null;
13
17
  }
14
18
  interface TablesAnywhere {
15
19
  loading: boolean;
package/dist/pickers.d.ts CHANGED
@@ -10,6 +10,10 @@ interface TableAnywhere {
10
10
  organization_name: string;
11
11
  kind: string | null;
12
12
  kept_by_the_app: boolean;
13
+ /** When the table last changed (the door's own column) — what tells two same-named tables apart. */
14
+ updated_at?: string | null;
15
+ /** Who made it, when the door says. */
16
+ created_by_name?: string | null;
13
17
  }
14
18
  interface TablesAnywhere {
15
19
  loading: boolean;
package/dist/pickers.js CHANGED
@@ -5,6 +5,40 @@ import { useEffect as useEffect12, useMemo as useMemo7, useState as useState16 }
5
5
  import { RecordsProvider as RecordsProvider2, useFields, useRecords, useTable } from "@ai-matrx/records/react";
6
6
  import { Button as Button9, CreatablePicker } from "@ai-matrx/design-system";
7
7
 
8
+ // src/askOnce.ts
9
+ var asked = /* @__PURE__ */ new WeakMap();
10
+ function askOnce(client, key, ask, freshMs) {
11
+ let held = asked.get(client);
12
+ if (!held) {
13
+ held = /* @__PURE__ */ new Map();
14
+ asked.set(client, held);
15
+ }
16
+ const now = Date.now();
17
+ const kept = held.get(key);
18
+ if (kept && now - kept.at <= freshMs) return kept.answer;
19
+ const answer = ask().then(
20
+ (result) => {
21
+ if (!result.ok) held.delete(key);
22
+ return result;
23
+ },
24
+ (thrown) => {
25
+ held.delete(key);
26
+ throw thrown;
27
+ }
28
+ );
29
+ held.set(key, { at: now, answer });
30
+ return answer;
31
+ }
32
+ function forgetAsked(client, prefix) {
33
+ const held = asked.get(client);
34
+ if (!held) return;
35
+ for (const key of [...held.keys()]) if (key.startsWith(prefix)) held.delete(key);
36
+ }
37
+ var FIELD_OPTIONS_FRESH_MS = 2e3;
38
+ function fieldOptionsOnce(client, fieldId) {
39
+ return askOnce(client, `field_options:${fieldId}`, () => client.fieldOptions({ field_id: fieldId }), FIELD_OPTIONS_FRESH_MS);
40
+ }
41
+
8
42
  // src/editors.tsx
9
43
  import { useCallback as useCallback4, useEffect as useEffect10, useRef as useRef9, useState as useState14 } from "react";
10
44
  import { allowsOtherValues, choiceValuesOf, coerceTypedAnswer, newerKindOf as newerKindOf4, decideTypedChoice, isTheChosen, optionKey as optionKey2, rankTypedMatches as rankTypedMatches3, readTypedTime as readTypedTime2, typedDateText as typedDateText2, typedTimeText as typedTimeText2 } from "@ai-matrx/records";
@@ -715,7 +749,12 @@ var REMEDY = {
715
749
  unreachable: "Nothing was changed. Check your connection and open this again.",
716
750
  needs_approval: "Nothing was changed yet. The people who can approve it have been asked.",
717
751
  stale_read: "Open this again in a moment and it will be up to date.",
718
- timed_out: "Nothing was changed. Try a smaller page, or try again in a moment.",
752
+ // 🚨 ONE REMEDY THAT IS TRUE OF EVERY DOOR (lane CHAIR-STORE-PERF, 2026-10-03). The store cancels the
753
+ // WHOLE statement at its clock, so a timed-out door wrote nothing — a create, a column add and a page
754
+ // read alike. "Try a smaller page" was written for the page read and was printed to a person adding
755
+ // one row to a three-row table (clone walk, 2026-10-03; the clock was a lock another lane's
756
+ // rehearsal held, not the row). A door whose page can shrink says so in its own hint, which wins here.
757
+ timed_out: "Nothing was changed. Try again in a moment.",
719
758
  internal: "Nothing you did caused this. Try again, and tell us if it keeps happening."
720
759
  };
721
760
  var ENGINEER_TEXT = [
@@ -955,7 +994,7 @@ function tellApart(rows, nameOf) {
955
994
  // src/RelationPicker.tsx
956
995
  import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
957
996
  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.";
958
- function RelationPicker({ field, value, onChange, disabled, id, className, allowCreate = true }) {
997
+ function RelationPicker({ field, value, onChange, disabled, id, className, allowCreate = true, onPicked }) {
959
998
  const client = useOptionalRecordsClient();
960
999
  const [rows, setRows] = useState3(null);
961
1000
  const [words, setWords] = useState3({});
@@ -1047,8 +1086,10 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1047
1086
  const full = field.multi && field.relation_max !== null && picked.length >= field.relation_max;
1048
1087
  const toggle = (recordId) => {
1049
1088
  if (!field.multi) {
1050
- onChange(picked[0] === recordId ? null : recordId);
1089
+ const choosing = picked[0] !== recordId;
1090
+ onChange(choosing ? recordId : null);
1051
1091
  setOpen(false);
1092
+ if (choosing && onPicked) setTimeout(onPicked, 0);
1052
1093
  return;
1053
1094
  }
1054
1095
  const next = new Set(picked);
@@ -1324,31 +1365,31 @@ var FILES = /* @__PURE__ */ new WeakMap();
1324
1365
  function signatureFilesOf(client, recordId) {
1325
1366
  let held = SOURCES.get(client);
1326
1367
  if (!held) SOURCES.set(client, held = /* @__PURE__ */ new Map());
1327
- let asked2 = held.get(recordId);
1328
- if (!asked2) {
1329
- asked2 = client.recordValuesVersioned({ record_id: recordId }).then((answered) => {
1368
+ let asked3 = held.get(recordId);
1369
+ if (!asked3) {
1370
+ asked3 = client.recordValuesVersioned({ record_id: recordId }).then((answered) => {
1330
1371
  if (!answered.ok) return {};
1331
1372
  return Object.fromEntries(answered.data.map((v) => [v.field_key, signatureFileOf(v.source)]));
1332
1373
  });
1333
- held.set(recordId, asked2);
1374
+ held.set(recordId, asked3);
1334
1375
  }
1335
- return asked2;
1376
+ return asked3;
1336
1377
  }
1337
1378
  function drawingOf(client, fileId) {
1338
1379
  let held = FILES.get(client);
1339
1380
  if (!held) FILES.set(client, held = /* @__PURE__ */ new Map());
1340
- let asked2 = held.get(fileId);
1341
- if (!asked2) {
1342
- asked2 = client.recordRead({ record_id: fileId }).then((answered) => {
1381
+ let asked3 = held.get(fileId);
1382
+ if (!asked3) {
1383
+ asked3 = client.recordRead({ record_id: fileId }).then((answered) => {
1343
1384
  if (!answered.ok) return { src: null, hostFileId: null };
1344
1385
  const doc = answered.data.document ?? {};
1345
1386
  const content = drawnSignatureOf(doc["content"]);
1346
1387
  const hostFileId = typeof doc["file_id"] === "string" && doc["file_id"] !== "" ? doc["file_id"] : null;
1347
1388
  return { src: content, hostFileId };
1348
1389
  });
1349
- held.set(fileId, asked2);
1390
+ held.set(fileId, asked3);
1350
1391
  }
1351
- return asked2;
1392
+ return asked3;
1352
1393
  }
1353
1394
  function DrawnImage({ src, inCell }) {
1354
1395
  return /* @__PURE__ */ jsx6(
@@ -1595,7 +1636,7 @@ function RecordLabelProvider({ children }) {
1595
1636
  const [archivedRefs, setArchivedRefs] = useState5({});
1596
1637
  const [settled, setSettled] = useState5({});
1597
1638
  const [waitingLines, setWaitingLines] = useState5({});
1598
- const asked2 = useRef3(/* @__PURE__ */ new Set());
1639
+ const asked3 = useRef3(/* @__PURE__ */ new Set());
1599
1640
  const chased = useRef3(/* @__PURE__ */ new Set());
1600
1641
  const timers = useRef3(/* @__PURE__ */ new Map());
1601
1642
  const startWaitClock = useCallback((field, target) => {
@@ -1633,12 +1674,12 @@ function RecordLabelProvider({ children }) {
1633
1674
  const want = useCallback(
1634
1675
  (field) => {
1635
1676
  if (!pointsAtRecords(field)) return;
1636
- if (asked2.current.has(field.id)) return;
1637
- asked2.current.add(field.id);
1677
+ if (asked3.current.has(field.id)) return;
1678
+ asked3.current.add(field.id);
1638
1679
  startWaitClock(field, null);
1639
1680
  void (async () => {
1640
1681
  if (field.type === "list") {
1641
- const result = await client.fieldOptions({ field_id: field.id });
1682
+ const result = await fieldOptionsOnce(client, field.id);
1642
1683
  if (result.ok) {
1643
1684
  setNames((current) => {
1644
1685
  const next = { ...current };
@@ -1722,7 +1763,7 @@ function RecordLabelProvider({ children }) {
1722
1763
  const once = choiceKeyName(field.id, id);
1723
1764
  if (settled[field.id] && !rereadFor.current.has(once)) {
1724
1765
  rereadFor.current.add(once);
1725
- asked2.current.delete(field.id);
1766
+ asked3.current.delete(field.id);
1726
1767
  want(field);
1727
1768
  }
1728
1769
  return null;
@@ -2964,15 +3005,15 @@ function TypedDateHelper({ kind, text, onText, label }) {
2964
3005
  // src/choiceNudge.ts
2965
3006
  import { useEffect as useEffect9, useState as useState13 } from "react";
2966
3007
  import { useOptionalRecordsClient as useOptionalRecordsClient6 } from "@ai-matrx/records/react";
2967
- var asked = /* @__PURE__ */ new WeakMap();
3008
+ var asked2 = /* @__PURE__ */ new WeakMap();
2968
3009
  function choiceNudgeOf(client) {
2969
- let answer = asked.get(client);
3010
+ let answer = asked2.get(client);
2970
3011
  if (!answer) {
2971
3012
  answer = Promise.resolve().then(() => client.choiceNudge()).then(
2972
3013
  (result) => result.ok ? result.data : "ask",
2973
3014
  () => "ask"
2974
3015
  );
2975
- asked.set(client, answer);
3016
+ asked2.set(client, answer);
2976
3017
  }
2977
3018
  return answer;
2978
3019
  }
@@ -3120,7 +3161,7 @@ function TypedTextControl({ field, value, onChange, disabled, id, onCommit, onCa
3120
3161
  useEffect10(() => {
3121
3162
  if (!needsChoices || !client) return;
3122
3163
  let cancelled = false;
3123
- void client.fieldOptions({ field_id: field.id }).then((result) => {
3164
+ void fieldOptionsOnce(client, field.id).then((result) => {
3124
3165
  if (!cancelled) setChoices(result.ok ? inTheStoresOrder(result.data ?? []) : []);
3125
3166
  });
3126
3167
  return () => {
@@ -3284,7 +3325,7 @@ function FieldControl({ field, value, onChange, disabled, id, autoOpen, onClose,
3284
3325
  }
3285
3326
  return /* @__PURE__ */ jsx16(RelationPicker, { field, value, onChange, disabled, id: inputId });
3286
3327
  case "relation":
3287
- return /* @__PURE__ */ jsx16(RelationPicker, { field, value, onChange, disabled, id: inputId });
3328
+ return /* @__PURE__ */ jsx16(RelationPicker, { field, value, onChange, disabled, id: inputId, onPicked: onCommit });
3288
3329
  default:
3289
3330
  if (typeof value === "boolean" || field.config?.["kind"] === "boolean") {
3290
3331
  return /* @__PURE__ */ jsx16(
@@ -3454,7 +3495,7 @@ function OptionControl({ field, value, onChange, disabled, id, autoOpen, onClose
3454
3495
  useEffect10(() => {
3455
3496
  if (!client) return;
3456
3497
  let cancelled = false;
3457
- void client.fieldOptions({ field_id: field.id }).then((result) => {
3498
+ void fieldOptionsOnce(client, field.id).then((result) => {
3458
3499
  if (cancelled) return;
3459
3500
  if (result.ok) setOptions(inTheStoresOrder(result.data ?? []));
3460
3501
  else setRefusal(result.error);
@@ -3517,7 +3558,10 @@ function OptionControl({ field, value, onChange, disabled, id, autoOpen, onClose
3517
3558
  initialSearch,
3518
3559
  addsChoices,
3519
3560
  allowOther: allowsOtherValues(field),
3520
- onChoicesChanged: () => setReread((n) => n + 1)
3561
+ onChoicesChanged: () => {
3562
+ if (client) forgetAsked(client, `field_options:${field.id}`);
3563
+ setReread((n) => n + 1);
3564
+ }
3521
3565
  }
3522
3566
  );
3523
3567
  }
@@ -3943,20 +3987,40 @@ function TablePicker({ dataSource, value, onChange, disabled, tables }) {
3943
3987
  const options = tablePickerEntries(
3944
3988
  list.rows.map((t) => ({ ...t, id: t.table_id, name: t.table_name })),
3945
3989
  { keep: value }
3946
- ).entries.map(({ table: t }) => ({
3947
- value: t.table_id,
3948
- // The organization hint already tells two same-named tables apart across organizations.
3949
- label: t.table_name,
3950
- hint: t.organization_name,
3951
- keywords: t.organization_name
3952
- }));
3990
+ ).entries.map(({ table: t }) => t);
3991
+ const sameName = /* @__PURE__ */ new Map();
3992
+ for (const t of options) {
3993
+ const key = `${t.organization_id}|${t.table_name.trim().toLowerCase()}`;
3994
+ sameName.set(key, (sameName.get(key) ?? 0) + 1);
3995
+ }
3996
+ const pickerOptions = options.map((t) => {
3997
+ const twin = (sameName.get(`${t.organization_id}|${t.table_name.trim().toLowerCase()}`) ?? 0) > 1;
3998
+ const row = t;
3999
+ const second = [
4000
+ row.created_by_name ? `by ${row.created_by_name}` : null,
4001
+ row.updated_at ? `changed ${new Date(row.updated_at).toLocaleDateString()}` : null
4002
+ ].filter(Boolean).join(" \xB7 ");
4003
+ return {
4004
+ value: t.table_id,
4005
+ // The organization hint already tells two same-named tables apart across organizations.
4006
+ label: t.table_name,
4007
+ hint: t.organization_name,
4008
+ keywords: t.organization_name,
4009
+ ...twin && second ? {
4010
+ render: /* @__PURE__ */ jsxs16("span", { className: "flex min-w-0 flex-col", children: [
4011
+ /* @__PURE__ */ jsx19("span", { className: "truncate", children: t.table_name }),
4012
+ /* @__PURE__ */ jsx19("span", { className: "truncate text-[11px] text-muted-foreground", children: second })
4013
+ ] })
4014
+ } : {}
4015
+ };
4016
+ });
3953
4017
  const missing = Boolean(value) && !list.loading && !list.error && !list.rows.some((t) => t.table_id === value);
3954
4018
  return /* @__PURE__ */ jsxs16("div", { className: "flex flex-col gap-1", children: [
3955
4019
  /* @__PURE__ */ jsx19(
3956
4020
  CreatablePicker,
3957
4021
  {
3958
4022
  value: value || null,
3959
- options,
4023
+ options: pickerOptions,
3960
4024
  onSelect: onChange,
3961
4025
  placeholder: missing ? "Table not available" : "Choose a table",
3962
4026
  searchPlaceholder: "Search tables",
@@ -4478,7 +4542,7 @@ function referencesOf(field, value, kind, labels) {
4478
4542
  }));
4479
4543
  }
4480
4544
  var ARCHIVED_REFERENCE_WORD = "Archived";
4481
- var ARCHIVED_REFERENCE_TIP = "In Trash; restore it to use it again";
4545
+ var ARCHIVED_REFERENCE_TIP = "In the Archived view; restore it to use it again";
4482
4546
  function ReferenceChip({ reference, waiting }) {
4483
4547
  const host = useRecordsUi();
4484
4548
  const words = reference.label ?? (reference.token === "record" ? waiting ?? "Loading\u2026" : WITHHELD_ENTITY_LABEL);