@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.
- package/CHANGELOG.md +15 -0
- package/LICENSE +21 -0
- package/README.md +33 -2
- package/dist/access-react-cant-remove-dialog.cjs +52 -0
- package/dist/access-react-cant-remove-dialog.cjs.map +1 -0
- package/dist/access-react-cant-remove-dialog.d.cts +6 -0
- package/dist/access-react-cant-remove-dialog.d.ts +6 -0
- package/dist/access-react-cant-remove-dialog.js +32 -0
- package/dist/access-react-cant-remove-dialog.js.map +1 -0
- package/dist/access-react.cjs +2816 -0
- package/dist/access-react.cjs.map +1 -0
- package/dist/access-react.d.cts +682 -0
- package/dist/access-react.d.ts +682 -0
- package/dist/access-react.js +2904 -0
- package/dist/access-react.js.map +1 -0
- package/dist/core.cjs +5396 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +4740 -0
- package/dist/core.d.ts +4740 -0
- package/dist/core.js +5372 -0
- package/dist/core.js.map +1 -0
- package/dist/react.cjs +4574 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +779 -0
- package/dist/react.d.ts +779 -0
- package/dist/react.js +4701 -0
- package/dist/react.js.map +1 -0
- package/package.json +133 -4
|
@@ -0,0 +1,682 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { AnchorHTMLAttributes, RefAttributes, ComponentType, ReactNode } from 'react';
|
|
3
|
+
import { ConfirmOptions } from '@ai-matrx/kit/confirm-opener';
|
|
4
|
+
import { JsonObject } from '@ai-matrx/kit/json-format';
|
|
5
|
+
import { RecordUnavailableResolution } from '@ai-matrx/data/db';
|
|
6
|
+
import * as _ai_matrx_data_access from '@ai-matrx/data/access';
|
|
7
|
+
import { AccessRequestCreated, AccessRequestability, AccessDeniedContext, RequestAccessTarget, AccessSupabaseClient, HiddenView, AccessRequestStatus, HiddenItem, RemovalRights, RequestContext } from '@ai-matrx/data/access';
|
|
8
|
+
export { RequestAccessTarget } from '@ai-matrx/data/access';
|
|
9
|
+
|
|
10
|
+
interface AccessUiLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
11
|
+
href: string;
|
|
12
|
+
}
|
|
13
|
+
/** The person using the screen. Any field may be null while the session boots. */
|
|
14
|
+
interface AccessUiCurrentUser {
|
|
15
|
+
id: string | null;
|
|
16
|
+
displayName: string | null;
|
|
17
|
+
email: string | null;
|
|
18
|
+
}
|
|
19
|
+
interface AccessUiToast {
|
|
20
|
+
success(message: string): void;
|
|
21
|
+
error(message: string): void;
|
|
22
|
+
warning(message: string): void;
|
|
23
|
+
info?(message: string): void;
|
|
24
|
+
}
|
|
25
|
+
interface AccessUiRouter {
|
|
26
|
+
push(href: string): void;
|
|
27
|
+
back(): void;
|
|
28
|
+
/** Re-render the current URL from the server (Next `router.refresh()`). */
|
|
29
|
+
refresh(): void;
|
|
30
|
+
}
|
|
31
|
+
/** What the gate needs to know about an entity token to link to a record. */
|
|
32
|
+
interface AccessUiEntityInfo {
|
|
33
|
+
hrefFor?: (id: string) => string | null | undefined;
|
|
34
|
+
}
|
|
35
|
+
interface AccessUiEntityRefProps {
|
|
36
|
+
token: string;
|
|
37
|
+
id: string;
|
|
38
|
+
name: string;
|
|
39
|
+
showIcon?: boolean;
|
|
40
|
+
className?: string;
|
|
41
|
+
}
|
|
42
|
+
interface AccessUiUserIdentityProps {
|
|
43
|
+
user: {
|
|
44
|
+
id?: string | null | undefined;
|
|
45
|
+
displayName?: string | null | undefined;
|
|
46
|
+
avatarUrl?: string | null | undefined;
|
|
47
|
+
};
|
|
48
|
+
size?: "sm" | "md";
|
|
49
|
+
subtitle?: boolean;
|
|
50
|
+
avatarOnly?: boolean;
|
|
51
|
+
}
|
|
52
|
+
interface AccessUiErrorMenuProps {
|
|
53
|
+
size?: "icon" | "default";
|
|
54
|
+
className?: string;
|
|
55
|
+
/** The error text (or error) the menu should offer to copy. */
|
|
56
|
+
error?: unknown;
|
|
57
|
+
/** Structured description of what failed (title, message, operation, records…). */
|
|
58
|
+
input?: Record<string, unknown>;
|
|
59
|
+
}
|
|
60
|
+
interface AccessUiTextareaProps {
|
|
61
|
+
className?: string;
|
|
62
|
+
value: string;
|
|
63
|
+
onChange: (event: {
|
|
64
|
+
target: {
|
|
65
|
+
value: string;
|
|
66
|
+
};
|
|
67
|
+
}) => void;
|
|
68
|
+
placeholder?: string;
|
|
69
|
+
maxLength?: number;
|
|
70
|
+
rows?: number;
|
|
71
|
+
autoFocus?: boolean;
|
|
72
|
+
"aria-label"?: string;
|
|
73
|
+
}
|
|
74
|
+
/** A setting-change action an admin completes from a request (messaging registry). */
|
|
75
|
+
interface AccessSettingRequestAction {
|
|
76
|
+
label: string;
|
|
77
|
+
completedLabel: string;
|
|
78
|
+
execute(payload: JsonObject, context: {
|
|
79
|
+
organizationId: string;
|
|
80
|
+
}): Promise<void>;
|
|
81
|
+
}
|
|
82
|
+
/** A resource action (e.g. delete) an owner completes from a request. */
|
|
83
|
+
interface AccessResourceActionRequestAction {
|
|
84
|
+
label: string;
|
|
85
|
+
completedLabel: string;
|
|
86
|
+
confirmTitle(name: string): string;
|
|
87
|
+
confirmDescription: string;
|
|
88
|
+
execute(context: {
|
|
89
|
+
resourceType: string;
|
|
90
|
+
resourceId: string;
|
|
91
|
+
}): Promise<void>;
|
|
92
|
+
}
|
|
93
|
+
interface AccessUiFeedbackInput {
|
|
94
|
+
feedback_type: "request";
|
|
95
|
+
route: string;
|
|
96
|
+
organization_id: string;
|
|
97
|
+
description: string;
|
|
98
|
+
metadata: Record<string, unknown>;
|
|
99
|
+
}
|
|
100
|
+
interface AccessUiEnv {
|
|
101
|
+
/** The sign-in URL that returns the person to where they are. Hook-shaped. */
|
|
102
|
+
useLoginHref(): string;
|
|
103
|
+
/** The current person (replaces the app's redux selectors). Hook-shaped. */
|
|
104
|
+
useCurrentUser(): AccessUiCurrentUser;
|
|
105
|
+
/** User-visible notices. */
|
|
106
|
+
toast: AccessUiToast;
|
|
107
|
+
/** Client-side navigation link. Default: a plain `<a href>` (full page load). */
|
|
108
|
+
Link?: ComponentType<AccessUiLinkProps & RefAttributes<HTMLAnchorElement>>;
|
|
109
|
+
/** Default: `history.back()` / `location.reload()` / `location.assign`. */
|
|
110
|
+
router?: AccessUiRouter;
|
|
111
|
+
/** Read one URL query parameter. Default: `window.location.search`. */
|
|
112
|
+
searchParam?(name: string): string | null;
|
|
113
|
+
/** Default: the kit's imperative `confirm()` (needs the design-system ConfirmDialogHost mounted). */
|
|
114
|
+
confirm?(options: ConfirmOptions): Promise<boolean>;
|
|
115
|
+
/** Default: the design-system `Textarea`. An app may pass its voice-enabled textarea. */
|
|
116
|
+
Textarea?: ComponentType<AccessUiTextareaProps>;
|
|
117
|
+
/** Default: avatar + name from the design-system Avatar. */
|
|
118
|
+
renderUserIdentity?(props: AccessUiUserIdentityProps): ReactNode;
|
|
119
|
+
/** Default: the plain name — NOT a door. Pass the app's EntityRef to make it one. */
|
|
120
|
+
renderEntityRef?(props: AccessUiEntityRefProps): ReactNode;
|
|
121
|
+
/** The error-copy menu slot. Default: renders nothing (it is a convenience, not a feature). */
|
|
122
|
+
renderErrorMenu?(props: AccessUiErrorMenuProps): ReactNode;
|
|
123
|
+
/** Classes of the selected / unselected inbox tab. Default: the app-shell nav CSS variables. */
|
|
124
|
+
navItemClasses?: {
|
|
125
|
+
selected: string;
|
|
126
|
+
unselected: string;
|
|
127
|
+
};
|
|
128
|
+
/** Label/route lookup per entity token. Absent: owners and records render as plain names. */
|
|
129
|
+
entityRegistry?: {
|
|
130
|
+
tryGetEntityInfo(token: string): AccessUiEntityInfo | null;
|
|
131
|
+
};
|
|
132
|
+
/** Restore from Trash. Absent: pressing Restore fails visibly naming this field. */
|
|
133
|
+
trash?: {
|
|
134
|
+
restore(token: string, id: string): Promise<void>;
|
|
135
|
+
};
|
|
136
|
+
/** Reconcile the diagnostics row of a captured read. Absent: no-op. */
|
|
137
|
+
recordUnavailableCapture?: {
|
|
138
|
+
resolve(error: unknown, resolution: RecordUnavailableResolution): void;
|
|
139
|
+
};
|
|
140
|
+
/** Setting-change actions by key. Absent: requests render without the primary action button. */
|
|
141
|
+
settingRequestActions?: {
|
|
142
|
+
get(key: string): AccessSettingRequestAction | undefined;
|
|
143
|
+
};
|
|
144
|
+
/** The action key the "request access" flows file. Default `request_access.manual`. */
|
|
145
|
+
manualRequestActionKey?: string;
|
|
146
|
+
/** Resource actions by key. Absent: requests render without the primary action button. */
|
|
147
|
+
resourceActionRequests?: {
|
|
148
|
+
get(key: string): AccessResourceActionRequestAction | undefined;
|
|
149
|
+
};
|
|
150
|
+
/** CMS-resolved tokens file through the host's CMS door. Absent: asking fails visibly. */
|
|
151
|
+
cms?: {
|
|
152
|
+
createAccessRequest(args: {
|
|
153
|
+
token: string;
|
|
154
|
+
resourceId: string;
|
|
155
|
+
message?: string;
|
|
156
|
+
currentUserId?: string | null;
|
|
157
|
+
}): Promise<AccessRequestCreated>;
|
|
158
|
+
};
|
|
159
|
+
/** The URL of an organization setting's door. Absent: asking for a setting change fails visibly. */
|
|
160
|
+
settingDoorHref?(target: {
|
|
161
|
+
scope: "organization";
|
|
162
|
+
organizationSlugOrId: string;
|
|
163
|
+
controlId: string;
|
|
164
|
+
}): string;
|
|
165
|
+
/** File a feedback item (system-owned access requests). Absent: asking the platform team fails visibly. */
|
|
166
|
+
reportFeedback?(input: AccessUiFeedbackInput): Promise<{
|
|
167
|
+
success: boolean;
|
|
168
|
+
error?: string;
|
|
169
|
+
}>;
|
|
170
|
+
/** Show "choose an organization first". Returns true when it handled the error. Default: false. */
|
|
171
|
+
presentOrganizationRefusal?(error: unknown, options: {
|
|
172
|
+
act: string;
|
|
173
|
+
subject: string;
|
|
174
|
+
}): boolean;
|
|
175
|
+
/**
|
|
176
|
+
* The host's overlay doors (AI Matrx: dispatch summon `openOverlay` / `closeOverlay` on its store). The
|
|
177
|
+
* "can't remove it" screen is the overlay `CANT_REMOVE_DIALOG` opened through them. Absent: that screen
|
|
178
|
+
* cannot open and says so on the console (the caller gets "closed").
|
|
179
|
+
*/
|
|
180
|
+
overlays?: {
|
|
181
|
+
open(overlayId: string, instanceId: string, data: unknown): void;
|
|
182
|
+
close(overlayId: string, instanceId: string): void;
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/** The documented default for `manualRequestActionKey`. */
|
|
186
|
+
declare const REQUEST_ACCESS_MANUAL_ACTION = "request_access.manual";
|
|
187
|
+
/** The host's `Link`, resolved at render. Components import this as `Link`. */
|
|
188
|
+
declare const AccessLink: react.ForwardRefExoticComponent<AccessUiLinkProps & RefAttributes<HTMLAnchorElement>>;
|
|
189
|
+
/**
|
|
190
|
+
* Inject the app inputs the access screens need. Call once at boot. Later
|
|
191
|
+
* calls MERGE over earlier ones, so a host can configure in two steps.
|
|
192
|
+
*/
|
|
193
|
+
declare function configureAccessUi(env: Partial<AccessUiEnv>): void;
|
|
194
|
+
/** True once every REQUIRED field has been supplied. */
|
|
195
|
+
declare function isAccessUiConfigured(): boolean;
|
|
196
|
+
/** The resolved environment: required fields throw when missing, the rest are defaulted. */
|
|
197
|
+
interface AccessUi {
|
|
198
|
+
useLoginHref(): string;
|
|
199
|
+
useCurrentUser(): AccessUiCurrentUser;
|
|
200
|
+
toast: AccessUiToast;
|
|
201
|
+
Link: ComponentType<AccessUiLinkProps & RefAttributes<HTMLAnchorElement>>;
|
|
202
|
+
router: AccessUiRouter;
|
|
203
|
+
searchParam(name: string): string | null;
|
|
204
|
+
confirm(options: ConfirmOptions): Promise<boolean>;
|
|
205
|
+
Textarea: ComponentType<AccessUiTextareaProps>;
|
|
206
|
+
renderUserIdentity(props: AccessUiUserIdentityProps): ReactNode;
|
|
207
|
+
renderEntityRef(props: AccessUiEntityRefProps): ReactNode;
|
|
208
|
+
renderErrorMenu(props: AccessUiErrorMenuProps): ReactNode;
|
|
209
|
+
navItemClasses: {
|
|
210
|
+
selected: string;
|
|
211
|
+
unselected: string;
|
|
212
|
+
};
|
|
213
|
+
manualRequestActionKey: string;
|
|
214
|
+
tryGetEntityInfo(token: string): AccessUiEntityInfo | null;
|
|
215
|
+
restoreFromTrash(token: string, id: string): Promise<void>;
|
|
216
|
+
resolveRecordUnavailableCapture(error: unknown, resolution: RecordUnavailableResolution): void;
|
|
217
|
+
getSettingRequestAction(key: string): AccessSettingRequestAction | undefined;
|
|
218
|
+
getResourceActionRequestAction(key: string): AccessResourceActionRequestAction | undefined;
|
|
219
|
+
/** Whether the host wired the CMS door (cms.createAccessRequest). */
|
|
220
|
+
createCmsAccessRequest(args: {
|
|
221
|
+
token: string;
|
|
222
|
+
resourceId: string;
|
|
223
|
+
message?: string;
|
|
224
|
+
currentUserId?: string | null;
|
|
225
|
+
}): Promise<AccessRequestCreated>;
|
|
226
|
+
settingDoorHref(target: {
|
|
227
|
+
scope: "organization";
|
|
228
|
+
organizationSlugOrId: string;
|
|
229
|
+
controlId: string;
|
|
230
|
+
}): string;
|
|
231
|
+
reportFeedback(input: AccessUiFeedbackInput): Promise<{
|
|
232
|
+
success: boolean;
|
|
233
|
+
error?: string;
|
|
234
|
+
}>;
|
|
235
|
+
presentOrganizationRefusal(error: unknown, options: {
|
|
236
|
+
act: string;
|
|
237
|
+
subject: string;
|
|
238
|
+
}): boolean;
|
|
239
|
+
/** Open an overlay through the host's doors; throws `notConfigured("overlays")` without them. */
|
|
240
|
+
openOverlay(overlayId: string, instanceId: string, data: unknown): void;
|
|
241
|
+
closeOverlay(overlayId: string, instanceId: string): void;
|
|
242
|
+
}
|
|
243
|
+
/** The module-level environment, for code that is not a component (openers, stores). */
|
|
244
|
+
declare function getAccessUi(): AccessUi;
|
|
245
|
+
/** Override fields for one subtree (tests, a second host). Merges over `configureAccessUi`. */
|
|
246
|
+
declare function AccessUiProvider({ env, children, }: {
|
|
247
|
+
env: Partial<AccessUiEnv>;
|
|
248
|
+
children: ReactNode;
|
|
249
|
+
}): react.JSX.Element;
|
|
250
|
+
/** The merged, defaulted environment. Stable while neither the provider value nor the configuration changes. */
|
|
251
|
+
declare function useAccessUi(): AccessUi;
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* One concrete way forward, offered by the surface that knows the feature.
|
|
255
|
+
* The gate cannot invent these — only the CMS knows that "create a website" is
|
|
256
|
+
* the useful thing to offer someone whose website link went nowhere.
|
|
257
|
+
*/
|
|
258
|
+
interface AccessDeniedSuggestion {
|
|
259
|
+
/** Short imperative label — "Your websites", "Create a website". */
|
|
260
|
+
title: string;
|
|
261
|
+
/** One line on what they will get. Optional. */
|
|
262
|
+
description?: string;
|
|
263
|
+
/** Where it goes. Must be a route this viewer can actually open (THE DOOR LAW). */
|
|
264
|
+
href: string;
|
|
265
|
+
icon?: React.ComponentType<{
|
|
266
|
+
className?: string;
|
|
267
|
+
}>;
|
|
268
|
+
}
|
|
269
|
+
interface AccessDeniedProps {
|
|
270
|
+
/** Canonical entity token of the thing the user tried to open. */
|
|
271
|
+
token: string;
|
|
272
|
+
/** Its id. */
|
|
273
|
+
id: string;
|
|
274
|
+
/** The original failed read, so the resolver can reconcile its capture. */
|
|
275
|
+
readError?: unknown | undefined;
|
|
276
|
+
/**
|
|
277
|
+
* Where "go back to what I can see" should land when the platform can't find
|
|
278
|
+
* a reachable ancestor — usually the feature's own list route.
|
|
279
|
+
*/
|
|
280
|
+
fallbackHref?: string | undefined;
|
|
281
|
+
fallbackLabel?: string | undefined;
|
|
282
|
+
/** Retry the surface's own read, offered only where retrying can work. */
|
|
283
|
+
onRetry?: (() => void) | undefined;
|
|
284
|
+
/** Concrete next steps in THIS feature. See AccessDeniedSuggestion. */
|
|
285
|
+
suggestions?: AccessDeniedSuggestion[] | undefined;
|
|
286
|
+
/**
|
|
287
|
+
* Whether asking is a real option here. Defaults to `"requestable"` — the
|
|
288
|
+
* behavior every existing caller already has. Pass `"absolute"` when the
|
|
289
|
+
* door is closed by law and the ask itself would leak. See
|
|
290
|
+
* `AccessRequestability`.
|
|
291
|
+
*/
|
|
292
|
+
requestability?: AccessRequestability | undefined;
|
|
293
|
+
/**
|
|
294
|
+
* The surface's own worded reason, shown in place of the generic explanation.
|
|
295
|
+
* On an `absolute` door this sentence must not confirm the record exists.
|
|
296
|
+
*/
|
|
297
|
+
reason?: string | undefined;
|
|
298
|
+
/** The surface's own headline, in place of the kind-naming generic one. */
|
|
299
|
+
headline?: string | undefined;
|
|
300
|
+
/** The surface's own trailing disclosure (a reference code, an audit id). */
|
|
301
|
+
footer?: React.ReactNode | undefined;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* The resolved surface. Split from the loader so a bespoke variant can reuse
|
|
305
|
+
* the same body with different chrome.
|
|
306
|
+
*/
|
|
307
|
+
declare function AccessDeniedView({ context, id, fallbackHref, fallbackLabel, onRetry, onChanged, suggestions, requestability, reason, headline: headlineOverride, footer, }: {
|
|
308
|
+
context: AccessDeniedContext;
|
|
309
|
+
id: string;
|
|
310
|
+
fallbackHref?: string | undefined;
|
|
311
|
+
fallbackLabel?: string | undefined;
|
|
312
|
+
onRetry?: (() => void) | undefined;
|
|
313
|
+
onChanged: () => void;
|
|
314
|
+
suggestions?: AccessDeniedSuggestion[] | undefined;
|
|
315
|
+
/** See `AccessRequestability`. `absolute` removes the request affordance entirely. */
|
|
316
|
+
requestability?: AccessRequestability | undefined;
|
|
317
|
+
/** The surface's own worded reason, in place of the generic explanation. */
|
|
318
|
+
reason?: string | undefined;
|
|
319
|
+
/** The surface's own headline, in place of the kind-naming generic one. */
|
|
320
|
+
headline?: string | undefined;
|
|
321
|
+
/**
|
|
322
|
+
* The surface's own trailing disclosure — a reference code, an audit id, the
|
|
323
|
+
* things a feature earned the right to show and the frame cannot know about.
|
|
324
|
+
* Rendered above the doors. Machine tokens belong in here behind a
|
|
325
|
+
* `<details>`, never in the body: that rule predates this slot and outlives it.
|
|
326
|
+
*/
|
|
327
|
+
footer?: React.ReactNode | undefined;
|
|
328
|
+
}): react.JSX.Element;
|
|
329
|
+
/**
|
|
330
|
+
* The loader. Resolves why the record couldn't be opened, then renders it.
|
|
331
|
+
*
|
|
332
|
+
* A feature that genuinely earns a bespoke screen composes `AccessDeniedView`
|
|
333
|
+
* (exported above) at its own call site — it does NOT register a variant here.
|
|
334
|
+
* A token→component registry consulted during render is a dynamic component
|
|
335
|
+
* boundary (react-hooks/static-components) for an extension point that has
|
|
336
|
+
* exactly zero users, which is the speculative abstraction docs/reuse-first
|
|
337
|
+
* bans. Passing your own component down is simpler and has no such hazard.
|
|
338
|
+
*/
|
|
339
|
+
declare function AccessDenied({ token, id, readError, fallbackHref, fallbackLabel, onRetry, suggestions, requestability, reason, headline, footer, }: AccessDeniedProps): react.JSX.Element;
|
|
340
|
+
|
|
341
|
+
interface AccessGateProps {
|
|
342
|
+
/** Canonical entity token of the record the surface tried to open. */
|
|
343
|
+
token: string;
|
|
344
|
+
id: string;
|
|
345
|
+
/**
|
|
346
|
+
* The error the read produced, if any. A null-row read (no error at all) is
|
|
347
|
+
* the common case and is exactly what this gate is for.
|
|
348
|
+
*/
|
|
349
|
+
error?: unknown | undefined;
|
|
350
|
+
onRetry?: (() => void) | undefined;
|
|
351
|
+
fallbackHref?: string | undefined;
|
|
352
|
+
fallbackLabel?: string | undefined;
|
|
353
|
+
/**
|
|
354
|
+
* Parallel failed reads hidden by this gate's early return. Each is resolved
|
|
355
|
+
* independently for truthful diagnostics; none renders a second surface.
|
|
356
|
+
*/
|
|
357
|
+
relatedReads?: ReadonlyArray<{
|
|
358
|
+
token: string;
|
|
359
|
+
id: string;
|
|
360
|
+
error: unknown;
|
|
361
|
+
}>;
|
|
362
|
+
/**
|
|
363
|
+
* Render this instead when the failure was a genuine fault rather than an
|
|
364
|
+
* access state. Defaults to the access surface, which handles faults with an
|
|
365
|
+
* honest "something went wrong on our side" + retry.
|
|
366
|
+
*/
|
|
367
|
+
renderFault?: (error: unknown) => React.ReactNode;
|
|
368
|
+
/**
|
|
369
|
+
* Two or three concrete things to do instead, in THIS feature — "your
|
|
370
|
+
* websites", "create one". Without them the gate is honest and still a dead
|
|
371
|
+
* end: it says what went wrong and leaves the user to re-navigate.
|
|
372
|
+
*/
|
|
373
|
+
suggestions?: AccessDeniedSuggestion[] | undefined;
|
|
374
|
+
/**
|
|
375
|
+
* `"absolute"` removes the request affordance entirely — for a door closed by
|
|
376
|
+
* law, where the ask itself would leak. Defaults to `"requestable"`.
|
|
377
|
+
*/
|
|
378
|
+
requestability?: AccessRequestability | undefined;
|
|
379
|
+
/** The surface's own worded reason, in place of the generic explanation. */
|
|
380
|
+
reason?: string | undefined;
|
|
381
|
+
/** The surface's own headline, in place of the kind-naming generic one. */
|
|
382
|
+
headline?: string | undefined;
|
|
383
|
+
/** The surface's own trailing disclosure (a reference code, an audit id). */
|
|
384
|
+
footer?: React.ReactNode | undefined;
|
|
385
|
+
}
|
|
386
|
+
declare function AccessGate({ token, id, error, onRetry, fallbackHref, fallbackLabel, relatedReads, renderFault, suggestions, requestability, reason, headline, footer, }: AccessGateProps): react.JSX.Element;
|
|
387
|
+
|
|
388
|
+
interface SlugAccessGateProps {
|
|
389
|
+
/** Entity tokens to try, in the page's own resolution order. */
|
|
390
|
+
tokens: readonly string[];
|
|
391
|
+
/** The raw route slug (already decoded by the App Router). */
|
|
392
|
+
slug: string;
|
|
393
|
+
/** What the thing is called in the "didn't match" sentence, e.g. "shape". */
|
|
394
|
+
noun: string;
|
|
395
|
+
fallbackHref: string;
|
|
396
|
+
fallbackLabel: string;
|
|
397
|
+
/**
|
|
398
|
+
* The page also filters by publication state (not just RLS), so a miss can
|
|
399
|
+
* be a row the viewer may read that simply is not published.
|
|
400
|
+
*/
|
|
401
|
+
publishFiltered?: boolean;
|
|
402
|
+
}
|
|
403
|
+
declare function SlugAccessGate({ tokens, slug, noun, fallbackHref, fallbackLabel, publishFiltered, }: SlugAccessGateProps): react.JSX.Element | null;
|
|
404
|
+
|
|
405
|
+
declare function SettingAccessGate({ canManage, organizationId, organizationSlugOrId, settingKey, settingLabel, actionKey, actionPayload, defaultMessage, requestReady, children, }: {
|
|
406
|
+
canManage: boolean;
|
|
407
|
+
organizationId: string;
|
|
408
|
+
organizationSlugOrId: string;
|
|
409
|
+
settingKey: string;
|
|
410
|
+
settingLabel: string;
|
|
411
|
+
actionKey: string;
|
|
412
|
+
actionPayload: JsonObject;
|
|
413
|
+
defaultMessage: string;
|
|
414
|
+
requestReady?: boolean;
|
|
415
|
+
children: React.ReactNode;
|
|
416
|
+
}): react.JSX.Element;
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* WHAT THE APP'S SERVER BOUNDARY READS AND HANDS DOWN. The package cannot call
|
|
420
|
+
* `headers()` or the session verdict (no `next/*`, no app imports, and an async
|
|
421
|
+
* Server Component cannot live in a client entry). The app's `forbidden.tsx`
|
|
422
|
+
* keeps a ~10-line server wrapper: read the verdict + the stamped request path,
|
|
423
|
+
* build `signInHref`, and render `<ForbiddenSurfaceView …/>`.
|
|
424
|
+
*/
|
|
425
|
+
interface ForbiddenSurfaceViewProps {
|
|
426
|
+
/** `unverified` = the session could not be confirmed (neither "no access" nor "sign in"). */
|
|
427
|
+
state: "authenticated" | "anonymous" | "unverified";
|
|
428
|
+
isAuthenticated: boolean;
|
|
429
|
+
/** The path the person was trying to open (the app stamps it on the request). */
|
|
430
|
+
pathname: string;
|
|
431
|
+
search?: string;
|
|
432
|
+
/** Sign-in URL that returns to `pathname`+`search`. */
|
|
433
|
+
signInHref: string;
|
|
434
|
+
/** Where "Back to what you can see" goes. Default `/dashboard`. */
|
|
435
|
+
homeHref?: string;
|
|
436
|
+
}
|
|
437
|
+
declare function ForbiddenSurfaceView({ state, isAuthenticated, pathname, search, signInHref, homeHref, }: ForbiddenSurfaceViewProps): react.JSX.Element;
|
|
438
|
+
|
|
439
|
+
declare function RequestAccess({ target, variant, reason, className, }: {
|
|
440
|
+
target: RequestAccessTarget;
|
|
441
|
+
/** `inline` = one line + button; `icon` = key icon only (tight headers). */
|
|
442
|
+
variant?: "inline" | "icon";
|
|
443
|
+
/** Inline only: the one-line why. Defaults from the owner. */
|
|
444
|
+
reason?: string;
|
|
445
|
+
className?: string;
|
|
446
|
+
}): react.JSX.Element;
|
|
447
|
+
|
|
448
|
+
interface RequestAccessPanelProps {
|
|
449
|
+
context: AccessDeniedContext;
|
|
450
|
+
/**
|
|
451
|
+
* The record's id. It is NOT on the context — the RPC never echoes back an id
|
|
452
|
+
* the caller already had, so the surface that knows it passes it through.
|
|
453
|
+
*/
|
|
454
|
+
resourceId: string;
|
|
455
|
+
/** Where the record lives, passed to the recipient's DM chip. */
|
|
456
|
+
href?: string | null;
|
|
457
|
+
onChanged: () => void;
|
|
458
|
+
}
|
|
459
|
+
declare function RequestAccessPanel({ context, resourceId, href, onChanged, }: RequestAccessPanelProps): react.JSX.Element | null;
|
|
460
|
+
|
|
461
|
+
declare function BlindAccessAsk({ token, id, href, }: {
|
|
462
|
+
token: string;
|
|
463
|
+
id: string;
|
|
464
|
+
href?: string | null;
|
|
465
|
+
}): react.JSX.Element;
|
|
466
|
+
|
|
467
|
+
interface UnresolvedEntityRefProps {
|
|
468
|
+
/** The record's id — the resolver never echoes back an id the caller had. */
|
|
469
|
+
id: string;
|
|
470
|
+
/**
|
|
471
|
+
* The resolved answer, or null while `useAccessStates` is still asking.
|
|
472
|
+
* Never synthesize one: a guessed state is the defect this component fixes.
|
|
473
|
+
*/
|
|
474
|
+
context: AccessDeniedContext | null;
|
|
475
|
+
/** Re-resolve after the owner grants, so the row heals in place. */
|
|
476
|
+
onChanged?: () => void;
|
|
477
|
+
/**
|
|
478
|
+
* The surface's own repair for a record it cannot resolve — "Remove from
|
|
479
|
+
* outreach list", "Detach", "Replace". Rendered inside the popover under the
|
|
480
|
+
* platform's own actions. THE DOOR LAW's third corollary: a problem you can
|
|
481
|
+
* detect ships with its fix.
|
|
482
|
+
*/
|
|
483
|
+
repairAction?: React.ReactNode;
|
|
484
|
+
className?: string;
|
|
485
|
+
}
|
|
486
|
+
declare function UnresolvedEntityRef({ id, context, onChanged, repairAction, className, }: UnresolvedEntityRefProps): react.JSX.Element;
|
|
487
|
+
|
|
488
|
+
type BrowserClient = AccessSupabaseClient;
|
|
489
|
+
/** Row readers per canonical entity token. Add a token when a page adopts this. */
|
|
490
|
+
declare const ROW_READERS: {
|
|
491
|
+
conversation: (supabase: BrowserClient, id: string) => Promise<boolean>;
|
|
492
|
+
};
|
|
493
|
+
type ServerReadRecheckToken = keyof typeof ROW_READERS;
|
|
494
|
+
declare function ServerReadRecheck({ token, id, children, pending, }: {
|
|
495
|
+
token: ServerReadRecheckToken;
|
|
496
|
+
id: string;
|
|
497
|
+
/** The access gate. */
|
|
498
|
+
children: ReactNode;
|
|
499
|
+
/** What shows while the browser re-reads. */
|
|
500
|
+
pending?: ReactNode;
|
|
501
|
+
}): react.JSX.Element;
|
|
502
|
+
|
|
503
|
+
declare function GovernedActionDialog({ open, onOpenChange, resourceType, resourceId, itemName, itemLabel, href, action, onHidden, }: {
|
|
504
|
+
open: boolean;
|
|
505
|
+
onOpenChange: (open: boolean) => void;
|
|
506
|
+
resourceType: string;
|
|
507
|
+
resourceId: string;
|
|
508
|
+
itemName: string;
|
|
509
|
+
itemLabel?: string;
|
|
510
|
+
href?: string | null;
|
|
511
|
+
/** The word for the removal the person tried: "archive" or "delete". */
|
|
512
|
+
action?: "archive" | "delete";
|
|
513
|
+
/** Called after the person hid the item (re-read a list that narrows server-side). */
|
|
514
|
+
onHidden?: () => void;
|
|
515
|
+
}): react.JSX.Element;
|
|
516
|
+
|
|
517
|
+
declare function HiddenByMeButton({ token, pluralLabel, }: {
|
|
518
|
+
/** Platform entity token of the list (`note`, `transcript`, …). */
|
|
519
|
+
token: string;
|
|
520
|
+
/** "notes" — the list's own word for its records. */
|
|
521
|
+
pluralLabel: string;
|
|
522
|
+
}): react.JSX.Element | null;
|
|
523
|
+
|
|
524
|
+
declare function HiddenFilter({ count, value, onValueChange, }: {
|
|
525
|
+
/** How many items of this list the person has hidden. */
|
|
526
|
+
count: number;
|
|
527
|
+
value: HiddenView;
|
|
528
|
+
onValueChange: (value: HiddenView) => void;
|
|
529
|
+
}): react.JSX.Element | null;
|
|
530
|
+
|
|
531
|
+
declare function ResourceActionRequestButtons({ requestId, actionKey, href, itemName, isOwn, compact, onDone, }: {
|
|
532
|
+
requestId: string;
|
|
533
|
+
actionKey: string;
|
|
534
|
+
href?: string | null;
|
|
535
|
+
itemName: string;
|
|
536
|
+
isOwn?: boolean;
|
|
537
|
+
compact?: boolean;
|
|
538
|
+
onDone?: (status: AccessRequestStatus) => void;
|
|
539
|
+
}): react.JSX.Element;
|
|
540
|
+
|
|
541
|
+
declare function SettingRequestActionButtons({ requestId, href, actionKey, isOwn, compact, onDone, }: {
|
|
542
|
+
requestId: string;
|
|
543
|
+
href: string;
|
|
544
|
+
actionKey: string;
|
|
545
|
+
isOwn?: boolean;
|
|
546
|
+
compact?: boolean;
|
|
547
|
+
onDone?: (status: AccessRequestStatus) => void;
|
|
548
|
+
}): react.JSX.Element;
|
|
549
|
+
|
|
550
|
+
/** The one address of this inbox: the settings tab `accessRequests`. */
|
|
551
|
+
declare const ACCESS_REQUESTS_HREF = "/user-settings/access-requests";
|
|
552
|
+
declare function AccessRequestsSurface(): react.JSX.Element;
|
|
553
|
+
|
|
554
|
+
interface UseAccessGateResult {
|
|
555
|
+
context: AccessDeniedContext | null;
|
|
556
|
+
status: AccessDeniedContext["status"] | "loading";
|
|
557
|
+
isLoading: boolean;
|
|
558
|
+
/** Re-resolve — used after a request is sent, granted, or withdrawn. */
|
|
559
|
+
refresh: () => void;
|
|
560
|
+
}
|
|
561
|
+
declare function useAccessGate(token: string | null | undefined, id: string | null | undefined, options?: {
|
|
562
|
+
enabled?: boolean;
|
|
563
|
+
readError?: unknown;
|
|
564
|
+
}): UseAccessGateResult;
|
|
565
|
+
|
|
566
|
+
interface UseAccessStatesResult {
|
|
567
|
+
/** Resolved answers by record id. An absent id is still resolving. */
|
|
568
|
+
states: ReadonlyMap<string, AccessDeniedContext>;
|
|
569
|
+
/** True while at least one id on this page has no answer yet. */
|
|
570
|
+
isLoading: boolean;
|
|
571
|
+
/** Drop the cached answers for these ids and ask again. */
|
|
572
|
+
refresh: () => void;
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Resolve the true access state of every id in `ids` for one entity token.
|
|
576
|
+
*
|
|
577
|
+
* Pass ONLY the ids that actually failed to resolve. Asking about records the
|
|
578
|
+
* caller can already read is a wasted round trip per row, and the resolver is
|
|
579
|
+
* deliberately a per-record call.
|
|
580
|
+
*/
|
|
581
|
+
declare function useAccessStates(token: string, ids: readonly string[]): UseAccessStatesResult;
|
|
582
|
+
|
|
583
|
+
/** (Re)read one token's hidden items into the store. Signed out → nothing is hidden. */
|
|
584
|
+
declare function refreshHiddenByMe(token: string): Promise<void>;
|
|
585
|
+
declare function useHiddenByMe(token: string, enabled?: boolean): {
|
|
586
|
+
data: string[] | undefined;
|
|
587
|
+
isPending: boolean;
|
|
588
|
+
};
|
|
589
|
+
declare function useHiddenItems(token: string, enabled?: boolean): {
|
|
590
|
+
data: HiddenItem[] | undefined;
|
|
591
|
+
isPending: boolean;
|
|
592
|
+
};
|
|
593
|
+
declare function useRemovalRights(token: string, id: string | null): {
|
|
594
|
+
data: RemovalRights | undefined;
|
|
595
|
+
error: unknown;
|
|
596
|
+
isLoading: boolean;
|
|
597
|
+
isPending: boolean;
|
|
598
|
+
isError: boolean;
|
|
599
|
+
refetch: () => void;
|
|
600
|
+
};
|
|
601
|
+
type HideArgs = string | {
|
|
602
|
+
id: string;
|
|
603
|
+
label?: string | null;
|
|
604
|
+
};
|
|
605
|
+
/** Hide / unhide for the person. `onChanged` runs after the store refreshed. */
|
|
606
|
+
declare function useHideForMe(token: string, onChanged?: () => void | Promise<void>): {
|
|
607
|
+
hide: {
|
|
608
|
+
isPending: boolean;
|
|
609
|
+
mutateAsync: (args: HideArgs) => Promise<void>;
|
|
610
|
+
};
|
|
611
|
+
unhide: {
|
|
612
|
+
isPending: boolean;
|
|
613
|
+
mutateAsync: (id: string) => Promise<void>;
|
|
614
|
+
};
|
|
615
|
+
};
|
|
616
|
+
|
|
617
|
+
interface RequestAccessResult {
|
|
618
|
+
/** A pending ask already existed — nothing new was sent. */
|
|
619
|
+
already: boolean;
|
|
620
|
+
/** Sentence for the confirmation toast. */
|
|
621
|
+
message: string;
|
|
622
|
+
/** Warning, not success: the ask is saved but no admin was messaged. */
|
|
623
|
+
undelivered: boolean;
|
|
624
|
+
}
|
|
625
|
+
declare function useRequestAccess(target: RequestAccessTarget): {
|
|
626
|
+
owner: _ai_matrx_data_access.ResolvedOwner;
|
|
627
|
+
destination: string;
|
|
628
|
+
context: () => RequestContext;
|
|
629
|
+
send: (note: string) => Promise<RequestAccessResult>;
|
|
630
|
+
sending: boolean;
|
|
631
|
+
sent: boolean;
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* THE imperative door to "you can't remove this: ask the owner, or hide it for me" (2026-10-14). Any code
|
|
636
|
+
* that tried to archive or delete a record the person may not remove calls `offerOwnerChoices(...)`.
|
|
637
|
+
* The screen is the summon overlay `CANT_REMOVE_DIALOG` (render: `./cantRemoveDialog.render`), opened
|
|
638
|
+
* through the host's overlay doors (`configureAccessUi({ overlays })`) and answered through summon's
|
|
639
|
+
* one-shot request (`requestOverlay`, `@ai-matrx/summon/request`, loaded on the first ask).
|
|
640
|
+
*/
|
|
641
|
+
interface CantRemoveRequest {
|
|
642
|
+
/** Platform entity token (`web_brand`, `note`, …). */
|
|
643
|
+
token: string;
|
|
644
|
+
id: string;
|
|
645
|
+
/** The record as a person says it ("Kiln log"). */
|
|
646
|
+
itemName: string;
|
|
647
|
+
/** The kind as a person says it ("note"). */
|
|
648
|
+
itemLabel: string;
|
|
649
|
+
action: "archive" | "delete";
|
|
650
|
+
href?: string | null;
|
|
651
|
+
}
|
|
652
|
+
/** "hidden" when the person hid it for themselves; "closed" otherwise. */
|
|
653
|
+
type CantRemoveOutcome = "hidden" | "closed";
|
|
654
|
+
/** The overlay id a host registers with `./cantRemoveDialog.render` as its render. */
|
|
655
|
+
declare const CANT_REMOVE_DIALOG = "cantRemoveDialog";
|
|
656
|
+
/**
|
|
657
|
+
* Open the owner-choices screen and await it: "hidden" when the person hid the record for themselves,
|
|
658
|
+
* "closed" otherwise (also when the host has no overlay doors). One screen per record at a time.
|
|
659
|
+
*/
|
|
660
|
+
declare function requestCantRemove(request: CantRemoveRequest): Promise<CantRemoveOutcome>;
|
|
661
|
+
/** Thrown by a removal path that checked first and found the record isn't the person's to remove. */
|
|
662
|
+
declare class RemovalNotYoursError extends Error {
|
|
663
|
+
readonly token: string;
|
|
664
|
+
readonly id: string;
|
|
665
|
+
constructor(token: string, id: string);
|
|
666
|
+
}
|
|
667
|
+
/** True for a check-first refusal or the database's governed refusal. */
|
|
668
|
+
declare function isRemovalNotYours(error: unknown): boolean;
|
|
669
|
+
/**
|
|
670
|
+
* When `error` means "not yours to remove", open the owner-choices screen and
|
|
671
|
+
* answer true (the caller shows nothing else). Otherwise answer false.
|
|
672
|
+
*/
|
|
673
|
+
declare function offerOwnerChoices(error: unknown, request: CantRemoveRequest): boolean;
|
|
674
|
+
/**
|
|
675
|
+
* CHECK FIRST, before any archive/delete confirm: answers true when the person
|
|
676
|
+
* may remove the record (show your confirm), or opens the owner-choices screen
|
|
677
|
+
* and answers false. An unreadable answer is true — the write's own refusal
|
|
678
|
+
* still lands on `offerOwnerChoices`.
|
|
679
|
+
*/
|
|
680
|
+
declare function ensureRemovable(request: CantRemoveRequest): Promise<boolean>;
|
|
681
|
+
|
|
682
|
+
export { ACCESS_REQUESTS_HREF, AccessDenied, type AccessDeniedProps, type AccessDeniedSuggestion, AccessDeniedView, AccessGate, type AccessGateProps, AccessLink, AccessRequestsSurface, type AccessResourceActionRequestAction, type AccessSettingRequestAction, type AccessUi, type AccessUiCurrentUser, type AccessUiEntityInfo, type AccessUiEntityRefProps, type AccessUiEnv, type AccessUiErrorMenuProps, type AccessUiFeedbackInput, type AccessUiLinkProps, AccessUiProvider, type AccessUiRouter, type AccessUiTextareaProps, type AccessUiToast, type AccessUiUserIdentityProps, BlindAccessAsk, CANT_REMOVE_DIALOG, type CantRemoveOutcome, type CantRemoveRequest, ForbiddenSurfaceView, type ForbiddenSurfaceViewProps, GovernedActionDialog, HiddenByMeButton, HiddenFilter, REQUEST_ACCESS_MANUAL_ACTION, RemovalNotYoursError, RequestAccess, RequestAccessPanel, type RequestAccessResult, ResourceActionRequestButtons, ServerReadRecheck, type ServerReadRecheckToken, SettingAccessGate, SettingRequestActionButtons, SlugAccessGate, type SlugAccessGateProps, UnresolvedEntityRef, type UnresolvedEntityRefProps, type UseAccessGateResult, type UseAccessStatesResult, configureAccessUi, ensureRemovable, getAccessUi, isAccessUiConfigured, isRemovalNotYours, offerOwnerChoices, refreshHiddenByMe, requestCantRemove, useAccessGate, useAccessStates, useAccessUi, useHiddenByMe, useHiddenItems, useHideForMe, useRemovalRights, useRequestAccess };
|