@ai-matrx/records-ui 0.83.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/CHANGELOG.md +15 -0
- package/dist/index.cjs +90 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -2
- package/dist/index.d.ts +8 -2
- package/dist/index.js +191 -137
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog — @ai-matrx/records-ui
|
|
2
2
|
|
|
3
|
+
## 0.83.1
|
|
4
|
+
|
|
5
|
+
**A table shared by link is in the World lane, not Community.** (lane HUB-FIX, chair ruling
|
|
6
|
+
2026-09-23.)
|
|
7
|
+
|
|
8
|
+
* `visibilityLaneFor` maps `link` → `world` (was `community`). A link anyone can open without
|
|
9
|
+
signing in reaches the world; Community is for things any SIGNED-IN account can reach, and
|
|
10
|
+
the store has no signed-in-only link yet, so no stored visibility lands in Community today.
|
|
11
|
+
`VISIBILITY_LANES` still carries all four — the chip stays.
|
|
12
|
+
* Test: `src/visibility-lanes.test.tsx` updated (link → world) plus a case pinning that no
|
|
13
|
+
stored visibility maps to Community yet — 5 cases.
|
|
14
|
+
|
|
15
|
+
**Consumer action:** none. The hub reads `visibilityLaneFor`, so link-shared tables move from
|
|
16
|
+
the Community chip to the World chip on install.
|
|
17
|
+
|
|
3
18
|
## 0.83.0
|
|
4
19
|
|
|
5
20
|
**Four visibility lanes a filter can read, and the store's own choice lists stop sitting
|
package/dist/index.cjs
CHANGED
|
@@ -2388,7 +2388,7 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow }
|
|
|
2388
2388
|
startedFrom.current = null;
|
|
2389
2389
|
}, []);
|
|
2390
2390
|
const writeCellNow = (0, import_react15.useCallback)(
|
|
2391
|
-
async (rowId, key, value, resent = false) => {
|
|
2391
|
+
async (rowId, key, value, base, resent = false) => {
|
|
2392
2392
|
const id = cellKey(rowId, key);
|
|
2393
2393
|
setOptimistic((held) => ({ ...held, [id]: value }));
|
|
2394
2394
|
setSaving((held) => ({ ...held, [id]: true }));
|
|
@@ -2435,8 +2435,8 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow }
|
|
|
2435
2435
|
if (written.error.code === "stale_write") {
|
|
2436
2436
|
if (conflict) rememberVersion(versions.current, rowId, conflict.current_version);
|
|
2437
2437
|
else versions.current.delete(rowId);
|
|
2438
|
-
if (conflict && !resent &&
|
|
2439
|
-
await writeCellNow(rowId, key, value, true);
|
|
2438
|
+
if (conflict && !resent && sameValue(conflict.contested_fields[key], base)) {
|
|
2439
|
+
await writeCellNow(rowId, key, value, base, true);
|
|
2440
2440
|
return;
|
|
2441
2441
|
}
|
|
2442
2442
|
}
|
|
@@ -2453,9 +2453,9 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow }
|
|
|
2453
2453
|
[client, reload]
|
|
2454
2454
|
);
|
|
2455
2455
|
const writeCell = (0, import_react15.useCallback)(
|
|
2456
|
-
(rowId, key, value) => {
|
|
2456
|
+
(rowId, key, value, base) => {
|
|
2457
2457
|
const behind = queues.current.get(rowId) ?? Promise.resolve();
|
|
2458
|
-
const next = behind.then(() => writeCellNow(rowId, key, value));
|
|
2458
|
+
const next = behind.then(() => writeCellNow(rowId, key, value, base));
|
|
2459
2459
|
queues.current.set(
|
|
2460
2460
|
rowId,
|
|
2461
2461
|
next.then(
|
|
@@ -2473,7 +2473,7 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow }
|
|
|
2473
2473
|
const open = editing;
|
|
2474
2474
|
if (!open) return;
|
|
2475
2475
|
const value = typed.current;
|
|
2476
|
-
if (!sameValue(startedFrom.current, value)) void writeCell(open.rowId, open.key, value);
|
|
2476
|
+
if (!sameValue(startedFrom.current, value)) void writeCell(open.rowId, open.key, value, startedFrom.current);
|
|
2477
2477
|
if (!move) {
|
|
2478
2478
|
setEditing(null);
|
|
2479
2479
|
setDraft(null);
|
|
@@ -2502,7 +2502,7 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow }
|
|
|
2502
2502
|
(rowId, key) => {
|
|
2503
2503
|
const held = refusals[cellKey(rowId, key)];
|
|
2504
2504
|
if (!held) return;
|
|
2505
|
-
void writeCell(rowId, key, held.attempted);
|
|
2505
|
+
void writeCell(rowId, key, held.attempted, held.conflict?.contested_fields[key]);
|
|
2506
2506
|
},
|
|
2507
2507
|
[refusals, writeCell]
|
|
2508
2508
|
);
|
|
@@ -2598,7 +2598,7 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow }
|
|
|
2598
2598
|
commit,
|
|
2599
2599
|
setCell: (rowId, key, value) => {
|
|
2600
2600
|
if (!writable(rowId)) return;
|
|
2601
|
-
void writeCell(rowId, key, value);
|
|
2601
|
+
void writeCell(rowId, key, value, shownValue(rowId, key));
|
|
2602
2602
|
},
|
|
2603
2603
|
retry,
|
|
2604
2604
|
keepTheirs,
|
|
@@ -2683,7 +2683,7 @@ function GridCell({
|
|
|
2683
2683
|
RefusalNotice,
|
|
2684
2684
|
{
|
|
2685
2685
|
error: refusal2.error,
|
|
2686
|
-
className: "py-1.5",
|
|
2686
|
+
className: "py-1.5 text-left",
|
|
2687
2687
|
onDiscard: () => editing.discard(row.id, field.key)
|
|
2688
2688
|
}
|
|
2689
2689
|
) : null
|
|
@@ -2797,7 +2797,7 @@ function CellRefusalNotice({
|
|
|
2797
2797
|
RefusalNotice,
|
|
2798
2798
|
{
|
|
2799
2799
|
error: refusal2.error,
|
|
2800
|
-
className: "py-1.5",
|
|
2800
|
+
className: "py-1.5 text-left",
|
|
2801
2801
|
actions: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "flex flex-col gap-1", "data-refusal-conflict": "", children: [
|
|
2802
2802
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "text-[11px]", children: [
|
|
2803
2803
|
"Theirs: ",
|
|
@@ -2815,7 +2815,7 @@ function CellRefusalNotice({
|
|
|
2815
2815
|
RefusalNotice,
|
|
2816
2816
|
{
|
|
2817
2817
|
error: refusal2.error,
|
|
2818
|
-
className: "py-1.5",
|
|
2818
|
+
className: "py-1.5 text-left",
|
|
2819
2819
|
...typed ? { onKeepEditing: () => editing?.keepEditing(row.id, field.key) } : {},
|
|
2820
2820
|
onDiscard: () => editing?.discard(row.id, field.key)
|
|
2821
2821
|
}
|
|
@@ -7131,7 +7131,39 @@ var import_design_system22 = require("@ai-matrx/design-system");
|
|
|
7131
7131
|
// src/RecordForm.tsx
|
|
7132
7132
|
var import_react41 = require("react");
|
|
7133
7133
|
var import_react42 = require("@ai-matrx/records/react");
|
|
7134
|
+
var import_core7 = require("@ai-matrx/records/core");
|
|
7135
|
+
|
|
7136
|
+
// src/writeOnTheWinningVersion.ts
|
|
7134
7137
|
var import_core6 = require("@ai-matrx/records/core");
|
|
7138
|
+
function sameStoredValue(left, right) {
|
|
7139
|
+
if (left === right) return true;
|
|
7140
|
+
const empty = (v) => v === null || v === void 0 || v === "";
|
|
7141
|
+
if (empty(left) || empty(right)) return empty(left) && empty(right);
|
|
7142
|
+
if (typeof left === "object" || typeof right === "object") {
|
|
7143
|
+
try {
|
|
7144
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
7145
|
+
} catch {
|
|
7146
|
+
return false;
|
|
7147
|
+
}
|
|
7148
|
+
}
|
|
7149
|
+
return false;
|
|
7150
|
+
}
|
|
7151
|
+
async function updateOnTheWinningVersion(client, recordId, patch, expectedVersion, base) {
|
|
7152
|
+
const send = (expected) => client.recordUpdate({
|
|
7153
|
+
record_id: recordId,
|
|
7154
|
+
patch,
|
|
7155
|
+
...expected === null ? {} : { expectedVersion: expected }
|
|
7156
|
+
});
|
|
7157
|
+
const first = await send(expectedVersion);
|
|
7158
|
+
if (first.ok) return first;
|
|
7159
|
+
const conflict = (0, import_core6.asWriteConflict)(first.error, recordId);
|
|
7160
|
+
if (!conflict) return first;
|
|
7161
|
+
const untouched = base === "always" || Object.keys(patch).every((key) => sameStoredValue(conflict.contested_fields[key], base[key]));
|
|
7162
|
+
if (!untouched) return first;
|
|
7163
|
+
return send(conflict.current_version);
|
|
7164
|
+
}
|
|
7165
|
+
|
|
7166
|
+
// src/RecordForm.tsx
|
|
7135
7167
|
var import_design_system20 = require("@ai-matrx/design-system");
|
|
7136
7168
|
|
|
7137
7169
|
// src/systemTable.ts
|
|
@@ -7319,15 +7351,38 @@ function RecordForm({
|
|
|
7319
7351
|
const fields = (0, import_react42.useFields)(tableId, recordType);
|
|
7320
7352
|
const existing = (0, import_react42.useRecord)(recordId ?? null);
|
|
7321
7353
|
const mutation = (0, import_react42.useRecordMutation)();
|
|
7354
|
+
const client = (0, import_react42.useRecordsClient)();
|
|
7355
|
+
const [edit, setEdit] = (0, import_react41.useState)({
|
|
7356
|
+
saving: false,
|
|
7357
|
+
error: null,
|
|
7358
|
+
conflict: null
|
|
7359
|
+
});
|
|
7360
|
+
const said = {
|
|
7361
|
+
saving: mutation.saving || edit.saving,
|
|
7362
|
+
error: recordId ? edit.error : mutation.error,
|
|
7363
|
+
conflict: recordId ? edit.conflict : mutation.conflict,
|
|
7364
|
+
clearConflict: () => {
|
|
7365
|
+
mutation.clearConflict();
|
|
7366
|
+
setEdit((e) => ({ ...e, conflict: null }));
|
|
7367
|
+
}
|
|
7368
|
+
};
|
|
7322
7369
|
const [draft, setDraft] = (0, import_react41.useState)({});
|
|
7323
7370
|
const [touched, setTouched] = (0, import_react41.useState)(false);
|
|
7324
7371
|
const [changed, setChanged] = (0, import_react41.useState)({});
|
|
7325
7372
|
const [answered, setAnswered] = (0, import_react41.useState)(null);
|
|
7326
7373
|
const loaded = useRecordVersion(recordId ?? null);
|
|
7327
7374
|
const loadedVersion = loaded.version;
|
|
7375
|
+
const changedRef = (0, import_react41.useRef)({});
|
|
7376
|
+
changedRef.current = changed;
|
|
7328
7377
|
(0, import_react41.useEffect)(() => {
|
|
7329
7378
|
const document3 = existing.data?.document;
|
|
7330
|
-
if (document3)
|
|
7379
|
+
if (!document3) return;
|
|
7380
|
+
const mine = changedRef.current;
|
|
7381
|
+
setDraft((held) => {
|
|
7382
|
+
const next = { ...document3 };
|
|
7383
|
+
for (const key of Object.keys(mine)) next[key] = held[key];
|
|
7384
|
+
return next;
|
|
7385
|
+
});
|
|
7331
7386
|
}, [existing.data?.record_id, loadedVersion]);
|
|
7332
7387
|
const document2 = (0, import_react41.useMemo)(() => {
|
|
7333
7388
|
const out = {};
|
|
@@ -7338,15 +7393,15 @@ function RecordForm({
|
|
|
7338
7393
|
return out;
|
|
7339
7394
|
}, [draft]);
|
|
7340
7395
|
const predicted = (0, import_react41.useMemo)(
|
|
7341
|
-
() => fields.data ? (0,
|
|
7396
|
+
() => fields.data ? (0, import_core7.predictWriteRefusals)({ fields: fields.data, document: document2, ...recordType ? { recordType } : {} }) : [],
|
|
7342
7397
|
[fields.data, document2, recordType]
|
|
7343
7398
|
);
|
|
7344
7399
|
if (fields.error) return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(RefusalNotice, { error: fields.error, className });
|
|
7345
7400
|
if (existing.error) return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(RefusalNotice, { error: existing.error, className });
|
|
7346
|
-
const storeSaid =
|
|
7401
|
+
const storeSaid = said.error && said.error !== answered ? said.error : null;
|
|
7347
7402
|
const fieldProblems = predicted.filter((p) => p.field_key);
|
|
7348
7403
|
const problemsShown = (key) => touched || changed[key] ? fieldProblems.filter((p) => p.field_key === key) : [];
|
|
7349
|
-
const storeRefusalIsOnAField = storeSaid !== null && !
|
|
7404
|
+
const storeRefusalIsOnAField = storeSaid !== null && !said.conflict && storeSaid.code === "refused_by_rule" && (fields.data ?? []).some((f) => problemsShown(f.key).length > 0);
|
|
7350
7405
|
const stored = existing.data?.document;
|
|
7351
7406
|
const focusField = (field) => {
|
|
7352
7407
|
if (!field) return;
|
|
@@ -7377,13 +7432,22 @@ function RecordForm({
|
|
|
7377
7432
|
setAnswered(null);
|
|
7378
7433
|
if (recordId) {
|
|
7379
7434
|
const version = loadedVersion;
|
|
7380
|
-
|
|
7381
|
-
|
|
7382
|
-
|
|
7383
|
-
|
|
7435
|
+
if (Object.keys(changed).length === 0) {
|
|
7436
|
+
onSaved?.(recordId);
|
|
7437
|
+
return;
|
|
7438
|
+
}
|
|
7439
|
+
const patch = Object.fromEntries(Object.entries(document2).filter(([key]) => changed[key]));
|
|
7440
|
+
setEdit({ saving: true, error: null, conflict: null });
|
|
7441
|
+
const answered2 = await updateOnTheWinningVersion(client, recordId, patch, version, stored ?? {});
|
|
7442
|
+
setEdit({
|
|
7443
|
+
saving: false,
|
|
7444
|
+
error: answered2.ok ? null : answered2.error,
|
|
7445
|
+
conflict: answered2.ok ? null : (0, import_core7.asWriteConflict)(answered2.error, recordId)
|
|
7384
7446
|
});
|
|
7447
|
+
const moved = answered2.ok ? answered2.data : null;
|
|
7385
7448
|
if (moved !== null) {
|
|
7386
7449
|
loaded.note(moved);
|
|
7450
|
+
setChanged({});
|
|
7387
7451
|
host.notify?.success("Saved");
|
|
7388
7452
|
existing.reload();
|
|
7389
7453
|
onSaved?.(recordId);
|
|
@@ -7432,9 +7496,9 @@ function RecordForm({
|
|
|
7432
7496
|
RefusalNotice,
|
|
7433
7497
|
{
|
|
7434
7498
|
error: storeSaid,
|
|
7435
|
-
...
|
|
7499
|
+
...said.conflict ? {
|
|
7436
7500
|
actions: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "mt-1 space-y-1", children: [
|
|
7437
|
-
Object.entries(
|
|
7501
|
+
Object.entries(said.conflict.contested_fields).map(([key, theirs]) => {
|
|
7438
7502
|
const column = (fields.data ?? []).find((f) => f.key === key);
|
|
7439
7503
|
return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("p", { className: "text-[11px]", children: [
|
|
7440
7504
|
column ? fieldName(column) : humanize(key),
|
|
@@ -7452,9 +7516,9 @@ function RecordForm({
|
|
|
7452
7516
|
size: "sm",
|
|
7453
7517
|
variant: "outline",
|
|
7454
7518
|
onClick: () => {
|
|
7455
|
-
if (
|
|
7519
|
+
if (said.conflict) loaded.note(said.conflict.current_version);
|
|
7456
7520
|
existing.reload();
|
|
7457
|
-
|
|
7521
|
+
said.clearConflict();
|
|
7458
7522
|
setAnswered(storeSaid);
|
|
7459
7523
|
},
|
|
7460
7524
|
children: "Take theirs and start again"
|
|
@@ -7466,7 +7530,7 @@ function RecordForm({
|
|
|
7466
7530
|
) : null,
|
|
7467
7531
|
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_design_system20.Separator, {}),
|
|
7468
7532
|
/* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
7469
|
-
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_design_system20.Button, { type: "submit", size: "sm", disabled:
|
|
7533
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_design_system20.Button, { type: "submit", size: "sm", disabled: said.saving, children: said.saving ? "Saving\u2026" : recordId ? "Save" : "Create" }),
|
|
7470
7534
|
onCancel ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_design_system20.Button, { type: "button", size: "sm", variant: "ghost", onClick: onCancel, children: "Cancel" }) : null
|
|
7471
7535
|
] })
|
|
7472
7536
|
]
|
|
@@ -7661,7 +7725,6 @@ function computedInPlainWords(row, field) {
|
|
|
7661
7725
|
}
|
|
7662
7726
|
|
|
7663
7727
|
// src/views.ts
|
|
7664
|
-
var import_core7 = require("@ai-matrx/records/core");
|
|
7665
7728
|
var VIEW_LAYOUTS = ["grid", "kanban", "calendar", "gallery"];
|
|
7666
7729
|
var LAYOUT_LABEL = {
|
|
7667
7730
|
grid: "Grid",
|
|
@@ -7786,16 +7849,7 @@ function viewFromRecord(row) {
|
|
|
7786
7849
|
};
|
|
7787
7850
|
}
|
|
7788
7851
|
async function saveViewPatch(client, viewId, document2, expectedVersion) {
|
|
7789
|
-
|
|
7790
|
-
record_id: viewId,
|
|
7791
|
-
patch: document2,
|
|
7792
|
-
...expected === null ? {} : { expectedVersion: expected }
|
|
7793
|
-
});
|
|
7794
|
-
const first = await send(expectedVersion);
|
|
7795
|
-
if (first.ok) return first;
|
|
7796
|
-
const conflict = (0, import_core7.asWriteConflict)(first.error, viewId);
|
|
7797
|
-
if (!conflict) return first;
|
|
7798
|
-
return send(conflict.current_version);
|
|
7852
|
+
return updateOnTheWinningVersion(client, viewId, document2, expectedVersion, "always");
|
|
7799
7853
|
}
|
|
7800
7854
|
function viewPatchDocument(patch) {
|
|
7801
7855
|
const out = {};
|
|
@@ -16505,8 +16559,7 @@ function visibilityLaneFor(table) {
|
|
|
16505
16559
|
if (table.is_kernel) return null;
|
|
16506
16560
|
if ((table.slug ?? "").startsWith(APP_TABLE_PREFIX) || keptByTheApp(table)) return null;
|
|
16507
16561
|
if (table.visibility === "personal") return "mine";
|
|
16508
|
-
if (table.visibility === "link") return "
|
|
16509
|
-
if (table.visibility === "public") return "world";
|
|
16562
|
+
if (table.visibility === "link" || table.visibility === "public") return "world";
|
|
16510
16563
|
return "organization";
|
|
16511
16564
|
}
|
|
16512
16565
|
function TablesHome({ onOpenTable, className }) {
|