Files
KSP-MissionControl/apps/live-map/src/panels/StatusPill.tsx
T
Mavis 9e76ec9328
CI / Lint, typecheck, test, build (pull_request) Failing after 9s
Phase 2: 3D live map driven by API WebSocket
- apps/live-map/src/hooks/useLiveState.ts: WebSocket subscription
  with exponential-backoff reconnect, polling fallback, status tracking
- apps/live-map/src/scene/Scene.tsx: refactored Three.js scene with
  per-frame orbit propagation, vessel marker color-coding by owner,
  orbit-line visibility tied to focus filters, smooth camera follow
  on selected vessel/body
- apps/live-map/src/scene/layout.ts: bodyPositionAt / vesselPositionAt
  helpers (heliocentric frame, walk up the parent chain), logScale
  helpers for the system view
- apps/live-map/src/scene/color.ts: per-body and per-owner color maps
- apps/live-map/src/panels/TimeControls.tsx: play/pause/reverse/reset
  buttons, ×1/×10/×100/×1k/×10k/×100k speeds, UT scrub slider,
  live-edge indicator (LIVE / Nh behind / Nh ahead)
- apps/live-map/src/panels/VesselList.tsx: vessel sidebar with click-
  to-track; color-coded by owner (KASA=blue, SPES=orange)
- apps/live-map/src/panels/FocusPanel.tsx: planet/moon/vessel orbit
  visibility toggles
- apps/live-map/src/panels/StatusPill.tsx: WS status (LIVE/POLLING/
  OFFLINE/STALE), body + vessel + message counts
- tests/scene.test.ts: 10 tests for layout helpers (periodicity,
  vessel-centered positioning, logScale round-trips)

End-to-end verified: mock publisher → API → live-map WebSocket →
scene re-renders with the new vessel positions and orbits.
2026-06-02 19:18:22 +00:00

74 lines
1.8 KiB
TypeScript

/**
* StatusPill — connection + stale-data indicator.
*
* Shows:
* - WS connection state (open / connecting / closed / fallback)
* - "Data may be stale" warning if lastUpdate > STALE_THRESHOLD_S
* - Number of messages received
*/
import { Pill } from '@kerbal-rt/ui';
import type { ConnectionState } from '../hooks/useLiveState.js';
export interface StatusPillProps {
status: ConnectionState;
lastUpdate: string | null;
messageCount: number;
vesselCount: number;
bodyCount: number;
}
const STALE_THRESHOLD_S = 60;
export function StatusPill({
status,
lastUpdate,
messageCount,
vesselCount,
bodyCount,
}: StatusPillProps) {
const isStale = lastUpdate
? (Date.now() - new Date(lastUpdate).getTime()) / 1000 > STALE_THRESHOLD_S
: true;
const tone =
status === 'open' && !isStale
? 'success'
: status === 'connecting' || status === 'fallback'
? 'warn'
: 'danger';
const label =
status === 'open'
? isStale
? 'STALE'
: 'LIVE'
: status === 'connecting'
? 'CONNECTING'
: status === 'fallback'
? 'POLLING'
: 'OFFLINE';
return (
<div
style={{
position: 'absolute',
top: 12,
left: '50%',
transform: 'translateX(-50%)',
display: 'flex',
gap: 6,
padding: '0.4rem 0.6rem',
background: 'rgba(0,0,0,0.6)',
borderRadius: 6,
backdropFilter: 'blur(4px)',
border: '1px solid rgba(255,255,255,0.1)',
fontFamily: 'monospace',
fontSize: 11,
}}
>
<Pill tone={tone}>{label}</Pill>
<Pill tone="neutral">{bodyCount} bodies</Pill>
<Pill tone="neutral">{vesselCount} vessels</Pill>
<Pill tone="neutral">{messageCount} msgs</Pill>
</div>
);
}