Skip to content

A few minor bug fixes#47

Merged
AlanRockefeller merged 4 commits intomainfrom
test
Mar 9, 2026
Merged

A few minor bug fixes#47
AlanRockefeller merged 4 commits intomainfrom
test

Conversation

@AlanRockefeller
Copy link
Copy Markdown
Owner

@AlanRockefeller AlanRockefeller commented Mar 9, 2026

  • Fixed image list refresh after filesystem watcher events so the current image stays selected when possible.
  • Fixed undo and rollback so deleted images are restored to the correct positions.
  • Fixed stack state being lost or shifted incorrectly after delete and undo operations.
  • Improved display cache invalidation so zoom, resize, filter, and edit changes refresh the correct image version.
  • Improved prefetch behavior when zooming or resizing to reduce stale background work.
  • Improved thumbnail lookup speed by adding a faster path-to-row mapping.
  • Reduced chances of UI state getting out of sync after external file changes.

Summary by CodeRabbit

Release Notes - Version 1.6.0

  • New Features

    • Todo flag feature: toggle with D key, filterable in dialogs, displays on thumbnails with date indicators.
  • Bug Fixes

    • Fixed image selection persistence when file system changes occur.
    • Improved undo/rollback to restore deleted images to correct positions.
    • Resolved batch range alignment issues following deletions.
    • Enhanced UI refresh consistency on zoom, resize, filter, and edit operations.
  • Performance

    • Optimized thumbnail lookup performance.
    • Improved prefetch efficiency and reduced stale background work.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 9, 2026

Warning

Rate limit exceeded

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

⌛ 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: 623d97e3-f1bf-46d9-be97-adf99f9d1cc3

📥 Commits

Reviewing files that changed from the base of the PR and between 1ebbef3 and e714141.

📒 Files selected for processing (3)
  • faststack/app.py
  • faststack/imaging/prefetch.py
  • faststack/thumbnail_view/model.py

Walkthrough

Release version 1.6.0 adds thread-safe display state management with a new watcher refresh handler, optimizes thumbnail path lookups via hash mapping, adjusts prefetch radius calculations, extends the deletion data structure for stack-level restoration, and updates documentation and version numbers accordingly.

Changes

Cohort / File(s) Summary
Documentation & Version
ChangeLog.md, README.md, pyproject.toml
Version bump to 1.6.0 (2026-03-06) with release notes documenting new todo flag toggle (D key), UI/state improvements, cross-platform robustness, and refresh behavior fixes. Keyboard shortcut documentation added for new todo flag feature.
Display State Thread-Safety & Watcher Integration
faststack/app.py
Introduces _display_lock for thread-safe display state management and new _bump_display_generation() helper to centralize cache invalidation calls. Adds new _on_watcher_refresh() slot to debounce file-system watcher events, clamp current_index, and trigger prefetch updates, reducing UI desync after external file changes.
Deletion & Undo Infrastructure
faststack/deletion_types.py
Extends DeleteJob dataclass with saved_stacks and saved_stack_start_index fields to enable stack-level state restoration during undo operations alongside existing batch-level restoration.
Prefetch Optimization
faststack/imaging/prefetch.py
Adjusts prefetch radius calculation by removing inclusive offset in asymmetric range computation (forward: ahead = effective_radius - behind instead of + 1; backward: behind = effective_radius - ahead instead of + 1), altering priority ordering of background prefetch tasks.
Thumbnail Lookup Performance
faststack/thumbnail_view/model.py
Adds _path_to_row internal mapping for O(1) image index lookups by normalized path, replacing previous linear scan in find_image_index(). Mapping is maintained during rebuild and refresh operations.

Sequence Diagram(s)

sequenceDiagram
    participant FSW as File System Watcher
    participant AC as AppController
    participant DG as Display Cache
    participant PF as Prefetch Engine
    participant IL as Image List

    FSW->>AC: File change detected
    AC->>AC: _on_watcher_refresh() called (debounced)
    AC->>AC: Clamp current_index if needed
    AC->>DG: _bump_display_generation() under _display_lock
    DG->>DG: Invalidate cached display
    AC->>IL: Load updated image list
    IL->>IL: Refresh from controller
    AC->>PF: Update prefetch based on new index
    PF->>PF: Recompute visible/prefetch ranges
    AC->>AC: Ensure UI consistency
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • minor bug fixes #22: Modifies undo/delete logic that now interacts with the new saved_stacks and saved_stack_start_index fields in DeleteJob, and updates display_generation calls that are now centralized via _bump_display_generation().
  • Release v0.6 — more improvements #8: Both PRs modify display_generation handling in faststack/app.py and adjust prefetch radius logic in faststack/imaging/prefetch.py, suggesting related cache invalidation and prefetch behavior refinements.
  • Version 1.5.6 #41: Modifies AppController thread-safety and debouncing logic in faststack/app.py alongside prefetch behavior changes, overlapping with the watcher refresh and display-lock mechanisms introduced here.
🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.47% 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 "A few minor bug fixes" is vague and does not clearly summarize the main changes. The PR contains substantial improvements including thread-safety mechanisms, filesystem watcher handling, undo/restore fixes, performance optimizations, and version bumping. Consider a more descriptive title that captures the primary focus, such as "Fix image refresh, undo/restore, and display cache invalidation" or "Improve UI state consistency and refresh handling".
✅ 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
  • Post copyable unit tests in a comment
  • 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: 5

🧹 Nitpick comments (1)
faststack/thumbnail_view/model.py (1)

671-673: Reuse the shared path normalizer here.

faststack/io/utils.py:9-25 already centralizes this logic as normalize_path_key(), and faststack/app.py:164-169 uses that helper for controller-side keys. Reimplementing the normalization inline here makes _path_to_row vulnerable to silent misses if the two copies ever drift.

♻️ Suggested cleanup
-                self._path_to_row[
-                    os.path.normcase(os.path.abspath(str(e.path)))
-                ] = i
+                self._path_to_row[normalize_path_key(e.path)] = i
...
-        key = os.path.normcase(os.path.abspath(str(path)))
+        key = normalize_path_key(path)
         return self._path_to_row.get(key, -1)

Also extend the faststack.io.utils import to include normalize_path_key.

Also applies to: 864-865

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

In `@faststack/thumbnail_view/model.py` around lines 671 - 673, Replace the inline
normalization in ThumbnailView's code that does
os.path.normcase(os.path.abspath(str(e.path))) with the shared helper
normalize_path_key from faststack.io.utils: import normalize_path_key and call
it when populating self._path_to_row (both at the current assignment and the
similar one at lines ~864-865) so all path keys use the centralized
normalization logic.
🤖 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`:
- Around line 10-16: The changelog bullets repeat the same leading verbs
("Fixed" and "Improved"), making scanning harder; update several adjacent
entries in the ChangeLog bullets so their openings vary (e.g., swap one "Fixed"
to "Resolved" or "Restored" and one "Improved" to "Enhanced" or "Optimized")
while keeping the original meaning—target the lines that start with "Fixed image
list...", "Fixed undo and rollback...", "Fixed stack state...", and the
"Improved..." lines and adjust 2–3 of their leading words to different verbs for
variety.

In `@faststack/app.py`:
- Around line 988-1004: When re-selecting after external file changes, clear the
stale variant override so get_decoded_image() doesn't keep pointing at a missing
override; in the block that computes target_key via self._key(preserved_path)
and looks up new_idx in self._path_to_index, if new_idx is None (or if
self.image_files is empty) set self.view_override_path (and any related override
state) to None before clamping self.current_index and calling self._do_prefetch
and self.sync_ui_state so the UI will fall back to the main image instead of a
deleted override.
- Around line 986-1004: The watcher-driven refresh path calls
self.refresh_image_list() but does not update the image cache generation, so
get_decoded_image() (which keys by path+display_generation) can return stale
frames; after refresh_image_list() (and before _do_prefetch()/sync_ui_state())
increment or otherwise change self.display_generation (e.g., bump the integer
generation counter) so cached decoded images are invalidated and new frames will
be re-decoded.
- Around line 4289-4296: The code resets self.current_index to
job.previous_index which breaks the visible selection when only a subset of rows
are reinserted; instead compute how many items were inserted before the original
selection and adjust the index accordingly: after reinserting (items), sort them
the same way and compute inserted_before = sum(1 for idx,_ in items if idx <=
job.previous_index) then set self.current_index = min(job.previous_index +
inserted_before, len(self.image_files) - 1); update the block around
self.image_files.insert(...) and the assignment to self.current_index
(referencing image_files, items, job.previous_index, current_index, and
_bump_display_generation) to apply this correction.

In `@faststack/imaging/prefetch.py`:
- Around line 267-271: The directional split can produce zero or negative slots
for the travel direction when effective_radius is small; update the allocation
logic around effective_radius, self._direction_bias, ahead, and behind so each
side gets at least one slot in the travel direction and the opposite side is
non-negative: compute the raw biased value (e.g. behind_raw =
int(effective_radius * (1 - self._direction_bias))) then clamp and rebalance
(behind = max(1, behind_raw); ahead = effective_radius - behind; if ahead < 1:
ahead = 1; behind = effective_radius - ahead) and mirror the logic for the
backward-moving branch to ensure ahead/behind are never zero or negative while
preserving the total budget.

---

Nitpick comments:
In `@faststack/thumbnail_view/model.py`:
- Around line 671-673: Replace the inline normalization in ThumbnailView's code
that does os.path.normcase(os.path.abspath(str(e.path))) with the shared helper
normalize_path_key from faststack.io.utils: import normalize_path_key and call
it when populating self._path_to_row (both at the current assignment and the
similar one at lines ~864-865) so all path keys use the centralized
normalization logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4c7b71a2-febd-449a-8d15-9be01f6848e5

📥 Commits

Reviewing files that changed from the base of the PR and between 8bf7f58 and 1ebbef3.

📒 Files selected for processing (7)
  • ChangeLog.md
  • README.md
  • faststack/app.py
  • faststack/deletion_types.py
  • faststack/imaging/prefetch.py
  • faststack/thumbnail_view/model.py
  • pyproject.toml

Comment thread ChangeLog.md
Comment on lines +10 to +16
- Fixed image list refresh after filesystem watcher events so the current image stays selected when possible.
- Fixed undo and rollback so deleted images are restored to the correct positions.
- Fixed stack state being lost or shifted incorrectly after delete and undo operations.
- Improved display cache invalidation so zoom, resize, filter, and edit changes refresh the correct image version.
- Improved prefetch behavior when zooming or resizing to reduce stale background work.
- Improved thumbnail lookup speed by adding a faster path-to-row mapping.
- Reduced chances of UI state getting out of sync after external file changes.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Vary the repeated bullet openings for easier scanning.

Several adjacent entries begin with the same verb (Fixed / Improved), which makes this release note section a bit repetitive to read. Rewording one or two bullets would make the changelog easier to skim.

Suggested wording tweak
-- Fixed image list refresh after filesystem watcher events so the current image stays selected when possible.
-- Fixed undo and rollback so deleted images are restored to the correct positions.
-- Fixed stack state being lost or shifted incorrectly after delete and undo operations.
-- Improved display cache invalidation so zoom, resize, filter, and edit changes refresh the correct image version.
-- Improved prefetch behavior when zooming or resizing to reduce stale background work.
-- Improved thumbnail lookup speed by adding a faster path-to-row mapping.
-- Reduced chances of UI state getting out of sync after external file changes.
+- Kept the current image selected when possible after filesystem watcher refresh events.
+- Corrected undo and rollback so deleted images return to the right positions.
+- Preserved stack state correctly across delete and undo operations.
+- Improved display cache invalidation so zoom, resize, filter, and edit changes refresh the correct image version.
+- Tuned prefetch behavior during zoom and resize to reduce stale background work.
+- Added a faster path-to-row mapping for thumbnail lookups.
+- Reduced the chance of UI state drifting after external file changes.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- Fixed image list refresh after filesystem watcher events so the current image stays selected when possible.
- Fixed undo and rollback so deleted images are restored to the correct positions.
- Fixed stack state being lost or shifted incorrectly after delete and undo operations.
- Improved display cache invalidation so zoom, resize, filter, and edit changes refresh the correct image version.
- Improved prefetch behavior when zooming or resizing to reduce stale background work.
- Improved thumbnail lookup speed by adding a faster path-to-row mapping.
- Reduced chances of UI state getting out of sync after external file changes.
- Kept the current image selected when possible after filesystem watcher refresh events.
- Corrected undo and rollback so deleted images return to the right positions.
- Preserved stack state correctly across delete and undo operations.
- Improved display cache invalidation so zoom, resize, filter, and edit changes refresh the correct image version.
- Tuned prefetch behavior during zoom and resize to reduce stale background work.
- Added a faster path-to-row mapping for thumbnail lookups.
- Reduced the chance of UI state drifting after external file changes.
🧰 Tools
🪛 LanguageTool

[style] ~12-~12: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...re restored to the correct positions. - Fixed stack state being lost or shifted incor...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~15-~15: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...zing to reduce stale background work. - Improved thumbnail lookup speed by adding a fast...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

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

In `@ChangeLog.md` around lines 10 - 16, The changelog bullets repeat the same
leading verbs ("Fixed" and "Improved"), making scanning harder; update several
adjacent entries in the ChangeLog bullets so their openings vary (e.g., swap one
"Fixed" to "Resolved" or "Restored" and one "Improved" to "Enhanced" or
"Optimized") while keeping the original meaning—target the lines that start with
"Fixed image list...", "Fixed undo and rollback...", "Fixed stack state...", and
the "Improved..." lines and adjust 2–3 of their leading words to different verbs
for variety.

Comment thread faststack/app.py
Comment thread faststack/app.py
Comment thread faststack/app.py
Comment on lines +4289 to +4296
# Insert in ascending index order so each insertion shifts subsequent
# indices correctly, restoring the original list positions.
for idx, img in sorted(items, key=lambda x: x[0]):
self.image_files.insert(min(idx, len(self.image_files)), img)

# Restore selection/focus (approximated)
self.current_index = min(job.previous_index, len(self.image_files) - 1)
self.display_generation += 1
self._bump_display_generation()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Preserve the currently visible image on partial rollback.

Resetting current_index to job.previous_index is only correct for a full restore. If just a subset of deleted rows comes back, reinserting rows before the current selection shifts the surviving image right, so this can land on the wrong file.

💡 Safer selection restore
     def _rollback_ui_items(self, items: List[Tuple[int, Any]], job: DeleteJob) -> None:
         """Restore items to the UI list in correct order."""
+        preserved_path = None
+        if self.image_files and 0 <= self.current_index < len(self.image_files):
+            preserved_path = self.image_files[self.current_index].path
+
         # Insert in ascending index order so each insertion shifts subsequent
         # indices correctly, restoring the original list positions.
         for idx, img in sorted(items, key=lambda x: x[0]):
             self.image_files.insert(min(idx, len(self.image_files)), img)
 
-        # Restore selection/focus (approximated)
-        self.current_index = min(job.previous_index, len(self.image_files) - 1)
+        if preserved_path is not None:
+            restored_idx = next(
+                (i for i, img in enumerate(self.image_files) if img.path == preserved_path),
+                None,
+            )
+            self.current_index = (
+                restored_idx
+                if restored_idx is not None
+                else min(job.previous_index, len(self.image_files) - 1)
+            )
+        else:
+            self.current_index = min(job.previous_index, len(self.image_files) - 1)
         self._bump_display_generation()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@faststack/app.py` around lines 4289 - 4296, The code resets
self.current_index to job.previous_index which breaks the visible selection when
only a subset of rows are reinserted; instead compute how many items were
inserted before the original selection and adjust the index accordingly: after
reinserting (items), sort them the same way and compute inserted_before = sum(1
for idx,_ in items if idx <= job.previous_index) then set self.current_index =
min(job.previous_index + inserted_before, len(self.image_files) - 1); update the
block around self.image_files.insert(...) and the assignment to
self.current_index (referencing image_files, items, job.previous_index,
current_index, and _bump_display_generation) to apply this correction.

Comment thread faststack/imaging/prefetch.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: 1ebbef30c7

ℹ️ 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".

if self._last_navigation_direction > 0: # Moving forward
behind = max(1, int(effective_radius * (1 - self._direction_bias)))
ahead = effective_radius - behind + 1
ahead = effective_radius - behind
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep directional prefetch for minimum radius

This change makes ahead/behind sum exactly to effective_radius, which means with prefetch_radius = 1 (allowed by settings) and forward navigation, behind becomes 1 and ahead becomes 0, so we stop prefetching the next image entirely. In that configuration the app now prefetches the previous frame instead of the one the user is about to open, which regresses navigation latency for low-radius users.

Useful? React with 👍 / 👎.

Comment thread faststack/thumbnail_view/model.py Outdated
Comment on lines +864 to +865
key = os.path.normcase(os.path.abspath(str(path)))
return self._path_to_row.get(key, -1)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve symlink-equivalent path matching in grid lookup

Switching find_image_index() from a .resolve() comparison to an abspath/normcase hash lookup breaks matches when the same file is referenced through different symlink aliases. ThumbnailModel navigation/refresh paths are canonicalized with .resolve(), but controller paths can still carry the launch alias, so the lookup can return -1 and fail to scroll/select the current image when entering grid view from symlinked directories.

Useful? React with 👍 / 👎.

@AlanRockefeller AlanRockefeller merged commit 99c7baa into main Mar 9, 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