feat: v0.17.0 — ISS-008 toasts for errors/events + browser notifications
CI / build + unit + e2e (pull_request) Failing after 6m59s

This commit is contained in:
2026-09-19 14:52:36 +00:00
parent eb69359a38
commit d8dc785757
12 changed files with 545 additions and 9 deletions
+26 -3
View File
@@ -1,11 +1,14 @@
'use client';
import dynamic from 'next/dynamic';
import { useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import LeftPane from '@/components/LeftPane';
import RightPane from '@/components/RightPane';
import ToastStack from '@/components/Toasts';
import type { HoverTarget } from '@/components/hoverTypes';
import { useGameState } from '@/lib/useGameState';
import { useToasts } from '@/lib/useToasts';
import { diffSnapshots } from '@/lib/deriveEvents';
const Map = dynamic(() => import('@/components/Map'), { ssr: false });
@@ -19,12 +22,32 @@ export default function Home() {
// either pane highlights the matching plane/route/airports on the map.
const [hovered, setHovered] = useState<HoverTarget | null>(null);
// ISS-008 (v0.17.0): bottom-right toasts — action errors from both panes plus game events
// derived by diffing consecutive snapshots (no server event feed exists).
const { toasts, push, dismiss } = useToasts();
const onError = (message: string) => push('error', message);
// Diff each new snapshot against the previous one → info toasts. When the window is hidden
// and notifications are granted, mirror them as browser notifications too.
const prevSnapshotRef = useRef<typeof state>(null);
useEffect(() => {
if (state === null) return;
for (const event of diffSnapshots(prevSnapshotRef.current, state)) {
push('info', event.message);
if (document.hidden && 'Notification' in window && Notification.permission === 'granted') {
new Notification('Flight Ops', { body: event.message });
}
}
prevSnapshotRef.current = state;
}, [state, push]);
return (
<div className="app-shell">
<LeftPane state={state} loading={loading} error={error} onRefresh={refresh} onHover={setHovered} />
<LeftPane state={state} loading={loading} error={error} onRefresh={refresh} onHover={setHovered} onError={onError} />
{/* v0.11.0 — the same polled snapshot drives the map's plane markers + route lines */}
<Map state={state} hovered={hovered} />
<RightPane state={state} loading={loading} applySnapshot={applySnapshot} onHover={setHovered} />
<RightPane state={state} loading={loading} applySnapshot={applySnapshot} onHover={setHovered} onError={onError} />
<ToastStack toasts={toasts} onDismiss={dismiss} />
</div>
);
}