Skip to content

feat: route detection, hexdb API, aircraft photos, trail improvements#20

Open
kewonit wants to merge 19 commits intomainfrom
feat/trail-improvements-v2
Open

feat: route detection, hexdb API, aircraft photos, trail improvements#20
kewonit wants to merge 19 commits intomainfrom
feat/trail-improvements-v2

Conversation

@kewonit
Copy link
Copy Markdown
Owner

@kewonit kewonit commented Mar 30, 2026

  • Add route detection and lookup system (route-detection.ts, route-lookup.ts, use-route-info.ts)
  • Add hexdb API route for aircraft metadata
  • Update flight API client and trace routes
  • Improve aircraft photos, flight card, and mobile toast UI
  • Update weather radar layer and map state tracker
  • Trail altitude and spline improvements

- Add route detection and lookup system (route-detection.ts, route-lookup.ts, use-route-info.ts)
- Add hexdb API route for aircraft metadata
- Update flight API client and trace routes
- Improve aircraft photos, flight card, and mobile toast UI
- Update weather radar layer and map state tracker
- Trail altitude and spline improvements
- Add local-only copilot config to .gitignore
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 30, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
aeris-flight Ready Ready Preview, Comment Apr 10, 2026 7:05pm

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a multi-source route (origin/destination) resolution system for flights, introduces new server proxies for upstream services without CORS support, and improves trail rendering/animation plus several UI elements (flight card/toast, photos, radar).

Changes:

  • Add route lookup + client-side departure detection and destination estimation, surfaced via a new useRouteInfo hook.
  • Extend flight/trace proxying and upstream selection (multi-provider flights proxy, smart trace source selection, new hexdb proxy).
  • Improve trail rendering/animation behavior (altitude handling, spline/trim logic, caching keys) and refresh several UI components.

Reviewed changes

Copilot reviewed 19 out of 20 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/lib/trail-spline.ts Adjust endpoint reflection to clamp only lat/lng while keeping altitude independent.
src/lib/trail-altitude.ts Clarify ground-segment filtering constant documentation.
src/lib/route-lookup.ts New callsign→route lookup service with caching, dedupe, rate limiting, and formatting helpers.
src/lib/route-detection.ts New client-side departure detection + destination estimation heuristics.
src/lib/flight-api-client.ts Update client fallback chain to use server proxy for airplanes.live/adsb.lol + OpenSky.
src/hooks/use-route-info.ts New hook combining API lookup + trace/live detection + estimation into a single route model.
src/components/ui/mobile-flight-toast.tsx Display route info and refresh badges/typography.
src/components/ui/flight-card.tsx Add route banner and collapsible vertical profile UI.
src/components/ui/aircraft-photos.tsx Fix separator/ellipsis rendering in the header line.
src/components/map/weather-radar-layer.tsx Refactor source/layer creation and add abortable fetch for radar frame updates.
src/components/map/map-state-tracker.tsx Update onChange prop description to reflect actual behavior.
src/components/map/flight-layer-builders.ts Refine trail color cache invalidation key to better track visible-window changes.
src/components/map/flight-animation-helpers.ts Improve trail trimming + head connection, altitude smoothing, and loop-handling logic.
src/components/flight-tracker.tsx Feed polled flights into departure detection and pass track into UI components.
src/app/api/hexdb/route.ts New SSRF-protected proxy to hexdb.io for client consumption.
src/app/api/flights/trace/route.ts Reorder trace sources and add preferred-source + fallback racing strategy.
src/app/api/flights/route.ts Convert adsb.lol proxy into a multi-provider proxy with server-side rate limiting.
src/app/api/aircraft-photos/route.ts Reduce upstream timeouts for photo metadata pipeline.
next.config.ts Update CSP connect-src to allow client-side adsbdb route lookups.
.gitignore Ignore local Copilot agent/skill/instruction files.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Introduced `trail-path-utils.ts` for trail path operations including smoothing, trimming, and fallback generation.
- Updated `control-panel-settings.tsx` to increase maximum trail distance from 100 to 120.
- Adjusted polling interval in `use-flights.ts` from 10s to 5s for improved data density.
- Increased maximum trail distance in `use-settings.tsx` from 100 to 120 and updated default trail distance from 40 to 80.
- Expanded maximum points in `use-trail-history.ts` from 55 to 120.
- Modified maximum altitude for departure detection in `route-detection.ts` from 3000m to 1500m.
- Changed cache description in `route-lookup.ts` from LRU to FIFO.
- Implemented post-processing for stitched trail paths in `trail-stitch-postprocess.ts` to ensure trails reach aircraft position, filter NaN/Infinity coordinates, cap total path length, and smooth junctions.
- Refactored `stitchHistoricalTrail` in `trail-stitching.ts` to utilize new post-processing functions for improved trail quality.
- Add server-trace-service.ts to handle fetching and processing flight trace data from various providers.
- Introduce createOpenSkyCooldownMs and preferNextProvider utility functions.
- Implement fetchServerTrace to manage trace fetching logic with provider preference.
- Create trace-proxy-client.ts for proxying trace requests.
- Establish trail-store.ts to manage flight trail state and history resolution.
- Add types for trail management in types.ts.
- Implement unit tests for server-trace-service and trail-store functionality.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 82 out of 84 changed files in this pull request and generated 3 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Implemented `removeSuspiciousDisplayCuts` to clean up trail points by removing suspicious display cuts based on various geometric criteria.
- Updated `buildTrailBasePath` to utilize the new cleanup function for both full history and live trails.
- Added tests for `buildTrailDisplayGeometry` to ensure proper handling of trail segments, including cases with older loops and sharp angles.
- Introduced `collapseDisplayBacktracks` to eliminate backtracks in display geometry.
- Enhanced `mergeSegments` logic to improve handling of historical and live trail merging, including new continuity checks.
- Updated `parseReadsbTrace` to drop older branches and impossible jumps in trace data.
- Added a new test suite for merging trail segments to validate the merging logic under various scenarios.
- Exposed selected envelope in the trail store to facilitate rendering of history and live boundaries.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 88 out of 90 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Implement tests for the useTrailHistory hook to ensure it does not ingest flights during render.
- Add tests for parseFlightTrack to validate trimming of OpenSky responses to the latest plausible departure leg and dropping implausible older jumps.
- Refactor useTrailHistory to utilize useEffect for handling flight ingestion.
- Introduce normalizeTrackWaypoints function to streamline waypoint normalization in track parsing.
- Enhance trail store to retain more live points and manage dynamic thresholds for flight movement.
- Update GPU memory monitor to improve buffer data handling and type safety.
- Adjust spline parameters for better curve handling in trail rendering.
- Improve provider health tracking with escalated cooldowns on consecutive failures.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 101 out of 103 changed files in this pull request and generated 13 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Adjusted the calculation of timestamps in `makeArcTrail` to remove unnecessary condition.
- Updated import paths in test files for consistency.
- Improved dropdown state management in `ControlPanel` using `useSyncExternalStore`.
- Added type imports and cleaned up unused imports in `flight-card`.
- Introduced new tests for `resolveDropdownState` in `status-bar-state`.
- Implemented `deriveAircraftPhotosFlags` to handle aircraft photo loading states.
- Enhanced `mergeSegments` to drop suspect bootstrap points and added related tests.
- Improved error handling in `fetchReadsbDirectTrack` to rethrow abort errors.
- Added tests for `fetchTraceViaProxy` to ensure proper handling of non-JSON responses.
- Updated `ingestLiveFlights` logic to maintain trail integrity across various scenarios.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 109 out of 111 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 109 out of 111 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 109 out of 111 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)

src/app/api/flights/route.ts:166

  • The timeout/abort detection checks err instanceof DOMException && err.name === "AbortError". In Next.js route handlers, aborted fetches can also surface as an Error with name === "AbortError" (not necessarily a DOMException), which would incorrectly return a 502 instead of a 504. Consider checking err instanceof Error && err.name === "AbortError" (or a more flexible typeof (err as any)?.name === "string") to reliably classify aborts across runtimes.
  } catch (err) {
    clearTimeout(timer);

    const isTimeout = err instanceof DOMException && err.name === "AbortError";

    return NextResponse.json(
      {
        error: isTimeout
          ? `${config.name} request timed out`
          : `${config.name} request failed`,
      },
      {
        status: isTimeout ? 504 : 502,
        headers: { "Cache-Control": "no-store" },
      },

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 109 out of 111 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 109 out of 111 changed files in this pull request and generated 6 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 109 out of 111 changed files in this pull request and generated 7 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)

src/hooks/use-aircraft-photos.ts:1

  • This effect can abort or duplicate the “phase 2” registration (JetAPI) fetch. In the reg && !hexCached path, calling setResolvedKey(normalized) triggers a rerender, which runs the effect cleanup and aborts controller.signal—cancelling the in-flight phase-2 fetch started on the same signal. The dependency array also includes cached/hexCached, which change as putCache runs, causing extra effect reruns and potentially restarting work. A concrete fix is to (1) remove cached/hexCached from the effect dependencies and only depend on stable inputs (cacheKey, normalized, reg, hasIcao24), and (2) manage “phase 1/phase 2” progression with explicit state/refs so caching updates don’t retrigger/abort the fetch pipeline.
"use client";

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

controller.abort();
};
}, [icao24, registration]);
}, [cacheKey, cached, hasIcao24, hexCached, normalized, reg]);
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This effect can abort or duplicate the “phase 2” registration (JetAPI) fetch. In the reg && !hexCached path, calling setResolvedKey(normalized) triggers a rerender, which runs the effect cleanup and aborts controller.signal—cancelling the in-flight phase-2 fetch started on the same signal. The dependency array also includes cached/hexCached, which change as putCache runs, causing extra effect reruns and potentially restarting work. A concrete fix is to (1) remove cached/hexCached from the effect dependencies and only depend on stable inputs (cacheKey, normalized, reg, hasIcao24), and (2) manage “phase 1/phase 2” progression with explicit state/refs so caching updates don’t retrigger/abort the fetch pipeline.

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +58
export function flattenPathColors(
pathLength: number,
color: Color | Color[],
): Uint8Array {
if (pathLength <= 0) {
return new Uint8Array(0);
}

if (Array.isArray(color[0])) {
const flattened = new Uint8Array(pathLength * 4);
let offset = 0;
const vertexColors = color as Color[];
if (vertexColors.length !== pathLength) {
throw new Error(
"PathLayer getColor() returned vertex colors that do not match the path length",
);
}

for (const vertexColor of vertexColors) {
const tuple = toColorTuple(vertexColor);
flattened[offset++] = tuple[0];
flattened[offset++] = tuple[1];
flattened[offset++] = tuple[2];
flattened[offset++] = tuple[3];
}

return flattened;
}

const renderedSegmentCount = Math.max(pathLength - 1, 0);
const flattened = new Uint8Array(renderedSegmentCount * 4);
let offset = 0;
const tuple = toColorTuple(color as Color);
for (let index = 1; index < pathLength; index += 1) {
flattened[offset++] = tuple[0];
flattened[offset++] = tuple[1];
flattened[offset++] = tuple[2];
flattened[offset++] = tuple[3];
}

return flattened;
}
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The per-vertex branch returns pathLength * 4 bytes, while the uniform-color branch returns (pathLength - 1) * 4, implying inconsistent expectations about whether colors are per-vertex or per-segment/instance. Since TrailGradientPathLayer registers instanceColors as an instanced attribute, deck.gl typically expects one color per rendered segment (instance), i.e. pathLength - 1. If pathLength * 4 is written into an instanced buffer sized for segments, this can misalign subsequent objects’ colors or overflow. Consider normalizing both branches to emit per-segment colors (length pathLength - 1) by blending adjacent vertex colors (e.g., average color[i] and color[i+1]) instead of returning raw per-vertex values.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +2
import type { AltitudeDisplayMode } from "@/lib/altitude-display-mode";
import { projectDisplayedAltitudeMeters } from "@/components/map/altitude-projection";
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This introduces a dependency from src/lib/* into src/components/* (flight-utilscomponents/map/altitude-projection). That can make the module graph harder to reason about and increases the chance of circular imports (and also risks pulling UI-layer code into server/runtime code paths that use flight-utils). A concrete fix is to move altitude-projection.ts into src/lib/ (or a shared src/shared/), then import it from both components/ and lib/ to keep layering consistent.

Copilot uses AI. Check for mistakes.
Comment on lines +52 to 57
export function altitudeToElevation(
altitude: number | null,
mode: AltitudeDisplayMode = "presentation",
): number {
return projectDisplayedAltitudeMeters(altitude, mode);
}
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This introduces a dependency from src/lib/* into src/components/* (flight-utilscomponents/map/altitude-projection). That can make the module graph harder to reason about and increases the chance of circular imports (and also risks pulling UI-layer code into server/runtime code paths that use flight-utils). A concrete fix is to move altitude-projection.ts into src/lib/ (or a shared src/shared/), then import it from both components/ and lib/ to keep layering consistent.

Copilot uses AI. Check for mistakes.
destinationConfidence: "known" | "high" | "medium" | "low" | null;
/** How the route was determined */
source: "api" | "detected" | "estimated" | "mixed" | null;
/** Whether route data is currently being fetched */
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loading field docstring says “Whether route data is currently being fetched”, but the returned value is conditional on !origin && !destination. This can report loading: false even while an API lookup is still in-flight (if detected/estimated info exists). Either update the docstring to reflect “only show loading when no route info is available yet”, or change the returned loading value to match the documented meaning (and let the UI decide when to display it).

Suggested change
/** Whether route data is currently being fetched */
/** Whether route data is being fetched and no route info is available yet */

Copilot uses AI. Check for mistakes.
destination,
destinationConfidence,
source,
loading: loading && !origin && !destination,
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loading field docstring says “Whether route data is currently being fetched”, but the returned value is conditional on !origin && !destination. This can report loading: false even while an API lookup is still in-flight (if detected/estimated info exists). Either update the docstring to reflect “only show loading when no route info is available yet”, or change the returned loading value to match the documented meaning (and let the UI decide when to display it).

Suggested change
loading: loading && !origin && !destination,
loading,

Copilot uses AI. Check for mistakes.
Comment on lines +44 to +65
const lastRequestTime: Record<string, number> = {};
const rateLimitQueues: Record<string, Promise<void>> = {};

async function enforceRateLimit(provider: ProviderKey): Promise<void> {
const previous = rateLimitQueues[provider] ?? Promise.resolve();

const next = previous.then(async () => {
const now = Date.now();
const last = lastRequestTime[provider] ?? 0;
const config = PROVIDERS[provider];
const wait = Math.max(0, config.rateMs - (now - last));
if (wait > 0) {
await new Promise((resolve) => setTimeout(resolve, wait));
}
lastRequestTime[provider] = Date.now();
});

// Ensure the chain continues even if a previous step rejects.
rateLimitQueues[provider] = next.catch(() => {});

const ADSB_LOL_BASE = "https://api.adsb.lol/v2";
return next;
}
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This rate limiter is in-memory and per-process, so it won’t enforce a global limit if the app runs across multiple server instances (or multiple concurrent route workers). That can still exceed upstream limits under load even though each instance behaves correctly. If this endpoint is expected to handle concurrent traffic, consider using a shared limiter (e.g., Redis-backed) or pushing the limiting responsibility to a single egress layer, and/or returning 429 to callers when the local queue depth grows past a threshold to avoid unbounded latency.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants