Pure refactor: moves the 14 effect dataclasses + the Effect union type
from `state.py` into a new sibling `effects.py` module. `state.py`
imports them at the top and re-exports them via `__all__`, so the
public API of `state.py` is unchanged — every existing import in the
test suite (and any future caller) keeps working without modification.
Two reasons for the split:
1. **Conceptual clarity.** State.py is the dispatch + transition
logic; effects.py is the data shapes the adapter consumes.
Splitting matches the architecture target in
docs/POPOUT_ARCHITECTURE.md and makes the effect surface
discoverable in one file.
2. **Import-purity gate stays in place for both modules.**
effects.py inherits the same hard constraint as state.py: no
PySide6, mpv, httpx, or any module that imports them. Verified
by running both modules through a meta_path import blocker that
refuses those packages — both import cleanly.
State.py still imports from effects.py via the standard
`from .effects import LoadImage, LoadVideo, ...` block. The dispatch
handlers continue to instantiate effect descriptors inline; only the
class definitions moved.
Files changed:
- NEW: booru_viewer/gui/popout/effects.py (~190 lines)
- MOD: booru_viewer/gui/popout/state.py (effect dataclasses
removed, import block added — net ~150 lines removed)
Tests passing after this commit: 65 / 65 (no change).
Phase A (16 tests in tests/core/) still green.
Test cases for commit 13 (hyprland.py extraction):
- import popout.hyprland and call helpers
- app launches with the shimmed window.py still using the helpers