# Catalogue URL State Contract Mechanica catalogue state is now represented by a canonical, shareable query-string contract. The parser and serializer live in `src/utils/catalogueUrlState.ts`, with a React Router adapter in `src/hooks/useCatalogueUrlState.ts`. ## Canonical Parameters | State | Canonical key | Accepted aliases | Default | Notes | | --- | --- | --- | --- | --- | | Search query | `q` | `query`, `search` | empty | Whitespace is collapsed and length-limited to avoid oversized shared URLs. | | Category | `category` | `cat` | `all` | Pass registry category slugs as `validCategories` to reject stale or malformed links. | | Difficulty | `difficulty` | `diff` | `all` | Defaults include beginner, introductory, intermediate, advanced, and expert. | | Sort | `sort` | `order` | `featured` | Common aliases like `name`, `a-z`, `hardest`, and `latest` canonicalise to explicit sort keys. | | Favourites only | `fav` | `favorite`, `favourite`, `bookmarked`, etc. | `false` | Serialises to `fav=1` only when enabled. | | Layout mode | `view` | `layout` | `grid` | Supports `grid`, `list`, and `compact`. | | Page | `page` | `p` | `1` | Positive integers only; pages above `maxPage` are capped with a warning. | Default values are omitted, so `/catalogue` remains the canonical URL for the default catalogue view. ## Behaviour The URL helpers are intentionally defensive: - Unknown aliases are normalised into the canonical key order. - Malformed categories, difficulties, sort keys, booleans, view modes, and pages fall back to defaults instead of throwing. - Parse warnings are returned for telemetry, debug banners, or non-blocking QA assertions. - Known catalogue parameters can be stripped while preserving unrelated query parameters such as `utm_*` values. - Pagination can be reset automatically when search, filter, sort, or favourites-only state changes. ## React Router Usage ```tsx import { useMemo } from 'react'; import { machineRegistry } from '../data/machineRegistry'; import { useCatalogueUrlState } from '../hooks/useCatalogueUrlState'; export function CataloguePage() { const categories = useMemo( () => Array.from(new Set(machineRegistry.map((machine) => machine.category))), [], ); const catalogueUrl = useCatalogueUrlState({ validCategories: categories, preserveUnknownParams: true, resetPageOnFilterChange: true, }); return (
); } ``` ## Integration Checklist When wiring the hook into catalogue controls: 1. Derive `validCategories` and any custom `validDifficulties` from the registry. 2. Use `catalogueUrl.state` as the single source of truth for filter controls. 3. Call `catalogueUrl.setState` for each control update; page reset is automatic for filter-like changes. 4. Use `catalogueUrl.hrefForState({ ... })` for precomputed links such as category chips. 5. If `catalogueUrl.isCanonical` is false, the page can optionally replace history with `catalogueUrl.canonicalHref` after first render. 6. Surface `catalogueUrl.warnings` only in development or diagnostics; malformed shared URLs should not interrupt the user. ## Test Coverage `tests/catalogueUrlState.test.ts` covers default URLs, alias canonicalisation, malformed input, query preservation, pagination reset, and canonical href construction. These tests protect the share-link contract before it is connected to the visible catalogue controls.