@seatlayer/js 0.48.1 → 0.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/manager.cjs CHANGED
@@ -836,6 +836,16 @@ function accessLinkErrorCopy(err) {
836
836
  return fromServer || "That did not go through. Try again.";
837
837
  }
838
838
  }
839
+ function dropReviewRows(details) {
840
+ return (details?.channels ?? []).map((channel) => ({
841
+ kind: "skip",
842
+ icon: "\u26A0",
843
+ count: channel.count,
844
+ text: `${channel.count.toLocaleString()} would leave ${channel.name ?? "a channel"}`,
845
+ why: "the new chart no longer has these seats",
846
+ peek: channel.labels?.length ? peekOf({ count: channel.count, labels: channel.labels, truncated: channel.truncated ?? false }) : void 0
847
+ }));
848
+ }
839
849
  function stateBadge(state) {
840
850
  return state === "builtin" ? "Built-in" : state === "active" ? "Active" : state === "paused" ? "Paused" : "Archived";
841
851
  }
@@ -3693,9 +3703,35 @@ var init_channelsMode = __esm({
3693
3703
  // src/manager.ts
3694
3704
  var manager_exports = {};
3695
3705
  __export(manager_exports, {
3706
+ ACCESS_LINK_DEFAULTS: () => ACCESS_LINK_DEFAULTS,
3707
+ ChannelsMode: () => ChannelsMode,
3696
3708
  ManageApi: () => ManageApi,
3697
3709
  ManageApiError: () => ManageApiError,
3698
- SeatManager: () => SeatManager
3710
+ PUBLIC_CHANNEL_ID: () => PUBLIC_CHANNEL_ID,
3711
+ PUBLIC_CHANNEL_NAME: () => PUBLIC_CHANNEL_NAME,
3712
+ SeatManager: () => SeatManager,
3713
+ accessIntentDescription: () => accessIntentDescription,
3714
+ accessIntentLabel: () => accessIntentLabel,
3715
+ accessLine: () => accessLine,
3716
+ accessLinkBadge: () => accessLinkBadge,
3717
+ accessLinkErrorCopy: () => accessLinkErrorCopy,
3718
+ accessLinkIsLive: () => accessLinkIsLive,
3719
+ accessLinkPolicyLines: () => accessLinkPolicyLines,
3720
+ bucketRows: () => bucketRows,
3721
+ bucketRowsHtml: () => bucketRowsHtml,
3722
+ dropReviewRows: () => dropReviewRows,
3723
+ intentForbidsCopy: () => intentForbidsCopy,
3724
+ intentSwitchBlockedCopy: () => intentSwitchBlockedCopy,
3725
+ isPublicChannelId: () => isPublicChannelId,
3726
+ markerLetter: () => markerLetter,
3727
+ markerOf: () => markerOf,
3728
+ mutationCount: () => mutationCount,
3729
+ needsMoveConfirmation: () => needsMoveConfirmation,
3730
+ planAssignment: () => planAssignment,
3731
+ retryAfterCopy: () => retryAfterCopy,
3732
+ selectionSources: () => selectionSources,
3733
+ stateBadge: () => stateBadge,
3734
+ suggestMarker: () => suggestMarker
3699
3735
  });
3700
3736
  module.exports = __toCommonJS(manager_exports);
3701
3737
 
@@ -3836,6 +3872,13 @@ function resolveContainer(container) {
3836
3872
  }
3837
3873
  return container;
3838
3874
  }
3875
+ function assertBrowserManageToken(token) {
3876
+ if (!token.startsWith("mse_")) {
3877
+ throw new Error(
3878
+ "seatmanager: token must be a short-lived event-scoped mse_ grant minted by your backend; tenant secret keys are unsupported in browsers"
3879
+ );
3880
+ }
3881
+ }
3839
3882
  function toRenderStatus(s) {
3840
3883
  return s === "blocked" ? "not_for_sale" : s;
3841
3884
  }
@@ -3874,7 +3917,18 @@ var MANAGER_CSS = (
3874
3917
  .slm input{font:inherit}
3875
3918
 
3876
3919
  /* top bar */
3877
- .slm-bar{display:grid;grid-template-columns:auto auto minmax(0,1fr);align-items:center;column-gap:14px;row-gap:10px;
3920
+ /* FOUR columns, because the bar has four children: modes, the tools
3921
+ select, the live badge, and the actions. With three, the actions had no
3922
+ column and fell to an implicit second row \u2014 where justify-self:end
3923
+ aligned them to the right of column ONE, so they read as indented
3924
+ rather than as either edge. The 1fr moves to the last column so the
3925
+ actions sit hard right and the live badge hugs its own text instead of
3926
+ stretching across the free space.
3927
+
3928
+ .slm.compact still spans the actions full-width on its own row below;
3929
+ that wrap is deliberate and is why this looked intermittent \u2014 the same
3930
+ broken layout had one designed cause and one accidental one. */
3931
+ .slm-bar{display:grid;grid-template-columns:auto auto auto minmax(0,1fr);align-items:center;column-gap:14px;row-gap:10px;
3878
3932
  padding:10px 16px;border-bottom:1px solid var(--slm-line);flex:none}
3879
3933
  .slm-modes{display:inline-flex;background:var(--slm-surface);border:1px solid var(--slm-line);border-radius:999px;padding:3px}
3880
3934
  .slm-mode{padding:6px 16px;border-radius:999px;font-weight:700;font-size:13px;color:var(--slm-muted)}
@@ -4173,6 +4227,8 @@ var SeatManager = class {
4173
4227
  /** Bumped whenever the seat model is replaced wholesale (a full snapshot). */
4174
4228
  this.modelVersion = 0;
4175
4229
  this.currency = "USD";
4230
+ /** Currency read from the event/control-room projection; host currency is fallback only. */
4231
+ this.authoritativeCurrency = null;
4176
4232
  this.authoritativeGrossRevenue = 0;
4177
4233
  this.revenueStatus = "loading";
4178
4234
  this.revenueRequest = 0;
@@ -4249,6 +4305,8 @@ var SeatManager = class {
4249
4305
  */
4250
4306
  this.channels = null;
4251
4307
  this.channelCaps = { view: false, manage: false };
4308
+ /** Supersedes an async capability probe after token/capability rotation. */
4309
+ this.channelCapabilityResolution = 0;
4252
4310
  /** In-flight `import('./channelsMode')`, so concurrent entries load once. */
4253
4311
  this.channelsLoading = null;
4254
4312
  this.onFullscreenChange = () => {
@@ -4285,6 +4343,7 @@ var SeatManager = class {
4285
4343
  if (feedButton?.dataset.feedId) this.locateActivity(feedButton.dataset.feedId);
4286
4344
  };
4287
4345
  this.sectionOptions = [];
4346
+ assertBrowserManageToken(options.token);
4288
4347
  this.opts = options;
4289
4348
  this.key = options.eventKey;
4290
4349
  this.mode = options.mode ?? "view";
@@ -4306,7 +4365,8 @@ var SeatManager = class {
4306
4365
  try {
4307
4366
  const res = await this.withAuthRetry(() => this.api.chart(this.key));
4308
4367
  this.doc = await this.organizerAssetUrls.prepareRendererChart(res.doc);
4309
- this.currency = res.event.currency ?? this.opts.currency ?? this.currency;
4368
+ this.authoritativeCurrency = res.event.currency ?? null;
4369
+ this.currency = this.authoritativeCurrency ?? this.opts.currency ?? this.currency;
4310
4370
  this.buildUnitUniverse(this.doc);
4311
4371
  this.buildRenderer();
4312
4372
  this.buildSectionOptions();
@@ -4351,36 +4411,38 @@ var SeatManager = class {
4351
4411
  * Decide what this token may do with sales channels.
4352
4412
  *
4353
4413
  * Declared capabilities win — a host that mints an `mse_…` grant knows exactly
4354
- * what it asked for. Otherwise a tenant secret (`sk_…`) is org authority the
4355
- * worker never narrows, so it is fully capable; and a delegated token with no
4356
- * declaration is probed for read access and then treated as READ-ONLY, because
4357
- * "we could not tell" must never render mutation controls.
4414
+ * what it asked for. A delegated token with no declaration is probed for read
4415
+ * access and then treated as READ-ONLY, because "we could not tell" must never
4416
+ * render mutation controls.
4358
4417
  */
4359
4418
  async resolveChannelCapabilities() {
4419
+ const resolution = ++this.channelCapabilityResolution;
4360
4420
  const declared = this.opts.capabilities;
4421
+ let next;
4361
4422
  if (declared) {
4362
4423
  const set = new Set(declared);
4363
- this.channelCaps = {
4424
+ next = {
4364
4425
  view: set.has("event:channels:view"),
4365
4426
  manage: set.has("event:channels:view") && set.has("event:channels:manage")
4366
4427
  };
4367
- } else if (/^sk_/.test(this.opts.token)) {
4368
- this.channelCaps = { view: true, manage: true };
4369
4428
  } else {
4370
- this.channelCaps = { view: false, manage: false };
4429
+ next = { view: false, manage: false };
4371
4430
  }
4372
- if (!this.channelCaps.view && !declared && !/^sk_/.test(this.opts.token)) {
4431
+ if (!next.view && !declared) {
4373
4432
  try {
4374
4433
  await this.api.channels(this.key);
4375
- this.channelCaps = { view: true, manage: false };
4434
+ next = { view: true, manage: false };
4376
4435
  } catch {
4377
- this.channelCaps = { view: false, manage: false };
4436
+ next = { view: false, manage: false };
4378
4437
  }
4379
4438
  }
4439
+ if (resolution !== this.channelCapabilityResolution) return;
4440
+ this.channelCaps = next;
4380
4441
  if (!this.channelCaps.view) {
4381
4442
  this.channels?.destroy();
4382
4443
  this.channels = null;
4383
- this.paintModeTabs();
4444
+ if (this.mode === "channels") this.setMode("view");
4445
+ else this.paintModeTabs();
4384
4446
  return;
4385
4447
  }
4386
4448
  this.channels?.setCapabilities(this.channelCaps);
@@ -4539,6 +4601,36 @@ var SeatManager = class {
4539
4601
  this.paintFollowLiveButton();
4540
4602
  if (changed) this.opts.onFollowLiveChange?.(enabled);
4541
4603
  }
4604
+ /** Update background-tab repaint policy without rebuilding the board. */
4605
+ setKeepLiveWhileHidden(enabled) {
4606
+ this.keepLive = enabled ?? true;
4607
+ this.opts.keepLiveWhileHidden = enabled;
4608
+ }
4609
+ /** Update the host fallback currency; an event/control-room currency still wins. */
4610
+ setCurrency(currency) {
4611
+ this.opts.currency = currency;
4612
+ if (this.authoritativeCurrency) return;
4613
+ this.currency = currency ?? "USD";
4614
+ if (this.ready) this.recomputeTallies();
4615
+ }
4616
+ /** Apply organizer chrome tokens in place without losing camera or selection. */
4617
+ setTheme(theme) {
4618
+ this.opts.theme = theme;
4619
+ if (!this.root) return;
4620
+ for (const [property, value] of Object.entries(themeVars(theme))) {
4621
+ this.root.style.setProperty(property, value);
4622
+ }
4623
+ }
4624
+ /** Replace the declared authority for the current token and fail closed. */
4625
+ setCapabilities(capabilities) {
4626
+ this.opts.capabilities = capabilities;
4627
+ if (this.ready) void this.resolveChannelCapabilities();
4628
+ }
4629
+ /** Change proactive token-refresh policy without rebuilding the manager. */
4630
+ setTokenRefresh(onTokenRefresh) {
4631
+ this.opts.onTokenRefresh = onTokenRefresh;
4632
+ this.scheduleTokenRefresh();
4633
+ }
4542
4634
  /** Change the current-vs-previous sales window and refresh the private projection. */
4543
4635
  setTrendWindow(windowMinutes) {
4544
4636
  const normalized = Number.isFinite(windowMinutes) ? Math.floor(windowMinutes) : 15;
@@ -4564,7 +4656,10 @@ var SeatManager = class {
4564
4656
  }
4565
4657
  /** Rotate the delegated credential without rebuilding DOM, canvas or socket. */
4566
4658
  setToken(token, expiresAt) {
4659
+ assertBrowserManageToken(token);
4567
4660
  this.api.setToken(token);
4661
+ this.opts.token = token;
4662
+ this.opts.tokenExpiresAt = expiresAt;
4568
4663
  this.tokenExpiresAt = expiresAt ?? null;
4569
4664
  this.scheduleTokenRefresh();
4570
4665
  if (this.ready) void this.resolveChannelCapabilities();
@@ -4716,11 +4811,6 @@ var SeatManager = class {
4716
4811
  this.opts.onError?.(err);
4717
4812
  }
4718
4813
  }
4719
- /** M2 — box-office booking from free seats. Stubbed (route is session-only today). */
4720
- boxBook(_labels, _bookingRef) {
4721
- this.toastErr("Box office ships in a later milestone.");
4722
- return Promise.resolve();
4723
- }
4724
4814
  zoomToFit() {
4725
4815
  this.renderer?.clearSectionFocus();
4726
4816
  this.renderer?.zoomToFit();
@@ -5237,6 +5327,7 @@ var SeatManager = class {
5237
5327
  this.rebaseServerTotals(snapshot);
5238
5328
  this.lastSyncedAt = Date.now();
5239
5329
  this.authoritativeGrossRevenue = snapshot.bookedValue.gross;
5330
+ this.authoritativeCurrency = snapshot.currency;
5240
5331
  this.currency = snapshot.currency;
5241
5332
  this.revenueStatus = "current";
5242
5333
  this.recomputeTallies();
@@ -6355,11 +6446,39 @@ var SeatManager = class {
6355
6446
  };
6356
6447
 
6357
6448
  // src/manager.ts
6449
+ init_channelsMode();
6450
+ init_channelPlan();
6358
6451
  init_manageApi();
6359
6452
  // Annotate the CommonJS export names for ESM import in node:
6360
6453
  0 && (module.exports = {
6454
+ ACCESS_LINK_DEFAULTS,
6455
+ ChannelsMode,
6361
6456
  ManageApi,
6362
6457
  ManageApiError,
6363
- SeatManager
6458
+ PUBLIC_CHANNEL_ID,
6459
+ PUBLIC_CHANNEL_NAME,
6460
+ SeatManager,
6461
+ accessIntentDescription,
6462
+ accessIntentLabel,
6463
+ accessLine,
6464
+ accessLinkBadge,
6465
+ accessLinkErrorCopy,
6466
+ accessLinkIsLive,
6467
+ accessLinkPolicyLines,
6468
+ bucketRows,
6469
+ bucketRowsHtml,
6470
+ dropReviewRows,
6471
+ intentForbidsCopy,
6472
+ intentSwitchBlockedCopy,
6473
+ isPublicChannelId,
6474
+ markerLetter,
6475
+ markerOf,
6476
+ mutationCount,
6477
+ needsMoveConfirmation,
6478
+ planAssignment,
6479
+ retryAfterCopy,
6480
+ selectionSources,
6481
+ stateBadge,
6482
+ suggestMarker
6364
6483
  });
6365
6484
  //# sourceMappingURL=manager.cjs.map