Skip to content

fix(category-dialog): defer state updates until after save succeeds#38

Merged
iomz merged 2 commits intodevfrom
feat/auto-assign-hotkeys-and-show-actions
Nov 25, 2025
Merged

fix(category-dialog): defer state updates until after save succeeds#38
iomz merged 2 commits intodevfrom
feat/auto-assign-hotkeys-and-show-actions

Conversation

@iomz
Copy link
Owner

@iomz iomz commented Nov 25, 2025

Summary

Fixes error handling in CategoryDialog to ensure state remains consistent with persistent storage.

Changes

  • Implement optimistic state updates with rollback mechanism
  • Separate error handling for saveAppData() and autoAssignHotkeyToCategory()
  • Provide distinct error messages for save vs hotkey assignment failures
  • Dialog closes on save success even if hotkey assignment fails

Problem

Previously, the component updated local state with new categories before persisting them. If saveAppData() failed, the UI would show a non-persistent category. If autoAssignHotkeyToCategory() failed, the error message incorrectly blamed saving.

Solution

  • State updates happen optimistically (required because saveAppData() reads from state)
  • On save failure, state is rolled back to original categories
  • Hotkey assignment errors are caught separately and shown via notification
  • Dialog only stays open if save fails (not if hotkey assignment fails)

Testing

  • ✅ Build passes
  • ✅ Linting errors resolved
  • ✅ State consistency maintained

Summary by CodeRabbit

  • New Features

    • Added automatic hotkey assignment for newly created categories.
  • Improvements

    • Hotkey list now displays action descriptions for better clarity.
    • Enhanced error notifications for hotkey assignment operations.
    • Expanded documentation describing automatic category hotkey behavior and visibility.

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

iomz added 2 commits November 25, 2025 22:21
…y list

Automatically assign number keys (1-9, then 0) to newly created categories
for quick toggling. Display action labels in the hotkey list sidebar.

Features:
- Auto-assign first available number key when creating a category
- Check keys in order: 1, 2, 3, 4, 5, 6, 7, 8, 9, then 0
- Only use unassigned number keys without modifiers
- Show action labels (e.g., 'Toggle Keep', 'Next Image') in hotkey list
- Extract formatActionLabel to reusable utility function

Changes:
- Add findUnassignedNumberKey() to find available number keys
- Add autoAssignHotkeyToCategory() to create and save hotkeys
- Add formatActionLabel() to format action strings for display
- Update CategoryDialog to auto-assign hotkeys on category creation
- Update HotkeyList to display action labels alongside key combinations
- Add comprehensive tests for all new functionality
- Update README with documentation for new features

Fixes #34
Implement optimistic updates with rollback mechanism to ensure state
remains consistent with persistent storage. Separate error handling
for saveAppData() and autoAssignHotkeyToCategory() with distinct
error messages. Dialog closes on save success even if hotkey
assignment fails, preventing non-persistent categories from appearing
in UI.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 25, 2025

Walkthrough

This PR introduces automatic hotkey assignment for new categories, adding utility functions to find unassigned numeric keys and auto-assign them with persistence, integrating this into the category creation flow with error handling and rollback, and updating documentation and UI to reflect this behavior.

Changes

Cohort / File(s) Change Summary
Documentation
README.md
Updated documentation to describe automatic category hotkey behavior, including rules for key ordering, assignment, and visibility in the Hotkeys tab. Added new asset URL and expanded multiple sections covering installation, keyboard shortcuts, and data file management.
Hotkey Utilities
src/ui/hotkeys.ts
Added three new public utility functions: findUnassignedNumberKey() to locate the first available numeric key (1–9, then 0), autoAssignHotkeyToCategory() to assign a numeric key to a category with persistence and rollback on save failure, and formatActionLabel() to convert action strings to human-readable labels.
Component Integration
src/components/CategoryDialog.tsx
Enhanced category creation and editing flows with optimistic state updates, save-and-rollback error handling, and optional hotkey auto-assignment for new categories. Hotkey assignment failures are reported via notification but do not block dialog closure.
UI Enhancement
src/components/HotkeyList.tsx
Extended HotkeyList to import and display action labels using formatActionLabel and categories data from state, adding human-readable descriptions to hotkey entries.
Test Coverage
src/ui/hotkeys.test.ts
Added comprehensive unit tests for new utilities covering edge cases: unassigned key discovery, hotkey auto-assignment with key ordering and fallback, action label formatting for navigation/category actions/legacy actions, and rollback behavior on save failures.

Sequence Diagram

sequenceDiagram
    actor User
    participant CategoryDialog
    participant AppState
    participant HotkeyUtil
    participant Storage
    
    User->>CategoryDialog: Create new category
    activate CategoryDialog
    CategoryDialog->>AppState: Update state optimistically
    CategoryDialog->>Storage: Save category
    activate Storage
    Storage-->>CategoryDialog: Save successful
    deactivate Storage
    
    rect rgb(220, 240, 230)
        Note over CategoryDialog,HotkeyUtil: Auto-assign hotkey (new flow)
        CategoryDialog->>HotkeyUtil: autoAssignHotkeyToCategory()
        activate HotkeyUtil
        HotkeyUtil->>HotkeyUtil: findUnassignedNumberKey()
        HotkeyUtil->>Storage: Save hotkey assignment
        activate Storage
        Storage-->>HotkeyUtil: Success
        deactivate Storage
        HotkeyUtil-->>CategoryDialog: true (hotkey assigned)
        deactivate HotkeyUtil
    end
    
    CategoryDialog-->>User: Dialog closes
    deactivate CategoryDialog
    
    alt Save fails
        activate CategoryDialog
        Storage-->>CategoryDialog: Failure
        CategoryDialog->>AppState: Rollback state
        CategoryDialog-->>User: Show error notification
        deactivate CategoryDialog
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • CategoryDialog.tsx: Requires careful review of optimistic update logic, rollback behavior on save errors, and the non-fatal hotkey assignment error handling path.
  • hotkeys.ts: Verify the key-finding algorithm (1–9 ordering, fallback to 0), persistence logic with rollback, and action label formatting edge cases (unknown categories, legacy actions).
  • HotkeyList.tsx: Confirm proper integration with categories data and correct usage of formatActionLabel.

Possibly related PRs

Suggested labels

enhancement

Poem

🐰 A rabbit hops through categories bright,
With keys assigned in perfect flight,
First 1–9, then 0 so neat,
Auto-hotkeys make workflows complete!
No more manual key-by-key delight,
Just save and assign—pure rabbit magic! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% 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 directly matches the main objective: fixing error handling in CategoryDialog by deferring state updates until after save succeeds, which is the primary change across the modified components.
✨ 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 feat/auto-assign-hotkeys-and-show-actions

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.

@iomz iomz linked an issue Nov 25, 2025 that may be closed by this pull request
@codecov
Copy link

codecov bot commented Nov 25, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.45%. Comparing base (24b8574) to head (c5997c5).
⚠️ Report is 2 commits behind head on dev.

Additional details and impacted files
@@            Coverage Diff             @@
##              dev      #38      +/-   ##
==========================================
+ Coverage   94.31%   94.45%   +0.13%     
==========================================
  Files          19       19              
  Lines        3045     3121      +76     
  Branches      535      568      +33     
==========================================
+ Hits         2872     2948      +76     
  Misses        173      173              
Flag Coverage Δ
rust 94.45% <100.00%> (+0.13%) ⬆️
unittests 94.45% <100.00%> (+0.13%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@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 (3)
src/components/HotkeyList.tsx (1)

13-13: Nit: Extra blank line.

There's an unnecessary extra blank line after the formatHotkeyDisplay function.

 }
-

 
 export function HotkeyList() {
src/components/CategoryDialog.tsx (2)

104-129: Variable shadowing: errorMessage shadows the state variable.

The const errorMessage on line 126 shadows the state variable errorMessage declared on line 31. While this works correctly, it reduces readability and could cause confusion during future maintenance.

         } catch (error) {
           // Rollback state on save failure
           setCategories(originalCategories);
           
           // Show error to user and keep dialog open so they can retry or cancel
-          const errorMessage = error instanceof Error ? error.message : String(error);
-          setErrorMessage(`Failed to save category: ${errorMessage}`);
+          const saveErrorMsg = error instanceof Error ? error.message : String(error);
+          setErrorMessage(`Failed to save category: ${saveErrorMsg}`);
           setShowError(true);
         }

138-169: Well-structured optimistic update with rollback and non-fatal hotkey assignment.

The implementation correctly:

  1. Stores original state before optimistic update
  2. Attempts save and rolls back on failure
  3. Treats hotkey assignment failure as non-fatal (shows notification but still closes dialog)

Same shadowing issue as the edit path applies here on line 166.

       } catch (error) {
         // Rollback state on save failure
         setCategories(originalCategories);
         
         // Show error to user and keep dialog open so they can retry or cancel
-        const errorMessage = error instanceof Error ? error.message : String(error);
-        setErrorMessage(`Failed to save category: ${errorMessage}`);
+        const saveErrorMsg = error instanceof Error ? error.message : String(error);
+        setErrorMessage(`Failed to save category: ${saveErrorMsg}`);
         setShowError(true);
       }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 24b8574 and c5997c5.

📒 Files selected for processing (5)
  • README.md (6 hunks)
  • src/components/CategoryDialog.tsx (3 hunks)
  • src/components/HotkeyList.tsx (2 hunks)
  • src/ui/hotkeys.test.ts (1 hunks)
  • src/ui/hotkeys.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursorrules)

Use soft tabs (spaces) for indentation, never hard tabs, following the existing codebase style of typically 2 spaces for TypeScript/React files

Files:

  • src/ui/hotkeys.test.ts
  • src/components/HotkeyList.tsx
  • src/ui/hotkeys.ts
  • src/components/CategoryDialog.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

When calling Tauri commands from TypeScript, use camelCase for parameters (e.g., dataFilePath: value). Do not use snake_case in TypeScript

Files:

  • src/ui/hotkeys.test.ts
  • src/components/HotkeyList.tsx
  • src/ui/hotkeys.ts
  • src/components/CategoryDialog.tsx
🧬 Code graph analysis (4)
src/ui/hotkeys.test.ts (3)
src/utils/jotaiStore.ts (1)
  • store (6-6)
src/state.ts (2)
  • hotkeysAtom (12-12)
  • categoriesAtom (14-14)
src/ui/hotkeys.ts (3)
  • findUnassignedNumberKey (304-320)
  • autoAssignHotkeyToCategory (327-356)
  • formatActionLabel (364-411)
src/components/HotkeyList.tsx (3)
src/ui/hotkeys.ts (1)
  • formatActionLabel (364-411)
src/types.ts (1)
  • HotkeyConfig (30-35)
src/state.ts (2)
  • hotkeysAtom (12-12)
  • categoriesAtom (14-14)
src/ui/hotkeys.ts (4)
src/utils/jotaiStore.ts (1)
  • store (6-6)
src/state.ts (1)
  • hotkeysAtom (12-12)
src/types.ts (1)
  • HotkeyConfig (30-35)
src/ui/categories.ts (1)
  • saveAppData (167-192)
src/components/CategoryDialog.tsx (2)
src/ui/categories.ts (1)
  • saveAppData (167-192)
src/ui/hotkeys.ts (1)
  • autoAssignHotkeyToCategory (327-356)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: codecov
🔇 Additional comments (9)
README.md (1)

17-17: LGTM!

The documentation updates accurately describe the new automatic hotkey assignment behavior, including the key ordering (1-9, then 0), the fallback when all keys are used, and the ability to edit/remove auto-assigned hotkeys. This aligns well with the implementation.

Also applies to: 30-30, 44-44, 128-128, 145-146, 173-181

src/components/HotkeyList.tsx (1)

3-5: LGTM!

The integration of formatActionLabel with categories data is well implemented. The component now properly reads categories from state and displays human-readable action labels for each hotkey entry.

Also applies to: 16-16, 44-44

src/components/CategoryDialog.tsx (1)

7-8: LGTM!

Clean imports for the new auto-assign functionality and error notification. The alias showErrorNotification avoids confusion with the local showError state variable.

src/ui/hotkeys.test.ts (3)

1099-1197: LGTM!

Comprehensive test coverage for findUnassignedNumberKey including:

  • Empty state returning "1"
  • Sequential assignment behavior
  • Full key ordering (1-9, then 0)
  • Null return when exhausted
  • Correctly ignoring keys with modifiers
  • Handling non-number keys
  • Finding gaps in assigned keys

1200-1336: LGTM!

Thorough test coverage for autoAssignHotkeyToCategory including the critical rollback scenario when saveAppData fails. The tests verify both success and failure paths.


1338-1415: LGTM!

Good coverage of formatActionLabel including all action types, category name resolution, fallback to IDs for missing categories, and legacy action handling.

src/ui/hotkeys.ts (3)

299-320: LGTM!

Clean implementation of findUnassignedNumberKey. The key ordering (1-9, then 0) is correct, and the check for modifiers.length === 0 ensures only unmodified number keys are considered.


327-356: Well-implemented optimistic update with rollback.

The implementation correctly:

  1. Checks for available keys first (early return)
  2. Stores original state before mutation
  3. Rolls back on save failure
  4. Returns boolean to indicate success

One minor consideration: hotkey_${Date.now()} could theoretically collide if called within the same millisecond, but this is extremely unlikely in the UI context where this is triggered by user actions.


358-411: LGTM!

The action prefix matching is correctly ordered - toggle_category_next_ is checked before toggle_category_ to avoid incorrect partial matches. The fallback to displaying the category ID when the category is not found provides good UX for edge cases (e.g., deleted categories).

@iomz iomz merged commit d503320 into dev Nov 25, 2025
4 checks passed
@iomz iomz deleted the feat/auto-assign-hotkeys-and-show-actions branch November 25, 2025 13:46
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.

feat: automatically assign a hot key when creating a category

1 participant