Skip to content

[Bug]: Visual asset fields warn "Image not found in package directory" for MRT-qualified assets (.scale-200, .targetsize-24_altform-unplated, etc.) #191

Description

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

  1. Create or open any default WinUI 3 / Windows App SDK project (the standard template ships only qualifier-suffixed assets).
  2. Open Package.appxmanifest in the WinApp manifest editor.
  3. Go to the Applications tab and expand the visual elements for an application.
  4. 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-200Assets.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 testssrc/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:

  1. 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.
  2. 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.

Metadata

Metadata

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions