Release v1.0 — new features#15
Conversation
WalkthroughVersion 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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
|
@CodeRabbit Does this program have any bugs? |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 267 🏁 Script executed: Length of output: 39871 🏁 Script executed: Length of output: 60760 🏁 Script executed: Length of output: 3668 🏁 Script executed: Length of output: 2647 @AlanRockefeller: I found several bugs and issues in this PR. Here are the significant ones: Critical Bugs1. Manual File Selection Feature is BrokenThe Impact: The Helicon Focus launch feature has two modes:
Since users can no longer populate 2. Unsafe List Modification During IterationIn 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 iterationWhen splitting a range (current_index in the middle), Fix: Iterate with enumeration and rebuild the list, or use a flag to track changes. 3. Documentation Completely WrongThe README.md has multiple errors: Line 13: Says Line 43: Says Line 43: Says The QML help dialog (Main.qml lines 356-383) is correct, but README is wrong. Medium Severity Issues4. Files Marked as "Uploaded" for Local DragsLines 1352-1369 mark files as "uploaded" after any successful drag operation: if result in (Qt.CopyAction, Qt.MoveAction):
# Mark all dragged files as uploadedIssue: 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 READMELine 22: 6. Dead CodeThe Recommendations
Would you like me to help fix any of these issues? |
There was a problem hiding this comment.
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 draggedIn
start_drag_current_image, you:
- Build
file_indices(all current + batch indices).- Build
file_pathsfrom those indices, but filter onpath.exists().- Use
file_pathsfor the drag payload and previews.- After a successful drag, you iterate over all
file_indicesto markmeta.uploaded = True.If some
image_files[idx].pathno longer exists (e.g., file deleted on disk but not yet refreshed), it will be excluded fromfile_pathsand 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 either1.0or1.0.0everywhere for clarity.faststack/faststack.egg-info/PKG-INFO (1)
3-5: Metadata updates are consistent; consider regenerating long descriptionThe 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 regeneratePKG-INFOfrom 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/isEditedand their dates, gated byimageCount > 0.- The new stack/batch info rectangles using
stackInfoText/batchInfoTextmatch the metadata exposed inUIStateand give clear visual status; relying on the truthiness of non‑empty strings in thevisible/ color bindings is fine, though you could use!== ""for slightly clearer intent.- The title bar
MouseAreanow basing movement on a computedlastGlobalPosremoves 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, andtoggle_stackedall:
- 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, emitdataChanged, 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
📒 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 coherentThe new mappings for batch lifecycle (
{,},\), removal (X/S→remove_from_batch_or_stack), and metadata toggles (U,Ctrl+E,Ctrl+S) are consistent with the documented behavior and QML help dialog. The extratext-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 featuresThe 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, andeditedDatefollow the same pattern as the existing stacked metadata, guard onimage_filesbeing non‑empty, and read fromget_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_indexandbatchesmirror 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 correctlyThe debug log now includes filename, uploaded flag, stack info, and batch info via
ui_stateaccessors, 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 cleanlyThe metadata dict now includes:
uploaded/uploaded_dateedited/edited_datestacked/stacked_datestack_info_textandbatch_info_textUsing
or ""for dates avoidsNoneleaking into the UI, and the cache key includesui_refresh_generation, while batch/stack operations correctly invalidate_metadata_cache_index. This looks consistent withui.provider’s accessors.
605-614: Batch clearing helper is straightforward and in sync with UI state
clear_all_batchesresetsbatchesandbatch_start_index, invalidates the metadata cache, emitsdataChanged, 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 markerResetting
stack_start_indexwhen 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 launchMarking the image as edited after
subprocess.Popensucceeds, 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_infofollows 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 atbatch_start_index. Logging at debug level is appropriate. This should integrate cleanly withbatchInfoTextin the provider/QML.
| 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") | ||
|
|
There was a problem hiding this comment.
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.
| 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") |
There was a problem hiding this comment.
🛠️ 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.
| 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 |
There was a problem hiding this comment.
🧩 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 -100Length 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 -80Length of output: 5023
🏁 Script executed:
#!/bin/bash
# Check if EntryMetadata has flag or reject attributes
rg -n "flag|reject" ./faststack/faststack/models.pyLength of output: 0
🏁 Script executed:
#!/bin/bash
# Find and read the sidecar.py file
find ./faststack/faststack -name "sidecar.py" -type fLength 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.pyLength of output: 2303
🏁 Script executed:
#!/bin/bash
# Get the full sidecar.py file to understand deserialization
cat ./faststack/faststack/io/sidecar.pyLength 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.pyline 44 unpacks JSON asEntryMetadata(**meta), which raisesTypeErrorwhen JSON containsflag/rejectkeys 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.flagand.rejectattributes that no longer exist, so those tests will fail
Required fixes:
- Update
load()to filter legacy fields before unpacking:meta_clean = {k: v for k, v in meta.items() if k in EntryMetadata.__dataclass_fields__} - Bump
Sidecar.versionto 3 and document schema migration, or add explicit version-conditional handling - Update tests to use only the new schema fields (
uploaded,edited,stacked)
| # 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. | ||
|
|
There was a problem hiding this comment.
Fix small typos and align shortcut docs with actual behavior
A few README issues to address so docs match the app:
- Line 12:
high peformance→high 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:
Gnow opens the Jump to Image dialog (show_jump_to_image_dialogandJumpToImageDialog), not a grid view.S/Xare wired toremove_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/stackAdjust 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).
Major Features
{to begin batch,}to end batch,\to clear all batchesXorSkeys remove individual images from batches/stacks (shrinks or splits ranges)Utoggles uploaded flagCtrl+Etoggles edited flagCtrl+Stoggles stacked flagCtrl+EGto jump directly to any image by numberUI/UX Improvements
Summary by CodeRabbit
Release Notes v1.0
New Features
UI/UX Improvements
Bug Fixes
✏️ Tip: You can customize this high-level summary in your review settings.