Skip to content

feat: Add theme module and enhance GUI styling#11

Merged
pl018 merged 1 commit intomasterfrom
gui-theme-improvements
Dec 18, 2025
Merged

feat: Add theme module and enhance GUI styling#11
pl018 merged 1 commit intomasterfrom
gui-theme-improvements

Conversation

@pl018
Copy link
Copy Markdown
Owner

@pl018 pl018 commented Dec 18, 2025

  • Add new theme.py module with Nord-inspired color palette
  • Update archive dialog with improved styling
  • Enhance tag editor widget appearance
  • Improve main window theme integration
  • Update CLAUDE.md with latest GUI development status

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added project archiving with ZIP export and archive filtering options
    • Added hard delete workflows with permanent removal capabilities
    • Introduced document discovery and browsing features
    • Enhanced tag management with improved editor interface
  • Style

    • Implemented dark theme system across the desktop application
    • Redesigned UI with improved typography, icons, and visual hierarchy
    • Enhanced document preview and table views with consistent theming

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

- Add new theme.py module with Nord-inspired color palette
- Update archive dialog with improved styling
- Enhance tag editor widget appearance
- Improve main window theme integration
- Update CLAUDE.md with latest GUI development status

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@bolt-new-by-stackblitz
Copy link
Copy Markdown

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Dec 18, 2025

Walkthrough

This pull request introduces archive and hard-delete features with ZIP archive creation and permanent database removal, implements a new dark theme system using TOKENS-based styling, adds new service layers (git_service, archive_service, docs_discovery_service), and significantly redesigns the desktop GUI with enhanced toolbars, typography, table delegation, and themed components throughout.

Changes

Cohort / File(s) Summary
Archive & Hard Delete Infrastructure
src/project_manager_cli/services/git_service.py, src/project_manager_cli/services/archive_service.py
New CLI service modules for Git repository utilities and archive creation operations
Archive Dialog & UI
src/project_manager_desktop/dialogs/archive_dialog.py, src/project_manager_desktop/dialogs/__init__.py
New dialog widget for archive operations with elevated PowerShell deletion, ZIP creation progress tracking, database updates, and enhanced error handling
Database & Core Models
src/core/database.py, src/core/models.py
Added archive_project() and get_archived_projects() methods; introduced DocFile model for document representation
Dark Theme System
src/project_manager_desktop/theme.py
New immutable ThemeTokens dataclass with predefined colors, radii, and state tokens; added build_qss() and apply_theme() functions for comprehensive QSS stylesheet generation and application
GUI Widgets & Components
src/project_manager_desktop/widgets/tag_editor.py, src/project_manager_desktop/widgets/__init__.py
New tag editor widget with TOKENS-based theme-driven styling, font manipulation, and role-based muted placeholders
Main Window Redesign
src/project_manager_desktop/window.py
Expanded UI with ProjectsTableDelegate for custom table rendering, typography primitives, redesigned toolbar with icons and action cluster, enhanced Docs tab with horizontal splitter and Markdown preview, TOKENS-based styling throughout
Documentation Discovery Service
src/project_manager_cli/services/docs_discovery_service.py
New shared service for document discovery across GUI and TUI layers
Application Setup & Dependencies
src/project_manager_desktop/main.py, pyproject.toml
Added theme application on app startup; extended GUI dependencies to support new features

Sequence Diagram

sequenceDiagram
    participant User
    participant ArchiveDialog
    participant FileSystem
    participant Database
    participant UI

    User->>ArchiveDialog: Initiate Archive
    ArchiveDialog->>FileSystem: Delete original directory<br/>(elevated PowerShell)
    activate FileSystem
    FileSystem-->>ArchiveDialog: Deletion complete
    deactivate FileSystem
    
    ArchiveDialog->>UI: Update progress: "Creating ZIP archive"
    ArchiveDialog->>FileSystem: Create ZIP from library
    activate FileSystem
    FileSystem-->>ArchiveDialog: ZIP created
    deactivate FileSystem
    
    ArchiveDialog->>UI: Update progress: "Updating database"
    ArchiveDialog->>Database: archive_project() with ZIP metadata
    activate Database
    Database-->>ArchiveDialog: Archive record stored
    deactivate Database
    
    ArchiveDialog->>UI: Show success state
    ArchiveDialog->>User: Archive complete
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Areas requiring extra attention:

  • Elevated PowerShell deletion logic in archive_dialog.py: Complex encoded command sequence with admin elevation handling, error capture, and timeout management
  • TOKENS-based styling integration: Widespread application across window.py requires verification that all color/state tokens are correctly applied and render consistently
  • ProjectsTableDelegate implementation in window.py: Custom item delegate for table cell rendering with font and color overrides
  • New service layer architecture: archive_service.py and git_service.py interactions with database and file system operations
  • Database schema migration: Archival fields and related methods in src/core/database.py
  • Theme system completeness: QSS generation in theme.py covering all widget types and ensuring dark/light mode compatibility

Poem

🐰 Hooray for archives in ZIPs so neat,
With tokens of color, styled sleek and sweet!
Hard deletions permanent, themes dark and bright,
The project manager hops into the night! 🌙✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 69.57% 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 pull request title accurately summarizes the main changes: adding a new theme module and enhancing GUI styling across multiple components (archive dialog, tag editor, main window, and theme integration).
✨ 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 gui-theme-improvements

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.

@claude
Copy link
Copy Markdown

claude Bot commented Dec 18, 2025

Pull Request Review: Theme Module and GUI Styling Enhancements

Overview

This PR successfully introduces a comprehensive theme system for the PySide6 desktop GUI, centralizing styling into a reusable module with Nord-inspired design tokens. The changes demonstrate good architectural thinking and improve code maintainability.


✅ Strengths

1. Architecture and Design

  • Excellent token system: The ThemeTokens dataclass provides a clean, type-safe way to manage design tokens
  • Separation of concerns: Moving theme logic to a dedicated module (theme.py) is the right approach
  • Reusability: The apply_theme() function with customizable tokens allows for easy theme variations
  • Consistent design language: Nord-inspired colors create a cohesive, professional appearance

2. Code Quality

  • Type hints: Good use of type annotations throughout (ThemeTokens, apply_theme())
  • Immutable design: Using @DataClass(frozen=True) prevents accidental token modification
  • Clear naming: Token names like bg_base, fg_muted, border_subtle are intuitive
  • Documentation: Inline comments in QSS explain design decisions

3. UI Improvements

  • Visual hierarchy: ProjectsTableDelegate adds proper information hierarchy with font weights and muted colors
  • Semantic variants: Button variants (primary, secondary, danger, warning) follow best practices
  • Consistent spacing: Updated margins/spacing (12px) across components
  • Better contrast: The new color palette improves readability significantly

⚠️ Issues and Concerns

1. Cross-Platform Font Handling (Medium Priority)

Location: theme.py:256

Issue: Hardcoding Segoe UI will fail on Linux/macOS where this font isn't available, falling back to unpredictable defaults.

Recommendation: Use system-appropriate fonts or Qt's built-in system font with platform detection.


2. PowerShell Security and Reliability (High Priority)

Location: archive_dialog.py:136-220

Issues:

  • Dependency on handle64.exe: Script now explicitly requires handle64.exe on PATH and will error if not found. This breaks the feature on systems without Sysinternals.
  • UAC Cancellation: If user cancels UAC prompt, function silently returns False without informing why
  • Timeout Increased: Timeout increased from 60s to 180s without justification in comments

Recommendation: Fall back gracefully when handle64.exe is missing, add better UAC cancellation messaging, and document timeout reasoning.


3. Incomplete Error Handling (Medium Priority)

Location: archive_dialog.py:282-308

Issue: The archive_btn is never re-enabled on error, leaving UI in inconsistent state.

Recommendation: Ensure UI state is always restored in finally block regardless of success/failure.


4. Minor Issues (Low Priority)

  • Magic numbers without explanation (theme.py:257, window.py:77-81, archive_dialog.py:209)
  • Very broad QSS selectors (QWidget selector affects all widgets globally)

🔍 Testing Recommendations

  1. Cross-platform testing on Linux/macOS (especially font rendering)
  2. Archive dialog with missing handle64.exe, UAC cancellation, locked files
  3. Tag editor styling verification
  4. Accessibility contrast ratio validation

🎯 Summary

Approve with minor changes recommended

This is a solid enhancement that significantly improves the GUI visual consistency and maintainability. Main concerns are cross-platform font compatibility and PowerShell error handling. None are blocking issues, but addressing them would make the feature more robust.

Estimated effort to address concerns: 1-2 hours

📊 Metrics

  • Lines added: 738
  • Lines deleted: 176
  • Net change: +562
  • Files modified: 6
  • New files: 1 (theme.py)

Great work on this PR! The theme system is a solid foundation for future UI enhancements.

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

Caution

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

⚠️ Outside diff range comments (3)
src/project_manager_desktop/window.py (2)

1377-1388: Path not escaped; potential command injection or failure with special characters.

Unlike archive_dialog.py which escapes single quotes, this method directly interpolates project_path into the PowerShell command. Paths containing quotes, backticks, or $ could cause failures or security issues.

🔎 Apply path escaping
+        # Escape for PowerShell double-quoted string
+        safe_path = project_path.replace('`', '``').replace('"', '`"').replace('$', '`$')
         # PowerShell command to kill processes holding handles and delete directory
-        ps_command = f'''$path="{project_path}"; if(Test-Path -LiteralPath $path){{ ...
+        ps_command = f'''$path="{safe_path}"; if(Test-Path -LiteralPath $path){{ ...

Alternatively, consider refactoring to share the escaping logic with archive_dialog.py and use single-quoted strings consistently.


1378-1378: Inconsistent Sysinternals tool reference: handle.exe vs handle64.exe.

archive_dialog.py uses handle64.exe while this method uses handle.exe. For consistency and to ensure the correct version runs on 64-bit Windows, consider standardizing on handle64.exe or using a helper that detects the available version.

CLAUDE.md (1)

270-270: Update status date to reflect current year (2025).

Line 270 states "Current Status (December 2024)" but the PR was created December 18, 2025. Update this to December 2025 to match the actual current date.

Also applies to: 274-274

🧹 Nitpick comments (7)
src/project_manager_desktop/widgets/tag_editor.py (2)

112-133: Consider extracting the bold font to a local variable with clearer scope.

The font f is defined for current_label and reused for available_label 20 lines later. This works but relies on implicit scope. Consider renaming to bold_font for clarity.

🔎 Suggested improvement
         current_label = QtWidgets.QLabel("Current Tags:")
-        f = current_label.font()
-        f.setBold(True)
-        current_label.setFont(f)
+        bold_font = current_label.font()
+        bold_font.setBold(True)
+        current_label.setFont(bold_font)
         layout.addWidget(current_label)
         ...
         available_label = QtWidgets.QLabel("Available Tags (click to add):")
-        available_label.setFont(f)
+        available_label.setFont(bold_font)

241-250: Hardcoded accent rgba values duplicate TOKENS.accent.

The rgba(91, 140, 255, ...) values correspond to TOKENS.accent (#5b8cff). While Qt stylesheets don't support hex-to-rgba conversion, consider documenting this relationship or extracting these derived colors as additional token fields if they're used frequently.

src/project_manager_desktop/theme.py (2)

255-258: Cross-platform font consideration.

"Segoe UI" is Windows-specific. On macOS/Linux, Qt will fall back to a default font, but the experience may be inconsistent. Consider specifying a font-family stack.

🔎 Suggested improvement
-    font = QtGui.QFont("Segoe UI")
+    # Prefer platform-native fonts: Segoe UI (Windows), SF Pro (macOS), system default (Linux)
+    font = QtGui.QFont()
+    font.setFamilies(["Segoe UI", "-apple-system", "BlinkMacSystemFont", "Ubuntu", "Cantarell", "sans-serif"])
     font.setPointSize(10)

Note: setFamilies() is available in Qt 5.13+. For older versions, the first font in the list that's available will be used.


230-249: Horizontal scrollbar styling may be needed for consistency.

Only QScrollBar:vertical is styled. If horizontal scrollbars appear (e.g., in tables or code previews), they'll use default styling. Consider adding matching horizontal scrollbar rules.

🔎 Add horizontal scrollbar styling
# After line 249, add:
QScrollBar:horizontal {{
  background: transparent;
  height: 12px;
  margin: 0px;
}}
QScrollBar::handle:horizontal {{
  background: rgba(154, 164, 178, 0.35);
  border-radius: 6px;
  min-width: 30px;
}}
QScrollBar::handle:horizontal:hover {{
  background: rgba(154, 164, 178, 0.5);
}}
QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal {{
  width: 0px;
}}
QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal {{
  background: transparent;
}}
src/project_manager_desktop/window.py (2)

197-204: Silent exception handling for optional icon decoration.

The try-except-pass pattern here is acceptable since icon loading is non-critical, but consider logging at debug level to aid troubleshooting if icons unexpectedly fail to load.

🔎 Optional: Add debug logging
         try:
             icon = QtGui.QIcon.fromTheme("edit-find")
             if icon.isNull():
                 icon = self.style().standardIcon(QtWidgets.QStyle.StandardPixmap.SP_FileDialogContentsView)
             act = self.search_input.addAction(icon, QtWidgets.QLineEdit.ActionPosition.LeadingPosition)
             act.setEnabled(False)
-        except Exception:
-            pass
+        except Exception as e:
+            # Icon decoration is non-critical; log and continue
+            import logging
+            logging.debug("Failed to add search icon: %s", e)

497-500: Consider using hasattr instead of try-except for attribute existence check.

If the concern is that projects_count might not be initialized, a guard check is cleaner than exception swallowing.

🔎 Suggested improvement
-            try:
-                self.projects_count.setText(f"({len(projects)})")
-            except Exception:
-                pass
+            if hasattr(self, 'projects_count'):
+                self.projects_count.setText(f"({len(projects)})")
CLAUDE.md (1)

281-339: Consider adding more detail about theme system implementation.

The PR title mentions "Add theme module and enhance GUI styling," and the AI summary references a "TOKENS-based styling system" with build_qss and apply_theme functions. However, the CLAUDE.md updates don't include details about the new src/project_manager_desktop/theme.py module or how the Nord-inspired theme system is integrated. Consider adding a brief section explaining the theming architecture for future Claude Code sessions.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 79aaf02 and e24e525.

📒 Files selected for processing (6)
  • CLAUDE.md (3 hunks)
  • src/project_manager_desktop/dialogs/archive_dialog.py (7 hunks)
  • src/project_manager_desktop/main.py (1 hunks)
  • src/project_manager_desktop/theme.py (1 hunks)
  • src/project_manager_desktop/widgets/tag_editor.py (6 hunks)
  • src/project_manager_desktop/window.py (16 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/project_manager_desktop/dialogs/archive_dialog.py (2)
src/project_manager_cli/services/archive_service.py (1)
  • ArchiveService (13-166)
src/project_manager_cli/services/git_service.py (1)
  • GitService (8-65)
src/project_manager_desktop/main.py (1)
src/project_manager_desktop/theme.py (1)
  • apply_theme (253-273)
src/project_manager_desktop/window.py (2)
src/project_manager_desktop/models.py (1)
  • ProjectsTableModel (22-127)
src/core/models.py (1)
  • DocFile (83-96)
🪛 markdownlint-cli2 (0.18.1)
CLAUDE.md

284-284: Strong style
Expected: asterisk; Actual: underscore

(MD050, strong-style)


284-284: Strong style
Expected: asterisk; Actual: underscore

(MD050, strong-style)

🪛 Ruff (0.14.8)
src/project_manager_desktop/dialogs/archive_dialog.py

205-205: subprocess call: check for execution of untrusted input

(S603)


206-206: Starting a process with a partial executable path

(S607)


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

(TRY300)


303-303: Do not catch blind exception: Exception

(BLE001)

src/project_manager_desktop/window.py

26-26: Unused noqa directive (non-enabled: N802)

Remove unused noqa directive

(RUF100)


203-204: try-except-pass detected, consider logging the exception

(S110)


203-203: Do not catch blind exception: Exception

(BLE001)


352-353: try-except-pass detected, consider logging the exception

(S110)


352-352: Do not catch blind exception: Exception

(BLE001)


499-500: try-except-pass detected, consider logging the exception

(S110)


499-499: Do not catch blind exception: Exception

(BLE001)

⏰ 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: claude-review
🔇 Additional comments (8)
src/project_manager_desktop/main.py (1)

18-22: LGTM!

The theme integration is correctly placed after QApplication creation and before MainWindow instantiation, ensuring all widgets inherit the styled palette and stylesheet.

src/project_manager_desktop/dialogs/archive_dialog.py (2)

61-65: LGTM!

The warning styling appropriately uses TOKENS.warning for the text color while using rgba variants for background and border, maintaining consistency with the theme system's semantic color usage.


146-180: Path escaping may be insufficient for PowerShell metacharacters.

The escaping path_ps = self.project_path.replace("'", "''") handles single quotes but PowerShell paths can contain other metacharacters like backticks (`) or $ which could cause issues or injection vectors in the inner script.

🔎 Consider additional escaping
-        path_ps = self.project_path.replace("'", "''")
+        # Escape both single quotes and backticks for PowerShell single-quoted strings
+        path_ps = self.project_path.replace("'", "''").replace("`", "``")

Alternatively, consider validating that project_path contains only safe filesystem characters before constructing the command.

Likely an incorrect or invalid review comment.

src/project_manager_desktop/theme.py (1)

8-40: Well-structured theme token system.

The frozen dataclass provides immutability and clear semantic naming. The token organization (surfaces, text, borders, brand/states, radii) follows established design system conventions.

src/project_manager_desktop/window.py (3)

19-42: LGTM! Clean table delegate implementation.

ProjectsTableDelegate effectively applies visual hierarchy by bolding the Name column and muting meta columns (Path, Tags, Updated). The palette manipulation for selected rows preserves readability.


942-1062: LGTM!

The markdown preview HTML generation effectively integrates TOKENS for consistent theming. The separation of body (margin: 0) and .doc container (padding: 16px) provides clean layout control.


1213-1230: LGTM!

The warning label styling consistently uses TOKENS.danger with appropriate rgba variants for backgrounds and borders, matching the theme system patterns established in theme.py.

CLAUDE.md (1)

450-455: No changes needed for model name validity.

o4-mini is OpenAI's latest small o-series model, released on April 16, 2025, to all ChatGPT users and via the Chat Completions API and Responses API. This is a distinct, valid model from gpt-4o-mini (which is in the gpt-4o series). The defensive comment "This is NOT a typo" is accurate—o4-mini is not a typo or unusual naming but an official OpenAI model identifier.

However, there is a documentation inaccuracy: the referenced file path src/project_manager_cli/config.py:81 does not exist in the repository. The actual model configuration is in src/core/config.py (line ~44) and src/core/config_manager.py.

Likely an incorrect or invalid review comment.

Comment thread CLAUDE.md
Comment on lines +281 to +302
**✅ ARCHIVE & HARD DELETE FEATURES COMPLETED** - Latest
- ✅ **Archive Project**: Backup projects to ZIP archives with library folder cleanup
- Git repository detection and uncommitted changes warning
- Automatic deletion of library folders (node_modules, venv, dist, build, __pycache__, etc.)
- ZIP archive creation with compression (excludes .git)
- Archives stored in `%APPDATA%\project-manager-cli\archives\`
- Archive naming: `{uuid}_{project-name}_YYYY-MM-DD.zip`
- Progress dialog with real-time feedback
- ✅ **Hard Delete**: Permanently remove project records from database
- Checkbox in delete dialog: "Permanently delete from database (cannot be undone)"
- Dynamic warning messages based on checkbox state
- Soft delete remains the default (enabled=0)
- ✅ **Archive Filtering**: Hide/show archived projects
- "📦 Show Archived" toggle button in toolbar
- Default view filters `enabled=1 AND archived=0`
- Database schema: Added `archived`, `archive_path`, `archive_date`, `archive_size_mb` columns
- ✅ **Directory Cleanup**: PowerShell-based physical directory deletion
- "Also delete project files from disk" checkbox in delete dialog
- "Delete original directory after archiving" checkbox in archive dialog
- Uses handle.exe to kill processes holding file locks
- Force-deletion with ownership takeover and permission grants
- CRITICAL warnings for destructive operations
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix markdown strong-style formatting to use asterisks.

The static analysis tool (markdownlint-cli2) flagged MD050 violations around lines 283-284. Markdown strong emphasis should use asterisks (**text**) instead of underscores (__text__). Review the formatting in this section and ensure consistency throughout.

🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

284-284: Strong style
Expected: asterisk; Actual: underscore

(MD050, strong-style)


284-284: Strong style
Expected: asterisk; Actual: underscore

(MD050, strong-style)

🤖 Prompt for AI Agents
In CLAUDE.md around lines 281 to 302, markdownlint flagged MD050 because strong
emphasis was applied using underscores; replace any strong-style underscores
(e.g., __text__) with asterisks (**text**) and ensure literal names that contain
underscores (like __pycache__) are not treated as emphasis—wrap those in
backticks (`__pycache__`) or escape the underscores. Update all occurrences in
this section to use **bold** with asterisks and use inline code for folder names
with underscores so the file passes markdownlint checks.

Comment thread src/project_manager_desktop/dialogs/archive_dialog.py
@pl018 pl018 merged commit abd7674 into master Dec 18, 2025
5 checks passed
@pl018 pl018 deleted the gui-theme-improvements branch December 18, 2025 11:01
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