Skip to content

test(shell): rule out two causes for #38, guard the reported sequence - #64

Merged
0xMMA merged 1 commit into
mainfrom
fix/router-stuck-settings
Sep 18, 2026
Merged

0xMMA merged 1 commit into
mainfrom
fix/router-stuck-settings

Conversation

@0xMMA

@0xMMA 0xMMA commented Sep 18, 2026

Copy link
Copy Markdown
Owner

Refs #38 — not a fix. The bug did not reproduce here; this is what the investigation found, so the next attempt starts further along.

Two of the three hypotheses are dead

"An exception thrown during destroy keeps the outgoing component attached." That was the most plausible one, and it is wrong on this Angular version. The guess was that RouterOutlet.deactivate() aborts before clearing its reference, leaving the old DOM for the next activation to land underneath. Measured instead: the throw takes the whole navigation down and the outlet ends up empty. A blank content area and a stuck page are different bugs. shell-routing.spec.ts asserts both directions so nobody has to guess again.

"A pending await resolving into a destroyed view." settings.component.ts carried the comment "refreshing a destroyed view throws", which is what made this look live — ngOnInit awaits five Wails calls before touching the view, and over the desktop bridge each is an IPC round trip. Measured on Angular 21: detectChanges() on a destroyed view is a no-op. The comment is corrected and now points at the test, because that one wrong sentence sent the investigation after a phantom. A test also drives the real race — leave Settings before its first Wails call answers — and nothing throws.

That leaves the animation hypothesis, which is the one I cannot test here: provideAnimationsAsync() loads a stub under Vitest and the real engine in a browser, and Chromium's engine is not WebView2's.

What ships as a guard

  • Playwright spec walking the exact reported sequence: version footer → every Settings tab → Pyramidize → Fix, asserting exactly one routed page in the DOM at each step and no application errors. Passes on Linux/Chromium, so it cannot confirm the bug — but it fails if anyone introduces it here. (It filters the ng serve resource 404s, which are the missing Wails bridge and not app errors.)
  • A second case for the remaining hypothesis in the issue: clicking the version while already on Settings, which is the one path where the router reuses the component rather than recreating it. Also clean.
  • Vitest spec driving the real router through the real shell for the same sequence — the fastest place a regression would surface.

What I tried and what it would take

Reproduced on Linux, headless Chromium, against ng serve: the page always swaps. The report is Windows, WebView2, a dev build from PR #31. Confirming the animation hypothesis needs that combination — specifically whether a PrimeNG tab-panel :leave animation fails to complete under WebView2, which has precedent in this repo (the StepPanel wizard, see CLAUDE.md).

Concretely, next step on a Windows box: run the sequence in wails3 dev, and check whether document.querySelectorAll('.settings-page').length > 1 afterwards and whether any element still carries an ng-animating class.

Verification

  • npm test — 193 passed (13 files); npx tsc --noEmit clean; npm run build clean
  • npx playwright test router-leave-settings — 2 passed locally, headless
  • No production behaviour changed: one comment corrected, two spec files added

🤖 Generated with Claude Code

#38 says Settings stays on screen after the version click and the next page is
appended below it. Reported on Windows; not reproduced here, and this commit is
the evidence rather than a fix.

Two of the issue's three hypotheses are now ruled out by test:

An exception during destroy does NOT produce that symptom. The intuition was
that RouterOutlet.deactivate() aborts before clearing its reference, leaving the
old DOM for the next activation to land under. On this Angular version the throw
takes the whole navigation down instead and the outlet ends up EMPTY — a blank
content area, not a stuck page. shell-routing.spec.ts measures both directions.

And a destroyed view does not throw when refreshed. settings.component.ts
carried a comment saying it does, which is what made "a pending await resolving
into a destroyed view" look like a live lead. Measured on Angular 21: it is a
no-op. The comment now says so, and points at the test, because that wrong
sentence cost an investigation.

What is left as a guard rather than a diagnosis:

- A Playwright spec walking the exact reported sequence — version footer, every
  Settings tab, then two further navigations — asserting exactly one routed page
  in the DOM and no application errors. It passes on Linux/Chromium, so it
  cannot confirm the bug, but it fails if anyone introduces it here.
- A second case for the issue's remaining hypothesis: clicking the version while
  already on Settings, which is the one path where the router reuses the
  component instead of recreating it. Also clean.
- A Vitest spec driving the real router through the real shell for the same
  sequence, which is where a regression would show up first.

What that leaves: the animation hypothesis, on WebView2. `provideAnimationsAsync()`
loads a stub under Vitest and the real engine in a browser, and Chromium's engine
is not WebView2's. Confirming or killing it needs the Windows build.

Refs #38

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@0xMMA
0xMMA merged commit 67a1167 into main Sep 18, 2026
4 checks passed
@0xMMA
0xMMA deleted the fix/router-stuck-settings branch September 18, 2026 04:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant