From fb25af913ae9fd12ed7f457fd0a701b3a55ac45e Mon Sep 17 00:00:00 2001 From: itsrubberduck Date: Fri, 10 Jul 2026 08:33:36 +0200 Subject: [PATCH] atc header --- CLAUDE.md | 12 +- app/app.vue | 2 +- app/components/CookieConsentBanner.vue | 2 +- app/composables/usePmTheme.ts | 25 +- app/pages/classroom-introduction.vue | 52 +- app/pages/classroom.vue | 2 +- app/pages/live-atc.vue | 6573 ++++++++++++++++++++++++ app/pages/pm.vue | 6551 +---------------------- app/pages/start.vue | 2 +- nuxt.config.ts | 4 +- 10 files changed, 6649 insertions(+), 6576 deletions(-) create mode 100644 app/pages/live-atc.vue diff --git a/CLAUDE.md b/CLAUDE.md index e02e9c4..1d30d19 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,7 +6,7 @@ - **Shared types/utils** in `/shared` - MongoDB models in `/server/models` - **Python backend** — a separate repo, `OpenSquawk-LiveATC-api`, owns the authoritative - Live ATC (`/pm`) session state and routing decisions. It runs as its own service + Live ATC (`/live-atc`, formerly `/pm` — legacy path now redirects) session state and routing decisions. It runs as its own service (default `http://127.0.0.1:8000`). This Nuxt app owns STT, TTS, client audio, auth, the flow editor, and account/admin features. @@ -16,21 +16,21 @@ ## Key Files - `/app/composables/useRadioBackend.ts` — Typed client for the Python backend REST API - (`createSession`, `transmit`, `deleteSession`, `fetchFlows`). This is how `/pm` talks + (`createSession`, `transmit`, `deleteSession`, `fetchFlows`). This is how `/live-atc` talks to the decision engine. -- `/shared/utils/communicationsEngine.ts` — Local state-machine composable used by `/pm`. +- `/shared/utils/communicationsEngine.ts` — Local state-machine composable used by `/live-atc`. It mirrors the flow for cursor tracking and TTS scheduling; the **Python backend owns the authoritative state**. `fetchRuntimeTree()` loads the flow definition from the backend's `/api/decision-flows/runtime`. - `/server/services/decisionFlowService.ts` — Builds/edits MongoDB-backed decision trees. - Used **only by the flow editor** (`/server/api/editor/flows*`), not by `/pm`. + Used **only by the flow editor** (`/server/api/editor/flows*`), not by `/live-atc`. - `/server/utils/openai.ts` — `getOpenAIClient()` only. Used for TTS (`/api/atc/say`) and STT (`/api/atc/ptt`). It is **not** a decision router anymore. -- `/app/pages/pm.vue` — Live ATC page (speech-to-text, PTT, text input). +- `/app/pages/live-atc.vue` — Live ATC page (speech-to-text, PTT, text input). - `/app/pages/classroom.vue` — Classroom learning mode (separate system, does NOT use communicationsEngine). -## Live ATC Flow (/pm) +## Live ATC Flow (/live-atc) 1. `startMonitoring()` → `fetchRuntimeTree('icao_atc_decision_tree', radioBackendUrl)` loads the flow definition from the Python backend into the local engine (cursor / TTS). 2. `startMonitoring()` → `radioBackend.createSession(...)` creates the authoritative diff --git a/app/app.vue b/app/app.vue index 967eb08..ea3a522 100644 --- a/app/app.vue +++ b/app/app.vue @@ -79,7 +79,7 @@ const scheduleHotjarInitialization = () => { const resolveTrackedProduct = (path: string): 'classroom' | 'liveatc' | null => { if (path.startsWith('/classroom')) return 'classroom'; - if (path.startsWith('/pm') || path.startsWith('/copilot') || path.startsWith('/bridge')) return 'liveatc'; + if (path.startsWith('/live-atc') || path.startsWith('/pm') || path.startsWith('/copilot') || path.startsWith('/bridge')) return 'liveatc'; return null; }; diff --git a/app/components/CookieConsentBanner.vue b/app/components/CookieConsentBanner.vue index 2e6cdd1..8c09589 100644 --- a/app/components/CookieConsentBanner.vue +++ b/app/components/CookieConsentBanner.vue @@ -110,7 +110,7 @@ watch( } ); -const restrictedRoute = computed(() => route.path.startsWith('/classroom') || route.path.startsWith('/pm') || route.path.startsWith('/login')); +const restrictedRoute = computed(() => route.path.startsWith('/classroom') || route.path.startsWith('/live-atc') || route.path.startsWith('/pm') || route.path.startsWith('/login')); const showManageButton = computed(() => hasConsent.value && !isDialogVisible.value && !restrictedRoute.value); diff --git a/app/composables/usePmTheme.ts b/app/composables/usePmTheme.ts index 1756e31..01bef07 100644 --- a/app/composables/usePmTheme.ts +++ b/app/composables/usePmTheme.ts @@ -8,11 +8,12 @@ export type PmEffectiveTheme = 'light' | 'dark' const PM_THEME_COOKIE = 'os_pm_theme' /** - * /pm-scoped light/dark toggle. Defaults to dark (matching the rest of the app) + * /live-atc-scoped light/dark toggle (formerly /pm — cookie name kept for + * backwards compatibility). Defaults to dark (matching the rest of the app) * until the user explicitly picks Light or System, at which point that choice is - * remembered and wins over the default. Only /pm has a light theme today — the - * Vuetify theme is reset back to the app-wide dark default on unmount so other - * pages are unaffected. + * remembered and wins over the default. Only /live-atc has a light theme today — + * the Vuetify theme is reset back to the app-wide dark default on unmount so + * other pages are unaffected. */ export function usePmTheme() { const vuetifyTheme = useTheme() @@ -41,6 +42,16 @@ export function usePmTheme() { storedPreference.value = next } + // Mirrors effectiveTheme onto so the --bg/--text/--border + // CSS variables (defined in global.css) reach content that escapes .pm-page in the + // DOM — Vuetify teleports menus, dialogs, and tooltips to , where they'd + // otherwise only see the app-wide dark defaults from :root. + function applyDocumentTheme() { + if (typeof document !== 'undefined') { + document.documentElement.setAttribute('data-theme', effectiveTheme.value) + } + } + if (typeof window !== 'undefined') { const media = window.matchMedia('(prefers-color-scheme: light)') systemPrefersLight.value = media.matches @@ -53,10 +64,14 @@ export function usePmTheme() { onBeforeUnmount(() => { media.removeEventListener('change', handleChange) vuetifyTheme.global.name.value = 'opensquawkDark' + document.documentElement.removeAttribute('data-theme') }) } - watch(effectiveTheme, applyVuetifyTheme, { immediate: true }) + watch(effectiveTheme, () => { + applyVuetifyTheme() + applyDocumentTheme() + }, { immediate: true }) return { preference, effectiveTheme, setPreference } } diff --git a/app/pages/classroom-introduction.vue b/app/pages/classroom-introduction.vue index 152323e..0333534 100644 --- a/app/pages/classroom-introduction.vue +++ b/app/pages/classroom-introduction.vue @@ -73,25 +73,25 @@

Delivery

-
+
@@ -916,23 +916,39 @@ onBeforeUnmount(() => { diff --git a/app/pages/pm.vue b/app/pages/pm.vue index 92483d7..f86dba9 100644 --- a/app/pages/pm.vue +++ b/app/pages/pm.vue @@ -1,6545 +1,14 @@ - - - + diff --git a/app/pages/start.vue b/app/pages/start.vue index fee42a4..088c34a 100644 --- a/app/pages/start.vue +++ b/app/pages/start.vue @@ -58,7 +58,7 @@