refactor(books): replace cover loading placeholder with simple skeleton

Simplify the loading state for book covers by removing the complex BookCoverPlaceholder in favor of a neutral pulsing skeleton. This ensures a cleaner visual experience while the actual cover image is being loaded.
This commit is contained in:
neon
2026-06-12 05:20:38 +00:00
parent b1dff917ad
commit 280aad0712
2 changed files with 23 additions and 7 deletions
@@ -132,14 +132,11 @@ function handleImageError() {
<template v-if="canRenderImage">
<span
:class="[
'absolute inset-0 z-0 transition-opacity duration-200 ease-out',
'absolute inset-0 z-0 animate-pulse bg-foreground/5 transition-opacity duration-200 ease-out',
loaded ? 'opacity-0 pointer-events-none' : 'opacity-100',
effectiveMode === 'natural-bottom' ? 'book-cover-artwork-placeholder-natural overflow-hidden rounded-[inherit]' : '',
]"
aria-hidden="true"
>
<BookCoverPlaceholder :title="title" :author-line="authorLine" :is-audio="isAudio" :seed="seed" />
</span>
/>
<img
v-if="showBlurredBackdrop"
:src="src ?? ''"
@@ -169,7 +166,6 @@ function handleImageError() {
/>
<span v-if="showSpineLayer" class="book-cover-spine-layer absolute inset-0 z-[3]" :style="spineStyle" />
</span>
<span v-if="!loaded" class="absolute inset-0 z-[2] animate-pulse bg-foreground/5" />
</template>
<span
v-else
@@ -138,7 +138,27 @@ describe('BookCoverArtwork', () => {
await wrapper.setProps({ resetKey: 'retry-1' })
expect(wrapper.find('img[alt="Dune cover"]').exists()).toBe(true)
expect(wrapper.find('[data-testid="placeholder"]').exists()).toBe(true)
expect(wrapper.find('[data-testid="placeholder"]').exists()).toBe(false)
})
it('shows a neutral skeleton, not the placeholder, while a real cover is loading', () => {
const wrapper = mountArtwork({ hasCover: true })
expect(wrapper.find('[data-testid="placeholder"]').exists()).toBe(false)
expect(wrapper.find('img[alt="Dune cover"]').exists()).toBe(true)
const skeleton = wrapper.find('.animate-pulse')
expect(skeleton.exists()).toBe(true)
expect(skeleton.classes()).toContain('opacity-100')
})
it('hides the skeleton once the cover has loaded and never shows a placeholder', async () => {
const wrapper = mountArtwork({ hasCover: true })
await triggerMainImageLoad(wrapper, 600, 900)
expect(wrapper.find('[data-testid="placeholder"]').exists()).toBe(false)
const skeleton = wrapper.find('.animate-pulse')
expect(skeleton.classes()).toContain('opacity-0')
})
it('does not render the fitted spine layer when spine is disabled', async () => {