From be6c1c3b3504edf28784fa4414a09f37cf7f1e9a Mon Sep 17 00:00:00 2001 From: Dan Moisan Date: Sat, 12 Sep 2026 17:40:16 -0400 Subject: [PATCH 01/12] #792 preparation: feature documents, research and preflight-cleared atomic plan --- .../issue.md | 114 ++ .../plan.2026-09-12T13-21.md | 676 +++++++++++ ...10-30-breadcrumb-webview2-init-research.md | 1065 +++++++++++++++++ .../spec.md | 390 ++++++ .../user-story.md | 86 ++ 5 files changed, 2331 insertions(+) create mode 100644 docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/issue.md create mode 100644 docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/plan.2026-09-12T13-21.md create mode 100644 docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/research/2026-09-12T10-30-breadcrumb-webview2-init-research.md create mode 100644 docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/spec.md create mode 100644 docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/user-story.md diff --git a/docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/issue.md b/docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/issue.md new file mode 100644 index 000000000..935506df7 --- /dev/null +++ b/docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/issue.md @@ -0,0 +1,114 @@ +# breadcrumb-webview2-init-fails-resource-not-in-correct-state (Issue #792) + +- Date captured: 2026-09-06 +- Author: Dan Moisan +- Status: Promoted -> docs/features/active/breadcrumb-webview2-init-fails-resource-not-in-correct-state/ (Issue #792) + +> Automation note: Keep the section headings below unchanged; the promotion tooling maps each of them into the GitHub bug issue template. + +- Issue: #792 +- Issue URL: https://github.com/drmoisan/TaskMaster/issues/792 +- Last Updated: 2026-09-06 +- Work Mode: full-bug + +## Summary + +The breadcrumb `CoreWebView2` initialization fails intermittently with HRESULT 0x8007139F ("The group or resource is not in the correct state to perform the requested operation"), logged by both `WebView2BreadcrumbHost` and `EfcFormController`. The failure is logged and swallowed; the session continues with a breadcrumb host that never initialized, and a later `BreadcrumbUiDispatcher` dispatch fails in the same session. Because the #677 keyboard-lock mechanism is WebView2 focus retention, a half-initialized WebView2 is a plausible contributor to the sporadic keyboard lock, but that link is unconfirmed. + +## Environment + +- OS/version: Windows 11 Pro 10.0.26200 +- Python version: n/a (C# / .NET Framework 4.8 VSTO add-in) +- Command/flags used: QuickFiler launched from the ribbon (High Confidence button); add-in loaded from `TaskMaster\bin\Debug` built 2026-09-06 08:51 from `7c8ac9ae` +- Data source or fixture: live Outlook Inbox view + +## Steps to Reproduce + +1. Launch QuickFiler from the ribbon several times in one Outlook session. +2. Inspect `TaskMaster\bin\Debug\logs\debug_.log` for `Breadcrumb CoreWebView2 initialization failed`. +3. Observe that the failure occurs on some launches (2 of 6 today: 08:55:22 and 10:06:51) and not others. + +Not reproducible on demand. + +## Expected Behavior + +WebView2 initialization either succeeds, or fails with a clear surfaced error and a defined fallback state that cannot retain keyboard focus. A failed initialization should be retried or the host disposed, not left half-constructed. + +## Actual Behavior + +Two ERROR lines per occurrence (`WebView2BreadcrumbHost - Breadcrumb CoreWebView2 initialization failed: ... (HRESULT: 0x8007139F)` and `EfcFormController - Breadcrumb WebView2 initialization failed: ...`), then normal operation continues. A `BreadcrumbUiDispatcher - Breadcrumb UI dispatch failed.` error followed at 09:01:56 in the same session. + +## Logs / Screenshots + +- [x] Attached minimal logs or screenshot +- Snippet (`debug_2026-09-06.log`): + +``` +2026-09-06 08:55:22,227 [VSTA_Main] ERROR QuickFiler.Viewers.WebView2BreadcrumbHost - Breadcrumb CoreWebView2 initialization failed: ... (HRESULT: 0x8007139F) +2026-09-06 08:55:22,286 [VSTA_Main] ERROR QuickFiler.Controllers.EfcFormController - Breadcrumb WebView2 initialization failed: ... (HRESULT: 0x8007139F) +2026-09-06 09:01:56,237 [VSTA_Main] ERROR QuickFiler.Viewers.BreadcrumbUiDispatcher - Breadcrumb UI dispatch failed. +2026-09-06 10:06:51,594 [VSTA_Main] ERROR QuickFiler.Viewers.WebView2BreadcrumbHost - Breadcrumb CoreWebView2 initialization failed: ... (HRESULT: 0x8007139F) +2026-09-06 10:06:51,661 [VSTA_Main] ERROR QuickFiler.Controllers.EfcFormController - Breadcrumb WebView2 initialization failed: ... (HRESULT: 0x8007139F) +``` + +## Impact / Severity + +- [ ] Blocker +- [x] High +- [ ] Medium +- [ ] Low + +High (raised 2026-09-06, see Update below; originally Medium): the breadcrumb folder selector is unavailable on affected launches, and the half-initialized control is a candidate contributor to the sporadic Outlook keyboard lock tracked under the sibling QuickFiler Cancel-teardown issue filed the same day. + +## Suspected Cause / Notes + +- 0x8007139F (`ERROR_INVALID_STATE`) from `CoreWebView2Environment`/`EnsureCoreWebView2Async` typically indicates the control was initialized while its handle or parent was not yet in a valid state, or a second initialization was attempted against a control already mid-initialization or disposed. Both `WebView2BreadcrumbHost` and `EfcFormController` log the same failure, suggesting the initialization is attempted from two paths. +- Files to inspect: `QuickFiler/Viewers/WebView2BreadcrumbHost.cs`, `QuickFiler/Controllers/EfcFormController.cs` (breadcrumb initialization), `QuickFiler/Viewers/BreadcrumbUiDispatcher.cs`, and the pooled-viewer handler-retention history in `docs/features/potential/promoted/2026-08-07-webview2breadcrumbhost-handler-retention-pooled-viewer.md`. +- Related: #677 (keyboard hook leak; WebView2 focus retention mechanism), the sibling potential entry `2026-09-06-quickfiler-high-confidence-cancel-teardown-and-deadline-defects.md`. + +## Proposed Fix / Validation Ideas + +- [ ] Unit coverage areas: initialization state machine of `WebView2BreadcrumbHost` (single initialization, guard against re-entry, disposed-host guard, failure leaves a defined non-focusable state). +- [ ] Integration scenario to retest: repeated QuickFiler launches in one session; confirm no `0x8007139F` and that a failed initialization cannot retain focus. +- [ ] Manual verification notes: live-Outlook log review across several launches. + +## Next Step + +- [x] Promote to GitHub issue (bug-report template) +- [ ] Move to active fix folder / branch + +## Update 2026-09-06: reproduces on every Efc open via pop-out and Sort Email; severity raised to High + +Two user-visible symptoms reported today are this failure: + +1. **Pop-out from a QfcItem to an EfcItem shows an empty folder list.** No suggestions, no banners, and typing a search string does nothing. +2. **Ribbon -> Sort Email opens an EfcViewer whose "Matched Folders:" section has no entries.** The label is a static WinForms label above the breadcrumb WebView2, which is why it survives while the list is blank. + +### Log evidence + +`TaskMaster\bin\Debug\logs\debug_2026-09-06.log` records the paired `WebView2BreadcrumbHost` / `EfcFormController` initialization failure with HRESULT 0x8007139F on every Efc open in the session: ten pop-out opens between 17:39:00 and 17:41:42, three at 19:04-19:06, and the Sort Email open at 19:56:36 (the `SortEmail_Click` stack at 19:56:36,171 is followed by the failure at 19:56:37,940). Today the failure is deterministic on the Efc entry points, not intermittent as originally recorded. + +``` +2026-09-06 19:56:37,940 [VSTA_Main] ERROR QuickFiler.Viewers.WebView2BreadcrumbHost - Breadcrumb CoreWebView2 initialization failed: The group or resource is not in the correct state to perform the requested operation. (Exception from HRESULT: 0x8007139F) +2026-09-06 19:56:38,004 [VSTA_Main] ERROR QuickFiler.Controllers.EfcFormController - Breadcrumb WebView2 initialization failed: The group or resource is not in the correct state to perform the requested operation. (Exception from HRESULT: 0x8007139F) +``` + +### Why the list is blank rather than degraded + +- `BreadcrumbBridgeRouter.DeliverDocument` (`QuickFiler\Controllers\BreadcrumbBridgeRouter.Selection.cs:168-180`) stashes the rendered document in `_pendingDocument` when `_host.IsCoreInitialized` is false. `WebView2BreadcrumbHost` (`:330-342`) returns on `!e.IsSuccess` without ever raising `CoreInitialized`, so the pending document is never navigated. There is no fallback rendering and no retry. +- `EfcFormController.InitializeBreadcrumbHostAsync` (`:1071-1081`) and `PopulateFolderCombobox` (`:1250-1271`) are fire-and-forget tasks with total catch blocks, so the failure is log-only. +- `EfcFormController.BindBreadcrumbRowsAsync` (`:1115-1118`) reads `_globals.Ol.ArchiveRootPath` unguarded; `TryGetArchiveRoot` (`EfcDataModel.cs:280-297`) is not used on the bind path. + +### Additional latent defect on the pop-out path + +`QfcCollectionController.PopOutControlGroup` (`QuickFiler\Controllers\QfcCollectionController.cs:710-735`) hands only the raw `MailItem` and `_globals` to `new EfcHomeController(...)` via the synchronous constructor, so the Efc view rebuilds prediction from scratch with an unloaded `MailItemHelper` (`EfcDataModel.cs:48-81` vs `CreateAsync` `:89-142`). The in-QuickFiler carry pattern from #678 (`QfcItemController.FolderHandling.cs:68-83`, `_carriedFolderHandler`) is not applied to the pop-out. `EfcViewerQueue.BuildQueue` has no production call site, so `EfcViewer` is always constructed inline on the calling thread and captures `SynchronizationContext.Current` as-is (`EfcViewer.cs:23-30`); if the pop-out continuation lands off the UI thread, `UiThread.SynchronizationContextAwaiter` throws on the null context (`UiThread.cs:91-98`) inside the same swallowed tasks. + +### Additional acceptance criteria (settled with the maintainer 2026-09-06) + +- [ ] AC-U1: A failed `CoreWebView2` initialization is retried, and on final failure the Efc view shows a visible error state in the folder area instead of a blank list. +- [ ] AC-U2: `_pendingDocument` is never silently dropped: it is delivered when initialization later succeeds or an error is surfaced. +- [ ] AC-U3: The pop-out path carries the already-initialized folder predictor and loaded `MailItemHelper` from the QfcItem, following the #678 carry pattern, and constructs the `EfcViewer` on the UI thread. +- [ ] AC-U4: `PopulateFolderCombobox` and `InitializeBreadcrumbHostAsync` report failures through `TryReportBoundaryFault` to the user, not log-only. +- [ ] AC-U5: Manual verification on both entry points: pop-out from QuickFiler and ribbon Sort Email each show suggestion rows and respond to typed search. + +Severity: raised from Medium to High. Both Efc entry points are unusable for folder selection in the affected sessions. diff --git a/docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/plan.2026-09-12T13-21.md b/docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/plan.2026-09-12T13-21.md new file mode 100644 index 000000000..66f5de8ca --- /dev/null +++ b/docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/plan.2026-09-12T13-21.md @@ -0,0 +1,676 @@ +# 2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state (Plan) + +- **Issue:** #792 +- **Kind:** bug +- **Work Mode:** full-bug +- **Owner:** drmoisan +- **Last Updated:** 2026-09-12T13-21 +- **Status:** Draft +- **Version:** 1.3 (preflight revision round 3, prose-only, applied 2026-09-12 in place; see "Preflight round 3 revision record" below. Version 1.2 was preflight revision round 2 applied 2026-09-12 in place; every citation the round touched was re-derived against commit 2405a829d; see "Preflight round 2 revision record" below. Version 1.1 was the adversarial self-review pass, corrections 7 through 13 and the acceptance-condition repairs listed under "Self-review revision record") +- **Acceptance-criteria source:** `docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/spec.md`, section `## Acceptance Criteria`, AC-U1 through AC-U9. No other document supplies acceptance criteria for this item. +- **Base anchor:** commit `2405a829d6afd3b12eb7c228d57158a97cb4e2ca`. Every anchored diff in this plan uses that SHA as its ref operand. +- **Fixed artifact timestamp token:** `2026-09-12T13-21`. Every evidence artifact this plan names uses that token so its path is deterministic and checkable. + +## Task counts (mechanical) + +Counted as lines matching the task-line form `- [ ] [P#-T#]`. + +| Phase | Tasks | +| --- | --- | +| Phase 0 | 11 | +| Phase 1 | 14 | +| Phase 2 | 10 | +| Phase 3 | 17 | +| Phase 4 | 19 | +| Phase 5 | 5 | +| Phase 6 | 20 | +| **Total** | **96** | + +## Fail-closed evidence rules + +**Evidence location.** Every evidence artifact resolves under `docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/evidence//` where `` is one of baseline, regression-testing, qa-gates, issue-updates, other. No artifacts directory is used for evidence. That directory does not yet exist in the tree and is created by P0-T1. + +**Evidence accounting.** Every evidence-producing task names its artifact path on the task line. A task is not complete without the artifact, and every command-step artifact carries `Timestamp:`, `Command:`, `EXIT_CODE:` and `Output Summary:`. + +**Projections only.** Per the maintainer decision on issue 671 of 2026-09-11, no test-result file and no raw coverage file is committed. No task in this plan passes a trx logger. The coverage runner writes raw Cobertura into the repository-ignored coverage directory named by its own default output parameter (the ignore rule is the coverage/* entry at line 144 of the root ignore file); the numeric figures are transcribed into the Markdown evidence artifacts and the raw file is left uncommitted. + +**Commit-record artifacts.** A task that both commits and records that commit in an artifact cannot satisfy an empty-porcelain clause unless the record is folded into the commit. Every such task (P0-T11, P5-T5, P6-T20) therefore commits, writes its record, stages the record with the same scoped pathspec and amends the commit it just made with `git -C . commit --amend --no-edit`. The amend touches only that task's own commit; no task amends a preceding task's commit. + +## Repository discipline this plan encodes + +**Bash allowlist.** Only `git`, `gh`, `pwsh`, `poetry run` and the three allowlisted scripts under the .claude lib directory are permitted, and every chained segment of a command line is checked independently. No task states a command of the form that changes directory and then chains a second command. Git is always invoked as `git -C .` from the worktree root. File inspection uses the Read, Grep and Glob tools, never cat, grep, sed, find or head. + +**No Python toolchain.** This repository has no scripts/dev_tools tree and no extensions tree. No task passes a coverage argument to a Python runner and no task references pytest. + +**C# toolchain order.** Format, then analyzer rebuild, then nullable rebuild, then test. Any failure or any file change restarts the loop at format. `dotnet tool restore` runs once before the first CSharpier invocation. No task adds the solution-wide nullable opt-in property, because no project in this repository carries a nullable element and CI deliberately omits it. No gate uses an incremental build target, because a warm incremental build skips compilation and the gate cannot fail. + +**Not SDK-style.** Both project files list every source file explicitly. Every added .cs file gets a bare self-closing Compile element with a backslash-separated project-relative path and no metadata. DependentUpon appears only on Designer and resx pairings in these project files and is never added to a hand-written partial part. Verified anchors, re-derived this pass: the production project file lists the existing partial parts of the breadcrumb router at its lines 291 through 293 and the existing data-model partial pair at its lines 289 and 290, each on its own line with no metadata. + +**Tests.** MSTest, Moq and FluentAssertions. No temporary files. No live Outlook. No Thread.Sleep, no Task.Delay and no wall-clock wait anywhere; the retry's delay is an injected delegate whose production default completes synchronously and which tests replace with a no-op. An MSTest Timeout attribute is a hang guard, not a wait, and is used only where the established pump-hosted tests already use one. + +**Test file location deviation, recorded explicitly.** The cross-language policy in the repository's general unit-test rule file under the .claude rules directory requires a mirrored tests directory tree. This repository does not use a top-level tests directory for C#: the established convention is a per-area folder inside the test project, for example the Controllers and Viewers folders of the QuickFiler test project. Matching the existing style takes precedence, so every test file this plan creates follows the tree convention QuickFiler.Test//Issue792Tests.cs. This deviation is deliberate and is recorded here so a reviewer does not read it as an oversight. + +**Test namespaces, re-derived this pass.** The test project mixes two namespace conventions and each new file follows the file it mirrors: the Viewers tests (WebView2BreadcrumbHostTests.cs line 13) use `QuickFiler.Test.Viewers`; the router queue tests (BreadcrumbBridgeRouterQueueTests.cs line 13) use `QuickFiler.Test.Controllers`; the form-controller, data-model, collection-controller and Efc item-controller tests use `QuickFiler.Controllers.Tests`; the Helper Classes tests (ViewerQueueStaticWrapperTests.cs line 9) use `QuickFiler.Test.HelperClasses`. Each task below names the namespace its file uses. + +**500-line ceiling.** Stated per file in the Write Set table below. The two files named by AC-U9 are over the ceiling before this change and remain over it after; their counts must strictly decrease and are recorded as pre-existing debt. + +**Staging scope.** Every staging span and every porcelain-status span in this plan carries an explicit pathspec limited to the QuickFiler production project directory, the QuickFiler test project directory, and this item's feature folder. No span in this plan reaches the potential-features directory, because this item's diff does not write there. + +**Search-literal defect, verified this run.** Git Bash rewrites a leading-slash argument into a Windows path before git sees it, so a git-side search for a literal beginning with a forward slash returns zero matches and exit 1 against a file that genuinely contains it. No acceptance condition in this plan searches for a literal beginning with a forward slash. Structural searches are stated as Grep tool invocations with an explicit pattern, an explicit path and an expected match count, which avoids shell quoting entirely. + +**Line-count instrument.** Every line count in this plan is the Grep tool run with the pattern `^` in count output mode against the single file. Verified this pass: that instrument returns 101 for `QuickFiler/Helper Classes/EfcViewerQueue.cs`, which is the value the Write Set table records. + +## Execution-phase notes to RECORD, not to act on + +1. The sandbox refuses `pwsh` under Agent worktree isolation, so the execution child for this item must be launched non-isolated. +2. The `atomic-executor` agent definition grants no msbuild and no dotnet Bash permission. Every msbuild step, every `dotnet tool run csharpier` step and every coverage-runner step in this plan must therefore be executed by an agent that holds those permissions. The plan names that handoff rather than assuming the executor can build; see P6-T19. + +## Manual, human-executed steps + +**M1. Outlook must be closed before any rebuild.** Close Outlook through its own normal exit path. Never end the process. A killed process leaves the build output locked and MSBuild fails with a file-lock error or produces a stale assembly. This is a precondition of every msbuild task in this plan and is captured as its own task at P0-T3 and again at P6-T1. + +**M2. AC-U5 is a manual live-Outlook verification.** It is executed by a person against a live Outlook session following the runbook already written in the user story for this item. It is not an automated gate and no acceptance condition in this plan asserts a command exit code for it. Its evidence is a Markdown record carrying the operator, the timestamp, the observed outcome per entry point, and a pass or fail verdict. See P6-T9. + +## Corrections to the input documents, established against the tree this pass + +1. **The AC-U4 combobox half is already fully pinned, not merely incidentally true.** The research record and the spec's test strategy both state that `QuickFiler.Test/Controllers/EfcFormControllerTests.cs` "today asserts only that the method logs once and does not fault". That is false. The method body at lines 300 through 328 of that file already installs a boundary sink counter at line 310 and asserts the count equals 1 at lines 321 through 327. Only the method **name** is stale. The work for that half is therefore a rename, not an assertion addition, and is planned as such at P3-T16. This correction is load-bearing: a plan that added a sink assertion there would have added a duplicate. +2. **`QuickFiler.Test/Controllers/EfcFormControllerTests.cs` is 485 lines**, so it has 15 lines of headroom. A rename is line-neutral and is the only edit that fits without a split. The sibling part of the same partial class is 490 lines and is not edited by this change. +3. **The host seam does not carry the initialization member.** The narrow breadcrumb host interface in the QuickFiler Viewers folder declares only navigate, post, the inbound event and the initialized flag; it has no initialization member. That interface is not in the binding Write Set, so the AC-U1 seam must not be an interface widening. It is an injectable initialization delegate on the form controller instead. See P3-T3. +4. **The Efc item controller holds no core-initializer field.** Its environment creation calls the SDK factory directly. Routing it through the mockable seam therefore requires introducing the seam property in the new partial, which P1-T11 does. +5. **All four other target types are already declared partial** (the collection controller, the home controller, the item controller and the data model). Only the Efc form controller is not; P2-T1 adds the modifier. +6. **The breadcrumb document has no browsing-storage dependence** on the evidence available at authoring time: a case-insensitive search of the QuickFiler Resources breadcrumb HTML file and of the UtilitiesCS folder-breadcrumb renderer and document assets for the six storage tokens returns zero matches. P1-T1 re-establishes this as a recorded verification. +7. **The Efc incognito constant is pinned by a test outside the Write Set.** QuickFiler.Test/Controllers/EfcItemControllerTests.cs lines 372 through 378 declare `IncognitoArgument_IsAsciiDoubleHyphenIncognitoWithTrailingSpace`, which reads the constant `EfcItemController.IncognitoArgument`. Deleting the constant, as the spec's proposed fix implies, would break the test project's compilation from a file this change may not edit. The constant is therefore moved into the new partial as a forwarding constant whose value is the contract's constant, not deleted. See P1-T11. This is also why the D1 fallback branch halts rather than inverting: an empty argument value would fail that pinned test. +8. **The item-controller interface has two implementers outside the Write Set.** QuickFiler.Test/Helper Classes/QfcThemeHelperTests.cs line 337 declares a private fake implementing the interface, and QuickFiler/Legacy/QfcController.cs line 20 implements it (that legacy file is not listed in the production project file and is not compiled, but the test fake is). Adding a member to the interface would break the test build from a file this change may not edit. The pop-out carry therefore pattern-matches the item group's controller to the concrete `QfcItemController` type and reads an internal accessor declared there; the interface file is in the Write Set of neither document (preflight round 2 moved it from the spec's Write Set into its prose exclusion paragraph) and is not written by this plan. See P4-T1, P4-T2 and P4-T6. +9. **Two target classes carry a class-level coverage exclusion.** `QuickFiler/Controllers/EfcItemController.cs` line 25 and `QuickFiler/Controllers/QfcCollectionController.cs` line 21 each carry a class-level exclude-from-code-coverage attribute. An attribute on one partial declaration applies to the whole type, so the two new partials this plan creates for those types produce no class element in the Cobertura document and no per-file rate can be demanded for them. P6-T7 records those two rows as unmeasurable by pre-existing exclusion and names the tests that stand as their evidence, rather than demanding a rate that cannot exist. +10. **Two non-code occurrences would defeat the one-owner gate.** `QuickFiler/Controllers/QfcItemController.ViewerSetup.cs` line 60 is a commented-out alternative construction that still contains the options-constructor token, and `QuickFiler/Viewers/WebView2BreadcrumbHost.cs` line 18 is a summary-comment line that names the shared folder literal. P1-T10 removes the commented-out line and P1-T9 rewords the summary line, so the P1-T13 gate counts only real sites. +11. **The data model is constructible in a test without Outlook, and its token is settable only through the constructor.** `QuickFiler/Controllers/EfcDataModel.cs` lines 48 through 75: with a null mail the constructor calls a selection probe that catches every exception and returns null, so a loose globals mock and a null mail construct the model with no resolver. `Token` (line 164) has a protected setter assigned from the constructor argument (line 58). Passing an already-cancelled token makes every `Task.Run(..., Token)` in the moved folder-handler initialization return a cancelled task without running its delegate, which is what makes the construction path observable without Outlook. A carried predictor is constructible as `new FolderPredictor(globals.Object)` with a loose globals mock, the pattern already used at line 378 of QuickFiler.Test/Controllers/QfcItemController.FolderHandlingTests.Part2.cs. See P4-T9. +12. **`UiThread.Dispatcher` throws when uninitialized, and the fixture's parked dispatcher never pumps.** UtilitiesCS/Threading/UiThread.cs lines 251 through 269 throw an invalid-operation exception when no dispatcher was captured. The test project's `UiThreadDispatcherFixture` (QuickFiler.Test/Controllers/QfcItemController.UiThreadDispatcherFixture.cs) exposes `Exchange` at line 55, `CompareExchange` at line 70 and `BeginTransactionAsync` at line 122 with a transaction type at line 220 carrying `Install` at line 242; its `EnsureDispatcher` at line 99 parks a dispatcher that never runs a frame (lines 145 through 147), so a blocking `Invoke` against it would never return. `QfcItemControllerTestSupport.StartRunningDispatcher` (QuickFiler.Test/Controllers/QfcItemController.TestSupport.cs line 251) starts a pumping STA dispatcher and `ShutdownDispatcher` (line 277) stops it. P4-T13 uses the transaction plus the running dispatcher, never the parked one. A dispatcher priority is not observable from inside the invoked action, so the spec's "forwards the priority" assertion is replaced by an assertion on the reset member, which is the second production site the change edits. +13. **The two discard tests that pin an empty buffer or a silent discard already pass against the defect-preserving stub, and one form-controller test already passes against the single-attempt body.** P3-T11's fail-before enumeration names exactly which methods must fail and which pin preserved behaviour, instead of demanding that every method fail. + +## Design decisions settled here, so the executor does not decide them + +**D1. Options direction.** All three environment creations converge on the additional browser argument `--incognito ` owned by the new contract file. Fallback branch, stated explicitly: if P1-T1 finds any browsing-storage dependence in the breadcrumb document, execution halts at P1-T1 and reports remediation-required, because the pinned test named in correction 7 lives outside the Write Set and an inverted direction cannot be landed within this plan's file set. Correction 6 records that the tree at the base anchor takes the primary branch. + +**D2. AC-U1 seam shape.** The form controller gains an injectable initialization delegate and an injectable retry-delay delegate. The delegate default must not be written as a property initializer capturing the host field, because a field initializer cannot reference a non-static instance field and that spelling is a compile error. The default is supplied lazily from a backing field in the property getter instead. + +**D3. AC-U1 error-state delivery.** The failed host cannot render, so the error banner is composed and rendered by the router and then delivered by the same two-state mechanism the router already uses in its private document delivery member (QuickFiler/Controllers/BreadcrumbBridgeRouter.Selection.cs lines 168 through 180): navigated immediately when the host reports initialized, retained as the pending document otherwise so a later successful initialization delivers it. The user-visible surfacing in the never-initialized case is the existing modeless fault notice reached through the form controller's boundary reporter, which AC-U4 covers. The banner row uses the existing banner prefix constant `BannerPrefix` (value `====`, UtilitiesCS/OutlookObjects/Folder/BreadcrumbRowBuilder.cs line 19) and banner rows are already non-selectable, so the error row cannot be chosen as a folder. No new WinForms control is added. + +**D4. AC-U7 discard primitive.** The outbound queue gains a discard member that clears the buffer and returns the discarded count. The router's failure notification flushes the buffer when the host is initialized and discards it otherwise. Either way the pending count is zero afterwards. + +**D5. AC-U3 carry typing and carry read.** The carried folder handler is typed as the existing folder-search-handler interface, which lives in the UtilitiesCS root namespace that the edited QuickFiler files already import, so no new using directive is required. The data model's concrete predictor property is not retyped and the UtilitiesCS interface is not widened. Adoption happens only when no explicit folder list was supplied and the carried instance matches the concrete predictor type by pattern match. The carried mail-item helper is held in a private carry field on the new data-model partial and preferred over the resolver-derived value inside the folder-handler initialization; the resolver property is not retyped and its protected setter is not touched. On the producing side, per correction 8, the pop-out reads the mail-item helper through the interface member `ItemHelper` that already exists (QuickFiler/Interfaces/IQfcItemController.cs line 41) and reads the folder handler through a new internal accessor on the concrete `QfcItemController`, reached by pattern-matching the item group's `ItemController` (typed as the interface at QuickFiler/Controllers/QfcItemGroup.cs line 39) to the concrete type. + +**D6. AC-U3 pop-out decomposition.** The two pop-out members keep their current ordering: the carry is read before the group is removed, and the home controller is created after. The read and the creation are each extracted into their own internal member so both are unit-testable without WinForms, and a wiring-sensitive structural gate asserts that each extracted member name occurs exactly three times in the new partial, once as a declaration and once in each of the two pop-out members. Comments in that file must not repeat either member name, so the count stays a wiring count. + +**D7. AC-U8 versus AC-U9.** AC-U8's ceiling is verified for every file in the Write Set except the two files AC-U9 names as pre-existing debt. For those two the gate is that the post-change count is strictly lower than the Phase 0 baseline count, which is the only reading consistent with the spec's own out-of-scope statement that a full split of either is out of scope and their remaining size is debt this change neither introduces nor resolves. + +## Write Set and expected resulting line counts + +The source Write Set below lists the 31 paths of the `## Write Set` section of the spec, element for element; the spec and this table agree on those 31 written paths (the spec was brought into agreement in preflight round 2, when the item-controller interface was moved from its Write Set into its plain-prose exclusion paragraph per correction 8). Evidence artifacts and the spec file itself are additionally written by this plan and are listed after the table. One read-only control path is measured but not written and is stated after the table in plain text so the blast-radius extractor does not harvest it. + +| Path | Before | Expected after | Ceiling status | +| --- | --- | --- | --- | +| `QuickFiler/Viewers/WebView2EnvironmentContract.cs` | new | 40 to 100 | under | +| `QuickFiler/Viewers/WebView2BreadcrumbHost.cs` | 368 | 360 to 372 | under | +| `QuickFiler/Controllers/QfcItemController.ViewerSetup.cs` | 467 | 458 to 472 | under | +| `QuickFiler/Controllers/EfcItemController.cs` | 1121 | 1060 to 1085 | AC-U9 debt, must strictly decrease | +| `QuickFiler/Controllers/EfcItemController.WebViewEnvironment.cs` | new | 55 to 110 | under | +| `QuickFiler/Controllers/BreadcrumbBridgeRouter.cs` | 407 | 430 to 475 | under | +| `QuickFiler/Controllers/BreadcrumbOutboundQueue.cs` | 67 | 76 to 95 | under | +| `QuickFiler/Controllers/EfcFormController.cs` | 1321 | 250 to 290 | under | +| `QuickFiler/Controllers/EfcFormController.Breadcrumb.cs` | new | 180 to 260 | under | +| `QuickFiler/Controllers/EfcFormController.SetupAndProperties.cs` | new | 215 to 270 | under | +| `QuickFiler/Controllers/EfcFormController.EventHandlers.cs` | new | 355 to 410 | under | +| `QuickFiler/Controllers/EfcFormController.Actions.cs` | new | 160 to 210 | under | +| `QuickFiler/Controllers/EfcFormController.Helpers.cs` | new | 205 to 260 | under | +| `QuickFiler/Controllers/QfcCollectionController.cs` | 2329 | 2295 to 2315 | AC-U9 debt, must strictly decrease | +| `QuickFiler/Controllers/QfcCollectionController.PopOut.cs` | new | 80 to 150 | under | +| `QuickFiler/Controllers/EfcHomeController.cs` | 447 | 452 to 480 | under | +| `QuickFiler/Controllers/EfcDataModel.cs` | 499 | 460 to 470 | under | +| `QuickFiler/Controllers/EfcDataModel.Carry.cs` | new | 70 to 140 | under | +| `QuickFiler/Controllers/QfcItemController.cs` | 334 | 337 to 352 | under | +| `QuickFiler/Helper Classes/EfcViewerQueue.cs` | 101 | 99 to 106 | under | +| `QuickFiler/QuickFiler.csproj` | project file | plus 9 Compile elements | project file, not size-gated | +| `QuickFiler.Test/Viewers/WebView2BreadcrumbHostIssue792Tests.cs` | new | 80 to 180 | under | +| `QuickFiler.Test/Viewers/WebView2EnvironmentContractTests.cs` | new | 70 to 160 | under | +| `QuickFiler.Test/Controllers/BreadcrumbBridgeRouterIssue792Tests.cs` | new | 140 to 270 | under | +| `QuickFiler.Test/Controllers/BreadcrumbOutboundQueueIssue792Tests.cs` | new | 80 to 160 | under | +| `QuickFiler.Test/Controllers/EfcFormControllerIssue792Tests.cs` | new | 160 to 320 | under | +| `QuickFiler.Test/Controllers/EfcFormControllerTests.cs` | 485 | 485 | under, rename is line-neutral | +| `QuickFiler.Test/Controllers/QfcCollectionControllerIssue792PopOutTests.cs` | new | 120 to 250 | under | +| `QuickFiler.Test/Controllers/EfcDataModelIssue792CarryTests.cs` | new | 120 to 260 | under | +| `QuickFiler.Test/Helper Classes/EfcViewerQueueIssue792Tests.cs` | new | 70 to 160 | under | +| `QuickFiler.Test/QuickFiler.Test.csproj` | project file | plus 8 Compile elements | project file, not size-gated | + +Two of the paths above contain a space in the directory name, under Helper Classes in each project. Those are the real tracked paths and are reproduced exactly. + +Control row, plain text, NOT written (correction 8): QuickFiler/Interfaces/IQfcItemController.cs, 113 lines at the base anchor, expected 113 and unchanged after. It is not in the Write Set of either document. P0-T9 and P5-T2 measure it as one additional labelled control row so P4-T1 has a recorded baseline to compare against. + +Additionally written by this plan: `docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/spec.md` (AC check-off marks only, in Phase 6) and the evidence artifacts named on the task lines below. + +## Files deliberately not modified + +Stated in prose with no backticks, because the blast-radius extractor has no notion of polarity and would harvest an excluded path as though it were written. + +The UtilitiesCS folder-search-handler interface and the folder predictor that implements it are not modified: the carry is typed as the existing interface and adopted by pattern match on the concrete predictor type, so neither needs widening and no change leaves the QuickFiler project. The QuickFiler item-controller interface in the Interfaces folder is not modified, and neither document lists it as written: two implementers outside the Write Set would fail to compile if a member were added, so the carry read pattern-matches the concrete item controller instead. The Efc item-controller test file in the test project's Controllers folder is not modified: its pinned constant test keeps compiling because the constant is forwarded rather than deleted. The narrow breadcrumb web-host interface in the QuickFiler Viewers folder is not modified: the AC-U1 seam is an injectable delegate on the form controller, not an interface member. The core-initializer interface in the QuickFiler Viewers folder is not modified: the Efc item controller adopts it as a consumer only. The breadcrumb UI dispatcher is not modified: research assessed its boundary check as self-consistent and a sibling item explicitly scoped it out. The breadcrumb row builder, the breadcrumb HTML renderer and the breadcrumb document assets in UtilitiesCS are not modified: the error state reuses the existing banner-prefix convention and the existing rendering path. The breadcrumb HTML resource under the QuickFiler Resources folder is not modified: it is read in P1-T1 and nothing else. The router's Selection and Arrows partial parts are not modified: the failure notification is added to the main router file next to the success notification. The item viewer files owned by a sibling item are not modified. The sibling part of the form-controller test class is not modified. The collection-controller test support helper and the item-controller test support and dispatcher fixture files are not modified; they are consumed as-is. + +## Known contention + +Sibling issue 645 edits the same collection-controller file at three date and time formatting call sites, which are disjoint from the pop-out members this item moves, and it also edits the QuickFiler production project file. A merge on the project file's item-group ordering is expected and accepted. New Compile elements are appended adjacent to their existing neighbours as bare self-closing elements with no metadata, which minimises the conflict surface. Sibling issue 781 is scoped to the item viewer files this item does not write; the overlap is conceptual only and this item adopts the owner-thread-identity idiom that sibling ratified rather than inventing a third convention. + +## Self-review revision record (2026-09-12, version 1.1) + +Applied in place during the adversarial self-review pass. Each entry names the defect and the repair so a later reader does not re-derive it. + +1. P1-T11 deleted a constant pinned by a test outside the Write Set; now forwarded (correction 7). +2. P4-T1 widened an interface with two out-of-Write-Set implementers; now a read-only non-widening verification, with the accessor moved to the concrete type (correction 8). +3. P6-T7 demanded a 0.90 rate for two files that cannot have a class element (correction 9); rows re-specified per file, with the exempt member and the pre-existing moved lambdas named by the planner rather than chosen by the executor. +4. P1-T13's "exactly 1" gates were defeated by a commented-out construction and a summary-comment folder name (correction 10); both occurrences are now removed by P1-T10 and P1-T9 and the gate is stated as "zero outside the contract file". +5. P2-T7's pattern also matched the parent's own Compile entry (six hits, not five); pattern narrowed to the five new names. +6. P0-T9 and P5-T3 said ten new files; the Write Set creates seventeen. +7. P2-T8's sum window (1400 to 1500) was below the arithmetic: 1316 retained source lines plus five file headers of roughly 10 to 14 lines each lands near 1370 to 1390; window is now 1360 to 1460. +8. Several lower bounds on new-file line counts were above the arithmetic of the moved spans plus a header; loosened. +9. P3-T11 demanded that all fifteen Phase 3 tests fail before the fix; two discard tests and one form-controller test pass against the stubs (correction 13); enumeration corrected. P4-T15 likewise. +10. P3-T14's `TryReportBoundaryFault` floor of 2 was already met by the two moved call sites (pre-split lines 1127 and 1270); floor raised to 3. +11. P1-T4's directory-existence test was environment-dependent; replaced by a rooted-path assertion. A forwarding-constant test was added so the Efc site's argument agreement is asserted by a test, not only by a structural gate. +12. P4-T13 relied on an uninitialized dispatcher and an unobservable priority (correction 12); re-specified over the running-dispatcher transaction. +13. P0-T11, P5-T5 and the final commit task (P6-T19 in version 1.1, P6-T20 since round 2) wrote their commit record after committing and then demanded empty porcelain; the amend step was added. +14. P6-T6 demanded exit 0 and zero failures unconditionally; the coverage runner asserts a 0.80 floor and exits non-zero below it, and a pre-existing failing test would make the clause unsatisfiable; both are now baseline-relative, and P0-T8 records the failing-test names the comparison needs. +15. P5-T1 and P6-T2 formatted two whole directories, which would sweep pre-existing drift outside the Write Set into the diff and break the "exactly the written paths" clause; both now format the explicit Write Set file list, and P6-T3 is baseline-relative. +16. Test namespaces were stated as `QuickFiler.Viewers.Tests` and `QuickFiler.Controllers.Tests` uniformly; corrected per file to the mirrored sibling's namespace. +17. P4-T11's fifth test was a cleanup assertion, not a test; replaced by a non-concrete-controller case. +18. The D1 fallback inverted the argument direction downstream; it now halts, because the pinned test outside the Write Set cannot absorb an inverted value. + +## Preflight round 2 revision record (2026-09-12, version 1.2) + +Applied in place from the executor's round-2 delta plus one orchestrator item. Every tree fact each entry relies on was re-derived against the base anchor in this pass. + +1. P0-T4 assumed a bootstrapped worktree; the worktree holds no repo-local SDK directory and no packages directory, and the PackageReference-only restore is a no-op against packages.config projects. Now runs the repo-local SDK installer, the tool restore and a packages.config-aware restore, and gates on observed installs rather than on exit codes alone. +2. P4-T1's bare accessor-token gate was already false at the base anchor because the pre-existing member `LoadFolderHandlerAsync` at interface line 77 contains the token; the gate now searches the typed declaration and pins the pre-existing member's count. +3. P6-T7's Breadcrumb rule gated moved members that no unit test can reach; the gate is now over the members this change writes, and every moved member is listed as information with its reason. +4. P6-T10 through P6-T18 appended evidence citations to acceptance-criterion lines, which the acceptance-criteria-tracking skill forbids; the only spec edit is now the marker flip, and citations go to an ac-status-summary artifact under evidence/issue-updates. +5. The final commit wrote evidence after itself; the handoff record is now P6-T19 and the final commit is P6-T20, with captures taken before the artifact write. +6. Four excluded paths were backticked (an Efc item-controller test file, the UiThread file, the router Selection part, and the coverage ignore glob); backticks removed. +7. P1-T6's second test asserted against the empty string, which the SDK's no-argument constructor does not produce (it leaves the property null); the test now asserts not-null-or-empty. +8. P1-T8 lacked the rebuild precondition that P3-T11 and P4-T15 carry; added. +9. P0-T11's listing clause omitted the five requirement documents untracked at the base anchor; the clause is now exact. +10. P3-T3 did not state how the single attempt reaches the injected delegate, and its match floor counted a lower-camel backing field that a case-sensitive pattern does not match; wiring stated, floor lowered to 2. +11. P4-T9's second test did not state its fail-before mechanism; stated. +12. P2-T10 demanded an exact passed-count equality that pre-existing flaky or newly passing tests could break; now a floor. +13. P6-T7's repository-rate clause had no formula; now stated over `lines-covered` and `lines-valid` with the 0.80 floor. +14. P6-T7's exclusion rows asserted no class element exists, which a compiler-generated closure type could falsify; now records whether one exists and gates no rate on it. +15. P6-T20's projections-only Grep had no concrete target; now the record artifact, whose prose must not carry either literal. +16. P1-T13's hit decomposition was wrong (count right); corrected to the two live sites plus the two commented-out lines, with the target-typed ViewerSetup site noted as a non-match. +17. P4-T11 and P4-T13 mutate process-wide statics under a class-parallel runsettings; both classes now carry `[DoNotParallelize]`, and P4-T13's timeout constant is stated as a value. +18. Orchestrator item: spec.md's `## Write Set` listed 32 paths including the interface this plan does not write; the spec now lists 31, the interface sits in its prose exclusion paragraph, the spec's summary and function-impact sentences no longer name the interface as written, and this plan's table and count statements agree on 31. + +## Preflight round 3 revision record (2026-09-12, version 1.3) + +Prose-only. The executor confirmed every round-2 delta landed and re-verified every tree citation, task count and gate; the sole remaining findings were prose residuals of the 32-to-31 Write Set reduction. No task identifier, task count, phase heading, count statement, gate value or command changed in this round. + +1. Spec regression-test list said seven new test files; the write set names eight. Corrected. +2. Spec backward-compatibility paragraph still described one interface change adding a read-only member; replaced with the non-widening statement (internal get-only property on the concrete item controller, reached by pattern match). +3. Spec compatibility note still said "the added interface member"; now "the added internal accessor on the concrete item controller". +4. Spec pop-out data-flow sentence still said both carried values are read through the interface; now states the mail item helper comes through the existing interface member and the folder handler through the internal accessor on the concrete type, null when the group's controller is not the concrete type. +5. Correction 8 still said the interface file is listed in the spec's Write Set; now states it is in the Write Set of neither document. +6. The files-not-modified paragraph still said "although the spec lists it"; now "neither document lists it as written". +7. The spec CITATION locator said Write Set 308-341; now 308-344 with the 31 paths at 310-340 and the exclusion paragraph at 344. +8. P6-T7's Breadcrumb row is advisory by design: every P3-T9 test substitutes both delegates, so the two lazy-default getter expressions are excluded from the gated span and recorded as information with their hit counts. + +--- + +### Phase 0 — Baseline Capture and Policy Reads + +- [ ] [P0-T1] Read the repository policy documents in the mandated order and record the read in `docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/evidence/baseline/phase0-instructions-read.2026-09-12T13-21.md`, creating the evidence directory tree as part of this task. + - Read, in this order and in full, using the Read tool: CLAUDE.md at the repository root; then .claude/rules/general-code-change.md; then .claude/rules/general-unit-test.md; then .claude/rules/csharp.md; then .claude/rules/quality-tiers.md; then .claude/rules/tonality.md; then .claude/rules/plan-acceptance-gates.md. All seven exist at the base anchor. + - Acceptance: the artifact exists and contains a `Timestamp:` field, a `Policy Order:` field listing those seven paths in that order, and a `Files Read:` list whose entries are exactly those seven paths. + +- [ ] [P0-T2] Capture the base anchor and confirm it, recording the result in `docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/evidence/baseline/base-anchor.2026-09-12T13-21.md`. + - Run `git -C . rev-parse HEAD`. + - Acceptance: the artifact records `Command:`, `EXIT_CODE: 0` and an `Output Summary:` whose recorded SHA is exactly `2405a829d6afd3b12eb7c228d57158a97cb4e2ca`. If the recorded SHA differs, stop and report a base-anchor mismatch rather than continuing, because every anchored diff in this plan names that SHA as its ref operand. + +- [ ] [P0-T3] MANUAL HUMAN STEP. Close Outlook through its normal exit path before any msbuild task in this phase runs, and record the confirmation in `docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/evidence/baseline/outlook-closed-precondition.2026-09-12T13-21.md`. + - Runbook: use Outlook's own File then Exit path, or the window close button, and wait until the process is gone from the task list of its own accord. Do not end the process from the task manager and do not use any process-kill command. A killed process leaves the debug build output locked, and MSBuild then fails with a file-lock error or silently links a stale assembly. + - Acceptance: the artifact records the operator name, the ISO-8601 local timestamp of the confirmed exit, and the sentence that Outlook was closed through its normal exit path and was not killed. This task has no exit code and no automated gate. + +- [ ] [P0-T4] Bootstrap the toolchain in this worktree and record the result in `docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/evidence/baseline/toolchain-bootstrap.2026-09-12T13-21.md`. + - This worktree holds no repo-local SDK directory and no packages directory at the base anchor (verified this pass with the Glob tool: no dotnet executable under the repo-local SDK directory and no entry under a packages directory). global.json pins SDK 8.0.205 with the latestFeature roll-forward and search paths of the repo-local SDK directory then the host, so `dotnet tool restore` exits non-zero with global.json's own error message unless a compatible SDK is present. `msbuild TaskMaster.sln /t:Restore` handles PackageReference only; against this repository's packages.config projects it exits 0, prints that none of the projects contain packages to restore, and installs nothing. + - Run, in this order, from the worktree root under PowerShell 7: scripts/vscode/Install-RepoDotNetSdk.ps1 (skip only when the repo-local SDK executable already exists, and record which branch was taken; the installer itself prints that the SDK is already installed and returns when its own marker directory, the sdk-slash-version directory under the install directory, exists at its lines 56 through 61); then `dotnet tool restore`; then `msbuild TaskMaster.sln /t:Restore /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:RestorePackagesConfig=true`. + - Acceptance: the artifact records all three commands, an `EXIT_CODE:` for each, and an `Output Summary:` that states the CSharpier version the manifest pinned as reported by the tool restore output, the number of packages the restore installed as printed by the restore output, and the Glob tool observations that the repo-local SDK executable exists and that the packages directory contains at least one package directory. Every exit code must be 0, and a restore that prints the nothing-to-do line or installs zero packages stops the phase. Both writes land on ignored or restore-output paths only (the root ignore file's dot-dotnet wildcard directory entry at its line 350 covers the SDK directory, and its bracket-class packages entry at line 191 covers the packages directory), so neither widens any anchored-diff footprint. + +- [ ] [P0-T5] Capture the CSharpier baseline into `docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/evidence/baseline/format-check.2026-09-12T13-21.md`. + - Run `dotnet tool run csharpier check .`. This is the read-only verify form; the write-mode form is not used in Phase 0, because a formatter that repairs pre-existing drift before the baseline is taken turns the baseline into a blanket waiver. + - Acceptance: the artifact records `Command:`, `EXIT_CODE:` and an `Output Summary:` carrying the count of files CSharpier reported as needing formatting and the repository-relative path of every such file under a `Reported set:` heading, or the literal statement that it reported none. The exit code is recorded as observed and is not asserted to be 0, because a solution-wide pre-existing drift is a property of the tree rather than of this change. P6-T3 compares against the `Reported set:` heading. + +- [ ] [P0-T6] Capture the analyzer-build baseline into `docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/evidence/baseline/analyzer-build.2026-09-12T13-21.md`. + - Precondition: P0-T3 is complete. Run `msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true`. + - Acceptance: the artifact records `Command:`, `EXIT_CODE:` and an `Output Summary:` carrying the warning count and the error count exactly as the build summary reports them, plus the deduplicated set of diagnostic identifiers observed. Record the counts as the two figures printed on the summary lines; do not infer them. The exit code is recorded as observed and is not asserted to be 0. + +- [ ] [P0-T7] Capture the nullable-build baseline into `docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/evidence/baseline/nullable-build.2026-09-12T13-21.md`. + - Precondition: P0-T3 is complete. Run `msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=true`. Do not add the solution-wide nullable opt-in property. + - Acceptance: the artifact records `Command:`, `EXIT_CODE:` and an `Output Summary:` carrying the error count from the build summary and the deduplicated set of diagnostic identifiers observed. The exit code is recorded as observed and is not asserted to be 0. + +- [ ] [P0-T8] Capture the test and coverage baseline into `docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/evidence/baseline/test-coverage.2026-09-12T13-21.md`. + - Precondition: P0-T3 is complete. Run the repository coverage runner at scripts/vscode/Invoke-MSTestWithCoverage.ps1 under PowerShell 7 with an explicit search root of the current directory and the Debug configuration. The runner already appends the isolation switch, the CLI runsettings and the live-Outlook test-category exclusion (its line 76), discovers assemblies relative to the search root so an agent worktree is not self-excluded (its line 301), post-processes the document so class elements are merged by source file name, and writes the processed Cobertura to its default output path under the repository-ignored coverage directory. Do not pass a trx logger. The runner asserts a 0.80 repository line-rate floor after writing the document and exits non-zero below it, so the document exists even when the exit code is non-zero. + - Acceptance: the artifact records `Command:`, `EXIT_CODE:` as observed, and an `Output Summary:` carrying five numeric figures read with the Read tool from the root element of the runner's Cobertura output: `lines-valid`, `lines-covered`, `line-rate`, `branches-valid` and `branch-rate`; the total, passed, failed and skipped test counts; and, under a `Failed set:` heading, the fully qualified name of every failed test, or the literal `none`. The figures are read from the XML attributes rather than from console text, so the acceptance does not depend on what the runner prints. The raw Cobertura file is left uncommitted. If the run does not complete, the phase stops and the item is reported as remediation-required with the runner's last output quoted; the runner is not edited, because it is outside the Write Set. + +- [ ] [P0-T9] Capture the baseline line count of every one of the 31 Write Set paths, plus the one control path, into `docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/evidence/baseline/write-set-line-counts.2026-09-12T13-21.md`. + - Measure each existing file with the Grep tool using the pattern `^` in count output mode against that single file. Record one row per Write Set path. For the seventeen paths that do not yet exist, record the literal value `absent`. Then record one additional row, labelled `control (not written)`, for the item-controller interface named in the control-row paragraph under the Write Set table. + - Acceptance: the artifact contains exactly 31 Write Set rows plus exactly one control row, 32 rows in all; the Write Set rows for the existing production files record 368 for the breadcrumb host, 467 for the item-controller viewer-setup part, 1121 for the Efc item controller, 407 for the router, 67 for the outbound queue, 1321 for the Efc form controller, 2329 for the collection controller, 447 for the home controller, 499 for the data model, 334 for the QuickFiler item controller and 101 for the Efc viewer queue; the row for `QuickFiler.Test/Controllers/EfcFormControllerTests.cs` records 485; exactly seventeen rows record `absent`; the control row records 113. AC-U8 and AC-U9 are judged against this artifact, and P4-T1 reads the control row. + +- [ ] [P0-T10] Capture the baseline Compile-item inventory of `QuickFiler/QuickFiler.csproj` and `QuickFiler.Test/QuickFiler.Test.csproj` into `docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/evidence/baseline/compile-item-inventory.2026-09-12T13-21.md`. + - Measure with the Grep tool using the pattern ` action\(\)` against this file returns 0 matches, and the Grep tool run with the pattern `UiThread\.Dispatcher\.Invoke\(action, priority\)` against this file returns exactly 2 matches; that pattern does not match the two existing asynchronous-invoke sites at lines 20 and 67, because the token after `Invoke` there is `Async`. The file's measured line count is between 99 and 106. + +- [ ] [P4-T19] Rebuild, run the three Phase 4 test classes green, and commit, recording the run in `docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/evidence/regression-testing/p4-pass-after.2026-09-12T13-21.md`. + - Precondition: P0-T3 is complete. Rebuild with the analyzer command, then run vstest filtered to the three Phase 4 class names. + - Acceptance: the artifact records `Command:`, `EXIT_CODE: 0` and an `Output Summary:` stating that all twelve Phase 4 test methods passed and zero failed. Then run `git -C . add -- QuickFiler QuickFiler.Test docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792` and `git -C . commit -m "#792 Phase 4 pop-out carry and dispatcher viewer construction"`; afterwards `git -C . status --porcelain --untracked-files=all -- QuickFiler QuickFiler.Test docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792` prints no lines. + +### Phase 5 — AC-U8 Ceiling and Compile-item Parity, AC-U9 Debt Record + +- [ ] [P5-T1] Run the formatter over the written .cs files so the ceiling audit measures formatted files, and record the observation in `docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/evidence/qa-gates/p5-scoped-format.2026-09-12T13-21.md`. + - Run `dotnet tool run csharpier format` followed by the twenty production .cs paths of the Write Set table (every production row except the project file), then run it a second time followed by the nine test .cs paths of the Write Set table; quote the two paths under Helper Classes because they contain a space. Do not pass a directory: a directory-scoped pass would also rewrite pre-existing drift in files outside the Write Set and break the exactly-the-written-paths clause of P6-T20. + - Acceptance: this is a write-mode command whose exit code is 0 both when it changed nothing and when it repaired drift, so the exit code alone is not the gate. The artifact records both commands, both exit codes, the processed-file counts the tool printed, the output of `git -C . status --porcelain --untracked-files=all -- QuickFiler QuickFiler.Test` captured immediately before and immediately after the two commands, and the result of `dotnet tool run csharpier check` run afterwards over the same twenty-nine paths. The gate passes when both format exit codes are 0, the check exit code is 0 and it reports no file needing formatting, and every path in the after-capture is one of the Write Set paths. + +- [ ] [P5-T2] AC-U8 ceiling audit. Measure every one of the 31 Write Set paths, plus the one control path, after formatting and record the result in `docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/evidence/qa-gates/ac-u8-line-counts.2026-09-12T13-21.md`. + - Measure with the Grep tool using the pattern `^` in count output mode, the same instrument P0-T9 used, so the before and after figures are comparable. Record the control row for the item-controller interface with the same `control (not written)` label P0-T9 used. + - Acceptance: the artifact contains 31 Write Set rows plus one control row, 32 rows in all, each carrying the path, the Phase 0 baseline value from the P0-T9 artifact and the measured post-change value. Every Write Set row except the two AC-U9 debt rows records a post-change value of at most 500. The two project-file rows are recorded but are not size-gated. The control row records 113, unchanged. The row for `QuickFiler/Controllers/EfcItemController.cs` records a value strictly less than 1121 and the row for `QuickFiler/Controllers/QfcCollectionController.cs` records a value strictly less than 2329. Any row over 500 that is not one of those two fails this gate. + +- [ ] [P5-T3] AC-U8 Compile-item parity. Compare the added and removed .cs files against the Compile element edits and record the result in `docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/evidence/qa-gates/ac-u8-compile-parity.2026-09-12T13-21.md`. + - Run `git -C . add -- QuickFiler QuickFiler.Test`, then `git -C . diff --name-status --cached 2405a829d6afd3b12eb7c228d57158a97cb4e2ca -- QuickFiler QuickFiler.Test`. The staging span is what makes the name-listing diff able to see the seventeen files this change created. Then measure the post-change Compile element count in each project file with the Grep tool using the pattern ` As a browser process may be shared among WebViews, WebView creation fails with +> `HRESULT_FROM_WIN32(ERROR_INVALID_STATE)` if the specified options does not match the options of +> the WebViews that are currently running in the shared browser process. + +and, in the same page's error table: + +> `HRESULT_FROM_WIN32(ERROR_INVALID_STATE)` — Specified options do not match the options of the +> WebViews that are currently running in the shared browser process. + +`HRESULT_FROM_WIN32(ERROR_INVALID_STATE)` is `0x8007139F`, the exact HRESULT in the logs. The .NET +`CoreWebView2Environment.CreateAsync` reference states the same rule in prose on the `options` +parameter ("WebView creation fails if the specified `options` does not match the options of the +WebViews that are currently running in the shared browser process"). + +Site 3 is the *only* site in the add-in that omits `--incognito`, and site 3 is the *only* site that +fails. Once any QuickFiler item-body or Efc item-body WebView is running (both are `--incognito`), +the shared browser process is pinned to those options and the breadcrumb host's no-argument +environment cannot produce a controller. This explains, without appeal to timing: + +- why the failure was originally intermittent (it depends on whether an `--incognito` WebView was + already running in the Outlook process when the Efc breadcrumb initialized), and +- why it is now deterministic on both reported entry points: a pop-out always follows an open + QuickFiler, and the logged 19:56 Sort Email open followed ten pop-outs in the same session. + +**Clause-by-clause verdict on the maintainer's mechanism:** + +| Clause | Verdict | +|---|---| +| "A half-initialized host never raises `CoreInitialized`" | **Confirmed.** `WebView2BreadcrumbHost.OnCoreInitializationCompleted` returns on `!e.IsSuccess` at `:335-342` before the `CoreInitialized?.Invoke` at `:353`. | +| "so the pending document is dropped" | **Confirmed and sharpened.** `_pendingDocument` has exactly one drain, `BreadcrumbBridgeRouter.NotifyCoreInitialized` (`BreadcrumbBridgeRouter.cs:320-329`), reachable only from that event. The outbound *queue* is likewise never flushed. | +| "fire-and-forget tasks swallow the failure with a log-only outcome" | **Half confirmed, half already fixed.** `InitializeBreadcrumbHostAsync` (`EfcFormController.cs:1072-1082`) is log-only. `PopulateFolderCombobox` (`EfcFormController.cs:1251-1272`) **already** routes through `TryReportBoundaryFault` at `:1270`. | +| "two log lines suggest initialization is attempted from two paths" | **Refuted.** It is one failure logged twice: the SDK's `CoreWebView2InitializationCompleted(IsSuccess=false)` handler logs once, and the same failure faults the task awaited in `InitializeBreadcrumbHostAsync`, which logs again. There is exactly one `InitializeAsync` call site for the Efc breadcrumb host. | +| "initialized while its handle or parent was not yet in a valid state" | **Refuted as the cause.** Not the documented meaning of this HRESULT. | +| "a second initialization against a control already mid-initialization" | **Refuted.** Single call site; the per-control owner registry (`WebView2BreadcrumbHost.cs:46-51, 101-110`) makes a second host detach the first. | +| "initialization against a disposed or pooled-and-reused control" | **Refuted for this HRESULT**, though pooling *is* real here (see Q6 — the issue's own pooling claim is wrong in the opposite direction). | +| "a user-data-folder or environment conflict between two WebView2 instances in the same process" | **Confirmed — this is the cause**, specifically an *options* conflict, not a folder conflict. The folder is identical at all three sites. | +| "apply the #678 carry pattern to the pop-out path" | **Valid but orthogonal.** It fixes a real second defect (Efc rebuilds prediction from scratch) but would not have prevented a single 0x8007139F. | + +**Prior findings.** `docs/features/potential/promoted/2026-08-07-webview2breadcrumbhost-handler-retention-pooled-viewer.md` (#458) and +`docs/features/potential/promoted/2026-08-07-webview2breadcrumbhost-unmarshalled-sdk-call-and-unsynchronized-state.md` (#476) +are **both already fixed in the current tree** and are **neither the same defect nor contributing +causes** of #792. Evidence in Q2. + +--- + +## Numeric Derivation Evidence + +One enumeration below is load-bearing for the proposed fix ("every production WebView2 environment +option set must agree"), so it is derived twice by independent means. + +### Claim: the add-in has exactly THREE production `CoreWebView2EnvironmentOptions` construction sites, of which exactly ONE omits the additional browser arguments + +The eleven canonical single-line declarations follow. The expanded narrative beneath them records the +same derivation with its per-hit classification. + +- Complete Family: CoreWebView2EnvironmentOptions, CreateEnvironmentAsync, CoreWebView2Environment.CreateAsync +- Exhaustive Search Scope: the entire repository source tree, covering every C-sharp file in all six production assemblies and in the test projects +- Inclusion Rules: production object-creation expressions that build a WebView2 environment options instance and hand it to an environment creation call, in explicit, target-typed, and object-initializer spellings +- Exclusion Rules: parameter declarations, XML documentation references, commented-out lines, the pass-through adapter forward that carries no options of its own, and every file under the test projects +- Primary Search Strategy or Query Expression: Grep the entire repository tree for the type name CoreWebView2EnvironmentOptions, then read every hit and retain only object-creation expressions whose instance is handed to CreateEnvironmentAsync or to CoreWebView2Environment.CreateAsync +- Primary Member Set: QuickFiler/Viewers/WebView2BreadcrumbHost.cs, QuickFiler/Controllers/QfcItemController.ViewerSetup.cs, QuickFiler/Controllers/EfcItemController.cs +- Primary Count: 3 +- Cross-check Search Strategy or Query Expression: Approach the family from the consumer end instead of the type name, running git grep over every tracked C-sharp file across the complete source tree for CoreWebView2Environment.CreateAsync and for CreateEnvironmentAsync, then opening each production hit to recover the CoreWebView2EnvironmentOptions value that call is given +- Cross-check Member Set: QuickFiler/Controllers/EfcItemController.cs, QuickFiler/Viewers/WebView2BreadcrumbHost.cs, QuickFiler/Controllers/QfcItemController.ViewerSetup.cs +- Cross-check Count: 3 +- Member-set Comparison: The primary and cross-check member sets are identical element for element, so the two independent derivations match on both membership and count. + +Expanded narrative of the same derivation: + +- **Complete Family**: every production (non-test) expression in the repository that constructs a + `Microsoft.Web.WebView2.Core.CoreWebView2EnvironmentOptions` instance and hands it, directly or + through the `IWebViewCoreInitializer` seam, to a `CoreWebView2Environment` creation call. +- **Exhaustive Search Scope**: all `*.cs` files under the six production assemblies `QuickFiler/`, + `UtilitiesCS/`, `TaskMaster/`, `ToDoModel/`, `Tags/`, `TaskVisualization/`. WebView2 package + references exist only in QuickFiler; the other five were searched to prove absence rather than + assumed empty. +- **Inclusion Rules**: object-creation expressions of the type, in any spelling — explicit + (`new CoreWebView2EnvironmentOptions(...)`), target-typed (`new(...)` on a declared variable of + that type), and object-initializer forms. +- **Exclusion Rules**: occurrences in parameter lists, `` documentation, commented-out + code, and any file under `QuickFiler.Test/` (test doubles are not production option sets). +- **Primary Search Strategy / Query Expression**: type-name grep + `CoreWebView2EnvironmentOptions` restricted to the six production assembly globs, then manual + classification of every hit into declaration / parameter / doc / comment / construction. +- **Primary Member Set** (constructions only): + 1. `QuickFiler/Viewers/WebView2BreadcrumbHost.cs:250` — `new CoreWebView2EnvironmentOptions()` — **no arguments** + 2. `QuickFiler/Controllers/QfcItemController.ViewerSetup.cs:61` — `new("--incognito ")` (target-typed) — `--incognito ` + 3. `QuickFiler/Controllers/EfcItemController.cs:187-189` — `new CoreWebView2EnvironmentOptions(IncognitoArgument)` with `IncognitoArgument = "--incognito "` at `:176` — `--incognito ` + + Non-construction hits excluded: `IWebViewCoreInitializer.cs:17` (doc), `:51` (parameter), + `WebView2CoreInitializer.cs:37` (parameter), `:69` (parameter), + `QfcItemController.ViewerSetup.cs:60` (commented-out), `EfcItemController.cs:168` (doc), + `EfcItemController.cs:186` (commented-out). +- **Primary Count**: 3 constructions; 1 with no additional browser arguments. +- **Cross-check Search Strategy / Query Expression**: approach the family from the *consumer* end + instead of the type name — grep `CreateEnvironmentAsync\(|EnsureCoreWebView2Async\(|WindowsFormsWebView2` + across the whole repository excluding `QuickFiler.Test/**`, plus an independent grep for the raw + SDK factory `CoreWebView2Environment\.CreateAsync` across all `*.cs`. Each environment creation + must be reached by exactly one options object, so enumerating creations enumerates option sets. +- **Cross-check Member Set**: + - `QuickFiler/Viewers/WebView2BreadcrumbHost.cs:246-269` — cache folder built at `:246-249`, + options at `:250`, `_initializer.CreateEnvironmentAsync(cacheFolder, options)` at `:265-268`. + - `QuickFiler/Controllers/QfcItemController.ViewerSetup.cs:55-77` — cache folder at `:58`, options + at `:61`, `_webViewInitializer.CreateEnvironmentAsync(cacheFolder, options)` at `:70-73`. + - `QuickFiler/Controllers/EfcItemController.cs:180-198` — cache folder at `:184`, options at + `:187-189`, direct `CoreWebView2Environment.CreateAsync(null, cacheFolder, options)` at + `:194-198` (this one bypasses the seam). + - The only other `CoreWebView2Environment.CreateAsync` hit in production is the seam forward + `QuickFiler/Viewers/WebView2CoreInitializer.cs:72`, which is a pass-through with no options of + its own, and `QfcItemController.ViewerSetup.cs:123` which is commented out. + - No hit in `UtilitiesCS/`, `TaskMaster/`, `ToDoModel/`, `Tags/`, `TaskVisualization/`. + - `QuickFiler/Viewers/BreadcrumbPopupUiOperations.cs:383` calls `EnsureCoreWebView2Async` but + constructs **no** environment: it forwards a caller-supplied one (the `--incognito` environment + threaded from `QfcItemController.ViewerSetup.cs:113-116, 118-122`). Correctly excluded. +- **Cross-check Count**: 3 environment creations, therefore 3 option sets; 1 with no additional + browser arguments. +- **Member-set Comparison**: normalized to `(file, member, additional-arguments)`, the primary set is + `{(WebView2BreadcrumbHost, InitializeAsync, none), (QfcItemController.ViewerSetup, InitializeWebViewAsync, "--incognito "), (EfcItemController, InitializeWebViewAsync, "--incognito ")}` + and the cross-check set is identical, element for element. The two searches used distinct + expressions (type name vs. consumer API names) and distinct file scopes (six-assembly glob vs. + whole repository minus the test project) and agree on both membership and count. + +--- + +## Q1 — `WebView2BreadcrumbHost` initialization state machine + +File: `QuickFiler/Viewers/WebView2BreadcrumbHost.cs` (368 lines, `#nullable enable`). + +**On `CoreWebView2InitializationCompleted` with `e.IsSuccess == false`** — `OnCoreInitializationCompleted`, +`:329-354` (attribute `[ExcludeFromCodeCoverage]` at `:329`): + +```csharp +if (!e.IsSuccess) +{ + log.Error( + $"Breadcrumb CoreWebView2 initialization failed: {e.InitializationException?.Message}", + e.InitializationException + ); + return; // :341 +} +``` + +- `CoreInitialized` is **not** raised (the invoke is at `:353`, after the early return). +- `IsCoreInitialized` is left **false**: `Volatile.Write(ref _isCoreInitialized, true)` is at `:352`, + also after the return. The backing field is `_isCoreInitialized` at `:64`; the property is + `Volatile.Read` at `:137`. +- `core.WebMessageReceived` is never subscribed (`:346-347`), so the inbound bridge is dead too. +- The host is **not** disposed, **not** retried, and left half-constructed: `_isAttached` stays true + (set at `:114`), the owner-registry entry stays (`:101-110`), and the control keeps the + `CoreWebView2InitializationCompleted` and `Disposed` subscriptions made at `:112-113`. +- There is **no** retry anywhere in the type. `InitializeAsync` (`:239-270`) is a straight-line + method with no loop and no catch. + +**Is initialization attempted from more than one path?** No. The only production construction of +`WebView2BreadcrumbHost` is `EfcFormController.ConfigureBreadcrumbControl` at +`QuickFiler/Controllers/EfcFormController.cs:1049-1052`, and the only production call of +`InitializeAsync` is `EfcFormController.InitializeBreadcrumbHostAsync` at +`QuickFiler/Controllers/EfcFormController.cs:1076`, fired once from `:1067`. + +**The two log lines are one failure, logged twice.** Line 1 (`QuickFiler.Viewers.WebView2BreadcrumbHost`) +is `:337-340`. Line 2 (`QuickFiler.Controllers.EfcFormController`) is `EfcFormController.cs:1080`, +inside the `catch` of `InitializeBreadcrumbHostAsync` (`:1072-1082`), which awaits +`_breadcrumbHost.InitializeAsync(...)`. The WinForms `WebView2.EnsureCoreWebView2Async` task faults +with the same initialization exception that the event reports, so one SDK failure produces both +lines. The 59-67 ms gap between the paired lines in every logged occurrence is consistent with one +event-then-task-continuation sequence, not with two independent SDK attempts. + +**Consequence for AC-U1.** The `!e.IsSuccess` branch is a dead end that a unit test cannot reach +(`CoreWebView2InitializationCompletedEventArgs` has no public constructor, which is the documented +exemption rationale at `:323-328`). The testable surface for retry and error-surfacing is the +awaited-task path in `InitializeBreadcrumbHostAsync`, which goes through the mockable +`IWebViewCoreInitializer` seam. + +--- + +## Q2 — What produces 0x8007139F here + +**Established cause: environment-options mismatch in a shared browser process.** See the executive +summary and the Numeric Derivation Evidence section for the enumeration and the two authoritative +documentation quotations. The three environment creations share the user-data folder +`%LocalAppData%\WindowsFormsWebView2` (built identically at +`WebView2BreadcrumbHost.cs:246-249`, `QfcItemController.ViewerSetup.cs:55-58`, +`EfcItemController.cs:181-184`) but disagree on `AdditionalBrowserArguments`. + +**Discrimination between the issue body's candidates:** + +| Candidate | Reachable in this code? | Evidence | +|---|---|---| +| Initialized before the control has a window handle / parent not valid | Reachable but not this HRESULT | `ConfigureBreadcrumbControl` runs from `WireEventHandlers` (`EfcFormController.cs:520`), which runs before `EfcHomeController.Run()`/`RunAsync()` shows the form (`EfcHomeController.cs:308-340`). So the control genuinely is un-shown at `EnsureCoreWebView2Async`. However the documented HRESULT for this condition is not `ERROR_INVALID_STATE`, and the identical pre-show ordering holds for `QfcItemController.InitializeWebViewAsync`, which does not fail. | +| Second initialization against a control already mid-initialization | **Not reachable** | One construction site, one `InitializeAsync` call site; the owner registry at `:46-51`/`:101-110` guarantees at most one attached host per control. | +| Disposed or pooled-and-reused control | Pooling is real, this HRESULT is not its symptom | `EfcViewer` instances *are* pooled (Q6), but each pooled viewer carries its own Designer-owned `WebView2`; a reused *control* would surface as the #458 duplicate-notification shape, which is already fixed. | +| User-data-folder or environment conflict between two WebView2 instances in one process | **This is the cause**, in its options form | Three sites, one folder, two option sets; documented HRESULT match. | + +**Prior finding `2026-08-07-webview2breadcrumbhost-handler-retention-pooled-viewer.md` (#458):** +**unrelated, and already fixed.** The document describes a constructor-side `-=` that could not remove +a predecessor's subscription. The current file replaced that with a per-control +`ConditionalWeakTable` owner registry (`:46-51`) guarded by +`_ownersGate` (`:51`), which detaches the predecessor explicitly (`:101-110`, `DetachCore` at +`:308-321`), plus a `Disposed` hygiene path at `:287-301`. Not a contributing cause of #792. + +**Prior finding `2026-08-07-webview2breadcrumbhost-unmarshalled-sdk-call-and-unsynchronized-state.md` (#476):** +**unrelated, and already fixed.** Defect 1 (unmarshalled SDK touch) is fixed: `NavigateToString` +(`:157-167`) and `PostMessageJson` (`:193-218`) both route through `BreadcrumbUiDispatcher.Dispatch`. +Defect 2 (unsynchronized state publication) is fixed: explicit `_isCoreInitialized` field at `:64` +with `Volatile.Read` at `:137` and `Volatile.Write` at `:352`, and the ordering comment at `:349-351`. +Not a contributing cause of #792. + +**One residual worth noting (not the cause).** `EfcItemController.InitializeWebViewAsync` +(`EfcItemController.cs:178-211`) calls `CoreWebView2Environment.CreateAsync` directly at `:194-198` +rather than through `IWebViewCoreInitializer`. Any options-parity fix must cover that site too, and +it is the only environment creation not behind the mockable seam. + +--- + +## Q3 — Pending-document path + +File: `QuickFiler/Controllers/BreadcrumbBridgeRouter.Selection.cs` (221 lines). + +`DeliverDocument` — `:168-180`: + +```csharp +private void DeliverDocument() +{ + string document = _renderer.RenderDocument(_rows, _darkMode, _selectedRowId); + if (_host.IsCoreInitialized) + { + _host.NavigateToString(document); + _pendingDocument = null; // :174 + } + else + { + _pendingDocument = document; // :178 + } +} +``` + +Stash condition: `_host.IsCoreInitialized == false`. Nothing else. + +Field declaration: `private string? _pendingDocument;` at `QuickFiler/Controllers/BreadcrumbBridgeRouter.cs:40`. + +**Complete set of writes** (grep for `_pendingDocument` over all `*.cs`, four production hits): +`BreadcrumbBridgeRouter.cs:40` (declaration), `:322` (read), `:325` (clear); +`BreadcrumbBridgeRouter.Selection.cs:174` (clear), `:178` (stash). There is no occurrence in +`BreadcrumbBridgeRouter.Arrows.cs`. + +**Complete set of drains** — exactly two: +1. `DeliverDocument` itself, on a later call that finds `IsCoreInitialized == true` (`:174`). +2. `BreadcrumbBridgeRouter.NotifyCoreInitialized()` — `BreadcrumbBridgeRouter.cs:320-329`: + navigates `_pendingDocument`, nulls it, then calls `_outboundQueue.OnInitializationCompleted()`. + +**Callers of `DeliverDocument`**: `BindRowsAsync(IReadOnlyList, IEnumerable, string, CancellationToken)` +at `BreadcrumbBridgeRouter.cs:176`, and `ApplyTheme(bool)` at `:313`. + +**Caller of `NotifyCoreInitialized`**: one production site, the lambda at +`EfcFormController.cs:1064`: `_breadcrumbHost.CoreInitialized += (s, e) => _router.NotifyCoreInitialized();` + +**Confirmed: a failed initialization leaves `_pendingDocument` permanently undrained.** `IsCoreInitialized` +can only become true inside `OnCoreInitializationCompleted` (`WebView2BreadcrumbHost.cs:352`), which +the `!e.IsSuccess` return at `:341` skips, and `CoreInitialized` is likewise never raised, so neither +drain can ever fire for the rest of the viewer's life. Every subsequent `BindRowsAsync` and +`ApplyTheme` overwrites the stash with a document nobody will read. + +**Additional finding not in the issue body.** `BreadcrumbOutboundQueue` +(`QuickFiler/Controllers/BreadcrumbOutboundQueue.cs`, 68 lines) has the same shape and the same +single drain. `PostOrQueue` (`:37-52`) enqueues to an unbounded `Queue` (`:18`) whenever +`_host.IsCoreInitialized` is false; `OnInitializationCompleted` (`:59-65`) is its only drain and is +called only from `NotifyCoreInitialized` (`BreadcrumbBridgeRouter.cs:328`). Every selection, +row-render and theme change in a failed session therefore accumulates a serialized payload that is +never released until the router is collected. AC-U2 should cover this queue as well as the document. + +--- + +## Q4 — Fault reporting + +**`TryReportBoundaryFault` does not live where the issue says it does.** It has exactly one +definition in the repository: + +`QuickFiler/Controllers/EfcFormController.cs:150-168` + +```csharp +private void TryReportBoundaryFault(string message, System.Exception exception) +``` + +Behaviour: reads the instance property `BoundaryErrorSink` into a local (`:152`); if null, falls back +to `logger.Error(message, exception)` and returns (`:153-157`); otherwise invokes the sink inside a +`try`, and on a throwing sink logs the sink failure and then the original (`:159-167`). + +What a caller must hold: an `EfcFormController` instance. It is a private instance member, so only +members of `EfcFormController` (including future partial parts) can invoke it. + +The sink it dispatches to: `internal System.Action BoundaryErrorSink { get; set; }` +at `:128-129`, defaulting to `DefaultBoundaryErrorSink` (`:137-141`), which logs and then calls +`UserFaultNotifier` (`:181-185`, an `AsyncLocal`-backed injectable, `:173-174`), whose production +default is the modeless notice `ShowModelessFaultNotice` (`:200-229`, coverage-exempt). + +**The item-controller files named by the issue contain a *different*, similarly-shaped member.** +`QuickFiler/Controllers/EfcItemController.WebViewFaultBoundary.cs:44-65` and +`QuickFiler/Controllers/QfcItemController.WebViewFaultBoundary.cs:44-65` each define +`private void TryReportWebViewInitializationFault(Exception ex)` over a separate +`WebViewInitializationErrorSink` property (`:14-15` and `:13-17` respectively). Their doc comments +explicitly state the naming is deliberate so that no shared contract with +`EfcFormController.BoundaryErrorSink` is implied. Both are reached from +`InitializeWebViewGuardedAsync` (`:25-42` / `:24-42`). + +**Existing call sites of `TryReportBoundaryFault`** (all in `EfcFormController.cs`): `:556`, `:573`, +`:591`, `:653`, `:668` (the five `async void` click-handler boundaries), `:1015` (`RunKbdGuardedAsync`), +`:1127` (`BindBreadcrumbRowsAsync`), `:1270` (`PopulateFolderCombobox`). + +**`EfcFormController.InitializeBreadcrumbHostAsync` — `:1070-1082`:** + +```csharp +// Fire-and-forget host initialization with an error boundary (the router queues every +// outbound payload until CoreWebView2InitializationCompleted fires). +private async Task InitializeBreadcrumbHostAsync() +{ + try { await _breadcrumbHost.InitializeAsync(_formViewer.UiSyncContext); } + catch (System.Exception ex) + { + logger.Error($"Breadcrumb WebView2 initialization failed: {ex.Message}", ex); // :1080 + } +} +``` + +Genuinely fire-and-forget (`_ = InitializeBreadcrumbHostAsync();` at `:1067`), with a total catch, +and **log-only**. AC-U4's claim holds for this member. Note the comment at `:1070-1071` is +now inaccurate: the queue is *not* released "until initialization fires" — on failure it is never +released at all. + +**`EfcFormController.PopulateFolderCombobox` — `:1250-1272`:** + +```csharp +/// #464 C: both call sites discard the result, so the boundary is here. +public async Task PopulateFolderCombobox(object folderList = null) +{ + try { ... } + catch (System.Exception ex) { TryReportBoundaryFault(ex.Message, ex); } // :1270 +} +``` + +It **already** reports through `TryReportBoundaryFault`. AC-U4's claim is **false for this member**; +that half of AC-U4 is already satisfied on `main`. Its two call sites, `:95` and `:115`, do discard +the task, which is why the boundary is inside the method. + +**What routing `InitializeBreadcrumbHostAsync` through the boundary takes:** nothing structural — +replace the `logger.Error(...)` at `:1080` with `TryReportBoundaryFault($"Breadcrumb WebView2 initialization failed: {ex.Message}", ex)`. +Both are instance members of the same type, so no new seam is needed. The only design decision is +whether an `OperationCanceledException` should be classified as non-fault, matching the precedent in +`BindBreadcrumbRowsAsync` (`:1121-1124`, `logger.Debug` for cancellation) and +`RunKbdGuardedAsync`; the existing test +`QuickFiler.Test/Controllers/EfcFormControllerTests.Part2.cs:174-200` pins that classification for +the keyboard guard, so mirroring it is the consistent choice. + +--- + +## Q5 — Pop-out path and the issue-678 carry pattern + +**What the pop-out hands over.** `QuickFiler/Controllers/QfcCollectionController.cs`, both overloads: + +`PopOutControlGroup(int selection)` — `:710-720`: +```csharp +MailItem mailItem = _itemGroups[selection - 1].MailItem; +RemoveSpecificControlGroup(selection); +var popOutForm = new EfcHomeController(_globals, () => { }, mailItem); +popOutForm.Run(); +``` + +`PopOutControlGroupAsync(int selection)` — `:722-735`: identical except +`await RemoveSpecificControlGroupAsync(selection)` (`:730`) and `await popOutForm.RunAsync()` (`:734`). + +So exactly three things cross the boundary: `_globals`, an empty cleanup lambda, and the raw +`MailItem`. The already-built `QfcItemGroup` — which holds `ItemController`, `ItemViewer`, +`PredeterminedFolder` and `CarriedFolderHandler` (`QuickFiler/Controllers/QfcItemGroup.cs:32-59`) — +is discarded. **Confirmed.** + +**The #678 carry pattern.** + +- Carried object: `IFolderSearchHandler` (interface at `UtilitiesCS/OutlookObjects/Folder/IFolderSearchHandler.cs:14-39`; + `FolderPredictor` implements it via the marker partial `UtilitiesCS/OutlookObjects/Folder/FolderPredictor.IFolderSearchHandler.cs:10`). +- Storage on the group: `internal IFolderSearchHandler CarriedFolderHandler { get; set; }` — + `QuickFiler/Controllers/QfcItemGroup.cs:59`. +- Storage on the controller: `private IFolderSearchHandler _carriedFolderHandler;` — + `QuickFiler/Controllers/QfcItemController.cs:259` (doc at `:251-258`). +- Construction sites: `QuickFiler/Controllers/QfcCollectionController.CarrierLoad.cs:124-156` + (`EncapsulateItemGroup`, carry parameter at `:131`, assigned at `:137`, forwarded to the controller + at `:152`), and `QuickFiler/Controllers/QfcQueue.Enqueue.cs:55` and `:180-190` + (`ResolveCarriedHandler`). +- Receiver contract: `QuickFiler/Controllers/QfcItemController.FolderHandling.cs:57-157`, + `LoadFolderHandlerAsync(CancellationToken cancel, object varList = null)`. The adoption is confined + to the `varList is null` branch (`:60-86`): `cancel.ThrowIfCancellationRequested()` then + `_folderHandler = _carriedFolderHandler;` at `:79`, then return. The doc at `:62-67` states the + contract: the carried handler must already be initialised for *this* item with the same + `FolderPredictor.InitOptions.FromField` sequence the branch would otherwise run. Release: + `_carriedFolderHandler = null;` at `QuickFiler/Controllers/QfcItemController.ViewerSetup.cs:433`. +- Constructor plumbing: `QuickFiler/Controllers/QfcItemController.Initialization.cs:52` and `:102`, + both `IFolderSearchHandler carriedFolderHandler = null` as a trailing optional parameter, assigned + at `:55` and `:116`. + +**What the pop-out would have to carry to match.** The Efc analogue of `_folderHandler` is +`EfcDataModel.FolderHelper`, and the Efc analogue of `ItemHelper` is `EfcDataModel.MailInfo`. The +pop-out would need to carry (a) the source `QfcItemController`'s initialized folder handler and +(b) its loaded `MailItemHelper`, and deposit both before `EfcFormController.Initialize()` runs. + +**Current receiver surface — exact signatures.** + +`QuickFiler/Controllers/EfcHomeController.cs` (447 lines): +```csharp +public EfcHomeController(IApplicationGlobals globals, System.Action parentCleanup, MailItem mail = null) // :47-52 +internal EfcHomeController(IApplicationGlobals globals, System.Action parentCleanup, + EfcHomeControllerDependencies dependencies, MailItem mail = null) // :54-95 +private EfcHomeController(IApplicationGlobals globals, System.Action parentCleanup) // :97-102 +public static async Task CreateAsync(IApplicationGlobals globals, + System.Action parentCleanup, MailItem mail = null) // :104-111 +internal static async Task CreateAsync(IApplicationGlobals globals, + System.Action parentCleanup, EfcHomeControllerDependencies dependencies, + MailItem mail = null) // :113-138 +``` + +`QuickFiler/Controllers/EfcDataModel.cs` (499 lines): +```csharp +public EfcDataModel(IApplicationGlobals globals, MailItem mail, + CancellationTokenSource tokenSource, CancellationToken token) // :48-81 +private EfcDataModel(IApplicationGlobals globals, MailItem mail) // :83-87 +public static async Task CreateAsync(IApplicationGlobals globals, IList mailItems, + CancellationTokenSource tokenSource, CancellationToken token, bool loadAll) // :89-142 +public FolderPredictor FolderHelper { get; protected set; } // :177-186 +public async Task InitFolderHandlerAsync(object folderList = null) // :188-221 +public MailItemHelper MailInfo => ConversationResolver?.MailHelper; // :241 +``` + +**Answer: neither the synchronous constructor nor `CreateAsync` can accept a carry today.** +`FolderHelper` has a `protected set`, `ConversationResolver` has a `protected set` (`:224-228`), and +no constructor or factory takes a folder handler or a `MailItemHelper`. Note also that the two +constructors and `CreateAsync` differ in *what* they build, not in whether they accept a carry: the +sync constructor at `:48-81` loads the conversation resolver synchronously (`:68`, +`_conversationResolver.LoadDf()`), while `CreateAsync` at `:89-142` awaits +`ConversationResolver.LoadAsync` (`:116-134`). The pop-out uses the sync constructor indirectly: +`EfcHomeController` internal ctor `:66-71` calls `_dependencies.DataModelFactory(...)`, whose +production binding is `EfcHomeControllerDependencyFactories.cs:20` → +`CreateProductionDataModel`, that is, the 4-argument public `EfcDataModel` constructor. + +**Deposit-point constraint (important for the plan).** `EfcFormController.Initialize()` +(`EfcFormController.cs:79-97`) fires `_ = PopulateFolderCombobox();` at `:95`, and +`InitializeDataFields(EfcDataModel)` (`:111-117`) fires it again at `:115`. +`PopulateFolderCombobox` calls `_dataModel.InitFolderHandlerAsync(folderList)` (`:1262`), which +unconditionally overwrites `FolderHelper` (`EfcDataModel.cs:194`, `:198-206`, `:211-219`). A carry +deposited *after* construction would therefore race a fire-and-forget task that overwrites it. The +carry must either be supplied before the form controller is constructed — that is, between +`EfcHomeController.cs:71` (`DataModel = ...`) and `:85` (`FormControllerWithDataFactory(...)`) — or +be consumed inside `InitFolderHandlerAsync` itself, mirroring +`QfcItemController.LoadFolderHandlerAsync`'s `varList is null` branch. The second is the closer +analogue of #678 and is the recommendation. + +**Type blocker to resolve in the plan.** The QFC carry is typed `IFolderSearchHandler` +(`QfcItemController.cs:259`), but `EfcDataModel.FolderHelper` is the concrete `FolderPredictor` +(`:177-186`). Retyping `FolderHelper` to `IFolderSearchHandler` is *almost* free — the three Efc +consumers are `EfcFormController.cs:1117` (`Suggestions`), `:1266` (`FolderArray`), and +`EfcDataModel.FindMatches` `:483-488` (`FindFolder`, whose named arguments match the interface +declaration exactly) — but it is blocked by one member: `EfcDataModel.RefreshSuggestions` at +`:491-495` calls `_folderHelper.RefreshSuggestions(mailItem: Mail)`, and +`FolderPredictor.RefreshSuggestions(object objItem, int topNfolderKeys = -1)` +(`UtilitiesCS/OutlookObjects/Folder/FolderPredictor.cs:967`) is **not** on `IFolderSearchHandler`. +The plan must choose one of: (a) widen `IFolderSearchHandler` with a `RefreshSuggestions` member, +(b) keep `FolderHelper` concrete and carry a `FolderPredictor` (which means exposing the QFC side as +`FolderPredictor` or performing a cast), or (c) keep the carry field separate from `FolderHelper`. + +**Source-side accessor gap.** `QfcItemController._folderHandler` (`QfcItemController.cs:41`) is +private and `IQfcItemController` exposes no folder-handler member — the only read seam is +`TopFolderScore` (`QfcItemController.cs:265`). `IQfcItemController.ItemHelper` **is** exposed +(`QuickFiler/Interfaces/IQfcItemController.cs:41`, `MailItemHelper ItemHelper { get; set; }`), so the +`MailItemHelper` half of the carry needs no new accessor; the folder-handler half does. + +--- + +## Q6 — UI-thread construction + +**Claim 1 — "`EfcViewerQueue.BuildQueue` has no production call site": CONFIRMED for that member, +but the conclusion drawn from it is REFUTED.** + +- `public static void BuildQueue(int count)` — `QuickFiler/Helper Classes/EfcViewerQueue.cs:29-32`. + Grep for `BuildQueue` across all `*.cs` finds only `QuickFiler.Test/Helper Classes/ViewerQueueStaticWrapperTests.cs:43` + as a caller of this static. **No production caller.** +- However, `ViewerQueueCore.Dequeue` **itself** rebuilds the queue: + `QuickFiler/Helper Classes/ViewerQueueCore.cs:63-85` calls `BuildQueue(cachedReplacementCount, replacementPriority)` + at `:78` on the cached path and `BuildQueue(emptyReplacementCount, replacementPriority)` at `:83` + on the empty path. +- `EfcViewerQueue.Dequeue()` (`:34-43`) passes `emptyQueuePriority: Render`, + `cachedReplacementCount: 1`, `emptyReplacementCount: 2`, `replacementPriority: Background`. +- `ViewerQueueCore.BuildQueue(int, DispatcherPriority)` (`:52-61`) schedules through + `_priorityScheduler`, whose production binding for the Efc queue is + `EfcViewerQueue.cs:16-20`: `(action, priority) => _ = UiThread.Dispatcher.InvokeAsync(action, priority)`. + +So the queue **is** populated in production, on the WPF `UiThread.Dispatcher`. Only the **first** +`Dequeue` in a process finds the queue empty. + +**Claim 2 — "`EfcViewer` is therefore always constructed inline on the calling thread": REFUTED as +stated; TRUE only for the first Efc open.** The empty-queue path calls +`ViewerQueueCore.CreateWithPriority` (`:126-142`), which uses `_blockingPriorityScheduler`. For the +Efc queue that binding is `EfcViewerQueue.cs:25`: +`ProductionBlockingPriorityScheduler = (action, priority) => action();` — literally inline, with the +`priority` argument discarded. (Contrast `ItemViewerQueue.cs:89-90`, whose blocking scheduler is +`UiThread.Dispatcher.Invoke(action, priority)`.) The first `EfcViewer` is therefore built on +whatever thread called `Dequeue`; every subsequent one is built on the WPF dispatcher thread. + +`ProductionViewerFactory` is bound to `EfcViewerQueue.Dequeue` at +`QuickFiler/Controllers/EfcHomeControllerDependencyFactories.cs:39-40` (and re-bound at `:112`), and +`EfcHomeControllerDependencies.ViewerFactory` defaults to it at `EfcHomeControllerDependencies.cs:68`. +`EfcHomeController` calls it at `:77` (sync ctor) and `:226` (`InitAsync`). + +**Claim 3 — "captures `SynchronizationContext.Current` at construction": CONFIRMED.** +`QuickFiler/Viewers/EfcViewer.cs:23-30`: +```csharp +public EfcViewer() +{ + InitializeComponent(); + _context = SynchronizationContext.Current; // :26 + _uiScheduler = TaskScheduler.FromCurrentSynchronizationContext(); // :27 + InitTipsLabelsList(); +} +``` +Exposed as `UiSyncContext` at `:37-40`. This is the value `EfcFormController` passes to +`_breadcrumbHost.InitializeAsync(_formViewer.UiSyncContext)` at `:1076`. + +**Can the pop-out continuation land off the UI thread?** Not established either way, and the answer +matters less than expected. Two findings: + +1. **A null context fails earlier and harder than the issue says.** + `TaskScheduler.FromCurrentSynchronizationContext()` at `EfcViewer.cs:27` throws + `InvalidOperationException` when `SynchronizationContext.Current` is null. So an off-UI-thread, + context-free pop-out continuation fails inside the `EfcViewer` **constructor**, before + `UiThread.SynchronizationContextAwaiter` is ever reached. The issue's cited throw site is real — + `UtilitiesCS/Threading/UiThread.cs:146-153`, `SynchronizationContextAwaiter(SynchronizationContext? context)` + throws `ArgumentNullException(nameof(context))` on a null context — but it is the *second* failure + on that path, not the first. (Note: the issue cites `UiThread.cs:91-98`; the struct now spans + `:140-196`.) +2. **The pop-out's awaits mostly resume on the dispatcher.** + `QfcCollectionController.PopOutControlGroupAsync` (`:722-735`) awaits + `RemoveSpecificControlGroupAsync(selection)` (`:913-1012`) before constructing the home + controller. That method's own awaits are `ToggleOffActiveItemAsync(false)` (`:925`) and + `UiThread.Dispatcher.InvokeAsync(...)` (`:951`). I did not trace `ToggleOffActiveItemAsync` to a + terminal `ConfigureAwait(false)`, so **I could not verify** that the continuation reaches + `new EfcHomeController(...)` off the UI thread. Treat "the pop-out continuation lands off the UI + thread" as **unverified**. + +**The guard or seam a fix would use.** The repository already ratified the answer for the sibling +viewer under issue #781 (`docs/features/active/2026-09-05-breadcrumb-ui-boundary-guard-rejects-dispatcher-built-viewers-781/issue.md`, +AC1-AC3): prove UI ownership by **owner-thread identity** (the `Dispatcher` captured at construction, +via `CheckAccess()`, or the constructing thread's managed thread id) rather than by +`SynchronizationContext` reference equality, because dispatcher-built viewers capture a +`DispatcherSynchronizationContext` that is never the thread's ambient context again. For #792 the +concrete seam is: +- construct the `EfcViewer` through `UiThread.Dispatcher.Invoke` rather than inline — that is, change + `EfcViewerQueue.ProductionBlockingPriorityScheduler` (`EfcViewerQueue.cs:25`) to match + `ItemViewerQueue.cs:89-90`; this is a one-line change in a 101-line file, and + `QuickFiler.Test/Helper Classes/ViewerQueueStaticWrapperTests.cs` already substitutes that + scheduler (`:244-261`), so it is directly testable; and/or +- assert that `_formViewer.UiSyncContext` is non-null before + `_breadcrumbHost.InitializeAsync(...)` at `EfcFormController.cs:1076`, failing through + `TryReportBoundaryFault` instead of letting `ArgumentNullException` escape a fire-and-forget task. + +--- + +## Q7 — Archive-root guard + +**The issue's factual claim is true; its implied severity is stale.** + +`EfcFormController.BindBreadcrumbRowsAsync` — `EfcFormController.cs:1111-1129`: +```csharp +internal async Task BindBreadcrumbRowsAsync(string[] rows) +{ + try + { + var scores = _dataModel?.FolderHelper?.Suggestions?.ToScoredArray() ?? Array.Empty(); + await _router.BindRowsAsync(rows, scores, _globals.Ol.ArchiveRootPath, Token); // :1119 + } + catch (OperationCanceledException) { logger.Debug("Breadcrumb bind canceled."); } // :1121-1124 + catch (System.Exception ex) { TryReportBoundaryFault($"Breadcrumb bind failed: {ex.Message}", ex); } // :1127 +} +``` + +- Confirmed: `_globals.Ol.ArchiveRootPath` is read directly at `:1119`, not through a `Try...` helper. +- Confirmed: `EfcDataModel.TryGetArchiveRoot(out string archiveRoot)` exists at + `QuickFiler/Controllers/EfcDataModel.cs:280-297` (doc `:271-279`, message constant `:267-269`) and + is **not** used on the bind path. Its three call sites are all filing/opening paths: + `MoveToFolderAsync` `:327`, `OpenOlFolderAsync` `:370`, `OpenFsFolderAsync` `:394`. It is also + `private`, so `EfcFormController` cannot call it without an access change. +- **But the read is already fail-soft and already user-surfaced.** The documented + `InvalidOperationException` is caught by the general `catch` at `:1125` and routed through + `TryReportBoundaryFault` at `:1127`, whose default sink surfaces to the user + (`DefaultBoundaryErrorSink` `:137-141` → `UserFaultNotifier` `:181-185`). This behaviour is pinned + by an existing test: + `QuickFiler.Test/Controllers/EfcFormControllerTests.Part2.cs:241-271`, + `BindBreadcrumbRowsAsync_WhenArchiveRootThrows_ReportsOnceAndDoesNotThrow`, which asserts no + throw, exactly one sink call, and `ArchiveRootPath` read exactly once. A second existing test, + `QuickFiler.Test/Controllers/EfcFormControllerTests.cs:61-...`, + `Issue439BindBreadcrumbRowsAsync_SubmitsArchiveRootToRealRouter`, pins the success path. + +**The remaining behavioural gap** is not the absence of a guard but the *shape* of the degradation: +`TryGetArchiveRoot` degrades to "no archive root, continue" (returns false, caller decides), whereas +the bind boundary aborts the whole bind, so an unresolvable archive root produces an **empty folder +list** — the same user-visible symptom #792 reports, from a different cause. Whether to converge on +the `TryGetArchiveRoot` semantics (bind with an empty root, which +`BreadcrumbBridgeRouter.BindRowsAsync` already tolerates: `_boundRoot` empty at +`BreadcrumbBridgeRouter.cs:115-117`, and the pass-through branches at `:192-195` and `:260-265`) is a +design decision the plan should make explicitly. + +**Note on the two commits named in the assignment.** The Bash tool is disabled in this session, so I +**could not verify** commits `f50fb7271` and `655130c5a` by SHA. What I verified instead is the +current source state described above, which is what the plan must be written against. The `#799 AC4` +and `#736 finding 4/5` annotations present in the files (`QfcItemController.FolderHandling.cs:226-230`, +`EfcFormController.cs:131-136`, `:170-172`, `:176-180`) are consistent with archive-root and +boundary-sink work having already landed. + +Related sibling, already open: `docs/features/active/2026-09-08-assignfoldercombobox-unguarded-archiverootpath-read-813/issue.md` +covers the QFC twin of this read, and the QFC side is already guarded at +`QfcItemController.FolderHandling.cs:231-245` with an explicit `catch (InvalidOperationException)` +that degrades to `string.Empty`. + +--- + +## Q8 — Testability and existing test coverage + +All test classes below are in project **`QuickFiler.Test`**, csproj +`QuickFiler.Test/QuickFiler.Test.csproj`, which lists every source file explicitly (no globbing). + +| Production type | Test classes | csproj `Compile` line | +|---|---|---| +| `WebView2BreadcrumbHost` | `QuickFiler.Test/Viewers/WebView2BreadcrumbHostTests.cs` (`WebView2BreadcrumbHostTests`), `QuickFiler.Test/Viewers/WebView2BreadcrumbHostContractTests.cs` | `:214` (and the contract file, listed separately) | +| `BreadcrumbBridgeRouter` | `BreadcrumbBridgeRouterTests.cs`, `BreadcrumbBridgeRouterTests.Selection.cs`, `BreadcrumbBridgeRouterQueueTests.cs`, `BreadcrumbBridgeRouterQueueTests.Part2.cs`, `BreadcrumbBridgeRouterIssue439Tests.cs`, `...Issue439Tests.Activation.cs`, `...Issue614Tests.cs`, `...Issue637Tests.cs`, `BreadcrumbBridgeRouterScoreJoinTests.cs` (all under `QuickFiler.Test/Controllers/`) | `:60`, `:61` and adjacent | +| `EfcFormController` | `QuickFiler.Test/Controllers/EfcFormControllerTests.cs` and `EfcFormControllerTests.Part2.cs` (one `partial class EfcFormControllerTests`, namespace `QuickFiler.Controllers.Tests`) | `:124`, `:125` | +| `EfcDataModel` | `EfcDataModelTests.cs`, `EfcDataModelArchiveRootTests.cs`, `EfcDataModelIssue614Tests.cs` | `:122`, `:123`, `:121` | +| `QfcCollectionController` | `QfcCollectionControllerTests.cs`, `QfcCollectionControllerTests.Part2.cs`, `QfcCollectionController.TestSupport.cs`, plus `...DarkModeTests`, `...Defects468*Tests`, `...Layout.StaTests`, `...NavigationDigitsTests`, `...NavigationLedgerTests` | `:139`, `:165` and adjacent | +| `EfcViewerQueue` / `ViewerQueueCore` | `QuickFiler.Test/Helper Classes/ViewerQueueStaticWrapperTests.cs`, `ViewerQueueCoreTests.cs` | `:226` and adjacent | + +**Infrastructure already proven in this project** (this is the decisive enabling fact): + +- Real `Microsoft.Web.WebView2.WinForms.WebView2` controls are constructed in unit tests on + `WinFormsPumpHost`: `WebView2BreadcrumbHostTests.cs:36-52`. Constructing the control needs no + Evergreen runtime; only `EnsureCoreWebView2Async` / `CoreWebView2Environment.CreateAsync` do. +- `Mock` drives `WebView2BreadcrumbHost.InitializeAsync` end-to-end without + reaching the SDK: `WebView2BreadcrumbHostTests.cs:399-419` (`BuildCompletingInitializer`), which + returns `Task.FromResult(null)` and `Task.CompletedTask`. +- `RecordingSynchronizationContext` (`WebView2BreadcrumbHostTests.cs:428-438`) counts posts without + draining them. +- `EfcFormController` is constructible headlessly via its private no-arg constructor: + `EfcFormControllerTests.cs:24-34` (`CreateMinimalController`), with `SetPrivateField` for + `_globals` / `_router`. +- The Efc viewer queue's four production scheduler delegates are settable seams: + `EfcViewerQueue.cs:10-25`, with `SetCoreForTesting` (`:48-51`), + `ResetCoreForTesting` (`:56-60`) and `ResetProductionCoreDefaultsForTesting` (`:62-69`), already + exercised at `ViewerQueueStaticWrapperTests.cs:18-19, 41-43, 75-77, 244-261`. + +**Reachability of Q1-Q7 behaviours from a unit test today:** + +| Behaviour | Reachable today? | Seam | +|---|---|---| +| Q1 failure branch of `OnCoreInitializationCompleted` | **No, and not makeable reachable.** `CoreWebView2InitializationCompletedEventArgs` has no public constructor. | none — must move the retry/surfacing logic to the awaited-task path | +| Q1 failure of `InitializeAsync` / `InitializeBreadcrumbHostAsync` | **Yes** | `Mock` whose `EnsureCoreWebView2Async` returns a faulted task | +| Q2 options parity across the three sites | **Yes, as a structural test** | assert the three option sets agree; the two seam sites are verifiable through `Mock.Verify(CreateEnvironmentAsync(folder, It.Is(o => o.AdditionalBrowserArguments == expected)))`; the third (`EfcItemController.cs:194`) bypasses the seam and needs one first | +| Q3 `_pendingDocument` stash and drain | **Yes, already** | `Mock` with `IsCoreInitialized` false then true; existing precedent `BreadcrumbBridgeRouterQueueTests.cs:117, 450-455` calls `NotifyCoreInitialized()` | +| Q3 outbound-queue drain | **Yes, already** | `BreadcrumbOutboundQueue.PendingCount` (`:29`) is public | +| Q4 boundary routing of `InitializeBreadcrumbHostAsync` | **New seam needed** | the member is `private` and reads `_breadcrumbHost` / `_formViewer`; make it `internal` and give the host field an injectable type (`IBreadcrumbWebHost` + a `Func` or an initializer delegate), or extract the retry policy into a testable helper | +| Q5 pop-out carry | **New seam needed** | `PopOutControlGroupAsync` news up `EfcHomeController` directly (`QfcCollectionController.cs:732`); needs a `Func<...,EfcHomeController>` factory seam, plus a read accessor for `QfcItemController._folderHandler` | +| Q6 inline vs. dispatcher viewer construction | **Yes, already** | `EfcViewerQueue.ProductionBlockingPriorityScheduler` / `ProductionPriorityScheduler` | +| Q7 archive-root throw at the bind boundary | **Yes, already covered** | `EfcFormControllerTests.Part2.cs:241-271` | + +**AC-by-AC automatability:** + +| AC | Automatable | Seam required | +|---|---|---| +| AC-U1 (retry; visible error state on final failure) | **Yes**, provided the retry lives in `InitializeBreadcrumbHostAsync` (or an extracted policy object) rather than in the SDK event handler | `Mock` returning a faulted task N times then a completed one; plus an `internal` entry point on `EfcFormController` and an injectable host/initializer. The "visible error state" must be asserted at the router/renderer level (a banner row via `BreadcrumbRowBuilder.BannerPrefix = "===="`, `UtilitiesCS/OutlookObjects/Folder/BreadcrumbRowBuilder.cs:19`; banner rows are already non-selectable, `BreadcrumbBridgeRouter.Selection.cs:85-88`), not at the WebView2 level | +| AC-U2 (`_pendingDocument` never silently dropped) | **Yes** | `Mock` only; no new seam. Assert (a) a later successful `NotifyCoreInitialized` navigates the stash, and (b) a new failure notification produces an error document and leaves no stash. Extend the same test to `BreadcrumbOutboundQueue.PendingCount` | +| AC-U3 (pop-out carries predictor + `MailItemHelper`; `EfcViewer` on the UI thread) | **Partly.** The carry half is automatable; the "constructs on the UI thread" half is automatable only as a scheduler-delegate assertion, not as a real thread-affinity assertion | carry half: an `EfcHomeController` factory seam on `QfcCollectionController` + a folder-handler accessor on `QfcItemController`/`IQfcItemController` + a carry parameter path into `EfcDataModel.InitFolderHandlerAsync`. UI-thread half: `EfcViewerQueue.ProductionBlockingPriorityScheduler` substitution, recording the priority and the fact that the action was scheduled rather than run inline | +| AC-U4 (`PopulateFolderCombobox` and `InitializeBreadcrumbHostAsync` report through `TryReportBoundaryFault`) | **Yes** | `PopulateFolderCombobox` already satisfies it and is already covered (`EfcFormControllerTests.cs:300-...`, `PopulateFolderCombobox_WhenDataModelFaults_LogsOnceAndDoesNotFault`) — that test asserts "logs once", so it will need strengthening to assert the sink. `InitializeBreadcrumbHostAsync` needs the seam described under AC-U1; then `BoundaryErrorSink` substitution is the assertion point | +| AC-U5 | **Not automatable** (manual live-Outlook), as instructed | — | + +Policy constraints observed throughout: MSTest + Moq + FluentAssertions, no temporary files, no live +Outlook, no `Thread.Sleep`/`Task.Delay`. None of the proposed tests need a temporary file; the +`%LocalAppData%` cache folder is only ever *computed* as a string in the testable path, never created +(`WebView2BreadcrumbHost.cs:246-249` computes it and hands it to the mocked seam). + +--- + +## Q9 — File-size and project-file constraints + +**Measured line counts (re-derived in this session):** + +| File | Lines | Status | +|---|---|---| +| `QuickFiler/Controllers/QfcCollectionController.cs` | 2329 | far over the 500 ceiling | +| `QuickFiler/Controllers/EfcFormController.cs` | 1321 | over the ceiling; any edit obliges a split | +| `QuickFiler/Controllers/EfcDataModel.cs` | 499 | **one line under**; any addition forces a split | +| `QuickFiler/Viewers/WebView2BreadcrumbHost.cs` | 368 | headroom ~132 | +| `QuickFiler/Controllers/BreadcrumbBridgeRouter.Selection.cs` | 221 | headroom | +| `QuickFiler/Controllers/BreadcrumbBridgeRouter.cs` | 407 | headroom ~93 | +| `QuickFiler/Viewers/BreadcrumbUiDispatcher.cs` | 285 | headroom | +| `QuickFiler/Viewers/EfcViewer.cs` | 169 | headroom (but `[ExcludeFromCodeCoverage]` at `:20`) | +| `QuickFiler/Helper Classes/EfcViewerQueue.cs` | 101 | headroom | +| `QuickFiler/Controllers/QfcItemController.FolderHandling.cs` | 310 | headroom | +| `QuickFiler/Controllers/EfcHomeController.cs` | 447 | headroom ~53 — tight | +| `QuickFiler/Controllers/EfcHomeControllerDependencies.cs` | 428 | headroom ~72 — tight | +| `QuickFiler/Controllers/BreadcrumbOutboundQueue.cs` | 67 | headroom | + +**Orchestrator re-measurement, 2026-09-12 (three additions and one correction to the table above).** +The following counts were taken independently by the orchestrator against the same HEAD and supersede +any figure carried from prior research: + +| File | Lines | Status | +|---|---|---| +| `QuickFiler/Controllers/EfcItemController.cs` | 1121 | **over the 500 ceiling.** The root-cause remedy edits this file, so the edit obliges a split of it. The Q9 table above omitted this file and the risk list did not record the obligation. | +| `QuickFiler/Controllers/QfcItemController.ViewerSetup.cs` | 467 | headroom 33. Prior research recorded 499; that figure is **wrong**. A line-neutral edit is still preferred, but the file is not at the ceiling. | +| `QuickFiler/Controllers/QfcItemController.cs` | 334 | headroom | +| `QuickFiler/Controllers/EfcHomeControllerDependencies.cs` | 428 | headroom 72 — tight | + +`QuickFiler/Controllers/EfcHomeController.cs` is confirmed at 447 lines. Note also that prior drafts of +this document spelled its path as a repository-root sibling; the tracked path is +`QuickFiler/Controllers/EfcHomeController.cs` and has been corrected throughout. + +**`Compile` item XML shape.** `QuickFiler/QuickFiler.csproj` is a non-SDK-style project with an +explicit item list. The plain shape is a self-closing element inside the ``: + +```xml + +``` + +(`QuickFiler.csproj:296`). Paths use backslashes and are project-relative. + +**Are partial-class files listed individually?** Yes, each on its own line, with no metadata. Direct +examples: `Controllers\BreadcrumbBridgeRouter.cs` `:291`, `Controllers\BreadcrumbBridgeRouter.Arrows.cs` +`:292`, `Controllers\BreadcrumbBridgeRouter.Selection.cs` `:293`; `Controllers\EfcDataModel.cs` `:289` +and `Controllers\EfcDataModel.FilingStem.cs` `:290`; the twelve `QfcItemController.*.cs` parts at +`:333-344`; the five `QfcFormController.*.cs` parts at `:321-325`. + +**Is there a `DependentUpon` convention for partial files?** **No.** Every `DependentUpon` in the file +is a Designer/resx pairing: `:381` (`Resources.resx`), `:386` (`Settings.settings`), `:392`, `:398`, +`:431`, `:435`, `:439`, `:443`, `:447`, `:451`, `:457`, `:463`, `:469`, `:475`, `:503`, `:506`, +`:510`, `:514`, `:518`, `:522`. Hand-written partial parts carry **no** metadata and **no** +`DependentUpon`. A new partial part must therefore be added as a bare self-closing ``. + +**Proposed split of `EfcFormController.cs`.** The type is declared at `:26` as +`internal class EfcFormController : IFilerFormController`; the split requires adding `partial` there. +The existing `#region` boundaries map cleanly onto files, and the names follow the +`QfcFormController.*` / `QfcItemController.*` convention already in the tree. + +| New file (all under `QuickFiler/Controllers/`) | Source span moved | Members | Approx. lines | +|---|---|---|---| +| `EfcFormController.cs` (retained) | `1-264` | usings, `partial class` declaration, `#region Constructors` (`:28-119`: two public ctors, private no-arg ctor, `Initialize`, `InitializeWithoutData`, `InitializeDataFields`), `#region Private Properties` (`:121-264`: `logger`, `BoundaryErrorSink`, `DefaultBoundaryErrorSink`, `TryReportBoundaryFault`, `_userFaultNotifier`, `UserFaultNotifier`, `ShowModelessFaultNotice`, all fields) | ~267 | +| `EfcFormController.SetupAndProperties.cs` | `266-479` | `#region Setup and Cleanup Methods` (`CaptureConfigureItemViewer`, `Cleanup`, `ConfigureFind`, `ResolveControlGroups`, `SetupThemes`) and `#region Public Properties` (`LoadTheme`, `FormHandle`, the remaining properties) | ~240 | +| `EfcFormController.EventHandlers.cs` | `481-834` | `#region Event Handlers`: `RegisterAlwaysOnAsyncKeyActions`, `WireEventHandlers`, `SearchText_DownArrow`, the five `async void` click handlers and their `...ClickAsync` cores, the four `CheckedChanged` handlers, `SearchText_TextChanged`, `EditFiltersMenuItem_Click`, `CharacterAsyncActions`/`GetAsyncCharacterActions`, `CharacterActions`/`GetKbdActions`, `DarkMode_Changed` | ~380 | +| `EfcFormController.Actions.cs` | `836-990` | `#region Major Actions`: `ActionOkAsync`, `ActionCancelAsync`, `WithTrashRow`, `ApplyDeleteGesture`, `ActionDeleteAsync`, `CreateFolderAsync`, `MatchesForSearchText`, `RefreshSuggestionsAsync` | ~180 | +| **`EfcFormController.Breadcrumb.cs`** | `1047-1129` + `1251-1287` | `ConfigureBreadcrumbControl`, `InitializeBreadcrumbHostAsync`, `BindFolderRows`, `BindSourceFolderRows`, `BindBreadcrumbRowsAsync`, `PopulateFolderCombobox`, `IsBannerRow`, `IsSelectableFolder`, `IsValidSelection` — **this is the only new file the #792 fix edits** | ~150 | +| `EfcFormController.Helpers.cs` | `992-1045` + `1131-1249` + `1289-1320` | `RunKbdGuardedAsync`, both `KbdExecuteAsync` overloads, `JumpToAsync`, `MaximizeFormViewer`, `MinimizeFormViewer`, `ShowMenu`, `ToggleCheckboxAsync`, the four `ToggleOn/OffNavigation*`, the three `ToggleTips*`, `LoadUserSettings`, `ToggleExpansionStyle` | ~230 | + +Each resulting file is under 500 lines. Six `` +lines must be added to `QuickFiler/QuickFiler.csproj` adjacent to `:296`, with no metadata. Because +`QuickFiler.Test` references the type only through `CreateMinimalController` reflection and +`internal` members (the project grants `InternalsVisibleTo`), no test file needs to change for the +split alone. + +**`EfcDataModel.cs` at 499/500.** Stated explicitly: **any addition to this file forces a split.** +The precedent partial is `QuickFiler/Controllers/EfcDataModel.FilingStem.cs` (csproj `:290`). If the +AC-U3 carry lands in `InitFolderHandlerAsync`, the recommended split target is a new +`QuickFiler/Controllers/EfcDataModel.Carry.cs` holding the carry field, the carry-aware +`InitFolderHandlerAsync` (moved from `:188-221`, 34 lines), and the adoption doc — roughly 70 lines, +leaving `EfcDataModel.cs` at about 465. An alternative, if the plan prefers not to move a live +method, is to move `ArchiveRootUnavailableMessage` + `TryGetArchiveRoot` (`:263-297`, 35 lines) into +a new `QuickFiler/Controllers/EfcDataModel.ArchiveRoot.cs`, leaving `EfcDataModel.cs` at about 464. +Either way a csproj line is required. + +**`QfcCollectionController.cs` at 2329.** The AC-U3 edit to `PopOutControlGroup` / +`PopOutControlGroupAsync` (`:710-735`) obliges a split of this file too. The existing precedent part +is `QuickFiler/Controllers/QfcCollectionController.CarrierLoad.cs` (csproj `:315`). The natural +target is a new `QuickFiler/Controllers/QfcCollectionController.PopOut.cs` holding just the two +pop-out members and the new factory seam (~60 lines). Note this does **not** bring the parent file +under 500; a full split of a 2329-line file is a much larger piece of work and should be declared +out of scope explicitly rather than attempted inside a bug fix. + +--- + +## Q10 — Contention awareness (report only) + +Two sibling items in the same parallel run touch adjacent surface. + +**Sibling A — `CultureInfo.InvariantCulture` on date/time formatting, editing `QfcCollectionController.cs`.** +This is issue **#645**, folder `docs/features/active/2026-09-02-quickfiler-session-metrics-twelve-hour-time-format-645/`. +Its issue body states the numeric format calls already pass `CultureInfo.InvariantCulture` and the +date/time calls were left, and proposes adding it. The un-cultured format calls in +`QuickFiler/Controllers/QfcCollectionController.cs` are at: +- `:235` — `grp.ItemController.Mail.SentOn.ToString("MM/dd/yyyy")` +- `:1296` — `c.ItemHelper.SentDate.ToString("MM/dd/yyyy")` and `.ToString("HH:mm")` +- `:2302` — `qf.ItemHelper.SentDate.ToString("MM/dd/yyyy")` and `.ToString("HH:mm")` + +**Collision risk: same file, disjoint line ranges.** #792's AC-U3 edit targets `:710-735` +(`PopOutControlGroup` / `PopOutControlGroupAsync`). There is no line overlap. **However**, if #792 +splits `QfcCollectionController.cs` into a new `.PopOut.cs` partial (Q9), the split deletes lines +`710-735` from the parent file and adds a `` line to `QuickFiler/QuickFiler.csproj`. Both +are textual changes to files #645 also edits, and the csproj is a single shared item list. Declare +`QuickFiler/Controllers/QfcCollectionController.cs` and `QuickFiler/QuickFiler.csproj` in #792's +write set and expect a merge with #645 on the csproj `` ordering. + +**Sibling B — a UI-marshalling seam on `ItemViewer`.** The best match is issue **#781**, folder +`docs/features/active/2026-09-05-breadcrumb-ui-boundary-guard-rejects-dispatcher-built-viewers-781/`, +which replaces `ItemViewer.ThrowIfOffUiBoundary`'s `SynchronizationContext` reference comparison with +owner-thread identity. Its **AC7 explicitly scopes it to `QuickFiler/Viewers/ItemViewer*.cs` +production files and their tests**, and explicitly forbids changing +`QfcCollectionController.LoadSecondaryAsync`, `QfcItemController.AssignFolderComboBox`, or +`QfcItemController.EnsureBreadcrumbPipeline`. A second candidate is issue **#489** +(`docs/features/active/2026-08-25-itemviewer-surface-defects-489/`). + +**Collision risk with #792: low but non-zero, and conceptual rather than textual.** +- No file overlap. #792 touches `QuickFiler/Viewers/WebView2BreadcrumbHost.cs`, + `QuickFiler/Viewers/EfcViewer.cs` (possibly), `QuickFiler/Helper Classes/EfcViewerQueue.cs`, + `QuickFiler/Controllers/EfcFormController*.cs`, `QuickFiler/Controllers/BreadcrumbBridgeRouter*.cs`, + `QuickFiler/Controllers/EfcDataModel*.cs`, `QuickFiler/Controllers/QfcCollectionController*.cs`, + `QuickFiler/Controllers/QfcItemController*.cs` (accessor), and `QuickFiler/QuickFiler.csproj`. + #781 touches `QuickFiler/Viewers/ItemViewer*.cs` only. **Disjoint except the csproj**, and #781 + adds no file, so even that is unlikely. +- **Conceptual overlap**: both items decide how "am I on the UI boundary?" is proven. + `QuickFiler/Viewers/BreadcrumbUiDispatcher.cs:255-278` (`IsCurrentBoundary`) uses context reference + equality with a currently-executing-callback escape hatch, and #781's issue body explicitly + assesses it as self-consistent and out of scope. #792's Q6 remedy should adopt the same + owner-thread-identity idiom #781 ratifies rather than inventing a third convention, but should not + edit `BreadcrumbUiDispatcher.cs` unless a test proves a need. +- **Third-party overlap worth flagging**: `QuickFiler/Controllers/QfcItemController.ViewerSetup.cs:61` + (the `--incognito` options site #792 must touch) sits 40 lines from + `QfcItemController.ViewerSetup.cs:113-122`, the breadcrumb drop-down attach path #781's manual + retest step names. The file is 499 lines (per prior research), so it has essentially **no headroom** + — a #792 edit that adds lines there forces yet another split. Prefer replacing the literal at `:61` + with a reference to a shared constant, which is line-neutral. + +--- + +## Corrections to the issue body + +| Issue-body claim | Current reality | +|---|---| +| `BreadcrumbBridgeRouter.DeliverDocument` at `BreadcrumbBridgeRouter.Selection.cs:168-180` | **Correct as written.** `DeliverDocument` is at `:168-180`. | +| `WebView2BreadcrumbHost` "(`:330-342`) returns on `!e.IsSuccess`" | Close but off by one at the start: `OnCoreInitializationCompleted` spans `:329-354` (attribute at `:329`, signature `:330-333`); the `!e.IsSuccess` block is `:335-342` and the `return` is `:341`. | +| `EfcFormController.InitializeBreadcrumbHostAsync (:1071-1081)` | Now `:1072-1082` (comment at `:1070-1071`). File is 1321 lines, not 1181. | +| `EfcFormController.PopulateFolderCombobox (:1250-1271)` "fire-and-forget with total catch blocks, so the failure is log-only" | Span is now `:1250-1272` (doc comment `:1250`, signature `:1251`). **The substantive claim is false**: `:1270` already calls `TryReportBoundaryFault`. Only `InitializeBreadcrumbHostAsync` is log-only. | +| `EfcFormController.BindBreadcrumbRowsAsync (:1115-1118)` reads `_globals.Ol.ArchiveRootPath` unguarded | Span is now `:1111-1129`; the read is at `:1119`. The read is literally unguarded, but it is inside a `try` whose `catch` routes to `TryReportBoundaryFault` at `:1127`, and that behaviour is pinned by `QuickFiler.Test/Controllers/EfcFormControllerTests.Part2.cs:241-271`. | +| `TryGetArchiveRoot (EfcDataModel.cs:280-297)` | **Correct.** `:280-297`, doc `:271-279`. Also note it is `private`, so `EfcFormController` cannot call it as-is. | +| `QfcCollectionController.PopOutControlGroup (:710-735)` | `PopOutControlGroup` is `:710-720`; `PopOutControlGroupAsync` is `:722-735`. The cited range covers both, which is probably intended. File is 2329 lines. | +| `EfcDataModel.cs:48-81` (sync ctor) vs `CreateAsync :89-142` | **Both correct.** | +| `QfcItemController.FolderHandling.cs:68-83`, `_carriedFolderHandler` | **Correct.** The carry branch is `:68-86` with the assignment at `:79`; `:68-83` covers the guard and the assignment. | +| "`EfcViewerQueue.BuildQueue` has no production call site, so `EfcViewer` is always constructed inline on the calling thread" | **Half wrong.** The public `EfcViewerQueue.BuildQueue(int)` (`:29-32`) indeed has no production caller, but `ViewerQueueCore.Dequeue` calls the internal `BuildQueue(count, priority)` overload at `ViewerQueueCore.cs:78` and `:83`, scheduling through `UiThread.Dispatcher.InvokeAsync` (`EfcViewerQueue.cs:20`). Only the **first** `EfcViewer` in a process is built inline; the rest are built on the WPF dispatcher. The inline behaviour comes from `ProductionBlockingPriorityScheduler = (action, priority) => action();` at `EfcViewerQueue.cs:25`, not from the absence of `BuildQueue`. | +| `EfcViewer.cs:23-30` captures the context | **Correct.** `:26` captures the context; `:27` also captures a `TaskScheduler`, which **throws** if the context is null. File is 169 lines. | +| "`UiThread.SynchronizationContextAwaiter` throws on the null context (`UiThread.cs:91-98`)" | Stale span. The struct is `UtilitiesCS/Threading/UiThread.cs:140-196`; the `ArgumentNullException` is at `:146-153`. It is also not the first throw on that path — `EfcViewer.cs:27` throws earlier. | +| "`TryReportBoundaryFault` ... likely lives in `EfcItemController.WebViewFaultBoundary.cs` and `QfcItemController.WebViewFaultBoundary.cs`" | **Wrong location.** `TryReportBoundaryFault` is defined once, at `EfcFormController.cs:150-168`. Those two files define a differently named member, `TryReportWebViewInitializationFault` (`:44-65` in each), over a separate `WebViewInitializationErrorSink`, and their docs state the separation is deliberate. | +| "Both `WebView2BreadcrumbHost` and `EfcFormController` log the same failure, suggesting the initialization is attempted from two paths" | **Refuted.** One SDK failure, two log statements (`WebView2BreadcrumbHost.cs:337-340` and `EfcFormController.cs:1080`). One construction site, one `InitializeAsync` call site. | +| Suspected cause: handle/parent state, double-init, disposed/pooled control, or user-data-folder conflict | The first three are refuted (Q2). The fourth is correct in family but must be stated as an **options** conflict, not a folder conflict: all three sites use the identical folder. | +| Reference to the `#458` pooled-viewer handler-retention history as relevant background | Both `#458` and `#476` are **already fixed in the current tree** and neither contributes to `#792` (Q2). | + +--- + +## Recommended fix shape + +### Root cause remedy (prerequisite to AC-U1 and AC-U5; not itself an AC) + +Give the process **one owner** of the WebView2 environment contract and make all three sites use it. + +- **New file** `QuickFiler/Viewers/WebView2EnvironmentContract.cs` (~60 lines), plus a + `` line in `QuickFiler/QuickFiler.csproj` + adjacent to `:421`. Contents: `internal const string AdditionalBrowserArguments = "--incognito ";`, + `internal static string ResolveUserDataFolder()` (the `Path.Combine(LocalApplicationData, "WindowsFormsWebView2")` + expression currently duplicated three times), and + `internal static CoreWebView2EnvironmentOptions CreateOptions()`. +- **Edit** `QuickFiler/Viewers/WebView2BreadcrumbHost.cs:246-250` to use it. This is the + behaviour-changing line: the breadcrumb environment acquires `--incognito` and stops conflicting. +- **Edit** `QuickFiler/Controllers/QfcItemController.ViewerSetup.cs:55-61` and + `QuickFiler/Controllers/EfcItemController.cs:176, 181-189` to reference the same owner + (line-neutral, so neither near-500-line file is pushed over). +- Consider, but do not require, routing `EfcItemController.cs:194-198` through + `IWebViewCoreInitializer` so all three creations are behind one mockable seam. + +**Test seam**: `Mock` verifying +`CreateEnvironmentAsync(It.Is(f => f.EndsWith(@"\WindowsFormsWebView2")), It.Is(o => o.AdditionalBrowserArguments == WebView2EnvironmentContract.AdditionalBrowserArguments))` +for `WebView2BreadcrumbHost.InitializeAsync`. Add a structural parity test in +`QuickFiler.Test/Viewers/` asserting the three sites resolve to one constant — the repository already +has precedent for set-equality structural tests (see the ribbon catalog/XML tests noted in prior +research). + +### AC-U1 — retry, and a visible error state on final failure + +- **File** `QuickFiler/Controllers/EfcFormController.Breadcrumb.cs` (new partial part, Q9). + Make `InitializeBreadcrumbHostAsync` `internal` and give it a bounded, deterministic retry (fixed + attempt count, **no** `Task.Delay` — the determinism rule in `.claude/rules/general-unit-test.md` + bans wall-clock waits in tests, so either retry immediately or inject a delay delegate defaulting + to a real one and substituted with a no-op in tests). +- On final failure, call a **new** router entry point (see AC-U2) so the folder area renders a + visible error banner rather than staying blank. +- Do **not** put the retry in `WebView2BreadcrumbHost.OnCoreInitializationCompleted` + (`:329-354`): that branch is structurally untestable. +- **Seam the new test uses**: `Mock` whose `EnsureCoreWebView2Async` returns + `Task.FromException(new COMException(..., unchecked((int)0x8007139F)))` for the first N calls; + assert the call count and, on exhaustion, that the router received the failure notification. This + requires `_breadcrumbHost` to be injectable — either widen the field to `IBreadcrumbWebHost` and + add an internal setter/test constructor, or extract the retry into a small testable policy type. + +### AC-U2 — `_pendingDocument` is never silently dropped + +- **File** `QuickFiler/Controllers/BreadcrumbBridgeRouter.cs` (407 lines, headroom ~93). Add + `public void NotifyInitializationFailed(Exception error)` next to `NotifyCoreInitialized` + (`:320-329`). It must (a) render and deliver an error document through `_renderer`/`_host` or, if + the host cannot navigate, expose the pending state rather than silently retaining it, (b) clear + `_pendingDocument`, and (c) drain or discard `_outboundQueue` explicitly rather than leaving it to + grow unboundedly (`BreadcrumbOutboundQueue.cs:37-52`). +- Preserve the existing success behaviour: a later successful `NotifyCoreInitialized` must still + navigate a stash produced before the failure. +- **File** `QuickFiler/Controllers/EfcFormController.Breadcrumb.cs`: wire the new notification next + to the existing `_breadcrumbHost.CoreInitialized += ...` lambda (currently `EfcFormController.cs:1064`). +- **Seam the new tests use**: `Mock` with a settable `IsCoreInitialized`, exactly + as `BreadcrumbBridgeRouterQueueTests.cs` already does (`:117`, `:450-455`). Assert + `BreadcrumbOutboundQueue.PendingCount == 0` after the failure notification. No new seam needed. + +### AC-U3 — pop-out carries the predictor and the loaded `MailItemHelper`, and builds the viewer on the UI thread + +Carry half: +1. **`QuickFiler/Interfaces/IQfcItemController.cs`** — add a read-only + `IFolderSearchHandler FolderHandler { get; }` (the interface already exposes `ItemHelper` at `:41`). + Implement it on `QuickFiler/Controllers/QfcItemController.cs` over the existing `_folderHandler` + field (`:41`), alongside the existing `TopFolderScore` accessor (`:265`). +2. **New** `QuickFiler/Controllers/QfcCollectionController.PopOut.cs` — move + `PopOutControlGroup` (`:710-720`) and `PopOutControlGroupAsync` (`:722-735`) there, add an + injectable `Func` + factory seam defaulting to the production construction, and read the carry from + `_itemGroups[selection - 1].ItemController`. +3. **`QuickFiler/Controllers/EfcHomeController.cs`** (447 lines, ~53 headroom — watch it) — add trailing optional + carry parameters to the public ctor (`:47-52`) and the internal ctor (`:54-95`), and deposit them + on `DataModel` between `:71` and `:73`, **before** `FormControllerWithDataFactory` at `:85`. + If the file cannot absorb the change, split out a `EfcHomeController.Carry.cs` part (the type is + already `public partial class`, `:18`, with an existing `EfcHomeController.Timing.cs` sibling at + csproj `:300`). +4. **New** `QuickFiler/Controllers/EfcDataModel.Carry.cs` — **required**, because `EfcDataModel.cs` + is 499/500. Move `InitFolderHandlerAsync` (`:188-221`) there and add the carry-adoption branch + mirroring `QfcItemController.FolderHandling.cs:60-86`: adopt only when `folderList is null` and + the carry is non-null, and release the carry after adoption. Resolve the + `FolderPredictor`-vs-`IFolderSearchHandler` typing decision recorded in Q5 before writing this. +5. `QuickFiler/QuickFiler.csproj` — three to four new `` lines, no metadata. + +UI-thread half: +- **`QuickFiler/Helper Classes/EfcViewerQueue.cs:25`** — change + `ProductionBlockingPriorityScheduler` from `(action, priority) => action();` to + `(action, priority) => UiThread.Dispatcher.Invoke(action, priority);`, matching + `ItemViewerQueue.cs:89-90`. Mirror the change in `ResetProductionCoreDefaultsForTesting` (`:68`). +- **Seam the new test uses**: substitute `EfcViewerQueue.ProductionBlockingPriorityScheduler` and + assert the action was scheduled rather than run inline — the existing + `ViewerQueueStaticWrapperTests.cs:244-261` already does exactly this shape for the other three + delegates. Carry-half tests use the new `EfcHomeController` factory seam and a + `Mock` returning a stub `IFolderSearchHandler`. + +### AC-U4 — boundary reporting + +- **`PopulateFolderCombobox`**: already satisfied at `EfcFormController.cs:1270`. Strengthen the + existing test `QuickFiler.Test/Controllers/EfcFormControllerTests.cs:300-...` + (`PopulateFolderCombobox_WhenDataModelFaults_LogsOnceAndDoesNotFault`) to assert a + `BoundaryErrorSink` call rather than only "does not fault", so the AC is pinned rather than + incidentally true. +- **`InitializeBreadcrumbHostAsync`**: replace `logger.Error(...)` at `EfcFormController.cs:1080` + with `TryReportBoundaryFault(...)`, in the new `EfcFormController.Breadcrumb.cs`. Classify + `OperationCanceledException` as non-fault, matching `BindBreadcrumbRowsAsync` (`:1121-1124`) and + the precedent test at `EfcFormControllerTests.Part2.cs:174-200`. +- **Seam the new test uses**: `EfcFormController.BoundaryErrorSink` (`:128-129`) substitution on a + `CreateMinimalController()` instance, plus the injectable host/initializer required by AC-U1. + `EfcFormController.UserFaultNotifier` (`:181-185`) is `AsyncLocal`-backed, so the test must be a + synchronous method if it asserts at the notifier level — the precedent is + `EfcFormControllerTests.Part2.cs:279-...`. + +### Do not do + +- Do not add the retry or the error surfacing inside + `WebView2BreadcrumbHost.OnCoreInitializationCompleted` — unreachable from any unit test. +- Do not route the `EfcFormController` fault through `TryReportWebViewInitializationFault`; that is a + different, deliberately separate contract on the item controllers. +- Do not attempt a full split of `QfcCollectionController.cs` (2329 lines) inside this bug fix. + +--- + +## Open questions and risks + +1. **Unverified: is the shared browser process cross-session persistent?** The options conflict is + scoped to "WebViews currently running in the shared browser process". Whether closing all + QuickFiler viewers tears the browser process down (and would therefore let the breadcrumb host + succeed on a fresh Efc open) is **not established** from the code. It affects only the predicted + manual-repro recipe for AC-U5, not the fix. +2. **Unverified: does the `--incognito` argument change breadcrumb behaviour?** Adopting + `--incognito` for the breadcrumb environment means the breadcrumb document runs with no persisted + browsing data. The breadcrumb document is generated locally by `BreadcrumbHtmlRenderer` and + delivered via `NavigateToString`, so no cookie, cache or storage dependence is apparent, but I did + **not** audit the generated HTML/JS for `localStorage` or similar. The plan should confirm this + before choosing "make everything incognito" over "make nothing incognito". +3. **Unverified: which of the two option sets should win.** Making all three no-argument would also + resolve the conflict and would preserve the breadcrumb's current behaviour while changing the item + bodies'. The evidence in this document does not decide the direction; it only establishes that + they must agree. Note that `--incognito` is the *older*, more widely exercised choice (two of three + sites, and the one the item-body preview has always used), which argues for it. +4. **Unverified: commits `f50fb7271` and `655130c5a`.** The Bash tool is disabled in this session, so + no git history was read. All findings are against the working tree at HEAD `2405a829d`. +5. **Unverified: whether the pop-out continuation reaches `new EfcHomeController(...)` off the UI + thread.** `ToggleOffActiveItemAsync` was not traced to a terminal `ConfigureAwait(false)` (Q6). + The AC-U3 UI-thread clause should therefore be justified as defence-in-depth and as parity with + `ItemViewerQueue`, not as a proven reproduction. +6. **Risk: `EfcHomeController.cs` headroom.** At 447 lines it has ~53 lines of room. The AC-U3 carry + parameters plus their XML documentation could exceed it, forcing a fourth new partial file and a + fourth csproj line. Size the change before committing to the file list. +7. **Risk: `QfcItemController.ViewerSetup.cs` headroom — corrected.** The file is **467** lines, not + the 499 prior research recorded, so it has 33 lines of headroom. A line-neutral options-contract + edit at `:61` (replace the literal with a constant reference) remains preferred, but the file does + not split on a small addition. +11. **Obligation missed above: `QuickFiler/Controllers/EfcItemController.cs` is 1121 lines.** The + root-cause remedy edits its options construction at `:176` and `:187-189`, and the 500-line + ceiling therefore obliges a split of that file as well. The Q9 proposal covered + `EfcFormController.cs`, `EfcDataModel.cs` and `QfcCollectionController.cs` but not this one. The + plan must either name a concrete `EfcItemController.*.cs` partial target and its csproj line, or + make the edit strictly line-neutral and record explicitly why a 1121-line file is being edited + without a split. This is the orchestrator's addition, not the researcher's. +8. **Risk: the `FolderPredictor` / `IFolderSearchHandler` typing decision (Q5) leaks into UtilitiesCS.** + Widening `IFolderSearchHandler` with `RefreshSuggestions` touches + `UtilitiesCS/OutlookObjects/Folder/IFolderSearchHandler.cs` and every implementer. Prior research + records that `UtilitiesCS` has no NetAnalyzers, so analyzer risk is low, but the blast radius + extends outside QuickFiler and should be declared in the plan's write set. +9. **Risk: AC-U1's "visible error state" has no existing rendering primitive for errors.** The + nearest fit is a banner row (`BreadcrumbRowBuilder.BannerPrefix = "===="`, + `UtilitiesCS/OutlookObjects/Folder/BreadcrumbRowBuilder.cs:19`; banner handling at + `BreadcrumbHtmlRenderer.cs:104` and `BreadcrumbRowBuilder.cs:101-106`), which is already + non-selectable at `BreadcrumbBridgeRouter.Selection.cs:85-88`. Whether the maintainer accepts a + banner row as "a visible error state in the folder area" should be confirmed before the plan + commits to it; the alternative is a WinForms label on `EfcViewer`, which lands in an + `[ExcludeFromCodeCoverage]` Designer-owned file (`EfcViewer.cs:20-21`). +10. **Risk: contention with #645 on `QuickFiler/QuickFiler.csproj` and `QfcCollectionController.cs`** + (Q10). Declare both in the write set. diff --git a/docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/spec.md b/docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/spec.md new file mode 100644 index 000000000..4557b1492 --- /dev/null +++ b/docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/spec.md @@ -0,0 +1,390 @@ +# 2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state (Spec) + +- **Issue:** #792 +- **Parent (optional):** none +- **Owner:** drmoisan +- **Last Updated:** 2026-09-12 +- **Status:** Draft +- **Version:** 1.0 +- **Kind:** bug +- **Work Mode:** full-bug + +## Context + +The breadcrumb `CoreWebView2` initialization fails with HRESULT 0x8007139F ("The group or resource is not in the correct state to perform the requested operation"). The failure is logged by both `WebView2BreadcrumbHost` and `EfcFormController` and then swallowed, so the session continues with a breadcrumb host that never initialized. + +The defect was originally recorded as intermittent (2 of 6 launches on 2026-09-06 morning). By the evening of the same day it reproduced on every Efc open: ten pop-out opens between 17:39:00 and 17:41:42, three between 19:04 and 19:06, and the ribbon Sort Email open at 19:56:36. The severity was raised from Medium to High on 2026-09-06 because both Efc entry points are unusable for folder selection in an affected session. + +Two user-visible symptoms are reported, and research confirms both are this single failure: + +1. Pop-out from a QuickFiler row to an Efc item shows an empty folder list. No suggestions, no banners, and typing a search string does nothing. +2. Ribbon -> Sort Email opens an Efc viewer whose "Matched Folders:" section has no entries. The label is a static WinForms label above the breadcrumb WebView2, which is why the label survives while the list is blank. + +Environment: + +- OS/version: Windows 11 Pro 10.0.26200 +- Language/runtime: C# / .NET Framework 4.8 VSTO add-in (not Python) +- Command/flags used: QuickFiler launched from the ribbon (High Confidence button); add-in loaded from TaskMaster\bin\Debug built 2026-09-06 08:51 from 7c8ac9ae +- Data source or fixture: live Outlook Inbox view + +Impact / Severity: + +- [ ] Blocker +- [x] High +- [ ] Medium +- [ ] Low + +High. The breadcrumb folder selector is the primary folder-selection surface on both Efc entry points, and it is unavailable for the whole session once the failure occurs. A half-initialized WebView2 was also raised as a candidate contributor to the sporadic Outlook keyboard lock tracked under #677; that link remains unconfirmed and is not claimed here. + +## Repro & Evidence + +Steps to reproduce (original, intermittent form): + +1. Launch QuickFiler from the ribbon several times in one Outlook session. +2. Inspect TaskMaster\bin\Debug\logs\debug_<date>.log for "Breadcrumb CoreWebView2 initialization failed". +3. Observe that the failure occurs on some launches and not others. + +Steps to reproduce (deterministic form, established 2026-09-06 evening and explained by the root cause below): + +1. Open QuickFiler from the ribbon so that at least one item-body WebView2 is created in the Outlook process. +2. Pop out any row to an Efc item, or invoke ribbon -> Sort Email. +3. Observe an empty folder list under "Matched Folders:" and the paired error lines in the log. + +Expected behavior: + +WebView2 initialization either succeeds, or fails with a clear surfaced error and a defined fallback state that cannot retain keyboard focus. A failed initialization is retried or the host is disposed, not left half-constructed. + +Actual behavior: + +Two ERROR lines per occurrence, then normal operation continues with a permanently blank folder list. A `BreadcrumbUiDispatcher` dispatch failure followed in the same session on 2026-09-06 at 09:01:56. + +Logs / Screenshots: + +- [x] Attached minimal logs +- Snippet (debug_2026-09-06.log): + +``` +2026-09-06 08:55:22,227 [VSTA_Main] ERROR QuickFiler.Viewers.WebView2BreadcrumbHost - Breadcrumb CoreWebView2 initialization failed: ... (HRESULT: 0x8007139F) +2026-09-06 08:55:22,286 [VSTA_Main] ERROR QuickFiler.Controllers.EfcFormController - Breadcrumb WebView2 initialization failed: ... (HRESULT: 0x8007139F) +2026-09-06 09:01:56,237 [VSTA_Main] ERROR QuickFiler.Viewers.BreadcrumbUiDispatcher - Breadcrumb UI dispatch failed. +2026-09-06 19:56:37,940 [VSTA_Main] ERROR QuickFiler.Viewers.WebView2BreadcrumbHost - Breadcrumb CoreWebView2 initialization failed: The group or resource is not in the correct state to perform the requested operation. (Exception from HRESULT: 0x8007139F) +2026-09-06 19:56:38,004 [VSTA_Main] ERROR QuickFiler.Controllers.EfcFormController - Breadcrumb WebView2 initialization failed: The group or resource is not in the correct state to perform the requested operation. (Exception from HRESULT: 0x8007139F) +``` + +The 59-67 ms gap between the paired lines in every logged occurrence is consistent with one event-then-task-continuation sequence, not with two independent SDK attempts. This is verified in the research record: the two lines are one failure logged twice. + +## Scope & Non-Goals + +In scope: + +- Converging all three production WebView2 environment creations on one shared owner of the user-data folder and the additional browser arguments, so the options conflict that produces 0x8007139F cannot recur. +- Bounded, deterministic retry of the breadcrumb host initialization, and a visible error state in the folder area on final failure. +- Guaranteeing that the router's pending document and the breadcrumb outbound queue are both drained or explicitly discarded, never left silently pending. +- Routing the breadcrumb initialization failure through the existing fault boundary so the user is notified rather than only the log. +- Carrying the already-initialized folder predictor and the loaded `MailItemHelper` from the QuickFiler item into the pop-out Efc view, and constructing the Efc viewer through the dispatcher rather than inline. +- The file-splitting and project-file edits that the 500-line ceiling and the non-SDK-style project format oblige for every file touched. + +Out of scope / non-goals: + +- A full split of `QuickFiler/Controllers/QfcCollectionController.cs` (2329 lines) or of `QuickFiler/Controllers/EfcItemController.cs` (1121 lines). Only the members this change edits move into new compliant partials; the parents' remaining over-ceiling size is pre-existing debt this change neither introduces nor resolves. See AC-U9. +- The archive-root read at the breadcrumb bind boundary. Research confirms the read is unguarded but already fail-soft and already user-surfaced through the existing boundary, and its behavior is pinned by an existing test. The QuickFiler twin of that read is tracked separately under issue #813. +- Any change to the UtilitiesCS folder-search-handler interface or to the folder predictor. See the carry typing decision below. +- Any claim about, or fix for, the #677 keyboard lock. The link between a half-initialized WebView2 and the keyboard lock is unconfirmed and is not addressed here. +- The two prior findings #458 (pooled-viewer handler retention) and #476 (unmarshalled SDK call and unsynchronized state). Research verified both are already fixed in the current tree and neither contributes to this defect. + +Explicitly excluded systems, integrations, or datasets: no Outlook Interop surface, no settings schema, no persisted data, no network call. + +## Root Cause Analysis + +0x8007139F is `HRESULT_FROM_WIN32(ERROR_INVALID_STATE)`. Microsoft documents it for WebView2 environment creation as "Specified options do not match the options of the WebViews that are currently running in the shared browser process." The same rule appears in prose on the `options` parameter of the .NET `CoreWebView2Environment.CreateAsync` reference. + +The add-in has exactly three production environment creations, all against the same user-data folder `%LocalAppData%\WindowsFormsWebView2`, but only two supply `--incognito `: + +- `QuickFiler/Controllers/QfcItemController.ViewerSetup.cs` at line 61 supplies `--incognito ` +- `QuickFiler/Controllers/EfcItemController.cs` at lines 176 and 187-189 supplies `--incognito ` +- `QuickFiler/Viewers/WebView2BreadcrumbHost.cs` at line 250 supplies no additional browser arguments + +The breadcrumb host is the odd one out and is the only one that fails. The count of three and the identification of the single divergent site are derived twice by independent search strategies in the research record's Numeric Derivation Evidence section, which agree on both membership and count. + +This explains the reported timeline without appeal to timing. The failure was originally intermittent because it depends on whether an `--incognito` WebView was already running in the Outlook process when the Efc breadcrumb initialized. It is now deterministic on both reported entry points because a pop-out always follows an open QuickFiler, and the logged 19:56 Sort Email open followed ten pop-outs in the same session. + +The consequences described in the issue are real and follow from this cause: + +- The failure branch of the initialization-completed handler returns before raising `CoreInitialized` and before publishing `IsCoreInitialized`, so neither of the two drains of the router's pending document can ever fire for the rest of the viewer's life. Every subsequent bind and theme change overwrites a stash nobody will read. +- The breadcrumb outbound queue has the same shape and the same single drain, so a failed session accumulates an unbounded serialized payload that is never released. +- The initialization task's failure is log-only. + +Candidates from the original issue body that research refuted: initialization before the control's handle or parent is valid (reachable but not this HRESULT, and the identical pre-show ordering holds for the item-body WebView that does not fail); a second initialization against a control already mid-initialization (one construction site, one initialization call site, and a per-control owner registry); a disposed or pooled-and-reused control (pooling is real but would surface as a different, already-fixed defect shape); and the reading that two log lines imply two initialization paths. + +## Proposed Fix + +### Design summary (what changes where) + +Give the process one owner of the WebView2 environment contract and make all three creations use it. A new file `QuickFiler/Viewers/WebView2EnvironmentContract.cs` holds the additional-browser-arguments constant, the user-data-folder resolution currently duplicated three times, and an options factory. `QuickFiler/Viewers/WebView2BreadcrumbHost.cs` is the behavior-changing site: its environment acquires `--incognito ` and stops conflicting. The two item-controller sites are re-pointed at the same owner, line-neutrally where possible. + +On top of that root-cause remedy, four behavioral changes land: + +- `QuickFiler/Controllers/EfcFormController.Breadcrumb.cs` (a new partial part) gains a bounded, deterministic retry of the host initialization, routes the final failure through the existing fault boundary, and notifies the router of the failure. +- `QuickFiler/Controllers/BreadcrumbBridgeRouter.cs` gains a failure-notification entry point next to the existing success notification. It renders a visible error banner into the folder area, clears the pending document, and explicitly drains or discards `QuickFiler/Controllers/BreadcrumbOutboundQueue.cs`. +- `QuickFiler/Controllers/QfcCollectionController.PopOut.cs` (a new partial part) carries the source item's initialized folder handler and loaded `MailItemHelper` into the pop-out, through `QuickFiler/Controllers/EfcHomeController.cs` and into `QuickFiler/Controllers/EfcDataModel.Carry.cs` (a new partial part). +- `QuickFiler/Helper Classes/EfcViewerQueue.cs` changes its production blocking scheduler from an inline invocation to a dispatcher invocation, matching the sibling item viewer queue. + +### Boundaries and invariants to preserve + +- One owner, one contract: after this change, no production code constructs WebView2 environment options other than through the shared contract. The three sites must resolve to the same user-data folder and the same additional browser arguments. +- The pending document and the outbound queue have exactly one terminal state each: delivered, or explicitly discarded with the user informed. Silent retention is the defect and must not survive in any branch. +- A later successful initialization must still navigate a stash produced before a failure. Adding the failure path must not remove the success path. +- The retry must be deterministic and must not use wall-clock waits. The repository determinism rule bans `Thread.Sleep` and `Task.Delay` in tests, so any delay is an injected delegate substituted with a no-op. +- The carry is adopted only when the carried handler is already initialized for that item under the same initialization sequence the construction branch would otherwise run, mirroring the #678 receiver contract. Otherwise the existing construction path runs unchanged. +- `OperationCanceledException` is classified as non-fault, matching the existing precedent on the bind boundary and the keyboard guard. +- The retry and the error surfacing do not go into the SDK initialization-completed handler. That branch is structurally unreachable from a unit test because the completed-event argument type has no public constructor, and it is already coverage-exempt for that documented reason. +- The item controllers' separate WebView initialization fault member is a deliberately distinct contract and must not be conflated with the form controller's boundary fault reporter. + +### Dependencies or blocked work + +- A prerequisite verification task confirms the breadcrumb document does not depend on persisted browsing storage before the options direction is finalized. See Risks. +- No dependency on any other in-flight item. Two siblings touch adjacent surface; see Known contention. + +### Implementation strategy (what changes, not sequencing) + +Settled scope decisions, recorded here so they are not relitigated: + +1. **Options direction.** All three sites converge on `--incognito `. Two of three already use it and the item-body preview has always used it. A prerequisite verification task will confirm the breadcrumb document does not depend on persisted browsing storage before this is finalized. +2. **Error-state primitive for AC-U1.** The visible error state is a banner row composed from the existing breadcrumb banner-prefix convention and delivered through the existing router and renderer. Banner rows are already non-selectable. No new WinForms control is added to the viewer, because that would land in a Designer-owned file that is excluded from coverage. +3. **Carry typing for AC-U3.** The carried object is typed as the folder-search-handler interface. The Efc data model's concrete predictor property is not retyped and the UtilitiesCS interface is not widened. The carry is adopted only when the carried instance is the concrete predictor type, by pattern match, and otherwise the existing construction path runs unchanged. This keeps the change inside the QuickFiler project. UtilitiesCS is not modified by this change. +4. **File-size obligations.** The 500-line ceiling applies. `QuickFiler/Controllers/EfcFormController.cs` is 1321 lines and is not currently declared partial; it is split into six files, five of them new, each under 500 lines. `QuickFiler/Controllers/EfcDataModel.cs` is 499 lines, one under the ceiling, so the carry work is placed in a new partial. `QuickFiler/Controllers/QfcCollectionController.cs` is 2329 lines and `QuickFiler/Controllers/EfcItemController.cs` is 1121 lines; a full split of either is out of scope for a bug fix, so the edited members move into new compliant partials and the parent files' remaining over-ceiling size is recorded as pre-existing debt this change does not introduce and does not resolve. +5. **Non-SDK-style projects.** Adding or removing any .cs file requires editing the owning project file's Compile item list. `QuickFiler/QuickFiler.csproj` and `QuickFiler.Test/QuickFiler.Test.csproj` are both in the write set for that reason. Hand-written partial parts are listed as bare self-closing Compile elements with no metadata; DependentUpon is used only for Designer and resx pairings and must not be added. +6. **Evidence convention.** Per the maintainer decision on issue 671 of 2026-09-11, commit projections only. No .trx file and no .cobertura.xml file is written into the repository. Numeric coverage and test-result figures are recorded inside the Markdown evidence artifacts under the feature folder's evidence directory, and the raw tool output is discarded. +7. **AC-U5 is manual.** It is a human-executed live-Outlook verification with a runbook. It is not an automated gate and must not be described as one. +8. **Manual build gate.** Outlook must be closed, never killed, before any rebuild, or the build output stays locked. This is a human step in the runbook. + +#### Files/modules to change + +The authoritative list is the `## Write Set` section below. In summary: + +- New shared contract: `QuickFiler/Viewers/WebView2EnvironmentContract.cs`. +- Environment creation sites: `QuickFiler/Viewers/WebView2BreadcrumbHost.cs`, `QuickFiler/Controllers/QfcItemController.ViewerSetup.cs`, and `QuickFiler/Controllers/EfcItemController.cs` with its edited members moved into `QuickFiler/Controllers/EfcItemController.WebViewEnvironment.cs`. +- Router and queue: `QuickFiler/Controllers/BreadcrumbBridgeRouter.cs`, `QuickFiler/Controllers/BreadcrumbOutboundQueue.cs`. +- Form controller split into six files, five new: `QuickFiler/Controllers/EfcFormController.cs` (retained), `QuickFiler/Controllers/EfcFormController.Breadcrumb.cs`, `QuickFiler/Controllers/EfcFormController.SetupAndProperties.cs`, `QuickFiler/Controllers/EfcFormController.EventHandlers.cs`, `QuickFiler/Controllers/EfcFormController.Actions.cs`, `QuickFiler/Controllers/EfcFormController.Helpers.cs`. +- Pop-out carry: `QuickFiler/Controllers/QfcCollectionController.cs`, `QuickFiler/Controllers/QfcCollectionController.PopOut.cs`, `QuickFiler/Controllers/EfcHomeController.cs`, `QuickFiler/Controllers/EfcDataModel.cs`, `QuickFiler/Controllers/EfcDataModel.Carry.cs`, `QuickFiler/Controllers/QfcItemController.cs`. The item-controller interface is not modified; see the exclusion paragraph under the Write Set. +- Viewer construction: `QuickFiler/Helper Classes/EfcViewerQueue.cs`. +- Project files: `QuickFiler/QuickFiler.csproj`, `QuickFiler.Test/QuickFiler.Test.csproj`. +- Tests: nine .cs files listed in the write set (eight new, one existing file whose stale test name is corrected). + +#### Functions/classes/CLI commands impacted + +- `WebView2BreadcrumbHost.InitializeAsync` — environment options now come from the shared contract. +- `QfcItemController.InitializeWebViewAsync` and `EfcItemController.InitializeWebViewAsync` — re-pointed at the shared contract; the Efc one moves into a new partial. +- `EfcFormController.InitializeBreadcrumbHostAsync` — becomes internal, gains bounded retry, routes the final failure through `TryReportBoundaryFault`, and notifies the router. +- `EfcFormController.ConfigureBreadcrumbControl` — wires the new failure notification next to the existing `CoreInitialized` subscription. +- `EfcFormController.PopulateFolderCombobox` — unchanged in behavior; its existing test is strengthened. +- `BreadcrumbBridgeRouter.NotifyInitializationFailed` — new. Renders the error banner, clears the pending document, drains or discards the outbound queue. +- `BreadcrumbBridgeRouter.NotifyCoreInitialized` — unchanged behavior, retained for the later-success path. +- `QfcCollectionController.PopOutControlGroup` and `PopOutControlGroupAsync` — move to the new partial and gain an injectable home-controller factory seam plus the carry read. +- `QfcItemController` — gains an internal read-only folder-handler accessor over the existing private field. The item-controller interface is not widened: it has an implementer in the test project outside the write set (a private fake in the theme-helper tests) and a legacy implementer, so adding a member would break a file this change may not edit. The pop-out reads the accessor by pattern-matching the group's item controller to the concrete type. The interface already exposes the mail item helper, so that half of the carry needs no new accessor. +- `EfcHomeController` constructors — gain trailing optional carry parameters and deposit them on the data model before the form controller is constructed. +- `EfcDataModel.InitFolderHandlerAsync` — moves to the new carry partial and gains the adoption branch. +- `EfcViewerQueue.ProductionBlockingPriorityScheduler` and `ResetProductionCoreDefaultsForTesting` — the inline invocation becomes a dispatcher invocation. + +#### Data flow and validation changes + +Environment creation: all three sites now read one folder string and one options instance from the shared contract, so the shared browser process sees a single consistent option set. + +Breadcrumb delivery on failure: initialization fails -> bounded retry exhausts -> the form controller calls the router's failure notification -> the router renders an error banner row and delivers it through the existing renderer -> the pending document is cleared -> the outbound queue is drained or discarded and its pending count returns to zero -> the form controller reports the fault through the existing boundary so the user is notified. + +Pop-out carry: the source item controller's mail item helper is read through the existing interface member and its folder handler through the internal accessor on the concrete item controller (null when the group's controller is not the concrete type); both are passed to the home controller, deposited on the data model before the form controller is constructed, and adopted inside the folder-handler initialization only when no explicit folder list was supplied and the carried instance matches the concrete predictor type. The carry is released after adoption. This ordering is required because the form controller's initialization fires a fire-and-forget folder-combobox population that otherwise overwrites the deposited handler. + +#### Error handling and logging updates + +- The breadcrumb initialization failure is reported through the form controller's existing boundary fault reporter instead of a bare logger call. Its default sink logs and then surfaces a modeless notice to the user. +- `OperationCanceledException` remains a debug-level, non-fault classification. +- The stale comment on the initialization member, which states the queue is released when initialization fires, is corrected: on failure it is released by the failure notification. +- No new logging category is introduced. + +#### Rollback/feature-flag considerations + +No feature flag. The change is a source-level fix delivered in one branch; rollback is a revert of the branch. The root-cause remedy is a single-value convergence and can be reverted independently of the retry and carry work if a browsing-storage dependence is discovered after the fact. + +### Technical specifications (interfaces/contracts) + +#### Inputs/outputs and formats + +- Shared contract: an internal constant string for the additional browser arguments, an internal static resolution of the user-data folder path, and an internal static options factory. No file is created by the resolution; the path is computed as a string. +- Router failure notification: takes the initialization exception and returns void. It must be safe to call when the host was never initialized. +- Carry parameters: trailing optional parameters typed as the folder-search-handler interface and the mail item helper, defaulting to null so every existing call site compiles unchanged. + +#### Required configuration keys and defaults + +None. No settings key is added, read, or changed. + +#### Backward-compatibility expectations + +All new constructor and factory parameters are trailing and optional. No public signature is removed or narrowed. No interface is widened: the folder-handler accessor is an internal get-only property on the concrete item controller, reached by pattern match, so the item-controller interface's out-of-write-set implementers compile unchanged. + +#### Performance constraints + +The retry is bounded by a fixed attempt count with no wall-clock delay by default, so the worst case adds a small constant number of failed environment-creation attempts to Efc open. No throughput or memory constraint applies beyond removing the unbounded outbound-queue growth, which this change fixes. + +## Assumptions, Constraints, Dependencies + +Assumptions: + +- The documented WebView2 rule applies to this add-in's process model: a single shared browser process per user-data folder, pinned to the options of the first WebView that starts it. +- The breadcrumb document, which is generated locally and delivered by navigating to a string, does not depend on persisted browsing storage. This is not yet audited and is a prerequisite verification task, not an established fact. +- The maintainer accepts a banner row in the folder area as the visible error state for AC-U1. This is a settled scope decision. + +Constraints: + +- 500-line ceiling on every production, test, and reusable script file. +- MSTest, Moq, and FluentAssertions only. No xUnit, no NUnit. +- No temporary files in tests, no live Outlook in tests, no `Thread.Sleep` or `Task.Delay` in tests. +- Non-SDK-style project files with explicit Compile item lists in both the production and the test project. +- Toolchain order: CSharpier format, then the analyzer rebuild, then the nullable rebuild, then vstest. Any failure restarts the loop at format. + +External dependencies: + +- Microsoft.Web.WebView2 SDK, already referenced by the QuickFiler project. No version change. + +## Data / API / Config Impact + +- User-facing changes: the folder area now shows a visible error banner instead of a blank list when initialization finally fails, and the user receives the existing modeless fault notice. On the pop-out path the folder list is populated from the carried predictor, so it appears without a rebuild from scratch. +- Data or migration considerations: none. No persisted data, no schema, no settings key. +- Logging/telemetry updates: the breadcrumb initialization failure moves from a bare logger error to the boundary fault reporter, which still logs. One stale explanatory comment is corrected. +- Compatibility notes: no CLI flag, no config schema, no versioned contract. The added internal accessor on the concrete item controller and the added optional parameters are source-compatible with every in-repo caller. + +## Test Strategy + +Framework: MSTest with Moq and FluentAssertions, in the `QuickFiler.Test` project. That project lists every source file explicitly, so each new test file needs a Compile item edit. + +Seam per criterion: + +- **AC-U1 (retry and visible error state).** Seam: a Moq double of the WebView core-initializer interface whose environment-creation or ensure-core call returns a faulted task for the first N calls, combined with an injectable initialization delegate on the form controller. The retry must live on the awaited-task path, not in the SDK completed-event handler, because the completed-event argument type has no public constructor and that branch is structurally unreachable. The error state is asserted at the router and renderer level as a banner row, not at the WebView2 level. Files: `QuickFiler.Test/Viewers/WebView2BreadcrumbHostIssue792Tests.cs` and `QuickFiler.Test/Controllers/EfcFormControllerIssue792Tests.cs`. +- **AC-U2 (pending document never silently dropped).** Seam: a Moq double of the breadcrumb web host interface with a settable initialized flag, exactly as the existing router queue tests already do. Assert that a later successful initialization notification navigates a stash produced earlier, and that a failure notification produces an error document and leaves no stash. No new production seam is required. File: `QuickFiler.Test/Controllers/BreadcrumbBridgeRouterIssue792Tests.cs`. +- **AC-U3 (pop-out carry and UI-thread construction).** Carry half seam: the new injectable home-controller factory on the pop-out partial, an uninitialized concrete item controller carrying a folder handler and a mail item helper set by reflection, plus a Moq double of the item-controller interface for the non-concrete-type case, and the carry parameter path into the data model's folder-handler initialization. Files: `QuickFiler.Test/Controllers/QfcCollectionControllerIssue792PopOutTests.cs` and `QuickFiler.Test/Controllers/EfcDataModelIssue792CarryTests.cs`. UI-thread half seam: substitution of the Efc viewer queue's production blocking priority scheduler, asserting the action was scheduled rather than run inline. Note explicitly: this half is verifiable only as a scheduler-delegate assertion, not as a real thread-affinity assertion. File: `QuickFiler.Test/Helper Classes/EfcViewerQueueIssue792Tests.cs`. +- **AC-U4 (boundary reporting).** Seam: substitution of the form controller's boundary error sink on a minimally constructed controller, plus the injectable initialization delegate required by AC-U1. Note: the `PopulateFolderCombobox` half of this criterion is already satisfied on main at line 1270 of the form controller. The work for that half is to strengthen the existing test in `QuickFiler.Test/Controllers/EfcFormControllerTests.cs`, which today asserts only that the method logs once and does not fault, so that it asserts the sink call and the criterion is pinned rather than incidentally true. The `InitializeBreadcrumbHostAsync` half is a real change. File: `QuickFiler.Test/Controllers/EfcFormControllerIssue792Tests.cs`. +- **AC-U5 (both entry points).** Not automatable. It is a human-executed live-Outlook verification with a runbook recorded in the user story. It is not an automated gate. +- **AC-U6 (one shared owner of the environment contract).** Seam: a Moq double of the WebView core-initializer interface verifying that the folder argument and the additional-browser-arguments property handed to environment creation equal the shared contract's values, for each site that goes through the seam; plus a structural parity test asserting the three sites resolve to one constant. The Efc item-controller site currently bypasses the seam by calling the SDK factory directly, so making it verifiable requires routing it through the seam. Files: `QuickFiler.Test/Viewers/WebView2EnvironmentContractTests.cs` and `QuickFiler.Test/Viewers/WebView2BreadcrumbHostIssue792Tests.cs`. +- **AC-U7 (outbound queue drained or discarded).** Seam: the outbound queue's existing public pending-count property, driven through the router's failure notification with a Moq host double. Assert the pending count is zero after the notification. File: `QuickFiler.Test/Controllers/BreadcrumbOutboundQueueIssue792Tests.cs`. +- **AC-U8 (500-line ceiling and Compile item parity).** Seam: a measured line count of every file in the write set, and a comparison of the set of .cs files added or removed against the Compile item edits in the two project files. Recorded as a Markdown evidence artifact, not as a runtime test. +- **AC-U9 (pre-existing debt recorded).** Verified by inspection of the change description; no test. + +Regression tests to add or update: + +- New: the eight new test files named in the write set. +- Updated: `QuickFiler.Test/Controllers/EfcFormControllerTests.cs`, to strengthen the folder-combobox fault test from "logs once and does not fault" to an assertion on the boundary sink. + +Edge cases and negative scenarios: + +- Initialization fails on every attempt; initialization fails then succeeds on a later attempt; initialization is canceled rather than failed. +- A document is stashed before the failure, and another after it. +- The outbound queue is non-empty at the moment of failure. +- The pop-out supplies no carry (null handler), supplies a carry of an unexpected runtime type, and supplies an explicit folder list alongside a carry. In all three the existing construction path must run unchanged. + +Error handling and logging verification: assert the boundary sink is invoked exactly once per failure and that a canceled initialization does not invoke it. + +Coverage impact and targets: every new production file targets at least 90 percent line coverage. No file in the write set may regress coverage on its changed lines. Repository floors continue to apply. Numeric figures are recorded inside the Markdown evidence artifacts under the feature folder's evidence directory; no .trx and no .cobertura.xml is committed. + +Toolchain commands, in order, restarting at step 1 on any failure or auto-fix: + +1. `dotnet tool run csharpier format .` (verify with `dotnet tool run csharpier check .`) +2. `msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true` +3. `msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=true` +4. `vstest.console.exe /EnableCodeCoverage` + +Manual validation steps: see AC-U5 and the runbook in user-story.md. Outlook must be closed, never killed, before the rebuild, or the build output stays locked. + +## Acceptance Criteria + +- [ ] AC-U1: A failed `CoreWebView2` initialization is retried, and on final failure the Efc view shows a visible error state in the folder area instead of a blank list. +- [ ] AC-U2: `_pendingDocument` is never silently dropped: it is delivered when initialization later succeeds or an error is surfaced. +- [ ] AC-U3: The pop-out path carries the already-initialized folder predictor and loaded `MailItemHelper` from the QfcItem, following the #678 carry pattern, and constructs the `EfcViewer` on the UI thread. +- [ ] AC-U4: `PopulateFolderCombobox` and `InitializeBreadcrumbHostAsync` report failures through `TryReportBoundaryFault` to the user, not log-only. +- [ ] AC-U5: Manual verification on both entry points: pop-out from QuickFiler and ribbon Sort Email each show suggestion rows and respond to typed search. +- [ ] AC-U6: All three production WebView2 environment creations resolve their user-data folder and their additional browser arguments from one shared owner, and a test asserts the three agree. +- [ ] AC-U7: The breadcrumb outbound queue is not left to grow without bound after a failed initialization: a failure notification drains or discards it explicitly, and a test asserts its pending count is zero afterwards. +- [ ] AC-U8: No file created or modified by this change exceeds 500 lines, and every added or removed .cs file has a matching Compile item edit in its owning project file. +- [ ] AC-U9: The pre-existing over-ceiling size of the two files that are not fully split is recorded explicitly in the change description as pre-existing debt, with the line counts before and after. + +## Write Set + +`QuickFiler/Viewers/WebView2EnvironmentContract.cs` +`QuickFiler/Viewers/WebView2BreadcrumbHost.cs` +`QuickFiler/Controllers/QfcItemController.ViewerSetup.cs` +`QuickFiler/Controllers/EfcItemController.cs` +`QuickFiler/Controllers/EfcItemController.WebViewEnvironment.cs` +`QuickFiler/Controllers/BreadcrumbBridgeRouter.cs` +`QuickFiler/Controllers/BreadcrumbOutboundQueue.cs` +`QuickFiler/Controllers/EfcFormController.cs` +`QuickFiler/Controllers/EfcFormController.Breadcrumb.cs` +`QuickFiler/Controllers/EfcFormController.SetupAndProperties.cs` +`QuickFiler/Controllers/EfcFormController.EventHandlers.cs` +`QuickFiler/Controllers/EfcFormController.Actions.cs` +`QuickFiler/Controllers/EfcFormController.Helpers.cs` +`QuickFiler/Controllers/QfcCollectionController.cs` +`QuickFiler/Controllers/QfcCollectionController.PopOut.cs` +`QuickFiler/Controllers/EfcHomeController.cs` +`QuickFiler/Controllers/EfcDataModel.cs` +`QuickFiler/Controllers/EfcDataModel.Carry.cs` +`QuickFiler/Controllers/QfcItemController.cs` +`QuickFiler/Helper Classes/EfcViewerQueue.cs` +`QuickFiler/QuickFiler.csproj` +`QuickFiler.Test/Viewers/WebView2BreadcrumbHostIssue792Tests.cs` +`QuickFiler.Test/Viewers/WebView2EnvironmentContractTests.cs` +`QuickFiler.Test/Controllers/BreadcrumbBridgeRouterIssue792Tests.cs` +`QuickFiler.Test/Controllers/BreadcrumbOutboundQueueIssue792Tests.cs` +`QuickFiler.Test/Controllers/EfcFormControllerIssue792Tests.cs` +`QuickFiler.Test/Controllers/EfcFormControllerTests.cs` +`QuickFiler.Test/Controllers/QfcCollectionControllerIssue792PopOutTests.cs` +`QuickFiler.Test/Controllers/EfcDataModelIssue792CarryTests.cs` +`QuickFiler.Test/Helper Classes/EfcViewerQueueIssue792Tests.cs` +`QuickFiler.Test/QuickFiler.Test.csproj` + +Two of the paths above contain a space in the directory name, under Helper Classes in the production project and in the test project. Those are the real tracked paths and are reproduced exactly. + +The list above holds 31 paths. Files deliberately not modified, stated in prose because the extractor has no notion of polarity and would harvest an excluded path as though it were written. The QuickFiler item-controller interface in the Interfaces folder is not touched: it has two implementers outside the write set (a private fake in the test project's theme-helper tests and a legacy controller), so a new member would break a file this change may not edit; the pop-out carry reads an internal accessor on the concrete item controller by pattern match instead. The UtilitiesCS folder-search-handler interface and the folder predictor that implements it are not touched: the carry is typed as the existing interface and adopted by pattern match on the concrete predictor type, so neither the interface nor the predictor needs widening, and no change leaves the QuickFiler project. The breadcrumb UI dispatcher is not touched: research assessed its boundary check as self-consistent and a sibling item explicitly scoped it out, so it is edited only if a test proves a need, which is not anticipated. The breadcrumb row builder and the breadcrumb HTML renderer are not touched: the error state reuses the existing banner-prefix convention and the existing rendering path rather than adding a new primitive. The item viewer files owned by a sibling item are not touched: the overlap there is conceptual only. The breadcrumb HTML resource is not touched: the document content is unchanged and only its delivery on the failure path changes. + +## Known contention + +Two sibling items in the same parallel run touch adjacent surface. This section records the overlap; it does not coordinate. The scheduler serializes. + +Sibling A adds invariant-culture date and time formatting and edits the QuickFiler collection controller at three format call sites. Those sites are disjoint from the pop-out members this item edits, so there is no line overlap. Both items nonetheless touch that collection-controller file and the QuickFiler project file, and this item adds Compile items to that project file while the sibling also edits it, so a project-file item-group merge is expected. + +Sibling B adds a UI-marshalling seam scoped to the item viewer files, which this item does not write. The overlap is conceptual only: both items decide how UI-boundary ownership is proven. This item adopts the owner-thread-identity idiom that sibling ratified rather than inventing a third convention. + +## Risks & Mitigations + +Technical and operational risks, including the research record's open questions: + +- **Whether the shared browser process is torn down when all viewers close is unverified.** The documented options rule is scoped to WebViews currently running in the shared browser process; whether closing every QuickFiler and Efc viewer releases that process is not established from the code. This affects only the AC-U5 manual repro recipe, specifically whether a clean Outlook session is needed to observe the pre-fix failure. It does not affect the fix. Mitigation: the runbook opens QuickFiler first so that an item-body WebView is running before the Efc open, which reproduces the conflict regardless of teardown behavior. +- **Whether the breadcrumb document depends on persisted browsing storage is unverified and must be verified before the options direction is finalized.** The document is generated locally and delivered by navigating to a string, so no cookie, cache or storage dependence is apparent, but the generated markup and script were not audited for local storage or similar. Mitigation: a prerequisite verification task audits the generated document before the convergence on `--incognito ` is committed. If a storage dependence is found, the alternative convergence direction (no additional arguments at all three sites) resolves the conflict equally well and the direction decision is revisited. +- **The claim that the pop-out continuation lands off the UI thread is unverified.** One await on the pop-out path was not traced to a terminal configure-await, so the off-thread continuation was not reproduced. The AC-U3 UI-thread clause is therefore justified as defence in depth and as parity with the sibling item viewer queue, which already schedules through the dispatcher, not as a proven reproduction. Mitigation: the change is a one-line scheduler substitution in a small file with an existing test seam, so its cost is low even if the failure mode never occurs in production. +- **The visible error state has no pre-existing error rendering primitive.** The banner row is the nearest fit and is a settled scope decision. Mitigation: banner rows are already non-selectable, so the error row cannot be chosen as a folder, and the alternative of a new WinForms control was rejected because it lands in a Designer-owned, coverage-excluded file. +- **Home-controller file headroom.** That file is 447 lines, leaving roughly 53 lines. The carry parameters plus their documentation could exceed the ceiling, forcing an additional partial and an additional Compile item. Mitigation: size the change before committing to the file list; the write set is adjusted if the measurement requires it, and AC-U8 makes the ceiling a blocking criterion rather than an afterthought. +- **The Efc item controller bypasses the mockable initializer seam.** Its environment creation calls the SDK factory directly, so AC-U6's assertion for that site requires routing it through the seam. Mitigation: the routing change is part of the write set for that file's new partial. +- **Retry could mask a genuine environment problem.** A bounded, small attempt count with no wall-clock delay keeps the worst case short, and the final failure is surfaced to the user rather than absorbed. +- **Project-file merge with the sibling item.** Both items edit the same explicit item list. Mitigation: additions are appended adjacent to their neighbours as bare self-closing elements with no metadata, which minimises the conflict surface; a merge on item-group ordering is expected and accepted. + +Mitigations and rollback: the change carries no feature flag; rollback is a branch revert. The root-cause convergence is a single-value change and can be reverted independently of the retry, router and carry work. + +## Rollout & Follow-up + +Release/rollout steps: + +1. Run the full toolchain in order and confirm all four steps pass in one pass. +2. Close Outlook, never kill it, then rebuild. Reopen Outlook and load the rebuilt add-in. +3. Execute the AC-U5 manual runbook in user-story.md against both entry points and record the observation. +4. Record the AC-U8 line-count and Compile-item parity measurements, and the AC-U9 pre-existing debt statement with before and after counts, in the change description and in the Markdown evidence artifacts under the feature folder's evidence directory. + +Post-fix monitoring or clean-up tasks: + +- Review the add-in debug log across several Outlook sessions for any remaining occurrence of HRESULT 0x8007139F. +- Track the remaining over-ceiling size of the collection controller and the Efc item controller as pre-existing debt for a separate split item. +- Re-assess whether the #677 keyboard lock persists once the half-initialized WebView2 state is eliminated. No claim is made here that it will. + +Links: + +- Issue: https://github.com/drmoisan/TaskMaster/issues/792 +- Issue record: docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/issue.md +- Research record: docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/research/2026-09-12T10-30-breadcrumb-webview2-init-research.md +- User story: docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/user-story.md +- Related: #678 (carry pattern), #677 (keyboard hook leak, unconfirmed link), #813 (QuickFiler twin of the archive-root read), #458 and #476 (verified already fixed, not contributing) diff --git a/docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/user-story.md b/docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/user-story.md new file mode 100644 index 000000000..3f0b38358 --- /dev/null +++ b/docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/user-story.md @@ -0,0 +1,86 @@ +# 2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state (User Story) + +- **Issue:** #792 +- **Kind:** bug +- **Work Mode:** full-bug +- **Last Updated:** 2026-09-12 + +> **Acceptance-criteria source.** This is a full-bug item. The sole authoritative acceptance-criteria source is spec.md. This document contains no checkboxes and no acceptance criteria of its own; nothing here is tracked or checked off. It describes the user-facing outcome and the manual verification runbook for AC-U5. + +## Why this document exists on a bug + +The feature-promotion lifecycle says a full-bug folder normally carries spec.md only. This document is required here for two stated reasons. + +First, the defect is reported as two distinct user-visible symptoms on two different entry points, so the user-facing outcome needs its own record rather than being inferred from a technical root-cause narrative. + +Second, AC-U5 is a human-executed live-Outlook verification. Its actor, its preconditions and its observable pass or fail outcome belong in a user story, not in a technical spec. + +## Actor + +An Outlook user filing mail with the TaskMaster add-in, working in a live Outlook session with the add-in loaded. The same person performs the manual verification; no separate QA role is assumed. + +## Scenario 1: pop-out from a QuickFiler row + +**Given** the user has opened QuickFiler from the ribbon and is looking at a list of mail rows, + +**When** the user pops a row out into the single-item Efc view, + +**Then** the folder area of the Efc view shows suggested folder rows for that mail item, and typing a search string in the folder search box narrows the rows as the user types. + +Before the fix, the folder area is blank: no suggestions, no banners, and typing has no effect for the remainder of the session. + +## Scenario 2: ribbon Sort Email + +**Given** the user has an Outlook session with the add-in loaded, and has already used QuickFiler at least once in that session, + +**When** the user selects a mail item and invokes Sort Email from the ribbon, + +**Then** the Efc viewer opens with suggested folder rows listed under the "Matched Folders:" label, and typing a search string narrows the rows. + +Before the fix, the "Matched Folders:" label is present but the list beneath it is empty. The label is a static WinForms control, which is why it survives while the list does not. + +## Observable outcome the user must see + +On both entry points, after the fix: + +- At least one folder suggestion row is visible in the folder area within the normal time it takes the Efc view to finish opening. +- Typing into the folder search box changes the set of visible rows. +- No error notice appears during a successful open. + +If initialization nevertheless fails after the retries are exhausted, the user must see a visible error banner in the folder area and a fault notice, rather than a silently blank list. A blank list with no explanation is the defect and is not an acceptable outcome in any branch. + +## AC-U5 manual verification runbook + +This verification is performed by a person against a live Outlook session. It is not automated and is not an automated gate. + +### Preconditions + +- A live Outlook session with the rebuilt add-in loaded from the debug output. +- Outlook is closed, not killed, before the rebuild. Killing the process leaves the build output locked and the rebuild fails or produces a stale assembly. Close Outlook through its own exit path, run the rebuild, then reopen Outlook. +- At least one mail folder tree with enough history for the folder predictor to produce suggestions. A newly configured profile with no filing history may legitimately produce no suggestions and is not a valid test subject. +- The add-in debug log for the current date is accessible for the log check in step 6. It is written to the logs folder under the add-in's debug output directory. + +### Steps + +1. Close Outlook through its normal exit. Do not end the process. +2. Run the full toolchain and rebuild the solution. +3. Reopen Outlook and confirm the add-in loaded. +4. Open QuickFiler from the ribbon. Confirm at least one item body renders, which establishes that an item-body WebView is running in the process. This ordering matters: it is the condition under which the defect reproduces deterministically before the fix. +5. Pop a row out into the Efc view. Observe the folder area. +6. In the popped-out Efc view, type a partial folder name into the folder search box. Observe the rows. +7. Close the Efc view. Select a mail item in the Outlook list and invoke Sort Email from the ribbon. Observe the folder area under the "Matched Folders:" label. +8. In that Efc viewer, type a partial folder name into the folder search box. Observe the rows. +9. Open the add-in debug log for the current date and search it for the text "Breadcrumb CoreWebView2 initialization failed". + +### Observation that decides pass or fail + +The verification passes only if all four of the following hold: + +1. In step 5 the folder area shows one or more suggestion rows, not a blank list. +2. In step 6 the visible rows change in response to the typed text. +3. In step 7 the area under the "Matched Folders:" label shows one or more suggestion rows, not a blank list. +4. In step 9 the log contains no occurrence of "Breadcrumb CoreWebView2 initialization failed" with HRESULT 0x8007139F for the current session. + +The verification fails if any one of those four does not hold. A blank folder area on either entry point, or any occurrence of that HRESULT in the session's log, is a fail. + +Record the result, the Outlook session start time, and the log lines inspected in the Markdown evidence artifact under the feature folder's evidence directory. Per the evidence convention for this change, raw tool output is not committed; the observation is recorded as text. From 658b234ea9f750c62a8be29da906e23f525614bd Mon Sep 17 00:00:00 2001 From: Dan Moisan Date: Thu, 17 Sep 2026 07:56:31 -0400 Subject: [PATCH 02/12] docs(792): refresh research and correct spec against current main 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) Claude-Session: https://claude.ai/code/session_01BRrLF9BAQWhvmtt9BeaoHs --- ...11-20-breadcrumb-webview2-init-research.md | 768 ++++++++++++++++++ .../spec.md | 29 +- 2 files changed, 788 insertions(+), 9 deletions(-) create mode 100644 docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/research/2026-09-17T11-20-breadcrumb-webview2-init-research.md diff --git a/docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/research/2026-09-17T11-20-breadcrumb-webview2-init-research.md b/docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/research/2026-09-17T11-20-breadcrumb-webview2-init-research.md new file mode 100644 index 000000000..75fbe586c --- /dev/null +++ b/docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/research/2026-09-17T11-20-breadcrumb-webview2-init-research.md @@ -0,0 +1,768 @@ +# Breadcrumb WebView2 initialization fails with 0x8007139F — refreshed diagnosis (issue #792) + +- **Issue:** #792 +- **Date:** 2026-09-17 +- **Status:** Research complete (revision 2). Both open questions are answered. One new causal finding, + now labelled for provenance. +- **Method:** every citation below was derived against the **item worktree** + `C:/Users/DanMoisan/repos/TaskMaster-wt/item-792` (HEAD `8d3ea6426`, which has merged current + `origin/main` `e7cbb5722`) using Read/Grep on absolute paths. External SDK behavior was + established from Microsoft Learn reference pages and is marked `[V-web]`. +- **Revision note.** Revision 1 of this artifact measured the session worktree + `.../2026-09-12T10-15` (HEAD `2405a829d`), which does not contain issues #742 (`96ea96318`) or + #743 (`cc236c8d2`, `bce810495`). Every line citation and line count below has been re-derived + against item-792. Revision 1's "premise disagreements" section asserted the delegation brief was + wrong; it was measuring a superseded tree and **is retracted**. See §1. +- **Tooling constraint:** the Bash tool is disabled in this session, so no `git log`, `git diff` or + `pwsh` command was run. Line counts were derived by full-line regex match count (`^`), which + equals the newline-terminated line count. No git archaeology was possible; where history would + have been the natural instrument, in-tree test documentation is cited instead and labelled. + +--- + +## 1. Retraction of revision 1's premise-disagreement section + +**Revision 1 reported three "brief is wrong" corrections and one dangling link. All four are +withdrawn.** They were artifacts of measuring a stale checkout. + +- The `EfcItemController.cs` and `QfcItemController.ViewerSetup.cs` citations in the delegation + brief are **correct for the merged tree**. Revision 1's uniformly `-1` values were correct only + for the pre-#742 tree, because #742 inserted a line above them. +- The line-count totals in the delegation brief (ViewerSetup 479, `QfcCollectionController` 2333, + `EfcItemController` 1122) are **correct for the merged tree** and are reproduced in §3. +- **`spec.md`'s figures are stale, not correct.** `spec.md:89,103,104,155` were authored at + `be6c1c3b3`, before #742/#743 landed, and carry the pre-merge values. They are being corrected to + the merged-tree values by this item; no discrepancy remains to adjudicate. +- **The "dangling link at `spec.md:388`" finding is FALSE and is removed.** The research directory is + not empty. `docs/features/active/2026-09-06-breadcrumb-webview2-init-fails-resource-not-in-correct-state-792/research/2026-09-12T10-30-breadcrumb-webview2-init-research.md` + is tracked and present in item-792 (verified by directory enumeration). **Do not re-point that + link.** + +Everything else in the delegation brief verified exactly against item-792, including all +`TryReportBoundaryFault` citations, the sole `CoreInitialized` subscription, every +`WebView2BreadcrumbHost.cs` line, and all five Designer `CreationProperties` sites. + +The one substantive addition to the brief is §5 (the #463 regression account), now carrying an +explicit provenance label. + +--- + +## 2. Confirmed mechanism + +`0x8007139F` is `HRESULT_FROM_WIN32(ERROR_INVALID_STATE)`. Microsoft documents it for WebView2 +initialization as `[V-web]`: + +> `HRESULT_FROM_WIN32(ERROR_INVALID_STATE)` — Specified options do not match the options of the +> WebViews that are currently running in the shared browser process. + +and, on the `options` parameter of `CoreWebView2Environment.CreateAsync` `[V-web]`: + +> As a browser process may be shared among WebViews, WebView creation fails if the specified +> `options` does not match the options of the WebViews that are currently running in the shared +> browser process. + +The add-in has exactly three production sites that construct `CoreWebView2EnvironmentOptions` +(derivation under **Numeric Derivation Evidence** below). All three resolve the same user-data +folder, `%LOCALAPPDATA%\WindowsFormsWebView2`, but supply divergent additional browser arguments: + +| Site | File:line (item-792) | Argument | +|---|---|---| +| 1 | `QuickFiler/Viewers/WebView2BreadcrumbHost.cs:250` | none — `var options = new CoreWebView2EnvironmentOptions();` | +| 2 | `QuickFiler/Controllers/QfcItemController.ViewerSetup.cs:62` | inline literal — `CoreWebView2EnvironmentOptions options = new("--incognito ");` | +| 3 | `QuickFiler/Controllers/EfcItemController.cs:188-190` | `IncognitoArgument`, declared at `EfcItemController.cs:177` | + +The user-data folder string `"WindowsFormsWebView2"` is likewise duplicated as an inline literal at +`EfcItemController.cs:182-185`, `QfcItemController.ViewerSetup.cs:56-59` and +`WebView2BreadcrumbHost.cs:246-249` rather than shared. + +Site 3 bypasses the `IWebViewCoreInitializer` seam entirely, calling +`CoreWebView2Environment.CreateAsync(null, cacheFolder, options)` directly at +`EfcItemController.cs:195-199`. Sites 1 and 2 route through the seam +(`WebView2BreadcrumbHost.cs:265-268`, `QfcItemController.ViewerSetup.cs:71-74`), whose sole SDK +forward is `WebView2CoreInitializer.cs:72`. + +Site 1 is the odd one out and is the only one that fails, which matches the logged symptom. + +### 2.1 The QFC path already implements the intended contract + +`QuickFiler/Controllers/QfcItemController.ViewerSetup.cs:110-123` reuses ONE environment for the +message-body pane, the ItemViewer breadcrumb control and the popup dropdown surface, with an +explicit comment at lines 110-112, quoted verbatim: + +``` + // #351: initialize the breadcrumb WebView2 through the same injected seam and the + // same CoreWebView2Environment/options object created above for the message-body + // pane (G7); no second environment is negotiated against the user-data folder. +``` + +So the "one environment per user-data folder" discipline already exists in the codebase and is +documented; the EFC path simply does not follow it. The fix direction proposed in `spec.md` +generalises an existing in-repo convention rather than inventing one. + +--- + +## 3. Measured line counts for the write set (item-792) + +TOTAL lines (the convention the 500-line ceiling uses). Files over 500 are marked. + +### Production + +| File | Lines | Over 500? | +|---|---|---| +| `QuickFiler/Controllers/QfcCollectionController.cs` | **2333** | **YES** | +| `QuickFiler/Controllers/EfcFormController.cs` | **1321** | **YES** | +| `QuickFiler/Controllers/EfcItemController.cs` | **1122** | **YES** | +| `QuickFiler/Controllers/EfcDataModel.cs` | 499 | no (1 under) | +| `QuickFiler/Viewers/BreadcrumbPopupUiOperations.cs` | 489 | no | +| `QuickFiler/Controllers/QfcItemController.ViewerSetup.cs` | **479** | no (21 headroom) | +| `QuickFiler/Viewers/ItemViewer.Breadcrumb.cs` | 460 | no | +| `QuickFiler/Controllers/EfcHomeController.cs` | 447 | no (53 headroom) | +| `QuickFiler/Controllers/BreadcrumbBridgeRouter.cs` | 407 | no | +| `QuickFiler/Viewers/WebView2BreadcrumbHost.cs` | 368 | no | +| `QuickFiler/Controllers/QfcItemController.cs` | 334 | no | +| `UtilitiesCS/OutlookObjects/Folder/BreadcrumbHtmlRenderer.cs` | 234 | no | +| `QuickFiler/Controllers/BreadcrumbBridgeRouter.Selection.cs` | 221 | no | +| `QuickFiler/Viewers/EfcViewer.cs` | 169 | no | +| `QuickFiler/Viewers/WebView2CoreInitializer.cs` | 103 | no | +| `QuickFiler/Helper Classes/EfcViewerQueue.cs` | 101 | no | +| `QuickFiler/Controllers/BreadcrumbOutboundQueue.cs` | 67 | no | +| `QuickFiler/Controllers/EfcItemController.WebViewFaultBoundary.cs` | 67 | no | +| `QuickFiler/Viewers/IWebViewCoreInitializer.cs` | 66 | no | +| `QuickFiler/Viewers/IBreadcrumbWebHost.cs` | 27 | no | + +The three bolded values changed from revision 1; the remaining seventeen are unchanged and were +re-measured against item-792. + +### Test files in or adjacent to the write set + +| File | Lines | Headroom to 500 | +|---|---|---| +| `QuickFiler.Test/Controllers/EfcFormControllerTests.cs` | 485 | **15** | +| `QuickFiler.Test/Controllers/EfcItemControllerTests.cs` | 470 | 30 | +| `QuickFiler.Test/Controllers/BreadcrumbBridgeRouterQueueTests.cs` | 462 | 38 | +| `QuickFiler.Test/Viewers/WebView2BreadcrumbHostTests.cs` | 440 | 60 | + +**Planning consequence (unchanged and re-verified).** `spec.md` requires strengthening the existing +folder-combobox fault test inside `EfcFormControllerTests.cs`, which has only **15 lines** of +headroom. A strengthened test with an Arrange-Act-Assert body and a doc comment will not fit. That +file must either be split or the strengthened assertion must be placed in a new +`EfcFormControllerIssue792Tests.cs`. Neither `spec.md` nor the write set currently accounts for +this. Flagging it as a write-set gap, not a blocker. + +`EfcDataModel.cs` at 499 has one line of headroom, which is why `spec.md` routes the carry work into +a new partial. Confirmed correct. + +`QfcItemController.ViewerSetup.cs` at 479 now has only 21 lines of headroom (it had 33 before #742). +Any edit that routes site 2 through a shared contract must not grow that file by more than 21 lines. + +--- + +## Numeric Derivation Evidence + +This section supports the numeric assertion used by `spec.md` AC-U6: **exactly three production +`CoreWebView2EnvironmentOptions` construction sites**. + +- **Complete Family:** WebView2BreadcrumbHost.cs, QfcItemController.ViewerSetup.cs, EfcItemController.cs +- **Exhaustive Search Scope:** the entire repository source tree, covering all tracked C# files in every project directory +- **Inclusion Rules:** a production C# file that constructs a CoreWebView2EnvironmentOptions instance and supplies it to WebView2 environment creation. +- **Exclusion Rules:** files in test projects; doc-comment references and parameter-type references that name the type without constructing it; interface declarations and seam parameter lists. +- **Primary Search Strategy or Query Expression:** enumerate by type name across the whole tree, matching both the explicit form and the target-typed form, then classify every hit by hand, which reaches WebView2BreadcrumbHost.cs, QfcItemController.ViewerSetup.cs and EfcItemController.cs. +- **Cross-check Search Strategy or Query Expression:** independently follow every call of CoreWebView2Environment.CreateAsync and of CreateEnvironmentAsync back to the options instance each receives, which reaches EfcItemController.cs, WebView2BreadcrumbHost.cs and QfcItemController.ViewerSetup.cs. +- **Primary Member Set:** WebView2BreadcrumbHost.cs, QfcItemController.ViewerSetup.cs, EfcItemController.cs +- **Cross-check Member Set:** EfcItemController.cs, WebView2BreadcrumbHost.cs, QfcItemController.ViewerSetup.cs +- **Primary Count:** 3 +- **Cross-check Count:** 3 +- **Member-set Comparison:** the primary and cross-check member sets are equal ignoring order and case. + +### Supporting enumeration — primary (by type name, 12 raw hits in item-792) + +| File:line | Classification | +|---|---| +| `QuickFiler/Viewers/WebView2BreadcrumbHost.cs:250` | **INCLUDE — construction, parameterless overload** | +| `QuickFiler/Controllers/QfcItemController.ViewerSetup.cs:62` | **INCLUDE — construction, target-typed `new("--incognito ")`** | +| `QuickFiler/Controllers/EfcItemController.cs:188` | **INCLUDE — construction, `(string)` overload** | +| `QuickFiler/Controllers/EfcItemController.cs:187` | exclude — commented-out `--disk-cache-size=1` line | +| `QuickFiler/Controllers/QfcItemController.ViewerSetup.cs:61` | exclude — commented-out `--disk-cache-size=1` line | +| `QuickFiler/Controllers/EfcItemController.cs:169` | exclude — `` XML doc reference | +| `QuickFiler/Viewers/WebView2CoreInitializer.cs:37` | exclude — parameter declaration | +| `QuickFiler/Viewers/WebView2CoreInitializer.cs:69` | exclude — parameter declaration | +| `QuickFiler/Viewers/IWebViewCoreInitializer.cs:17` | exclude — XML doc reference | +| `QuickFiler/Viewers/IWebViewCoreInitializer.cs:51` | exclude — interface parameter declaration | +| `QuickFiler.Test/Viewers/WebView2BreadcrumbHostTests.cs:406` | exclude — test project, `It.IsAny<>` matcher | +| `QuickFiler.Test/Controllers/QfcItemController.InitializationTests.Part2.cs:254` | exclude — test project, `It.IsAny<>` matcher | + +### Supporting enumeration — cross-check (by consumer, options-argument origin) + +| Invocation site | `options` argument origin | +|---|---| +| `QuickFiler/Controllers/EfcItemController.cs:195` (`CoreWebView2Environment.CreateAsync`, seam bypassed) | `EfcItemController.cs:188` | +| `QuickFiler/Viewers/WebView2BreadcrumbHost.cs:265` (`_initializer.CreateEnvironmentAsync`) | `WebView2BreadcrumbHost.cs:250` | +| `QuickFiler/Controllers/QfcItemController.ViewerSetup.cs:71` (`_webViewInitializer.CreateEnvironmentAsync`) | `QfcItemController.ViewerSetup.cs:62` | +| `QuickFiler/Viewers/WebView2CoreInitializer.cs:55,72` (`ForwardCreateEnvironmentAsync` -> SDK static) | **not an origin** — shared adapter; forwards the `options` parameter received from the two seam callers above | +| `QuickFiler/Controllers/QfcItemController.ViewerSetup.cs:124` | exclude — commented out | + +The adapter at `WebView2CoreInitializer.cs:72` is a forwarder, not an independent owner, so it +collapses onto its two callers and adds no member. Test-project invocations +(`WebView2BreadcrumbHostTests.cs:404`, `QfcItemController.InitializationTests.Part2.cs:252`, +`WebView2CoreInitializerTests.cs:43,70,129,136`) are excluded by the Exclusion Rules, as are the +three doc-comment references (`IWebViewCoreInitializer.cs:15,17`, `WebView2CoreInitializer.cs:23,58`). + +The two strategies are structurally independent: the primary never names an environment-creation +API, and the cross-check never names the options type. A search restricted to either consumer API +alone would be non-exhaustive, because site 3 bypasses the seam and sites 1-2 never reference the +SDK static. + +### Trap recorded for reviewers — AC-U6 vacuity + +A source-text search for the construction syntax `new\s+CoreWebView2EnvironmentOptions` returns only +**2** of the 3 sites. It misses `QfcItemController.ViewerSetup.cs:62`, which uses the C# 9 +target-typed form: + +```csharp + CoreWebView2EnvironmentOptions options = new("--incognito "); +``` + +Consequence: **an AC-U6 structural-parity test implemented as a search for the literal +`new CoreWebView2EnvironmentOptions` PASSES ON THE UNFIXED TREE.** It would find two sites, observe +that they agree, and report success while a divergent third site exists. Any such test must +enumerate by type name, not by `new` expression. + +**The trap is worse at file granularity, and this is the form a reviewer is most likely to write.** +The orchestrator measured the same query counted per file rather than per line. It matches **all +three files**, because `QfcItemController.ViewerSetup.cs` contains the string on its commented-out +dead line 61: + +``` +EfcItemController.cs:187: // CoreWebView2EnvironmentOptions options = new CoreWebView2EnvironmentOptions("--disk-cache-size=1 "); +EfcItemController.cs:188: CoreWebView2EnvironmentOptions options = new CoreWebView2EnvironmentOptions( +QfcItemController.ViewerSetup.cs:61: // CoreWebView2EnvironmentOptions options = new CoreWebView2EnvironmentOptions("--disk-cache-size=1 "); +WebView2BreadcrumbHost.cs:250: var options = new CoreWebView2EnvironmentOptions(); +``` + +So a file-level assertion of the form "three files construct environment options" reaches a count of +three **on the strength of a dead comment** and reports success with no fix present. That is the +second and more dangerous failure mode named in the OBSERVED-FAILING CRITERIA block: not a gate that +cannot pass, but one that passes for a reason unrelated to correctness. Any AC-U6 gate must +therefore (a) enumerate by type name, (b) exclude comment text, and (c) be observed failing on the +unfixed tree before it is trusted. The cross-check leg must likewise name both +`CoreWebView2Environment.CreateAsync` and `CreateEnvironmentAsync`; naming either alone is +non-exhaustive for the reason given above. Both queries were run and compared. + +--- + +## 5. NEW FINDING — the conflict appears to have been introduced by issue #463's fix + +This was not in the brief and it changes the regression narrative. **Provenance is labelled +explicitly below; part of this is inference, not assertion by the cited sources.** + +### What the in-tree test documentation asserts (verbatim, item-792) + +`QuickFiler.Test/Controllers/EfcItemControllerTests.cs:360-364`: + +```csharp + /// + /// #463. The additional-browser-arguments literal opened with U+2013 EN DASH rather than two + /// ASCII hyphen-minus characters, so Chromium silently ignored the unrecognised token and the + /// preview WebView2 was never incognito. + /// +``` + +`QuickFiler.Test/Controllers/EfcItemControllerTests.cs:89-93`: + +```csharp + /// + /// #466 B, and the dead third site of #463. InitializeWebView() had zero call sites, + /// so the EN DASH incognito literal it contained is removed with its container rather than + /// edited in place. + /// +``` + +### What is asserted versus what is inferred + +- **ASSERTED by the sources.** That the additional-browser-arguments literal previously opened with + U+2013 EN DASH; that Chromium silently ignored the unrecognised token; and that in consequence + "the preview WebView2 was never incognito." These are first-party, contemporaneous statements in + the doc comments quoted above. They are statements about the **EFC preview site** + (`EfcItemController`) and about a now-deleted third site inside `InitializeWebView()`. +- **INFERRED by me, not stated by any source.** That this en-dash history is the cause of #792. The + inference chain is: if the pre-#463 literal was discarded by Chromium, the preview environment's + *effective* option set was empty; the breadcrumb host's option set is empty + (`WebView2BreadcrumbHost.cs:250`); therefore all environments agreed and `ERROR_INVALID_STATE` + could not arise; #463 made the literal a real Chromium switch, which is what first created the + divergence. **This is my reconstruction. Label it as inference wherever it is repeated.** +- **NOT ESTABLISHED BY THIS AGENT.** Whether the QFC site (`QfcItemController.ViewerSetup.cs:62`) + ever carried the en dash, and the dates of any of these changes. The cited doc comments name only + the EFC sites. I could not run `git log`/`git blame` (Bash disabled), so no commit-level + confirmation exists in this section. **See the subsection immediately below: the orchestrator + subsequently supplied that confirmation, and it resolves this item.** + +### Commit-level confirmation supplied by the orchestrator (resolves the item above) + +The orchestrator ran the git archaeology this agent could not. Commit +`abb825d94827ce4c3f825e1f79ddaa407b5d48a0`, *"fix(efc-464): correct the WebView2 incognito argument +at both live sites (#463)"*, dated Thu Aug 27 2026, states in its own message: + +> The additional-browser-arguments literal was "-incognito " with a leading U+2013 EN DASH rather +> than two ASCII hyphen-minus characters. Chromium introduces command-line switches with two ASCII +> hyphens and passes CoreWebView2EnvironmentOptions.AdditionalBrowserArguments through verbatim, so +> the unrecognised token was discarded silently and the item preview retained browsing data. + +Its diff changes `"–incognito "` to `IncognitoArgument` in `EfcItemController.cs` and +`new("–incognito ")` to `new("--incognito ")` in `QfcItemController.ViewerSetup.cs`. The commit +message additionally records the byte comparison for the QFC site: `E2 80 93` becoming `2D 2D`. + +This upgrades the provenance as follows: + +- The QFC site **did** carry the en dash. The "NOT ESTABLISHED" item above is resolved in the + affirmative by first-party commit evidence. +- Both live sites became real Chromium switches **for the first time** on 2026-08-27, and the + breadcrumb host was not touched by that commit. +- The same commit records that the third site, inside the dead `InitializeWebView()`, was removed + with its container rather than edited, which confirms three live sites today. + +What remains inference is only the final causal step — that this divergence is the mechanism behind +the `ERROR_INVALID_STATE` report in #792. Every premise feeding that step is now first-party +confirmed, but the causal conclusion itself is still a reconstruction and must keep that label. + +### What follows regardless of the inference + +The measured present-day facts are independent of the history: the breadcrumb host constructs empty +options, and the other two sites each supply `"--incognito "`. The divergence is real and measured. + +Two existing tests pin the corrected ASCII value — `EfcItemControllerTests.cs:371-396` asserts +`EfcItemController.IncognitoArgument == "--incognito "` and that every character is `<= 0x7F`, and +`EfcItemControllerTests.cs:94-...` asserts the dead `InitializeWebView` member is absent. Those +tests constrain the fix on their own terms, independent of whether my causal reconstruction is +right: **converging the third site onto `"--incognito "` satisfies them; reverting #463 would break +them.** + +**Instruction, stated with the correct strength.** Do not revert #463 — not because the regression +narrative is proven, but because two existing green tests pin the ASCII value and because reverting +would reinstate a documented privacy defect (§6). The regression narrative is a supporting +explanation of *timing*, not the basis of the fix direction. + +**Recommended confirmation for anyone with shell access:** `git log -S "incognito" -- QuickFiler/` +and `git log -S $'\u2013incognito' -- QuickFiler/`. Not required for the fix. + +--- + +## 6. OPEN QUESTION 1 — does the breadcrumb document depend on persisted browsing storage? + +**Answer: No. The evidence is sufficient and it selects direction (a) — converge on `--incognito ` +at all three sites. The prerequisite verification task this decision depends on is hereby +discharged.** + +### Evidence (re-verified against item-792) + +The document is produced by `UtilitiesCS/OutlookObjects/Folder/BreadcrumbHtmlRenderer.cs` +(234 lines) and assembled at `BreadcrumbHtmlRenderer.cs:32-52`: + +- Inline `