add relations to stop page #115

Closed
opened 2026-05-11 01:03:12 +00:00 by maxtkc · 0 comments
Owner

Summary

The stop page currently shows which routes and agencies serve a stop but doesn't link to any timetables. This feature adds a "Timetables" section to the stop page — one row per unique (route_id, service_id) pair — using the same renderServiceReference card rows the route page uses. No direction button is shown; clicking a row navigates to the timetable. The two redundant stop_times queries that exist today are collapsed into one shared pass. The meaningless "Transit Network" heading is removed.

Relevant Context

  • src/modules/stop-view-controller.ts — the entire stop page. renderStopView calls getAgenciesServingStop and getRoutesServingStop independently, each doing a full stop_times + trips scan. renderTransitNetwork renders the "Transit Network" heading + agency-grouped route cards.
  • src/utils/entity-references.ts — contains renderServiceReference (used by route page) and renderRouteReference (used by service page). ServiceReferenceOpts accepts route_id, tripCount, calendarDates. When route_id is set, the row gets data-route-id and navigates to timetable. SERVICE_REF_ROW CSS class keys the existing click handler.
  • src/modules/page-content-renderer.ts line ~711 — the SERVICE_REF_ROW click handler: if both data-route-id and data-service-id are present, calls onTimetableClick(route_id, service_id). This handler already runs for the entire container, so stop-page service rows get timetable navigation for free without any new handler code.
  • src/modules/page-content-renderer.ts line ~183 — constructs StopViewDependencies. Currently does NOT pass onTimetableClick, even though ContentRendererDependencies already has it.
  • Timetable page state: { type: 'timetable', route_id: string, service_id: string, direction_id?: string }. Navigation without direction_id is valid — the timetable page shows all directions.
  • src/utils/entity-display.tsgetServiceDisplay(record) returns { primary: service_id }. formatDaysOfWeek and formatDateRange are used in renderServiceReference to show human-readable service context.
  • Stop → timetable join path: stop_times.stop_id → trips.trip_id → (route_id, service_id). Deduplicate by ${route_id}||${service_id}.

Phase 1 — Consolidate data fetching in StopViewController

getAgenciesServingStop and getRoutesServingStop both independently query stop_times and trips. This phase merges them.

  • Add a local interface at the top of stop-view-controller.ts:
    interface TimetableKey { route_id: string; service_id: string; }
    interface StopRelations {
      agencies: Agency[];
      routes: Routes[];
      timetableKeys: TimetableKey[];
    }
    
  • Add a private fetchStopRelations(stop_id: string): Promise<StopRelations> method:
    1. Query stop_times filtered by stop_id once → collect unique trip_ids.
    2. Query trips (full table) once → filter to those trip_ids → collect unique route_ids and unique (route_id, service_id) pairs (deduplicate with a Set keyed on ${route_id}||${service_id}; ignore direction_id).
    3. Query routes once → filter to relevant route_ids → collect agency_ids.
    4. Query agency once → filter to relevant agency_ids.
    5. Return { agencies, routes, timetableKeys }.
  • In renderStopView, replace the two Promise.all([getAgenciesServingStop, getRoutesServingStop]) calls with a single fetchStopRelations call.
  • Delete the now-unused getAgenciesServingStop and getRoutesServingStop methods.

Gotcha: queryRows('trips') with no filter is the existing pattern — keep it. Virtual table copy-on-read means results are safe to mutate.


Phase 2 — Add timetable section and wire navigation

  • In StopViewDependencies, add onTimetableClick: (route_id: string, service_id: string) => void. No direction_id parameter (matching the "no direction button" requirement).
  • In page-content-renderer.ts at the StopViewController constructor call (~line 183), add onTimetableClick: (route_id, service_id) => dependencies.onTimetableClick(route_id, service_id) to the dependencies object.
  • In stop-view-controller.ts, add renderTimetablesSection as a private method mirroring ServiceViewController.renderTimetablesSection:
    • Accepts timetableKeys: TimetableKey[], routes: Routes[], and calendarByServiceId: Map<string, Record<string, unknown>>.
    • For each timetable key, look up the route object and the calendar record; call renderServiceReference(calendarOrFallback, { route_id, tripCount: undefined }). Omit tripCount unless it's easily available from the Phase 1 data (it's not without an extra scan — leave it out for now).
    • Renders the same card-in-card structure: <div class="space-y-4"><h2>Timetables</h2><div class="card ..."><div class="card-body p-4"><div class="space-y-2">…rows…</div></div></div></div>.
    • Empty state: "This stop is not included in any timetables."
  • In renderStopView, fetch calendar data for the relevant service_ids: queryRows('calendar') filtered (or post-filtered) to the service_ids in timetableKeys. Build calendarByServiceId: Map<string, Record<string, unknown>> from this. Also fetched calendar_dates for exception-only services.
  • Call renderTimetablesSection from renderStopView and include its output in the returned HTML, after the stop properties and in place of renderTransitNetwork.
  • Import renderServiceReference and SERVICE_REF_ROW from entity-references.ts in stop-view-controller.ts.
  • Remove renderTransitNetwork, renderRouteCard, and the "Transit Network" heading entirely. Also remove the now-unused onAgencyClick, onRouteClick, agency buttons, and route-card-mini event listeners from addEventListeners.

Navigation works via existing SERVICE_REF_ROW delegation in page-content-renderer.ts (~line 711) — no new click handler needed in StopViewController. calendar_dates was also fetched so exception-only services show a date range.

Gotcha: renderServiceReference uses SERVICE_REF_ROW which is already handled by the page-content-renderer event listener at line 711. No new click handler is needed in StopViewController.addEventListeners for the timetable rows — the existing delegation covers it. Only the delete button needs its own handler.

Gotcha: After removing onAgencyClick and onRouteClick from StopViewDependencies, remove them from the dependency object in page-content-renderer.ts constructor to avoid a TypeScript error (or keep them if they're still used elsewhere on the stop page for some reason).

Gotcha: calendarDates (exceptions) is an optional param to renderServiceReference — fetch calendar_dates rows as well if you want the date range to show for exception-only services. Can do in Phase 2 or defer.


Original Issue

As a continuation of #93, lets make sure that stops have the relevant references to other objects. I want to see all of the timetables that include a stop, ideally, if this isn't too complicated/bad performance.

## Summary The stop page currently shows which routes and agencies serve a stop but doesn't link to any timetables. This feature adds a "Timetables" section to the stop page — one row per unique `(route_id, service_id)` pair — using the same `renderServiceReference` card rows the route page uses. No direction button is shown; clicking a row navigates to the timetable. The two redundant `stop_times` queries that exist today are collapsed into one shared pass. The meaningless "Transit Network" heading is removed. ## Relevant Context - **`src/modules/stop-view-controller.ts`** — the entire stop page. `renderStopView` calls `getAgenciesServingStop` and `getRoutesServingStop` independently, each doing a full `stop_times` + `trips` scan. `renderTransitNetwork` renders the "Transit Network" heading + agency-grouped route cards. - **`src/utils/entity-references.ts`** — contains `renderServiceReference` (used by route page) and `renderRouteReference` (used by service page). `ServiceReferenceOpts` accepts `route_id`, `tripCount`, `calendarDates`. When `route_id` is set, the row gets `data-route-id` and navigates to timetable. `SERVICE_REF_ROW` CSS class keys the existing click handler. - **`src/modules/page-content-renderer.ts` line ~711** — the `SERVICE_REF_ROW` click handler: if both `data-route-id` and `data-service-id` are present, calls `onTimetableClick(route_id, service_id)`. This handler already runs for the entire container, so stop-page service rows get timetable navigation for free without any new handler code. - **`src/modules/page-content-renderer.ts` line ~183** — constructs `StopViewDependencies`. Currently does NOT pass `onTimetableClick`, even though `ContentRendererDependencies` already has it. - **Timetable page state**: `{ type: 'timetable', route_id: string, service_id: string, direction_id?: string }`. Navigation without `direction_id` is valid — the timetable page shows all directions. - **`src/utils/entity-display.ts`** — `getServiceDisplay(record)` returns `{ primary: service_id }`. `formatDaysOfWeek` and `formatDateRange` are used in `renderServiceReference` to show human-readable service context. - Stop → timetable join path: `stop_times.stop_id → trips.trip_id → (route_id, service_id)`. Deduplicate by `${route_id}||${service_id}`. --- ## Phase 1 — Consolidate data fetching in `StopViewController` `getAgenciesServingStop` and `getRoutesServingStop` both independently query `stop_times` and `trips`. This phase merges them. - [x] Add a local interface at the top of `stop-view-controller.ts`: ```typescript interface TimetableKey { route_id: string; service_id: string; } interface StopRelations { agencies: Agency[]; routes: Routes[]; timetableKeys: TimetableKey[]; } ``` - [x] Add a private `fetchStopRelations(stop_id: string): Promise<StopRelations>` method: 1. Query `stop_times` filtered by `stop_id` once → collect unique `trip_id`s. 2. Query `trips` (full table) once → filter to those `trip_id`s → collect unique `route_id`s and unique `(route_id, service_id)` pairs (deduplicate with a `Set` keyed on `${route_id}||${service_id}`; ignore `direction_id`). 3. Query `routes` once → filter to relevant `route_id`s → collect `agency_id`s. 4. Query `agency` once → filter to relevant `agency_id`s. 5. Return `{ agencies, routes, timetableKeys }`. - [x] In `renderStopView`, replace the two `Promise.all([getAgenciesServingStop, getRoutesServingStop])` calls with a single `fetchStopRelations` call. - [x] Delete the now-unused `getAgenciesServingStop` and `getRoutesServingStop` methods. **Gotcha:** `queryRows('trips')` with no filter is the existing pattern — keep it. Virtual table copy-on-read means results are safe to mutate. --- ## Phase 2 — Add timetable section and wire navigation - [x] In `StopViewDependencies`, add `onTimetableClick: (route_id: string, service_id: string) => void`. No `direction_id` parameter (matching the "no direction button" requirement). - [x] In `page-content-renderer.ts` at the `StopViewController` constructor call (~line 183), add `onTimetableClick: (route_id, service_id) => dependencies.onTimetableClick(route_id, service_id)` to the dependencies object. - [x] In `stop-view-controller.ts`, add `renderTimetablesSection` as a private method mirroring `ServiceViewController.renderTimetablesSection`: - Accepts `timetableKeys: TimetableKey[]`, `routes: Routes[]`, and `calendarByServiceId: Map<string, Record<string, unknown>>`. - For each timetable key, look up the route object and the calendar record; call `renderServiceReference(calendarOrFallback, { route_id, tripCount: undefined })`. Omit `tripCount` unless it's easily available from the Phase 1 data (it's not without an extra scan — leave it out for now). - Renders the same card-in-card structure: `<div class="space-y-4"><h2>Timetables</h2><div class="card ..."><div class="card-body p-4"><div class="space-y-2">…rows…</div></div></div></div>`. - Empty state: "This stop is not included in any timetables." - [x] In `renderStopView`, fetch calendar data for the relevant `service_id`s: `queryRows('calendar')` filtered (or post-filtered) to the service_ids in `timetableKeys`. Build `calendarByServiceId: Map<string, Record<string, unknown>>` from this. Also fetched `calendar_dates` for exception-only services. - [x] Call `renderTimetablesSection` from `renderStopView` and include its output in the returned HTML, after the stop properties and in place of `renderTransitNetwork`. - [x] Import `renderServiceReference` and `SERVICE_REF_ROW` from `entity-references.ts` in `stop-view-controller.ts`. - [x] Remove `renderTransitNetwork`, `renderRouteCard`, and the "Transit Network" heading entirely. Also remove the now-unused `onAgencyClick`, `onRouteClick`, agency buttons, and route-card-mini event listeners from `addEventListeners`. Navigation works via existing `SERVICE_REF_ROW` delegation in `page-content-renderer.ts` (~line 711) — no new click handler needed in `StopViewController`. `calendar_dates` was also fetched so exception-only services show a date range. **Gotcha:** `renderServiceReference` uses `SERVICE_REF_ROW` which is already handled by the `page-content-renderer` event listener at line 711. No new click handler is needed in `StopViewController.addEventListeners` for the timetable rows — the existing delegation covers it. Only the delete button needs its own handler. **Gotcha:** After removing `onAgencyClick` and `onRouteClick` from `StopViewDependencies`, remove them from the dependency object in `page-content-renderer.ts` constructor to avoid a TypeScript error (or keep them if they're still used elsewhere on the stop page for some reason). **Gotcha:** `calendarDates` (exceptions) is an optional param to `renderServiceReference` — fetch calendar_dates rows as well if you want the date range to show for exception-only services. Can do in Phase 2 or defer. --- ## Original Issue > As a continuation of #93, lets make sure that stops have the relevant references to other objects. I want to see all of the timetables that include a stop, ideally, if this isn't too complicated/bad performance.
maxtkc self-assigned this 2026-05-11 01:03:12 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
gtfs.zone/coloring-book#115
No description provided.