fix: reserve detail cover box and async-decode list/shelf covers

- The manga detail desktop overview cover had no reserved aspect-ratio, so
  the meta column reflowed when it decoded. Give .cover aspect-ratio: 2/3 +
  object-fit: cover (mirrors MangaCard).
- Add decoding="async" to the detail hero/overview covers and the list/shelf
  covers (bookmarks, history, continue-reading, collections, tagged rows) so
  decode stays off the main thread — previously only MangaCard had it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-06 21:43:21 +02:00
parent 9cb2f152d3
commit 3622dcc02f
12 changed files with 28 additions and 8 deletions

2
backend/Cargo.lock generated
View File

@@ -1558,7 +1558,7 @@ checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
[[package]] [[package]]
name = "mangalord" name = "mangalord"
version = "0.115.1" version = "0.115.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"argon2", "argon2",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "mangalord" name = "mangalord"
version = "0.115.1" version = "0.115.2"
edition = "2021" edition = "2021"
default-run = "mangalord" default-run = "mangalord"

View File

@@ -1,6 +1,6 @@
{ {
"name": "mangalord-frontend", "name": "mangalord-frontend",
"version": "0.115.1", "version": "0.115.2",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {

View File

@@ -23,6 +23,7 @@
alt="" alt=""
class="cover" class="cover"
loading="lazy" loading="lazy"
decoding="async"
/> />
{:else} {:else}
<div class="cover cover-placeholder"> <div class="cover cover-placeholder">

View File

@@ -26,6 +26,7 @@
alt="" alt=""
class="collage-cover" class="collage-cover"
loading="lazy" loading="lazy"
decoding="async"
/> />
{/each} {/each}
{/if} {/if}

View File

@@ -51,6 +51,7 @@
alt="" alt=""
class="cover" class="cover"
loading="lazy" loading="lazy"
decoding="async"
/> />
{:else} {:else}
<span class="cover cover-placeholder"> <span class="cover cover-placeholder">

View File

@@ -89,6 +89,7 @@
alt="" alt=""
class="cover" class="cover"
loading="lazy" loading="lazy"
decoding="async"
/> />
{:else} {:else}
<div class="cover cover-placeholder"> <div class="cover cover-placeholder">

View File

@@ -29,6 +29,16 @@ describe('HistoryList', () => {
expect(cont.getAttribute('href')).toBe('/manga/m1/chapter/c9'); expect(cont.getAttribute('href')).toBe('/manga/m1/chapter/c9');
}); });
it('renders covers with lazy loading and async decode', () => {
render(HistoryList, {
props: { entries: [entry({ manga_cover_image_path: 'mangas/m1/cover.jpg' })] }
});
const img = document.querySelector('img.cover') as HTMLImageElement;
expect(img).not.toBeNull();
expect(img.getAttribute('loading')).toBe('lazy');
expect(img.getAttribute('decoding')).toBe('async');
});
it('appends the page number to the continue line only past page 1', () => { it('appends the page number to the continue line only past page 1', () => {
// The page suffix is a separate text node from "Continue Chapter N", // The page suffix is a separate text node from "Continue Chapter N",
// so assert against the link's normalized text rather than getByText. // so assert against the link's normalized text rather than getByText.

View File

@@ -27,7 +27,7 @@
<li class="row" data-testid={testid}> <li class="row" data-testid={testid}>
<a {href} class="cover-link" aria-hidden="true" tabindex="-1"> <a {href} class="cover-link" aria-hidden="true" tabindex="-1">
{#if primaryCover} {#if primaryCover}
<img src={fileUrl(primaryCover)} alt="" class="cover" loading="lazy" /> <img src={fileUrl(primaryCover)} alt="" class="cover" loading="lazy" decoding="async" />
{:else} {:else}
<div class="cover cover-placeholder"></div> <div class="cover cover-placeholder"></div>
{/if} {/if}
@@ -46,7 +46,7 @@
{#if item.sample_storage_keys.length > 1} {#if item.sample_storage_keys.length > 1}
<div class="samples"> <div class="samples">
{#each item.sample_storage_keys.slice(1) as key (key)} {#each item.sample_storage_keys.slice(1) as key (key)}
<img src={fileUrl(key)} alt="" class="sample" loading="lazy" /> <img src={fileUrl(key)} alt="" class="sample" loading="lazy" decoding="async" />
{/each} {/each}
</div> </div>
{/if} {/if}

View File

@@ -36,7 +36,7 @@
<li class="row" data-testid={testid}> <li class="row" data-testid={testid}>
<a {href} class="cover-link" aria-hidden="true" tabindex="-1"> <a {href} class="cover-link" aria-hidden="true" tabindex="-1">
{#if primaryCover} {#if primaryCover}
<img src={fileUrl(primaryCover)} alt="" class="cover" loading="lazy" /> <img src={fileUrl(primaryCover)} alt="" class="cover" loading="lazy" decoding="async" />
{:else} {:else}
<div class="cover cover-placeholder"></div> <div class="cover cover-placeholder"></div>
{/if} {/if}
@@ -50,7 +50,7 @@
{#if sampleStrip.length > 0} {#if sampleStrip.length > 0}
<div class="samples"> <div class="samples">
{#each sampleStrip as key (key)} {#each sampleStrip as key (key)}
<img src={fileUrl(key)} alt="" class="sample" loading="lazy" /> <img src={fileUrl(key)} alt="" class="sample" loading="lazy" decoding="async" />
{/each} {/each}
</div> </div>
{/if} {/if}

View File

@@ -35,6 +35,7 @@
alt="" alt=""
class="cover" class="cover"
loading="lazy" loading="lazy"
decoding="async"
/> />
</a> </a>
<div class="meta"> <div class="meta">

View File

@@ -380,6 +380,7 @@
src={fileUrl(manga.cover_image_path)} src={fileUrl(manga.cover_image_path)}
alt="" alt=""
loading="eager" loading="eager"
decoding="async"
/> />
{/if} {/if}
<div class="hero-text"> <div class="hero-text">
@@ -401,6 +402,7 @@
alt="{manga.title} cover" alt="{manga.title} cover"
class="cover" class="cover"
loading="eager" loading="eager"
decoding="async"
data-testid="manga-cover" data-testid="manga-cover"
/> />
{/if} {/if}
@@ -791,7 +793,10 @@
.cover { .cover {
width: 100%; width: 100%;
height: auto; /* Reserve the 2/3 box so the meta column doesn't reflow when the
cover decodes (mirrors MangaCard's cover). */
aspect-ratio: 2 / 3;
object-fit: cover;
border-radius: var(--radius-md); border-radius: var(--radius-md);
background: var(--surface); background: var(--surface);
} }