Add WinGet COM API backend with runtime selection + eight COM-backed features#7
Merged
Conversation
Standalone console project that exercises the smallest end-to-end path
through the WinGet COM API (PackageManager → ConnectAsync → FindPackages
with PackageMatchFilter → CatalogPackage property access). Goal is to
catch the AOT-blocking warnings before committing to a full ComBackend
implementation.
Findings (full writeup in spikes/ComBackendSpike/SPIKE-RESULTS.md):
- Trim analysis from Linux cross-compile shows 0 IL2026 and 0 IL3050
warnings — the two categories that historically block CsWinRT
adoption in AOT-published apps.
- 35 IL2081 warnings all originate from CsWinRT marshaler fallback
paths (ABI.System.Collections.Generic.*, ABI.Windows.Foundation.*,
WinRT.Marshaler<>). Zero warnings originate from the
Microsoft.Management.Deployment surface itself.
- The COM projection requires net*-windows10.0.26100.0 TFM; consuming
project must pick a Windows TFM (or multi-target).
- InProcCom package is ~440MB extracted; only needed when bundling
the OOP COM server. Standard winget installations don't require it.
- Cross-OS Native AOT is not supported by ilc, so the final
`PublishAot=true` codegen and runtime smoke must be done on Windows.
Compile-time gates pass; runtime gates remain.
Verdict: green light to build the real ComBackend on this branch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lets the AOT publish + smoke run be invoked from any directory by absolute path. Wraps the publish/run sequence with cleaner output, parameters for query/RID, and a -SkipPublish flag for rerunning the already-built binary. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ions First Windows AOT run proved codegen + COM activation work (3 catalogs returned) but threw InvalidCastException in IReadOnlyListImpl.GetEnumerator the moment we foreach'd a projected IReadOnlyList<T>: the IIterable<T> RCW factory for that generic instantiation must be generated in the consuming app, and ComInterop ships only the WinRT.Runtime runtime, not the CsWinRT source generator. Fix (ComBackendSpike.csproj): - reference Microsoft.Windows.CsWinRT 2.2.0 (matches WinRT.Runtime in graph) so the AOT optimizer source generator runs - CsWinRTAotOptimizerEnabled=Auto, AllowUnsafeBlocks=true - CsWinRTGenerateProjection=false so it consumes ComInterop's prebuilt projection instead of re-running cswinrt.exe over the winmd - CsWinRTRcwFactoryFallbackGeneratorForceOptIn=true — the by-name knob that generates RCW factories for projected types consumed under AOT Program.cs rewritten as a two-lever diagnostic: Probe<T>() traverses each projected collection via foreach (IIterable<T>) and, on failure, via indexed access (IVectorView.GetAt), reporting which survives AOT — so one Windows run yields a decision table instead of a crash. Verified as far as a non-Windows host allows: restore (no version skew), compile, and WinRT.SourceGenerator running with ForceOptIn flowing to the compiler. Runtime confirmation pending a Windows re-run of Run-AotSpike.ps1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…optimizer Windows runtime result: even with Microsoft.Windows.CsWinRT 2.2.0 + CsWinRTRcwFactoryFallbackGeneratorForceOptIn=true wired in and confirmed reaching the compiler, `foreach` over a projected IReadOnlyList<T> still throws InvalidCastException under AOT for both catalogs and matches — the IIterable<T> generic-instantiation RCW is not generated for this third-party projection. Indexed access (IVectorView.GetAt) works perfectly with no optimizer at all (same surface as .Count, which worked in the very first run). So the optimizer package earned nothing: removed it and all four CsWinRT properties + AllowUnsafeBlocks. Final recipe is just the ComInterop reference plus index-based loops over WinRT collections. SPIKE-RESULTS.md updated with the decision table, the Materialize<T> helper pattern for the real backend, and the corrected verdict. Compile-clean on Linux with no cswinrt.exe in the graph; runtime behavior validated on Windows. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implements ComBackend (IBackend over the WinGet COM API) as a third backend alongside CliBackend and MockBackend, selectable at runtime, plus the build plumbing to ship it only where it can exist. Structure: - Main project now multi-targets net10.0 (cross-platform; mock/CLI) and net10.0-windows10.0.26100.0 (adds COM). ComBackend.cs + the ComInterop package + the WINGET_COM compile constant are scoped to the Windows TFM via csproj conditions; on net10.0 the file compiles to nothing so the Linux/mac dev loop is unaffected. EnableWindowsTargeting lets the Windows TFM build from a non-Windows host. Windows publish now needs -f net10.0-windows10.0.26100.0 (README updated). - Also fixes a pre-existing breakage: the root project globbed spikes/** into the main compile; now excluded alongside tests/**. ComBackend: - Search/Installed/Upgrades via composite catalogs (RemotePackagesFromRemote for search, LocalCatalogs for installed/upgrades); Show reads CatalogPackageMetadata; Install/Upgrade/Uninstall via the PackageManager async ops with status->OpResult mapping. - AOT rule from the spike honored throughout: never foreach a WinRT-projected collection (IIterable<T> RCW isn't generated under AOT). All projected lists go through Materialize<T>(), which copies via indexed IVectorView.GetAt. Trim analysis on the Windows TFM: 0 IL2026/IL3050. - No COM pin API, so Pin/Unpin/ListPins delegate to an internal CliBackend (winget.exe is always present where the COM server is) — keeps full parity. - HRESULT ExtendedErrorCode projects to System.Exception in this projection; HResultOf() pulls the numeric code back out for error messages. Runtime selection (Program.cs): --mock/-m, --cli, --com; default is COM on the Windows build, CLI elsewhere; both degrade to mock if winget is unusable. COM activation failure is caught and falls back to CLI rather than crashing. Verified: net10.0 builds + tests pass on Linux; Windows TFM compiles and trim-analyzes clean via EnableWindowsTargeting. Live COM runtime still to be exercised on Windows. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Threads structured operation progress from the backend to the status bar via an IProgress<OpProgress> parameter on InstallAsync/UpgradeAsync/UninstallAsync. - Models: OpPhase (Queued/Downloading/Installing/Finalizing/Done) + OpProgress (phase + 0..1 fraction), both cross-platform. - ComBackend: sets the WinRT op's .Progress handler before awaiting and maps InstallProgress / UninstallProgress (state + Download/Installation fraction) onto OpProgress. The handler fires on a COM thread; UiProgress marshals it. - MockBackend: synthesizes a download->install ramp so the progress UI is visible/testable on any host (no Windows needed). CliBackend ignores progress (winget.exe only emits an ANSI bar we don't scrape). - StatusBar: renders a determinate bar (▕████░░░░░░▏ 42% Installing X) with the phase label, replacing the spinner while an op runs. - App: RunOperation builds a UiProgress that App.Invoke-marshals reports to the UI thread; OnOpProgress ignores late reports after the op settles. Batch upgrade passes null (the loop owns its own per-item status line). Verified: both TFMs build, tests pass, Windows-TFM trim analysis still 0 IL2026/IL3050 (the .Progress delegate-callback marshaling rides the same CCW path the spike's awaited ConnectAsync already exercised under AOT). Live COM progress to be confirmed on Windows. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The progress plumbing already forwarded a token to the COM op's AsTask(ct) (which calls IAsyncInfo.Cancel cooperatively), but the UI passed CancellationToken.None and had no cancel gesture. This wires it up. - App owns a nullable _opCts; non-null = an operation is in flight (distinct from _viewCts/_detailCts, which cover list/detail refreshes). RunOperation creates it, passes its token to the op, and refuses to start a second op while one runs (avoids a leaked CTS and an ambiguous cancel target). - Esc is split out of the shared Q/Esc quit case: while an op runs it cancels the op (and shows "Cancelling…") instead of quitting; otherwise unchanged. - OperationCanceledException is caught distinctly -> "Cancelled" (not an error), and the list still refreshes since state may have partially changed. - Batch upgrade shares the same _opCts: Esc aborts the in-flight item via its token and the loop stops on the next iteration. - Status activity text advertises "· Esc to cancel" while running. Cross-backend: COM cancels cooperatively; mock's simulated ramp honors the token; CLI stops awaiting but does not kill winget.exe (unsafe mid-install). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Gate op-progress reports on _opCts (the precise "operation in flight" signal) instead of _state.Loading, which is also toggled by ordinary list/detail refreshes — closes a race where a concurrent refresh could drop progress samples or let a late report through after the op settled. - Skip malformed package rows in Search/Installed/Upgrades instead of letting a bad HRESULT on a single Id/Name read tear down the whole listing. - Add an Uninstalling phase so the status bar no longer says "Installing" during an uninstall. Documented two deferred findings as known limitations in ComBackend's header: all-or-nothing composite connect (mitigated by the 'f' source filter) and by-id-only operation resolution (matches CliBackend; needs an IBackend change to carry source identity). Both left as-is rather than ship untested COM connection-probing or an interface change that a Linux build can't verify. Both TFMs build, tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Backend gate/switch (Program.cs): - --cli now takes precedence over --com: COM is chosen only when --cli was not passed (was: --com won even alongside --cli, contradicting the documented order). Precedence is now --mock > --cli > --com > default. - Reworded the selection comment: flags are preferences, not hard guarantees — an unavailable requested backend degrades with a stderr note (--com off-Windows → CLI; CLI with no winget → mock). Behavior unchanged (graceful fallback is right for an interactive TUI); the docs just no longer claim "force". ComBackend.cs: - FindVersionId and ShowAsync now guard their COM property reads (AvailableVersions, vid.Version, pkg.Id/Name); a bad HRESULT yields "version not found" / a null detail (→ stub) instead of throwing, matching the list path's skip behavior. - Unknown uninstall progress state maps to Uninstalling (was Installing). - Upgrade failures now say "Upgrade failed" (DescribeInstall takes the verb). Documented two untestable-from-Linux items as known limitations: the shared PackageManager's thread-agility assumption (watch for RPC_E_WRONG_THREAD on Windows) and that pin ops require winget.exe on PATH even on the COM backend. Both TFMs build, tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…klist Single P0/P1/P2 checklist of everything that can only be confirmed on Windows (AOT can't cross-compile from Linux; the WinGet COM server and installs need Windows): foundational COM runtime, search/list/upgrade/show, the operations, the live progress bar (incl. the .Progress delegate CCW unknown), Esc cancellation, and the review-flagged concerns (shared PackageManager thread agility, unhealthy-source composite connect, pin-needs-winget, binary-size measurement). Includes build/run commands and expected results per item. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two COM-backed UX upgrades, plumbed through IBackend so CLI/Mock degrade. - IBackend gains ListVersionsAsync and GetInstallerPreviewAsync, plus an InstallerPreview model (installer type / architecture / scope / elevation + a "MSI · x64 · machine · admin" Summary). Note: the WinGet COM API exposes NO installer download size (BytesRequired is runtime-progress-only), so size is intentionally omitted rather than faked. - ComBackend: ListVersions enumerates AvailableVersions (indexed/Materialize, newest-first, deduped); GetInstallerPreview resolves PackageVersionInfo (for the chosen or default version) and maps GetApplicableInstaller(InstallOptions) → InstallerPreview, with friendly enum mapping and full exception guarding. - CliBackend degrades: empty version list (→ free-text fallback) and null preview (→ plain confirm). MockBackend returns representative data so both features are exercisable on Linux. - UI: new VersionPickerDialog (ListView). App.AskInstall is now async: `i` → fetch preview → confirm showing the installer summary → install; `I` → fetch versions → picker (or free-text fallback) → preview confirm → install. A FetchThen<T> helper runs the short async fetch off-thread with a transient status, then marshals the modal onto the UI thread. Both TFMs build, tests pass, Windows-TFM trim analysis 0 IL2026/IL3050. WINDOWS-TESTING.md updated with preview/picker verification items. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…gate - GetInstallerPreviewAsync no longer falls back to the installed version when a SPECIFIC version was requested but couldn't be resolved (that produced a preview computed from the wrong installer while the confirm said "Install X <version>"). Explicit version → resolve exactly or return null; latest → default-install else installed. - FetchThen now serializes preflight fetches via a _preflightBusy gate, so a rapid second trigger can't queue a duplicate modal or race the status line. Rejected from the review: the claim that ListView.SelectedItem is `int` (it's `int?` in Terminal.Gui 2.4.3-develop.9 — the build proves it; `?? -1` stays). Other low items (picker empty-list defensiveness, PickVersion using-dispose) left to match existing dialog patterns / caller contract. Both TFMs build, tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two more COM-backed operations, plumbed through IBackend (CLI maps to winget
flags, mock simulates).
Download-only ('d'):
- IBackend.DownloadAsync; ComBackend uses DownloadPackageAsync(DownloadOptions)
into %USERPROFILE%\Downloads\winget-tui, mapping PackageDownloadProgress onto
the existing OpProgress bar; CLI runs `winget download -d <dir>`; mock ramps a
download. Reuses RunOperation, so the progress bar + Esc-cancel work for free.
Advanced install ('A'):
- InstallSettings model (scope / mode / arch / custom args). InstallAsync gains
an InstallSettings? param across the interface + all three backends.
- ComBackend maps it onto InstallOptions (PackageInstallScope, PackageInstallMode,
AllowedArchitectures.Add, AdditionalInstallerArguments); CLI maps to --scope /
--silent|--interactive / --architecture / --custom; mock echoes it.
- New AdvancedInstallDialog (OptionSelector x3 + args field) gathers settings;
the install confirm shows an "Options: …" line alongside the installer preview.
InstallArgs keeps a default settings=null param so existing parser tests are
unaffected. OperationKind += Download. Help text + WINDOWS-TESTING.md updated.
Both TFMs build, tests pass, Windows-TFM trim analysis 0 IL2026/IL3050.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Advanced install: normalize an all-default selection to null ("backend
defaults") via InstallSettings.IsDefault, so it behaves identically to a plain
install on every backend instead of passing a no-op settings object. (The
underlying COM-forces-silent default is pre-existing and unchanged.)
- Download: stop swallowing Directory.CreateDirectory failures. Both COM and CLI
now fail fast with a clear "could not prepare download folder" message instead
of degrading into a more obscure winget/COM error later.
Kept (Low): the AdvancedInstallDialog enum-by-index cast — the alignment is
documented and an unexpected value degrades safely to Default; explicit map
arrays weren't worth the churn.
Both TFMs build, tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Verify install ('V'):
- IBackend.VerifyInstalledAsync; ComBackend maps CheckInstalledStatusAsync
(InstalledStatusType.AllChecks) onto an InstallVerification (Ok / Issues /
NotApplicable / Error + per-check list). Traverses the nested projected
vectors (PackageInstalledStatus → InstallerInstalledStatus) via Materialize
(AOT rule); an InstalledStatus whose HRESULT projects to a null Exception =
S_OK = passed. CLI returns null (no equivalent); mock fakes Ok/Issues.
- UI: 'V' runs the check via FetchThen and shows a ✓/✗ result MessageBox; the
detail panel and help advertise it.
Richer detail panel:
- PackageDetail gains Tags, SupportUrl, Documentation (DocLink list),
ProductCodes, PackageFamilyNames. ComBackend.ShowAsync fills them from
CatalogPackageMetadata (Tags/Documentations/PublisherSupportUrl) and
PackageVersionInfo (ProductCodes/PackageFamilyNames) via guarded indexed
reads. DetailPanel renders Tags/identifiers as KV lines and Support/Docs as
clickable link rows; absent fields are omitted (no empty rows). Mock supplies
representative values; CLI leaves them null.
Both TFMs build, tests pass, Windows-TFM trim analysis 0 IL2026/IL3050.
Help + WINDOWS-TESTING.md updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
VerifyInstalledAsync hardening: - Track hadReadError across the nested COM reads; if any installer/entry read throws, don't report Ok/NotApplicable (which would falsely call a partially- read install "clean") — report Error. - Wrap the result.Status / PackageInstalledStatus traversal in a guard so a throwing getter/materialization maps to VerifyOutcome.Error instead of leaking. - Stop overloading null: a not-found/not-installed package now returns NotApplicable; null is reserved for "backend can't verify" (CLI), so the UI's "only on the COM backend" message is no longer shown for a Windows lookup miss. DocLinks: per-element try/catch so one malformed Documentation entry is skipped instead of dropping the whole documentation list. Both TFMs build, tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces a new Windows-only WinGet COM API backend alongside the existing CLI/mock backends, adds runtime backend selection, and extends the UI/UX to support COM-only capabilities like structured progress, cancellation, version picking, download-only, advanced install options, verification, and richer manifest details.
Changes:
- Multi-target the app (
net10.0+net10.0-windows10.0.26100.0) and add a Windows-TFM-only COM backend (plus runtime backend selection via--mock/--cli/--com). - Extend
IBackendwith progress-aware operations and new COM-enabled capabilities (version listing, installer preview, download-only, verify install), with CLI/mock graceful degradation. - Add UI elements for live progress + cancel (Esc), version picker, advanced install dialog, download-only action, verify action, and richer detail panel fields; add Windows verification checklist and a spike project documenting AOT constraints.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| WingetTuiSharp.csproj | Multi-targeting + Windows-only COM dependency/constant; exclude spikes from main compile globs. |
| WINDOWS-TESTING.md | New Windows runtime verification checklist for COM/AOT behavior and features. |
| src/Ui.cs | Status bar determinate progress UI; version picker + advanced install dialogs; help text updates. |
| src/Models.cs | New models/enums for progress, installer preview, advanced install settings, verify results, richer details. |
| src/MockBackend.cs | Implement new IBackend surface (versions/preview/progress/download/verify) with simulated behavior. |
| src/GlobalUsings.cs | Add global using for ObservableCollection (used by version picker). |
| src/DetailPanel.cs | Render richer manifest fields (tags/product code/family name/support/docs) + add Verify action. |
| src/ComBackend.cs | New WinGet COM API backend (Windows TFM only), including progress mapping, download, verify, installer preview, versions; pins delegated to CLI. |
| src/CliBackend.cs | Extend CLI backend to satisfy new IBackend surface; add winget download and advanced install flag mapping; verify/versions/preview degrade. |
| src/Backend.cs | Expand IBackend API for new COM-backed features and progress reporting. |
| src/AppState.cs | Track in-flight operation progress in app state. |
| src/App.cs | Wire new actions (download/advanced/verify/version picker), Esc-to-cancel, one-op-at-a-time gate, progress plumbing. |
| Program.cs | Runtime backend selection logic with graceful degradation and precedence rules. |
| README.md | Update build/run docs for multi-targeting and runtime backend selection; document COM backend behavior. |
| spikes/ComBackendSpike/SPIKE-RESULTS.md | Document AOT findings and “index, don’t enumerate WinRT projections” constraint. |
| spikes/ComBackendSpike/Run-AotSpike.ps1 | PowerShell helper to publish/run the spike under Native AOT on Windows. |
| spikes/ComBackendSpike/Program.cs | Spike program probing COM + AOT enumeration failure modes and the indexing workaround. |
| spikes/ComBackendSpike/ComBackendSpike.csproj | Spike project configuration mirroring AOT/trim settings and COM package reference. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a WinGet COM API backend (
Microsoft.Management.Deployment) alongside the existing CLI and mock backends, plus eight UX features built on capabilities the COM API exposes that CLI stdout-parsing can't (or can only do badly). The COM backend returns structured objects instead of parsingwinget.exetabular output — no truncated IDs, no locale/encoding scraping, real progress and cancellation.Backend is selectable at runtime (
--mock/--cli/--com), defaulting to COM on Windows, CLI elsewhere.Why COM (the spike)
Before writing the backend,
spikes/ComBackendSpike/validated AOT readiness on a real Windows host. Key finding: neverforeach/LINQ over a WinRT-projected collection under Native AOT — theIIterable<T>runtime-callable-wrapper isn't generated, so enumeration throwsInvalidCastException. Indexed access (IVectorView.GetAt) works. The whole backend funnels projected collections through aMaterialize<T>()helper that copies via indexing; ordinaryforeach/LINQ then runs on the managed copy. The CsWinRT AOT optimizer was tried and did not fix the enumeration path, so it's intentionally not referenced — the dependency footprint is justMicrosoft.WindowsPackageManager.ComInterop. Full writeup inspikes/ComBackendSpike/SPIKE-RESULTS.md.Architecture
net10.0(cross-platform; mock/CLI dev loop on Linux) +net10.0-windows10.0.26100.0(the Windows deploy target, which adds COM).ComBackend.cs, theComInteroppackage, and aWINGET_COMcompile constant are scoped to the Windows TFM via csproj conditions; onnet10.0the file compiles to nothing. Windows publish:dotnet publish -f net10.0-windows10.0.26100.0 -r win-x64.Program.cs): precedence--mock > --cli > --com > default; COM activation failure degrades to CLI rather than crashing.IBackendabstraction so the three backends stay swappable; CLI/mock degrade gracefully for COM-only features.Features
InstallProgress/DownloadProgress/UninstallProgress→ a determinate status-bar bar with phase labels (Downloading → Installing → …).EscIAsyncInfo.Cancel()(CLI can only orphan/kill the process).iGetApplicableInstaller→ confirm showsMSI · x64 · machine · admin(type/arch/scope/elevation). (COM exposes no download size, so size is intentionally omitted.)IAvailableVersions→ selectable list instead of free-text (CLI falls back to free-text).dDownloadPackageAsyncfetches the installer without installing, reusing the progress bar + cancel.AInstallOptions(CLI → winget flags).VCheckInstalledStatusAsyncflags broken files/registration (corrupt-install detection). No CLI equivalent.Cross-backend behavior
winget.exe+ parsewinget.exe(no COM pin API)InstallOptionsReview & quality
Each feature was followed by a read-only GitHub Copilot CLI review (inline-diff, no autonomous tooling); findings were triaged with judgment — valid ones fixed (e.g. an operation-progress race, a version-preview fallback bug, verify falsely reporting "clean" on partial COM reads), false positives rejected with evidence (e.g.
ListView.SelectedItemisint?in this Terminal.Gui build), and untestable-from-Linux concerns documented as known limitations. Thefix:commits are these review passes.Known limitations (documented in
ComBackend.cs)PackageManageracross background threads assumes COM agility — verify noRPC_E_WRONG_THREADon Windows.msstoresource can fail anAllquery (workaround:fto narrow source).winget.exeon PATH even on the COM backend.Stats
~2,350 insertions across the app + ~600 in the spike. 12
feat/fixcommits + 4 spike commits.Test plan
net10.0builds on Linux;net10.0-windows10.0.26100.0builds viaEnableWindowsTargetingdotnet testgreen (parser tests unaffected)WINDOWS-TESTING.md(P0 foundational → P1 features → P2 edges)🤖 Generated with Claude Code