From 365252f53426e97ce6512d465db3cb185cc7b7f2 Mon Sep 17 00:00:00 2001 From: Senrokai Date: Fri, 18 Sep 2026 13:42:11 +0200 Subject: [PATCH] Let the Routes panel be clicked as soon as it is back, and stop it departing from elsewhere MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From the review of #24. Two defects, both in a real browser. The panel keeps its entries across a trip to another tab, but still replayed the acquire wipe on the way back, and for the 380 ms that runs, its clip path swallows clicks: type "Siri", leave for Readout, come back and click the Sirius suggestion, and the click lands on the star field behind it — measured, the element under the pointer is the canvas, and the field stays "Siri". The wipe is gone from this one panel: it is not acquiring anything it did not already have. The departure field fell back to the star the view is in whenever nothing had been chosen, text in the field or not. So a field reading "Sol" that was never resolved plotted from Barnard's Star: "1 Barnard's Star, 2 Sol, 3 Sirius", the panel naming one departure and the route leaving from another. Text nobody chose is no longer a departure, and the button waits until it is one or the field is empty again. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi --- .../features/hud/hud-dock.component.spec.ts | 44 +++++++++++++++++++ src/app/features/hud/hud-dock.component.ts | 6 ++- .../features/hud/routes-panel.component.ts | 8 +++- 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/src/app/features/hud/hud-dock.component.spec.ts b/src/app/features/hud/hud-dock.component.spec.ts index 9018ecf..df35861 100644 --- a/src/app/features/hud/hud-dock.component.spec.ts +++ b/src/app/features/hud/hud-dock.component.spec.ts @@ -253,6 +253,50 @@ describe('HudDockComponent', () => { expect(tab('Readout').getAttribute('aria-selected')).toBe('true'); }); + it('will not plot from a departure that was typed but never chosen', () => { + setReadout(); + fixture.componentRef.setInput('routing', true); + fixture.componentRef.setInput('currentStar', { id: 3, name: "Barnard's Star", subtitle: '1.8 pc' }); + fixture.componentRef.setInput('routeOptions', [{ id: 7, name: 'Sirius', subtitle: '2.6 pc' }]); + fixture.componentRef.setInput('defaultTab', 'routes'); + fixture.detectChanges(); + const plot = () => [...host().querySelectorAll('button')].find((button) => button.textContent?.includes('Plot route'))!; + const type = (field: string, value: string) => { + const input = host().querySelector(`#route-${field}`)!; + input.value = value; + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); + }; + + type('to', 'Sir'); + host().querySelector('#dock-panel-routes ul button')!.click(); + fixture.detectChanges(); + // With the departure field empty, the view's own star stands in for it. + expect(plot().disabled).toBe(false); + + // Text that names no chosen star is not a departure: plotting from the view's star instead + // would name one place and leave from another. + type('from', 'Sol'); + expect(plot().disabled).toBe(true); + + type('from', ''); + expect(plot().disabled).toBe(false); + }); + + it('does not replay the acquire wipe over the Routes panel, whose entries survive the trip', () => { + setReadout(); + fixture.componentRef.setInput('routing', true); + fixture.componentRef.setInput('display', DEFAULT_HUD_DISPLAY); + fixture.componentRef.setInput('defaultTab', 'routes'); + fixture.detectChanges(); + + // The wipe clips its panel for 380 ms, which swallows clicks on entries that are already there. + expect(host().querySelector('#dock-panel-routes')?.classList.contains('hud-acquire')).toBe(false); + tab('Display').click(); + fixture.detectChanges(); + expect(host().querySelector('#dock-panel-display')?.classList.contains('hud-acquire')).toBe(true); + }); + it('keeps what the Routes panel was set to across a trip to another tab', () => { setReadout(); fixture.componentRef.setInput('routing', true); diff --git a/src/app/features/hud/hud-dock.component.ts b/src/app/features/hud/hud-dock.component.ts index ddf757e..3a0ad5d 100644 --- a/src/app/features/hud/hud-dock.component.ts +++ b/src/app/features/hud/hud-dock.component.ts @@ -186,9 +186,11 @@ function isWideViewport(): boolean { } + the old range. No acquire wipe, unlike the panels around it: this is the one that comes + back with what it had, so it is not acquiring anything — and for the 380 ms the wipe + runs, its clip path swallows clicks on the suggestions it just brought back. --> @if (routing()) { -
+
>({ from: null, to: null }); private readonly typed = signal>({ from: '', to: '' }); - /** Departure falls back to wherever the view already is, so one field is usually enough. */ - private readonly departure = computed(() => this.chosen().from ?? this.currentStar()); + /** + * Departure falls back to wherever the view already is, so one field is usually enough — but only + * while the field is empty. Text left in it that names no chosen star used to fall back all the + * same, so the panel read "Sol" and the route left from whatever the view had since flown to. + */ + private readonly departure = computed(() => this.chosen().from ?? (this.typed().from ? null : this.currentStar())); readonly canPlot = computed(() => this.departure() !== null && this.chosen().to !== null); readonly rangeLabel = computed(() => formatParsecs(this.rangePc()));