mirror of
https://github.com/OpenSquawk/OpenSquawk
synced 2026-08-04 16:22:48 +08:00
fix(sim-control): narrow SimControlParseResult via type predicates
3fe0ea5 left `nuxt typecheck` failing on main:
useLiveAtcSession.ts: Property 'reason' does not exist on type
'SimControlParseResult'.
Cause: the union is discriminated by a boolean, and the project builds
with `strict: false` (nuxt.config.ts). With strictNullChecks off,
TypeScript does not treat `true`/`false` literal types as discriminants,
so `if (r.matched) … else r.reason` never narrows. Nothing about the
sim-control types themselves is wrong — the same three lines with any
boolean-discriminated union fail identically.
Adds isSimControlMatch/isSimControlRejection type predicates, which narrow
regardless of strictNullChecks, and routes the one caller through them.
Turning on strictNullChecks would fix it at the root but is a repo-wide
change, not a bug fix.
The pre-push hook was correctly refusing to push this; origin/main is
clean, so the breakage never escaped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,8 @@ import { useApi } from '~/composables/useApi'
|
||||
import type { useRadioSpeech } from '~/composables/useRadioSpeech'
|
||||
import useCommunicationsEngine from '../../shared/utils/communicationsEngine'
|
||||
import {
|
||||
isSimControlMatch,
|
||||
isSimControlRejection,
|
||||
parseSimControl,
|
||||
simControlRejectionSpeech,
|
||||
simControlResultSpeech,
|
||||
@@ -331,11 +333,14 @@ export function useLiveAtcSession(
|
||||
// anchors already keep it from ever matching real ICAO phraseology either way.
|
||||
if (bridgeConnected.value) {
|
||||
const simResult = parseSimControl(transcript)
|
||||
if (simResult.matched) {
|
||||
if (isSimControlMatch(simResult)) {
|
||||
void sendSimControlCommand(simResult.command)
|
||||
return
|
||||
}
|
||||
if (simResult.reason !== 'no_intent') {
|
||||
// A rejection with a reason means the pilot clearly addressed their sim but
|
||||
// got something wrong — tell them. 'no_intent' just means this was ordinary
|
||||
// radio traffic, so it falls through to the flow untouched.
|
||||
if (isSimControlRejection(simResult) && simResult.reason !== 'no_intent') {
|
||||
const reply = simControlRejectionSpeech(simResult.reason)
|
||||
scheduleControllerSpeech(reply)
|
||||
appendLogEntry('atc', reply, currentState.value?.id ?? '', { frequency: frequencies.value.active })
|
||||
|
||||
@@ -38,9 +38,26 @@ export type SimControlNoMatchReason =
|
||||
| 'missing_runway'
|
||||
| 'missing_airport'
|
||||
|
||||
export type SimControlParseResult =
|
||||
| { matched: true; command: SimControlCommand; text: string }
|
||||
| { matched: false; reason: SimControlNoMatchReason; text: string }
|
||||
export type SimControlMatch = { matched: true; command: SimControlCommand; text: string }
|
||||
export type SimControlRejection = { matched: false; reason: SimControlNoMatchReason; text: string }
|
||||
|
||||
export type SimControlParseResult = SimControlMatch | SimControlRejection
|
||||
|
||||
/**
|
||||
* Narrowing helpers. This union is discriminated by a boolean, and the project
|
||||
* builds with `strict: false` (nuxt.config.ts) — so `strictNullChecks` is off,
|
||||
* and without it TypeScript does not treat `true`/`false` literal types as
|
||||
* discriminants. `if (result.matched) … else result.reason` therefore does NOT
|
||||
* narrow and fails to compile. An explicit type predicate narrows regardless,
|
||||
* which is why callers must go through these rather than testing `.matched`.
|
||||
*/
|
||||
export function isSimControlMatch(result: SimControlParseResult): result is SimControlMatch {
|
||||
return result.matched
|
||||
}
|
||||
|
||||
export function isSimControlRejection(result: SimControlParseResult): result is SimControlRejection {
|
||||
return !result.matched
|
||||
}
|
||||
|
||||
/** Hard value ranges; anything outside is a refusal, never a clamp. */
|
||||
export const SIM_CONTROL_LIMITS = {
|
||||
|
||||
Reference in New Issue
Block a user