@@ -272,6 +272,8 @@ public async Task Landing_Page_Carries_The_House_Style_Surfaces()
272272 twitterCard: document.querySelector('meta[name=""twitter:card""]')?.getAttribute('content') ?? null,
273273 twitterImage: document.querySelector('meta[name=""twitter:image""]')?.getAttribute('content') ?? null,
274274 heroLogoSrc: document.querySelector('.VPNavBarTitle img.logo')?.getAttribute('src') ?? null,
275+ heroImageSrc: (document.querySelector('.VPHero .VPImage')
276+ ?? document.querySelector('.VPHero img[src*=""logo""]'))?.getAttribute('src') ?? null,
275277 downloadCtaHref: (() => {
276278 const link = Array.from(document.querySelectorAll('.VPHero a, a'))
277279 .find(a => /Download latest release/i.test(a.textContent ?? ''));
@@ -314,6 +316,17 @@ public async Task Landing_Page_Carries_The_House_Style_Surfaces()
314316 Assert . That ( probes . HeroLogoSrc , Does . EndWith ( "/logo.png" ) ,
315317 $ "nav logo src does not point at /logo.png — got '{ probes . HeroLogoSrc } '") ;
316318
319+ // Hero image — pinned per §1.0d-trigies-semel (the maintainer-supplied
320+ // logo must render in the landing hero block, not just the nav bar).
321+ // VitePress's default home layout renders the hero image as
322+ // .VPHero .VPImage when `hero.image.src` is set in index.md;
323+ // the fallback `.VPHero img[src*='logo']` covers theme-overridden
324+ // cases where a custom hero component is in play.
325+ Assert . That ( probes . HeroImageSrc , Is . Not . Null . And . Not . Empty ,
326+ "no image rendered inside .VPHero — hero.image did not take effect" ) ;
327+ Assert . That ( probes . HeroImageSrc , Does . EndWith ( "/logo.png" ) ,
328+ $ "hero image src does not point at /logo.png — got '{ probes . HeroImageSrc } '") ;
329+
317330 // Hero 'Download latest release' CTA — text + canonical link.
318331 Assert . That ( probes . DownloadCtaHref , Is . EqualTo (
319332 "https://github.com/TrakHound/MTConnect.NET/releases/latest" ) ,
@@ -370,10 +383,51 @@ private sealed class HouseStyleProbes
370383 [ JsonPropertyName ( "heroLogoSrc" ) ]
371384 public string ? HeroLogoSrc { get ; set ; }
372385
386+ [ JsonPropertyName ( "heroImageSrc" ) ]
387+ public string ? HeroImageSrc { get ; set ; }
388+
373389 [ JsonPropertyName ( "downloadCtaHref" ) ]
374390 public string ? DownloadCtaHref { get ; set ; }
375391 }
376392
393+ /// <summary>
394+ /// Negative companion to <see cref="Landing_Page_Carries_The_House_Style_Surfaces"/>
395+ /// per §10a: the positive test only proves the hero image
396+ /// <c>src</c> attribute is wired through to <c>/logo.png</c>, not
397+ /// that the asset is actually reachable. A future asset-pipeline
398+ /// regression (renamed file, missed copy step, accidental
399+ /// <c>.gitignore</c> entry) would pass the meta-/DOM-attribute
400+ /// check above but break the hero image for end users. Fetching
401+ /// the asset over HTTP and asserting <c>200 + image/png</c> closes
402+ /// that gap.
403+ /// </summary>
404+ [ Test ]
405+ public async Task Landing_Hero_Image_Asset_Resolves ( )
406+ {
407+ Assert . That ( _browser , Is . Not . Null , "browser was not initialised" ) ;
408+
409+ var context = await _browser ! . NewContextAsync ( ) ;
410+ try
411+ {
412+ // Reuse the Playwright APIRequest plumbing the favicon check
413+ // in the positive test already exercises — keeps the
414+ // negative test on the same HTTP transport as everything
415+ // else in this fixture (no parallel HttpClient surface to
416+ // keep in sync with the preview-server lifecycle).
417+ var assetUrl = _baseUrl + "/logo.png" ;
418+ var response = await context . APIRequest . GetAsync ( assetUrl ) ;
419+ Assert . That ( response . Status , Is . EqualTo ( 200 ) ,
420+ $ "the hero image asset at { assetUrl } returned HTTP { response . Status } ") ;
421+ var contentType = response . Headers . TryGetValue ( "content-type" , out var ct ) ? ct : null ;
422+ Assert . That ( contentType , Is . Not . Null . And . StartsWith ( "image/png" ) ,
423+ $ "the hero image asset content-type is '{ contentType } ', expected 'image/png'") ;
424+ }
425+ finally
426+ {
427+ await context . CloseAsync ( ) ;
428+ }
429+ }
430+
377431 /// <summary>
378432 /// Walks every markdown-backed route the docs/ tree implies and
379433 /// asserts none rendered a 404. Failures are collected across all
0 commit comments