@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/dist/index.d.cts CHANGED
@@ -3,8 +3,8 @@ import { ReactNode, KeyboardEvent, ReactElement, ComponentType, RefObject, SVGAt
3
3
  import * as _ai_matrx_records from '@ai-matrx/records';
4
4
  import { Uuid, RowAction, Field, RowActionStep, RecordScopeContext, EnrichCell, RecordsError, RecordDocument, Table, ReadRow, RecordRead, PermissionLevel, AggregateFilter, RelationDisplay, WriteConflict, RecordsErrorCode, TableDecorations, RecordFilter, RuleExpression, RecordsResult, SavedViewRow, StoreFilter as StoreFilter$1, ImportRun, DeclarableFieldKind, NewerFieldKind, ParityFieldType, NewFieldDeclaration, ChoiceRehome, FieldOptionEdit, StoredRecord, ChoiceNudge, HiddenFieldNotice, ValueEnvelope, ChecklistRequirementKind, ContextPolicy, WorkDueState, FieldSensitivity, StageRuleOnFail, SubscriptionCadence, WorkApprovalState, WorkInboxKind, FieldKind, ArchiveLane, ArchivedRow, WorkInboxView, WorkInboxItem, PortalAccent, FormSummary, PortalCard, PortalPrincipal, PortalPreviewRow, PublicPicture, PortalStyle, MySubscription, DashboardBlockKind, AggregateBucket, AggregateMeasure, AggregateCompare, DashboardTarget, DashboardBlock, DashboardSummary, AggregateGroupValue, DocTemplateRow, DocRenderRow, QuietHours, DashboardBlockResult, ChecklistRunStep, BookingSummary, CaptureSheetFace, CaptureField, AnonTokenBinding, RecordsConfig, RecordsActor, RecordsDataSource, TableDuplicated, HistoryActor, FieldHistoryEntry, DrillDefinition, DrillSource, TableDuplicateProgress, TableArchiveProgress } from '@ai-matrx/records';
5
5
  import { ReversibleAnnouncement } from '@ai-matrx/kit/reversible';
6
- import { W as WhatYouMayDo, T as TableActionHost, O as ObjectAction, a as ObjectActionIcon } from './objectActions-XkhxU-DP.cjs';
7
- export { A as ACTION_CHECKING_ACCESS_REASON, b as ACTION_NOT_HERE_REASON, c as ACTION_NO_ACCESS_REASON, d as ACTION_PLATFORM_TABLE_REASON, B as BuiltOnDestination, C as Capability, D as DEFAULT_ACTION_ORIGIN, K as KERNEL_REASON, L as LEVEL_WORD, N as NOT_ANSWERED_YET, e as OBJECT_ACTION_GROUPS, f as ObjectActionDropdownEntry, g as ObjectActionGroup, h as ObjectActionGroupDef, i as ObjectActionSection, S as STANDARD_VERBS, j as STRIP_FEATURE_ROWS, k as StandardVerbId, l as StripAndRows, m as TableActionSubject, n as TableActionsInput, o as dropdownActionIds, p as dropdownActions, q as groupObjectActions, r as levelNeededFor, s as rightsReason, t as tableActions, u as tableLink, v as toDropdownEntries, w as toStripAndRows, x as whatYouMayDo, y as whatYouMayDoWithTable, z as withExtensions } from './objectActions-XkhxU-DP.cjs';
6
+ import { W as WhatYouMayDo, T as TableActionHost, O as ObjectAction, a as ObjectActionIcon } from './objectActions-B7AUBkcl.cjs';
7
+ export { A as ACTION_CHECKING_ACCESS_REASON, b as ACTION_NOT_HERE_REASON, c as ACTION_NO_ACCESS_REASON, d as ACTION_PLATFORM_TABLE_REASON, B as BuiltOnDestination, C as Capability, D as DEFAULT_ACTION_ORIGIN, K as KERNEL_REASON, L as LEVEL_WORD, N as NOT_ANSWERED_YET, e as OBJECT_ACTION_GROUPS, f as ObjectActionDropdownEntry, g as ObjectActionGroup, h as ObjectActionGroupDef, i as ObjectActionSection, S as STANDARD_VERBS, j as STRIP_FEATURE_ROWS, k as StandardVerbId, l as StripAndRows, m as TableActionSubject, n as TableActionsInput, o as dropdownActionIds, p as dropdownActions, q as groupObjectActions, r as levelNeededFor, s as rightsReason, t as tableActions, u as tableLink, v as toDropdownEntries, w as toStripAndRows, x as whatYouMayDo, y as whatYouMayDoWithTable, z as withExtensions } from './objectActions-B7AUBkcl.cjs';
8
8
  import { Uuid as Uuid$1, Table as Table$1, Field as Field$1, ReadRow as ReadRow$1, StoreFilter, useRecordsClient } from '@ai-matrx/records/react';
9
9
  import { RecordsClient, ParsedImport, NewFieldSpec } from '@ai-matrx/records/core';
10
10
  export { DEFAULT_FIELDS, DeclareResult, NewFieldSpec, NewTableSpec, addFields, declareTable, fieldDeclarationFor, tokenFor } from '@ai-matrx/records/core';
package/dist/index.d.ts CHANGED
@@ -3,8 +3,8 @@ import { ReactNode, KeyboardEvent, ReactElement, ComponentType, RefObject, SVGAt
3
3
  import * as _ai_matrx_records from '@ai-matrx/records';
4
4
  import { Uuid, RowAction, Field, RowActionStep, RecordScopeContext, EnrichCell, RecordsError, RecordDocument, Table, ReadRow, RecordRead, PermissionLevel, AggregateFilter, RelationDisplay, WriteConflict, RecordsErrorCode, TableDecorations, RecordFilter, RuleExpression, RecordsResult, SavedViewRow, StoreFilter as StoreFilter$1, ImportRun, DeclarableFieldKind, NewerFieldKind, ParityFieldType, NewFieldDeclaration, ChoiceRehome, FieldOptionEdit, StoredRecord, ChoiceNudge, HiddenFieldNotice, ValueEnvelope, ChecklistRequirementKind, ContextPolicy, WorkDueState, FieldSensitivity, StageRuleOnFail, SubscriptionCadence, WorkApprovalState, WorkInboxKind, FieldKind, ArchiveLane, ArchivedRow, WorkInboxView, WorkInboxItem, PortalAccent, FormSummary, PortalCard, PortalPrincipal, PortalPreviewRow, PublicPicture, PortalStyle, MySubscription, DashboardBlockKind, AggregateBucket, AggregateMeasure, AggregateCompare, DashboardTarget, DashboardBlock, DashboardSummary, AggregateGroupValue, DocTemplateRow, DocRenderRow, QuietHours, DashboardBlockResult, ChecklistRunStep, BookingSummary, CaptureSheetFace, CaptureField, AnonTokenBinding, RecordsConfig, RecordsActor, RecordsDataSource, TableDuplicated, HistoryActor, FieldHistoryEntry, DrillDefinition, DrillSource, TableDuplicateProgress, TableArchiveProgress } from '@ai-matrx/records';
5
5
  import { ReversibleAnnouncement } from '@ai-matrx/kit/reversible';
6
- import { W as WhatYouMayDo, T as TableActionHost, O as ObjectAction, a as ObjectActionIcon } from './objectActions-XkhxU-DP.js';
7
- export { A as ACTION_CHECKING_ACCESS_REASON, b as ACTION_NOT_HERE_REASON, c as ACTION_NO_ACCESS_REASON, d as ACTION_PLATFORM_TABLE_REASON, B as BuiltOnDestination, C as Capability, D as DEFAULT_ACTION_ORIGIN, K as KERNEL_REASON, L as LEVEL_WORD, N as NOT_ANSWERED_YET, e as OBJECT_ACTION_GROUPS, f as ObjectActionDropdownEntry, g as ObjectActionGroup, h as ObjectActionGroupDef, i as ObjectActionSection, S as STANDARD_VERBS, j as STRIP_FEATURE_ROWS, k as StandardVerbId, l as StripAndRows, m as TableActionSubject, n as TableActionsInput, o as dropdownActionIds, p as dropdownActions, q as groupObjectActions, r as levelNeededFor, s as rightsReason, t as tableActions, u as tableLink, v as toDropdownEntries, w as toStripAndRows, x as whatYouMayDo, y as whatYouMayDoWithTable, z as withExtensions } from './objectActions-XkhxU-DP.js';
6
+ import { W as WhatYouMayDo, T as TableActionHost, O as ObjectAction, a as ObjectActionIcon } from './objectActions-B7AUBkcl.js';
7
+ export { A as ACTION_CHECKING_ACCESS_REASON, b as ACTION_NOT_HERE_REASON, c as ACTION_NO_ACCESS_REASON, d as ACTION_PLATFORM_TABLE_REASON, B as BuiltOnDestination, C as Capability, D as DEFAULT_ACTION_ORIGIN, K as KERNEL_REASON, L as LEVEL_WORD, N as NOT_ANSWERED_YET, e as OBJECT_ACTION_GROUPS, f as ObjectActionDropdownEntry, g as ObjectActionGroup, h as ObjectActionGroupDef, i as ObjectActionSection, S as STANDARD_VERBS, j as STRIP_FEATURE_ROWS, k as StandardVerbId, l as StripAndRows, m as TableActionSubject, n as TableActionsInput, o as dropdownActionIds, p as dropdownActions, q as groupObjectActions, r as levelNeededFor, s as rightsReason, t as tableActions, u as tableLink, v as toDropdownEntries, w as toStripAndRows, x as whatYouMayDo, y as whatYouMayDoWithTable, z as withExtensions } from './objectActions-B7AUBkcl.js';
8
8
  import { Uuid as Uuid$1, Table as Table$1, Field as Field$1, ReadRow as ReadRow$1, StoreFilter, useRecordsClient } from '@ai-matrx/records/react';
9
9
  import { RecordsClient, ParsedImport, NewFieldSpec } from '@ai-matrx/records/core';
10
10
  export { DEFAULT_FIELDS, DeclareResult, NewFieldSpec, NewTableSpec, addFields, declareTable, fieldDeclarationFor, tokenFor } from '@ai-matrx/records/core';
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ import { atLeast } from "@ai-matrx/records";
9
9
  var LEVEL_WORD = {
10
10
  viewer: "Viewer",
11
11
  commenter: "Commenter",
12
+ edit_content: "Content editor",
12
13
  editor: "Editor",
13
14
  admin: "Admin"
14
15
  };
@@ -16,8 +17,12 @@ var NEEDS = {
16
17
  read: "viewer",
17
18
  // custom.io_comment_write
18
19
  comment: "commenter",
19
- // custom.record_update / custom.record_write
20
- write: "editor",
20
+ // custom.record_update / custom.record_write — `edit_content` changes rows and page content;
21
+ // columns, views and settings (structure) stay with admin below, and sharing with admin.
22
+ write: "edit_content",
23
+ // custom.view_designate / view_declare / migrate_merge / record_restore — all ask `editor`: more than
24
+ // changing content (`edit_content`), less than changing columns (`admin`).
25
+ organise: "editor",
21
26
  // custom.record_delete
22
27
  remove: "editor",
23
28
  // custom.share_grant — passing your access on is an administrator's right
@@ -38,6 +43,7 @@ function sentenceFor(what, level) {
38
43
  read: "open this",
39
44
  comment: "leave a comment",
40
45
  write: "change anything here",
46
+ organise: "organise this",
41
47
  remove: "delete this",
42
48
  share: "pass this on to somebody else",
43
49
  structure: "change the columns of this table"
@@ -52,6 +58,7 @@ function whatYouMayDo(level, known = true) {
52
58
  read: may("read"),
53
59
  comment: may("comment"),
54
60
  write: may("write"),
61
+ organise: may("organise"),
55
62
  remove: may("remove"),
56
63
  share: may("share"),
57
64
  structure: may("structure"),
@@ -14944,7 +14951,18 @@ function useMergedGridControls(args) {
14944
14951
  label: "Duplicate property",
14945
14952
  icon: "copy-plus",
14946
14953
  disabledReason: mayStructure ? void 0 : editable ? rights.why("structure") : "Editing is off here.",
14947
- onSelect: () => setDialog({ kind: "insert", anchor: columnId2, side: "right", copyOf: field2 })
14954
+ // DUPLICATE PROPERTY (Notion N5): one door declares the copy right after this column and copies
14955
+ // every record's value into it (`custom.field_duplicate`); nothing to fill in, as in Notion.
14956
+ onSelect: () => {
14957
+ void client.fieldDuplicate({ field_id: field2.id }).then((made) => {
14958
+ if (!made.ok) {
14959
+ notify?.error(`${fieldName(field2)} was not duplicated: ${refusalSentence(made.error)}`);
14960
+ return;
14961
+ }
14962
+ tableShapeChanged5(tableId);
14963
+ notify?.success(`${made.data.label} is added.`);
14964
+ });
14965
+ }
14948
14966
  });
14949
14967
  if (summaries) {
14950
14968
  out.push(summaryMenuItem(field2, columnId2 === firstKey, summaries.chosen[columnId2], summaries.operations, (op2) => summaries.choose(columnId2, op2)));
@@ -15291,7 +15309,7 @@ function useMergedGridControls(args) {
15291
15309
  },
15292
15310
  children: /* @__PURE__ */ jsxs49(DialogContent8, { className: "max-h-[85vh] overflow-y-auto sm:max-w-2xl", children: [
15293
15311
  /* @__PURE__ */ jsxs49(DialogHeader8, { children: [
15294
- /* @__PURE__ */ jsx54(DialogTitle8, { 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 }),
15312
+ /* @__PURE__ */ jsx54(DialogTitle8, { 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 }),
15295
15313
  /* @__PURE__ */ jsx54(DialogDescription6, { className: "sr-only", children: "Name, type and look" })
15296
15314
  ] }),
15297
15315
  columnDialog?.kind === "settings" ? /* @__PURE__ */ jsx54(
@@ -15315,7 +15333,6 @@ function useMergedGridControls(args) {
15315
15333
  FieldEditor,
15316
15334
  {
15317
15335
  tableId,
15318
- ...columnDialog.copyOf ? { copyOf: columnDialog.copyOf } : {},
15319
15336
  onCancel: () => {
15320
15337
  pendingInsert.current = null;
15321
15338
  close();
@@ -24744,6 +24761,7 @@ function createEntityRealtime(options = {}) {
24744
24761
  }
24745
24762
 
24746
24763
  // ../entity-data/src/port/port.ts
24764
+ import { isUuidShape as isUuidShape3 } from "@ai-matrx/kit/uuid";
24747
24765
  import { mintOpId } from "@ai-matrx/records";
24748
24766
 
24749
24767
  // ../entity-data/src/core/wire.ts
@@ -24844,8 +24862,6 @@ function toColumns(wire) {
24844
24862
  }
24845
24863
 
24846
24864
  // ../entity-data/src/port/port.ts
24847
- var UUID_SHAPE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
24848
- var isUuidShape3 = (value) => typeof value === "string" && UUID_SHAPE.test(value);
24849
24865
  function toWireValue(value) {
24850
24866
  if (isRef(value)) return value.id;
24851
24867
  if (Array.isArray(value) && value.length > 0 && value.every(isRef)) {
@@ -25444,6 +25460,7 @@ import { useFields as useFields10, useRecords as useRecords6, useRecordsClient a
25444
25460
  import { exportCsv, exportXlsx, wholeValueOf as wholeValueOf5, withWholeValues } from "@ai-matrx/records/core";
25445
25461
 
25446
25462
  // src/viewExport.ts
25463
+ import { formatCount as formatCount3 } from "@ai-matrx/kit/format";
25447
25464
  var VIEW_EXPORT_PAGE = 500;
25448
25465
  var VIEW_EXPORT_CEILING = 5e3;
25449
25466
  function viewColumns(fields, presentation) {
@@ -25482,7 +25499,7 @@ async function readViewRows(client, tableId, fields, view, ceiling = VIEW_EXPORT
25482
25499
  }
25483
25500
  }
25484
25501
  function cutWords(written) {
25485
- return `First ${written.toLocaleString("en-US")} rows only`;
25502
+ return `First ${formatCount3(written, { locale: "en-US" })} rows only`;
25486
25503
  }
25487
25504
 
25488
25505
  // src/ExportMenu.tsx
@@ -26650,7 +26667,7 @@ function tableActions(input) {
26650
26667
  // custom.view_designate asks editor on the table — `canDesignate` in TablePage.
26651
26668
  action(
26652
26669
  { id: "make-default", label: makeDefaultWords(host.layoutLabel), icon: "layout-grid", group: "manage", rank: 4 },
26653
- "write",
26670
+ "organise",
26654
26671
  input,
26655
26672
  bind(host.makeDefault, table)
26656
26673
  ),
@@ -27829,7 +27846,7 @@ function SaveToTable({ source, shapeIndex = 0, onOpenTable, onSaved, onClose, cl
27829
27846
  const levels = useMyLevels3(own3.map((t) => t.id));
27830
27847
  const writable = own3.filter((t) => {
27831
27848
  const level = levels.data?.[t.id];
27832
- return level === "editor" || level === "admin";
27849
+ return level === "edit_content" || level === "editor" || level === "admin";
27833
27850
  }).sort((a, b) => a.name.localeCompare(b.name));
27834
27851
  const [tableId, setTableId] = useState68("");
27835
27852
  const proposedName = shape?.name ?? source.title ?? "Saved table";
@@ -30206,7 +30223,7 @@ import {
30206
30223
  resolveRowColor as resolveRowColor4,
30207
30224
  ROW_TINT_CLASS as ROW_TINT_CLASS3
30208
30225
  } from "@ai-matrx/design-system/data-table";
30209
- import { formatCount as formatCount3 } from "@ai-matrx/kit/format";
30226
+ import { formatCount as formatCount4 } from "@ai-matrx/kit/format";
30210
30227
 
30211
30228
  // src/recordMoves.tsx
30212
30229
  import { useCallback as useCallback36, useEffect as useEffect69, useMemo as useMemo49, useRef as useRef43, useState as useState81 } from "react";
@@ -30473,10 +30490,10 @@ function recordActions(input) {
30473
30490
  input,
30474
30491
  copyText13 ? () => copyText13(url, record) : void 0
30475
30492
  ),
30476
- action2({ id: "duplicate", label: "Duplicate", icon: "copy", group: "organize", verb: "duplicate" }, "write", input, bind2(host.duplicate, record)),
30493
+ action2({ id: "duplicate", label: "Duplicate", icon: "copy", group: "organize", verb: "duplicate" }, "organise", input, bind2(host.duplicate, record)),
30477
30494
  action2(
30478
30495
  { id: "merge", label: "Merge with\u2026", icon: "git-merge", group: "organize" },
30479
- "write",
30496
+ "organise",
30480
30497
  input,
30481
30498
  bind2(host.merge, record)
30482
30499
  ),
@@ -34111,9 +34128,9 @@ function ViewPager({ shown: shown4, total, onMore, step: step2 }) {
34111
34128
  return /* @__PURE__ */ jsxs99("p", { className: "flex items-center gap-2 text-xs text-muted-foreground", "data-view-pager": "", children: [
34112
34129
  /* @__PURE__ */ jsxs99("span", { children: [
34113
34130
  "Showing ",
34114
- formatCount3(shown4),
34131
+ formatCount4(shown4),
34115
34132
  " of ",
34116
- formatCount3(total),
34133
+ formatCount4(total),
34117
34134
  " records."
34118
34135
  ] }),
34119
34136
  /* @__PURE__ */ jsxs99(Button67, { size: "sm", variant: "outline", onClick: onMore, children: [
@@ -34590,8 +34607,8 @@ function ArchivedView({
34590
34607
  onRestored?.(recordId);
34591
34608
  };
34592
34609
  const titleKey = table.data?.title_field ?? null;
34593
- const mayRestore = rights.write;
34594
- const whyNot = mayRestore ? "" : mayNotRestoreLine(rights.why("write"));
34610
+ const mayRestore = rights.organise;
34611
+ const whyNot = mayRestore ? "" : mayNotRestoreLine(rights.why("organise"));
34595
34612
  return /* @__PURE__ */ jsxs100("div", { className: cn56("flex min-h-0 flex-col gap-2", className), "data-testid": "archived-view", children: [
34596
34613
  /* @__PURE__ */ jsxs100("div", { className: "flex flex-wrap items-center gap-2", children: [
34597
34614
  /* @__PURE__ */ jsx110("span", { className: "text-sm font-medium", children: "Archived" }),
@@ -37952,7 +37969,7 @@ import {
37952
37969
  } from "@ai-matrx/records";
37953
37970
  import { mapPgError as mapPgError3 } from "@ai-matrx/records/core";
37954
37971
  import { cn as cn66 } from "@ai-matrx/design-system";
37955
- import { formatCount as formatCount4 } from "@ai-matrx/kit/format";
37972
+ import { formatCount as formatCount5 } from "@ai-matrx/kit/format";
37956
37973
 
37957
37974
  // src/linkWords.ts
37958
37975
  import { useEffect as useEffect85, useMemo as useMemo60, useState as useState98 } from "react";
@@ -38565,7 +38582,7 @@ function BigNumber({
38565
38582
  /* @__PURE__ */ jsx120("span", { className: "text-2xl font-semibold tabular-nums", children: pretty(total) }),
38566
38583
  windows ? /* @__PURE__ */ jsx120(Trend, { pct: block.totals?.change_pct, aimingUp: !!target, className: "text-sm" }) : null
38567
38584
  ] }),
38568
- /* @__PURE__ */ jsx120("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 ${formatCount4(records)} ${records === 1 ? "record" : "records"}` })
38585
+ /* @__PURE__ */ jsx120("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 ${formatCount5(records)} ${records === 1 ? "record" : "records"}` })
38569
38586
  ] });
38570
38587
  const ring = target && typeof target.progress === "number" && typeof target.value === "number" ? /* @__PURE__ */ jsxs110("span", { className: "ml-auto flex shrink-0 items-center gap-2", children: [
38571
38588
  /* @__PURE__ */ jsxs110("span", { className: "text-right text-xs text-muted-foreground", children: [
@@ -38684,7 +38701,7 @@ var HEIGHT = CHART_PLOT_PX;
38684
38701
  function compactTick(value) {
38685
38702
  if (typeof value !== "number" || !Number.isFinite(value)) return String(value ?? "");
38686
38703
  if (Math.abs(value) < 1e3 && !Number.isInteger(value)) return String(Math.round(value * 10) / 10);
38687
- return formatCount4(value, { style: "compact" });
38704
+ return formatCount5(value, { style: "compact" });
38688
38705
  }
38689
38706
  function Drawing({
38690
38707
  kind,
@@ -38823,9 +38840,7 @@ function onlyWholeNumbers(rows) {
38823
38840
 
38824
38841
  // src/FormResultsPanel.tsx
38825
38842
  import { jsx as jsx121, jsxs as jsxs111 } from "react/jsx-runtime";
38826
- function numberBlock(title, value) {
38827
- return { title, kind: "number", group_by: [], measures: [{ op: "count" }], rows: [{ groups: {}, measures: { count: value }, row_count: 1 }] };
38828
- }
38843
+ var CHARTED_KINDS = /* @__PURE__ */ new Set(["select", "multi_select", "checkbox", "number", "currency", "percent", "member", "relation"]);
38829
38844
  function dropOffBlock(results, label) {
38830
38845
  return {
38831
38846
  title: "Reached each question",
@@ -38867,6 +38882,13 @@ function FormResultsPanel({ form, className }) {
38867
38882
  cancelled = true;
38868
38883
  };
38869
38884
  }, [client, form.id, round]);
38885
+ const charted = useMemo62(() => {
38886
+ const byKey = new Map((fields.data ?? []).map((f) => [f.key, f]));
38887
+ return (key) => {
38888
+ const field2 = byKey.get(key);
38889
+ return field2 ? CHARTED_KINDS.has(editorKindFor(field2)) : false;
38890
+ };
38891
+ }, [fields.data]);
38870
38892
  const label = useMemo62(() => {
38871
38893
  const byKey = new Map((fields.data ?? []).map((f) => [f.key, f]));
38872
38894
  const asks = new Map((form.questions ?? []).map((q) => [q.field, q.ask]));
@@ -38880,12 +38902,15 @@ function FormResultsPanel({ form, className }) {
38880
38902
  /* @__PURE__ */ jsx121("span", { className: "text-sm font-medium", children: "Results" }),
38881
38903
  /* @__PURE__ */ jsx121(Button77, { size: "sm", variant: "ghost", className: "ml-auto", onClick: () => setRound((n) => n + 1), children: "Refresh" })
38882
38904
  ] }),
38883
- /* @__PURE__ */ jsxs111("div", { className: "grid grid-cols-2 gap-2 @xl:grid-cols-4", children: [
38884
- /* @__PURE__ */ jsx121(ChartBlock, { block: numberBlock("Views", results.views), subject: form.subject }),
38885
- /* @__PURE__ */ jsx121(ChartBlock, { block: numberBlock("Starts", results.starts), subject: form.subject }),
38886
- /* @__PURE__ */ jsx121(ChartBlock, { block: numberBlock("Completions", results.completions), subject: form.subject }),
38887
- /* @__PURE__ */ jsx121(ChartBlock, { block: numberBlock("Completion rate %", results.completion_rate), subject: form.subject })
38888
- ] }),
38905
+ /* @__PURE__ */ jsx121("dl", { className: "grid grid-cols-2 gap-2 @xl:grid-cols-4", "data-records-form-kpis": "", children: [
38906
+ ["Views", results.views],
38907
+ ["Starts", results.starts],
38908
+ ["Completions", results.completions],
38909
+ ["Completion rate", results.completion_rate === null ? null : `${results.completion_rate}%`]
38910
+ ].map(([name, value]) => /* @__PURE__ */ jsxs111("div", { className: "flex flex-col gap-1 rounded-md border px-3 py-2", children: [
38911
+ /* @__PURE__ */ jsx121("dt", { className: "text-xs text-muted-foreground", children: name }),
38912
+ /* @__PURE__ */ jsx121("dd", { className: "text-2xl font-semibold tabular-nums", children: value ?? "\u2014" })
38913
+ ] }, name)) }),
38889
38914
  results.questions.length > 0 ? /* @__PURE__ */ jsx121(ChartBlock, { block: dropOffBlock(results, label), subject: form.subject }) : null,
38890
38915
  /* @__PURE__ */ jsx121("ul", { className: "flex flex-col gap-1 text-xs", "data-records-form-dropoff": "", children: results.questions.map((q) => /* @__PURE__ */ jsxs111("li", { className: "flex items-center gap-2", children: [
38891
38916
  /* @__PURE__ */ jsx121("span", { className: "min-w-0 flex-1 truncate", children: label(q.field_key) }),
@@ -38924,7 +38949,16 @@ function FormResultsPanel({ form, className }) {
38924
38949
  /* @__PURE__ */ jsx121("span", { className: "flex-1 truncate", children: h.value }),
38925
38950
  /* @__PURE__ */ jsx121("span", { className: "tabular-nums text-muted-foreground", children: h.count })
38926
38951
  ] }, `${h.key}:${h.value}`)) }) : null,
38927
- results.questions.filter((q) => q.answers.length > 0).map((q) => /* @__PURE__ */ jsx121(ChartBlock, { block: answersBlock(q, label(q.field_key)), subject: form.subject }, q.field_key))
38952
+ results.questions.filter((q) => q.answers.length > 0).map(
38953
+ (q) => charted(q.field_key) ? /* @__PURE__ */ jsx121(ChartBlock, { block: answersBlock(q, label(q.field_key)), subject: form.subject }, q.field_key) : /* @__PURE__ */ jsxs111("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: [
38954
+ /* @__PURE__ */ jsx121("span", { className: "font-medium", children: label(q.field_key) }),
38955
+ /* @__PURE__ */ jsxs111("span", { className: "text-muted-foreground", children: [
38956
+ q.answered,
38957
+ " answered"
38958
+ ] }),
38959
+ /* @__PURE__ */ jsx121("ul", { className: "flex flex-col gap-0.5", children: q.answers.map((a) => /* @__PURE__ */ jsx121("li", { className: "truncate", children: a.value }, a.value)) })
38960
+ ] }, q.field_key)
38961
+ )
38928
38962
  ] });
38929
38963
  }
38930
38964
 
@@ -41384,8 +41418,9 @@ function QuestionLogic({
41384
41418
  const jumps = question.jumps ?? [];
41385
41419
  const targets = jumpTargets(questions, index, endings, label);
41386
41420
  const points = question.points ?? {};
41387
- const rawOptions = field2?.options;
41388
- const choices = Array.isArray(rawOptions) ? rawOptions.filter((o) => typeof o === "string") : [];
41421
+ const choiceFields = field2 && isChoiceField(field2) ? [field2] : [];
41422
+ const options = useChoiceOptions(choiceFields, choiceFields.map((f) => f.key));
41423
+ const choices = field2 ? (options.get(field2.key) ?? []).map((o) => ({ key: o.key, label: o.label })) : [];
41389
41424
  const [answer, setAnswer] = useState108("");
41390
41425
  const setJump = (i, part) => onChange({ jumps: jumps.map((j, k) => k === i ? { ...j, ...part } : j) });
41391
41426
  return /* @__PURE__ */ jsxs121("div", { className: "col-span-2 flex flex-col gap-1.5 text-xs", "data-records-form-logic": question.field, children: [
@@ -41409,25 +41444,25 @@ function QuestionLogic({
41409
41444
  /* @__PURE__ */ jsxs121("div", { className: "flex flex-wrap items-center gap-2", children: [
41410
41445
  targets.length > 0 ? /* @__PURE__ */ jsx131(Button84, { size: "sm", variant: "outline", onClick: () => onChange({ jumps: [...jumps, { when: null, to: targetOf(targets[0].value) }] }), children: "Add a jump" }) : null,
41411
41446
  /* @__PURE__ */ jsx131("span", { className: "text-muted-foreground", children: "Points:" }),
41412
- (choices.length > 0 ? choices : Object.keys(points)).map((choice) => /* @__PURE__ */ jsxs121("label", { className: "flex items-center gap-1", children: [
41413
- /* @__PURE__ */ jsx131("span", { children: choice }),
41447
+ (choices.length > 0 ? choices : Object.keys(points).map((k) => ({ key: k, label: k }))).map((choice) => /* @__PURE__ */ jsxs121("label", { className: "flex items-center gap-1", children: [
41448
+ /* @__PURE__ */ jsx131("span", { children: choice.label }),
41414
41449
  /* @__PURE__ */ jsx131(
41415
41450
  BasicInput27,
41416
41451
  {
41417
41452
  className: "h-7 w-14 text-xs",
41418
41453
  inputMode: "decimal",
41419
- "aria-label": `Points for ${choice}`,
41420
- value: points[choice] ?? "",
41454
+ "aria-label": `Points for ${choice.label}`,
41455
+ value: points[choice.key] ?? "",
41421
41456
  onChange: (e) => {
41422
41457
  const next = { ...points };
41423
41458
  const n = Number(e.target.value);
41424
- if (e.target.value.trim() === "" || Number.isNaN(n)) delete next[choice];
41425
- else next[choice] = n;
41459
+ if (e.target.value.trim() === "" || Number.isNaN(n)) delete next[choice.key];
41460
+ else next[choice.key] = n;
41426
41461
  onChange({ points: Object.keys(next).length > 0 ? next : null });
41427
41462
  }
41428
41463
  }
41429
41464
  )
41430
- ] }, choice)),
41465
+ ] }, choice.key)),
41431
41466
  choices.length === 0 ? /* @__PURE__ */ jsxs121("span", { className: "flex items-center gap-1", children: [
41432
41467
  /* @__PURE__ */ jsx131(BasicInput27, { className: "h-7 w-28 text-xs", "aria-label": "Answer worth points", placeholder: "An answer", value: answer, onChange: (e) => setAnswer(e.target.value) }),
41433
41468
  /* @__PURE__ */ jsx131(
@@ -43731,6 +43766,8 @@ function recordsDataSource(client, fallbackSchema = "custom") {
43731
43766
  function makeRecordsDataSource(client, fallbackSchema) {
43732
43767
  const supabase = client;
43733
43768
  return {
43769
+ // PAGE-BUNDLE: the real adapter opens a table page with one `custom.table_page_bundle` call.
43770
+ opensInOneCall: true,
43734
43771
  async rpc(fn, args, options) {
43735
43772
  const schema = options?.schema ?? fallbackSchema;
43736
43773
  if (schema === "custom" && supabase.auth?.getSession && !await hasSession(supabase.auth.getSession)) {
@@ -46050,7 +46087,7 @@ import {
46050
46087
  import { onRecordChanged } from "@ai-matrx/records/core";
46051
46088
  import { BasicInput as BasicInput34, Button as Button96, Checkbox as Checkbox17, Skeleton as Skeleton37, cn as cn86 } from "@ai-matrx/design-system";
46052
46089
  import { Fragment as Fragment59, jsx as jsx145, jsxs as jsxs134 } from "react/jsx-runtime";
46053
- var EDIT_LEVELS = /* @__PURE__ */ new Set(["editor", "admin", "owner"]);
46090
+ var EDIT_LEVELS = /* @__PURE__ */ new Set(["edit_content", "editor", "admin", "owner"]);
46054
46091
  function PageScreen({
46055
46092
  pageId,
46056
46093
  newOnTable,
@@ -48275,6 +48312,7 @@ function TableReferenceDialog({
48275
48312
  }
48276
48313
 
48277
48314
  // src/TableDuplicate.tsx
48315
+ import { formatCount as formatCount6 } from "@ai-matrx/kit/format";
48278
48316
  import { useState as useState130 } from "react";
48279
48317
  import { tableShapeChanged as tableShapeChanged12, useRecordsClient as useRecordsClient95 } from "@ai-matrx/records/react";
48280
48318
  import { Button as Button105, Dialog as Dialog16, DialogContent as DialogContent16, DialogFooter as DialogFooter11, DialogHeader as DialogHeader16, DialogTitle as DialogTitle16, ErrorBox as ErrorBox8, Input as Input10, Switch as Switch5 } from "@ai-matrx/design-system";
@@ -48283,7 +48321,7 @@ function copyNameFor(name) {
48283
48321
  return `${name.trim() || "Table"} (copy)`;
48284
48322
  }
48285
48323
  function duplicateProgressWords(p) {
48286
- if (p.records && p.records.total > 0) return `Copying records \xB7 ${p.records.done.toLocaleString()} of ${p.records.total.toLocaleString()}`;
48324
+ if (p.records && p.records.total > 0) return `Copying records \xB7 ${formatCount6(p.records.done)} of ${formatCount6(p.records.total)}`;
48287
48325
  if (p.views.total > 0 && p.step === "views") return `Copying views \xB7 ${p.views.done} of ${p.views.total}`;
48288
48326
  return "Copying columns\u2026";
48289
48327
  }
@@ -48339,7 +48377,7 @@ function TableDuplicateDialog({ tableId, name, open, onOpenChange, onDuplicated
48339
48377
  " columns, ",
48340
48378
  phase.copy.copied.views,
48341
48379
  " views",
48342
- phase.copy.copied.withRecords ? `, ${phase.copy.copied.records.toLocaleString()} records` : "",
48380
+ phase.copy.copied.withRecords ? `, ${formatCount6(phase.copy.copied.records)} records` : "",
48343
48381
  "."
48344
48382
  ] }),
48345
48383
  phase.copy.notCopied.length > 0 ? /* @__PURE__ */ jsxs144("p", { className: "text-xs text-muted-foreground", children: [
@@ -49296,7 +49334,7 @@ function TablePage({
49296
49334
  const [designateRefused, setDesignateRefused] = useState133(null);
49297
49335
  const opensAs = view?.isDefault ? view.layout : null;
49298
49336
  const canDesignate = Boolean(
49299
- rights.write && view && layoutShowing && layoutShowing !== opensAs && storeKeepsLayout(layoutShowing)
49337
+ rights.organise && view && layoutShowing && layoutShowing !== opensAs && storeKeepsLayout(layoutShowing)
49300
49338
  );
49301
49339
  const layoutShowingLabel = layoutShowing ? LAYOUT_LABEL[layoutShowing] : "";
49302
49340
  const designate = async () => {
@@ -50004,6 +50042,7 @@ function usePublicReadOnly(client, organizationId, tableId, table, rights) {
50004
50042
  }
50005
50043
 
50006
50044
  // src/entityViews.tsx
50045
+ import { formatCount as formatCount7 } from "@ai-matrx/kit/format";
50007
50046
  import { TapTargetSearchGroup as TapTargetSearchGroup2, TooltipProvider } from "@ai-matrx/design-system";
50008
50047
  import { MatrxTableCardShowMore as MatrxTableCardShowMore3 } from "@ai-matrx/design-system/data-table";
50009
50048
  import { useEffect as useEffect114, useMemo as useMemo79, useState as useState134 } from "react";
@@ -50194,7 +50233,7 @@ function EntityTablePageBody({
50194
50233
  return /* @__PURE__ */ jsxs148("div", { className: cn96("flex min-w-0 flex-col gap-2", className), "data-records-entity-page": source.token, children: [
50195
50234
  presentation === "page" || boardable || entity.creatable ? /* @__PURE__ */ jsxs148("div", { className: "flex min-w-0 items-center gap-2", children: [
50196
50235
  presentation === "page" ? /* @__PURE__ */ jsx159("h2", { className: "truncate text-base font-semibold", children: entity.label ?? (entity.loading ? "Loading\u2026" : "Table") }) : null,
50197
- presentation === "page" ? /* @__PURE__ */ jsx159("span", { className: "text-xs tabular-nums text-muted-foreground", children: entity.loading || entity.total === null ? "" : entity.total.toLocaleString() }) : null,
50236
+ presentation === "page" ? /* @__PURE__ */ jsx159("span", { className: "text-xs tabular-nums text-muted-foreground", children: entity.loading || entity.total === null ? "" : formatCount7(entity.total) }) : null,
50198
50237
  /* @__PURE__ */ jsxs148("span", { className: "ml-auto flex items-center gap-1", children: [
50199
50238
  boardable ? /* @__PURE__ */ jsxs148(Fragment67, { children: [
50200
50239
  /* @__PURE__ */ jsx159(Button108, { size: "sm", variant: layout === "grid" ? "secondary" : "ghost", "aria-pressed": layout === "grid", onClick: () => choose("grid"), children: "Table" }),