Skip to content

feat(web): show live departures per platform in stop search results#22

Merged
i11v merged 3 commits into
mainfrom
claude/stop-search-live-data-o5gjxa
Jun 21, 2026
Merged

feat(web): show live departures per platform in stop search results#22
i11v merged 3 commits into
mainfrom
claude/stop-search-live-data-o5gjxa

Conversation

@i11v

@i11v i11v commented Jun 21, 2026

Copy link
Copy Markdown
Owner

What & why

Multi-platform stops (e.g. Anděl) are the hard case in search: a result card names the platforms but never says which one to stand on. This adds live public-transport data to search results so the choice is informed — each multi-platform result lists its platforms inline, each showing the next vehicle leaving from it (route, headsign, live countdown), so you can pick the right platform before adding it.

This carries over just the live-data idea from #16's spatial picker, without any geo: no map, no Leaflet dependency, no per-platform coordinates, and no change to the StopPlatform contract.

How it's built

StopPlatform { code, stop }  +  live board (DepartureVM[])
   │  lib/platforms.ts — buildPlatformPicks
   │    join by raw platform === code (per-line metro codes "1"/"2" match)
   ▼
PlatformPick { code, stop, lead?, kind? }   (index order preserved)
   ▼
SearchView — subscribes the whole-node board of every visible
             multi-platform result (capped at MAX_SELECTORS),
             builds a node → (departures | "loading") map
   ▼
search.tsx ResultCard / PlatformRow — inline live list per platform
  • lib/platforms.ts (new): pure buildPlatformPicks joins index platforms with an optional live board by raw platform === code (NOT platformKey, which collapses metro to one key and wouldn't match the index's per-line metro codes). Index order (by code) is preserved. No geo: search has no per-platform walk time, so countdowns render neutral-tier with no reachability coloring.
  • search.tsx: SearchView computes the visible result entries, then subscribes via useDepartures to the whole-node board of every multi-platform result, capped at MAX_SELECTORS (20) — the board route is unmounted on /search, so the whole budget is free. It threads a node → (DepartureVM[] | "loading") map down. ResultCard renders each platform's live PlatformRow (vehicle icon, route chip, headsign, countdown, add toggle) inline — no tap required.
  • PlatformRow states: a pulsing skeleton while the subscribed board is in flight; the live departure once it arrives; a quiet "no departures" only after arrival when a platform has nothing upcoming; and just the code + Add for results past the subscription cap (un-fetched).

Behaviour notes

  • Single-platform stops: no platform list, unchanged.
  • The platform set comes from the index, so it's always complete regardless of the live board.
  • The board route is unmounted while /search is open, so the preview subscriptions are the only live socket.

Verification

  • bun run typecheck — clean (the remaining TS11 messages are pre-existing Effect-LS info diagnostics in the untouched worker package).
  • bun run test — 82 passing, incl. a new platforms.test.ts (index-order listing, soonest-departure join, metro raw-code join).
  • bun run lint / bun run fmt:check — clean.
  • bun run build:web — succeeds; no Leaflet chunk (no map dependency added).

🤖 Generated with Claude Code

https://claude.ai/code/session_017UFBVqE6WdTKpVybhkQvST

Multi-platform stops (e.g. Anděl) are the hard case in search: the result
card named the platforms but never said which one to stand on. Each
multi-platform result now has a disclosure toggle that expands an inline,
per-platform list showing the next vehicle leaving from each platform —
route, headsign, and a live countdown — so the user can pick the right
platform before adding it.

- lib/platforms.ts: buildPlatformPicks joins index platforms with an
  optional live board by raw platform code (so per-line metro codes match),
  keeping index order. No geo — search has no per-platform walk time, so
  countdowns render neutral-tier with no reachability coloring.
- search.tsx: SearchView owns an accordion expandedNode and subscribes to
  that one node's whole-node board on demand via useDepartures (one result
  open at a time → a single extra subscription); ResultCard renders the
  live PlatformRow list, replacing the old static "nást. A" list.
- icons.tsx: ChevronIcon disclosure glyph.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017UFBVqE6WdTKpVybhkQvST
@github-actions

github-actions Bot commented Jun 21, 2026

Copy link
Copy Markdown

🚀 Preview deployed: https://preview-22.tablo.run
fallback: https://tablo-pr-22.i11v.workers.dev

claude added 2 commits June 21, 2026 09:26
The disclosure toggle was redundant — search results already listed each
platform inline before, so hiding them behind a tap was a regression.

Show the per-platform live departures inline for every multi-platform
result instead. SearchView now subscribes to the whole-node board of each
visible multi-platform result (capped at MAX_SELECTORS; the board route is
unmounted on /search, so the full budget is free) and threads a
node→departures map down. Results past the cap, or before their board
arrives, render the platform list without countdowns.

Removes the accordion expandedNode state and the ChevronIcon.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017UFBVqE6WdTKpVybhkQvST
…oads

departuresByNode.get() returned undefined both while a subscribed board
was in flight and for nodes past the cap, so loading platforms fell
through to the "no departures" empty state. Encode the three states
explicitly ("loading" | departures | absent): rows render a pulsing
skeleton while loading, "no departures" only once the board has arrived,
and nothing extra for un-subscribed nodes past the cap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017UFBVqE6WdTKpVybhkQvST
@i11v i11v marked this pull request as ready for review June 21, 2026 09:46
@i11v i11v merged commit c90a6f7 into main Jun 21, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants