Files
flights_web/docs/superpowers/plans/2026-04-15-phase-2b-url-serializer.md
T
gnezim 73d724f76a Add Phase 2B URL serializer implementation plan
TDD plan for porting Angular OnlineBoardUrlBuilder/Parser to pure
TypeScript functions covering all 6 URL types (start, flight, departure,
arrival, route, details) with roundtrip and edge case tests.
2026-04-15 08:00:12 +03:00

6.9 KiB

Phase 2B — URL Serializer/Parser

Parent: 2026-04-14-phase-2-online-board-master.md (sub-plan 2B) Status: Ready to execute

Goal

TDD port of the Angular OnlineBoardUrlBuilderService / OnlineBoardUrlParserService into pure TypeScript functions with zero Angular dependencies. Byte-exact URL parity with Angular.

Deliverable

src/features/online-board/url.ts — pure functions, no side effects, no Date objects.

URL Format Reference (from Angular analysis)

Type Pattern Example
Start /onlineboard or /onlineboard/ /onlineboard
Flight /onlineboard/flight/{carrier}{flightNumber}{suffix}-{yyyyMMdd} /onlineboard/flight/SU0100D-20250115
Departure /onlineboard/departure/{station}-{yyyyMMdd}[-{HHmmHHmm}] /onlineboard/departure/SVO-20250115-08001800
Arrival /onlineboard/arrival/{station}-{yyyyMMdd}[-{HHmmHHmm}] /onlineboard/arrival/LED-20250115
Route /onlineboard/route/{dep}-{arr}-{yyyyMMdd}[-{HHmmHHmm}] /onlineboard/route/SVO-LED-20250115
Details /onlineboard/{carrier}{flightNumber}{suffix}-{yyyyMMdd} /onlineboard/SU0100-20250115

Key rules

  • Date format: yyyyMMdd (8 digits, no separators)
  • Time range: {HHmm}{HHmm} (8 digits continuous, from-to)
  • Flight number: zero-padded to 4 digits in the URL (e.g., 100 becomes 0100), total flightNumber+suffix = last 5 chars
  • Carrier code: 2 characters (IATA), or 3 characters if 3rd char is a letter (rare 3-char carriers)
  • Suffix: optional single trailing letter on a flight identifier (e.g., D in SU0100D)
  • Station codes: 3-letter IATA airport codes

Exported API

type OnlineBoardParams =
  | { type: "start" }
  | { type: "flight"; carrier: string; flightNumber: string; suffix?: string; date: string }
  | { type: "departure"; station: string; date: string; timeFrom?: string; timeTo?: string }
  | { type: "arrival"; station: string; date: string; timeFrom?: string; timeTo?: string }
  | { type: "route"; departure: string; arrival: string; date: string; timeFrom?: string; timeTo?: string }
  | { type: "details"; carrier: string; flightNumber: string; suffix?: string; date: string };

function parseOnlineBoardUrl(path: string): OnlineBoardParams | null;
function buildOnlineBoardUrl(params: OnlineBoardParams): string;

// Lower-level helpers (also exported for 2C/2E consumption)
function parseFlightUrlParams(raw: string): IParsedFlightId | null;
function buildFlightUrlParams(id: IParsedFlightId): string;
function parseStationUrlParams(raw: string): { station: string; date: string; timeFrom?: string; timeTo?: string } | null;
function parseRouteUrlParams(raw: string): { departure: string; arrival: string; date: string; timeFrom?: string; timeTo?: string } | null;

Tasks

Task 1: Write failing tests for parseFlightUrlParams

File: src/features/online-board/url.test.ts

Test cases:

  • SU0100-20250115 -> { carrier: "SU", flightNumber: "0100", date: "20250115" }
  • SU0100D-20250115 -> { carrier: "SU", flightNumber: "0100", suffix: "D", date: "20250115" }
  • SU100-20250115 -> { carrier: "SU", flightNumber: "100", date: "20250115" } (3-digit)
  • SU1234-20250115 -> { carrier: "SU", flightNumber: "1234", date: "20250115" } (4-digit)
  • Empty string -> null
  • Missing date part -> null

Task 2: Implement parseFlightUrlParams to pass tests

Task 3: Write failing tests for buildFlightUrlParams

Test cases:

  • { carrier: "SU", flightNumber: "100", date: "20250115" } -> SU0100-20250115 (padded)
  • { carrier: "SU", flightNumber: "0100", suffix: "D", date: "20250115" } -> SU0100D-20250115
  • { carrier: "SU", flightNumber: "1234", date: "20250115" } -> SU1234-20250115

Task 4: Implement buildFlightUrlParams to pass tests

Task 5: Write failing tests for parseStationUrlParams

Test cases:

  • SVO-20250115 -> { station: "SVO", date: "20250115" }
  • SVO-20250115-08001800 -> { station: "SVO", date: "20250115", timeFrom: "0800", timeTo: "1800" }
  • Empty string -> null

Task 6: Implement parseStationUrlParams to pass tests

Task 7: Write failing tests for parseRouteUrlParams

Test cases:

  • SVO-LED-20250115 -> { departure: "SVO", arrival: "LED", date: "20250115" }
  • SVO-LED-20250115-08001800 -> { departure: "SVO", arrival: "LED", date: "20250115", timeFrom: "0800", timeTo: "1800" }
  • Empty string -> null

Task 8: Implement parseRouteUrlParams to pass tests

Task 9: Write failing tests for parseOnlineBoardUrl

Test cases:

  • /onlineboard -> { type: "start" }
  • /onlineboard/ -> { type: "start" }
  • /onlineboard/flight/SU0100-20250115 -> { type: "flight", carrier: "SU", flightNumber: "0100", date: "20250115" }
  • /onlineboard/departure/SVO-20250115 -> { type: "departure", station: "SVO", date: "20250115" }
  • /onlineboard/arrival/LED-20250115-08001800 -> { type: "arrival", station: "LED", date: "20250115", timeFrom: "0800", timeTo: "1800" }
  • /onlineboard/route/SVO-LED-20250115 -> { type: "route", departure: "SVO", arrival: "LED", date: "20250115" }
  • /onlineboard/SU0100-20250115 -> { type: "details", carrier: "SU", flightNumber: "0100", date: "20250115" }
  • /some/other/path -> null
  • Empty string -> null

Task 10: Implement parseOnlineBoardUrl to pass tests

Task 11: Write failing tests for buildOnlineBoardUrl

Test cases:

  • { type: "start" } -> onlineboard
  • { type: "flight", carrier: "SU", flightNumber: "100", date: "20250115" } -> onlineboard/flight/SU0100-20250115
  • { type: "departure", station: "SVO", date: "20250115" } -> onlineboard/departure/SVO-20250115
  • { type: "departure", station: "SVO", date: "20250115", timeFrom: "0800", timeTo: "1800" } -> onlineboard/departure/SVO-20250115-08001800
  • { type: "arrival", station: "LED", date: "20250115" } -> onlineboard/arrival/LED-20250115
  • { type: "route", departure: "SVO", arrival: "LED", date: "20250115" } -> onlineboard/route/SVO-LED-20250115
  • { type: "details", carrier: "SU", flightNumber: "0100", date: "20250115" } -> onlineboard/SU0100-20250115

Task 12: Implement buildOnlineBoardUrl to pass tests

Task 13: Roundtrip tests

For every URL type: buildOnlineBoardUrl(parseOnlineBoardUrl(url)) === url (after normalization).

Task 14: Edge case tests

  • Suffixed flights roundtrip: SU0100D-20250115
  • 3-digit flight numbers: build pads to 4, parse handles both
  • Time range only partially specified (only timeFrom, no timeTo) -> no time range in URL
  • Invalid date format -> null
  • Unknown route prefix -> null

Task 15: Export from barrel

Update src/features/online-board/index.ts to re-export the public API.

Task 16: Verification

Run pnpm typecheck && pnpm lint && pnpm test.

Exit criteria

  • All tests pass
  • parseOnlineBoardUrl returns null for invalid inputs, never throws
  • Zero any types
  • Pure functions only — no side effects, no imports from Angular
  • Dates are strings (yyyyMMdd), not Date objects