Skip to content

Stage browse loading for network drives and 1000-image folders#21

Merged
jacobson30-bot merged 2 commits into
mainfrom
browse-loading-staging
Jun 10, 2026
Merged

Stage browse loading for network drives and 1000-image folders#21
jacobson30-bot merged 2 commits into
mainfrom
browse-loading-staging

Conversation

@jacobson30-bot

@jacobson30-bot jacobson30-bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Implements the browse-loading review fixes (network-drive freezes, 1000-image folders), plus the py3.12 CI failure fix.

GUI-thread I/O removed (the freezes)

  • Card click: previously two synchronous full load_scan calls (channel previews + a second one just for .header). New ChannelPreviewLoader reads the scan once on a pool worker, emits meta_ready (plane names + header) then per-plane QImages; the info panel builds slots, previews, and the metadata table from those signals. Token-guarded against stale clicks.
  • Viewer open: thumbnail_plane_index_for_entry no longer full-loads the scan to map the thumbnail channel — header-only read_scan_metadata.
  • Folder navigation: index_folder_shallow now runs in a FolderIndexLoader pool worker; the breadcrumb/path strip update immediately ("Indexing …") and the previous grid stays interactive. Token-guarded against rapid navigation.

Bounded I/O and staged rendering

  • Subfolder peek budget: _peek_subfolder stops after examining 400 files (was: content-sniffs every file two levels deep — megabytes of 8 KB reads per folder card on big network trees). counts_capped flows through FolderEntry and folder cards show N+ lower-bound counts. Regression test added.
  • Timer-sliced card creation: first batch of 120 cards builds synchronously, the rest in zero-delay timer slices, so 1000-entry folders don't freeze the GUI constructing widgets. Thumbnail scheduling defers entries whose cards aren't built yet; filtered-out cards are now deleteLater'd on navigation rather than lingering until GC (the wrapper-recycling hazard).
  • Pool priority: thumbnail runnables enter the global pool at priority −1 so ViewerLoader/ChannelPreviewLoader/folder indexing never queue behind a screenful of slow network thumbnail reads.

CI fix (py3.12 failure on main)

The failure ('QGraphicsItemGroup' object has no attribute 'connect' inside QMenu.addAction) is the recycled-Shiboken-wrapper class conftest already documents. Two-part resolution:

  1. The designed mitigation — forking each GUI test — turned out to be silently inactive (conftest gated on hasplugin("forked"), but pytest-forked registers as pytest_forked). Actually enabling it showed pytest-forked (archived, last release 2023) breaks modern pytest teardown bookkeeping ("previous item was not torn down properly" on every forked→unforked transition), so the mechanism and the pytest-forked dependency are removed.
  2. The recycling window is instead closed in-process: the per-test drain fixture now runs gc.collect() after each GUI test, so parentless widgets a test leaked are destroyed deterministically at the test boundary (visible to Shiboken) instead of at a random GC point inside a later test — which is what reused C++ addresses into stale wrappers. The collect is gated to GUI modules to keep the suite fast.

Tests

  • Full suite: 2204 passed, 3 skipped locally and on CI (3.11 + 3.12); ruff clean.
  • New regression test for the peek file budget; fake-pool fixtures updated for the start(runnable, priority) signature.

🤖 Generated with Claude Code

jacobson30-bot and others added 2 commits June 10, 2026 10:53
Removes the synchronous GUI-thread file I/O around the (already staged)
thumbnail queue, which froze browsing on networked drives:

- Card click previously ran TWO full load_scan calls on the GUI thread
  (channel previews + a second one just for the header metadata table).
  New ChannelPreviewLoader reads the scan once on a pool worker and emits
  meta_ready (plane names + header) then per-plane QImages; the panel
  builds its slots, previews, and metadata table from those signals.
- Opening a viewer previously full-loaded the scan a third time just to
  map the thumbnail channel to a plane index; now a header-only
  read_scan_metadata call.
- _navigate ran index_folder_shallow synchronously; a cold network
  folder froze the UI for the whole index. New FolderIndexLoader runs it
  on a pool worker with token-guarded delivery; the path strip shows
  "Indexing …" and the previous grid stays interactive meanwhile.
- _peek_subfolder content-sniffed every file up to two levels deep with
  no count cap; a file budget (default 400) now bounds the I/O and a
  counts_capped flag flows through FolderEntry so folder cards show
  "N+" lower-bound counts.
- Card construction is timer-sliced (first batch synchronous, then
  zero-delay batches of 120) so 1000-entry folders no longer freeze the
  GUI building widgets; thumbnail scheduling defers entries whose cards
  are not built yet, and filtered-out cards are now deleteLater'd on
  navigation instead of lingering until GC.
- Thumbnail runnables enter the global pool at low priority so
  ViewerLoader / ChannelPreviewLoader / folder indexing never queue
  behind a screenful of slow network thumbnail reads.

Also fixes the py3.12 CI failure: conftest gated the per-test forked
isolation on hasplugin("forked"), but pytest-forked registers as
"pytest_forked" — the GUI-test process isolation was silently inactive
everywhere, which is exactly the recycled-wrapper AttributeError CI hit
(QGraphicsItemGroup wrapper reused inside QMenu.addAction). The check
now matches the real plugin name (kept in-process on macOS, where
fork() under AppKit is unsafe).

Co-Authored-By: Claude Fable 5 <[email protected]>
Enabling the forked marker (previous commit) surfaced that pytest-forked
(archived, last release 2023) no longer cooperates with modern pytest
teardown bookkeeping: every non-forked test following a forked one
errors with "previous item was not torn down properly", and the changed
import order broke the matplotlib backend test.

Remove the forked mechanism entirely (and the pytest-forked dependency)
and close the wrapper-recycling window in-process instead: the drain
fixture now garbage-collects after each GUI test, so parentless widgets
a test leaked are destroyed deterministically at the test boundary —
visible to Shiboken — rather than at a random GC point mid-way through
a later test, which is what recycled C++ addresses into stale wrappers
("'QGraphicsItemGroup' object has no attribute 'connect'" on CI py3.12).
The collect is gated to GUI-module tests to keep the suite fast.

Co-Authored-By: Claude Fable 5 <[email protected]>
@jacobson30-bot jacobson30-bot merged commit 4d389e0 into main Jun 10, 2026
3 checks passed
@jacobson30-bot jacobson30-bot deleted the browse-loading-staging branch June 10, 2026 01:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant