Query Controls And Browsing State
This page records the reusable query architecture that emerged from the artists index query rollout. It should guide future events, venues, event series, users, admin tables, and relationship-management surfaces that need searchable, filterable, sortable, or pageable collections.
Relationship sheets are one consumer of these patterns. Association-row modeling and relationship-management boundaries live in Domain Relationships.
Query-Control Contracts
Section titled “Query-Control Contracts”Applied query-control contracts live in @wavemap/api-contracts. UI draft state and backend SQL composition should
adapt to those contracts instead of each defining unrelated filter and sort shapes.
The durable applied shapes are:
TQueryFilterGroupTQueryFilterClauseTQuerySortInstructionTQueryControlEndpointCapabilitiesQueryFilterGroupDTOQuerySortInstructionDTO
Endpoint capability metadata should describe what a route supports:
- Endpoint identity.
- Supported criteria.
- Optional query keys.
- Label keys for localized UI chrome.
- Sort capability and default direction.
- Filter data type and allowed operation codes.
- Pagination support, default page, default limit, max limit, and allowed limits.
The frontend should derive available sort/filter controls from endpoint capabilities. Page code can translate labels and adapt presentation, but it should not invent unsupported criteria.
The frontend draft model can be richer than the API model. For example, TQueryFilterGroupDraft carries editing-only
fields such as condition type, selected option keys, and typeahead/select/combo metadata. The apply boundary normalizes
draft groups into TQueryFilterGroup[] and drops incomplete clauses before calling an API or serializing URL state.
Filter semantics are intentionally bounded:
- Clauses within one filter group use that group’s
joinOperator. - Separate filter groups are implicitly ANDed together by backend composition.
- The first artists-page pass does not expose configurable joins between groups.
- Unsupported criteria, unsupported operations, and empty clauses should be ignored or sanitized at boundaries rather than partially corrupting the query.
Backend query composition should use shared mechanics and explicit entity-specific builders:
- Shared pagination helpers resolve
page,limit,offset, and response pagination metadata. - Shared filter composition joins groups and clauses.
- Shared sort composition appends stable fallback ordering.
- Entity-specific builders map criteria IDs to SQL clauses and order expressions.
Always append stable fallback sort clauses after user-requested sorts. This keeps paginated browsing deterministic when the active sort field has ties.
Browsing Page Shape
Section titled “Browsing Page Shape”Data-heavy index pages should use page-ready summary DTOs instead of returning full nested graphs.
The artists index is the reference pattern:
GET /artistssupports the page-browsing route.ArtistSummaryDTOincludes the row identity, display name, computed profile image URL, optional description, event count, and lightweight external links.- The response includes pagination metadata.
- Full nested event, venue, media, and relationship graphs stay out of the list payload.
- Profile media and links are hydrated only for the artists on the current page.
Keep a page-browsing endpoint distinct from a future flexible embedded query endpoint when the use cases are different.
For artists, GET /artists remains the index browsing route, while POST /artists/query is deferred until a concrete
embedded or secondary artist-querying consumer needs it.
Search is a primary page action, not just another side-panel filter. The artists page uses a larger typeahead/search surface for elevated search and applies submitted searches to the browsing state. Submitted search should:
- Reset pagination to page 1.
- Preserve active filters and sorts.
- Stay visible in the URL.
- Produce a combined empty state when search plus filters returns no rows.
- Keep direct result selection separate from the
view all matching resultsaction.
Use compact typeahead components for small form-field search and larger typeahead presentations for modal, page, or dedicated search contexts. Shared typeahead mechanics should live in headless controller hooks, while data fetching and DTO-to-row rendering stay in page/domain wrappers.
URL State And Saved Views
Section titled “URL State And Saved Views”Treat route URL state as reloadable, shareable, transient browsing state.
The current reusable URL-state helpers cover:
pagelimitfilterssorts
Page-specific helpers should add only page-owned state, such as artist view mode or submitted search query. Invalid pagination values should fall back to defaults. Unsupported sort and filter criteria should be dropped using endpoint capability metadata.
Saved views, also called page query presets, are the durable named layer above URL state. They should be typed by page and schema version.
For artists.index, the saved state currently includes:
- View mode.
- Page.
- Page size.
- Search query.
- Active filters.
- Active sorts.
Display-only preferences should stay out of saved views until the page exposes them as first-class controls. The artists page therefore excludes table column visibility/order and gallery density/card preferences for now.
Guest users persist saved views in local storage. Authenticated users persist them through the page query preset API. When local guest presets differ from account presets after login, the app can offer an advisory import path without deleting the device-local copies.
View Modes And Pagination
Section titled “View Modes And Pagination”When multiple views share the same query result set, keep query state page-owned rather than view-owned.
The artists page keeps table/gallery view mode, pagination, search, filters, and sorts in the page frame. Table and gallery render the same server-backed result set with different layouts. The page can switch views immediately while URL state catches up, and inactive views may stay mounted when preserving local layout or animation state matters.
Pagination should follow endpoint capabilities:
- Use endpoint
allowedLimitswhen present. - Fall back to shared default allowed limits filtered by max limit.
- Reset to page 1 when page size, search, or a dataset-changing query control changes.
- Keep pagination metadata in the API response so views do not infer totals from the current page length.
Tests And Proof
Section titled “Tests And Proof”For new queryable collection pages, prefer coverage at the contract boundaries:
- API contract tests when DTO constraints or preset migration rules are non-obvious.
- Backend route tests for pagination parsing, bad query params, response shape, and keyed errors.
- DB-backed tests for filter SQL, sort SQL, aggregate filters, stable fallback ordering, search behavior, and totals.
- Frontend API and hook tests for serialized query params and query keys.
- URL-state tests for parsing, sanitization, serialization, unsupported criteria, and invalid limits.
- Page integration tests for empty/loading/error states, view switching, search submission, saved-view behavior, and query-control state preservation.
Still Working Notes
Section titled “Still Working Notes”The following item is intentionally not treated as settled architecture yet:
- When
POST /artists/queryor similar flexible secondary query endpoints become necessary.