Add multi-airline ACARS summarizer for readable datalink dossiers.

Parse common position, OOOI, performance, and ops formats across major carriers while hiding VDL binary fragments and duplicate frames from the dossier feed.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
BigBodyCobain
2026-06-24 22:52:00 -06:00
co-authored by Cursor
parent 9ad0a5ffce
commit 8c4619179e
4 changed files with 819 additions and 9 deletions
@@ -9,11 +9,49 @@ type DatalinkMessage = {
label?: string;
text?: string;
source_type?: string;
summary?: string;
kind?: string;
readable?: boolean;
};
const PRIORITY_POLL_MS = 3_000;
const PRIORITY_POLL_MAX_MS = 45_000;
function DatalinkMessageRow({ message }: { message: DatalinkMessage }) {
const [showRaw, setShowRaw] = useState(false);
const summary = message.summary?.trim();
const raw = message.text?.trim() || '';
const hasSummary = Boolean(summary);
const showRawBlock = showRaw || (!hasSummary && raw);
return (
<div className="text-[10px] font-mono leading-snug border border-[var(--border-primary)]/60 bg-black/20 px-2 py-1.5">
<div className="flex items-center gap-2 text-[var(--text-muted)] mb-0.5">
<span>{formatDatalinkTime(message.timestamp)}</span>
{message.label ? <span className="text-orange-400/90">{message.label}</span> : null}
{message.source_type ? <span className="truncate">{message.source_type}</span> : null}
</div>
{hasSummary ? (
<div className="text-[var(--text-primary)] break-words">{summary}</div>
) : null}
{hasSummary && raw ? (
<button
type="button"
onClick={() => setShowRaw((value) => !value)}
className="mt-0.5 text-[var(--text-muted)] hover:text-cyan-400/90 underline underline-offset-2"
>
{showRaw ? 'hide raw' : 'show raw'}
</button>
) : null}
{showRawBlock && raw ? (
<div className="mt-0.5 text-[var(--text-muted)] whitespace-pre-wrap break-words text-[9px] leading-relaxed max-h-24 overflow-y-auto">
{raw}
</div>
) : null}
</div>
);
}
function formatDatalinkTime(value?: string): string {
if (!value) return '--:--';
try {
@@ -40,6 +78,8 @@ export default function DatalinkMessagesBlock({
const [hint, setHint] = useState<string | null>(null);
const [loadError, setLoadError] = useState<string | null>(null);
const [priorityScanning, setPriorityScanning] = useState(false);
const [hiddenCount, setHiddenCount] = useState(0);
const [showHidden, setShowHidden] = useState(false);
const pollUntilRef = useRef(0);
const buildParams = useCallback(() => {
@@ -71,6 +111,7 @@ export default function DatalinkMessagesBlock({
const json = await res.json();
setConfigured(Boolean(json.configured));
setMessages(Array.isArray(json.messages) ? json.messages : []);
setHiddenCount(typeof json.hidden_count === 'number' ? json.hidden_count : 0);
setHint(typeof json.hint === 'string' ? json.hint : null);
setLoadError(null);
if (json.priority_scan || json.queued_refresh) {
@@ -163,16 +204,27 @@ export default function DatalinkMessagesBlock({
) : null}
<div className="max-h-36 overflow-y-auto space-y-1.5 pr-1">
{messages.map((message) => (
<div key={message.id} className="text-[10px] font-mono leading-snug border border-[var(--border-primary)]/60 bg-black/20 px-2 py-1.5">
<div className="flex items-center gap-2 text-[var(--text-muted)] mb-0.5">
<span>{formatDatalinkTime(message.timestamp)}</span>
{message.label ? <span className="text-orange-400/90">{message.label}</span> : null}
{message.source_type ? <span className="truncate">{message.source_type}</span> : null}
</div>
<div className="text-[var(--text-primary)] whitespace-pre-wrap break-words">{message.text}</div>
</div>
<DatalinkMessageRow key={message.id} message={message} />
))}
</div>
{hiddenCount > 0 ? (
<p className="text-[9px] font-mono text-[var(--text-muted)] mt-1">
{hiddenCount} binary/fragment message{hiddenCount === 1 ? '' : 's'} hidden.{' '}
<button
type="button"
onClick={() => setShowHidden((value) => !value)}
className="underline underline-offset-2 hover:text-cyan-400/90"
>
{showHidden ? 'Hide note' : 'Why?'}
</button>
{showHidden ? (
<span className="block mt-0.5 text-[var(--text-muted)]/80">
VDL splits long telemetry into many frames. Southwest also uses proprietary formats
that cannot be decoded without airline keys.
</span>
) : null}
</p>
) : null}
</div>
);
}