@ai-matrx/records-ui 0.115.38 → 0.115.39

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/CHANGELOG.md CHANGED
@@ -1,9 +1,15 @@
1
1
  # Changelog — @ai-matrx/records-ui
2
2
 
3
- ## 0.115.38
3
+ ## 0.115.39
4
4
 
5
5
  Automatic changed-only republish (docs/metadata drift since the last tag — see
6
- `git diff npm/records-ui/v0.115.37..npm/records-ui/v0.115.38 -- apps/shared/records-ui`).
6
+ `git diff npm/records-ui/v0.115.38..npm/records-ui/v0.115.39 -- apps/shared/records-ui`).
7
+ No source changes intended and no consumer action required.
8
+
9
+ ## 0.115.37
10
+
11
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
12
+ `git diff npm/records-ui/v0.115.36..npm/records-ui/v0.115.37 -- apps/shared/records-ui`).
7
13
  No source changes intended and no consumer action required.
8
14
 
9
15
  ## 0.115.36
package/dist/index.cjs CHANGED
@@ -11978,6 +11978,9 @@ function useGridEditing({ tableId, fields, rows, reload, reloadAfterWrite, canWr
11978
11978
  const startedFrom = (0, import_react66.useRef)(null);
11979
11979
  const startedStored = (0, import_react66.useRef)(null);
11980
11980
  const host = useRecordsUi();
11981
+ const fieldsRef = (0, import_react66.useRef)(fields);
11982
+ fieldsRef.current = fields;
11983
+ const forceNext = (0, import_react66.useRef)(/* @__PURE__ */ new Set());
11981
11984
  const type = (0, import_react66.useCallback)((value) => {
11982
11985
  typed2.current = value;
11983
11986
  setDraft(value);
@@ -12134,6 +12137,47 @@ function useGridEditing({ tableId, fields, rows, reload, reloadAfterWrite, canWr
12134
12137
  delete next[id];
12135
12138
  return next;
12136
12139
  });
12140
+ const syncedField = fieldsRef.current.find((f) => f.key === key);
12141
+ if (syncedField?.source === "synced" && host.writeSyncedCell) {
12142
+ const force = forceNext.current.delete(id);
12143
+ let outcome;
12144
+ try {
12145
+ outcome = await host.writeSyncedCell({ tableId, recordId: rowId, fieldKey: key, value: shown5, force });
12146
+ } catch (thrown) {
12147
+ outcome = { ok: false, message: thrown instanceof Error ? thrown.message : String(thrown) };
12148
+ }
12149
+ setSaving((held) => {
12150
+ const next = { ...held };
12151
+ delete next[id];
12152
+ return next;
12153
+ });
12154
+ if (outcome.ok && outcome.status === "written") {
12155
+ if (typeof outcome.version === "number") {
12156
+ rememberVersion(versions.current, rowId, outcome.version);
12157
+ source.wrote(rowId, outcome.version);
12158
+ }
12159
+ settling.current.set(id, base2);
12160
+ writtenRef.current?.({ rowId, key, before: base2, after: shown5 });
12161
+ afterWrite();
12162
+ return;
12163
+ }
12164
+ setOptimistic((held) => {
12165
+ const next = { ...held };
12166
+ delete next[id];
12167
+ return next;
12168
+ });
12169
+ const sheetValue = outcome.ok ? outcome.sheetValue : null;
12170
+ setRefusals((held) => ({
12171
+ ...held,
12172
+ [id]: outcome.ok ? {
12173
+ error: { code: "stale_write", message: `The sheet now says "${sheetValue ?? ""}". Retry keeps yours.` },
12174
+ conflict: null,
12175
+ attempted: value,
12176
+ sheet: { value: sheetValue }
12177
+ } : { error: { code: "refused_by_rule", message: outcome.message }, conflict: null, attempted: value }
12178
+ }));
12179
+ return;
12180
+ }
12137
12181
  let written;
12138
12182
  try {
12139
12183
  let version = await versionOf(source, versions.current, versionReads.current, rowId);
@@ -12293,6 +12337,7 @@ function useGridEditing({ tableId, fields, rows, reload, reloadAfterWrite, canWr
12293
12337
  (rowId, key) => {
12294
12338
  const held = refusals[cellKey(rowId, key)];
12295
12339
  if (!held) return;
12340
+ if (held.sheet) forceNext.current.add(cellKey(rowId, key));
12296
12341
  void writeCell(rowId, key, held.attempted, held.conflict?.contested_fields[key]);
12297
12342
  },
12298
12343
  [refusals, writeCell]
@@ -20007,6 +20052,20 @@ function columnForField(field2, editing, mayWriteRow, whyNotRow, look, onAskWhoC
20007
20052
  const name = fieldName(field2);
20008
20053
  const format = cellFormatFor(field2, look?.format);
20009
20054
  const workedOut = workedOutFrom(field2);
20055
+ const syncedMark = field2.source === "synced" ? /* @__PURE__ */ (0, import_jsx_runtime69.jsx)(
20056
+ "span",
20057
+ {
20058
+ "data-records-synced": field2.key,
20059
+ title: `Synced from ${look?.syncedFrom || "its source"}`,
20060
+ "aria-label": `Synced from ${look?.syncedFrom || "its source"}`,
20061
+ className: "text-[10px] text-muted-foreground",
20062
+ children: "\u27F3"
20063
+ }
20064
+ ) : null;
20065
+ const withSynced = (header) => syncedMark ? /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("span", { className: "inline-flex items-center gap-1", children: [
20066
+ syncedMark,
20067
+ header
20068
+ ] }) : header;
20010
20069
  return {
20011
20070
  id: field2.key,
20012
20071
  /*
@@ -20014,38 +20073,40 @@ function columnForField(field2, editing, mayWriteRow, whyNotRow, look, onAskWhoC
20014
20073
  * (`custom.formula_eval`, on read or on write) and nobody types it — so its header carries
20015
20074
  * the mark, and the formula it is worked out from is one hover away, in its own words.
20016
20075
  */
20017
- header: look?.rowLabel ? (
20018
- // THE COLUMN THAT NAMES EACH RECORD says so (tranche 6i, the older grid's key marker).
20019
- /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("span", { className: "inline-flex items-center gap-1", children: [
20076
+ header: withSynced(
20077
+ look?.rowLabel ? (
20078
+ // THE COLUMN THAT NAMES EACH RECORD says so (tranche 6i, the older grid's key marker).
20079
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("span", { className: "inline-flex items-center gap-1", children: [
20080
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)(
20081
+ "span",
20082
+ {
20083
+ "data-records-row-label": field2.key,
20084
+ title: "This column names each record",
20085
+ "aria-label": "Names each record",
20086
+ className: "text-[10px] text-muted-foreground",
20087
+ children: "\u26BF"
20088
+ }
20089
+ ),
20090
+ name
20091
+ ] })
20092
+ ) : workedOut && stampMarkOf(field2) ? (
20093
+ // A STAMP OR A RECORD NUMBER (walk 2026-10-08 D7): kept by the store, but nobody wrote a formula
20094
+ // for it, so its header wears its own mark — a clock, a person, a number sign — never "fx".
20095
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("span", { className: "inline-flex items-center", title: workedOut, "data-records-stamp": stampMarkOf(field2), children: withTypeMark(field2, name) })
20096
+ ) : workedOut ? /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("span", { className: "inline-flex items-center gap-1", children: [
20097
+ name,
20020
20098
  /* @__PURE__ */ (0, import_jsx_runtime69.jsx)(
20021
20099
  "span",
20022
20100
  {
20023
- "data-records-row-label": field2.key,
20024
- title: "This column names each record",
20025
- "aria-label": "Names each record",
20026
- className: "text-[10px] text-muted-foreground",
20027
- children: "\u26BF"
20101
+ "data-records-worked-out": field2.key,
20102
+ title: workedOut,
20103
+ "aria-label": workedOut,
20104
+ className: "rounded bg-muted px-1 font-mono text-[10px] font-normal text-muted-foreground",
20105
+ children: "fx"
20028
20106
  }
20029
- ),
20030
- name
20031
- ] })
20032
- ) : workedOut && stampMarkOf(field2) ? (
20033
- // A STAMP OR A RECORD NUMBER (walk 2026-10-08 D7): kept by the store, but nobody wrote a formula
20034
- // for it, so its header wears its own mark — a clock, a person, a number sign — never "fx".
20035
- /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("span", { className: "inline-flex items-center", title: workedOut, "data-records-stamp": stampMarkOf(field2), children: withTypeMark(field2, name) })
20036
- ) : workedOut ? /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("span", { className: "inline-flex items-center gap-1", children: [
20037
- name,
20038
- /* @__PURE__ */ (0, import_jsx_runtime69.jsx)(
20039
- "span",
20040
- {
20041
- "data-records-worked-out": field2.key,
20042
- title: workedOut,
20043
- "aria-label": workedOut,
20044
- className: "rounded bg-muted px-1 font-mono text-[10px] font-normal text-muted-foreground",
20045
- children: "fx"
20046
- }
20047
- )
20048
- ] }) : name,
20107
+ )
20108
+ ] }) : name
20109
+ ),
20049
20110
  label: name,
20050
20111
  accessorFn: (row) => row.document?.[field2.key],
20051
20112
  // A column the store keeps no value for is not offered a sort it would refuse (`sortRefusal`).
@@ -25981,7 +26042,8 @@ function GridBody({
25981
26042
  ...presentation.formats?.[field2.key] ? { format: presentation.formats[field2.key] } : {},
25982
26043
  frozen: frozenKeys.has(field2.key),
25983
26044
  // The column that names each record wears the mark (merged grid, tranche 6i).
25984
- rowLabel: merged && field2.key === (table.data?.title_field ?? null)
26045
+ rowLabel: merged && field2.key === (table.data?.title_field ?? null),
26046
+ syncedFrom: syncedFromWords(table.data)
25985
26047
  },
25986
26048
  onAskWhoChanged,
25987
26049
  {
@@ -27170,6 +27232,12 @@ function withHeaderMenus(columns, menuFor, filterFor) {
27170
27232
  return filter ? { ...withMenu, ...filter } : withMenu;
27171
27233
  });
27172
27234
  }
27235
+ function syncedFromWords(table) {
27236
+ const source = table?.sync_source;
27237
+ if (!source?.provider) return null;
27238
+ const from = source.provider === "google_sheets" ? "Google Sheets" : source.provider === "csv_url" ? "a CSV link" : source.provider === "postgres" ? "an outside database" : source.provider === "matrx_view" ? "another table" : source.provider;
27239
+ return source.label ? `${from} \xB7 ${source.label}` : from;
27240
+ }
27173
27241
  function withoutGoneColumns(query, liveKeys, openingSort) {
27174
27242
  const sortGone = Boolean(query.sort && !liveKeys.has(query.sort.id));
27175
27243
  const goneFilters = Object.keys(query.columnFilters ?? {}).filter((key) => !liveKeys.has(key));
@@ -35362,7 +35430,7 @@ function TimelineView({
35362
35430
  try {
35363
35431
  const make = async (current, key, label) => {
35364
35432
  if (current) return current.key;
35365
- const made = await knobClient.fieldDeclare({ table_id: tableId, spec: { key, label, type: "datetime", kind: "date" } });
35433
+ const made = await knobClient.fieldDeclare({ table_id: tableId, spec: { key, label, parity_type: "datetime", kind: "date" } });
35366
35434
  if (!made.ok) {
35367
35435
  moves.say(refusalSentence(made.error));
35368
35436
  return null;