Skip to content

feat: WinGet COM backend enrichment — richer details, dynamic sources, Repair, version badge#10

Merged
harder merged 6 commits into
mainfrom
feat/com-backend
Jun 13, 2026
Merged

feat: WinGet COM backend enrichment — richer details, dynamic sources, Repair, version badge#10
harder merged 6 commits into
mainfrom
feat/com-backend

Conversation

@harder

@harder harder commented Jun 13, 2026

Copy link
Copy Markdown
Owner

Summary

Builds on the initial WinGet COM backend (merged in #7) to cover more of the COM API surface, add the Repair operation, make source filtering dynamic, and surface which backend is actually live. It also folds in a full Windows verification pass — whose headline result is a confirmed finding (below) — plus a few UI parity/quality fixes.

Both target frameworks (net10.0, net10.0-windows10.0.26100.0) build with 0 warnings; the test suite is green (109 tests).

🚨 Headline finding — COM does not activate under Native AOT

Verification on a real Windows host showed the Native-AOT build cannot activate the WinGet PackageManager COM class (0x80073D54 APPMODEL_ERROR_NO_PACKAGE). SelectBackend catches it and silently falls back to the CLI backend (the stderr note is painted over by the TUI), so the default AOT launch is effectively CLI-backed.

  • The same source built non-AOT (JIT, self-contained x64) activates COM fine (3 catalogs) — so it's AOT-specific, not server state and not apartment (both threads are MTA).
  • Did not fix it: CsWinRT AOT optimizer, an app.manifest with supportedOS + longPathAware, or warming the server first.
  • Caveat: an early-session AOT spike did activate COM once, so AOT-COM may not be categorically impossible. Decisive next experiment (clean reboot → --comdiag as the first COM activity) and the full resume plan are in HANDOFF.md.

Consequence: items previously believed "verified on COM" were largely exercising the CLI path (which also returns structured data). The richer-detail gap (#17) is a symptom of the CLI fallback, not a separate bug. This PR documents the finding rather than working around it.

COM backend enrichment

  • Richer detail panel — surfaces Author, Copyright, Privacy/Purchase links, Installation notes, and (for installed packages) Scope + Installed-to, from CatalogPackageMetadata / PackageVersionInfo.GetMetadata. Each field renders only when present, so sparse packages stay clean.
  • Dynamic sources — replaces the hard-coded winget/msstore enum with source names discovered via PackageManager.GetPackageCatalogs() (COM) / winget source list (CLI). The f filter now cycles every configured source — custom/enterprise REST sources included — and All expands to all of them.
  • Backend + version badgeIBackend.DescribeAsync shows the live backend and winget version (PackageManager.Version) in a header badge and the help dialog: a quick tell of whether AOT activated COM or fell back to CLI.
  • Search match hint — non-obvious matches (tag/moniker/command/etc.) from MatchResult.MatchCriteria get a dim "matched on …" footnote.
  • Search cap — COM search is bounded at 1000 via FindPackagesOptions.ResultLimit, with a nudge to refine when hit.

Repair operation (COM-only)

  • New R action and a Verify → Repair offer, backed by PackageManager.RepairPackageAsync. Reuses the existing operation/progress plumbing (Esc-cancel, single-op gate, progress bar). CLI reports it unavailable; the mock synthesizes it for Linux dev. Design spec in docs/superpowers/specs/.

COM stability/perf fixes

  • Detail-load debounce (200 ms) before an uncached ShowAsync, so fast list-scrolling no longer fires a COM detail fetch per row — that was throttling/wedging the out-of-proc COM server and stalling the detail panel for 30–60 s.
  • msstore contrast fix — the Source-column color getter no longer overrides the selected-row foreground (was rendering Accent-on-Accent / invisible when highlighted).

UI parity & quality

  • Click-to-sort headers — click a Name/Id/Version header to sort (click again to reverse); marker/Available/Source are no-ops. Keyboard S cycling unchanged.
  • Truncated-id upgrade fallback — upgrading a row whose id winget truncated with no longer blocks; it confirms and resolves the package by exact name (--name --exact). Other operations keep the truncated-id guard.
  • Contextual empty-state messages — the empty list now explains why it's empty ("All packages are up to date!", "No unpinned packages with upgrades found.", a local-filter note) instead of a bare header.

Dependencies

  • Bump Terminal.Gui to 2.4.7-develop.1, picking up the 2.4.4-line render/input fixes (viewport-local redraw narrowing, WSL/Linux clipboard Unicode, Windows VT input for IME/non-ASCII, AnsiOutput resize).

Testing

  • 109 unit tests pass on Linux (source-list parsing, repair, version/sort/filter logic, upgrade-arg construction, the new sort/empty-state/upgrade-query helpers).
  • COM-only and interactive paths can't be exercised from Linux; every such item is tracked in WINDOWS-TESTING.md with repro steps, and session state/resume plan in HANDOFF.md.

🤖 Generated with Claude Code

harder and others added 6 commits May 29, 2026 16:15
…cation handoff

Windows COM-backend verification run against WINDOWS-TESTING.md (paused mid-P1).

P0 (foundational COM-on-AOT runtime): all 9 items PASS on a real ARM64 Windows host
(x64 AOT, ~23.3 MB, no coreclr.dll). The AOT InvalidCastException risk does not occur
in the app — the indexed Materialize<T> pattern holds across search/list/upgrades/show.

Fixes applied (compile clean; runtime-verify on Windows still pending):
- src/App.cs: DetailLoadDebounceMs=200 before uncached ShowAsync so fast list-scrolling
  no longer fires a COM detail fetch per row (was throttling/wedging the WinGet COM
  server and stalling the detail panel 30-60s).
- src/App.cs: Source-column ColorGetter no longer overrides the selected-row foreground,
  fixing msstore rows rendering Accent-on-Accent (invisible) when highlighted.
- spikes/ComBackendSpike/Program.cs: added a GetCatalogPackageMetadata field probe
  (Step 5) that proved the COM richer-detail data (Tags/SupportUrl/Documentations) IS
  present and readable via the indexed pattern from a single-catalog connect.

Open: P1 #17 (richer detail panel) is a CONFIRMED BUG — Tags/Support/Documentation never
render though the data exists; suspect is ShowAsync's composite-All catalog path. Root
cause unconfirmed (needs a --comshow dump on Windows with a healthy COM server). Rest of
P1 (install/version-picker/download/advanced/verify/progress/cancellation) and P2 are
untested — all need COM. Full status, build instructions, repro, and resume plan in
HANDOFF.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ve AOT

Session 2 of the Windows verification uncovered the central result: the AOT build
cannot activate the WinGet PackageManager COM class (0x80073D54 APPMODEL_ERROR_NO_PACKAGE),
so SelectBackend silently falls back to the CLI backend (the stderr note is hidden by the
TUI). Proven via the COM-only `V` action reporting "only available on the COM backend".

- Same source built non-AOT (JIT, self-contained x64) activates COM fine (3 catalogs),
  even with the server warm — so it's AOT-specific, not server state, not apartment (MTA).
- A clean AOT spike (no coreclr.dll) also fails — not app-vs-spike.
- Did NOT fix: CsWinRT AOT optimizer; app.manifest supportedOS+longPathAware; warming server.
- Caveat: an early-session AOT spike DID activate COM, so AOT-COM isn't categorically
  impossible — current failure may be entangled with COM-server state the diagnostic abuse +
  reboot left odd. Decisive next step: clean-reboot `--comdiag` as the first COM activity.

Consequences recorded in WINDOWS-TESTING.md + HANDOFF.md:
- P0 item 3 (default = COM) FAILS; most "P0 on COM" passes were actually the CLI backend.
- #17 (missing Tags/Support/Docs) is a SYMPTOM of the CLI fallback, not a composite bug
  (that hypothesis withdrawn).

All experimental changes (CsWinRT optimizer, app.manifest, --comshow/--comdiag diagnostics)
reverted; tree carries only the committed debounce + contrast fixes. --comdiag snippet and the
clean-reboot resume plan are in HANDOFF.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tracks the develop prerelease per the csproj policy. Picks up the 2.4.4-line
render/input fixes for free: viewport-local NeedsDrawRect normalization,
draw-pipeline narrowing for child-only redraws, WSL/Linux clipboard Unicode,
Windows VT input encoding for IME/non-ASCII, and the AnsiOutput resize fix.
All other packages (ComInterop, xunit, Test.Sdk) were already latest. Builds
clean on both TFMs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… Repair

Leverages more of the WinGet COM API surface and adds the Repair action.

Detail panel enrichment: surface Author, Copyright, Privacy/Purchase links,
Installation notes, and (for installed packages) Scope + Installed-to from
CatalogPackageMetadata / PackageVersionInfo.GetMetadata. Each renders only when
present.

Dynamic sources: replace the hard-coded winget/msstore SourceFilter enum with
string-based source names discovered via PackageManager.GetPackageCatalogs()
(COM) / 'winget source list' (CLI). The f filter now cycles through all
configured sources, so custom/enterprise REST sources are filterable. 'All' now
expands to every configured source.

Backend badge: IBackend.DescribeAsync surfaces the live backend + winget version
(PackageManager.Version) in a header badge and the help dialog — a quick tell of
whether the AOT build activated COM or fell back to CLI.

Search match hint: annotate non-obvious search matches (tag/moniker/command/etc.)
via MatchResult.MatchCriteria with a dim 'matched on …' footnote.

Search cap: bound COM search at 1000 via FindPackagesOptions.ResultLimit and
nudge the user to refine when hit.

Repair (COM-only, like Verify): new R action and a Verify->Repair offer, backed
by PackageManager.RepairPackageAsync. Reuses the existing operation/progress
plumbing (Esc-cancel, one-op gate, progress bar). CLI reports it unavailable;
the mock synthesizes it for Linux dev. Design spec under docs/superpowers/specs.

Both TFMs build with 0 warnings; 95 tests pass (incl. new source-list parser and
repair tests). COM-only paths captured in WINDOWS-TESTING.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-state messages

- Click a Name/Id/Version column header to sort by it (ascending);
  click the active column again to reverse. Marker/Available/Source
  headers are no-ops. Keyboard 'S' cycling is unchanged. Implemented
  via a SortableTableView that reports header clicks through ScreenToCell.
- Upgrading a row whose id winget truncated with '…' no longer blocks:
  AskUpgrade now confirms and hands the backend the package name, which
  the CLI backend resolves via '--name --exact'. Other operations keep
  the truncated-id guard.
- Empty package list now shows a contextual reason ("All packages are up
  to date\!", "No unpinned packages with upgrades found.", a local-filter
  note, etc.) instead of a bare header.

Adds unit tests for UpgradeQueryFor, EmptyStateMessage, and
SortFieldForHeader; logs the interactive/real-winget verification steps
to WINDOWS-TESTING.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 13, 2026 03:26
@harder harder merged commit c7f3593 into main Jun 13, 2026
3 checks passed
@harder harder deleted the feat/com-backend branch June 13, 2026 03:30

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR extends the WinGet backend abstraction to surface richer package details and operational capabilities, while improving UI behavior and diagnostics around which backend (COM/CLI/Mock) is currently active.

Changes:

  • Enriches the COM backend (more detail metadata, match-hint, result cap, Repair operation) and expands IBackend with dynamic sources + backend description.
  • Updates the UI to support click-to-sort headers, contextual empty-state messaging, backend/version badge, and debounced detail loading.
  • Refactors source filtering from a fixed enum to dynamic source names discovered at runtime (COM + CLI), with corresponding unit tests.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
WingetTuiSharp.csproj Bumps Terminal.Gui dependency version.
WINDOWS-TESTING.md Updates Windows manual verification checklist and documents AOT/COM activation finding.
tests/ParserTests.cs Updates source-filter arg tests and adds CLI source-list parsing tests.
tests/AppBehaviorTests.cs Adds tests for truncated-id upgrade query, empty-state messaging, header sort mapping, and mock repair.
src/Ui.cs Moves source filter to string? and adds backend line to help dialog.
src/Models.cs Removes SourceFilter enum; adds match-field + enriched detail fields; adds Repair operation/phase.
src/MockBackend.cs Adapts to string source filters; adds ListSources, Repair, and Describe.
src/DetailPanel.cs Renders new metadata fields, match hint, installation notes, and Repair action.
src/ComBackend.cs Adds search cap, match-field extraction, enriched metadata, dynamic sources, Repair, and Describe.
src/CliBackend.cs Adapts to string source filters; adds ListSources, ParseSourceNames, Repair unavailable, and Describe.
src/Backend.cs Extends IBackend with dynamic sources, Repair capability, and backend description.
src/AppState.cs Adds dynamic source list and cycling; adds search result limit constant; stores backend description.
src/App.cs Adds backend badge, loads sources at startup, debounces detail loading, click-to-sort, empty-state message row, upgrade truncated-id fallback, and Verify→Repair flow.
spikes/ComBackendSpike/Program.cs Extends spike diagnostic probing for richer metadata field access.
HANDOFF.md Adds detailed Windows verification handoff notes and AOT/COM activation findings.
docs/superpowers/specs/2026-06-12-repair-feature-design.md Adds Repair feature design spec.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/App.cs
Comment on lines +437 to 443
// Render a single message row explaining *why* the list is empty, instead of a bare
// headered table. The message is contextual: "All packages are up to date!" vs. a
// filter/pin-specific note. Mirrors upstream winget-tui's empty-state messages (#228).
_packageTable.Table = new EnumerableTableSource<string> ([EmptyStateMessage (_state)], new ()
{
{ _state.Mode == AppMode.Upgrades ? "Name" : "Name", _ => string.Empty }
{ " ", message => message }
});
Comment thread WINDOWS-TESTING.md
Comment on lines +121 to +123
- [ ] On a stock machine, `f` still cycles **All → Winget → MsStore → All** (the discovered list matches the two predefined sources).
- [ ] **Add a custom source** (`winget source add -n contoso <url>`), relaunch, and confirm `f` now includes **contoso** in the cycle and filtering to it scopes the list/search to that source. (This is the whole point of going dynamic — verify it on both COM and `--cli`.)
- [ ] With a source selected that no longer exists (remove it while the app holds it selected, then refresh), the filter resets to **All** rather than erroring.
Comment thread src/ComBackend.cs
Comment on lines +77 to +80
// Cap a pathologically broad query (e.g. a one-letter term) so it can't materialize tens
// of thousands of rows. The app already blocks empty queries; this guards the merely-broad
// ones. result.WasLimitExceeded below tells the UI to nudge the user to refine.
opts.ResultLimit = AppState.SearchResultLimit;
Comment thread src/App.cs
Comment on lines +388 to +393
// A search that hit the result cap means there's more the user can't see;
// nudge them to narrow it (only the COM backend actually caps).
if (mode == AppMode.Search && packages.Count >= AppState.SearchResultLimit)
{
_state.StatusMessage = $"{AppState.SearchResultLimit}+ matches — refine your search to narrow";
}
Comment thread src/App.cs
Comment on lines +597 to +599
AppMode.Search => string.IsNullOrEmpty (state.SearchQuery)
? "Type to search for packages."
: "No packages found.",
harder added a commit that referenced this pull request Jun 13, 2026
…OM tell

The COM backend work merged to main (PR #10), so the checklist no longer
references a feature branch. Elevate the top-right backend badge
(COM/CLI/Mock, from PackageManager.Version) to the primary COM-vs-CLI tell,
and turn the default-backend P0 item into a re-check against the badge on a
freshly-pulled main build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants