@ai-matrx/sharing 0.0.0 → 0.1.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.
@@ -0,0 +1,2816 @@
1
+ "use client";
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // src/access/react/index.ts
32
+ var react_exports = {};
33
+ __export(react_exports, {
34
+ ACCESS_REQUESTS_HREF: () => ACCESS_REQUESTS_HREF,
35
+ AccessDenied: () => AccessDenied,
36
+ AccessDeniedView: () => AccessDeniedView,
37
+ AccessGate: () => AccessGate,
38
+ AccessLink: () => AccessLink,
39
+ AccessRequestsSurface: () => AccessRequestsSurface,
40
+ AccessUiProvider: () => AccessUiProvider,
41
+ BlindAccessAsk: () => BlindAccessAsk,
42
+ CANT_REMOVE_DIALOG: () => CANT_REMOVE_DIALOG,
43
+ ForbiddenSurfaceView: () => ForbiddenSurfaceView,
44
+ GovernedActionDialog: () => GovernedActionDialog,
45
+ HiddenByMeButton: () => HiddenByMeButton,
46
+ HiddenFilter: () => HiddenFilter,
47
+ REQUEST_ACCESS_MANUAL_ACTION: () => REQUEST_ACCESS_MANUAL_ACTION,
48
+ RemovalNotYoursError: () => RemovalNotYoursError,
49
+ RequestAccess: () => RequestAccess,
50
+ RequestAccessPanel: () => RequestAccessPanel,
51
+ ResourceActionRequestButtons: () => ResourceActionRequestButtons,
52
+ ServerReadRecheck: () => ServerReadRecheck,
53
+ SettingAccessGate: () => SettingAccessGate,
54
+ SettingRequestActionButtons: () => SettingRequestActionButtons,
55
+ SlugAccessGate: () => SlugAccessGate,
56
+ UnresolvedEntityRef: () => UnresolvedEntityRef,
57
+ configureAccessUi: () => configureAccessUi,
58
+ ensureRemovable: () => ensureRemovable,
59
+ getAccessUi: () => getAccessUi,
60
+ isAccessUiConfigured: () => isAccessUiConfigured,
61
+ isRemovalNotYours: () => isRemovalNotYours,
62
+ offerOwnerChoices: () => offerOwnerChoices,
63
+ refreshHiddenByMe: () => refreshHiddenByMe,
64
+ requestCantRemove: () => requestCantRemove,
65
+ useAccessGate: () => useAccessGate,
66
+ useAccessStates: () => useAccessStates,
67
+ useAccessUi: () => useAccessUi,
68
+ useHiddenByMe: () => useHiddenByMe,
69
+ useHiddenItems: () => useHiddenItems,
70
+ useHideForMe: () => useHideForMe,
71
+ useRemovalRights: () => useRemovalRights,
72
+ useRequestAccess: () => useRequestAccess
73
+ });
74
+ module.exports = __toCommonJS(react_exports);
75
+
76
+ // src/access/react/env.tsx
77
+ var import_react = require("react");
78
+ var import_design_system = require("@ai-matrx/design-system");
79
+ var import_format = require("@ai-matrx/kit/format");
80
+ var import_confirm_opener = require("@ai-matrx/kit/confirm-opener");
81
+ var import_jsx_runtime = require("react/jsx-runtime");
82
+ var REQUEST_ACCESS_MANUAL_ACTION = "request_access.manual";
83
+ var DEFAULT_NAV_ITEM_CLASSES = {
84
+ selected: "bg-[var(--shell-nav-selected-bg)] text-[var(--shell-nav-selected-text)] shadow-[var(--shell-nav-selected-shadow)] font-semibold",
85
+ unselected: "text-[var(--shell-nav-unselected-text)] hover:text-[var(--shell-nav-text-hover)] hover:bg-[var(--matrx-glass-bg-hover)]"
86
+ };
87
+ var DefaultLink = (0, import_react.forwardRef)(
88
+ function DefaultLink2(props, ref) {
89
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { ref, ...props });
90
+ }
91
+ );
92
+ var AccessLink = (0, import_react.forwardRef)(
93
+ function AccessLink2(props, ref) {
94
+ const { Link } = useAccessUi();
95
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Link, { ref, ...props });
96
+ }
97
+ );
98
+ var DEFAULT_ROUTER = {
99
+ push: (href) => {
100
+ if (typeof window !== "undefined") window.location.assign(href);
101
+ },
102
+ back: () => {
103
+ if (typeof window !== "undefined") window.history.back();
104
+ },
105
+ refresh: () => {
106
+ if (typeof window !== "undefined") window.location.reload();
107
+ }
108
+ };
109
+ function defaultSearchParam(name) {
110
+ if (typeof window === "undefined") return null;
111
+ return new URLSearchParams(window.location.search).get(name);
112
+ }
113
+ function DefaultTextarea(props) {
114
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_design_system.Textarea, { ...props });
115
+ }
116
+ function DefaultUserIdentity({ user, size = "md", avatarOnly }) {
117
+ const name = user.displayName ?? "Someone";
118
+ const box = size === "sm" ? "h-7 w-7" : "h-9 w-9";
119
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "inline-flex min-w-0 items-center gap-2", children: [
120
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_design_system.Avatar, { className: box, children: [
121
+ user.avatarUrl ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_design_system.AvatarImage, { src: user.avatarUrl, alt: name }) : null,
122
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_design_system.AvatarFallback, { className: "text-[11px]", children: (0, import_format.getInitials)(user.displayName) })
123
+ ] }),
124
+ avatarOnly ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "min-w-0 truncate text-sm", children: name })
125
+ ] });
126
+ }
127
+ function DefaultEntityRef({ name, className }) {
128
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className, children: name });
129
+ }
130
+ var moduleEnv = {};
131
+ var moduleVersion = 0;
132
+ function configureAccessUi(env) {
133
+ moduleEnv = { ...moduleEnv, ...env };
134
+ moduleVersion += 1;
135
+ }
136
+ function isAccessUiConfigured() {
137
+ return Boolean(moduleEnv.useLoginHref && moduleEnv.useCurrentUser && moduleEnv.toast);
138
+ }
139
+ function notConfigured(field) {
140
+ return new Error(
141
+ `[@ai-matrx/sharing/access/react] \`${field}\` was not supplied: add it to configureAccessUi({ ${field} }) once at app boot.`
142
+ );
143
+ }
144
+ function resolveAccessUi(base, override) {
145
+ const env = { ...base, ...override ?? {} };
146
+ const required = (key) => {
147
+ const value = env[key];
148
+ if (value === void 0 || value === null) throw notConfigured(key);
149
+ return value;
150
+ };
151
+ const ui = {
152
+ // Required fields are getters so a screen that never reads one still renders.
153
+ get useLoginHref() {
154
+ return required("useLoginHref");
155
+ },
156
+ get useCurrentUser() {
157
+ return required("useCurrentUser");
158
+ },
159
+ get toast() {
160
+ return required("toast");
161
+ },
162
+ Link: env.Link ?? DefaultLink,
163
+ router: env.router ?? DEFAULT_ROUTER,
164
+ searchParam: env.searchParam ?? defaultSearchParam,
165
+ confirm: env.confirm ?? import_confirm_opener.confirm,
166
+ Textarea: env.Textarea ?? DefaultTextarea,
167
+ renderUserIdentity: env.renderUserIdentity ?? ((p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DefaultUserIdentity, { ...p })),
168
+ renderEntityRef: env.renderEntityRef ?? ((p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DefaultEntityRef, { ...p })),
169
+ renderErrorMenu: env.renderErrorMenu ?? (() => null),
170
+ navItemClasses: env.navItemClasses ?? DEFAULT_NAV_ITEM_CLASSES,
171
+ manualRequestActionKey: env.manualRequestActionKey ?? REQUEST_ACCESS_MANUAL_ACTION,
172
+ tryGetEntityInfo: (token) => env.entityRegistry?.tryGetEntityInfo(token) ?? null,
173
+ restoreFromTrash: async (token, id) => {
174
+ if (!env.trash) throw notConfigured("trash");
175
+ await env.trash.restore(token, id);
176
+ },
177
+ resolveRecordUnavailableCapture: (error, resolution) => env.recordUnavailableCapture?.resolve(error, resolution),
178
+ getSettingRequestAction: (key) => env.settingRequestActions?.get(key),
179
+ getResourceActionRequestAction: (key) => env.resourceActionRequests?.get(key),
180
+ createCmsAccessRequest: async (args) => {
181
+ if (!env.cms) throw notConfigured("cms");
182
+ return env.cms.createAccessRequest(args);
183
+ },
184
+ settingDoorHref: (target) => {
185
+ if (!env.settingDoorHref) throw notConfigured("settingDoorHref");
186
+ return env.settingDoorHref(target);
187
+ },
188
+ reportFeedback: async (input) => {
189
+ if (!env.reportFeedback) throw notConfigured("reportFeedback");
190
+ return env.reportFeedback(input);
191
+ },
192
+ presentOrganizationRefusal: (error, options) => env.presentOrganizationRefusal?.(error, options) ?? false,
193
+ openOverlay: (overlayId, instanceId, data) => {
194
+ if (!env.overlays) throw notConfigured("overlays");
195
+ env.overlays.open(overlayId, instanceId, data);
196
+ },
197
+ closeOverlay: (overlayId, instanceId) => env.overlays?.close(overlayId, instanceId)
198
+ };
199
+ return ui;
200
+ }
201
+ function getAccessUi() {
202
+ return resolveAccessUi(moduleEnv, null);
203
+ }
204
+ var AccessUiContext = (0, import_react.createContext)(null);
205
+ function AccessUiProvider({
206
+ env,
207
+ children
208
+ }) {
209
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AccessUiContext.Provider, { value: env, children });
210
+ }
211
+ function useAccessUi() {
212
+ const override = (0, import_react.useContext)(AccessUiContext);
213
+ return (0, import_react.useMemo)(
214
+ () => resolveAccessUi(moduleEnv, override),
215
+ // `moduleVersion` is read on purpose: configuring after first render is picked up by the next mount.
216
+ // eslint-disable-next-line react-hooks/exhaustive-deps
217
+ [override, moduleVersion]
218
+ );
219
+ }
220
+
221
+ // src/access/react/AccessDenied.tsx
222
+ var import_react5 = require("react");
223
+ var import_lucide_react3 = require("lucide-react");
224
+ var import_design_system4 = require("@ai-matrx/design-system");
225
+
226
+ // src/access/react/RequestAccessPanel.tsx
227
+ var import_react2 = require("react");
228
+ var import_lucide_react = require("lucide-react");
229
+ var import_design_system2 = require("@ai-matrx/design-system");
230
+ var import_access = require("@ai-matrx/data/access");
231
+ var import_access2 = require("@ai-matrx/data/access");
232
+ var import_jsx_runtime2 = require("react/jsx-runtime");
233
+ function ownerName(context) {
234
+ return context.owner?.displayName ?? "the owner";
235
+ }
236
+ function RequestAccessPanel({
237
+ context,
238
+ resourceId,
239
+ href,
240
+ onChanged
241
+ }) {
242
+ const ui = useAccessUi();
243
+ const { toast } = ui;
244
+ const ProTextarea = ui.Textarea;
245
+ const currentUserId = ui.useCurrentUser().id;
246
+ const [level, setLevel] = (0, import_react2.useState)("viewer");
247
+ const [note, setNote] = (0, import_react2.useState)("");
248
+ const [busy, setBusy] = (0, import_react2.useState)(false);
249
+ const [delivered, setDelivered] = (0, import_react2.useState)(null);
250
+ const [reasking, setReasking] = (0, import_react2.useState)(false);
251
+ const request = context.request;
252
+ const kind = context.entity.label.toLowerCase();
253
+ const cmsToken = (0, import_access.getAccessEnv)().entityTokens?.isCmsAccessGateToken(context.entity.token) ? context.entity.token : null;
254
+ const hrToken = context.entity.token === "hr_review_cycle";
255
+ const requestRecipientName = cmsToken ? context.organization?.name ?? "the organization\u2019s admins" : hrToken ? "HR" : ownerName(context);
256
+ async function send() {
257
+ setBusy(true);
258
+ try {
259
+ const result = cmsToken ? await ui.createCmsAccessRequest({
260
+ token: cmsToken,
261
+ resourceId,
262
+ message: note,
263
+ currentUserId
264
+ }) : await (0, import_access2.createAccessRequest)({
265
+ resourceType: context.entity.token,
266
+ resourceId,
267
+ level,
268
+ message: note,
269
+ currentUserId,
270
+ href: href ?? null
271
+ });
272
+ if (result.blindAnswer) {
273
+ toast.success(result.blindAnswer);
274
+ } else if (result.already) {
275
+ toast.success("You've already asked \u2014 they haven't answered yet.");
276
+ } else if (result.delivered === 0) {
277
+ toast.warning(
278
+ "Your request is saved, but we couldn't message them just now."
279
+ );
280
+ } else {
281
+ toast.success(
282
+ `Request sent to ${(hrToken ? null : result.recipients[0]?.displayName) ?? requestRecipientName}.`
283
+ );
284
+ }
285
+ setDelivered(result.already ? null : result.delivered ?? null);
286
+ onChanged();
287
+ } catch (error) {
288
+ toast.error(
289
+ error instanceof Error ? error.message : "We couldn't send that."
290
+ );
291
+ } finally {
292
+ setBusy(false);
293
+ }
294
+ }
295
+ async function withdraw() {
296
+ if (!request) return;
297
+ setBusy(true);
298
+ try {
299
+ await (0, import_access2.withdrawAccessRequest)(request.id);
300
+ toast.success("Request withdrawn.");
301
+ onChanged();
302
+ } catch (error) {
303
+ toast.error(
304
+ error instanceof Error ? error.message : "We couldn't withdraw that."
305
+ );
306
+ } finally {
307
+ setBusy(false);
308
+ }
309
+ }
310
+ if (request?.status === "pending") {
311
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "rounded-lg border border-border bg-muted/40 p-4", children: [
312
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "text-sm text-foreground", children: [
313
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.Check, { className: "mr-1.5 inline h-4 w-4 text-primary", "aria-hidden": true }),
314
+ "Waiting on ",
315
+ requestRecipientName,
316
+ "."
317
+ ] }),
318
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "mt-1 text-xs text-muted-foreground", children: [
319
+ cmsToken ? "Your request is saved. Once an owner adds you to the organization, this site will open." : delivered === 0 ? "We couldn't reach them with a message, but your request is saved and they'll see it." : "They\u2019ve been messaged. You\u2019ll get a message back the moment they answer, and this page will open.",
320
+ ui.renderErrorMenu({})
321
+ ] }),
322
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
323
+ import_design_system2.Button,
324
+ {
325
+ className: "mt-3 h-8",
326
+ size: "sm",
327
+ variant: "outline",
328
+ disabled: busy,
329
+ onClick: () => void withdraw(),
330
+ children: [
331
+ busy ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.Loader2, { className: "mr-1.5 h-3.5 w-3.5 animate-spin", "aria-hidden": true }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.Undo2, { className: "mr-1.5 h-3.5 w-3.5", "aria-hidden": true }),
332
+ "Withdraw request"
333
+ ]
334
+ }
335
+ )
336
+ ] });
337
+ }
338
+ if (request?.status === "declined" && !reasking) {
339
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "rounded-lg border border-border bg-muted/40 p-4", children: [
340
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "text-sm text-foreground", children: [
341
+ requestRecipientName,
342
+ " declined this request."
343
+ ] }),
344
+ request.decisionNote ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "mt-1 text-xs text-muted-foreground", children: [
345
+ "\u201C",
346
+ request.decisionNote,
347
+ "\u201D"
348
+ ] }) : null,
349
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
350
+ import_design_system2.Button,
351
+ {
352
+ className: "mt-3 h-8",
353
+ size: "sm",
354
+ variant: "outline",
355
+ onClick: () => setReasking(true),
356
+ children: [
357
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.Send, { className: "mr-1.5 h-3.5 w-3.5", "aria-hidden": true }),
358
+ "Ask again"
359
+ ]
360
+ }
361
+ )
362
+ ] });
363
+ }
364
+ if (request?.status === "reported" || !context.canRequest) {
365
+ return null;
366
+ }
367
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "rounded-lg border border-border bg-card p-4", children: [
368
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "text-sm font-medium text-foreground", children: [
369
+ "Ask ",
370
+ requestRecipientName,
371
+ " for access"
372
+ ] }),
373
+ !cmsToken ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-3 flex flex-wrap gap-2", children: [
374
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
375
+ LevelChoice,
376
+ {
377
+ active: level === "viewer",
378
+ label: "Just to view",
379
+ onClick: () => setLevel("viewer")
380
+ }
381
+ ),
382
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
383
+ LevelChoice,
384
+ {
385
+ active: level === "editor",
386
+ label: "To view and make changes",
387
+ onClick: () => setLevel("editor")
388
+ }
389
+ )
390
+ ] }) : null,
391
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
392
+ ProTextarea,
393
+ {
394
+ className: "mt-3 min-h-16 text-base md:text-sm",
395
+ placeholder: `Why you need this ${kind} (optional)`,
396
+ value: note,
397
+ maxLength: 500,
398
+ onChange: (event) => setNote(event.target.value)
399
+ }
400
+ ),
401
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
402
+ import_design_system2.Button,
403
+ {
404
+ className: "mt-3",
405
+ size: "sm",
406
+ disabled: busy,
407
+ onClick: () => void send(),
408
+ children: [
409
+ busy ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.Loader2, { className: "mr-1.5 h-4 w-4 animate-spin", "aria-hidden": true }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.Send, { className: "mr-1.5 h-4 w-4", "aria-hidden": true }),
410
+ "Request access"
411
+ ]
412
+ }
413
+ )
414
+ ] });
415
+ }
416
+ function LevelChoice({
417
+ active,
418
+ label,
419
+ onClick
420
+ }) {
421
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
422
+ "button",
423
+ {
424
+ type: "button",
425
+ "aria-pressed": active,
426
+ onClick,
427
+ className: [
428
+ "rounded-full border px-3 py-1 text-xs font-medium transition-colors",
429
+ active ? "border-primary bg-primary/10 text-primary-ink" : "border-border bg-background text-muted-foreground hover:bg-accent"
430
+ ].join(" "),
431
+ children: label
432
+ }
433
+ );
434
+ }
435
+
436
+ // src/access/react/BlindAccessAsk.tsx
437
+ var import_react3 = require("react");
438
+ var import_lucide_react2 = require("lucide-react");
439
+ var import_design_system3 = require("@ai-matrx/design-system");
440
+ var import_access3 = require("@ai-matrx/data/access");
441
+ var import_jsx_runtime3 = require("react/jsx-runtime");
442
+ function BlindAccessAsk({
443
+ token,
444
+ id,
445
+ href
446
+ }) {
447
+ const ui = useAccessUi();
448
+ const [state, setState] = (0, import_react3.useState)({ kind: "idle" });
449
+ if (state.kind === "asked") {
450
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-sm text-foreground", role: "status", "data-blind-ask": "asked", children: state.says || import_access3.BLIND_ASK_ANSWER });
451
+ }
452
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-col items-start gap-2", "data-blind-ask": state.kind, children: [
453
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
454
+ import_design_system3.Button,
455
+ {
456
+ size: "sm",
457
+ disabled: state.kind === "asking",
458
+ "aria-busy": state.kind === "asking",
459
+ onClick: () => {
460
+ setState({ kind: "asking" });
461
+ void (0, import_access3.askForAccessBlind)({
462
+ resourceType: token,
463
+ resourceId: id,
464
+ href: href ?? (typeof window !== "undefined" ? window.location.pathname : null)
465
+ }).then(
466
+ (says) => setState({ kind: "asked", says }),
467
+ (e) => setState({
468
+ kind: "failed",
469
+ why: e instanceof Error ? e.message : "We could not send that just now."
470
+ })
471
+ );
472
+ },
473
+ "data-tap-target": true,
474
+ children: [
475
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.KeyRound, { className: "mr-1.5 h-4 w-4", "aria-hidden": true }),
476
+ state.kind === "asking" ? "Asking\u2026" : "Ask for access"
477
+ ]
478
+ }
479
+ ),
480
+ state.kind === "failed" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: "text-xs text-destructive", role: "alert", children: [
481
+ state.why,
482
+ " Try again.",
483
+ ui.renderErrorMenu({ className: "ml-auto" })
484
+ ] }) : null
485
+ ] });
486
+ }
487
+
488
+ // src/access/react/useAccessGate.ts
489
+ var import_react4 = require("react");
490
+ var import_access4 = require("@ai-matrx/data/access");
491
+ var import_access5 = require("@ai-matrx/data/access");
492
+ function useAccessGate(token, id, options = {}) {
493
+ const resolveRecordUnavailableCapture = useAccessUi().resolveRecordUnavailableCapture;
494
+ const [nonce, setNonce] = (0, import_react4.useState)(0);
495
+ const enabled = options.enabled !== false && Boolean(token && id);
496
+ const key = enabled ? `${token}:${id}:${nonce}` : null;
497
+ const [resolved, setResolved] = (0, import_react4.useState)(null);
498
+ (0, import_react4.useEffect)(() => {
499
+ if (!key || !token || !id) return;
500
+ let active = true;
501
+ const read = (0, import_access5.classifyDataError)(options.readError) === "fault" ? "fault" : "access-question";
502
+ void (0, import_access4.fetchAccessDeniedContext)(token, id, read).then((next) => {
503
+ if (!active) return;
504
+ const resolution = next.status === "anonymous" ? "signed-out" : next.status === "denied" || next.status === "deleted" || next.status === "missing" || next.status === "ok" ? next.status : null;
505
+ if (resolution) {
506
+ resolveRecordUnavailableCapture(options.readError, resolution);
507
+ }
508
+ setResolved({ key, context: next });
509
+ });
510
+ return () => {
511
+ active = false;
512
+ };
513
+ }, [key, token, id, options.readError, resolveRecordUnavailableCapture]);
514
+ const context = resolved && resolved.key === key ? resolved.context : null;
515
+ const isLoading = key !== null && context === null;
516
+ const refresh = (0, import_react4.useCallback)(() => setNonce((n) => n + 1), []);
517
+ return {
518
+ context,
519
+ status: isLoading ? "loading" : context?.status ?? "error",
520
+ isLoading,
521
+ refresh
522
+ };
523
+ }
524
+
525
+ // src/access/react/AccessDenied.tsx
526
+ var import_format2 = require("@ai-matrx/kit/format");
527
+ var import_design_system5 = require("@ai-matrx/design-system");
528
+ var import_access6 = require("@ai-matrx/data/access");
529
+ var import_jsx_runtime4 = require("react/jsx-runtime");
530
+ function RestoreArchived({
531
+ token,
532
+ id,
533
+ kind,
534
+ onRestored
535
+ }) {
536
+ const ui = useAccessUi();
537
+ const [busy, setBusy] = (0, import_react5.useState)(false);
538
+ const [failure, setFailure] = (0, import_react5.useState)(null);
539
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
540
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
541
+ import_design_system4.Button,
542
+ {
543
+ size: "sm",
544
+ disabled: busy,
545
+ onClick: async () => {
546
+ setBusy(true);
547
+ setFailure(null);
548
+ try {
549
+ await ui.restoreFromTrash(token, id);
550
+ ui.toast.success(`Restored the ${kind}.`);
551
+ onRestored();
552
+ } catch (error) {
553
+ setFailure(error);
554
+ } finally {
555
+ setBusy(false);
556
+ }
557
+ },
558
+ children: [
559
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.RotateCcw, { className: "mr-1.5 h-4 w-4", "aria-hidden": true }),
560
+ busy ? "Restoring\u2026" : "Restore"
561
+ ]
562
+ }
563
+ ),
564
+ failure ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
565
+ import_design_system5.ErrorNotice,
566
+ {
567
+ size: "inline",
568
+ className: "basis-full text-sm",
569
+ title: "Not restored",
570
+ error: failure,
571
+ operation: `Restore this ${kind}`,
572
+ records: [{ type: token, id }]
573
+ }
574
+ ) : null
575
+ ] });
576
+ }
577
+ function headline(context) {
578
+ const kind = context.entity.label.toLowerCase();
579
+ switch (context.status) {
580
+ case "denied":
581
+ return `You don't have access to this ${kind}`;
582
+ case "deleted":
583
+ return (0, import_access6.archivedHeadline)(context);
584
+ case "missing":
585
+ return `We couldn't find this ${kind}`;
586
+ case "anonymous":
587
+ return `Sign in to open this ${kind}`;
588
+ case "ok":
589
+ return `We couldn't load this ${kind}`;
590
+ default:
591
+ return "Something went wrong";
592
+ }
593
+ }
594
+ function explanation(context) {
595
+ const kind = context.entity.label.toLowerCase();
596
+ switch (context.status) {
597
+ case "denied": {
598
+ const owner = context.organization ? context.organization.name : context.owner?.displayName;
599
+ if (!context.canRequest && context.request?.status !== "pending") {
600
+ return owner ? `It belongs to ${owner}, and there's no one for us to pass a request to.` : `It belongs to someone else, and there's no one for us to pass a request to.`;
601
+ }
602
+ return owner ? `It belongs to ${owner}. You can ask for access below.` : `It belongs to someone else. You can ask for access below.`;
603
+ }
604
+ case "deleted":
605
+ return (0, import_access6.archivedExplanation)(context);
606
+ case "missing":
607
+ return `If someone shared a link with you, you can ask for access.`;
608
+ case "anonymous":
609
+ return `We can't tell you anything about it until we know who you are.`;
610
+ case "ok":
611
+ return `You do have access to it \u2014 something went wrong on our side. Try again.`;
612
+ default:
613
+ return `We couldn't work out what happened. Try again in a moment.`;
614
+ }
615
+ }
616
+ function StatusIcon({ status }) {
617
+ const className = "h-7 w-7 text-muted-foreground";
618
+ if (status === "denied") return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.Lock, { className, "aria-hidden": true });
619
+ if (status === "deleted") return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.Trash2, { className, "aria-hidden": true });
620
+ if (status === "missing")
621
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.SearchX, { className, "aria-hidden": true });
622
+ if (status === "anonymous")
623
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.LogIn, { className, "aria-hidden": true });
624
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.TriangleAlert, { className, "aria-hidden": true });
625
+ }
626
+ function IdentityBlock({
627
+ label,
628
+ name,
629
+ href,
630
+ avatar
631
+ }) {
632
+ const body = /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
633
+ avatar,
634
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "min-w-0", children: [
635
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "block text-[11px] uppercase tracking-wide text-muted-foreground", children: label }),
636
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "block truncate text-foreground", children: name })
637
+ ] })
638
+ ] });
639
+ if (!href) {
640
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "flex min-w-0 items-center gap-2 text-sm", children: body });
641
+ }
642
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
643
+ AccessLink,
644
+ {
645
+ href,
646
+ className: "flex min-w-0 items-center gap-2 text-sm hover:underline",
647
+ children: body
648
+ }
649
+ );
650
+ }
651
+ function AccessDeniedView({
652
+ context,
653
+ id,
654
+ fallbackHref,
655
+ fallbackLabel,
656
+ onRetry,
657
+ onChanged,
658
+ suggestions = [],
659
+ requestability = "requestable",
660
+ reason,
661
+ headline: headlineOverride,
662
+ footer
663
+ }) {
664
+ const ui = useAccessUi();
665
+ const router = ui.router;
666
+ const signInHref = ui.useLoginHref();
667
+ const tryGetEntityInfo = ui.tryGetEntityInfo;
668
+ const showOrg = Boolean(context.organization);
669
+ const ancestor = context.ancestor;
670
+ const ancestorInfo = ancestor ? tryGetEntityInfo(ancestor.token) : null;
671
+ const ancestorHref = ancestorInfo?.hrefFor?.(ancestor?.id ?? "") ?? null;
672
+ const selfInfo = tryGetEntityInfo(context.entity.token);
673
+ const selfHref = selfInfo?.hrefFor?.(id) ?? null;
674
+ const absolute = requestability === "absolute" && context.status === "denied";
675
+ const title = headlineOverride?.trim() || (absolute ? "You can't open this here" : headline(context));
676
+ const body = reason?.trim() || (absolute ? "" : explanation(context));
677
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "matrx-touch-targets flex h-full min-h-64 w-full items-center justify-center p-6", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "w-full max-w-xl", children: [
678
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-start gap-4", children: [
679
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl border border-border bg-muted", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(StatusIcon, { status: context.status }) }),
680
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "min-w-0 flex-1", children: [
681
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-start gap-2", children: [
682
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h1", { className: "min-w-0 flex-1 text-xl font-semibold tracking-tight text-foreground", children: title }),
683
+ ui.renderErrorMenu({
684
+ size: "icon",
685
+ input: {
686
+ title,
687
+ message: body || title,
688
+ operation: absolute || context.status === "missing" ? "Open this record" : `Open this ${context.entity.label.toLowerCase()}`,
689
+ ...absolute || context.status === "missing" ? {} : { records: [{ type: context.entity.token, id }] },
690
+ details: { access_status: context.status },
691
+ source: "route-boundary"
692
+ }
693
+ })
694
+ ] }),
695
+ context.entity.title ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "mt-1 truncate text-sm text-muted-foreground", children: [
696
+ context.entity.label,
697
+ " \xB7",
698
+ " ",
699
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-foreground", children: context.entity.title })
700
+ ] }) : null,
701
+ body ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "mt-2 text-sm text-muted-foreground", children: body }) : null
702
+ ] })
703
+ ] }),
704
+ !absolute && (context.owner || showOrg) ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mt-5 flex flex-wrap items-center gap-4 rounded-lg border border-border bg-muted/30 px-4 py-3", children: [
705
+ context.owner ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
706
+ IdentityBlock,
707
+ {
708
+ label: "Owner",
709
+ name: context.owner.displayName ?? "Someone else",
710
+ href: context.owner.creatorHandle ? `/c/${context.owner.creatorHandle}` : null,
711
+ avatar: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system4.Avatar, { className: "h-7 w-7", children: [
712
+ context.owner.avatarUrl ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
713
+ import_design_system4.AvatarImage,
714
+ {
715
+ src: context.owner.avatarUrl,
716
+ alt: context.owner.displayName ?? "Owner"
717
+ }
718
+ ) : null,
719
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system4.AvatarFallback, { className: "text-[11px]", children: (0, import_format2.getInitials)(context.owner.displayName) })
720
+ ] })
721
+ }
722
+ ) : null,
723
+ showOrg && context.organization ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
724
+ IdentityBlock,
725
+ {
726
+ label: "Organization",
727
+ name: context.organization.name ?? "An organization",
728
+ href: context.organization.viewerIsMember ? `/organizations/${context.organization.id}` : null,
729
+ avatar: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "flex h-7 w-7 items-center justify-center rounded-full bg-background", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
730
+ import_lucide_react3.Building2,
731
+ {
732
+ className: "h-3.5 w-3.5 text-muted-foreground",
733
+ "aria-hidden": true
734
+ }
735
+ ) })
736
+ }
737
+ ) : null
738
+ ] }) : null,
739
+ context.status === "denied" && !absolute ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mt-4", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
740
+ RequestAccessPanel,
741
+ {
742
+ context,
743
+ resourceId: id,
744
+ href: selfHref,
745
+ onChanged
746
+ }
747
+ ) }) : null,
748
+ context.status === "missing" && requestability !== "absolute" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mt-4", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(BlindAccessAsk, { token: context.entity.token, id, href: selfHref }) }) : null,
749
+ suggestions.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mt-5 grid gap-2 sm:grid-cols-2", children: suggestions.map((s) => {
750
+ const Icon = s.icon;
751
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
752
+ AccessLink,
753
+ {
754
+ href: s.href,
755
+ "data-tap-target": true,
756
+ className: "group flex items-start gap-3 rounded-lg border border-border bg-card px-4 py-3 transition-colors hover:border-foreground/20 hover:bg-accent",
757
+ children: [
758
+ Icon ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground group-hover:text-foreground", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Icon, { className: "h-4 w-4" }) }) : null,
759
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "min-w-0", children: [
760
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "block text-sm font-medium text-foreground", children: s.title }),
761
+ s.description ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mt-0.5 block text-xs leading-snug text-muted-foreground", children: s.description }) : null
762
+ ] })
763
+ ]
764
+ },
765
+ s.href + s.title
766
+ );
767
+ }) }) : null,
768
+ footer ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mt-4", children: footer }) : null,
769
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mt-5 flex flex-wrap items-center gap-2", children: [
770
+ context.status === "anonymous" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system4.Button, { asChild: true, size: "sm", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(AccessLink, { href: signInHref, "data-tap-target": true, children: [
771
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.LogIn, { className: "mr-1.5 h-4 w-4", "aria-hidden": true }),
772
+ "Sign in"
773
+ ] }) }) : null,
774
+ (0, import_access6.mayRestoreArchived)(context) ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
775
+ RestoreArchived,
776
+ {
777
+ token: context.entity.token,
778
+ id,
779
+ kind: context.entity.label.toLowerCase(),
780
+ onRestored: () => {
781
+ onChanged();
782
+ if (onRetry) onRetry();
783
+ else router.refresh();
784
+ }
785
+ }
786
+ ) : null,
787
+ (context.status === "ok" || context.status === "error") && onRetry ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system4.Button, { size: "sm", onClick: onRetry, children: [
788
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.RefreshCw, { className: "mr-1.5 h-4 w-4", "aria-hidden": true }),
789
+ "Try again"
790
+ ] }) : null,
791
+ ancestor && ancestorHref ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system4.Button, { asChild: true, size: "sm", variant: "outline", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(AccessLink, { href: ancestorHref, "data-tap-target": true, children: [
792
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.ArrowLeft, { className: "mr-1.5 h-4 w-4", "aria-hidden": true }),
793
+ "Open ",
794
+ ancestor.title ?? ancestor.label.toLowerCase()
795
+ ] }) }) : fallbackHref ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system4.Button, { asChild: true, size: "sm", variant: "outline", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(AccessLink, { href: fallbackHref, "data-tap-target": true, children: [
796
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.ArrowLeft, { className: "mr-1.5 h-4 w-4", "aria-hidden": true }),
797
+ fallbackLabel ?? "Back to what you can see"
798
+ ] }) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system4.Button, { size: "sm", variant: "outline", onClick: () => router.back(), children: [
799
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.ArrowLeft, { className: "mr-1.5 h-4 w-4", "aria-hidden": true }),
800
+ "Go back"
801
+ ] })
802
+ ] })
803
+ ] }) });
804
+ }
805
+ function AccessDenied({
806
+ token,
807
+ id,
808
+ readError,
809
+ fallbackHref,
810
+ fallbackLabel,
811
+ onRetry,
812
+ suggestions,
813
+ requestability,
814
+ reason,
815
+ headline: headline2,
816
+ footer
817
+ }) {
818
+ const { context, isLoading, refresh } = useAccessGate(token, id, {
819
+ readError
820
+ });
821
+ if (isLoading || !context) {
822
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex h-full min-h-64 items-center justify-center p-6", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "h-8 w-8 animate-pulse rounded-full bg-muted" }) });
823
+ }
824
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
825
+ AccessDeniedView,
826
+ {
827
+ context,
828
+ id,
829
+ fallbackHref,
830
+ fallbackLabel,
831
+ onRetry,
832
+ onChanged: refresh,
833
+ suggestions,
834
+ requestability,
835
+ reason,
836
+ headline: headline2,
837
+ footer
838
+ }
839
+ );
840
+ }
841
+
842
+ // src/access/react/AccessGate.tsx
843
+ var import_access7 = require("@ai-matrx/data/access");
844
+ var import_access8 = require("@ai-matrx/data/access");
845
+ var import_jsx_runtime5 = require("react/jsx-runtime");
846
+ function AccessGate({
847
+ token,
848
+ id,
849
+ error,
850
+ onRetry,
851
+ fallbackHref,
852
+ fallbackLabel,
853
+ relatedReads = [],
854
+ renderFault,
855
+ suggestions,
856
+ requestability,
857
+ reason,
858
+ headline: headline2,
859
+ footer
860
+ }) {
861
+ if (error && (0, import_access7.classifyDataError)(error) === "fault" && renderFault) {
862
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: renderFault(error) });
863
+ }
864
+ const refusal = (0, import_access8.readServerRefusal)(error);
865
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
866
+ relatedReads.map((read) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
867
+ AccessGateCaptureResolver,
868
+ {
869
+ token: read.token,
870
+ id: read.id,
871
+ error: read.error
872
+ },
873
+ `${read.token}:${read.id}`
874
+ )),
875
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
876
+ AccessDenied,
877
+ {
878
+ token,
879
+ id,
880
+ readError: error,
881
+ onRetry: refusal ? void 0 : onRetry,
882
+ fallbackHref,
883
+ fallbackLabel,
884
+ suggestions,
885
+ requestability,
886
+ reason: refusal?.message ?? reason,
887
+ headline: headline2,
888
+ footer: refusal?.code ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
889
+ footer,
890
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ServerRefusalCode, { code: refusal.code, issues: refusal.issues })
891
+ ] }) : footer
892
+ }
893
+ )
894
+ ] });
895
+ }
896
+ function AccessGateCaptureResolver({
897
+ token,
898
+ id,
899
+ error
900
+ }) {
901
+ useAccessGate(token, id, { readError: error });
902
+ return null;
903
+ }
904
+ function ServerRefusalCode({
905
+ code,
906
+ issues
907
+ }) {
908
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("details", { className: "text-[11px] text-muted-foreground", children: [
909
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("summary", { className: "cursor-pointer select-none", children: "What the server said" }),
910
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mt-1 font-mono", children: code }),
911
+ issues.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { className: "mt-1 list-disc pl-4", children: issues.map((issue) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("li", { children: issue }, issue)) }) : null
912
+ ] });
913
+ }
914
+
915
+ // src/access/react/SlugAccessGate.tsx
916
+ var import_react6 = require("react");
917
+ var import_lucide_react4 = require("lucide-react");
918
+ var import_uuid = require("@ai-matrx/kit/uuid");
919
+ var import_controls = require("@ai-matrx/design-system/controls");
920
+ var import_access9 = require("@ai-matrx/data/access");
921
+ var import_jsx_runtime6 = require("react/jsx-runtime");
922
+ function SlugAccessGate({
923
+ tokens,
924
+ slug,
925
+ noun,
926
+ fallbackHref,
927
+ fallbackLabel,
928
+ publishFiltered = false
929
+ }) {
930
+ const tokenKey = tokens.join("|");
931
+ const [resolved, setResolved] = (0, import_react6.useState)(null);
932
+ const key = `${tokenKey}::${slug}`;
933
+ (0, import_react6.useEffect)(() => {
934
+ let active = true;
935
+ const list = tokenKey ? tokenKey.split("|") : [];
936
+ void (async () => {
937
+ for (const token of list) {
938
+ const id = slug ? await (0, import_access9.resolveAccessGateSlug)(token, slug) : null;
939
+ if (!active) return;
940
+ if (id) {
941
+ const readable = publishFiltered ? (await (0, import_access9.fetchAccessDeniedContext)(token, id, "fault")).status === "ok" : false;
942
+ if (active) setResolved({ key, token, id, readable });
943
+ return;
944
+ }
945
+ }
946
+ if (active && list.length && (0, import_uuid.isUuidShape)(slug)) {
947
+ setResolved({ key, token: list[0] ?? null, id: slug, readable: false });
948
+ return;
949
+ }
950
+ if (active) setResolved({ key, token: null, id: null, readable: false });
951
+ })();
952
+ return () => {
953
+ active = false;
954
+ };
955
+ }, [key, tokenKey, slug, publishFiltered]);
956
+ if (!resolved || resolved.key !== key) return null;
957
+ if (resolved.token && resolved.id && resolved.readable) {
958
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
959
+ Notice,
960
+ {
961
+ title: `This ${noun} isn't published`,
962
+ body: `It exists and you can open it, but it has no public page until it's published.`,
963
+ fallbackHref,
964
+ fallbackLabel
965
+ }
966
+ );
967
+ }
968
+ if (resolved.token && resolved.id) {
969
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
970
+ AccessGate,
971
+ {
972
+ token: resolved.token,
973
+ id: resolved.id,
974
+ fallbackHref,
975
+ fallbackLabel
976
+ }
977
+ );
978
+ }
979
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
980
+ Notice,
981
+ {
982
+ title: `This address doesn't match ${/^[aeiou]/i.test(noun) ? "an" : "a"} ${noun} you can open`,
983
+ body: "Check the link, or sign in with the account it was shared with.",
984
+ fallbackHref,
985
+ fallbackLabel
986
+ }
987
+ );
988
+ }
989
+ function Notice({
990
+ title,
991
+ body,
992
+ fallbackHref,
993
+ fallbackLabel
994
+ }) {
995
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex h-full min-h-64 w-full items-center justify-center p-6", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "w-full max-w-lg", children: [
996
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex items-start gap-4", children: [
997
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex h-12 w-12 shrink-0 items-center justify-center rounded-full bg-muted", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react4.Link2Off, { className: "h-6 w-6 text-muted-foreground", "aria-hidden": true }) }),
998
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "min-w-0 flex-1", children: [
999
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h1", { className: "text-lg font-semibold text-foreground", children: title }),
1000
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mt-2 text-sm text-muted-foreground", children: body })
1001
+ ] })
1002
+ ] }),
1003
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mt-5", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_controls.Button, { asChild: true, variant: "outline", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(AccessLink, { href: fallbackHref, children: [
1004
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react4.ArrowLeft, { className: "mr-1.5 h-4 w-4", "aria-hidden": true }),
1005
+ fallbackLabel
1006
+ ] }) }) })
1007
+ ] }) });
1008
+ }
1009
+
1010
+ // src/access/react/SettingAccessGate.tsx
1011
+ var import_react7 = require("react");
1012
+ var import_lucide_react5 = require("lucide-react");
1013
+ var import_controls2 = require("@ai-matrx/design-system/controls");
1014
+ var import_access10 = require("@ai-matrx/data/access");
1015
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1016
+ function SettingAccessGate({
1017
+ canManage,
1018
+ organizationId,
1019
+ organizationSlugOrId,
1020
+ settingKey,
1021
+ settingLabel,
1022
+ actionKey,
1023
+ actionPayload,
1024
+ defaultMessage,
1025
+ requestReady = true,
1026
+ children
1027
+ }) {
1028
+ const ui = useAccessUi();
1029
+ const currentUserId = ui.useCurrentUser().id;
1030
+ const ProTextarea = ui.Textarea;
1031
+ const toast = ui.toast;
1032
+ const [customMessage, setCustomMessage] = (0, import_react7.useState)(null);
1033
+ const [busy, setBusy] = (0, import_react7.useState)(false);
1034
+ const [pending, setPending] = (0, import_react7.useState)(false);
1035
+ const message = customMessage ?? defaultMessage;
1036
+ if (canManage) return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children });
1037
+ async function send() {
1038
+ setBusy(true);
1039
+ try {
1040
+ const result = await (0, import_access10.createSettingAccessRequest)({
1041
+ organizationId,
1042
+ settingKey,
1043
+ settingLabel,
1044
+ href: ui.settingDoorHref({
1045
+ scope: "organization",
1046
+ organizationSlugOrId,
1047
+ controlId: settingKey
1048
+ }),
1049
+ actionKey,
1050
+ actionPayload,
1051
+ message,
1052
+ currentUserId
1053
+ });
1054
+ setPending(true);
1055
+ if (result.already) {
1056
+ toast.success("Your request is already waiting for an admin.");
1057
+ } else if (result.delivered === 0) {
1058
+ toast.warning(
1059
+ "Your request is saved, but we couldn't message an admin just now."
1060
+ );
1061
+ } else {
1062
+ toast.success("Request sent to your organization admins.");
1063
+ }
1064
+ } catch (error) {
1065
+ toast.error(
1066
+ error instanceof Error ? error.message : "We couldn't send that request."
1067
+ );
1068
+ } finally {
1069
+ setBusy(false);
1070
+ }
1071
+ }
1072
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "rounded-lg border border-border bg-muted/30 p-4", children: [
1073
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-start gap-3", children: [
1074
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-background", children: pending ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react5.Check, { className: "h-4 w-4 text-primary", "aria-hidden": true }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react5.Lock, { className: "h-4 w-4 text-muted-foreground", "aria-hidden": true }) }),
1075
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "min-w-0 flex-1", children: [
1076
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "text-sm font-medium text-foreground", children: pending ? "Your request is waiting" : "An organization admin controls this setting" }),
1077
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "mt-1 text-xs leading-5 text-muted-foreground", children: pending ? "It is saved in Access requests, even if the direct message could not be delivered." : "Tell them what you need. The request is filled with this setting and includes an action they can complete inside the message." })
1078
+ ] })
1079
+ ] }),
1080
+ !pending ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "mt-3 space-y-2", children: [
1081
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1082
+ ProTextarea,
1083
+ {
1084
+ value: message,
1085
+ onChange: (event) => setCustomMessage(event.target.value),
1086
+ rows: 3,
1087
+ maxLength: 1e3,
1088
+ "aria-label": "Message to organization admins"
1089
+ }
1090
+ ),
1091
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1092
+ import_controls2.Button,
1093
+ {
1094
+ icon: busy ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react5.Loader2, { className: "animate-spin", "aria-hidden": true }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react5.Send, { "aria-hidden": true }),
1095
+ variant: "primary",
1096
+ disabled: busy || !message.trim() || !requestReady,
1097
+ onClick: () => void send(),
1098
+ children: "Request this change"
1099
+ }
1100
+ )
1101
+ ] }) : null
1102
+ ] });
1103
+ }
1104
+
1105
+ // src/access/react/ForbiddenSurface.tsx
1106
+ var import_lucide_react6 = require("lucide-react");
1107
+ var import_controls3 = require("@ai-matrx/design-system/controls");
1108
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1109
+ function ForbiddenSurfaceView({
1110
+ state,
1111
+ isAuthenticated,
1112
+ pathname,
1113
+ search = "",
1114
+ signInHref,
1115
+ homeHref = "/dashboard"
1116
+ }) {
1117
+ const unverified = state === "unverified";
1118
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "flex h-full min-h-64 w-full items-center justify-center p-6", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "w-full max-w-lg", children: [
1119
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-start gap-4", children: [
1120
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "flex h-12 w-12 shrink-0 items-center justify-center rounded-full bg-muted", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react6.Lock, { className: "h-6 w-6 text-muted-foreground", "aria-hidden": true }) }),
1121
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "min-w-0 flex-1", children: [
1122
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h1", { className: "text-lg font-semibold text-foreground", children: unverified ? "We could not confirm who you are just now" : isAuthenticated ? "You don't have access to this page" : "Sign in to open this page" }),
1123
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "mt-2 text-sm text-muted-foreground", children: unverified ? "You have not been signed out. This usually clears in a few seconds \u2014 open the page again." : isAuthenticated ? "Your account can't open it. If someone shared it with you, ask them to share it with this account." : "We can't tell you anything about it until we know who you are." })
1124
+ ] })
1125
+ ] }),
1126
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "mt-5 flex flex-wrap items-center gap-2", children: [
1127
+ unverified ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_controls3.Button, { variant: "primary", asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(AccessLink, { href: `${pathname}${search}`, children: [
1128
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react6.RotateCw, { className: "mr-1.5 h-4 w-4", "aria-hidden": true }),
1129
+ "Open it again"
1130
+ ] }) }) : isAuthenticated ? null : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_controls3.Button, { variant: "primary", asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(AccessLink, { href: signInHref, children: [
1131
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react6.LogIn, { className: "mr-1.5 h-4 w-4", "aria-hidden": true }),
1132
+ "Sign in"
1133
+ ] }) }),
1134
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_controls3.Button, { asChild: true, variant: "outline", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(AccessLink, { href: homeHref, children: [
1135
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react6.ArrowLeft, { className: "mr-1.5 h-4 w-4", "aria-hidden": true }),
1136
+ "Back to what you can see"
1137
+ ] }) })
1138
+ ] })
1139
+ ] }) });
1140
+ }
1141
+
1142
+ // src/access/react/RequestAccess.tsx
1143
+ var import_react9 = require("react");
1144
+ var import_lucide_react7 = require("lucide-react");
1145
+ var import_controls4 = require("@ai-matrx/design-system/controls");
1146
+ var import_design_system6 = require("@ai-matrx/design-system");
1147
+ var import_design_system7 = require("@ai-matrx/design-system");
1148
+
1149
+ // src/access/react/useRequestAccess.ts
1150
+ var import_react8 = require("react");
1151
+ var import_access11 = require("@ai-matrx/data/access");
1152
+ var import_access12 = require("@ai-matrx/data/access");
1153
+ var import_access13 = require("@ai-matrx/data/access");
1154
+ function useRequestAccess(target) {
1155
+ const ui = useAccessUi();
1156
+ const { id: userId, displayName, email } = ui.useCurrentUser();
1157
+ const owner = (0, import_access13.resolveRequestOwner)(target.owner);
1158
+ const [sending, setSending] = (0, import_react8.useState)(false);
1159
+ const [sent, setSent] = (0, import_react8.useState)(false);
1160
+ const context = () => ({
1161
+ pageUrl: typeof window === "undefined" ? "" : window.location.href,
1162
+ requesterName: displayName,
1163
+ requesterEmail: email
1164
+ });
1165
+ async function send(note) {
1166
+ setSending(true);
1167
+ try {
1168
+ const ctx = context();
1169
+ if (owner.kind === "system") {
1170
+ const organizationId = await (0, import_access11.requireAccessEnvField)("ensureOrgId", "useRequestAccess")(null);
1171
+ const result2 = await ui.reportFeedback({
1172
+ // The feedback vocabulary's access-request type; `metadata` carries
1173
+ // the structured target (FEATURE.md, RequestAccess).
1174
+ feedback_type: "request",
1175
+ route: typeof window === "undefined" ? "" : window.location.pathname,
1176
+ organization_id: organizationId,
1177
+ description: `Access request: ${(0, import_access13.requestTitle)(target)}
1178
+
1179
+ ${(0, import_access13.requestBody)(target, ctx, note)}`,
1180
+ metadata: (0, import_access13.feedbackMetadata)(target, ctx)
1181
+ });
1182
+ if (!result2.success) {
1183
+ throw new Error(result2.error ?? "We couldn't send that request.");
1184
+ }
1185
+ setSent(true);
1186
+ return {
1187
+ already: false,
1188
+ undelivered: false,
1189
+ message: `Request sent to ${(0, import_access13.destinationLabel)(owner)}.`
1190
+ };
1191
+ }
1192
+ const result = await (0, import_access12.createSettingAccessRequest)({
1193
+ organizationId: owner.organizationId,
1194
+ settingKey: (0, import_access13.orgSettingKey)(target),
1195
+ settingLabel: (0, import_access13.requestTitle)(target),
1196
+ href: (0, import_access13.orgManageHref)(target, owner),
1197
+ actionKey: ui.manualRequestActionKey,
1198
+ actionPayload: (0, import_access13.orgActionPayload)(target, owner),
1199
+ message: (0, import_access13.requestBody)(target, ctx, note),
1200
+ currentUserId: userId
1201
+ });
1202
+ setSent(true);
1203
+ if (result.already) {
1204
+ return {
1205
+ already: true,
1206
+ undelivered: false,
1207
+ message: "Your request is already waiting for an answer."
1208
+ };
1209
+ }
1210
+ if (result.delivered === 0) {
1211
+ return {
1212
+ already: false,
1213
+ undelivered: true,
1214
+ message: "Your request is saved in Access requests, but we couldn't message an admin just now."
1215
+ };
1216
+ }
1217
+ return {
1218
+ already: false,
1219
+ undelivered: false,
1220
+ message: `Request sent to ${(0, import_access13.destinationLabel)(owner)}.`
1221
+ };
1222
+ } finally {
1223
+ setSending(false);
1224
+ }
1225
+ }
1226
+ return {
1227
+ owner,
1228
+ destination: (0, import_access13.destinationLabel)(owner),
1229
+ context,
1230
+ send,
1231
+ sending,
1232
+ sent
1233
+ };
1234
+ }
1235
+
1236
+ // src/access/react/RequestAccess.tsx
1237
+ var import_access14 = require("@ai-matrx/data/access");
1238
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1239
+ function RequestAccess({
1240
+ target,
1241
+ variant = "inline",
1242
+ reason,
1243
+ className
1244
+ }) {
1245
+ const [open, setOpen] = (0, import_react9.useState)(false);
1246
+ const request = useRequestAccess(target);
1247
+ const label = `Ask for access to ${target.action.toLowerCase()}`;
1248
+ const why = reason ?? (request.owner.kind === "system" ? "Only the platform team can change this." : `Only ${request.destination} can change this.`);
1249
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1250
+ variant === "icon" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1251
+ "button",
1252
+ {
1253
+ type: "button",
1254
+ "aria-label": label,
1255
+ title: label,
1256
+ onClick: () => setOpen(true),
1257
+ className: (0, import_design_system7.cn)(
1258
+ "inline-flex size-7 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1259
+ className
1260
+ ),
1261
+ children: request.sent ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react7.Check, { className: "size-3.5 text-primary", "aria-hidden": "true" }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react7.KeyRound, { className: "size-3.5", "aria-hidden": "true" })
1262
+ }
1263
+ ) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1264
+ "div",
1265
+ {
1266
+ className: (0, import_design_system7.cn)(
1267
+ "flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground",
1268
+ className
1269
+ ),
1270
+ children: [
1271
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: request.sent ? "Request sent." : why }),
1272
+ request.sent ? null : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1273
+ import_controls4.Button,
1274
+ {
1275
+ icon: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react7.KeyRound, { "aria-hidden": "true" }),
1276
+ variant: "outline",
1277
+ onClick: () => setOpen(true),
1278
+ children: "Ask for access"
1279
+ }
1280
+ )
1281
+ ]
1282
+ }
1283
+ ),
1284
+ open ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1285
+ RequestAccessDialog,
1286
+ {
1287
+ target,
1288
+ request,
1289
+ onOpenChange: setOpen
1290
+ }
1291
+ ) : null
1292
+ ] });
1293
+ }
1294
+ function RequestAccessDialog({
1295
+ target,
1296
+ request,
1297
+ onOpenChange
1298
+ }) {
1299
+ const ui = useAccessUi();
1300
+ const { toast } = ui;
1301
+ const ProTextarea = ui.Textarea;
1302
+ const [note, setNote] = (0, import_react9.useState)("");
1303
+ const lines = (0, import_access14.contextLines)(target, request.context());
1304
+ async function send() {
1305
+ try {
1306
+ const result = await request.send(note);
1307
+ if (result.undelivered) toast.warning(result.message);
1308
+ else toast.success(result.message);
1309
+ onOpenChange(false);
1310
+ } catch (error) {
1311
+ if (ui.presentOrganizationRefusal(error, { act: "sent", subject: "Your request" })) return;
1312
+ toast.error(
1313
+ error instanceof Error ? error.message : "We couldn't send that request."
1314
+ );
1315
+ }
1316
+ }
1317
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_design_system6.Dialog, { open: true, onOpenChange, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_design_system6.DialogContent, { className: "matrx-touch-targets sm:max-w-md", children: [
1318
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_design_system6.DialogHeader, { children: [
1319
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_design_system6.DialogTitle, { children: "Ask for access" }),
1320
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_design_system6.DialogDescription, { children: [
1321
+ "Goes to ",
1322
+ request.destination,
1323
+ "."
1324
+ ] })
1325
+ ] }),
1326
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("dl", { className: "grid grid-cols-[auto_1fr] gap-x-3 gap-y-1 rounded-md border border-border bg-muted/30 p-3 text-xs", children: lines.map(([label, value]) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "contents", children: [
1327
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("dt", { className: "text-muted-foreground", children: label }),
1328
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("dd", { className: "min-w-0 text-foreground [overflow-wrap:anywhere]", children: value })
1329
+ ] }, label)) }),
1330
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1331
+ ProTextarea,
1332
+ {
1333
+ className: "min-h-20 text-base md:text-sm",
1334
+ value: note,
1335
+ maxLength: 1e3,
1336
+ autoFocus: true,
1337
+ placeholder: "What do you need, and why? (optional)",
1338
+ "aria-label": "Note",
1339
+ onChange: (event) => setNote(event.target.value)
1340
+ }
1341
+ ),
1342
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_design_system6.DialogFooter, { children: [
1343
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_controls4.Button, { variant: "outline", onClick: () => onOpenChange(false), children: "Cancel" }),
1344
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_controls4.Button, { icon: request.sending ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react7.Loader2, { className: "animate-spin", "aria-hidden": "true" }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react7.Send, { "aria-hidden": "true" }), variant: "primary", disabled: request.sending, onClick: () => void send(), children: "Send request" })
1345
+ ] })
1346
+ ] }) });
1347
+ }
1348
+
1349
+ // src/access/react/UnresolvedEntityRef.tsx
1350
+ var import_react10 = require("react");
1351
+ var import_lucide_react8 = require("lucide-react");
1352
+ var import_design_system8 = require("@ai-matrx/design-system");
1353
+ var import_controls5 = require("@ai-matrx/design-system/controls");
1354
+ var import_design_system9 = require("@ai-matrx/design-system");
1355
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1356
+ function chipLabel(context) {
1357
+ const kind = context.entity.label.toLowerCase();
1358
+ switch (context.status) {
1359
+ // The state has to be readable WITHOUT opening the popover — a bare name in
1360
+ // a roster reads as an ordinary row, which is the lie all over again.
1361
+ case "denied":
1362
+ return context.entity.title ? `${context.entity.title} \u2014 no access` : `A ${kind} you can't open`;
1363
+ case "deleted":
1364
+ return context.entity.title ? `${context.entity.title} \u2014 deleted` : `Deleted ${kind}`;
1365
+ case "missing":
1366
+ return `This ${kind} no longer exists`;
1367
+ case "anonymous":
1368
+ return "Sign in to see this";
1369
+ case "ok":
1370
+ return context.entity.title ?? `This ${kind} didn't load`;
1371
+ default:
1372
+ return "Couldn't check this record";
1373
+ }
1374
+ }
1375
+ function explanation2(context) {
1376
+ const kind = context.entity.label.toLowerCase();
1377
+ switch (context.status) {
1378
+ case "denied":
1379
+ return `This ${kind} exists, but it isn't shared with you. You can ask for access \u2014 the row stays in the list either way.`;
1380
+ case "deleted":
1381
+ return `This ${kind} was moved to the trash. Whoever deleted it can restore it; until then there is nothing here to open.`;
1382
+ case "missing":
1383
+ return `There is no ${kind} with this id. The reference is stale.`;
1384
+ case "anonymous":
1385
+ return "Your session ended, so we can't tell you anything about this record. Sign in and it will load.";
1386
+ case "ok":
1387
+ return `You DO have access to this ${kind} \u2014 the read failed for some other reason. Trying again should work.`;
1388
+ default:
1389
+ return "We couldn't work out why this didn't load. That's a fault on our side, not a permission problem.";
1390
+ }
1391
+ }
1392
+ function StatusIcon2({ status }) {
1393
+ const className = "h-3.5 w-3.5 shrink-0";
1394
+ switch (status) {
1395
+ case "denied":
1396
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react8.Ban, { className, "aria-hidden": true });
1397
+ case "deleted":
1398
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react8.Trash2, { className, "aria-hidden": true });
1399
+ case "missing":
1400
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react8.HelpCircle, { className, "aria-hidden": true });
1401
+ case "anonymous":
1402
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react8.LogIn, { className, "aria-hidden": true });
1403
+ case "ok":
1404
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react8.RefreshCw, { className, "aria-hidden": true });
1405
+ default:
1406
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react8.TriangleAlert, { className, "aria-hidden": true });
1407
+ }
1408
+ }
1409
+ function UnresolvedEntityRef({
1410
+ id,
1411
+ context,
1412
+ onChanged,
1413
+ repairAction,
1414
+ className
1415
+ }) {
1416
+ const [open, setOpen] = (0, import_react10.useState)(false);
1417
+ const signInHref = useAccessUi().useLoginHref();
1418
+ if (!context) {
1419
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1420
+ "span",
1421
+ {
1422
+ className: (0, import_design_system9.cn)(
1423
+ "inline-flex items-center gap-1.5 text-sm text-muted-foreground",
1424
+ className
1425
+ ),
1426
+ children: [
1427
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react8.Loader2, { className: "h-3.5 w-3.5 animate-spin", "aria-hidden": true }),
1428
+ "Checking\u2026"
1429
+ ]
1430
+ }
1431
+ );
1432
+ }
1433
+ const label = chipLabel(context);
1434
+ const tone = context.status === "error" || context.status === "missing" ? "text-amber-700 dark:text-amber-400" : "text-muted-foreground";
1435
+ const showOrg = Boolean(context.organization);
1436
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_design_system8.Popover, { open, onOpenChange: setOpen, children: [
1437
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_design_system8.PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1438
+ "button",
1439
+ {
1440
+ type: "button",
1441
+ onClick: (e) => e.stopPropagation(),
1442
+ className: (0, import_design_system9.cn)(
1443
+ "inline-flex min-w-0 items-center gap-1.5 rounded-md border border-dashed border-border px-1.5 py-0.5 text-sm italic transition-colors hover:bg-muted",
1444
+ tone,
1445
+ className
1446
+ ),
1447
+ title: "Why can't I see this?",
1448
+ children: [
1449
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StatusIcon2, { status: context.status }),
1450
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "truncate", children: label })
1451
+ ]
1452
+ }
1453
+ ) }),
1454
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1455
+ import_design_system8.PopoverContent,
1456
+ {
1457
+ sizing: "content",
1458
+ align: "start",
1459
+ className: "p-3",
1460
+ onClick: (e) => e.stopPropagation(),
1461
+ children: [
1462
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-sm font-medium text-foreground", children: context.entity.title ?? context.entity.label }),
1463
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "mt-1 text-xs text-muted-foreground", children: explanation2(context) }),
1464
+ context.owner || showOrg ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("dl", { className: "mt-2 space-y-0.5 rounded-md bg-muted/50 px-2 py-1.5 text-xs", children: [
1465
+ context.owner ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex gap-1.5", children: [
1466
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("dt", { className: "text-muted-foreground", children: "Owner" }),
1467
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("dd", { className: "min-w-0 truncate text-foreground", children: context.owner.creatorHandle ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1468
+ AccessLink,
1469
+ {
1470
+ href: `/c/${context.owner.creatorHandle}`,
1471
+ className: "underline underline-offset-2",
1472
+ children: context.owner.displayName ?? "Someone else"
1473
+ }
1474
+ ) : context.owner.displayName ?? "Someone else" })
1475
+ ] }) : null,
1476
+ showOrg && context.organization ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex gap-1.5", children: [
1477
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("dt", { className: "text-muted-foreground", children: "Organization" }),
1478
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("dd", { className: "min-w-0 truncate text-foreground", children: context.organization.viewerIsMember ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1479
+ AccessLink,
1480
+ {
1481
+ href: `/organizations/${context.organization.id}`,
1482
+ className: "underline underline-offset-2",
1483
+ children: context.organization.name ?? "An organization"
1484
+ }
1485
+ ) : context.organization.name ?? "An organization" })
1486
+ ] }) : null
1487
+ ] }) : null,
1488
+ context.status === "denied" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "mt-2", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1489
+ RequestAccessPanel,
1490
+ {
1491
+ context,
1492
+ resourceId: id,
1493
+ onChanged: () => onChanged?.()
1494
+ }
1495
+ ) }) : null,
1496
+ context.status === "anonymous" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_controls5.Button, { variant: "primary", asChild: true, className: "mt-2 w-full", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(AccessLink, { href: signInHref, children: [
1497
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react8.LogIn, { className: "mr-1.5 h-3.5 w-3.5", "aria-hidden": true }),
1498
+ "Sign in"
1499
+ ] }) }) : null,
1500
+ (context.status === "ok" || context.status === "error") && onChanged ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1501
+ import_controls5.Button,
1502
+ {
1503
+ icon: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react8.RefreshCw, { "aria-hidden": true }),
1504
+ variant: "primary",
1505
+ className: "mt-2 w-full",
1506
+ onClick: () => onChanged(),
1507
+ children: "Try again"
1508
+ }
1509
+ ) : null,
1510
+ repairAction ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "mt-2", children: repairAction }) : null
1511
+ ]
1512
+ }
1513
+ )
1514
+ ] });
1515
+ }
1516
+
1517
+ // src/access/react/ServerReadRecheck.tsx
1518
+ var import_react11 = require("react");
1519
+ var import_claims = require("@ai-matrx/auth/claims");
1520
+ var import_access15 = require("@ai-matrx/data/access");
1521
+ var import_access16 = require("@ai-matrx/data/access");
1522
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1523
+ var ROW_READERS = {
1524
+ conversation: async (supabase, id) => {
1525
+ const { data, error } = await supabase.schema("chat").from("conversation").select("id").eq("id", id).is("deleted_at", null).maybeSingle();
1526
+ return !error && Boolean(data);
1527
+ }
1528
+ };
1529
+ var refreshedOnce = /* @__PURE__ */ new Set();
1530
+ function ServerReadRecheck({
1531
+ token,
1532
+ id,
1533
+ children,
1534
+ pending
1535
+ }) {
1536
+ const router = useAccessUi().router;
1537
+ const key = `${token}:${id}`;
1538
+ const [showGate, setShowGate] = (0, import_react11.useState)(false);
1539
+ (0, import_react11.useEffect)(() => {
1540
+ let active = true;
1541
+ void (async () => {
1542
+ let signedIn = false;
1543
+ let rowReadable = false;
1544
+ try {
1545
+ const supabase = (0, import_access15.getAccessEnv)().getClient();
1546
+ const { data } = await (0, import_claims.getClaimsUser)(supabase);
1547
+ signedIn = Boolean(data.user);
1548
+ if (signedIn) rowReadable = await ROW_READERS[token](supabase, id);
1549
+ } catch (error) {
1550
+ console.error(`[ServerReadRecheck] ${key}: browser re-read failed`, error);
1551
+ }
1552
+ if (!active) return;
1553
+ const verdict = (0, import_access16.decideServerReadRecheck)({
1554
+ signedIn,
1555
+ rowReadable,
1556
+ alreadyRefreshed: refreshedOnce.has(key)
1557
+ });
1558
+ if (verdict === "refresh") {
1559
+ console.warn(
1560
+ `[ServerReadRecheck] ${key}: the server read came back empty but this browser can read it \u2014 re-rendering with the refreshed session`
1561
+ );
1562
+ refreshedOnce.add(key);
1563
+ router.refresh();
1564
+ return;
1565
+ }
1566
+ setShowGate(true);
1567
+ })();
1568
+ return () => {
1569
+ active = false;
1570
+ };
1571
+ }, [key, token, id, router]);
1572
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_jsx_runtime11.Fragment, { children: showGate ? children : pending ?? null });
1573
+ }
1574
+
1575
+ // src/access/react/GovernedActionDialog.tsx
1576
+ var import_react13 = require("react");
1577
+ var import_lucide_react9 = require("lucide-react");
1578
+ var import_controls6 = require("@ai-matrx/design-system/controls");
1579
+ var import_design_system10 = require("@ai-matrx/design-system");
1580
+ var import_access19 = require("@ai-matrx/data/access");
1581
+
1582
+ // src/access/react/useHiddenByMe.ts
1583
+ var import_react12 = require("react");
1584
+ var import_access17 = require("@ai-matrx/data/access");
1585
+ var import_access18 = require("@ai-matrx/data/access");
1586
+ var slots = /* @__PURE__ */ new Map();
1587
+ var listeners = /* @__PURE__ */ new Set();
1588
+ var version = 0;
1589
+ var watchingAuth = false;
1590
+ function emit() {
1591
+ version += 1;
1592
+ listeners.forEach((listener) => listener());
1593
+ }
1594
+ function watchAuth() {
1595
+ if (watchingAuth || typeof window === "undefined") return;
1596
+ watchingAuth = true;
1597
+ try {
1598
+ (0, import_access18.getAccessEnv)().getClient().auth.onAuthStateChange((event) => {
1599
+ if (event === "SIGNED_IN" || event === "SIGNED_OUT" || event === "USER_UPDATED") {
1600
+ slots.clear();
1601
+ emit();
1602
+ }
1603
+ });
1604
+ } catch {
1605
+ }
1606
+ }
1607
+ function slotFor(token) {
1608
+ let slot = slots.get(token);
1609
+ if (!slot) {
1610
+ slot = { items: void 0, ids: void 0, loading: null, failed: false };
1611
+ slots.set(token, slot);
1612
+ }
1613
+ return slot;
1614
+ }
1615
+ function settle(slot, items, failed) {
1616
+ slot.items = items;
1617
+ slot.ids = items.map((item) => item.id);
1618
+ slot.failed = failed;
1619
+ }
1620
+ function refreshHiddenByMe(token) {
1621
+ watchAuth();
1622
+ const slot = slotFor(token);
1623
+ const loading = (async () => {
1624
+ try {
1625
+ const { data } = await (0, import_access18.getAccessEnv)().getClient().auth.getSession();
1626
+ if (!data.session) {
1627
+ settle(slot, [], true);
1628
+ return;
1629
+ }
1630
+ settle(slot, await (0, import_access17.listHiddenItems)(token), false);
1631
+ } catch {
1632
+ settle(slot, slot.items ?? [], true);
1633
+ }
1634
+ })().finally(() => {
1635
+ slot.loading = null;
1636
+ emit();
1637
+ });
1638
+ slot.loading = loading;
1639
+ return loading;
1640
+ }
1641
+ function useHiddenSlot(token, enabled) {
1642
+ (0, import_react12.useSyncExternalStore)(
1643
+ (listener) => {
1644
+ listeners.add(listener);
1645
+ return () => listeners.delete(listener);
1646
+ },
1647
+ () => version,
1648
+ () => version
1649
+ );
1650
+ const active = enabled && Boolean(token);
1651
+ const slot = active ? slotFor(token) : void 0;
1652
+ (0, import_react12.useEffect)(() => {
1653
+ if (!active) return;
1654
+ const current = slotFor(token);
1655
+ if ((current.items === void 0 || current.failed) && !current.loading) void refreshHiddenByMe(token);
1656
+ }, [active, token]);
1657
+ return slot;
1658
+ }
1659
+ function useHiddenByMe(token, enabled = true) {
1660
+ const slot = useHiddenSlot(token, enabled);
1661
+ return {
1662
+ data: slot?.ids,
1663
+ isPending: enabled && Boolean(token) && slot?.ids === void 0
1664
+ };
1665
+ }
1666
+ function useHiddenItems(token, enabled = true) {
1667
+ const slot = useHiddenSlot(token, enabled);
1668
+ return { data: slot?.items, isPending: enabled && slot?.items === void 0 };
1669
+ }
1670
+ function useRemovalRights(token, id) {
1671
+ const [nonce, setNonce] = (0, import_react12.useState)(0);
1672
+ const key = id ? `${token}:${id}:${nonce}` : null;
1673
+ const [settled, setSettled] = (0, import_react12.useState)(null);
1674
+ (0, import_react12.useEffect)(() => {
1675
+ if (!key || !id) return;
1676
+ let active = true;
1677
+ (0, import_access17.fetchRemovalRights)(token, id).then(
1678
+ (data) => active && setSettled({ key, data }),
1679
+ (error) => active && setSettled({ key, error })
1680
+ );
1681
+ return () => {
1682
+ active = false;
1683
+ };
1684
+ }, [key, token, id]);
1685
+ const current = key && settled?.key === key ? settled : null;
1686
+ const isLoading = key !== null && current === null;
1687
+ return {
1688
+ data: current?.data,
1689
+ error: current?.error,
1690
+ isLoading,
1691
+ isPending: isLoading,
1692
+ isError: current?.error !== void 0,
1693
+ refetch: () => setNonce((n) => n + 1)
1694
+ };
1695
+ }
1696
+ function useHideForMe(token, onChanged) {
1697
+ const [busy, setBusy] = (0, import_react12.useState)(false);
1698
+ const run = async (work) => {
1699
+ setBusy(true);
1700
+ try {
1701
+ await work();
1702
+ await refreshHiddenByMe(token);
1703
+ await onChanged?.();
1704
+ } finally {
1705
+ setBusy(false);
1706
+ }
1707
+ };
1708
+ return {
1709
+ hide: {
1710
+ isPending: busy,
1711
+ mutateAsync: (args) => run(
1712
+ () => typeof args === "string" ? (0, import_access17.hideForMe)(token, args) : (0, import_access17.hideForMe)(token, args.id, args.label)
1713
+ )
1714
+ },
1715
+ unhide: {
1716
+ isPending: busy,
1717
+ mutateAsync: (id) => run(() => (0, import_access17.unhideForMe)(token, id))
1718
+ }
1719
+ };
1720
+ }
1721
+
1722
+ // src/access/react/GovernedActionDialog.tsx
1723
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1724
+ function GovernedActionDialog({
1725
+ open,
1726
+ onOpenChange,
1727
+ resourceType,
1728
+ resourceId,
1729
+ itemName,
1730
+ itemLabel = "site",
1731
+ href,
1732
+ action = "delete",
1733
+ onHidden
1734
+ }) {
1735
+ const ui = useAccessUi();
1736
+ const { toast } = ui;
1737
+ const ProTextarea = ui.Textarea;
1738
+ const currentUserId = ui.useCurrentUser().id;
1739
+ const { context, isLoading } = useAccessGate(resourceType, resourceId, {
1740
+ enabled: open
1741
+ });
1742
+ const [choice, setChoice] = (0, import_react13.useState)("delete");
1743
+ const [note, setNote] = (0, import_react13.useState)("");
1744
+ const [busy, setBusy] = (0, import_react13.useState)(false);
1745
+ const [sent, setSent] = (0, import_react13.useState)(null);
1746
+ const { hide } = useHideForMe(resourceType);
1747
+ const verb = action === "archive" ? "archive" : "delete";
1748
+ const ActionIcon = action === "archive" ? import_lucide_react9.Archive : import_lucide_react9.Trash2;
1749
+ const ownerName2 = context?.owner?.displayName ?? (context?.organization ? context.organization.name : null) ?? "the owner";
1750
+ async function hideIt() {
1751
+ setBusy(true);
1752
+ try {
1753
+ await hide.mutateAsync({ id: resourceId, label: itemName });
1754
+ toast.success(`Hidden for you. Find ${itemName} under Hidden.`);
1755
+ onHidden?.();
1756
+ onOpenChange(false);
1757
+ } catch (error) {
1758
+ toast.error(
1759
+ error instanceof Error ? error.message : "We couldn't hide that."
1760
+ );
1761
+ } finally {
1762
+ setBusy(false);
1763
+ }
1764
+ }
1765
+ async function sendRequest() {
1766
+ if (choice === "hide") return hideIt();
1767
+ setBusy(true);
1768
+ try {
1769
+ const result = choice === "delete" ? await (0, import_access19.createDeleteRequest)({
1770
+ resourceType,
1771
+ resourceId,
1772
+ message: note,
1773
+ currentUserId,
1774
+ href: href ?? null
1775
+ }) : await (0, import_access19.createAccessRequest)({
1776
+ resourceType,
1777
+ resourceId,
1778
+ level: "admin",
1779
+ message: note,
1780
+ currentUserId,
1781
+ href: href ?? null
1782
+ });
1783
+ setSent(choice);
1784
+ if (result.blindAnswer) {
1785
+ toast.success(result.blindAnswer);
1786
+ } else if (result.already) {
1787
+ toast.success("Your request is already waiting for an answer.");
1788
+ } else if (result.delivered === 0) {
1789
+ toast.warning(
1790
+ "Your request is saved, but we couldn't message them just now."
1791
+ );
1792
+ } else {
1793
+ toast.success(
1794
+ `Request sent to ${result.recipients[0]?.displayName ?? ownerName2}.`
1795
+ );
1796
+ }
1797
+ } catch (error) {
1798
+ toast.error(
1799
+ error instanceof Error ? error.message : "We couldn't send that request."
1800
+ );
1801
+ } finally {
1802
+ setBusy(false);
1803
+ }
1804
+ }
1805
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_design_system10.Dialog, { open, onOpenChange, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_design_system10.DialogContent, { className: "matrx-touch-targets sm:max-w-xl", children: [
1806
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_design_system10.DialogHeader, { children: [
1807
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "mb-2 flex h-11 w-11 items-center justify-center rounded-full bg-destructive/10", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ActionIcon, { className: "h-5 w-5 text-destructive", "aria-hidden": true }) }),
1808
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_design_system10.DialogTitle, { children: [
1809
+ "Ask ",
1810
+ ownerName2,
1811
+ " to ",
1812
+ verb,
1813
+ " this ",
1814
+ itemLabel
1815
+ ] }),
1816
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_design_system10.DialogDescription, { children: [
1817
+ "You can work on",
1818
+ " ",
1819
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "font-medium text-foreground", children: itemName }),
1820
+ ", but removing it is up to them. Ask them, or hide it just for you."
1821
+ ] })
1822
+ ] }),
1823
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-2 rounded-lg border border-border bg-muted/30 p-4 text-sm text-muted-foreground", children: [
1824
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react9.Loader2, { className: "h-4 w-4 animate-spin", "aria-hidden": true }),
1825
+ "Finding the person who can help\u2026"
1826
+ ] }) : context?.status === "error" || context?.status === "anonymous" ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "rounded-lg border border-destructive/30 bg-destructive/10 p-4 text-sm text-destructive-ink", children: [
1827
+ "We couldn't find the right person to contact. Try again after refreshing.",
1828
+ ui.renderErrorMenu({})
1829
+ ] }) : sent ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "rounded-lg border border-primary/30 bg-primary/5 p-4", children: [
1830
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("p", { className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
1831
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react9.Check, { className: "h-4 w-4 text-primary", "aria-hidden": true }),
1832
+ sent === "delete" ? action === "archive" ? "Archive requested" : "Deletion requested" : "Full access requested"
1833
+ ] }),
1834
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("p", { className: "mt-1 text-xs text-muted-foreground", children: [
1835
+ "The request is saved in Access requests and sent to ",
1836
+ ownerName2,
1837
+ " in Messages."
1838
+ ] })
1839
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1840
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "grid gap-2 sm:grid-cols-3", children: [
1841
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1842
+ RequestChoiceButton,
1843
+ {
1844
+ active: choice === "delete",
1845
+ icon: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ActionIcon, { className: "h-4 w-4", "aria-hidden": true }),
1846
+ title: `Ask them to ${verb} it`,
1847
+ detail: "They approve it from your message.",
1848
+ onClick: () => setChoice("delete")
1849
+ }
1850
+ ),
1851
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1852
+ RequestChoiceButton,
1853
+ {
1854
+ active: choice === "hide",
1855
+ icon: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react9.EyeOff, { className: "h-4 w-4", "aria-hidden": true }),
1856
+ title: "Hide it for me",
1857
+ detail: "Only you stop seeing it; undo under Hidden.",
1858
+ onClick: () => setChoice("hide")
1859
+ }
1860
+ ),
1861
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1862
+ RequestChoiceButton,
1863
+ {
1864
+ active: choice === "full",
1865
+ icon: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react9.ShieldCheck, { className: "h-4 w-4", "aria-hidden": true }),
1866
+ title: "Request full access",
1867
+ detail: `Then you can ${verb} it yourself.`,
1868
+ onClick: () => setChoice("full")
1869
+ }
1870
+ )
1871
+ ] }),
1872
+ choice === "hide" ? null : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1873
+ ProTextarea,
1874
+ {
1875
+ className: "min-h-20 text-base md:text-sm",
1876
+ value: note,
1877
+ maxLength: 500,
1878
+ placeholder: "Add a note for the owner (optional)",
1879
+ onChange: (event) => setNote(event.target.value)
1880
+ }
1881
+ )
1882
+ ] }),
1883
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_design_system10.DialogFooter, { children: [
1884
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_controls6.Button, { variant: "outline", onClick: () => onOpenChange(false), children: sent ? "Done" : "Cancel" }),
1885
+ !sent && context?.status !== "error" && context?.status !== "anonymous" ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1886
+ import_controls6.Button,
1887
+ {
1888
+ icon: busy ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react9.Loader2, { className: "animate-spin", "aria-hidden": true }) : choice === "hide" ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react9.EyeOff, { "aria-hidden": true }) : choice === "delete" ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react9.Send, { "aria-hidden": true }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react9.KeyRound, { "aria-hidden": true }),
1889
+ variant: "primary",
1890
+ disabled: busy || isLoading,
1891
+ onClick: () => void sendRequest(),
1892
+ children: choice === "hide" ? "Hide for me" : "Send request"
1893
+ }
1894
+ ) : null
1895
+ ] })
1896
+ ] }) });
1897
+ }
1898
+ function RequestChoiceButton({
1899
+ active,
1900
+ icon,
1901
+ title,
1902
+ detail,
1903
+ onClick
1904
+ }) {
1905
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1906
+ "button",
1907
+ {
1908
+ type: "button",
1909
+ "aria-pressed": active,
1910
+ onClick,
1911
+ className: `rounded-lg border p-3 text-left transition-colors ${active ? "border-primary bg-primary/5" : "border-border hover:bg-muted/40"}`,
1912
+ children: [
1913
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
1914
+ icon,
1915
+ title
1916
+ ] }),
1917
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "mt-1 block text-xs text-muted-foreground", children: detail })
1918
+ ]
1919
+ }
1920
+ );
1921
+ }
1922
+
1923
+ // src/access/react/HiddenByMeButton.tsx
1924
+ var import_react14 = require("react");
1925
+ var import_lucide_react10 = require("lucide-react");
1926
+ var import_controls7 = require("@ai-matrx/design-system/controls");
1927
+ var import_design_system11 = require("@ai-matrx/design-system");
1928
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1929
+ function HiddenByMeButton({
1930
+ token,
1931
+ pluralLabel
1932
+ }) {
1933
+ const ui = useAccessUi();
1934
+ const { toast } = ui;
1935
+ const [open, setOpen] = (0, import_react14.useState)(false);
1936
+ const ids = useHiddenByMe(token);
1937
+ const items = useHiddenItems(token, open);
1938
+ const { unhide } = useHideForMe(token);
1939
+ const count = ids.data?.length ?? 0;
1940
+ if (count === 0 && !open) return null;
1941
+ const showAgain = async (id, name) => {
1942
+ try {
1943
+ await unhide.mutateAsync(id);
1944
+ toast.success(`${name} is back in your list`);
1945
+ if (count <= 1) setOpen(false);
1946
+ } catch (error) {
1947
+ toast.error(error instanceof Error ? error.message : "We couldn't show that again.");
1948
+ }
1949
+ };
1950
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
1951
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1952
+ import_controls7.Button,
1953
+ {
1954
+ type: "button",
1955
+ variant: "quiet",
1956
+ icon: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react10.EyeOff, {}),
1957
+ title: `${pluralLabel} you hid`,
1958
+ onClick: () => setOpen(true),
1959
+ children: `Hidden ${count}`
1960
+ }
1961
+ ),
1962
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_design_system11.Dialog, { open, onOpenChange: setOpen, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_design_system11.DialogContent, { className: "matrx-touch-targets sm:max-w-lg", children: [
1963
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_design_system11.DialogHeader, { children: [
1964
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_design_system11.DialogTitle, { children: "Hidden by me" }),
1965
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_design_system11.DialogDescription, { children: [
1966
+ "Only you stopped seeing these ",
1967
+ pluralLabel,
1968
+ "."
1969
+ ] })
1970
+ ] }),
1971
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("ul", { className: "flex max-h-[60dvh] flex-col divide-y divide-border overflow-y-auto", children: (items.data ?? []).map((item) => {
1972
+ const name = item.label ?? "Untitled";
1973
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("li", { className: "flex items-center gap-2 py-2", children: [
1974
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "min-w-0 flex-1", children: ui.renderEntityRef({ token: item.token, id: item.id, name }) }),
1975
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1976
+ import_controls7.Button,
1977
+ {
1978
+ type: "button",
1979
+ variant: "outline",
1980
+ icon: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react10.Eye, {}),
1981
+ disabled: unhide.isPending,
1982
+ onClick: () => void showAgain(item.id, name),
1983
+ children: "Show again"
1984
+ }
1985
+ )
1986
+ ] }, item.id);
1987
+ }) })
1988
+ ] }) })
1989
+ ] });
1990
+ }
1991
+
1992
+ // src/access/react/HiddenFilter.tsx
1993
+ var import_lucide_react11 = require("lucide-react");
1994
+ var import_controls8 = require("@ai-matrx/design-system/controls");
1995
+ var import_jsx_runtime14 = require("react/jsx-runtime");
1996
+ function HiddenFilter({
1997
+ count,
1998
+ value,
1999
+ onValueChange
2000
+ }) {
2001
+ if (count === 0 && value === "visible") return null;
2002
+ const showing = value === "hidden";
2003
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2004
+ import_controls8.Button,
2005
+ {
2006
+ type: "button",
2007
+ variant: showing ? "outline" : "quiet",
2008
+ icon: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react11.EyeOff, {}),
2009
+ "aria-pressed": showing,
2010
+ title: showing ? "Back to the list" : "Items you hid",
2011
+ onClick: () => onValueChange(showing ? "visible" : "hidden"),
2012
+ children: `Hidden ${count}`
2013
+ }
2014
+ );
2015
+ }
2016
+
2017
+ // src/access/react/ResourceActionRequestButtons.tsx
2018
+ var import_react15 = require("react");
2019
+ var import_lucide_react12 = require("lucide-react");
2020
+ var import_controls9 = require("@ai-matrx/design-system/controls");
2021
+ var import_access20 = require("@ai-matrx/data/access");
2022
+ var import_jsx_runtime15 = require("react/jsx-runtime");
2023
+ function ResourceActionRequestButtons({
2024
+ requestId,
2025
+ actionKey,
2026
+ href,
2027
+ itemName,
2028
+ isOwn = false,
2029
+ compact = false,
2030
+ onDone
2031
+ }) {
2032
+ const ui = useAccessUi();
2033
+ const { toast, confirm, getResourceActionRequestAction } = ui;
2034
+ const currentUserId = ui.useCurrentUser().id;
2035
+ const [busy, setBusy] = (0, import_react15.useState)(false);
2036
+ const [done, setDone] = (0, import_react15.useState)(null);
2037
+ const action = getResourceActionRequestAction(actionKey);
2038
+ const safeHref = href?.startsWith("/") && !href.startsWith("//") ? href : null;
2039
+ const size = compact ? "h-7 px-2 text-[11px]" : "h-11 sm:h-8";
2040
+ if (isOwn) {
2041
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 text-[11px] text-inherit", children: [
2042
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react12.Trash2, { className: "h-3 w-3", "aria-hidden": true }),
2043
+ "Deletion requested"
2044
+ ] });
2045
+ }
2046
+ if (done) {
2047
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 text-[11px] text-muted-foreground", children: [
2048
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react12.Check, { className: "h-3 w-3", "aria-hidden": true }),
2049
+ done === "granted" ? "Completed" : done === "reported" ? "Reported" : "Declined"
2050
+ ] });
2051
+ }
2052
+ async function completeAction() {
2053
+ if (!action) {
2054
+ toast.error("This request uses an action the app does not recognize.");
2055
+ return;
2056
+ }
2057
+ const ok = await confirm({
2058
+ title: action.confirmTitle(itemName),
2059
+ description: action.confirmDescription,
2060
+ confirmLabel: action.label,
2061
+ variant: "destructive"
2062
+ });
2063
+ if (!ok) return;
2064
+ setBusy(true);
2065
+ try {
2066
+ const request = await (0, import_access20.getResourceActionRequestForDecision)(requestId);
2067
+ const durableAction = getResourceActionRequestAction(
2068
+ request.resourceAction.actionKey
2069
+ );
2070
+ if (!durableAction) {
2071
+ throw new Error(
2072
+ "This request uses an action the app does not recognize."
2073
+ );
2074
+ }
2075
+ await durableAction.execute({
2076
+ resourceType: request.resourceType,
2077
+ resourceId: request.resourceId
2078
+ });
2079
+ const result = await (0, import_access20.decideAccessRequest)({
2080
+ requestId,
2081
+ decision: "complete",
2082
+ currentUserId
2083
+ });
2084
+ setDone(result.status);
2085
+ onDone?.(result.status);
2086
+ toast.success(durableAction.completedLabel);
2087
+ } catch (error) {
2088
+ toast.error(
2089
+ error instanceof Error ? error.message : "We couldn't complete that action."
2090
+ );
2091
+ } finally {
2092
+ setBusy(false);
2093
+ }
2094
+ }
2095
+ async function grantFullAccess() {
2096
+ setBusy(true);
2097
+ try {
2098
+ const result = await (0, import_access20.decideAccessRequest)({
2099
+ requestId,
2100
+ decision: "grant",
2101
+ level: "admin",
2102
+ currentUserId
2103
+ });
2104
+ setDone(result.status);
2105
+ onDone?.(result.status);
2106
+ toast.success("Full access granted.");
2107
+ } catch (error) {
2108
+ toast.error(
2109
+ error instanceof Error ? error.message : "We couldn't grant access."
2110
+ );
2111
+ } finally {
2112
+ setBusy(false);
2113
+ }
2114
+ }
2115
+ async function decline() {
2116
+ setBusy(true);
2117
+ try {
2118
+ const result = await (0, import_access20.decideAccessRequest)({
2119
+ requestId,
2120
+ decision: "decline",
2121
+ currentUserId
2122
+ });
2123
+ setDone(result.status);
2124
+ onDone?.(result.status);
2125
+ toast.success("Request declined.");
2126
+ } catch (error) {
2127
+ toast.error(
2128
+ error instanceof Error ? error.message : "We couldn't decline that."
2129
+ );
2130
+ } finally {
2131
+ setBusy(false);
2132
+ }
2133
+ }
2134
+ async function report() {
2135
+ const ok = await confirm({
2136
+ title: "Report this request?",
2137
+ description: "This ends the conversation for good. They won't be able to ask about this item again.",
2138
+ confirmLabel: "Report",
2139
+ variant: "destructive"
2140
+ });
2141
+ if (!ok) return;
2142
+ setBusy(true);
2143
+ try {
2144
+ await (0, import_access20.reportAccessRequest)(requestId);
2145
+ setDone("reported");
2146
+ onDone?.("reported");
2147
+ toast.success("Request reported.");
2148
+ } catch (error) {
2149
+ toast.error(
2150
+ error instanceof Error ? error.message : "We couldn't report that."
2151
+ );
2152
+ } finally {
2153
+ setBusy(false);
2154
+ }
2155
+ }
2156
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex flex-wrap gap-2", children: [
2157
+ action ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2158
+ import_controls9.Button,
2159
+ {
2160
+ icon: busy ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react12.Loader2, { className: "animate-spin", "aria-hidden": true }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react12.Trash2, { "aria-hidden": true }),
2161
+ variant: "primary",
2162
+ className: size,
2163
+ disabled: busy,
2164
+ onClick: () => void completeAction(),
2165
+ children: action.label
2166
+ }
2167
+ ) : null,
2168
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2169
+ import_controls9.Button,
2170
+ {
2171
+ icon: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react12.KeyRound, { "aria-hidden": true }),
2172
+ className: size,
2173
+ variant: "outline",
2174
+ disabled: busy,
2175
+ onClick: () => void grantFullAccess(),
2176
+ children: "Give full access"
2177
+ }
2178
+ ),
2179
+ safeHref ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_controls9.Button, { asChild: true, className: size, variant: "outline", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(AccessLink, { href: safeHref, target: "_blank", rel: "noopener noreferrer", children: [
2180
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react12.ExternalLink, { className: "h-3.5 w-3.5", "aria-hidden": true }),
2181
+ "Open item"
2182
+ ] }) }) : null,
2183
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2184
+ import_controls9.Button,
2185
+ {
2186
+ icon: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react12.CircleSlash, { "aria-hidden": true }),
2187
+ className: size,
2188
+ variant: "quiet",
2189
+ disabled: busy,
2190
+ onClick: () => void decline(),
2191
+ children: "Decline"
2192
+ }
2193
+ ),
2194
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2195
+ import_controls9.Button,
2196
+ {
2197
+ icon: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react12.Flag, { "aria-hidden": true }),
2198
+ className: size,
2199
+ variant: "quiet",
2200
+ disabled: busy,
2201
+ onClick: () => void report(),
2202
+ children: "Report"
2203
+ }
2204
+ )
2205
+ ] });
2206
+ }
2207
+
2208
+ // src/access/react/SettingRequestActionButtons.tsx
2209
+ var import_react16 = require("react");
2210
+ var import_lucide_react13 = require("lucide-react");
2211
+ var import_controls10 = require("@ai-matrx/design-system/controls");
2212
+ var import_access21 = require("@ai-matrx/data/access");
2213
+ var import_jsx_runtime16 = require("react/jsx-runtime");
2214
+ function SettingRequestActionButtons({
2215
+ requestId,
2216
+ href,
2217
+ actionKey,
2218
+ isOwn = false,
2219
+ compact = false,
2220
+ onDone
2221
+ }) {
2222
+ const ui = useAccessUi();
2223
+ const toast = ui.toast;
2224
+ const getSettingRequestAction = ui.getSettingRequestAction;
2225
+ const [busy, setBusy] = (0, import_react16.useState)(false);
2226
+ const [done, setDone] = (0, import_react16.useState)(null);
2227
+ const action = getSettingRequestAction(actionKey);
2228
+ const safeHref = href.startsWith("/") && !href.startsWith("//") ? href : null;
2229
+ if (isOwn) {
2230
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 text-[11px] text-inherit", children: [
2231
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react13.Settings, { className: "h-3 w-3", "aria-hidden": true }),
2232
+ "Setting change requested"
2233
+ ] });
2234
+ }
2235
+ if (done) {
2236
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 text-[11px] text-muted-foreground", children: [
2237
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react13.Check, { className: "h-3 w-3", "aria-hidden": true }),
2238
+ done === "granted" ? "Change completed" : "Declined"
2239
+ ] });
2240
+ }
2241
+ async function apply() {
2242
+ if (!action) {
2243
+ toast.error("This request uses an action the app does not recognize.");
2244
+ return;
2245
+ }
2246
+ setBusy(true);
2247
+ try {
2248
+ const request = await (0, import_access21.getSettingAccessRequestForDecision)(requestId);
2249
+ const durableAction = getSettingRequestAction(
2250
+ request.settingRequest.actionKey
2251
+ );
2252
+ if (!durableAction) {
2253
+ throw new Error(
2254
+ "This request uses an action the app does not recognize."
2255
+ );
2256
+ }
2257
+ await durableAction.execute(request.settingRequest.actionPayload, {
2258
+ organizationId: request.resourceId
2259
+ });
2260
+ const result = await (0, import_access21.decideSettingAccessRequest)({
2261
+ requestId,
2262
+ decision: "complete"
2263
+ });
2264
+ setDone(result.status);
2265
+ onDone?.(result.status);
2266
+ toast.success(durableAction.completedLabel);
2267
+ } catch (error) {
2268
+ toast.error(
2269
+ error instanceof Error ? error.message : "We couldn't make that change."
2270
+ );
2271
+ } finally {
2272
+ setBusy(false);
2273
+ }
2274
+ }
2275
+ async function decline() {
2276
+ setBusy(true);
2277
+ try {
2278
+ const result = await (0, import_access21.decideSettingAccessRequest)({
2279
+ requestId,
2280
+ decision: "decline"
2281
+ });
2282
+ setDone(result.status);
2283
+ onDone?.(result.status);
2284
+ toast.success("Request declined.");
2285
+ } catch (error) {
2286
+ toast.error(
2287
+ error instanceof Error ? error.message : "We couldn't decline that."
2288
+ );
2289
+ } finally {
2290
+ setBusy(false);
2291
+ }
2292
+ }
2293
+ const size = compact ? "h-7 px-2 text-[11px]" : "h-11 sm:h-8";
2294
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex flex-wrap gap-2", children: [
2295
+ action ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2296
+ import_controls10.Button,
2297
+ {
2298
+ icon: busy ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react13.Loader2, { className: "animate-spin", "aria-hidden": true }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react13.Check, { "aria-hidden": true }),
2299
+ variant: "primary",
2300
+ className: size,
2301
+ disabled: busy,
2302
+ onClick: () => void apply(),
2303
+ children: action.label
2304
+ }
2305
+ ) : null,
2306
+ safeHref ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_controls10.Button, { asChild: true, className: size, variant: "outline", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(AccessLink, { href: safeHref, target: "_blank", rel: "noopener noreferrer", children: [
2307
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react13.ExternalLink, { className: "h-3.5 w-3.5", "aria-hidden": true }),
2308
+ "Open setting"
2309
+ ] }) }) : null,
2310
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2311
+ import_controls10.Button,
2312
+ {
2313
+ icon: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react13.CircleSlash, { "aria-hidden": true }),
2314
+ className: size,
2315
+ variant: "quiet",
2316
+ disabled: busy,
2317
+ onClick: () => void decline(),
2318
+ children: "Decline"
2319
+ }
2320
+ )
2321
+ ] });
2322
+ }
2323
+
2324
+ // src/access/react/AccessRequestsSurface.tsx
2325
+ var import_react17 = require("react");
2326
+ var import_lucide_react14 = require("lucide-react");
2327
+ var import_design_system12 = require("@ai-matrx/design-system");
2328
+ var import_controls11 = require("@ai-matrx/design-system/controls");
2329
+ var import_buttons = require("@ai-matrx/design-system/tap-target/buttons");
2330
+ var import_format3 = require("@ai-matrx/kit/format");
2331
+ var import_access22 = require("@ai-matrx/data/access");
2332
+ var import_jsx_runtime17 = require("react/jsx-runtime");
2333
+ var ACCESS_REQUESTS_HREF = "/user-settings/access-requests";
2334
+ var STATUS_LABEL = {
2335
+ pending: "Pending",
2336
+ granted: "Granted",
2337
+ declined: "Declined",
2338
+ withdrawn: "Withdrawn",
2339
+ reported: "Reported"
2340
+ };
2341
+ var STATUS_TONE = {
2342
+ pending: "bg-muted text-muted-foreground",
2343
+ granted: "bg-primary/10 text-primary-ink",
2344
+ declined: "bg-muted text-muted-foreground",
2345
+ withdrawn: "bg-muted text-muted-foreground",
2346
+ reported: "bg-destructive/10 text-destructive-ink"
2347
+ };
2348
+ function statusLabel(row) {
2349
+ return row.requestKind === "resource_action" && row.status === "granted" ? "Completed" : STATUS_LABEL[row.status];
2350
+ }
2351
+ function boxFromParam(value) {
2352
+ return value === "sent" ? "sent" : "inbox";
2353
+ }
2354
+ async function fetchBoxes() {
2355
+ try {
2356
+ const [inbox, sent] = await Promise.all([
2357
+ (0, import_access22.listAccessRequests)("inbox"),
2358
+ (0, import_access22.listAccessRequests)("sent")
2359
+ ]);
2360
+ return { rows: { inbox, sent }, error: null };
2361
+ } catch (e) {
2362
+ return {
2363
+ rows: { inbox: [], sent: [] },
2364
+ error: e instanceof Error ? e.message : "We couldn't load your access requests."
2365
+ };
2366
+ }
2367
+ }
2368
+ function EntityCell({ row }) {
2369
+ const ui = useAccessUi();
2370
+ const label = row.entityLabel ?? "Item";
2371
+ if (row.requestKind === "setting" && row.settingRequest) {
2372
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "flex min-w-0 items-center gap-1.5 text-sm", children: [
2373
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "shrink-0 text-xs uppercase tracking-wide text-muted-foreground", children: "Setting" }),
2374
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2375
+ AccessLink,
2376
+ {
2377
+ href: row.settingRequest.href,
2378
+ className: "min-w-0 truncate font-medium text-primary hover:underline",
2379
+ children: row.settingRequest.settingLabel
2380
+ }
2381
+ )
2382
+ ] });
2383
+ }
2384
+ if (!row.entityTitle) {
2385
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "text-sm text-muted-foreground", children: [
2386
+ "Untitled ",
2387
+ label.toLowerCase()
2388
+ ] });
2389
+ }
2390
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "flex min-w-0 items-center gap-1.5 text-sm", children: [
2391
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "shrink-0 text-xs uppercase tracking-wide text-muted-foreground", children: label }),
2392
+ ui.renderEntityRef({
2393
+ token: row.resourceType,
2394
+ id: row.resourceId,
2395
+ name: row.entityTitle,
2396
+ showIcon: false,
2397
+ className: "min-w-0 font-medium"
2398
+ })
2399
+ ] });
2400
+ }
2401
+ function RowShell({ children }) {
2402
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("li", { className: "rounded-lg border border-border bg-card p-3 sm:p-4", children });
2403
+ }
2404
+ function LoadingRows() {
2405
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("ul", { className: "space-y-2", children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(RowShell, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-start gap-3", children: [
2406
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_design_system12.Skeleton, { className: "h-8 w-8 shrink-0 rounded-full" }),
2407
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "min-w-0 flex-1 space-y-2", children: [
2408
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_design_system12.Skeleton, { className: "h-4 w-1/3" }),
2409
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_design_system12.Skeleton, { className: "h-3 w-2/3" })
2410
+ ] })
2411
+ ] }) }, i)) });
2412
+ }
2413
+ function EmptyState({ box }) {
2414
+ const Icon = box === "inbox" ? import_lucide_react14.Inbox : import_lucide_react14.Send;
2415
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex flex-col items-center gap-2 rounded-lg border border-dashed border-border px-6 py-12 text-center", children: [
2416
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Icon, { className: "h-6 w-6 text-muted-foreground", "aria-hidden": true }),
2417
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { className: "text-sm font-medium text-foreground", children: box === "inbox" ? "Nothing waiting on you" : "You haven't asked for anything" }),
2418
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { className: "max-w-sm text-xs text-muted-foreground", children: box === "inbox" ? "When someone asks to open something you own, it lands here \u2014 and in your messages." : "When you open something you don't have access to yet, you can ask the owner from there. Your request shows up here." })
2419
+ ] });
2420
+ }
2421
+ function AccessRequestsSurface() {
2422
+ const ui = useAccessUi();
2423
+ const { toast, confirm } = ui;
2424
+ const NAV_ITEM_SELECTED = ui.navItemClasses.selected;
2425
+ const NAV_ITEM_UNSELECTED = ui.navItemClasses.unselected;
2426
+ const [box, setBox] = (0, import_react17.useState)(() => boxFromParam(ui.searchParam("box")));
2427
+ const currentUserId = ui.useCurrentUser().id;
2428
+ const [rows, setRows] = (0, import_react17.useState)({
2429
+ inbox: null,
2430
+ sent: null
2431
+ });
2432
+ const [error, setError] = (0, import_react17.useState)(null);
2433
+ const [busyId, setBusyId] = (0, import_react17.useState)(null);
2434
+ const [refreshing, setRefreshing] = (0, import_react17.useState)(false);
2435
+ const refresh = (0, import_react17.useCallback)(async () => {
2436
+ setRefreshing(true);
2437
+ const result = await fetchBoxes();
2438
+ setRows(result.rows);
2439
+ setError(result.error);
2440
+ setRefreshing(false);
2441
+ }, []);
2442
+ (0, import_react17.useEffect)(() => {
2443
+ let alive = true;
2444
+ void (async () => {
2445
+ const result = await fetchBoxes();
2446
+ if (!alive) return;
2447
+ setRows(result.rows);
2448
+ setError(result.error);
2449
+ })();
2450
+ return () => {
2451
+ alive = false;
2452
+ };
2453
+ }, []);
2454
+ const current = rows[box];
2455
+ const inboxCount = rows.inbox?.length ?? 0;
2456
+ const sentPending = rows.sent?.filter((r) => r.status === "pending").length ?? 0;
2457
+ async function run(id, work, done) {
2458
+ setBusyId(id);
2459
+ try {
2460
+ await work();
2461
+ toast.success(done);
2462
+ await refresh();
2463
+ } catch (e) {
2464
+ toast.error(e instanceof Error ? e.message : "We couldn't do that.");
2465
+ } finally {
2466
+ setBusyId(null);
2467
+ }
2468
+ }
2469
+ function decide(row, level) {
2470
+ void run(
2471
+ row.id,
2472
+ async () => {
2473
+ await (0, import_access22.decideAccessRequest)({
2474
+ requestId: row.id,
2475
+ decision: "grant",
2476
+ level,
2477
+ currentUserId
2478
+ });
2479
+ },
2480
+ `Access granted${row.entityTitle ? ` to ${row.entityTitle}` : ""}.`
2481
+ );
2482
+ }
2483
+ async function decline(row) {
2484
+ const ok = await confirm({
2485
+ title: "Decline this request?",
2486
+ description: "They'll be told you said no. They can ask again later if something changes.",
2487
+ confirmLabel: "Decline"
2488
+ });
2489
+ if (!ok) return;
2490
+ void run(
2491
+ row.id,
2492
+ async () => {
2493
+ await (0, import_access22.decideAccessRequest)({
2494
+ requestId: row.id,
2495
+ decision: "decline",
2496
+ currentUserId
2497
+ });
2498
+ },
2499
+ "Request declined."
2500
+ );
2501
+ }
2502
+ async function report(row) {
2503
+ const ok = await confirm({
2504
+ title: "Report this request?",
2505
+ description: "This ends the conversation for good \u2014 they won't be able to ask about this again.",
2506
+ confirmLabel: "Report",
2507
+ variant: "destructive"
2508
+ });
2509
+ if (!ok) return;
2510
+ void run(
2511
+ row.id,
2512
+ async () => {
2513
+ await (0, import_access22.reportAccessRequest)(row.id);
2514
+ },
2515
+ "Reported. They can't ask about this again."
2516
+ );
2517
+ }
2518
+ async function withdraw(row) {
2519
+ const ok = await confirm({
2520
+ title: "Withdraw your request?",
2521
+ description: "The owner will no longer see it waiting for an answer.",
2522
+ confirmLabel: "Withdraw"
2523
+ });
2524
+ if (!ok) return;
2525
+ void run(
2526
+ row.id,
2527
+ async () => {
2528
+ await (0, import_access22.withdrawAccessRequest)(row.id);
2529
+ },
2530
+ "Request withdrawn."
2531
+ );
2532
+ }
2533
+ const tab = (target, label, count) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
2534
+ "button",
2535
+ {
2536
+ type: "button",
2537
+ onClick: () => setBox(target),
2538
+ "aria-current": box === target ? "page" : void 0,
2539
+ className: (0, import_design_system12.cn)(
2540
+ "inline-flex items-center gap-1.5 whitespace-nowrap rounded-full px-3 py-1 text-xs transition-colors",
2541
+ box === target ? NAV_ITEM_SELECTED : NAV_ITEM_UNSELECTED
2542
+ ),
2543
+ children: [
2544
+ target === "inbox" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react14.Inbox, { className: "h-3.5 w-3.5", "aria-hidden": true }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react14.Send, { className: "h-3.5 w-3.5", "aria-hidden": true }),
2545
+ label,
2546
+ count > 0 && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_design_system12.Badge, { className: "pointer-events-none h-4 min-w-[16px] border-0 bg-primary px-1 text-[9px] font-semibold text-primary-foreground hover:bg-primary", children: count > 99 ? "99+" : count })
2547
+ ]
2548
+ },
2549
+ target
2550
+ );
2551
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "mx-auto w-full max-w-3xl", children: [
2552
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "mb-3 flex items-center justify-between gap-2", children: [
2553
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "hidden items-center gap-1.5 text-sm font-medium sm:inline-flex", children: [
2554
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react14.KeyRound, { className: "h-4 w-4", "aria-hidden": true }),
2555
+ "Access requests"
2556
+ ] }),
2557
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "ml-auto flex shrink-0 items-center gap-2", children: [
2558
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "matrx-glass-thin-border inline-flex items-center gap-1 rounded-full p-1", children: [
2559
+ tab("inbox", "To me", inboxCount),
2560
+ tab("sent", "I sent", sentPending)
2561
+ ] }),
2562
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2563
+ import_buttons.RefreshCwTapButton,
2564
+ {
2565
+ ariaLabel: "Refresh access requests",
2566
+ disabled: refreshing,
2567
+ onClick: () => void refresh()
2568
+ }
2569
+ )
2570
+ ] })
2571
+ ] }),
2572
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { children: [
2573
+ error && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "mb-3 rounded-lg border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive-ink", children: [
2574
+ error,
2575
+ ui.renderErrorMenu({ error })
2576
+ ] }),
2577
+ error ? null : current === null ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(LoadingRows, {}) : current.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(EmptyState, { box }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("ul", { className: "space-y-2", children: current.map((row) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(RowShell, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex min-w-0 flex-col gap-3", children: [
2578
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex min-w-0 flex-wrap items-start justify-between gap-2", children: [
2579
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex min-w-0 items-start gap-3", children: [
2580
+ box === "inbox" && ui.renderUserIdentity({
2581
+ user: {
2582
+ id: row.requester?.userId,
2583
+ displayName: row.requester?.displayName,
2584
+ avatarUrl: row.requester?.avatarUrl
2585
+ },
2586
+ size: "sm",
2587
+ subtitle: false,
2588
+ avatarOnly: true
2589
+ }),
2590
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "min-w-0 space-y-1", children: [
2591
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("p", { className: "text-sm text-foreground", children: [
2592
+ box === "inbox" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "font-medium", children: row.requester?.displayName ?? "Someone" }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "font-medium", children: "You" }),
2593
+ " ",
2594
+ row.requestKind === "setting" ? "asked for a setting change" : row.requestKind === "resource_action" ? "asked you to delete" : `asked to ${row.requestedLevel === "admin" ? "give full access to" : row.requestedLevel === "editor" ? "edit" : "view"}`
2595
+ ] }),
2596
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(EntityCell, { row })
2597
+ ] })
2598
+ ] }),
2599
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex shrink-0 items-center gap-2", children: [
2600
+ box === "sent" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2601
+ "span",
2602
+ {
2603
+ className: (0, import_design_system12.cn)(
2604
+ "rounded-full px-2 py-0.5 text-[11px] font-medium",
2605
+ STATUS_TONE[row.status]
2606
+ ),
2607
+ children: statusLabel(row)
2608
+ }
2609
+ ),
2610
+ row.createdAt && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2611
+ "span",
2612
+ {
2613
+ className: "text-xs tabular-nums text-muted-foreground",
2614
+ title: new Date(row.createdAt).toLocaleString(),
2615
+ children: (0, import_format3.formatRelativeTime)(row.createdAt)
2616
+ }
2617
+ )
2618
+ ] })
2619
+ ] }),
2620
+ row.message && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { className: "whitespace-pre-line rounded-md bg-muted px-3 py-2 text-sm text-muted-foreground [overflow-wrap:anywhere]", children: row.message }),
2621
+ box === "sent" && row.decisionNote && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
2622
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "font-medium text-foreground", children: "Their note:" }),
2623
+ " ",
2624
+ row.decisionNote
2625
+ ] }),
2626
+ box === "inbox" && row.requestKind === "resource_action" && row.resourceAction ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2627
+ ResourceActionRequestButtons,
2628
+ {
2629
+ requestId: row.id,
2630
+ actionKey: row.resourceAction.actionKey,
2631
+ itemName: row.entityTitle ?? row.entityLabel ?? "this item",
2632
+ onDone: () => void refresh()
2633
+ }
2634
+ ) : box === "inbox" && row.requestKind === "setting" && row.settingRequest ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2635
+ SettingRequestActionButtons,
2636
+ {
2637
+ requestId: row.id,
2638
+ href: row.settingRequest.href,
2639
+ actionKey: row.settingRequest.actionKey,
2640
+ onDone: () => void refresh()
2641
+ }
2642
+ ) : box === "inbox" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex flex-wrap gap-2", children: [
2643
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2644
+ import_controls11.Button,
2645
+ {
2646
+ icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react14.CircleCheck, { "aria-hidden": true }),
2647
+ variant: "primary",
2648
+ disabled: busyId === row.id,
2649
+ onClick: () => decide(row, "viewer"),
2650
+ children: "Let them view"
2651
+ }
2652
+ ),
2653
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2654
+ import_controls11.Button,
2655
+ {
2656
+ icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react14.PenLine, { "aria-hidden": true }),
2657
+ variant: "outline",
2658
+ disabled: busyId === row.id,
2659
+ onClick: () => decide(row, "editor"),
2660
+ children: "Let them edit"
2661
+ }
2662
+ ),
2663
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2664
+ import_controls11.Button,
2665
+ {
2666
+ icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react14.KeyRound, { "aria-hidden": true }),
2667
+ variant: "outline",
2668
+ disabled: busyId === row.id,
2669
+ onClick: () => decide(row, "admin"),
2670
+ children: "Give full access"
2671
+ }
2672
+ ),
2673
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2674
+ import_controls11.Button,
2675
+ {
2676
+ icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react14.CircleSlash, { "aria-hidden": true }),
2677
+ variant: "outline",
2678
+ disabled: busyId === row.id,
2679
+ onClick: () => void decline(row),
2680
+ children: "Decline"
2681
+ }
2682
+ ),
2683
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2684
+ import_controls11.Button,
2685
+ {
2686
+ icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react14.Flag, { "aria-hidden": true }),
2687
+ variant: "quiet",
2688
+ disabled: busyId === row.id,
2689
+ onClick: () => void report(row),
2690
+ children: "Report"
2691
+ }
2692
+ )
2693
+ ] }) : row.status === "pending" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex flex-wrap gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2694
+ import_controls11.Button,
2695
+ {
2696
+ icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react14.Undo2, { "aria-hidden": true }),
2697
+ variant: "outline",
2698
+ disabled: busyId === row.id,
2699
+ onClick: () => void withdraw(row),
2700
+ children: "Withdraw"
2701
+ }
2702
+ ) })
2703
+ ] }) }, row.id)) })
2704
+ ] })
2705
+ ] });
2706
+ }
2707
+
2708
+ // src/access/react/useAccessStates.ts
2709
+ var import_react18 = require("react");
2710
+ var import_access23 = require("@ai-matrx/data/access");
2711
+ var CACHE = /* @__PURE__ */ new Map();
2712
+ var INFLIGHT = /* @__PURE__ */ new Map();
2713
+ function cacheKey(token, id) {
2714
+ return `${token}:${id}`;
2715
+ }
2716
+ function resolveOne(token, id) {
2717
+ const key = cacheKey(token, id);
2718
+ const cached = CACHE.get(key);
2719
+ if (cached) return Promise.resolve(cached);
2720
+ const existing = INFLIGHT.get(key);
2721
+ if (existing) return existing;
2722
+ const promise = (0, import_access23.fetchAccessDeniedContext)(token, id).then((context) => {
2723
+ INFLIGHT.delete(key);
2724
+ if (context.status !== "error") CACHE.set(key, context);
2725
+ return context;
2726
+ });
2727
+ INFLIGHT.set(key, promise);
2728
+ return promise;
2729
+ }
2730
+ function useAccessStates(token, ids) {
2731
+ const [nonce, setNonce] = (0, import_react18.useState)(0);
2732
+ const [resolved, setResolved] = (0, import_react18.useState)(() => /* @__PURE__ */ new Map());
2733
+ const idKey = (0, import_react18.useMemo)(() => Array.from(new Set(ids)).sort().join(","), [ids]);
2734
+ (0, import_react18.useEffect)(() => {
2735
+ const wanted2 = idKey ? idKey.split(",") : [];
2736
+ if (wanted2.length === 0) return;
2737
+ let active = true;
2738
+ void Promise.all(
2739
+ wanted2.map(
2740
+ (id) => resolveOne(token, id).then(
2741
+ (context) => [id, context]
2742
+ )
2743
+ )
2744
+ ).then((entries) => {
2745
+ if (active) setResolved(new Map(entries));
2746
+ });
2747
+ return () => {
2748
+ active = false;
2749
+ };
2750
+ }, [token, idKey, nonce]);
2751
+ const refresh = (0, import_react18.useCallback)(() => {
2752
+ for (const id of idKey ? idKey.split(",") : []) {
2753
+ CACHE.delete(cacheKey(token, id));
2754
+ }
2755
+ setNonce((n) => n + 1);
2756
+ }, [token, idKey]);
2757
+ const wanted = (0, import_react18.useMemo)(() => idKey ? idKey.split(",") : [], [idKey]);
2758
+ const states = (0, import_react18.useMemo)(() => {
2759
+ const out = /* @__PURE__ */ new Map();
2760
+ for (const id of wanted) {
2761
+ const context = resolved.get(id);
2762
+ if (context) out.set(id, context);
2763
+ }
2764
+ return out;
2765
+ }, [wanted, resolved]);
2766
+ return {
2767
+ states,
2768
+ isLoading: states.size < wanted.length,
2769
+ refresh
2770
+ };
2771
+ }
2772
+
2773
+ // src/access/react/cantRemoveOpener.ts
2774
+ var import_access24 = require("@ai-matrx/data/access");
2775
+ var import_access25 = require("@ai-matrx/data/access");
2776
+ var CANT_REMOVE_DIALOG = "cantRemoveDialog";
2777
+ async function requestCantRemove(request) {
2778
+ const { requestOverlay } = await import("@ai-matrx/summon/request");
2779
+ const ui = getAccessUi();
2780
+ return requestOverlay(CANT_REMOVE_DIALOG, request, {
2781
+ cancel: "closed",
2782
+ dedupeKey: `${request.token}:${request.id}`,
2783
+ open: ({ instanceId, data }) => ui.openOverlay(CANT_REMOVE_DIALOG, instanceId, data),
2784
+ close: ({ instanceId }) => ui.closeOverlay(CANT_REMOVE_DIALOG, instanceId)
2785
+ });
2786
+ }
2787
+ var RemovalNotYoursError = class extends Error {
2788
+ token;
2789
+ id;
2790
+ constructor(token, id) {
2791
+ super("Removing this is up to its owner.");
2792
+ this.name = "RemovalNotYoursError";
2793
+ this.token = token;
2794
+ this.id = id;
2795
+ }
2796
+ };
2797
+ function isRemovalNotYours(error) {
2798
+ return error instanceof RemovalNotYoursError || (0, import_access24.isGovernedActionDenial)(error);
2799
+ }
2800
+ function openOnce(request) {
2801
+ void requestCantRemove(request);
2802
+ }
2803
+ function offerOwnerChoices(error, request) {
2804
+ if (!isRemovalNotYours(error)) return false;
2805
+ openOnce(request);
2806
+ return true;
2807
+ }
2808
+ async function ensureRemovable(request) {
2809
+ const rights = await (0, import_access25.fetchRemovalRights)(request.token, request.id).catch(() => null);
2810
+ if (rights?.canView && !rights.canRemove) {
2811
+ openOnce(request);
2812
+ return false;
2813
+ }
2814
+ return true;
2815
+ }
2816
+ //# sourceMappingURL=access-react.cjs.map