# Refactor Backlog
## Derived thumbnails for sites without preview assets
Status: completed and accepted for the v3.2.0 release.
The architecture refactor and the final thumbnail policy are complete. Real-site
acceptance passed on 18comic, E-Hentai and 4KHD.
Implementation state:
- The previous 200 ms / three-item fallback through scroll placeholders has
been removed.
- Explicit panel scrolling now arms one 300 ms settled batch containing every
actually visible missing preview, ordered centre-out with concurrency two.
- Thumbnail leases use the shared image lifecycle directly and release after a
bounded 300 px Canvas is cached. Reader navigation reuses/promotes the same
active or cached image task.
- Renewed scrolling, Reader navigation and Reader close release stale work;
visual panel auto-hide lets the already-confirmed finite batch finish.
- Unit tests cover filtering, ordering, concurrency, cancellation and shared
Reader reuse. Local browser smoke verified zero pre-settle work, ten visible
loading states, peak thumbnail concurrency two, full batch completion after
auto-hide, and no duplicate load after selecting a preloaded item.
18comic does not expose cheap thumbnail assets. The panel currently shows a
numbered placeholder until the full image has been loaded through another
consumer. Do not restore eager loading for every visible thumbnail because each
preview would require a full download, anti-scramble decode and Blob allocation.
Target policy:
- Keep URL and Sprite preview loading unchanged. The shared fallback applies
automatically only to `none` and `derived` preview descriptors, without a
site-name branch.
- Do not start full-image preview work when Reader opens, the panel appears or
the panel is positioned programmatically. Only explicit user panel scrolling
can arm fallback preloading.
- After scrolling remains stable for 300 ms, snapshot every actually visible
item (excluding the virtualized buffer) and mark the whole missing-preview
batch as loading.
- Queue the complete visible batch from the viewport centre outwards. Start at
most two thumbnail jobs concurrently; completed items may render
progressively instead of waiting for the slowest item.
- Acquire full images directly from the shared image lifecycle with thumbnail
intent. Do not materialize a scroll placeholder solely to obtain a preview.
- Reuse the same active/cached resolve, download, descramble and Blob when the
Reader, scroll view or prefetch consumer requests that item.
- Cache the downscaled Canvas near 300 px in the existing bounded thumbnail
LRU, then release the full-image thumbnail lease.
- On renewed scrolling, Reader navigation or Reader close, discard queued work
and release stale thumbnail-only leases. Visual panel auto-hide does not
cancel an already-confirmed finite batch. Shared work continues when another
consumer still owns it.
Acceptance checks:
- Opening or programmatically centering the panel starts no fallback request.
- Scrubbing the panel does not create an unbounded download/decode queue.
- A settled panel schedules every actually visible missing preview, but never
a virtualized buffer item.
- No more than two thumbnail jobs are active at once, ordered centre-out.
- Renewed scrolling, Reader navigation and Reader close release stale
thumbnail-only leases; visual auto-hide may finish the confirmed batch.
- Selecting an item never starts a duplicate resolve/decode operation.
- E-Hentai and 4KHD thumbnail behavior remains unchanged.
Real-site acceptance result:
- 18comic passed fallback preview loading and descrambling with the shared image
lifecycle.
- E-Hentai retained its native URL/Sprite thumbnail behavior; no post-settle
fallback burst is expected because native previews load through the virtualized
panel and may be satisfied from browser cache.
- 4KHD retained its native `w300-h300-rw` preview path and does not download the
`w2500-h2500-rw` Reader image solely for a thumbnail.
- Desktop and mobile functional testing found no blocking regression. The
v3.2.0 thumbnail release gate is closed.
## Scroll position drift after closing the reader on an unloaded image
Status: resolved and accepted. Stage 8 is complete.
Observed behavior from real-site testing:
- Open the reader from scroll mode and navigate to an image that has not yet
loaded in the underlying scroll page.
- Close the reader. The page returns to the corresponding image, but its
viewport position is offset.
- After that image has loaded successfully, opening and closing the reader on
the same image restores the position correctly.
- Other tested reader behavior remains functional.
Working hypothesis (not yet proven):
- The current close path measures and centers the scroll element after two
animation frames.
- An unloaded item may still be a placeholder, may be replaced by an `
`,
or may change height after its real dimensions become available.
- The restoration therefore uses geometry that can become stale immediately
after `scrollTo()`. Already-loaded images have stable geometry and do not
reproduce the drift.
Target fix:
- Record a stable Gallery item key and its relative viewport offset when the
reader opens or changes the intended return target.
- Resolve the live element again by item key when closing; do not retain a DOM
reference that may have been replaced.
- If the target is still unloaded, preserve a stable aspect-ratio placeholder
or perform one bounded correction after the real image dimensions settle.
- Restore with the element anchor plus relative offset instead of always
forcing the target to the viewport center.
- Keep restoration independent of duplicate image loads and avoid a persistent
resize/scroll feedback loop.
Acceptance checks:
- Closing on an unloaded image and on an already-loaded image produces the same
stable target position.
- Placeholder-to-image replacement does not move the target out of position.
- Opening and closing without reader navigation returns to the original
relative viewport offset.
- Navigating in the reader returns to the selected image with a deterministic
offset near the viewport reading position.
- The fix works when a previous Gallery page was prepended while the reader was
open and at document top/bottom boundaries.
Implementation note:
- Reader close now resolves the live element by stable item key and restores an
element anchor plus relative viewport offset.
- If the target is still a placeholder, one scoped image-loaded subscription
performs a bounded correction after replacement and then removes itself.
- Local browser smoke reproduced an unloaded third image and measured `0 px`
error after placeholder-to-image replacement. Real sites still need to
confirm their layout and native scripts do not introduce an additional shift.
- Real-site testing then reproduced a remaining offset after a large jump to an
unloaded later page. The first fix corrected only the target replacement;
earlier placeholders and page batches could continue changing height and move
the target afterward.
- Reader close now starts a bounded 12-second stabilization period. It observes
Gallery batch resize, relevant DOM insertion and shared image-loaded events,
coalescing changes into at most one double-RAF correction at a time.
- The stabilizer disconnects on timeout or the next Reader open. Scroll
correction does not change batch size, so it does not feed ResizeObserver.
- Browser smoke inserted a delayed `600 px` layout shift before an unloaded
target after close; the final measured anchor error remained `0 px`.
- A subsequent 4KHD mobile test found visible jitter because the second fix
observed every Gallery batch for the full stabilization window, including
already-stable content and layout changes below the target.
- The stabilizer is now conditional: an already-loaded target with no nearby
preceding placeholders receives one restoration and no long-lived observer.
- For unstable targets, only the target and eight preceding scroll elements are
resize-observed. Corrections under `2 px` are ignored, and observers exit
after `750 ms` of stable loaded layout or at the existing 12-second maximum.
- Browser regression retained `0 px` error for the delayed `600 px` shift and
measured `0 px` drift over the following second for an already-loaded target.
- Real-site testing then exposed the stabilizer's architectural side effects:
4KHD mobile visibly shook during correction, and a user-triggered back-to-top
action could be pulled back to the stale Reader anchor for up to 12 seconds.
- The bounded stabilizer is no longer part of the active close path. Every item
now owns a permanent `.hr-image-slot`; placeholder/image state changes happen
inside that slot, while the shared loader publishes ready dimensions once to
the scroll session.
- Reader close synchronizes known dimensions and performs one instant key-based
position update while PhotoSwipe still covers the viewport. Reader navigation
returns the target slot to a fixed top baseline, so target height is not part
of the positioning calculation. No code writes scroll position after reveal.
- Local browser regression measured `top = 0` immediately and one second after
closing on a previously unrendered target. A subsequent user back-to-top
remained at `scrollY = 0` after 1.5 seconds. Mobile UI styling and unchanged
Reader navigation both retained stable close behavior.
- An immediate-close edge case (about 30 ms after a large Reader jump) also
remained at `top = 0` after dimensions arrived, then respected back-to-top.
- Further real-site evaluation showed the remaining requirement itself was not
well-defined: a tall viewport can contain several short images, while scroll
placeholders remain unloaded during Reader use. Resuming them can move the
selected image even when the initial jump was correct. Exact centering would
therefore require loading or geometrically controlling the whole visible
neighbourhood, which is disproportionate to this convenience feature.
- Final implementation removes the geometric slot/dimension registry and retains
the existing `ReaderSession index -> GalleryItem.key -> .r-ph/.r-img` mapping.
No-navigation close restores the captured scrollY once. For a navigated close,
cached dimensions are projected only within a fixed five-item radius before
one centered jump. Unknown target dimensions use the current placeholder
geometry without waiting. The
bounded range matches the existing Reader prefetch radius and creates no image,
request, decode or new cache. Later natural layout movement is accepted, and
no post-close code may reclaim scroll ownership.
- Local browser regression confirmed direct positioning even when the host page
declares smooth scrolling, stable centered placement when cached geometry is
available, page-start fallback without waiting when it is not, unchanged
scrollY when Reader navigation does not occur, and correct key lookup after
content is prepended while Reader is open.
- Final alignment smoke measured a tall `1080px` target at `-182px` versus an
ideal `-180px` center in a `720px` viewport, and a short `360px` target at
`178px` versus an ideal `180px` center. Matching the placeholder's projected
bottom margin to the final image removed the prior roughly 30px replacement
offset. Returning to the original item key after Reader navigation restored
the captured `scrollY = 900` exactly instead of performing another jump.
- Real-site verification confirmed that already-rendered targets retain their
correct position without an extra jump. A large jump to a target that still
existed as a placeholder could still finish with the resulting image aligned
near the viewport start instead of centered.
- Real-site verification found that the one-frame target upgrade was not useful
on 4KHD: the target still finished high in the viewport and the delayed image
appearance made the second layout calculation visibly shake. That close-time
upgrade and its temporary image-loaded listener have been removed.
- Reader close returns to one direct centered jump; no close-time request,
target materialization, wait or second correction remains. Adapters without
reliable dimensions retain the shared placeholder fallback.
- E-Hentai real-site testing then showed a correctly loaded `800 x 1400` target
could still finish high in the viewport. Its resolved hath URL, cached natural
dimensions and final `
` aspect ratio were already correct, so this was
not another metadata gap. Reader return now computes the document scroll top
directly from the target rectangle and viewport height, avoiding host
`scrollIntoView` alignment rules while retaining one instantaneous write.
- Mobile E-Hentai tracing confirmed the direct jump itself reached `-0.2 px`
center error. Within 50 ms the document then shrank by `3,798 px` without any
additional placeholder replacement: off-screen `.r-ph` elements had been
estimated by `contain-intrinsic-size: auto 100vh`, then fell back to their real
`400 px` minimum when the far jump made them render-relevant. Browser scroll
clamping moved the target from about `501 px` to `82 px`. The placeholder
intrinsic fallback now matches its actual `400 px` fallback height. Actual
images retain `content-visibility` and the `100vh` intrinsic fallback; no
request, observer, delayed scroll or extra DOM is added.
- 4KHD time tracing subsequently showed why its static HTML dimensions were not
trustworthy: the target placeholder advertised `1300 x 1500`, while the
loaded image was `1350 x 2024`. Reader return stayed within `0.2 px` of center
for one second, then four preceding replacements reduced both target document
top and total document height by about `446 px`. The 4KHD adapter no longer
publishes those generic display dimensions. It uses the same stable `400 px`
unknown fallback, while the Reader-loaded target still receives exact shared
cache geometry before the single centered jump.
Final resolution:
- Reader navigation returns by stable item key and performs at most one direct
viewport-centered document jump using geometry already available in the
shared cache.
- Unknown placeholders use a `400 px` intrinsic fallback that matches their real
minimum; no image is requested for positioning.
- No post-close observer, timer, retry, target materialization or scroll
correction remains.
- E-Hentai and 18comic real-site verification passed. 4KHD verified exact
initial centering; later movement caused by previously unknown images loading
is accepted natural layout behavior, not owned by Reader restoration.
- The issue is closed. Reopen only for a blocking regression such as wrong item
identity, incorrect initial jump, repeated scroll writes or user input being
overridden.