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