@seatlayer/js 0.52.0 → 0.53.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.
@@ -1,1808 +1,16 @@
1
- import { ChartDoc, AvailabilityRule, ChartTheme, ExpandedSeat } from '@seatlayer/core';
1
+ import { B as BucketRow } from './channelsMode-BX7s9bUl.cjs';
2
+ export { a9 as ACCESS_LINK_DEFAULTS, A as AccessIntentForbidsDetails, a as AccessLinkRecord, b as AccessLinkReveal, c as AccessLinkState, d as AccessLinkStatus, e as AccessLinkStatusRecord, f as ArchiveBlockedDetails, g as AssignmentBuckets, h as AssignmentDropDetails, i as AssignmentResult, C as ChannelAccessIntent, j as ChannelAccessSummary, k as ChannelAllocationPage, l as ChannelAttribution, m as ChannelAuditEntry, n as ChannelAuditPage, o as ChannelCounts, p as ChannelListResult, q as ChannelPreviewProjection, r as ChannelRecord, s as ChannelReport, t as ChannelReportLinkRecord, u as ChannelReportLinkReveal, v as ChannelReportResult, w as ChannelReportRow, x as ChannelSeatStatus, y as ChannelState, z as ChannelsCapabilities, D as ChannelsClient, E as ChannelsMode, F as ChannelsModeHost, G as ChannelsRowView, H as ChannelsSeatView, I as ControlRoomActivityEntry, J as ControlRoomSectionMetric, K as ControlRoomSnapshot, L as EventScopedManageToken, M as IntentSwitchBlockedDetails, N as InventoryBooking, O as InventoryBookingActivity, P as InventoryBookingDetail, Q as InventoryBookingObject, R as InventoryBookingState, S as InventoryBookingsPage, T as InventoryBookingsQuery, U as LogEntry, V as LogPage, W as ManageApi, X as ManageApiError, aa as PUBLIC_CHANNEL_ID, ab as PUBLIC_CHANNEL_NAME, Y as ReportByStatus, Z as ReportCategoryMeta, _ as ReportCategoryRow, $ as ReportResult, a0 as SeatManager, a1 as SeatManagerActionResult, a2 as SeatManagerActivity, a3 as SeatManagerCapability, a4 as SeatManagerConnection, a5 as SeatManagerMode, a6 as SeatManagerOptions, a7 as SeatManagerTallies, a8 as SelectionSourceRow, ac as accessIntentDescription, ad as accessIntentLabel, ae as accessLine, af as accessLinkBadge, ag as accessLinkErrorCopy, ah as accessLinkIsLive, ai as accessLinkPolicyLines, aj as bucketRows, ak as dropReviewRows, al as intentForbidsCopy, am as intentSwitchBlockedCopy, an as isPublicChannelId, ao as markerLetter, ap as markerOf, aq as mutationCount, ar as needsMoveConfirmation, as as planAssignment, at as retryAfterCopy, au as selectionSources, av as stateBadge, aw as suggestMarker } from './channelsMode-BX7s9bUl.cjs';
2
3
  export { ExpandedSeat, SeatHoverDetails } from '@seatlayer/core';
3
4
 
4
5
  /**
5
- * Sales-channel planning — the pure, DOM-free half of Channels mode.
6
+ * channelsFragments — the Channels cockpit's pure builders and field readers.
6
7
  *
7
- * Everything here is deterministic and testable without a canvas: the marker
8
- * palette, the mixed-source selection summary, the LOCAL staged preview of an
9
- * assignment, and the bucket rows the Review sheet renders.
10
- *
11
- * The local preview deliberately produces the SAME `AssignmentBuckets` shape the
12
- * server returns from `POST /channels/assignments`. There is no dry-run endpoint,
13
- * so the review sheet is drawn from this local computation and then REDRAWN from
14
- * the authoritative server response after Apply. One renderer, two sources —
15
- * which is why the shapes must match exactly.
16
- *
17
- * Spec: sales-channels-product-ux-spec §8.4–8.5.
18
- */
19
- /**
20
- * Public sale is a built-in pseudo-channel. The server's sentinel for it is the
21
- * literal string `'public'` — it is what `GET /channels` returns as
22
- * `publicSale.id`, what `GET /channels/allocation` reports for an unallocated
23
- * unit, and what `POST /channels/assignments` accepts (alongside `null`) as the
24
- * target meaning "send these back to public sale".
25
- *
26
- * This constant was `''` until 2026-08-02, which silently made every public unit
27
- * look like an unknown PRIVATE channel to `planAssignment` and `markerOf` — the
28
- * cause of the Review sheet's phantom "moved out of another channel" line and
29
- * the rail's "?" marker. Keep it byte-identical to the server's
30
- * `eventChannels.PUBLIC_CHANNEL_ID`.
31
- */
32
- declare const PUBLIC_CHANNEL_ID = "public";
33
- declare const PUBLIC_CHANNEL_NAME = "Public sale";
34
- /** True for every spelling of "public sale" a worker may hand us. */
35
- declare function isPublicChannelId(id: string | null | undefined): boolean;
36
- type ChannelState = 'active' | 'paused' | 'archived';
37
- /** Physical inventory status, as the manage surface speaks it. */
38
- type ChannelSeatStatus = 'free' | 'held' | 'booked' | 'blocked';
39
- interface ChannelCounts {
40
- allocated: number;
41
- free: number;
42
- held: number;
43
- booked: number;
44
- blocked: number;
45
- units: number;
46
- }
47
- /** Buyer-access intents the server stores per channel. */
48
- type ChannelAccessIntent = 'none' | 'internal' | 'server' | 'hosted_link';
49
- /**
50
- * Buyer-access summary on a channel row. Shipped by the access hardening branch
51
- * (merged to app main). Still optional in this type: a worker that predates the
52
- * merge simply omits it and the rail reads "—" rather than inventing a state.
53
- */
54
- interface ChannelAccessSummary {
55
- intent?: ChannelAccessIntent | string;
56
- hasActiveGrants?: boolean;
57
- lastMintAt?: number | null;
58
- /** Free-text detail (partner host, who paused it) when the server offers one. */
59
- detail?: string | null;
60
- }
61
- interface ChannelRecord {
62
- id: string;
63
- name: string;
64
- color: string | null;
65
- marker: string | null;
66
- externalRef: string | null;
67
- state: ChannelState;
68
- archiveDestination: string | null;
69
- createdAt: number;
70
- updatedAt: number;
71
- archivedAt: number | null;
72
- counts: ChannelCounts;
73
- access?: ChannelAccessSummary | null;
74
- }
75
- interface PublicSaleChannel {
76
- /** `'public'` on every shipped worker; typed loosely so an older build that
77
- * still answers `''` is normalised rather than rejected. */
78
- id: string;
79
- name: string;
80
- state: 'active';
81
- counts: ChannelCounts;
82
- access?: ChannelAccessSummary | null;
83
- }
84
- interface ChannelListResult {
85
- assignmentVersion: number;
86
- publicSale: PublicSaleChannel;
87
- channels: ChannelRecord[];
88
- }
89
- interface AssignmentBucketCount {
90
- count: number;
91
- }
92
- interface AssignmentSkippedBucket extends AssignmentBucketCount {
93
- labels: string[];
94
- truncated: boolean;
95
- }
96
- interface AssignmentBuckets {
97
- changedFromPublic: AssignmentBucketCount;
98
- movedFromOtherChannel: AssignmentBucketCount & {
99
- channels: Array<{
100
- channelId: string;
101
- name: string | null;
102
- count: number;
103
- }>;
104
- };
105
- alreadyInTarget: AssignmentBucketCount;
106
- skippedHeld: AssignmentSkippedBucket;
107
- skippedBooked: AssignmentSkippedBucket;
108
- /** Requested labels that are not inventory in this event. */
109
- notFound: AssignmentSkippedBucket;
110
- }
111
- interface AssignmentResult {
112
- ok: true;
113
- targetChannelId: string;
114
- assignmentVersion: number;
115
- requested: number;
116
- applied: number;
117
- buckets: AssignmentBuckets;
118
- }
119
- interface ArchiveBlockedDetails {
120
- activeHolds?: number;
121
- heldUnits?: number;
122
- latestHoldExpiresAt?: number | null;
123
- retryAfterMs?: number;
124
- }
125
- /**
126
- * Clamp any marker text down to the ONE uppercase character every surface draws.
127
- *
128
- * The server stores `marker` as free text (it only length-caps it), so a channel
129
- * created outside this widget can carry "star" or "VIP". The comp's marker chip
130
- * is a single glyph: taking two characters ("ST") overflows the 22px chip and
131
- * stops reading as a letter. Non-letter leading characters (an emoji, a digit,
132
- * punctuation) are skipped in favour of the first real letter.
133
- */
134
- declare function markerLetter(raw: string | null | undefined, fallback: string): string;
135
- /**
136
- * Suggest a marker for a new channel: the first letter of its name when that
137
- * letter is still free, otherwise the next unused letter. Deterministic so the
138
- * Create dialog's preview matches what actually gets stored.
139
- */
140
- declare function suggestMarker(name: string, taken: Iterable<string>): {
141
- letter: string;
142
- color: string;
143
- };
144
- /** The letter + color a channel actually renders with (server value wins). */
145
- declare function markerOf(channel: {
146
- id: string;
147
- name: string;
148
- marker?: string | null;
149
- color?: string | null;
150
- }, index?: number): {
151
- letter: string;
152
- color: string;
153
- };
154
- /** One line of the rail's mixed-source selection summary (§8.4). */
155
- interface SelectionSourceRow {
156
- channelId: string;
157
- name: string;
158
- count: number;
159
- }
160
- /**
161
- * Group the current selection by the channel each unit is allocated to today.
162
- * Public sale is listed first; the rest follow in list order so the rail's
163
- * ordering never jitters as the selection changes.
164
- */
165
- declare function selectionSources(labels: string[], allocation: Map<string, string>, list: ChannelListResult | null): SelectionSourceRow[];
166
- /**
167
- * The LOCAL staged preview of "move these labels to this channel".
168
- *
169
- * Mirrors the DO's rules exactly (eventChannels.applyAssignment):
170
- * - a unit already in the target is `alreadyInTarget`, whatever its status;
171
- * - otherwise held and booked units are skipped and never rewritten;
172
- * - otherwise a public unit is `changedFromPublic`, a private one is
173
- * `movedFromOtherChannel` (itemised per source);
174
- * - a label that is not inventory in this event is `notFound`.
175
- *
176
- * Every requested label lands in exactly one bucket — the property the Review
177
- * sheet's "every selected seat is in exactly one line" promise depends on.
178
- */
179
- declare function planAssignment(input: {
180
- labels: string[];
181
- targetChannelId: string;
182
- allocation: Map<string, string>;
183
- statusOf: (label: string) => ChannelSeatStatus | undefined;
184
- nameOf: (channelId: string) => string | null;
185
- }): AssignmentBuckets;
186
- /** Units this plan will actually mutate — what the Apply button counts. */
187
- declare function mutationCount(buckets: AssignmentBuckets): number;
188
- /** True when moving inventory out of another PRIVATE channel — §8.5 requires an
189
- * explicit confirmation line for exactly this case. */
190
- declare function needsMoveConfirmation(buckets: AssignmentBuckets): boolean;
191
- interface BucketRow {
192
- kind: 'add' | 'move' | 'same' | 'skip';
193
- icon: string;
194
- count: number;
195
- text: string;
196
- why?: string;
197
- /** Sampled seat labels for a skipped bucket, when the server sent any. */
198
- peek?: string;
199
- }
200
- /**
201
- * Render-ready rows for the Review sheet. The comp shows five lines; the server
202
- * carries a sixth bucket (`notFound`) which is emitted only when it is non-zero,
203
- * so a normal review still reads exactly like the approved design.
204
- *
205
- * Empty buckets are dropped — a zero line is noise, not honesty.
206
- */
207
- declare function bucketRows(buckets: AssignmentBuckets, targetName: string): BucketRow[];
208
- /**
209
- * "try again in ~N minutes" for the archive-blocked-by-holds 409 (§8.8).
210
- * Rounds up so the organizer never comes back one tick early.
211
- */
212
- declare function retryAfterCopy(details: ArchiveBlockedDetails | null | undefined): string;
213
- /** The access line under a channel row. Falls back to "—" before the hardening
214
- * branch lands the `access` field, never to a guess. */
215
- declare function accessLine(access: ChannelAccessSummary | null | undefined): string;
216
- /**
217
- * The name of each sale route, WORD FOR WORD as the server says it.
218
- *
219
- * `eventChannels.ts` builds its refusal sentences from an `INTENT_LABEL` map
220
- * with exactly these four strings. Diverging here would mean the picker calls a
221
- * route one thing and the refusal it produces calls it another, so these are
222
- * copied deliberately rather than paraphrased.
223
- */
224
- declare function accessIntentLabel(intent: ChannelAccessIntent): string;
225
- /**
226
- * What choosing this route actually DOES, now that the server enforces it.
227
- *
228
- * Written against the enforcement matrix, not against intent: each route opens
229
- * exactly one way to reach a buyer and refuses the other three, so each sentence
230
- * says both halves. The old copy for these values promised nothing and delivered
231
- * nothing; it was deleted in 0.42.0 and is not coming back.
232
- */
233
- declare function accessIntentDescription(intent: ChannelAccessIntent): string;
234
- /** `channel_access_intent_forbids` (409) — the route this channel declares is
235
- * not the one the action needed. */
236
- interface AccessIntentForbidsDetails {
237
- channelId?: string;
238
- accessIntent?: ChannelAccessIntent | string;
239
- /** The route the refused action arrived on: `hosted_link` | `server` | `staff` | `public`. */
240
- route?: string;
241
- }
242
- /**
243
- * The refusal, said as a decision the organizer can act on.
244
- *
245
- * The server's own sentence stops at "…so it cannot be sold through a buyer
246
- * link" — true, but it leaves the reader to work out what to do. This adds the
247
- * second half: which route to switch to. The code itself is never shown.
248
- */
249
- declare function intentForbidsCopy(details: AccessIntentForbidsDetails | null | undefined): string;
250
- /** `channel_intent_switch_blocked` (409) — buyers are inside the current route. */
251
- interface IntentSwitchBlockedDetails {
252
- channelId?: string;
253
- from?: ChannelAccessIntent | string;
254
- to?: ChannelAccessIntent | string;
255
- liveAccessLinks?: number;
256
- activeSessions?: number;
257
- acknowledgeWith?: {
258
- acknowledgeLiveAccess?: boolean;
259
- };
260
- }
261
- /**
262
- * What is live right now, and what acknowledging would do to it.
263
- *
264
- * Both halves are checked against the server rather than guessed: an
265
- * acknowledged switch REVOKES the channel's hosted links (redemption refuses
266
- * from that moment, so a link left listed as active would be a door the
267
- * management surface advertises and the buyer path denies), and deliberately
268
- * LEAVES buyer sessions and their holds alone — nobody is thrown out of a
269
- * checkout. Sessions cap at 12 hours (30 minutes by default) and no new ones can
270
- * be minted, so the old route drains on its own.
271
- */
272
- declare function intentSwitchBlockedCopy(details: IntentSwitchBlockedDetails | null | undefined): {
273
- headline: string;
274
- consequences: string[];
275
- };
276
- /** Lifecycle the server stores. `rotated` means a newer link replaced this one. */
277
- type AccessLinkState = 'active' | 'revoked' | 'rotated';
278
- /** What the organizer surface renders: `state`, unless an active link has run
279
- * out of time or out of redemptions. Never a capability, never a hash. */
280
- type AccessLinkStatus = AccessLinkState | 'expired' | 'exhausted';
281
- /**
282
- * One hosted link, exactly as `GET …/access-links` projects it.
283
- *
284
- * There is deliberately NO `url` and NO `capability` field here — the listing
285
- * route does not return them, no other route returns them, and this type must
286
- * not tempt a caller into believing otherwise. The secret exists in exactly one
287
- * place for exactly one moment: the create/rotate response (`AccessLinkReveal`).
288
- */
289
- interface AccessLinkRecord {
290
- id: string;
291
- channelId: string;
292
- label: string | null;
293
- includePublic: boolean;
294
- expiresAt: number;
295
- maxRedemptions: number;
296
- redemptions: number;
297
- /** Guest-weighted per-buyer ceiling handed to every session this link mints. */
298
- maxQuantity: number;
299
- sessionTtlSeconds: number;
300
- state: AccessLinkState;
301
- status: AccessLinkStatus;
302
- createdAt: number;
303
- createdBy: string | null;
304
- revokedAt: number | null;
305
- lastRedeemedAt: number | null;
306
- /** Rotation lineage: the link this replaced, and the one that replaced it. */
307
- rotatedFrom: string | null;
308
- rotatedTo: string | null;
309
- }
310
- /** A listed link, with the live session count the rotate dialog needs to state
311
- * "N buyers got in with this link and still have access". */
312
- interface AccessLinkStatusRecord extends AccessLinkRecord {
313
- activeSessions?: number;
314
- }
315
- /**
316
- * The ONE-TIME reveal. `url` and `capability` are on the wire exactly once, in
317
- * the create/rotate response, and are unrecoverable afterwards: SeatLayer stores
318
- * only a hash. Nothing may persist this — see `ChannelsMode.revealLink`.
319
- */
320
- interface AccessLinkReveal {
321
- link: AccessLinkRecord;
322
- url: string;
323
- capability: string;
324
- revealedOnce: true;
325
- /** Rotation only: the link that just stopped working, and how many live buyer
326
- * sessions from it were ended (0 when the organizer let them finish). */
327
- previous?: AccessLinkRecord;
328
- endedSessions?: number;
329
- }
330
- /**
331
- * Owner-set defaults for a new link. Expiry is NOT here: "when the event starts"
332
- * is the server's own default (it knows `starts_at`; the cockpit does not), so
333
- * the create form expresses that choice by omitting `expiresAt` entirely rather
334
- * than by guessing a timestamp the server would then have to correct.
335
- */
336
- declare const ACCESS_LINK_DEFAULTS: {
337
- readonly maxRedemptions: 100;
338
- readonly maxQuantity: 4;
339
- };
340
- /** Plain-language state badge for a hosted link (§9: no internal vocabulary). */
341
- declare function accessLinkBadge(link: Pick<AccessLinkRecord, 'status' | 'state'>): {
342
- text: string;
343
- kind: 'active' | 'paused' | 'archived';
344
- };
345
- /** Only an `active` link can be rotated or revoked; the server agrees (409
346
- * `access_link_not_active`), so the buttons are absent rather than failing. */
347
- declare function accessLinkIsLive(link: Pick<AccessLinkRecord, 'status' | 'state'>): boolean;
348
- /**
349
- * The policy an organizer is agreeing to, in one list. Used by BOTH the reveal
350
- * (what you just created) and the status card (what is live), so the two can
351
- * never drift into describing the same link differently.
352
- */
353
- declare function accessLinkPolicyLines(link: AccessLinkRecord): Array<{
354
- k: string;
355
- v: string;
356
- }>;
357
- /**
358
- * Plain language for a refused hosted-link call.
359
- *
360
- * The PLATFORM BOUNDS live on the server (60s–180d expiry, 1–10 000 redemptions,
361
- * 1–100 seats per buyer, 20 live links per channel) and the server states them
362
- * in `message`. We surface that sentence rather than re-encoding the numbers
363
- * here, so the client can never disagree with the rule it is reporting.
364
- */
365
- declare function accessLinkErrorCopy(err: {
366
- code?: string;
367
- serverMessage?: string;
368
- status?: number;
369
- details?: Record<string, unknown>;
370
- } | null | undefined): string;
371
- /**
372
- * The chart-update refusal `channel_assignment_would_drop` (409) deliberately
373
- * mirrors the Apply skipped buckets, so ONE review component renders both.
374
- * This adapts it into the same `BucketRow[]` the Review sheet already draws.
375
- */
376
- interface AssignmentDropDetails {
377
- droppedUnits?: number;
378
- channels?: Array<{
379
- channelId: string;
380
- name: string | null;
381
- count: number;
382
- labels?: string[];
383
- truncated?: boolean;
384
- }>;
385
- acknowledgeWith?: string;
386
- }
387
- declare function dropReviewRows(details: AssignmentDropDetails | null | undefined): BucketRow[];
388
- /** Plain-language state badge text (§9: no internal vocabulary on user surfaces). */
389
- declare function stateBadge(state: ChannelState | 'builtin'): string;
390
-
391
- /**
392
- * Organizer manage-surface client for workers/api (the `/v1/events/:key/*`
393
- * inventory routes + the public realtime channel). Companion to api.ts (the
394
- * buyer `/pub/*` client) — kept separate because the manage surface is
395
- * token-authed (Bearer) and cross-origin from the CMS:
396
- *
397
- * - Writes + reports send `Authorization: Bearer <token>`. Browser code must
398
- * use a short-lived, event-scoped organizer grant (`mse_…`, minted by a
399
- * trusted backend). The low-level client retains tenant-secret (`sk_…`)
400
- * compatibility for trusted server runtimes only; never pass one to
401
- * SeatManager or any other browser bundle.
402
- * - `credentials: 'omit'` — there is no session cookie; the CMS runs
403
- * cross-origin. The worker's credentialed CORS still echoes the CMS origin.
404
- * - chart geometry and authored media use authenticated organizer Event
405
- * routes. A manage bearer is not buyer authority and is never sent to
406
- * `/pub`. The seat STATE reads are private too. `/pub/.../objects` and an unticketed
407
- * `/pub/.../subscribe` both answer with the BUYER projection, which shows
408
- * inventory the caller may not buy as a neutral `blocked` — so an organizer
409
- * reading them sees its own channel allocations as blocked seats. Both now
410
- * go through the token: `/v1/events/:key/objects` for the snapshot, and a
411
- * `/v1/events/:key/subscribe-tickets` mint for the socket's scope.
412
- *
413
- * Managed sales are deliberately not exposed by this browser client. Create
414
- * them from trusted server code through the inventory booking API; SeatManager
415
- * exposes only the organizer operations it can actually complete itself.
416
- */
417
-
418
- /** One page of the organizer-only label → channel projection. */
419
- interface ChannelAllocationPage {
420
- assignmentVersion: number;
421
- allocations: Array<{
422
- label: string;
423
- channelId: string;
424
- }>;
425
- nextAfterLabel: string | null;
426
- }
427
- interface ChannelAuditEntry {
428
- id: number;
429
- at: number;
430
- actor: string | null;
431
- action: string;
432
- channelId: string | null;
433
- assignmentVersion: number;
434
- before: unknown;
435
- after: unknown;
436
- reason: string | null;
437
- }
438
- interface ChannelAuditPage {
439
- entries: ChannelAuditEntry[];
440
- nextBefore: number | null;
441
- }
442
- /**
443
- * Buyer projection for a preview audience — the same scoped server view the
444
- * buyer SDK receives. When an audience cannot be previewed (a paused or
445
- * archived channel), the server answers `{available:false, unavailable:[…]}`
446
- * and the UI shows the real paused/unavailable landing state instead of
447
- * rendering those seats as eligible.
448
- *
449
- * Fields stay optional: a worker that predates the hardening merge 404s here,
450
- * and Channels mode says the preview needs a newer server rather than faking a
451
- * projection client-side.
452
- */
453
- interface ChannelPreviewProjection {
454
- available?: boolean;
455
- unavailable?: Array<{
456
- channelId: string;
457
- state: 'paused' | 'archived' | string;
458
- }>;
459
- channelIds?: string[];
460
- includePublic?: boolean;
461
- /** Labels this audience may buy. Everything else renders as ONE neutral
462
- * unavailable state so preview never leaks which channel holds a seat. */
463
- eligible?: string[];
464
- counts?: {
465
- eligible?: number;
466
- free?: number;
467
- held?: number;
468
- booked?: number;
469
- };
470
- }
471
- declare class ManageApiError extends Error {
472
- status: number;
473
- code?: string;
474
- /** Present when a block/unbook 409s because seats were just taken. */
475
- conflicts?: {
476
- label: string;
477
- reason?: string;
478
- }[];
479
- /**
480
- * Structured refusal detail. The channel routes use it for the two 409s a UI
481
- * must render rather than merely report: `channel_archive_blocked_by_holds`
482
- * carries {activeHolds, heldUnits, latestHoldExpiresAt, retryAfterMs}, and
483
- * `channel_assignment_conflict` carries the current assignmentVersion.
484
- */
485
- details?: Record<string, unknown>;
486
- /**
487
- * The server's own human sentence, when it sent one. `message` is the machine
488
- * code (that is what `error` carries), so a UI that wants to state a PLATFORM
489
- * RULE — "redemptions must be between 1 and 10 000" — reads this instead of
490
- * re-encoding the bound locally and risking disagreement with the server.
491
- */
492
- serverMessage?: string;
493
- constructor(status: number, message: string, code?: string, conflicts?: {
494
- label: string;
495
- reason?: string;
496
- }[], details?: Record<string, unknown>, serverMessage?: string);
497
- }
498
- interface ReportByStatus {
499
- free: number;
500
- held: number;
501
- booked: number;
502
- not_for_sale: number;
503
- }
504
- interface ReportCategoryRow {
505
- category: string;
506
- total: number;
507
- free: number;
508
- held: number;
509
- booked: number;
510
- not_for_sale: number;
511
- /** Configured-price snapshots for booked inventory; not proof of payment. */
512
- bookedValue: number;
513
- /** @deprecated Use `bookedValue`. */
514
- bookedRevenue: number;
515
- }
516
- interface ReportCategoryMeta {
517
- key: string;
518
- label: string;
519
- color: string;
520
- price: number;
521
- }
522
- interface ReportResult {
523
- report: {
524
- byStatus: ReportByStatus;
525
- byCategory: ReportCategoryRow[];
526
- bySection?: ControlRoomSectionMetric[];
527
- };
528
- event: {
529
- key: string;
530
- name: string;
531
- seatTotal: number;
532
- currency?: string;
533
- };
534
- categories: ReportCategoryMeta[];
535
- }
536
- interface ControlRoomSectionMetric {
537
- sectionId: string;
538
- sectionLabel: string;
539
- zoneId: string | null;
540
- total: number;
541
- free: number;
542
- held: number;
543
- booked: number;
544
- not_for_sale: number;
545
- bookedValue: number;
546
- /** @deprecated Use `bookedValue`. */
547
- bookedRevenue: number;
548
- }
549
- /** Recent seat-state change safe for an event:view control-room grant. Full
550
- * audit references remain available only through the event:reports log API. */
551
- interface ControlRoomActivityEntry {
552
- id: number;
553
- at: number;
554
- action: string;
555
- labels: string[];
556
- }
557
- interface ControlRoomSnapshot {
558
- version: number;
559
- currency: string;
560
- totals: {
561
- free: number;
562
- held: number;
563
- booked: number;
564
- blocked: number;
565
- };
566
- /** Configured value attached to booked inventory, never payment revenue. */
567
- bookedValue: {
568
- gross: number;
569
- bySection: ControlRoomSectionMetric[];
570
- };
571
- /** @deprecated Use `bookedValue`. */
572
- revenue: {
573
- gross: number;
574
- bySection: ControlRoomSectionMetric[];
575
- };
576
- velocity: {
577
- windowMinutes: number;
578
- bySection: Array<{
579
- sectionId: string;
580
- netBooked: number;
581
- bookedValue: number;
582
- /** @deprecated Use `bookedValue`. */
583
- grossRevenue: number;
584
- previousNetBooked: number;
585
- trend: 'rising' | 'steady' | 'cooling';
586
- }>;
587
- };
588
- presence: {
589
- shoppingSessions: number;
590
- activeHolds: number;
591
- };
592
- /** Present on workers that support reload-safe activity hydration. */
593
- activity?: ControlRoomActivityEntry[];
594
- event: {
595
- key: string;
596
- name: string;
597
- seatTotal: number;
598
- currency?: string;
599
- };
600
- }
601
- interface LogEntry {
602
- id: number;
603
- at: number;
604
- action: string;
605
- labels: string[];
606
- ref: string | null;
607
- }
608
- interface LogPage {
609
- entries: LogEntry[];
610
- nextBefore: number | null;
611
- }
612
- /** Platform/SDK inventory history — deliberately unrelated to commerce Orders. */
613
- type InventoryBookingState = 'booked' | 'partially_cancelled' | 'cancelled';
614
- interface InventoryBookingObject {
615
- label: string;
616
- objectId: string;
617
- objectType: 'seat' | 'booth' | 'ga' | 'table';
618
- categoryKey: string;
619
- sectionId: string | null;
620
- sectionLabel: string | null;
621
- zoneId: string | null;
622
- tierId: string | null;
623
- releaseId: string | null;
624
- bookingMode: 'individual' | 'whole' | 'variable';
625
- quantity: number;
626
- /** Event-configured price snapshot; not proof of what a buyer paid. */
627
- unitPrice: number;
628
- configuredValue: number;
629
- currency: string;
630
- channelId: string | null;
631
- channelExternalRef: string | null;
632
- source: string;
633
- state: 'booked' | 'cancelled';
634
- bookedAt: number;
635
- cancelledAt: number | null;
636
- }
637
- interface InventoryBooking {
638
- eventKey: string;
639
- eventMode: 'live' | 'test';
640
- bookingRef: string;
641
- state: InventoryBookingState;
642
- bookedAt: number;
643
- updatedAt: number;
644
- cancelledAt: number | null;
645
- source: string;
646
- bookedBy: string | null;
647
- lastActor: string | null;
648
- lastSource: string;
649
- labels: string[];
650
- objects: InventoryBookingObject[];
651
- quantity: number;
652
- activeQuantity: number;
653
- configuredValue: number;
654
- activeConfiguredValue: number;
655
- currency: string | null;
656
- }
657
- interface InventoryBookingsQuery {
658
- q?: string;
659
- state?: InventoryBookingState | null;
660
- cursor?: string | null;
661
- limit?: number;
662
- }
663
- interface InventoryBookingsPage {
664
- bookings: InventoryBooking[];
665
- nextCursor: string | null;
666
- }
667
- interface InventoryBookingActivity {
668
- id: number;
669
- action: 'book' | 'replay' | 'partial_cancel' | 'cancel' | 'reconcile';
670
- at: number;
671
- labels: string[];
672
- actor: string | null;
673
- source: string;
674
- }
675
- interface InventoryBookingDetail {
676
- booking: InventoryBooking;
677
- activity: InventoryBookingActivity[];
678
- activityTruncated: boolean;
679
- }
680
- /** Booking-time facts attached to the channel that produced the inventory move. */
681
- interface ChannelAttribution {
682
- sold: number;
683
- units: number;
684
- bookedValue: number | null;
685
- /** @deprecated Use `bookedValue`. */
686
- revenue: number | null;
687
- }
688
- interface ChannelReportRow {
689
- channelId: string;
690
- name: string;
691
- externalRef: string | null;
692
- state: ChannelState;
693
- allocation: ChannelCounts;
694
- attribution: ChannelAttribution;
695
- sellThrough: number | null;
696
- }
697
- interface ChannelReport {
698
- assignmentVersion: number;
699
- includesBookedValue: boolean;
700
- /** @deprecated Use `includesBookedValue`. */
701
- includesRevenue: boolean;
702
- methodology: {
703
- allocation: string;
704
- attribution: string;
705
- sellThrough: string;
706
- };
707
- rows: ChannelReportRow[];
708
- totals: {
709
- allocated: number;
710
- free: number;
711
- held: number;
712
- booked: number;
713
- blocked: number;
714
- sold: number;
715
- bookedValue: number | null;
716
- /** @deprecated Use `bookedValue`. */
717
- revenue: number | null;
718
- };
719
- }
720
- interface ChannelReportResult {
721
- report: ChannelReport;
722
- event: {
723
- key: string;
724
- name: string;
725
- seatTotal?: number;
726
- currency?: string;
727
- };
728
- }
729
- interface ChannelReportLinkRecord {
730
- id: string;
731
- channelId: string;
732
- label: string | null;
733
- includesBookedValue: boolean;
734
- /** @deprecated Use `includesBookedValue`. */
735
- includesRevenue: boolean;
736
- expiresAt: number;
737
- state: 'active' | 'revoked';
738
- status: 'active' | 'revoked' | 'expired';
739
- views: number;
740
- lastViewedAt: number | null;
741
- createdAt: number;
742
- createdBy: string | null;
743
- revokedAt: number | null;
744
- }
745
- interface ChannelReportLinkReveal {
746
- link: ChannelReportLinkRecord;
747
- url: string;
748
- capability: string;
749
- revealedOnce: true;
750
- }
751
- /**
752
- * A one-use WebSocket subscribe ticket. `protocols` is exactly what to hand
753
- * `new WebSocket(url, protocols)` — the ticket rides in `Sec-WebSocket-Protocol`
754
- * because a browser socket cannot carry an Authorization header and a bearer
755
- * must never travel in a URL.
756
- */
757
- interface SubscribeTicket {
758
- ticket: string;
759
- expiresAt: number;
760
- protocol: string;
761
- protocols: string[];
762
- }
763
- interface PubObjectsResult {
764
- /** Every non-free seat's status keyed by label (free seats omitted). */
765
- seats: Record<string, string>;
766
- hidden?: string[];
767
- closed?: string[];
768
- updatedAt: number;
769
- }
770
- interface PubChartResult {
771
- event: {
772
- key: string;
773
- name: string;
774
- status?: string;
775
- venue?: string | null;
776
- startsAt?: number | null;
777
- currency?: string;
778
- mode?: string;
779
- };
780
- doc: ChartDoc;
781
- }
782
- /**
783
- * Bound to one apiBase + bearer token. Browser callers use an event-scoped
784
- * `mse_…` grant. A tenant `sk_…` remains accepted only so trusted server code
785
- * can use this low-level client; SeatManager rejects it before construction.
786
- * Rebuild (or `setToken`) when a token is re-minted on 401.
787
- */
788
- declare class ManageApi {
789
- private base;
790
- private token;
791
- constructor(apiBase: string, token: string);
792
- /** Swap the Bearer token in place (SeatManager re-mints on 401). */
793
- setToken(token: string): void;
794
- private auth;
795
- private authBlob;
796
- /** Event-pinned organizer geometry. A manage token is never sent to `/pub`. */
797
- chart(key: string): Promise<PubChartResult>;
798
- /** Authenticated bytes for an Event-scoped organizer chart asset. */
799
- asset(key: string, asset: string): Promise<Blob>;
800
- /**
801
- * The ORGANIZER's seat map: physical state, token-authed.
802
- *
803
- * This used to read `/pub/events/:key/objects` with no credential, which
804
- * answers with the BUYER projection — every unit the caller may not buy
805
- * collapses to a neutral `blocked`. An anonymous caller may buy only Public
806
- * sale inventory, so the cockpit rendered every channel-allocated seat as
807
- * blocked and then computed its KPIs, sell-through and (worse) its
808
- * block/unblock target sets from that. `/v1/events/:key/objects` returns the
809
- * unprojected snapshot the control-room read model already trusts.
810
- */
811
- objects(key: string): Promise<PubObjectsResult>;
812
- /**
813
- * Exchange the manage token for a one-use organizer socket ticket.
814
- *
815
- * A browser `WebSocket` cannot send an Authorization header, so the socket's
816
- * scope is established here, over ordinary HTTPS. Without it the DO treats a
817
- * manager socket as an anonymous public buyer and projects its deltas — so a
818
- * hold inside a private allocation is structurally suppressed and the map
819
- * drifts away from the truth `objects()` just established.
820
- *
821
- * Tickets are single-redemption and expire in ~30s: mint one per connect.
822
- */
823
- subscribeTicket(key: string): Promise<SubscribeTicket>;
824
- socketUrl(key: string): string;
825
- /** Take FREE seats off sale in one batched call. Optional `releaseAt` (epoch
826
- * ms, future) auto-returns them to sale; `reason` tags the block (M3 uses it).
827
- * Throws ManageApiError 409 (conflicts) if any seat was just taken. */
828
- block(key: string, labels: string[], opts?: {
829
- releaseAt?: number;
830
- reason?: string;
831
- }): Promise<{
832
- ok: true;
833
- blocked: string[];
834
- }>;
835
- /** Return specific blocked seats to sale (one batched call). */
836
- unblock(key: string, labels: string[]): Promise<{
837
- ok: true;
838
- unblocked: string[];
839
- }>;
840
- /** Return every blocked seat to sale; resolves with the freed count. */
841
- unblockAll(key: string): Promise<{
842
- ok: true;
843
- freed: number;
844
- }>;
845
- /** Cancel bookings — return BOOKED seats to free (credit not refunded).
846
- * Guarded by the original booking reference. */
847
- unbook(key: string, labels: string[], bookingRef: string): Promise<{
848
- ok: true;
849
- unbooked: string[];
850
- }>;
851
- /** Set (ms, clamped 1–60 min server-side) or clear (null) the hold TTL. */
852
- setHoldTtl(key: string, holdTtlMs: number | null): Promise<{
853
- ok: true;
854
- holdTtlMs: number | null;
855
- }>;
856
- /** Inventory lifecycle by stable integrator bookingRef. Absent on Managed. */
857
- bookings(key: string, query?: InventoryBookingsQuery): Promise<InventoryBookingsPage>;
858
- /** Exact configured-value snapshot plus book/replay/cancellation audit. */
859
- booking(key: string, bookingRef: string): Promise<InventoryBookingDetail>;
860
- /** Alias matching the server SDK vocabulary. */
861
- listBookings(key: string, query?: InventoryBookingsQuery): Promise<InventoryBookingsPage>;
862
- /** Alias matching the server SDK vocabulary. */
863
- retrieveBooking(key: string, bookingRef: string): Promise<InventoryBookingDetail>;
864
- /** The organizer's current per section/zone availability windows (needs
865
- * `event:view`). Ids absent from `rules` are open / on sale. */
866
- availability(key: string): Promise<{
867
- rules: Record<string, AvailabilityRule>;
868
- }>;
869
- /** Replace the availability windows for a set of section/zone ids (needs
870
- * `event:block`). Ids absent from `rules` become open / on sale; a zone rule
871
- * cascades to its sections. The worker derives each id's seat labels, so
872
- * `labels` on the sent rules is best-effort. Resolves with the authoritative
873
- * effective `hidden` set (a due rule may fire at once) and the server-cleaned
874
- * `rules` map (fired timed/threshold windows dropped). */
875
- setAvailability(key: string, rules: Record<string, AvailabilityRule>): Promise<{
876
- ok: true;
877
- hidden: string[];
878
- rules: Record<string, AvailabilityRule>;
879
- }>;
880
- /** Allocation list with exact per-channel counts. `includeArchived` adds the
881
- * read-only archived rows behind the rail's "Show archived" control. */
882
- channels(key: string, opts?: {
883
- includeArchived?: boolean;
884
- }): Promise<ChannelListResult>;
885
- /** One page of the label → channel map that paints the allocation overlay.
886
- * Paged by label; follow `nextAfterLabel` until it is null. */
887
- channelAllocation(key: string, opts?: {
888
- afterLabel?: string;
889
- limit?: number;
890
- }): Promise<ChannelAllocationPage>;
891
- channelAudit(key: string, opts?: {
892
- limit?: number;
893
- before?: number;
894
- }): Promise<ChannelAuditPage>;
895
- createChannel(key: string, input: {
896
- name: string;
897
- color?: string | null;
898
- marker?: string | null;
899
- externalRef?: string | null;
900
- }): Promise<{
901
- ok: true;
902
- channel: ChannelRecord;
903
- }>;
904
- renameChannel(key: string, channelId: string, name: string): Promise<{
905
- ok: true;
906
- channel: ChannelRecord;
907
- }>;
908
- setChannelPaused(key: string, channelId: string, paused: boolean): Promise<{
909
- ok: true;
910
- channel: ChannelRecord;
911
- }>;
912
- /** Archive with a mandatory destination for the remaining allocation.
913
- * Throws ManageApiError 409 `channel_archive_blocked_by_holds` while any hold
914
- * is live; `err.details` carries the exact counts + retry window. */
915
- archiveChannel(key: string, channelId: string, destination: string | null): Promise<{
916
- ok: true;
917
- channel: ChannelRecord;
918
- assignmentVersion: number;
919
- moved: number;
920
- }>;
921
- /**
922
- * Versioned Apply. A stale `assignmentVersion` mutates NOTHING and throws
923
- * ManageApiError 409 `channel_assignment_conflict` — the caller keeps its
924
- * selection and offers "Refresh and review". There is no dry-run: the review
925
- * sheet previews locally, this call returns the authoritative buckets.
926
- */
927
- applyChannelAssignment(key: string, input: {
928
- targetChannelId: string | null;
929
- labels: string[];
930
- assignmentVersion: number;
931
- }): Promise<AssignmentResult>;
932
- /**
933
- * Read-only buyer projection for an audience (§8.6) — the SAME scoped server
934
- * view the buyer SDK receives, never a local approximation.
935
- *
936
- * Ships on the access-hardening branch. Older workers 404/405 here; callers
937
- * MUST feature-detect and quietly say the preview needs a newer server rather
938
- * than faking a projection client-side.
939
- */
940
- channelPreview(key: string, channelIds: string[], opts?: {
941
- includePublic?: boolean;
942
- }): Promise<ChannelPreviewProjection>;
943
- /**
944
- * Choose which sale route this channel opens.
945
- *
946
- * Since the server's 2026-08-06 change this is AUTHORIZATION, not a label:
947
- * exactly one of the four routes may mint buyer access for the channel and the
948
- * other three refuse with 409 `channel_access_intent_forbids`. The default is
949
- * `none`, which refuses all four — so a route has to be declared before any
950
- * buyer-facing action on the channel can succeed.
951
- *
952
- * Switching the route while buyers are already inside the current one is
953
- * refused with 409 `channel_intent_switch_blocked`, whose `details` name what
954
- * is live (`liveAccessLinks`, `activeSessions`). Retry with
955
- * `acknowledgeLiveAccess: true`: hosted links on the channel are revoked,
956
- * while sessions already minted keep their holds and drain on their own.
957
- * `intentSwitch` is present on the response ONLY when the switch disturbed
958
- * something, so the ordinary case stays the two-key body it has always been.
959
- */
960
- setChannelAccessIntent(key: string, channelId: string, accessIntent: ChannelAccessIntent, opts?: {
961
- acknowledgeLiveAccess?: boolean;
962
- reason?: string;
963
- }): Promise<{
964
- ok: true;
965
- channel: ChannelRecord;
966
- intentSwitch?: {
967
- closedLinks: number;
968
- keptSessions: number;
969
- };
970
- }>;
971
- /**
972
- * Mint a hosted access link. The 201 is the ONE and ONLY time `url` and
973
- * `capability` exist outside the buyer's browser — SeatLayer keeps a hash, so
974
- * there is no route, cache, or support escalation that can produce this string
975
- * again. Callers must reveal it immediately and then let it go.
976
- *
977
- * Every omitted field takes the server's default: expiry = when the event
978
- * starts, 100 redemptions, 4 seats per buyer, this channel's allocation only.
979
- * Platform bounds are enforced server-side and reported as 422 with the rule
980
- * spelled out in `ManageApiError.serverMessage`.
981
- *
982
- * NOT a side effect any more. This used to SET the channel's access intent to
983
- * `hosted_link`; since 2026-08-06 it REQUIRES it, and a channel declaring any
984
- * other route refuses with 409 `channel_access_intent_forbids`. Callers must
985
- * declare the route first — `ChannelsMode` does exactly that before it
986
- * creates, so a first buyer link on a fresh channel is still one gesture.
987
- */
988
- createAccessLink(key: string, channelId: string, input?: {
989
- label?: string | null;
990
- /** Absolute epoch ms. Omit for "when the event starts". */
991
- expiresAt?: number;
992
- maxRedemptions?: number;
993
- maxQuantity?: number;
994
- includePublic?: boolean;
995
- }): Promise<AccessLinkReveal>;
996
- /** Status only — label, expiry, redemptions, per-buyer cap, lineage, and the
997
- * live session count. Never the url, never the capability. Needs `:view`. */
998
- accessLinks(key: string, channelId: string): Promise<{
999
- links: AccessLinkStatusRecord[];
1000
- }>;
1001
- /**
1002
- * Rotate — the ONLY recovery for a link nobody kept. The old URL stops opening
1003
- * immediately and the response is a fresh one-time reveal.
1004
- *
1005
- * `endActiveSessions` is REQUIRED, not defaulted: the organizer must say
1006
- * whether buyers already inside finish their checkout or lose access now. The
1007
- * server answers 422 `end_active_sessions_required` if it is omitted, and that
1008
- * refusal is correct — a UI must not pick either branch on their behalf.
1009
- */
1010
- rotateAccessLink(key: string, channelId: string, linkId: string, endActiveSessions: boolean): Promise<AccessLinkReveal & {
1011
- previous: AccessLinkRecord;
1012
- endedSessions: number;
1013
- }>;
1014
- /** Revoke. The link stops opening immediately; `endActiveSessions` decides
1015
- * whether the buyers already inside keep their sessions. */
1016
- revokeAccessLink(key: string, channelId: string, linkId: string, endActiveSessions?: boolean): Promise<{
1017
- ok: true;
1018
- link: AccessLinkRecord;
1019
- endedSessions: number;
1020
- }>;
1021
- report(key: string): Promise<ReportResult>;
1022
- controlRoom(key: string, windowMinutes?: number): Promise<ControlRoomSnapshot>;
1023
- /** Allocation beside immutable booking-time channel attribution. */
1024
- channelReport(key: string): Promise<ChannelReportResult>;
1025
- createChannelReportLink(key: string, channelId: string, input?: {
1026
- label?: string;
1027
- includesBookedValue?: boolean;
1028
- /** @deprecated Use `includesBookedValue`. */
1029
- includesRevenue?: boolean;
1030
- expiresAt?: number;
1031
- }): Promise<ChannelReportLinkReveal>;
1032
- channelReportLinks(key: string, channelId: string): Promise<{
1033
- links: ChannelReportLinkRecord[];
1034
- }>;
1035
- revokeChannelReportLink(key: string, channelId: string, linkId: string): Promise<{
1036
- ok: true;
1037
- link: ChannelReportLinkRecord;
1038
- }>;
1039
- log(key: string, opts?: {
1040
- limit?: number;
1041
- before?: number;
1042
- }): Promise<LogPage>;
1043
- /** CSV report as a Blob (Bearer auth can't ride a plain <a href>). Host builds
1044
- * an object URL for download. */
1045
- reportCsv(key: string): Promise<Blob>;
1046
- }
1047
-
1048
- /**
1049
- * SeatManager — the organizer manage surface, packaged for the SDK.
1050
- *
1051
- * Productizes the SeatLayer dashboard's ManageEventPage into a framework-
1052
- * agnostic class (mirrors how SeatPicker productized the buyer flow). It mounts
1053
- * the shared engine in `manageMode`, subscribes to the event's realtime channel
1054
- * and drives three control-room tools on one persistent canvas:
1055
- *
1056
- * - **view** — a live board: realtime seat repaint (flash on hold/book),
1057
- * live KPI tallies + configured booked value, and a streaming activity
1058
- * feed derived from the delta stream + audit log. Read-only.
1059
- * - **inspect** — select one seat to read its live inventory context.
1060
- * - **block** — bulk-first block/unblock: marquee-drag, ⌘A select-all,
1061
- * whole-category / whole-section select, single-seat fallback →
1062
- * one batched block/unblock (optimistic, reconciled by the WS),
1063
- * and timed auto-release.
1064
- *
1065
- * Auth: organizer chart/media, inventory, realtime and reports all carry a
1066
- * short-lived event-scoped browser grant (`mse_…`) via {@link ManageApi}.
1067
- * Tenant secret keys are unsupported in this browser surface, and organizer
1068
- * authority is never downgraded to buyer `/pub`.
1069
- */
1070
-
1071
- type SeatManagerMode = 'view' | 'inspect' | 'block' | 'sections' | 'channels';
1072
- /** Short-lived, event-scoped browser credential minted by a trusted backend. */
1073
- type EventScopedManageToken = `mse_${string}`;
1074
- /**
1075
- * Capabilities the cockpit's token was minted with. Channels mode is gated on
1076
- * these and fails CLOSED: no `event:channels:view` ⇒ no Channels pill at all;
1077
- * view without `event:channels:manage` ⇒ read-only inspection with every
1078
- * mutation control absent, not merely disabled.
1079
- */
1080
- type SeatManagerCapability = 'event:view' | 'event:block' | 'event:cancel' | 'event:reports' | 'event:channels:view' | 'event:channels:manage';
1081
- /** DO seat status — 'blocked' has no engine analogue (→ 'not_for_sale'). */
1082
- type DoStatus = 'free' | 'held' | 'booked' | 'blocked';
1083
- /** Live KPI snapshot pushed to `onTallies` on every state change. */
1084
- interface SeatManagerTallies {
1085
- free: number;
1086
- held: number;
1087
- booked: number;
1088
- blocked: number;
1089
- /** Total seats on the chart. */
1090
- total: number;
1091
- /** booked / total, 0–100. */
1092
- capacityPct: number;
1093
- /** booked / (total − blocked), 0–100 — sell-through of sellable inventory. */
1094
- sellThroughPct: number;
1095
- /** Exact Σ booked unit_price snapshots from the authenticated report. */
1096
- bookedValue: number;
1097
- /** @deprecated Use `bookedValue`. */
1098
- grossRevenue: number;
1099
- /** Booked value is never reconstructed from the chart's current list price. */
1100
- bookedValueStatus: 'loading' | 'current' | 'stale';
1101
- /** @deprecated Use `bookedValueStatus`. */
1102
- revenueStatus: 'loading' | 'current' | 'stale';
1103
- /** ISO-4217 currency for `bookedValue`. */
1104
- currency: string;
1105
- }
1106
- /** One streamed activity line for the live feed. */
1107
- interface SeatManagerActivity {
1108
- id: string;
1109
- at: number;
1110
- label: string;
1111
- /** Full labels affected by this one backend/realtime operation. */
1112
- labels: string[];
1113
- count: number;
1114
- /** Human verb: held / booked / released / blocked / unblocked. */
1115
- verb: string;
1116
- status: DoStatus;
1117
- /** Spatial context for grouped activity when the chart defines sections. */
1118
- sectionIds?: string[];
1119
- sectionLabels?: string[];
1120
- }
1121
- /** Fired after a successful organizer action, for host toasts/telemetry. */
1122
- interface SeatManagerActionResult {
1123
- action: 'block' | 'unblock' | 'unblockAll' | 'cancelBooking' | 'setHoldTtl';
1124
- labels: string[];
1125
- count: number;
1126
- }
1127
- interface SeatManagerOptions {
1128
- /** CSS selector or element to mount into. */
1129
- container: string | HTMLElement;
1130
- /** API origin. Defaults to https://api.seatlayer.io. */
1131
- apiBase?: string;
1132
- /** Event key (e.g. `ev_xxx` / `west-end-p3`). */
1133
- eventKey: string;
1134
- /**
1135
- * Short-lived, event-scoped `mse_…` browser grant minted by your backend.
1136
- * Tenant secret keys are unsupported in SeatManager and must stay server-side.
1137
- */
1138
- token: EventScopedManageToken;
1139
- /** Absolute token expiry (epoch ms). Enables proactive in-place rotation. */
1140
- tokenExpiresAt?: number;
1141
- /** Initial mode. Default 'view'. */
1142
- mode?: SeatManagerMode;
1143
- /**
1144
- * The capability set this token was minted with. Supply it whenever you mint
1145
- * an `mse_…` grant — it is the only way the widget can know a delegated token
1146
- * carries `event:channels:manage`, and without it Channels mode stays
1147
- * read-only (fail-closed).
1148
- */
1149
- capabilities?: SeatManagerCapability[] | string[];
1150
- /** ISO-4217 fallback currency for configured booked value (chart/event wins). */
1151
- currency?: string;
1152
- /** Chart theme override for the chrome (rails/bar). Chart colors come from the doc. */
1153
- theme?: ChartTheme;
1154
- /**
1155
- * Keep the canvas painting even when the tab is hidden/backgrounded (a war-room
1156
- * board on a second monitor). Calls `forceDraw()` after each delta so Chrome's
1157
- * rAF throttling on occluded tabs never leaves the board stale. Default true.
1158
- */
1159
- keepLiveWhileHidden?: boolean;
1160
- /**
1161
- * Opt in to camera-following for new buyer holds/bookings. Off by default so
1162
- * a live event never steals an operator's current map context.
1163
- */
1164
- followLive?: boolean;
1165
- /** Chart + first snapshot are loaded and the board is live. */
1166
- onReady?: () => void;
1167
- /** Live KPI tallies changed. */
1168
- onTallies?: (tallies: SeatManagerTallies) => void;
1169
- /** A grouped live/audit activity item arrived. */
1170
- onActivity?: (activity: SeatManagerActivity) => void;
1171
- /** Exact private control-room projection changed. */
1172
- onControlRoom?: (snapshot: ControlRoomSnapshot) => void;
1173
- /** Called before token expiry. The manager swaps the result without remounting. */
1174
- onTokenRefresh?: () => Promise<{
1175
- token: EventScopedManageToken;
1176
- expiresAt: number;
1177
- }>;
1178
- /** Tool/mode changed from inside the shared cockpit. */
1179
- onModeChange?: (mode: SeatManagerMode) => void;
1180
- /** Follow-live preference changed from inside the cockpit. */
1181
- onFollowLiveChange?: (enabled: boolean) => void;
1182
- /** Block-mode selection changed (marquee / ⌘A / category / section / tap). */
1183
- onSelectionChange?: (seats: ExpandedSeat[]) => void;
1184
- /** A block/unblock/cancel action completed successfully. */
1185
- onActionComplete?: (result: SeatManagerActionResult) => void;
1186
- /**
1187
- * The realtime link connected or dropped, with the moment the numbers on
1188
- * screen were last known good.
1189
- *
1190
- * A host embedding this cockpit renders its own chrome around it, and until
1191
- * now had no way to know the board had gone stale: the manager tracked the
1192
- * drop internally (its own LIVE/RECONNECTING pill) and told nobody. A host
1193
- * that polls on a timer and pauses while the tab is hidden therefore showed
1194
- * arbitrarily old numbers that looked exactly like fresh ones.
1195
- */
1196
- onConnectionChange?: (state: SeatManagerConnection) => void;
1197
- onError?: (err: unknown) => void;
1198
- }
1199
- /** Realtime link state, as reported to the embedding host. */
1200
- interface SeatManagerConnection {
1201
- /** `live` while the socket is open; `reconnecting` from drop until reopen. */
1202
- status: 'live' | 'reconnecting';
1203
- /**
1204
- * `Date.now()` of the last snapshot or delta accepted from the server, or
1205
- * null before the first one. This is the honest "as of" for whatever the host
1206
- * is displaying — NOT the time the connection dropped, which is later and
1207
- * would overstate freshness.
1208
- */
1209
- lastMessageAt: number | null;
1210
- }
1211
- declare class SeatManager {
1212
- private readonly opts;
1213
- private readonly api;
1214
- private readonly key;
1215
- private keepLive;
1216
- private host;
1217
- private root;
1218
- private mapHost;
1219
- private els;
1220
- private renderer;
1221
- private doc;
1222
- private mode;
1223
- private labelToId;
1224
- private labelToSeat;
1225
- private allIds;
1226
- /**
1227
- * GA inventory units — real sellable labels the server counts, with NO seat
1228
- * geometry and therefore no renderer binding. They live here rather than in
1229
- * `labelToId`/`allIds` so every paint path keeps addressing paintable nodes
1230
- * only, while the tally denominator finally covers the same universe the
1231
- * numerator does. Without them a GA sale hit `booked` but not `total`:
1232
- * Free under-reported by GA capacity and SOLD% could exceed 100%.
1233
- */
1234
- private gaUnitLabelSet;
1235
- private status;
1236
- /** Live non-free counters, moved by each delta rather than re-walked. */
1237
- private counts;
1238
- /** Bumped whenever the seat model is replaced wholesale (a full snapshot). */
1239
- private modelVersion;
1240
- private currency;
1241
- /** Currency read from the event/control-room projection; host currency is fallback only. */
1242
- private authoritativeCurrency;
1243
- private authoritativeGrossRevenue;
1244
- private revenueStatus;
1245
- private revenueRequest;
1246
- private controlRoomSnapshot;
1247
- /** Bearer-fetched organizer chart media, revoked with this manager. */
1248
- private readonly organizerAssetUrls;
1249
- /**
1250
- * The server's own totals, pinned to the client model they were read against.
1251
- * Display = server baseline + (client now − client then), so the authoritative
1252
- * numbers land exactly on arrival and deltas still move them between reads.
1253
- * A wholesale model replacement invalidates the pairing (`model`), and the
1254
- * client tallies — themselves a fresh authenticated read — take over.
1255
- */
1256
- private serverBaseline;
1257
- /** Latest presence frame, held whether or not a snapshot has landed yet. */
1258
- private livePresence;
1259
- /** Latest cumulative booked gross pushed on a delta frame. */
1260
- private liveGross;
1261
- /** Coalesces a burst of deltas into one KPI/rail repaint. */
1262
- private paintHandle;
1263
- private trendWindowMinutes;
1264
- private heatEnabled;
1265
- private followLive;
1266
- private lastKpiValues;
1267
- private activeKpiDeltas;
1268
- private ws;
1269
- private reconnectTimer;
1270
- private attempt;
1271
- private closed;
1272
- /** Mirrors the `live` root class, so the getter never has to read the DOM. */
1273
- private connectionStatus;
1274
- /** When the server last told us something. Stamped on accepted traffic only —
1275
- * a socket that opens and says nothing has not refreshed anything. */
1276
- private lastMessageAt;
1277
- private ready;
1278
- private feed;
1279
- private feedTimer;
1280
- private toastTimer;
1281
- private liveEventTimer;
1282
- private kpiCleanupTimer;
1283
- private followLiveTimer;
1284
- private followSeatTimer;
1285
- private releaseAt;
1286
- private layoutObserver;
1287
- private tokenExpiresAt;
1288
- private tokenRefreshTimer;
1289
- private tokenRefreshInFlight;
1290
- private sectionByObject;
1291
- private sectionLabelById;
1292
- private sectionsBase;
1293
- private availabilityRules;
1294
- private effectiveHidden;
1295
- private effectiveClosed;
1296
- private availabilitySaving;
1297
- private lastSyncedAt;
1298
- private blockedQuery;
1299
- private blockedSection;
1300
- private blockedResultLimit;
1301
- private unblockAllConfirmTimer;
1302
- /**
1303
- * Sales channels (M6b).
1304
- *
1305
- * Two fields, and the split between them is the whole point. `channelCaps` is
1306
- * AUTHORITY and is known early — it is read from the token's declared
1307
- * capabilities before the first rail paint, so the Channels pill either
1308
- * exists from the start or never appears. `channels` is the loaded sub-app,
1309
- * and it now arrives late: its module is fetched on first entry into Channels
1310
- * mode (`ensureChannels`), not at mount.
1311
- *
1312
- * So every gate that used to ask `!!this.channels` — the pill, the mode
1313
- * whitelist, the `c` shortcut — asks `channelCaps.view` instead. Asking the
1314
- * instance would make a permission the member genuinely has look like one
1315
- * they do not for as long as a network fetch takes.
1316
- */
1317
- private channels;
1318
- private channelCaps;
1319
- /** Supersedes an async capability probe after token/capability rotation. */
1320
- private channelCapabilityResolution;
1321
- /** In-flight `import('./channelsMode')`, so concurrent entries load once. */
1322
- private channelsLoading;
1323
- private readonly onFullscreenChange;
1324
- private readonly onKeyDown;
1325
- private readonly onRailClick;
1326
- constructor(options: SeatManagerOptions);
1327
- /** Build the DOM, load the chart, subscribe to realtime, mount the board. */
1328
- render(): Promise<this>;
1329
- setMode(mode: SeatManagerMode): void;
1330
- /**
1331
- * Decide what this token may do with sales channels.
1332
- *
1333
- * Declared capabilities win — a host that mints an `mse_…` grant knows exactly
1334
- * what it asked for. A delegated token with no declaration is probed for read
1335
- * access and then treated as READ-ONLY, because "we could not tell" must never
1336
- * render mutation controls.
1337
- */
1338
- private resolveChannelCapabilities;
1339
- /**
1340
- * Load Channels mode, once, on first entry.
1341
- *
1342
- * Everything about this method is shaped by one rule: the cockpit must stay
1343
- * usable and honest while the module is in the air.
1344
- *
1345
- * - The promise is memoized, so a member who taps the pill twice, or a host
1346
- * whose deep link and initial `mode` prop both ask for Channels, loads one
1347
- * module and builds one instance.
1348
- * - Authority is re-checked on arrival. A token rotation can revoke
1349
- * `event:channels:view` between the tap and the load, and building the
1350
- * sub-app for a token that no longer carries the capability would put
1351
- * mutation controls on screen that every server call then refuses.
1352
- * - The mode is re-checked too. Someone who taps Channels and then Monitor
1353
- * before the chunk lands must not be yanked into Channels when it does; the
1354
- * instance is kept (it is paid for) but only entered if we are still there.
1355
- * - A failed load is stated in the rail rather than swallowed. Channels is a
1356
- * whole surface — silently showing an empty one would read as "this event
1357
- * has no channels", which is a lie about inventory.
1358
- */
1359
- private ensureChannels;
1360
- /** The adapter between the cockpit's internals and Channels mode. */
1361
- private buildChannelsHost;
1362
- /** Actual on-screen seat diameter, for the channel overlay's marks. The
1363
- * renderer's base seat radius is 9 chart units; retaining the camera scale
1364
- * (rather than capping it) keeps every preview paint aligned with the real
1365
- * chart geometry at deep zoom. */
1366
- private seatPixelSize;
1367
- /** Toggle the normalized sales-velocity outline overlay without changing seat colors. */
1368
- setHeatOverlay(enabled: boolean): void;
1369
- /** Toggle opt-in camera following for new buyer hold/book events. */
1370
- setFollowLive(enabled: boolean): void;
1371
- /** Update background-tab repaint policy without rebuilding the board. */
1372
- setKeepLiveWhileHidden(enabled: boolean | undefined): void;
1373
- /** Update the host fallback currency; an event/control-room currency still wins. */
1374
- setCurrency(currency: string | undefined): void;
1375
- /** Apply organizer chrome tokens in place without losing camera or selection. */
1376
- setTheme(theme: ChartTheme | undefined): void;
1377
- /** Replace the declared authority for the current token and fail closed. */
1378
- setCapabilities(capabilities: SeatManagerOptions['capabilities']): void;
1379
- /** Change proactive token-refresh policy without rebuilding the manager. */
1380
- setTokenRefresh(onTokenRefresh: SeatManagerOptions['onTokenRefresh']): void;
1381
- /** Change the current-vs-previous sales window and refresh the private projection. */
1382
- setTrendWindow(windowMinutes: number): Promise<ControlRoomSnapshot>;
1383
- enterFullscreen(): Promise<void>;
1384
- exitFullscreen(): Promise<void>;
1385
- isFullscreen(): boolean;
1386
- private toggleFullscreen;
1387
- /** Rotate the delegated credential without rebuilding DOM, canvas or socket. */
1388
- setToken(token: EventScopedManageToken, expiresAt?: number): void;
1389
- private scheduleTokenRefresh;
1390
- private rotateToken;
1391
- /** Bulk block the given labels (or the current selection when omitted). */
1392
- block(labels?: string[], opts?: {
1393
- releaseAt?: number;
1394
- reason?: string;
1395
- }): Promise<void>;
1396
- unblock(labels?: string[]): Promise<void>;
1397
- unblockAll(): Promise<void>;
1398
- /** Cancel bookings (BOOKED → free), guarded by the original booking ref. */
1399
- cancelBooking(labels: string[], bookingRef: string): Promise<void>;
1400
- selectAll(): ExpandedSeat[];
1401
- selectSection(sectionId: string): ExpandedSeat[];
1402
- selectByLabels(labels: string[]): ExpandedSeat[];
1403
- clearSelection(): void;
1404
- getSelection(): ExpandedSeat[];
1405
- getReport(): Promise<ReportResult>;
1406
- getControlRoomSnapshot(windowMinutes?: number): Promise<ControlRoomSnapshot>;
1407
- /**
1408
- * The realtime link's current state and the "as of" behind it.
1409
- *
1410
- * Pair with `onConnectionChange` for the edges: a host that mounts after a
1411
- * drop, or re-reads on tab focus, needs to be able to ASK rather than wait
1412
- * for the next transition that may never come.
1413
- */
1414
- getConnection(): SeatManagerConnection;
1415
- getLog(opts?: {
1416
- limit?: number;
1417
- before?: number;
1418
- }): Promise<{
1419
- entries: LogEntry[];
1420
- nextBefore: number | null;
1421
- }>;
1422
- setHoldTtl(ms: number | null): Promise<void>;
1423
- zoomToFit(): void;
1424
- destroy(): void;
1425
- private buildRenderer;
1426
- /** Block always uses a marquee. Channels only enables its marquee after the
1427
- * organizer deliberately chooses Assign seats; Pan map keeps desktop drag
1428
- * available for large charts. */
1429
- private isBulkSelectMode;
1430
- /**
1431
- * Block never touches held or booked inventory, so it cannot select it.
1432
- * Channels must be able to select it — the Review sheet's honesty depends on
1433
- * counting the held and sold units inside a marquee and saying they will not
1434
- * move, rather than silently omitting them from the selection.
1435
- */
1436
- private selectableStatuses;
1437
- private updateRendererInteraction;
1438
- private handleSeatSelect;
1439
- /**
1440
- * Build the client's inventory universe from the chart.
1441
- *
1442
- * `expandChart` yields SEATS — it has no output for a GA area, whose capacity
1443
- * is sold as N synthetic unit labels. The server's seat map keys, its deltas
1444
- * and its `totals` all speak those labels, so a client that only knows seats
1445
- * counts GA sales in the numerator (every key of the snapshot is written into
1446
- * `status`) while leaving them out of the denominator. Registering the GA
1447
- * units here — labels only, never a render binding — is what makes the two
1448
- * agree.
1449
- */
1450
- private buildUnitUniverse;
1451
- /** Every sellable unit the client knows: seats + GA capacity. */
1452
- private unitTotal;
1453
- /** Every label the client models, whether or not it can be painted. */
1454
- private knownLabels;
1455
- private repaintAll;
1456
- /**
1457
- * Open the cockpit's realtime socket AS THE ORGANIZER.
1458
- *
1459
- * The scope has to be established before the upgrade, because a browser
1460
- * `WebSocket` cannot send an Authorization header: the manage token is traded
1461
- * over HTTPS for a one-use ticket which rides in `Sec-WebSocket-Protocol`.
1462
- * Without it the server treats this socket as an anonymous public buyer and
1463
- * projects its deltas, so any change inside a private channel allocation is
1464
- * structurally suppressed and the map silently drifts.
1465
- *
1466
- * If the mint fails, remain reconnecting. An unticketed socket is a buyer
1467
- * projection, so applying it to organizer state would be worse than staying
1468
- * visibly offline while the host refreshes authority or upgrades the API.
1469
- */
1470
- private connect;
1471
- private scheduleReconnect;
1472
- private onMessage;
1473
- /**
1474
- * Adopt the cumulative booked gross a delta frame carried.
1475
- *
1476
- * Stashed with its arrival time so an in-flight control-room read can decide
1477
- * whether it is holding the newer number: a frame that landed after the
1478
- * request started is newer than the response, one that landed before is not.
1479
- */
1480
- private applyLiveGross;
1481
- /** The single writer for a label's status, so the counters never drift. */
1482
- private setStatusLabel;
1483
- private resnapshot;
1484
- /**
1485
- * Replace the whole seat model.
1486
- *
1487
- * `fallback` is the compact frame's modal status: those snapshots list only
1488
- * the seats that DIFFER from it, so every other known label takes it. Without
1489
- * this the omitted majority would silently fall back to `free` — fine when
1490
- * the mode really is free, wrong the moment it is not.
1491
- */
1492
- private applySnapshot;
1493
- /** The one O(n) walk left: a wholesale model replacement re-bases the counters. */
1494
- private recountAll;
1495
- /** Optimistic local write shared by organizer actions. Paint and tally once,
1496
- * even when an arena-sized operation changes hundreds of seats. */
1497
- private setSeatsLocal;
1498
- /** Keep the canvas painting on hidden/occluded tabs (war-room second monitor). */
1499
- private afterPaint;
1500
- private activityColor;
1501
- private sectionsForLabels;
1502
- private pulseSeatLabels;
1503
- /** Render one grouped realtime operation at the right semantic zoom level. */
1504
- private paintSpatialActivity;
1505
- private locateSection;
1506
- private locateActivity;
1507
- private showLiveEvent;
1508
- private applyReportRevenue;
1509
- /**
1510
- * Read the server's own control-room projection.
1511
- *
1512
- * Called on mount, on every socket (re)connect and after an organizer action —
1513
- * never on a timer and never per delta frame. Presence and gross that arrived
1514
- * on the socket AFTER this request started are newer than the response, so
1515
- * they survive it; anything older defers to the read.
1516
- */
1517
- private refreshControlRoom;
1518
- /** Pin the server's totals to the client model they were read against. */
1519
- private rebaseServerTotals;
1520
- /** What the client's own model says — GA units included since `render()`. */
1521
- private clientTallies;
1522
- /**
1523
- * The numbers the KPI bar and rail render.
1524
- *
1525
- * The server is the authority: its totals land exactly as read, and the
1526
- * delta-driven client model carries them forward until the next read. Before
1527
- * the first snapshot — and after a wholesale model replacement invalidates the
1528
- * pairing — the client model stands alone.
1529
- */
1530
- private buildTallies;
1531
- /**
1532
- * Queue one KPI/rail repaint for this burst of changes.
1533
- *
1534
- * A delta frame can carry hundreds of seats and `paintKpis` rebuilds eight
1535
- * nodes from scratch, so painting per change is what made an arena-sized
1536
- * frame expensive. Coalescing on a frame keeps the burst to a single rebuild;
1537
- * without `requestAnimationFrame` (SSR, an older test env) it paints inline
1538
- * rather than dropping the update.
1539
- */
1540
- private recomputeTallies;
1541
- private flushTallies;
1542
- private verbFor;
1543
- private pushActivity;
1544
- private seedFeed;
1545
- private startFeedClock;
1546
- private selectionLabels;
1547
- private syncSelection;
1548
- private buildChrome;
1549
- private updateContainerLayout;
1550
- private sectionOptions;
1551
- private buildSectionOptions;
1552
- private paintModeTabs;
1553
- private paintFollowLiveButton;
1554
- private paintHeatButton;
1555
- private paintMomentumHelp;
1556
- private paintFullscreenButton;
1557
- private paintTrendWindow;
1558
- private setLive;
1559
- private updateZoomHint;
1560
- private formatKpiDelta;
1561
- private paintKpis;
1562
- private paintRail;
1563
- private renderViewRail;
1564
- /** Live presence wins over the snapshot's copy — it is the fresher channel,
1565
- * and it exists from the first frame rather than the first fetch. */
1566
- private presenceCounts;
1567
- private paintMonitorInsights;
1568
- private applyHeatOverlay;
1569
- private renderInspectRail;
1570
- /** Pull the organizer's availability rules (event:view). Called on load and on
1571
- * every WS (re)connect, mirroring how the other panels re-hydrate. `closed` is
1572
- * deterministic from the rules; `hidden` (which folds in already-due timed /
1573
- * threshold windows) comes from the snapshot + WS effective set. */
1574
- private refreshAvailability;
1575
- /** Run a token-authed op; on a 401 re-mint via onTokenRefresh and retry once. */
1576
- private withAuthRetry;
1577
- private closedIdsFromRules;
1578
- /** Adopt a new effective hidden/closed set (from a snapshot or WS broadcast) and
1579
- * repaint the rail + canvas when it actually moves. */
1580
- private updateEffectiveAvailability;
1581
- /** Canvas read of the availability state: dim hidden sections to a whisper,
1582
- * half-light closed sections, leave open sections normal. Only in Sections mode;
1583
- * cleared in every other tool. */
1584
- private applySectionCanvasTreatment;
1585
- /** Zone-grouped render tree: each zone header then its sections (which follow the
1586
- * zone window), then loose sections + the ungrouped bucket. Effective hidden /
1587
- * closed come from the live sets, rules from the organizer map. */
1588
- private buildSectionRows;
1589
- private renderSectionsRail;
1590
- private sectionRowHtml;
1591
- private wireSectionRail;
1592
- /** Change one row's availability mode. A zone rule subsumes its child section
1593
- * rules, so those are dropped from the map (the zone window is the truth). */
1594
- private setSectionMode;
1595
- /** Edit a timed reveal time / threshold percent on an existing row rule. */
1596
- private setSectionRulePatch;
1597
- /** Optimistically adopt the new rules, then reconcile with the server-cleaned
1598
- * map + effective hidden/closed sets. Rolls back the rules on failure. */
1599
- private persistAvailability;
1600
- private paintLegend;
1601
- private paintFeed;
1602
- private renderBlockRail;
1603
- private toggleCategory;
1604
- /** A category/filter is a real toggle: add the missing seats, or remove the
1605
- * whole group when every eligible seat in it is already selected. */
1606
- private toggleLabels;
1607
- private isBlockSelectable;
1608
- private paintSelBar;
1609
- private paintCategoryControls;
1610
- private filteredBlockedSeats;
1611
- private paintBlockedInventory;
1612
- private confirmUnblockAll;
1613
- private resetUnblockAllConfirm;
1614
- private done;
1615
- private toastOk;
1616
- private toastErr;
1617
- private toast;
1618
- private fail;
1619
- }
1620
-
1621
- /**
1622
- * Channels mode — the sales-channel management surface inside the SeatManager
1623
- * cockpit. It ships in the SDK, so every embedding platform (and our own Control
1624
- * Room) gets the same organizer experience.
1625
- *
1626
- * Design contract: `SeatmapUX/05 Event Manager.dc.html` (all 8 desktop states +
1627
- * the mobile artboard). Behaviour: sales-channels-product-ux-spec §8 (states),
1628
- * §9 (language), §13 (a11y + compact detents). Motion: motion-system §2 tokens,
1629
- * mirrored here as `--slm-mo-*` so an embed is self-contained, and §3 cockpit
1630
- * choreography.
1631
- *
1632
- * Boundaries this module keeps:
1633
- *
1634
- * - **Capability gating is fail-closed.** Without `event:channels:view` the
1635
- * cockpit never renders the pill (SeatManager's job). Without
1636
- * `event:channels:manage` every mutation control is ABSENT — not disabled —
1637
- * so a read-only operator is never shown authority they do not have.
1638
- * - **Nothing here mutates physical inventory.** Assignment moves an
1639
- * allocation; held and booked units are never rewritten.
1640
- * - **Staging is local, truth is the server.** There is no dry-run endpoint,
1641
- * so the Review sheet previews the buckets client-side and then re-renders
1642
- * the AUTHORITATIVE bucket counts from the Apply response. A stale
1643
- * `assignmentVersion` mutates nothing: the selection survives and the only
1644
- * action offered is Refresh and review.
1645
- * - **Forward-compatible reads.** The `access` field and the buyer-preview
1646
- * projection land on the access-hardening branch. Both are feature-detected;
1647
- * absent, the access line reads "—" and the Preview segment says it needs a
1648
- * newer server. Neither ever blocks allocation work.
8
+ * Every function here takes values and returns a string (or reads one field off
9
+ * a DOM node it is handed). None of them touch `this`, the client, or any
10
+ * cockpit state. Bodies are verbatim from channelsMode.ts; `bucketRowsHtml` is
11
+ * re-exported there because `@seatlayer/js/manager` publishes it.
1649
12
  */
1650
13
 
1651
- /** The seat facts the overlay needs. Chart space, not screen space. */
1652
- interface ChannelsSeatView {
1653
- id: string;
1654
- label: string;
1655
- x: number;
1656
- y: number;
1657
- }
1658
- /** One row offered to the bulk assignment chooser. A physically segmented row
1659
- * (one label split across several row objects) shares one logical id, so the
1660
- * organizer picks "Row AA" once rather than three fragments of it. */
1661
- interface ChannelsRowView {
1662
- id: string;
1663
- label: string;
1664
- sectionId: string;
1665
- sectionLabel: string;
1666
- labels: string[];
1667
- }
1668
- /** The `ManageApi` subset Channels mode uses — structural so tests can pass a
1669
- * hand-rolled double without constructing a real client. */
1670
- interface ChannelsClient {
1671
- channels(key: string, opts?: {
1672
- includeArchived?: boolean;
1673
- }): Promise<ChannelListResult>;
1674
- channelAllocation(key: string, opts?: {
1675
- afterLabel?: string;
1676
- limit?: number;
1677
- }): Promise<ChannelAllocationPage>;
1678
- createChannel(key: string, input: {
1679
- name: string;
1680
- color?: string | null;
1681
- marker?: string | null;
1682
- externalRef?: string | null;
1683
- }): Promise<{
1684
- ok: true;
1685
- channel: ChannelRecord;
1686
- }>;
1687
- renameChannel(key: string, channelId: string, name: string): Promise<{
1688
- ok: true;
1689
- channel: ChannelRecord;
1690
- }>;
1691
- setChannelPaused(key: string, channelId: string, paused: boolean): Promise<{
1692
- ok: true;
1693
- channel: ChannelRecord;
1694
- }>;
1695
- archiveChannel(key: string, channelId: string, destination: string | null): Promise<{
1696
- ok: true;
1697
- channel: ChannelRecord;
1698
- assignmentVersion: number;
1699
- moved: number;
1700
- }>;
1701
- applyChannelAssignment(key: string, input: {
1702
- targetChannelId: string | null;
1703
- labels: string[];
1704
- assignmentVersion: number;
1705
- }): Promise<AssignmentResult>;
1706
- channelPreview(key: string, channelIds: string[], opts?: {
1707
- includePublic?: boolean;
1708
- }): Promise<ChannelPreviewProjection>;
1709
- /** Choose the channel's sale route. Authorization since 2026-08-06, so this is
1710
- * a precondition of every buyer-facing action, not a label. `opts` carries the
1711
- * acknowledgement that unblocks a switch with live buyer access. */
1712
- setChannelAccessIntent(key: string, channelId: string, accessIntent: ChannelAccessIntent, opts?: {
1713
- acknowledgeLiveAccess?: boolean;
1714
- reason?: string;
1715
- }): Promise<{
1716
- ok: true;
1717
- channel: ChannelRecord;
1718
- intentSwitch?: {
1719
- closedLinks: number;
1720
- keptSessions: number;
1721
- };
1722
- }>;
1723
- /** 201 with the ONE-TIME reveal. Every omitted field takes the server default
1724
- * (expiry = event start, 100 redemptions, 4 seats per buyer). */
1725
- createAccessLink(key: string, channelId: string, input: {
1726
- label?: string | null;
1727
- expiresAt?: number;
1728
- maxRedemptions?: number;
1729
- maxQuantity?: number;
1730
- includePublic?: boolean;
1731
- }): Promise<AccessLinkReveal>;
1732
- /** Status only. This response has no url and no capability, by contract. */
1733
- accessLinks(key: string, channelId: string): Promise<{
1734
- links: AccessLinkStatusRecord[];
1735
- }>;
1736
- /** `endActiveSessions` is required — the server 422s without it, deliberately. */
1737
- rotateAccessLink(key: string, channelId: string, linkId: string, endActiveSessions: boolean): Promise<AccessLinkReveal>;
1738
- revokeAccessLink(key: string, channelId: string, linkId: string, endActiveSessions?: boolean): Promise<{
1739
- ok: true;
1740
- link: unknown;
1741
- endedSessions: number;
1742
- }>;
1743
- }
1744
- interface ChannelsCapabilities {
1745
- view: boolean;
1746
- manage: boolean;
1747
- }
1748
- /** Everything Channels mode needs from the cockpit around it. */
1749
- interface ChannelsModeHost {
1750
- eventKey: string;
1751
- api: ChannelsClient;
1752
- /** The rail scroll container the mode paints into. */
1753
- rail: HTMLElement;
1754
- /** An absolutely-positioned layer over the map (overlay canvas, flags, bars). */
1755
- mapLayer: HTMLElement;
1756
- /** The widget root — dialogs mount here so they inherit the widget's tokens. */
1757
- root: HTMLElement;
1758
- seats(): ChannelsSeatView[];
1759
- statusOf(label: string): ChannelSeatStatus | undefined;
1760
- selectionLabels(): string[];
1761
- selectByLabels(labels: string[]): void;
1762
- clearSelection(): void;
1763
- selectSection(sectionId: string): void;
1764
- sections(): Array<{
1765
- id: string;
1766
- label: string;
1767
- }>;
1768
- /** Every selectable label inside one section, for the additive scope chooser. */
1769
- labelsInSection(sectionId: string): string[];
1770
- /** Logical rows across the whole chart, grouped by their section. */
1771
- rows(): ChannelsRowView[];
1772
- categories(): Array<{
1773
- key: string;
1774
- label: string;
1775
- color?: string;
1776
- }>;
1777
- labelsInCategory(key: string): string[];
1778
- sectionOfLabel(label: string): {
1779
- id: string;
1780
- label: string;
1781
- } | null;
1782
- /** Chart-space → container pixels. Null when there is no live renderer. */
1783
- worldToScreen(point: {
1784
- x: number;
1785
- y: number;
1786
- }): {
1787
- x: number;
1788
- y: number;
1789
- } | null;
1790
- /** Approximate on-screen seat size in CSS pixels, for the overlay marks. */
1791
- seatPixelSize(): number;
1792
- /** Whether the live renderer is currently showing individual seats. */
1793
- isSeatDetail(): boolean;
1794
- /** Return a sectional venue to its section-only overview. */
1795
- showSectionOverview(): void;
1796
- /** Focus one section using the renderer's real camera transition. */
1797
- focusSection(sectionId: string): void;
1798
- isCompact(): boolean;
1799
- /** Make the canvas non-interactive behind a full-detent sheet (§13). */
1800
- setMapInert(inert: boolean): void;
1801
- toast(message: string, kind: 'ok' | 'err'): void;
1802
- onError(err: unknown): void;
1803
- /** Fired whenever the staged mutation count changes, for host telemetry. */
1804
- onStagedChange?(staged: number): void;
1805
- }
1806
14
  /**
1807
15
  * Render bucket rows to markup. Deliberately generic over `BucketRow`, because
1808
16
  * three different refusals share this exact presentation: the local staged
@@ -1811,466 +19,5 @@ interface ChannelsModeHost {
1811
19
  * one visual language for "here is every affected unit, in exactly one line".
1812
20
  */
1813
21
  declare function bucketRowsHtml(rows: BucketRow[]): string;
1814
- declare class ChannelsMode {
1815
- private readonly host;
1816
- private caps;
1817
- private active;
1818
- private list;
1819
- private allocation;
1820
- private assignmentVersion;
1821
- private loadError;
1822
- private loading;
1823
- private view;
1824
- /** Pan is intentionally the initial desktop interaction. Assignment's
1825
- * marquee is powerful, but must never make an organizer lose map navigation. */
1826
- private mapIntent;
1827
- private focusedSectionId;
1828
- private showArchived;
1829
- private detailChannelId;
1830
- /**
1831
- * Whether the organizer has asked to assign seats.
1832
- *
1833
- * The list rail leads with the CHANNELS. The assignment tooling — destination
1834
- * picker plus five select-by routes — used to paint unconditionally above that
1835
- * list, so a first-time organizer met a workbench for a job they had not asked
1836
- * to do, with the thing they came for pushed below the fold. It is now
1837
- * disclosed on intent: the "Assign seats to a channel" action under the list,
1838
- * the map's own "Assign seats" segment, or simply having a selection. The
1839
- * selection rail always shows the tools, because there the intent is proven.
1840
- */
1841
- private assignOpen;
1842
- private targetChannelId;
1843
- private conflict;
1844
- private dialog;
1845
- private detent;
1846
- private seatListLimit;
1847
- /**
1848
- * Hosted-link STATUS for the channel whose detail panel is open. This is the
1849
- * listing projection — it carries no url and no capability, because no route
1850
- * returns one. `unsupported` is the honest answer for a worker that predates
1851
- * M8, exactly like the buyer-preview probe.
1852
- */
1853
- private links;
1854
- private linksChannelId;
1855
- private linksState;
1856
- /**
1857
- * Monotonic read generations — one for the channel list + allocation, one for
1858
- * the open channel's links. Reads are concurrent (a 10s poll versus a
1859
- * mutation's own reload), and the network does not promise to answer them in
1860
- * order. Only the NEWEST read of each kind may write to state; an older
1861
- * answer that arrives late is dropped, never painted.
1862
- */
1863
- private listSeq;
1864
- private linksSeq;
1865
- private previewAudience;
1866
- private previewIncludePublic;
1867
- private previewProjection;
1868
- /**
1869
- * The buyer-preview read, as one honest state rather than a boolean.
1870
- *
1871
- * `previewSupported: boolean | null` could say "this worker has no projection
1872
- * route", but it could not say "the read is in flight" or "the read failed" —
1873
- * both of those painted an audience picker with no result underneath, which
1874
- * reads as "this audience can buy nothing". Loading and error are now states
1875
- * of their own, and the error one offers a retry.
1876
- */
1877
- private previewState;
1878
- private pollTimer;
1879
- private layer;
1880
- private canvas;
1881
- /** undefined = not resolved yet, null = this environment has no 2d canvas. */
1882
- private ctx;
1883
- private bannerEl;
1884
- private stagedEl;
1885
- private liveEl;
1886
- private scrimEl;
1887
- private lastFocus;
1888
- private stagedDoneTimer;
1889
- private lastSelectionCount;
1890
- private lastCounts;
1891
- /** Rows are structural chart data — they do not move while the organizer is
1892
- * allocating. Deriving them walks every seat, so the view is cached for the
1893
- * lifetime of this mode entry and ordinary selection repaints stay O(1). */
1894
- private assignmentRowsCache;
1895
- /** The markup currently in the rail. An identical repaint is skipped, which is
1896
- * what keeps the organizer's scroll position (and open <select>) alive. */
1897
- private railHtml;
1898
- /**
1899
- * The `assignmentVersion` the allocation map was built from. Walking every
1900
- * allocation page is the expensive half of a refresh and the server already
1901
- * tells us, in the channels response, whether ANY seat moved. Unchanged
1902
- * version, unchanged allocation — so the walk is skipped entirely.
1903
- */
1904
- private allocationVersion;
1905
- private onVisibility;
1906
- constructor(host: ChannelsModeHost, capabilities: ChannelsCapabilities);
1907
- /** Called when the cockpit switches into Channels mode. */
1908
- enter(): void;
1909
- /** Called when the cockpit leaves Channels mode. Everything this mode painted
1910
- * over the map goes with it — no other tool ever inherits a channel overlay. */
1911
- leave(): void;
1912
- destroy(): void;
1913
- /** Capabilities can change when a token rotates. Re-render, fail-closed. */
1914
- setCapabilities(capabilities: ChannelsCapabilities): void;
1915
- isActive(): boolean;
1916
- /**
1917
- * Whether the map should accept bulk selection right now. Preview is a
1918
- * read-only simulation of somebody else's view, and a view-only token has no
1919
- * assignment to stage — in both cases the canvas must not offer selection at
1920
- * all rather than collect a selection nothing can act on.
1921
- */
1922
- canSelect(): boolean;
1923
- /** Bulk seat assignment is explicit. In Pan map, clicks can still inspect a
1924
- * single seat, while a primary-button drag always moves the camera. */
1925
- usesMarqueeSelection(): boolean;
1926
- /** The renderer calls this when the organizer opens a section from overview. */
1927
- handleSectionFocus(sectionId: string): void;
1928
- /**
1929
- * Organizer realtime integration point. M5 ships a per-scope socket for
1930
- * buyers; the organizer channel-count stream is a later milestone. When it
1931
- * arrives, call this from the cockpit's WS handler instead of waiting for the
1932
- * poll — everything downstream already reacts to a fresh list.
1933
- */
1934
- applyRealtimeHint(): void;
1935
- /** The cockpit's selection changed (marquee / click / section / category). */
1936
- handleSelectionChange(): void;
1937
- /** Camera moved or the container resized — the overlay is screen-space. */
1938
- handleViewChange(): void;
1939
- handleLayoutChange(): void;
1940
- private refresh;
1941
- /** Walk every allocation page. Bounded by the event's seat count, and the
1942
- * server caps each page, so an arena is a handful of round trips. */
1943
- private loadAllocation;
1944
- private channelById;
1945
- private nameOf;
1946
- private markerFor;
1947
- /** Channels an organizer may assign INTO: public sale plus every live channel. */
1948
- private assignableChannels;
1949
- private currentPlan;
1950
- private ensureLayer;
1951
- private announce;
1952
- /**
1953
- * Repaint the allocation (or preview) overlay in ONE canvas pass.
1954
- *
1955
- * Channel identity on the map is a fill in the administrative color PLUS the
1956
- * letter flags below — never color alone. In buyer preview the map instead
1957
- * uses two explicit, channel-neutral access states. Physical status keeps its
1958
- * own cue: only FREE units are repainted, so sold/held/blocked seats still
1959
- * read exactly as they do in every other tool.
1960
- */
1961
- private paintOverlay;
1962
- /** Draw an eligible seat's actual chart label without inventing a new buyer
1963
- * identifier. Long labels scale down and are omitted rather than overflowing
1964
- * into an adjacent seat. */
1965
- private paintPreviewSeatLabel;
1966
- /** A section overview is a navigation map. These transparent, keyboardable
1967
- * hit areas sit over the renderer's section shells so both mouse and keyboard
1968
- * always take the organizer into the real focused-section camera state. */
1969
- private paintSectionTargets;
1970
- /** Keep renderer section names legible over a dense, zoomed-out preview. */
1971
- private paintPreviewSectionLabels;
1972
- /** Letter flags at each channel's centroid — the non-color identity cue. */
1973
- private paintFlags;
1974
- private setStaged;
1975
- private paintStagedBar;
1976
- private setBanner;
1977
- private setView;
1978
- /** Set by the cockpit so a view switch can re-arm canvas selection. */
1979
- onInteractionChange?: () => void;
1980
- private loadPreview;
1981
- /**
1982
- * Replace the rail's markup — but only when it actually differs, and never at
1983
- * the cost of where the organizer had scrolled to.
1984
- *
1985
- * The rail repaints on a clock, on every selection change and after every
1986
- * mutation. Rewriting `innerHTML` each time resets `scrollTop`, which is
1987
- * exactly what "the rail gets stuck" was: scroll down to Create channel, the
1988
- * poll ticks, and the list snaps back to the top under the cursor. So skip the
1989
- * write when the markup is byte-identical, and restore the offset when it is
1990
- * not.
1991
- *
1992
- * Returns whether the DOM was rewritten. Callers must only re-wire listeners
1993
- * when it was — a skipped paint keeps the old nodes AND their listeners, so
1994
- * re-wiring would double every handler.
1995
- */
1996
- private setRailHtml;
1997
- paintRail(): void;
1998
- private viewSegmentHtml;
1999
- private mapNavigationHtml;
2000
- private countsHtml;
2001
- private channelRowHtml;
2002
- private listRailHtml;
2003
- /**
2004
- * The assignment entry point on the list rail.
2005
- *
2006
- * Collapsed it is ONE plain-language action, so the channels the organizer came
2007
- * for stay at the top of the panel. Opened it is the same tool set that has
2008
- * always worked, in the same order, plus the way back out — and opening it is
2009
- * also what the map's "Assign seats" segment does, so the two routes into the
2010
- * job cannot disagree about whether it is running.
2011
- */
2012
- private assignEntryHtml;
2013
- /**
2014
- * Destination-first assignment controls.
2015
- *
2016
- * These used to live only inside the selection rail, which meant every route
2017
- * into them was gated behind "select a seat on the map first" — the section,
2018
- * row and category choosers were invisible until the organizer had already
2019
- * done the work by hand. They are still reachable before a seat is selected,
2020
- * but they are no longer the first thing in the panel: on the list rail they
2021
- * are disclosed by `assignEntryHtml`, and there they carry a way back out
2022
- * (`collapsible`). In the selection rail there is nothing to disclose — a
2023
- * selection IS the intent — so they paint unconditionally and without Done.
2024
- */
2025
- private assignmentToolsHtml;
2026
- private selectionRailHtml;
2027
- private detailRailHtml;
2028
- /**
2029
- * "Distribute" — the ONE way this channel's seats reach a buyer.
2030
- *
2031
- * All four routes are here, and this is a real chooser again. It was cut down
2032
- * to two actions in 0.42.0 for a good reason: `access_intent` was stored,
2033
- * audited, and read by nothing, so "Keep as protected reserve" and "Sell
2034
- * through your own staff" were labels an organizer could set and then wait
2035
- * forever for something to happen. The server closed that hole on 2026-08-06 —
2036
- * each declaration now opens exactly one route and REFUSES the other three —
2037
- * so all four are honest choices and belong on the surface.
2038
- *
2039
- * None of the old copy came back with them. These sentences are written
2040
- * against the enforcement matrix (`accessIntentDescription`), which is why
2041
- * each one says what the route refuses as well as what it allows.
2042
- *
2043
- * The current route is stated, not merely styled: a chooser whose selection
2044
- * you have to infer from a border is not a chooser. Its card carries a
2045
- * "Current route" marker and drops its own select button, because pressing it
2046
- * would do nothing.
2047
- */
2048
- private distributeHtml;
2049
- /**
2050
- * Read the status projection for the open channel. Never paints — the caller
2051
- * decides when the rail repaints, so a poll-driven reload does not fight a
2052
- * user-driven one. A worker without M8 answers 404/405 and gets the honest
2053
- * "needs a newer server" line rather than an error toast.
2054
- */
2055
- private loadLinks;
2056
- /**
2057
- * The buyer-link status section of the detail panel.
2058
- *
2059
- * STATUS ONLY, by design (comp 06 `hosted`): label, state, expiry,
2060
- * redemptions, seats per buyer, live sessions. There is no Copy control here
2061
- * and no field to hang one on — the URL was shown once at creation and cannot
2062
- * be produced again. Rotation is the recovery path, and it says so.
2063
- *
2064
- * Creating a link is the Distribute card's action, not this section's, so a
2065
- * channel with no links renders nothing here rather than an empty heading and
2066
- * a second button saying the same thing.
2067
- */
2068
- private hostedLinksHtml;
2069
- private linkCardHtml;
2070
- private previewRailHtml;
2071
- private paintSelection;
2072
- private wireRail;
2073
- /** Open a channel's detail panel and start its buyer-link read. */
2074
- private openChannel;
2075
- private railAction;
2076
- /** Derived once per mode entry — see `assignmentRowsCache`. */
2077
- private assignmentRows;
2078
- private pickCategory;
2079
- /** A tiny modal chooser reusing the dialog primitive (focus trap + Escape). */
2080
- private promptChoice;
2081
- /**
2082
- * Add several whole sections, or several whole rows, in one operation.
2083
- *
2084
- * ADDITIVE by contract: the chooser starts from the labels already selected
2085
- * and only ever grows that set, so opening it never destroys a hard-won
2086
- * marquee or seat-list selection. The confirm button always states the net
2087
- * number of seats it will add, and refuses to exceed `MAX_ASSIGNMENT_UNITS`.
2088
- *
2089
- * Rows get the richer variant. A large venue has thousands of them, so they
2090
- * arrive collapsed under their section, with a section-level tri-state
2091
- * checkbox, a search across every row, and a render cap — the flat list used
2092
- * for sections would be an unusable wall of buttons.
2093
- */
2094
- private renderScopeDialog;
2095
- private openDialog;
2096
- private renderDialog;
2097
- /**
2098
- * `channel_intent_switch_blocked` — buyers are inside the route being left.
2099
- *
2100
- * The same review-then-acknowledge shape as the archive and chart-drop guards,
2101
- * because it is the same kind of decision: the server refuses once, names
2102
- * exactly what is at stake, and only a deliberate second press goes through.
2103
- * What acknowledging does is spelled out per consequence — links close now,
2104
- * checkouts already running survive and drain — rather than hidden behind a
2105
- * word like "force".
2106
- */
2107
- private renderIntentSwitchDialog;
2108
- /**
2109
- * The acknowledged retry. The declare and the create stay one gesture across
2110
- * the sheet: if this switch was the first half of a declare-then-create, the
2111
- * held form finishes on the far side of the acknowledgement.
2112
- */
2113
- private acknowledgeIntentSwitch;
2114
- /**
2115
- * Mount a modal: `aria-modal` dialog, programmatic name, focus moved inside,
2116
- * Tab trapped, Escape closes WITHOUT mutating, focus restored on close (§13).
2117
- */
2118
- private renderScrim;
2119
- private closeDialog;
2120
- private renderCreateDialog;
2121
- private createChannel;
2122
- private showDialogError;
2123
- private renderReviewDialog;
2124
- /**
2125
- * Apply. On success the sheet CLOSES and the staged bar confirms what moved,
2126
- * naming the destination and any skipped seats, with the AUTHORITATIVE server
2127
- * buckets still one Details press away. Leaving the modal up on success made
2128
- * the organizer dismiss a sheet to get back to a map they had just changed.
2129
- * On a stale version the server mutated nothing: keep the selection, shake
2130
- * the bar once, and offer exactly one action — Refresh and review.
2131
- */
2132
- private apply;
2133
- /**
2134
- * The success receipt, now that the review sheet closes on Apply. It names the
2135
- * destination and the seats that could not move, keeps the authoritative
2136
- * buckets reachable through Details, and stays up long enough to be read —
2137
- * 1.2s was tuned for a confirmation the organizer was already looking at.
2138
- */
2139
- private showApplied;
2140
- private shakeStaged;
2141
- /**
2142
- * The ⋯ menu.
2143
- *
2144
- * Opening a channel is the ROW's job now, so ⋯ carries what is left: the
2145
- * secondary and destructive lifecycle actions. It is rendered through the same
2146
- * scrim primitive as every other sheet, which is what gives it a focus trap,
2147
- * Escape, and a name — a bare absolutely-positioned popup would have had none
2148
- * of those. Archive keeps its own confirmation dialog; this menu never
2149
- * destroys anything by itself.
2150
- */
2151
- private renderMenuDialog;
2152
- private renderRenameDialog;
2153
- /**
2154
- * "Use a website or app" — declare the channel's route as `server`.
2155
- *
2156
- * This is no longer a flag the Embed page happens to read: since 2026-08-06 it
2157
- * is what AUTHORIZES `POST /v1/events/:key/buyer-access-sessions` to mint for
2158
- * this channel at all. Without it, an integration that is otherwise perfectly
2159
- * wired up gets a 409 on every buyer.
2160
- */
2161
- private chooseWebsiteIntegration;
2162
- /**
2163
- * Declare this channel's sale route.
2164
- *
2165
- * Reports through the toast lane the rest of the rail's direct actions use.
2166
- * The two enforcement refusals get real answers rather than a generic failure:
2167
- * `channel_intent_switch_blocked` opens the review sheet (there is a decision
2168
- * to make, and a sheet is where decisions live), and
2169
- * `channel_access_intent_forbids` — which the organizer can hit by racing
2170
- * their own second tab — says which route is in the way.
2171
- */
2172
- private setAccessIntent;
2173
- /** What just happened, including anything the switch took down with it — the
2174
- * server reports `intentSwitch` only when it actually disturbed something. */
2175
- private intentSavedCopy;
2176
- /** `channelId` is explicit because this is reachable from the ⋯ menu on a row
2177
- * that is NOT the open channel, as well as from the detail panel itself. */
2178
- private togglePause;
2179
- private renderArchiveDialog;
2180
- private archive;
2181
- /**
2182
- * The synchronized inventory list (§13): the keyboard and screen-reader
2183
- * equivalent of canvas click / marquee / brush, grouped by section with
2184
- * per-section select actions.
2185
- */
2186
- private renderSeatListDialog;
2187
- private reloadLinks;
2188
- /**
2189
- * The reload EVERY link mutation owes the panel.
2190
- *
2191
- * A create/rotate/revoke changes two things the detail panel renders: the
2192
- * channel's access line (the server sets `access.intent` on create, and clears
2193
- * it when the last live link goes) and the link status list. Both are re-read
2194
- * here and the rail repainted, so the panel the organizer is already looking
2195
- * at is current the moment the mutation lands — no reload, and no dependence
2196
- * on HOW the one-time reveal was dismissed (the button, Escape, or never).
2197
- */
2198
- private reloadAfterLinkChange;
2199
- private linkById;
2200
- /**
2201
- * Create. The three policy fields carry the owner's defaults and every one of
2202
- * them is editable; the PLATFORM bounds (60s–180d, 1–10 000, 1–100, 20 live
2203
- * links) are the server's to enforce and the server's to explain, so this form
2204
- * checks only that a number is a number and surfaces the server's sentence for
2205
- * everything else.
2206
- */
2207
- private renderLinkCreateDialog;
2208
- /**
2209
- * DECLARE, then create.
2210
- *
2211
- * `createAccessLink` used to set the channel's route to `hosted_link` as a
2212
- * side effect, which is exactly why the picker could never refuse anything.
2213
- * The server took that side effect away and now REQUIRES the declaration —
2214
- * and channels default to `none`, so a create that did not declare first
2215
- * would 409 on the organizer's very first "Create buyer link".
2216
- *
2217
- * So the route is declared here, immediately before the create. It stays ONE
2218
- * gesture: nothing is declared while the organizer is still filling the form
2219
- * in (cancelling changes nothing), and if the declaration is the part that is
2220
- * refused, the review sheet holds this form and finishes the job on the far
2221
- * side of the acknowledgement.
2222
- */
2223
- private createLink;
2224
- /**
2225
- * The create half, on its own.
2226
- *
2227
- * Separate from `createLink` because the acknowledge path has ALREADY declared
2228
- * the route — with the very acknowledgement the plain declaration was refused
2229
- * for. Sending it back through `ensureHostedLinkRoute` would re-derive the
2230
- * route from a channel list that has not necessarily caught up, and could
2231
- * refuse the organizer a second time for a decision they just made.
2232
- */
2233
- private mintLink;
2234
- /**
2235
- * Make sure the channel declares the buyer-link route before a link is minted.
2236
- *
2237
- * Returns false when the create must NOT proceed — either it was refused, or
2238
- * the decision has been handed to the switch-review sheet, which resumes it.
2239
- * A channel already on `hosted_link` costs no request at all, so creating a
2240
- * second link is the same single call it has always been.
2241
- */
2242
- private ensureHostedLinkRoute;
2243
- /**
2244
- * The ONE-TIME reveal.
2245
- *
2246
- * Three things make this unrecoverable rather than merely "not shown twice":
2247
- *
2248
- * 1. `url` is a local const. It is never assigned to a field on this class,
2249
- * never handed to the host, never put in a `DialogState`.
2250
- * 2. `this.dialog` is cleared FIRST, so `renderDialog()` — the only function
2251
- * that rebuilds a sheet — has nothing to rebuild this one from.
2252
- * 3. The string exists in exactly one DOM node inside the scrim. Dismissing
2253
- * the dialog removes the scrim, and the closure goes with it.
2254
- *
2255
- * The server holds only a hash, so even a compromised client cannot ask for it
2256
- * again. Rotation is the recovery path, and the copy says so.
2257
- */
2258
- private revealLink;
2259
- /**
2260
- * Rotate. The organizer must SAY what happens to the buyers already inside —
2261
- * the confirm stays disabled until one of the two choices is picked, because
2262
- * the gentle branch and the destructive branch are both real decisions and the
2263
- * server refuses (422 `end_active_sessions_required`) to guess either.
2264
- */
2265
- private renderLinkRotateDialog;
2266
- private rotateLink;
2267
- private renderLinkRevokeDialog;
2268
- private revokeLink;
2269
- private applySheetClasses;
2270
- private cycleDetent;
2271
- /** Back/Close from the full detent returns to the previous one and keeps the
2272
- * selection — losing a hard-won selection to a Back press is unforgivable. */
2273
- handleBack(): boolean;
2274
- }
2275
22
 
2276
- export { ACCESS_LINK_DEFAULTS, type AccessIntentForbidsDetails, type AccessLinkRecord, type AccessLinkReveal, type AccessLinkState, type AccessLinkStatus, type AccessLinkStatusRecord, type ArchiveBlockedDetails, type AssignmentBuckets, type AssignmentDropDetails, type AssignmentResult, type BucketRow, type ChannelAccessIntent, type ChannelAccessSummary, type ChannelAllocationPage, type ChannelAttribution, type ChannelAuditEntry, type ChannelAuditPage, type ChannelCounts, type ChannelListResult, type ChannelPreviewProjection, type ChannelRecord, type ChannelReport, type ChannelReportLinkRecord, type ChannelReportLinkReveal, type ChannelReportResult, type ChannelReportRow, type ChannelSeatStatus, type ChannelState, type ChannelsCapabilities, type ChannelsClient, ChannelsMode, type ChannelsModeHost, type ChannelsRowView, type ChannelsSeatView, type ControlRoomActivityEntry, type ControlRoomSectionMetric, type ControlRoomSnapshot, type EventScopedManageToken, type IntentSwitchBlockedDetails, type InventoryBooking, type InventoryBookingActivity, type InventoryBookingDetail, type InventoryBookingObject, type InventoryBookingState, type InventoryBookingsPage, type InventoryBookingsQuery, type LogEntry, type LogPage, ManageApi, ManageApiError, PUBLIC_CHANNEL_ID, PUBLIC_CHANNEL_NAME, type ReportByStatus, type ReportCategoryMeta, type ReportCategoryRow, type ReportResult, SeatManager, type SeatManagerActionResult, type SeatManagerActivity, type SeatManagerCapability, type SeatManagerConnection, type SeatManagerMode, type SeatManagerOptions, type SeatManagerTallies, type SelectionSourceRow, accessIntentDescription, accessIntentLabel, accessLine, accessLinkBadge, accessLinkErrorCopy, accessLinkIsLive, accessLinkPolicyLines, bucketRows, bucketRowsHtml, dropReviewRows, intentForbidsCopy, intentSwitchBlockedCopy, isPublicChannelId, markerLetter, markerOf, mutationCount, needsMoveConfirmation, planAssignment, retryAfterCopy, selectionSources, stateBadge, suggestMarker };
23
+ export { BucketRow, bucketRowsHtml };