forked from akai/readest
The background texture (mounted on the reader container as `.foliate-viewer::before`) showed in scrolled mode but was absent in paginated mode: the paginator painted an opaque #background container over it. Bump foliate-js to leave that container transparent under a texture, and add a regression test for the shared textureAwareBackground helper. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
- [TOC expand + auto-scroll](toc-expand-and-autoscroll.md) — #4059 collapse-by-default policy in `tocTree.ts`; pinned-sidebar mounts before progress → dynamic expansion breaks scroll-to-current via (1) spurious onScroll clearing pending and (2) Virtuoso scrollToIndex landing short after row growth (re-assert on rAF)
|
||||
- [Swipe page-turn bg flash](paginator-swipe-bg-flash.md) — white↔black flash on swipe+animation only; `#background` was static screen-space and didn't track content during drag/snap; fix = sliding per-view full-bleed segments (`computeBackgroundSegments`) rebuilt on scroll + per-rAF synced to the view transform during snap
|
||||
- [Duokan fullscreen cover hidden in scroll mode](duokan-fullscreen-cover-scroll.md) — #4379 `data-duokan-page-fullscreen` cover pinned `position:absolute height:100%` collapses against auto-height scroll container; gate fullscreen on `this.#column` + reset stale absolute props on toggle (`setImageSize` in paginator.js)
|
||||
- [Paginated texture occlusion](paginated-texture-occlusion-4399.md) — #4399 host `.foliate-viewer::before` texture absent in paginated (shown in scrolled); opaque `#background` container (`= fallbackBg`) from the swipe-flash fix occludes it; shared `textureAwareBackground` helper + `hasTexture ? '' : fallbackBg` container
|
||||
|
||||
## Critical Files (Most Bug-Prone)
|
||||
- `src/utils/style.ts` - Central EPUB CSS transformation hub (14+ bug fixes)
|
||||
@@ -26,6 +27,7 @@
|
||||
## Sync Notes
|
||||
- [KOSync CFI spine resolution](kosync-cfi-spine-resolution.md) — convert via the CFI's own spine (`getXPointerFromCFI`/`getCFIFromXPointer`), never `new XCFI(primaryDoc, primaryIndex)`; primaryIndex lags during scroll → spine-mismatch throw
|
||||
- [Empty-start CFI sync bug](empty-start-cfi-sync.md) — `epubcfi(/6/24!/4,,/20/1:58)` (empty-start range) from the cfi-inert skip-link transitional window; jumps to wrong section end; `isMalformedLocationCfi` → discard the synced value in `useProgressSync` (NOT the local open path); foliate fix doesn't repair already-synced values
|
||||
- [Custom fonts disappear on cloud sync (#4410)](custom-fonts-reincarnation-4410.md) — CRDT remove-wins: re-import-after-delete needs a `reincarnation` token or the pull re-applies the tombstone; `addFont`/`addTexture` minted none; fix mirrors dictionary (both cases) + OPDS token style; coverage matrix per kind
|
||||
|
||||
## Testing
|
||||
- [Tauri Rust↔JS parser parity tests](tauri-parser-parity-tests.md) — #4369 native Rust EPUB/MOBI parser; how to cross-check vs foliate-js in the `.tauri.test.ts` WebView suite (CWD disk path for Rust, Vite URL for JS, normalizer-based compare, cover presence-only, desc whitespace-collapse); the `dcterms:modified`→`published` divergence fix
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
name: custom-fonts-reincarnation-4410
|
||||
description: Custom fonts/textures disappear when logged into cloud sync after re-import-after-delete; CRDT remove-wins needs a reincarnation token
|
||||
metadata:
|
||||
node_type: memory
|
||||
type: project
|
||||
originSessionId: 2b61b392-4d32-4516-84bd-f362bba22378
|
||||
---
|
||||
|
||||
# #4410 — disappearing custom fonts when logged into cloud
|
||||
|
||||
**Symptom:** custom fonts vanish a few seconds after opening a book (or ~1 min idle) ONLY when logged into cloud sync; logging out fixes it; a brand-new never-deleted font is fine; problem starts after deleting a font / "Clear Custom Fonts" then re-uploading the same file.
|
||||
|
||||
**Root cause — CRDT remove-wins.** The replica sync (`src/libs/crdt.ts`, `src/libs/replicaInterpret.ts`) is remove-wins: once a row has a `deleted_at_ts` tombstone, a plain field upsert does NOT revive it. Only a `reincarnation` token whose effective HLC beats the tombstone revives it. `isReplicaRowAlive(row)` = `!deleted_at_ts || (reincarnation && updated_at_ts >= deleted_at_ts)`. In `mergeReplica` the reincarnation candidate's timestamp is the **row's `updated_at_ts`** (fresh on every upsert), NOT the token's mint time — so preserving an old token still revives, as long as the upsert carries it.
|
||||
|
||||
Flow that broke: import→delete writes a server tombstone (`publishReplicaDelete`). Re-upload same file → same `contentId` → `addFont` cleared `deletedAt` locally and called `publishFontUpsert` with `reincarnation = undefined` → server tombstone survives → next pull (boot 5s / periodic / book-open / visibility) sees `isReplicaRowAlive===false` → `softDeleteByContentId` → font disappears.
|
||||
|
||||
**Fix (PR for #4410):** in `addFont` (`src/store/customFontStore.ts`) and `addTexture` (`src/store/customTextureStore.ts`), when re-adding an existing entry, mint a reincarnation token (`Math.random().toString(36).slice(2)`, matching OPDS) when `!!contentId && !existing.reincarnation && (existing.deletedAt || existing.contentId === new.contentId)`; otherwise preserve `existing.reincarnation`. Covers both re-import-after-local-delete AND the stale-local race (local still live but another device tombstoned the row). Token is inert without a tombstone, so live re-imports are safe.
|
||||
|
||||
**Coverage matrix across collection kinds (all share the remove-wins replica):**
|
||||
- Dictionary — handles BOTH cases (gold standard): `dictionaryService.ts importDictionaries` via `findTombstonedDictionaryMatches` + `shouldMintReincarnationForLiveReimport` (helpers in `dictionaries/dictionaryDedup.ts`), mints `uuidv4()`.
|
||||
- OPDS — case 1 only: `customOPDSStore.addCatalog` (`existing?.deletedAt && !input.reincarnation`).
|
||||
- Fonts / Textures — handled NEITHER → this bug. Now fixed to dictionary-parity.
|
||||
|
||||
Whole chain carries the token: returned font → `publishFontUpsert` upsert AND `queueReplicaBinaryUpload` → manifest publish (`replicaBinaryUpload.ts` uses `record.reincarnation`).
|
||||
|
||||
Note: `saveCustomFonts`/`saveCustomTextures` persist tombstoned (deletedAt) entries too, so the soft-deleted entry is still in the store at re-import time → the `existing.deletedAt` branch fires. (OPDS strips deleted at save; fonts/textures keep them.)
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
name: paginated-texture-occlusion-4399
|
||||
description: Background texture absent in paginated mode (shown in scrolled) — opaque
|
||||
metadata:
|
||||
node_type: memory
|
||||
type: project
|
||||
originSessionId: 1bd8a73e-f279-4ed8-9562-98e96d9723d5
|
||||
---
|
||||
|
||||
#4399: a background texture (Settings → Color → texture, e.g. Leaves) shows in
|
||||
**scrolled** mode but is **absent in paginated** mode. The texture is NOT in the
|
||||
iframe — it's mounted on the HOST as `.foliate-viewer::before` (`src/styles/textures.ts`,
|
||||
`mountBackgroundTexture`): `position:absolute; inset:0; z-index:0; mix-blend-mode:multiply;
|
||||
opacity:.6` over the reader container. For it to show, the whole foliate view tree
|
||||
(iframe page bg + the paginator `#background` layer) must be transparent so the host
|
||||
`::before` composites over the white page fill that a **parent** element provides
|
||||
(`oklch(1 0 0)` on the reader grid cell, NOT on `.foliate-viewer`, which is transparent).
|
||||
|
||||
**Root cause.** foliate-js `paginator.js` `#replaceBackground`. Scrolled mode already
|
||||
left things transparent under a texture (`#background.style.background = hasTexture ? '' : fallbackBg`
|
||||
+ blanking transparent view elements). The **paginated** branch hard-set
|
||||
`this.#background.style.background = fallbackBg` (opaque theme color, e.g. white) on the
|
||||
segment **container** — that opaque container, a descendant of `.foliate-viewer`, paints
|
||||
over the host `::before` texture. The per-view *segments* were already transparent for
|
||||
transparent pages (`rgba(0,0,0,0)`); only the container was the occluder. Verified live
|
||||
via Chrome MCP: `#background` inline bg was `rgb(255,255,255)`; setting it `''` revealed
|
||||
the leaves texture instantly.
|
||||
|
||||
**Regression** = commit `167757a` "Fix background flash when swiping between
|
||||
differently-colored pages" (2026-05-31, see [[paginator-swipe-bg-flash]]). The OLD
|
||||
paginated path was a CSS `grid` of per-column divs and never set the `#background`
|
||||
container background (kept the `''` reset), so a transparent page let the texture through.
|
||||
167757a swapped to `computeBackgroundSegments` and added the opaque container line.
|
||||
|
||||
**Fix.** Extract a shared exported pure helper `textureAwareBackground(resolved, hasTexture)`
|
||||
→ returns `''` when `hasTexture && isTransparent(resolved)`, else `resolved`. Use it for
|
||||
BOTH scrolled view elements and paginated segment bgs, and set the paginated container
|
||||
`this.#background.style.background = hasTexture ? '' : fallbackBg` (mirroring scrolled).
|
||||
No-texture path unchanged → swipe-flash fix for colored pages fully preserved; a
|
||||
book-forced opaque page still paints its segment (texture correctly does not show there).
|
||||
|
||||
Test: `src/__tests__/document/paginator-background-segments.test.ts` (new `describe` for
|
||||
the pure helper, alongside `computeBackgroundSegments`). foliate-js is a submodule —
|
||||
commit there + bump the pointer. See [[paginator-swipe-bg-flash]].
|
||||
@@ -13,7 +13,7 @@
|
||||
// See readest/readest swipe background flash.
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { computeBackgroundSegments } from 'foliate-js/paginator.js';
|
||||
import { computeBackgroundSegments, textureAwareBackground } from 'foliate-js/paginator.js';
|
||||
|
||||
describe('computeBackgroundSegments', () => {
|
||||
it('splits the background at the content seam mid-swipe (incoming page keeps its own colour)', () => {
|
||||
@@ -75,3 +75,35 @@ describe('computeBackgroundSegments', () => {
|
||||
expect(segments).toEqual([{ start: 0, size: 430, bg: 'rgb(1, 1, 1)' }]);
|
||||
});
|
||||
});
|
||||
|
||||
// Regression test for readest/readest#4399: a background texture mounted on the
|
||||
// reader container (`.foliate-viewer::before`) shows in scrolled mode but is
|
||||
// absent in paginated mode. The texture is occluded when the paginator paints an
|
||||
// opaque fill over a page whose own background is transparent. Both modes must
|
||||
// drop the fill for transparent pages while a texture is active so the texture
|
||||
// shows through; pages that force their own opaque colour keep painting.
|
||||
describe('textureAwareBackground', () => {
|
||||
it('drops a transparent page background when a texture is active (texture shows through)', () => {
|
||||
expect(textureAwareBackground('rgba(0, 0, 0, 0)', true)).toBe('');
|
||||
expect(textureAwareBackground('transparent', true)).toBe('');
|
||||
expect(textureAwareBackground('', true)).toBe('');
|
||||
// The real value captured from the iframe html computed `background`.
|
||||
expect(
|
||||
textureAwareBackground(
|
||||
'rgba(0, 0, 0, 0) none repeat scroll 0% 0% / auto padding-box border-box',
|
||||
true,
|
||||
),
|
||||
).toBe('');
|
||||
});
|
||||
|
||||
it('keeps an opaque page background even when a texture is active', () => {
|
||||
expect(textureAwareBackground('rgb(0, 0, 0)', true)).toBe('rgb(0, 0, 0)');
|
||||
expect(textureAwareBackground('rgb(255, 255, 255)', true)).toBe('rgb(255, 255, 255)');
|
||||
});
|
||||
|
||||
it('never drops a background when no texture is active (no regression)', () => {
|
||||
expect(textureAwareBackground('rgba(0, 0, 0, 0)', false)).toBe('rgba(0, 0, 0, 0)');
|
||||
expect(textureAwareBackground('rgb(0, 0, 0)', false)).toBe('rgb(0, 0, 0)');
|
||||
expect(textureAwareBackground('', false)).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
+1
-1
Submodule packages/foliate-js updated: 2fab7ecc25...142bf11466
Reference in New Issue
Block a user