Release 1.65.0

Release date: April 29, 2026

Release Notes - Seatmap Platform

Version 1.65.0 - 2026-04-29

Release Focus: Venues Library — cross-org schema discovery, copy, and provenance audit, with HTTP federation for on-prem consumer instances. Plus global-admin impersonation in the editor, a /directsale booking endpoint for back-office integrations, V1 booking-disabled enforcement parity, and customer-driven editor improvements (table labels, multi-section combine).


What’s New

Venues Library — cross-org schema discovery and copy

The new Venues Library lets organizations within an instance discover and copy schemas published by other organizations. This is the foundation for the SaaS schema marketplace and unblocks the Free tier introduced in 1.64.0. Issues: SEAT-944 (epic), SEAT-945, SEAT-946, SEAT-947, SEAT-948.

Publisher side (orgs that mark themselves as a publisher):

  • A new admin toggle promotes an organization to publisher status. Per-schema visibility is PUBLIC / UNLISTED / PRIVATE.
  • Publishers can audit which other orgs have copied each of their schemas (admin-only audit viewer for global admins; per-publisher visibility is intentionally out of scope).
  • Schemas are frozen at copy time. Publisher-side updates do not propagate to existing copies.

Browse side (any org, with editor access):

  • New library section in the editor lists PUBLIC schemas in reverse chronological order. No search / filter / curation in this release — that’s tracked separately.
  • Hovering a schema shows a lightweight preview rendered from a stored thumbnail; clicking opens a full preview modal.
  • A single “Copy to my organization” action materialises the schema, hydrates assets (PDF underlay, background image, raster thumbnails), and progresses through a copy progress bar to a success banner with a direct link to the new schema.
  • Provenance is preserved: copied schemas record library_origin_instance_url and library_origin_venue_id so the source is always traceable.

Why it matters

Customers can stop rebuilding standard venues from scratch. Mid-market integrators (SportsThread, RaceEntry, Fastender) get a starter library; SaaS prospects can preview real layouts before committing.

Full documentation: Venues Library.

Library federation — cross-instance copy

For self-hosted operators, an instance can be configured to pull library content from another instance over HTTP. This is the path Timepad uses to consume schemas published from seatmap.pro from their on-prem deployment. Issue: SEAT-952.

How it works

  • Global admins manage federation API keys at the new admin route (/api/admin/federation/instances). Keys are issued once, hashed in storage, and never displayed again — copy on creation or reissue.
  • Consumer instances point at a source via Helm values: editor.config.seatmap.federation.source.url plus a secrets.federation.sourceApiKey value. The editor picks them up as SEATMAP_FEDERATION_SOURCE_URL / SEATMAP_FEDERATION_SOURCE_API_KEY.
  • LibraryService.materialize and hydrate from the in-instance library are reused for federation — schemas copied from a remote source go through the exact same code path, with provenance columns capturing the source URL and remote venue id.
  • Per-instance rate limiting on the source side; admin-initiated revoke + create only (no auto-rotation in this release).

Why it matters

Self-hosted customers no longer need to manage their own schema bootstrap. The first cross-instance consumer is Timepad; the same path is offered to any on-prem operator who wants to pull from seatmap.pro or another internal instance.

Full documentation: Library Federation and plans/1.65/FEDERATION.md.

Direct sale endpoint (booking API)

Back-office and admin integrations can now mark seats as sold without first calling /lock. A new endpoint POST /api/private/v2.0/booking/directsale performs an atomic ACTIVE -> SOLD transition for seats and general-admission capacity in one call. Issue: SEAT-957.

Why it matters

The existing /sale endpoint requires seats to be in LOCKED state before they can be sold — a deliberate design that pairs with interactive checkout flows where a customer holds seats while completing payment. Integrations that sell tickets through other channels (comp tickets, manual sales, imports from external POS systems) had to issue a /lock immediately followed by a /sale to get the same outcome, which doubled the request count and required holding matched sessionId state across two calls. /directsale replaces that pair with one call.

How it works

  • Endpoint: POST /api/private/v2.0/booking/directsale?eventId={uuid}
  • Request body: same StateSelection shape as /sale (seats, GA, sessionId, optional holdType)
  • Auth: same hasAccessToEvent check as /sale
  • Returns 200 true on success, 200 false if any seat is not in ACTIVE state or any GA activeCount is below the requested capacity. The operation is atomic per request — partial application is rejected.

Constraints

  • Seats must be in ACTIVE state. LOCKED, SOLD, and BLOCKED are rejected (returns false, no state change).
  • GA capacity is deducted from activeCount directly (no intermediate locked bucket).
  • The audit row in seat_state_events carries old_state=ACTIVE, new_state=SOLD so downstream metrics and outbox consumers see the transition correctly and can recover the source state.
  • The existing /sale endpoint is unchanged. /directsale is opt-in.

When to use which

  • /sale — interactive checkout flows where a customer holds seats during payment. Continue to /lock first, then /sale.
  • /directsale — non-interactive flows (back-office sales, comp tickets, imports). One call, no holding state.

Full documentation: Direct Sale Endpoint.

Global-admin impersonation

Global admins can now act as any non-admin user directly from the editor admin area. This unlocks support workflows that previously required customers to share credentials, and lets admins reproduce permission-scoped issues in the context of the affected user. Issue: SEAT-956.

How it works

  • Open the user list or any user details page in the admin area.
  • An Impersonate button appears for non-admin targets when the current user is a global admin.
  • The admin re-authenticates with their own password in a confirmation dialog. In production, an extra warning is shown. If the editor has unsaved changes, the admin must explicitly acknowledge that the changes will be lost.
  • After confirming, the admin is logged in as the target user. The navbar turns amber, shows “Impersonating ”, a live countdown of remaining time, and a Stop button.
  • The impersonation session lasts up to 30 minutes and is not renewable.
  • The admin can press Stop at any time to return to their own session.

Audit trail

Every impersonation start and stop is recorded in a dedicated impersonation_audit table with the admin user, the target user, start and end timestamps, the session UUID, and the originating IP / user-agent. All requests issued during an impersonation session carry the impersonator identity in MDC, so server logs preserve who actually performed the action.

Constraints

  • Only ROLE_GLOBAL_ADMIN can impersonate.
  • Cannot impersonate yourself.
  • Cannot impersonate another global admin.
  • Cannot impersonate a disabled user.
  • Tokens are non-renewable — expiry forces a return to the login page.

Why it matters

Customer support no longer needs shared passwords to investigate customer issues. Permission bugs and tenant-scoped problems can be reproduced authoritatively, and there is now a tamper-evident record of every admin intervention.

Full documentation: Admin Impersonation.

Combine sections (editor)

Operators can now select seats from multiple existing sections in one continuous gesture and consolidate them into a single new section. Issue: SEAT-896.

How it works

  • A new “Combine sections” toolbar entry switches the editor into a dedicated COMBINE_SEATS mode.
  • The sidebar swaps in a Combine panel showing a running pick list and a Confirm button.
  • Click seats anywhere on the canvas — picks toggle in/out and render with a distinct fill so they are visually separable from the regular selection state.
  • Confirm creates a new section at the centroid of the picked seats; each pick is reassigned to a row in the new section while keeping its on-screen position. Mode returns to the standard section editor with the new section active.
  • Cancel discards the pick list and returns to the standard editor.

The existing single-source extraction flow (Extract Seats inside one section) is unchanged.

Section name on tables

Tables can now display their section name directly on the table object, with a draggable label anchor for fine positioning on irregular shapes. Issue: SEAT-895.

How it works

  • A new Show label checkbox appears in the Table panel when a table is selected.
  • Default is off, so existing schemas open with no visible change.
  • When enabled, the section name renders on the table in both Canvas2D and WebGL paths. The label uses the same font ladder as other section labels for visual consistency.
  • The label position is the table’s geometric center by default; dragging the label anchor (same UX as the existing GA section anchor control) moves the label without affecting the table geometry.

Why it matters

Experticket — like several other arena and theater customers — needs section names on round / rectangular tables to label VIP and reserved sections. The toggle keeps existing layouts unchanged while opening the door for table-heavy venues.

Booking-disabled enforcement on V1 paths

The legacy V1 booking API now honors the booking_disabled flag introduced in 1.63.0, returning the same HTTP 402 ORGANIZATION_DISABLED response as V2. Issue: SEAT-959. Customer: MemberYO.

Background

SEAT-928 (1.63.0) introduced the two-flag model (booking_disabled / login_disabled) and enforcement on V2 paths only. A production audit on 2026-04-29 found MemberYO’s V1 integration continued locking seats with booking_disabled=true because the V1 paths (pro.seatmap.booking.security.v1.*) were missed during the original rollout. Fifteen successful V1 lock transitions in the previous 24 hours confirmed the gap.

Fix

V1 token authentication (ApiUserDetailsService) and access checks (SecurityService.hasAccessTo*) now throw OrganizationDisabledException when the resolved organization has booking_disabled=true. Behavior on enabled organizations is unchanged.

Admin tenant visibility + move-to-tenant

The admin organization edit screen now shows the tenant the organization belongs to. Global admins can move an organization between tenants directly from the same screen with a confirmation modal. Issue: SEAT-953 (retro — shipped without a ticket, retroactively tracked).

A new i18n bundle ships field.tenant and field.tenantUnassigned strings in 8 languages.

Observability — SaaS dashboards, outbox panels, Release Watch

Observability work piggybacks on the 1.64.0 outbox foundation:

  • Seat-state outbox panels in the SaaS Fleet and SaaS Monthly dashboards expose the live event stream from seat_state_events, joined with org and tenant metadata.
  • Zero-config on-prem telemetry — instance and component tags now flow from Helm values into Sentry / Loki without operator action.
  • Release Watch dashboard — new dashboard for tracking deployment health in real time during release rollouts.
  • Analytics: v_bookings_log recreated on the analytics schema with new columns to align with the seat-state outbox model.

Issue: SEAT-954 (retro — shipped without a ticket, retroactively tracked).

Bug fixes

  • booking-client GlitchTip coverage (SEAT-958) — the admin renderer now initialises Sentry (previously every error was a silent no-op), the booking renderer initialises Sentry early so constructor and schema-load failures are reported, WebGL context-loss is captured as a Sentry warning with heap / GPU / canvas diagnostics, and 4xx ApiError exceptions are dropped from GlitchTip to clear noise from expected business errors.
  • Editor — lazy Tenant proxyOrganization.getTenant() now initialises the Hibernate proxy before returning, fixing a LazyInitializationException during admin org edit.
  • Editor — library browse loader — the library browse preview modal now renders a visible loader while the remote bundle is fetching.
  • Editor — VenueListContainer — stopped an infinite /api/city/ polling loop introduced when city selection was nullable.
  • Editor — library publisher banner — global admins no longer see the “not a publisher” banner on the library admin pages.
  • Booking renderer — table labels — labels render correctly in the WebGL and Canvas2D paths for venues that have no GA bindings, fixing a regression that hid labels on table-only schemas.
  • Observability — Grafana legend — escaped curly braces in the booking outbox panel so dashboard labels render correctly in the Grafana renderer.
  • SEAT-948 hardening series — orphan-row cleanup in the update() payload, validate update payload before any mutation, zone lifecycle and orphan-cleanup gate, perf bound on copy/hydrate, global-admin org listing without orgId, batch-insert routing, decoded query in search param assertion. All under SEAT-948.

Database Changes

Two Flyway migrations run automatically on editor-service startup:

  • V104__venues_library_and_federation.sql — adds is_publisher and library_status columns to organisation, library_status to schema, library_origin_instance_url and library_origin_venue_id provenance columns to venue, partial indexes for the publisher and library-status lookups, and the new federation_instance table for federation API keys.
  • V105__create_impersonation_audit.sql — adds the impersonation_audit table that records every impersonation start/stop with admin id, target id, session UUID, IP, user-agent, and timestamps.

Both migrations are additive. No down migration is needed; downgrading editor-service to 1.64.0 with these tables present is safe (the unused columns and tables are ignored).

The analytics schema (products/db-migrations/manual/analytics-schema.sql) was updated to recreate v_bookings_log with new columns; apply via the seatmap_analytics role on the analytics schema.


Configuration

Two new optional Helm values for federation-consumer instances:

editor:
  config:
    seatmap:
      federation:
        source:
          url: 'https://seatmap.pro'
secrets:
  federation:
    sourceApiKey: '<one-time key issued by the source instance>'

Both default to empty. When set, the editor pod gains SEATMAP_FEDERATION_SOURCE_URL and SEATMAP_FEDERATION_SOURCE_API_KEY env vars and the federation-consumer client activates.

The observability Helm chart version was bumped (deployment/helm/observability/Chart.yaml) to ship the new dashboards and the Release Watch dashboard.


Migration Guide

What operators should know: standard rolling upgrade. The V104 and V105 Flyway migrations run automatically on editor-service startup. SaaS prod (single-instance) does not need to set the new federation Helm values. Self-hosted consumer instances pulling from another source set both editor.config.seatmap.federation.source.url and secrets.federation.sourceApiKey via the standard seatmap-helm-secrets valuesFrom pattern.

What integrators should know:

  • Public REST contract is unchanged. The booking API gains an opt-in /directsale endpoint; existing /sale callers are unaffected.
  • V1 booking integrations against organizations that get disabled now receive HTTP 402 ORGANIZATION_DISABLED (matches V2). If your integration ignored 4xx responses on the V1 path, surface this state to operators.
  • The @seatmap.pro/renderer package public API is unchanged. The internal Sentry initialisation surface was refactored — host applications that import initSentry directly from the package internals must migrate to initSentryEarly + setSentryInstanceId (see Booking Client API Changes).

Additional Documentation


Questions? Contact the Seatmap team or check our documentation at seatmap.pro.