Skip to content

Converge the WebView2 environment contract and stop the breadcrumb host stranding its buffers on failure - #910

Merged
drmoisan merged 13 commits into
mainfrom
bug/breadcrumb-webview2-init-fails-resource-not-in-correct-state-792
Sep 18, 2026
Merged

drmoisan merged 13 commits into
mainfrom
bug/breadcrumb-webview2-init-fails-resource-not-in-correct-state-792

Conversation

@drmoisan

Copy link
Copy Markdown
Owner

Converge the WebView2 environment contract and stop the breadcrumb host stranding its buffers on failure

Summary

  • Converges all three production WebView2 environment creations onto a single owner, so the breadcrumb host, the QuickFiler item preview and the Efc item preview no longer request conflicting environments from one shared user-data folder.
  • Adds a bounded retry to breadcrumb host initialization and a visible error state in the folder area on final failure, replacing a silent return that left the user looking at a blank list.
  • Drains or explicitly discards the pending breadcrumb document and the outbound message queue when initialization fails, instead of stranding both behind a trigger that only fires on success.
  • Routes InitializeBreadcrumbHostAsync failures through the form controller's boundary fault reporter so they reach the user rather than only the log.
  • Carries the already-initialized folder predictor and loaded mail-item helper through the pop-out path, and constructs the viewer on the UI thread.
  • Splits the 1321-line form controller into six compliant files and moves the edited members of two other over-ceiling files into new partials, under a line-conserving gate.

Why

The breadcrumb folder selector is the primary folder-selection surface on the Efc view, and it was presenting an empty list with no error.

The root cause is that three production sites construct CoreWebView2EnvironmentOptions independently while sharing one user-data folder. Two supplied --incognito and one supplied no arguments at all. A shared user-data folder requires agreeing options across every WebView running in that browser process, so the odd site out fails initialization.

The divergence is a regression with a precise origin. Before 2026-08-27 the two preview sites carried a U+2013 EN DASH in their argument literal, which Chromium silently discarded as an unrecognised token, so their effective option set was empty and matched the breadcrumb host's empty set. Correcting that literal to ASCII made the arguments real switches for the first time and created the conflict. The breadcrumb host was never touched by that correction.

On the failure path the damage compounded. The router stashes a pending document and the outbound queue buffers messages, and both are drained by a single method reachable only through the host's CoreInitialized event. That event is raised only on the success path, so a failed initialization left both buffers stranded with no retry, no error document and no user-visible signal. This is one defect with one trigger, not two independent ones.

What Changed

Core fix

  • New QuickFiler/Viewers/WebView2EnvironmentContract.cs owns the user-data folder and the additional browser arguments. Each literal now appears exactly once in the tree.
  • All three creation sites read from that contract. The Efc item site additionally moves onto the IWebViewCoreInitializer seam instead of calling the SDK factory directly, which also makes it testable.
  • EfcFormController.InitializeBreadcrumbHostAsync gains a bounded retry and reports through TryReportBoundaryFault on final failure; the folder-area label carries the visible error state.
  • BreadcrumbBridgeRouter gains an explicit failure notification that clears the pending document, navigates an error banner and discards the outbound queue.
  • The pop-out path carries the initialized folder predictor and mail-item helper forward and adopts them when present.

Structure

  • EfcFormController.cs is split six ways along its existing region boundaries and is now 266 lines. The split is proven by a multiset conservation gate over non-blank lines.
  • Edited members of EfcItemController and QfcCollectionController move into new partials. Both parent files remain over the 500-line ceiling as pre-existing debt this change neither introduces nor resolves, recorded with before and after counts.
  • Seventeen new .cs files each carry a matching Compile item in their owning non-SDK-style project file.

Tests

  • Eight new test files covering the environment contract, the router failure path, the outbound queue, the form controller retry and reporting, the data-model carry and the pop-out carry.

Architecture / How It Fits Together

WebView2EnvironmentContract is the single owner of the environment inputs. The two preview controllers and the breadcrumb host each read it and hand the result to IWebViewCoreInitializer, which forwards to the SDK. Because every caller now supplies identical options for the shared user-data folder, no site can be the odd one out.

On the failure path, EfcFormController.InitializeBreadcrumbHostAsync owns the retry loop and the reporting decision; the host's SDK event handler stays a thin, coverage-exempt notifier. When the retry is exhausted the controller sets the folder-area label and notifies the router, which clears its pending document, navigates a banner and discards the queue. The success path is unchanged.

Verification

Completed

  • CSharpier: Checked 1658 files, exit 0, no write-set file rewritten.
  • .NET analyzers, /t:Rebuild: 0 Warning(s), 0 Error(s). Non-vacuity confirmed by 36 compiler invocations with zero skipped CoreCompile targets.
  • Nullable analysis, /t:Rebuild with warnings as errors: 0 Warning(s), 0 Error(s), same non-vacuity counters.
  • QuickFiler.Test: 1468 of 1468 passed, run in parallel under the repository runsettings.
  • TaskMaster.Test regression sweep: 452 of 452 passed, no new failures against the recorded baseline.
  • Coverage over the QuickFiler package rose from 81.73% to 82.01% line and 78.04% to 78.24% branch. The new contract file is fully covered. Figures are rolled up from class elements rather than read from the document rate attribute.
  • Every regression test was recorded failing before the fix, and each failure was matched against a pre-declared assertion.
  • Ten mutations were applied and each produced its pre-predicted failure, then was reverted with hash-verified restoration. This includes a criterion that was already satisfied before this work and therefore could not be observed failing, for which mutation is the only available instrument.
  • A structural gate over the three creation sites was observed failing before the change and passing after.
  • Manual verification on both entry points was performed by the maintainer against a live Outlook session, with the session log independently checked for initialization failures.

Recommended

  • dotnet tool run csharpier check .
  • msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true
  • msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=true
  • vstest.console.exe QuickFiler.Test/bin/Debug/QuickFiler.Test.dll /Settings:scripts/vscode/TaskMaster.cli.runsettings /InIsolation

Backward Compatibility / Migration Notes

  • No public API is removed or renamed. The environment contract is additive and internal to the QuickFiler project.
  • One member changes accessibility from private to internal so the retry path can be asserted directly; it remains internal to the assembly.
  • The six-way split of the form controller relocates members between files without changing behaviour. Callers are unaffected because all parts declare the same partial class.
  • Seventeen files are added to two project files. A build that does not pick up the project-file changes will fail to compile the new partials.

Risks and Mitigations

  • The environment convergence direction. Converging on --incognito rather than dropping it keeps the preview panes from persisting browsing data. The panes navigate untrusted message HTML and share the user-data folder, so removing the argument would newly persist tracking-pixel cookies and cache. An audit of the three files that generate the breadcrumb document found no use of any browser storage API and no external resource reference, so the breadcrumb document is unaffected by the argument.
  • Retry masking a live conflict. A bounded retry can make a still-broken environment look healthy to a user. The manual verification therefore included a session-log check for initialization failures, which is the only observation that separates a genuine convergence from a masked conflict.
  • Two files remain over the line ceiling. This is pre-existing debt, recorded with before and after counts rather than silently carried. A full split of either is out of scope for a bug fix.
  • Mechanical diff volume. The six-way split dominates the diff. It is a pure move proven by a conservation gate, so it can be reviewed structurally rather than line by line.

Review Guide

Suggested order:

  1. QuickFiler/Viewers/WebView2EnvironmentContract.cs — the new single owner, 53 lines.
  2. The three creation sites, to confirm each reads the contract: the breadcrumb host, the QuickFiler viewer setup, and the new Efc item environment partial.
  3. QuickFiler/Controllers/EfcFormController.Breadcrumb.cs — the retry loop, the reporting decision and the folder-area error state.
  4. QuickFiler/Controllers/BreadcrumbBridgeRouter.cs and BreadcrumbOutboundQueue.cs — the failure notification and the explicit discard.
  5. The carry path: EfcDataModel.Carry.cs and QfcCollectionController.PopOut.cs.
  6. The new test files.

Mechanical and safe to skim: the five EfcFormController.* split parts and the project-file Compile item additions. The split conserves every non-blank line of the original.

Follow-ups

  • The two parent files that remain over the line ceiling are candidates for a dedicated split.
  • A pre-existing analyzer package version skew in the project files is tracked separately and is unrelated to this change.

GitHub Auto-close

  • None. No verified closing issue was reported by the context bundle, so no auto-close bullet is emitted here and the tracking issue is resolved by hand.

drmoisan and others added 13 commits September 12, 2026 17:40
…w2-init-fails-resource-not-in-correct-state-792
Re-measured the issue 792 diagnosis against the merged tree and corrected six staleness defects in spec.md that dated from its authoring 229 commits ago.

Research: adds a refreshed diagnosis artifact recording the three environment sites, the single-trigger defect shape, and closing both open questions. Both are answered: the breadcrumb document depends on no persisted browsing storage, so the three sites converge on the incognito argument rather than dropping it; and Designer-set creation properties are inert because the failing control declares none. Records the commit-level provenance for the en-dash correction that first made the arguments diverge, and the AC-U6 vacuity trap in which a text search for the construction syntax matches a commented-out dead line and so passes on the unfixed tree.

Spec: updates two line counts that drifted when issues 742 and 743 merged, in both places they appear; corrects the root-cause citations that were off by one for the same reason, one of which pointed at that dead comment; adds issue 726 context noting the Efc item path is already fault-guarded but its sink has no user-visible path; records that the shared-contract disclaimer covers the error sink only and not the incognito constant, so AC-U6 reverses no documented decision; rewrites the contention and risk sections now that both siblings have merged; and points the research link at the current record.

No implementation. Planning only. Acceptance criteria unchanged, all nine still open.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BRrLF9BAQWhvmtt9BeaoHs
Adds a 93-task, nine-phase atomic plan replacing the stale plan written 229 commits ago. Planning only; no implementation is executed by this commit.

The plan cleared three preflight rounds, which surfaced 23 defects, eight of them blocking. Notable classes: a regression test that passed on the unfixed tree; two structural gates whose acceptance omitted output lines the gate always prints, so a correct run would have halted; a coverage command passing two parameters the runner does not declare; eight immutability gates anchored to a commit in which the feature folder does not exist; a test writing shared static state that an existing serial class also substitutes; and three coverage sub-gates demanding a ratio their own tests could not reach.

Both forks the earlier spec left open are closed before execution. The breadcrumb document depends on no persisted browsing storage, so the three environment sites converge on the incognito argument rather than dropping it. Designer-set creation properties are inert because the control that fails declares none, so no verification task is needed.

Structural gates are proven non-vacuous by observation rather than by reading: the AC-U6 gate was executed read-only against the unfixed tree and recorded a failing result. The criterion whose behaviour is already satisfied is proven by mutation instead, and is not presented as observed-failing. The manual live-Outlook verification is marked human-executed throughout and is never described as an automated gate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BRrLF9BAQWhvmtt9BeaoHs
The spec mandated a prerequisite audit that the preflight-cleared plan does not contain, because the research record had already answered it. The spec is the authoritative acceptance source, so a reviewer would have read the plan as non-compliant. This closes the fork in all four places the spec carried it: the scope list, settled decision 1, the assumptions, and the open-risk entry.

The evidence is independent of the en-dash correction that first made the arguments diverge. That history establishes when the divergence began and that converging preserves the earlier privacy fix rather than undoing it, but it cannot answer the storage question, because the breadcrumb host has never run under incognito. What answers it is that the document uses no browser storage at all: every stylesheet and script is a compile-time constant emitted inline, a scoped search of the three generating files returns zero matches across thirteen storage and external-resource terms, and the only host-communication surface is the WebView2 message bridge, which incognito does not restrict. All display state is re-pushed from managed code on each initialization.

The zero-match result was checked with a positive control so that a mistyped search could not masquerade as an absence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BRrLF9BAQWhvmtt9BeaoHs
…retry, explicit discard, pop-out carry

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BRrLF9BAQWhvmtt9BeaoHs
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BRrLF9BAQWhvmtt9BeaoHs
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BRrLF9BAQWhvmtt9BeaoHs
@drmoisan
drmoisan merged commit 734112e into main Sep 18, 2026
6 checks passed
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