Skip to content

Release v1.0 — new features#16

Closed
AlanRockefeller wants to merge 2 commits intomainfrom
test
Closed

Release v1.0 — new features#16
AlanRockefeller wants to merge 2 commits intomainfrom
test

Conversation

@AlanRockefeller
Copy link
Copy Markdown
Owner

@AlanRockefeller AlanRockefeller commented Nov 21, 2025

Summary by CodeRabbit

  • New Features

    • Built-in Image Editor with live preview, crop/aspect ratios, save (with EXIF preserved) and undoable quick auto white balance (A).
    • Enhanced batch/stack controls showing total selected images.
  • UI/UX Improvements

    • Updated top navigation to button-based menus and editor dialog integration.
    • Key bindings refreshed: G (jump), B (batch), S (stack), P (Photoshop), E (toggle editor), A (auto white balance).
  • Documentation

    • README and changelog updated with version 1.1 and new shortcut mappings.

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

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Nov 21, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds a full image-editing feature with live preview, crop and edit parameters (brightness/contrast/saturation/white-balance/rotate/save with EXIF preservation), an auto white-balance action, redesigned batch/stack membership handling, a byte-aware image cache and adaptive prefetcher with ICC handling, filesystem sidecar and watcher, executable validators/Helicon launcher, QML UI rework (menus, editor dialog, dialogs), packaging/metadata updates, and new unit tests and tooling.

Changes

Cohort / File(s) Summary
Image editor & UI integration
faststack/imaging/editor.py, faststack/patch (descriptions), faststack/faststack/qml/ImageEditorDialog.qml, faststack/faststack/qml/ImageEditorDialog.qml.*, faststack/faststack/qml/Components.qml, faststack/faststack/qml/Main.qml, faststack/qml/ImageEditorDialog.qml.new, faststack/qml/ImageEditorDialog.qml.old
New ImageEditor backend, ASPECT_RATIOS/INSTAGRAM_RATIOS, QML editor dialog(s), editor wiring in app/controller/provider/UI state, edited-image provider for live previews, crop/edit parameter flows, save/discard with EXIF-preserving backups.
AppController / core app logic
faststack/faststack/app.py, faststack/working-menus/faststack/app.py
Refactored batch/stack model to explicit ranges and new toggle methods, removed selected_raws, added image_editor integration and undo_history, updated Helicon/Photoshop launch flows and preflight/drag logic, added many editor-related controller methods and UI sync points.
Prefetching & decoding & cache
faststack/working-menus/faststack/imaging/prefetch.py, faststack/working-menus/faststack/imaging/jpeg.py, faststack/working-menus/faststack/imaging/cache.py, faststack/working-menus/faststack/benchmark_decode.py
New adaptive Prefetcher with ICC transform caching and saturation compensation; TurboJPEG-backed decoding with Pillow fallback and scaling heuristics; ByteLRUCache with byte accounting and eviction logging; benchmark utility.
UI state, provider & keybindings
faststack/working-menus/faststack/ui/provider.py, faststack/faststack/ui/keystrokes.py, faststack/working-menus/faststack/ui/keystrokes.py, faststack/faststack/qml/Main.qml
New comprehensive UIState and ImageProvider (including edited preview support), expanded editor properties/signals, keybinding remaps (E→P for Photoshop, added A quick AWB, B batch membership, S toggle stack membership), QML menu/button navigation and keyboard handlers integrated with controller.
IO, sidecar, watcher, executable helpers
faststack/working-menus/faststack/io/sidecar.py, faststack/working-menus/faststack/io/watcher.py, faststack/working-menus/faststack/io/executable_validator.py, faststack/working-menus/faststack/io/helicon.py, faststack/working-menus/faststack/io/indexer.py
New SidecarManager for faststack.json, filesystem Watcher and event handler, executable path validator, secure Helicon Focus launcher (temporary file list), and directory indexer pairing JPG↔RAW by mtime.
Config, logging, models & packaging
faststack/working-menus/faststack/config.py, faststack/working-menus/faststack/logging_setup.py, faststack/working-menus/faststack/models.py, pyproject.toml, faststack/faststack.egg-info/*, faststack.working-menus/*
New AppConfig backed by INI, logging setup with rotating file, dataclass models (ImageFile, Sidecar, DecodedImage), package/egg-info and version bump to 1.1, ChangeLog/README/LICENSE/README additions.
Tests & QA
faststack/working-menus/faststack/tests/*
New unit tests for cache, executable validator, RAW-JPG pairing, and sidecar behavior.

Sequence Diagram(s)

sequenceDiagram
    participant QML as QML UI
    participant UI as UIState/ImageProvider
    participant C as AppController
    participant E as ImageEditor
    participant FS as Sidecar/Indexer/Prefetch
    Note over QML, C: Open editor (E key / Edit action)
    QML->>UI: request isEditorOpen true
    UI->>C: controller.load_image_for_editing()
    C->>E: load_image(path)
    E-->>C: preview ready (bytes)
    C->>UI: set currentEditedImageData (notify)
    UI->>QML: image source updated (edited provider)
    Note over QML, C: User adjusts slider / crop
    QML->>C: set_edit_parameter / set_crop_selection_normalized
    C->>E: set_edit_param / set_crop_box
    E-->>C: updated preview
    C->>UI: update currentEditedImageData
    UI->>QML: live preview refresh
    Note over QML, C: Save
    QML->>C: save_edited_image
    C->>E: save_image() (backup + write with EXIF)
    E-->>C: saved path
    C->>FS: update sidecar/metadata and UI state
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

  • Heterogeneous, high-surface changes across core logic (AppController), new concurrent prefetch/cache/ICC code, image-edit pipeline with image transform and EXIF-preserving saves, UI/QML surface and provider changes, secure subprocess launching, and many new modules and tests.
  • Pay special attention to:
    • Concurrency and cancellation in Prefetcher (generation handling, cache_put correctness).
    • ByteLRUCache size accounting and eviction correctness (getsizeof integration).
    • ImageEditor save semantics (backup rotation, EXIF preservation) and path/permission error handling.
    • AppController state invariants after refactoring batches/stacks and undo history semantics.
    • UIState / ImageProvider threading and lifetime of image buffers passed to QML (avoid use-after-free).
    • Executable validation and Helicon subprocess invocation security (shell-escaping, safe args).
    • Tests that mock filesystem/time behavior — verify deterministic assumptions.

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.73% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Release v1.0 — new features' is too vague and generic. While it indicates a release version bump, it fails to describe the specific changes: keyboard shortcut remapping, batch/stack membership toggling, UI updates, and ChangeLog additions. Use a more specific title that highlights key changes, such as 'Refactor batch/stack selection with new keyboard shortcuts' or 'Update to v1.0 with improved batch/stack toggling and keyboard mappings'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5a451fd and 83bf949.

📒 Files selected for processing (53)
  • faststack.working-menus/ChangeLog.md (1 hunks)
  • faststack.working-menus/LICENSE (1 hunks)
  • faststack.working-menus/README.md (1 hunks)
  • faststack.working-menus/faststack.egg-info/PKG-INFO (1 hunks)
  • faststack.working-menus/faststack.egg-info/SOURCES.txt (1 hunks)
  • faststack.working-menus/faststack.egg-info/dependency_links.txt (1 hunks)
  • faststack.working-menus/faststack.egg-info/entry_points.txt (1 hunks)
  • faststack.working-menus/faststack.egg-info/requires.txt (1 hunks)
  • faststack.working-menus/faststack.egg-info/top_level.txt (1 hunks)
  • faststack.working-menus/faststack/app.py (1 hunks)
  • faststack.working-menus/faststack/benchmark_decode.py (1 hunks)
  • faststack.working-menus/faststack/config.py (1 hunks)
  • faststack.working-menus/faststack/imaging/cache.py (1 hunks)
  • faststack.working-menus/faststack/imaging/jpeg.py (1 hunks)
  • faststack.working-menus/faststack/imaging/prefetch.py (1 hunks)
  • faststack.working-menus/faststack/io/executable_validator.py (1 hunks)
  • faststack.working-menus/faststack/io/helicon.py (1 hunks)
  • faststack.working-menus/faststack/io/indexer.py (1 hunks)
  • faststack.working-menus/faststack/io/sidecar.py (1 hunks)
  • faststack.working-menus/faststack/io/watcher.py (1 hunks)
  • faststack.working-menus/faststack/logging_setup.py (1 hunks)
  • faststack.working-menus/faststack/models.py (1 hunks)
  • faststack.working-menus/faststack/qml/Components.qml (1 hunks)
  • faststack.working-menus/faststack/qml/FilterDialog.qml (1 hunks)
  • faststack.working-menus/faststack/qml/JumpToImageDialog.qml (1 hunks)
  • faststack.working-menus/faststack/qml/Main.qml (1 hunks)
  • faststack.working-menus/faststack/qml/Main.qml.bak (1 hunks)
  • faststack.working-menus/faststack/qml/SettingsDialog.qml (1 hunks)
  • faststack.working-menus/faststack/tests/test_cache.py (1 hunks)
  • faststack.working-menus/faststack/tests/test_executable_validator.py (1 hunks)
  • faststack.working-menus/faststack/tests/test_pairing.py (1 hunks)
  • faststack.working-menus/faststack/tests/test_sidecar.py (1 hunks)
  • faststack.working-menus/faststack/ui/keystrokes.py (1 hunks)
  • faststack.working-menus/faststack/ui/keystrokes.py.bak (1 hunks)
  • faststack.working-menus/faststack/ui/provider.py (1 hunks)
  • faststack.working-menus/faststack/ui/provider.py.bak (1 hunks)
  • faststack.working-menus/patch (1 hunks)
  • faststack.working-menus/pyproject.toml (1 hunks)
  • faststack.working-menus/requirements.txt (1 hunks)
  • faststack/ChangeLog.md (1 hunks)
  • faststack/README.md (4 hunks)
  • faststack/faststack.egg-info/PKG-INFO (2 hunks)
  • faststack/faststack/app.py (18 hunks)
  • faststack/faststack/imaging/editor.py (1 hunks)
  • faststack/faststack/qml/Components.qml (1 hunks)
  • faststack/faststack/qml/ImageEditorDialog.qml (1 hunks)
  • faststack/faststack/qml/ImageEditorDialog.qml.new (1 hunks)
  • faststack/faststack/qml/ImageEditorDialog.qml.old (1 hunks)
  • faststack/faststack/qml/Main.qml (7 hunks)
  • faststack/faststack/ui/keystrokes.py (1 hunks)
  • faststack/faststack/ui/provider.py (3 hunks)
  • faststack/patch (1 hunks)
  • faststack/pyproject.toml (1 hunks)

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: 1

Caution

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

⚠️ Outside diff range comments (3)
faststack/README.md (1)

16-16: Photoshop shortcut docs still mention E while code and shortcuts use P

The feature bullet still says “Edit current image in Photoshop (E key)”, but the keyboard shortcuts section (line 55), Keybinder (Qt.Key_P → edit_in_photoshop), and Main.qml now expose P as the Photoshop key. Please update the feature line to reference P instead of E to avoid confusing users.

Also applies to: 55-55

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

355-386: Update key-bindings dialog text for S/B/X to match new behavior

Switching the Photoshop entry to P: Edit in Photoshop here is correct and matches Keybinder and the README. However, the earlier line still says X or S: Remove current image from batch/stack, which no longer matches the code:

  • S now calls toggle_stack_membership
  • B now calls toggle_batch_membership
  • X is the only key that calls remove_from_batch_or_stack

Suggest updating this block to something like:

-                      "&nbsp;&nbsp;\\: Clear all batches<br>" +
-                      "&nbsp;&nbsp;X or S: Remove current image from batch/stack<br><br>" +
+                      "&nbsp;&nbsp;\\: Clear all batches<br>" +
+                      "&nbsp;&nbsp;S: Toggle selection for stacking<br>" +
+                      "&nbsp;&nbsp;B: Toggle selection for batch drag-and-drop<br>" +
+                      "&nbsp;&nbsp;X: Remove current image from batch/stack<br><br>" +

so the on-screen help matches keystrokes.py and the new toggle semantics.

faststack/faststack/app.py (1)

615-696: Tidy up remove_from_batch_or_stack: redundant sidecar save and minor lint issues

The range-splitting logic for both batches and stacks looks sound, but there are a few cleanups worth making:

  • Inside the stack-removal branch you call self.sidecar.data.stacks = self.stacks and self.sidecar.save() before overwriting self.stacks with new_stacks, then do the same again after the loop. The first assignment/save persists stale data and is redundant; keeping only the final block after self.stacks = new_stacks is clearer and avoids unnecessary disk I/O.
  • update_status_message(f"Removed from batch") and update_status_message(f"Removed from stack") are f-strings without placeholders; tools like Ruff flag these as F541. They should be plain string literals.
  • Behavior note: as written, an image that happens to be in both a batch and a stack will be removed from the first matching batch only (removed becomes True so the stack-removal branch is skipped). If you ever allow overlapping membership and want X to remove from both, you’d need to adjust the removed/batch_modified/stack_modified flow.

A minimal diff addressing the first two points:

-                log.info("Removed index %d from batch [%d, %d]", self.current_index, start, end)
-                self.update_status_message(f"Removed from batch")
+                log.info("Removed index %d from batch [%d, %d]", self.current_index, start, end)
+                self.update_status_message("Removed from batch")
@@
-                    self.sidecar.data.stacks = self.stacks # Update sidecar BEFORE self.stacks is replaced
-                    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")
+                    log.info("Removed index %d from stack [%d, %d]", self.current_index, start, end)
+                    self.update_status_message("Removed from stack")
@@
-            if stack_modified:
-                self.stacks = new_stacks
-                self.sidecar.data.stacks = self.stacks
-                self.sidecar.save()
+            if stack_modified:
+                self.stacks = new_stacks
+                self.sidecar.data.stacks = self.stacks
+                self.sidecar.save()
🧹 Nitpick comments (3)
faststack/faststack/app.py (3)

738-841: toggle_stack_membership logic is robust; doc/spec alignment may be useful

The stack toggle algorithm looks correct:

  • When the image is in an existing stack, it splits/shrinks the appropriate [start, end] range.
  • When not in any stack:
    • Creates a new single-image stack if none exist.
    • Otherwise finds the nearest stack (backward/forward scan over image_files), extends that stack to include the current index, then sorts and merges overlapping/adjacent ranges.
  • Sidecar stacks is updated once at the end and saved; metadata cache and stack summary signals are refreshed.

Given this is now the primary way to manage stacks, it may be worth double‑checking that user-facing docs (README, ChangeLog, key-bindings text) explicitly describe this “join nearest stack / create new stack” behavior so users aren’t surprised by how S grows stacks.


845-875: launch_helicon now relies solely on stacks; update docstring/log text

The implementation now only launches Helicon Focus when self.stacks is non-empty; there is no longer any concept of an ad-hoc “selection” list. However:

  • The docstring still says “with selected files (RAW preferred, JPG fallback) or stacks”.
  • The warning message is “No selection or stacks defined to launch Helicon Focus.”

To avoid confusion now that stacks are the only driver, consider tightening this to something like:

-        """Launches Helicon Focus with selected files (RAW preferred, JPG fallback) or stacks."""
+        """Launches Helicon Focus once per defined stack (RAW preferred, JPG fallback)."""
@@
-        else:
-            log.warning("No selection or stacks defined to launch Helicon Focus.")
+        else:
+            log.warning("No stacks defined to launch Helicon Focus.")

and aligning README wording with that “stacks-only” behavior.


1519-1531: Simplify _get_batch_info and avoid unused locals

Switching _get_batch_info to return just "In Batch" is fine given the simplified UI, but count_in_batch and pos_in_batch are now computed and unused. You can simplify this block and avoid potential “assigned but never used” lint warnings:

-        for i, (start, end) in enumerate(self.batches):
-            if start <= index <= end:
-                count_in_batch = end - start + 1
-                pos_in_batch = index - start + 1
-                info = "In Batch"
-                break
+        for start, end in self.batches:
+            if start <= index <= end:
+                info = "In Batch"
+                break

Everything else in the method (including “Batch Start Marked” handling and logging) looks good.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a76d5ea and 5a451fd.

📒 Files selected for processing (5)
  • faststack/ChangeLog.md (1 hunks)
  • faststack/README.md (3 hunks)
  • faststack/faststack/app.py (8 hunks)
  • faststack/faststack/qml/Main.qml (1 hunks)
  • faststack/faststack/ui/keystrokes.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
faststack/faststack/app.py (3)
faststack/faststack/io/watcher.py (1)
  • start (49-62)
faststack/faststack/io/sidecar.py (1)
  • save (62-91)
faststack/faststack/ui/provider.py (1)
  • launch_helicon (258-259)
🪛 Ruff (0.14.5)
faststack/faststack/app.py

644-644: f-string without any placeholders

Remove extraneous f prefix

(F541)


678-678: f-string without any placeholders

Remove extraneous f prefix

(F541)

🔇 Additional comments (6)
faststack/README.md (2)

22-22: “Has Memory” description matches new metadata behavior

The updated “Has Memory” bullet now explicitly mentions edited/stacked/uploaded, which aligns with the sidecar flags and footer labels. No issues here.


41-46: Updated G/S/B shortcuts are consistent with controller/keybinder

The shortcut descriptions for G, S, and B match the new methods (show_jump_to_image_dialog, toggle_stack_membership, toggle_batch_membership) wired in Keybinder and AppController. This keeps the README in sync with the implementation.

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

27-36: New S/B/P key mappings are consistent with AppController and docs

Mapping Qt.Key_S to toggle_stack_membership, Qt.Key_B to toggle_batch_membership, and Qt.Key_P to edit_in_photoshop lines up with the new methods in AppController and with the README/Main.qml key-binding text. No functional issues spotted.

Also applies to: 44-47

faststack/faststack/app.py (3)

577-604: end_current_batch UX and state updates look correct

The batch range creation, sorting, metadata cache invalidation, and dataChanged/UI sync are all wired correctly. The new warning/status message path when batch_start_index is None (“Press '{' first.”) is a nice touch and doesn’t introduce any state inconsistencies.


697-737: toggle_batch_membership correctly toggles range membership

The new toggle_batch_membership implementation correctly:

  • Detects whether the current index is already inside any batch.
  • Splits/shrinks the first matching range when removing.
  • Creates a single-index [i, i] batch and keeps self.batches sorted when adding.
  • Invalidates metadata cache, emits dataChanged, and resyncs UI.

This aligns with the README’s “Toggle selection of current image for batch drag & drop”.


1431-1505: Filtering drag payload to existing files is a good robustness improvement

The changes to start_drag_current_image to:

  • Build file_indices from the batch indices,
  • Filter them to existing_indices where path.exists(),
  • Drag only file_paths for existing files, and
  • Mark only those existing indices as uploaded after a successful drag,

prevent nonexistent paths from entering the drag payload or being marked as uploaded. This is a solid defensive change and keeps sidecar metadata in sync with what was actually dragged.

Comment thread faststack/ChangeLog.md Outdated
@@ -1,5 +1,7 @@
# ChangeLog

Todo: Add image brightness control / cropping. Make batches a bit more intuitive. Make it work on Linux / Mac. Create Windows .exe. Write better documentation / help. Add splash screen / icon.
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

Align changelog description of S/X behavior with new toggle semantics

The new Todo line is fine, but in the 1.0.0 entry Line 10 still says “X or S keys remove individual images from batches/stacks (shrinks or splits ranges)”. In this PR, S becomes a toggle for stack membership while X remains the removal key (see toggle_stack_membership, toggle_batch_membership, and remove_from_batch_or_stack in app.py and mappings in keystrokes.py). Consider rephrasing that bullet so the shipped 1.0.0 changelog matches actual behavior.

Also applies to: 8-11

🤖 Prompt for AI Agents
faststack/ChangeLog.md lines 3 and 8-11: the 1.0.0 changelog text still states
“X or S keys remove individual images from batches/stacks (shrinks or splits
ranges)”, but the code now makes S a toggle for stack membership and X the
removal key; update the bullet to reflect actual behavior by replacing that
sentence with one that says X removes individual images from batches/stacks
(shrinks or splits ranges) and S toggles stack membership for the selected
image(s), and verify any other mentions in lines 8-11 use the new semantics.

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