@ai-matrx/records-ui 0.104.2 → 0.105.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 CHANGED
@@ -1,5 +1,41 @@
1
1
  # Changelog — @ai-matrx/records-ui
2
2
 
3
+ ## 0.105.1
4
+
5
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
6
+ `git diff npm/records-ui/v0.104.2..npm/records-ui/v0.105.1 -- apps/shared/records-ui`).
7
+ No source changes intended and no consumer action required.
8
+
9
+ ## 0.105.0
10
+
11
+ 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.
12
+
13
+ **Consumer action:** none.
14
+
15
+ ## 0.104.5
16
+
17
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
18
+ `git diff npm/records-ui/v0.104.2..npm/records-ui/v0.104.5 -- apps/shared/records-ui`).
19
+ No source changes intended and no consumer action required.
20
+
21
+ ## 0.104.4
22
+
23
+ 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.
24
+
25
+ Consumer action: none.
26
+
27
+ ## 0.104.3
28
+
29
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
30
+ `git diff npm/records-ui/v0.104.2..npm/records-ui/v0.104.3 -- apps/shared/records-ui`).
31
+ No source changes intended and no consumer action required.
32
+
33
+ ## 0.104.5
34
+
35
+ - `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`.
36
+
37
+ **Consumer action:** none.
38
+
3
39
  ## 0.104.2
4
40
 
5
41
  - (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"),
@@ -25057,6 +25064,7 @@ function createEntityRealtime(options = {}) {
25057
25064
  }
25058
25065
 
25059
25066
  // ../entity-data/src/port/port.ts
25067
+ var import_uuid3 = require("@ai-matrx/kit/uuid");
25060
25068
  var import_records38 = require("@ai-matrx/records");
25061
25069
 
25062
25070
  // ../entity-data/src/core/wire.ts
@@ -25157,8 +25165,6 @@ function toColumns(wire) {
25157
25165
  }
25158
25166
 
25159
25167
  // ../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
25168
  function toWireValue(value) {
25163
25169
  if (isRef(value)) return value.id;
25164
25170
  if (Array.isArray(value) && value.length > 0 && value.every(isRef)) {
@@ -25358,7 +25364,7 @@ function createEntityDataPort(options) {
25358
25364
  async get(alias, id) {
25359
25365
  const binding = bindingOf(alias);
25360
25366
  if (!binding) return noSuchAlias(alias);
25361
- if (!isUuidShape3(id)) return refusal2("invalid_argument", `"${String(id)}" is not a row id.`);
25367
+ if (!(0, import_uuid3.isUuidShape)(id)) return refusal2("invalid_argument", `"${String(id)}" is not a row id.`);
25362
25368
  return getCoalesced(alias, binding.token, id);
25363
25369
  },
25364
25370
  async create(alias, values) {
@@ -25378,20 +25384,20 @@ function createEntityDataPort(options) {
25378
25384
  async update(alias, id, patch, baseVersion) {
25379
25385
  const binding = bindingOf(alias);
25380
25386
  if (!binding) return noSuchAlias(alias);
25381
- if (!isUuidShape3(id)) return refusal2("invalid_argument", `"${String(id)}" is not a row id.`);
25387
+ if (!(0, import_uuid3.isUuidShape)(id)) return refusal2("invalid_argument", `"${String(id)}" is not a row id.`);
25382
25388
  const answer = await client.update({ token: binding.token, id, baseVersion, patch: toWireValues(patch), opId: (0, import_records38.mintOpId)() });
25383
25389
  return readBack(alias, answer, "The change");
25384
25390
  },
25385
25391
  async archive(alias, id) {
25386
25392
  const binding = bindingOf(alias);
25387
25393
  if (!binding) return noSuchAlias(alias);
25388
- if (!isUuidShape3(id)) return refusal2("invalid_argument", `"${String(id)}" is not a row id.`);
25394
+ if (!(0, import_uuid3.isUuidShape)(id)) return refusal2("invalid_argument", `"${String(id)}" is not a row id.`);
25389
25395
  return done(await client.archive({ token: binding.token, ids: [id], opId: (0, import_records38.mintOpId)() }));
25390
25396
  },
25391
25397
  async restore(alias, id) {
25392
25398
  const binding = bindingOf(alias);
25393
25399
  if (!binding) return noSuchAlias(alias);
25394
- if (!isUuidShape3(id)) return refusal2("invalid_argument", `"${String(id)}" is not a row id.`);
25400
+ if (!(0, import_uuid3.isUuidShape)(id)) return refusal2("invalid_argument", `"${String(id)}" is not a row id.`);
25395
25401
  return done(await client.unarchive({ token: binding.token, ids: [id], opId: (0, import_records38.mintOpId)() }));
25396
25402
  },
25397
25403
  subscribe(event, listener) {
@@ -25757,6 +25763,7 @@ var import_react128 = require("@ai-matrx/records/react");
25757
25763
  var import_core19 = require("@ai-matrx/records/core");
25758
25764
 
25759
25765
  // src/viewExport.ts
25766
+ var import_format5 = require("@ai-matrx/kit/format");
25760
25767
  var VIEW_EXPORT_PAGE = 500;
25761
25768
  var VIEW_EXPORT_CEILING = 5e3;
25762
25769
  function viewColumns(fields, presentation) {
@@ -25795,7 +25802,7 @@ async function readViewRows(client, tableId, fields, view, ceiling = VIEW_EXPORT
25795
25802
  }
25796
25803
  }
25797
25804
  function cutWords(written) {
25798
- return `First ${written.toLocaleString("en-US")} rows only`;
25805
+ return `First ${(0, import_format5.formatCount)(written, { locale: "en-US" })} rows only`;
25799
25806
  }
25800
25807
 
25801
25808
  // src/ExportMenu.tsx
@@ -26245,7 +26252,7 @@ var import_clipboard3 = require("@ai-matrx/kit/clipboard");
26245
26252
  var import_design_system66 = require("@ai-matrx/design-system");
26246
26253
  var import_react133 = require("react");
26247
26254
  var import_react134 = require("@ai-matrx/records/react");
26248
- var import_format5 = require("@ai-matrx/kit/format");
26255
+ var import_format6 = require("@ai-matrx/kit/format");
26249
26256
  var import_records39 = require("@ai-matrx/records");
26250
26257
  var import_design_system67 = require("@ai-matrx/design-system");
26251
26258
 
@@ -26582,7 +26589,7 @@ function BookingBuilder({ tableId, bookingId, onSaved, onClose, startNew, classN
26582
26589
  className: "h-9 rounded border bg-background px-2 text-sm",
26583
26590
  value: lead,
26584
26591
  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))
26592
+ 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
26593
  }
26587
26594
  )
26588
26595
  ] }),
@@ -26935,7 +26942,7 @@ function tableActions(input) {
26935
26942
  // custom.view_designate asks editor on the table — `canDesignate` in TablePage.
26936
26943
  action(
26937
26944
  { id: "make-default", label: makeDefaultWords(host.layoutLabel), icon: "layout-grid", group: "manage", rank: 4 },
26938
- "write",
26945
+ "organise",
26939
26946
  input,
26940
26947
  bind(host.makeDefault, table)
26941
26948
  ),
@@ -28114,7 +28121,7 @@ function SaveToTable({ source, shapeIndex = 0, onOpenTable, onSaved, onClose, cl
28114
28121
  const levels = (0, import_react144.useMyLevels)(own3.map((t) => t.id));
28115
28122
  const writable = own3.filter((t) => {
28116
28123
  const level = levels.data?.[t.id];
28117
- return level === "editor" || level === "admin";
28124
+ return level === "edit_content" || level === "editor" || level === "admin";
28118
28125
  }).sort((a, b) => a.name.localeCompare(b.name));
28119
28126
  const [tableId, setTableId] = (0, import_react143.useState)("");
28120
28127
  const proposedName = shape?.name ?? source.title ?? "Saved table";
@@ -30458,7 +30465,7 @@ var import_react188 = require("@ai-matrx/records/react");
30458
30465
  var import_records47 = require("@ai-matrx/records");
30459
30466
  var import_design_system93 = require("@ai-matrx/design-system");
30460
30467
  var import_data_table20 = require("@ai-matrx/design-system/data-table");
30461
- var import_format6 = require("@ai-matrx/kit/format");
30468
+ var import_format7 = require("@ai-matrx/kit/format");
30462
30469
 
30463
30470
  // src/recordMoves.tsx
30464
30471
  var import_react171 = require("react");
@@ -30695,10 +30702,10 @@ function recordActions(input) {
30695
30702
  input,
30696
30703
  copyText13 ? () => copyText13(url, record) : void 0
30697
30704
  ),
30698
- action2({ id: "duplicate", label: "Duplicate", icon: "copy", group: "organize", verb: "duplicate" }, "write", input, bind2(host.duplicate, record)),
30705
+ action2({ id: "duplicate", label: "Duplicate", icon: "copy", group: "organize", verb: "duplicate" }, "organise", input, bind2(host.duplicate, record)),
30699
30706
  action2(
30700
30707
  { id: "merge", label: "Merge with\u2026", icon: "git-merge", group: "organize" },
30701
- "write",
30708
+ "organise",
30702
30709
  input,
30703
30710
  bind2(host.merge, record)
30704
30711
  ),
@@ -34333,9 +34340,9 @@ function ViewPager({ shown: shown4, total, onMore, step: step2 }) {
34333
34340
  return /* @__PURE__ */ (0, import_jsx_runtime109.jsxs)("p", { className: "flex items-center gap-2 text-xs text-muted-foreground", "data-view-pager": "", children: [
34334
34341
  /* @__PURE__ */ (0, import_jsx_runtime109.jsxs)("span", { children: [
34335
34342
  "Showing ",
34336
- (0, import_format6.formatCount)(shown4),
34343
+ (0, import_format7.formatCount)(shown4),
34337
34344
  " of ",
34338
- (0, import_format6.formatCount)(total),
34345
+ (0, import_format7.formatCount)(total),
34339
34346
  " records."
34340
34347
  ] }),
34341
34348
  /* @__PURE__ */ (0, import_jsx_runtime109.jsxs)(import_design_system93.Button, { size: "sm", variant: "outline", onClick: onMore, children: [
@@ -34805,8 +34812,8 @@ function ArchivedView({
34805
34812
  onRestored?.(recordId);
34806
34813
  };
34807
34814
  const titleKey = table.data?.title_field ?? null;
34808
- const mayRestore = rights.write;
34809
- const whyNot = mayRestore ? "" : mayNotRestoreLine(rights.why("write"));
34815
+ const mayRestore = rights.organise;
34816
+ const whyNot = mayRestore ? "" : mayNotRestoreLine(rights.why("organise"));
34810
34817
  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
34818
  /* @__PURE__ */ (0, import_jsx_runtime110.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
34812
34819
  /* @__PURE__ */ (0, import_jsx_runtime110.jsx)("span", { className: "text-sm font-medium", children: "Archived" }),
@@ -35548,7 +35555,7 @@ var import_react201 = require("react");
35548
35555
  var import_react202 = require("@ai-matrx/records/react");
35549
35556
  var import_design_system99 = require("@ai-matrx/design-system");
35550
35557
  var import_data_table21 = require("@ai-matrx/design-system/data-table");
35551
- var import_format7 = require("@ai-matrx/kit/format");
35558
+ var import_format8 = require("@ai-matrx/kit/format");
35552
35559
 
35553
35560
  // src/ChecklistRunner.tsx
35554
35561
  var import_react199 = require("react");
@@ -36928,7 +36935,7 @@ function ActionInbox({
36928
36935
  size: "sm",
36929
36936
  variant: "ghost",
36930
36937
  className: "h-6 px-2 text-[11px]",
36931
- title: (0, import_format7.formatAbsoluteDate)(c.until),
36938
+ title: (0, import_format8.formatAbsoluteDate)(c.until),
36932
36939
  onClick: () => void snooze(item.item_id, item.title, c.until),
36933
36940
  children: [
36934
36941
  c.label,
@@ -36970,7 +36977,7 @@ function ActionInbox({
36970
36977
  /* @__PURE__ */ (0, import_jsx_runtime115.jsx)("span", { className: "font-medium", children: held[item.item_id].approve ? "Approved" : "Declined" }),
36971
36978
  /* @__PURE__ */ (0, import_jsx_runtime115.jsxs)("span", { className: "text-[11px] text-muted-foreground tabular-nums", children: [
36972
36979
  "\xB7 made in ",
36973
- (0, import_format7.formatDurationMs)(Math.max(0, Math.ceil((held[item.item_id].until - Date.now()) / 1e3) * 1e3), { style: "compact" })
36980
+ (0, import_format8.formatDurationMs)(Math.max(0, Math.ceil((held[item.item_id].until - Date.now()) / 1e3) * 1e3), { style: "compact" })
36974
36981
  ] }),
36975
36982
  /* @__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
36983
  ] })
@@ -37195,7 +37202,7 @@ function ActionInbox({
37195
37202
  var import_react203 = require("react");
37196
37203
  var import_react204 = require("@ai-matrx/records/react");
37197
37204
  var import_design_system100 = require("@ai-matrx/design-system");
37198
- var import_format8 = require("@ai-matrx/kit/format");
37205
+ var import_format9 = require("@ai-matrx/kit/format");
37199
37206
  var import_text_case14 = require("@ai-matrx/kit/text-case");
37200
37207
  var import_jsx_runtime116 = require("react/jsx-runtime");
37201
37208
  function HistoryPanel({ tableId, recordId, onAskAboutField, className }) {
@@ -37488,7 +37495,7 @@ function whenText(at) {
37488
37495
  const then = new Date(at);
37489
37496
  const age = Date.now() - then.getTime();
37490
37497
  if (Number.isNaN(age)) return "\u2014";
37491
- if (age < 864e5) return (0, import_format8.formatRelativeTime)(then, { style: "intl" });
37498
+ if (age < 864e5) return (0, import_format9.formatRelativeTime)(then, { style: "intl" });
37492
37499
  if (age < 7 * 864e5) {
37493
37500
  return then.toLocaleString(void 0, { weekday: "short", hour: "numeric", minute: "2-digit" });
37494
37501
  }
@@ -38134,7 +38141,7 @@ var import_recharts = require("recharts");
38134
38141
  var import_records51 = require("@ai-matrx/records");
38135
38142
  var import_core21 = require("@ai-matrx/records/core");
38136
38143
  var import_design_system104 = require("@ai-matrx/design-system");
38137
- var import_format9 = require("@ai-matrx/kit/format");
38144
+ var import_format10 = require("@ai-matrx/kit/format");
38138
38145
 
38139
38146
  // src/linkWords.ts
38140
38147
  var import_react209 = require("react");
@@ -38742,7 +38749,7 @@ function BigNumber({
38742
38749
  /* @__PURE__ */ (0, import_jsx_runtime120.jsx)("span", { className: "text-2xl font-semibold tabular-nums", children: pretty(total) }),
38743
38750
  windows ? /* @__PURE__ */ (0, import_jsx_runtime120.jsx)(Trend, { pct: block.totals?.change_pct, aimingUp: !!target, className: "text-sm" }) : null
38744
38751
  ] }),
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"}` })
38752
+ /* @__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
38753
  ] });
38747
38754
  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
38755
  /* @__PURE__ */ (0, import_jsx_runtime120.jsxs)("span", { className: "text-right text-xs text-muted-foreground", children: [
@@ -38861,7 +38868,7 @@ var HEIGHT = CHART_PLOT_PX;
38861
38868
  function compactTick(value) {
38862
38869
  if (typeof value !== "number" || !Number.isFinite(value)) return String(value ?? "");
38863
38870
  if (Math.abs(value) < 1e3 && !Number.isInteger(value)) return String(Math.round(value * 10) / 10);
38864
- return (0, import_format9.formatCount)(value, { style: "compact" });
38871
+ return (0, import_format10.formatCount)(value, { style: "compact" });
38865
38872
  }
38866
38873
  function Drawing({
38867
38874
  kind,
@@ -39000,9 +39007,7 @@ function onlyWholeNumbers(rows) {
39000
39007
 
39001
39008
  // src/FormResultsPanel.tsx
39002
39009
  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
- }
39010
+ var CHARTED_KINDS = /* @__PURE__ */ new Set(["select", "multi_select", "checkbox", "number", "currency", "percent", "member", "relation"]);
39006
39011
  function dropOffBlock(results, label) {
39007
39012
  return {
39008
39013
  title: "Reached each question",
@@ -39044,6 +39049,13 @@ function FormResultsPanel({ form, className }) {
39044
39049
  cancelled = true;
39045
39050
  };
39046
39051
  }, [client, form.id, round]);
39052
+ const charted = (0, import_react215.useMemo)(() => {
39053
+ const byKey = new Map((fields.data ?? []).map((f) => [f.key, f]));
39054
+ return (key) => {
39055
+ const field2 = byKey.get(key);
39056
+ return field2 ? CHARTED_KINDS.has(editorKindFor(field2)) : false;
39057
+ };
39058
+ }, [fields.data]);
39047
39059
  const label = (0, import_react215.useMemo)(() => {
39048
39060
  const byKey = new Map((fields.data ?? []).map((f) => [f.key, f]));
39049
39061
  const asks = new Map((form.questions ?? []).map((q) => [q.field, q.ask]));
@@ -39057,12 +39069,15 @@ function FormResultsPanel({ form, className }) {
39057
39069
  /* @__PURE__ */ (0, import_jsx_runtime121.jsx)("span", { className: "text-sm font-medium", children: "Results" }),
39058
39070
  /* @__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
39071
  ] }),
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
- ] }),
39072
+ /* @__PURE__ */ (0, import_jsx_runtime121.jsx)("dl", { className: "grid grid-cols-2 gap-2 @xl:grid-cols-4", "data-records-form-kpis": "", children: [
39073
+ ["Views", results.views],
39074
+ ["Starts", results.starts],
39075
+ ["Completions", results.completions],
39076
+ ["Completion rate", results.completion_rate === null ? null : `${results.completion_rate}%`]
39077
+ ].map(([name, value]) => /* @__PURE__ */ (0, import_jsx_runtime121.jsxs)("div", { className: "flex flex-col gap-1 rounded-md border px-3 py-2", children: [
39078
+ /* @__PURE__ */ (0, import_jsx_runtime121.jsx)("dt", { className: "text-xs text-muted-foreground", children: name }),
39079
+ /* @__PURE__ */ (0, import_jsx_runtime121.jsx)("dd", { className: "text-2xl font-semibold tabular-nums", children: value ?? "\u2014" })
39080
+ ] }, name)) }),
39066
39081
  results.questions.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime121.jsx)(ChartBlock, { block: dropOffBlock(results, label), subject: form.subject }) : null,
39067
39082
  /* @__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
39083
  /* @__PURE__ */ (0, import_jsx_runtime121.jsx)("span", { className: "min-w-0 flex-1 truncate", children: label(q.field_key) }),
@@ -39101,7 +39116,16 @@ function FormResultsPanel({ form, className }) {
39101
39116
  /* @__PURE__ */ (0, import_jsx_runtime121.jsx)("span", { className: "flex-1 truncate", children: h.value }),
39102
39117
  /* @__PURE__ */ (0, import_jsx_runtime121.jsx)("span", { className: "tabular-nums text-muted-foreground", children: h.count })
39103
39118
  ] }, `${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))
39119
+ results.questions.filter((q) => q.answers.length > 0).map(
39120
+ (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: [
39121
+ /* @__PURE__ */ (0, import_jsx_runtime121.jsx)("span", { className: "font-medium", children: label(q.field_key) }),
39122
+ /* @__PURE__ */ (0, import_jsx_runtime121.jsxs)("span", { className: "text-muted-foreground", children: [
39123
+ q.answered,
39124
+ " answered"
39125
+ ] }),
39126
+ /* @__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)) })
39127
+ ] }, q.field_key)
39128
+ )
39105
39129
  ] });
39106
39130
  }
39107
39131
 
@@ -41539,8 +41563,9 @@ function QuestionLogic({
41539
41563
  const jumps = question.jumps ?? [];
41540
41564
  const targets = jumpTargets(questions, index, endings, label);
41541
41565
  const points = question.points ?? {};
41542
- const rawOptions = field2?.options;
41543
- const choices = Array.isArray(rawOptions) ? rawOptions.filter((o) => typeof o === "string") : [];
41566
+ const choiceFields = field2 && isChoiceField(field2) ? [field2] : [];
41567
+ const options = useChoiceOptions(choiceFields, choiceFields.map((f) => f.key));
41568
+ const choices = field2 ? (options.get(field2.key) ?? []).map((o) => ({ key: o.key, label: o.label })) : [];
41544
41569
  const [answer, setAnswer] = (0, import_react229.useState)("");
41545
41570
  const setJump = (i, part) => onChange({ jumps: jumps.map((j, k) => k === i ? { ...j, ...part } : j) });
41546
41571
  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 +41589,25 @@ function QuestionLogic({
41564
41589
  /* @__PURE__ */ (0, import_jsx_runtime131.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
41565
41590
  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
41591
  /* @__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 }),
41592
+ (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: [
41593
+ /* @__PURE__ */ (0, import_jsx_runtime131.jsx)("span", { children: choice.label }),
41569
41594
  /* @__PURE__ */ (0, import_jsx_runtime131.jsx)(
41570
41595
  import_design_system115.BasicInput,
41571
41596
  {
41572
41597
  className: "h-7 w-14 text-xs",
41573
41598
  inputMode: "decimal",
41574
- "aria-label": `Points for ${choice}`,
41575
- value: points[choice] ?? "",
41599
+ "aria-label": `Points for ${choice.label}`,
41600
+ value: points[choice.key] ?? "",
41576
41601
  onChange: (e) => {
41577
41602
  const next = { ...points };
41578
41603
  const n = Number(e.target.value);
41579
- if (e.target.value.trim() === "" || Number.isNaN(n)) delete next[choice];
41580
- else next[choice] = n;
41604
+ if (e.target.value.trim() === "" || Number.isNaN(n)) delete next[choice.key];
41605
+ else next[choice.key] = n;
41581
41606
  onChange({ points: Object.keys(next).length > 0 ? next : null });
41582
41607
  }
41583
41608
  }
41584
41609
  )
41585
- ] }, choice)),
41610
+ ] }, choice.key)),
41586
41611
  choices.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime131.jsxs)("span", { className: "flex items-center gap-1", children: [
41587
41612
  /* @__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
41613
  /* @__PURE__ */ (0, import_jsx_runtime131.jsx)(
@@ -43854,6 +43879,8 @@ function recordsDataSource(client, fallbackSchema = "custom") {
43854
43879
  function makeRecordsDataSource(client, fallbackSchema) {
43855
43880
  const supabase = client;
43856
43881
  return {
43882
+ // PAGE-BUNDLE: the real adapter opens a table page with one `custom.table_page_bundle` call.
43883
+ opensInOneCall: true,
43857
43884
  async rpc(fn, args, options) {
43858
43885
  const schema = options?.schema ?? fallbackSchema;
43859
43886
  if (schema === "custom" && supabase.auth?.getSession && !await hasSession(supabase.auth.getSession)) {
@@ -46167,7 +46194,7 @@ var import_react256 = require("@ai-matrx/records/react");
46167
46194
  var import_core24 = require("@ai-matrx/records/core");
46168
46195
  var import_design_system129 = require("@ai-matrx/design-system");
46169
46196
  var import_jsx_runtime145 = require("react/jsx-runtime");
46170
- var EDIT_LEVELS = /* @__PURE__ */ new Set(["editor", "admin", "owner"]);
46197
+ var EDIT_LEVELS = /* @__PURE__ */ new Set(["edit_content", "editor", "admin", "owner"]);
46171
46198
  function PageScreen({
46172
46199
  pageId,
46173
46200
  newOnTable,
@@ -48361,6 +48388,7 @@ function TableReferenceDialog({
48361
48388
  }
48362
48389
 
48363
48390
  // src/TableDuplicate.tsx
48391
+ var import_format11 = require("@ai-matrx/kit/format");
48364
48392
  var import_react274 = require("react");
48365
48393
  var import_react275 = require("@ai-matrx/records/react");
48366
48394
  var import_design_system139 = require("@ai-matrx/design-system");
@@ -48369,7 +48397,7 @@ function copyNameFor(name) {
48369
48397
  return `${name.trim() || "Table"} (copy)`;
48370
48398
  }
48371
48399
  function duplicateProgressWords(p) {
48372
- if (p.records && p.records.total > 0) return `Copying records \xB7 ${p.records.done.toLocaleString()} of ${p.records.total.toLocaleString()}`;
48400
+ 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
48401
  if (p.views.total > 0 && p.step === "views") return `Copying views \xB7 ${p.views.done} of ${p.views.total}`;
48374
48402
  return "Copying columns\u2026";
48375
48403
  }
@@ -48425,7 +48453,7 @@ function TableDuplicateDialog({ tableId, name, open, onOpenChange, onDuplicated
48425
48453
  " columns, ",
48426
48454
  phase.copy.copied.views,
48427
48455
  " views",
48428
- phase.copy.copied.withRecords ? `, ${phase.copy.copied.records.toLocaleString()} records` : "",
48456
+ phase.copy.copied.withRecords ? `, ${(0, import_format11.formatCount)(phase.copy.copied.records)} records` : "",
48429
48457
  "."
48430
48458
  ] }),
48431
48459
  phase.copy.notCopied.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime155.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
@@ -48839,7 +48867,7 @@ function useSlotScroll(slot) {
48839
48867
  }
48840
48868
 
48841
48869
  // src/TablePage.tsx
48842
- var import_uuid3 = require("@ai-matrx/kit/uuid");
48870
+ var import_uuid4 = require("@ai-matrx/kit/uuid");
48843
48871
  var import_jsx_runtime158 = require("react/jsx-runtime");
48844
48872
  var RAIL_TITLE = {
48845
48873
  settings: "Table settings",
@@ -49061,7 +49089,7 @@ function openingView(activeView, activeDashboardId) {
49061
49089
  }
49062
49090
  function savedViewFromParam(activeView) {
49063
49091
  const word = typeof activeView === "string" ? activeView.trim() : "";
49064
- return (0, import_uuid3.isUuidShape)(word) ? word.toLowerCase() : null;
49092
+ return (0, import_uuid4.isUuidShape)(word) ? word.toLowerCase() : null;
49065
49093
  }
49066
49094
  function addressForView(view, layout) {
49067
49095
  return view && !view.isDefault ? view.id : layout;
@@ -49382,7 +49410,7 @@ function TablePage({
49382
49410
  const [designateRefused, setDesignateRefused] = (0, import_react279.useState)(null);
49383
49411
  const opensAs = view?.isDefault ? view.layout : null;
49384
49412
  const canDesignate = Boolean(
49385
- rights.write && view && layoutShowing && layoutShowing !== opensAs && storeKeepsLayout(layoutShowing)
49413
+ rights.organise && view && layoutShowing && layoutShowing !== opensAs && storeKeepsLayout(layoutShowing)
49386
49414
  );
49387
49415
  const layoutShowingLabel = layoutShowing ? LAYOUT_LABEL[layoutShowing] : "";
49388
49416
  const designate = async () => {
@@ -49865,7 +49893,7 @@ function TablePage({
49865
49893
  QuestionPanel,
49866
49894
  {
49867
49895
  tableId,
49868
- activeQuestionId: questionsFromLink && activeItemId && (0, import_uuid3.isUuidShape)(activeItemId) ? activeItemId : null,
49896
+ activeQuestionId: questionsFromLink && activeItemId && (0, import_uuid4.isUuidShape)(activeItemId) ? activeItemId : null,
49869
49897
  draft: questionDraft
49870
49898
  }
49871
49899
  ) : main === "archived" ? (
@@ -50090,6 +50118,7 @@ function usePublicReadOnly(client, organizationId, tableId, table, rights) {
50090
50118
  }
50091
50119
 
50092
50120
  // src/entityViews.tsx
50121
+ var import_format12 = require("@ai-matrx/kit/format");
50093
50122
  var import_design_system143 = require("@ai-matrx/design-system");
50094
50123
  var import_data_table24 = require("@ai-matrx/design-system/data-table");
50095
50124
  var import_react281 = require("react");
@@ -50280,7 +50309,7 @@ function EntityTablePageBody({
50280
50309
  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
50310
  presentation === "page" || boardable || entity.creatable ? /* @__PURE__ */ (0, import_jsx_runtime159.jsxs)("div", { className: "flex min-w-0 items-center gap-2", children: [
50282
50311
  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,
50312
+ 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
50313
  /* @__PURE__ */ (0, import_jsx_runtime159.jsxs)("span", { className: "ml-auto flex items-center gap-1", children: [
50285
50314
  boardable ? /* @__PURE__ */ (0, import_jsx_runtime159.jsxs)(import_jsx_runtime159.Fragment, { children: [
50286
50315
  /* @__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" }),