Skip to content

update route selection screen #505

Open

Description

@MaximumTrainer

Update the route selection screen

The route selection screen on the next/svelte-webgpu rebuild branch (epic #542) is the SvelteKit Routes route at src/routes/search/+page.svelte (App.tsx's old currentView === 'route-search' view). Its catalogue state lives in the shared RoutesStore (src/lib/ui/routesStore.svelte.ts, reached through src/lib/ui/routesContext.ts); services are provided via Svelte context (getServices() / getRoutesStore() / getAuth()), never via a concrete service class. The React App.tsx + three.js app this screen once lived in is being deleted (PR #587), so there is no React path left to keep behind a flag.

Status on the branch: the map-primary redesign described below is already substantially built in Svelte. src/routes/search/+page.svelte already renders a RouteOverviewMap (src/lib/ui/RouteOverviewMap.svelte) above a slide-in drawer (data-drawer-open, routes-drawer-toggle), a RouteDetailOverlay (src/lib/ui/RouteDetailOverlay.svelte), the difficulty filter, the rownative import (RownativeRouteImport.svelte), the file import, per-card RouteThumbnail previews (src/lib/ui/RouteThumbnail.svelte, with showBackdrop), and the default-route badge/toggle, with dimmedRouteIds wired from the store. This issue is therefore a verification + refinement pass over that screen rather than a build-from-scratch: confirm every FR/AC holds on the Svelte screen, close any gaps, and lock the DOM contracts the Routes Playwright specs rely on.

The attached Biketerra screenshots show the pattern this issue targets: a world map with route pins at approximate geographic locations, a card list that cross-references the pins, and a detail view that shows each route's outline against a basemap (and labels its region in context).

This issue upgrades the Routes screen (src/routes/search/+page.svelte + its src/lib/ui/Route* components) to that shape, reusing #493's OSM enrichment data where available (water/island/coastline geometry from src/lib/services/routeEnrichmentService.ts) and leaving the Row screen (src/routes/row/+page.svelte) alone.


Functional requirements

  • FR1. The Routes screen renders a MapLibre GL overview map (map-primary layout) via RouteOverviewMap.svelte, with the route list in a slide-in drawer (.route-search-drawer, toggled by routes.routesDrawerOpen). The map shows one pin per route in the current catalogue (routes.routes), positioned at the route's approximate geographic location.
  • FR2. Hovering a pin highlights the matching list row; clicking a pin opens the route's detail panel (RouteDetailOverlay.svelte, driven by routes.routeDetailOpen) — route selection happens here via its onStartRow.
  • FR3. Hovering a list row highlights the matching pin on the map.
  • FR4. The map clusters co-located pins; clicking a cluster zooms in until its children separate (world-view clustering, D4), inside RouteOverviewMap.svelte.
  • FR5. Each route card in the drawer shows a route-outline preview with geographic context — RouteThumbnail.svelte with showBackdrop draws the polyline over OSM raster tiles per card (D3), falling back to the bare-polyline SVG when offline or tiles fail to load.
  • FR6. The map and the list respond to the existing difficulty filter (routes.difficultyFilter): filtered-out rows are hidden (routes.filteredRoutes); filtered-out pins are dimmed on the map via routes.dimmedRouteIds (D7).
  • FR7. Clicking a pin or a drawer row opens the detail overlay (RouteDetailOverlay.svelte, D8) with: name, location, distance, estimated time, difficulty, the geometry-provenance badge (geometryProvenanceBadge from src/lib/utils/geometryProvenance.ts), a large route outline drawn over route.enrichment OSM water/land geometry (D9), and a "Row this route" action that selects the route and navigates to the Row screen (routes.selectRoute(route) then goto(base + '/')).
  • FR8. The user's default route (routes.defaultRouteId) is marked on both the map pin and the drawer row (.route-default-badge), and the map auto-centres on it when the screen first opens (D10).
  • FR9. OpenStreetMap attribution ("© OpenStreetMap contributors") is visible on the overview map and on any card/detail outline that shows OSM-sourced tiles or geometry (D13).
  • FR10. The rownative-import (RownativeRouteImport.svelte) and file-import controls stay on the screen, inside the drawer, unchanged in behaviour (routes.importFile, routes.routeImported).
  • FR11. On viewports < 600 px, the map stays above the drawer at a reduced fixed height; the drawer is always open and inline, no slide-out (D11).

Non-functional requirements

  • NFR1. OSM raster tiles are fetched from a configurable tile URL (default: standard OSM tile server) and cached in the browser's HTTP cache + a MapLibre tile source cache. First open fetches tiles; subsequent opens within the same session reuse them. An offline / tile-fetch-failure path renders the bare SVG polyline fallback on both the overview (pins over a neutral background) and the cards.
  • NFR2. Visual-baseline coverage: the map region of the Routes screen is excluded from test:visual (MapLibre + WebGL is not bit-deterministic under SwiftShader; the WebGPU ride canvas is excluded for the same Cascaded Shadow Maps for the sun #495 reason). The surrounding chrome (drawer, filters, attribution, modal) is visual-baselined via assertCanvasNotBlank.ts-style DOM checks where a frozen frame is used. DOM-level Playwright assertions cover the map region (pin count, dimmed state, cluster count).
  • NFR3. New screen bundle weight ≤ 120 KB gzipped over the pre-redesign Routes screen, with MapLibre loaded via a dynamic import() inside RouteOverviewMap.svelte so the Row screen and signed-out catalogue are unaffected. Measured by scripts/measure-bundle.mjs.
  • NFR4. Interaction latency ≤ 100 ms from pin click to detail-overlay open on a mid-tier device, measured in a routes-screen Playwright perf spec.
  • NFR5. npm run verify stays green: lint, tsc, Vitest unit tests, Playwright E2E.
  • NFR6. Coverage ratchets up for the new/changed files (per CLAUDE.md), with per-file thresholds added to vitest.config.ts.
  • NFR7. Because the React route-search markup has already been replaced by the Svelte screen on the branch, no feature flag is needed (D14). Work ships directly against src/routes/search/+page.svelte; test:visual baselines for the chrome are recorded via the visual-baseline label flow (Linux/SwiftShader, never a local GPU).
  • NFR8. Clustering uses supercluster (~5 KB gzipped) or an equivalent small library inside RouteOverviewMap.svelte; it must be covered by unit tests over cluster/decluster transitions as pins are filtered.

Acceptance criteria

  • AC1. On the Routes screen, the overview map renders map-primary with a slide-in drawer; one data-testid="route-pin" is rendered per visible route (or data-testid="route-cluster" for a cluster). (route-overview-section is already present.)
  • AC2. Clicking a pin opens the detail overlay and dispatches the same selection path as the matching drawer row (both call routes.selectRoute); asserted by a unit test on the store + a component test.
  • AC3. Setting the difficulty filter to "hard" hides every non-hard drawer row (routes.filteredRoutes) and dims every non-hard pin (via routes.dimmedRouteIds → a data-dimmed="true" attribute) in the same render.
  • AC4. Opening the Routes screen with a defaultRouteId set centres the map on that route's centroid, zooms to a sensible level, and marks its pin + its drawer row with data-default="true" (the .route-default-badge is already rendered).
  • AC5. When tile fetch fails (Playwright blocks the tile host), each card falls back to the RouteThumbnail SVG polyline and the overview map renders pins over a neutral background with no uncaught errors (regression test).
  • AC6. With route.enrichment present, the RouteDetailOverlay outline renders at least one OSM water polygon under the route polyline (component test on RouteDetailOverlay.svelte).
  • AC7. The "© OpenStreetMap contributors" attribution is present in the DOM on the overview map and in the detail overlay whenever OSM-sourced geometry or tiles are shown (unit/DOM grep).
  • AC8. npm run test:visual records baselines for the drawer, filters, attribution and detail chrome via the visual-baseline label flow; the map tiles region is explicitly masked out in the baseline comparator.
  • AC9. (Replaces the former "flag off renders exactly as today's React path".) With the React path removed post-cutover, the SvelteKit Routes route renders the map-primary screen as its only state; a Playwright snapshot of src/routes/search/+page.svelte's chrome stays green and the Row screen (src/routes/row/+page.svelte) is unaffected. There is no legacy state to fall back to.
  • AC10. A Playwright E2E spec clusters two routes at identical centroids and verifies clicking the cluster zooms in until the children separate.
  • AC11. npm run verify passes end-to-end.

Open decisions — LOCKED

# Decision Choice
D1 Basemap source b — OSM raster tiles via MapLibre
D2 Pin location b — centroid of all coordinates
D3 Card thumbnail b — OSM raster tiles behind polyline per card (RouteThumbnail showBackdrop)
D4 Map viewport b — clustered world view, zoom-on-click
D5 Pin interaction c — click opens detail overlay
D6 Layout c — map primary, list in a slide-in drawer
D7 Filter coupling c — list reduced, pins dimmed (dimmedRouteIds)
D8 Detail view c — overlay with "Row" button (RouteDetailOverlay.svelte)
D9 Detail backdrop b — polyline over #493 OSM geometry
D10 Default route c — badge + auto-centre on mount
D11 Narrow viewport a — map above drawer, shorter fixed height
D12 Map testability b — MapLibre + WebGL; map region excluded from visual baselines (DOM tests only)
D13 OSM attribution a — on overview map and detail backdrop
D14 Rollout Superseded — the React route-search markup is already replaced by the Svelte screen on next/svelte-webgpu; no flag, no follow-up removal step. Work ships directly against src/routes/search/+page.svelte.

Activity

  1. added 16 commits that reference this issue on Oct 18, 2026
  2. changed the title [-]update route selection screen - placeholder[/-] [+]update route selection screen[/+] on Oct 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions