Skip to content

test(missions): serve SSE in navigation fixtures - #1244

Merged
pascalandr merged 3 commits into
devfrom
fix/mission-navigation-cancellation
Nov 1, 2026
Merged

pascalandr merged 3 commits into
devfrom
fix/mission-navigation-cancellation

Conversation

@pascalandr

@pascalandr pascalandr commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Serve valid text/event-stream responses in both Mission navigation fixtures instead of the generic JSON fallback; preserve the bounded 120-second EventSource readiness wait.
  • Share test(missions): identify owned catalog read cancellations #1247's exact catalog-cancellation mechanism between native-family and cross-navigation via tests/browser/panel-read-fixture.ts. Retain every raw failure and separately classify only the exact production request's owning aborted signal plus native DOMException / AbortError receipt.
  • Bind each identity to its first Playwright request. Genuine same-URL aborts and missing, unknown or reused identities remain failures. Retain all navigation state/write-count, unrelated aborted-fetch and failed-EventSource assertions.

Root cause and justified follow-up

The original fixtures imported the production event transport but replied to /api/events with 200 application/json and {}. Chromium rejects that MIME type; the existing error handler closes the rejected EventSource and schedules reconnection. The SSE contract regression deterministically fails against the JSON stub and passes with the valid SSE comment response. Native session frames continue through the real dispatcher.

Both approved commits were rebased onto dev 186d8fc6f, preserving #1246/#1249 bounded Mission reads, #1247's native-family cancellation correction and #1248's Monaco readiness fix. The only conflict was adjacent guidance in dev-docs/BROWSER_TESTING.md; both the Monaco readiness and SSE bullets were retained.

The initial rebased head 2bc24cd79001a77ff679dd5d56bcb62b1a0f5925 exposed three cross-navigation failures on /api/panel-extensions net::ERR_ABORTED: reader session-aba, preview and preview-aba. Tracing RightPanel -> usePanelExtensions -> panelExtensionsApi.list confirmed the lifecycle handled by #1247: refresh() aborts the previous controller-owned list read when SSE reports connected; disconnect/visibility cleanup also retires demand. #1247's instrumentation was confined to native-family, leaving this PR's touched cross-navigation fixture without exact receipt handling.

This additional fixture correction is included because those failures blocked PR validation. It extracts and reuses the existing mechanism instead of duplicating another collector. Production transport, controller behavior and navigation ownership are unchanged.

Deterministic regression evidence

  • In each of the three affected existing reader paths, hold the initial SSE response, complete external navigation, start and hold a real controller-owned catalog read, then release the native EventSource response. Its actual open supersedes that pending read.
  • Red: with the held-read/SSE-open control and the original strict collector, all three cases fail the existing empty-network-errors assertion with /api/panel-extensions net::ERR_ABORTED, before page cleanup.
  • Green: the same cases pass using the shared mechanism. Each asserts its unique identity, true owning-signal/native-AbortError receipt, raw failure retention and classified cancellation, then retains its original unchanged state/write-count, selected-tab and error assertions.
  • Negative controls retain failures for a real catalog read aborted by the network, same-URL reads with missing or unknown identities, and a second request reusing a known-cancelled identity. Existing unrelated ERR_ABORTED and failed-EventSource controls also pass.
  • Targeted green controls: 4/4. Three full navigation runs captured 30 unchanged before/after records, including nine forced SSE-open cancellations, with zero page errors and unexpected network failures. Raw cancellations remain recorded.

Validation at a4fd2e0177b34d8a141c45043120abaec48c6b72

Windows / Chromium 153, serial browser execution, no automatic retries.

From packages/ui:

node --import tsx --test --test-concurrency=1 --test-timeout=900000 tests/browser/mission-cross-navigation.test.ts tests/browser/mission-navigation.test.ts

Three complete runs, 38/38 each: cross-navigation 14/14 and navigation 24/24; 114 passing checks.

node --import tsx --test --test-concurrency=1 --test-timeout=900000 tests/browser/mission-native-family.test.ts tests/browser/git-history.test.ts tests/browser/fixture-wiring.test.ts tests/browser/fixture-navigation-budget.test.ts

37/37: native-family 12/12, Git-history 21/21, fixture wiring 1/1 and navigation budget 3/3. Includes #1247's existing cancellation controls and #1248's deferred computed-diff/native-selection regression.

  • npm run typecheck --workspace @codenomad/ui — pass.
  • npm run typecheck --workspace @neuralnomads/codenomad — pass.
  • git diff --check — pass.
  • Evidence: .codenomad/validation/pr1244/run-1, run-2, run-3 in the dedicated worktree; all 30 JSON records independently checked for identical before/after state and empty page/unexpected-network-error arrays.

Total full-suite validation: 151/151 passing checks, plus 4/4 targeted green controls. Existing assertions and timing budgets retained; no sleeps or retries added. Console subprocesses hidden. No production behavior or public help changes. Pushed with the exact lease against 2bc24cd79001a77ff679dd5d56bcb62b1a0f5925; no merge, review, CI manipulation or shared OpenCode daemon changes.

@pascalandr pascalandr left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: Changes requested

Severity counts: P0: 0 | P1: 0 | P2: 1 | P3: 0

Reviewed head 735a394c7c9d3ac19803db5dab053ac9192f0bb2 against freshly fetched dev (abb14f83c6168b9eb04001359574e5d31ecc72d0).

[P2] Preserve the startup navigation budget for the new response wait

Location: packages/ui/tests/browser/mission-cross-navigation.test.ts:65-68 (especially line 66).

page.waitForResponse() uses Playwright's ordinary 30-second timeout, whereas armFixtureNavigationBudget(browser) only raises navigation timeouts to 120 seconds. Starting this wait alongside page.goto() makes every cross-navigation setup fail at 30 seconds if a cold fixture takes longer to reach its first EventSource request, even though the navigation remains within the explicitly supported startup budget. This reintroduces the cold-start failure that the shared navigation-budget helper documents and handles.

Reproduction: In an external scratch copy of this exact test, delay only the initial document route by 31 seconds, preserving the valid SSE response, real production imports and all assertions. The current code fails with page.waitForResponse: Timeout 30000ms exceeded while waiting for event "response". The same control with only { timeout: 120_000 } added to this response wait passes in approximately 49 seconds, including both network-failure probes. No reviewed worktree files were changed.

Fix: Give the initial SSE response wait the same explicit 120-second startup budget as the navigation (or share that existing budget), while retaining its predicate and strict network assertions.

Transport and regression assessment

  • The SSE correction itself is sound. Both fixtures previously let /api/events reach the JSON fallback. The actual singleton lifecycle is serverEvents -> connectWorkspaceEvents -> serverApi.connectEvents -> native EventSource; MIME rejection reaches attachEventSourceHandlers, then the existing disconnect/reconnect path closes the source. Before testing the corrected head, removing only the SSE branch in a scratch copy makes the new regression fail deterministically with application/json !== text/event-stream and Chromium's exact MIME-rejection diagnostic. The precise historical intermittent ERR_ABORTED was not reproduced by this contract control.
  • Both changed routes return finite SSE comments through route.fulfill, matching established fixtures and leaving no held server response. Test pages/contexts own the production source and reconnect timers; the additional failing EventSource explicitly closes itself. Held navigation routes are released before page cleanup, and the owned-server teardown checks remain intact.
  • The new regression uses the real shell's EventSource response and actual browser requestfailed events. Its unrelated aborted fetch produces net::ERR_ABORTED, and its native EventSource failure produces net::ERR_FAILED. The existing navigation state/write-count assertions, native session dispatcher, and narrow pre-existing cancellation exceptions are unchanged; no broad network-error suppression was added.

Validation

  • node --import tsx --test --test-concurrency=1 tests/browser/mission-cross-navigation.test.ts tests/browser/mission-navigation.test.ts: three runs, 37/37 each (111/111 passed) on the unchanged reviewed head.
  • Additional scratch custody control: the actual SSE response's finished() resolves without error, and the owned browser context count is 0 after page cleanup; both strict network probes still pass.
  • fixture-wiring.test.ts: 1/1 passed.
  • fixture-navigation-budget.test.ts: 3/3 passed.
  • fixture-shutdown.test.ts: 7/7 passed, including actual failed/partial requests, drained optimizer contexts and owned-cache cleanup.
  • event-source-handlers.test.ts and server-events-lifecycle.test.ts: 6/6 passed.
  • UI npm run typecheck --workspace @codenomad/ui: passed.
  • Server npx tsc --noEmit -p tsconfig.json: passed.
  • git diff --check: passed. Branch/head and worktree cleanliness preserved. Windows, Node 25.2.1, Chromium 153.0.8010.12.

@pascalandr pascalandr left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: Approve

Severity counts: P0: 0 | P1: 0 | P2: 0 | P3: 0

Reviewed exact head 0e5820489aeda482369157eaf530e63e26a6fcb1 against latest fetched dev (abb14f83c6168b9eb04001359574e5d31ecc72d0) and the previously reviewed head. No new defects found.

Previous P2: closed

packages/ui/tests/browser/mission-cross-navigation.test.ts:66 now gives the initial SSE response wait an explicit timeout: 120_000, matching the existing navigation startup budget. This is the only change since the first review. The response predicate, navigation assertions and strict network-error collectors remain unchanged.

I regenerated the delayed-start control from this exact head in an external scratch file: delaying only the initial document route by 31 seconds now passes the targeted regression in approximately 54 seconds, including detection of both unrelated net::ERR_ABORTED and genuine EventSource net::ERR_FAILED. The corresponding control in the first review failed at the ordinary 30-second response timeout. This verifies closure of the finding without changing the reviewed worktree.

Validation

  • Both affected suites together: 37/37 passed, including the targeted SSE/network regression, navigation ownership checks and owned-server cleanup assertions.
  • Delayed-start targeted regression: 1/1 passed.
  • Fixture wiring and navigation-budget checks: 4/4 passed.
  • UI typecheck: passed.
  • git diff --check: passed.

The SSE fixture correction and its strict failure-detection coverage remain sound. Branch fix/mission-navigation-cancellation, exact head and worktree cleanliness were preserved; no repository edits, commits, pushes, merge or shared-daemon operations were performed. Validation used Windows and Chromium 153.0.8010.12.

@pascalandr pascalandr left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: Approve — P0: 0 | P1: 0 | P2: 0 | P3: 0

Evidence: 151/151 full-suite checks passed, plus 4/4 targeted cancellation controls, a delayed-start control and a streaming-body control. Expected-negative controls rejected the raw cancellation collector, malformed SSE MIME type and removal of exact request binding.

Reviewed exact head a4fd2e0177b34d8a141c45043120abaec48c6b72 against freshly fetched/current dev (186d8fc6fa1e979c531af1a5531f039eaafc2b4a). No actionable defects found.

Ownership and failure detection

  • Traced the production path through RightPanel -> usePanelExtensions -> panelExtensionsApi.list -> authenticatedFetch and response.json(). Connected status refreshes supersede the previous controller-owned read; disconnect and disposal abort that same demand. Production behavior is unchanged.
  • The shared helper requires the exact GET URL, net::ERR_ABORTED, a fixture identity bound to its first Playwright Request, and that call's owning aborted signal plus native DOMException / AbortError rejection. Missing/unknown identities fail their receipt check; another request reusing an identity cannot match its bound Request. Every raw failure is retained, and pending classifications are drained before assertions.
  • Reproduced all three forced native-SSE-open cases with an unconditional raw-failure collector in an external scratch copy: all three fail the existing empty-network-errors assertion. The unchanged head passes those cases and the genuine same-URL, missing/unknown/reused identity rejection test: 4/4. Removing only the exact Request binding in a scratch helper makes the reused-identity assertion fail, verifying that guard is exercised.
  • A separate scratch control used the actual shell/controller, shared observer and a real HTTP server returning partial JSON. After confirming Response.json() had started, controller supersession produced net::ERR_ABORTED, a true owning receipt and exactly one classified cancellation. Destroying a subsequent partial response instead produced net::ERR_INCOMPLETE_CHUNKED_ENCODING, a false cancellation receipt and a retained failure. The owned context closed and the fixture HTTP server was closed afterward.
  • Observation state and listeners are scoped to each fixture page; the temporary fetch wrapper is restored synchronously in finally, including while body consumption remains pending. Held routes are released during cleanup. The shared helper and its window bridge are confined to tests/browser; production source exposes no new test API.

SSE, navigation and rebase

  • Both navigation fixtures still provide valid finite SSE comments, and the initial response wait retains its explicit 120-second budget. A scratch JSON-response control fails the SSE MIME assertion; a 31-second delayed-document control passes the real regression in approximately 50 seconds, including unrelated ERR_ABORTED and genuine EventSource ERR_FAILED detection.
  • Real components/native session dispatch remain in use. Existing navigation state/write-count, selected-tab and page-error assertions are preserved. The new panel-read classification is shared with native-family rather than duplicated; existing unrelated cancellation exceptions remain unchanged.
  • The documentation conflict retained both the computed-Monaco-diff readiness guidance and the SSE convention. The Git-history suite, including deferred worker-result/native-selection coverage, passes.

Commands and results

From packages/ui, with serial browser execution:

  • Navigation suites (mission-cross-navigation.test.ts, mission-navigation.test.ts): three runs, 38/38 each — 114/114.
  • Native-family plus fixture wiring/navigation-budget checks: 16/16.
  • Git-history: 21/21.
  • Targeted catalog/native-SSE-open controls: 4/4.
  • Additional delayed-start and streaming-body scratch controls: 2/2 after preparing the scratch harness.
  • UI and server workspace typechecks: passed.
  • git diff --check: passed.

Branch fix/mission-navigation-cancellation, exact head and worktree cleanliness are preserved. No repository edits, commits, pushes, merge, CI actions or shared OpenCode daemon operations were performed. Scratch controls and logs are outside the checkout. Validation used Windows / Chromium 153.0.8010.12.

Oversized modified files: none under the repository's source/test thresholds. The largest modified test is packages/ui/tests/browser/mission-native-family.test.ts, approximately 645 lines; the new shared helper is 58 lines.

@pascalandr

Copy link
Copy Markdown
Contributor Author

Rebased onto latest dev fb44655cdde57efc767ebe5e068c0e024b1d2ae3 after #1217 merged. New head: 9776aed7d3877344955cc164dc1a81b7d88bef20.

  • No conflicts. All three approved commits are equivalent (git range-diff marks each =); all seven PR files, including documentation, are byte-for-byte unchanged from a4fd2e0177b34d8a141c45043120abaec48c6b72. Old and new complete diff patch IDs both equal c5754aec118fa7ab3dc331401eb1a23518915bbb.
  • scripts/test-subagent-nesting-native.mjs is tracked at HEAD with dev's blob 14b9d6e9f3c28817110a8326422787c9aa788378, exists locally, and passes node --check. The reference in .github/workflows/pr-build.yml:217 resolves.
  • Fresh targeted serial browser validation: 54/54 (cross-navigation 14, navigation 24, native-family 12, fixture wiring 1, navigation budget 3). Includes the held-read/native-SSE cancellation and wrong-identity/genuine-abort controls.
  • UI and server workspace typechecks pass; diff whitespace check passes; working tree clean. The earlier three full navigation runs and Git-history regression results remain documented against the equivalent approved patch.
  • Pushed with the exact force lease against a4fd2e0177b34d8a141c45043120abaec48c6b72. No merge, review or CI actions; console subprocesses hidden and shared daemon untouched.

The navigation fixtures import the production event transport but let
/api/events fall through to their generic JSON response. Chromium rejects
that MIME type and the normal reconnect handler closes the source; a body
cancellation can then appear as an unrelated navigation network failure.
The same malformed response is present on dev and PR #1217, whose reported
assertion runs before page cleanup.

Use the existing finite SSE-comment fixture convention in both navigation
setups. Observe the actual native EventSource response during setup and
check its status and MIME type in a real-shell browser regression. Keep
the network collector unchanged and prove that an unrelated ERR_ABORTED
fetch and a genuinely failed EventSource remain recorded. Native session
frames continue through the real dispatcher; production behavior is intact.

The new contract assertion fails against the old application/json stub.
Both navigation suites pass three consecutive serial runs (37 tests each),
including captured unchanged session/preview state and empty failure lists.
Fixture-wiring, UI and server typechecks and diff whitespace checks pass.
Give the initial native EventSource response wait the same explicit 120-second budget as fixture navigation. Playwright action waits otherwise stop setup at 30 seconds even when a cold document is still within the supported navigation budget. Keep the exact response predicate, strict error assertions and post-startup failure-probe timeouts unchanged; the sibling navigation fixture adds no response wait.

An isolated 31-second delayed-document control fails with the old 30000ms response timeout and passes with this one-line change in about 46 seconds, including both network probes. Both navigation suites, navigation-budget and fixture-wiring tests pass together (41/41), UI typecheck passes, and diff whitespace checks pass. The diagnostic control is temporary; no sleeps, retries or error exclusions are added.
The cross-navigation shell imports the real panel-extension controller. Its
SSE connection supersedes an optional pending catalog read, so treating every
ERR_ABORTED as a network defect fails the reader session-ABA and preview
navigation checks before page cleanup. A held production read followed by an
actual native EventSource open reproduces all three failures deterministically.

Extract the native-family fixture's existing request-identity, owning aborted
signal and native DOMException AbortError observation into one shared fixture
module. Both collectors retain every raw failure and separately record only
verified catalog cancellations. Bind each identity to its first Playwright
Request so another request reusing a valid cancelled identity remains a failure.
Production transport, controller behavior and navigation ownership are intact.

Gate the initial SSE response in the three affected reader paths, hold the
controller-owned catalog request, then open the real stream and prove its
cancellation receipt alongside the original state/write-count assertions.
Negative controls retain same-URL network aborts, missing and unknown identities,
reused valid identities, unrelated aborted fetches and failed EventSources.
Drain receipt classifications before asserting; serve the real list's empty
array contract. Document the shared fixture convention without public UI changes.

Validation on Windows / Chromium 153: deterministic red controls fail all three
navigation assertions without classification; green targeted controls pass 4/4.
Both full navigation suites pass three serial runs at 38/38 each. Native-family
passes 12/12, Git-history 21/21, fixture wiring 1/1 and navigation budget 3/3.
UI and server typechecks and whitespace checks pass. Thirty captured navigation
records preserve identical before/after state, including nine forced SSE-open
cancellations with retained raw failures and zero unexpected network/page errors.
@pascalandr
pascalandr force-pushed the fix/mission-navigation-cancellation branch from 9776aed to 1101d18 Compare November 1, 2026 09:59
@pascalandr

Copy link
Copy Markdown
Contributor Author

Rebased onto latest dev 72942a9cd407f16ce6d73edc718fa5cec2d518e6, including #1251 (2c7ff9a71) and #1252 (72942a9cd). New head: 1101d18b37815f5e3c8530211cb6c0f50cbf55e6.

No conflicts. All three approved commits remain equivalent (git range-diff: three = entries), and all seven PR files, including documentation, are byte-for-byte unchanged from 9776aed7d3877344955cc164dc1a81b7d88bef20. Complete diff patch ID remains c5754aec118fa7ab3dc331401eb1a23518915bbb.

Fresh targeted serial browser validation passed 54/54: cross-navigation 14/14, navigation 24/24, native-family 12/12, fixture wiring 1/1, navigation budget 3/3. UI and server workspace typechecks and whitespace checks passed. Application code and assertions were untouched; working tree clean.

Pushed with the exact force lease against 9776aed7d3877344955cc164dc1a81b7d88bef20. No merge, review or CI actions; subprocess consoles hidden and shared daemon untouched.

@pascalandr
pascalandr merged commit c250db6 into dev Nov 1, 2026
26 checks passed
@pascalandr
pascalandr deleted the fix/mission-navigation-cancellation branch November 1, 2026 10:03
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