@ai-matrx/records-ui 0.101.11 → 0.101.14

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,5 +1,17 @@
1
1
  # Changelog — @ai-matrx/records-ui
2
2
 
3
+ ## 0.101.14
4
+
5
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
6
+ `git diff npm/records-ui/v0.101.11..npm/records-ui/v0.101.14 -- apps/shared/records-ui`).
7
+ No source changes intended and no consumer action required.
8
+
9
+ ## 0.101.12
10
+
11
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
12
+ `git diff npm/records-ui/v0.101.11..npm/records-ui/v0.101.12 -- apps/shared/records-ui`).
13
+ No source changes intended and no consumer action required.
14
+
3
15
  ## 0.101.11
4
16
 
5
17
  - **The Action Inbox key hints answer to the inbox's width, not the screen's.** In a ~360px window on
package/dist/index.cjs CHANGED
@@ -13732,6 +13732,44 @@ function replanPastedBlock(plan, args, change = {}) {
13732
13732
  droppedLines: (plan.droppedLines ?? 0) + dropped
13733
13733
  });
13734
13734
  }
13735
+ function storedValueAgrees(field2, stored, written, options) {
13736
+ const empty = (v) => v === void 0 || v === null || v === "";
13737
+ if (empty(stored) || empty(written)) return empty(stored) && empty(written);
13738
+ if (field2 !== void 0) {
13739
+ if (isDateField(field2)) {
13740
+ if (hasTimePart(written)) {
13741
+ const a = new Date(stored).getTime();
13742
+ const b = new Date(written).getTime();
13743
+ if (!Number.isNaN(a) && !Number.isNaN(b)) return a === b;
13744
+ } else {
13745
+ const dayOf = (v) => typeof v === "string" && /^\d{4}-\d{2}-\d{2}/.test(v.trim()) ? v.trim().slice(0, 10) : calendarDayOf(v);
13746
+ const a = dayOf(stored);
13747
+ const b = dayOf(written);
13748
+ if (a !== null && b !== null) return a === b;
13749
+ }
13750
+ }
13751
+ const kind = editorKindFor(field2);
13752
+ if (kind === "number" || kind === "currency" || kind === "percent") {
13753
+ const a = typeof stored === "number" ? stored : Number(String(stored).trim());
13754
+ const b = typeof written === "number" ? written : Number(String(written).trim());
13755
+ if (Number.isFinite(a) && Number.isFinite(b)) return a === b;
13756
+ }
13757
+ if (options !== void 0 && typeof stored === "string" && typeof written === "string") {
13758
+ const option = (v) => options.byWord.get(v.trim().toLowerCase()) ?? v.trim().toLowerCase();
13759
+ return option(stored) === option(written);
13760
+ }
13761
+ }
13762
+ return JSON.stringify(stored) === JSON.stringify(written);
13763
+ }
13764
+ function columnsTheGridTurnedAway(turnedAway, columnKeys, columnName, already) {
13765
+ const said = /* @__PURE__ */ new Map();
13766
+ for (const refused of turnedAway) {
13767
+ if (!columnKeys.includes(refused.columnId)) continue;
13768
+ if (already.some((r) => r.column === columnName(refused.columnId))) continue;
13769
+ said.set(refused.columnId, refused.reason);
13770
+ }
13771
+ return [...said].map(([columnId, why]) => ({ fromLine: 0, column: columnName(columnId), raw: "", why }));
13772
+ }
13735
13773
 
13736
13774
  // src/gridControls.tsx
13737
13775
  var import_jsx_runtime48 = require("react/jsx-runtime");
@@ -15503,12 +15541,7 @@ function PastePreview({
15503
15541
  plan.refusals.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("div", { "data-matrx-paste-refusals": true, className: "flex flex-col gap-1", children: [
15504
15542
  /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("p", { className: "text-xs font-medium", children: plan.refusals.length === 1 ? "One cell will be left alone:" : `${plan.refusals.length} cells will be left alone:` }),
15505
15543
  /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("ul", { className: "flex flex-col gap-1", children: plan.refusals.slice(0, 12).map((refusal3, index) => /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("li", { className: "text-xs text-muted-foreground", children: [
15506
- /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("span", { className: "font-medium", children: [
15507
- "Row ",
15508
- refusal3.fromLine,
15509
- ", ",
15510
- refusal3.column
15511
- ] }),
15544
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("span", { className: "font-medium", children: refusal3.fromLine > 0 ? `Row ${refusal3.fromLine}, ${refusal3.column}` : refusal3.column }),
15512
15545
  " \u2014 ",
15513
15546
  refusal3.why
15514
15547
  ] }, index)) }),
@@ -21280,19 +21313,9 @@ function GridBody({
21280
21313
  pastRightEdge: sheetPlan.pastRightEdge,
21281
21314
  ...headed ? { header: { unmatched: headed.unmatched } } : {}
21282
21315
  });
21283
- const notWritable = /* @__PURE__ */ new Map();
21284
- for (const refused of sheetPlan.refusals) {
21285
- if (built.refusals.some((r) => r.column === columnLabel(fieldList, refused.columnId))) continue;
21286
- notWritable.set(refused.columnId, refused.reason);
21287
- }
21288
- for (const [columnId, why] of notWritable) {
21289
- built.refusals.push({
21290
- fromLine: 0,
21291
- column: columnLabel(fieldList, columnId),
21292
- raw: "",
21293
- why
21294
- });
21295
- }
21316
+ built.refusals.push(
21317
+ ...columnsTheGridTurnedAway(sheetPlan.refusals, columnKeys, (id) => columnLabel(fieldList, id), built.refusals)
21318
+ );
21296
21319
  if (merged && built.cells > 0 && built.creates === 0 && built.refusals.length === 0 && built.pastRightEdge === 0 && (built.header?.unmatched.length ?? 0) === 0 && built.unmatchedOptions.length === 0) {
21297
21320
  const outcome = await pasteAtOnce.current(built);
21298
21321
  if (outcome.refusals.length > 0 || outcome.missing > 0) {
@@ -21393,20 +21416,21 @@ function GridBody({
21393
21416
  const reread = await client.listByIds({ table_id: tableId, ids: expected.map((e) => e.recordId) });
21394
21417
  let readBack = 0;
21395
21418
  let missing = 0;
21419
+ const fieldOf2 = new Map((fields.data ?? []).map((f) => [f.key, f]));
21396
21420
  const refusals = [];
21397
21421
  if (!reread.ok) refusals.push(`The rows were written and this could not read them back to check: ${refusalSentence(reread.error)}`);
21398
21422
  const seen = new Map((reread.ok ? reread.data : []).map((r) => [r.id, r.document ?? {}]));
21399
21423
  for (const entry of expected) {
21400
21424
  const stored = seen.get(entry.recordId);
21401
21425
  for (const [key, value] of Object.entries(entry.document)) {
21402
- if (stored !== void 0 && JSON.stringify(stored[key]) === JSON.stringify(value)) readBack += 1;
21426
+ if (stored !== void 0 && storedValueAgrees(fieldOf2.get(key), stored[key], value, choiceOptions.current.get(key))) readBack += 1;
21403
21427
  else missing += 1;
21404
21428
  }
21405
21429
  }
21406
21430
  records.reload();
21407
21431
  return { written: lines, readBack, missing, refusals };
21408
21432
  },
21409
- [rows, client, tableId, editing, undo, records, versions]
21433
+ [rows, client, tableId, editing, undo, records, versions, fields.data]
21410
21434
  );
21411
21435
  const pasteAtOnce = (0, import_react107.useRef)(applyPasteAsOne);
21412
21436
  pasteAtOnce.current = applyPasteAsOne;
@@ -21481,6 +21505,7 @@ function GridBody({
21481
21505
  }
21482
21506
  let readBack = 0;
21483
21507
  let missing = 0;
21508
+ const fieldOf2 = new Map((fields.data ?? []).map((f) => [f.key, f]));
21484
21509
  const seen = /* @__PURE__ */ new Map();
21485
21510
  const READ_BACK_PAGE = 500;
21486
21511
  for (let offset = 0; ; offset += READ_BACK_PAGE) {
@@ -21500,7 +21525,7 @@ function GridBody({
21500
21525
  for (const entry of expected) {
21501
21526
  const stored = seen.get(entry.recordId);
21502
21527
  for (const [key, value] of Object.entries(entry.document)) {
21503
- if (stored !== void 0 && JSON.stringify(stored[key]) === JSON.stringify(value)) {
21528
+ if (stored !== void 0 && storedValueAgrees(fieldOf2.get(key), stored[key], value, choiceOptions.current.get(key))) {
21504
21529
  readBack += 1;
21505
21530
  } else {
21506
21531
  missing += 1;
@@ -21509,7 +21534,7 @@ function GridBody({
21509
21534
  }
21510
21535
  records.reload();
21511
21536
  return { written, readBack, missing, refusals };
21512
- }, [pastePlan, client, tableId, records, merged, applyPasteAsOne, versions]);
21537
+ }, [pastePlan, client, tableId, records, merged, applyPasteAsOne, versions, fields.data]);
21513
21538
  const columnIds = (0, import_react107.useMemo)(() => ordered.map((field2) => field2.key), [ordered]);
21514
21539
  const lookWrites = (0, import_react107.useRef)(null);
21515
21540
  const writeLook = (0, import_react107.useCallback)(
@@ -23132,6 +23157,7 @@ var import_records40 = require("@ai-matrx/records");
23132
23157
  var import_design_system67 = require("@ai-matrx/design-system");
23133
23158
 
23134
23159
  // src/newTableColumns.ts
23160
+ var import_templates = require("@ai-matrx/records/templates");
23135
23161
  var NEW_COLUMN_TYPES = [
23136
23162
  { type: "text", words: "Text" },
23137
23163
  { type: "rich_text", words: "Formatted text" },
@@ -23149,7 +23175,80 @@ var NEW_COLUMN_TYPES = [
23149
23175
  ];
23150
23176
  var MOST_NEW_COLUMNS = 40;
23151
23177
  var BULLET = /^\s*(?:[-*•]|\d+[.)])\s+/;
23152
- function columnNamesFrom(text) {
23178
+ var TYPE_WORD_ALIASES = {
23179
+ choice: "select",
23180
+ dropdown: "select",
23181
+ status: "select",
23182
+ list: "select",
23183
+ choices: "multi_select",
23184
+ tags: "multi_select",
23185
+ money: "currency",
23186
+ boolean: "checkbox",
23187
+ bool: "checkbox",
23188
+ "yes/no": "checkbox",
23189
+ "yes / no": "checkbox",
23190
+ link: "url",
23191
+ integer: "number",
23192
+ decimal: "number",
23193
+ float: "number",
23194
+ string: "text",
23195
+ long_text: "rich_text",
23196
+ "formatted text": "rich_text",
23197
+ "date and time": "datetime",
23198
+ "date time": "datetime"
23199
+ };
23200
+ function typeFromWord(raw) {
23201
+ const word = raw.trim().toLowerCase();
23202
+ if (word === "") return void 0;
23203
+ const type = TYPE_WORD_ALIASES[word] ?? NEW_COLUMN_TYPES.find((t) => t.type === word.replace(/[\s-]+/g, "_"))?.type;
23204
+ if (!type) return void 0;
23205
+ return import_templates.RESOLVABLE_FIELD_TYPE_WORDS.includes(type === "date" ? "datetime" : type) ? type : void 0;
23206
+ }
23207
+ function maskBrackets(text) {
23208
+ let depth = 0;
23209
+ let out = "";
23210
+ for (const ch of text) {
23211
+ if (ch === "(" || ch === "[") {
23212
+ depth++;
23213
+ out += depth === 1 ? ch : "\0";
23214
+ } else if ((ch === ")" || ch === "]") && depth > 0) {
23215
+ depth--;
23216
+ out += depth === 0 ? ch : "\0";
23217
+ } else out += depth > 0 ? "\0" : ch;
23218
+ }
23219
+ return out;
23220
+ }
23221
+ function splitOutsideBrackets(text, sep) {
23222
+ const masked = maskBrackets(text);
23223
+ const parts = [];
23224
+ let from = 0;
23225
+ const re = new RegExp(sep.source, sep.flags.includes("g") ? sep.flags : sep.flags + "g");
23226
+ for (const m of masked.matchAll(re)) {
23227
+ if (m[0] === "") continue;
23228
+ parts.push(text.slice(from, m.index));
23229
+ from = m.index + m[0].length;
23230
+ }
23231
+ parts.push(text.slice(from));
23232
+ return parts;
23233
+ }
23234
+ function specFromPart(part) {
23235
+ const clean = (t) => t.replace(/^["'\s]+|["'.\s]+$/g, "").replace(/\s+/g, " ");
23236
+ const m = /^(.*?)\s*[(\[]([^()\[\]]*)[)\]]\s*$/.exec(part.trim());
23237
+ if (m) {
23238
+ const inner2 = m[2];
23239
+ const colon = inner2.indexOf(":");
23240
+ const type = typeFromWord(colon >= 0 ? inner2.slice(0, colon) : inner2);
23241
+ const name = clean(m[1]);
23242
+ if (type && name !== "") {
23243
+ const options = colon >= 0 ? [...new Set(inner2.slice(colon + 1).split(/\s*[,;|]\s*/).map(clean).filter((o) => o !== ""))] : void 0;
23244
+ return { label: name, type, ...options && options.length > 0 && (type === "select" || type === "multi_select") ? { options } : {} };
23245
+ }
23246
+ }
23247
+ const withoutPairs = part.replace(/\([^()\[\]]*\)|\[[^()\[\]]*\]/g, "");
23248
+ const label = /[()\[\]]/.test(withoutPairs) ? part.replace(/[()\[\]]/g, " ") : part;
23249
+ return { label: clean(label) };
23250
+ }
23251
+ function specsFrom(text) {
23153
23252
  const trimmed = text.trim();
23154
23253
  if (trimmed === "") return [];
23155
23254
  let parts;
@@ -23160,23 +23259,28 @@ function columnNamesFrom(text) {
23160
23259
  parts = trimmed.split(/\r?\n/).map((line) => line.replace(BULLET, ""));
23161
23260
  } else {
23162
23261
  let line = trimmed;
23163
- const colon = line.indexOf(":");
23262
+ const masked = maskBrackets(line);
23263
+ const colon = masked.indexOf(":");
23164
23264
  if (colon >= 0) line = line.slice(colon + 1);
23165
23265
  else {
23166
- const withAt = line.toLowerCase().lastIndexOf(" with ");
23266
+ const withAt = masked.toLowerCase().lastIndexOf(" with ");
23167
23267
  if (withAt >= 0) line = line.slice(withAt + " with ".length);
23168
23268
  }
23169
- parts = line.split(/\s*[,;|]\s*|\s+and\s+|\s*&\s*/i);
23269
+ parts = splitOutsideBrackets(line, /\s*[,;|]\s*|\s+and\s+|\s*&\s*/i);
23170
23270
  }
23171
- const names = parts.map((p) => p.replace(/^["'\s]+|["'.\s]+$/g, "").replace(/\s+/g, " ")).filter((p) => p !== "").map((p) => p === p.toLowerCase() ? p[0].toUpperCase() + p.slice(1) : p);
23271
+ const specs = parts.map(specFromPart).filter((c) => c.label !== "").map((c) => c.label === c.label.toLowerCase() ? { ...c, label: c.label[0].toUpperCase() + c.label.slice(1) } : c);
23172
23272
  const seen = /* @__PURE__ */ new Set();
23173
- const once = names.filter((n) => {
23174
- const k = n.toLowerCase();
23273
+ const once = specs.filter((c) => {
23274
+ const k = c.label.toLowerCase();
23175
23275
  if (seen.has(k)) return false;
23176
23276
  seen.add(k);
23177
23277
  return true;
23178
23278
  });
23179
- return uniqueLabels(once).slice(0, MOST_NEW_COLUMNS);
23279
+ const labels = uniqueLabels(once.map((c) => c.label));
23280
+ return once.map((c, i) => ({ ...c, label: labels[i] })).slice(0, MOST_NEW_COLUMNS);
23281
+ }
23282
+ function columnNamesFrom(text) {
23283
+ return specsFrom(text).map((c) => c.label);
23180
23284
  }
23181
23285
  var STATUS_CHOICES = ["Not started", "In progress", "Done"];
23182
23286
  var PRIORITY_CHOICES = ["Low", "Medium", "High"];
@@ -23201,7 +23305,9 @@ function columnFromName(label) {
23201
23305
  return { label, type: "text" };
23202
23306
  }
23203
23307
  function columnsFrom(text) {
23204
- return columnNamesFrom(text).map(columnFromName);
23308
+ return specsFrom(text).map(
23309
+ (c) => c.type ? { label: c.label, type: c.type, ...c.options && c.options.length > 0 ? { options: c.options } : {} } : columnFromName(c.label)
23310
+ );
23205
23311
  }
23206
23312
  function fieldsForNewColumns(columns) {
23207
23313
  const keys = /* @__PURE__ */ new Set();