annie.pages.viewport

Drop a row’s preview images while it is far off screen (UI helper).

Paging bounds how fast rows appear, not how many end up on the page: a reviewer who scrolls through 2800 samples has 2800 rows mounted, each pinning ~60 KB of base64 WebP in the server’s element tree and in the browser tab, plus its decoded bitmaps. That adds up to hundreds of megabytes long after the row has left the screen.

So each row carries a viewport observer — a Quasar q-intersection stretched over the row — and a OffscreenGate. When the row has been out of view for annie.app_state.UiSettings.unload_after_seconds, its images are cleared back to the grey placeholders they started as; when it scrolls back, they are decoded again. Both tabs size their media boxes in fixed pixels, so an emptied row keeps its exact height and nothing below it jumps.

The delay is what makes this safe to do on scroll: a row flicking past the edge of the viewport, or a small scroll jitter, re-enters long before its unload fires, so normal scrolling never triggers a redecode. Only rows genuinely left behind are dropped.

What this does not reclaim is the row skeleton itself (~43 elements: the card, its badges, buttons and selects). Those are far cheaper per row than the images, but they still accumulate; capping the number of mounted rows is a separate change.

Classes

OffscreenGate

Decides when an off-screen row's images may be dropped, and when to restore them.

Functions

observe_row(→ None)

Watch the enclosing row and drop its images once it sits off screen.

Module Contents

class annie.pages.viewport.OffscreenGate[source]

Decides when an off-screen row’s images may be dropped, and when to restore them.

A row can leave and re-enter the viewport many times while an unload is waiting out its delay. Rather than trying to cancel the pending task, each scroll event bumps pending; a fired unload only takes effect if its generation is still the newest, so a row that came back is left alone.

loaded

Whether the row’s images are currently mounted.

pending

Generation of the most recent visibility change.

hide() → int | None[source]

Record that the row left the viewport.

Returns:

The generation to arm an unload with, or None if the row’s images are already gone and there is nothing to drop.

show() → bool[source]

Record that the row entered the viewport.

Returns:

True if the caller must rebuild the images (they had been dropped).

expire(generation: int) → bool[source]

Resolve an armed unload whose delay has elapsed.

Parameters:

generation – The generation the unload was armed with.

Returns:

True if the row is still off screen and its images should be dropped.

annie.pages.viewport.observe_row(load: collections.abc.Callable[[], None], unload: collections.abc.Callable[[], None], *, delay: float | None = None) → None[source]

Watch the enclosing row and drop its images once it sits off screen.

Must be called inside a relatively positioned container (the row card); the observer stretches over it, so its visibility is the row’s visibility.

Parameters:
  • load – Rebuilds the row’s images. Called when a dropped row scrolls back in.

  • unload – Clears the row’s images back to their placeholders.

  • delay – How long the row may sit off screen before its images are dropped. Defaults to annie.app_state.UiSettings.unload_after_seconds; the Browse grid view passes a shorter value.