Skip to content

Added a sort feature#68

Merged
AlanRockefeller merged 6 commits intomainfrom
test
Apr 11, 2026
Merged

Added a sort feature#68
AlanRockefeller merged 6 commits intomainfrom
test

Conversation

@AlanRockefeller
Copy link
Copy Markdown
Owner

@AlanRockefeller AlanRockefeller commented Apr 10, 2026

Summary by CodeRabbit

  • New Features

    • Added three photo sort options (Default, By Filename, By Date) in the app menu; selection is shown in the UI. Sorting preserves the currently viewed image when possible and refreshes thumbnails immediately in grid view.
  • Behavior / UX

    • If sorting would break existing stacks, the app prompts to confirm clearing stacks; cancel leaves state unchanged.
  • Tests

    • Added comprehensive tests covering sorting, edge cases, and UI refresh behavior.
  • Documentation

    • Reformatted changelog and added a note about the new photo sort feature.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 10, 2026

Warning

Rate limit exceeded

@AlanRockefeller has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 1 minutes and 25 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 1 minutes and 25 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1dd560bb-77c4-4473-a543-ac346d5a597b

📥 Commits

Reviewing files that changed from the base of the PR and between db43707 and 0ad5260.

📒 Files selected for processing (1)
  • faststack/app.py

Walkthrough

Adds controller-level sorting (default/filename/date), QML menu controls, UIState binding/signal, sorting/filtering/remapping logic that preserves or clears stacks with user confirmation, grid/thumbnail refresh handling, unit tests and fixture, plus minor IO and watcher/helicon tweaks.

Changes

Cohort / File(s) Summary
Changelog
ChangeLog.md
Whitespace/formatting normalization and added 1.6.2 bullet: "Added a feature to sort the photos."
Controller & Sorting Logic
faststack/app.py
Added sort_mode, get_sort_mode()/set_sort_mode(); preview/simulate filtered+sorted lists; contiguity checks; range/path remapping helpers; preserve current image or clear stacks with user confirmation; refresh thumbnail/grid and emit UI state change.
QML UI
faststack/qml/Main.qml
Inserted "Sort" section in actionsMenu with three ItemDelegate options (Default, By Filename, By Date); options close menu and reflect selection via uiState.sortMode.
UI State Provider
faststack/ui/provider.py
Added sortModeChanged Signal and sortMode Property exposing controller sort mode to QML.
Tests & Fixtures
faststack/tests/test_sort_mode.py, faststack/tests/conftest.py
New pytest fixture app_controller and comprehensive tests covering date/filename/default sorting, fs-mtime edge cases, grid refresh semantics, no-op/invalid inputs, stack contiguity/confirmation, batch-splitting, and current-image preservation.
IO / Dialogs
faststack/io/deletion.py
Updated PySide6 QMessageBox enum usage to use QMessageBox.Icon.* and QMessageBox.ButtonRole.*.
Helicon & Watcher tweaks
faststack/io/helicon.py, faststack/io/watcher.py
Removed narrow IOError/PermissionError handler in helicon; changed Watcher observer typing to Optional[BaseObserver] and delay assignment until after start scheduling.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant QML as QML UI
    participant Ctrl as AppController
    participant State as UIState
    participant Model as Image Model

    User->>QML: Select sort option
    QML->>Ctrl: set_sort_mode(mode)

    rect rgba(100,150,200,0.5)
    Note over Ctrl: Validate mode\nSimulate reorder\nCheck stacks contiguity
    end

    Ctrl->>Model: _filtered_sorted_copy(mode)
    Model-->>Ctrl: sorted image list

    alt stacks non-contiguous
        Ctrl->>User: prompt to clear stacks (QMessageBox)
        User-->>Ctrl: confirm / cancel
    end

    Ctrl->>Ctrl: remap ranges / update indices\nrefresh thumbnail/grid if needed
    Ctrl->>State: emit sortModeChanged
    State->>QML: notify sortMode property
    QML->>User: update selection UI and displayed images
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 79.07% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Added a sort feature' is vague and generic, lacking specificity about what type of sorting was implemented. Consider a more descriptive title such as 'Add photo sorting by filename, date, and default order' or 'Implement photo sorting feature with multiple modes' to better convey the specific changes.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch test

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
ChangeLog.md (1)

19-19: Make the sort release note more specific

Line 19 is accurate, but adding the available sort modes would make release notes clearer for users.

Suggested wording
-- Added a feature to sort the photos.
+- Added photo sorting options: Default, By Filename, and By Date (Newest first).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ChangeLog.md` at line 19, The release note "Added a feature to sort the
photos." is too vague—update the ChangeLog entry to list the available photo
sort modes (e.g., "date (newest/oldest), name (A→Z/Z→A), and size
(largest/smallest)") so users know what sorting options were added; edit the
line that currently reads "Added a feature to sort the photos." in ChangeLog.md
to a more specific sentence that names the supported sort modes and any default
behavior.
faststack/tests/test_sort_mode.py (1)

11-51: Consolidate duplicated app_controller fixture to avoid test drift.

This fixture largely duplicates faststack/tests/test_reactive_delete.py:9-51. Moving it to a shared conftest.py fixture will reduce divergence and keep controller test setup consistent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@faststack/tests/test_sort_mode.py` around lines 11 - 51, The app_controller
fixture in this test duplicates setup used elsewhere; extract the fixture into a
shared conftest.py to avoid drift by moving the entire pytest fixture named
app_controller that constructs an AppController (uses
QCoreApplication.instance()/QCoreApplication, image_dir tmp_path, mock_engine)
and the patched dependencies (Watcher, SidecarManager, Prefetcher, ByteLRUCache,
config, ThumbnailProvider, ThumbnailModel, ThumbnailPrefetcher, ThumbnailCache,
Keybinder, UIState) into conftest.py, then import/use that fixture in this test;
ensure the shared fixture still applies the same post-creation mocks on the
controller (controller.refresh_image_list, controller.update_status_message,
controller.sync_ui_state, controller.image_cache, controller.prefetcher,
controller._thumbnail_model with rowCount, controller._thumbnail_prefetcher,
controller._path_resolver, controller.dataChanged, controller.ui_state) so
behavior remains identical.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ChangeLog.md`:
- Line 372: The ChangeLog entry for version "0.8.0" is malformed because the
header and its bullet are on the same line; update the line that currently reads
"## [0.8.0] - 2025-11-20- Backspace key now deletes images (in addition to
Delete key). Control-Z restores." by splitting it into a proper header "##
[0.8.0] - 2025-11-20" followed by a separate bullet line "- Backspace key now
deletes images (in addition to Delete key). Control-Z restores." so the markdown
header and content are on distinct lines.

In `@faststack/tests/test_sort_mode.py`:
- Around line 146-153: The test currently creates p_missing (gone.jpg) but never
deletes it, so the "missing file" branch isn't exercised; update the test around
ImageFile and _populate to remove or unlink p_missing before calling _populate
(or before constructing imgs used for date sort) so that
ImageFile(path=p_missing, timestamp=0.0) truly represents a missing/failed stat;
ensure the rest of the test still writes ok.jpg and calls
_populate(app_controller, imgs) to verify behavior without filesystem access to
gone.jpg.

---

Nitpick comments:
In `@ChangeLog.md`:
- Line 19: The release note "Added a feature to sort the photos." is too
vague—update the ChangeLog entry to list the available photo sort modes (e.g.,
"date (newest/oldest), name (A→Z/Z→A), and size (largest/smallest)") so users
know what sorting options were added; edit the line that currently reads "Added
a feature to sort the photos." in ChangeLog.md to a more specific sentence that
names the supported sort modes and any default behavior.

In `@faststack/tests/test_sort_mode.py`:
- Around line 11-51: The app_controller fixture in this test duplicates setup
used elsewhere; extract the fixture into a shared conftest.py to avoid drift by
moving the entire pytest fixture named app_controller that constructs an
AppController (uses QCoreApplication.instance()/QCoreApplication, image_dir
tmp_path, mock_engine) and the patched dependencies (Watcher, SidecarManager,
Prefetcher, ByteLRUCache, config, ThumbnailProvider, ThumbnailModel,
ThumbnailPrefetcher, ThumbnailCache, Keybinder, UIState) into conftest.py, then
import/use that fixture in this test; ensure the shared fixture still applies
the same post-creation mocks on the controller (controller.refresh_image_list,
controller.update_status_message, controller.sync_ui_state,
controller.image_cache, controller.prefetcher, controller._thumbnail_model with
rowCount, controller._thumbnail_prefetcher, controller._path_resolver,
controller.dataChanged, controller.ui_state) so behavior remains identical.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7dab5f82-cc9d-47d1-81a1-fef08513c353

📥 Commits

Reviewing files that changed from the base of the PR and between 03ec45b and 78c7f89.

📒 Files selected for processing (5)
  • ChangeLog.md
  • faststack/app.py
  • faststack/qml/Main.qml
  • faststack/tests/test_sort_mode.py
  • faststack/ui/provider.py

Comment thread ChangeLog.md Outdated
Comment thread faststack/tests/test_sort_mode.py
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 78c7f895fb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread faststack/app.py Outdated
Comment on lines +1137 to +1138
if self.sort_mode == "filename":
filtered.sort(key=lambda img: img.path.name.lower())
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Rebase stack and batch indices after reordering images

The new sort path reorders self.image_files, but existing self.stacks/self.batches (which are stored as positional index ranges) are left unchanged. In the scenario where a user defines stacks or batches and then changes sort mode, subsequent index-based actions (for example Helicon launch or batch deletion) will target different files than the user originally selected, because those ranges now point at different rows after sorting.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
faststack/tests/test_sort_mode.py (1)

188-191: Make the no-op assertion resilient to fixture-side calls.

At Line 191, assert_not_called() can become flaky if sync_ui_state was touched before this assertion. Compare call count before vs after this action instead.

Proposed test hardening
-    app_controller.set_sort_mode("default")  # already default
-
-    # sync_ui_state should not be called again (the fixture's mock tracks calls)
-    app_controller.sync_ui_state.assert_not_called()
+    before = app_controller.sync_ui_state.call_count
+    app_controller.set_sort_mode("default")  # already default
+    # Ensure no additional call was made
+    assert app_controller.sync_ui_state.call_count == before
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@faststack/tests/test_sort_mode.py` around lines 188 - 191, The test currently
uses app_controller.sync_ui_state.assert_not_called() which is flaky if the
fixture touched sync_ui_state earlier; instead capture the prior call count
(e.g., prev = app_controller.sync_ui_state.call_count) before calling
app_controller.set_sort_mode("default") and then assert that
app_controller.sync_ui_state.call_count == prev to ensure no additional calls
were made; update the assertion around the set_sort_mode call to use call_count
comparison rather than assert_not_called(), referencing
app_controller.set_sort_mode and app_controller.sync_ui_state.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@faststack/app.py`:
- Around line 717-755: The code snapshots and remaps stacks using
self.image_files (the filtered view) then writes the rebuilt ranges into
sidecar.data.stacks, which can drop hidden members; before calling
self._apply_filter_to_cached_list() capture the canonical/unfiltered image list
(or its paths) and use that to compute old_stack_paths via
_resolve_ranges_to_paths and to rebuild stacks via _rebuild_ranges_from_paths,
and use the canonical _path_to_index when resolving stack_start_index;
alternatively, if you cannot access an unfiltered list, prevent persisting stack
remaps while a filter is active (i.e., skip writing to sidecar.data.stacks when
a filter is applied). Ensure you update references to image_files usage for
stack remapping to the canonical list and only call sidecar.save() when
remapping against that canonical list.

In `@faststack/tests/conftest.py`:
- Around line 44-69: The fixture currently exits the patch context before tests
run because it uses "return controller" inside the "with (patch(...)):" block;
change that "return controller" to "yield controller" so the patch context stays
active for the duration of the test execution (keep the same "with
(patch(...)):" block and the controller setup, just replace return with yield to
turn the fixture into a generator-style fixture).

In `@faststack/tests/test_sort_mode.py`:
- Around line 305-322: The current test only checks that a_idx and b_idx are
included in batch_indices, which misses over-inclusive remaps; replace the two
inclusion asserts with a strict equality check: build the expected set from the
located indices (a_idx and b_idx found via app_controller.image_files) and
assert batch_indices == expected (using the existing variables batch_indices,
a_idx, b_idx, and app_controller.batches/image_files) so the test fails if any
extra indices are present.
- Around line 344-349: The test currently verifies app_controller.sort_mode and
that app_controller.image_files[app_controller.stack_start_index].path.name ==
"a.jpg", but doesn't assert the actual remapped index; add an explicit assertion
that app_controller.stack_start_index == 2 (the expected remapped index under
date sort) immediately after the sort is applied so the test confirms both the
index remapping and the pointed image identity (use the existing app_controller,
stack_start_index, and image_files symbols to locate where to add the
assertion).

---

Nitpick comments:
In `@faststack/tests/test_sort_mode.py`:
- Around line 188-191: The test currently uses
app_controller.sync_ui_state.assert_not_called() which is flaky if the fixture
touched sync_ui_state earlier; instead capture the prior call count (e.g., prev
= app_controller.sync_ui_state.call_count) before calling
app_controller.set_sort_mode("default") and then assert that
app_controller.sync_ui_state.call_count == prev to ensure no additional calls
were made; update the assertion around the set_sort_mode call to use call_count
comparison rather than assert_not_called(), referencing
app_controller.set_sort_mode and app_controller.sync_ui_state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8019d8ee-1e02-4ea2-9699-3e7c4e3a7b22

📥 Commits

Reviewing files that changed from the base of the PR and between 78c7f89 and c52282f.

📒 Files selected for processing (7)
  • ChangeLog.md
  • faststack/app.py
  • faststack/io/deletion.py
  • faststack/io/helicon.py
  • faststack/io/watcher.py
  • faststack/tests/conftest.py
  • faststack/tests/test_sort_mode.py
💤 Files with no reviewable changes (1)
  • faststack/io/helicon.py

Comment thread faststack/app.py
Comment thread faststack/tests/conftest.py Outdated
Comment thread faststack/tests/test_sort_mode.py
Comment thread faststack/tests/test_sort_mode.py
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
faststack/app.py (1)

716-720: ⚠️ Potential issue | 🟠 Major

Filtered sort still mutates stack membership from a partial list.

At Line 717 and Line 752, stack remap is computed from self.image_files (filtered view). Hidden members are dropped from self.stacks in-memory, and that truncated state can later be persisted by unrelated stack actions. Please avoid stack-preserving remap when a filter is active (or remap against an unfiltered canonical list).

💡 Safer fallback (block stack-preserving remap while filter is active)
         if have_stacks:
+            if self._filter_enabled:
+                if not self._confirm_clear_stacks_for_sort():
+                    return
+                clear_stacks = True
+            else:
                 new_order = self._simulate_sorted_list(mode)
                 new_path_to_idx = {
                     self._key(img.path): i for i, img in enumerate(new_order)
                 }
                 if not self._stacks_stay_contiguous(new_path_to_idx):
                     if not self._confirm_clear_stacks_for_sort():
                         return  # user cancelled — no state changed
                     clear_stacks = True

Also applies to: 752-756

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@faststack/app.py` around lines 716 - 720, The stack-preserving remap
currently uses the filtered view self.image_files and thus can drop hidden stack
members; in the block that computes old_stack_paths via
self._resolve_ranges_to_paths(self.stacks) (and the later remap logic around
where stacks are rewritten), detect when a filter is active (e.g.,
self.image_files is a filtered subset compared to the canonical/unfiltered list)
and in that case avoid performing the stack remap — either set old_stack_paths =
[] (skip remap) or compute remap against the unfiltered canonical image list
instead (call _resolve_ranges_to_paths on the canonical list); apply the same
guard to the later remap computation so no hidden members are dropped when a
filter is active.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@faststack/app.py`:
- Line 849: The for-loop using zip(indices, indices[1:]) should be replaced with
itertools.pairwise(indices) to avoid slicing and satisfy Ruff rules; update the
loop in the function/method where "for a, b in zip(indices, indices[1:]):"
appears (in app.py around the logic handling indices) to "for a, b in
pairwise(indices):" and add "from itertools import pairwise" to the module
imports (or import pairwise in the same scope), ensuring the codebase runs on
Python >=3.10.

---

Duplicate comments:
In `@faststack/app.py`:
- Around line 716-720: The stack-preserving remap currently uses the filtered
view self.image_files and thus can drop hidden stack members; in the block that
computes old_stack_paths via self._resolve_ranges_to_paths(self.stacks) (and the
later remap logic around where stacks are rewritten), detect when a filter is
active (e.g., self.image_files is a filtered subset compared to the
canonical/unfiltered list) and in that case avoid performing the stack remap —
either set old_stack_paths = [] (skip remap) or compute remap against the
unfiltered canonical image list instead (call _resolve_ranges_to_paths on the
canonical list); apply the same guard to the later remap computation so no
hidden members are dropped when a filter is active.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f4d5c7ee-b785-4559-b0e1-bcf96a68bf27

📥 Commits

Reviewing files that changed from the base of the PR and between c52282f and db43707.

📒 Files selected for processing (3)
  • faststack/app.py
  • faststack/tests/conftest.py
  • faststack/tests/test_sort_mode.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • faststack/tests/conftest.py
  • faststack/tests/test_sort_mode.py

Comment thread faststack/app.py Outdated
@AlanRockefeller AlanRockefeller merged commit 8e75d05 into main Apr 11, 2026
3 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Apr 11, 2026
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