@xenosystem/blocks 0.2.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-WE6A2DTY.js +68 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/ops/index.d.ts +2235 -0
- package/dist/ops/index.js +3757 -0
- package/dist/trust/index.d.ts +780 -0
- package/dist/trust/index.js +1003 -0
- package/dist/xterm-R3GIKSHA.js +83 -0
- package/package.json +80 -58
|
@@ -0,0 +1,780 @@
|
|
|
1
|
+
import { PanelModule, PanelManifest } from '@xenosystem/panel-sdk';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The `xeno.core.consent` contract.
|
|
6
|
+
*
|
|
7
|
+
* ## Why nothing in the catalog absorbs this
|
|
8
|
+
*
|
|
9
|
+
* Consent is a **blocking, request-scoped, decision-returning** surface. The Inspector edits values
|
|
10
|
+
* that already exist; the Table shows rows that already exist; this panel exists to *await a
|
|
11
|
+
* decision that does not exist yet* and hand it back. No other archetype models "await a decision",
|
|
12
|
+
* which is why five separate implementations grew independently.
|
|
13
|
+
*
|
|
14
|
+
* ## The three rules that make it a security surface rather than a dialog
|
|
15
|
+
*
|
|
16
|
+
* 1. **A decision must be EXPLICIT.** No default-allow on timeout, dismiss, unmount or navigation.
|
|
17
|
+
* An expiring request resolves as `deny` with `reason: 'expired'`.
|
|
18
|
+
* 2. **The queue is ordered and individually resolvable** — never collapsed into one "allow all".
|
|
19
|
+
* Bulk approval is how consent fatigue becomes a security hole.
|
|
20
|
+
* 3. **Scope and duration are visible IN the decision**, not buried in a settings page. A user who
|
|
21
|
+
* cannot see that they are granting "always" has not granted it.
|
|
22
|
+
*
|
|
23
|
+
* ## The fourth rule, added when the question kinds landed
|
|
24
|
+
*
|
|
25
|
+
* 4. **An answer is not a permission.** `text`, `path` and `choice` are separate KINDS with their
|
|
26
|
+
* own results (`./elicitation`), because rule 1's safe automatic outcome — `deny` — has no
|
|
27
|
+
* meaning for a question. Their safe automatic outcome is *no answer at all*.
|
|
28
|
+
*
|
|
29
|
+
* @module
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/** What is being asked for. Presentation + telemetry; never behaviour. */
|
|
33
|
+
type XenoConsentKind = 'capability' | 'tool' | 'mount' | 'device' | 'credential' | 'network' | 'permission';
|
|
34
|
+
/** How dangerous the grant is. Drives emphasis only — never a default. */
|
|
35
|
+
type XenoConsentRisk = 'low' | 'medium' | 'high';
|
|
36
|
+
/** One thing being granted. Rendered individually so a user sees what they are agreeing to. */
|
|
37
|
+
interface XenoConsentScope {
|
|
38
|
+
/** Stable id. */
|
|
39
|
+
id: string;
|
|
40
|
+
/** Short label ("Read files in ~/Projects"). */
|
|
41
|
+
label: string;
|
|
42
|
+
/** Longer explanation. */
|
|
43
|
+
detail?: string;
|
|
44
|
+
/** Per-scope risk, when it differs from the request's. */
|
|
45
|
+
risk?: XenoConsentRisk;
|
|
46
|
+
/**
|
|
47
|
+
* The scope is already granted and is shown for context.
|
|
48
|
+
*
|
|
49
|
+
* A re-prompt that hides what the user already allowed makes the new ask look bigger than it is.
|
|
50
|
+
*/
|
|
51
|
+
alreadyGranted?: boolean;
|
|
52
|
+
}
|
|
53
|
+
/** How long a decision lasts. **Always visible in the UI that produces it.** */
|
|
54
|
+
type XenoConsentDuration = 'once' | 'session' | 'always';
|
|
55
|
+
/** A pending request. */
|
|
56
|
+
interface XenoConsentRequest {
|
|
57
|
+
/** Stable id — the correlation key for the decision. */
|
|
58
|
+
id: string;
|
|
59
|
+
/**
|
|
60
|
+
* Marks this as a PERMISSION rather than one of the question kinds in `./elicitation`.
|
|
61
|
+
*
|
|
62
|
+
* ⚠️ **Optional, and it must stay optional.** Every request ever written carries no `ask`, and a
|
|
63
|
+
* required discriminant would have invalidated all of them at once. Absent means `'permission'`,
|
|
64
|
+
* which is what this shape has always meant.
|
|
65
|
+
*/
|
|
66
|
+
ask?: 'permission';
|
|
67
|
+
kind: XenoConsentKind;
|
|
68
|
+
/** The question, in the host's words. */
|
|
69
|
+
title: string;
|
|
70
|
+
/** Supporting explanation, rendered verbatim. The panel never composes consent prose. */
|
|
71
|
+
detail?: string;
|
|
72
|
+
/** Who is asking — an agent name, a plugin id, a device name. */
|
|
73
|
+
subject?: string;
|
|
74
|
+
/** What is being asked for. */
|
|
75
|
+
scopes: XenoConsentScope[];
|
|
76
|
+
risk: XenoConsentRisk;
|
|
77
|
+
/** Epoch ms after which the request auto-denies. */
|
|
78
|
+
expiresAt?: number;
|
|
79
|
+
/**
|
|
80
|
+
* Which durations the host will accept.
|
|
81
|
+
*
|
|
82
|
+
* `'forbidden'` means only `once` is offered — a host may refuse to let a high-risk grant be
|
|
83
|
+
* remembered at all, and the panel must not offer what the host will not honour.
|
|
84
|
+
*/
|
|
85
|
+
remember: 'allowed' | 'forbidden';
|
|
86
|
+
/** Epoch ms the request arrived. Queue order. */
|
|
87
|
+
requestedAt: number;
|
|
88
|
+
/** Pre-formatted extra fields (a path, a fingerprint, a safety number). */
|
|
89
|
+
fields?: Record<string, string>;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The answer to a PERMISSION.
|
|
93
|
+
*
|
|
94
|
+
* 🔴 **Frozen shape — do not add an `answer` field here.** Text, path and choice results live in
|
|
95
|
+
* `./elicitation` as a separate type on a separate port, precisely so this one never has to carry
|
|
96
|
+
* a value with no safe default. See that module's header for the full reasoning; the one-line
|
|
97
|
+
* version is *an answer is not a permission*.
|
|
98
|
+
*/
|
|
99
|
+
interface XenoConsentDecision {
|
|
100
|
+
/** Echoes the request id. */
|
|
101
|
+
id: string;
|
|
102
|
+
decision: 'allow' | 'deny';
|
|
103
|
+
/** How long it lasts. Always `'once'` for a deny. */
|
|
104
|
+
scope: XenoConsentDuration;
|
|
105
|
+
/** Epoch ms the grant lapses, when the host computes one. */
|
|
106
|
+
rememberedUntil?: number;
|
|
107
|
+
/** Why, when the panel decided rather than the user. */
|
|
108
|
+
reason?: 'user' | 'expired' | 'superseded' | 'dismissed';
|
|
109
|
+
/** Scope ids the user allowed, when they answered per-scope. Absent ⇒ all of them. */
|
|
110
|
+
grantedScopeIds?: string[];
|
|
111
|
+
}
|
|
112
|
+
/** The panel's serialized state. **Requests are never persisted.** */
|
|
113
|
+
interface ConsentPanelState {
|
|
114
|
+
/** Nothing durable — a pending decision must never survive a reload. */
|
|
115
|
+
readonly _empty?: never;
|
|
116
|
+
}
|
|
117
|
+
/** What the controller exposes to its view. */
|
|
118
|
+
interface ConsentViewState {
|
|
119
|
+
/**
|
|
120
|
+
* The elicitation being answered — the head of the queue.
|
|
121
|
+
*
|
|
122
|
+
* ⚠️ **Widened from `XenoConsentRequest | null` when the question kinds landed.** Narrow with
|
|
123
|
+
* `isPermissionElicitation()` before reading `scopes` or `remember`. Runtime behaviour is
|
|
124
|
+
* unchanged for a host that only ever enqueues permissions: it only ever sees permissions here.
|
|
125
|
+
*/
|
|
126
|
+
current: XenoElicitation | null;
|
|
127
|
+
/** Everything else waiting, in arrival order. */
|
|
128
|
+
queued: XenoElicitation[];
|
|
129
|
+
/** Total pending. */
|
|
130
|
+
pending: number;
|
|
131
|
+
/** Scope ids the user has ticked for the current request. Empty for a non-permission head. */
|
|
132
|
+
selectedScopeIds: string[];
|
|
133
|
+
/** The duration the user has chosen for the current request. `'once'` for a non-permission head. */
|
|
134
|
+
duration: XenoConsentDuration;
|
|
135
|
+
/** ms until the current request expires, or `null` when it does not. */
|
|
136
|
+
expiresInMs: number | null;
|
|
137
|
+
/**
|
|
138
|
+
* What the user has typed for a text/path/choice head. `''` for a permission.
|
|
139
|
+
*
|
|
140
|
+
* ⚠️ **A draft is not an answer.** Nothing is emitted from this field until `submit()` runs, and
|
|
141
|
+
* every path that settles a question without `submit()` discards it — which is why expiry cannot
|
|
142
|
+
* leak a half-typed value.
|
|
143
|
+
*/
|
|
144
|
+
draftText: string;
|
|
145
|
+
/** The option the user has highlighted, or `null`. Highlighting is not answering. */
|
|
146
|
+
selectedOptionId: string | null;
|
|
147
|
+
/**
|
|
148
|
+
* Would `submit()` succeed right now?
|
|
149
|
+
*
|
|
150
|
+
* Drives the button's `disabled` state. Computed from the SAME predicate `submit()` enforces, so
|
|
151
|
+
* an enabled button cannot be a button that refuses — the affordance and the guard can never
|
|
152
|
+
* disagree.
|
|
153
|
+
*/
|
|
154
|
+
canSubmit: boolean;
|
|
155
|
+
}
|
|
156
|
+
/** Durations a request permits, in escalating order. */
|
|
157
|
+
declare function allowedDurations(request: XenoConsentRequest): XenoConsentDuration[];
|
|
158
|
+
/** The scopes a user can actually toggle (already-granted ones are context, not choices). */
|
|
159
|
+
declare function selectableScopes(request: XenoConsentRequest): XenoConsentScope[];
|
|
160
|
+
/**
|
|
161
|
+
* The highest risk present, so the view can emphasize honestly.
|
|
162
|
+
*
|
|
163
|
+
* ⚠️ Takes any elicitation, not only a permission: a question carries `risk` too, and one without
|
|
164
|
+
* `scopes` simply has nothing to raise it.
|
|
165
|
+
*/
|
|
166
|
+
declare function effectiveRisk(request: XenoElicitation): XenoConsentRisk;
|
|
167
|
+
/** Has this request passed its expiry? */
|
|
168
|
+
declare function isExpired(request: XenoElicitation, now: number): boolean;
|
|
169
|
+
/**
|
|
170
|
+
* Is this actually a consent request?
|
|
171
|
+
*
|
|
172
|
+
* **Why this guard exists.** `xeno.core.tree` emits an op called `consentRequest` whose payload is
|
|
173
|
+
* `{id, reason}` — a *notification that consent is needed*, not a request this panel can render.
|
|
174
|
+
* Both ports are `object`, and tree's is deliberately untagged, so one-sided tags pass and a host
|
|
175
|
+
* can legitimately wire the two together. Before this guard that wire threw
|
|
176
|
+
* `Cannot read properties of undefined (reading 'filter')` on the first delivery.
|
|
177
|
+
*
|
|
178
|
+
* A consent surface that crashes is a consent surface that grants nothing and blocks everything, so
|
|
179
|
+
* the panel refuses the payload instead — visibly, and without taking the host down with it.
|
|
180
|
+
*
|
|
181
|
+
* @param value - A candidate payload.
|
|
182
|
+
* @returns Whether it carries the fields the panel needs to ask the question.
|
|
183
|
+
*/
|
|
184
|
+
declare function isConsentRequest(value: unknown): value is XenoConsentRequest;
|
|
185
|
+
/** A deny produced by the panel rather than the user. */
|
|
186
|
+
declare function autoDeny(request: XenoConsentRequest, reason: XenoConsentDecision['reason']): XenoConsentDecision;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Elicitation — the general case, of which a permission is one kind.
|
|
190
|
+
*
|
|
191
|
+
* ## Why this module exists, and why widening {@link XenoConsentDecision} would have been wrong
|
|
192
|
+
*
|
|
193
|
+
* The panel's charter is *"await a decision that does not exist yet and hand it back"*. A
|
|
194
|
+
* permission is one shape of that; it is not the only one. Real producers need three more, and
|
|
195
|
+
* **none of them has an allow/deny axis**:
|
|
196
|
+
*
|
|
197
|
+
* | ask | the question | the answer |
|
|
198
|
+
* |---|---|---|
|
|
199
|
+
* | `permission` | "may I read your project files?" | allow / deny, with scopes and a duration |
|
|
200
|
+
* | `text` | "what should I name it?" | a string |
|
|
201
|
+
* | `path` | "which directory?" | a filesystem path |
|
|
202
|
+
* | `choice` | "inbox, reuse, or new?" | one option, sometimes plus a name |
|
|
203
|
+
*
|
|
204
|
+
* 🔴 **The trap, stated plainly.** The obvious fix — widen `decision` to a string, or bolt
|
|
205
|
+
* `answer?: string` onto {@link XenoConsentDecision} — destroys this panel's stated security
|
|
206
|
+
* invariant, *"there is no path in this module that produces `allow` without a user action"*. It
|
|
207
|
+
* destroys it because a free-text answer has no safe value. `autoDeny`'s whole job is to return
|
|
208
|
+
* the SAFE member of a two-member set; a string has no safe member. `''` is not safety, it is an
|
|
209
|
+
* answer that happens to be empty, and a host cannot tell the two apart.
|
|
210
|
+
*
|
|
211
|
+
* ✅ **The framing that survives: an answer is not a permission.** They are different KINDS with
|
|
212
|
+
* different safe defaults. The safe automatic outcome for a permission is `deny`. The safe
|
|
213
|
+
* automatic outcome for a question is **not an answer at all** — and
|
|
214
|
+
* {@link XenoElicitationCancelled} makes that structural rather than conventional: it carries
|
|
215
|
+
* `answered: false` and **has no `value` key in the type**. There is no string for the panel to
|
|
216
|
+
* invent, because there is nowhere to put one.
|
|
217
|
+
*
|
|
218
|
+
* The invariant therefore GENERALISES rather than breaking:
|
|
219
|
+
*
|
|
220
|
+
* > **No path in this module produces a value the host can act on without a user action — for any
|
|
221
|
+
* > kind.** Permissions auto-deny; questions auto-cancel; nothing auto-answers.
|
|
222
|
+
*
|
|
223
|
+
* ⚠️ **Two ports, not one widened port.** Permission answers keep flowing out of `decision`
|
|
224
|
+
* (`xeno.consentdecision@1`) in the exact shape they always had; elicitation results flow out of
|
|
225
|
+
* `result` (`xeno.elicitationresult@1`). Two shapes under one tag is precisely the failure the
|
|
226
|
+
* SDK's tagging system was introduced to prevent (see `WELL_KNOWN_PORT_SCHEMAS.QUERY_REQUEST`, and
|
|
227
|
+
* the two-shapes-one-tag incident recorded there). The split also makes the emission a
|
|
228
|
+
* **partition**: a permission never appears on `result`, so no host can apply one grant twice by
|
|
229
|
+
* wiring both ports.
|
|
230
|
+
*
|
|
231
|
+
* @module
|
|
232
|
+
*/
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* The shape tag for the `elicitations` input port.
|
|
236
|
+
*
|
|
237
|
+
* ⚠️ **Coined here, and it belongs in `WELL_KNOWN_PORT_SCHEMAS`.** That registry is the right home
|
|
238
|
+
* for the same reason MIME types are registered centrally — a tag is only useful if two
|
|
239
|
+
* independently built panels spell it identically — and the SDK's own docblock says a third party
|
|
240
|
+
* may coin its own. Promoting it is a `@xenosystem/panel-sdk` edit and therefore a coordinated
|
|
241
|
+
* cross-package change; the STRING must not change when it moves, or every wire built against it
|
|
242
|
+
* silently stops attaching.
|
|
243
|
+
*/
|
|
244
|
+
declare const ELICITATION_SCHEMA = "xeno.elicitation@1";
|
|
245
|
+
/** The shape tag for the `result` output port. Same registration note as {@link ELICITATION_SCHEMA}. */
|
|
246
|
+
declare const ELICITATION_RESULT_SCHEMA = "xeno.elicitationresult@1";
|
|
247
|
+
/**
|
|
248
|
+
* What kind of answer the host needs. Absent means `'permission'`.
|
|
249
|
+
*
|
|
250
|
+
* ⚠️ **Optional on the wire, deliberately.** Every request written before this vocabulary existed
|
|
251
|
+
* carries no `ask` and must keep meaning exactly what it meant. A required discriminant would have
|
|
252
|
+
* invalidated every existing producer overnight.
|
|
253
|
+
*/
|
|
254
|
+
type XenoElicitationAsk = 'permission' | 'text' | 'path' | 'choice';
|
|
255
|
+
/**
|
|
256
|
+
* One offered answer.
|
|
257
|
+
*
|
|
258
|
+
* The request side had no way to say "here are the options". A permission's `scopes` are things
|
|
259
|
+
* being GRANTED, not alternatives being CHOSEN BETWEEN — rendering one as the other would ask the
|
|
260
|
+
* user to tick three boxes when exactly one may be true.
|
|
261
|
+
*/
|
|
262
|
+
interface XenoElicitationOption {
|
|
263
|
+
/** Stable id — what comes back in the answer. */
|
|
264
|
+
id: string;
|
|
265
|
+
/** Short label ("Reuse the existing workspace"). */
|
|
266
|
+
label: string;
|
|
267
|
+
/** Longer explanation. */
|
|
268
|
+
detail?: string;
|
|
269
|
+
/** Per-option risk, so "create a new one" can read louder than "reuse". */
|
|
270
|
+
risk?: XenoConsentRisk;
|
|
271
|
+
/**
|
|
272
|
+
* Choosing this option is not a complete answer — it also needs a value ("…and call it what?").
|
|
273
|
+
*
|
|
274
|
+
* This is the `workspace-placement` shape exactly: two options answer on their own, the third
|
|
275
|
+
* needs a name. The panel refuses to submit a `requiresText` option with an empty value, for the
|
|
276
|
+
* same reason it refuses an allow that would grant nothing — a control that reports success while
|
|
277
|
+
* carrying nothing is a control that lies about what it did.
|
|
278
|
+
*/
|
|
279
|
+
requiresText?: boolean;
|
|
280
|
+
}
|
|
281
|
+
/** What every elicitation carries, whatever kind it is. */
|
|
282
|
+
interface XenoElicitationBase {
|
|
283
|
+
/** Stable id — the correlation key for the result. */
|
|
284
|
+
id: string;
|
|
285
|
+
/** The question, in the host's words. */
|
|
286
|
+
title: string;
|
|
287
|
+
/** Supporting explanation, rendered verbatim. The panel never composes prose. */
|
|
288
|
+
detail?: string;
|
|
289
|
+
/** Who is asking — an agent name, a plugin id, a device name. */
|
|
290
|
+
subject?: string;
|
|
291
|
+
/** How dangerous answering is. Drives emphasis only — never a default. */
|
|
292
|
+
risk: XenoConsentRisk;
|
|
293
|
+
/** Epoch ms the request arrived. Queue order. */
|
|
294
|
+
requestedAt: number;
|
|
295
|
+
/** Epoch ms after which the request settles itself. **Never as an answer.** */
|
|
296
|
+
expiresAt?: number;
|
|
297
|
+
/** Pre-formatted extra fields (a path, a fingerprint, a safety number). */
|
|
298
|
+
fields?: Record<string, string>;
|
|
299
|
+
/** Presentation + telemetry only, and only meaningful for a permission. */
|
|
300
|
+
kind?: XenoConsentKind;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* "What should I call it?" — an answer string.
|
|
304
|
+
*
|
|
305
|
+
* `options` are shortcuts, not a closed set: picking one fills the field and the user may still
|
|
306
|
+
* edit it. When the set IS closed, that is a {@link XenoChoiceElicitation}.
|
|
307
|
+
*/
|
|
308
|
+
interface XenoTextElicitation extends XenoElicitationBase {
|
|
309
|
+
ask: 'text';
|
|
310
|
+
/** Prefilled value. **Prefilling is not answering** — nothing is emitted until the user submits. */
|
|
311
|
+
initialValue?: string;
|
|
312
|
+
placeholder?: string;
|
|
313
|
+
/** Render a multi-line field. */
|
|
314
|
+
multiline?: boolean;
|
|
315
|
+
/** Submission is REFUSED above this length rather than silently truncated. */
|
|
316
|
+
maxLength?: number;
|
|
317
|
+
/** Suggested values. Selecting one fills the field. */
|
|
318
|
+
options?: XenoElicitationOption[];
|
|
319
|
+
/**
|
|
320
|
+
* May the user submit an empty string?
|
|
321
|
+
*
|
|
322
|
+
* Default `false`. ⚠️ A host that genuinely accepts `''` must say so, because the panel cannot
|
|
323
|
+
* distinguish "the user meant blank" from "the user pressed the button before typing" — and
|
|
324
|
+
* defaulting to the permissive reading is how a blank name reaches a filesystem.
|
|
325
|
+
*/
|
|
326
|
+
allowEmpty?: boolean;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* "Which directory?" — a filesystem path.
|
|
330
|
+
*
|
|
331
|
+
* 🔴 **The panel does not browse the filesystem, and must not.** Its manifest declares
|
|
332
|
+
* `storage.local` and nothing else; a consent surface able to enumerate the disk would BE the
|
|
333
|
+
* capability it exists to gate. So the host supplies candidates in `options` and the user may type
|
|
334
|
+
* a path. A host that wants a native picker owns that dialog itself and delivers the chosen path
|
|
335
|
+
* as `initialValue` on a fresh elicitation.
|
|
336
|
+
*
|
|
337
|
+
* ⚠️ **The panel does not validate the path either** — it cannot stat, and a syntactic check that
|
|
338
|
+
* happily passes a nonexistent directory is worse than none because it reads like verification.
|
|
339
|
+
* `mustExist` is a statement of intent the HOST enforces when it receives the answer.
|
|
340
|
+
*/
|
|
341
|
+
interface XenoPathElicitation extends XenoElicitationBase {
|
|
342
|
+
ask: 'path';
|
|
343
|
+
/** What the host will do with it. Presentation plus host-side validation; the panel cannot check. */
|
|
344
|
+
pathKind: 'file' | 'directory';
|
|
345
|
+
/** Prefilled path. */
|
|
346
|
+
initialValue?: string;
|
|
347
|
+
placeholder?: string;
|
|
348
|
+
/** Host-supplied candidates (recent folders, the project root, a default location). */
|
|
349
|
+
options?: XenoElicitationOption[];
|
|
350
|
+
/** The host will reject a path that does not exist. Declared for the user's benefit. */
|
|
351
|
+
mustExist?: boolean;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* "Inbox, reuse, or new?" — exactly one of a closed set, sometimes plus a value.
|
|
355
|
+
*
|
|
356
|
+
* ⚠️ **Nothing is selected unless the host asks for it**, and even then `defaultOptionId` only
|
|
357
|
+
* pre-highlights. A pre-selected option that could be auto-submitted would be a default answer,
|
|
358
|
+
* which is the thing this module refuses to produce; since expiry cancels rather than submits, a
|
|
359
|
+
* highlight is only ever a starting point.
|
|
360
|
+
*/
|
|
361
|
+
interface XenoChoiceElicitation extends XenoElicitationBase {
|
|
362
|
+
ask: 'choice';
|
|
363
|
+
/** The alternatives, in display order. At least one — a choice of nothing is not a question. */
|
|
364
|
+
options: XenoElicitationOption[];
|
|
365
|
+
/** Pre-highlighted option. **Highlighting is not answering.** */
|
|
366
|
+
defaultOptionId?: string;
|
|
367
|
+
/** Label for the value field an option may require ("Name"). */
|
|
368
|
+
textLabel?: string;
|
|
369
|
+
placeholder?: string;
|
|
370
|
+
/** Submission is refused above this length. */
|
|
371
|
+
maxLength?: number;
|
|
372
|
+
}
|
|
373
|
+
/** Anything the panel can put in front of a user. */
|
|
374
|
+
type XenoElicitation = XenoConsentRequest | XenoTextElicitation | XenoPathElicitation | XenoChoiceElicitation;
|
|
375
|
+
/** Every elicitation that is NOT a permission — the three that needed a new answer vocabulary. */
|
|
376
|
+
type XenoQuestionElicitation = XenoTextElicitation | XenoPathElicitation | XenoChoiceElicitation;
|
|
377
|
+
/**
|
|
378
|
+
* A text answer the user actually gave.
|
|
379
|
+
*
|
|
380
|
+
* 🔴 **`reason` is the literal `'user'`, by type.** Every other producer of a result in this module
|
|
381
|
+
* returns {@link XenoElicitationCancelled}, so "an answer exists" and "a user acted" become the
|
|
382
|
+
* same statement — checkable by the compiler rather than by reading the controller.
|
|
383
|
+
*/
|
|
384
|
+
interface XenoTextAnswer {
|
|
385
|
+
id: string;
|
|
386
|
+
ask: 'text';
|
|
387
|
+
answered: true;
|
|
388
|
+
reason: 'user';
|
|
389
|
+
/** What the user typed. Non-empty unless the request set `allowEmpty`. */
|
|
390
|
+
value: string;
|
|
391
|
+
/** The suggestion they started from, when they picked one. */
|
|
392
|
+
optionId?: string;
|
|
393
|
+
}
|
|
394
|
+
/** A path the user supplied. */
|
|
395
|
+
interface XenoPathAnswer {
|
|
396
|
+
id: string;
|
|
397
|
+
ask: 'path';
|
|
398
|
+
answered: true;
|
|
399
|
+
reason: 'user';
|
|
400
|
+
/** The path, verbatim and unvalidated — see {@link XenoPathElicitation}. */
|
|
401
|
+
value: string;
|
|
402
|
+
/** The candidate they started from, when they picked one. */
|
|
403
|
+
optionId?: string;
|
|
404
|
+
}
|
|
405
|
+
/** The option the user chose. */
|
|
406
|
+
interface XenoChoiceAnswer {
|
|
407
|
+
id: string;
|
|
408
|
+
ask: 'choice';
|
|
409
|
+
answered: true;
|
|
410
|
+
reason: 'user';
|
|
411
|
+
/** Which option. */
|
|
412
|
+
optionId: string;
|
|
413
|
+
/** The accompanying value, when the chosen option required one. */
|
|
414
|
+
value?: string;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* The question settled without an answer.
|
|
418
|
+
*
|
|
419
|
+
* 🔴 **This type has no `value` key, and that ABSENCE is the security property.** It is not "an
|
|
420
|
+
* empty answer" or "a safe default" — there is structurally nowhere for the panel to put a string
|
|
421
|
+
* it was never given, so no future edit can quietly make the timeout path produce one. A consumer
|
|
422
|
+
* must narrow on `answered` before it can read anything, which is the same discipline
|
|
423
|
+
* `decision === 'allow'` imposes on the permission side.
|
|
424
|
+
*/
|
|
425
|
+
interface XenoElicitationCancelled {
|
|
426
|
+
id: string;
|
|
427
|
+
/** Which kind of question went unanswered. Never `'permission'` — those settle as a deny. */
|
|
428
|
+
ask: 'text' | 'path' | 'choice';
|
|
429
|
+
answered: false;
|
|
430
|
+
/** Why the panel settled it. There is no `'user'`: a user who answers produces an answer. */
|
|
431
|
+
reason: 'expired' | 'superseded' | 'dismissed';
|
|
432
|
+
}
|
|
433
|
+
/** An answer, as opposed to a cancellation. */
|
|
434
|
+
type XenoElicitationAnswer = XenoTextAnswer | XenoPathAnswer | XenoChoiceAnswer;
|
|
435
|
+
/**
|
|
436
|
+
* What leaves the `result` port.
|
|
437
|
+
*
|
|
438
|
+
* ⚠️ **`XenoConsentDecision` is deliberately NOT a member.** Permission answers leave through
|
|
439
|
+
* `decision` in their original shape and never appear here, so the two ports PARTITION the
|
|
440
|
+
* outcomes instead of overlapping. A host may wire both and can never receive one grant twice.
|
|
441
|
+
*/
|
|
442
|
+
type XenoElicitationResult = XenoElicitationAnswer | XenoElicitationCancelled;
|
|
443
|
+
/** Which kind of question is this? A request with no `ask` is a permission, as it always was. */
|
|
444
|
+
declare function elicitationAsk(value: XenoElicitation): XenoElicitationAsk;
|
|
445
|
+
/** Is this a permission request rather than a question? */
|
|
446
|
+
declare function isPermissionElicitation(value: XenoElicitation): value is XenoConsentRequest;
|
|
447
|
+
/** The options a question offers, or `[]` when it offers none. */
|
|
448
|
+
declare function elicitationOptions(value: XenoElicitation): XenoElicitationOption[];
|
|
449
|
+
/** The option with this id, or `undefined`. */
|
|
450
|
+
declare function findOption(value: XenoElicitation, optionId: string | null): XenoElicitationOption | undefined;
|
|
451
|
+
/** The longest value this question accepts, or `null` for unbounded. */
|
|
452
|
+
declare function maxTextLength(value: XenoElicitation): number | null;
|
|
453
|
+
/**
|
|
454
|
+
* Is this a question the panel can render and answer?
|
|
455
|
+
*
|
|
456
|
+
* Same job as `isConsentRequest` and the same reason: a wire is a user-editable connection, so the
|
|
457
|
+
* wrong shape arriving is a normal mis-wire, not an exceptional condition. A surface that crashes
|
|
458
|
+
* on one grants nothing and blocks everything queued behind it.
|
|
459
|
+
*/
|
|
460
|
+
declare function isElicitation(value: unknown): value is XenoElicitation;
|
|
461
|
+
/** Is this an answer the user gave? */
|
|
462
|
+
declare function isElicitationAnswer(value: unknown): value is XenoElicitationAnswer;
|
|
463
|
+
/** Is this a question that settled without an answer? */
|
|
464
|
+
declare function isElicitationCancelled(value: unknown): value is XenoElicitationCancelled;
|
|
465
|
+
/**
|
|
466
|
+
* A question settled by the panel rather than by the user.
|
|
467
|
+
*
|
|
468
|
+
* 🔴 The counterpart of `autoDeny`, and the reason the security invariant survived this feature:
|
|
469
|
+
* it **cannot** return an answer. {@link XenoElicitationCancelled} has no `value` field, so there
|
|
470
|
+
* is no edit to this function that quietly starts producing one.
|
|
471
|
+
*
|
|
472
|
+
* @param request - The question being abandoned.
|
|
473
|
+
* @param reason - Why the panel settled it. Never `'user'`.
|
|
474
|
+
* @returns A cancellation carrying no answer.
|
|
475
|
+
*/
|
|
476
|
+
declare function autoCancel(request: XenoElicitation, reason: XenoElicitationCancelled['reason']): XenoElicitationCancelled;
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* The Consent controller — ONE ordered queue of things the user has not answered yet.
|
|
480
|
+
*
|
|
481
|
+
* **The panel grants nothing and answers nothing.** It emits and the host enforces.
|
|
482
|
+
*
|
|
483
|
+
* 🔴 **The invariant, in its general form.** *No path in this controller produces a value the host
|
|
484
|
+
* can act on without a user action.* Concretely:
|
|
485
|
+
*
|
|
486
|
+
* | path | permission head | question head |
|
|
487
|
+
* |---|---|---|
|
|
488
|
+
* | user acted | `allow()` / `deny()` | `submit()` |
|
|
489
|
+
* | timeout, withdrawal, dismissal, disposal | `autoDeny` ⇒ `deny` | `autoCancel` ⇒ `answered:false` |
|
|
490
|
+
*
|
|
491
|
+
* The right-hand column is what makes the invariant survive free text: a cancellation is not a safe
|
|
492
|
+
* answer, it is the ABSENCE of one, and `XenoElicitationCancelled` has no field to put a value in.
|
|
493
|
+
*
|
|
494
|
+
* ⚠️ **One queue, not two.** A text question and a permission ask both block the same user; two
|
|
495
|
+
* independent queues would put two prompts on screen at once and lose arrival order between them.
|
|
496
|
+
*
|
|
497
|
+
* @module
|
|
498
|
+
*/
|
|
499
|
+
|
|
500
|
+
/** The host seam. */
|
|
501
|
+
interface ConsentHostBridge {
|
|
502
|
+
emit(portId: string, value: unknown): void;
|
|
503
|
+
now?: () => number;
|
|
504
|
+
setTimer?: (fn: () => void, ms: number) => unknown;
|
|
505
|
+
clearTimer?: (handle: unknown) => void;
|
|
506
|
+
}
|
|
507
|
+
/** Construction options. */
|
|
508
|
+
interface ConsentControllerOptions {
|
|
509
|
+
host: ConsentHostBridge;
|
|
510
|
+
/** Per-scope checkboxes rather than one all-or-nothing answer. Default `true`. */
|
|
511
|
+
perScope?: boolean;
|
|
512
|
+
/** The duration pre-selected when a request permits more than one. Default `'once'`. */
|
|
513
|
+
defaultDuration?: XenoConsentDuration;
|
|
514
|
+
}
|
|
515
|
+
/** The Consent panel controller. */
|
|
516
|
+
declare class ConsentController {
|
|
517
|
+
private readonly host;
|
|
518
|
+
private readonly listeners;
|
|
519
|
+
private readonly now;
|
|
520
|
+
private readonly setTimer;
|
|
521
|
+
private readonly clearTimer;
|
|
522
|
+
private readonly perScope;
|
|
523
|
+
private readonly defaultDuration;
|
|
524
|
+
private queue;
|
|
525
|
+
private selected;
|
|
526
|
+
private duration;
|
|
527
|
+
/** What the user has typed for the head question. Discarded whenever the head changes. */
|
|
528
|
+
private draftText;
|
|
529
|
+
/** The option highlighted for the head question. Highlighting is not answering. */
|
|
530
|
+
private selectedOptionId;
|
|
531
|
+
private expiryTimer;
|
|
532
|
+
/** Ids already decided, so a re-delivery cannot produce a second decision. */
|
|
533
|
+
private readonly resolved;
|
|
534
|
+
private snapshot;
|
|
535
|
+
constructor(options: ConsentControllerOptions);
|
|
536
|
+
subscribe: (listener: () => void) => (() => void);
|
|
537
|
+
getState: () => ConsentViewState;
|
|
538
|
+
private notify;
|
|
539
|
+
/**
|
|
540
|
+
* Enqueue a request.
|
|
541
|
+
*
|
|
542
|
+
* Requests are **ordered and individually resolvable**. They are never merged, never deduplicated
|
|
543
|
+
* into one "allow all", and a second request does not supersede the first — bulk approval is how
|
|
544
|
+
* consent fatigue turns into a security hole.
|
|
545
|
+
*
|
|
546
|
+
* @returns `true` if enqueued; `false` if already pending or already decided.
|
|
547
|
+
*/
|
|
548
|
+
enqueue(request: XenoElicitation): boolean;
|
|
549
|
+
/** Enqueue several. */
|
|
550
|
+
enqueueMany(requests: readonly XenoElicitation[]): number;
|
|
551
|
+
/**
|
|
552
|
+
* Withdraw a request the host no longer needs an answer to.
|
|
553
|
+
*
|
|
554
|
+
* Emits `superseded` so the host's awaiting promise resolves — a withdrawn request that resolves
|
|
555
|
+
* nothing leaves the caller hanging forever.
|
|
556
|
+
*/
|
|
557
|
+
withdraw(id: string): boolean;
|
|
558
|
+
/**
|
|
559
|
+
* Selection state for the head of the queue.
|
|
560
|
+
*
|
|
561
|
+
* 🔴 **Called on every head change, and that is what stops a draft leaking between questions.**
|
|
562
|
+
* A name typed for one ask must never arrive as the answer to the next one — the ids differ, so
|
|
563
|
+
* the host would attribute it to a question the user never saw.
|
|
564
|
+
*/
|
|
565
|
+
private resetSelection;
|
|
566
|
+
/**
|
|
567
|
+
* Arm a timer for the head request's expiry.
|
|
568
|
+
*
|
|
569
|
+
* **Expiry denies.** There is no configuration that makes a timeout allow — a grant nobody was
|
|
570
|
+
* present to give is not a grant.
|
|
571
|
+
*/
|
|
572
|
+
private armExpiry;
|
|
573
|
+
/**
|
|
574
|
+
* Settle every request whose deadline has passed — permissions as a deny, questions as a
|
|
575
|
+
* cancellation.
|
|
576
|
+
*
|
|
577
|
+
* Sweeps the WHOLE queue, not just the head: a request waiting behind a slow decision can expire
|
|
578
|
+
* while it is still queued, and showing it afterwards would ask for an answer nobody can use.
|
|
579
|
+
*
|
|
580
|
+
* @returns How many expired.
|
|
581
|
+
*/
|
|
582
|
+
expireDue(): number;
|
|
583
|
+
/** Toggle one scope of the current request. Refused when the head is not a permission. */
|
|
584
|
+
toggleScope(scopeId: string, selected?: boolean): boolean;
|
|
585
|
+
/** Choose how long the grant lasts. Refuses a duration the request forbids. */
|
|
586
|
+
setDuration(duration: XenoConsentDuration): boolean;
|
|
587
|
+
/**
|
|
588
|
+
* Record what the user has typed.
|
|
589
|
+
*
|
|
590
|
+
* ⚠️ **A draft is not an answer and never leaves the panel on its own.** Only `submit()` emits,
|
|
591
|
+
* and every other settlement path discards this — so a half-typed name cannot escape through a
|
|
592
|
+
* timeout.
|
|
593
|
+
*
|
|
594
|
+
* @param value - The current field contents.
|
|
595
|
+
* @returns `true` when the head is a question that takes a value.
|
|
596
|
+
*/
|
|
597
|
+
setDraftText(value: string): boolean;
|
|
598
|
+
/**
|
|
599
|
+
* Highlight one of the offered options.
|
|
600
|
+
*
|
|
601
|
+
* For a `choice` this IS the answer-in-progress; for `text`/`path` it is a shortcut that also
|
|
602
|
+
* fills the field. Either way nothing is emitted — highlighting is not answering.
|
|
603
|
+
*
|
|
604
|
+
* @param optionId - The option, or `null` to clear.
|
|
605
|
+
* @returns `true` when the option belongs to the current question.
|
|
606
|
+
*/
|
|
607
|
+
selectOption(optionId: string | null): boolean;
|
|
608
|
+
/**
|
|
609
|
+
* Allow the current request.
|
|
610
|
+
*
|
|
611
|
+
* The ONLY path in this controller that produces `decision: 'allow'`, and it requires a call from
|
|
612
|
+
* a user gesture. Refuses when nothing is selected — an "Allow" that grants nothing is a button
|
|
613
|
+
* that lies about what it did.
|
|
614
|
+
*/
|
|
615
|
+
allow(): boolean;
|
|
616
|
+
/**
|
|
617
|
+
* Deny the current request.
|
|
618
|
+
*
|
|
619
|
+
* ⚠️ **Permissions only.** "Deny" is a permission verb; a question is refused with
|
|
620
|
+
* {@link ConsentController.dismiss}, which is what `cancel_request` calls. Returning `false`
|
|
621
|
+
* here rather than quietly cancelling keeps the two refusals distinguishable to a caller.
|
|
622
|
+
*/
|
|
623
|
+
deny(): boolean;
|
|
624
|
+
/**
|
|
625
|
+
* Submit the answer to the current QUESTION.
|
|
626
|
+
*
|
|
627
|
+
* 🔴 **The only path in this controller that produces an answer**, and the exact counterpart of
|
|
628
|
+
* `allow()`: it requires a user gesture and it refuses to emit something the user did not
|
|
629
|
+
* supply. An empty text answer, a choice with nothing chosen, or a `requiresText` option with no
|
|
630
|
+
* value are all refusals — for the same reason `allow()` refuses to grant nothing. A control
|
|
631
|
+
* that reports success while carrying nothing is a control that lies about what it did.
|
|
632
|
+
*
|
|
633
|
+
* @returns `true` when an answer was emitted.
|
|
634
|
+
*/
|
|
635
|
+
submit(): boolean;
|
|
636
|
+
/**
|
|
637
|
+
* The user dismissed the prompt without answering.
|
|
638
|
+
*
|
|
639
|
+
* Resolves as **deny** for a permission and as a **cancellation** for a question — never as an
|
|
640
|
+
* allow, never as an answer, and never as "ask again later": an unanswered request that stays
|
|
641
|
+
* pending forever blocks the host that is awaiting it.
|
|
642
|
+
*/
|
|
643
|
+
dismiss(): boolean;
|
|
644
|
+
/** The head of the queue when it is a permission. */
|
|
645
|
+
private currentPermission;
|
|
646
|
+
/** The head of the queue when it is a question rather than a permission. */
|
|
647
|
+
private currentQuestion;
|
|
648
|
+
/**
|
|
649
|
+
* The answer the current draft would produce, or `null` when it would produce none.
|
|
650
|
+
*
|
|
651
|
+
* ⚠️ **Pure, and shared with `getState().canSubmit`.** One predicate drives both the guard and
|
|
652
|
+
* the button's enabled state, so an enabled button cannot be a button that refuses — the class of
|
|
653
|
+
* bug where an affordance and its guard disagree has no representation here.
|
|
654
|
+
*/
|
|
655
|
+
private answerFor;
|
|
656
|
+
/**
|
|
657
|
+
* Settle a request the user did not answer.
|
|
658
|
+
*
|
|
659
|
+
* 🔴 **The generalised safe default lives here, and it is kind-dependent BY NECESSITY.** A
|
|
660
|
+
* permission's safe outcome is `deny`; a question's safe outcome is the absence of an answer.
|
|
661
|
+
* There is no third branch and no configuration that reaches one.
|
|
662
|
+
*/
|
|
663
|
+
private settleUnanswered;
|
|
664
|
+
private resolveHead;
|
|
665
|
+
/**
|
|
666
|
+
* Emit one resolution, on exactly one port.
|
|
667
|
+
*
|
|
668
|
+
* ⚠️ **The two output ports PARTITION the outcomes** — a permission answer only ever appears on
|
|
669
|
+
* `decision`, a question result only ever on `result`. Emitting a permission on both would let a
|
|
670
|
+
* host that wired both ports apply the same grant twice, which on a security surface is a defect
|
|
671
|
+
* to design out rather than to document.
|
|
672
|
+
*/
|
|
673
|
+
private emitResolution;
|
|
674
|
+
/** Has this request already been answered? */
|
|
675
|
+
isResolved(id: string): boolean;
|
|
676
|
+
/**
|
|
677
|
+
* Pending requests, in order.
|
|
678
|
+
*
|
|
679
|
+
* ⚠️ Widened from `XenoConsentRequest[]` with the question kinds. A host that only enqueues
|
|
680
|
+
* permissions only ever gets permissions back; narrow with `isPermissionElicitation()`.
|
|
681
|
+
*/
|
|
682
|
+
pending(): XenoElicitation[];
|
|
683
|
+
/**
|
|
684
|
+
* Serialize — deliberately empty.
|
|
685
|
+
*
|
|
686
|
+
* **A pending decision must never survive a reload.** Restoring one would show a prompt whose
|
|
687
|
+
* host is long gone, and any answer would resolve nothing.
|
|
688
|
+
*/
|
|
689
|
+
serialize(): Record<string, never>;
|
|
690
|
+
/** Restore — a no-op, for the same reason. */
|
|
691
|
+
deserialize(): void;
|
|
692
|
+
/**
|
|
693
|
+
* Tear down.
|
|
694
|
+
*
|
|
695
|
+
* Every still-pending request is settled — permissions **denied**, questions **cancelled** — so a
|
|
696
|
+
* host awaiting one is never left hanging by a panel that simply vanished.
|
|
697
|
+
*
|
|
698
|
+
* 🔴 **Disposal is not the same event as unmounting, and the difference matters for a question.**
|
|
699
|
+
* `render()`/`unrender()` only move the DOM; the controller and its queue outlive them, so
|
|
700
|
+
* hiding a panel tab does NOT answer or cancel anything and a half-typed draft survives being
|
|
701
|
+
* hidden. `dispose()` is the panel going away for good, and THAT is what settles the queue. A
|
|
702
|
+
* host that tears the panel down mid-question receives `answered: false` with reason
|
|
703
|
+
* `'dismissed'` — never an empty string, which it could not distinguish from a deliberate blank.
|
|
704
|
+
*/
|
|
705
|
+
dispose(): void;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* The `PanelModule` — view wired inside the package (one React copy).
|
|
710
|
+
*
|
|
711
|
+
* @module
|
|
712
|
+
*/
|
|
713
|
+
|
|
714
|
+
/** Everything a renderer needs: the controller plus the resolved config. */
|
|
715
|
+
interface ConsentRenderContext {
|
|
716
|
+
controller: ConsentController;
|
|
717
|
+
config: {
|
|
718
|
+
showQueueCount: boolean;
|
|
719
|
+
emptyHint?: string;
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
/** Options for {@link createConsentPanel}. */
|
|
723
|
+
interface CreateConsentPanelOptions {
|
|
724
|
+
/** Override the view. Rarely needed — mounting inside the package keeps React singular. */
|
|
725
|
+
render?: (root: HTMLElement, context: ConsentRenderContext) => () => void;
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Build the Consent panel module.
|
|
729
|
+
*
|
|
730
|
+
* @param options - Optional renderer override.
|
|
731
|
+
* @returns The module.
|
|
732
|
+
*/
|
|
733
|
+
declare function createConsentPanel(options?: CreateConsentPanelOptions): PanelModule;
|
|
734
|
+
/** The default Consent panel module — view already wired. Register THIS. */
|
|
735
|
+
declare const consentPanel: PanelModule;
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* The `xeno.core.consent` manifest.
|
|
739
|
+
*
|
|
740
|
+
* Capabilities: `storage.local` only. The panel grants nothing and enforces nothing — it emits a
|
|
741
|
+
* decision and the host acts on it. A consent surface that could itself grant a capability would be
|
|
742
|
+
* the capability.
|
|
743
|
+
*
|
|
744
|
+
* @module
|
|
745
|
+
*/
|
|
746
|
+
|
|
747
|
+
/** The canonical manifest id. */
|
|
748
|
+
declare const CONSENT_PANEL_ID = "xeno.core.consent";
|
|
749
|
+
/** The `xeno.core.consent` manifest. */
|
|
750
|
+
declare const consentManifest: PanelManifest;
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* The Consent panel view.
|
|
754
|
+
*
|
|
755
|
+
* Composes `@xenosystem/workbench/primitives`; the host must ensure `@xenosystem/workbench/primitives.css` is
|
|
756
|
+
* present.
|
|
757
|
+
*
|
|
758
|
+
* Two deliberate absences: **no "Allow all"** (bulk approval is how consent fatigue becomes a
|
|
759
|
+
* security hole) and **no auto-focus on Allow** (a stray Enter must not grant anything).
|
|
760
|
+
*
|
|
761
|
+
* 🔴 **A third absence, added with the question kinds: nothing here submits on its own.** The
|
|
762
|
+
* question forms carry no auto-submit, no debounce-then-send, and no Enter-to-answer on the
|
|
763
|
+
* single-line field. The emitted answer is always the product of a click the user made, which is
|
|
764
|
+
* the view's half of the controller's *"no value without a user action"* invariant — the guard
|
|
765
|
+
* lives in `submit()`, but a view that fired it on a keystroke would satisfy the guard while
|
|
766
|
+
* defeating the point.
|
|
767
|
+
*
|
|
768
|
+
* @module
|
|
769
|
+
*/
|
|
770
|
+
|
|
771
|
+
/** Props for {@link ConsentPanelView}. */
|
|
772
|
+
interface ConsentPanelViewProps {
|
|
773
|
+
controller: ConsentController;
|
|
774
|
+
showQueueCount?: boolean;
|
|
775
|
+
emptyHint?: string;
|
|
776
|
+
}
|
|
777
|
+
/** The Consent panel view. */
|
|
778
|
+
declare function ConsentPanelView({ controller, showQueueCount, emptyHint, }: ConsentPanelViewProps): ReactNode;
|
|
779
|
+
|
|
780
|
+
export { CONSENT_PANEL_ID, ConsentController, type ConsentControllerOptions, type ConsentHostBridge, type ConsentPanelState, ConsentPanelView, type ConsentPanelViewProps, type ConsentRenderContext, type ConsentViewState, type CreateConsentPanelOptions, ELICITATION_RESULT_SCHEMA, ELICITATION_SCHEMA, type XenoChoiceAnswer, type XenoChoiceElicitation, type XenoConsentDecision, type XenoConsentDuration, type XenoConsentKind, type XenoConsentRequest, type XenoConsentRisk, type XenoConsentScope, type XenoElicitation, type XenoElicitationAnswer, type XenoElicitationAsk, type XenoElicitationBase, type XenoElicitationCancelled, type XenoElicitationOption, type XenoElicitationResult, type XenoPathAnswer, type XenoPathElicitation, type XenoQuestionElicitation, type XenoTextAnswer, type XenoTextElicitation, allowedDurations, autoCancel, autoDeny, consentManifest, consentPanel, createConsentPanel, effectiveRisk, elicitationAsk, elicitationOptions, findOption, isConsentRequest, isElicitation, isElicitationAnswer, isElicitationCancelled, isExpired, isPermissionElicitation, maxTextLength, selectableScopes };
|