New setting "Record recent searches" (on by default). When disabled,
searches are not recorded and the Recent section is hidden from the
history dropdown. Saved searches are unaffected.
behavior change: opt-in setting, on by default (preserves existing behavior)
New setting "Remove bookmark when saved to library" (off by default).
When enabled, _maybe_unbookmark runs directly in each save callback
after save_post_file succeeds -- handles DB removal, grid dot, preview
state, popout sync, and bookmarks tab refresh. Wired into all 4 save
paths: save_to_library, bulk_save, save_as, batch_download_to.
behavior change: opt-in setting, off by default
New setting "Remove bookmark when saved to library" (off by default).
When enabled, saving a post to the library automatically removes its
bookmark. Handles both single saves (on_bookmark_done) and bulk saves
(on_batch_done). UI toggle in Settings > General.
behavior change: opt-in setting, off by default
CI installs httpx + Pillow + pytest but not PySide6. The Phase C
tests import pure functions from controller modules, which had
top-level PySide6 imports (QTimer, QPixmap, QApplication, QMessageBox).
Move these to lazy imports inside the methods that need them so the
module-level pure functions remain importable without Qt.
1. Move controller construction before _setup_signals/_setup_ui —
signals reference controller methods at connect time.
2. Restore _post_id_from_library_path, _set_library_info,
_on_library_selected, _on_library_activated — accidentally deleted
in the commit 4/6 line-range removals (they lived adjacent to
methods being extracted and got caught in the sweep).
behavior change: none (restores lost code, fixes startup crash)
Move 26 bookmark/save/library/batch/blacklist methods and _batch_dest
state into gui/post_actions.py. Rewire 8 signal connections and update
popout_controller signal targets.
Extract is_batch_message and is_in_library as pure functions for
Phase 2 tests. main_window.py: 1935 -> 1400 lines.
behavior change: none
Move 5 popout lifecycle methods (_open_fullscreen_preview,
_on_fullscreen_closed, _navigate_fullscreen, _update_fullscreen,
_update_fullscreen_state) and 4 state attributes (_fullscreen_window,
_popout_active, _info_was_visible, _right_splitter_sizes) into
gui/popout_controller.py.
Rename pass across ALL gui/ files: self._fullscreen_window ->
self._popout_ctrl.window (or self._app._popout_ctrl.window in other
controllers), self._popout_active -> self._popout_ctrl.is_active.
Zero remaining references outside popout_controller.py.
Extract build_video_sync_dict as a pure function for Phase 2 tests.
main_window.py: 2145 -> 1935 lines.
behavior change: none
These two methods were accidentally deleted in the commit 4 line-range
removal (they lived between _set_preview_media and _on_image_done).
Restored from pre-commit-4 state.
behavior change: none (restores lost code)
Move 10 media loading methods (_on_post_activated, _on_image_done,
_on_video_stream, _on_download_progress, _set_preview_media,
_prefetch_adjacent, _on_prefetch_progress, _auto_evict_cache,
_image_dimensions) and _prefetch_pause state into
gui/media_controller.py.
Extract compute_prefetch_order as a pure function for Phase 2 tests.
Update search_controller.py cross-references to use media_ctrl.
main_window.py: 2525 -> 2114 lines.
behavior change: none
Move 21 search/pagination/scroll/blacklist methods and 8 state
attributes (_current_page, _current_tags, _current_rating, _min_score,
_loading, _search, _last_scroll_page, _infinite_scroll) into
gui/search_controller.py.
Extract pure functions for Phase 2 tests: build_search_tags,
filter_posts, should_backfill. Replace inline _filter closures with
calls to the module-level filter_posts function.
Rewire 11 signal connections and update _on_site_changed,
_on_rating_changed, _navigate_preview, _apply_settings to use the
controller. main_window.py: 3068 -> 2525 lines.
behavior change: none
Move _toggle_privacy and its lazy state (_privacy_on, _privacy_overlay,
_popout_was_visible) into gui/privacy.py. Rewire menu action, popout
signal, resizeEvent, and keyPressEvent to use the controller.
No behavior change. main_window.py: 3111 -> 3068 lines.
delete_site() leaked rows in tag_types, search_history, and
saved_searches; reconcile_library_meta() was implemented but never
called. Add tests for both fixes plus tag cache pruning.