@seatlayer/js 0.38.0 → 0.39.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/index.cjs CHANGED
@@ -9570,13 +9570,39 @@ var SeatManager = class {
9570
9570
  this.labelToId = /* @__PURE__ */ new Map();
9571
9571
  this.labelToSeat = /* @__PURE__ */ new Map();
9572
9572
  this.allIds = [];
9573
+ /**
9574
+ * GA inventory units — real sellable labels the server counts, with NO seat
9575
+ * geometry and therefore no renderer binding. They live here rather than in
9576
+ * `labelToId`/`allIds` so every paint path keeps addressing paintable nodes
9577
+ * only, while the tally denominator finally covers the same universe the
9578
+ * numerator does. Without them a GA sale hit `booked` but not `total`:
9579
+ * Free under-reported by GA capacity and SOLD% could exceed 100%.
9580
+ */
9581
+ this.gaUnitLabelSet = /* @__PURE__ */ new Set();
9573
9582
  this.status = /* @__PURE__ */ new Map();
9583
+ /** Live non-free counters, moved by each delta rather than re-walked. */
9584
+ this.counts = { held: 0, booked: 0, blocked: 0 };
9585
+ /** Bumped whenever the seat model is replaced wholesale (a full snapshot). */
9586
+ this.modelVersion = 0;
9574
9587
  this.currency = "USD";
9575
9588
  this.authoritativeGrossRevenue = 0;
9576
9589
  this.revenueStatus = "loading";
9577
9590
  this.revenueRequest = 0;
9578
- this.revenueRefreshTimer = null;
9579
9591
  this.controlRoomSnapshot = null;
9592
+ /**
9593
+ * The server's own totals, pinned to the client model they were read against.
9594
+ * Display = server baseline + (client now − client then), so the authoritative
9595
+ * numbers land exactly on arrival and deltas still move them between reads.
9596
+ * A wholesale model replacement invalidates the pairing (`model`), and the
9597
+ * client tallies — themselves a fresh authenticated read — take over.
9598
+ */
9599
+ this.serverBaseline = null;
9600
+ /** Latest presence frame, held whether or not a snapshot has landed yet. */
9601
+ this.livePresence = null;
9602
+ /** Latest cumulative booked gross pushed on a delta frame. */
9603
+ this.liveGross = null;
9604
+ /** Coalesces a burst of deltas into one KPI/rail repaint. */
9605
+ this.paintHandle = null;
9580
9606
  this.trendWindowMinutes = 15;
9581
9607
  this.heatEnabled = false;
9582
9608
  this.lastKpiValues = /* @__PURE__ */ new Map();
@@ -9674,12 +9700,7 @@ var SeatManager = class {
9674
9700
  const res = await this.api.chart(this.key);
9675
9701
  this.doc = res.doc;
9676
9702
  this.currency = res.event.currency ?? this.opts.currency ?? this.currency;
9677
- const seats = (0, import_core3.expandChart)(res.doc);
9678
- for (const s of seats) {
9679
- this.labelToId.set(s.label, s.id);
9680
- this.labelToSeat.set(s.label, s);
9681
- this.allIds.push(s.id);
9682
- }
9703
+ this.buildUnitUniverse(res.doc);
9683
9704
  this.buildRenderer();
9684
9705
  this.buildSectionOptions();
9685
9706
  const [, controlRoom] = await Promise.all([
@@ -10040,7 +10061,10 @@ var SeatManager = class {
10040
10061
  if (this.followLiveTimer) clearTimeout(this.followLiveTimer);
10041
10062
  if (this.followSeatTimer) clearTimeout(this.followSeatTimer);
10042
10063
  if (this.unblockAllConfirmTimer) clearTimeout(this.unblockAllConfirmTimer);
10043
- if (this.revenueRefreshTimer) clearTimeout(this.revenueRefreshTimer);
10064
+ if (this.paintHandle !== null && typeof cancelAnimationFrame === "function") {
10065
+ cancelAnimationFrame(this.paintHandle);
10066
+ }
10067
+ this.paintHandle = null;
10044
10068
  this.channels?.destroy();
10045
10069
  this.channels = null;
10046
10070
  if (this.tokenRefreshTimer) clearTimeout(this.tokenRefreshTimer);
@@ -10123,6 +10147,37 @@ var SeatManager = class {
10123
10147
  }
10124
10148
  this.syncSelection();
10125
10149
  }
10150
+ /**
10151
+ * Build the client's inventory universe from the chart.
10152
+ *
10153
+ * `expandChart` yields SEATS — it has no output for a GA area, whose capacity
10154
+ * is sold as N synthetic unit labels. The server's seat map keys, its deltas
10155
+ * and its `totals` all speak those labels, so a client that only knows seats
10156
+ * counts GA sales in the numerator (every key of the snapshot is written into
10157
+ * `status`) while leaving them out of the denominator. Registering the GA
10158
+ * units here — labels only, never a render binding — is what makes the two
10159
+ * agree.
10160
+ */
10161
+ buildUnitUniverse(doc) {
10162
+ for (const seat of (0, import_core3.expandChart)(doc)) {
10163
+ this.labelToId.set(seat.label, seat.id);
10164
+ this.labelToSeat.set(seat.label, seat);
10165
+ this.allIds.push(seat.id);
10166
+ }
10167
+ for (const area of (0, import_core3.gaAreasOf)(doc)) {
10168
+ for (const label of (0, import_core3.gaUnitLabels)(area)) {
10169
+ if (!this.labelToId.has(label)) this.gaUnitLabelSet.add(label);
10170
+ }
10171
+ }
10172
+ }
10173
+ /** Every sellable unit the client knows: seats + GA capacity. */
10174
+ unitTotal() {
10175
+ return this.allIds.length + this.gaUnitLabelSet.size;
10176
+ }
10177
+ /** Every label the client models, whether or not it can be painted. */
10178
+ knownLabels() {
10179
+ return [...this.labelToId.keys(), ...this.gaUnitLabelSet];
10180
+ }
10126
10181
  repaintAll() {
10127
10182
  const r = this.renderer;
10128
10183
  if (!r) return;
@@ -10172,7 +10227,7 @@ var SeatManager = class {
10172
10227
  ws.onopen = () => {
10173
10228
  this.attempt = 0;
10174
10229
  this.setLive(true);
10175
- void this.resnapshot().then(() => this.scheduleRevenueRefresh(0));
10230
+ void this.resnapshot().then(() => this.refreshControlRoom()).catch((err) => this.opts.onError?.(err));
10176
10231
  void this.refreshAvailability();
10177
10232
  };
10178
10233
  ws.onmessage = (e) => this.onMessage(e);
@@ -10210,15 +10265,18 @@ var SeatManager = class {
10210
10265
  this.updateEffectiveAvailability(m.hidden, m.closed);
10211
10266
  }
10212
10267
  if (m.type === "presence") {
10213
- if (this.controlRoomSnapshot && typeof m.shoppingSessions === "number" && typeof m.activeHolds === "number") {
10214
- this.controlRoomSnapshot = {
10215
- ...this.controlRoomSnapshot,
10216
- presence: { shoppingSessions: m.shoppingSessions, activeHolds: m.activeHolds }
10268
+ if (typeof m.shoppingSessions === "number" && typeof m.activeHolds === "number") {
10269
+ this.livePresence = {
10270
+ at: Date.now(),
10271
+ value: { shoppingSessions: m.shoppingSessions, activeHolds: m.activeHolds }
10217
10272
  };
10273
+ if (this.controlRoomSnapshot) {
10274
+ this.controlRoomSnapshot = { ...this.controlRoomSnapshot, presence: this.livePresence.value };
10275
+ this.opts.onControlRoom?.(this.controlRoomSnapshot);
10276
+ }
10218
10277
  this.lastSyncedAt = Date.now();
10219
10278
  this.recomputeTallies();
10220
10279
  this.paintMonitorInsights();
10221
- this.opts.onControlRoom?.(this.controlRoomSnapshot);
10222
10280
  }
10223
10281
  return;
10224
10282
  }
@@ -10232,7 +10290,7 @@ var SeatManager = class {
10232
10290
  const st = ["free", "held", "booked", "blocked"].includes(ch.status) ? ch.status : "free";
10233
10291
  const prev = this.status.get(ch.label) ?? "free";
10234
10292
  if (prev === st) continue;
10235
- this.status.set(ch.label, st);
10293
+ this.setStatusLabel(ch.label, st, prev);
10236
10294
  const id = this.labelToId.get(ch.label);
10237
10295
  if (id) {
10238
10296
  this.renderer?.setStatus([id], toRenderStatus(st));
@@ -10252,10 +10310,38 @@ var SeatManager = class {
10252
10310
  this.lastSyncedAt = Date.now();
10253
10311
  this.afterPaint();
10254
10312
  }
10313
+ if (typeof m.revenue?.gross === "number" && Number.isFinite(m.revenue.gross)) {
10314
+ this.applyLiveGross(m.revenue.gross);
10315
+ }
10255
10316
  this.recomputeTallies();
10256
- if (ids.length) this.scheduleRevenueRefresh();
10257
10317
  }
10258
10318
  }
10319
+ /**
10320
+ * Adopt the cumulative booked gross a delta frame carried.
10321
+ *
10322
+ * Stashed with its arrival time so an in-flight control-room read can decide
10323
+ * whether it is holding the newer number: a frame that landed after the
10324
+ * request started is newer than the response, one that landed before is not.
10325
+ */
10326
+ applyLiveGross(gross) {
10327
+ this.liveGross = { at: Date.now(), value: gross };
10328
+ this.authoritativeGrossRevenue = gross;
10329
+ this.revenueStatus = "current";
10330
+ if (this.controlRoomSnapshot) {
10331
+ this.controlRoomSnapshot = {
10332
+ ...this.controlRoomSnapshot,
10333
+ revenue: { ...this.controlRoomSnapshot.revenue, gross }
10334
+ };
10335
+ this.opts.onControlRoom?.(this.controlRoomSnapshot);
10336
+ }
10337
+ }
10338
+ /** The single writer for a label's status, so the counters never drift. */
10339
+ setStatusLabel(label, next, prev = this.status.get(label) ?? "free") {
10340
+ this.status.set(label, next);
10341
+ if (prev === next) return;
10342
+ if (prev !== "free") this.counts[prev] -= 1;
10343
+ if (next !== "free") this.counts[next] += 1;
10344
+ }
10259
10345
  async resnapshot() {
10260
10346
  try {
10261
10347
  const objs = await this.api.objects(this.key);
@@ -10278,23 +10364,31 @@ var SeatManager = class {
10278
10364
  const next = /* @__PURE__ */ new Map();
10279
10365
  if (fallback !== void 0) {
10280
10366
  const base = known(fallback);
10281
- for (const label of this.labelToId.keys()) next.set(label, base);
10367
+ for (const label of this.knownLabels()) next.set(label, base);
10282
10368
  }
10283
10369
  for (const [label, st] of Object.entries(seats)) {
10284
10370
  next.set(label, known(st));
10285
10371
  }
10286
10372
  this.status = next;
10373
+ this.modelVersion += 1;
10374
+ this.recountAll();
10287
10375
  this.lastSyncedAt = Date.now();
10288
10376
  this.repaintAll();
10289
10377
  this.afterPaint();
10290
10378
  this.recomputeTallies();
10291
10379
  }
10380
+ /** The one O(n) walk left: a wholesale model replacement re-bases the counters. */
10381
+ recountAll() {
10382
+ const counts = { held: 0, booked: 0, blocked: 0 };
10383
+ for (const st of this.status.values()) if (st !== "free") counts[st] += 1;
10384
+ this.counts = counts;
10385
+ }
10292
10386
  /** Optimistic local write shared by organizer actions. Paint and tally once,
10293
10387
  * even when an arena-sized operation changes hundreds of seats. */
10294
10388
  setSeatsLocal(labels, st) {
10295
10389
  const ids = [];
10296
10390
  for (const label of labels) {
10297
- this.status.set(label, st);
10391
+ this.setStatusLabel(label, st);
10298
10392
  const id = this.labelToId.get(label);
10299
10393
  if (id) ids.push(id);
10300
10394
  }
@@ -10420,12 +10514,33 @@ var SeatManager = class {
10420
10514
  this.revenueStatus = "current";
10421
10515
  this.recomputeTallies();
10422
10516
  }
10517
+ /**
10518
+ * Read the server's own control-room projection.
10519
+ *
10520
+ * Called on mount, on every socket (re)connect and after an organizer action —
10521
+ * never on a timer and never per delta frame. Presence and gross that arrived
10522
+ * on the socket AFTER this request started are newer than the response, so
10523
+ * they survive it; anything older defers to the read.
10524
+ */
10423
10525
  async refreshControlRoom() {
10424
10526
  const request = ++this.revenueRequest;
10527
+ const requestedAt = Date.now();
10425
10528
  try {
10426
- const snapshot = await this.api.controlRoom(this.key, this.trendWindowMinutes);
10529
+ const fetched = await this.api.controlRoom(this.key, this.trendWindowMinutes);
10530
+ let snapshot = fetched;
10427
10531
  if (request === this.revenueRequest) {
10532
+ if (this.livePresence && this.livePresence.at >= requestedAt) {
10533
+ snapshot = { ...snapshot, presence: this.livePresence.value };
10534
+ } else {
10535
+ this.livePresence = null;
10536
+ }
10537
+ if (this.liveGross && this.liveGross.at >= requestedAt) {
10538
+ snapshot = { ...snapshot, revenue: { ...snapshot.revenue, gross: this.liveGross.value } };
10539
+ } else {
10540
+ this.liveGross = null;
10541
+ }
10428
10542
  this.controlRoomSnapshot = snapshot;
10543
+ this.rebaseServerTotals(snapshot);
10429
10544
  this.lastSyncedAt = Date.now();
10430
10545
  this.authoritativeGrossRevenue = snapshot.revenue.gross;
10431
10546
  this.currency = snapshot.currency;
@@ -10444,37 +10559,80 @@ var SeatManager = class {
10444
10559
  throw err;
10445
10560
  }
10446
10561
  }
10447
- scheduleRevenueRefresh(delay = 140) {
10448
- this.revenueStatus = "stale";
10449
- this.recomputeTallies();
10450
- if (this.revenueRefreshTimer) clearTimeout(this.revenueRefreshTimer);
10451
- this.revenueRefreshTimer = setTimeout(() => {
10452
- this.revenueRefreshTimer = null;
10453
- void this.refreshControlRoom().catch((err) => this.opts.onError?.(err));
10454
- }, delay);
10562
+ /** Pin the server's totals to the client model they were read against. */
10563
+ rebaseServerTotals(snapshot) {
10564
+ const totals = snapshot.totals;
10565
+ if (!totals || ["free", "held", "booked", "blocked"].some(
10566
+ (key) => !Number.isFinite(totals[key])
10567
+ )) {
10568
+ this.serverBaseline = null;
10569
+ return;
10570
+ }
10571
+ this.serverBaseline = {
10572
+ model: this.modelVersion,
10573
+ server: { free: totals.free, held: totals.held, booked: totals.booked, blocked: totals.blocked },
10574
+ client: this.clientTallies()
10575
+ };
10455
10576
  }
10456
- recomputeTallies() {
10577
+ /** What the client's own model says — GA units included since `render()`. */
10578
+ clientTallies() {
10579
+ const { held, booked, blocked } = this.counts;
10580
+ return { held, booked, blocked, free: Math.max(0, this.unitTotal() - held - booked - blocked) };
10581
+ }
10582
+ /**
10583
+ * The numbers the KPI bar and rail render.
10584
+ *
10585
+ * The server is the authority: its totals land exactly as read, and the
10586
+ * delta-driven client model carries them forward until the next read. Before
10587
+ * the first snapshot — and after a wholesale model replacement invalidates the
10588
+ * pairing — the client model stands alone.
10589
+ */
10590
+ buildTallies() {
10591
+ const client = this.clientTallies();
10592
+ const baseline = this.serverBaseline?.model === this.modelVersion ? this.serverBaseline : null;
10593
+ const of = (key) => baseline ? Math.max(0, baseline.server[key] + (client[key] - baseline.client[key])) : client[key];
10594
+ const seatTotal = this.controlRoomSnapshot?.event?.seatTotal;
10457
10595
  const t3 = {
10458
- free: 0,
10459
- held: 0,
10460
- booked: 0,
10461
- blocked: 0,
10462
- total: this.allIds.length,
10596
+ free: of("free"),
10597
+ held: of("held"),
10598
+ booked: of("booked"),
10599
+ blocked: of("blocked"),
10600
+ total: Number.isFinite(seatTotal) ? seatTotal : this.unitTotal(),
10463
10601
  capacityPct: 0,
10464
10602
  sellThroughPct: 0,
10465
10603
  grossRevenue: this.authoritativeGrossRevenue,
10466
10604
  revenueStatus: this.revenueStatus,
10467
10605
  currency: this.currency
10468
10606
  };
10469
- let nonFree = 0;
10470
- for (const st of this.status.values()) {
10471
- t3[st] += 1;
10472
- if (st !== "free") nonFree += 1;
10473
- }
10474
- t3.free = Math.max(0, t3.total - nonFree);
10475
10607
  t3.capacityPct = t3.total ? Math.round(t3.booked / t3.total * 100) : 0;
10476
10608
  const sellable = t3.total - t3.blocked;
10477
10609
  t3.sellThroughPct = sellable > 0 ? Math.round(t3.booked / sellable * 100) : 0;
10610
+ return t3;
10611
+ }
10612
+ /**
10613
+ * Queue one KPI/rail repaint for this burst of changes.
10614
+ *
10615
+ * A delta frame can carry hundreds of seats and `paintKpis` rebuilds eight
10616
+ * nodes from scratch, so painting per change is what made an arena-sized
10617
+ * frame expensive. Coalescing on a frame keeps the burst to a single rebuild;
10618
+ * without `requestAnimationFrame` (SSR, an older test env) it paints inline
10619
+ * rather than dropping the update.
10620
+ */
10621
+ recomputeTallies() {
10622
+ if (this.closed) return;
10623
+ if (typeof requestAnimationFrame !== "function") {
10624
+ this.flushTallies();
10625
+ return;
10626
+ }
10627
+ if (this.paintHandle !== null) return;
10628
+ this.paintHandle = requestAnimationFrame(() => {
10629
+ this.paintHandle = null;
10630
+ this.flushTallies();
10631
+ });
10632
+ }
10633
+ flushTallies() {
10634
+ if (this.closed) return;
10635
+ const t3 = this.buildTallies();
10478
10636
  this.paintKpis(t3);
10479
10637
  if (this.mode === "view") {
10480
10638
  this.paintLegend(t3);
@@ -10761,16 +10919,16 @@ var SeatManager = class {
10761
10919
  paintKpis(t3) {
10762
10920
  if (!this.els.kpis) return;
10763
10921
  const rev = t3.revenueStatus === "current" ? fmtMoney(t3.grossRevenue, t3.currency) : "\u2014";
10764
- const presence = this.controlRoomSnapshot?.presence;
10922
+ const presence = this.presenceCounts();
10765
10923
  const items = [
10766
- { key: "sold-seats", raw: t3.booked, n: t3.booked.toLocaleString(), l: "Sold seats", dot: "#22a06b" },
10767
- { key: "held-seats", raw: t3.held, n: t3.held.toLocaleString(), l: "Held seats", dot: "#f4b740" },
10768
- { key: "buyers", raw: presence?.shoppingSessions ?? null, n: presence ? presence.shoppingSessions.toLocaleString() : "\u2014", l: "Buyers" },
10769
- { key: "active-holds", raw: presence?.activeHolds ?? null, n: presence ? presence.activeHolds.toLocaleString() : "\u2014", l: "Active holds" },
10770
- { key: "free-seats", raw: t3.free, n: t3.free.toLocaleString(), l: "Free seats", dot: "#6e7bff" },
10771
- { key: "blocked", raw: t3.blocked, n: t3.blocked.toLocaleString(), l: "Blocked", dot: "#8b94ac" },
10772
- { key: "sold-pct", raw: t3.capacityPct, n: `${t3.capacityPct}%`, l: "Sold" },
10773
- { key: "gross-sales", raw: t3.revenueStatus === "current" ? t3.grossRevenue : null, n: rev, l: "Gross sales" }
10924
+ { key: "sold-seats", raw: t3.booked, n: t3.booked.toLocaleString(), l: "Sold seats", dot: "#22a06b", title: "Seats booked" },
10925
+ { key: "held-seats", raw: t3.held, n: t3.held.toLocaleString(), l: "Held seats", dot: "#f4b740", title: "Seats held in a checkout right now" },
10926
+ { key: "free-seats", raw: t3.free, n: t3.free.toLocaleString(), l: "Free seats", dot: "#6e7bff", title: "Seats on sale and unsold" },
10927
+ { key: "blocked", raw: t3.blocked, n: t3.blocked.toLocaleString(), l: "Blocked", dot: "#8b94ac", title: "Seats withheld from sale" },
10928
+ { key: "buyers", raw: presence?.shoppingSessions ?? null, n: presence ? presence.shoppingSessions.toLocaleString() : "\u2014", l: "Buyers", title: "People on the map right now" },
10929
+ { key: "carts", raw: presence?.activeHolds ?? null, n: presence ? presence.activeHolds.toLocaleString() : "\u2014", l: "Carts", title: "Checkouts holding seats right now \u2014 sessions, not seats" },
10930
+ { key: "sold-pct", raw: t3.capacityPct, n: `${t3.capacityPct}%`, l: "Sold", title: "Sold seats as a share of the whole event" },
10931
+ { key: "gross-sales", raw: t3.revenueStatus === "current" ? t3.grossRevenue : null, n: rev, l: "Gross sales", title: "Exact booked gross" }
10774
10932
  ];
10775
10933
  let hasChanges = false;
10776
10934
  this.els.kpis.innerHTML = items.map((item) => {
@@ -10786,7 +10944,7 @@ var SeatManager = class {
10786
10944
  }
10787
10945
  if (item.raw != null) this.lastKpiValues.set(item.key, item.raw);
10788
10946
  const activeDelta = this.activeKpiDeltas.get(item.key);
10789
- return `<div class="slm-kpi${activeDelta ? " changed" : ""}" data-kpi="${item.key}">
10947
+ return `<div class="slm-kpi${activeDelta ? " changed" : ""}" data-kpi="${item.key}" title="${esc2(item.title)}">
10790
10948
  <b>${item.dot ? `<span class="dot" style="background:${item.dot}"></span>` : ""}${item.n}</b><span>${item.l}</span>
10791
10949
  ${activeDelta ? `<span class="slm-kpidelta${activeDelta.down ? " down" : ""}">${activeDelta.text}</span>` : ""}
10792
10950
  </div>`;
@@ -10844,15 +11002,21 @@ var SeatManager = class {
10844
11002
  this.paintMomentumHelp();
10845
11003
  this.paintFeed();
10846
11004
  }
11005
+ /** Live presence wins over the snapshot's copy — it is the fresher channel,
11006
+ * and it exists from the first frame rather than the first fetch. */
11007
+ presenceCounts() {
11008
+ return this.livePresence?.value ?? this.controlRoomSnapshot?.presence ?? null;
11009
+ }
10847
11010
  paintMonitorInsights() {
10848
11011
  if (this.mode !== "view") return;
10849
11012
  const snapshot = this.controlRoomSnapshot;
10850
11013
  if (this.els.presence) {
10851
11014
  const connected = this.root?.classList.contains("live");
10852
11015
  const sync = this.lastSyncedAt ? relTime(this.lastSyncedAt, Date.now()) : "waiting";
11016
+ const presence = this.presenceCounts();
10853
11017
  this.els.presence.innerHTML = `
10854
- <div class="slm-healthitem"><b>${snapshot ? snapshot.presence.shoppingSessions.toLocaleString() : "\u2014"}</b><span>Buyer sessions</span></div>
10855
- <div class="slm-healthitem"><b>${snapshot ? snapshot.presence.activeHolds.toLocaleString() : "\u2014"}</b><span>Active holds</span></div>
11018
+ <div class="slm-healthitem" title="People on the map right now"><b>${presence ? presence.shoppingSessions.toLocaleString() : "\u2014"}</b><span>Buyers</span></div>
11019
+ <div class="slm-healthitem" title="Checkouts holding seats right now \u2014 sessions, not seats"><b>${presence ? presence.activeHolds.toLocaleString() : "\u2014"}</b><span>Carts</span></div>
10856
11020
  <div class="slm-healthitem"><b>${connected ? "Healthy" : "Reconnecting"}</b><span>Live connection</span></div>
10857
11021
  <div class="slm-healthitem"><b>${sync}</b><span>Last sync</span></div>`;
10858
11022
  }
@@ -11463,7 +11627,7 @@ var SeatManager = class {
11463
11627
  const activity = action === "block" ? this.pushActivity(labels, "blocked", "blocked") : action === "unblock" || action === "unblockAll" ? this.pushActivity(labels, "unblocked", "free") : action === "cancelBooking" ? this.pushActivity(labels, "cancelled", "free") : null;
11464
11628
  if (activity) this.paintSpatialActivity(activity);
11465
11629
  }
11466
- if (action !== "setHoldTtl") this.scheduleRevenueRefresh(0);
11630
+ if (action !== "setHoldTtl") void this.refreshControlRoom().catch((err) => this.opts.onError?.(err));
11467
11631
  this.opts.onActionComplete?.({ action, labels, count: labels.length });
11468
11632
  }
11469
11633
  toastOk(msg) {