@ai-matrx/records-ui 0.93.133 → 0.93.140
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 +65 -1
- package/dist/index.cjs +10580 -6613
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +672 -158
- package/dist/index.d.ts +672 -158
- package/dist/index.js +9965 -5949
- package/dist/index.js.map +1 -1
- package/dist/object-actions.cjs +240 -0
- package/dist/object-actions.cjs.map +1 -0
- package/dist/object-actions.d.cts +2 -0
- package/dist/object-actions.d.ts +2 -0
- package/dist/object-actions.js +218 -0
- package/dist/object-actions.js.map +1 -0
- package/dist/objectActions-BSbblgf-.d.cts +168 -0
- package/dist/objectActions-BSbblgf-.d.ts +168 -0
- package/dist/pickers.cjs +4532 -0
- package/dist/pickers.cjs.map +1 -0
- package/dist/pickers.d.cts +137 -0
- package/dist/pickers.d.ts +137 -0
- package/dist/pickers.js +4559 -0
- package/dist/pickers.js.map +1 -0
- package/dist/table-shape.cjs +2 -1
- package/dist/table-shape.cjs.map +1 -1
- package/dist/table-shape.d.cts +1 -1
- package/dist/table-shape.d.ts +1 -1
- package/dist/table-shape.js +4 -1
- package/dist/table-shape.js.map +1 -1
- package/package.json +27 -1
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/objectActions.ts
|
|
22
|
+
var objectActions_exports = {};
|
|
23
|
+
__export(objectActions_exports, {
|
|
24
|
+
ACTION_CHECKING_ACCESS_REASON: () => ACTION_CHECKING_ACCESS_REASON,
|
|
25
|
+
ACTION_NOT_HERE_REASON: () => ACTION_NOT_HERE_REASON,
|
|
26
|
+
ACTION_NO_ACCESS_REASON: () => ACTION_NO_ACCESS_REASON,
|
|
27
|
+
ACTION_PLATFORM_TABLE_REASON: () => ACTION_PLATFORM_TABLE_REASON,
|
|
28
|
+
DEFAULT_ACTION_ORIGIN: () => DEFAULT_ACTION_ORIGIN,
|
|
29
|
+
OBJECT_ACTION_GROUPS: () => OBJECT_ACTION_GROUPS,
|
|
30
|
+
dropdownActionIds: () => dropdownActionIds,
|
|
31
|
+
groupObjectActions: () => groupObjectActions,
|
|
32
|
+
rightsReason: () => rightsReason,
|
|
33
|
+
tableActions: () => tableActions,
|
|
34
|
+
tableLink: () => tableLink,
|
|
35
|
+
toDropdownEntries: () => toDropdownEntries,
|
|
36
|
+
withExtensions: () => withExtensions
|
|
37
|
+
});
|
|
38
|
+
module.exports = __toCommonJS(objectActions_exports);
|
|
39
|
+
|
|
40
|
+
// src/rights.ts
|
|
41
|
+
var import_records = require("@ai-matrx/records");
|
|
42
|
+
var LEVEL_WORD = {
|
|
43
|
+
viewer: "Viewer",
|
|
44
|
+
commenter: "Commenter",
|
|
45
|
+
editor: "Editor",
|
|
46
|
+
admin: "Admin"
|
|
47
|
+
};
|
|
48
|
+
var NEEDS = {
|
|
49
|
+
read: "viewer",
|
|
50
|
+
// custom.io_comment_write
|
|
51
|
+
comment: "commenter",
|
|
52
|
+
// custom.record_update / custom.record_write
|
|
53
|
+
write: "editor",
|
|
54
|
+
// custom.record_delete
|
|
55
|
+
remove: "editor",
|
|
56
|
+
// custom.share_grant — passing your access on is an administrator's right
|
|
57
|
+
share: "admin",
|
|
58
|
+
// custom.field_declare / field_update / field_retire / table settings
|
|
59
|
+
structure: "admin"
|
|
60
|
+
};
|
|
61
|
+
function levelNeededFor(what) {
|
|
62
|
+
return NEEDS[what];
|
|
63
|
+
}
|
|
64
|
+
var NOT_YET = "Still asking the store what you may do here. Nothing is offered until it has answered.";
|
|
65
|
+
var NOTHING_AT_ALL = "You have not been given this. Ask somebody who administers it to share it with you.";
|
|
66
|
+
function sentenceFor(what, level) {
|
|
67
|
+
if (level === null) return NOTHING_AT_ALL;
|
|
68
|
+
const need = LEVEL_WORD[NEEDS[what]];
|
|
69
|
+
const have = LEVEL_WORD[level];
|
|
70
|
+
const doing = {
|
|
71
|
+
read: "open this",
|
|
72
|
+
comment: "leave a comment",
|
|
73
|
+
write: "change anything here",
|
|
74
|
+
remove: "delete this",
|
|
75
|
+
share: "pass this on to somebody else",
|
|
76
|
+
structure: "change the columns of this table"
|
|
77
|
+
};
|
|
78
|
+
return `You are ${have} here, and it takes ${need} to ${doing[what]}. Ask somebody who is Admin on it to move you up.`;
|
|
79
|
+
}
|
|
80
|
+
function whatYouMayDo(level, known = true) {
|
|
81
|
+
const may = (what) => known && (0, import_records.atLeast)(level, NEEDS[what]);
|
|
82
|
+
return {
|
|
83
|
+
level: known ? level : null,
|
|
84
|
+
known,
|
|
85
|
+
read: may("read"),
|
|
86
|
+
comment: may("comment"),
|
|
87
|
+
write: may("write"),
|
|
88
|
+
remove: may("remove"),
|
|
89
|
+
share: may("share"),
|
|
90
|
+
structure: may("structure"),
|
|
91
|
+
why: (what) => known ? sentenceFor(what, level) : NOT_YET
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
var NOT_ANSWERED_YET = whatYouMayDo(null, false);
|
|
95
|
+
|
|
96
|
+
// src/objectActions.ts
|
|
97
|
+
var OBJECT_ACTION_GROUPS = [
|
|
98
|
+
{ id: "open", submenu: false },
|
|
99
|
+
{ id: "organize", submenu: false },
|
|
100
|
+
{ id: "share", submenu: false },
|
|
101
|
+
{ id: "manage", submenu: false },
|
|
102
|
+
{ id: "built-on", label: "Built on it", icon: "blocks", submenu: true },
|
|
103
|
+
{ id: "danger", submenu: false }
|
|
104
|
+
];
|
|
105
|
+
var DEFAULT_ACTION_ORIGIN = "https://www.aimatrx.com";
|
|
106
|
+
function tableLink(tableId, origin = DEFAULT_ACTION_ORIGIN) {
|
|
107
|
+
return `${origin.replace(/\/+$/, "")}/data-v2/${encodeURIComponent(tableId)}`;
|
|
108
|
+
}
|
|
109
|
+
var ACTION_NOT_HERE_REASON = "Not available here";
|
|
110
|
+
var ACTION_CHECKING_ACCESS_REASON = "Checking your access\u2026";
|
|
111
|
+
var ACTION_NO_ACCESS_REASON = "You don't have access";
|
|
112
|
+
var ACTION_PLATFORM_TABLE_REASON = "Platform tables can't be changed here";
|
|
113
|
+
function rightsReason(rights, what, table) {
|
|
114
|
+
if (!rights.known) return ACTION_CHECKING_ACCESS_REASON;
|
|
115
|
+
if (what === "structure" && table?.is_kernel) return ACTION_PLATFORM_TABLE_REASON;
|
|
116
|
+
if (rights[what]) return null;
|
|
117
|
+
if (rights.level === null) return ACTION_NO_ACCESS_REASON;
|
|
118
|
+
return `Needs ${LEVEL_WORD[levelNeededFor(what)]} access`;
|
|
119
|
+
}
|
|
120
|
+
var BUILT_ON = [
|
|
121
|
+
{ destination: "forms", label: "Forms", icon: "file-text" },
|
|
122
|
+
{ destination: "bookings", label: "Bookings", icon: "calendar-clock" },
|
|
123
|
+
{ destination: "checklists", label: "Checklists", icon: "list-checks" },
|
|
124
|
+
{ destination: "notifications", label: "Notifications", icon: "bell" },
|
|
125
|
+
{ destination: "portals", label: "Portals", icon: "door-open" },
|
|
126
|
+
{ destination: "inbox", label: "Inbox", icon: "inbox" },
|
|
127
|
+
{ destination: "dashboards", label: "Dashboards", icon: "layout-dashboard" },
|
|
128
|
+
{ destination: "archived", label: "Archived records", icon: "archive-restore" }
|
|
129
|
+
];
|
|
130
|
+
var NOOP = () => {
|
|
131
|
+
};
|
|
132
|
+
function action(base, needs, input, perform) {
|
|
133
|
+
const refused = rightsReason(input.rights, needs, input.table);
|
|
134
|
+
if (refused !== null) return { ...base, disabledReason: refused, run: NOOP };
|
|
135
|
+
if (perform === void 0) return { ...base, disabledReason: ACTION_NOT_HERE_REASON, run: NOOP };
|
|
136
|
+
return { ...base, run: perform };
|
|
137
|
+
}
|
|
138
|
+
function bind(handler, table) {
|
|
139
|
+
return handler ? () => handler(table) : void 0;
|
|
140
|
+
}
|
|
141
|
+
function tableActions(input) {
|
|
142
|
+
const { table, host } = input;
|
|
143
|
+
const url = tableLink(table.id, host.origin);
|
|
144
|
+
const favorite = host.isFavorite === true;
|
|
145
|
+
const openInNewTab = host.openInNewTab;
|
|
146
|
+
const copyText = host.copyText;
|
|
147
|
+
const openBuiltOn = host.openBuiltOn;
|
|
148
|
+
const built = [
|
|
149
|
+
action({ id: "open", label: "Open", icon: "folder-open", group: "open" }, "read", input, bind(host.open, table)),
|
|
150
|
+
action(
|
|
151
|
+
{ id: "open-new-tab", label: "Open in new tab", icon: "external-link", group: "open" },
|
|
152
|
+
"read",
|
|
153
|
+
input,
|
|
154
|
+
openInNewTab ? () => openInNewTab(url, table) : void 0
|
|
155
|
+
),
|
|
156
|
+
action(
|
|
157
|
+
{ id: "copy-link", label: "Copy link", icon: "link", group: "open" },
|
|
158
|
+
"read",
|
|
159
|
+
input,
|
|
160
|
+
copyText ? () => copyText(url, table) : void 0
|
|
161
|
+
),
|
|
162
|
+
action({ id: "rename", label: "Rename\u2026", icon: "pencil", group: "organize" }, "structure", input, bind(host.rename, table)),
|
|
163
|
+
action({ id: "duplicate", label: "Duplicate", icon: "copy", group: "organize" }, "read", input, bind(host.duplicate, table)),
|
|
164
|
+
action({ id: "move", label: "Move to\u2026", icon: "folder-input", group: "organize" }, "structure", input, bind(host.move, table)),
|
|
165
|
+
action(
|
|
166
|
+
{
|
|
167
|
+
id: "favorite",
|
|
168
|
+
label: favorite ? "Remove from favorites" : "Add to favorites",
|
|
169
|
+
icon: favorite ? "star-off" : "star",
|
|
170
|
+
group: "organize"
|
|
171
|
+
},
|
|
172
|
+
"read",
|
|
173
|
+
input,
|
|
174
|
+
bind(host.toggleFavorite, table)
|
|
175
|
+
),
|
|
176
|
+
action({ id: "share", label: "Share\u2026", icon: "share-2", group: "share" }, "share", input, bind(host.share, table)),
|
|
177
|
+
action({ id: "export", label: "Export\u2026", icon: "download", group: "share" }, "read", input, bind(host.export, table)),
|
|
178
|
+
action({ id: "import", label: "Import\u2026", icon: "upload", group: "share" }, "structure", input, bind(host.import, table)),
|
|
179
|
+
action({ id: "add-column", label: "Add column", icon: "columns-3", group: "manage" }, "structure", input, bind(host.addColumn, table)),
|
|
180
|
+
action({ id: "settings", label: "Settings", icon: "settings", group: "manage" }, "structure", input, bind(host.settings, table)),
|
|
181
|
+
action({ id: "history", label: "History", icon: "history", group: "manage" }, "read", input, bind(host.history, table)),
|
|
182
|
+
...BUILT_ON.map(
|
|
183
|
+
({ destination, label, icon }) => action(
|
|
184
|
+
{ id: `built-on.${destination}`, label, icon, group: "built-on" },
|
|
185
|
+
"read",
|
|
186
|
+
input,
|
|
187
|
+
openBuiltOn ? () => openBuiltOn(destination, table) : void 0
|
|
188
|
+
)
|
|
189
|
+
),
|
|
190
|
+
action(
|
|
191
|
+
{ id: "archive", label: "Archive table", icon: "archive", group: "danger", destructive: true },
|
|
192
|
+
"structure",
|
|
193
|
+
input,
|
|
194
|
+
bind(host.archive, table)
|
|
195
|
+
)
|
|
196
|
+
];
|
|
197
|
+
return withExtensions(built, host.extend ? host.extend(table, input.rights) : []);
|
|
198
|
+
}
|
|
199
|
+
function withExtensions(base, extra) {
|
|
200
|
+
if (extra.length === 0) return [...base];
|
|
201
|
+
const seen = new Set(base.map((a) => a.id));
|
|
202
|
+
for (const entry of extra) {
|
|
203
|
+
if (entry.group === "danger") {
|
|
204
|
+
throw new Error(`Object action "${entry.id}": the danger group is closed to extensions.`);
|
|
205
|
+
}
|
|
206
|
+
if (seen.has(entry.id)) {
|
|
207
|
+
throw new Error(`Object action "${entry.id}" is already in the list.`);
|
|
208
|
+
}
|
|
209
|
+
seen.add(entry.id);
|
|
210
|
+
}
|
|
211
|
+
const out = [];
|
|
212
|
+
for (const group of OBJECT_ACTION_GROUPS) {
|
|
213
|
+
out.push(...base.filter((a) => a.group === group.id), ...extra.filter((a) => a.group === group.id));
|
|
214
|
+
}
|
|
215
|
+
return out;
|
|
216
|
+
}
|
|
217
|
+
function groupObjectActions(actions) {
|
|
218
|
+
const sections = [];
|
|
219
|
+
for (const group of OBJECT_ACTION_GROUPS) {
|
|
220
|
+
const inGroup = actions.filter((a) => a.group === group.id);
|
|
221
|
+
if (inGroup.length > 0) sections.push({ group, actions: inGroup });
|
|
222
|
+
}
|
|
223
|
+
return sections;
|
|
224
|
+
}
|
|
225
|
+
function toDropdownEntries(actions) {
|
|
226
|
+
const entries = [];
|
|
227
|
+
groupObjectActions(actions).forEach(({ group, actions: inGroup }, index) => {
|
|
228
|
+
if (index > 0) entries.push({ kind: "separator", id: `separator:${group.id}` });
|
|
229
|
+
if (group.submenu) {
|
|
230
|
+
entries.push({ kind: "submenu", id: group.id, label: group.label ?? group.id, icon: group.icon, actions: inGroup });
|
|
231
|
+
} else {
|
|
232
|
+
for (const a of inGroup) entries.push({ kind: "item", action: a });
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
return entries;
|
|
236
|
+
}
|
|
237
|
+
function dropdownActionIds(entries) {
|
|
238
|
+
return entries.flatMap((e) => e.kind === "item" ? [e.action.id] : e.kind === "submenu" ? e.actions.map((a) => a.id) : []);
|
|
239
|
+
}
|
|
240
|
+
//# sourceMappingURL=object-actions.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/objectActions.ts","../src/rights.ts"],"sourcesContent":["// src/objectActions.ts — @ai-matrx/records-ui\n//\n// THE ONE ACTION LIST FOR A RECORD-STORE TABLE (lane TABLE-ACTIONS, v6 lane 1).\n//\n// Every menu a person meets on a table — the header ⋯, a row ⋯ on the Data\n// home, right-click, a card, the phone sheet, later ⌘K — is drawn from the\n// array `tableActions()` returns. Before this there were three hand-made lists\n// (`TABLE_MENU` in TablePage, `dataHomeRowActions` in matrx-frontend, the\n// Sheet's own v3 sections) and they drifted: no Archive, no Copy link, no\n// Duplicate, Rename and Share only as hand JSX beside the list.\n//\n// THE SHAPE IS COPIED, NOT INVENTED. It is the in-house registry pattern of\n// `features/agents/browse/agentActionRegistry.tsx` and `conversationActionRegistry.tsx`\n// in matrx-frontend: a context of HOST HANDLERS in, one ordered list out, every\n// renderer reading the same list. The one difference is deliberate: those two\n// build matrx-frontend's `ItemMenuConfig` directly, which this package cannot\n// import, so the list here is renderer-neutral (`ObjectAction[]`) and each\n// renderer has a pure adapter (`toDropdownEntries` here; `toItemMenuConfig` and\n// `toExtraSections` in matrx-frontend `features/unified-data/actions/`).\n//\n// TWO INVARIANTS, each with a guard (`one-table-action-list-*.test.tsx`):\n// G1 — every adapter renders the same ids in the same order.\n// G2 — rights change only `disabledReason`, never which ids appear or their order\n// (Airtable hides items from members and confuses them; we beat it).\n// A missing host handler is the same: the action stays, disabled, with its reason.\n//\n// THE ORDER (handoff §4, the same everywhere):\n// Open · Open in new tab · Copy link | Rename · Duplicate · Move to… · Favorite |\n// Share… · Export… · Import… | Add column · Settings · History |\n// Built on it ▸ (Forms, Bookings, Checklists, Notifications, Portals, Inbox,\n// Dashboards, Archived records) | Archive table (last, destructive).\n// \"Add column\" is TABLE_MENU's first entry; it sits with Settings because both\n// change the table's shape.\n//\n// No JSX here: this file is the `./object-actions` subpath, light enough for a\n// list row's menu to import without pulling every screen of the package.\n\nimport type { Table } from \"@ai-matrx/records\";\nimport { LEVEL_WORD, levelNeededFor, type Capability, type WhatYouMayDo } from \"./rights\";\n\n/**\n * A Lucide icon NAME, kebab-case — the same currency `Table.icon` already uses.\n * Closed on purpose: each renderer maps every member to its own component, and a\n * new member fails that renderer's typecheck until it is mapped.\n */\nexport type ObjectActionIcon =\n | \"folder-open\"\n | \"external-link\"\n | \"link\"\n | \"pencil\"\n | \"copy\"\n | \"folder-input\"\n | \"star\"\n | \"star-off\"\n | \"share-2\"\n | \"download\"\n | \"upload\"\n | \"columns-3\"\n | \"settings\"\n | \"history\"\n | \"blocks\"\n | \"file-text\"\n | \"calendar-clock\"\n | \"list-checks\"\n | \"bell\"\n | \"door-open\"\n | \"inbox\"\n | \"layout-dashboard\"\n | \"archive-restore\"\n | \"archive\"\n | \"link-2\"\n | \"layout-grid\";\n\n/** The groups, in their one order. Separators fall out of group boundaries. */\nexport type ObjectActionGroup = \"open\" | \"organize\" | \"share\" | \"manage\" | \"built-on\" | \"danger\";\n\nexport interface ObjectActionGroupDef {\n id: ObjectActionGroup;\n /** Present only on a group that renders as a submenu. */\n label?: string;\n icon?: ObjectActionIcon;\n /** Rendered as ONE submenu entry holding the group's actions. */\n submenu: boolean;\n}\n\nexport const OBJECT_ACTION_GROUPS: readonly ObjectActionGroupDef[] = [\n { id: \"open\", submenu: false },\n { id: \"organize\", submenu: false },\n { id: \"share\", submenu: false },\n { id: \"manage\", submenu: false },\n { id: \"built-on\", label: \"Built on it\", icon: \"blocks\", submenu: true },\n { id: \"danger\", submenu: false },\n];\n\n/** One thing a person can do to an object, for any renderer. */\nexport interface ObjectAction {\n /** Stable within the object's list. React key, test handle, future ⌘K id. */\n id: string;\n label: string;\n icon: ObjectActionIcon;\n group: ObjectActionGroup;\n /** Display-only hint (\"⌘D\"). */\n shortcut?: string;\n destructive?: boolean;\n /** Present = shown disabled with this line (≤ 60 chars). Absent = enabled. */\n disabledReason?: string;\n /** Called only when enabled. The host owns the side effect and its feedback. */\n run: () => void | Promise<void>;\n}\n\n/** What the list needs to know about the table. A full `Table` satisfies it. */\nexport type TableActionSubject = Pick<Table, \"id\" | \"name\"> & Partial<Pick<Table, \"is_kernel\">>;\n\n/** The places built on a table, in their one order. */\nexport type BuiltOnDestination =\n | \"forms\"\n | \"bookings\"\n | \"checklists\"\n | \"notifications\"\n | \"portals\"\n | \"inbox\"\n | \"dashboards\"\n | \"archived\";\n\ntype Handler = (table: TableActionSubject) => void | Promise<void>;\n\n/**\n * THE SIDE EFFECTS THIS PACKAGE CANNOT OWN. Every handler is optional: one the\n * host leaves out keeps its action in the list, disabled, saying so.\n */\nexport interface TableActionHost {\n /** Origin for Copy link and Open in new tab. Default `https://www.aimatrx.com`. */\n origin?: string;\n open?: Handler;\n /** Receives the table's link (`<origin>/data-v2/<id>`). */\n openInNewTab?: (url: string, table: TableActionSubject) => void;\n /** Receives the table's link. Runs inside the click (clipboard needs the gesture). */\n copyText?: (text: string, table: TableActionSubject) => void | Promise<void>;\n rename?: Handler;\n duplicate?: Handler;\n move?: Handler;\n /** Current favorite state; flips the Favorite label. Unknown = \"Add to favorites\". */\n isFavorite?: boolean;\n toggleFavorite?: Handler;\n share?: Handler;\n export?: Handler;\n import?: Handler;\n addColumn?: Handler;\n settings?: Handler;\n history?: Handler;\n /** The \"Built on it\" destinations, one handler for all eight. */\n openBuiltOn?: (destination: BuiltOnDestination, table: TableActionSubject) => void;\n archive?: Handler;\n /**\n * ANOTHER LANE'S ENTRIES (\"Link a record…\", a hub tile's verbs). Return the\n * same ids whatever `rights` say — express rights as `disabledReason`. Each\n * entry lands at the END of its named group, in the order returned; `danger`\n * is refused (Archive is always last) and so is an id already in the list.\n */\n extend?: (table: TableActionSubject, rights: WhatYouMayDo) => readonly ObjectAction[];\n}\n\nexport interface TableActionsInput {\n table: TableActionSubject;\n rights: WhatYouMayDo;\n host: TableActionHost;\n}\n\nexport const DEFAULT_ACTION_ORIGIN = \"https://www.aimatrx.com\";\n\n/** The one link to a table: `<origin>/data-v2/<tableId>`. */\nexport function tableLink(tableId: string, origin: string = DEFAULT_ACTION_ORIGIN): string {\n return `${origin.replace(/\\/+$/, \"\")}/data-v2/${encodeURIComponent(tableId)}`;\n}\n\n// Reasons — one line, ≤ 60 characters, said on the action itself.\nexport const ACTION_NOT_HERE_REASON = \"Not available here\";\nexport const ACTION_CHECKING_ACCESS_REASON = \"Checking your access…\";\nexport const ACTION_NO_ACCESS_REASON = \"You don't have access\";\nexport const ACTION_PLATFORM_TABLE_REASON = \"Platform tables can't be changed here\";\n\n/** Why `rights` refuse `what`, in one short line — or null when they allow it. */\nexport function rightsReason(\n rights: WhatYouMayDo,\n what: Capability,\n table?: TableActionSubject,\n): string | null {\n if (!rights.known) return ACTION_CHECKING_ACCESS_REASON;\n if (what === \"structure\" && table?.is_kernel) return ACTION_PLATFORM_TABLE_REASON;\n if (rights[what]) return null;\n if (rights.level === null) return ACTION_NO_ACCESS_REASON;\n return `Needs ${LEVEL_WORD[levelNeededFor(what)]} access`;\n}\n\nconst BUILT_ON: ReadonlyArray<{ destination: BuiltOnDestination; label: string; icon: ObjectActionIcon }> = [\n { destination: \"forms\", label: \"Forms\", icon: \"file-text\" },\n { destination: \"bookings\", label: \"Bookings\", icon: \"calendar-clock\" },\n { destination: \"checklists\", label: \"Checklists\", icon: \"list-checks\" },\n { destination: \"notifications\", label: \"Notifications\", icon: \"bell\" },\n { destination: \"portals\", label: \"Portals\", icon: \"door-open\" },\n { destination: \"inbox\", label: \"Inbox\", icon: \"inbox\" },\n { destination: \"dashboards\", label: \"Dashboards\", icon: \"layout-dashboard\" },\n { destination: \"archived\", label: \"Archived records\", icon: \"archive-restore\" },\n];\n\nconst NOOP = () => {};\n\n/**\n * One action, from its needs: the capability it asks for and the host handler\n * that performs it. Rights speak first; a missing handler second.\n */\nfunction action(\n base: Omit<ObjectAction, \"run\" | \"disabledReason\">,\n needs: Capability,\n input: TableActionsInput,\n perform: (() => void | Promise<void>) | undefined,\n): ObjectAction {\n const refused = rightsReason(input.rights, needs, input.table);\n if (refused !== null) return { ...base, disabledReason: refused, run: NOOP };\n if (perform === undefined) return { ...base, disabledReason: ACTION_NOT_HERE_REASON, run: NOOP };\n return { ...base, run: perform };\n}\n\nfunction bind(handler: Handler | undefined, table: TableActionSubject): (() => void | Promise<void>) | undefined {\n return handler ? () => handler(table) : undefined;\n}\n\n/**\n * THE TABLE'S ACTIONS, in the one order. Same ids, same order, for every seat\n * and every host — only `disabledReason` varies.\n */\nexport function tableActions(input: TableActionsInput): ObjectAction[] {\n const { table, host } = input;\n const url = tableLink(table.id, host.origin);\n const favorite = host.isFavorite === true;\n const openInNewTab = host.openInNewTab;\n const copyText = host.copyText;\n const openBuiltOn = host.openBuiltOn;\n\n const built: ObjectAction[] = [\n action({ id: \"open\", label: \"Open\", icon: \"folder-open\", group: \"open\" }, \"read\", input, bind(host.open, table)),\n action(\n { id: \"open-new-tab\", label: \"Open in new tab\", icon: \"external-link\", group: \"open\" },\n \"read\",\n input,\n openInNewTab ? () => openInNewTab(url, table) : undefined,\n ),\n action(\n { id: \"copy-link\", label: \"Copy link\", icon: \"link\", group: \"open\" },\n \"read\",\n input,\n copyText ? () => copyText(url, table) : undefined,\n ),\n action({ id: \"rename\", label: \"Rename…\", icon: \"pencil\", group: \"organize\" }, \"structure\", input, bind(host.rename, table)),\n action({ id: \"duplicate\", label: \"Duplicate\", icon: \"copy\", group: \"organize\" }, \"read\", input, bind(host.duplicate, table)),\n action({ id: \"move\", label: \"Move to…\", icon: \"folder-input\", group: \"organize\" }, \"structure\", input, bind(host.move, table)),\n action(\n {\n id: \"favorite\",\n label: favorite ? \"Remove from favorites\" : \"Add to favorites\",\n icon: favorite ? \"star-off\" : \"star\",\n group: \"organize\",\n },\n \"read\",\n input,\n bind(host.toggleFavorite, table),\n ),\n action({ id: \"share\", label: \"Share…\", icon: \"share-2\", group: \"share\" }, \"share\", input, bind(host.share, table)),\n action({ id: \"export\", label: \"Export…\", icon: \"download\", group: \"share\" }, \"read\", input, bind(host.export, table)),\n action({ id: \"import\", label: \"Import…\", icon: \"upload\", group: \"share\" }, \"structure\", input, bind(host.import, table)),\n action({ id: \"add-column\", label: \"Add column\", icon: \"columns-3\", group: \"manage\" }, \"structure\", input, bind(host.addColumn, table)),\n action({ id: \"settings\", label: \"Settings\", icon: \"settings\", group: \"manage\" }, \"structure\", input, bind(host.settings, table)),\n action({ id: \"history\", label: \"History\", icon: \"history\", group: \"manage\" }, \"read\", input, bind(host.history, table)),\n ...BUILT_ON.map(({ destination, label, icon }) =>\n action(\n { id: `built-on.${destination}`, label, icon, group: \"built-on\" },\n \"read\",\n input,\n openBuiltOn ? () => openBuiltOn(destination, table) : undefined,\n ),\n ),\n action(\n { id: \"archive\", label: \"Archive table\", icon: \"archive\", group: \"danger\", destructive: true },\n \"structure\",\n input,\n bind(host.archive, table),\n ),\n ];\n\n return withExtensions(built, host.extend ? host.extend(table, input.rights) : []);\n}\n\n/**\n * Another lane's entries, placed deterministically: each at the end of its\n * group, in the order given. Loud on the two mistakes that would break the\n * invariants — a `danger` entry (Archive must stay last) and a duplicate id.\n */\nexport function withExtensions(base: readonly ObjectAction[], extra: readonly ObjectAction[]): ObjectAction[] {\n if (extra.length === 0) return [...base];\n const seen = new Set(base.map((a) => a.id));\n for (const entry of extra) {\n if (entry.group === \"danger\") {\n throw new Error(`Object action \"${entry.id}\": the danger group is closed to extensions.`);\n }\n if (seen.has(entry.id)) {\n throw new Error(`Object action \"${entry.id}\" is already in the list.`);\n }\n seen.add(entry.id);\n }\n const out: ObjectAction[] = [];\n for (const group of OBJECT_ACTION_GROUPS) {\n out.push(...base.filter((a) => a.group === group.id), ...extra.filter((a) => a.group === group.id));\n }\n return out;\n}\n\n/** A run of actions that renders together: a plain section, or one submenu. */\nexport interface ObjectActionSection {\n group: ObjectActionGroupDef;\n actions: ObjectAction[];\n}\n\n/**\n * THE ONE GROUPING every adapter uses: the list cut at group boundaries, in\n * group order, empty groups dropped, the order inside a group kept.\n */\nexport function groupObjectActions(actions: readonly ObjectAction[]): ObjectActionSection[] {\n const sections: ObjectActionSection[] = [];\n for (const group of OBJECT_ACTION_GROUPS) {\n const inGroup = actions.filter((a) => a.group === group.id);\n if (inGroup.length > 0) sections.push({ group, actions: inGroup });\n }\n return sections;\n}\n\n/** The header ⋯ dropdown's entries — records-ui's own renderer reads these. */\nexport type ObjectActionDropdownEntry =\n | { kind: \"separator\"; id: string }\n | { kind: \"item\"; action: ObjectAction }\n | { kind: \"submenu\"; id: string; label: string; icon: ObjectActionIcon | undefined; actions: ObjectAction[] };\n\nexport function toDropdownEntries(actions: readonly ObjectAction[]): ObjectActionDropdownEntry[] {\n const entries: ObjectActionDropdownEntry[] = [];\n groupObjectActions(actions).forEach(({ group, actions: inGroup }, index) => {\n if (index > 0) entries.push({ kind: \"separator\", id: `separator:${group.id}` });\n if (group.submenu) {\n entries.push({ kind: \"submenu\", id: group.id, label: group.label ?? group.id, icon: group.icon, actions: inGroup });\n } else {\n for (const a of inGroup) entries.push({ kind: \"item\", action: a });\n }\n });\n return entries;\n}\n\n/** The action ids a dropdown draws, in order — what G1 compares. */\nexport function dropdownActionIds(entries: readonly ObjectActionDropdownEntry[]): string[] {\n return entries.flatMap((e) => (e.kind === \"item\" ? [e.action.id] : e.kind === \"submenu\" ? e.actions.map((a) => a.id) : []));\n}\n","// src/rights.ts — @ai-matrx/records-ui\n//\n// THE ONE PLACE A LEVEL BECOMES A SCREEN.\n//\n// WHAT A PERSON HIT (independent verdict, sixth pass, 19-20 September). A\n// colleague who had been shared a table at viewer opened it, double-clicked a\n// cell, got a live editor, typed a value and pressed Enter — and only THEN was\n// told she may not write. \"The permission is right; the screen told her she\n// could.\" Measured against Airtable, Notion and Linear the verdict called it\n// out by name: \"None of these three products lets you start an action it\n// already knows it will reject.\"\n//\n// WHY THE SCREEN COULD NOT KNOW. The rights question was a PORT with two honest\n// answers and no third: `NO_RIGHTS` (every administering control absent, which\n// is useless for a host whose authority IS the store's) and\n// `storeDecidesRights` — `{admin: true, comment: true, write: true}` for every\n// non-kernel table, for everybody. That second one is the defect, written down:\n// it is the screen ASSUMING the answer and letting the door say no afterwards.\n//\n// WHAT REPLACES IT. The store already answers this, and the client now carries\n// it: `custom.read_records` returns the reader's level on the TABLE on every\n// row (so `Table.your_level` costs nothing), and `custom.my_levels` — the door\n// this lane added — answers the caller's level on any subject they name, one\n// call for a whole page. This file is the only place in the package that turns\n// one of those four words into what a screen may DRAW.\n//\n// THE LADDER IS THE STORE'S, NOT OURS: viewer < commenter < editor < admin, and\n// which rung each door requires is read from the doors themselves —\n// `custom.record_update` asserts `editor` on the record, `custom.field_declare`\n// asserts `admin` on the TABLE (\"the SHAPE of this table, which is an admin's\n// right and not an editor's\"), `custom.io_comment_write` admits a commenter.\n// Nothing here invents a rule; it mirrors the four assertions those doors make\n// and is wrong the moment they change, which is what the suite is for.\n//\n// ABSENT OR HONEST, NEVER DEAD. Every `false` here carries the sentence that\n// says why and what to do about it, so a control can be left out AND explained.\n// A screen that hid a control with nothing to say is the silent half of the\n// same defect.\n\nimport { atLeast, type PermissionLevel, type Table } from \"@ai-matrx/records\";\n\n/** The six things a person does to a record or a table, as the screens offer them. */\nexport type Capability = \"read\" | \"comment\" | \"write\" | \"remove\" | \"share\" | \"structure\";\n\nexport interface WhatYouMayDo {\n /** The word the store answered, or null when it answered nothing. */\n level: PermissionLevel | null;\n /**\n * Whether the store has ANSWERED yet. `false` is a third state and not a\n * refusal: a screen that drew \"you may not\" while the answer was in flight\n * would blink every control off on every reload, which is its own lie.\n */\n known: boolean;\n read: boolean;\n comment: boolean;\n write: boolean;\n remove: boolean;\n share: boolean;\n structure: boolean;\n /** Why this person may not do that, in one sentence naming the way in. */\n why: (what: Capability) => string;\n}\n\n/** What each rung of the ladder is called where a person reads it. */\nexport const LEVEL_WORD: Record<PermissionLevel, string> = {\n viewer: \"Viewer\",\n commenter: \"Commenter\",\n editor: \"Editor\",\n admin: \"Admin\",\n};\n\n/**\n * WHICH RUNG EACH DOOR ASKS FOR. Read off the doors themselves, in one place,\n * so a screen never re-decides it and the suite has one thing to check.\n */\nconst NEEDS: Record<Capability, PermissionLevel> = {\n read: \"viewer\",\n // custom.io_comment_write\n comment: \"commenter\",\n // custom.record_update / custom.record_write\n write: \"editor\",\n // custom.record_delete\n remove: \"editor\",\n // custom.share_grant — passing your access on is an administrator's right\n share: \"admin\",\n // custom.field_declare / field_update / field_retire / table settings\n structure: \"admin\",\n};\n\n/** The rung a capability asks for — so a short \"Needs Admin access\" never re-decides the ladder. */\nexport function levelNeededFor(what: Capability): PermissionLevel {\n return NEEDS[what];\n}\n\nconst NOT_YET =\n \"Still asking the store what you may do here. Nothing is offered until it has answered.\";\n\nconst NOTHING_AT_ALL =\n \"You have not been given this. Ask somebody who administers it to share it with you.\";\n\n/** The sentence for one refusal: what the level is, what it would have to be. */\nfunction sentenceFor(what: Capability, level: PermissionLevel | null): string {\n if (level === null) return NOTHING_AT_ALL;\n const need = LEVEL_WORD[NEEDS[what]];\n const have = LEVEL_WORD[level];\n const doing: Record<Capability, string> = {\n read: \"open this\",\n comment: \"leave a comment\",\n write: \"change anything here\",\n remove: \"delete this\",\n share: \"pass this on to somebody else\",\n structure: \"change the columns of this table\",\n };\n return `You are ${have} here, and it takes ${need} to ${doing[what]}. Ask somebody who is Admin on it to move you up.`;\n}\n\n/**\n * WHAT ONE LEVEL LETS A PERSON DO. `level` null and `known` true means the\n * store said \"nothing\" — a real answer. `known` false means it has not said.\n */\nexport function whatYouMayDo(level: PermissionLevel | null, known = true): WhatYouMayDo {\n const may = (what: Capability) => known && atLeast(level, NEEDS[what]);\n return {\n level: known ? level : null,\n known,\n read: may(\"read\"),\n comment: may(\"comment\"),\n write: may(\"write\"),\n remove: may(\"remove\"),\n share: may(\"share\"),\n structure: may(\"structure\"),\n why: (what) => (known ? sentenceFor(what, level) : NOT_YET),\n };\n}\n\n/** Nothing offered, because nothing is known yet. */\nexport const NOT_ANSWERED_YET: WhatYouMayDo = whatYouMayDo(null, false);\n\n/**\n * REC-27. The nine Tables the platform ships are not administered from a\n * person's screen whatever level they hold on them — so the structure controls\n * are absent for a kernel Table and say why, and everything else follows the\n * level as usual.\n */\nexport const KERNEL_REASON =\n \"This is one of the tables the platform ships, so its columns are not changed from a screen. \" +\n \"Make your own table to change columns.\";\n\nexport function whatYouMayDoWithTable(\n table: Table | null | undefined,\n level: PermissionLevel | null,\n known: boolean,\n): WhatYouMayDo {\n const base = whatYouMayDo(level, known);\n if (!table?.is_kernel) return base;\n return {\n ...base,\n structure: false,\n why: (what) => (what === \"structure\" ? KERNEL_REASON : base.why(what)),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACuCA,qBAA0D;AAyBnD,IAAM,aAA8C;AAAA,EACzD,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,OAAO;AACT;AAMA,IAAM,QAA6C;AAAA,EACjD,MAAM;AAAA;AAAA,EAEN,SAAS;AAAA;AAAA,EAET,OAAO;AAAA;AAAA,EAEP,QAAQ;AAAA;AAAA,EAER,OAAO;AAAA;AAAA,EAEP,WAAW;AACb;AAGO,SAAS,eAAe,MAAmC;AAChE,SAAO,MAAM,IAAI;AACnB;AAEA,IAAM,UACJ;AAEF,IAAM,iBACJ;AAGF,SAAS,YAAY,MAAkB,OAAuC;AAC5E,MAAI,UAAU,KAAM,QAAO;AAC3B,QAAM,OAAO,WAAW,MAAM,IAAI,CAAC;AACnC,QAAM,OAAO,WAAW,KAAK;AAC7B,QAAM,QAAoC;AAAA,IACxC,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,WAAW;AAAA,EACb;AACA,SAAO,WAAW,IAAI,uBAAuB,IAAI,OAAO,MAAM,IAAI,CAAC;AACrE;AAMO,SAAS,aAAa,OAA+B,QAAQ,MAAoB;AACtF,QAAM,MAAM,CAAC,SAAqB,aAAS,wBAAQ,OAAO,MAAM,IAAI,CAAC;AACrE,SAAO;AAAA,IACL,OAAO,QAAQ,QAAQ;AAAA,IACvB;AAAA,IACA,MAAM,IAAI,MAAM;AAAA,IAChB,SAAS,IAAI,SAAS;AAAA,IACtB,OAAO,IAAI,OAAO;AAAA,IAClB,QAAQ,IAAI,QAAQ;AAAA,IACpB,OAAO,IAAI,OAAO;AAAA,IAClB,WAAW,IAAI,WAAW;AAAA,IAC1B,KAAK,CAAC,SAAU,QAAQ,YAAY,MAAM,KAAK,IAAI;AAAA,EACrD;AACF;AAGO,IAAM,mBAAiC,aAAa,MAAM,KAAK;;;ADnD/D,IAAM,uBAAwD;AAAA,EACnE,EAAE,IAAI,QAAQ,SAAS,MAAM;AAAA,EAC7B,EAAE,IAAI,YAAY,SAAS,MAAM;AAAA,EACjC,EAAE,IAAI,SAAS,SAAS,MAAM;AAAA,EAC9B,EAAE,IAAI,UAAU,SAAS,MAAM;AAAA,EAC/B,EAAE,IAAI,YAAY,OAAO,eAAe,MAAM,UAAU,SAAS,KAAK;AAAA,EACtE,EAAE,IAAI,UAAU,SAAS,MAAM;AACjC;AA4EO,IAAM,wBAAwB;AAG9B,SAAS,UAAU,SAAiB,SAAiB,uBAA+B;AACzF,SAAO,GAAG,OAAO,QAAQ,QAAQ,EAAE,CAAC,YAAY,mBAAmB,OAAO,CAAC;AAC7E;AAGO,IAAM,yBAAyB;AAC/B,IAAM,gCAAgC;AACtC,IAAM,0BAA0B;AAChC,IAAM,+BAA+B;AAGrC,SAAS,aACd,QACA,MACA,OACe;AACf,MAAI,CAAC,OAAO,MAAO,QAAO;AAC1B,MAAI,SAAS,eAAe,OAAO,UAAW,QAAO;AACrD,MAAI,OAAO,IAAI,EAAG,QAAO;AACzB,MAAI,OAAO,UAAU,KAAM,QAAO;AAClC,SAAO,SAAS,WAAW,eAAe,IAAI,CAAC,CAAC;AAClD;AAEA,IAAM,WAAsG;AAAA,EAC1G,EAAE,aAAa,SAAS,OAAO,SAAS,MAAM,YAAY;AAAA,EAC1D,EAAE,aAAa,YAAY,OAAO,YAAY,MAAM,iBAAiB;AAAA,EACrE,EAAE,aAAa,cAAc,OAAO,cAAc,MAAM,cAAc;AAAA,EACtE,EAAE,aAAa,iBAAiB,OAAO,iBAAiB,MAAM,OAAO;AAAA,EACrE,EAAE,aAAa,WAAW,OAAO,WAAW,MAAM,YAAY;AAAA,EAC9D,EAAE,aAAa,SAAS,OAAO,SAAS,MAAM,QAAQ;AAAA,EACtD,EAAE,aAAa,cAAc,OAAO,cAAc,MAAM,mBAAmB;AAAA,EAC3E,EAAE,aAAa,YAAY,OAAO,oBAAoB,MAAM,kBAAkB;AAChF;AAEA,IAAM,OAAO,MAAM;AAAC;AAMpB,SAAS,OACP,MACA,OACA,OACA,SACc;AACd,QAAM,UAAU,aAAa,MAAM,QAAQ,OAAO,MAAM,KAAK;AAC7D,MAAI,YAAY,KAAM,QAAO,EAAE,GAAG,MAAM,gBAAgB,SAAS,KAAK,KAAK;AAC3E,MAAI,YAAY,OAAW,QAAO,EAAE,GAAG,MAAM,gBAAgB,wBAAwB,KAAK,KAAK;AAC/F,SAAO,EAAE,GAAG,MAAM,KAAK,QAAQ;AACjC;AAEA,SAAS,KAAK,SAA8B,OAAqE;AAC/G,SAAO,UAAU,MAAM,QAAQ,KAAK,IAAI;AAC1C;AAMO,SAAS,aAAa,OAA0C;AACrE,QAAM,EAAE,OAAO,KAAK,IAAI;AACxB,QAAM,MAAM,UAAU,MAAM,IAAI,KAAK,MAAM;AAC3C,QAAM,WAAW,KAAK,eAAe;AACrC,QAAM,eAAe,KAAK;AAC1B,QAAM,WAAW,KAAK;AACtB,QAAM,cAAc,KAAK;AAEzB,QAAM,QAAwB;AAAA,IAC5B,OAAO,EAAE,IAAI,QAAQ,OAAO,QAAQ,MAAM,eAAe,OAAO,OAAO,GAAG,QAAQ,OAAO,KAAK,KAAK,MAAM,KAAK,CAAC;AAAA,IAC/G;AAAA,MACE,EAAE,IAAI,gBAAgB,OAAO,mBAAmB,MAAM,iBAAiB,OAAO,OAAO;AAAA,MACrF;AAAA,MACA;AAAA,MACA,eAAe,MAAM,aAAa,KAAK,KAAK,IAAI;AAAA,IAClD;AAAA,IACA;AAAA,MACE,EAAE,IAAI,aAAa,OAAO,aAAa,MAAM,QAAQ,OAAO,OAAO;AAAA,MACnE;AAAA,MACA;AAAA,MACA,WAAW,MAAM,SAAS,KAAK,KAAK,IAAI;AAAA,IAC1C;AAAA,IACA,OAAO,EAAE,IAAI,UAAU,OAAO,gBAAW,MAAM,UAAU,OAAO,WAAW,GAAG,aAAa,OAAO,KAAK,KAAK,QAAQ,KAAK,CAAC;AAAA,IAC1H,OAAO,EAAE,IAAI,aAAa,OAAO,aAAa,MAAM,QAAQ,OAAO,WAAW,GAAG,QAAQ,OAAO,KAAK,KAAK,WAAW,KAAK,CAAC;AAAA,IAC3H,OAAO,EAAE,IAAI,QAAQ,OAAO,iBAAY,MAAM,gBAAgB,OAAO,WAAW,GAAG,aAAa,OAAO,KAAK,KAAK,MAAM,KAAK,CAAC;AAAA,IAC7H;AAAA,MACE;AAAA,QACE,IAAI;AAAA,QACJ,OAAO,WAAW,0BAA0B;AAAA,QAC5C,MAAM,WAAW,aAAa;AAAA,QAC9B,OAAO;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA,KAAK,KAAK,gBAAgB,KAAK;AAAA,IACjC;AAAA,IACA,OAAO,EAAE,IAAI,SAAS,OAAO,eAAU,MAAM,WAAW,OAAO,QAAQ,GAAG,SAAS,OAAO,KAAK,KAAK,OAAO,KAAK,CAAC;AAAA,IACjH,OAAO,EAAE,IAAI,UAAU,OAAO,gBAAW,MAAM,YAAY,OAAO,QAAQ,GAAG,QAAQ,OAAO,KAAK,KAAK,QAAQ,KAAK,CAAC;AAAA,IACpH,OAAO,EAAE,IAAI,UAAU,OAAO,gBAAW,MAAM,UAAU,OAAO,QAAQ,GAAG,aAAa,OAAO,KAAK,KAAK,QAAQ,KAAK,CAAC;AAAA,IACvH,OAAO,EAAE,IAAI,cAAc,OAAO,cAAc,MAAM,aAAa,OAAO,SAAS,GAAG,aAAa,OAAO,KAAK,KAAK,WAAW,KAAK,CAAC;AAAA,IACrI,OAAO,EAAE,IAAI,YAAY,OAAO,YAAY,MAAM,YAAY,OAAO,SAAS,GAAG,aAAa,OAAO,KAAK,KAAK,UAAU,KAAK,CAAC;AAAA,IAC/H,OAAO,EAAE,IAAI,WAAW,OAAO,WAAW,MAAM,WAAW,OAAO,SAAS,GAAG,QAAQ,OAAO,KAAK,KAAK,SAAS,KAAK,CAAC;AAAA,IACtH,GAAG,SAAS;AAAA,MAAI,CAAC,EAAE,aAAa,OAAO,KAAK,MAC1C;AAAA,QACE,EAAE,IAAI,YAAY,WAAW,IAAI,OAAO,MAAM,OAAO,WAAW;AAAA,QAChE;AAAA,QACA;AAAA,QACA,cAAc,MAAM,YAAY,aAAa,KAAK,IAAI;AAAA,MACxD;AAAA,IACF;AAAA,IACA;AAAA,MACE,EAAE,IAAI,WAAW,OAAO,iBAAiB,MAAM,WAAW,OAAO,UAAU,aAAa,KAAK;AAAA,MAC7F;AAAA,MACA;AAAA,MACA,KAAK,KAAK,SAAS,KAAK;AAAA,IAC1B;AAAA,EACF;AAEA,SAAO,eAAe,OAAO,KAAK,SAAS,KAAK,OAAO,OAAO,MAAM,MAAM,IAAI,CAAC,CAAC;AAClF;AAOO,SAAS,eAAe,MAA+B,OAAgD;AAC5G,MAAI,MAAM,WAAW,EAAG,QAAO,CAAC,GAAG,IAAI;AACvC,QAAM,OAAO,IAAI,IAAI,KAAK,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AAC1C,aAAW,SAAS,OAAO;AACzB,QAAI,MAAM,UAAU,UAAU;AAC5B,YAAM,IAAI,MAAM,kBAAkB,MAAM,EAAE,8CAA8C;AAAA,IAC1F;AACA,QAAI,KAAK,IAAI,MAAM,EAAE,GAAG;AACtB,YAAM,IAAI,MAAM,kBAAkB,MAAM,EAAE,2BAA2B;AAAA,IACvE;AACA,SAAK,IAAI,MAAM,EAAE;AAAA,EACnB;AACA,QAAM,MAAsB,CAAC;AAC7B,aAAW,SAAS,sBAAsB;AACxC,QAAI,KAAK,GAAG,KAAK,OAAO,CAAC,MAAM,EAAE,UAAU,MAAM,EAAE,GAAG,GAAG,MAAM,OAAO,CAAC,MAAM,EAAE,UAAU,MAAM,EAAE,CAAC;AAAA,EACpG;AACA,SAAO;AACT;AAYO,SAAS,mBAAmB,SAAyD;AAC1F,QAAM,WAAkC,CAAC;AACzC,aAAW,SAAS,sBAAsB;AACxC,UAAM,UAAU,QAAQ,OAAO,CAAC,MAAM,EAAE,UAAU,MAAM,EAAE;AAC1D,QAAI,QAAQ,SAAS,EAAG,UAAS,KAAK,EAAE,OAAO,SAAS,QAAQ,CAAC;AAAA,EACnE;AACA,SAAO;AACT;AAQO,SAAS,kBAAkB,SAA+D;AAC/F,QAAM,UAAuC,CAAC;AAC9C,qBAAmB,OAAO,EAAE,QAAQ,CAAC,EAAE,OAAO,SAAS,QAAQ,GAAG,UAAU;AAC1E,QAAI,QAAQ,EAAG,SAAQ,KAAK,EAAE,MAAM,aAAa,IAAI,aAAa,MAAM,EAAE,GAAG,CAAC;AAC9E,QAAI,MAAM,SAAS;AACjB,cAAQ,KAAK,EAAE,MAAM,WAAW,IAAI,MAAM,IAAI,OAAO,MAAM,SAAS,MAAM,IAAI,MAAM,MAAM,MAAM,SAAS,QAAQ,CAAC;AAAA,IACpH,OAAO;AACL,iBAAW,KAAK,QAAS,SAAQ,KAAK,EAAE,MAAM,QAAQ,QAAQ,EAAE,CAAC;AAAA,IACnE;AAAA,EACF,CAAC;AACD,SAAO;AACT;AAGO,SAAS,kBAAkB,SAAyD;AACzF,SAAO,QAAQ,QAAQ,CAAC,MAAO,EAAE,SAAS,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,YAAY,EAAE,QAAQ,IAAI,CAAC,MAAM,EAAE,EAAE,IAAI,CAAC,CAAE;AAC5H;","names":[]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import '@ai-matrx/records';
|
|
2
|
+
export { A as ACTION_CHECKING_ACCESS_REASON, a as ACTION_NOT_HERE_REASON, b as ACTION_NO_ACCESS_REASON, c as ACTION_PLATFORM_TABLE_REASON, B as BuiltOnDestination, D as DEFAULT_ACTION_ORIGIN, d as OBJECT_ACTION_GROUPS, O as ObjectAction, e as ObjectActionDropdownEntry, f as ObjectActionGroup, g as ObjectActionGroupDef, h as ObjectActionIcon, i as ObjectActionSection, T as TableActionHost, j as TableActionSubject, k as TableActionsInput, l as dropdownActionIds, m as groupObjectActions, r as rightsReason, t as tableActions, o as tableLink, p as toDropdownEntries, s as withExtensions } from './objectActions-BSbblgf-.cjs';
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import '@ai-matrx/records';
|
|
2
|
+
export { A as ACTION_CHECKING_ACCESS_REASON, a as ACTION_NOT_HERE_REASON, b as ACTION_NO_ACCESS_REASON, c as ACTION_PLATFORM_TABLE_REASON, B as BuiltOnDestination, D as DEFAULT_ACTION_ORIGIN, d as OBJECT_ACTION_GROUPS, O as ObjectAction, e as ObjectActionDropdownEntry, f as ObjectActionGroup, g as ObjectActionGroupDef, h as ObjectActionIcon, i as ObjectActionSection, T as TableActionHost, j as TableActionSubject, k as TableActionsInput, l as dropdownActionIds, m as groupObjectActions, r as rightsReason, t as tableActions, o as tableLink, p as toDropdownEntries, s as withExtensions } from './objectActions-BSbblgf-.js';
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/rights.ts
|
|
4
|
+
import { atLeast } from "@ai-matrx/records";
|
|
5
|
+
var LEVEL_WORD = {
|
|
6
|
+
viewer: "Viewer",
|
|
7
|
+
commenter: "Commenter",
|
|
8
|
+
editor: "Editor",
|
|
9
|
+
admin: "Admin"
|
|
10
|
+
};
|
|
11
|
+
var NEEDS = {
|
|
12
|
+
read: "viewer",
|
|
13
|
+
// custom.io_comment_write
|
|
14
|
+
comment: "commenter",
|
|
15
|
+
// custom.record_update / custom.record_write
|
|
16
|
+
write: "editor",
|
|
17
|
+
// custom.record_delete
|
|
18
|
+
remove: "editor",
|
|
19
|
+
// custom.share_grant — passing your access on is an administrator's right
|
|
20
|
+
share: "admin",
|
|
21
|
+
// custom.field_declare / field_update / field_retire / table settings
|
|
22
|
+
structure: "admin"
|
|
23
|
+
};
|
|
24
|
+
function levelNeededFor(what) {
|
|
25
|
+
return NEEDS[what];
|
|
26
|
+
}
|
|
27
|
+
var NOT_YET = "Still asking the store what you may do here. Nothing is offered until it has answered.";
|
|
28
|
+
var NOTHING_AT_ALL = "You have not been given this. Ask somebody who administers it to share it with you.";
|
|
29
|
+
function sentenceFor(what, level) {
|
|
30
|
+
if (level === null) return NOTHING_AT_ALL;
|
|
31
|
+
const need = LEVEL_WORD[NEEDS[what]];
|
|
32
|
+
const have = LEVEL_WORD[level];
|
|
33
|
+
const doing = {
|
|
34
|
+
read: "open this",
|
|
35
|
+
comment: "leave a comment",
|
|
36
|
+
write: "change anything here",
|
|
37
|
+
remove: "delete this",
|
|
38
|
+
share: "pass this on to somebody else",
|
|
39
|
+
structure: "change the columns of this table"
|
|
40
|
+
};
|
|
41
|
+
return `You are ${have} here, and it takes ${need} to ${doing[what]}. Ask somebody who is Admin on it to move you up.`;
|
|
42
|
+
}
|
|
43
|
+
function whatYouMayDo(level, known = true) {
|
|
44
|
+
const may = (what) => known && atLeast(level, NEEDS[what]);
|
|
45
|
+
return {
|
|
46
|
+
level: known ? level : null,
|
|
47
|
+
known,
|
|
48
|
+
read: may("read"),
|
|
49
|
+
comment: may("comment"),
|
|
50
|
+
write: may("write"),
|
|
51
|
+
remove: may("remove"),
|
|
52
|
+
share: may("share"),
|
|
53
|
+
structure: may("structure"),
|
|
54
|
+
why: (what) => known ? sentenceFor(what, level) : NOT_YET
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
var NOT_ANSWERED_YET = whatYouMayDo(null, false);
|
|
58
|
+
|
|
59
|
+
// src/objectActions.ts
|
|
60
|
+
var OBJECT_ACTION_GROUPS = [
|
|
61
|
+
{ id: "open", submenu: false },
|
|
62
|
+
{ id: "organize", submenu: false },
|
|
63
|
+
{ id: "share", submenu: false },
|
|
64
|
+
{ id: "manage", submenu: false },
|
|
65
|
+
{ id: "built-on", label: "Built on it", icon: "blocks", submenu: true },
|
|
66
|
+
{ id: "danger", submenu: false }
|
|
67
|
+
];
|
|
68
|
+
var DEFAULT_ACTION_ORIGIN = "https://www.aimatrx.com";
|
|
69
|
+
function tableLink(tableId, origin = DEFAULT_ACTION_ORIGIN) {
|
|
70
|
+
return `${origin.replace(/\/+$/, "")}/data-v2/${encodeURIComponent(tableId)}`;
|
|
71
|
+
}
|
|
72
|
+
var ACTION_NOT_HERE_REASON = "Not available here";
|
|
73
|
+
var ACTION_CHECKING_ACCESS_REASON = "Checking your access\u2026";
|
|
74
|
+
var ACTION_NO_ACCESS_REASON = "You don't have access";
|
|
75
|
+
var ACTION_PLATFORM_TABLE_REASON = "Platform tables can't be changed here";
|
|
76
|
+
function rightsReason(rights, what, table) {
|
|
77
|
+
if (!rights.known) return ACTION_CHECKING_ACCESS_REASON;
|
|
78
|
+
if (what === "structure" && table?.is_kernel) return ACTION_PLATFORM_TABLE_REASON;
|
|
79
|
+
if (rights[what]) return null;
|
|
80
|
+
if (rights.level === null) return ACTION_NO_ACCESS_REASON;
|
|
81
|
+
return `Needs ${LEVEL_WORD[levelNeededFor(what)]} access`;
|
|
82
|
+
}
|
|
83
|
+
var BUILT_ON = [
|
|
84
|
+
{ destination: "forms", label: "Forms", icon: "file-text" },
|
|
85
|
+
{ destination: "bookings", label: "Bookings", icon: "calendar-clock" },
|
|
86
|
+
{ destination: "checklists", label: "Checklists", icon: "list-checks" },
|
|
87
|
+
{ destination: "notifications", label: "Notifications", icon: "bell" },
|
|
88
|
+
{ destination: "portals", label: "Portals", icon: "door-open" },
|
|
89
|
+
{ destination: "inbox", label: "Inbox", icon: "inbox" },
|
|
90
|
+
{ destination: "dashboards", label: "Dashboards", icon: "layout-dashboard" },
|
|
91
|
+
{ destination: "archived", label: "Archived records", icon: "archive-restore" }
|
|
92
|
+
];
|
|
93
|
+
var NOOP = () => {
|
|
94
|
+
};
|
|
95
|
+
function action(base, needs, input, perform) {
|
|
96
|
+
const refused = rightsReason(input.rights, needs, input.table);
|
|
97
|
+
if (refused !== null) return { ...base, disabledReason: refused, run: NOOP };
|
|
98
|
+
if (perform === void 0) return { ...base, disabledReason: ACTION_NOT_HERE_REASON, run: NOOP };
|
|
99
|
+
return { ...base, run: perform };
|
|
100
|
+
}
|
|
101
|
+
function bind(handler, table) {
|
|
102
|
+
return handler ? () => handler(table) : void 0;
|
|
103
|
+
}
|
|
104
|
+
function tableActions(input) {
|
|
105
|
+
const { table, host } = input;
|
|
106
|
+
const url = tableLink(table.id, host.origin);
|
|
107
|
+
const favorite = host.isFavorite === true;
|
|
108
|
+
const openInNewTab = host.openInNewTab;
|
|
109
|
+
const copyText = host.copyText;
|
|
110
|
+
const openBuiltOn = host.openBuiltOn;
|
|
111
|
+
const built = [
|
|
112
|
+
action({ id: "open", label: "Open", icon: "folder-open", group: "open" }, "read", input, bind(host.open, table)),
|
|
113
|
+
action(
|
|
114
|
+
{ id: "open-new-tab", label: "Open in new tab", icon: "external-link", group: "open" },
|
|
115
|
+
"read",
|
|
116
|
+
input,
|
|
117
|
+
openInNewTab ? () => openInNewTab(url, table) : void 0
|
|
118
|
+
),
|
|
119
|
+
action(
|
|
120
|
+
{ id: "copy-link", label: "Copy link", icon: "link", group: "open" },
|
|
121
|
+
"read",
|
|
122
|
+
input,
|
|
123
|
+
copyText ? () => copyText(url, table) : void 0
|
|
124
|
+
),
|
|
125
|
+
action({ id: "rename", label: "Rename\u2026", icon: "pencil", group: "organize" }, "structure", input, bind(host.rename, table)),
|
|
126
|
+
action({ id: "duplicate", label: "Duplicate", icon: "copy", group: "organize" }, "read", input, bind(host.duplicate, table)),
|
|
127
|
+
action({ id: "move", label: "Move to\u2026", icon: "folder-input", group: "organize" }, "structure", input, bind(host.move, table)),
|
|
128
|
+
action(
|
|
129
|
+
{
|
|
130
|
+
id: "favorite",
|
|
131
|
+
label: favorite ? "Remove from favorites" : "Add to favorites",
|
|
132
|
+
icon: favorite ? "star-off" : "star",
|
|
133
|
+
group: "organize"
|
|
134
|
+
},
|
|
135
|
+
"read",
|
|
136
|
+
input,
|
|
137
|
+
bind(host.toggleFavorite, table)
|
|
138
|
+
),
|
|
139
|
+
action({ id: "share", label: "Share\u2026", icon: "share-2", group: "share" }, "share", input, bind(host.share, table)),
|
|
140
|
+
action({ id: "export", label: "Export\u2026", icon: "download", group: "share" }, "read", input, bind(host.export, table)),
|
|
141
|
+
action({ id: "import", label: "Import\u2026", icon: "upload", group: "share" }, "structure", input, bind(host.import, table)),
|
|
142
|
+
action({ id: "add-column", label: "Add column", icon: "columns-3", group: "manage" }, "structure", input, bind(host.addColumn, table)),
|
|
143
|
+
action({ id: "settings", label: "Settings", icon: "settings", group: "manage" }, "structure", input, bind(host.settings, table)),
|
|
144
|
+
action({ id: "history", label: "History", icon: "history", group: "manage" }, "read", input, bind(host.history, table)),
|
|
145
|
+
...BUILT_ON.map(
|
|
146
|
+
({ destination, label, icon }) => action(
|
|
147
|
+
{ id: `built-on.${destination}`, label, icon, group: "built-on" },
|
|
148
|
+
"read",
|
|
149
|
+
input,
|
|
150
|
+
openBuiltOn ? () => openBuiltOn(destination, table) : void 0
|
|
151
|
+
)
|
|
152
|
+
),
|
|
153
|
+
action(
|
|
154
|
+
{ id: "archive", label: "Archive table", icon: "archive", group: "danger", destructive: true },
|
|
155
|
+
"structure",
|
|
156
|
+
input,
|
|
157
|
+
bind(host.archive, table)
|
|
158
|
+
)
|
|
159
|
+
];
|
|
160
|
+
return withExtensions(built, host.extend ? host.extend(table, input.rights) : []);
|
|
161
|
+
}
|
|
162
|
+
function withExtensions(base, extra) {
|
|
163
|
+
if (extra.length === 0) return [...base];
|
|
164
|
+
const seen = new Set(base.map((a) => a.id));
|
|
165
|
+
for (const entry of extra) {
|
|
166
|
+
if (entry.group === "danger") {
|
|
167
|
+
throw new Error(`Object action "${entry.id}": the danger group is closed to extensions.`);
|
|
168
|
+
}
|
|
169
|
+
if (seen.has(entry.id)) {
|
|
170
|
+
throw new Error(`Object action "${entry.id}" is already in the list.`);
|
|
171
|
+
}
|
|
172
|
+
seen.add(entry.id);
|
|
173
|
+
}
|
|
174
|
+
const out = [];
|
|
175
|
+
for (const group of OBJECT_ACTION_GROUPS) {
|
|
176
|
+
out.push(...base.filter((a) => a.group === group.id), ...extra.filter((a) => a.group === group.id));
|
|
177
|
+
}
|
|
178
|
+
return out;
|
|
179
|
+
}
|
|
180
|
+
function groupObjectActions(actions) {
|
|
181
|
+
const sections = [];
|
|
182
|
+
for (const group of OBJECT_ACTION_GROUPS) {
|
|
183
|
+
const inGroup = actions.filter((a) => a.group === group.id);
|
|
184
|
+
if (inGroup.length > 0) sections.push({ group, actions: inGroup });
|
|
185
|
+
}
|
|
186
|
+
return sections;
|
|
187
|
+
}
|
|
188
|
+
function toDropdownEntries(actions) {
|
|
189
|
+
const entries = [];
|
|
190
|
+
groupObjectActions(actions).forEach(({ group, actions: inGroup }, index) => {
|
|
191
|
+
if (index > 0) entries.push({ kind: "separator", id: `separator:${group.id}` });
|
|
192
|
+
if (group.submenu) {
|
|
193
|
+
entries.push({ kind: "submenu", id: group.id, label: group.label ?? group.id, icon: group.icon, actions: inGroup });
|
|
194
|
+
} else {
|
|
195
|
+
for (const a of inGroup) entries.push({ kind: "item", action: a });
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
return entries;
|
|
199
|
+
}
|
|
200
|
+
function dropdownActionIds(entries) {
|
|
201
|
+
return entries.flatMap((e) => e.kind === "item" ? [e.action.id] : e.kind === "submenu" ? e.actions.map((a) => a.id) : []);
|
|
202
|
+
}
|
|
203
|
+
export {
|
|
204
|
+
ACTION_CHECKING_ACCESS_REASON,
|
|
205
|
+
ACTION_NOT_HERE_REASON,
|
|
206
|
+
ACTION_NO_ACCESS_REASON,
|
|
207
|
+
ACTION_PLATFORM_TABLE_REASON,
|
|
208
|
+
DEFAULT_ACTION_ORIGIN,
|
|
209
|
+
OBJECT_ACTION_GROUPS,
|
|
210
|
+
dropdownActionIds,
|
|
211
|
+
groupObjectActions,
|
|
212
|
+
rightsReason,
|
|
213
|
+
tableActions,
|
|
214
|
+
tableLink,
|
|
215
|
+
toDropdownEntries,
|
|
216
|
+
withExtensions
|
|
217
|
+
};
|
|
218
|
+
//# sourceMappingURL=object-actions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/rights.ts","../src/objectActions.ts"],"sourcesContent":["// src/rights.ts — @ai-matrx/records-ui\n//\n// THE ONE PLACE A LEVEL BECOMES A SCREEN.\n//\n// WHAT A PERSON HIT (independent verdict, sixth pass, 19-20 September). A\n// colleague who had been shared a table at viewer opened it, double-clicked a\n// cell, got a live editor, typed a value and pressed Enter — and only THEN was\n// told she may not write. \"The permission is right; the screen told her she\n// could.\" Measured against Airtable, Notion and Linear the verdict called it\n// out by name: \"None of these three products lets you start an action it\n// already knows it will reject.\"\n//\n// WHY THE SCREEN COULD NOT KNOW. The rights question was a PORT with two honest\n// answers and no third: `NO_RIGHTS` (every administering control absent, which\n// is useless for a host whose authority IS the store's) and\n// `storeDecidesRights` — `{admin: true, comment: true, write: true}` for every\n// non-kernel table, for everybody. That second one is the defect, written down:\n// it is the screen ASSUMING the answer and letting the door say no afterwards.\n//\n// WHAT REPLACES IT. The store already answers this, and the client now carries\n// it: `custom.read_records` returns the reader's level on the TABLE on every\n// row (so `Table.your_level` costs nothing), and `custom.my_levels` — the door\n// this lane added — answers the caller's level on any subject they name, one\n// call for a whole page. This file is the only place in the package that turns\n// one of those four words into what a screen may DRAW.\n//\n// THE LADDER IS THE STORE'S, NOT OURS: viewer < commenter < editor < admin, and\n// which rung each door requires is read from the doors themselves —\n// `custom.record_update` asserts `editor` on the record, `custom.field_declare`\n// asserts `admin` on the TABLE (\"the SHAPE of this table, which is an admin's\n// right and not an editor's\"), `custom.io_comment_write` admits a commenter.\n// Nothing here invents a rule; it mirrors the four assertions those doors make\n// and is wrong the moment they change, which is what the suite is for.\n//\n// ABSENT OR HONEST, NEVER DEAD. Every `false` here carries the sentence that\n// says why and what to do about it, so a control can be left out AND explained.\n// A screen that hid a control with nothing to say is the silent half of the\n// same defect.\n\nimport { atLeast, type PermissionLevel, type Table } from \"@ai-matrx/records\";\n\n/** The six things a person does to a record or a table, as the screens offer them. */\nexport type Capability = \"read\" | \"comment\" | \"write\" | \"remove\" | \"share\" | \"structure\";\n\nexport interface WhatYouMayDo {\n /** The word the store answered, or null when it answered nothing. */\n level: PermissionLevel | null;\n /**\n * Whether the store has ANSWERED yet. `false` is a third state and not a\n * refusal: a screen that drew \"you may not\" while the answer was in flight\n * would blink every control off on every reload, which is its own lie.\n */\n known: boolean;\n read: boolean;\n comment: boolean;\n write: boolean;\n remove: boolean;\n share: boolean;\n structure: boolean;\n /** Why this person may not do that, in one sentence naming the way in. */\n why: (what: Capability) => string;\n}\n\n/** What each rung of the ladder is called where a person reads it. */\nexport const LEVEL_WORD: Record<PermissionLevel, string> = {\n viewer: \"Viewer\",\n commenter: \"Commenter\",\n editor: \"Editor\",\n admin: \"Admin\",\n};\n\n/**\n * WHICH RUNG EACH DOOR ASKS FOR. Read off the doors themselves, in one place,\n * so a screen never re-decides it and the suite has one thing to check.\n */\nconst NEEDS: Record<Capability, PermissionLevel> = {\n read: \"viewer\",\n // custom.io_comment_write\n comment: \"commenter\",\n // custom.record_update / custom.record_write\n write: \"editor\",\n // custom.record_delete\n remove: \"editor\",\n // custom.share_grant — passing your access on is an administrator's right\n share: \"admin\",\n // custom.field_declare / field_update / field_retire / table settings\n structure: \"admin\",\n};\n\n/** The rung a capability asks for — so a short \"Needs Admin access\" never re-decides the ladder. */\nexport function levelNeededFor(what: Capability): PermissionLevel {\n return NEEDS[what];\n}\n\nconst NOT_YET =\n \"Still asking the store what you may do here. Nothing is offered until it has answered.\";\n\nconst NOTHING_AT_ALL =\n \"You have not been given this. Ask somebody who administers it to share it with you.\";\n\n/** The sentence for one refusal: what the level is, what it would have to be. */\nfunction sentenceFor(what: Capability, level: PermissionLevel | null): string {\n if (level === null) return NOTHING_AT_ALL;\n const need = LEVEL_WORD[NEEDS[what]];\n const have = LEVEL_WORD[level];\n const doing: Record<Capability, string> = {\n read: \"open this\",\n comment: \"leave a comment\",\n write: \"change anything here\",\n remove: \"delete this\",\n share: \"pass this on to somebody else\",\n structure: \"change the columns of this table\",\n };\n return `You are ${have} here, and it takes ${need} to ${doing[what]}. Ask somebody who is Admin on it to move you up.`;\n}\n\n/**\n * WHAT ONE LEVEL LETS A PERSON DO. `level` null and `known` true means the\n * store said \"nothing\" — a real answer. `known` false means it has not said.\n */\nexport function whatYouMayDo(level: PermissionLevel | null, known = true): WhatYouMayDo {\n const may = (what: Capability) => known && atLeast(level, NEEDS[what]);\n return {\n level: known ? level : null,\n known,\n read: may(\"read\"),\n comment: may(\"comment\"),\n write: may(\"write\"),\n remove: may(\"remove\"),\n share: may(\"share\"),\n structure: may(\"structure\"),\n why: (what) => (known ? sentenceFor(what, level) : NOT_YET),\n };\n}\n\n/** Nothing offered, because nothing is known yet. */\nexport const NOT_ANSWERED_YET: WhatYouMayDo = whatYouMayDo(null, false);\n\n/**\n * REC-27. The nine Tables the platform ships are not administered from a\n * person's screen whatever level they hold on them — so the structure controls\n * are absent for a kernel Table and say why, and everything else follows the\n * level as usual.\n */\nexport const KERNEL_REASON =\n \"This is one of the tables the platform ships, so its columns are not changed from a screen. \" +\n \"Make your own table to change columns.\";\n\nexport function whatYouMayDoWithTable(\n table: Table | null | undefined,\n level: PermissionLevel | null,\n known: boolean,\n): WhatYouMayDo {\n const base = whatYouMayDo(level, known);\n if (!table?.is_kernel) return base;\n return {\n ...base,\n structure: false,\n why: (what) => (what === \"structure\" ? KERNEL_REASON : base.why(what)),\n };\n}\n","// src/objectActions.ts — @ai-matrx/records-ui\n//\n// THE ONE ACTION LIST FOR A RECORD-STORE TABLE (lane TABLE-ACTIONS, v6 lane 1).\n//\n// Every menu a person meets on a table — the header ⋯, a row ⋯ on the Data\n// home, right-click, a card, the phone sheet, later ⌘K — is drawn from the\n// array `tableActions()` returns. Before this there were three hand-made lists\n// (`TABLE_MENU` in TablePage, `dataHomeRowActions` in matrx-frontend, the\n// Sheet's own v3 sections) and they drifted: no Archive, no Copy link, no\n// Duplicate, Rename and Share only as hand JSX beside the list.\n//\n// THE SHAPE IS COPIED, NOT INVENTED. It is the in-house registry pattern of\n// `features/agents/browse/agentActionRegistry.tsx` and `conversationActionRegistry.tsx`\n// in matrx-frontend: a context of HOST HANDLERS in, one ordered list out, every\n// renderer reading the same list. The one difference is deliberate: those two\n// build matrx-frontend's `ItemMenuConfig` directly, which this package cannot\n// import, so the list here is renderer-neutral (`ObjectAction[]`) and each\n// renderer has a pure adapter (`toDropdownEntries` here; `toItemMenuConfig` and\n// `toExtraSections` in matrx-frontend `features/unified-data/actions/`).\n//\n// TWO INVARIANTS, each with a guard (`one-table-action-list-*.test.tsx`):\n// G1 — every adapter renders the same ids in the same order.\n// G2 — rights change only `disabledReason`, never which ids appear or their order\n// (Airtable hides items from members and confuses them; we beat it).\n// A missing host handler is the same: the action stays, disabled, with its reason.\n//\n// THE ORDER (handoff §4, the same everywhere):\n// Open · Open in new tab · Copy link | Rename · Duplicate · Move to… · Favorite |\n// Share… · Export… · Import… | Add column · Settings · History |\n// Built on it ▸ (Forms, Bookings, Checklists, Notifications, Portals, Inbox,\n// Dashboards, Archived records) | Archive table (last, destructive).\n// \"Add column\" is TABLE_MENU's first entry; it sits with Settings because both\n// change the table's shape.\n//\n// No JSX here: this file is the `./object-actions` subpath, light enough for a\n// list row's menu to import without pulling every screen of the package.\n\nimport type { Table } from \"@ai-matrx/records\";\nimport { LEVEL_WORD, levelNeededFor, type Capability, type WhatYouMayDo } from \"./rights\";\n\n/**\n * A Lucide icon NAME, kebab-case — the same currency `Table.icon` already uses.\n * Closed on purpose: each renderer maps every member to its own component, and a\n * new member fails that renderer's typecheck until it is mapped.\n */\nexport type ObjectActionIcon =\n | \"folder-open\"\n | \"external-link\"\n | \"link\"\n | \"pencil\"\n | \"copy\"\n | \"folder-input\"\n | \"star\"\n | \"star-off\"\n | \"share-2\"\n | \"download\"\n | \"upload\"\n | \"columns-3\"\n | \"settings\"\n | \"history\"\n | \"blocks\"\n | \"file-text\"\n | \"calendar-clock\"\n | \"list-checks\"\n | \"bell\"\n | \"door-open\"\n | \"inbox\"\n | \"layout-dashboard\"\n | \"archive-restore\"\n | \"archive\"\n | \"link-2\"\n | \"layout-grid\";\n\n/** The groups, in their one order. Separators fall out of group boundaries. */\nexport type ObjectActionGroup = \"open\" | \"organize\" | \"share\" | \"manage\" | \"built-on\" | \"danger\";\n\nexport interface ObjectActionGroupDef {\n id: ObjectActionGroup;\n /** Present only on a group that renders as a submenu. */\n label?: string;\n icon?: ObjectActionIcon;\n /** Rendered as ONE submenu entry holding the group's actions. */\n submenu: boolean;\n}\n\nexport const OBJECT_ACTION_GROUPS: readonly ObjectActionGroupDef[] = [\n { id: \"open\", submenu: false },\n { id: \"organize\", submenu: false },\n { id: \"share\", submenu: false },\n { id: \"manage\", submenu: false },\n { id: \"built-on\", label: \"Built on it\", icon: \"blocks\", submenu: true },\n { id: \"danger\", submenu: false },\n];\n\n/** One thing a person can do to an object, for any renderer. */\nexport interface ObjectAction {\n /** Stable within the object's list. React key, test handle, future ⌘K id. */\n id: string;\n label: string;\n icon: ObjectActionIcon;\n group: ObjectActionGroup;\n /** Display-only hint (\"⌘D\"). */\n shortcut?: string;\n destructive?: boolean;\n /** Present = shown disabled with this line (≤ 60 chars). Absent = enabled. */\n disabledReason?: string;\n /** Called only when enabled. The host owns the side effect and its feedback. */\n run: () => void | Promise<void>;\n}\n\n/** What the list needs to know about the table. A full `Table` satisfies it. */\nexport type TableActionSubject = Pick<Table, \"id\" | \"name\"> & Partial<Pick<Table, \"is_kernel\">>;\n\n/** The places built on a table, in their one order. */\nexport type BuiltOnDestination =\n | \"forms\"\n | \"bookings\"\n | \"checklists\"\n | \"notifications\"\n | \"portals\"\n | \"inbox\"\n | \"dashboards\"\n | \"archived\";\n\ntype Handler = (table: TableActionSubject) => void | Promise<void>;\n\n/**\n * THE SIDE EFFECTS THIS PACKAGE CANNOT OWN. Every handler is optional: one the\n * host leaves out keeps its action in the list, disabled, saying so.\n */\nexport interface TableActionHost {\n /** Origin for Copy link and Open in new tab. Default `https://www.aimatrx.com`. */\n origin?: string;\n open?: Handler;\n /** Receives the table's link (`<origin>/data-v2/<id>`). */\n openInNewTab?: (url: string, table: TableActionSubject) => void;\n /** Receives the table's link. Runs inside the click (clipboard needs the gesture). */\n copyText?: (text: string, table: TableActionSubject) => void | Promise<void>;\n rename?: Handler;\n duplicate?: Handler;\n move?: Handler;\n /** Current favorite state; flips the Favorite label. Unknown = \"Add to favorites\". */\n isFavorite?: boolean;\n toggleFavorite?: Handler;\n share?: Handler;\n export?: Handler;\n import?: Handler;\n addColumn?: Handler;\n settings?: Handler;\n history?: Handler;\n /** The \"Built on it\" destinations, one handler for all eight. */\n openBuiltOn?: (destination: BuiltOnDestination, table: TableActionSubject) => void;\n archive?: Handler;\n /**\n * ANOTHER LANE'S ENTRIES (\"Link a record…\", a hub tile's verbs). Return the\n * same ids whatever `rights` say — express rights as `disabledReason`. Each\n * entry lands at the END of its named group, in the order returned; `danger`\n * is refused (Archive is always last) and so is an id already in the list.\n */\n extend?: (table: TableActionSubject, rights: WhatYouMayDo) => readonly ObjectAction[];\n}\n\nexport interface TableActionsInput {\n table: TableActionSubject;\n rights: WhatYouMayDo;\n host: TableActionHost;\n}\n\nexport const DEFAULT_ACTION_ORIGIN = \"https://www.aimatrx.com\";\n\n/** The one link to a table: `<origin>/data-v2/<tableId>`. */\nexport function tableLink(tableId: string, origin: string = DEFAULT_ACTION_ORIGIN): string {\n return `${origin.replace(/\\/+$/, \"\")}/data-v2/${encodeURIComponent(tableId)}`;\n}\n\n// Reasons — one line, ≤ 60 characters, said on the action itself.\nexport const ACTION_NOT_HERE_REASON = \"Not available here\";\nexport const ACTION_CHECKING_ACCESS_REASON = \"Checking your access…\";\nexport const ACTION_NO_ACCESS_REASON = \"You don't have access\";\nexport const ACTION_PLATFORM_TABLE_REASON = \"Platform tables can't be changed here\";\n\n/** Why `rights` refuse `what`, in one short line — or null when they allow it. */\nexport function rightsReason(\n rights: WhatYouMayDo,\n what: Capability,\n table?: TableActionSubject,\n): string | null {\n if (!rights.known) return ACTION_CHECKING_ACCESS_REASON;\n if (what === \"structure\" && table?.is_kernel) return ACTION_PLATFORM_TABLE_REASON;\n if (rights[what]) return null;\n if (rights.level === null) return ACTION_NO_ACCESS_REASON;\n return `Needs ${LEVEL_WORD[levelNeededFor(what)]} access`;\n}\n\nconst BUILT_ON: ReadonlyArray<{ destination: BuiltOnDestination; label: string; icon: ObjectActionIcon }> = [\n { destination: \"forms\", label: \"Forms\", icon: \"file-text\" },\n { destination: \"bookings\", label: \"Bookings\", icon: \"calendar-clock\" },\n { destination: \"checklists\", label: \"Checklists\", icon: \"list-checks\" },\n { destination: \"notifications\", label: \"Notifications\", icon: \"bell\" },\n { destination: \"portals\", label: \"Portals\", icon: \"door-open\" },\n { destination: \"inbox\", label: \"Inbox\", icon: \"inbox\" },\n { destination: \"dashboards\", label: \"Dashboards\", icon: \"layout-dashboard\" },\n { destination: \"archived\", label: \"Archived records\", icon: \"archive-restore\" },\n];\n\nconst NOOP = () => {};\n\n/**\n * One action, from its needs: the capability it asks for and the host handler\n * that performs it. Rights speak first; a missing handler second.\n */\nfunction action(\n base: Omit<ObjectAction, \"run\" | \"disabledReason\">,\n needs: Capability,\n input: TableActionsInput,\n perform: (() => void | Promise<void>) | undefined,\n): ObjectAction {\n const refused = rightsReason(input.rights, needs, input.table);\n if (refused !== null) return { ...base, disabledReason: refused, run: NOOP };\n if (perform === undefined) return { ...base, disabledReason: ACTION_NOT_HERE_REASON, run: NOOP };\n return { ...base, run: perform };\n}\n\nfunction bind(handler: Handler | undefined, table: TableActionSubject): (() => void | Promise<void>) | undefined {\n return handler ? () => handler(table) : undefined;\n}\n\n/**\n * THE TABLE'S ACTIONS, in the one order. Same ids, same order, for every seat\n * and every host — only `disabledReason` varies.\n */\nexport function tableActions(input: TableActionsInput): ObjectAction[] {\n const { table, host } = input;\n const url = tableLink(table.id, host.origin);\n const favorite = host.isFavorite === true;\n const openInNewTab = host.openInNewTab;\n const copyText = host.copyText;\n const openBuiltOn = host.openBuiltOn;\n\n const built: ObjectAction[] = [\n action({ id: \"open\", label: \"Open\", icon: \"folder-open\", group: \"open\" }, \"read\", input, bind(host.open, table)),\n action(\n { id: \"open-new-tab\", label: \"Open in new tab\", icon: \"external-link\", group: \"open\" },\n \"read\",\n input,\n openInNewTab ? () => openInNewTab(url, table) : undefined,\n ),\n action(\n { id: \"copy-link\", label: \"Copy link\", icon: \"link\", group: \"open\" },\n \"read\",\n input,\n copyText ? () => copyText(url, table) : undefined,\n ),\n action({ id: \"rename\", label: \"Rename…\", icon: \"pencil\", group: \"organize\" }, \"structure\", input, bind(host.rename, table)),\n action({ id: \"duplicate\", label: \"Duplicate\", icon: \"copy\", group: \"organize\" }, \"read\", input, bind(host.duplicate, table)),\n action({ id: \"move\", label: \"Move to…\", icon: \"folder-input\", group: \"organize\" }, \"structure\", input, bind(host.move, table)),\n action(\n {\n id: \"favorite\",\n label: favorite ? \"Remove from favorites\" : \"Add to favorites\",\n icon: favorite ? \"star-off\" : \"star\",\n group: \"organize\",\n },\n \"read\",\n input,\n bind(host.toggleFavorite, table),\n ),\n action({ id: \"share\", label: \"Share…\", icon: \"share-2\", group: \"share\" }, \"share\", input, bind(host.share, table)),\n action({ id: \"export\", label: \"Export…\", icon: \"download\", group: \"share\" }, \"read\", input, bind(host.export, table)),\n action({ id: \"import\", label: \"Import…\", icon: \"upload\", group: \"share\" }, \"structure\", input, bind(host.import, table)),\n action({ id: \"add-column\", label: \"Add column\", icon: \"columns-3\", group: \"manage\" }, \"structure\", input, bind(host.addColumn, table)),\n action({ id: \"settings\", label: \"Settings\", icon: \"settings\", group: \"manage\" }, \"structure\", input, bind(host.settings, table)),\n action({ id: \"history\", label: \"History\", icon: \"history\", group: \"manage\" }, \"read\", input, bind(host.history, table)),\n ...BUILT_ON.map(({ destination, label, icon }) =>\n action(\n { id: `built-on.${destination}`, label, icon, group: \"built-on\" },\n \"read\",\n input,\n openBuiltOn ? () => openBuiltOn(destination, table) : undefined,\n ),\n ),\n action(\n { id: \"archive\", label: \"Archive table\", icon: \"archive\", group: \"danger\", destructive: true },\n \"structure\",\n input,\n bind(host.archive, table),\n ),\n ];\n\n return withExtensions(built, host.extend ? host.extend(table, input.rights) : []);\n}\n\n/**\n * Another lane's entries, placed deterministically: each at the end of its\n * group, in the order given. Loud on the two mistakes that would break the\n * invariants — a `danger` entry (Archive must stay last) and a duplicate id.\n */\nexport function withExtensions(base: readonly ObjectAction[], extra: readonly ObjectAction[]): ObjectAction[] {\n if (extra.length === 0) return [...base];\n const seen = new Set(base.map((a) => a.id));\n for (const entry of extra) {\n if (entry.group === \"danger\") {\n throw new Error(`Object action \"${entry.id}\": the danger group is closed to extensions.`);\n }\n if (seen.has(entry.id)) {\n throw new Error(`Object action \"${entry.id}\" is already in the list.`);\n }\n seen.add(entry.id);\n }\n const out: ObjectAction[] = [];\n for (const group of OBJECT_ACTION_GROUPS) {\n out.push(...base.filter((a) => a.group === group.id), ...extra.filter((a) => a.group === group.id));\n }\n return out;\n}\n\n/** A run of actions that renders together: a plain section, or one submenu. */\nexport interface ObjectActionSection {\n group: ObjectActionGroupDef;\n actions: ObjectAction[];\n}\n\n/**\n * THE ONE GROUPING every adapter uses: the list cut at group boundaries, in\n * group order, empty groups dropped, the order inside a group kept.\n */\nexport function groupObjectActions(actions: readonly ObjectAction[]): ObjectActionSection[] {\n const sections: ObjectActionSection[] = [];\n for (const group of OBJECT_ACTION_GROUPS) {\n const inGroup = actions.filter((a) => a.group === group.id);\n if (inGroup.length > 0) sections.push({ group, actions: inGroup });\n }\n return sections;\n}\n\n/** The header ⋯ dropdown's entries — records-ui's own renderer reads these. */\nexport type ObjectActionDropdownEntry =\n | { kind: \"separator\"; id: string }\n | { kind: \"item\"; action: ObjectAction }\n | { kind: \"submenu\"; id: string; label: string; icon: ObjectActionIcon | undefined; actions: ObjectAction[] };\n\nexport function toDropdownEntries(actions: readonly ObjectAction[]): ObjectActionDropdownEntry[] {\n const entries: ObjectActionDropdownEntry[] = [];\n groupObjectActions(actions).forEach(({ group, actions: inGroup }, index) => {\n if (index > 0) entries.push({ kind: \"separator\", id: `separator:${group.id}` });\n if (group.submenu) {\n entries.push({ kind: \"submenu\", id: group.id, label: group.label ?? group.id, icon: group.icon, actions: inGroup });\n } else {\n for (const a of inGroup) entries.push({ kind: \"item\", action: a });\n }\n });\n return entries;\n}\n\n/** The action ids a dropdown draws, in order — what G1 compares. */\nexport function dropdownActionIds(entries: readonly ObjectActionDropdownEntry[]): string[] {\n return entries.flatMap((e) => (e.kind === \"item\" ? [e.action.id] : e.kind === \"submenu\" ? e.actions.map((a) => a.id) : []));\n}\n"],"mappings":"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","names":[]}
|