Skip to content

Latest commit

 

History

History
183 lines (156 loc) · 10.7 KB

AGENTS.md

File metadata and controls

183 lines (156 loc) · 10.7 KB

Worldview — Project-Specific Agent Guide

Worldview is a tactical intelligence 3D globe viewer built with Next.js 16.2.0, React 19.2.4, CesiumJS (via Resium), and Tailwind CSS v4.

Commands

npm run dev      # start dev server on http://localhost:3000
npm run build    # production build
npm run start    # run production server
npm run lint     # ESLint (eslint-config-next/core-web-vitals + typescript)
npm run proxy    # optional standalone Express proxy on http://localhost:3001
npm run copy-cesium-assets # manually sync Cesium assets to public/cesium (also runs on postinstall)

Directory Layout

app/                     # Next.js App Router — entry points only
  layout.tsx             # Root layout (Geist fonts, h-full body)
  page.tsx               # Main page — mounts GlobeViewer dynamically (ssr: false)
  globals.css            # Tailwind v4 import + wv-* design tokens
  api/
    flights/route.ts     # Global flights (FR24 primary, adsb.fi fallback) + route registry seed
    flights/live/route.ts # Regional adsb.fi feed (lat/lon/dist query)
    satellites/route.ts  # TLE proxy (primary API + CelesTrak fallback)
    ships/route.ts       # AISStream burst collection + moving filter
    cctv/route.ts        # Multi-source CCTV aggregation (TfL, Austin, NSW)
    cctv/image/route.ts  # CORS-safe image proxy with short TTL cache
    geolocation/route.ts # IP geolocation fallback endpoint
    traffic/roads/route.ts # Road network endpoint (Overpass + static Sydney fallback)
    health/route.ts      # Lightweight health/uptime probe
src/                     # All application source (NOT inside app/)
  globe/
    GlobeViewer.tsx      # Cesium <Viewer> root; ShaderManager inner component
    EntityClickHandler.tsx  # drillPick → entity classification → camera tracking
  hooks/                 # Data/utility hooks (telemetry hooks accept `enabled: boolean`)
    useFlights.ts        # Global aircraft (20 s poll → /api/flights)
    useFlightsLive.ts    # Regional aircraft (5 s poll → /api/flights/live, adsb.fi)
    useSatellites.ts     # TLE propagation via satellite.js (→ /api/satellites)
    useShips.ts          # AIS vessels (30 s poll → /api/ships)
    useEarthquakes.ts    # USGS direct fetch (60 s poll, no proxy)
    useTraffic.ts        # Road network + animated vehicles (→ /api/traffic/roads)
    useCameras.ts        # CCTV feeds (5 min poll → /api/cctv)
    useGeolocation.ts    # Browser GPS → IP fallback (→ /api/geolocation)
    useAudio.ts          # Wraps tacticalAudio singleton from src/lib/audio.ts
    useIsMobile.ts       # matchMedia breakpoint (768 px)
  lib/server/
    flightRouteRegistry.ts # In-memory route metadata bridge: /api/flights -> /api/flights/live
  layers/                # Cesium layer renderers (children of <Viewer>)
    FlightLayer.tsx      # BillboardCollection + LabelCollection + PolylineCollection
    ShipLayer.tsx        # Same imperative pattern as FlightLayer
    SatelliteLayer.tsx   # Mix: React <Entity> per satellite + useCesium() for occluder
    EarthquakeLayer.tsx  # React <Entity> with memo() — small dataset, declarative OK
    CCTVLayer.tsx        # BillboardCollection for camera icons
    TrafficLayer.tsx     # PolylineCollection (roads) + PointPrimitiveCollection (vehicles)
  shaders/
    postprocess.ts       # GLSL strings for CRT / NVG / FLIR PostProcessStage
  types/
    camera.ts            # CameraFeed, CameraApiResponse, CameraCountry
  ui/                    # HUD overlay components (all purely client-side)
    OperationsPanel.tsx  # Layer toggles, shader picker, map tile switcher
    IntelFeed.tsx        # Scrolling event log (flight/seismic/satellite/cctv/ship)
    StatusBar.tsx        # UTC clock, camera lat/lon/alt/heading
    TrackedEntityPanel.tsx  # Entity details when tracking (aircraft/ship/satellite/…)
    CCTVPanel.tsx        # Camera thumbnails via /api/cctv/image proxy
    SplashScreen.tsx     # Boot sequence with typewriter effect + audio cues
    MobileModal.tsx      # Full-screen modal for compact viewports
    AudioToggle.tsx      # Mute/unmute button
    Crosshair.tsx        # Centre reticle overlay
    FilmGrain.tsx        # Canvas-based analogue noise at ~12 fps
server/
  .env                   # Server-side API credentials (see Environment Variables)

Path Alias

@/* resolves to the project root, not src/. Files inside src/ use relative imports between each other (e.g. ../../hooks/useFlights). Only import from @/src/... when crossing from app/ into src/.

Cesium / Resium Rules

  • Always dynamic-import GlobeViewer in app/page.tsx with { ssr: false } — Cesium requires window.
  • Layer components (FlightLayer, ShipLayer, etc.) must be rendered as children of <Viewer> so useCesium() works.
  • Access the raw viewer via const { viewer } = useCesium() inside any component that lives inside <Viewer>.
  • EllipsoidalOccluder exists at runtime but is absent from Cesium's TypeScript declarations — cast it: const EllipsoidalOccluder = (Cesium as any).EllipsoidalOccluder as new (...) => ....

Layer Rendering Pattern (Performance-Critical)

For large datasets (flights, ships, CCTV, traffic), do not use React <Entity> components — they freeze the UI at ~1,000+ objects. Use imperative Primitive Collections:

// ✅ correct — imperative, bypasses React reconciliation
const { viewer } = useCesium();
useEffect(() => {
  const billboards = new BillboardCollection();
  viewer.scene.primitives.add(billboards);
  // populate with billboards.add(...)
  return () => { viewer.scene.primitives.remove(billboards); };
}, [viewer, data]);

// ❌ wrong for large datasets
data.map(item => <Entity key={item.id} ... />)

EarthquakeLayer (typically < 200 entities) is the only layer that uses declarative <Entity> + memo().

Data Hook Conventions

Telemetry hooks (useFlights, useSatellites, useShips, useEarthquakes, useCameras) typically:

  • Take enabled: boolean and clear state/return early when false.
  • Return domain data plus feedItems: IntelFeedItem[] for IntelFeed.
  • Import IntelFeedItem from ../ui/IntelFeed.

Polling strategy is hook-specific in the current codebase:

  • setTimeout recursion with exponential backoff: useFlights, useFlightsLive, useShips.
  • setInterval cadence: useEarthquakes, useSatellites, useCameras.
  • useTraffic is on-demand by camera bbox and returns { roads, vehicles, loading, shouldFetchRoads } (no feedItems).

Polling intervals: flights 20 s · flights-live 5 s · ships 30 s · satellites TLE 2 h (positions 2 s) · earthquakes 60 s · traffic on-demand · cameras 5 min. useFlightsLive only activates when camera altitude < 5,000,000 m or an entity is being tracked. Camera lat/lon is snapped to 0.5° grid to minimise cache misses.

API Route Pattern

// app/api/<feature>/route.ts
const cache = new Map();
const CACHE_TTL = 30_000;

export async function GET(request: Request) {
  const cached = cache.get('key');
  if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
    return NextResponse.json(cached.data);
  }
  // fetch upstream, cache, return
}

Route handlers are implemented for /api/flights, /api/flights/live, /api/satellites, /api/ships, /api/cctv, /api/cctv/image, /api/geolocation, /api/traffic/roads, and /api/health. Note: traffic clients should call /api/traffic/roads (there is no app/api/traffic/route.ts).

Environment Variables

Server-side credentials live in server/.env (not .env.local). They must be read in route handlers via process.env.*. For Next.js route handlers to pick them up, the file must be loaded explicitly or the variables copied to .env.local. Client-accessible vars need the NEXT_PUBLIC_ prefix. src/globe/GlobeViewer.tsx reads process.env.NEXT_PUBLIC_GOOGLE_API_KEY and process.env.NEXT_PUBLIC_CESIUM_ION_TOKEN. Optional UI version override: src/ui/StatusBar.tsx reads process.env.NEXT_PUBLIC_APP_VERSION. If unset, next.config.ts derives it from git tags at build time.

Key credentials available in server/.env:

  • OPENSKY_CLIENT_ID / OPENSKY_CLIENT_SECRET — OpenSky Network OAuth2 (flight data)
  • AISSTREAM_API_KEY — AISStream.io WebSocket API (ship AIS data)
  • NSW_TRANSPORT_API_KEY — Transport for NSW camera API (CCTV AU source)

Tailwind CSS v4

Uses the v4 API — different from what most training data knows:

/* globals.css — v4 syntax */
@import "tailwindcss";           /* NOT @tailwind base/components/utilities */

@theme inline {
  --color-wv-black: #0a0a0f;
  --color-wv-cyan:  #00D4FF;
  /* etc. */
}

PostCSS plugin is @tailwindcss/postcss (not tailwindcss directly). Custom wv-* colour tokens (wv-black, wv-cyan, wv-green, wv-amber, wv-muted, wv-text, wv-border, wv-dark, wv-red) are used throughout components but must be declared in globals.css under @theme. The panel-glass utility class is also used and must be defined there.

Missing Source Files (Not Yet Created)

None currently. Previously missing modules now exist:

Import path Notes
src/lib/audio.ts Tactical audio singleton used by useAudio.ts and SplashScreen.tsx
src/data/airports.ts Airport coordinate lookup used by FlightLayer.tsx

Known Issues & Vite Migration Leftovers

Migration leftovers documented in older status files have been addressed in current source:

  • Hooks import IntelFeedItem from ../ui/IntelFeed.
  • app/page.tsx imports UI/components from @/src/....
  • src/globe/GlobeViewer.tsx uses process.env.NEXT_PUBLIC_* (not import.meta.env).
  • package.json includes cesium, resium, and satellite.js.

Shader System

src/shaders/postprocess.ts exports GLSL strings (CRT_SHADER, NVG_SHADER, FLIR_SHADER) and a ShaderMode type. Applied via Cesium's PostProcessStage inside ShaderManager (an inner component of GlobeViewer that calls useCesium()). Stages must be removed before adding a new one to prevent accumulation.

Entity Classification & Tracking

EntityClickHandler uses viewer.scene.drillPick() (limit 10) to pierce 3D tiles, then falls back to scene.pick(). Entities are classified by inspecting their description HTML for keywords (callsign/icao24 → aircraft, mmsi/imo → ship, norad → satellite, magnitude/depth → earthquake). Camera follow uses Cesium's viewer.trackedEntity; ESC or clicking empty space calls viewer.trackedEntity = undefined.

Type Definitions

  • IntelFeedItem — defined and exported from src/ui/IntelFeed.tsx. Telemetry hooks return feedItems: IntelFeedItem[]. Shape: { id, time, type, message } where type is one of 'flight' | 'seismic' | 'satellite' | 'system' | 'cctv' | 'ship'.