Skip to content

Replay archived tracks on the map - #697

Open
Volyarosh94 wants to merge 2 commits into
feat/archive-tracks-apifrom
feat/archive-map-replay
Open

Volyarosh94 wants to merge 2 commits into
feat/archive-tracks-apifrom
feat/archive-map-replay

Conversation

@Volyarosh94

@Volyarosh94 Volyarosh94 commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Based on #696 (archived tracks API), so merge that first.

What

  • A #t=<ISO> link (with span, 15 minutes by default) opens /map in archive mode, and dropping t returns to live. The pill reads LIVE, PAUSED or ARCHIVE, and the ⋯ menu gets "Open archive", which opens an hour back.
  • The archive bar has:
    • a UTC date and time field, with the local time beside it;
    • play/pause, ±1 s steps and 1×/5×/20× rates;
    • a scrubber over the loaded window;
    • an availability strip on the same @edsc/timeline the Data Explorer uses;
    • Jump to now.
  • pages/map/replay/ fetches the window around t and groups rows into tracks: by track key, then ADS-B hex, then for older files by same-lane linking within 60 s and 3 km. Each tick it builds a frame in the live feed's shape and passes it through the live feed hook, so icons, trails, the panel and shortcuts are the live ones. Replay does no dead reckoning.
  • The detail panel shows when the solve was made, in UTC and local time.
  • On phones (600 px and below) the aircraft list and the detail panel are sheets over the map instead of side columns. The map and the bar get the full width, and the panel's close button stays on screen. This applies to the live map too; wider layouts are unchanged.

Calls I made

  • I took the spec's defaults. The map always asks for fleet=real, and /sim gets no archive mode. Rows with private nodes never reach the client, because the API drops them. Times are UTC in the link and the field, with local time beside them. How far back the bar reaches follows the store, which is one year once phase 4 lands.
  • A row appears once it was published, and expires 30 s (dark and follow) or 60 s (ADS-B keyed) after its frame time, as on the live feed.
  • The archive ends two minutes before now, so the newest flush is on disk. Play is disabled at that edge and points to Jump to now. A link past the edge is rewritten to the instant shown, with a notice.
  • A truncated window stops at its last row, then halves the span down to 2 minutes before it shows an error.
  • A 429 gets its own message, and a reply the map can't read says so instead of "Couldn't reach the archive."
  • On older days without track keys, minted keys carry over between windows, so a selection survives playback.
  • The panel shows the pseudonymous hex, as live does. Whether a solve carried an ADS-B identity is in its Source row.
  • Node markers show today's nodes, not the fleet at t.
  • The replay test fixtures are cuts of real /api/data/tracks responses from a seeded local stack.

Tested

just check green, replay tests on real response cuts fail without their code, and checked in Chrome signed out at 1440, 390 and 360 px: deep link with trails, panel, rates, steps, scrubber, availability strip, Jump to now, every request fleet=real.

@claude

This comment has been minimized.

A #t= link opens the map in archive mode and replays the tracks window around that instant through the live feed hook.
Carry linked track keys whatever the claim order, read archive errors
through describeFailure, and keep unchanged replay trails between ticks.
@claude

claude Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Claude finished @Volyarosh94's task in 4m 8s —— View job


Code review

  • Gather context (diff vs origin/feat/archive-tracks-api, prior review, CLAUDE.md conventions)
  • Understand the request (re-review this PR after latest push)
  • Check whether the previously-reported group.ts carryKeys bug was addressed
  • Review the rest of the new/changed commits since the prior review
  • Synthesize findings and post review

The base branch (origin/feat/archive-tracks-api) already carries the backend archive work, so this PR's diff is just the two map-replay commits: ea2b1eb (the original PR) and 99dc9b5 ("Tidy the map replay after review"), the latter responding directly to the earlier review on this thread. I re-traced the fix for the confirmed bug and checked the other findings against this latest commit.

The carryKeys bug is fixed

group.ts:106-190 replaces the single-pass held Set with a proper claim-graph walk (KeyClaim nodes, UNDECIDED/ON_WALK/GRANTED/REFUSED), resolving chains and cycles of claims regardless of processing order:

  • Each linked track contributes at most one claim (its earliest row matching a previous window's minted key).
  • For each undecided claim, it walks claimant → current holder → the holder's own claim, marking nodes ON_WALK, until it hits: a free/self-held key (→ GRANTED), a non-claiming holder such as a server-keyed track (→ REFUSED), an already-decided node (→ inherits that verdict), or back to the start (→ a cycle, GRANTED as a trade).
  • A second pass applies the verdict to every node walked.

I traced the self-hold, chain, and cycle termination cases by hand (including the second loop's claim.holder && claimOf.get(claim.holder) walk back through already-resolved nodes) and didn't find a non-terminating or incorrect path. group.test.ts now adds explicit coverage for exactly the scenario the original report raised: claim order independence (%s-parameterized "the track leaving the key starts first" / "taking the key starts first"), chains of three in all six orderings, a 2-cycle swap, a 3-cycle rotation, and refusal chains ending at a server-keyed or non-claiming holder. This is good, targeted regression coverage for a subtle graph problem.

Everything else from the prior review was also addressed in this commit

  • Reuse: useArchiveReplay.ts and useTrackAvailability.ts now both route through utils/requestFailure.ts's describeFailure instead of duplicating isAbort/status-based classification; archiveErrorOf and availabilityErrorOf both become | null for the abort case and callers were updated accordingly. synthesize.ts and archiveDisplay.ts now import KNOTS_PER_MS/M_PER_FT/MS_PER_KNOT from units.ts instead of redeclaring them.
  • Simplification: ArchiveBar.tsx/AvailabilityStrip.tsx import ArchiveError/LoadStatus/ArchiveClamp/ReplayRate/TrackAvailability from their source hooks instead of redeclaring; errorText()'s switch is now checked exhaustively against the real FailureKind union (const unhandled: never = kind), so a future kind won't silently fall through. useStoreSession.ts extracts the shared external-store session lifecycle, now used by both useArchiveReplay and useTrackAvailability. Toolbar.tsx's two near-duplicate pause/resume buttons are merged into one.
  • Accessibility: AircraftListPanel.tsx's collapsed title is now hidden via display: none on desktop (CSS-verified by the new AircraftListPanel.css.test.ts, which parses the actual stylesheet rule), addressing the a11y-tree concern, and shown via display: flex only inside the phone media query.
  • Efficiency: both synthesize.ts (trailRow/trails cache keyed by track+row) and LiveAircraftMap.tsx (archivedTrailSources WeakMap keyed by the buffer, checked against trail+sigma) now skip rebuilding a track's trail buffer when its underlying row hasn't changed since the last tick. hooks.ts got the equivalent treatment for the live-feed merge path (trailMergedFrom), which wasn't even called out before but is the same class of fix.
  • Foot-gun noted (not a bug): ingestAircraft's two trailing positional booleans are now a { nowMs, fromSource } options object, closing the latent mis-ordering risk flagged in the architecture notes.

Not addressed (both were explicitly low-severity/architectural, not bugs, so leaving them is reasonable):

  • AvailabilityStrip.tsx still duplicates the @edsc/timeline lazy-loader/error-boundary class from dataExplorer/AvailabilityTimeline.tsx rather than sharing one implementation.
  • useArchiveReplay.ts's target field is still carried as a field rather than computed at its one call site.
  • useUrlHashState.ts's two hash writers still coordinate by manual key ownership rather than a single write path.

New issues in this commit

None found. I didn't spot anything the tidying introduced — the new carryKeys graph walk, the WeakMap-based dirty-checks, and the describeFailure plumbing all look correct on manual trace, and each is backed by new or updated tests (group.test.ts, useArchiveReplay.test.tsx, useTrackAvailability.test.tsx, AircraftListPanel.css.test.ts).

Note: I could not run npm install/vitest/just check in this sandbox (dependency install required approval I don't have here), so the above is based on manual code tracing against the existing test files rather than a green test run. Worth confirming CI is green on this push.

@Volyarosh94
Volyarosh94 force-pushed the feat/archive-map-replay branch from b55794e to 99dc9b5 Compare October 21, 2026 15:32
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.

1 participant