Skip to content

Dashboard Aggregates

Dashboard aggregates answer complete-data questions independently of entity browsing pagination. The implemented catalog provides Event, Venue, Artist and Event Series inventory, Upcoming Events, Operating Venues, Event activity totals and time series, distinct Event rankings by Venue, Artist, Series or country, and a complete reported-locality hierarchy grouped by country. The built-in public Overview starts with four compact inventory metrics (Events, Venues, Artists and Series Recorded) and four wide visualizations (Events Over Time, Events Per Venue, Events Per Artist and the reported-locality hierarchy). Its activity period defaults to All Time, inherited by all four charts; inventory metrics retain their fixed scope. It supports live editing, personal saved views, and maintainer-published compositions that readers can copy independently. Readers can add the other cards and ranking widgets through Add Widget; catalog admission does not rewrite stored published or personal compositions. Further metric families remain catalog work. The component showcase uses synthetic data.

packages/api-contracts/src/constants/dashboard.ts owns semantic constants, canonical as const arrays, derived union types, capabilities, and bounds. Dashboard DTOs and calendar/identity helpers live in the same package. UI-only loading states and preset compatibility remain frontend-owned; renderer options and library protocol strings retain their local meaning.

Keep these identities separate:

  • A metric identifies a data question and semantic version.
  • A widget definition identifies a supported presentation and configuration contract.
  • A widget instance has a stable UUID, definition/version, configuration, filter bindings, and preferred size. Duplicates receive new UUIDs.
  • A composition stores ordered instances, reporting time zone, and dashboard filters in the existing dashboard preset contract. Runtime results, resolved relative dates, localized status labels, chart coordinates, and library options are not persisted.

The editor owns one TanStack Form draft. TanStack Query owns remote results and request state. The applied composition is a baseline for dirty comparison and Discard; it is not another editable draft. Persistence schema version, metric/widget semantic versions, and a saved view’s concurrency revision describe different changes.

The canonical product workspace is /[locale]/dashboard. DashboardPage owns aggregate prefetch and hydration; DashboardPageContent composes the editor, live readings and saved-view workflow. The page is public, while personal account persistence and platform curation retain their existing access boundaries.

RoutePurpose And State Owner
/[locale]/dashboardProduction reading, editing, widget settings, saved views and permission-controlled curation in one composition workspace.
/[locale]/component-showcase/dashboardSynthetic DashboardPreview scenarios reuse the production editor without production queries or persistence.
/[locale]/component-showcase/dashboard/renderersSynthetic WidgetShowcase exercises widget kinds, sizes and repeated instances; kind, size and count seed its local controls.

?view=platform-overview selects the stable Overview alias; ?view=<ID> selects an available personal or published composition. A device or account still determines whether its personal identifier is available. Settings, saved-view management and publication history are in-page workflows, not child routes. A prepared curated composition is neither a new route nor a publication. Showcase URLs and fixtures remain separate from the product’s composition, query and persistence contracts.

The synthetic preview and widget showcase explain their in-memory fixture scope and provide localized links between examples and back to the live dashboard. Their existing deployment visibility remains unchanged. Keep these links with the showcase owners; the production navbar does not need to expose development fixtures.

The production workspace uses the existing NavBarSlot, as the entity index pages do. DashboardSavedViews supplies the visible dashboard heading, saved-view picker, dirty-only Save Custom View, pencil Edit, close/Finish and the secondary page-action menu. The navbar component arranges prepared commands; it does not own composition state or remote queries. Because the shared slot retains outgoing content during its exit animation, the saved-view owner rejects selection/deletion commands after unmount before they can change recovery, form state or the URL. Narrow layouts keep the editing and menu triggers available, while Save Custom View remains reachable through the page menu when there are unsaved changes. Refresh, Saved Views and Edit use the same Codon tooltip pattern with localized current-action labels; the Saved Views tooltip dismisses while its popover is open. Edit and Finish controls remain transparent, including hover. Dashboard, navbar and Saved Views buttons use the 5px theme spacing unit for padding; buttons size to their content plus that padding, with natural 30px icon controls. Avoid local minimum-size overrides that silently add blank space despite correct padding declarations.

Ordinary desktop content uses one supporting toolbar. Its Saved Views selector groups the built-in Overview and published views with the reader’s personal views; selection follows the applied identifier and the existing dirty-navigation guard. A small warning-colored circle beside the selected name indicates unsaved changes, with a hover label and screen-reader status text. Activity period, reporting zone and Add Widget share that toolbar; compact Refresh (with an explanatory tooltip) and conditional Discard live in the navbar. Discard remains in the page menu at narrow widths. Specific Dates opens the established date-range picker and validation in a bounded popover, editing the same form field without making the toolbar taller. Done requires a valid range; Escape returns to the surviving date trigger, which keeps its correction label and accessible error description for incomplete input while using the normal foreground color. Storage context appears in the saved-view management title; the toolbar retains the default-view marker when applicable. The naming form places Cancel then Save at the bottom right. Confirmed save, copy, metadata and deletion actions use the application’s temporary success toast; failures remain inline with the existing draft and retry ownership. Publication revision/copy controls retain their existing ownership. Recovery, errors and newer-publication notices remain explicit and may add space; narrow screens wrap controls instead of clipping them.

DashboardEditor supplies prepared fields, Add Widget and Discard through its local toolbar render slot; DashboardReading supplies the existing refresh command through the reading slot. DashboardSavedViews arranges those controls with the view selector and registers the navbar. Loading and unavailable views use a navbar fallback; tests await the applied registration after the shell’s exit animation. Commands rendered outside the disabled editor fieldset retain explicit busy/recovery guards, and Refresh/Discard reject actions after their owning component unmounts. Portaled naming and publication forms retain their own submit handlers; the editor ignores their bubbled submissions. The editor remains the sole composition form owner and the reader remains the query owner. Synthetic previews retain inline editor controls without the live reader or navbar workflow.

Maintainers reach publication, revision, history and deletion commands from Manage Published Views beside the public selector. Existing permission and revision guards remain authoritative. A surviving menu trigger owns focus return after a curation dialog closes; a menu item cannot remain a focus target after the menu unmounts. Name and history dialogs keep viewport bounds and scrolling independently of dashboard card dimensions.

DashboardGrid owns collection navigation and drag semantics. DashboardWidget selects the established renderer through the typed result-family registry and coordinates transient explorer visibility. The corner WidgetActionMenu presents canonical action IDs and forwards callbacks to their existing owners. Editing exposes Settings, Duplicate, Move Earlier/Later and Remove; the drag handle stays directly visible at the upper left, opposite the action menu. The handle, title and menu share a vertically centered heading row, including when titles wrap. Invalid, busy, edge and widget-limit guards remain editor decisions.

Settings edit the widget’s fields in the same TanStack Form used by the complete dashboard. Done validates and closes the settings modal; the dashboard’s Discard command restores the applied composition. Opening or closing settings creates no separate persistence or widget draft store.

Populated time-series, ranking and reported-locality widgets expose Explore Data in the same menu. Their expanded modal reads the existing prepared result, exact count labels, scope, coverage, refresh failure and freshness. CartesianChart retains the selected row; HierarchyChart retains both selected identity and navigation path. A populated refresh preserves valid reading state, clears removed selections and returns from a removed hierarchy group to its nearest surviving drill ancestor. Reappearing data does not silently restore discarded selections or reopen removed groups. The card stays mounted while the modal renders the table and applicable node picker or drill controls from that same state. Opening, navigating or closing the explorer neither changes query limits nor requests another aggregate. The editor preview and renderer showcase adapt synthetic fixtures to that same prepared-widget boundary and use the same menus and expanded readings. Direct chart consumers can still omit the optional explorer and use the embedded fallback controls.

Both chart families use one app-local ChartTooltip built on the existing Codon Tooltip. ECharts grid hits and D3 circle hits supply pointer coordinates; their chart owners supply the same exact localized reading shown inline, including independent unique Event counts where supplied. Cursor updates stay inside the overlay at animation-frame cadence, so moving within one datum does not rebuild the renderer. A small cursor anchor is portaled outside clipped or transformed cards, and Codon owns viewport adjustment and flipping. Because the current Tooltip API has no explicit reposition handle, the adapter renews its target ref when the pointer moves; same-datum browser coverage protects that compatibility detail. Leaving the chart, scrolling, resizing, keyboard input and unmount dismiss the overlay. Touch retains inline selection without a persistent cursor tooltip; keyboard navigation and expanded tables retain their existing owners.

Closing returns focus to the widget action trigger. If an unavailable result removes the reading, exploration ends and focus can return to the containing widget row; later data does not reopen it automatically. The modal uses the established stacking token above app navigation, viewport bounds and a scrolling body. Nested controls retain their keyboard behavior: Escape can clear table selection or return from a hierarchy level, while the dialog controls provide dismissal. Selection and navigation are transient reading state and never enter saved composition JSON. The collection owns entry into a widget row; keyboard/browser journeys enter that row before focusing a nested chart or action. This prevents a direct jump into an inactive row from being redirected to its first control.

Widget spacing and row minimums are presentation choices; the composition still stores only the supported preferred-size token and ordered UUIDs. The grid measures its available width to fit columns with a 300px minimum, falling back to one fluid column below that width. New scalar widgets default to Compact, while time-series, ranking and geography widgets default to Wide using the shared result-family capabilities. Wide/Large preferences span two columns when available; Tall/Large preferences retain their row spans. At single-column mobile widths, every preference renders as one column and one row without changing its stored size. Cards fill their grid row so adjacent metric borders remain aligned despite differing scope text. Metric-only rows remain compact; at intermediate widths a metric sharing a row with a chart also fills that taller row. Auto-placement preserves DOM order and does not fill earlier holes. Local Motion wrappers animate controlled reorders after the native drop event, while an animated visual marker follows the shared insertion geometry and React Aria retains its semantic drop targets. Reduced-motion preferences disable both transitions. Component state remains visible in DashboardGrid, with measurement and observer cleanup in its existing helper module. The production page uses no top padding and 15px theme-unit gutters at the sides and bottom. Expanded readers use a 650px maximum width for two visible data columns and 850px for additional columns, bounded by the viewport. Their header keeps a bold title beside a localized, transparent, 5px-padded X close control. Title, scope and content use 5px gaps. Outer table content aligns with the dialog content edges; interior column spacing remains and focus outlines stay inset within the scrolling surface. Expanded tables use local readable body typography, wrapping column headings and numeric alignment without changing the shared Table component. The modal body owns overflow for long data, while card scope, coverage and freshness remain visible.

MetricEligibilityResult
events-recordedCurrent active Event records, including cancelled Events, with no activity date filter.Exact scalar inventory count.
venues-recordedCurrent active Venue records across every operating status, including unassociated Venues.Exact scalar Venue inventory count.
artists-recordedCurrent active Artist records, including Artists without Event relationships.Exact scalar Artist inventory count.
event-series-recordedCurrent active Event Series parents across both structures and all continuity states.Exact scalar Series inventory count.
events-in-periodActive, non-cancelled Events whose authored startLocalDate lies in the resolved period.Exact scalar activity count.
events-over-timeThe same activity eligibility, grouped by calendar day, Monday week, or month.Ordered, zero-filled buckets with exact counts and clipped bounds.

Each inventory executor counts rows in its own entity table with the established active-entity predicate. Soft-deleted records are excluded; operating status and Series continuity are separate domain facts. An ended Series still counts while its entity record is active. Editions and program Events never multiply Series parents, and no relationship is required for an inventory record to count. Inventory query contracts accept only metric identity and version, with no period or reporting zone. Venue, Artist and Series scopes omit the Event-only cancellation flag and declare their own counting units. Event activity executors read only the Event table. Count values cross the API as nonnegative decimal strings, preserving PostgreSQL bigint precision. A complete result covers all eligible recorded data; corpusCoverage: "unknown" deliberately makes no claim about coverage of real-world events. Current inventory is not historical acquisition growth.

Activity uses the organizer’s authored start date for both date-only and timed Events. It does not count duration overlap, reinterpret dates in the browser’s zone, or exclude future dates that an explicit period includes. Results state their effective scope, cancellation rule, counting unit, and coverage.

Upcoming Events counts active, non-cancelled Events whose canonical temporal phase is upcoming at the batch operation time. Its executor reuses buildEventTemporalPhaseCodeExpression, including each Event’s reference zone, native date precision and exact instants. A timed Event leaves Upcoming at its start instant; a started Event without an end can be indeterminate rather than receiving an invented duration. Date-only Events change phase at their own reference-zone calendar boundary. There is no future horizon.

Operating Venues counts active Venue records whose normalized status code is operational. The status foreign-key join cannot multiply records, and Event associations are irrelevant. Record lifecycle remains distinct from operating status; a deleted operational Venue is excluded.

Both definitions have metric-defined scope, accept only metric identity and version, and ignore the dashboard activity period and reporting zone. Their results declare timeBasis: "current-state", the evaluated operation instant, and the exact phase/status eligibility. Response correlation requires that instant to match the batch operation time. They share the existing exact-count scalar renderer, settings, saved-view and refresh owners. Stable semantic cache identity deliberately omits the changing operation instant; the standard freshness deadline and explicit stale presentation describe when the current result was evaluated. Expiry alone does not start polling or guarantee an immediate transition at an Event start; refresh, mount, focus or reconnect obtains a new evaluation.

The composition defaults to explicit UTC; supported IANA reporting zones determine the current calendar date from server operation time. The server resolves relative API requests against that time, and frontend relative queries require a server-derived clock rather than the device’s uncorrected calendar. Last 30 Days includes today and the preceding 29 dates. Last 6/12 Months starts at the first day of the month five/eleven months earlier and ends after today. These are calendar ranges, not fixed elapsed durations.

Custom date selections are inclusive. Shared helpers normalize them to half-open [startDate, endDateExclusive) ranges for SQL and response metadata. Absolute ranges are limited to five calendar years. Series allow at most 366 buckets; excess geometry returns bucket-limit-exceeded rather than silently resampling. Bounded requests check geometry before SQL; All Time requires the observed corpus extent first. Weeks start Monday. First and last buckets are clipped to the selected range and marked partial when they do not cover a complete calendar bucket.

All Time removes the activity date predicate, including any past/future horizon, while preserving active, non-cancelled eligibility. Its scalar scope carries period: "all-time" and range: null. Time series derive their observed first/last authored dates and counts from one grouped SQL statement, fetch at most 367 populated groups to detect overflow, and zero-fill gaps only when the complete extent fits the 366-bucket ceiling. Empty all-time series have range: null and no buckets; no synthetic range is introduced. All Time is independent of the five-year limit on explicit authoring ranges, and never truncates a successful result. Future Event Series activity and other dated metric families must declare their own eligibility and date meaning when they join the catalog.

An activity widget either follows the dashboard period or supplies a custom period that replaces it. Inventory has its own fixed scope. Display style, widget title, instance identity, order, and size do not change the aggregate query.

Events Per Venue, Events Per Artist and Events Per Series count distinct eligible Events within each active related entity. They inherit the same organizer-date activity scope, including All Time, and exclude cancelled/deleted Events and deleted groups. One Event can contribute to several groups. The unique eligible and linked Event counts therefore remain separate from the additive Event–group association total; the interface does not describe a sum of group counts as unique Events.

Events Per Series follows the canonical Series browsing membership rules: direct Events belong to active direct-events Series; Edition program Events traverse active Editions beneath active editions Series. Repeated appearances in several Editions of the same Series count once for that Series. Series continuity and Edition disposition do not filter eligible Events, and Edition windows do not supply or override Event activity dates. An ended Series or cancelled Edition can therefore contribute its independently eligible, non-cancelled Events while its records remain active. Inactive ancestry suppresses traversal.

A shared SQL owner takes each metric’s active public membership projection and computes all memberships, distinct coverage, group totals and displayed groups in one statement. It ranks by exact count descending, then public ID ascending under deterministic byte ordering. Public IDs distinguish equal names and survive renames. Top N supports 5, 10 or 20 groups; it limits the returned groups after complete aggregation. The response also carries the omitted group count and association count. The UI presents that remainder as a separately identified Other Groups entry, so it is never silently dropped or mistaken for one entity.

Bars and packed circles consume the same groups and remainder. Circle parent weights sum Event–group associations; coverage separately reports unique linked and eligible Events. All table, selection and summary readings preserve exact decimal counts, while numeric conversion is confined to geometry. Empty membership results use the existing empty-widget state. The editor’s existing form owns style, Top N and inherited/custom period settings; style changes do not change the data query, while Top N does.

The shared buildDashboardWidgetQuery registry is the sole semantic composition-to-query projection. The frontend projects results through a typed result-family registry into scalar, time-series, ranking or geographic hierarchy readings, then selects the existing rendering owner. Adding a metric in an existing family extends registrations rather than another sequence of metric branches.

Events Per Country and Events Per Reported Locality use the same organizer-date eligibility as the other Event activity metrics, including All Time. Attribution follows actual Event–Venue relationships and each active Venue’s current address snapshot. A qualifying location must have exact publication policy and complete persisted coordinates, matching the public Venue map’s eligibility. Aggregates do not call the bounded map endpoint or an address provider. Correcting or relocating a Venue changes current attribution even for historical Events; these queries do not reconstruct past locations.

Country identity is the recorded two-letter country code. The interface localizes its display name while retaining the code; this is not external validation of the snapshot. Locality identity is the tuple of country code, region and locality. Outer spaces are trimmed, an empty region becomes null, and a nonempty locality is required. Case, spelling and other reported text remain distinct. Null region is an explicit tuple component, not an exclusion. These are reported localities rather than canonical cities: aliases can split one place and identical tuples can combine places. The shared JSON tuple helper supplies reversible, namespaced reading identities without exposing address UUIDs or using coordinates as city identifiers. Locality labels include country and region context for the cross-country node picker.

Both metrics report nested distinct-Event coverage:

CoverageAn Eligible Event Has At Least One…
Published locationActive, exactly published Venue with complete coordinates.
CountryQualifying published Venue with a recorded country code.
LocalityQualifying published Venue with a country and nonempty reported locality; region may be null.

The gaps between these sets are mutually exclusive Event counts: no published location, a published location without country coverage, and country coverage without locality coverage. Coordinate-only snapshots are not described as having no public location. The separate online count uses the stored online Event type and can overlap every geographic set; missing geography does not establish that an Event is online, and hybrid Events are not included in that classification count.

Events Per Country deduplicates each Event within a country, includes country-only snapshots, and reuses Top 5/10/20 rankings, explicit remainder and bar/packed displays. Events Per Reported Locality deduplicates each Event within a complete reported tuple. Several Venues in one locality do not multiply the Event; one Event can contribute to several locality groups or countries. Country and root circle weights therefore sum Event–locality associations. Their primary exact readings use those same additive counts. A separate table column and selected/path reading show unique locality-covered Events within each country and across the whole result. Country coverage that lacks a locality remains visible in coverage metadata but creates no locality-tree node.

The geographic SQL owner computes complete memberships, coverage and counts in one statement per result. The locality transport has fixed country and locality arrays, not arbitrary recursive chart configuration. It admits at most 250 total rendered nodes, including the root and country nodes. The complete node count is evaluated before either JSON array is constructed; an oversized tree returns group-limit-exceeded for that item while other batch items remain available. The interface suggests a narrower period or the country ranking. Admitted locality rows are ordered by the shared encoded tuple identity after the bound is checked, avoiding differences between SQL collation and JavaScript ordering. The node ceiling bounds rendering cardinality; it is not a production query-cost or arbitrary-text byte-size guarantee.

Development Venue imports retain explicit reviewed country codes alongside the original locality and country name. The legacy converter rejects country labels outside its reviewed seed mapping. Existing address snapshots from earlier imports may have locality and country names with a null code; changing seed source does not repair a retained database. Review those snapshots against the original stable Venue IDs and address fields before a targeted local correction. Preserve edited or ambiguous records for manual review, and do not substitute display-name inference in the aggregate query or a destructive reseed. Country enrichment does not validate city/region spelling or establish canonical city identity.

The existing hierarchy reader owns country drilldown, back navigation, tables and keyboard exploration. Handled chart keys stop propagation so they do not also navigate the surrounding dashboard collection. Both chart families retain exact labels independently of numeric geometry. A failed refresh may retain cached data, but an explicit limit message remains visible alongside its failed-refresh freshness label. Empty geographic states describe missing country/locality qualification without claiming that Event relationships are absent.

apps/wavemap-back-end/src/queries/dashboard/metricRegistry.ts maps every supported metric ID to its typed executor. defineDashboardMetric binds the exact query/result variant and checks the discriminator at dispatch. The common dispatcher validates the query and operation time, then selects one registration. Each executor owns its SQL and resource preparation; Event activity helpers share only the eligibility, scope, and metadata used by the applicable metrics.

To add an approved metric, follow its existing result family through these owners:

  1. Add canonical metric/widget constants, arrays, derived types and capabilities in the contracts package. Declare eligibility, counting unit, time basis and supported configuration before implementing a query.
  2. Extend the strict aggregate/composition DTO variants and utils/dashboardWidgetQueries.ts. Keep semantic query identity independent of widget title, UUID, size, order and display style; include every field that actually changes the aggregate.
  3. Implement and register the backend executor. Aggregate complete eligible memberships before applying any output bound, and prove lifecycle/date/relationship rules, zero results, exact counts and limits against disposable PostgreSQL.
  4. Register the frontend catalog title, localized description and createDashboardWidget defaults. Reuse the result-family projector and rendering owner; add a new family only when the transport or reading contract actually differs. SQL and resource preparation stay in the backend.
  5. Check each mutation that can change eligibility or membership and reuse aggregate cancellation/invalidation. Cover both directions of relationship editing when the product exposes them.
  6. Prove valid settings, semantic queries, scope/coverage labels, unavailable configuration and independent saved copies. The complete-catalog tests cross-check advertised sizes/styles with strict DTO admission and hydrate every implemented family together.
  7. Exercise the representative reading/editor journey in the browser before propagating the pattern. Review curated composition changes separately; adding a catalog entry does not modify an existing saved or published snapshot.

Avoid extending a sequential chain of metric-specific branches or moving database behavior into shared contracts. New restricted metrics require a reviewed authorization and cache partitioning boundary before admission.

The public POST /api/v1/dashboard/aggregates/query route accepts 1–20 correlated requests. It validates the whole envelope before execution. Unknown metrics, unsupported versions, malformed configuration, and duplicate correlation IDs fail admission. Distinct semantic queries execute serially within each batch; equivalent queries share a projection while retaining each request ID. A valid item can return query-failed, bucket-limit-exceeded or group-limit-exceeded without discarding successful siblings. Standard API envelopes and localized request errors follow the existing handler pattern.

One operation time governs range resolution and response freshness. It does not promise a common database snapshot across independent statements. Direct query callers can supply their existing transaction. Series group through the selected SQL bucket alias so SELECT and GROUP BY share the same bound expression.

apps/wavemap-front-end/src/api/axios/dashboard.ts validates the request and correlates the parsed response, including metric identity and effective scope. The query family under src/api/queries/dashboard uses one TanStack entry per semantic metric/version, reporting zone, resolved period, bucket choice and ranking Top N. Equivalent relative and absolute periods share an entry. Duplicate widgets share in-flight work and results.

Before transport, the client freezes a relative query to the absolute dates used by its cache key. Queued work crossing reporting midnight therefore cannot put a new period under yesterday’s identity. useDashboardAggregateQueries requires a getOperationTime callback for any relative query, including disabled queries; it rejects missing clock context before transport. Low-level query options likewise require an explicit operation time. Inventory, absolute periods and All Time have no calendar dependency and can run without a clock callback. All Time remains unbounded in transport and has a stable semantic identity across reporting midnight; it is never frozen into an absolute range or assigned a midnight rollover timer.

The callback must read an advancing clock derived from server operation time. Midnight delays, focus/visibility reconciliation and manual refresh use that clock, including 23/25-hour DST days; the device’s raw date never selects a relative scope. The public route prefetches the built-in semantic queries and hydrates both results and operation time. A QueryClient-owned clock advances from that operation time using monotonic elapsed time; older responses cannot move it backward. Hydration rebases client freshness timestamps. If prefetch fails, inventory bootstraps the clock before relative activity queries are admitted.

A transient queue per QueryClient coalesces starts into batches of at most 20 and allows one active HTTP batch. It stores no result cache. Cancellation detaches the obsolete metric; the shared HTTP request is aborted when no active consumers remain. Late responses cannot populate a different semantic scope. New scopes expose their own pending state without previous-scope placeholder values.

The server advertises a 30-second freshness deadline from operation start and sends Cache-Control: no-store. The frontend uses the remaining deadline, capped at the advertised lifetime, rather than restarting the full window on receipt. Inactive query entries are collected after 60 seconds. There is no polling, server result cache, or automatic retry. Expiry makes an entry stale; ordinary TanStack mount/focus/reconnect behavior or manual refresh can fetch it again. A continuously open, unfocused view may retain stale data until one of those triggers, so the rendering owner must display freshness honestly.

Manual refresh and successful Event/Venue create/update and Artist/Event Series create/update/delete cancel pending aggregate reads before invalidation, preventing pre-mutation responses from restoring old counts. Successful Event/Venue and Event/Artist relationship edits invalidate aggregates from both sides of each relationship; rejected domain writes do not invalidate dashboard aggregates. Successful direct Series membership and Edition program changes, plus Edition creation/deletion, use the same aggregate invalidation helper. Existing Series/Edition relationship cache behavior remains independently owned. The current frontend has no Event or Venue deletion mutation to integrate; future deletion clients must use the same helper. Changes from other clients become observable on a later fetch. Results expose scope, freshness, and query status for the page’s loading/error/empty/stale presentation.

The “Updated” label formats the server computation instant in the viewer’s detected browser time zone and includes a short zone label. It uses the existing hydration-safe viewer-zone hook: server and initial-client output stay in labeled UTC, then switch to the validated browser zone after hydration; unavailable detection retains UTC. This is transient display context, not another saved setting. Chart periods, reporting-zone selection, query identity and expiry comparisons continue to use their existing server/calendar owners. Expanded readers receive the same prepared freshness label as their card.

The public route has no sign-in requirement. DashboardPageContent creates an actor-scoped workspace; DashboardEditor owns the sole editable TanStack Form composition. The saved-view workflow receives that form through a composition slot and keeps only applied preset metadata, the immutable baseline and transient interaction state. The live reader consumes the current semantic configuration through TanStack Query. Layout changes, widget order and configuration participate in dirty comparison; chart interaction and query results do not.

The initial selection waits for both the applicable device/account list and the public publication list. An explicit view URL identifier wins over the saved default, followed by Platform Overview. Its stable platform-overview alias selects the current published Overview when present and otherwise uses the built-in composition. Missing, foreign or incompatible explicit UUIDs remain unavailable instead of silently becoming Overview. Only an owned incompatible entry can expose its original JSON for download. Account changes remount the workspace, clear its draft and suppress the prior actor’s selected identifier.

Save Custom View appears only for net changes. It updates an applied personal view; for a built-in or platform view it opens the existing validated naming form with a suggested personal name, then saves the edited composition as a new personal view with fresh widget UUIDs. The platform source remains unchanged. Save As remains available in saved-view management for making an additional personal copy. A pending save locks editing; failure leaves the draft and applied revision intact. Finish Editing closes immediately when clean. With unsaved changes it offers saving, discarding the draft and closing, or continuing to edit. A successful save completes the pending finish action; cancelling naming keeps the draft open. Switching or deleting the applied view while dirty uses the same save/discard/keep decision. Declining URL navigation restores the applied identity in the URL, including cancellation from the nested naming dialog. Rename/default changes advance the applied revision without replacing an in-progress composition.

Add Widget opens a selectable catalog table with descriptions and select-all. Existing definitions stay checked and cannot be selected again; Duplicate remains available in the widget menu. New selections are transient, and Cancel or dismissal leaves the composition untouched. The count and primary Add to Dashboard action apply only to new definitions, creating fresh instances and updating the sole editor form once. The handler checks existing definitions and the widget limit against the live form before mutation; an over-capacity selection stays open for correction. The table uses square row separators without a final-row border, and scrolls independently of its header and action footer. Its transparent X uses the same 5px padding as the explorer close control.

Account writes reuse the existing personal preset endpoint, handlers and table. Dashboard responses require a positive server revision, and updates require the applied expectedRevision. The query first establishes active ownership, validates the complete resulting composition even for metadata-only changes, then updates with an atomic owner/status/revision predicate. A stale revision returns the keyed conflict response. Default promotion locks the owner row within a transaction, then clears other active dashboard defaults only after the requested write succeeds. A stale revision cannot clear the existing default, and affected sibling revisions advance. Reopening the refreshed view is explicit; a conflict never replaces the current draft automatically. Foreign, missing and inactive targets share the unavailable boundary.

usePageQueryPresetController partitions list queries by actor and forwards cancellation. Its record-returning commands provide a new saved baseline without changing the existing browsing-page boolean commands. Device persistence preserves unrecognized original entries during unrelated writes, refuses collisions with unknown IDs, and refuses to replace a corrupt container. Failed storage writes do not publish a false saved list.

Platform Publications And Independent Copies

Section titled “Platform Publications And Independent Copies”

Platform views use the same page_query_presets parent capability with an explicit ownership scope. Personal rows require an account owner and cannot carry a platform key or publication pointer. Platform rows have no personal owner, belong to the dashboard page, and cannot become an account default. Checks enforce these combinations, valid provenance pairs, and publication pointer bounds. A partial unique index admits only one active platform-overview key. The migration retains legacy personal IDs, JSON, audit fields, revisions and defaults; it does not silently deduplicate old default selections.

page_query_preset_publications retains complete validated composition, name, schema version, revision and publisher audit metadata. The publication query owner appends snapshots and advances the parent’s published pointer in the same transaction. Public responses project the snapshot, not mutable parent content. Publication and deletion lock the parent and require its expected revision. These guarantees apply to the application query owner; privileged direct SQL is not an immutable-storage boundary.

Route Under /api/v1/page-query-presets/platformAccess And Behavior
GET / and GET /:IDPublic listing and current publication.
GET /:ID/publications/:revisionPublic exact snapshot; a missing revision never falls back to the latest one.
POST /:ID/copiesAuthenticated personal copy of the requested exact revision.
POST /, POST /:ID/publications, DELETE /:IDCreate, publish a complete new revision, or soft-delete; require manage-platform-saved-views.
GET /:ID/publicationsCapability-protected history metadata, at most 50 entries per revision-cursor page.

Admin, root and development-debug roles hold the curation capability. UI controls use the existing permission wrapper, while server middleware enforces access independently. Personal query paths explicitly exclude platform records. Missing, deleted and personal targets share the public unavailable response. Soft deletion retains stored snapshots while removing the source from public reads, new copies and active history access.

Copy Published Revision copies the displayed revision even if a newer publication becomes available in the background. Account copies share-lock source liveness, read that exact snapshot and create the personal preset in one transaction. Device copies revalidate the exact source before writing through the existing local controller. Both preserve relative filters and regenerate widget UUIDs. Optional source ID/revision metadata survives device import but grants no access; it has no cascading foreign key, so a later publication or source deletion cannot alter an existing copy. Save Custom View retains edited working state as a personal view; Copy Published Revision retains the exact published snapshot.

The dashboard’s existing editor owns curation composition state. Publish New View creates a separate platform identity from the complete working composition. When Overview has no active publication, Publish Platform Overview explicitly creates its first revision and stable alias. No migration or seed invents a publisher or automatically publishes that bootstrap.

Publish Revision uses the applied publication’s expected revision and the complete current form state. The name dialog owns only the submitted name. While a command is pending, the editor and competing actions are disabled. Background list refreshes never replace the applied composition. A conflict preserves the draft and directs the maintainer to Save As or to discard and explicitly reopen the latest revision before retrying.

Publication History mounts an actor-scoped, bounded metadata query only while its dialog is open. Loading an older revision fetches its exact complete composition into the existing form, retaining the current applied baseline and revision. The maintainer can inspect or discard that draft; publishing it appends a new revision rather than moving the pointer backward. Save or discard current changes before loading history or deleting the source. Confirmed deletion returns to Overview and leaves existing personal copies intact.

The mutation owner aborts obsolete account visits, checks actor identity before and after requests, and invalidates the initiating account’s data. Late responses cannot apply a prior actor’s publication to a new workspace. Private history queries are removed after their dialog unmounts; exact public snapshot cache identity includes the requested revision. Applied publication revisions also participate in the existing per-tab recovery key.

A curated composition is a normal complete publication request, validated by CreatePublishedDashboardRequestDTO or PublishDashboardRevisionRequestDTO. Review its ordered widgets, sizes, display settings, inherited/custom periods, reporting zone and names as one snapshot. Keep preparation artifacts beside the active roadmap; production code does not import them, and checking them into the repository does not publish them.

Widget UUIDs identify instances within the source composition and should survive ordinary layout/configuration revisions. The existing copy commands generate fresh UUIDs for personal copies. Publication identity, revision, publisher and timestamps remain server-owned. Use the Overview platform key only for its first publication; revising an existing Overview uses that parent’s identity and expected revision. The normal selection owner prefers a published Overview over the eight-widget built-in fallback, while previously saved views retain their own state.

Publication names and widget titles are stored text. A title localized when the view is authored becomes part of that snapshot; changing the reader’s locale does not translate stored titles. Controls, scope, count formatting and geographic display names still use the reader’s locale. Review the intended language alongside the composition and target environment.

Publication proof should cover the exact candidate state through persistence, rendering, a newer source revision, copies of the displayed older revision, and source deletion. Use the disposable-stack browser fixture for that proof. Shared publication remains a separate explicit action through the existing maintainer workflow; it needs neither a schema migration nor a second template persistence system.

The existing import registry includes supported dashboard presets. After login, its coordinator compares canonical complete device/account state and offers an explicit copy. It retains device originals, skips incompatible presets, and records handled differences per actor/page. Partial failures remain explicitly retryable. The active import scope remembers completed items, checks the current actor before each remaining request and after each response, and prevents stale onboarding metadata from replacing another account’s cache. An interrupted batch may have successfully created earlier items; the refreshed account list deduplicates those items on a later session.

dashboardDrafts.ts stores supported composition JSON in per-tab sessionStorage, using a versioned actor/device, view and applied-revision key. It debounces edits by 400 ms and expires snapshots after 24 hours. Clean or discarded drafts are removed immediately so an immediate page unmount cannot cancel their removal. A matching loaded baseline offers Restore or Discard; expired, incompatible and obsolete revision snapshots are removed. Restore changes form fields while retaining the saved baseline, so the restored composition remains dirty. Save, Discard and explicit view replacement clear the corresponding recovery. Successful logout clears account recovery; the existing logout broadcast also clears it in other open tabs. Device recovery stays separate.

Storage denial or an invalid draft does not prevent continued in-memory editing. Only a schema-valid composition is recoverable, so an incomplete invalid edit cannot overwrite the last valid snapshot. The page tells the user when the current draft cannot be backed up. Recovery is neither account autosave nor cross-tab collaboration.

The initial policy retains on-demand SQL and the existing Event date index. The inventory expansion adds no indexes or stored aggregates. A September 2026 disposable PostgreSQL measurement used 100,000 synthetic Events across ten years, including inactive, cancelled, date-only, and timed records. Date-scoped queries used eventsStartLocalDateIndex; inventory used a sequential scan. The three-metric batch had a warm median near 38 ms; 20 distinct five-year monthly queries had a median near 702 ms. These local, serial Event measurements justify the bounded initial design, not a production SLO, a performance measurement of the later inventory families, or a claim about concurrent traffic. Raw plans and run details belong in local evidence and the roadmap, outside published documentation.

Focused proof is owned by:

  • Backend test/db-backed/__tests__/dashboard: complete country/locality grouping, nested coverage, current-snapshot attribution, online overlap and 250/251-node admission, complete relationship rankings with overlap, deterministic ties and remainder, complete own-entity inventories, zero and deleted exclusion, operating-status/continuity coverage and relationship/Edition independence, native dates and zones, lifecycle/cancellation, calendar boundaries, zero filling, bucket ceilings, and the real mounted route.
  • Backend test/mocked-requests/__tests__/dashboard: admission, public access, localization, correlation, bounded scheduling, and isolated failures.
  • Frontend src/api/axios/__tests__/dashboard and src/api/queries/dashboard/__tests__: transport validation, identity sharing, cancellation, stale responses, freshness, midnight rollover, and mutation invalidation.
  • Backend preset database/route tests: migration compatibility, active personal/platform ownership, complete-state validation, publication/copy/deletion concurrency, immutable revision selection, bounded history, protected curation and existing browsing compatibility.
  • Frontend dashboard workflow and src/utils/pageQueryPresets/__tests__: form ownership, UUID commands, saved-view selection, exact copying, conflicts, history rollback, failed writes, account changes, import and recovery.
  • Frontend Dashboard/__tests__/explorer.test.tsx: exact counts independent of numeric geometry, calendar/bar/packed readings, shared selection/path, keyboard reopening, focus return, result-unavailability lifecycle and populated refreshes that remove a selection or drilled group.
  • Frontend EChartsRenderer/__tests__/EChartsRenderer.test.tsx and ChartViewport/__tests__/ChartViewport.test.tsx: reuse during data/size updates, per-instance disposal and observer disconnection across repeated mounts. These mocked lifecycle checks establish owner cleanup calls, not browser heap or frame-time measurements.
  • Frontend data/__tests__/catalog.test.ts, composition.integration.test.tsx and curatedCompositions.test.ts: complete catalog configuration/admission, metric-defined versus inherited/custom filters, retained future widget state, independent copies, full-catalog hydration and review-candidate validation.
  • test/e2e/dashboard/dashboard-inventory.e2e.test.ts: keyboard add/settings focus, independent device copies and reload, real inventory/current-state counts, metric-defined scope labels and light/dark desktop/mobile layouts.
  • test/e2e/dashboard/dashboard-rankings.e2e.test.ts: actual-API Venue/Series bars and Artist circles, keyboard settings/exploration, exact tables, Top N, All Time, independent saved copies/reload and light/dark desktop/mobile containment.
  • test/e2e/dashboard/dashboard-geography.e2e.test.ts: actual-API geographic coverage, independent unique/association counts, Cartesian and hierarchy keyboard focus, country drill/back, exact tables, saved copies/reload and light/dark desktop/mobile containment.
  • test/e2e/dashboard/dashboard-expanded-explorer.e2e.test.ts: EN/FR calendar and venue bar/packed modals, narrow French three-column geography, readable table headings/body, keyboard dismissal and reopening, retained selection, nested picker access, no extra aggregate request, viewport containment and maximum-length title visibility above navigation.
  • test/e2e/dashboard/dashboard-grid-refinement.e2e.test.ts: actual dropped/displaced-card transforms, marker exit, reduced motion, responsive column density and opposite-corner controls.
  • test/e2e/dashboard/dashboard-workspace.e2e.test.ts: custom save/finish/discard/reload, actual Back/Forward and dirty-cancel URL consistency, EN/FR multi-select catalog, navbar and supporting-row layout, desktop/tablet/320px reading/editing, text spacing, page-menu access, narrow saved-view/name/settings tasks and focus return. History setup waits for saved-view URL replacement separately from the rendered name.
  • test/e2e/dashboard/dashboard-publications.e2e.test.ts: public exact-copy independence and the maintainer publication/history/rollback/deletion journey. This test mutates public fixture views and requires DASHBOARD_CURATION_PROOF=1 against a disposable stack. Existing browser checks separately cover live counts/table/filtering and editing/device reload. Native screen-reader, physical touch and native zoom proof remain separate release evidence.
  • test/e2e/dashboard/dashboard-curated-catalog.e2e.test.ts: complete candidate snapshots, real aggregate admission, desktop/mobile light/dark layouts, keyboard device copying and independent account/device state after later publication and source deletion. It uses the same disposable-stack opt-in, creates separate fixture identities and cleans up only its own records.

Use the exact test file with its owning Vitest configuration. Database proof requires the guarded disposable target described in Testing Runtime And CI. Revisit this page when metric semantics, the audience, range/batch bounds, freshness, mutation ownership, or production composition changes. Measure realistic corpus growth and concurrent traffic before adding indexes, rollups, polling, or server caches.

The opt-in renderer benchmark exercises synthetic showcase fixtures. Run it against a separately prepared optimized runtime and report its hardware, build, fixture and sample posture; do not interpret development-server compilation or historical renderer candidates as the current production bundle. Evaluate cold and warm live loads, aggregate requests, transferred renderer code, repeated resize/reorder/mount cycles and concurrency separately when setting production budgets. Avoid concurrent Next processes writing the same checkout’s build directory.

Keyboard, reflow, text-spacing and emulated-touch tests support accessibility work without establishing whole-page conformance. Native screen-reader, physical-touch and browser-zoom checks must record the complete reading/editing journey, including toolbar/date controls, widget settings and explorers, saved-view/curation dialogs, recovery and unavailable states. Keep unexecuted device or performance proof explicit in the active roadmap rather than substituting library capabilities or narrower tests.