Skip to content

Fix some bugs#25

Merged
AlanRockefeller merged 6 commits intomainfrom
test
Jan 2, 2026
Merged

Fix some bugs#25
AlanRockefeller merged 6 commits intomainfrom
test

Conversation

@AlanRockefeller
Copy link
Copy Markdown
Owner

@AlanRockefeller AlanRockefeller commented Jan 2, 2026

Fix histogram and bugs in crop/rotate

Summary by CodeRabbit

  • New Features

    • Added "restacked" tracking with timestamps and UI exposure
    • New per-channel histogram component and richer editor UI (controls, layout, shortcuts)
  • Bug Fixes

    • Crop/straighten improvements and safer crop execution flow
    • Batch deletion now preserves expected navigation/focus
  • Performance

    • Throttled histogram updates for smoother UI responsiveness
  • Documentation

    • Reformatted ChangeLog and README entries
  • Tests

    • Adjusted editor tests for export/rotation and timing tolerances

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

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 2, 2026

Walkthrough

Adds histogram update throttling, per-image "restacked" metadata with UI exposure and toggle, crop/straighten execution improvements, QML image-editor and histogram UI refactor (including a new SingleChannelHistogram), logging in the image editor, minor imaging/input validation tweaks, test adjustments, and cleanup of debug/config files.

Changes

Cohort / File(s) Summary
Core app controller
faststack/faststack/app.py
Added histogram throttling (50ms timer + _perform_update_histogram), update_histogram queuing, execute_crop(), straighten/crop integration, toggle_restacked(), restacked marking on stack/delete flows, landing-index fix after batch deletes, and get_current_metadata now includes restacked fields.
Data model & persistence
faststack/faststack/models.py, faststack/faststack/faststack.json
Added restacked: bool and restacked_date: Optional[str] to EntryMetadata; removed local faststack.json file.
UI state & provider
faststack/faststack/ui/provider.py, faststack/faststack/qml/Main.qml
Exposed isRestacked and restackedDate properties, added reset_editor_state slot, guarded histogram updates; Main.qml adds editor shortcut and status label for restacked state.
QML — Image editor & histograms
faststack/faststack/qml/ImageEditorDialog.qml, faststack/faststack/qml/HistogramWindow.qml, faststack/faststack/qml/SingleChannelHistogram.qml
Large editor UI refactor: new layout, shortcuts, getBackendValue(), redesigned editSlider, histogram section using new public SingleChannelHistogram component (per-channel properties), responsive sizing and bindings.
QML — Minor components
faststack/faststack/qml/Components.qml, faststack/faststack/qml/DeleteBatchDialog.qml, faststack/faststack/qml/FilterDialog.qml
Crop variable renames/rotation guard, Delete All button text color bound to dialog textColor, FilterDialog styling and controller guards.
Imaging internals
faststack/faststack/imaging/editor.py, faststack/faststack/imaging/jpeg.py, faststack/faststack/imaging/prefetch.py, faststack/faststack/imaging/cache.py
Switched prints → logging in editor; adjusted whites white-point math; decode_jpeg_resized treats non-positive dims as early-return; added timing capture in prefetch; removed hits/misses counters and clarified eviction logging in ByteLRUCache.
IO & integration
faststack/faststack/io/helicon.py
Docstring clarified caller responsibility to remove temp file after Helicon processing; app now marks images restacked when stacking via Helicon flows.
Docs & cleanup
faststack/ChangeLog.md, faststack/README.md, faststack/faststack/next.prompt, faststack/faststack/debug_output.txt
Changelog/README formatting edits; removed planning/debug files.
Tests & build
faststack/faststack/tests/*, pyproject.toml
Adjusted editor tests (path string comparisons, relaxed mtime), rotation tests use for_export and autocrop comparisons, minor test patches; added optional dev dependency for pytest in pyproject.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant QML
    participant AppController
    participant Timer as histogram_timer
    participant ImageEditor
    participant UIState

    User->>QML: interaction (zoom/crop/slider)
    QML->>AppController: update_histogram(requestArgs)
    AppController->>AppController: store _pending_histogram_args
    AppController->>Timer: start/restart 50ms timer

    Note over Timer: timer fires after 50ms
    Timer->>AppController: _perform_update_histogram()
    AppController->>AppController: validate UI visible / not in edit mode
    alt zoomed / crop region
        AppController->>ImageEditor: request cropped region
    end
    AppController->>UIState: set histogramData / clip counts
    UIState->>QML: metadataChanged / histogramData notified
    QML->>QML: SingleChannelHistogram re-renders channels
Loading
sequenceDiagram
    actor User
    participant QML
    participant AppController
    participant EntryMetadata
    participant Sidecar
    participant UIState

    User->>QML: trigger restack (Stack Source Raws / Helicon)
    QML->>AppController: stack_source_raws() / toggle_restacked()
    AppController->>EntryMetadata: set restacked = true, restacked_date = today
    AppController->>Sidecar: sidecar.save(currentEntry)
    AppController->>UIState: emit metadataChanged
    UIState->>QML: isRestacked / restackedDate update
    QML->>QML: display "Restacked on [date]"
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Release v0.2 — more improvements #3: Adds per-image stack/"restack" boolean/date fields and touches models/UI metadata plumbing—strong overlap with restacked fields and sidecar saves.
  • Test #20: Modifies app-level crop and histogram functionality (execute_crop, histogram update paths), overlapping the new throttling and crop execution changes.
  • New features and bug fixes #21: Refactors histogram UI components and editor dialogs; closely related to the new SingleChannelHistogram and histogram wiring in QML.

Pre-merge checks

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 70.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The PR title 'Fix some bugs' is extremely vague and generic, failing to convey what specific bugs were fixed or which features were affected despite substantial changes. Use a more specific title that highlights the main changes, such as 'Add histogram throttling and restacked metadata support' or 'Fix crop/rotate handling and histogram updates'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

❤️ Share

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (6)
faststack/faststack/config.py (1)

18-20: Add more comprehensive documentation for the new INI keys.

While inline comments are present, the coding guidelines require documenting new INI keys. Consider adding more detailed documentation that explains:

  • How these auto-level parameters interact with each other
  • When and how the auto-level feature is triggered
  • The practical impact of adjusting these values

The current comments describe what each value is but not when or how they're used in the application.

Based on coding guidelines that require documenting new INI keys in config.py.

faststack/faststack/imaging/prefetch.py (1)

417-418: Timing variable captured but not logged.

The t_after_copy timestamp is now captured in both fallback paths (lines 418, 451) to align with the primary ICC path. However, unlike the primary ICC path (line 388-392), the fallback paths don't include this timing in their log messages (lines 422-425, 455-457).

🔎 Optional: Include copy timing in fallback logs for consistency

For the ICC fallback path:

                         if self.debug:
                             decoder = "TurboJPEG" if TURBO_AVAILABLE else "Pillow"
-                            log.info("ICC fallback decode timing for index %d (%s): read=%.3fs, decode=%.3fs, total=%.3fs, size=%dx%d",
+                            log.info("ICC fallback decode timing for index %d (%s): read=%.3fs, decode=%.3fs, copy=%.3fs, total=%.3fs, size=%dx%d",
                                      index, decoder, t_after_fallback_read - t_before_fallback_read,
                                      t_after_fallback_decode - t_after_fallback_read,
+                                     t_after_copy - t_after_fallback_decode,
-                                     t_after_fallback_decode - t_start, w, h)
+                                     t_after_copy - t_start, w, h)

For the no-ICC-profile path:

                     if self.debug:
                         decoder = "TurboJPEG" if TURBO_AVAILABLE else "Pillow"
-                        log.info("Standard decode timing (no ICC profile) for index %d (%s): read=%.3fs, decode=%.3fs, total=%.3fs, size=%dx%d",
+                        log.info("Standard decode timing (no ICC profile) for index %d (%s): read=%.3fs, decode=%.3fs, copy=%.3fs, total=%.3fs, size=%dx%d",
                                  index, decoder, t_after_read - t_before_read, t_after_decode - t_after_read,
+                                 t_after_copy - t_after_decode,
-                                 t_after_decode - t_start, w, h)
+                                 t_after_copy - t_start, w, h)

Also applies to: 450-451

faststack/faststack/imaging/editor.py (1)

57-57: Consider using logging.exception for better stack traces.

For exceptions caught in except blocks, logging.exception automatically includes the stack trace, making debugging easier than logging.error.

Example refactor
 except OSError as e:
-    log.error(f"Failed to create backup: {e}")
+    log.exception(f"Failed to create backup: {e}")
     return None

Also applies to: 235-235, 528-528, 636-636, 645-645

faststack/faststack/qml/Components.qml (1)

657-801: Rotate branch now correctly avoids clobbering crop box; minor naming cleanup possible

The new cb/startBox usage and early return in the rotate branch fix the prior issue where rotation could overwrite the crop box while dragging. The move/resize path in the else now only runs for non-rotate drag modes, which is correct.

Very minor: in the “Seed cropBoxStart variables” block you set cropBoxStartLeft/Top from startBox and cropBoxStartRight/Bottom from box. Since both are uiState.currentCropBox, using one variable consistently would reduce reader confusion.

faststack/faststack/tests/test_editor_rotation.py (1)

84-87: Tests now correctly target the export path and allow for rotation rounding

Switching to _apply_edits(..., for_export=True) and comparing res_a vs res_b with a small pixel tolerance is appropriate for rotated/cropped images where rounding can differ slightly. The broader 60–80px bounds and < 5 deltas look reasonable.

If you find this test flaky in practice, consider inlining a short comment near the < 5 checks explaining why that specific tolerance was chosen (rounding + PIL/NumPy differences), but it’s not strictly necessary.

Also applies to: 132-136, 180-185

faststack/faststack/app.py (1)

1822-1839: Batch delete landing index logic is fine; drop unused previous_index

The revised landing logic after batch deletion—using min_deleted_index and clamping to [0, len(image_files)-1]—is reasonable and matches the intent of staying conceptually at the start of the deleted range.

previous_index = self.current_index (Line 1838) is now unused and can be removed to satisfy Ruff (F841) and reduce noise.

Small cleanup
-        min_deleted_index = min(sorted_indices)
-        
-        previous_index = self.current_index # This might be inside the deleted range
+        min_deleted_index = min(sorted_indices)

Also applies to: 1886-1895

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 25dca42 and d5b0bc2.

⛔ Files ignored due to path filters (1)
  • faststack/faststack/tests/dummy_images/test.jpg is excluded by !**/*.jpg
📒 Files selected for processing (27)
  • faststack/ChangeLog.md
  • faststack/README.md
  • faststack/faststack/app.py
  • faststack/faststack/config.py
  • faststack/faststack/debug_output.txt
  • faststack/faststack/faststack.json
  • faststack/faststack/imaging/cache.py
  • faststack/faststack/imaging/editor.py
  • faststack/faststack/imaging/jpeg.py
  • faststack/faststack/imaging/prefetch.py
  • faststack/faststack/io/helicon.py
  • faststack/faststack/models.py
  • faststack/faststack/next.prompt
  • faststack/faststack/qml/Components.qml
  • faststack/faststack/qml/DeleteBatchDialog.qml
  • faststack/faststack/qml/FilterDialog.qml
  • faststack/faststack/qml/HistogramWindow.qml
  • faststack/faststack/qml/ImageEditorDialog.qml
  • faststack/faststack/qml/Main.qml
  • faststack/faststack/qml/SingleChannelHistogram.qml
  • faststack/faststack/tests/test_editor.py
  • faststack/faststack/tests/test_editor_rotation.py
  • faststack/faststack/tests/test_executable_validator.py
  • faststack/faststack/tests/test_pairing.py
  • faststack/faststack/ui/provider.py
  • faststack/pyproject.toml
  • faststack/test_output.txt
💤 Files with no reviewable changes (3)
  • faststack/faststack/debug_output.txt
  • faststack/faststack/faststack.json
  • faststack/faststack/next.prompt
🧰 Additional context used
📓 Path-based instructions (7)
faststack/faststack/**/*.py

📄 CodeRabbit inference engine (faststack/faststack/AGENTS.md)

faststack/faststack/**/*.py: Use four-space indentation in Python files
Use trailing commas for multi-line structures in Python
Use descriptive snake_case identifiers for variables and functions in Python
Use CamelCase exclusively for class names and Qt signal names in Python
Include type hints in all Python code as seen in models.py and AppController
Every new Python module should expose a top-level log = logging.getLogger(__name__) logger
Format Python code with Black before opening a pull request
Lint Python code with Ruff before opening a pull request
Prefer relative paths in configuration and file handling

Files:

  • faststack/faststack/tests/test_executable_validator.py
  • faststack/faststack/imaging/jpeg.py
  • faststack/faststack/imaging/editor.py
  • faststack/faststack/tests/test_editor_rotation.py
  • faststack/faststack/config.py
  • faststack/faststack/models.py
  • faststack/faststack/tests/test_editor.py
  • faststack/faststack/imaging/cache.py
  • faststack/faststack/imaging/prefetch.py
  • faststack/faststack/io/helicon.py
  • faststack/faststack/ui/provider.py
  • faststack/faststack/tests/test_pairing.py
  • faststack/faststack/app.py
faststack/faststack/**/test_*.py

📄 CodeRabbit inference engine (faststack/faststack/AGENTS.md)

faststack/faststack/**/test_*.py: Place new test cases in test_*.py files located beside the modules being tested
Mock filesystem and Qt dependencies in tests to keep them deterministic
Mark tests that perform long disk scans with pytest.mark.slow

Files:

  • faststack/faststack/tests/test_executable_validator.py
  • faststack/faststack/tests/test_editor_rotation.py
  • faststack/faststack/tests/test_editor.py
  • faststack/faststack/tests/test_pairing.py
faststack/faststack/imaging/**/*.py

📄 CodeRabbit inference engine (faststack/faststack/AGENTS.md)

Cover every new branch in imaging/ or io/ modules with focused pytest test cases

Files:

  • faststack/faststack/imaging/jpeg.py
  • faststack/faststack/imaging/editor.py
  • faststack/faststack/imaging/cache.py
  • faststack/faststack/imaging/prefetch.py
faststack/faststack/**/config.py

📄 CodeRabbit inference engine (faststack/faststack/AGENTS.md)

Document any new INI key in config.py

Files:

  • faststack/faststack/config.py
faststack/faststack/io/**/*.py

📄 CodeRabbit inference engine (faststack/faststack/AGENTS.md)

faststack/faststack/io/**/*.py: Cover every new branch in imaging/ or io/ modules with focused pytest test cases
Validate all user-supplied paths before invoking external tools such as Helicon Focus

Files:

  • faststack/faststack/io/helicon.py
faststack/faststack/ui/**/*.py

📄 CodeRabbit inference engine (faststack/faststack/AGENTS.md)

Treat drag-and-drop helpers like make_hdrop as security-sensitive and validate all user-supplied paths before invoking external tools

Files:

  • faststack/faststack/ui/provider.py
faststack/faststack/**/app.py

📄 CodeRabbit inference engine (faststack/faststack/AGENTS.md)

app.py is the PySide6 entrypoint that coordinates image indexing, caching, and QML screens

Files:

  • faststack/faststack/app.py
🧠 Learnings (6)
📚 Learning: 2025-12-08T04:46:41.632Z
Learnt from: CR
Repo: AlanRockefeller/faststack PR: 0
File: faststack/faststack/AGENTS.md:0-0
Timestamp: 2025-12-08T04:46:41.632Z
Learning: Applies to faststack/faststack/**/test_*.py : Mock filesystem and Qt dependencies in tests to keep them deterministic

Applied to files:

  • faststack/faststack/tests/test_executable_validator.py
  • faststack/faststack/tests/test_editor.py
  • faststack/faststack/tests/test_pairing.py
📚 Learning: 2025-12-08T04:46:41.632Z
Learnt from: CR
Repo: AlanRockefeller/faststack PR: 0
File: faststack/faststack/AGENTS.md:0-0
Timestamp: 2025-12-08T04:46:41.632Z
Learning: `ui/` module contains providers and keystroke maps shared with QML

Applied to files:

  • faststack/faststack/qml/Main.qml
  • faststack/faststack/qml/HistogramWindow.qml
📚 Learning: 2025-12-08T04:46:41.632Z
Learnt from: CR
Repo: AlanRockefeller/faststack PR: 0
File: faststack/faststack/AGENTS.md:0-0
Timestamp: 2025-12-08T04:46:41.632Z
Learning: `imaging/` module manages decode workflow, metadata, editing, and ICC caches

Applied to files:

  • faststack/faststack/imaging/editor.py
📚 Learning: 2025-12-08T04:46:41.632Z
Learnt from: CR
Repo: AlanRockefeller/faststack PR: 0
File: faststack/faststack/AGENTS.md:0-0
Timestamp: 2025-12-08T04:46:41.632Z
Learning: Applies to faststack/faststack/**/config.py : Document any new INI key in `config.py`

Applied to files:

  • faststack/faststack/config.py
📚 Learning: 2025-12-08T04:46:41.632Z
Learnt from: CR
Repo: AlanRockefeller/faststack PR: 0
File: faststack/faststack/AGENTS.md:0-0
Timestamp: 2025-12-08T04:46:41.632Z
Learning: Applies to faststack/faststack/**/*.py : Prefer relative paths in configuration and file handling

Applied to files:

  • faststack/faststack/config.py
📚 Learning: 2025-12-08T04:46:41.632Z
Learnt from: CR
Repo: AlanRockefeller/faststack PR: 0
File: faststack/faststack/AGENTS.md:0-0
Timestamp: 2025-12-08T04:46:41.632Z
Learning: Applies to faststack/faststack/**/app.py : `app.py` is the PySide6 entrypoint that coordinates image indexing, caching, and QML screens

Applied to files:

  • faststack/faststack/app.py
🧬 Code graph analysis (2)
faststack/faststack/imaging/editor.py (2)
faststack/faststack/config.py (1)
  • save (76-84)
faststack/faststack/io/sidecar.py (1)
  • save (61-90)
faststack/faststack/ui/provider.py (2)
faststack/faststack/app.py (3)
  • get_current_metadata (723-756)
  • update_histogram (2555-2571)
  • set_crop_box (2476-2481)
faststack/faststack/imaging/editor.py (1)
  • set_crop_box (536-538)
🪛 Ruff (0.14.10)
faststack/faststack/imaging/editor.py

57-57: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


235-235: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


526-526: Consider moving this statement to an else block

(TRY300)


528-528: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


590-590: Do not catch blind exception: Exception

(BLE001)


624-624: Do not catch blind exception: Exception

(BLE001)


636-636: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


645-645: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

faststack/faststack/ui/provider.py

623-623: Do not catch blind exception: Exception

(BLE001)

faststack/faststack/app.py

1838-1838: Local variable previous_index is assigned to but never used

Remove assignment to unused variable previous_index

(F841)


2714-2715: try-except-pass detected, consider logging the exception

(S110)


2714-2714: Do not catch blind exception: Exception

(BLE001)


2735-2735: Redundant exception object included in logging.exception call

(TRY401)


2954-2955: try-except-pass detected, consider logging the exception

(S110)


2954-2954: Do not catch blind exception: Exception

(BLE001)


2957-2957: Multiple statements on one line (colon)

(E701)

🔇 Additional comments (22)
faststack/pyproject.toml (1)

27-32: Good practice to separate dev dependencies.

The addition of optional dev dependencies follows Python packaging best practices and keeps production installations lean. The pytest 8.0.2 version is available on PyPI and the >=8.0,<9.0 constraint is sound for maintaining stability. Note that pytest 9.0.2 was released in December 2025; consider testing compatibility with the latest major version if you wish to adopt it.

faststack/faststack/io/helicon.py (1)

25-26: LGTM! Documentation clarifies caller responsibility.

The updated docstring correctly documents that the caller must clean up the temporary file after Helicon Focus completes processing, which aligns with the delete=False parameter on line 49.

faststack/faststack/imaging/cache.py (1)

39-39: LGTM! Improved eviction logging message.

The updated log message provides clearer information about cache eviction and is consistent with the logging format used in __setitem__ (line 32).

faststack/faststack/tests/test_pairing.py (1)

6-6: LGTM! Removed unused import.

The patch import has been correctly removed as it's not used anywhere in this test file. All tests only require MagicMock.

faststack/faststack/tests/test_executable_validator.py (1)

89-89: LGTM! More explicit patch syntax.

Using the keyword argument new='nt' instead of a positional argument makes the intent clearer and improves readability.

faststack/faststack/qml/DeleteBatchDialog.qml (1)

83-83: LGTM! Consistent color binding.

The change ensures all button text uses the same textColor property, maintaining visual consistency across the dialog.

faststack/README.md (1)

18-18: LGTM! Documentation formatting improved.

The line formatting makes the feature descriptions clearer and easier to read.

faststack/ChangeLog.md (1)

16-16: LGTM! Changelog formatting improved.

The formatting changes improve readability and organization of the changelog entries.

Also applies to: 106-107, 109-109

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

295-305: LGTM! Restacked metadata properties correctly implemented.

The new isRestacked and restackedDate properties follow the established pattern for other metadata properties (stacked, uploaded, edited) and correctly delegate to get_current_metadata().


621-624: Good defensive programming for histogram updates.

Wrapping the histogram update in a try-except block prevents UI disruption if histogram generation fails. While the static analysis tool flags catching blind Exception, this is appropriate for UI code where robustness is prioritized over granular error handling.


626-646: Comprehensive editor state reset implementation.

The reset_editor_state slot correctly resets all 15 editor-related properties to their default values, covering brightness, contrast, saturation, white balance, sharpness, rotation, exposure, highlights, shadows, vibrance, vignette, blacks, whites, clarity, crop settings, and aspect ratio.


788-789: Good guard check prevents null pointer errors.

The hasattr check and existence validation before calling set_crop_box prevents crashes when image_editor is not yet initialized.

faststack/faststack/imaging/editor.py (1)

1-1: LGTM! Logging integration improves observability.

The migration from print statements to structured logging using log.error, log.warning, and log.info significantly improves debugging capabilities across backup creation, image loading, editing, saving, and timestamp restoration workflows.

As per coding guidelines, every new Python module should expose a top-level logger.

Also applies to: 16-16, 57-57, 235-235, 523-523, 528-528, 556-556, 588-591, 609-625, 631-636, 645-645, 653-653

faststack/faststack/tests/test_editor.py (1)

24-24: LGTM! Test improvements handle filesystem realities.

The changes appropriately handle:

  • Path comparison via string representation (Line 24) - avoids potential Path object identity issues
  • Increased mtime tolerance to 2 seconds (Line 27) - accommodates filesystem timestamp granularity (FAT32 and some network filesystems have 2-second precision)

Also applies to: 27-27

faststack/faststack/models.py (1)

24-25: LGTM! Metadata fields follow established patterns.

The new restacked and restacked_date fields are correctly implemented following the same pattern as existing metadata pairs (stacked/stacked_date, uploaded/uploaded_date, edited/edited_date).

faststack/faststack/qml/FilterDialog.qml (2)

76-76: LGTM! Defensive null checks improve robustness.

The guard checks for controller existence and method availability prevent crashes if the controller is not properly initialized or if methods are missing. This aligns with the broader UI stability improvements in this PR.

Also applies to: 82-84, 87-89


51-54: Nice UI polish on the TextField styling.

The updated background styling (lighter color, visible border, rounded corners) improves visual clarity and follows modern UI design patterns.

faststack/faststack/qml/SingleChannelHistogram.qml (1)

1-131: Single-channel histogram component is well-factored and robust to empty data

The component cleanly encapsulates per-channel rendering, handles null/empty data and zero maxVal safely, and exposes all the right knobs (colors, minimal mode, clip/pre-clip counts) for reuse in both the histogram window and editor.

faststack/faststack/qml/HistogramWindow.qml (1)

11-12: Histogram window wiring to per-channel component looks solid

The window now sensibly supports small sizes, consumes uiState.histogramData via three SingleChannelHistogram instances, and the H key handler correctly marks events as accepted to avoid propagation. This matches the new backend histogram structure.

Also applies to: 20-24, 52-56, 57-100

faststack/faststack/app.py (2)

181-187: Histogram throttling and zoom-region logic look correct

The new histogram_timer + _pending_histogram_args setup properly coalesces rapid calls to update_histogram, caps work to ~20 FPS, and safely bails out when neither the histogram window nor the editor is visible. The ROI computation when zoom > 1.1 and ui_state.isZoomed uses decoded dimensions and pan/scale coherently and falls back to the full frame otherwise.

Also applies to: 2554-2597


675-697: Restacked metadata wiring is consistent with existing flags

toggle_restacked() mirrors the existing uploaded/edited/stacked toggles (including date stamping, sidecar save, cache invalidation, and sync_ui_state()), and get_current_metadata() now exposes restacked and restacked_date. Marking images as restacked in stack_source_raws() on Helicon success completes the flow so the new footer label in QML can reflect this state.

Also applies to: 742-752, 2915-2924

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

634-649: Editor toggle & restack footer wiring are correct; no E-key conflicts detected

The new Shortcut for "E" cleanly centralizes editor open/close in QML with Qt.ApplicationShortcut context and correctly calls controller.load_image_for_editing() only on open. The Ctrl+S handler is properly scoped to uiState.isEditorOpen and prevents event propagation. The "Restacked on …" footer label is correctly wired to display uiState.restackedDate and only shows when imageCount > 0 and isRestacked is true.

No conflicting plain E-key bindings exist in the codebase, and the implementation aligns with the new restack metadata infrastructure.

Comment thread faststack/faststack/app.py
Comment thread faststack/faststack/imaging/editor.py
Comment thread faststack/faststack/imaging/jpeg.py

import numpy as np
from PIL import Image as PILImage, ImageCms
from PySide6.QtCore import QTimer
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

🧩 Analysis chain

🏁 Script executed:

# First, let's check if the file exists and examine its content
cat -n faststack/faststack/imaging/prefetch.py

Repository: AlanRockefeller/faststack

Length of output: 32936


Remove unused QTimer import.

QTimer is imported on line 14 but is never used in this file. The timing implementation uses time.perf_counter() instead. Remove the unused import.

🤖 Prompt for AI Agents
In faststack/faststack/imaging/prefetch.py around line 14, the file imports
QTimer from PySide6.QtCore but never uses it; remove the unused QTimer import
(delete the "from PySide6.QtCore import QTimer" line) so the module no longer
contains an unused import and linting warnings are resolved.

Comment thread faststack/faststack/qml/ImageEditorDialog.qml
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: 0

🧹 Nitpick comments (1)
faststack/faststack/qml/Components.qml (1)

660-676: Consider consolidating variable names for clarity.

Within the same rotation handle hit-test block, you read uiState.currentCropBox twice and store it in two different variables (cb at line 660 and startBox at line 670). While both serve different purposes (aspect calculation vs. seeding start positions), using the same variable name would improve readability since they reference identical data at the same point in time.

🔎 Suggested consolidation
                    var k = mainMouseArea.mapFromItem(rotateKnob, rotateKnob.width/2, rotateKnob.height/2)
                    var dxk = mouse.x - k.x
                    var dyk = mouse.y - k.y
                    var distk = Math.sqrt(dxk*dxk + dyk*dyk)

                    if (distk < 22 * Screen.devicePixelRatio) { // a little forgiving
                        cropDragMode = "rotate"

                        // crop center in mainMouseArea coords -> Changed to IMAGE center to avoid feedback loop
                        var c = mainMouseArea.mapFromItem(mainImage, mainImage.width/2, mainImage.height/2)
                        cropStartAngle = Math.atan2(mouse.y - c.y, mouse.x - c.x) * 180 / Math.PI
                        cropStartRotation = cropRotation
                        
                        // Calculate start aspect ratio (in pixels)
+                       var cropBox = uiState.currentCropBox
-                       var cb = uiState.currentCropBox
-                       if (cb && cb.length === 4) {
-                           var boxW = (cb[2] - cb[0]) / 1000 * mainImage.width
-                           var boxH = (cb[3] - cb[1]) / 1000 * mainImage.height
+                       if (cropBox && cropBox.length === 4) {
+                           var boxW = (cropBox[2] - cropBox[0]) / 1000 * mainImage.width
+                           var boxH = (cropBox[3] - cropBox[1]) / 1000 * mainImage.height
                            cropStartAspect = boxW / boxH
                        }


                        // Seed cropBoxStart variables
-                       var startBox = uiState.currentCropBox
-                       if (startBox && startBox.length === 4) {
-                           cropBoxStartLeft = startBox[0]
-                           cropBoxStartTop = startBox[1]
-                           cropBoxStartRight = startBox[2]
-                           cropBoxStartBottom = startBox[3]
+                       if (cropBox && cropBox.length === 4) {
+                           cropBoxStartLeft = cropBox[0]
+                           cropBoxStartTop = cropBox[1]
+                           cropBoxStartRight = cropBox[2]
+                           cropBoxStartBottom = cropBox[3]
                        }

                        isCropDragging = true
                        return
                    }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d5b0bc2 and 6d111ff.

📒 Files selected for processing (1)
  • faststack/faststack/qml/Components.qml
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: AlanRockefeller/faststack PR: 0
File: faststack/faststack/AGENTS.md:0-0
Timestamp: 2025-12-08T04:46:41.632Z
Learning: `imaging/` module manages decode workflow, metadata, editing, and ICC caches
🔇 Additional comments (2)
faststack/faststack/qml/Components.qml (2)

752-752: Verify that decoupling aspect ratio from rotation is the intended behavior.

The change from pendingAspect = cropStartAspect to pendingAspect = -1 means rotation no longer attempts to maintain the crop box's aspect ratio. This value is passed to controller.set_straighten_angle() (line 574), which likely interprets -1 as "no aspect constraint."

Please confirm this is the desired behavior. If users expect the crop box to maintain its aspect ratio during rotation (especially when an aspect ratio preset is selected), this change might cause unexpected results.


758-760: Excellent fix: Early return prevents state corruption during rotation.

The early return after rotation handling (line 759) effectively prevents the move/resize logic from overwriting the crop box state during rotation operations. This separation of concerns aligns with the PR objective to fix crop/rotate bugs and ensures that rotation and move/resize operations don't interfere with each other.

The explicit else branch (line 760) makes the code flow clearer and more maintainable.

@AlanRockefeller AlanRockefeller merged commit fda086c into main Jan 2, 2026
1 check passed
@coderabbitai coderabbitai Bot mentioned this pull request Jan 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant