Describe the bug
Every visual asset field on the Applications tab (and Properties > Logo) shows ⚠ Image not found in package directory for a standard WinUI/Windows App SDK project, and the logo thumbnail preview stays blank.
The cause is that the designer resolves the manifest's image path literally, but an MSIX manifest is expected to reference the unqualified asset name. MRT resolves it at runtime against the qualifier-suffixed files that actually ship in the package.
So a manifest that correctly says:
<uap:VisualElements
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png" ... />
is backed on disk by:
Assets\Square150x150Logo.scale-200.png
Assets\Square44x44Logo.scale-200.png
Assets\Square44x44Logo.targetsize-24_altform-unplated.png
...
Assets\Square150x150Logo.png genuinely does not exist — and that is correct, expected MRT authoring. The designer flags it as broken anyway.
Where it happens
checkImagePath in src/manifest-editor/manifest-editor-provider.ts (~lines 250–313) is a plain fs.existsSync(path.resolve(manifestDir, imgPath)) chain. For Assets\Square150x150Logo.png it falls through every branch (inside package dir but missing → not external → not absolute → no .. workspace fallback) and posts status: 'notFound', which webview-script.ts:621 renders as ⚠ Image not found in package directory.
Every .form-group[data-field*="visualElements."] field is run through this (webview-script-applications.ts:551–558), so the warning appears on all of them at once.
The same literal-path assumption breaks the preview: updateLogoPreview (webview-script.ts:651) sets img.src to the unqualified path, which 404s, so no thumbnail renders.
Note that manifest-validator.ts is qualifier-aware for extension checking (hasUnsupportedImageExtension explicitly allows .scale-, .targetsize-, .contrast-, .altform- style patterns, lines 56–66) — the existence check simply never got the same treatment. There's an inconsistency inside the extension too: the Regenerate Assets button shells out to winapp manifest update-assets (manifest-editor-provider.ts:392–420), which generates the qualified asset set, and the editor then immediately warns that all of those paths are missing.
To Reproduce
- Create or open any default WinUI 3 / Windows App SDK project (the standard template ships only qualifier-suffixed assets).
- Open
Package.appxmanifest in the WinApp manifest editor.
- Go to the Applications tab and expand the visual elements for an application.
- Every logo field shows
⚠ Image not found in package directory, and the logo preview is blank — even though the assets are present and the manifest is correct.
Expected behavior
The existence check should be MRT-aware rather than literal:
- Given
Assets\Foo.png, also probe qualifier variants in the same folder — Foo.scale-*.png, Foo.targetsize-*.png, Foo.altform-*.png, Foo.contrast-*.png, Foo.theme-*.png, and combinations like Foo.targetsize-24_altform-unplated.png — plus qualifier folder layouts (Assets\scale-200\Foo.png).
- If any variant resolves, treat the path as found: no warning, and the preview should fall back to the best-matching variant (e.g. the highest scale, or scale-200) so the thumbnail actually renders.
- Only warn when neither the literal file nor any qualifier variant exists.
- If a warning is still desired for the "only qualified variants exist" case, it should be informational and accurately worded — something like "Resolved via MRT to
Foo.scale-200.png" or "No unqualified Foo.png; MRT will resolve a qualified variant at runtime" — rather than the current "not found in package directory", which reads as an authoring error when the authoring is in fact correct.
Aspect-ratio checking (checkAspectRatio) should measure whichever variant was resolved, ideally normalizing for scale.
Screenshots
N/A
OS Version and details
Windows 11
Existing logic to leverage: MrtAssetHelper in the WinApp CLI
This wheel does not need reinventing — the WinApp CLI (microsoft/winappCli) already implements exactly this resolution, and the extension already ships and shells out to that CLI.
src/winapp-CLI/WinApp.Cli/Services/MrtAssetHelper.cs
The relevant API surface:
| Member |
What it does |
IsSingleQualifierToken(token) |
Validates one qualifier token. Covers scale-N, targetsize-N, altform-*, theme-light|dark, contrast-standard|high, dxfeaturelevel-9|10|11, device-family-*, homeregion-XX, configuration-*, language tags (en-US, zh-Hans, pt-BR), and ltr/rtl. |
IsQualifierToken(token) |
Handles compound _-joined qualifiers, e.g. targetsize-24_altform-unplated. |
IsMrtVariantName(logicalBaseName, candidate) |
True if Square44x44Logo.targetsize-24_altform-unplated is a valid variant of Square44x44Logo. Correctly rejects near-misses like Logo.backup and LogoExtra. |
GetMrtVariantBaseName(name) |
Strips trailing qualifiers to get the family base, while preserving non-qualifier dots (Assets.Logo.scale-200 → Assets.Logo). |
ExpandManifestReferencedFiles(manifestDir, referencedFiles, ...) |
The core routine. Given manifest-relative logical paths, enumerates <base>*<ext> in the sibling directory, keeps only true MRT variants, falls back to the literal file when no variants exist, and only reports "Referenced file not found (no MRT variants)" when neither the literal file nor any variant is present. |
That last method's semantics are precisely the behavior requested above. Compare its fallback chain with the extension's checkImagePath:
// MrtAssetHelper.ExpandManifestReferencedFiles
if (!anyIncludedForLogical && logicalSourceFile.Exists && ...)
{
expandedFilesByRelativePath[relativeFilePath] = logicalSourceFile;
}
else if (!anyIncludedForLogical && !logicalSourceFile.Exists)
{
taskContext?.AddDebugMessage($"{UiSymbols.Warning} Referenced file not found (no MRT variants): {logicalSourceFile}");
}
The CLI warns only after variant expansion finds nothing. The extension warns before ever attempting expansion.
It is already well covered by tests — src/winapp-CLI/WinApp.Cli.Tests/MrtAssetHelperTests.cs has ~40 cases, including ones that describe this exact scenario:
ExpandManifestReferencedFiles_FindsMrtVariants
ExpandManifestReferencedFiles_FallsBackToExactFile_WhenNoVariants
ExpandManifestReferencedFiles_ExcludesNonVariantFiles (Logo.backup.png must not count)
ExpandManifestReferencedFiles_HandlesSubdirectories (Assets\Logo.png)
ExpandManifestReferencedFiles_IncludesUnplatedVariants (Square44x44Logo.targetsize-24_altform-unplated.png)
ExpandManifestReferencedFiles_IncludesLightUnplatedVariants
These double as a ready-made test matrix to port alongside the implementation.
Also relevant: ManifestService.ExtractAssetReferencesFromManifest
src/winapp-CLI/WinApp.Cli/Services/ManifestService.cs enumerates the manifest's image attributes and carries each asset type's expected base dimensions (Square44x44Logo → 44×44, Square150x150Logo → 150×150, Wide310x150Logo → 310×150, StoreLogo/Logo → 50×50, SplashScreen → 620×300, BadgeLogo/LockScreenLogo → 24×24, plus the newer AppList/SmallTile/MedTile/WideTile/LargeTile naming).
That table is directly applicable to checkAspectRatio in manifest-editor-provider.ts:507 — and it's the piece needed to size-check a resolved variant correctly, since a scale-200 file is legitimately 2× the base dimensions and shouldn't be flagged.
Note on the "generic file" wording
MrtAssetHelper treats the unqualified file as optional, not canonical — it's just one more candidate in the family, used as a fallback. So the fix shouldn't tell users to "point it to the generic version"; per MRT the unqualified reference in the manifest is already correct authoring, and the CLI's own msix/Assets/ folder ships StoreLogo.scale-100/125/150/200/400.png with no unqualified StoreLogo.png. Any message should reflect that this is valid, not something to fix.
Suggested approach
There's currently no CLI command that surfaces this (winapp manifest only exposes add-alias, generate, and update-assets), so either:
- Port
MrtAssetHelper to TypeScript in the extension — it's self-contained, pure path/regex logic with no external dependencies, and the C# test cases port directly. Lowest-risk option and keeps the editor responsive with no process spawn per field.
- Expose it via the CLI (e.g. a
winapp manifest resolve-assets --json command) and have checkImagePath call it, keeping a single source of truth for qualifier parsing across both repos.
Whichever route is taken, the qualifier regex set and the variant-matching rules should stay in sync with the CLI's, since the two tools are describing the same MRT behavior and disagreeing would be worse than the current bug.
Describe the bug
Every visual asset field on the Applications tab (and
Properties > Logo) shows⚠ Image not found in package directoryfor a standard WinUI/Windows App SDK project, and the logo thumbnail preview stays blank.The cause is that the designer resolves the manifest's image path literally, but an MSIX manifest is expected to reference the unqualified asset name. MRT resolves it at runtime against the qualifier-suffixed files that actually ship in the package.
So a manifest that correctly says:
is backed on disk by:
Assets\Square150x150Logo.pnggenuinely does not exist — and that is correct, expected MRT authoring. The designer flags it as broken anyway.Where it happens
checkImagePathinsrc/manifest-editor/manifest-editor-provider.ts(~lines 250–313) is a plainfs.existsSync(path.resolve(manifestDir, imgPath))chain. ForAssets\Square150x150Logo.pngit falls through every branch (inside package dir but missing → not external → not absolute → no..workspace fallback) and postsstatus: 'notFound', whichwebview-script.ts:621renders as⚠ Image not found in package directory.Every
.form-group[data-field*="visualElements."]field is run through this (webview-script-applications.ts:551–558), so the warning appears on all of them at once.The same literal-path assumption breaks the preview:
updateLogoPreview(webview-script.ts:651) setsimg.srcto the unqualified path, which 404s, so no thumbnail renders.Note that
manifest-validator.tsis qualifier-aware for extension checking (hasUnsupportedImageExtensionexplicitly allows.scale-,.targetsize-,.contrast-,.altform-style patterns, lines 56–66) — the existence check simply never got the same treatment. There's an inconsistency inside the extension too: the Regenerate Assets button shells out towinapp manifest update-assets(manifest-editor-provider.ts:392–420), which generates the qualified asset set, and the editor then immediately warns that all of those paths are missing.To Reproduce
Package.appxmanifestin the WinApp manifest editor.⚠ Image not found in package directory, and the logo preview is blank — even though the assets are present and the manifest is correct.Expected behavior
The existence check should be MRT-aware rather than literal:
Assets\Foo.png, also probe qualifier variants in the same folder —Foo.scale-*.png,Foo.targetsize-*.png,Foo.altform-*.png,Foo.contrast-*.png,Foo.theme-*.png, and combinations likeFoo.targetsize-24_altform-unplated.png— plus qualifier folder layouts (Assets\scale-200\Foo.png).Foo.scale-200.png" or "No unqualifiedFoo.png; MRT will resolve a qualified variant at runtime" — rather than the current "not found in package directory", which reads as an authoring error when the authoring is in fact correct.Aspect-ratio checking (
checkAspectRatio) should measure whichever variant was resolved, ideally normalizing for scale.Screenshots
N/A
OS Version and details
Windows 11
Existing logic to leverage:
MrtAssetHelperin the WinApp CLIThis wheel does not need reinventing — the WinApp CLI (
microsoft/winappCli) already implements exactly this resolution, and the extension already ships and shells out to that CLI.src/winapp-CLI/WinApp.Cli/Services/MrtAssetHelper.csThe relevant API surface:
IsSingleQualifierToken(token)scale-N,targetsize-N,altform-*,theme-light|dark,contrast-standard|high,dxfeaturelevel-9|10|11,device-family-*,homeregion-XX,configuration-*, language tags (en-US,zh-Hans,pt-BR), andltr/rtl.IsQualifierToken(token)_-joined qualifiers, e.g.targetsize-24_altform-unplated.IsMrtVariantName(logicalBaseName, candidate)Square44x44Logo.targetsize-24_altform-unplatedis a valid variant ofSquare44x44Logo. Correctly rejects near-misses likeLogo.backupandLogoExtra.GetMrtVariantBaseName(name)Assets.Logo.scale-200→Assets.Logo).ExpandManifestReferencedFiles(manifestDir, referencedFiles, ...)<base>*<ext>in the sibling directory, keeps only true MRT variants, falls back to the literal file when no variants exist, and only reports "Referenced file not found (no MRT variants)" when neither the literal file nor any variant is present.That last method's semantics are precisely the behavior requested above. Compare its fallback chain with the extension's
checkImagePath:The CLI warns only after variant expansion finds nothing. The extension warns before ever attempting expansion.
It is already well covered by tests —
src/winapp-CLI/WinApp.Cli.Tests/MrtAssetHelperTests.cshas ~40 cases, including ones that describe this exact scenario:ExpandManifestReferencedFiles_FindsMrtVariantsExpandManifestReferencedFiles_FallsBackToExactFile_WhenNoVariantsExpandManifestReferencedFiles_ExcludesNonVariantFiles(Logo.backup.pngmust not count)ExpandManifestReferencedFiles_HandlesSubdirectories(Assets\Logo.png)ExpandManifestReferencedFiles_IncludesUnplatedVariants(Square44x44Logo.targetsize-24_altform-unplated.png)ExpandManifestReferencedFiles_IncludesLightUnplatedVariantsThese double as a ready-made test matrix to port alongside the implementation.
Also relevant:
ManifestService.ExtractAssetReferencesFromManifestsrc/winapp-CLI/WinApp.Cli/Services/ManifestService.csenumerates the manifest's image attributes and carries each asset type's expected base dimensions (Square44x44Logo→ 44×44,Square150x150Logo→ 150×150,Wide310x150Logo→ 310×150,StoreLogo/Logo→ 50×50,SplashScreen→ 620×300,BadgeLogo/LockScreenLogo→ 24×24, plus the newerAppList/SmallTile/MedTile/WideTile/LargeTilenaming).That table is directly applicable to
checkAspectRatioinmanifest-editor-provider.ts:507— and it's the piece needed to size-check a resolved variant correctly, since ascale-200file is legitimately 2× the base dimensions and shouldn't be flagged.Note on the "generic file" wording
MrtAssetHelpertreats the unqualified file as optional, not canonical — it's just one more candidate in the family, used as a fallback. So the fix shouldn't tell users to "point it to the generic version"; per MRT the unqualified reference in the manifest is already correct authoring, and the CLI's ownmsix/Assets/folder shipsStoreLogo.scale-100/125/150/200/400.pngwith no unqualifiedStoreLogo.png. Any message should reflect that this is valid, not something to fix.Suggested approach
There's currently no CLI command that surfaces this (
winapp manifestonly exposesadd-alias,generate, andupdate-assets), so either:MrtAssetHelperto TypeScript in the extension — it's self-contained, pure path/regex logic with no external dependencies, and the C# test cases port directly. Lowest-risk option and keeps the editor responsive with no process spawn per field.winapp manifest resolve-assets --jsoncommand) and havecheckImagePathcall it, keeping a single source of truth for qualifier parsing across both repos.Whichever route is taken, the qualifier regex set and the variant-matching rules should stay in sync with the CLI's, since the two tools are describing the same MRT behavior and disagreeing would be worse than the current bug.