CmdPal: Extension Gallery#46636
Conversation
There was a problem hiding this comment.
check-spelling found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.
This comment has been minimized.
This comment has been minimized.
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
@copilot fix spell check |
Agent-Logs-Url: https://github.com/microsoft/PowerToys/sessions/e2de7cde-3866-4cfd-a69d-79629aa2b9fb Co-authored-by: niels9001 <9866362+niels9001@users.noreply.github.com>
Fixed in b4ce152. Added all 16 flagged words to the spell-check allow lists:
Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
@niels9001 I'll deal with our mighty spell checker overlord, in a different way |
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [ ] Closes: #xxx <!-- - [ ] Closes: #yyy (add separate lines for additional resolved issues) --> - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [ ] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed --------- Co-authored-by: Jiří Polášek <me@jiripolasek.com>
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
…ache, fix icon placeholder, fix gallery page stale data info bar
| if (_trackerSubscriptionActive) | ||
| { | ||
| case PackageInstallProgressState.Queued: | ||
| _installBanner.Message = string.Format(CultureInfo.CurrentCulture, QueuedPackageDownload, _package.Name); | ||
| break; | ||
| case PackageInstallProgressState.Downloading: | ||
| if (progress.BytesRequired > 0) | ||
| { | ||
| var downloadText = string.Format(CultureInfo.CurrentCulture, DownloadProgress, FormatBytes(progress.BytesDownloaded), FormatBytes(progress.BytesRequired)); | ||
| _installBanner.Progress ??= new ProgressState() { IsIndeterminate = false }; | ||
| var downloaded = progress.BytesDownloaded / (float)progress.BytesRequired; | ||
| var percent = downloaded * 100.0f; | ||
| ((ProgressState)_installBanner.Progress).ProgressPercent = (uint)percent; | ||
| _installBanner.Message = downloadText; | ||
| } | ||
| return; | ||
| } | ||
|
|
||
| break; | ||
| case PackageInstallProgressState.Installing: | ||
| _installBanner.Message = string.Format(CultureInfo.CurrentCulture, InstallingPackage, _package.Name); | ||
| _installBanner.Progress = new ProgressState() { IsIndeterminate = true }; | ||
| break; | ||
| case PackageInstallProgressState.PostInstall: | ||
| _installBanner.Message = string.Format(CultureInfo.CurrentCulture, InstallPackageFinishing, _package.Name); | ||
| break; | ||
| case PackageInstallProgressState.Finished: | ||
| _installBanner.Message = Properties.Resources.winget_install_finished; | ||
| _trackerSubscriptionActive = true; | ||
| _winGetOperationTrackerService.OperationStarted += OnTrackedOperationChanged; |
There was a problem hiding this comment.
nit: _trackerSubscriptionActive could be an atomic?
zadjii-msft
left a comment
There was a problem hiding this comment.
I am okay with this with only nits. In practice, this is incredible
| var fadeOut = new DoubleAnimation | ||
| { | ||
| To = 0, | ||
| Duration = new Duration(TimeSpan.FromMilliseconds(200)), | ||
| EasingFunction = new CubicEase { EasingMode = EasingMode.EaseIn }, | ||
| }; | ||
| Storyboard.SetTarget(fadeOut, BreadcrumbContainer); | ||
| Storyboard.SetTargetProperty(fadeOut, "Opacity"); | ||
|
|
||
| _breadcrumbStoryboard = new Storyboard(); | ||
| _breadcrumbStoryboard.Children.Add(fadeOut); | ||
| _breadcrumbStoryboard.Completed += (_, _) => | ||
| { | ||
| BreadcrumbContainer.Visibility = Visibility.Collapsed; | ||
| BreadcrumbContainer.Opacity = 1; | ||
| _breadcrumbStoryboard = null; | ||
| }; | ||
| _breadcrumbStoryboard.Begin(); | ||
| } | ||
|
|
||
| private void ShowBreadcrumb() | ||
| { | ||
| _breadcrumbStoryboard?.Stop(); | ||
| _breadcrumbStoryboard = null; | ||
|
|
||
| if (BreadcrumbContainer.Visibility == Visibility.Collapsed) | ||
| { | ||
| BreadcrumbContainer.Opacity = 0; | ||
| BreadcrumbContainer.Visibility = Visibility.Visible; | ||
|
|
||
| var fadeIn = new DoubleAnimation | ||
| { | ||
| To = 1, | ||
| Duration = new Duration(TimeSpan.FromMilliseconds(250)), | ||
| EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut }, | ||
| }; | ||
| Storyboard.SetTarget(fadeIn, BreadcrumbContainer); | ||
| Storyboard.SetTargetProperty(fadeIn, "Opacity"); | ||
|
|
||
| _breadcrumbStoryboard = new Storyboard(); | ||
| _breadcrumbStoryboard.Children.Add(fadeIn); | ||
| _breadcrumbStoryboard.Completed += (_, _) => _breadcrumbStoryboard = null; | ||
| _breadcrumbStoryboard.Begin(); | ||
| } | ||
| else | ||
| { | ||
| BreadcrumbContainer.Opacity = 1; | ||
| } | ||
| } |
There was a problem hiding this comment.
maybe I just don't know XAML but this feels like something that can be done entirely in XAML. Like... do we really need to instantiate a new animation and start it every time we fade in/out? or can we just have one pair of fadein & fadeout animations that we just play when we need them?
There was a problem hiding this comment.
Fair, this is nicer.. but then it's also not composition. We could just default back to the standard animations or using the Community Toolkit Show/Hide animations?
| { | ||
| BreadcrumbContainer.Visibility = Visibility.Collapsed; | ||
| BreadcrumbContainer.Opacity = 1; | ||
| _breadcrumbStoryboard = null; |
There was a problem hiding this comment.
I really hope that nulling out the storyboard also then removes the Completed handlers we've added. Does it? cause I have a feeling it doesn't
There was a problem hiding this comment.
It does not, but that does not matter here because the entire object cycle gets collected. It works in this case, but it could have been written in a better way. #47900
can make this go away entirely.
Thanks for review. I'll try to address the comments asap. |
There was a problem hiding this comment.
Pull request overview
Adds the Command Palette Extension Gallery so users can browse community extensions, view details/screenshots, and install/update via shared WinGet infrastructure.
Changes:
- Adds gallery feed models/services, HTTP disk caching, cache directory support, and WinGet service abstractions.
- Adds gallery/settings UI, screenshot viewer, WinGet operations progress button, and related view models.
- Updates WinGet extension integration, tests, docs, and spell-check configuration.
Reviewed changes
Copilot reviewed 108 out of 109 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
src/modules/cmdpal/Tests/Microsoft.CmdPal.UI.ViewModels.UnitTests/OpenSettingsCommandTests.cs |
Tests settings/gallery command navigation messages. |
src/modules/cmdpal/Tests/Microsoft.CmdPal.UI.ViewModels.UnitTests/Microsoft.CmdPal.UI.ViewModels.UnitTests.csproj |
Adds Common project reference. |
src/modules/cmdpal/Tests/Microsoft.CmdPal.Common.UnitTests/WinGet/Services/WinGetPackageManagerServiceTests.cs |
Tests WinGet unavailable behavior. |
src/modules/cmdpal/Tests/Microsoft.CmdPal.Common.UnitTests/WinGet/Services/WinGetOperationTrackerServiceTests.cs |
Tests WinGet operation tracking. |
src/modules/cmdpal/Tests/Microsoft.CmdPal.Common.UnitTests/ExtensionGallery/Models/GalleryExtensionEntryTests.cs |
Tests gallery JSON model serialization. |
src/modules/cmdpal/Microsoft.CmdPal.UI/Styles/Button.xaml |
Adds shared button styles. |
src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw |
Adds localized gallery and WinGet strings. |
src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/SettingsWindow.xaml.cs |
Adds gallery navigation and screenshot viewer logic. |
src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/SettingsWindow.xaml |
Adds gallery nav item, WinGet operations button, and screenshot popup. |
src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/InternalPage.xaml.cs |
Adds custom gallery feed setting handling. |
src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/InternalPage.xaml |
Adds hidden custom feed input. |
src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/ExtensionsPage.xaml |
Cleans up old extension discovery UI. |
src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/ExtensionGalleryPage.xaml.cs |
Adds gallery page behavior. |
src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/ExtensionGalleryItemPage.xaml.cs |
Adds gallery details/screenshot interactions. |
src/modules/cmdpal/Microsoft.CmdPal.UI/Microsoft.CmdPal.UI.csproj |
Includes new UI controls/packages. |
src/modules/cmdpal/Microsoft.CmdPal.UI/Messages/OpenExtensionGalleryScreenshotViewerMessage.cs |
Adds screenshot viewer message. |
src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/WinGetServiceRegistration.cs |
Registers shared WinGet services. |
src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/GalleryServiceRegistration.cs |
Registers gallery services/view models. |
src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/BindTransformers.cs |
Adds boolean visibility helpers. |
src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/WinGetOperationsButton.xaml.cs |
Adds WinGet operation progress control logic. |
src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/WinGetOperationsButton.xaml |
Adds WinGet operation progress flyout UI. |
src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ScrollContainer.xaml |
Uses shared scroll button style. |
src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/IconCarouselControl.xaml |
Adds gallery icon carousel shell. |
src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml.cs |
Wires gallery/WinGet services and disposal. |
src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml |
Loads new button styles. |
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/WinGet/WinGetOperationViewModel.cs |
Adds WinGet operation item view model. |
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/WinGet/WinGetOperationsViewModel.cs |
Adds WinGet operations collection view model. |
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsModel.cs |
Adds gallery feed setting. |
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/DefaultCommandProviderCache.cs |
Formatting cleanup. |
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/ExtensionService.cs |
Adds refreshable installed extension cache. |
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Gallery/ExtensionGalleryItemViewModel.cs |
Adds gallery item actions/status handling. |
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Gallery/GallerySourceViewModel.cs |
Adds install source metadata view model. |
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Gallery/GallerySourceDetailItemViewModel.cs |
Adds source detail row model. |
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Gallery/ExtensionGallerySortOption.cs |
Adds gallery sort enum. |
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Gallery/ExtensionGalleryScreenshotViewModel.cs |
Adds screenshot view model. |
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Gallery/ExtensionGalleryItemViewModelFactory.cs |
Adds gallery item factory. |
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/OpenSettingsCommand.cs |
Supports opening specific settings pages. |
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/OpenGallerySettingsCommand.cs |
Adds gallery command. |
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/BuiltInsCommandProvider.cs |
Exposes gallery command. |
src/modules/cmdpal/Microsoft.CmdPal.Common/WinGet/* |
Adds shared WinGet models, services, and interop. |
src/modules/cmdpal/Microsoft.CmdPal.Common/Services/* |
Adds cache directory and HTTP caching infrastructure. |
src/modules/cmdpal/Microsoft.CmdPal.Common/ExtensionGallery/* |
Adds gallery feed models and service. |
src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/* |
Refactors WinGet extension to shared services. |
doc/devdocs/modules/cmdpal/extension-gallery.md |
Adds gallery developer documentation. |
.github/actions/spell-check/* |
Updates spell-check allow/exclude lists. |
Files not reviewed (1)
- src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.Designer.cs: Language not supported
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Agent-Logs-Url: https://github.com/microsoft/PowerToys/sessions/cd12c2f6-f138-4850-bdf6-065110739fc3 Co-authored-by: niels9001 <9866362+niels9001@users.noreply.github.com>
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
These files were intentionally deleted on main by recent PRs but were resurrected when 7aae734 merged main with a bad conflict resolution: - HideWindowMessage.cs (deleted by #47826 CmdPal parameters) - BookmarkPlaceholderForm.cs (deleted by #47886 bookmarks-as-parameters) - NativeMethods.json/.txt + WindowsPackageManager.Interop/* (deleted by #46636 Extension Gallery) - Ext.Indexer/Assets/Actions.png (moved/deleted by #46636) - DdcCiValidationResult.cs (deleted by #47875 PowerDisplay max-compat) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>


Summary of the Pull Request
Adds the Extension Gallery to Command Palette — a built-in page where users can discover, browse, and install community extensions without leaving the app.
ExtensionGallery.mp4
How it works
1. The extension author's side
Extensions are listed in the external repo
microsoft/CmdPal-Extensions. To get an extension into the in-app gallery, an author opens a PR there that adds a single entry toextensions.json. Nothing in PowerToys itself needs to change. A typical entry looks like:{ "id": "contoso.sample", "title": "Sample Extension", "description": "Short blurb shown in the list and detail view.", "author": { "name": "Contoso", "url": "https://github.com/contoso" }, "homepage": "https://github.com/contoso/sample", "iconUrl": "https://.../icon.png", "screenshotUrls": ["https://.../screenshot-1.png"], "tags": ["sample"], "installSources": [ { "type": "winget", "id": "Contoso.SampleExtension" }, { "type": "msstore", "id": "9P..." }, { "type": "url", "uri": "https://github.com/contoso/sample/releases/latest" } ], "detection": { "packageFamilyName": "Contoso.SampleExtension_8wekyb..." } }id,title,description,author.name, and at least oneinstallSourcesentry are required; everything else is optional.installSourcescan mix and matchwinget/msstore/url. The gallery shows an install button for the first source it can handle (WinGet preferred) and exposes any remaining sources as links.detection.packageFamilyNamelets CmdPal recognise an already-installed packaged extension before any WinGet lookup resolves, so the "Installed" badge appears instantly.Once the PR is merged into
CmdPal-Extensions, every running copy of CmdPal picks the new entry up the next time its feed cache expires (within 4 hours) or when the user clicks Refresh.2. What CmdPal does with it
ExtensionGalleryService(inMicrosoft.CmdPal.Common) owns the whole pipeline:https://raw.githubusercontent.com/microsoft/CmdPal-Extensions/refs/heads/main/extensions.json. A hidden setting (GalleryFeedUrl) lets developers point at a custom URL or afile://path for local testing.ExtensionGalleryHttpClient, which wrapsHttpCachingClient— a conditional-GET + on-disk cache layer built onHttpClient(ETag /If-None-Match, 30 s timeout, UAPowerToys-CmdPal/1.0).GallerySerializationContextinto a strongly-typedGalleryRemoteIndex({ "extensions": [ ... ] }). Entries without anidare dropped.iconUrl/screenshotUrlsagainst the feed URL (useful for localfile://feeds).file://URI before the view model binds to it, so the list renders instantly on subsequent loads and works offline.The gallery page itself is built on top of
ExtensionGalleryViewModel, withExtensionGalleryItemViewModelhandling per-entry concerns — install/update/uninstall (via the shared WinGet service), installed-state detection, and joining in-flight install progress so the globalWinGetOperationsButtonin the top bar stays in sync.3. Caching + offline behaviour
The cache lives under
ApplicationData.Current.LocalCacheFolder\GalleryCache\when CmdPal runs packaged, or%LOCALAPPDATA%\Microsoft\PowerToys\Microsoft.CmdPal\Cache\GalleryCache\when unpackaged.extensions.jsonfeedEach fetch returns a
GalleryFetchResultwhose flags drive the UI:FromCache— cache was still fresh, no network call was made.UsedFallbackCache— network failed; the last-known-good cached copy was served instead. The page shows a "showing cached data" info bar.RateLimited— origin returned429and no fallback was available. The page shows a rate-limit error.RefreshAsync(wired up to the gallery's refresh button) forces a fresh conditional GET, then prunes any cached files that the new feed no longer references.4. WinGet install flow
installSources[type=winget].idis handed to the shared WinGet service for install/update/uninstall.WinGetOperationsButtonin the top bar with per-operation progress.detection.packageFamilyNameis consulted first so that the gallery can show "Installed" / "Update available" without waiting on WinGet metadata.Top-level command cleanup
PR Checklist
New projects / areas
ExtensionGalleryService(fetch + cache), HTTP caching layer (HttpCachingClient,FileSystemHttpResourceCacheStore), WinGet service abstractions and implementationsExtensionGalleryViewModel,ExtensionGalleryItemViewModel, WinGet operation view models, gallery sort optionsExtensionGalleryPage.xaml,ExtensionGalleryItemPage.xaml,IconCarouselControl,WinGetOperationsButton, service registrationsdoc/devdocs/modules/cmdpal/extension-gallery/extension-gallery.md— dev reference for the runtime, caching, and feed shapeValidation Steps Performed