@ai-matrx/records-ui 0.104.2 → 0.106.0

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,47 @@
1
1
  # Changelog — @ai-matrx/records-ui
2
2
 
3
+ ## 0.106.0
4
+
5
+ Duplicate property copies the values (Notion N5): the column menu's "Duplicate property" calls `client.fieldDuplicate` (records 0.79.0) and the copy lands right after the original with every value; the panel it used to open (an empty new column started from the original) is gone from the grid. `FieldEditor`'s `copyOf` prop stays for hosts that want the panel.
6
+
7
+ **Consumer action:** none; install records 0.79.0 with it.
8
+
9
+ ## 0.105.1
10
+
11
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
12
+ `git diff npm/records-ui/v0.104.2..npm/records-ui/v0.105.1 -- apps/shared/records-ui`).
13
+ No source changes intended and no consumer action required.
14
+
15
+ ## 0.105.0
16
+
17
+ PAGE-BUNDLE: `recordsDataSource` marks itself `opensInOneCall`, so every table page (grid, board, record page, embedded table) opens its shape with one `custom.table_page_bundle` call through @ai-matrx/records 0.78. The `clinicStore` test kit can model the bundle (`bundle: true`); `a-table-opens-in-one-call.test.tsx` fails if a shape door is asked beside the bundle.
18
+
19
+ **Consumer action:** none.
20
+
21
+ ## 0.104.5
22
+
23
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
24
+ `git diff npm/records-ui/v0.104.2..npm/records-ui/v0.104.5 -- apps/shared/records-ui`).
25
+ No source changes intended and no consumer action required.
26
+
27
+ ## 0.104.4
28
+
29
+ Table-duplicate progress, entity list totals and the export row-cap note format counts through `@ai-matrx/kit/format` `formatCount` (same digits; the export note keeps en-US). Package-twins collapse.
30
+
31
+ Consumer action: none.
32
+
33
+ ## 0.104.3
34
+
35
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
36
+ `git diff npm/records-ui/v0.104.2..npm/records-ui/v0.104.3 -- apps/shared/records-ui`).
37
+ No source changes intended and no consumer action required.
38
+
39
+ ## 0.104.5
40
+
41
+ - `FormResultsPanel`: the four numbers are plain tiles (no "from 1 record" caption, the rate as a percentage); only choice, checkbox and number questions get an answer chart — free-text questions list their answers. Real-browser walk `demo/typeform-walk.mjs` (welcome, jump, ending, results; desktop and phone) joins `pnpm test:layout`.
42
+
43
+ **Consumer action:** none.
44
+
3
45
  ## 0.104.2
4
46
 
5
47
  - (0.104.1 never reached npm: its gate found native selects in `FormFlowEditor` — now the package's `PickOne` — and the owner's-preview test still stubbing `form_preview_asks`; it now stubs `form_preview_route`.)
package/dist/index.cjs CHANGED
@@ -619,6 +619,7 @@ var import_records = require("@ai-matrx/records");
619
619
  var LEVEL_WORD = {
620
620
  viewer: "Viewer",
621
621
  commenter: "Commenter",
622
+ edit_content: "Content editor",
622
623
  editor: "Editor",
623
624
  admin: "Admin"
624
625
  };
@@ -626,8 +627,12 @@ var NEEDS = {
626
627
  read: "viewer",
627
628
  // custom.io_comment_write
628
629
  comment: "commenter",
629
- // custom.record_update / custom.record_write
630
- write: "editor",
630
+ // custom.record_update / custom.record_write — `edit_content` changes rows and page content;
631
+ // columns, views and settings (structure) stay with admin below, and sharing with admin.
632
+ write: "edit_content",
633
+ // custom.view_designate / view_declare / migrate_merge / record_restore — all ask `editor`: more than
634
+ // changing content (`edit_content`), less than changing columns (`admin`).
635
+ organise: "editor",
631
636
  // custom.record_delete
632
637
  remove: "editor",
633
638
  // custom.share_grant — passing your access on is an administrator's right
@@ -648,6 +653,7 @@ function sentenceFor(what, level) {
648
653
  read: "open this",
649
654
  comment: "leave a comment",
650
655
  write: "change anything here",
656
+ organise: "organise this",
651
657
  remove: "delete this",
652
658
  share: "pass this on to somebody else",
653
659
  structure: "change the columns of this table"
@@ -662,6 +668,7 @@ function whatYouMayDo(level, known = true) {
662
668
  read: may("read"),
663
669
  comment: may("comment"),
664
670
  write: may("write"),
671
+ organise: may("organise"),
665
672
  remove: may("remove"),
666
673
  share: may("share"),
667
674
  structure: may("structure"),
@@ -15345,7 +15352,18 @@ function useMergedGridControls(args) {
15345
15352
  label: "Duplicate property",
15346
15353
  icon: "copy-plus",
15347
15354
  disabledReason: mayStructure ? void 0 : editable ? rights.why("structure") : "Editing is off here.",
15348
- onSelect: () => setDialog({ kind: "insert", anchor: columnId2, side: "right", copyOf: field2 })
15355
+ // DUPLICATE PROPERTY (Notion N5): one door declares the copy right after this column and copies
15356
+ // every record's value into it (`custom.field_duplicate`); nothing to fill in, as in Notion.
15357
+ onSelect: () => {
15358
+ void client.fieldDuplicate({ field_id: field2.id }).then((made) => {
15359
+ if (!made.ok) {
15360
+ notify?.error(`${fieldName(field2)} was not duplicated: ${refusalSentence(made.error)}`);
15361
+ return;
15362
+ }
15363
+ (0, import_react86.tableShapeChanged)(tableId);
15364
+ notify?.success(`${made.data.label} is added.`);
15365
+ });
15366
+ }
15349
15367
  });
15350
15368
  if (summaries) {
15351
15369
  out.push(summaryMenuItem(field2, columnId2 === firstKey, summaries.chosen[columnId2], summaries.operations, (op2) => summaries.choose(columnId2, op2)));
@@ -15692,7 +15710,7 @@ function useMergedGridControls(args) {
15692
15710
  },
15693
15711
  children: /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(import_design_system47.DialogContent, { className: "max-h-[85vh] overflow-y-auto sm:max-w-2xl", children: [
15694
15712
  /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(import_design_system47.DialogHeader, { children: [
15695
- /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_design_system47.DialogTitle, { children: columnDialog?.kind === "settings" ? `Column settings \xB7 ${fieldName(columnDialog.field)}` : columnDialog?.kind === "insert" && columnDialog.copyOf ? `Duplicate property \xB7 ${fieldName(columnDialog.copyOf)}` : columnDialog?.kind === "insert" && columnDialog.anchor ? `Add column ${columnDialog.side === "left" ? "left" : "right"} of ${fieldOf2(columnDialog.anchor) ? fieldName(fieldOf2(columnDialog.anchor)) : columnDialog.anchor}` : ADD_FIELD_OPEN_LABEL }),
15713
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_design_system47.DialogTitle, { children: columnDialog?.kind === "settings" ? `Column settings \xB7 ${fieldName(columnDialog.field)}` : columnDialog?.kind === "insert" && columnDialog.anchor ? `Add column ${columnDialog.side === "left" ? "left" : "right"} of ${fieldOf2(columnDialog.anchor) ? fieldName(fieldOf2(columnDialog.anchor)) : columnDialog.anchor}` : ADD_FIELD_OPEN_LABEL }),
15696
15714
  /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_design_system47.DialogDescription, { className: "sr-only", children: "Name, type and look" })
15697
15715
  ] }),
15698
15716
  columnDialog?.kind === "settings" ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
@@ -15716,7 +15734,6 @@ function useMergedGridControls(args) {
15716
15734
  FieldEditor,
15717
15735
  {
15718
15736
  tableId,
15719
- ...columnDialog.copyOf ? { copyOf: columnDialog.copyOf } : {},
15720
15737
  onCancel: () => {
15721
15738
  pendingInsert.current = null;
15722
15739
  close();
@@ -25057,6 +25074,7 @@ function createEntityRealtime(options = {}) {
25057
25074
  }
25058
25075
 
25059
25076
  // ../entity-data/src/port/port.ts
25077
+ var import_uuid3 = require("@ai-matrx/kit/uuid");
25060
25078
  var import_records38 = require("@ai-matrx/records");
25061
25079
 
25062
25080
  // ../entity-data/src/core/wire.ts
@@ -25157,8 +25175,6 @@ function toColumns(wire) {
25157
25175
  }
25158
25176
 
25159
25177
  // ../entity-data/src/port/port.ts
25160
- var UUID_SHAPE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
25161
- var isUuidShape3 = (value) => typeof value === "string" && UUID_SHAPE.test(value);
25162
25178
  function toWireValue(value) {
25163
25179
  if (isRef(value)) return value.id;
25164
25180
  if (Array.isArray(value) && value.length > 0 && value.every(isRef)) {
@@ -25358,7 +25374,7 @@ function createEntityDataPort(options) {
25358
25374
  async get(alias, id) {
25359
25375
  const binding = bindingOf(alias);
25360
25376
  if (!binding) return noSuchAlias(alias);
25361
- if (!isUuidShape3(id)) return refusal2("invalid_argument", `"${String(id)}" is not a row id.`);
25377
+ if (!(0, import_uuid3.isUuidShape)(id)) return refusal2("invalid_argument", `"${String(id)}" is not a row id.`);
25362
25378
  return getCoalesced(alias, binding.token, id);
25363
25379
  },
25364
25380
  async create(alias, values) {
@@ -25378,20 +25394,20 @@ function createEntityDataPort(options) {
25378
25394
  async update(alias, id, patch, baseVersion) {
25379
25395
  const binding = bindingOf(alias);
25380
25396
  if (!binding) return noSuchAlias(alias);
25381
- if (!isUuidShape3(id)) return refusal2("invalid_argument", `"${String(id)}" is not a row id.`);
25397
+ if (!(0, import_uuid3.isUuidShape)(id)) return refusal2("invalid_argument", `"${String(id)}" is not a row id.`);
25382
25398
  const answer = await client.update({ token: binding.token, id, baseVersion, patch: toWireValues(patch), opId: (0, import_records38.mintOpId)() });
25383
25399
  return readBack(alias, answer, "The change");
25384
25400
  },
25385
25401
  async archive(alias, id) {
25386
25402
  const binding = bindingOf(alias);
25387
25403
  if (!binding) return noSuchAlias(alias);
25388
- if (!isUuidShape3(id)) return refusal2("invalid_argument", `"${String(id)}" is not a row id.`);
25404
+ if (!(0, import_uuid3.isUuidShape)(id)) return refusal2("invalid_argument", `"${String(id)}" is not a row id.`);
25389
25405
  return done(await client.archive({ token: binding.token, ids: [id], opId: (0, import_records38.mintOpId)() }));
25390
25406
  },
25391
25407
  async restore(alias, id) {
25392
25408
  const binding = bindingOf(alias);
25393
25409
  if (!binding) return noSuchAlias(alias);
25394
- if (!isUuidShape3(id)) return refusal2("invalid_argument", `"${String(id)}" is not a row id.`);
25410
+ if (!(0, import_uuid3.isUuidShape)(id)) return refusal2("invalid_argument", `"${String(id)}" is not a row id.`);
25395
25411
  return done(await client.unarchive({ token: binding.token, ids: [id], opId: (0, import_records38.mintOpId)() }));
25396
25412
  },
25397
25413
  subscribe(event, listener) {
@@ -25757,6 +25773,7 @@ var import_react128 = require("@ai-matrx/records/react");
25757
25773
  var import_core19 = require("@ai-matrx/records/core");
25758
25774
 
25759
25775
  // src/viewExport.ts
25776
+ var import_format5 = require("@ai-matrx/kit/format");
25760
25777
  var VIEW_EXPORT_PAGE = 500;
25761
25778
  var VIEW_EXPORT_CEILING = 5e3;
25762
25779
  function viewColumns(fields, presentation) {
@@ -25795,7 +25812,7 @@ async function readViewRows(client, tableId, fields, view, ceiling = VIEW_EXPORT
25795
25812
  }
25796
25813
  }
25797
25814
  function cutWords(written) {
25798
- return `First ${written.toLocaleString("en-US")} rows only`;
25815
+ return `First ${(0, import_format5.formatCount)(written, { locale: "en-US" })} rows only`;
25799
25816
  }
25800
25817
 
25801
25818
  // src/ExportMenu.tsx
@@ -26245,7 +26262,7 @@ var import_clipboard3 = require("@ai-matrx/kit/clipboard");
26245
26262
  var import_design_system66 = require("@ai-matrx/design-system");
26246
26263
  var import_react133 = require("react");
26247
26264
  var import_react134 = require("@ai-matrx/records/react");
26248
- var import_format5 = require("@ai-matrx/kit/format");
26265
+ var import_format6 = require("@ai-matrx/kit/format");
26249
26266
  var import_records39 = require("@ai-matrx/records");
26250
26267
  var import_design_system67 = require("@ai-matrx/design-system");
26251
26268
 
@@ -26582,7 +26599,7 @@ function BookingBuilder({ tableId, bookingId, onSaved, onClose, startNew, classN
26582
26599
  className: "h-9 rounded border bg-background px-2 text-sm",
26583
26600
  value: lead,
26584
26601
  onChange: (e) => setLead(Number(e.target.value)),
26585
- children: [0, 60, 120, 240, 1440].map((n) => /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("option", { value: n, children: n === 0 ? "Right away" : n === 1440 ? "A day from now" : `${(0, import_format5.formatDurationMinutes)(n, { style: "long" })} from now` }, n))
26602
+ children: [0, 60, 120, 240, 1440].map((n) => /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("option", { value: n, children: n === 0 ? "Right away" : n === 1440 ? "A day from now" : `${(0, import_format6.formatDurationMinutes)(n, { style: "long" })} from now` }, n))
26586
26603
  }
26587
26604
  )
26588
26605
  ] }),
@@ -26935,7 +26952,7 @@ function tableActions(input) {
26935
26952
  // custom.view_designate asks editor on the table — `canDesignate` in TablePage.
26936
26953
  action(
26937
26954
  { id: "make-default", label: makeDefaultWords(host.layoutLabel), icon: "layout-grid", group: "manage", rank: 4 },
26938
- "write",
26955
+ "organise",
26939
26956
  input,
26940
26957
  bind(host.makeDefault, table)
26941
26958
  ),
@@ -28114,7 +28131,7 @@ function SaveToTable({ source, shapeIndex = 0, onOpenTable, onSaved, onClose, cl
28114
28131
  const levels = (0, import_react144.useMyLevels)(own3.map((t) => t.id));
28115
28132
  const writable = own3.filter((t) => {
28116
28133
  const level = levels.data?.[t.id];
28117
- return level === "editor" || level === "admin";
28134
+ return level === "edit_content" || level === "editor" || level === "admin";
28118
28135
  }).sort((a, b) => a.name.localeCompare(b.name));
28119
28136
  const [tableId, setTableId] = (0, import_react143.useState)("");
28120
28137
  const proposedName = shape?.name ?? source.title ?? "Saved table";
@@ -30458,7 +30475,7 @@ var import_react188 = require("@ai-matrx/records/react");
30458
30475
  var import_records47 = require("@ai-matrx/records");
30459
30476
  var import_design_system93 = require("@ai-matrx/design-system");
30460
30477
  var import_data_table20 = require("@ai-matrx/design-system/data-table");
30461
- var import_format6 = require("@ai-matrx/kit/format");
30478
+ var import_format7 = require("@ai-matrx/kit/format");
30462
30479
 
30463
30480
  // src/recordMoves.tsx
30464
30481
  var import_react171 = require("react");
@@ -30695,10 +30712,10 @@ function recordActions(input) {
30695
30712
  input,
30696
30713
  copyText13 ? () => copyText13(url, record) : void 0
30697
30714
  ),
30698
- action2({ id: "duplicate", label: "Duplicate", icon: "copy", group: "organize", verb: "duplicate" }, "write", input, bind2(host.duplicate, record)),
30715
+ action2({ id: "duplicate", label: "Duplicate", icon: "copy", group: "organize", verb: "duplicate" }, "organise", input, bind2(host.duplicate, record)),
30699
30716
  action2(
30700
30717
  { id: "merge", label: "Merge with\u2026", icon: "git-merge", group: "organize" },
30701
- "write",
30718
+ "organise",
30702
30719
  input,
30703
30720
  bind2(host.merge, record)
30704
30721
  ),
@@ -34333,9 +34350,9 @@ function ViewPager({ shown: shown4, total, onMore, step: step2 }) {
34333
34350
  return /* @__PURE__ */ (0, import_jsx_runtime109.jsxs)("p", { className: "flex items-center gap-2 text-xs text-muted-foreground", "data-view-pager": "", children: [
34334
34351
  /* @__PURE__ */ (0, import_jsx_runtime109.jsxs)("span", { children: [
34335
34352
  "Showing ",
34336
- (0, import_format6.formatCount)(shown4),
34353
+ (0, import_format7.formatCount)(shown4),
34337
34354
  " of ",
34338
- (0, import_format6.formatCount)(total),
34355
+ (0, import_format7.formatCount)(total),
34339
34356
  " records."
34340
34357
  ] }),
34341
34358
  /* @__PURE__ */ (0, import_jsx_runtime109.jsxs)(import_design_system93.Button, { size: "sm", variant: "outline", onClick: onMore, children: [
@@ -34805,8 +34822,8 @@ function ArchivedView({
34805
34822
  onRestored?.(recordId);
34806
34823
  };
34807
34824
  const titleKey = table.data?.title_field ?? null;
34808
- const mayRestore = rights.write;
34809
- const whyNot = mayRestore ? "" : mayNotRestoreLine(rights.why("write"));
34825
+ const mayRestore = rights.organise;
34826
+ const whyNot = mayRestore ? "" : mayNotRestoreLine(rights.why("organise"));
34810
34827
  return /* @__PURE__ */ (0, import_jsx_runtime110.jsxs)("div", { className: (0, import_design_system94.cn)("flex min-h-0 flex-col gap-2", className), "data-testid": "archived-view", children: [
34811
34828
  /* @__PURE__ */ (0, import_jsx_runtime110.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
34812
34829
  /* @__PURE__ */ (0, import_jsx_runtime110.jsx)("span", { className: "text-sm font-medium", children: "Archived" }),
@@ -35548,7 +35565,7 @@ var import_react201 = require("react");
35548
35565
  var import_react202 = require("@ai-matrx/records/react");
35549
35566
  var import_design_system99 = require("@ai-matrx/design-system");
35550
35567
  var import_data_table21 = require("@ai-matrx/design-system/data-table");
35551
- var import_format7 = require("@ai-matrx/kit/format");
35568
+ var import_format8 = require("@ai-matrx/kit/format");
35552
35569
 
35553
35570
  // src/ChecklistRunner.tsx
35554
35571
  var import_react199 = require("react");
@@ -36928,7 +36945,7 @@ function ActionInbox({
36928
36945
  size: "sm",
36929
36946
  variant: "ghost",
36930
36947
  className: "h-6 px-2 text-[11px]",
36931
- title: (0, import_format7.formatAbsoluteDate)(c.until),
36948
+ title: (0, import_format8.formatAbsoluteDate)(c.until),
36932
36949
  onClick: () => void snooze(item.item_id, item.title, c.until),
36933
36950
  children: [
36934
36951
  c.label,
@@ -36970,7 +36987,7 @@ function ActionInbox({
36970
36987
  /* @__PURE__ */ (0, import_jsx_runtime115.jsx)("span", { className: "font-medium", children: held[item.item_id].approve ? "Approved" : "Declined" }),
36971
36988
  /* @__PURE__ */ (0, import_jsx_runtime115.jsxs)("span", { className: "text-[11px] text-muted-foreground tabular-nums", children: [
36972
36989
  "\xB7 made in ",
36973
- (0, import_format7.formatDurationMs)(Math.max(0, Math.ceil((held[item.item_id].until - Date.now()) / 1e3) * 1e3), { style: "compact" })
36990
+ (0, import_format8.formatDurationMs)(Math.max(0, Math.ceil((held[item.item_id].until - Date.now()) / 1e3) * 1e3), { style: "compact" })
36974
36991
  ] }),
36975
36992
  /* @__PURE__ */ (0, import_jsx_runtime115.jsx)(import_design_system99.Button, { size: "sm", variant: "ghost", className: "h-6 px-2 text-xs", onClick: () => undoHold(held[item.item_id].hold), children: "Undo" })
36976
36993
  ] })
@@ -37195,7 +37212,7 @@ function ActionInbox({
37195
37212
  var import_react203 = require("react");
37196
37213
  var import_react204 = require("@ai-matrx/records/react");
37197
37214
  var import_design_system100 = require("@ai-matrx/design-system");
37198
- var import_format8 = require("@ai-matrx/kit/format");
37215
+ var import_format9 = require("@ai-matrx/kit/format");
37199
37216
  var import_text_case14 = require("@ai-matrx/kit/text-case");
37200
37217
  var import_jsx_runtime116 = require("react/jsx-runtime");
37201
37218
  function HistoryPanel({ tableId, recordId, onAskAboutField, className }) {
@@ -37488,7 +37505,7 @@ function whenText(at) {
37488
37505
  const then = new Date(at);
37489
37506
  const age = Date.now() - then.getTime();
37490
37507
  if (Number.isNaN(age)) return "\u2014";
37491
- if (age < 864e5) return (0, import_format8.formatRelativeTime)(then, { style: "intl" });
37508
+ if (age < 864e5) return (0, import_format9.formatRelativeTime)(then, { style: "intl" });
37492
37509
  if (age < 7 * 864e5) {
37493
37510
  return then.toLocaleString(void 0, { weekday: "short", hour: "numeric", minute: "2-digit" });
37494
37511
  }
@@ -38134,7 +38151,7 @@ var import_recharts = require("recharts");
38134
38151
  var import_records51 = require("@ai-matrx/records");
38135
38152
  var import_core21 = require("@ai-matrx/records/core");
38136
38153
  var import_design_system104 = require("@ai-matrx/design-system");
38137
- var import_format9 = require("@ai-matrx/kit/format");
38154
+ var import_format10 = require("@ai-matrx/kit/format");
38138
38155
 
38139
38156
  // src/linkWords.ts
38140
38157
  var import_react209 = require("react");
@@ -38742,7 +38759,7 @@ function BigNumber({
38742
38759
  /* @__PURE__ */ (0, import_jsx_runtime120.jsx)("span", { className: "text-2xl font-semibold tabular-nums", children: pretty(total) }),
38743
38760
  windows ? /* @__PURE__ */ (0, import_jsx_runtime120.jsx)(Trend, { pct: block.totals?.change_pct, aimingUp: !!target, className: "text-sm" }) : null
38744
38761
  ] }),
38745
- /* @__PURE__ */ (0, import_jsx_runtime120.jsx)("span", { className: "text-xs text-muted-foreground", children: windows && typeof priorTotal === "number" ? `${windowName(windows.window, windows.period)} \xB7 ${pretty(priorTotal)} in ${windowName(windows.prior_window, windows.period)}` : `from ${(0, import_format9.formatCount)(records)} ${records === 1 ? "record" : "records"}` })
38762
+ /* @__PURE__ */ (0, import_jsx_runtime120.jsx)("span", { className: "text-xs text-muted-foreground", children: windows && typeof priorTotal === "number" ? `${windowName(windows.window, windows.period)} \xB7 ${pretty(priorTotal)} in ${windowName(windows.prior_window, windows.period)}` : `from ${(0, import_format10.formatCount)(records)} ${records === 1 ? "record" : "records"}` })
38746
38763
  ] });
38747
38764
  const ring = target && typeof target.progress === "number" && typeof target.value === "number" ? /* @__PURE__ */ (0, import_jsx_runtime120.jsxs)("span", { className: "ml-auto flex shrink-0 items-center gap-2", children: [
38748
38765
  /* @__PURE__ */ (0, import_jsx_runtime120.jsxs)("span", { className: "text-right text-xs text-muted-foreground", children: [
@@ -38861,7 +38878,7 @@ var HEIGHT = CHART_PLOT_PX;
38861
38878
  function compactTick(value) {
38862
38879
  if (typeof value !== "number" || !Number.isFinite(value)) return String(value ?? "");
38863
38880
  if (Math.abs(value) < 1e3 && !Number.isInteger(value)) return String(Math.round(value * 10) / 10);
38864
- return (0, import_format9.formatCount)(value, { style: "compact" });
38881
+ return (0, import_format10.formatCount)(value, { style: "compact" });
38865
38882
  }
38866
38883
  function Drawing({
38867
38884
  kind,
@@ -39000,9 +39017,7 @@ function onlyWholeNumbers(rows) {
39000
39017
 
39001
39018
  // src/FormResultsPanel.tsx
39002
39019
  var import_jsx_runtime121 = require("react/jsx-runtime");
39003
- function numberBlock(title, value) {
39004
- return { title, kind: "number", group_by: [], measures: [{ op: "count" }], rows: [{ groups: {}, measures: { count: value }, row_count: 1 }] };
39005
- }
39020
+ var CHARTED_KINDS = /* @__PURE__ */ new Set(["select", "multi_select", "checkbox", "number", "currency", "percent", "member", "relation"]);
39006
39021
  function dropOffBlock(results, label) {
39007
39022
  return {
39008
39023
  title: "Reached each question",
@@ -39044,6 +39059,13 @@ function FormResultsPanel({ form, className }) {
39044
39059
  cancelled = true;
39045
39060
  };
39046
39061
  }, [client, form.id, round]);
39062
+ const charted = (0, import_react215.useMemo)(() => {
39063
+ const byKey = new Map((fields.data ?? []).map((f) => [f.key, f]));
39064
+ return (key) => {
39065
+ const field2 = byKey.get(key);
39066
+ return field2 ? CHARTED_KINDS.has(editorKindFor(field2)) : false;
39067
+ };
39068
+ }, [fields.data]);
39047
39069
  const label = (0, import_react215.useMemo)(() => {
39048
39070
  const byKey = new Map((fields.data ?? []).map((f) => [f.key, f]));
39049
39071
  const asks = new Map((form.questions ?? []).map((q) => [q.field, q.ask]));
@@ -39057,12 +39079,15 @@ function FormResultsPanel({ form, className }) {
39057
39079
  /* @__PURE__ */ (0, import_jsx_runtime121.jsx)("span", { className: "text-sm font-medium", children: "Results" }),
39058
39080
  /* @__PURE__ */ (0, import_jsx_runtime121.jsx)(import_design_system105.Button, { size: "sm", variant: "ghost", className: "ml-auto", onClick: () => setRound((n) => n + 1), children: "Refresh" })
39059
39081
  ] }),
39060
- /* @__PURE__ */ (0, import_jsx_runtime121.jsxs)("div", { className: "grid grid-cols-2 gap-2 @xl:grid-cols-4", children: [
39061
- /* @__PURE__ */ (0, import_jsx_runtime121.jsx)(ChartBlock, { block: numberBlock("Views", results.views), subject: form.subject }),
39062
- /* @__PURE__ */ (0, import_jsx_runtime121.jsx)(ChartBlock, { block: numberBlock("Starts", results.starts), subject: form.subject }),
39063
- /* @__PURE__ */ (0, import_jsx_runtime121.jsx)(ChartBlock, { block: numberBlock("Completions", results.completions), subject: form.subject }),
39064
- /* @__PURE__ */ (0, import_jsx_runtime121.jsx)(ChartBlock, { block: numberBlock("Completion rate %", results.completion_rate), subject: form.subject })
39065
- ] }),
39082
+ /* @__PURE__ */ (0, import_jsx_runtime121.jsx)("dl", { className: "grid grid-cols-2 gap-2 @xl:grid-cols-4", "data-records-form-kpis": "", children: [
39083
+ ["Views", results.views],
39084
+ ["Starts", results.starts],
39085
+ ["Completions", results.completions],
39086
+ ["Completion rate", results.completion_rate === null ? null : `${results.completion_rate}%`]
39087
+ ].map(([name, value]) => /* @__PURE__ */ (0, import_jsx_runtime121.jsxs)("div", { className: "flex flex-col gap-1 rounded-md border px-3 py-2", children: [
39088
+ /* @__PURE__ */ (0, import_jsx_runtime121.jsx)("dt", { className: "text-xs text-muted-foreground", children: name }),
39089
+ /* @__PURE__ */ (0, import_jsx_runtime121.jsx)("dd", { className: "text-2xl font-semibold tabular-nums", children: value ?? "\u2014" })
39090
+ ] }, name)) }),
39066
39091
  results.questions.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime121.jsx)(ChartBlock, { block: dropOffBlock(results, label), subject: form.subject }) : null,
39067
39092
  /* @__PURE__ */ (0, import_jsx_runtime121.jsx)("ul", { className: "flex flex-col gap-1 text-xs", "data-records-form-dropoff": "", children: results.questions.map((q) => /* @__PURE__ */ (0, import_jsx_runtime121.jsxs)("li", { className: "flex items-center gap-2", children: [
39068
39093
  /* @__PURE__ */ (0, import_jsx_runtime121.jsx)("span", { className: "min-w-0 flex-1 truncate", children: label(q.field_key) }),
@@ -39101,7 +39126,16 @@ function FormResultsPanel({ form, className }) {
39101
39126
  /* @__PURE__ */ (0, import_jsx_runtime121.jsx)("span", { className: "flex-1 truncate", children: h.value }),
39102
39127
  /* @__PURE__ */ (0, import_jsx_runtime121.jsx)("span", { className: "tabular-nums text-muted-foreground", children: h.count })
39103
39128
  ] }, `${h.key}:${h.value}`)) }) : null,
39104
- results.questions.filter((q) => q.answers.length > 0).map((q) => /* @__PURE__ */ (0, import_jsx_runtime121.jsx)(ChartBlock, { block: answersBlock(q, label(q.field_key)), subject: form.subject }, q.field_key))
39129
+ results.questions.filter((q) => q.answers.length > 0).map(
39130
+ (q) => charted(q.field_key) ? /* @__PURE__ */ (0, import_jsx_runtime121.jsx)(ChartBlock, { block: answersBlock(q, label(q.field_key)), subject: form.subject }, q.field_key) : /* @__PURE__ */ (0, import_jsx_runtime121.jsxs)("section", { className: "flex flex-col gap-1 rounded-md border px-3 py-2 text-xs", "data-records-form-text-answers": q.field_key, children: [
39131
+ /* @__PURE__ */ (0, import_jsx_runtime121.jsx)("span", { className: "font-medium", children: label(q.field_key) }),
39132
+ /* @__PURE__ */ (0, import_jsx_runtime121.jsxs)("span", { className: "text-muted-foreground", children: [
39133
+ q.answered,
39134
+ " answered"
39135
+ ] }),
39136
+ /* @__PURE__ */ (0, import_jsx_runtime121.jsx)("ul", { className: "flex flex-col gap-0.5", children: q.answers.map((a) => /* @__PURE__ */ (0, import_jsx_runtime121.jsx)("li", { className: "truncate", children: a.value }, a.value)) })
39137
+ ] }, q.field_key)
39138
+ )
39105
39139
  ] });
39106
39140
  }
39107
39141
 
@@ -41539,8 +41573,9 @@ function QuestionLogic({
41539
41573
  const jumps = question.jumps ?? [];
41540
41574
  const targets = jumpTargets(questions, index, endings, label);
41541
41575
  const points = question.points ?? {};
41542
- const rawOptions = field2?.options;
41543
- const choices = Array.isArray(rawOptions) ? rawOptions.filter((o) => typeof o === "string") : [];
41576
+ const choiceFields = field2 && isChoiceField(field2) ? [field2] : [];
41577
+ const options = useChoiceOptions(choiceFields, choiceFields.map((f) => f.key));
41578
+ const choices = field2 ? (options.get(field2.key) ?? []).map((o) => ({ key: o.key, label: o.label })) : [];
41544
41579
  const [answer, setAnswer] = (0, import_react229.useState)("");
41545
41580
  const setJump = (i, part) => onChange({ jumps: jumps.map((j, k) => k === i ? { ...j, ...part } : j) });
41546
41581
  return /* @__PURE__ */ (0, import_jsx_runtime131.jsxs)("div", { className: "col-span-2 flex flex-col gap-1.5 text-xs", "data-records-form-logic": question.field, children: [
@@ -41564,25 +41599,25 @@ function QuestionLogic({
41564
41599
  /* @__PURE__ */ (0, import_jsx_runtime131.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
41565
41600
  targets.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime131.jsx)(import_design_system115.Button, { size: "sm", variant: "outline", onClick: () => onChange({ jumps: [...jumps, { when: null, to: targetOf(targets[0].value) }] }), children: "Add a jump" }) : null,
41566
41601
  /* @__PURE__ */ (0, import_jsx_runtime131.jsx)("span", { className: "text-muted-foreground", children: "Points:" }),
41567
- (choices.length > 0 ? choices : Object.keys(points)).map((choice) => /* @__PURE__ */ (0, import_jsx_runtime131.jsxs)("label", { className: "flex items-center gap-1", children: [
41568
- /* @__PURE__ */ (0, import_jsx_runtime131.jsx)("span", { children: choice }),
41602
+ (choices.length > 0 ? choices : Object.keys(points).map((k) => ({ key: k, label: k }))).map((choice) => /* @__PURE__ */ (0, import_jsx_runtime131.jsxs)("label", { className: "flex items-center gap-1", children: [
41603
+ /* @__PURE__ */ (0, import_jsx_runtime131.jsx)("span", { children: choice.label }),
41569
41604
  /* @__PURE__ */ (0, import_jsx_runtime131.jsx)(
41570
41605
  import_design_system115.BasicInput,
41571
41606
  {
41572
41607
  className: "h-7 w-14 text-xs",
41573
41608
  inputMode: "decimal",
41574
- "aria-label": `Points for ${choice}`,
41575
- value: points[choice] ?? "",
41609
+ "aria-label": `Points for ${choice.label}`,
41610
+ value: points[choice.key] ?? "",
41576
41611
  onChange: (e) => {
41577
41612
  const next = { ...points };
41578
41613
  const n = Number(e.target.value);
41579
- if (e.target.value.trim() === "" || Number.isNaN(n)) delete next[choice];
41580
- else next[choice] = n;
41614
+ if (e.target.value.trim() === "" || Number.isNaN(n)) delete next[choice.key];
41615
+ else next[choice.key] = n;
41581
41616
  onChange({ points: Object.keys(next).length > 0 ? next : null });
41582
41617
  }
41583
41618
  }
41584
41619
  )
41585
- ] }, choice)),
41620
+ ] }, choice.key)),
41586
41621
  choices.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime131.jsxs)("span", { className: "flex items-center gap-1", children: [
41587
41622
  /* @__PURE__ */ (0, import_jsx_runtime131.jsx)(import_design_system115.BasicInput, { className: "h-7 w-28 text-xs", "aria-label": "Answer worth points", placeholder: "An answer", value: answer, onChange: (e) => setAnswer(e.target.value) }),
41588
41623
  /* @__PURE__ */ (0, import_jsx_runtime131.jsx)(
@@ -43854,6 +43889,8 @@ function recordsDataSource(client, fallbackSchema = "custom") {
43854
43889
  function makeRecordsDataSource(client, fallbackSchema) {
43855
43890
  const supabase = client;
43856
43891
  return {
43892
+ // PAGE-BUNDLE: the real adapter opens a table page with one `custom.table_page_bundle` call.
43893
+ opensInOneCall: true,
43857
43894
  async rpc(fn, args, options) {
43858
43895
  const schema = options?.schema ?? fallbackSchema;
43859
43896
  if (schema === "custom" && supabase.auth?.getSession && !await hasSession(supabase.auth.getSession)) {
@@ -46167,7 +46204,7 @@ var import_react256 = require("@ai-matrx/records/react");
46167
46204
  var import_core24 = require("@ai-matrx/records/core");
46168
46205
  var import_design_system129 = require("@ai-matrx/design-system");
46169
46206
  var import_jsx_runtime145 = require("react/jsx-runtime");
46170
- var EDIT_LEVELS = /* @__PURE__ */ new Set(["editor", "admin", "owner"]);
46207
+ var EDIT_LEVELS = /* @__PURE__ */ new Set(["edit_content", "editor", "admin", "owner"]);
46171
46208
  function PageScreen({
46172
46209
  pageId,
46173
46210
  newOnTable,
@@ -48361,6 +48398,7 @@ function TableReferenceDialog({
48361
48398
  }
48362
48399
 
48363
48400
  // src/TableDuplicate.tsx
48401
+ var import_format11 = require("@ai-matrx/kit/format");
48364
48402
  var import_react274 = require("react");
48365
48403
  var import_react275 = require("@ai-matrx/records/react");
48366
48404
  var import_design_system139 = require("@ai-matrx/design-system");
@@ -48369,7 +48407,7 @@ function copyNameFor(name) {
48369
48407
  return `${name.trim() || "Table"} (copy)`;
48370
48408
  }
48371
48409
  function duplicateProgressWords(p) {
48372
- if (p.records && p.records.total > 0) return `Copying records \xB7 ${p.records.done.toLocaleString()} of ${p.records.total.toLocaleString()}`;
48410
+ if (p.records && p.records.total > 0) return `Copying records \xB7 ${(0, import_format11.formatCount)(p.records.done)} of ${(0, import_format11.formatCount)(p.records.total)}`;
48373
48411
  if (p.views.total > 0 && p.step === "views") return `Copying views \xB7 ${p.views.done} of ${p.views.total}`;
48374
48412
  return "Copying columns\u2026";
48375
48413
  }
@@ -48425,7 +48463,7 @@ function TableDuplicateDialog({ tableId, name, open, onOpenChange, onDuplicated
48425
48463
  " columns, ",
48426
48464
  phase.copy.copied.views,
48427
48465
  " views",
48428
- phase.copy.copied.withRecords ? `, ${phase.copy.copied.records.toLocaleString()} records` : "",
48466
+ phase.copy.copied.withRecords ? `, ${(0, import_format11.formatCount)(phase.copy.copied.records)} records` : "",
48429
48467
  "."
48430
48468
  ] }),
48431
48469
  phase.copy.notCopied.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime155.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
@@ -48839,7 +48877,7 @@ function useSlotScroll(slot) {
48839
48877
  }
48840
48878
 
48841
48879
  // src/TablePage.tsx
48842
- var import_uuid3 = require("@ai-matrx/kit/uuid");
48880
+ var import_uuid4 = require("@ai-matrx/kit/uuid");
48843
48881
  var import_jsx_runtime158 = require("react/jsx-runtime");
48844
48882
  var RAIL_TITLE = {
48845
48883
  settings: "Table settings",
@@ -49061,7 +49099,7 @@ function openingView(activeView, activeDashboardId) {
49061
49099
  }
49062
49100
  function savedViewFromParam(activeView) {
49063
49101
  const word = typeof activeView === "string" ? activeView.trim() : "";
49064
- return (0, import_uuid3.isUuidShape)(word) ? word.toLowerCase() : null;
49102
+ return (0, import_uuid4.isUuidShape)(word) ? word.toLowerCase() : null;
49065
49103
  }
49066
49104
  function addressForView(view, layout) {
49067
49105
  return view && !view.isDefault ? view.id : layout;
@@ -49382,7 +49420,7 @@ function TablePage({
49382
49420
  const [designateRefused, setDesignateRefused] = (0, import_react279.useState)(null);
49383
49421
  const opensAs = view?.isDefault ? view.layout : null;
49384
49422
  const canDesignate = Boolean(
49385
- rights.write && view && layoutShowing && layoutShowing !== opensAs && storeKeepsLayout(layoutShowing)
49423
+ rights.organise && view && layoutShowing && layoutShowing !== opensAs && storeKeepsLayout(layoutShowing)
49386
49424
  );
49387
49425
  const layoutShowingLabel = layoutShowing ? LAYOUT_LABEL[layoutShowing] : "";
49388
49426
  const designate = async () => {
@@ -49865,7 +49903,7 @@ function TablePage({
49865
49903
  QuestionPanel,
49866
49904
  {
49867
49905
  tableId,
49868
- activeQuestionId: questionsFromLink && activeItemId && (0, import_uuid3.isUuidShape)(activeItemId) ? activeItemId : null,
49906
+ activeQuestionId: questionsFromLink && activeItemId && (0, import_uuid4.isUuidShape)(activeItemId) ? activeItemId : null,
49869
49907
  draft: questionDraft
49870
49908
  }
49871
49909
  ) : main === "archived" ? (
@@ -50090,6 +50128,7 @@ function usePublicReadOnly(client, organizationId, tableId, table, rights) {
50090
50128
  }
50091
50129
 
50092
50130
  // src/entityViews.tsx
50131
+ var import_format12 = require("@ai-matrx/kit/format");
50093
50132
  var import_design_system143 = require("@ai-matrx/design-system");
50094
50133
  var import_data_table24 = require("@ai-matrx/design-system/data-table");
50095
50134
  var import_react281 = require("react");
@@ -50280,7 +50319,7 @@ function EntityTablePageBody({
50280
50319
  return /* @__PURE__ */ (0, import_jsx_runtime159.jsxs)("div", { className: (0, import_design_system144.cn)("flex min-w-0 flex-col gap-2", className), "data-records-entity-page": source.token, children: [
50281
50320
  presentation === "page" || boardable || entity.creatable ? /* @__PURE__ */ (0, import_jsx_runtime159.jsxs)("div", { className: "flex min-w-0 items-center gap-2", children: [
50282
50321
  presentation === "page" ? /* @__PURE__ */ (0, import_jsx_runtime159.jsx)("h2", { className: "truncate text-base font-semibold", children: entity.label ?? (entity.loading ? "Loading\u2026" : "Table") }) : null,
50283
- presentation === "page" ? /* @__PURE__ */ (0, import_jsx_runtime159.jsx)("span", { className: "text-xs tabular-nums text-muted-foreground", children: entity.loading || entity.total === null ? "" : entity.total.toLocaleString() }) : null,
50322
+ presentation === "page" ? /* @__PURE__ */ (0, import_jsx_runtime159.jsx)("span", { className: "text-xs tabular-nums text-muted-foreground", children: entity.loading || entity.total === null ? "" : (0, import_format12.formatCount)(entity.total) }) : null,
50284
50323
  /* @__PURE__ */ (0, import_jsx_runtime159.jsxs)("span", { className: "ml-auto flex items-center gap-1", children: [
50285
50324
  boardable ? /* @__PURE__ */ (0, import_jsx_runtime159.jsxs)(import_jsx_runtime159.Fragment, { children: [
50286
50325
  /* @__PURE__ */ (0, import_jsx_runtime159.jsx)(import_design_system144.Button, { size: "sm", variant: layout === "grid" ? "secondary" : "ghost", "aria-pressed": layout === "grid", onClick: () => choose("grid"), children: "Table" }),