Skip to content

Release v1.0 — new features#15

Merged
AlanRockefeller merged 1 commit intomainfrom
test
Nov 21, 2025
Merged

Release v1.0 — new features#15
AlanRockefeller merged 1 commit intomainfrom
test

Conversation

@AlanRockefeller
Copy link
Copy Markdown
Owner

@AlanRockefeller AlanRockefeller commented Nov 21, 2025

Major Features

  • Batch Selection System: New batch selection mode for drag-and-drop operations
    • { to begin batch, } to end batch, \ to clear all batches
    • X or S keys remove individual images from batches/stacks (shrinks or splits ranges)
    • Batches automatically cleared after successful drag operation
    • Multiple files can now be dragged to browsers and external applications simultaneously
  • Manual Flag Toggles: Added keyboard shortcuts to manually control metadata flags
    • U toggles uploaded flag
    • Ctrl+E toggles edited flag
    • Ctrl+S toggles stacked flag
  • Edited Flag Tracking: New metadata flag for images edited in Photoshop
    • Displays "Edited on [date]" in status bar (green)
    • Can be manually toggled with Ctrl+E
  • Jump to Image Dialog: Press G to jump directly to any image by number
    • Dynamic input field sizing based on image count
    • Proper keyboard event capture while dialog is open

UI/UX Improvements

  • Auto Zoom Reset: Image view automatically resets to fit-window after drag operations
  • Smooth Window Dragging: Fixed flickering when dragging title bar by using global coordinates
  • Status Bar Enhancements:
    • Added batch info display (green badge showing position/count)
    • Added uploaded status display
    • Added edited status display

Summary by CodeRabbit

Release Notes v1.0

  • New Features

    • Batch Selection System with keyboard shortcuts for marking batch start/end and clearing batches
    • Manual flag toggles to mark images as uploaded, edited, or stacked with timestamps
    • Enhanced drag-and-drop supporting multiple files in batches
  • UI/UX Improvements

    • Status bar now displays batch, uploaded, and edited information
    • Smoother window dragging and auto-zoom reset after drag operations
    • Updated help dialogs with new batch and flag controls
  • Bug Fixes

    • Improved multi-file drag compatibility across browsers

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Nov 21, 2025

Walkthrough

Version 1.0.0 release introducing batch selection system with start/end markers and per-image removal; three flag toggles (uploaded, edited, stacked) with date tracking; enhanced drag-and-drop supporting batch-level dragging; restructured metadata replacing flag/reject fields with uploaded/edited tracking; updated UI to display batch and status information.

Changes

Cohort / File(s) Change Summary
Documentation & Metadata
faststack/ChangeLog.md, faststack/README.md, faststack/faststack.egg-info/PKG-INFO, faststack/pyproject.toml
Version bumped to 1.0; ChangeLog documents batch selection, flag toggles, drag improvements, and UI enhancements; README updated with keyboard shortcuts and feature descriptions; package metadata reflects version and feature updates.
Data Model
faststack/faststack/models.py
EntryMetadata dataclass restructured: removed flag and reject boolean fields; added uploaded, uploaded_date, edited, and edited_date fields to track image processing state with timestamps.
Core Application Logic
faststack/faststack/app.py
Added batch lifecycle methods (begin_new_batch, end_current_batch, clear_all_batches, remove_from_batch_or_stack); introduced flag toggle methods (toggle_uploaded, toggle_edited, toggle_stacked) with metadata persistence; enhanced start_drag_current_image to include batched images and mark as uploaded on success; augmented metadata gathering to include batch and flag information; added Photoshop post-launch editing flag marking.
UI State Exposure
faststack/faststack/ui/provider.py
Removed isFlagged and isRejected properties and toggleFlag slot; added isUploaded, uploadedDate, batchInfoText, isEdited, and editedDate properties to expose new metadata structure to QML.
Keyboard Bindings
faststack/faststack/ui/keystrokes.py
Added batch operation bindings: {/} for batch start/end, \ for clear all batches; added flag toggle binding U for uploaded; added Ctrl+E and Ctrl+S for edit/stacked toggles; removed/moved previous Space and X bindings; added text-input handler support for batch delimiters.
QML User Interface
faststack/faststack/qml/Main.qml
Replaced footer status indicators showing Flag/Rejected/Stacked with Stacked/Uploaded/Edited date display; added stackInfo and batchInfo rectangles displaying batch membership and stack position; updated window dragging to use global coordinates; expanded About dialog size and replaced contentItem with ScrollView for scrollable help text including batch and flag toggle instructions.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as QML UI
    participant App as AppController
    participant Model as EntryMetadata
    participant Sidecar as Metadata Cache

    User->>UI: Press { (begin_new_batch)
    UI->>App: begin_new_batch()
    App->>App: batch_start_index = current_index

    User->>UI: Navigate to end image
    
    User->>UI: Press } (end_current_batch)
    UI->>App: end_current_batch()
    App->>App: batches.append([start, current_index])

    User->>UI: Drag batch images
    UI->>App: start_drag_current_image()
    App->>App: Collect all images in batch range
    App->>App: Create drag data with Qt URLs
    App->>App: Initiate drag (Copy/Move action)

    Note over App: On successful drop
    App->>Model: Mark images as uploaded
    App->>Sidecar: Persist metadata
    App->>App: clear_all_batches()
    UI->>UI: Update batch display
Loading
sequenceDiagram
    participant User
    participant UI as QML UI
    participant App as AppController
    participant Model as EntryMetadata
    participant Sidecar as Sidecar File

    User->>UI: Press U (toggle_uploaded)
    UI->>App: toggle_uploaded()
    App->>Model: uploaded = !uploaded
    App->>Model: uploaded_date = now() if True
    App->>Sidecar: Persist metadata
    App->>UI: sync_ui_state()
    UI->>UI: Update uploadedDate property

    User->>UI: Press Ctrl+E (toggle_edited)
    UI->>App: toggle_edited()
    App->>Model: edited = !edited
    App->>Model: edited_date = now() if True
    App->>Sidecar: Persist metadata
    App->>UI: sync_ui_state()
    UI->>UI: Update editedDate property

    Note over App: Photoshop integration
    User->>App: Launch Photoshop
    App->>App: edit_in_photoshop()
    App->>Model: Mark as edited + date
    App->>Sidecar: Persist changes
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Metadata restructuring: Replaced flag/reject with uploaded/edited + date fields across models, app controller, UI provider, and QML—requires verification of migration logic and backward compatibility concerns.
  • Batch system implementation: New batch lifecycle methods (begin_new_batch, end_current_batch, remove_from_batch_or_stack) with state tracking in app.py—verify batch index boundaries, overlaps with existing stack handling, and proper cleanup.
  • Enhanced drag-and-drop: Modified start_drag_current_image to collect batch-scoped images, handle Qt URLs, and auto-clear batches post-drag—requires careful review of drag action handling and state consistency.
  • QML UI composition changes: Significant footer restructuring with new stackInfo/batchInfo rectangles and About dialog ScrollView wrapping—verify layout correctness and accessibility in different window sizes.
  • Photoshop integration side effects: Post-launch metadata marking with date tracking—verify guard conditions against failures and date format consistency.

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.11% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Release v1.0 — new features' clearly summarizes the main change: a version release with new functionality, which aligns perfectly with the PR content.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 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.

@AlanRockefeller
Copy link
Copy Markdown
Owner Author

@CodeRabbit Does this program have any bugs?

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Nov 21, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get the list of changed files in PR #15
gh pr view 15 --json files --jq '.files[].path'

Length of output: 267


🏁 Script executed:

#!/bin/bash
# Get the diff for PR #15
gh pr diff 15

Length of output: 39871


🏁 Script executed:

#!/bin/bash
# Read the full app.py file to understand the complete context
cat faststack/faststack/app.py

Length of output: 60760


🏁 Script executed:

#!/bin/bash
# Read the keystrokes.py file to check keyboard bindings
cat faststack/faststack/ui/keystrokes.py

Length of output: 3668


🏁 Script executed:

#!/bin/bash
# Read the README to check documentation accuracy
cat faststack/README.md

Length of output: 2647


@AlanRockefeller: I found several bugs and issues in this PR. Here are the significant ones:

Critical Bugs

1. Manual File Selection Feature is Broken

The S key was previously used for toggle_selection() but is now remapped to remove_from_batch_or_stack(). The toggle_selection() method still exists (line 689-704 in app.py) and maintains the selected_raws set, but there's no keyboard shortcut to call it anymore.

Impact: The Helicon Focus launch feature has two modes:

  • Launch with manually selected files (line 744: if self.selected_raws:)
  • Launch with stacks (line 749: elif self.stacks:)

Since users can no longer populate selected_raws, the first mode is completely inaccessible.

2. Unsafe List Modification During Iteration

In remove_from_batch_or_stack() (lines 642-716), the code modifies lists while iterating:

for i in range(len(self.batches)):
    start, end = self.batches[i]
    if start <= self.current_index <= end:
        # ...
        self.batches[i:i+1] = new_ranges  # Modifies list during iteration

When splitting a range (current_index in the middle), new_ranges contains 2 elements, which could cause index shifting issues if not for the break statement. While the break prevents actual bugs, this pattern is fragile and could break if the logic changes.

Fix: Iterate with enumeration and rebuild the list, or use a flag to track changes.

3. Documentation Completely Wrong

The README.md has multiple errors:

Line 13: Says S is for "Toggle selection of current image for stacking" - but S now removes images from batches/stacks

Line 43: Says G: Toggle Grid View (not implemented yet) - but G actually opens the "Jump to Image" dialog

Line 43: Says S or 'X': Toggle selection of current image for stacking / drag & drop - completely incorrect, these keys remove from batch/stack

The QML help dialog (Main.qml lines 356-383) is correct, but README is wrong.

Medium Severity Issues

4. Files Marked as "Uploaded" for Local Drags

Lines 1352-1369 mark files as "uploaded" after any successful drag operation:

if result in (Qt.CopyAction, Qt.MoveAction):
    # Mark all dragged files as uploaded

Issue: Files are marked as "uploaded" even when dragged to a local folder or application, not necessarily uploaded to a website. The app can't distinguish between dragging to a browser vs. dragging to a local folder.

5. Typo in README

Line 22: - **Has Memory**:** - extra colon

6. Dead Code

The toggle_selection() method (lines 689-704) and selected_raws set are maintained but never used, wasting memory and cluttering the codebase.

Recommendations

  1. Add a keyboard shortcut for toggle_selection() if manual selection is still needed, or remove the feature entirely and simplify Helicon launch to only use stacks
  2. Fix the README to match actual keyboard shortcuts
  3. Refactor remove_from_batch_or_stack() to avoid list modification during iteration
  4. Consider renaming the "uploaded" flag to something like "exported" or "dragged" to better reflect its actual behavior
  5. Update line 13 in README: Change "select them for processing (S)" to "select them for processing (by defining stacks with [ and ])"

Would you like me to help fix any of these issues?

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
faststack/faststack/app.py (1)

1307-1374: Minor edge case: uploaded flag is set for indices whose files weren’t actually dragged

In start_drag_current_image, you:

  • Build file_indices (all current + batch indices).
  • Build file_paths from those indices, but filter on path.exists().
  • Use file_paths for the drag payload and previews.
  • After a successful drag, you iterate over all file_indices to mark meta.uploaded = True.

If some image_files[idx].path no longer exists (e.g., file deleted on disk but not yet refreshed), it will be excluded from file_paths and not part of the actual drag, but you’ll still mark its metadata as uploaded.

To keep metadata aligned with what was actually dragged, consider updating only the indices whose paths existed:

-        # Convert to sorted list and get paths
-        file_indices = sorted(files_to_drag)
-        file_paths = [self.image_files[idx].path for idx in file_indices if self.image_files[idx].path.exists()]
+        # Convert to sorted list and get only existing paths
+        file_indices = sorted(files_to_drag)
+        existing_indices = [idx for idx in file_indices if self.image_files[idx].path.exists()]
+        file_paths = [self.image_files[idx].path for idx in existing_indices]
@@
-        # Mark all dragged files as uploaded if drag was successful
-        if result in (Qt.CopyAction, Qt.MoveAction):
+        # Mark all dragged files as uploaded if drag was successful
+        if result in (Qt.CopyAction, Qt.MoveAction):
             from datetime import datetime
             today = datetime.now().strftime("%Y-%m-%d")
-            
-            for idx in file_indices:
+
+            for idx in existing_indices:
                 stem = self.image_files[idx].path.stem
                 meta = self.sidecar.get_metadata(stem)
                 meta.uploaded = True
                 meta.uploaded_date = today
@@
-            log.info("Marked %d file(s) as uploaded on %s. Cleared all batches.", len(file_indices), today)
+            log.info("Marked %d file(s) as uploaded on %s. Cleared all batches.", len(existing_indices), today)

This keeps the metadata consistent with the actual drag payload while preserving the rest of the behavior (zoom reset, batch clearing, UI refresh).

🧹 Nitpick comments (4)
faststack/pyproject.toml (1)

6-8: Version bump looks good; consider aligning format with changelog

version = "1.0" is fine and consistent with PKG-INFO, but ChangeLog uses [1.0.0]. Not a blocker, though you may want to standardize on either 1.0 or 1.0.0 everywhere for clarity.

faststack/faststack.egg-info/PKG-INFO (1)

3-5: Metadata updates are consistent; consider regenerating long description

The version, summary, and version/date header updates all look consistent with the 1.0 release and README.
One thing to watch: the long description still talks about sidecar metadata saving “flags, rejections” while the model now uses uploaded/edited fields. When you next cut a release, it would be good to regenerate PKG-INFO from the updated README so the long description matches the current metadata semantics.

Also applies to: 24-28

faststack/faststack/qml/Main.qml (1)

72-85: Footer metadata, batch badges, drag behavior, and key bindings UI are consistent

  • Footer labels for stacked/uploaded/edited correctly hook into uiState.isStacked/isUploaded/isEdited and their dates, gated by imageCount > 0.
  • The new stack/batch info rectangles using stackInfoText/batchInfoText match the metadata exposed in UIState and give clear visual status; relying on the truthiness of non‑empty strings in the visible / color bindings is fine, though you could use !== "" for slightly clearer intent.
  • The title bar MouseArea now basing movement on a computed lastGlobalPos removes the flicker seen with purely local deltas and looks correct for a frameless window.
  • The enlarged, scrollable Key Bindings dialog text is in sync with the new key mappings (jump dialog on G, batch selection keys, X/S removal, flag toggles).

Overall this QML side of the 1.0 features hangs together well.

Also applies to: 107-139, 191-201, 348-391

faststack/faststack/app.py (1)

447-517: Flag toggle methods correctly update sidecar and UI

toggle_uploaded, toggle_edited, and toggle_stacked all:

  • Guard against empty/out-of-range indices.
  • Flip the corresponding flag and maintain a YYYY‑MM‑DD date field.
  • Save via SidecarManager, invalidate the metadata cache, emit dataChanged, and resync UI, plus a clear status message.

Behavior aligns with the new metadata model and UI expectations. If you want to reduce duplication later, you could extract a small helper that takes (field_name, date_field_name, status_label) and implements the common pattern, but this is optional given the clarity of the current code.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1ddd9fb and 1acb2bc.

📒 Files selected for processing (9)
  • faststack/ChangeLog.md (1 hunks)
  • faststack/README.md (2 hunks)
  • faststack/faststack.egg-info/PKG-INFO (2 hunks)
  • faststack/faststack/app.py (10 hunks)
  • faststack/faststack/models.py (1 hunks)
  • faststack/faststack/qml/Main.qml (4 hunks)
  • faststack/faststack/ui/keystrokes.py (3 hunks)
  • faststack/faststack/ui/provider.py (1 hunks)
  • faststack/pyproject.toml (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
faststack/faststack/app.py (2)
faststack/faststack/ui/provider.py (5)
  • currentFilename (154-157)
  • isUploaded (178-181)
  • stackInfoText (172-175)
  • batchInfoText (190-193)
  • resetZoomPan (340-342)
faststack/faststack/io/sidecar.py (2)
  • get_metadata (93-95)
  • save (62-91)
faststack/faststack/ui/provider.py (1)
faststack/faststack/app.py (1)
  • get_current_metadata (519-550)
🪛 LanguageTool
faststack/README.md

[style] ~3-~3: Some style guides suggest that commas should set off the year in a month-day-year date.
Context: ...FastStack # Version 1.0 - November 21, 2025 # By Alan Rockefeller Ultra-fast, cach...

(MISSING_COMMA_AFTER_YEAR)


[grammar] ~12-~12: Ensure spelling is correct
Context: ...0ms next/previous image switching, high peformance decoding via PyTurboJPEG. - **Zoom & ...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🪛 Ruff (0.14.5)
faststack/faststack/app.py

646-646: f-string without any placeholders

Remove extraneous f prefix

(F541)


677-677: f-string without any placeholders

Remove extraneous f prefix

(F541)

🔇 Additional comments (10)
faststack/faststack/ui/keystrokes.py (1)

26-49: Batch/flag keybindings and text fallbacks look coherent

The new mappings for batch lifecycle ({, }, \), removal (X/Sremove_from_batch_or_stack), and metadata toggles (U, Ctrl+E, Ctrl+S) are consistent with the documented behavior and QML help dialog. The extra text-based handling for {, }, and \ is a nice safeguard for keyboard/layout quirks. No issues spotted here.

Also applies to: 51-57, 91-106

faststack/ChangeLog.md (1)

3-32: 1.0.0 changelog entry matches implemented features

The new 1.0.0 section accurately describes the batch selection system, manual flag toggles, edited tracking, jump dialog, and status bar/drag fixes as reflected in the code changes. Looks good as the authoritative release note.

faststack/faststack/ui/provider.py (1)

171-205: New metadata properties cleanly extend UI state

isUploaded, uploadedDate, batchInfoText, isEdited, and editedDate follow the same pattern as the existing stacked metadata, guard on image_files being non‑empty, and read from get_current_metadata() with matching keys. This is a straightforward, low‑risk extension of the UI surface.

faststack/faststack/app.py (7)

119-122: Batch selection state initialization is consistent with stack handling

batch_start_index and batches mirror the existing stack state and are kept in-memory only, which matches the intended transient batch-selection semantics for drag-and-drop. No issues here.


390-395: Extended metadata logging uses new fields correctly

The debug log now includes filename, uploaded flag, stack info, and batch info via ui_state accessors, which should be very helpful when validating the new status bar behavior. Looks correct and low-risk.


536-548: get_current_metadata now surfaces uploaded/edited/batch info cleanly

The metadata dict now includes:

  • uploaded / uploaded_date
  • edited / edited_date
  • stacked / stacked_date
  • stack_info_text and batch_info_text

Using or "" for dates avoids None leaking into the UI, and the cache key includes ui_refresh_generation, while batch/stack operations correctly invalidate _metadata_cache_index. This looks consistent with ui.provider’s accessors.


605-614: Batch clearing helper is straightforward and in sync with UI state

clear_all_batches resets batches and batch_start_index, invalidates the metadata cache, emits dataChanged, resyncs the UI, and posts a status message. This matches the documented \ behavior and the way stack clearing works.


786-790: clear_all_stacks now also clears the start marker

Resetting stack_start_index when clearing stacks avoids leaving a dangling “stack start marked” state, and the log message now reflects both stacks and the marker. This is a good behavioral fix.


1245-1269: Photoshop edit flow correctly updates edited metadata only on successful launch

Marking the image as edited after subprocess.Popen succeeds, then saving sidecar, invalidating metadata cache, and syncing UI is consistent with the new edited flag semantics. The two exception handlers returning early (and explicitly noting “Don’t mark as edited if launch failed”) ensure you don’t set a false edited state. This looks good.


1389-1401: Batch info helper mirrors stack info helper and matches UI expectations

_get_batch_info follows the same pattern as _get_stack_info, returning "Batch N (pos/count)" when the index is inside a batch, or "Batch Start Marked" when it’s exactly at batch_start_index. Logging at debug level is appropriate. This should integrate cleanly with batchInfoText in the provider/QML.

Comment on lines +577 to +604
def begin_new_batch(self):
"""Mark the start of a new batch for drag-and-drop."""
self.batch_start_index = self.current_index
log.info("Batch start marked at index %d", self.batch_start_index)
self._metadata_cache_index = (-1, -1) # Invalidate cache
self.dataChanged.emit()
self.sync_ui_state()
self.update_status_message("Batch start marked")

def end_current_batch(self):
"""End the current batch and save the range."""
log.info("end_current_batch called. batch_start_index: %s", self.batch_start_index)
if self.batch_start_index is not None:
start = min(self.batch_start_index, self.current_index)
end = max(self.batch_start_index, self.current_index)
self.batches.append([start, end])
self.batches.sort() # Keep batches sorted by start index
log.info("Defined new batch: [%d, %d]", start, end)
self.batch_start_index = None
self._metadata_cache_index = (-1, -1) # Invalidate cache
self.dataChanged.emit()
self.sync_ui_state()
count = end - start + 1
self.update_status_message(f"Batch defined: {count} images")
else:
log.warning("No batch start marked. Press '{{' first.")
self.update_status_message("No batch start marked")

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

Minor UX nit: log message shows {{ instead of the { key

The else branch of end_current_batch logs:

log.warning("No batch start marked. Press '{{' first.")

Because this is a plain string, "{{" will render as two { characters. Given the analogous stack message (Press '[' first.) and your shortcut of { for batch start, this will likely confuse users.

You can fix the message as:

-            log.warning("No batch start marked. Press '{{' first.")
+            log.warning("No batch start marked. Press '{' first.")

Optionally, you could also guard begin_new_batch with an image_files/index check for consistency with the toggle methods, but it’s not strictly necessary.

🤖 Prompt for AI Agents
In faststack/faststack/app.py around lines 577 to 604, the warning message in
end_current_batch uses "No batch start marked. Press '{{' first." which shows
two braces; change the log.warning call to use a single brace character (e.g.,
"No batch start marked. Press '{' first.") so the message correctly instructs
the user which key to press; keep the rest of the method unchanged.

Comment on lines +615 to +687
def remove_from_batch_or_stack(self):
"""Remove current image from any batch or stack it's in."""
if not self.image_files or self.current_index >= len(self.image_files):
return

removed = False

# Check and remove from batches
for i in range(len(self.batches)):
start, end = self.batches[i]
if start <= self.current_index <= end:
# Build new ranges excluding current_index
new_ranges = []
if start == end:
# Single image batch - remove entirely (don't add anything)
pass
elif self.current_index == start:
# Remove from beginning - shift start forward
new_ranges.append([start + 1, end])
elif self.current_index == end:
# Remove from end - shift end backward
new_ranges.append([start, end - 1])
else:
# Remove from middle - split into two ranges
new_ranges.append([start, self.current_index - 1])
new_ranges.append([self.current_index + 1, end])

# Replace the old range with new range(s)
self.batches[i:i+1] = new_ranges

log.info("Removed index %d from batch [%d, %d]", self.current_index, start, end)
self.update_status_message(f"Removed from batch")
removed = True
break

# Check and remove from stacks
if not removed:
for i in range(len(self.stacks)):
start, end = self.stacks[i]
if start <= self.current_index <= end:
# Build new ranges excluding current_index
new_ranges = []
if start == end:
# Single image stack - remove entirely (don't add anything)
pass
elif self.current_index == start:
# Remove from beginning - shift start forward
new_ranges.append([start + 1, end])
elif self.current_index == end:
# Remove from end - shift end backward
new_ranges.append([start, end - 1])
else:
# Remove from middle - split into two ranges
new_ranges.append([start, self.current_index - 1])
new_ranges.append([self.current_index + 1, end])

# Replace the old range with new range(s)
self.stacks[i:i+1] = new_ranges

self.sidecar.data.stacks = self.stacks
self.sidecar.save()
log.info("Removed index %d from stack [%d, %d]", self.current_index, start, end)
self.update_status_message(f"Removed from stack")
removed = True
break

if removed:
self._metadata_cache_index = (-1, -1)
self.dataChanged.emit()
self.ui_state.stackSummaryChanged.emit()
self.sync_ui_state()
else:
self.update_status_message("Not in any batch or stack")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Batch/stack removal logic is sound; clean up f‑strings flagged by Ruff

The range-splitting logic for removing the current index from a batch or stack (including shrinking ends and splitting in the middle) is correct and will keep ranges well-formed.

Ruff’s F541 hint is valid here: these are f-strings with no interpolated expressions:

self.update_status_message(f"Removed from batch")
...
self.update_status_message(f"Removed from stack")

They work, but they’ll fail linting and provide no benefit. You can simplify them as:

-                self.update_status_message(f"Removed from batch")
+                self.update_status_message("Removed from batch")
...
-                    self.update_status_message(f"Removed from stack")
+                    self.update_status_message("Removed from stack")

Everything else in this method looks correct, including sidecar persistence and UI invalidation when a removal actually occurs.

🧰 Tools
🪛 Ruff (0.14.5)

646-646: f-string without any placeholders

Remove extraneous f prefix

(F541)


677-677: f-string without any placeholders

Remove extraneous f prefix

(F541)

🤖 Prompt for AI Agents
In faststack/faststack/app.py around lines 615 to 687, replace the two f-strings
used with no interpolation—self.update_status_message(f"Removed from batch") and
self.update_status_message(f"Removed from stack")—with plain string literals
("Removed from batch" and "Removed from stack") to satisfy Ruff F541; make the
change in both the batch and stack removal branches and keep all other logic
(logging, sidecar save, signals) unchanged.

Comment on lines 15 to +23
class EntryMetadata:
"""Sidecar metadata for a single image entry."""
flag: bool = False
reject: bool = False
stack_id: Optional[int] = None
stacked: bool = False
stacked_date: Optional[str] = None
uploaded: bool = False
uploaded_date: Optional[str] = None
edited: bool = False
edited_date: Optional[str] = None
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 | 🔴 Critical

🧩 Analysis chain

New uploaded/edited fields are fine; double‑check sidecar compatibility

The added uploaded / uploaded_date / edited / edited_date fields on EntryMetadata align with how get_current_metadata and the UI read these values. Structurally, this is sound.

What’s worth confirming is backward compatibility for existing faststack.json files that may still contain flag / reject keys and lack the new fields, especially since Sidecar.version is still 2. Please make sure:

  • The sidecar loading path tolerates extra legacy keys and missing uploaded* / edited* fields without raising, and
  • Either the version is bumped when schema changes, or you explicitly treat these additions as backward-compatible and document the deprecation of flag/reject.

You can quickly scan for remaining usages of the old fields with:


🏁 Script executed:

#!/bin/bash
rg -n "flag" .
rg -n "reject" .

Length of output: 4836


Now I need to check the actual sidecar module to understand how it's loading and what mechanisms are in place:


🏁 Script executed:

#!/bin/bash
# Get the full EntryMetadata class and surrounding context
cat ./faststack/faststack/models.py | head -100

Length of output: 1198


🏁 Script executed:

#!/bin/bash
# Search for how Sidecar data is loaded/deserialized
rg -n "from_dict|from_json|loads|load" ./faststack/faststack --type=py -A 2 -B 2 | head -80

Length of output: 5023


🏁 Script executed:

#!/bin/bash
# Check if EntryMetadata has flag or reject attributes
rg -n "flag|reject" ./faststack/faststack/models.py

Length of output: 0



🏁 Script executed:

#!/bin/bash
# Find and read the sidecar.py file
find ./faststack/faststack -name "sidecar.py" -type f

Length of output: 35


🏁 Script executed:

#!/bin/bash
# Get the full test_sidecar.py file to see what's actually being tested
cat ./faststack/faststack/tests/test_sidecar.py

Length of output: 2303



🏁 Script executed:

#!/bin/bash
# Get the full sidecar.py file to understand deserialization
cat ./faststack/faststack/io/sidecar.py

Length of output: 3572


Update sidecar backward compatibility handling before merging

The added fields are structurally sound, but verification reveals a critical backward compatibility gap: old faststack.json files containing flag/reject keys will fail to load.

The problem:

  • sidecar.py line 44 unpacks JSON as EntryMetadata(**meta), which raises TypeError when JSON contains flag/reject keys that don't exist in the dataclass
  • The exception is caught (line 48), silently returning an empty Sidecar() — users lose all metadata without warning
  • Tests in test_sidecar.py (lines 34–45, 55, 64) expect .flag and .reject attributes that no longer exist, so those tests will fail

Required fixes:

  1. Update load() to filter legacy fields before unpacking: meta_clean = {k: v for k, v in meta.items() if k in EntryMetadata.__dataclass_fields__}
  2. Bump Sidecar.version to 3 and document schema migration, or add explicit version-conditional handling
  3. Update tests to use only the new schema fields (uploaded, edited, stacked)

Comment thread faststack/README.md
Comment on lines +3 to 26
# Version 1.0 - November 21, 2025
# By Alan Rockefeller

Ultra-fast, caching JPG viewer designed for culling and selecting RAW files for focus stacking.
Ultra-fast, caching JPG viewer designed for culling and selecting RAW or JPG files for focus stacking and website upload.

This tool is optimized for speed, using `libjpeg-turbo` for decoding, aggressive prefetching, and byte-aware LRU caches to provide a fluid experience when reviewing thousands of images.

## Features

- **Instant Navigation:** Sub-10ms next/previous image switching on cache hits.
- **High-Performance Decoding:** Uses `PyTurboJPEG` for fast JPEG decoding, with a fallback to `Pillow`.
- **Zoom & Pan:** Smooth, mipmapped zooming and panning.
- **RAW Pairing:** Automatically maps JPGs to their corresponding RAW files (`.CR3`, `.ARW`, `.NEF`, etc.).
- **Instant Navigation:** Sub-10ms next/previous image switching, high peformance decoding via `PyTurboJPEG`.
- **Zoom & Pan:** Smooth zooming and panning.
- **Stack Selection:** Group images into stacks (`[`, `]`) and select them for processing (`S`).
- **Helicon Focus Integration:** Launch Helicon Focus with your selected RAW files with a single keypress (`Enter`).
- **Sidecar Metadata:** Saves flags, rejections, and stack groupings to a non-destructive `faststack.json` file.
- **Configurable:** Adjust cache sizes, prefetch behavior, and Helicon Focus path via a settings dialog and a persistent `.ini` file.
- **Photoshop Integration:** Edit current image in Photoshop (E key) - automatically uses RAW files when available
- **Photoshop Integration:** Edit current image in Photoshop (E key) - uses RAW files when available
- **Clipboard Support:** Copy image path to clipboard (Ctrl+C)
- **Image Filtering:** Filter images by filename
- **Drag & Drop:** Drag images to external applications
- **Drag & Drop:** Drag images to external applications. Press { and } to batch files to drag & drop multiple images.
- **Theme Support:** Toggle between light and dark themes
- **Delete & Undo:** Move images to recycle bin (Delete/Backspace) with undo support (Ctrl+Z)
- **Has Memory**:** Starts where you left off, tells you which images have been edited, stacked and uploaded
- **RAW Pairing:** Automatically maps JPGs to their corresponding RAW files (`.CR3`, `.ARW`, `.NEF`, etc.).
- **Configurable:** Adjust cache sizes, prefetch behavior, and Helicon Focus / Photoshop paths via a settings dialog and a persistent `.ini` file.
- **Accurate Colors:** Uses monitor ICC profile to display colors correctly.

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

Fix small typos and align shortcut docs with actual behavior

A few README issues to address so docs match the app:

  • Line 12: high peformancehigh performance.
  • Line 22: **Has Memory**:** has an extra **; should just be **Has Memory:**.

More importantly, the keyboard shortcut descriptions are out of sync with the implementation and the in‑app Key Bindings dialog:

  • G now opens the Jump to Image dialog (show_jump_to_image_dialog and JumpToImageDialog), not a grid view.
  • S / X are wired to remove_from_batch_or_stack, per the changelog and QML help (“Remove current image from batch/stack”), not a generic “toggle selection” anymore.

Suggested diff:

-# Version 1.0 - November 21, 2025
+# Version 1.0 - November 21, 2025

- Ultra-fast, caching JPG viewer designed for culling and selecting RAW or JPG files for focus stacking and website upload.
+ Ultra-fast, caching JPG viewer designed for culling and selecting RAW or JPG files for focus stacking and website upload.

-- **Instant Navigation:** Sub-10ms next/previous image switching, high peformance decoding via `PyTurboJPEG`.
+- **Instant Navigation:** Sub-10ms next/previous image switching, high performance decoding via `PyTurboJPEG`.
 ...
-- **Has Memory**:** Starts where you left off, tells you which images have been edited, stacked and uploaded
+- **Has Memory:** Starts where you left off, tells you which images have been edited, stacked and uploaded
 ...
-- `G`: Toggle Grid View (not implemented yet)
-- `S` or 'X': Toggle selection of current image for stacking / drag & drop
+- `G`: Jump to image number dialog
+- `S` or 'X': Remove current image from current batch/stack

Adjust the wording on the last line to whatever matches your precise UX, but keeping it consistent with remove_from_batch_or_stack and the Key Bindings dialog will avoid confusion.

Also applies to: 43-52

🧰 Tools
🪛 LanguageTool

[style] ~3-~3: Some style guides suggest that commas should set off the year in a month-day-year date.
Context: ...FastStack # Version 1.0 - November 21, 2025 # By Alan Rockefeller Ultra-fast, cach...

(MISSING_COMMA_AFTER_YEAR)


[grammar] ~12-~12: Ensure spelling is correct
Context: ...0ms next/previous image switching, high peformance decoding via PyTurboJPEG. - **Zoom & ...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🤖 Prompt for AI Agents
In faststack/README.md around lines 3-26 (also apply same changes to lines
43-52), fix the typos and align shortcut docs with the app: change "high
peformance" to "high performance", collapse the extra bold markers so "**Has
Memory**:**" becomes "**Has Memory:**", update the keyboard shortcut
descriptions so G is described as "Open Jump to Image dialog" (matching
show_jump_to_image_dialog / JumpToImageDialog) instead of "grid view", and
change the S / X description to "Remove current image from batch/stack" (or
wording that matches remove_from_batch_or_stack and the Key Bindings dialog).

@AlanRockefeller AlanRockefeller merged commit a76d5ea into main Nov 21, 2025
1 check passed
This was referenced Nov 23, 2025
Merged
Merged
@coderabbitai coderabbitai Bot mentioned this pull request Jan 2, 2026
Merged
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