Skip to content

More bug fixes for modern UI - #35

Merged
github-actions[bot] merged 10 commits into
mainfrom
more-bug-fixes
Jan 15, 2026
Merged

github-actions[bot] merged 10 commits into
mainfrom
more-bug-fixes

Conversation

@FaserF

@FaserF FaserF commented Jan 14, 2026 •

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • URL download in Analyzer, Winget manifest creator UI, Intune/Entra packager & uploader, GitHub addon install, app protocol handler, persistent notifications with optional system toasts, admin-elevation prompts, Winget search/details.
  • Enhancements

    • Large EN/DE i18n expansion, modernized UI/navigation and sidebar, dashboard/library/packaging UX improvements, asset/icon path updates, Windows toast integration, improved error/cleanup flows.
  • Tests

    • New UI, navigation, i18n integrity and interaction test suites.
  • Chores

    • CI auto-fix jobs disabled; README and .gitignore updated.

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

@FaserF FaserF self-assigned this Jan 14, 2026
@github-actions github-actions Bot added documentation Improvements or additions to documentation ci-cd backend labels Jan 14, 2026
@coderabbitai

coderabbitai Bot commented Jan 14, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Centralizes assets under src/switchcraft/assets, disables automated Ruff/PR-assistant fixes in CI, adds Windows protocol handler and protocol-driven startup, expands modern Flet UI with NavIndex and many localized views, extends services (addon GitHub install, persistent notifications, winget helpers), and adds extensive tests.

Changes

Cohort / File(s) Summary
CI / Tooling
\.github/workflows/lint.yml`, `.github/workflows/pr-assistant.yml`, `.github/workflows/test.yml`, `.github/workflows/tests-modern.yml`, `.gitignore``
Disabled Ruff auto-fix and PR-assistant auto-fix job, adjusted test job guards/matrix, removed tests-modern workflow, and added inspection/test artifacts to .gitignore.
Assets & Packaging
\README.md`, `docs/index.md`, `docs/.vitepress/config.mts`, `src/switchcraft/gui/app.py`, \switchcraft*.spec`, `switchcraft_legacy*.iss`, \tests/test_ui_startup.py``
Repointed image/favicon/icon paths from images/ to src/switchcraft/assets/ and updated packaging/installer specs; test path adjusted.
Core App & Protocols
\src/switchcraft/modern_main.py`, `src/switchcraft/utils/protocol_handler.py``
Added Windows protocol handler (register/unregister/parse/is_registered) and startup wiring: parse protocol args, auto-register if needed, and dispatch initial protocol action to the app.
GUI Core & Navigation
\src/switchcraft/gui_modern/app.py`, `src/switchcraft/gui_modern/nav_constants.py`, `src/switchcraft/gui_modern/controls/sidebar.py`, `src/switchcraft/gui_modern/utils/view_utils.py`, `src/switchcraft/gui_modern/utils/flet_compat.py``
Introduced NavIndex/NAV_CATEGORIES, navigation/back-history, view caching/lazy loading, ViewMixin snack helper, assets-dir resolution, and a Flet Tabs compatibility helper.
Views Modernization
\src/switchcraft/gui_modern/views/... ` (many files)`
Large i18n-driven refactors across many views (analyzer, home, dashboard, library, packaging_wizard, intune/intune_store, winget, wingetcreate, stack/ addon/ group/history/etc.): tabbed UIs, URL download flows, admin elevation prompts, NavIndex routing, ViewMixin usage, and many widget/layout updates.
Localization
\src/switchcraft/assets/lang/en.json`, `src/switchcraft/assets/lang/de.json``
Removed/renamed keys (Intune→Entra/Graph, removed winget_search_hint), and added hundreds of new i18n keys covering dashboards, stacks, scripts, Intune/Entra flows, Winget, notifications, and UI actions.
Services & Backend
\src/switchcraft/services/addon_service.py`, `src/switchcraft/services/history_service.py`, `src/switchcraft/services/notification_service.py`, `src/switchcraft/services/intune_service.py`, `src/switchcraft/services/ai_service.py``
Added AddonService.install_from_github, HistoryService.get_recent, persistent NotificationService (storage + system notify options), Intune Graph helpers (apps/groups/users), and enriched AI stub responses (i18n-aware).
Utilities & Models
\src/switchcraft/utils/winget.py`, `src/switchcraft/utils/config.py`, `src/switchcraft/utils/logging_handler.py`, `src/switchcraft/models.py`’`
Added winget search/details APIs, SwitchCraftConfig.set_secure_value, file logger initial write + level alignment, and InstallerInfo.to_dict serialization.
CLI & Fallbacks
\src/switchcraft/cli/commands.py`, `src/switchcraft/services/addon_service.py` (addon fallbacks)`
Hardened winget addon integration with defensive fallbacks to internal WingetHelper; AddonService gained GitHub-release installer flow.
Build Scripts
\scripts/build_release.ps1`, `switchcraft_.spec`, `switchcraft_modern.``
Added LocalDev switch, OS-aware process handling, post-build notifications/launch prompt, and updated icon asset references in specs.
Tests
\tests/**` (many new/updated files)`
Added i18n integrity tests, navigation/navigation_map/integrity tests, comprehensive modern UI interaction tests, startup test updates, addon/path fixes, and added page.open mocks where needed.

Sequence Diagram(s)

sequenceDiagram
    participant Launcher
    participant ModernMain
    participant ProtocolHandler
    participant ModernApp
    participant AddonService

    Launcher->>ModernMain: start(args / switchcraft:// URL)
    ModernMain->>ProtocolHandler: parse_protocol_url(URL)
    ProtocolHandler-->>ModernMain: action payload
    ModernMain->>ModernApp: instantiate & initialize services
    ModernApp->>AddonService: load/install dynamic addons
    AddonService-->>ModernApp: addon view/helper ready
    ModernApp->>ModernApp: map action -> NavIndex -> _switch_to_tab
    ModernApp-->>Launcher: render requested view
Loading
sequenceDiagram
    participant User
    participant ModernApp
    participant AnalyzerView
    participant AddonModule
    participant WingetHelper

    User->>ModernApp: open Analyzer
    ModernApp->>AnalyzerView: start_analysis(file/url)
    AnalyzerView->>AddonModule: try to obtain WingetHelper
    alt addon exposes WingetHelper.search_by_name
        AddonModule-->>AnalyzerView: winget_url via addon
    else
        AnalyzerView->>WingetHelper: internal search_by_name/search_packages
        WingetHelper-->>AnalyzerView: winget_url or None
    end
    AnalyzerView-->>ModernApp: display analysis results
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Poem

🐰
I hopped through folders, tidy and neat,
Assets nested snug where old paths meet.
NavIndex hums as tabs find their way,
Protocols wake the app to greet the day.
Keys in many tongues — carrots all around! 🥕

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.73% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The PR title 'More bug fixes for modern UI' is vague and overly broad, failing to convey the specific nature of the extensive changes across 50+ files. Consider a more descriptive title that captures the main focus, such as 'Refactor GUI navigation, add i18n support, and reorganize assets' or similar that reflects the primary objectives.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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.

coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (5)
tests/test_addons.py (1)

76-89: Test no longer tests backslash path handling as intended.

The test name test_extract_and_install_zip_backslashes, docstring, and comments all indicate this should test Windows-style backslash paths in ZIP entries. However, the paths now use forward slashes, so the backslash normalization logic is no longer exercised.

Either restore the backslash paths to actually test the edge case, or update the test name and comments to reflect what's being tested.

🔧 Option 1: Restore backslash paths to match test intent
         # We must force backslashes in arcname
         # Note: switchcraft_winget/utils/winget.py -> switchcraft_winget\utils\winget.py
         # But the content should be a valid pkg structure
-            z.writestr('switchcraft_winget/__init__.py', '')
-            z.writestr('switchcraft_winget/utils/__init__.py', '')
-            z.writestr('switchcraft_winget/utils/winget.py', 'print("ok")')
+            z.writestr('switchcraft_winget\\__init__.py', '')
+            z.writestr('switchcraft_winget\\utils\\__init__.py', '')
+            z.writestr('switchcraft_winget\\utils\\winget.py', 'print("ok")')
🔧 Option 2: Update test name and comments if forward slashes are intentional
-    def test_extract_and_install_zip_backslashes(self):
-        """Test extraction of zip with backslash paths (Windows style)."""
+    def test_extract_and_install_zip_paths(self):
+        """Test extraction of zip with standard paths."""
         import zipfile
         import io

         # Create a mock zip with backslash names
         bio = io.BytesIO()
         with zipfile.ZipFile(bio, 'w') as z:
-            # We must force backslashes in arcname
-            # Note: switchcraft_winget/utils/winget.py -> switchcraft_winget\utils\winget.py
-            # But the content should be a valid pkg structure
+            # Standard POSIX-style paths
             z.writestr('switchcraft_winget/__init__.py', '')
src/switchcraft/gui_modern/views/detection_tester_view.py (2)

172-200: Missing null check for winreg module.

The winreg module is lazily imported and may be None if the import failed (line 9-16). Accessing winreg.HKEY_LOCAL_MACHINE on line 175 without a null check will raise AttributeError if winreg is None.

Proposed fix
 def _check_registry(self, key_path, value_name, expected_value):
+    if winreg is None:
+        return False, "Registry check unavailable (winreg module not loaded)"
     # Parse HIVE
     key_path = key_path.upper()
     hive = winreg.HKEY_LOCAL_MACHINE

224-269: Missing null check for win32api module.

Similar to winreg, win32api is lazily imported and could be None. Line 230 calls win32api.GetFileVersionInfo() without verifying the module is available.

Proposed fix
 def _check_file_version(self, path, operator, target_version):
     if not Path(path).exists():
         return False, "File Not Found"
+    if win32api is None:
+        return False, "Version check unavailable (win32api module not loaded)"

     try:
         # Get file version
         info = win32api.GetFileVersionInfo(path, "\\")
tests/test_full_coverage.py (1)

95-101: pytest.importorskip is ineffective due to decorator ordering.

The @patch("winreg.OpenKey") decorator on line 95 executes before the test body runs. If winreg is unavailable, the patch will fail with ModuleNotFoundError before pytest.importorskip on line 97 has a chance to skip the test.

Proposed fix: Use skipif decorator instead
+import sys
+
+@pytest.mark.skipif(sys.platform != 'win32', reason="Windows-only test")
 `@patch`("winreg.OpenKey")
 def test_addon_detection(mock_open_key):
-    pytest.importorskip("winreg")
     """Test registry detection for advanced addon."""
src/switchcraft/gui_modern/views/helper_view.py (1)

73-77: Fix incorrect Flet BorderRadius API usage.

The code uses ft.BorderRadius.only(...) which is incorrect. The correct API is ft.border_radius.only(...) (lowercase). BorderRadius is a class, while border_radius is the helper module with the .only() method. This will raise an AttributeError at runtime.

Note: The same issue exists in src/switchcraft/gui_modern/views/dashboard_view.py.

🤖 Fix all issues with AI agents
In `@src/switchcraft/gui_modern/views/analyzer_view.py`:
- Around line 247-249: The temp directory (temp_dir) and temp_path are created
but not removed if the download fails; wrap the download/write flow in a
try/except/finally (or use tempfile.TemporaryDirectory) so that on any exception
or download failure you call shutil.rmtree(temp_dir) to remove the temp
directory (or let the TemporaryDirectory context auto-clean), and only keep the
temp_dir when the download succeeds and the file has been safely moved/used;
update the code around the temp_dir/temp_path creation and the download handling
to ensure cleanup in the failure branch.

In `@src/switchcraft/gui_modern/views/wingetcreate_view.py`:
- Around line 56-71: The Tab constructors in the ft.Tabs call use the
deprecated/removed text parameter which causes a runtime error in Flet 0.80.1+;
update both ft.Tab(...) instances to use label= instead of text= (e.g., replace
text=i18n.get("wingetcreate_new") or "New Manifest" and
text=i18n.get("wingetcreate_update") or "Update Manifest" with label=...) so the
tab display names render correctly with ft.Tab.

In `@tests/test_ui_interactions.py`:
- Around line 489-492: The test currently only logs a "CRITICAL" message when
tenant_field.on_change is missing, so the test can pass silently; change this to
a failing assertion by asserting tenant_field.on_change or calling pytest.fail
with a clear message instead of just logging. Update the block that checks
tenant_field.on_change (the code referencing tenant_field and on_change) to use
assert tenant_field.on_change, or import pytest and call pytest.fail("Entra
Tenant ID missing on_change handler!") when the condition is false, and ensure
pytest is imported at the top of tests/test_ui_interactions.py if not already.
- Around line 151-156: The test patches the wrong target: DashboardView imports
HistoryService into the dashboard_view module, so patching
switchcraft.services.history_service.HistoryService won't replace the
already-imported reference; update the test to patch the symbol where it's used
(e.g., patch("switchcraft.ui.dashboard_view.HistoryService") or
patch.object(dashboard_view, "HistoryService")) and keep the mocked return for
get_history (MockHistoryService.return_value.get_history.return_value = []).
This ensures DashboardView(mock_page) instantiates the patched HistoryService
instead of the real one.
♻️ Duplicate comments (11)
src/switchcraft/gui_modern/views/macos_wizard_view.py (1)

114-128: Use label instead of text for ft.Tab parameter.

This issue was previously identified and causes the CI pipeline failure. The ft.Tab component uses label as the parameter name, not text.

Proposed fix
         source_tabs = ft.Tabs(
             selected_index=0,
             animation_duration=300,
             on_change=on_source_change,
             tabs=[
                 ft.Tab(
-                    text=i18n.get("download_url") or "Download URL",
+                    label=i18n.get("download_url") or "Download URL",
                     icon=ft.Icons.LINK
                 ),
                 ft.Tab(
-                    text=i18n.get("local_file") or "Local File",
+                    label=i18n.get("local_file") or "Local File",
                     icon=ft.Icons.COMPUTER
                 )
             ]
         )
src/switchcraft/gui_modern/views/script_upload_view.py (1)

61-80: Replace text with label parameter in ft.Tab() constructor.

This issue was previously flagged: Flet 0.80.1+ uses label instead of text for the Tab display name. The current code will cause: Tab.__init__() got an unexpected keyword argument 'text'.

🐛 Proposed fix
         t = ft.Tabs(
             selected_index=0,
             animation_duration=300,
             expand=True,
             on_change=on_change,
             tabs=[
                 ft.Tab(
-                    text=i18n.get("tab_platform_scripts") or "Platform Scripts",
+                    label=i18n.get("tab_platform_scripts") or "Platform Scripts",
                     icon=ft.Icons.TERMINAL
                 ),
                 ft.Tab(
-                    text=i18n.get("tab_remediations") or "Remediations",
+                    label=i18n.get("tab_remediations") or "Remediations",
                     icon=ft.Icons.HEALING
                 ),
                 ft.Tab(
-                    text=i18n.get("tab_github_import") or "GitHub Import",
+                    label=i18n.get("tab_github_import") or "GitHub Import",
                     icon=ft.Icons.CODE
                 )
             ]
         )
src/switchcraft/gui_modern/views/library_view.py (1)

286-292: os.startfile is Windows-only and will raise AttributeError on macOS/Linux.

This issue was previously flagged. The code will crash on non-Windows platforms.

🐛 Proposed cross-platform fix
     def _open_folder(self, path):
         """Open the folder containing the file."""
         try:
+            import sys
+            import subprocess
             folder = os.path.dirname(path)
-            os.startfile(folder)
+            if sys.platform == 'win32':
+                os.startfile(folder)
+            elif sys.platform == 'darwin':
+                subprocess.run(['open', folder])
+            else:
+                subprocess.run(['xdg-open', folder])
         except Exception as ex:
             logger.error(f"Failed to open folder: {ex}")
src/switchcraft/gui_modern/views/wingetcreate_view.py (2)

496-505: Windows-only subprocess.STARTUPINFO lacks platform guard.

This issue was previously flagged. subprocess.STARTUPINFO() and STARTF_USESHOWWINDOW are Windows-only and will raise AttributeError on macOS/Linux.

The same issue exists at lines 585-586 (_update_manifest) and lines 635-636 (_validate_manifest).

🛠️ Suggested fix with platform guard
+                import sys
+                if sys.platform == "win32":
+                    startupinfo = subprocess.STARTUPINFO()
+                    startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
+                else:
+                    startupinfo = None
-                startupinfo = subprocess.STARTUPINFO()
-                startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW

                 result = subprocess.run(
                     cmd,
                     capture_output=True,
                     text=True,
                     startupinfo=startupinfo,
                     timeout=120
                 )

Apply this pattern to all three locations: _generate_new_manifest, _update_manifest, and _validate_manifest.


664-669: os.startfile is Windows-only.

This will raise AttributeError on macOS/Linux. Apply the same cross-platform fix pattern suggested for library_view.py.

🐛 Proposed cross-platform fix
     def _open_manifest_dir(self, e):
         """Open the manifest directory in file explorer."""
         try:
-            os.startfile(str(self.manifest_dir))
+            import sys
+            import subprocess as sp
+            if sys.platform == 'win32':
+                os.startfile(str(self.manifest_dir))
+            elif sys.platform == 'darwin':
+                sp.run(['open', str(self.manifest_dir)])
+            else:
+                sp.run(['xdg-open', str(self.manifest_dir)])
         except Exception as ex:
             logger.error(f"Failed to open manifest dir: {ex}")
src/switchcraft/gui_modern/views/analyzer_view.py (1)

170-188: Tab construction uses non-standard nested structure.

The ft.Tabs construction here uses content=ft.TabBar(tabs=[...], on_change=...) with a length parameter, which is an unusual pattern. Additionally, a past review flagged that label= should be used instead of text= for ft.Tab. The current code uses label= which is correct, but the overall Tabs structure with nested TabBar may cause issues.

Flet Tabs component API ft.Tabs with TabBar content parameter
src/switchcraft/gui_modern/views/intune_view.py (3)

36-53: Pipeline failure: Tab.__init__() got unexpected keyword argument text.

The past review flagged this issue and it appears to still be present. The ft.Tab constructor in recent Flet versions doesn't accept text as a keyword argument - use positional argument or label= instead.

🐛 Proposed fix
         self.tabs = ft.Tabs(
             selected_index=0,
             animation_duration=300,
             tabs=[
                 ft.Tab(
-                    text=i18n.get("tab_packager") or "Packager",
+                    i18n.get("tab_packager") or "Packager",
                     icon=ft.Icons.INVENTORY_2,
                     content=self._build_packager_tab()
                 ),
                 ft.Tab(
-                    text=i18n.get("tab_uploader") or "Uploader & Update",
+                    i18n.get("tab_uploader") or "Uploader & Update",
                     icon=ft.Icons.CLOUD_UPLOAD,
                     content=self._build_uploader_tab()
                 ),
             ],
             expand=True
         )

330-334: subprocess.run with shell=True poses command injection risk.

This issue was flagged in a past review and is still present. If output_file contains shell metacharacters, this could lead to command injection.

🔒️ Proposed secure fix
                 def open_folder(e):
                     import subprocess
-                    if os.name == 'nt': subprocess.run(f'explorer /select,"{output_file}"', shell=True)
+                    if os.name == 'nt':
+                        subprocess.run(['explorer', f'/select,{output_file}'])
                     dlg.open = False
                     self.app_page.update()

413-418: Bare except: pass swallows all exceptions silently.

This issue was flagged in a past review. At minimum, log the exception for debugging purposes.

♻️ Proposed fix
     def _show_snack(self, msg, color="GREEN"):
         try:
             self.app_page.snack_bar = ft.SnackBar(ft.Text(msg), bgcolor=color)
             self.app_page.snack_bar.open = True
             self.app_page.update()
-        except: pass
+        except Exception as ex:
+            logger.debug(f"Failed to show snack: {ex}")
src/switchcraft/gui_modern/views/settings_view.py (1)

709-715: os.execv restart pattern will fail on Windows with PyInstaller builds.

This issue was flagged in a past review. The os.execv approach doesn't work properly on Windows, especially with PyInstaller. The codebase has a working pattern in crash_view.py that uses subprocess.Popen instead.

🔒️ Proposed fix using subprocess pattern
         def do_restart(e):
             dlg.open = False
             self.app_page.update()
-            # Request restart
             import sys
-            import os
-            os.execv(sys.executable, [sys.executable] + sys.argv)
+            import subprocess
+            
+            try:
+                if getattr(sys, 'frozen', False):
+                    # PyInstaller build
+                    subprocess.Popen([sys.executable] + sys.argv[1:])
+                else:
+                    subprocess.Popen([sys.executable] + sys.argv)
+                
+                # Close current instance
+                if hasattr(self.app_page, 'window'):
+                    self.app_page.window.destroy()
+                else:
+                    sys.exit(0)
+            except Exception as ex:
+                self._show_snack(f"Restart failed: {ex}", "RED")
src/switchcraft/gui_modern/app.py (1)

455-476: Potential race condition: UI updates from background thread without safety checks.

This issue was flagged in a past review. The _base_install background thread directly modifies dialog content (content.controls, dlg.actions) and calls dlg.update(). If the user closes the dialog before the thread completes, this could cause errors.

♻️ Suggested improvement
             def _base_install():
                 # Install Advanced
                 success, msg = self.addon_service.install_from_github("advanced")
 
-                # UI Update needs to happen on loop? Flet is thread-safe for simple updates usually
-                if success:
+                try:
+                    if not dlg.open:
+                        return  # Dialog was closed
+                    if success:
                          # Update Dialog to ask for Optional
                          content.controls.clear()
                          content.controls.append(ft.Icon(ft.Icons.CHECK_CIRCLE, color="GREEN", size=48))
                          # ... rest of success handling
-                else:
+                    else:
                          content.controls.append(ft.Text(f"Failed to install base: {msg}", color="RED"))
                          dlg.actions.clear()
                          dlg.actions.append(ft.TextButton("Close", on_click=close_wizard))
                          dlg.update()
+                except Exception as ex:
+                    logger.warning(f"Could not update first-run dialog: {ex}")
🧹 Nitpick comments (26)
src/switchcraft/gui_modern/views/macos_wizard_view.py (1)

266-271: Consider sanitizing name to prevent shell script issues.

The app name is directly interpolated into the shell script. If a user enters a name containing shell metacharacters (e.g., "; rm -rf /;), it could produce malformed or dangerous scripts. While this is an internal tool, sanitizing input improves robustness.

Suggested sanitization
# Before using in script template
import re
safe_name = re.sub(r'[^a-zA-Z0-9 _.-]', '', name)
src/switchcraft/modern_main.py (2)

261-279: Sub-action parameter from protocol URL is not utilized.

The parse_protocol_url function returns a sub key for URLs like switchcraft://settings/updates, but the action handling only checks the top-level action. For example, switchcraft://settings/updates would navigate to settings (not updates) since _INITIAL_ACTION.get("sub") is never used.

Consider whether this is intentional or if sub-action routing should be added:

♻️ Suggested enhancement for sub-action handling
         if _INITIAL_ACTION:
             action = _INITIAL_ACTION.get("action", "home")
+            sub_action = _INITIAL_ACTION.get("sub")
             from switchcraft.gui_modern.nav_constants import NavIndex

             action_map = {
                 "notifications": lambda: app._toggle_notification_drawer(None),
                 "settings": lambda: app.goto_tab(NavIndex.SETTINGS),
                 "updates": lambda: app.goto_tab(NavIndex.SETTINGS_UPDATES),
                 "analyzer": lambda: app.goto_tab(NavIndex.ANALYZER),
                 "home": lambda: app.goto_tab(NavIndex.HOME),
             }

+            # Handle settings sub-actions
+            if action == "settings" and sub_action:
+                sub_map = {
+                    "updates": lambda: app.goto_tab(NavIndex.SETTINGS_UPDATES),
+                }
+                handler = sub_map.get(sub_action, action_map.get(action))
+            else:
+                handler = action_map.get(action)
-            handler = action_map.get(action)
             if handler:

267-267: Add a public API method for notification drawer to match other action handlers.

The _toggle_notification_drawer method is private, while other similar protocol actions use the public app.goto_tab(NavIndex.XXX) API (lines 268–271). Consider creating a public method (e.g., open_notifications()) to maintain consistency and avoid coupling to internal implementation details.

src/switchcraft/gui_modern/views/detection_tester_view.py (1)

290-296: PowerShell script execution lacks timeout.

The subprocess.run call has no timeout parameter. A malicious or poorly written script could hang indefinitely, blocking the UI thread callback.

Proposed fix
-            res = subprocess.run(cmd, capture_output=True, text=True, startupinfo=startupinfo)
+            res = subprocess.run(cmd, capture_output=True, text=True, startupinfo=startupinfo, timeout=60)

You may also want to catch subprocess.TimeoutExpired in the exception handler.

src/switchcraft/gui_modern/views/group_manager_view.py (1)

131-155: Consider using i18n's built-in format support.

The error handling improvements are valuable—differentiating permission (403) and authentication (401) errors provides better UX. However, lines 136 and 148 use manual str.replace("{permissions}", ...) instead of leveraging i18n.get()'s native **kwargs formatting support (as shown in the relevant snippet from i18n.py).

Proposed improvement
-                error_msg = i18n.get("graph_permission_error") or "Missing Graph API permissions: {permissions}"
-                error_msg = error_msg.replace("{permissions}", missing_perms)
+                error_msg = i18n.get("graph_permission_error", permissions=missing_perms) or f"Missing Graph API permissions: {missing_perms}"

This approach is cleaner and consistent with the i18n module's design.

src/switchcraft/gui_modern/views/stack_manager_view.py (1)

292-335: Deployment is a placeholder - consider adding a TODO or logging.

The _execute_deploy method only shows a snack message but doesn't perform actual deployment. While this is acceptable for scaffolding, consider adding a # TODO comment or logging to indicate this needs implementation.

Would you like me to open an issue to track the implementation of the actual deployment logic?

src/switchcraft/gui_modern/views/script_upload_view.py (1)

508-518: Placeholder implementations are acceptable for scaffolding.

These placeholder methods correctly show feedback to users. Consider adding TODO comments to track the implementation.

src/switchcraft/gui_modern/views/winget_view.py (2)

240-245: Pluralization uses positional format but i18n.get supports kwargs.

The format string uses positional {0} but i18n.get supports named kwargs for formatting. This works, but consider using .format(count=count) with a {count} placeholder for better readability and consistency with the i18n API.

♻️ Suggested improvement
-        self.results_count.value = (i18n.get("apps_found") or "Found {0} apps").format(count) if count != 1 else (i18n.get("app_found") or "Found 1 app")
+        self.results_count.value = (i18n.get("apps_found", count=count) or f"Found {count} apps") if count != 1 else (i18n.get("app_found") or "Found 1 app")

576-612: Admin elevation flow duplicates pattern from analyzer_view.py - consider extracting to shared utility.

The admin check and elevation logic (IsUserAnAdmin, ShellExecuteW with runas, sys.exit) is nearly identical to the pattern in analyzer_view.py (lines 803-837). This duplication increases maintenance burden.

Additionally, ctypes is imported inside the function but used later at line 598 without re-importing after the except block scope ends - this works because ctypes is already imported at module level in some views but not explicitly here.

♻️ Suggested improvement - extract to utility

Consider creating a shared utility function:

# In a new file like utils/admin_helper.py
def check_admin() -> bool:
    """Check if running with admin privileges."""
    try:
        import ctypes
        return ctypes.windll.shell32.IsUserAnAdmin() != 0
    except Exception:
        return False

def restart_as_admin() -> bool:
    """Restart the application with admin privileges. Returns False on failure."""
    try:
        import ctypes
        import sys
        executable = sys.executable
        params = f'"{sys.argv[0]}"'
        if len(sys.argv) > 1:
            params += " " + " ".join(f'"{a}"' for a in sys.argv[1:])
        ctypes.windll.shell32.ShellExecuteW(None, "runas", executable, params, None, 1)
        sys.exit(0)
    except Exception:
        return False
src/switchcraft/gui_modern/views/analyzer_view.py (3)

219-275: URL download lacks SSL certificate verification control and has potential security considerations.

The URL download implementation:

  1. Accepts any HTTP/HTTPS URL without domain validation
  2. Downloads executable files directly
  3. The filename derivation from URL (line 243) could be exploited with crafted URLs

Consider adding warnings for HTTP (non-HTTPS) URLs and potentially limiting to known safe domains or showing a security warning.

🔒️ Suggested security improvements
         # Validate URL
         if not url.startswith(("http://", "https://")):
             self._show_snack("Invalid URL format", "RED")
             return
+        
+        # Warn about non-HTTPS
+        if url.startswith("http://") and not url.startswith("https://"):
+            self._show_snack(
+                i18n.get("warning_http_insecure") or "Warning: HTTP is insecure. Consider using HTTPS.",
+                "ORANGE"
+            )

1006-1013: Debug print statements should be removed or converted to logger calls.

The print(f"DEBUG: ...") statements at lines 1007 and 1013 should use the existing logger instead. Debug prints can clutter stdout and are not controllable via logging configuration.

♻️ Proposed fix
     def _show_snack(self, msg, color="GREEN"):
-        print(f"DEBUG: Showing Snack: {msg}")
+        logger.debug(f"Showing Snack: {msg}")
         try:
             self.app_page.snack_bar = ft.SnackBar(ft.Text(msg), bgcolor=color)
             self.app_page.snack_bar.open = True
             self.app_page.update()
         except Exception as e:
-             print(f"DEBUG: Failed to show snack: {e}")
+             logger.debug(f"Failed to show snack: {e}")

803-837: Admin elevation flow is duplicated from winget_view.py.

This is the same admin check and elevation pattern as in winget_view.py (lines 576-612). Consider extracting to a shared utility to reduce duplication and ensure consistent behavior across views.

src/switchcraft/gui_modern/views/intune_view.py (2)

5-5: Unused import: time is imported but never used.

The time module is imported at line 5 but there's no usage of it in the file.

♻️ Proposed fix
-import time

420-422: Empty method _open_explorer_select should be removed or implemented.

The method body is just pass with a comment that it's "kept for methods that might call it". If it's not being called, it should be removed. If it is being called, it should be implemented.

src/switchcraft/gui_modern/app.py (2)

1065-1113: Windows toast notification logic is comprehensive but complex.

The toast notification handling is well-implemented with:

  • Proper gating on WINOTIFY_AVAILABLE
  • Deduplication via _last_notif_id
  • Different actions for update vs. regular notifications
  • Appropriate audio settings

However, the nested conditionals (lines 1091-1106) add complexity. Consider extracting toast creation to a helper method.


1007-1009: Dynamic addon index calculation uses magic number 21.

The calculation dynamic_idx = idx - 21 uses a hardcoded value. If NavIndex is updated or new navigation items are added, this offset could become incorrect. Consider deriving this from NavIndex constants.

♻️ Suggested improvement
         else:
-            # Dynamic Addons (start at idx 21)
-            dynamic_idx = idx - 21
+            # Dynamic Addons (start after last NavIndex constant)
+            DYNAMIC_ADDON_START = 21  # Should match NavIndex.DYNAMIC_ADDONS_START or similar
+            dynamic_idx = idx - DYNAMIC_ADDON_START

Or better, add a constant to nav_constants.py:

class NavIndex:
    # ... existing constants ...
    DYNAMIC_ADDONS_START = 21
tests/test_navigation_map.py (4)

43-50: Consider removing or condensing these design notes.

These inline comments explain the rationale for patching in the test rather than the fixture. While useful during development, they add noise. Consider moving to a brief docstring or removing entirely since the test implementation already demonstrates the correct approach.


140-146: Dead code: tuple handling branch is unreachable.

The expected_views dictionary contains only class types, never tuples. This isinstance(expected_type, tuple) branch will never execute.

♻️ Remove dead code
                 expected_type = expected_views.get(idx)
 
-                if isinstance(expected_type, tuple):
-                     # Handle placeholders (Type, Value)
-                     t, val = expected_type
-                     assert isinstance(view_instance, t), f"Index {idx} expected {t}, got {type(view_instance)}"
-                     if isinstance(view_instance, ft.Text):
-                          # Allow "Unknown Tab" if expected?
-                          pass
-                else:
-                     # Real View
+                # Real View

116-118: Consider using pytest -v or capsys instead of print statements.

Print statements work but can clutter test output. Use pytest's verbose mode (-v) or capsys fixture for cleaner debugging output that integrates with pytest's reporting.


160-163: Consider using elif or a dictionary for tab index assertions.

Multiple independent if statements all execute even when only one can match. Using elif or a mapping would be cleaner.

♻️ Cleaner pattern with dictionary lookup
settings_tab_map = {2: 1, 3: 2, 4: 3, 13: 0}
if idx in settings_tab_map:
    expected_tab = settings_tab_map[idx]
    assert view_instance.initial_tab_index == expected_tab, f"Index {idx} should be Settings Tab {expected_tab}"
tests/test_ui_interactions.py (6)

20-28: Remove duplicate page.open assignment.

page.open is assigned twice (lines 20 and 28). The second assignment overwrites the first.

♻️ Remove redundant line
     page.open = MagicMock() # Fix for Flet 0.21+ dialogs
 
     # Mock window object
     page.window = MagicMock()
     page.theme_mode = ft.ThemeMode.DARK
     page.padding = 10
-
-    # Mock open for dialogs (newer Flet API)
-    page.open = MagicMock()
     return page

51-58: Remove or implement the TabBar handling block.

This code block iterates over tabs but does nothing (pass). Either remove the dead code or implement actual tab content traversal if needed.

♻️ Remove dead code
     elif hasattr(control, "content") and control.content:
         buttons.extend(find_buttons(control.content))
 
-    # Handle TabBar tabs
-    if isinstance(control, ft.TabBar) and control.tabs:
-        for tab in control.tabs:
-             # Tabs usually have content if they are TabBarView, but Tab control has content/label
-             # We just traverse Tab children? Tab doesn't have children usually unless it stores content
-             # Wait, Tab has 'content' or 'icon' etc.
-             pass
-
     return buttons

64-71: File-based logging is non-standard for pytest.

Writing to test_result.log creates side effects and may cause issues in CI/parallel test runs. Consider using pytest's capfd fixture or Python's logging module configured with pytest's caplog.

♻️ Alternative using capsys
def test_settings_view_buttons(mock_page, capsys):
    """Test all buttons in SettingsView."""
    
    def log(msg):
        print(msg)  # Will be captured by capsys
    
    # ... rest of test ...
    
    # At end, if needed:
    captured = capsys.readouterr()
    # captured.out contains all printed output

269-270: Comment contradicts actual behavior.

The comment says "Don't fail immediately, try others?" but the code immediately calls pytest.fail. Remove the misleading comment.

♻️ Remove contradictory comment
                 except Exception as ex:
                     log(f"CRITICAL: Button '{label}' crashed: {ex}")
                     import traceback
                     log(traceback.format_exc())
-                    # Don't fail immediately, try others? No, fail is better to fix one by one.
                     pytest.fail(f"Button '{label}' crashed: {ex}")

290-292: Consider importing LibraryView at module level.

Importing inside the test function works but is non-standard. If this is intentional to ensure patches are applied before import, document the reason.


408-435: Consider consolidating with module-level find_buttons.

This local helper is similar to the module-level find_buttons but adds TextField support. Consider extending find_buttons with a parameter for additional control types, or create a second module-level helper.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 31b4ff8 and 9179804.

📒 Files selected for processing (25)
  • .github/workflows/tests-modern.yml
  • .gitignore
  • src/switchcraft/gui_modern/app.py
  • src/switchcraft/gui_modern/views/addon_manager_view.py
  • src/switchcraft/gui_modern/views/analyzer_view.py
  • src/switchcraft/gui_modern/views/dashboard_view.py
  • src/switchcraft/gui_modern/views/detection_tester_view.py
  • src/switchcraft/gui_modern/views/group_manager_view.py
  • src/switchcraft/gui_modern/views/helper_view.py
  • src/switchcraft/gui_modern/views/intune_store_view.py
  • src/switchcraft/gui_modern/views/intune_view.py
  • src/switchcraft/gui_modern/views/library_view.py
  • src/switchcraft/gui_modern/views/macos_wizard_view.py
  • src/switchcraft/gui_modern/views/packaging_wizard_view.py
  • src/switchcraft/gui_modern/views/script_upload_view.py
  • src/switchcraft/gui_modern/views/settings_view.py
  • src/switchcraft/gui_modern/views/stack_manager_view.py
  • src/switchcraft/gui_modern/views/winget_view.py
  • src/switchcraft/gui_modern/views/wingetcreate_view.py
  • src/switchcraft/modern_main.py
  • tests/test_addons.py
  • tests/test_full_coverage.py
  • tests/test_navigation.py
  • tests/test_navigation_map.py
  • tests/test_ui_interactions.py
💤 Files with no reviewable changes (1)
  • .github/workflows/tests-modern.yml
🚧 Files skipped from review as they are similar to previous changes (4)
  • .gitignore
  • src/switchcraft/gui_modern/views/packaging_wizard_view.py
  • tests/test_navigation.py
  • src/switchcraft/gui_modern/views/dashboard_view.py
🧰 Additional context used
🧬 Code graph analysis (11)
src/switchcraft/gui_modern/views/intune_store_view.py (4)
src/switchcraft/gui_modern/nav_constants.py (1)
  • NavIndex (13-47)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/gui_modern/app.py (1)
  • goto_tab (756-764)
src/switchcraft/gui_modern/views/intune_view.py (1)
  • _show_snack (413-418)
src/switchcraft/gui_modern/views/group_manager_view.py (3)
src/switchcraft/gui_modern/nav_constants.py (1)
  • NavIndex (13-47)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/gui_modern/app.py (1)
  • goto_tab (756-764)
src/switchcraft/modern_main.py (2)
src/switchcraft/utils/protocol_handler.py (3)
  • register_protocol_handler (22-96)
  • parse_protocol_url (134-157)
  • is_protocol_registered (160-173)
src/switchcraft/gui_modern/app.py (2)
  • ModernApp (25-1145)
  • goto_tab (756-764)
src/switchcraft/gui_modern/views/stack_manager_view.py (4)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/gui_modern/views/addon_manager_view.py (1)
  • _show_snack (162-168)
src/switchcraft/gui_modern/views/intune_store_view.py (1)
  • _show_snack (207-213)
src/switchcraft/gui_modern/views/winget_view.py (1)
  • _show_snack (666-672)
src/switchcraft/gui_modern/views/macos_wizard_view.py (2)
src/switchcraft/gui_modern/utils/file_picker_helper.py (2)
  • FilePickerHelper (5-85)
  • pick_file (12-38)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/gui_modern/views/script_upload_view.py (2)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/gui_modern/views/intune_view.py (1)
  • _show_snack (413-418)
src/switchcraft/gui_modern/views/winget_view.py (4)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/gui_modern/views/analyzer_view.py (2)
  • on_restart_confirm (811-826)
  • _show_snack (1006-1013)
src/switchcraft/gui_modern/views/intune_store_view.py (1)
  • _show_snack (207-213)
src/switchcraft/gui_modern/views/intune_view.py (1)
  • _show_snack (413-418)
tests/test_ui_interactions.py (3)
src/switchcraft/gui_modern/views/dashboard_view.py (2)
  • DashboardView (7-174)
  • did_mount (52-53)
src/switchcraft/services/history_service.py (1)
  • get_history (22-35)
src/switchcraft/gui_modern/views/library_view.py (1)
  • did_mount (90-91)
src/switchcraft/gui_modern/views/settings_view.py (4)
tests/test_full_coverage.py (1)
  • intune_service (54-55)
src/switchcraft/services/intune_service.py (2)
  • IntuneService (16-736)
  • authenticate (147-164)
src/switchcraft/utils/config.py (4)
  • get_value (31-88)
  • set_user_preference (175-216)
  • get_secure_value (219-273)
  • set_secret (291-305)
src/switchcraft/utils/i18n.py (2)
  • get (143-170)
  • set_language (136-141)
src/switchcraft/gui_modern/views/library_view.py (3)
src/switchcraft/gui_modern/nav_constants.py (1)
  • NavIndex (13-47)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/utils/config.py (1)
  • get_value (31-88)
src/switchcraft/gui_modern/views/analyzer_view.py (5)
src/switchcraft/gui_modern/utils/file_picker_helper.py (2)
  • FilePickerHelper (5-85)
  • pick_file (12-38)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/gui_modern/views/macos_wizard_view.py (1)
  • _show_snack (361-367)
tests/test_full_coverage.py (1)
  • intune_service (54-55)
src/switchcraft/services/intune_service.py (1)
  • create_intunewin (59-143)
⏰ 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). (2)
  • GitHub Check: test-backend / test-backend (windows-latest, 3.14)
  • GitHub Check: test-backend / test-backend (windows-latest, 3.13)
🔇 Additional comments (44)
src/switchcraft/gui_modern/views/macos_wizard_view.py (5)

21-32: LGTM!

Class initialization correctly sets up state for both URL and local file sources, and the new local_file_path attribute supports the dual-source workflow.


34-55: LGTM!

The i18n integration with fallback strings ensures graceful degradation when translations are missing.


130-202: LGTM!

The two-step wizard layout with clear visual separation and i18n-enabled labels provides a good user experience.


204-219: LGTM!

Good use of FilePickerHelper for cross-platform file selection. The auto-fill logic from filename improves UX by reducing manual input.


319-359: LGTM!

Proper use of background threading for the Intune upload prevents UI blocking. The credential validation and status feedback provide good user experience.

src/switchcraft/modern_main.py (3)

81-85: LGTM!

The protocol handler imports are properly placed within the existing try-except block, ensuring that any import failures are captured in _IMPORT_ERROR and handled gracefully downstream.


92-107: LGTM!

The guard at line 94 correctly addresses the previous review concern about NameError when imports fail. The protocol URL parsing logic handles both --protocol <url> and direct switchcraft:// argument formats with appropriate exception handling.


348-370: Button styling is consistent and appropriate.

The use of ft.Button with explicit ButtonStyle for colors is correct. The four buttons in the crash UI row maintain consistent styling (blue for actions, red for close), which aligns with UX best practices.

src/switchcraft/gui_modern/views/helper_view.py (1)

37-43: Verify ft.Button property compatibility.

Same concern as in detection_tester_view.py: bgcolor and color may need to be passed via ft.ButtonStyle rather than as direct properties on ft.Button.

src/switchcraft/gui_modern/views/addon_manager_view.py (2)

30-38: LGTM with API verification note.

The button changes are consistent with the broader UI modernization. The delete_btn correctly initializes with disabled=True and binds on_click=self._confirm_delete. Same bgcolor/color property verification applies as noted in other files.


155-156: Dialog action button updated consistently.

The delete confirmation dialog action button is updated to ft.Button, maintaining consistency with the rest of the view.

src/switchcraft/gui_modern/views/group_manager_view.py (6)

3-3: LGTM!

Import of NavIndex enables type-safe navigation constants throughout the view.


32-36: Button component updated consistently.

The "Go to Settings" button uses ft.Button with explicit styling, consistent with the UI modernization effort.


51-68: i18n integration and button updates look good.

Search field label and action buttons properly use i18n.get() with fallback defaults. The delete button correctly starts disabled with the confirmation handler bound.


234-234: Dialog create button updated.

Consistent with other dialog button changes in the PR.


269-269: Dialog delete button updated.

Consistent with the delete confirmation pattern in addon_manager_view.py.


292-306: NavIndex-based navigation is well implemented.

Using NavIndex.SETTINGS_GRAPH instead of a magic number improves maintainability. The fallback logic searching for app_ref with goto_tab is a reasonable defensive pattern.

src/switchcraft/gui_modern/views/detection_tester_view.py (1)

67-73: No changes needed—ft.Button supports bgcolor and color directly.

The migration from ft.ElevatedButton to ft.Button is valid. Flet's ft.Button accepts bgcolor and color as direct control properties; they are not required to be passed via ft.ButtonStyle. The codebase consistently uses this pattern across multiple views without issues.

Likely an incorrect or invalid review comment.

src/switchcraft/gui_modern/views/stack_manager_view.py (3)

1-36: LGTM!

The imports and UI component initialization are well-structured. The i18n integration with fallback defaults is correctly implemented, and the TextField components have appropriate styling with border_radius.


37-158: LGTM!

The _build_ui method creates a clean two-panel layout with proper padding, borders, and styling. The description block and panel organization follow good UI practices. All UI strings are properly localized via i18n.get() with sensible fallbacks.


185-213: LGTM!

The _refresh_stack_list method correctly handles the empty state with a localized placeholder and renders stack items with proper count display and delete actions. The lambda closure pattern lambda e, n=name: correctly captures the loop variable.

src/switchcraft/gui_modern/views/intune_store_view.py (4)

7-7: LGTM!

The NavIndex import enables centralized navigation index management, which improves maintainability.


80-92: LGTM!

The _switch_to_settings method correctly checks for goto_tab availability and uses NavIndex.SETTINGS_GRAPH for direct navigation to the Graph settings tab. The fallback to page.go("/settings") provides graceful degradation.


163-169: LGTM!

Metadata field labels are properly localized with sensible fallbacks.


191-213: Previously flagged issue resolved - _show_snack method added.

The _show_snack helper method has been added, resolving the previously identified AttributeError. The implementation matches the pattern used in other views (addon_manager_view.py, winget_view.py).

src/switchcraft/gui_modern/views/script_upload_view.py (2)

336-420: LGTM - Well-structured GitHub Import tab UI.

The GitHub import tab scaffolding is well-designed with proper input fields for repo URL, PAT (with password reveal), branch, and path. The ListView for script listing and action buttons follow consistent patterns.


422-506: Good error handling in _browse_github_repo.

The background thread correctly handles various error cases (401 auth failure, 404 not found) with localized messages. The GitHub API usage and .ps1 file filtering are implemented correctly.

One minor note: the limit of 50 scripts (line 487) is a reasonable safeguard.

src/switchcraft/gui_modern/views/library_view.py (4)

1-11: LGTM!

The imports are appropriate for the filesystem-based scanning approach. The NavIndex import aligns with the broader navigation refactoring in this PR.


93-125: LGTM - Good directory scanning strategy.

The _get_scan_directories method uses a sensible approach:

  • Checks configured output folder first
  • Falls back to common default locations
  • Deduplicates and normalizes paths
  • Limits to 5 directories to prevent slow scanning

127-170: LGTM - Robust file scanning implementation.

The _load_data method correctly:

  • Handles non-existent directories gracefully
  • Scans one level deep (common structure)
  • Collects useful metadata (path, size, modified time)
  • Logs warnings for scan failures per directory
  • Sorts by modification time and limits to 50 files

265-284: LGTM - Good detail dialog implementation.

The tile click handler shows relevant file details (location, size, modified time) with an option to open the containing folder. The dialog follows the consistent pattern used elsewhere.

src/switchcraft/gui_modern/views/wingetcreate_view.py (2)

1-31: LGTM - Good module structure and helper function.

The module docstring clearly explains the purpose. The get_manifest_dir() function properly creates the manifest directory using APPDATA with a fallback to home directory.


438-538: Good manifest generation flow with comprehensive error handling.

The _generate_new_manifest method is well-implemented:

  • Validates required input (URLs)
  • Builds the command dynamically from form fields
  • Handles FileNotFoundError with helpful installation instructions
  • Handles TimeoutExpired gracefully
  • Uses background threading to avoid UI blocking
src/switchcraft/gui_modern/views/winget_view.py (3)

44-50: LGTM - Button styling looks correct.

The ft.Button with explicit bgcolor and color styling is appropriate for this navigation action.


96-113: LGTM - Layout with margins properly configured.

The left and right pane containers have appropriate margin settings for visual separation.


666-672: LGTM - Snack helper matches pattern in other views.

The _show_snack implementation is consistent with other views like intune_store_view.py and macos_wizard_view.py.

src/switchcraft/gui_modern/views/settings_view.py (3)

743-773: Graph connection test implementation looks correct.

The _test_graph_connection method properly validates fields, shows progress, runs authentication in a background thread, and handles errors appropriately.


775-784: Test notification implementation is correct.

The _send_test_notification method properly instantiates NotificationService and sends a test notification with system toast enabled.


97-97: Language dropdown value initialization may not sync with i18n.language.

The dropdown value is set from SwitchCraftConfig.get_value("Language", i18n.language) which correctly falls back to the current i18n language. However, if the config value differs from i18n.language, there could be a mismatch until restart.

src/switchcraft/gui_modern/app.py (4)

17-21: LGTM - Conditional import with availability flag is the correct pattern.

The WINOTIFY_AVAILABLE flag properly handles the optional dependency and prevents import errors on non-Windows platforms.


756-797: Navigation history and back button implementation is well-structured.

The goto_tab, _go_back_handler, and _update_back_btn_visibility methods properly manage navigation history and back button visibility. The RuntimeError catch in _update_back_btn_visibility appropriately handles cases where the control isn't attached to the page yet.


1036-1039: RuntimeError catch for fade_container.update() is appropriate.

This handles the case where the control may not be attached to the page during the fade animation.


860-876: Sidebar category initialization is guaranteed; no defensive check needed here.

The categories attribute is always initialized in HoverSidebar.__init__ at construction time, and the bounds check at line 864 (if 0 <= cat_index < len(self.sidebar.categories):) already prevents IndexError. No AttributeError risk exists for categories since it's guaranteed by the sidebar's constructor. The code is safe as-is.

Likely an incorrect or invalid review comment.

tests/test_navigation_map.py (1)

1-24: LGTM on imports.

All necessary view classes are imported to validate type mappings. The import organization is clear and appropriate for the test's purpose.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment thread src/switchcraft/gui_modern/views/analyzer_view.py
Comment thread src/switchcraft/gui_modern/views/wingetcreate_view.py Outdated
Comment thread tests/test_ui_interactions.py Outdated
Comment on lines +489 to +492
if tenant_field.on_change:
log("SUCCESS: 'Entra Tenant ID' has on_change handler.")
else:
log("CRITICAL: 'Entra Tenant ID' missing on_change handler!")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing pytest.fail for on_change handler check.

The test logs "CRITICAL" when on_change is missing but doesn't fail the test. This allows the test to pass silently when the handler is absent.

🛠️ Add assertion for on_change handler
              if tenant_field.on_change:
                  log("SUCCESS: 'Entra Tenant ID' has on_change handler.")
              else:
                  log("CRITICAL: 'Entra Tenant ID' missing on_change handler!")
+                 pytest.fail("'Entra Tenant ID' missing on_change handler")
🤖 Prompt for AI Agents
In `@tests/test_ui_interactions.py` around lines 489 - 492, The test currently
only logs a "CRITICAL" message when tenant_field.on_change is missing, so the
test can pass silently; change this to a failing assertion by asserting
tenant_field.on_change or calling pytest.fail with a clear message instead of
just logging. Update the block that checks tenant_field.on_change (the code
referencing tenant_field and on_change) to use assert tenant_field.on_change, or
import pytest and call pytest.fail("Entra Tenant ID missing on_change handler!")
when the condition is false, and ensure pytest is imported at the top of
tests/test_ui_interactions.py if not already.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 11

Caution

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

⚠️ Outside diff range comments (1)
src/switchcraft/gui_modern/app.py (1)

566-629: Add WINGET_CREATE destination to the destinations array.

The _switch_to_tab method at lines 890-1021 routes to NavIndex.WINGET_CREATE, but this entry is missing from the destinations array (lines 566-629). The destinations array currently has 20 items (indices 0-19), with dynamic addons appended afterward. Without a corresponding destination entry for WINGET_CREATE, the NavigationRail cannot render or navigate to that view. Add a destination for WINGET_CREATE before the dynamic addon loop at line 641.

🤖 Fix all issues with AI agents
In `@src/switchcraft/assets/lang/de.json`:
- Around line 475-486: The German strings in this block mix formal "Sie" with
informal "Du"; update the keys admin_required_msg,
btn_restart_admin/btn_run_now, confirm_local_test_msg, run_local_test, and
home_subtitle (and any other lines in this diff using "Sie"/"Ihren") to use the
informal "Du" phrasing consistently (e.g., "Möchtest du SwitchCraft...",
"Möchtest du den Installer lokal testen?", "Hier ist, was mit deinen Deployments
passiert."), keeping sentence structure and capitalization appropriate for
informal German.

In `@src/switchcraft/gui_modern/utils/flet_compat.py`:
- Around line 29-34: Fix the fallback path in the Tabs construction: remove the
extra leading space before the comment so indentation matches the surrounding
code, and validate `tabs` before calling `len(tabs)` to avoid a masked TypeError
— e.g., check `if tabs is None or not hasattr(tabs, "__len__")` (or use a safe
try/except around len(tabs)) and handle that case explicitly; keep the fallback
behavior of constructing `t = ft.Tabs(**kwargs)` and assigning `t.tabs = tabs`
(which is supported) if `tabs` is a valid sequence.

In `@src/switchcraft/gui_modern/views/analyzer_view.py`:
- Around line 811-845: Wrap the Windows-specific admin checks and elevation
logic with an explicit platform guard (e.g., check sys.platform == "win32")
before accessing ctypes.windll or calling ShellExecuteW; specifically, guard the
is_admin detection and the on_restart_confirm logic (the block that defines
is_admin, the on_restart_confirm handler that calls
ctypes.windll.shell32.ShellExecuteW and sys.exit, and the restart_dlg
creation/opening) so non-Windows platforms don't rely on exception flow—import
sys if needed and only use ctypes.windll/ShellExecuteW when the platform check
passes.

In `@src/switchcraft/gui_modern/views/dashboard_view.py`:
- Around line 41-45: The recent_container is set to height=280 but the nested
ListView uses height=300 causing overflow; update the ListView inside the same
view (the ListView instance) to match or be constrained by recent_container by
either reducing its height to 280 (or a value <=280) or removing the fixed
height and using expand=True/fill behavior so it adapts to recent_container;
adjust the ListView height/property wherever it’s created to ensure it does not
exceed recent_container.

In `@src/switchcraft/gui_modern/views/group_manager_view.py`:
- Around line 131-153: The current except blocks for PermissionError and
ConnectionError are incorrect for errors raised by IntuneService.list_groups
(which uses requests); replace the PermissionError handler with except
requests.exceptions.HTTPError as e and the ConnectionError with except
requests.exceptions.ConnectionError as e, update their log messages and
error_msg construction to use the HTTPError/ConnectionError instances (still
calling i18n.get and self._show_snack as before), and leave the generic except
Exception fallback in place for anything else; update references in these
handlers to use the same symbols shown (logger, i18n.get, self._show_snack) so
the HTTP/connection errors are caught properly.

In `@src/switchcraft/gui_modern/views/intune_view.py`:
- Around line 170-190: The connect handler spawns a background thread (_bg) that
directly mutates UI (self.up_status, self.btn_upload) and calls self.update(),
which violates Flet thread-safety; change the background work to run via
page.run_task() or page.run_thread() for authenticate calls (e.g., wrap
self.intune_service.authenticate in an async task or a page.run_thread target)
and schedule all UI mutations back onto the event loop using
page.loop.call_soon_threadsafe() (or use page.run_thread’s safe callback) to set
self.up_status.value, self.up_status.color, self.btn_upload.disabled and call
self.update() from the main thread only. Ensure the same pattern replaces other
threading.Thread sites in this file that reference self.update() and UI widgets.

In `@src/switchcraft/gui_modern/views/macos_wizard_view.py`:
- Around line 267-272: The generated script string (variable script)
interpolates user-controlled name and url into APP_NAME and the Source comment
(and possibly download_section), creating a shell injection risk; sanitize or
safely escape those values before embedding them (e.g., escape quotes,
backticks, dollar signs, and backslashes or use a robust shell-escaping utility)
and replace direct uses of name and url in the script generation with the
escaped versions (reference symbols: script, APP_NAME, name, url,
download_section in macos_wizard_view.py).

In `@src/switchcraft/gui_modern/views/script_upload_view.py`:
- Around line 468-473: The bare except around
datetime.datetime.fromtimestamp(int(reset_time)) swallows all errors; change it
to catch specific exceptions (e.g., ValueError, TypeError, OSError) when
parsing/formatting the timestamp so interrupts and system exits are not masked.
Update the try/except block that sets reset_dt and appends to msg (the code
using reset_time, reset_dt and datetime.datetime.fromtimestamp) to catch only
those expected errors and optionally log or ignore them, rather than using a
bare except.

In `@src/switchcraft/services/addon_service.py`:
- Around line 306-323: In the fallback-to-latest block inside the addon fetching
logic in addon_service.py (where release_data, resp, used_tag and logger are
used), do not leave the "except Exception as e:" empty—catch and log the
exception (including exception text) via logger.error or logger.exception and
return a clear failure tuple instead of silently falling through; additionally,
mirror the earlier tag-specific handling by wrapping resp.json() in a try/except
for JSONDecodeError (or ValueError) and log/return a meaningful error if parsing
fails so you don't call resp.json() unchecked and lose diagnostic context.
♻️ Duplicate comments (7)
src/switchcraft/gui_modern/views/library_view.py (1)

290-303: LGTM - Cross-platform folder opening implemented.

The _open_folder method now correctly handles Windows (os.startfile), macOS (open), and Linux (xdg-open), addressing the previous platform compatibility concern.

src/switchcraft/gui_modern/views/settings_view.py (1)

709-740: LGTM - Subprocess-based restart for PyInstaller compatibility.

The restart logic now correctly uses subprocess.Popen instead of os.execv, detects PyInstaller frozen state with getattr(sys, 'frozen', False), and uses os._exit(0) to avoid cleanup issues. This addresses the previous concern about PyInstaller builds.

src/switchcraft/gui_modern/views/analyzer_view.py (1)

244-285: Temp directory not cleaned up on download failure.

If the download fails at lines 274-285, the temporary directory created at line 245 is never cleaned up. The cleanup_path is only passed to start_analysis on success (line 272), leaving orphaned temp directories on failure.

🐛 Proposed fix
             except requests.exceptions.RequestException as ex:
                 self.url_download_progress.visible = False
                 self.url_download_status.value = f"Download failed: {ex}"
                 self.url_download_status.color = "RED"
                 self.update()
                 logger.error(f"URL download failed: {ex}")
+                # Cleanup temp directory on failure
+                try:
+                    shutil.rmtree(temp_dir, ignore_errors=True)
+                except Exception:
+                    pass
             except Exception as ex:
                 self.url_download_progress.visible = False
                 self.url_download_status.value = f"Error: {ex}"
                 self.url_download_status.color = "RED"
                 self.update()
                 logger.error(f"URL download error: {ex}")
+                # Cleanup temp directory on failure
+                try:
+                    shutil.rmtree(temp_dir, ignore_errors=True)
+                except Exception:
+                    pass
tests/test_ui_interactions.py (2)

150-155: Patch location is incorrect for HistoryService.

HistoryService is imported at module level in dashboard_view.py, so patching at the source module won't affect the already-imported reference. Patch where the class is used instead.

🛠️ Fix: patch at the usage location
-    with patch("switchcraft.services.history_service.HistoryService") as MockHistoryService:
+    with patch("switchcraft.gui_modern.views.dashboard_view.HistoryService") as MockHistoryService:

481-484: Missing assertion for on_change handler check.

The test logs "CRITICAL" when on_change is missing but doesn't fail the test. This allows silent test passes when the handler is absent.

🛠️ Add assertion
              if tenant_field.on_change:
                  log("SUCCESS: 'Entra Tenant ID' has on_change handler.")
              else:
                  log("CRITICAL: 'Entra Tenant ID' missing on_change handler!")
+                 pytest.fail("'Entra Tenant ID' missing on_change handler")
src/switchcraft/gui_modern/views/intune_view.py (1)

427-433: Exception handling improved but still swallows errors silently.

The change from bare except: to except Exception: is an improvement, but the exception is still being silently ignored. Per the previous review suggestion, consider logging the exception for debugging purposes.

♻️ Add logging for failed snack display
     def _show_snack(self, msg, color="GREEN"):
         try:
             self.app_page.snack_bar = ft.SnackBar(ft.Text(msg), bgcolor=color)
             self.app_page.snack_bar.open = True
             self.app_page.update()
-        except Exception:
-            pass
+        except Exception as ex:
+            logger.debug(f"Failed to show snack: {ex}")
src/switchcraft/gui_modern/views/wingetcreate_view.py (1)

63-70: Replace text with label parameter in ft.Tab() constructor.

This issue was previously identified. Flet 0.80.1+ uses label instead of text for Tab display names.

🧹 Nitpick comments (15)
src/switchcraft/gui_modern/views/script_upload_view.py (2)

500-507: Consider tracking checkbox selection state for future implementation.

The checkboxes in the script list don't have an on_change handler or data association to identify which scripts are selected. When implementing _import_github_scripts and _deploy_github_scripts, you'll need a way to track selections.

Example approach for tracking selections
# Store selected scripts
self.selected_scripts = set()

# In the loop:
def on_script_select(script_path):
    def handler(e):
        if e.control.value:
            self.selected_scripts.add(script_path)
        else:
            self.selected_scripts.discard(script_path)
    return handler

for script_path in ps_files[:50]:
    cb = ft.Checkbox(value=False, on_change=on_script_select(script_path))
    self.github_script_list.controls.append(
        ft.ListTile(
            leading=cb,
            title=ft.Text(script_path),
            trailing=ft.Icon(ft.Icons.DESCRIPTION, color="BLUE_400")
        )
    )

440-448: URL parsing could be more robust for edge cases.

The current parsing handles basic GitHub URLs but may fail on variations like:

  • github.com/owner/repo (missing protocol)
  • https://github.com/owner/repo.git (with .git suffix)
  • [email protected]:owner/repo.git (SSH format)
More robust URL parsing
import re

# Parse GitHub URL - handles various formats
match = re.match(
    r'(?:https?://)?(?:www\.)?github\.com[/:]([^/]+)/([^/.]+)(?:\.git)?/?',
    repo_url
)
if not match:
    raise ValueError(i18n.get("invalid_github_url") or "Invalid GitHub URL")
owner, repo = match.groups()
src/switchcraft/gui_modern/utils/flet_compat.py (1)

14-17: Consider logging the initial TypeError for debugging.

When the standard approach fails and fallback succeeds, the original error is silently discarded. If the fallback later exhibits issues, it would be helpful to know the initial failure reason.

Suggested improvement
     try:
         # Standard Flet
         return ft.Tabs(tabs=tabs, **kwargs)
     except TypeError:
+        logger.debug("Standard Tabs creation failed, trying fallback")
         # Fallback for environments where Tabs requires content/length (e.g. tests)
src/switchcraft/gui_modern/views/intune_store_view.py (1)

24-24: Consider consistent i18n fallback pattern.

The file mixes two fallback patterns: i18n.get("key") or "default" (line 24) and i18n.get("key", default="default") (lines 164-168). The default= parameter is preferable as it also handles empty string returns correctly. This is a minor consistency nit.

src/switchcraft/gui_modern/views/macos_wizard_view.py (1)

15-16: Unused imports: tempfile and requests.

These modules are imported but not used anywhere in the file.

♻️ Proposed fix
 import logging
 import threading
-import tempfile
-import requests
 from pathlib import Path
src/switchcraft/gui_modern/views/library_view.py (1)

104-114: Windows-specific paths in cross-platform code.

The hardcoded C:/Temp and C:/IntuneWin paths are Windows-specific. While the exists() check prevents errors on other platforms, consider using platform-specific paths for better clarity.

♻️ Suggested improvement
+        import sys
+        
         # Common default locations
         user_home = Path.home()
         default_dirs = [
             user_home / "Downloads",
             user_home / "Documents",
             user_home / "Desktop",
-            Path("C:/Temp"),
-            Path("C:/IntuneWin"),
         ]
+        
+        # Add Windows-specific paths
+        if sys.platform == "win32":
+            default_dirs.extend([
+                Path("C:/Temp"),
+                Path("C:/IntuneWin"),
+            ])
src/switchcraft/gui_modern/views/analyzer_view.py (1)

943-946: Windows-specific os.startfile usage.

os.startfile() is Windows-only. While this file appears to be Windows-focused, consider adding a platform check or try/except for robustness.

♻️ Suggested defensive approach
                 def open_folder(e):
-                    import os
-                    os.startfile(str(source))
+                    try:
+                        import os
+                        os.startfile(str(source))
+                    except AttributeError:
+                        # os.startfile only available on Windows
+                        import subprocess
+                        subprocess.run(['xdg-open', str(source)], check=False)
                     dlg.open = False
                     self.app_page.update()
tests/test_ui_interactions.py (2)

19-19: Duplicate assignment of page.open.

page.open = MagicMock() is assigned twice (lines 19 and 27). Remove one.

♻️ Proposed fix
     # Init empty dialogs
     page.dialog = None
     page.snack_bar = None
-    page.open = MagicMock() # Fix for Flet 0.21+ dialogs

     # Mock window object
     page.window = MagicMock()
     page.theme_mode = ft.ThemeMode.DARK
     page.padding = 10

     # Mock open for dialogs (newer Flet API)
     page.open = MagicMock()
     return page

Also applies to: 27-27


63-70: Consider using pytest's built-in logging instead of file-based logging.

Writing to "test_result.log" creates artifacts. Use capfd fixture or pytest's logging capture for cleaner test output.

tests/test_i18n_integrity.py (2)

48-50: Remove extra blank lines inside function.

Lines 49-50 are empty, adding unnecessary vertical space inside the function definition.

♻️ Clean up
         def find_duplicates(filepath):
-
-
             with open(filepath, "r", encoding="utf-8") as f:

100-116: Weak heuristic for filtering dynamic keys.

The space check (if " " not in k) is fragile. Consider maintaining an explicit allowlist for known dynamic key patterns or using a more robust detection method.

♻️ Suggested improvement
# Define known dynamic/constructed key patterns to ignore
DYNAMIC_KEY_PATTERNS = [
    # Add patterns like: r'^addon_\w+_name$' if keys are constructed
]

missing = []
for k in found_keys:
    if k not in self.en:
        # Skip if matches known dynamic pattern
        if any(re.match(pattern, k) for pattern in DYNAMIC_KEY_PATTERNS):
            continue
        missing.append(k)
src/switchcraft/gui_modern/views/intune_view.py (3)

110-116: ft.Button with bgcolor, color, and height may not be valid.

Standard Flet button controls (ft.ElevatedButton, ft.TextButton) use the style parameter with ButtonStyle for customization, not direct bgcolor/color properties. This could cause runtime errors.

If this is intentional custom styling, verify the Flet version supports this syntax. Otherwise, consider using:

♻️ Suggested fix using standard Flet API
-        btn_create = ft.Button(
-            i18n.get("btn_create_intunewin") or "Create .intunewin",
-            bgcolor="GREEN_700",
-            color="WHITE",
-            height=50,
-            on_click=self._run_creation
-        )
+        btn_create = ft.ElevatedButton(
+            text=i18n.get("btn_create_intunewin") or "Create .intunewin",
+            style=ft.ButtonStyle(
+                bgcolor=ft.Colors.GREEN_700,
+                color=ft.Colors.WHITE,
+            ),
+            height=50,
+            on_click=self._run_creation
+        )

343-348: Previous issue partially addressed but path handling still fragile.

The shell=True was removed, which is good. However, the current format f'/select,{output_file}' as a single argument is correct for Explorer, but paths with spaces may still cause issues since Explorer expects the full /select,<path> as one argument.

Consider quoting the path or using os.path.normpath() for robustness:

♻️ More robust path handling
                 def open_folder(e):
                     import subprocess
                     if os.name == 'nt':
-                        subprocess.run(['explorer', f'/select,{output_file}'])
+                        # Explorer needs /select, and path as single argument
+                        subprocess.run(['explorer', '/select,' + os.path.normpath(output_file)])
                     dlg.open = False
                     self.app_page.update()

435-437: Remove the unused placeholder method _open_explorer_select.

This method is defined but never called anywhere in the codebase. Removing it will eliminate dead code.

src/switchcraft/gui_modern/views/wingetcreate_view.py (1)

100-105: Localized string may not include manifest path.

The fallback concatenates the path, but if i18n.get("wingetcreate_info") returns a localized string without a {path} placeholder, users won't see the actual path. Consider using format kwargs:

-                            i18n.get("wingetcreate_info") or
-                                "Manifests are saved to: " + str(self.manifest_dir),
+                            i18n.get("wingetcreate_info", path=str(self.manifest_dir)) or
+                                f"Manifests are saved to: {self.manifest_dir}",

And ensure the i18n string uses {path} placeholder.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9179804 and 1e7eef9.

📒 Files selected for processing (23)
  • .github/workflows/test.yml
  • .gitignore
  • src/switchcraft/assets/lang/de.json
  • src/switchcraft/gui_modern/app.py
  • src/switchcraft/gui_modern/utils/flet_compat.py
  • src/switchcraft/gui_modern/views/analyzer_view.py
  • src/switchcraft/gui_modern/views/category_view.py
  • src/switchcraft/gui_modern/views/crash_view.py
  • src/switchcraft/gui_modern/views/dashboard_view.py
  • src/switchcraft/gui_modern/views/group_manager_view.py
  • src/switchcraft/gui_modern/views/intune_store_view.py
  • src/switchcraft/gui_modern/views/intune_view.py
  • src/switchcraft/gui_modern/views/library_view.py
  • src/switchcraft/gui_modern/views/macos_wizard_view.py
  • src/switchcraft/gui_modern/views/script_upload_view.py
  • src/switchcraft/gui_modern/views/settings_view.py
  • src/switchcraft/gui_modern/views/stack_manager_view.py
  • src/switchcraft/gui_modern/views/wingetcreate_view.py
  • src/switchcraft/services/addon_service.py
  • src/switchcraft/services/ai_service.py
  • tests/test_i18n_integrity.py
  • tests/test_navigation.py
  • tests/test_ui_interactions.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • tests/test_navigation.py
  • src/switchcraft/gui_modern/views/stack_manager_view.py
  • src/switchcraft/gui_modern/views/category_view.py
  • .gitignore
🧰 Additional context used
🧬 Code graph analysis (11)
src/switchcraft/gui_modern/views/group_manager_view.py (6)
src/switchcraft/gui_modern/nav_constants.py (1)
  • NavIndex (13-47)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/gui_modern/views/addon_manager_view.py (1)
  • _show_snack (162-168)
src/switchcraft/gui_modern/views/intune_store_view.py (1)
  • _show_snack (207-213)
src/switchcraft/gui_modern/views/settings_view.py (1)
  • _show_snack (690-696)
src/switchcraft/gui_modern/app.py (1)
  • goto_tab (768-776)
src/switchcraft/gui_modern/views/intune_store_view.py (3)
src/switchcraft/gui_modern/nav_constants.py (1)
  • NavIndex (13-47)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/gui_modern/views/analyzer_view.py (1)
  • _show_snack (1014-1020)
src/switchcraft/gui_modern/views/settings_view.py (3)
src/switchcraft/services/intune_service.py (1)
  • authenticate (147-164)
src/switchcraft/utils/config.py (1)
  • get_value (31-88)
src/switchcraft/utils/i18n.py (2)
  • get (143-170)
  • set_language (136-141)
src/switchcraft/gui_modern/views/macos_wizard_view.py (4)
src/switchcraft/gui_modern/utils/file_picker_helper.py (2)
  • FilePickerHelper (5-85)
  • pick_file (12-38)
src/switchcraft/gui_modern/utils/flet_compat.py (1)
  • create_tabs (7-37)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/gui_modern/views/intune_view.py (1)
  • _show_snack (427-433)
src/switchcraft/gui_modern/views/wingetcreate_view.py (3)
src/switchcraft/utils/config.py (1)
  • SwitchCraftConfig (10-439)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/gui_modern/views/winget_view.py (2)
  • _show_snack (666-672)
  • target (167-171)
src/switchcraft/gui_modern/views/library_view.py (2)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/utils/config.py (1)
  • get_value (31-88)
src/switchcraft/gui_modern/views/script_upload_view.py (1)
src/switchcraft/gui_modern/utils/flet_compat.py (1)
  • create_tabs (7-37)
src/switchcraft/services/ai_service.py (1)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
tests/test_ui_interactions.py (6)
src/switchcraft/gui_modern/views/settings_view.py (2)
  • ModernSettingsView (15-1170)
  • did_mount (59-65)
src/switchcraft/gui_modern/views/dashboard_view.py (2)
  • DashboardView (7-174)
  • did_mount (52-53)
src/switchcraft/gui_modern/views/home_view.py (1)
  • ModernHomeView (6-144)
src/switchcraft/gui_modern/views/analyzer_view.py (3)
  • ModernAnalyzerView (32-1035)
  • log (724-726)
  • _show_results (407-593)
src/switchcraft/services/history_service.py (1)
  • get_history (22-35)
src/switchcraft/gui_modern/views/library_view.py (2)
  • did_mount (90-91)
  • LibraryView (13-336)
src/switchcraft/gui_modern/views/intune_view.py (5)
src/switchcraft/services/intune_service.py (1)
  • authenticate (147-164)
src/switchcraft/gui_modern/utils/file_picker_helper.py (2)
  • FilePickerHelper (5-85)
  • pick_file (12-38)
src/switchcraft/utils/config.py (1)
  • get_value (31-88)
src/switchcraft/gui_modern/utils/flet_compat.py (1)
  • create_tabs (7-37)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/gui_modern/views/dashboard_view.py (1)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
🔇 Additional comments (54)
src/switchcraft/gui_modern/views/script_upload_view.py (5)

1-46: LGTM - Clean initialization with proper i18n fallbacks.

The imports are well-organized, and the use of create_tabs from flet_compat ensures version compatibility. The i18n strings with fallback values (i18n.get("key") or "fallback") provide good resilience against missing translations.


48-83: Tab parameter issue resolved.

The previous review flagged the use of text instead of label in ft.Tab() constructor. This has been correctly addressed - all tabs now use label= parameter (lines 69, 73, 77), which is compatible with Flet 0.80.1+.


86-196: Well-structured upload implementation.

Good practices observed:

  • Input validation before processing (lines 153-158)
  • Credential validation before starting background work (lines 161-170)
  • UI disabled during upload to prevent double-submission (line 174)
  • Background threading for network operations with proper status updates

199-335: Consistent implementation with platform script tab.

The remediation tab follows the same well-structured pattern: input validation, credential checking, and background threading for network operations.


521-539: Placeholder methods and helper are appropriately implemented.

The placeholder methods provide clear user feedback about unimplemented features. The _show_snack helper defensively handles exceptions to prevent snackbar errors from breaking the main flow.

src/switchcraft/gui_modern/views/group_manager_view.py (5)

1-42: LGTM!

The NavIndex import and Button replacement align with the modernization effort. The i18n fallback pattern i18n.get("key") or "fallback" is consistent with usage elsewhere in the codebase.


48-111: LGTM!

The i18n integration for search, toggle, and button labels is well implemented. The Container wrapper with expand=True and padding=20 provides consistent layout styling. The Button replacements align with the modernization effort described in the AI summary.


227-236: LGTM!

The switch from page.dialog = dlg; dlg.open = True to page.open(dlg) uses Flet's modern dialog API. The Button replacement is consistent with the rest of the modernization changes.


261-271: LGTM!

The delete confirmation dialog changes are consistent with the create dialog modernization. The red background color for the destructive "Delete" action is appropriate UX.


288-303: LGTM!

Using NavIndex.SETTINGS_GRAPH instead of the hardcoded index 9 is a solid improvement for maintainability. The fallback mechanism for finding the app reference provides good defensive coding against different page configurations.

src/switchcraft/gui_modern/utils/flet_compat.py (1)

7-13: Function signature and docstring are clear.

The docstring adequately explains the fallback strategy. Consider adding type hints for better IDE support (e.g., tabs: list[ft.Tab]), though this is optional.

src/switchcraft/gui_modern/views/crash_view.py (2)

42-73: LGTM - Robust clipboard handling with proper fallback chain.

The implementation correctly tries pyperclip first, falls back to Flet's native clipboard, and provides appropriate user feedback via snack bars. The except Exception: usage is appropriate for this crash-handling context where stability is paramount.


102-116: LGTM - Well-implemented cross-platform restart logic.

The Windows-specific DETACHED_PROCESS flag, close_fds=True for handle inheritance prevention, and CWD handling for PyInstaller bundles are all appropriate fixes for robust application restart. The conditional creationflags ensures no impact on non-Windows platforms.

src/switchcraft/services/addon_service.py (1)

344-366: LGTM - Download and install logic with proper cleanup.

Good use of tempfile.NamedTemporaryFile with delete=False for controlled cleanup, and the finally block ensures the temp file is removed even on failure. The streaming download with chunked writes is memory-efficient.

.github/workflows/test.yml (1)

40-61: LGTM - Well-structured CLI test isolation.

Installing without GUI extras (pip install . vs pip install .[test,gui]) ensures CLI tests verify the minimal dependency path. The explicit test file targeting is appropriate for component-specific testing.

src/switchcraft/gui_modern/views/intune_store_view.py (2)

80-92: LGTM - Navigation improvement with NavIndex fallback.

Good enhancement to use NavIndex.SETTINGS_GRAPH for direct tab navigation when the app context is available, with appropriate fallbacks to routing and a silent no-op when navigation isn't possible.


207-213: LGTM - _show_snack helper matches established pattern.

The implementation correctly mirrors the pattern from analyzer_view.py, with proper exception handling to avoid crashes in edge cases.

src/switchcraft/gui_modern/views/dashboard_view.py (1)

28-50: LGTM - Layout restructuring with proper i18n integration.

The layout changes improve consistency with padding, spacing, and i18n-enabled strings with sensible English fallbacks. The responsive wrapping behavior on stats_row and the Row containing chart/recent containers is appropriate.

src/switchcraft/gui_modern/views/macos_wizard_view.py (2)

115-129: LGTM - Tab parameter corrected.

The ft.Tab components now correctly use the label parameter instead of text, addressing the previous CI failure.


205-220: LGTM - Local file picker implementation.

The _pick_local_file method correctly uses FilePickerHelper, auto-fills the app name from the filename, and updates the UI state appropriately.

src/switchcraft/gui_modern/views/library_view.py (2)

127-174: LGTM - File scanning with appropriate error handling.

The scanning logic handles permission errors gracefully (debug level), limits results to 50 files to prevent UI overload, and sorts by modification time for relevance.


269-288: LGTM - Detail dialog with folder navigation.

The tile click handler shows a well-structured dialog with file details and action buttons. The comma-based lambda chaining at line 284 works correctly.

src/switchcraft/gui_modern/views/settings_view.py (3)

768-798: LGTM - Graph connection test with proper validation and threading.

The test connection flow validates inputs, runs authentication in a background thread to prevent UI blocking, and provides clear status feedback to the user.


800-809: LGTM - Test notification implementation.

Clean implementation that triggers both in-app notification and Windows toast notification for testing purposes.


331-363: LGTM - Entra/Graph configuration fields with test workflow.

The migration from Intune-centric to Entra Graph terminology is consistent. The test connection button provides immediate validation feedback for the configuration.

src/switchcraft/gui_modern/app.py (4)

460-488: Improved race condition handling with page.run_task.

The background thread now uses page.run_task(update_ui) to schedule UI updates on the main thread, and includes a check for dialog state before modifying controls. The exception handling at lines 482-484 provides graceful fallback.


254-277: LGTM - Robust asset path resolution for both development and production.

The asset path handling correctly differentiates between PyInstaller frozen builds (sys._MEIPASS) and development mode, with fallback handling for missing assets.


17-21: LGTM - Optional winotify integration with graceful fallback.

The conditional import with WINOTIFY_AVAILABLE flag ensures the app works on systems without winotify installed, while enabling Windows toast notifications where available.


1103-1119: Protocol handler registration exists but silently fails if permissions are denied.

The switchcraft://notifications protocol is registered automatically on first run in modern_main.py (lines 251–254) via the register_protocol_handler() function. However, the registration is wrapped in a silent try/except block. If the user lacks registry write permissions, registration will fail silently, and the toast action buttons will not work without any error message to the user. Consider logging a warning when registration fails so users are aware of the limitation.

src/switchcraft/gui_modern/views/analyzer_view.py (4)

7-20: LGTM - Imports properly organized.

The new imports for requests, tempfile, and create_tabs are appropriate for the URL download and tabbed UI features.


171-185: Tab creation now uses compatibility helper.

The create_tabs helper from flet_compat abstracts Flet version differences, addressing API compatibility concerns. This is a good pattern for handling Flet version variations.


346-346: Good cleanup implementation in start_analysis.

The cleanup_path parameter and finally block cleanup logic is well implemented, handling both directory and file cleanup cases gracefully.

Also applies to: 394-403


908-914: Good dialog API compatibility handling.

Using hasattr(self.app_page, 'open') for feature detection ensures compatibility across Flet versions, falling back to the older dialog property pattern when needed.

tests/test_ui_interactions.py (2)

30-58: Good extraction of find_buttons to module level.

The helper function is now defined once at module level, eliminating duplication across tests. The dynamic button type detection with hasattr checks handles Flet version differences gracefully.


203-205: Revert to source module patching—the current approach is correct for local imports.

HistoryService is imported locally inside methods (lines 414 and 1024 in analyzer_view.py), not at module level. When mocking modules imported locally within functions, patching at the source location (switchcraft.services.history_service.HistoryService) is the correct approach, not the usage location. The test's own comment confirms this: "Patch the SOURCE of HistoryService so local imports mock it too."

src/switchcraft/assets/lang/de.json (2)

386-388: Key rename from Intune to Entra is correct.

The rename from settings_intune_* to settings_entra_* aligns with Microsoft's rebranding from Azure AD/Intune to Microsoft Entra ID.


451-802: Extensive i18n expansion looks complete.

The new keys cover analyzer view, dashboard, stacks, scripts, Winget, and wizard features comprehensively. Key naming follows consistent conventions.

tests/test_i18n_integrity.py (4)

7-18: Good test setup with proper path resolution.

The path resolution from test file to lang directory is correct and the JSON loading handles encoding properly.


20-29: Solid key symmetry validation.

This test will catch missing translations in either language file, which is valuable for maintaining localization parity.


44-73: Duplicate key detection addresses past review feedback.

The object_pairs_hook approach correctly detects duplicate keys that JSON silently overwrites. This prevents localization bugs from duplicate definitions.


93-94: Good error handling for file read failures.

Printing the error and continuing allows the test to complete even if some files are unreadable, while still providing diagnostic information.

src/switchcraft/gui_modern/views/intune_view.py (5)

1-12: LGTM!

Imports are well-organized and the use of create_tabs from flet_compat for cross-version Flet compatibility is appropriate.


50-65: Previous issue addressed: Tab keyword argument fixed.

The text= keyword argument issue has been resolved by using label= and the create_tabs compatibility helper. This should work across different Flet versions.


269-299: LGTM with previously noted thread safety concern.

The metadata copy logic is correct. The thread safety issue with self.update() from background threads applies here as well.


386-416: Upload flow looks correct with good error handling.

The upload logic properly validates inputs, handles progress reporting, and logs errors. The setattr(...) or self.update() pattern on line 393 works but is somewhat unconventional.


418-424: LGTM!

The success dialog implementation is clean and follows the established patterns in the file.

src/switchcraft/gui_modern/views/wingetcreate_view.py (8)

1-22: LGTM!

Module imports are appropriate for the functionality. Good inclusion of sys for platform detection.


25-31: LGTM!

Good cross-platform handling with APPDATA fallback to home directory, and safe directory creation with exist_ok=True.


334-437: LGTM!

The update tab implementation is well-structured and consistent with the new manifest tab pattern.


497-510: Platform guard correctly implemented.

Good fix addressing the cross-platform compatibility concern. The conditional kwargs approach is clean.


590-603: Platform guard correctly implemented.

Consistent with the pattern in _generate_new_manifest.


678-689: LGTM!

Good cross-platform implementation for opening the file explorer.


691-697: LGTM!

Implementation is consistent with _show_snack in winget_view.py.


311-323: ft.Button is valid in Flet 0.80.1 (your project's requirement) and is widely used throughout the codebase without issues.

The code at lines 311–323 is correct. The project specifies flet>=0.80.1, which supports ft.Button. The control exists and functions properly in this version; it has not been removed. The codebase uses ft.Button 70+ times across multiple files, indicating this is the intended pattern for your Flet version.

Likely an incorrect or invalid review comment.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment thread src/switchcraft/assets/lang/de.json
Comment thread src/switchcraft/gui_modern/utils/flet_compat.py Outdated
Comment thread src/switchcraft/gui_modern/views/analyzer_view.py
Comment thread src/switchcraft/gui_modern/views/dashboard_view.py
Comment thread src/switchcraft/gui_modern/views/group_manager_view.py Outdated
Comment thread src/switchcraft/gui_modern/views/macos_wizard_view.py
Comment thread src/switchcraft/gui_modern/views/script_upload_view.py
Comment thread src/switchcraft/gui_modern/views/wingetcreate_view.py
Comment thread src/switchcraft/services/addon_service.py
Comment thread src/switchcraft/services/ai_service.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 Fix all issues with AI agents
In `@src/switchcraft/assets/lang/de.json`:
- Around line 538-548: Change the inconsistent formal German to the informal
"Du" form for the new keys: replace "Ihre Stacks" in "your_stacks" with "Deine
Stacks", change "Noch keine Stacks. Erstellen Sie oben einen!" in
"no_stacks_yet" to "Noch keine Stacks. Erstelle oben einen!" and change
"Importieren Sie..." in "github_import_desc" to "Importiere..." so all strings
match the existing informal tone used elsewhere.

In `@src/switchcraft/gui_modern/app.py`:
- Around line 1022-1031: The dynamic addon offset logic is fragile because it
assumes NavIndex.WINGET_CREATE is the last static destination; replace the
hardcoded calculation with a robust derivation of the first-dynamic index (e.g.,
compute first_dynamic_index = destinations.index(NavIndex.WINGET_CREATE) + 1 or
maintain an explicit static_dest_count constant/property) and then compute
dynamic_idx = idx - first_dynamic_index; update the block using
self.dynamic_addons, NavIndex.WINGET_CREATE, idx, load_view, and
addon_service.load_addon_view so dynamic addon indexing will remain correct if
static destinations change.
- Around line 460-464: The update_ui function currently checks "dlg not in
self.page.dialogs" which will raise AttributeError on Flet 0.80.1+ because
self.page.dialogs was removed; update the condition to avoid referencing
self.page.dialogs and instead check dialog state defensively (e.g., test
dlg.open and compare against the app's custom self.page.dialog only if that
attribute exists), or migrate to the modern API by replacing uses of
self.page.dialog/self.page.dialogs with page.show_dialog/page.pop_dialog flows;
specifically modify update_ui to short-circuit if not dlg.open or
(hasattr(self.page, "dialog") and dlg != self.page.dialog) and consider
switching dialog lifecycle to page.show_dialog()/page.pop_dialog() where
appropriate.

In `@src/switchcraft/gui_modern/utils/flet_compat.py`:
- Around line 30-31: The fallback builds ft.Tabs with only ft.TabBar as content
which Flet 0.80.1 rejects; update the fallback in the block that computes length
= len(tabs) if tabs else 0 and returns ft.Tabs(...) to supply a Control that
contains both the TabBar and a TabBarView (for example wrap ft.TabBar(tabs=tabs)
and a corresponding ft.TabBarView(children=...) inside an ft.Column), or remove
the fallback entirely; ensure the TabBarView children align with the provided
tabs and keep the length argument passed to ft.Tabs.

In `@src/switchcraft/gui_modern/views/script_upload_view.py`:
- Around line 516-517: There is a syntax error caused by two consecutive `else:`
clauses; locate the duplicated `else:` in the conditional block (in the same
scope as the surrounding if/elif chain in script_upload_view.py) and remove the
redundant `else:` so there is only a single final fallback branch, then ensure
the indented block that should run for that fallback is directly under the
remaining `else:` and that any shared logic is merged appropriately.

In `@src/switchcraft/gui_modern/views/wingetcreate_view.py`:
- Around line 489-491: When building the command in the block that checks
self.new_submit_pr.value and self.new_github_token.value, do not append the
token as "--token" to cmd (this exposes it in process listings); instead set
WINGET_CREATE_GITHUB_TOKEN in the process environment (e.g. use
os.environ.setdefault("WINGET_CREATE_GITHUB_TOKEN", self.new_github_token.value)
or create a copy of os.environ, set
env["WINGET_CREATE_GITHUB_TOKEN"]=self.new_github_token.value and pass that env
to the subprocess invocation that runs the cmd), and keep cmd.append("--submit")
as-is so authentication is done via the environment rather than a command-line
argument.
♻️ Duplicate comments (3)
src/switchcraft/gui_modern/views/wingetcreate_view.py (1)

644-668: Critical bug: Duplicate platform check causes NameError on non-Windows.

This issue was previously flagged but remains unfixed. The nested if sys.platform == "win32": (lines 644-645) means subprocess.run only executes on Windows. On other platforms, result is never defined, causing NameError at line 660. Additionally, line 656 has inconsistent indentation.

🐛 Proposed fix
         def _bg():
             try:
                 cmd = ["winget", "validate", str(self.manifest_dir)]

                 if sys.platform == "win32":
-                    if sys.platform == "win32":
-                        startupinfo = subprocess.STARTUPINFO()
-                        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
-                        kwargs = {"startupinfo": startupinfo}
-                    else:
-                        kwargs = {}
-
-                    result = subprocess.run(
-                        cmd,
-                        capture_output=True,
-                        text=True,
-                    timeout=30,
-                        **kwargs
-                )
+                    startupinfo = subprocess.STARTUPINFO()
+                    startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
+                    kwargs = {"startupinfo": startupinfo}
+                else:
+                    kwargs = {}
+
+                result = subprocess.run(
+                    cmd,
+                    capture_output=True,
+                    text=True,
+                    timeout=30,
+                    **kwargs
+                )

                 output = result.stdout + "\n" + result.stderr
src/switchcraft/gui_modern/views/intune_view.py (1)

217-235: Thread safety pattern inconsistent with connect handler.

While the connect handler (lines 172-202) correctly uses run_task() to marshal UI updates, search_apps (lines 217-235), _copy_metadata_from_supersedence (lines 288-311), and _run_upload (lines 408-438) still call self.update() directly from background threads. Apply the same pattern used in connect for consistency.

src/switchcraft/assets/lang/de.json (1)

774-774: Additional formal "Sie" inconsistency.

Line 774 uses formal form: "Ihr .intunewin-Paket" → should be "Dein .intunewin-Paket" for consistency.

🧹 Nitpick comments (22)
src/switchcraft/services/addon_service.py (2)

263-277: Method structure looks good.

The lazy import pattern for requests is acceptable for optional dependencies. The hardcoded repository values are appropriate for this single-repo use case.

Consider catching ImportError for requests to provide a friendlier error message if the dependency isn't installed, though this is optional if requests is a required dependency.


306-325: Exception handling improved, but inconsistent exception type.

The empty exception handler from previous review is now properly addressed (lines 323-325). However, line 317 catches ValueError while line 299 catches json.JSONDecodeError. While JSONDecodeError is a subclass of ValueError, using the same exception type improves consistency and clarity.

Suggested consistency fix
-                    except ValueError: # JSONDecodeError
+                    except json.JSONDecodeError:
                         logger.error(f"Invalid JSON from latest release: {resp.text[:100]}")
                         return False, "Invalid response from GitHub."
src/switchcraft/gui_modern/views/group_manager_view.py (3)

100-111: Hard-coded strings should use i18n for consistency.

Lines 102-103 contain hard-coded English strings ("Entra Group Manager", "Manage your Microsoft Entra ID (Azure AD) groups.") while other UI elements in this file use i18n.get(). This breaks i18n consistency with the rest of the modernized views.

Similarly, the DataTable column headers at lines 84-87 ("Name", "Description", "ID", "Type") are hard-coded.

Consider wrapping these in i18n lookups:

-                    ft.Text("Entra Group Manager", size=28, weight=ft.FontWeight.BOLD),
-                    ft.Text("Manage your Microsoft Entra ID (Azure AD) groups.", color="GREY"),
+                    ft.Text(i18n.get("entra_group_manager_title") or "Entra Group Manager", size=28, weight=ft.FontWeight.BOLD),
+                    ft.Text(i18n.get("entra_group_manager_desc") or "Manage your Microsoft Entra ID (Azure AD) groups.", color="GREY"),

132-142: Exception handling logic may misclassify errors.

The exception types are now correctly using requests.exceptions, but the error classification is imprecise:

  1. Line 133-134: HTTPError catches all HTTP errors (400, 401, 404, 500, etc.), not just 403 permission errors. A 404 or 500 would incorrectly display as a permission error.

  2. Line 139-140: ConnectionError indicates network connectivity issues (DNS failure, connection refused), not authentication failures. The comment and error message are misleading.

  3. Line 135: The fallback str(e) if str(e) else "Group.Read.All..." will show raw HTTP error text to users, which may be confusing.

Consider checking the response status code for HTTPError:

♻️ Suggested improvement
             except requests.exceptions.HTTPError as e:
-                # Handle specific permission error (403)
-                logger.error(f"Permission denied loading groups: {e}")
-                missing_perms = str(e) if str(e) else "Group.Read.All, Group.ReadWrite.All"
-                error_msg = i18n.get("graph_permission_error", permissions=missing_perms) or f"Missing Graph API permissions: {missing_perms}"
-                self._show_snack(error_msg, "RED")
+                status_code = e.response.status_code if e.response is not None else None
+                logger.error(f"HTTP error loading groups: {e}")
+                if status_code == 403:
+                    error_msg = i18n.get("graph_permission_error", permissions="Group.Read.All") or "Missing Graph API permissions: Group.Read.All"
+                elif status_code == 401:
+                    error_msg = i18n.get("graph_auth_error") or "Authentication failed. Please check your credentials."
+                else:
+                    error_msg = i18n.get("graph_http_error") or f"HTTP error: {e}"
+                self._show_snack(error_msg, "RED")
             except requests.exceptions.ConnectionError as e:
-                # Handle authentication failure
-                logger.error(f"Authentication failed: {e}")
-                error_msg = i18n.get("graph_auth_error") or "Authentication failed. Please check your credentials."
+                # Handle network connectivity issues
+                logger.error(f"Connection error: {e}")
+                error_msg = i18n.get("graph_connection_error") or "Network connection failed. Please check your internet connection."
                 self._show_snack(error_msg, "RED")

203-237: Several dialog strings are hard-coded.

The dialog handling pattern using app_page.open(dlg) is correct and consistent with other views. However, several strings should use i18n for consistency:

  • Line 207: "Group Name"
  • Line 208: "Description"
  • Line 214: "Not connected to Intune"
  • Line 229: "Create New Group"
  • Line 232: "Cancel"
  • Line 233: "Create"

This is optional but would align with the broader i18n expansion in this PR.

src/switchcraft/gui_modern/views/macos_wizard_view.py (1)

28-31: dmg_url state variable appears unused.

The dmg_url attribute is initialized but never referenced elsewhere in the class. The URL is read directly from self.url_field.value in _generate_script. Consider removing this dead state variable.

Proposed fix
         # State
-        self.dmg_url = ""
         self.local_file_path = None
         self.generated_script = ""
src/switchcraft/gui_modern/views/wingetcreate_view.py (1)

20-20: Unused import SwitchCraftConfig.

This import is not used anywhere in the file and can be removed.

♻️ Suggested fix
 from switchcraft.utils.i18n import i18n
-from switchcraft.utils.config import SwitchCraftConfig
src/switchcraft/gui_modern/views/script_upload_view.py (1)

561-567: Silent exception handling in _show_snack.

While the pattern matches other views in the codebase, silently swallowing exceptions makes debugging harder. Consider logging at debug level.

♻️ Suggested improvement
     def _show_snack(self, msg, color="GREEN"):
         try:
             self.app_page.snack_bar = ft.SnackBar(ft.Text(msg), bgcolor=color)
             self.app_page.snack_bar.open = True
             self.app_page.update()
-        except Exception:
-            pass
+        except Exception as ex:
+            logger.debug(f"Failed to show snack: {ex}")
src/switchcraft/gui_modern/views/library_view.py (1)

110-115: Move sys import to module level.

sys is imported inside _get_scan_directories (line 110) and again inside _open_folder (line 297). Move it to module level for consistency and minor efficiency.

♻️ Suggested fix
 import flet as ft
 from switchcraft.utils.config import SwitchCraftConfig
 from switchcraft.utils.i18n import i18n

 import logging
 from datetime import datetime
 from pathlib import Path
 import os
+import sys

 logger = logging.getLogger(__name__)

Then remove the local imports at lines 110 and 297.

.github/workflows/test.yml (1)

30-32: Redundant pytest installation.

pip install .[test,gui] likely already includes pytest as a test dependency. The subsequent pip install pytest pytest-cov may be redundant. Consider consolidating by either adding pytest-cov to your [test] extras or removing the duplicate pytest install.

♻️ Suggested simplification
       - name: Install dependencies
         run: |
           python -m pip install --upgrade pip
           pip install .[test,gui]
-          pip install pytest pytest-cov
+          pip install pytest-cov  # pytest should be in [test] extras
src/switchcraft/gui_modern/views/intune_store_view.py (3)

80-92: Empty fallback branch does nothing.

The else block at lines 87-92 contains only pass with a comment. If neither goto_tab nor go is available, the user gets no feedback. Consider showing a snackbar message to inform users they need to navigate manually.

♻️ Suggested improvement
         else:
-             # Fallback: check if we have a way to signal tab change
-             # ModernApp stores 'app' on page in some instances or we can find the rail
-             # Since this is a view, we usually just show snackbar or let user navigate.
-             # But for best UX, we attempt to find the navigation method.
-             pass
+             self._show_snack("Please navigate to Settings manually.", "ORANGE")

163-169: Inconsistent i18n fallback pattern.

Lines 164-168 use default= parameter while line 56 and others use or pattern. For consistency and to leverage i18n's built-in fallback mechanism, prefer using default= throughout.


207-213: Silent exception swallowing may hide issues.

The _show_snack method catches all exceptions with a bare pass. While this prevents crashes, it also hides legitimate errors. Consider logging warnings for debugging purposes, consistent with the pattern in intune_view.py (line 448-454).

♻️ Proposed fix matching intune_view.py pattern
     def _show_snack(self, msg, color="GREEN"):
         try:
             self.app_page.snack_bar = ft.SnackBar(ft.Text(msg), bgcolor=color)
             self.app_page.snack_bar.open = True
             self.app_page.update()
-        except Exception:
-             pass
+        except Exception as e:
+            logger.warning(f"Failed to show snackbar: {e}")
tests/test_ui_interactions.py (1)

400-427: Consider extracting find_all_buttons_and_inputs to module level.

This helper is similar to find_buttons but includes TextFields. To improve maintainability and enable reuse, consider extracting it to module level alongside find_buttons.

src/switchcraft/gui_modern/app.py (1)

250-252: Redundant import statement.

import os at line 251 is redundant since os is already imported at line 2.

♻️ Remove redundant import
             # Set window icon paths
-            import os
             import sys
src/switchcraft/gui_modern/views/analyzer_view.py (7)

171-185: Verify create_tabs argument order matches expected signature.

The create_tabs helper function signature expects tabs as the first positional argument: def create_tabs(tabs, **kwargs). However, this call passes tabs as a keyword argument. While this works in Python, it's clearer to pass it positionally as intended:

♻️ Suggested refactor for clarity
-        source_tabs = create_tabs(
-            selected_index=0,
-            animation_duration=300,
-            on_change=on_source_tab_change,
-            tabs=[
+        source_tabs = create_tabs(
+            [
                 ft.Tab(
                     label=i18n.get("local_file") or "Local File",
                     icon=ft.Icons.COMPUTER
                 ),
                 ft.Tab(
                     label=i18n.get("download_url") or "URL Download",
                     icon=ft.Icons.LINK
                 )
-            ]
-        )
+            ],
+            selected_index=0,
+            animation_duration=300,
+            on_change=on_source_tab_change
+        )

250-267: Use context manager for HTTP response to ensure proper resource cleanup.

The requests.get() response object should be used with a context manager to ensure the connection is properly closed, especially with stream=True.

♻️ Suggested fix
-                # Download with progress
-                response = requests.get(url, stream=True, timeout=60)
-                response.raise_for_status()
-
-                total_size = int(response.headers.get('content-length', 0))
-                downloaded = 0
-
-                with open(temp_path, 'wb') as f:
-                    for chunk in response.iter_content(chunk_size=8192):
-                        if chunk:
-                            f.write(chunk)
-                            downloaded += len(chunk)
-                            if total_size > 0:
-                                pct = downloaded / total_size
-                                self.url_download_progress.value = pct
-                                self.url_download_status.value = f"{i18n.get('downloading') or 'Downloading'}: {int(pct*100)}%"
-                                self.update()
+                # Download with progress
+                with requests.get(url, stream=True, timeout=60) as response:
+                    response.raise_for_status()
+
+                    total_size = int(response.headers.get('content-length', 0))
+                    downloaded = 0
+
+                    with open(temp_path, 'wb') as f:
+                        for chunk in response.iter_content(chunk_size=8192):
+                            if chunk:
+                                f.write(chunk)
+                                downloaded += len(chunk)
+                                if total_size > 0:
+                                    pct = downloaded / total_size
+                                    self.url_download_progress.value = pct
+                                    self.url_download_status.value = f"{i18n.get('downloading') or 'Downloading'}: {int(pct*100)}%"
+                                    self.update()

822-866: LGTM - Platform detection properly implemented.

The admin elevation code now includes proper sys.platform == "win32" guards before Windows-specific ctypes.windll calls. This addresses the previous review concern about relying on exception handling for control flow.

Minor: Lines 839-840 have a duplicate comment # Restart as admin.

Remove duplicate comment
-                # Restart as admin
                 # Restart as admin
                 try:

952-961: Unused variable result_path - consider removing or renaming.

The result_path variable captures the return value from create_intunewin(), but it's never used. According to the IntuneService, this returns the tool's output text, not the file path. The code correctly searches for the .intunewin file instead, making this variable unnecessary.

♻️ Suggested fix
-                result_path = self.intune_service.create_intunewin(str(source), setup_file, str(output), quiet=True)
+                self.intune_service.create_intunewin(str(source), setup_file, str(output), quiet=True)

928-935: Inconsistent dialog opening patterns across methods.

This method correctly uses hasattr(self.app_page, 'open') with fallback for Flet compatibility. However, _show_manual_cmds (lines 1029-1031) still uses the old pattern without this check. Consider standardizing the approach.

♻️ Apply same pattern to _show_manual_cmds
-        self.app_page.dialog = dlg
-        dlg.open = True
-        self.app_page.update()
+        if hasattr(self.app_page, 'open'):
+            self.app_page.open(dlg)
+        else:
+            self.app_page.dialog = dlg
+            dlg.open = True
+            self.app_page.update()

1047-1053: Consider logging snackbar failures for debugging.

The exception is silently swallowed, which can hide UI issues during development. Adding a debug log would help troubleshoot snackbar display problems.

♻️ Add debug logging
         except Exception:
-            pass
+            logger.debug("Failed to show snackbar", exc_info=True)

226-229: Missing i18n for error message.

The error message "Invalid URL format" should use i18n.get() for consistency with other user-facing strings in this file.

♻️ Use i18n for error message
         if not url.startswith(("http://", "https://")):
-            self._show_snack("Invalid URL format", "RED")
+            self._show_snack(i18n.get("invalid_url_format") or "Invalid URL format", "RED")
             return
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e7eef9 and fdbd004.

📒 Files selected for processing (17)
  • .github/workflows/test.yml
  • src/switchcraft/assets/lang/de.json
  • src/switchcraft/assets/lang/en.json
  • src/switchcraft/gui_modern/app.py
  • src/switchcraft/gui_modern/utils/flet_compat.py
  • src/switchcraft/gui_modern/views/analyzer_view.py
  • src/switchcraft/gui_modern/views/dashboard_view.py
  • src/switchcraft/gui_modern/views/group_manager_view.py
  • src/switchcraft/gui_modern/views/intune_store_view.py
  • src/switchcraft/gui_modern/views/intune_view.py
  • src/switchcraft/gui_modern/views/library_view.py
  • src/switchcraft/gui_modern/views/macos_wizard_view.py
  • src/switchcraft/gui_modern/views/script_upload_view.py
  • src/switchcraft/gui_modern/views/wingetcreate_view.py
  • src/switchcraft/services/addon_service.py
  • tests/test_i18n_integrity.py
  • tests/test_ui_interactions.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/test_i18n_integrity.py
  • src/switchcraft/assets/lang/en.json
🧰 Additional context used
🧬 Code graph analysis (10)
src/switchcraft/gui_modern/views/intune_store_view.py (4)
src/switchcraft/gui_modern/nav_constants.py (1)
  • NavIndex (13-47)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/gui_modern/app.py (1)
  • goto_tab (771-779)
src/switchcraft/gui_modern/views/intune_view.py (1)
  • _show_snack (449-455)
src/switchcraft/gui_modern/views/dashboard_view.py (1)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/gui_modern/views/macos_wizard_view.py (3)
src/switchcraft/gui_modern/utils/file_picker_helper.py (2)
  • FilePickerHelper (5-85)
  • pick_file (12-38)
src/switchcraft/gui_modern/utils/flet_compat.py (1)
  • create_tabs (7-40)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/gui_modern/views/wingetcreate_view.py (2)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/gui_modern/views/winget_view.py (2)
  • _show_snack (666-672)
  • target (167-171)
src/switchcraft/gui_modern/views/analyzer_view.py (3)
src/switchcraft/gui_modern/utils/flet_compat.py (1)
  • create_tabs (7-40)
src/switchcraft/gui_modern/utils/file_picker_helper.py (2)
  • FilePickerHelper (5-85)
  • pick_file (12-38)
src/switchcraft/services/intune_service.py (1)
  • create_intunewin (59-143)
src/switchcraft/gui_modern/app.py (2)
src/switchcraft/gui_modern/nav_constants.py (1)
  • NavIndex (13-47)
src/switchcraft/services/notification_service.py (3)
  • add_notification (66-91)
  • get_notifications (115-116)
  • get_unread_count (112-113)
src/switchcraft/services/addon_service.py (2)
src/switchcraft/gui_modern/views/group_manager_view.py (1)
  • delete (246-260)
src/switchcraft/gui_modern/views/addon_manager_view.py (1)
  • delete (135-148)
src/switchcraft/gui_modern/views/library_view.py (1)
src/switchcraft/utils/config.py (1)
  • get_value (31-88)
src/switchcraft/gui_modern/views/group_manager_view.py (4)
src/switchcraft/gui_modern/nav_constants.py (1)
  • NavIndex (13-47)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/gui_modern/views/intune_store_view.py (1)
  • _show_snack (207-213)
src/switchcraft/gui_modern/app.py (1)
  • goto_tab (771-779)
src/switchcraft/gui_modern/views/script_upload_view.py (4)
src/switchcraft/gui_modern/utils/flet_compat.py (1)
  • create_tabs (7-40)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/gui_modern/views/addon_manager_view.py (1)
  • _show_snack (162-168)
src/switchcraft/gui_modern/views/winget_view.py (2)
  • _show_snack (666-672)
  • target (167-171)
🔇 Additional comments (49)
src/switchcraft/services/addon_service.py (4)

6-7: LGTM!

Standard library imports for temporary file handling are appropriate and correctly utilized in the new install_from_github method.


291-304: JSON parsing error handling properly implemented.

The explicit json.JSONDecodeError catch at lines 299-300 addresses the previous review concern about malformed JSON responses.


327-347: LGTM!

Asset discovery logic is clean and handles the expected naming conventions. The safety check for release_data being None at line 327 provides appropriate defensive coding.


349-374: Download and cleanup logic implemented correctly.

  • Streaming download with stream=True is appropriate for potentially large files
  • The 30-second timeout correctly applies to connection establishment
  • Temporary file cleanup in finally block ensures cleanup even on failure
  • Using os.path.exists before os.remove prevents errors if the file wasn't created
src/switchcraft/gui_modern/views/group_manager_view.py (4)

1-10: LGTM!

The imports are well-organized. NavIndex is correctly imported for navigation constants, and requests is appropriately imported for handling HTTP-specific exceptions in the error handling logic.


33-37: LGTM!

The ft.Button replacement aligns with the UI modernization pattern across views, and the i18n lookup with fallback is correctly implemented.


262-272: LGTM!

The delete confirmation dialog correctly uses app_page.open(dlg) and the ft.Button component. The background thread for deletion is appropriately daemonized.


289-304: LGTM!

The navigation correctly uses NavIndex.SETTINGS_GRAPH for both the primary and fallback navigation paths, which directs users to the Graph API settings tab where they can configure credentials. This aligns with the centralized navigation constants pattern.

src/switchcraft/gui_modern/views/macos_wizard_view.py (6)

1-19: LGTM on imports.

Good use of shlex for shell escaping and create_tabs helper for Flet compatibility.


35-56: LGTM!

Clean UI initialization with proper i18n fallbacks and layout structure.


107-132: LGTM!

Good use of create_tabs helper for Flet compatibility and correct label parameter for ft.Tab.


205-220: LGTM!

Clean implementation of local file picker with automatic app name derivation from filename.


338-360: LGTM on background upload logic.

The threading pattern with UI updates via self.update() is appropriate for Flet. Good error handling with logging for failures.


362-368: LGTM!

Defensive exception handling for snackbar display is appropriate for non-critical UI feedback.

src/switchcraft/gui_modern/views/wingetcreate_view.py (7)

25-31: LGTM!

The function properly handles the APPDATA environment variable with a fallback to the user's home directory, and safely creates the directory structure with parents=True, exist_ok=True.


57-72: LGTM!

The ft.Tab() constructor correctly uses the label parameter, which addresses the previous review feedback about the Flet API change.


497-510: Platform guard correctly implemented.

The subprocess startupinfo handling now properly checks sys.platform and conditionally passes the Windows-specific startup info. This addresses the previous review feedback.


590-603: Platform guard correctly implemented.

Consistent with _generate_new_manifest, the subprocess startup info is properly guarded.


678-689: LGTM!

Cross-platform implementation for opening the manifest directory is correct, handling Windows, macOS, and Linux appropriately with proper exception logging.


691-697: LGTM!

The snackbar helper follows the established pattern used in winget_view.py and packaging_wizard_view.py. The broad exception catch is acceptable here to prevent UI feedback operations from crashing the application.


311-322: No action required. ft.Button is valid and supported in Flet 0.80.1 (the minimum version in use). It remains part of the Flet API alongside newer Material 3 controls like FilledButton. The code is correct as written.

Likely an incorrect or invalid review comment.

src/switchcraft/gui_modern/views/script_upload_view.py (1)

437-541: Background thread directly updates UI controls.

The _browse_github_repo method spawns a background thread that directly modifies UI controls (self.github_script_list, self.github_status) and calls self.update() from the thread (line 539). Flet recommends marshaling UI updates to the main event loop using page.run_task() to avoid race conditions.

This pattern also appears in _upload_ps_script (line 197) and _upload_rem_script (line 336).

Consider using self.app_page.run_task() to schedule UI updates:

♻️ Example pattern
def _bg():
    try:
        # ... background work ...
        def update_ui():
            self.github_status.value = "Done"
            self.update()
        self.app_page.run_task(update_ui)
    except Exception as ex:
        def update_error():
            self.github_status.value = f"Error: {ex}"
            self.update()
        self.app_page.run_task(update_error)
src/switchcraft/gui_modern/views/library_view.py (1)

286-291: LGTM - Cross-platform folder opening implemented correctly.

The _open_folder method now correctly handles Windows, macOS, and Linux platforms. The dialog action properly closes the dialog and opens the folder.

src/switchcraft/gui_modern/views/intune_view.py (3)

172-202: Good: Connect handler properly marshals UI updates via run_task.

The connect handler correctly uses self.app_page.run_task() to schedule UI updates from the background thread. This is the proper Flet thread-safety pattern.


355-370: Good: Shell injection risk mitigated.

The open_folder handler now uses subprocess.run(['explorer', f'/select,{safe_path}']) with list arguments instead of shell=True, addressing the previous command injection concern.


449-455: Good: Exception now logged instead of silently swallowed.

The _show_snack method now logs failures with logger.warning() instead of using a bare except: pass, improving debuggability.

src/switchcraft/gui_modern/utils/flet_compat.py (1)

7-16: Function signature doesn't match caller usage.

The function signature create_tabs(tabs, **kwargs) expects tabs as the first positional argument, but callers (e.g., script_upload_view.py line 63) pass it as a keyword argument via tabs=[...] within **kwargs. This causes tabs to be None (or missing) when the caller doesn't pass it positionally.

🐛 Proposed fix
-def create_tabs(tabs, **kwargs):
+def create_tabs(tabs=None, **kwargs):
     """
     Create a Tabs control compatible with different Flet versions/environments.

     Tries standard ft.Tabs(tabs=...).
     Falls back to ft.Tabs(content=ft.TabBar(tabs=...), length=...) if needed.
     """
+    # Allow tabs to be passed as kwarg
+    if tabs is None:
+        tabs = kwargs.pop('tabs', None)
+    
     try:
         # Standard Flet
         return ft.Tabs(tabs=tabs, **kwargs)

Likely an incorrect or invalid review comment.

.github/workflows/test.yml (2)

34-38: LGTM - Test execution setup.

The pytest configuration with verbose output, short tracebacks, and explicit PYTHONPATH for the src layout is appropriate.


40-61: LGTM - CLI test job configuration.

The test-cli-core job correctly separates CLI testing with minimal dependencies (no GUI) and appropriate PYTHONPATH configuration.

src/switchcraft/gui_modern/views/intune_store_view.py (2)

23-27: LGTM - i18n integration for search field.

The i18n lookup with a sensible default fallback is properly implemented.


192-203: LGTM - Deploy button implementation.

The Deploy/Package button with placeholder snackbar feedback is a reasonable approach for work-in-progress functionality.

src/switchcraft/gui_modern/views/dashboard_view.py (4)

9-9: LGTM - Layout improvements.

The added spacing and scroll mode provide better visual consistency.


28-50: LGTM - Responsive layout structure.

The new container-based layout with explicit height constraints and responsive wrapping addresses the overflow concerns from past reviews.


155-158: LGTM - Fixed ListView overflow.

Using expand=True instead of a fixed height addresses the past review concern about ListView exceeding container bounds.


137-145: Review comment is incorrect - no localization bug exists.

The status values ("Analyzed", "Packaged", "Deployed") are stored in history as English strings and are never localized before storage or retrieval. The i18n.get("status_analyzed") in line 138 serves only as a fallback default if the status key is missing from the item dict—it does not translate the actual status value retrieved from item.get("status", ...). Therefore, the string comparisons in lines 140-145 correctly match against the canonical English values.

Likely an incorrect or invalid review comment.

tests/test_ui_interactions.py (4)

9-28: LGTM - Well-structured mock fixture.

The mock_page fixture properly mocks the essential Flet Page attributes needed for UI testing.


30-58: LGTM - Module-level helper extraction.

The find_buttons helper is now defined once at module level, addressing the previous duplication issue.


481-485: LGTM - on_change handler validation now fails test properly.

The missing pytest.fail call from the past review has been addressed at line 485.


203-205: No action needed. The current patch location is correct.

ModernAnalyzerView does not import HistoryService at the module level—it only imports it dynamically inside methods (lines 425 and 1057). For dynamic imports, patching at the source (switchcraft.services.history_service.HistoryService) is the correct approach. Patching at switchcraft.gui_modern.views.analyzer_view.HistoryService would not work since HistoryService is never imported into that namespace at module level.

The DashboardView comparison is not applicable here: DashboardView imports HistoryService at module level (line 2 of dashboard_view.py), which is why it can be patched at switchcraft.gui_modern.views.dashboard_view.HistoryService. ModernAnalyzerView uses a different import pattern and requires a different patch location accordingly.

Likely an incorrect or invalid review comment.

src/switchcraft/gui_modern/app.py (6)

17-21: LGTM - Graceful winotify import handling.

The try/except pattern for optional Windows toast notifications is appropriate for cross-platform compatibility.


37-38: LGTM - Navigation history initialization.

Initializing with [0] (Home) provides a sensible starting state for the back button functionality.


486-488: LGTM - Thread-safe UI updates.

Using self.page.run_task(update_ui) ensures UI mutations happen on the main thread, addressing the race condition concern from the past review.


862-871: LGTM - Robust view loading with error recovery.

The load_view helper with CrashDumpView fallback is a good pattern for graceful degradation when view initialization fails.


1056-1128: LGTM - Windows toast notification implementation.

The notification logic properly:

  • Checks notify_system flag before showing system toasts
  • Tracks last notification ID to avoid duplicates
  • Adds contextual action buttons based on notification type
  • Uses appropriate audio cues for errors

1168-1170: LGTM - Assets directory configuration.

Passing assets_dir to ft.run ensures Flet can locate static assets correctly during development.

src/switchcraft/gui_modern/views/analyzer_view.py (4)

7-8: LGTM - New imports support URL download and tab compatibility.

The addition of requests and tempfile supports the new URL download feature, and using create_tabs from flet_compat properly addresses Flet version compatibility concerns.

Also applies to: 20-20


121-158: LGTM - Well-structured URL download UI.

The URL download panel is well organized with proper i18n support, a clear visual hierarchy, and appropriate controls for user feedback (progress bar, status text).


405-414: LGTM - Proper cleanup handling for temporary files.

The cleanup logic correctly handles both directory and file paths, uses ignore_errors=True for robustness, and logs warnings on failure without disrupting the flow.


444-451: LGTM - Consistent i18n pattern with fallbacks.

The localization follows a consistent pattern using i18n.get("key") or "Fallback" throughout the results display, ensuring graceful degradation if translations are missing.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment thread src/switchcraft/assets/lang/de.json Outdated
Comment thread src/switchcraft/gui_modern/app.py
Comment thread src/switchcraft/gui_modern/app.py
Comment on lines +30 to +31
length = len(tabs) if tabs else 0
return ft.Tabs(content=ft.TabBar(tabs=tabs), length=length, **kwargs)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n src/switchcraft/gui_modern/utils/flet_compat.py

Repository: FaserF/SwitchCraft

Length of output: 1862


🌐 Web query:

Flet 0.80.1 Tabs TabBar API documentation

💡 Result:

Here are the official Flet API docs for Tabs / TabBar (v0.80.1):

  • TabBar (properties, indicator, alignment, secondary tabs, etc.). See docs. [1]
  • Tabs control (Tabs container: selected_index, length, scrollable, events, move_to, animation_duration, etc.). See docs. [2]
  • Tab (individual tab: label, icon, height, icon_margin, content). See docs. [3]

Sources:
[1] https://docs.flet.dev/controls/tabbar/
[2] https://docs.flet.dev/controls/tabs/
[3] https://docs.flet.dev/controls/tab/


🌐 Web query:

Flet Tabs content parameter API reference

💡 Result:

Short summary — Tabs.content (and Tab.content):

  • Property: content (type: Control) — the Control to display for a given tab (shown when that tab is selected). (docs.flet.dev)
  • Location: defined on Tab (each Tab has a content property) and Tabs also has a content property describing the currently displayed content. (flet.dev)
  • Usage notes:
    • Tab.content should be a Flet Control (Container, Row, Column, Text, etc.). (flet.dev)
    • When using TabBarView, the TabBarView.controls list length must match the Tabs.length; mismatches raise errors/ValueError. (docs.flet.dev)

For full reference and examples, see the Flet Tabs and TabBarView docs. (flet.dev)

Citations:


🌐 Web query:

Flet 0.80.1 Tabs constructor parameters example

💡 Result:

Example (Flet 0.80.1) — shows common Tabs constructor parameters:

import flet as ft

def main(page: ft.Page):
    page.add(
        ft.Tabs(
            # constructor parameters
            selected_index=1,        # int: which tab is selected
            length=3,                # int: total number of tabs
            expand=True,             # bool: let Tabs expand to fill available space
            animation_duration=ft.Duration(milliseconds=200),
            content=ft.Column(       # Control: layout containing TabBar + TabBarView
                expand=True,
                controls=[
                    ft.TabBar(
                        tabs=[
                            ft.Tab(label="Tab 1"),
                            ft.Tab(label="Tab 2"),
                            ft.Tab(label="Tab 3"),
                        ]
                    ),
                    ft.TabBarView(
                        expand=True,
                        controls=[
                            ft.Text("Content 1"),
                            ft.Text("Content 2"),
                            ft.Text("Content 3"),
                        ],
                    ),
                ],
            ),
            on_change=lambda e: print("selected_index:", e.control.selected_index),
        )
    )

if __name__ == "__main__":
    ft.run(main)

Source: Flet Tabs documentation (constructor usage and parameters). (docs.flet.dev) [1]

Citations:


Fix TabBar fallback—it incorrectly passes ft.TabBar alone as content.

The fallback at line 31 uses ft.Tabs(content=ft.TabBar(tabs=tabs), length=length, **kwargs), but Flet 0.80.1 requires content to be a Control containing both TabBar and TabBarView. Passing only TabBar will fail. Either remove this fallback or reconstruct it to include both controls in a Column.

🤖 Prompt for AI Agents
In `@src/switchcraft/gui_modern/utils/flet_compat.py` around lines 30 - 31, The
fallback builds ft.Tabs with only ft.TabBar as content which Flet 0.80.1
rejects; update the fallback in the block that computes length = len(tabs) if
tabs else 0 and returns ft.Tabs(...) to supply a Control that contains both the
TabBar and a TabBarView (for example wrap ft.TabBar(tabs=tabs) and a
corresponding ft.TabBarView(children=...) inside an ft.Column), or remove the
fallback entirely; ensure the TabBarView children align with the provided tabs
and keep the length argument passed to ft.Tabs.

Comment thread src/switchcraft/gui_modern/views/macos_wizard_view.py
Comment thread src/switchcraft/gui_modern/views/script_upload_view.py Outdated
Comment thread src/switchcraft/gui_modern/views/wingetcreate_view.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

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

⚠️ Outside diff range comments (1)
tests/test_full_coverage.py (1)

97-103: Redundant guard and broken docstring position.

Two issues:

  1. pytest.importorskip("winreg") is redundant since the skipif decorator already ensures this test only runs on Windows where winreg is available.

  2. The docstring now appears after a statement, making it a dead string literal rather than a proper docstring.

Proposed fix
 `@patch`("winreg.OpenKey")
 `@pytest.mark.skipif`(sys.platform != "win32", reason="winreg only on Windows")
 def test_addon_detection(mock_open_key):
-    pytest.importorskip("winreg")
     """Test registry detection for advanced addon."""
     # This is tricky to test on non-windows or without registry,
     # but we can verify it doesn't crash on import/usage
     assert not AddonService().is_addon_installed("fake_addon")
🤖 Fix all issues with AI agents
In `@src/switchcraft/assets/lang/de.json`:
- Line 811: The translation string for key "entra_group_manager_desc" uses the
formal "Sie" form ("Verwalten Sie Ihre ...") but should be informal; update the
value for "entra_group_manager_desc" to the informal phrasing (e.g., "Verwalte
deine Microsoft Entra ID (Azure AD) Gruppen.") to match the rest of the informal
translations.
- Line 624: The German translation for the key "update_manifest_desc" uses the
formal imperative "Aktualisieren Sie"; change it to the informal imperative form
by replacing the value with "Aktualisiere ein bestehendes Paket im winget-pkgs
Repository mit einer neuen Version." so the key "update_manifest_desc" uses the
informal tone consistent with other strings.
- Line 561: The translation for the "deploy_stack_started" key uses the formal
"Sie" while the rest of the file is informal; update the value of
"deploy_stack_started" to the informal imperative (e.g., replace "Prüfen Sie
Intune für den Fortschritt." with "Prüfe Intune auf den Fortschritt." or another
informal phrasing) so it matches the informal "Du" tone used across the file.

In `@src/switchcraft/gui_modern/views/addon_manager_view.py`:
- Around line 49-51: The code uses the wrong API for borders: replace the
incorrect ft.Border.all(...) call with the module-style ft.border.all(...) so it
matches the existing vertical_lines and horizontal_lines usage; update the call
in the AddonManagerView (the property named border) to use ft.border.all(1,
"GREY_400") to avoid AttributeError when constructing the control.

In `@src/switchcraft/gui_modern/views/group_manager_view.py`:
- Around line 82-90: The block that lists widgets (self.search_field,
self.refresh_btn, ..., self.delete_btn) needs to be constructed into an ft.Row
and assigned to the header variable so header exists for later use; replace the
bare list with header = ft.Row([self.search_field, self.refresh_btn,
ft.VerticalDivider(), self.create_btn, ft.Container(expand=True),
self.members_btn, self.delete_toggle, self.delete_btn],
alignment=ft.MainAxisAlignment.SPACE_BETWEEN) ensuring the variable name header
and ft.Row wrapper are used instead of leaving a raw list.

In `@src/switchcraft/gui_modern/views/intune_view.py`:
- Around line 324-326: The _log method mutates Flet UI and calls self.update()
directly, which is unsafe when invoked from background threads (e.g.,
progress_callback); modify _log to schedule the UI mutation on the main/UI
thread by wrapping the append and update calls inside a call to the Flet page's
run_task (e.g., self.page.run_task or run_async equivalent) so that the UI
append of ft.Text and self.update() execute on the UI thread; update any callers
(like progress_callback) to keep calling _log unchanged.
- Around line 383-388: The AlertDialog creation (dlg) is fine but calling
self.app_page.open(dlg) happens inside the _bg background thread; wrap that UI
call in ft.run_task to marshal it to the main thread (e.g. ft.run_task(lambda:
self.app_page.open(dlg))) so the dialog is opened safely from the main thread
and the open_folder callback remains captured.
♻️ Duplicate comments (4)
src/switchcraft/gui_modern/utils/flet_compat.py (1)

29-52: Incomplete fallback path—extracted tab_contents is never used.

Lines 29-34 iterate over tabs to extract .content into tab_contents, but this list is never passed to the constructed Tabs or TabBarView. Additionally, line 52 returns ft.Tabs(content=ft.TabBar(tabs=tabs), ...) which according to Flet 0.80.1 API is incomplete—the content property requires a single Control containing both TabBar (headers) and TabBarView (body), not just TabBar.

Suggested fix using extracted tab_contents
                 tab_contents = []
                 for t in (tabs or []):
                     if hasattr(t, "content"):
                         tab_contents.append(t.content)
                     else:
                         tab_contents.append(ft.Container()) # Empty placeholder

-                # Create a Column with TabBar and the content view (TabBarView not strict req if we manage visibility?)
-                # Actually, standard pattern is Column([TabBar, Expanded(TabBarView)])
-                # But we can just return a Column that acts as the container.
-
-                # We need to ensure logic works. Tabs usually handles switching.
-                # If we return a Column, existing code might expect .tabs property.
-                # But standard Flet Tabs has .tabs.
-
-                # Let's try to construct a valid ft.Tabs via kwargs, matching 0.80.1 requirement:
-                # ft.Tabs(selected_index=..., animation_duration=..., tabs=[...], expand=...)
-                # If that failed above (TypeError), it implies signature mismatch.
-
-                # Re-try assuming it's the `content` argument issue or similar.
-                # If we really need a fallback:
-
                 length = len(tabs) if tabs else 0
-                return ft.Tabs(content=ft.TabBar(tabs=tabs), length=length, **kwargs)
+                content_column = ft.Column([
+                    ft.TabBar(tabs=tabs),
+                    ft.TabBarView(controls=tab_contents, expand=True)
+                ], expand=True)
+                return ft.Tabs(content=content_column, length=length, **kwargs)
src/switchcraft/gui_modern/views/macos_wizard_view.py (1)

248-272: Critical: shlex.quote() double-quoting issue persists.

The past review identified that wrapping shlex.quote() output in additional quotes produces invalid shell syntax. This issue appears unresolved:

  • Line 250: FILENAME="{shlex.quote(filename)}" produces FILENAME="'installer.dmg'"
  • Line 257: DOWNLOAD_URL="{shlex.quote(url)}" produces DOWNLOAD_URL="'https://...'"
  • Line 271: APP_NAME="{shlex.quote(name)}" produces APP_NAME="'My App'"

shlex.quote() already returns a properly quoted string. The additional double quotes break the shell script.

🐛 Fix double-quoting
         if local_file:
             source_type = "local"
             filename = Path(local_file).name
             download_section = f'''# Local file deployment
-# Ensure {shlex.quote(filename)} is copied to the same location as this script
-FILENAME="{shlex.quote(filename)}"
+# Ensure {filename} is copied to the same location as this script
+FILENAME={shlex.quote(filename)}
 FILEPATH="$(dirname "$0")/$FILENAME"
 '''
         else:
             source_type = "url"
             filename = url.split("/")[-1] or "installer"
             download_section = f'''# Download from URL
-DOWNLOAD_URL="{shlex.quote(url)}"
+DOWNLOAD_URL={shlex.quote(url)}
 TEMP_DIR=$(mktemp -d)
 FILENAME=$(basename "$DOWNLOAD_URL")
 FILEPATH="$TEMP_DIR/$FILENAME"

-echo "Downloading {shlex.quote(name)}..."
+echo "Downloading $APP_NAME..."
 curl -L -o "$FILEPATH" "$DOWNLOAD_URL"
 '''

         # Template for DMG/PKG installation
         script = f'''#!/bin/bash
-# Auto-generated by SwitchCraft for {shlex.quote(name)}
-# Source: {"Local file" if source_type == "local" else shlex.quote(url)}
+# Auto-generated by SwitchCraft for {name}
+# Source: {"Local file" if source_type == "local" else url}

-APP_NAME="{shlex.quote(name)}"
+APP_NAME={shlex.quote(name)}
 {download_section}
src/switchcraft/gui_modern/views/wingetcreate_view.py (1)

585-588: Security: GitHub token exposed via command-line argument in _update_manifest.

While _generate_new_manifest correctly passes the token via environment variable (lines 491-495), _update_manifest still uses --token as a CLI argument (line 587). This exposes the token in process listings.

🔒 Use environment variable for token
                 # Output directory
                 cmd.extend(["--output", str(self.manifest_dir)])

                 # Submit PR?
+                env = os.environ.copy()
                 if self.upd_submit_pr.value and self.upd_github_token.value:
-                    cmd.extend(["--token", self.upd_github_token.value])
+                    env["WINGET_CREATE_GITHUB_TOKEN"] = self.upd_github_token.value
                     cmd.append("--submit")

                 # Run command
                 self.upd_output.value = f"Running: wingetcreate update {pkg_id}...\n\n"
                 self.update()

                 if sys.platform == "win32":
                     startupinfo = subprocess.STARTUPINFO()
                     startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
-                    kwargs = {"startupinfo": startupinfo}
+                    kwargs = {"startupinfo": startupinfo, "env": env}
                 else:
-                    kwargs = {}
+                    kwargs = {"env": env}
src/switchcraft/gui_modern/views/analyzer_view.py (1)

171-185: Tab parameter naming inconsistency with other views.

The code uses label= for Tab parameters (lines 174, 178), while other views in the project (intune_view.py, script_upload_view.py, macos_wizard_view.py) use text=. The create_tabs helper from flet_compat.py handles the compatibility, but for consistency with the codebase, consider using text=.

🧹 Nitpick comments (21)
.github/workflows/test.yml (2)

12-18: Remove unused os matrix variable or use it in runs-on.

The matrix.os is defined but never referenced—runs-on is hardcoded to windows-latest. Either remove the unused matrix variable or reference it properly.

Option 1: Remove unused matrix variable
     strategy:
       fail-fast: true
       max-parallel: 1
       matrix:
-        os: [windows-latest]
         python-version: ["3.13", "3.14"]
Option 2: Use matrix.os in runs-on
   test-backend:
-    runs-on: windows-latest
+    runs-on: ${{ matrix.os }}
     if: inputs.component == 'backend'

51-58: Clean up commented code and consider using .[test] extra for consistency.

The commented lines (55-56) add noise. For consistency with the backend job, consider installing the [test] extra instead of pytest separately.

♻️ Suggested cleanup
       - name: Install Core (No GUI)
         run: |
            python -m pip install --upgrade pip
            pip install .
-           # pip install pytest  <-- Included in .[test] or separate step if needed, but here we installed core only.
-           # Assuming we need pytest for testing:
            pip install pytest

Or, if [test] extra doesn't pull GUI dependencies:

       - name: Install Core (No GUI)
         run: |
            python -m pip install --upgrade pip
-           pip install .
-           # pip install pytest  <-- Included in .[test] or separate step if needed, but here we installed core only.
-           # Assuming we need pytest for testing:
-           pip install pytest
+           pip install .[test]
src/switchcraft/services/addon_service.py (1)

304-304: Consider: Response text logging could expose unexpected data.

Lines 304 and 322 log resp.text[:100] on parse failures. While useful for debugging, if GitHub's response ever includes unexpected content (e.g., error pages with tokens), this could leak into logs. This is low risk for GitHub's API but worth noting.

Also applies to: 322-322

src/switchcraft/gui_modern/views/group_manager_view.py (1)

321-456: Well-implemented members management dialog with proper threading.

The dialog implementation includes:

  • Lazy loading of members with progress indicator
  • Background threading for all API calls
  • Nested dialog for user search and addition
  • Proper i18n integration throughout

Minor observation: Line 389 uses if not query: return which will also trigger on whitespace-only input. Consider if not query.strip(): return for robustness.

src/switchcraft/gui_modern/views/script_upload_view.py (3)

447-464: URL parsing may fail for certain valid GitHub URL formats.

The URL parsing logic handles several formats but could fail on:

  • SSH URLs like [email protected]:owner/repo.git - the replace(":", "/") would produce [email protected]/owner/repo which lacks https://
  • URLs with trailing paths like https://github.com/owner/repo/tree/main

The current parsing at line 447 replaces : with / globally, which could break URLs containing port numbers or other colons.

♻️ Suggested improvement using urllib.parse
+                from urllib.parse import urlparse
+
                 # Parse GitHub URL
-                # Handle formats:
-                # https://github.com/owner/repo
-                # https://github.com/owner/repo.git
-                # github.com/owner/repo
-
-                clean_url = repo_url.replace("git@", "https://").replace(":", "/").rstrip("/")
-                if clean_url.endswith(".git"):
-                    clean_url = clean_url[:-4]
-                if not clean_url.startswith("http"):
-                    clean_url = "https://" + clean_url
-
-                # Split and find owner/repo
-                parts = clean_url.split("/")
-                # Expect https://github.com/owner/repo -> parts: [https:, '', github.com, owner, repo]
-                # Filter out empty strings
-                parts = [p for p in parts if p]
-
-                if len(parts) < 3:
-                     # fallback logic or error
-                     raise ValueError(i18n.get("invalid_github_url") or "Invalid GitHub URL")
-
-                owner = parts[-2]
-                repo = parts[-1]
+                # Handle SSH format: [email protected]:owner/repo.git
+                if repo_url.startswith("git@"):
+                    # Convert [email protected]:owner/repo.git -> github.com/owner/repo
+                    clean_url = repo_url.replace("git@", "").replace(":", "/", 1)
+                else:
+                    clean_url = repo_url
+                
+                if clean_url.endswith(".git"):
+                    clean_url = clean_url[:-4]
+                clean_url = clean_url.rstrip("/")
+                
+                if not clean_url.startswith("http"):
+                    clean_url = "https://" + clean_url
+                
+                parsed = urlparse(clean_url)
+                path_parts = [p for p in parsed.path.split("/") if p]
+                
+                if len(path_parts) < 2:
+                    raise ValueError(i18n.get("invalid_github_url") or "Invalid GitHub URL")
+                
+                owner = path_parts[0]
+                repo = path_parts[1]

460-461: Inconsistent indentation.

Line 460 has an extra space of indentation compared to surrounding code. Same issue at lines 545-547.

Fix indentation
                 if len(parts) < 3:
-                     # fallback logic or error
-                     raise ValueError(i18n.get("invalid_github_url") or "Invalid GitHub URL")
+                    # fallback logic or error
+                    raise ValueError(i18n.get("invalid_github_url") or "Invalid GitHub URL")

518-528: Script limit of 50 is undocumented to the user.

The code silently limits displayed scripts to 50 (line 518: ps_files[:50]). Users with larger repositories won't see all scripts and may not understand why. Consider showing a message when scripts are truncated.

♻️ Inform user when scripts are truncated
                 else:
                     self.repo_script_items = []
-                    for script_path in ps_files[:50]:  # Limit to 50
+                    MAX_SCRIPTS = 50
+                    for script_path in ps_files[:MAX_SCRIPTS]:
                         cb = ft.Checkbox(value=False)
                         self.repo_script_items.append({"path": script_path, "checkbox": cb})

                         self.github_script_list.controls.append(
                             ft.ListTile(
                                 leading=cb,
                                 title=ft.Text(script_path),
                                 trailing=ft.Icon(ft.Icons.DESCRIPTION, color="BLUE_400")
                             )
                         )
+                    
+                    if len(ps_files) > MAX_SCRIPTS:
+                        self.github_script_list.controls.append(
+                            ft.Text(
+                                f"Showing first {MAX_SCRIPTS} of {len(ps_files)} scripts",
+                                italic=True,
+                                color="ORANGE"
+                            )
+                        )
src/switchcraft/gui_modern/views/wingetcreate_view.py (2)

694-700: _show_snack is duplicated across multiple view files.

This identical method exists in at least 6 view files (see relevant_code_snippets). Consider extracting to a shared mixin or utility.

♻️ Extract to shared utility

Create a mixin or utility function:

# In src/switchcraft/gui_modern/utils/snack_helper.py
def show_snack(page, msg, color="GREEN"):
    try:
        page.snack_bar = ft.SnackBar(ft.Text(msg), bgcolor=color)
        page.snack_bar.open = True
        page.update()
    except Exception:
        pass

Then views can use show_snack(self.app_page, msg, color) instead of duplicating the method.


26-32: get_manifest_dir creates directories as side effect.

The function name suggests it only retrieves a path, but it also creates directories. This side effect could be unexpected for callers.

♻️ Rename or document the side effect
-def get_manifest_dir():
-    """Get the SwitchCraft manifest directory."""
+def get_or_create_manifest_dir():
+    """Get the SwitchCraft manifest directory, creating it if needed."""
src/switchcraft/gui_modern/app.py (2)

293-297: Hardcoded pending navigation indices don't use NavIndex constants.

Lines 293-295 use hardcoded indices (2, 3) for --wizard and --analyzer arguments instead of NavIndex constants. This creates a maintenance burden if indices change.

♻️ Use NavIndex constants
         # Handle Jump List Arguments (Launch Flags)
         import sys
         if "--wizard" in sys.argv:
             # We need to defer this until UI is built
-            self._pending_nav_index = 2 # Wizard index
+            self._pending_nav_index = NavIndex.PACKAGING_WIZARD
         elif "--analyzer" in sys.argv or "--all-in-one" in sys.argv:
-             self._pending_nav_index = 3 # Analyzer index
+             self._pending_nav_index = NavIndex.ANALYZER
         else:
             self._pending_nav_index = None

1060-1063: Swallowing RuntimeError silently may hide issues.

The try/except RuntimeError: pass pattern at lines 1060-1063 (and similarly at 1083-1086, 801-804) hides all RuntimeErrors. While this handles "Control not attached" scenarios, it could mask other legitimate errors.

♻️ Log suppressed errors at debug level
         try:
             fade_container.update()
         except RuntimeError:
-            pass
+            pass  # Control not yet attached to page - expected during transitions
tests/test_navigation_integrity.py (1)

69-83: Test uses hardcoded index 21 which may become incorrect.

The test assumes dynamic addons start at index 21 (app._switch_to_tab(21)), but this depends on the number of static destinations. If static destinations change, this test will break. Consider using app.first_dynamic_index instead.

♻️ Use dynamic index calculation
     def test_dynamic_addon_handling(self):
         """Test that dynamic addons logic works (index >= 20)."""
         app = ModernApp(self.mock_page)

         # Mock dynamic addons
         app.dynamic_addons = [{'id': 'test_addon', 'name': 'Test Addon'}]
         # Mock addon service load
         app.addon_service = MagicMock()
         app.addon_service.load_addon_view.return_value = MagicMock()

-        # Dynamic index = 21 (20 + 1)
-        app._switch_to_tab(21)
+        # Use first_dynamic_index to calculate the correct tab index
+        dynamic_tab_index = app.first_dynamic_index  # First dynamic addon
+        app._switch_to_tab(dynamic_tab_index)

         # Check calls
         app.addon_service.load_addon_view.assert_called_with('test_addon')
src/switchcraft/gui_modern/views/intune_store_view.py (1)

191-202: Consider extracting the hardcoded placeholder message to i18n.

The "Deployment logic coming soon to Store view!" message on line 199 is hardcoded while other UI strings use i18n lookups.

Suggested improvement
-                    on_click=lambda _: self._show_snack("Deployment logic coming soon to Store view!", "BLUE")
+                    on_click=lambda _: self._show_snack(i18n.get("deploy_coming_soon") or "Deployment logic coming soon!", "BLUE")
src/switchcraft/gui_modern/views/stack_manager_view.py (2)

273-284: Missing validation for empty input when adding items to stack.

The _add_item_to_stack method checks if val is truthy but doesn't provide user feedback when the input is empty. Users won't know why nothing happened.

Suggested improvement
     def _add_item_to_stack(self, e):
         if not self.current_stack:
             self._show_snack(i18n.get("select_stack_first") or "Select a stack first", "ORANGE")
             return

         val = self.new_item_field.value
+        if not val or not val.strip():
+            self._show_snack(i18n.get("enter_app_name") or "Please enter an app name", "ORANGE")
+            return
+        val = val.strip()
         if val:
             self.stacks[self.current_stack].append(val)
             self._save_stacks()
             self._select_stack(self.current_stack)  # Refresh list
             self.new_item_field.value = ""
             self.update()

330-336: TODO placeholder for deployment logic.

The _execute_deploy method contains a TODO comment indicating the actual Intune deployment logic is not yet implemented. The snackbar message misleadingly says "Deployment started!" when nothing actually happens.

Would you like me to help draft the Intune deployment integration, or should this be tracked as a separate issue?

src/switchcraft/gui_modern/views/library_view.py (2)

94-131: Directory deduplication uses case-insensitive comparison but stores original case.

The normalization on line 126 uses .lower() for deduplication, which is correct for Windows but may cause issues on case-sensitive filesystems (Linux). Two directories differing only in case would be incorrectly deduplicated.

Platform-aware deduplication
         # Remove duplicates
         seen = set()
         unique_dirs = []
         for d in dirs:
-            d_normalized = os.path.normpath(d).lower()
+            d_normalized = os.path.normpath(d)
+            if sys.platform == "win32":
+                d_normalized = d_normalized.lower()
             if d_normalized not in seen:
                 seen.add(d_normalized)
                 unique_dirs.append(d)

154-166: Nested directory scanning could be slow with many subdirectories.

The one-level-down scan iterates all subdirectories without limit. In directories with many subdirectories (e.g., Downloads with hundreds of folders), this could cause performance issues.

Consider limiting subdirectory iteration
                 # Also check one level down (common structure)
-                for subdir in path.iterdir():
+                subdir_count = 0
+                for subdir in path.iterdir():
+                    if subdir_count >= 20:  # Limit subdirectory scanning
+                        break
                     if subdir.is_dir():
+                        subdir_count += 1
                         for file in subdir.glob("*.intunewin"):
src/switchcraft/gui_modern/views/analyzer_view.py (2)

226-230: Duplicate comment on line 227.

There's a duplicate # Validate URL comment on consecutive lines (227-228).

Remove duplicate comment
-        # Validate URL
         # Validate URL
         if not url.startswith(("http://", "https://")):

243-245: URL filename extraction may produce invalid filenames.

The filename extraction on line 243 doesn't sanitize for invalid filesystem characters. URLs with special characters in the path could cause issues.

Add filename sanitization
                 # Get filename from URL
                 filename = url.split("/")[-1].split("?")[0]
+                # Sanitize filename
+                filename = "".join(c for c in filename if c.isalnum() or c in ".-_")
                 if not filename.lower().endswith((".exe", ".msi")):
                     filename = "installer.exe"
tests/test_ui_interactions.py (2)

50-57: Remove dead TabBar handling code.

The TabBar handling block does nothing (just pass). Either implement proper Tab content traversal or remove this dead code.

♻️ Proposed fix
-    # Handle TabBar tabs
-    if isinstance(control, ft.TabBar) and control.tabs:
-        for tab in control.tabs:
-             # Tabs usually have content if they are TabBarView, but Tab control has content/label
-             # We just traverse Tab children? Tab doesn't have children usually unless it stores content
-             # Wait, Tab has 'content' or 'icon' etc.
-             pass
-
     return buttons

6-6: Remove unused import.

ModernHomeView is imported but never used in this file.

♻️ Proposed fix
-from switchcraft.gui_modern.views.home_view import ModernHomeView
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4febf20 and f24632a.

📒 Files selected for processing (26)
  • .github/workflows/test.yml
  • src/switchcraft/assets/lang/de.json
  • src/switchcraft/assets/lang/en.json
  • src/switchcraft/gui_modern/app.py
  • src/switchcraft/gui_modern/utils/flet_compat.py
  • src/switchcraft/gui_modern/views/addon_manager_view.py
  • src/switchcraft/gui_modern/views/analyzer_view.py
  • src/switchcraft/gui_modern/views/group_manager_view.py
  • src/switchcraft/gui_modern/views/helper_view.py
  • src/switchcraft/gui_modern/views/history_view.py
  • src/switchcraft/gui_modern/views/intune_store_view.py
  • src/switchcraft/gui_modern/views/intune_view.py
  • src/switchcraft/gui_modern/views/library_view.py
  • src/switchcraft/gui_modern/views/macos_wizard_view.py
  • src/switchcraft/gui_modern/views/script_upload_view.py
  • src/switchcraft/gui_modern/views/stack_manager_view.py
  • src/switchcraft/gui_modern/views/wingetcreate_view.py
  • src/switchcraft/services/addon_service.py
  • src/switchcraft/services/ai_service.py
  • src/switchcraft/services/intune_service.py
  • tests/test_full_coverage.py
  • tests/test_modern_layout.py
  • tests/test_navigation_integrity.py
  • tests/test_ui_interactions.py
  • tests/test_ui_regressions.py
  • tests/test_ui_startup.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/switchcraft/gui_modern/views/history_view.py
  • src/switchcraft/services/ai_service.py
  • src/switchcraft/services/intune_service.py
  • src/switchcraft/gui_modern/views/helper_view.py
  • tests/test_ui_startup.py
🧰 Additional context used
🧬 Code graph analysis (9)
tests/test_navigation_integrity.py (1)
src/switchcraft/gui_modern/app.py (3)
  • ModernApp (25-1169)
  • _switch_to_tab (830-1063)
  • main (1171-1175)
src/switchcraft/gui_modern/views/stack_manager_view.py (3)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/gui_modern/views/addon_manager_view.py (1)
  • _show_snack (162-168)
src/switchcraft/gui_modern/views/group_manager_view.py (1)
  • _show_snack (289-295)
src/switchcraft/gui_modern/app.py (2)
src/switchcraft/gui_modern/nav_constants.py (1)
  • NavIndex (13-47)
src/switchcraft/services/notification_service.py (3)
  • NotificationService (11-138)
  • add_notification (66-91)
  • get_notifications (115-116)
tests/test_ui_regressions.py (3)
tests/test_all_views.py (1)
  • page (12-16)
tests/test_gui_views.py (1)
  • page (19-20)
src/switchcraft/debug_views.py (1)
  • open (22-22)
src/switchcraft/gui_modern/views/wingetcreate_view.py (6)
src/switchcraft/gui_modern/views/stack_manager_view.py (1)
  • _show_snack (338-344)
src/switchcraft/gui_modern/views/intune_view.py (1)
  • _show_snack (472-478)
src/switchcraft/gui_modern/views/addon_manager_view.py (1)
  • _show_snack (162-168)
src/switchcraft/gui_modern/views/analyzer_view.py (1)
  • _show_snack (1050-1056)
src/switchcraft/gui_modern/views/macos_wizard_view.py (1)
  • _show_snack (362-368)
src/switchcraft/gui_modern/views/winget_view.py (1)
  • _show_snack (666-672)
src/switchcraft/gui_modern/views/intune_store_view.py (5)
src/switchcraft/gui_modern/nav_constants.py (1)
  • NavIndex (13-47)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/gui_modern/views/addon_manager_view.py (1)
  • _show_snack (162-168)
src/switchcraft/gui_modern/views/group_manager_view.py (1)
  • _show_snack (289-295)
src/switchcraft/gui_modern/views/intune_view.py (1)
  • _show_snack (472-478)
src/switchcraft/gui_modern/views/library_view.py (1)
src/switchcraft/utils/config.py (1)
  • get_value (31-88)
src/switchcraft/gui_modern/views/analyzer_view.py (3)
src/switchcraft/gui_modern/utils/flet_compat.py (1)
  • create_tabs (7-61)
src/switchcraft/gui_modern/utils/file_picker_helper.py (2)
  • FilePickerHelper (5-85)
  • pick_file (12-38)
src/switchcraft/services/intune_service.py (1)
  • create_intunewin (59-143)
src/switchcraft/gui_modern/views/group_manager_view.py (3)
src/switchcraft/gui_modern/nav_constants.py (1)
  • NavIndex (13-47)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/services/intune_service.py (4)
  • list_group_members (738-754)
  • remove_group_member (774-786)
  • search_users (788-812)
  • add_group_member (756-772)
🔇 Additional comments (33)
.github/workflows/test.yml (2)

21-38: LGTM!

The backend test steps are well-structured: proper Python version matrix usage, appropriate extras installation ([test,gui]), and correct PYTHONPATH configuration for the src-layout.


40-49: Consider adding Python 3.14 matrix for CLI tests.

The backend job tests both Python 3.13 and 3.14, but CLI tests only 3.13. If this inconsistency is intentional (e.g., CLI stability), this is fine. Otherwise, consider adding a matrix strategy here as well for consistent coverage.

tests/test_full_coverage.py (1)

5-5: LGTM!

The sys import is correctly added to support the sys.platform check in the skipif decorator.

tests/test_ui_regressions.py (1)

13-13: LGTM! Consistent mock setup for page.open.

The addition of page.open = MagicMock() across all test cases ensures the mock ft.Page object has the open method available, which is required by ModernApp and related components. This aligns with similar patterns in other test files.

Also applies to: 34-34, 44-44, 60-60, 79-79

src/switchcraft/gui_modern/views/addon_manager_view.py (1)

30-38: LGTM! Button type updates for UI consistency.

The change from ft.ElevatedButton to ft.Button aligns with the PR's broader UI refactoring across modern views.

src/switchcraft/services/addon_service.py (1)

263-378: Well-structured GitHub addon installation with proper error handling.

The implementation correctly:

  • Handles JSONDecodeError for both specific tag and latest release fetches (lines 299-304, 316-323)
  • Properly logs and returns on network errors (lines 327-329)
  • Uses streaming download for efficient memory usage (line 356)
  • Cleans up temporary files in a finally block (lines 372-374)
src/switchcraft/gui_modern/views/group_manager_view.py (1)

143-168: Good improvement: Proper requests exception handling.

The exception handlers now correctly use requests.exceptions.HTTPError and requests.exceptions.ConnectionError instead of Python built-ins. The fallback string-based detection in the generic Exception handler (lines 158-165) provides additional resilience for edge cases.

src/switchcraft/gui_modern/views/script_upload_view.py (2)

63-84: LGTM - Tab creation using compatibility helper.

Good use of create_tabs helper for Flet version compatibility. The tab switching logic is clean with proper index handling and dynamic content updates.


542-558: Placeholder implementations are clearly marked.

The import and deploy methods are appropriately stubbed with user-facing "Feature coming soon" messages. This is acceptable for incremental development.

src/switchcraft/gui_modern/views/macos_wizard_view.py (2)

115-129: LGTM - Tab creation with compatibility helper.

The dual-source tab implementation correctly uses create_tabs for Flet compatibility and handles tab switching cleanly.


205-220: Good UX: Auto-fill app name from filename.

The _pick_local_file method nicely auto-populates the app name from the selected file, cleaning up common patterns like dashes and underscores.

src/switchcraft/gui_modern/views/wingetcreate_view.py (2)

501-514: LGTM - Platform-guarded subprocess with environment variable for token.

Good implementation: the platform check for STARTUPINFO is properly guarded, and the GitHub token is passed via environment variable rather than CLI argument for security.


58-73: LGTM - Tab creation with compatibility helper and proper i18n.

Clean implementation using create_tabs for Flet compatibility with appropriate i18n fallbacks.

src/switchcraft/gui_modern/app.py (5)

460-463: Dialog state check is now defensive but self.page.dialog is app-managed.

The condition hasattr(self.page, "dialog") and dlg != self.page.dialog is a reasonable defensive check for the app-managed dialog pattern. The code now correctly handles cases where the dialog might be closed.


633-635: Good fix: Dynamic addon offset is now robust.

Capturing first_dynamic_index = len(self.destinations) after building static destinations (line 635) ensures dynamic addon indexing remains correct even if static destinations change. This addresses the previous fragility concern.


783-804: Back button navigation logic is correct.

The _go_back_handler properly pops the current view from history and navigates to the previous one. The visibility update correctly shows the back button only when there's history to navigate.


863-874: Good error handling: Views fall back to CrashDumpView on exception.

The load_view helper properly catches exceptions during view initialization and displays a CrashDumpView with error details and traceback. This prevents the app from crashing on view load failures.


1089-1137: Windows toast notification implementation is comprehensive and properly handles protocol registration.

The toast logic correctly:

  • Checks WINOTIFY_AVAILABLE and notification flags
  • Tracks _last_notif_id to avoid duplicate toasts
  • Adds contextual action buttons based on notification type
  • Uses appropriate audio for error vs. other notifications
  • Launches switchcraft:// URLs which are automatically registered in Windows Registry on first application run

The protocol handler is fully implemented in src/switchcraft/utils/protocol_handler.py and automatically registered during startup via is_protocol_registered() and register_protocol_handler() checks in modern_main.py. The implementation properly handles both frozen executables and script execution contexts, with no administrator privileges required.

tests/test_navigation_integrity.py (2)

34-67: Good coverage: Test verifies all static navigation destinations.

The test properly iterates through all destinations and verifies none result in "Unknown Tab" or "Unknown Category" errors. This provides confidence that the NavIndex-based routing covers all cases.


11-28: Test setup properly isolates UI-dependent code.

Good use of mocking to:

  • Patch _on_notification_update to avoid notification side effects
  • Patch flet.Control.update to prevent "Control must be added to page first" errors

This allows testing navigation logic without a real Flet runtime.

src/switchcraft/gui_modern/views/intune_store_view.py (2)

206-212: LGTM! _show_snack implementation properly added.

The method matches the pattern used in intune_view.py (lines 471-477), including the debug logging on exception. This resolves the previously flagged AttributeError issue.


80-91: Navigation fallback logic is well-structured.

The three-tier approach (NavIndex-based → routing → snackbar fallback) provides good resilience. Using NavIndex.SETTINGS_GRAPH correctly navigates to the Graph API settings tab.

src/switchcraft/gui_modern/views/stack_manager_view.py (1)

37-158: Well-structured UI layout with proper i18n integration.

The _build_ui method cleanly separates concerns with left panel for stack list and right panel for stack contents. The consistent use of i18n.get() with fallback defaults ensures graceful degradation.

tests/test_modern_layout.py (1)

8-66: LGTM! Consistent mocking of page.open across tests.

Adding page.open = MagicMock() to all test methods ensures ModernApp can be instantiated without errors when it tries to open dialogs. This aligns with the broader test suite update pattern.

src/switchcraft/gui_modern/views/library_view.py (1)

296-309: Cross-platform folder opening properly implemented.

The _open_folder method now correctly handles Windows (os.startfile), macOS (open), and Linux (xdg-open). This resolves the previously flagged AttributeError issue on non-Windows platforms.

src/switchcraft/gui_modern/views/analyzer_view.py (3)

238-298: LGTM! URL download with proper temp directory cleanup.

The implementation correctly handles temp directory cleanup in the finally block when download fails (lines 291-297). The analysis_started flag ensures cleanup only happens on failure, not when analysis takes over.


406-415: LGTM! Cleanup path handling in analysis completion.

The finally block properly cleans up both directory and file paths using appropriate methods (shutil.rmtree for directories, os.remove for files).


823-866: LGTM! Platform guard added for Windows-specific admin elevation.

The code now explicitly checks sys.platform == "win32" before using ctypes.windll APIs (lines 827-831, 843-845). Non-Windows platforms are handled gracefully. This resolves the previously flagged issue.

src/switchcraft/gui_modern/views/intune_view.py (1)

463-469: LGTM with minor style note.

The dialog structure is correct and properly called via run_task. The setattr(dlg, "open", False) pattern works but could be slightly clearer.

src/switchcraft/assets/lang/en.json (2)

364-366: LGTM - Key rename aligns with Microsoft's Entra rebranding.

The rename from settings_intune_* to settings_entra_* correctly reflects Microsoft's rebranding of Azure AD to Entra ID.


428-510: LGTM - Comprehensive localization expansion.

The new keys provide good coverage for the expanded UI features including dashboard, analyzer, Winget explorer, and Intune management.

src/switchcraft/assets/lang/de.json (1)

380-388: LGTM - Key renames and new keys properly translated.

The Entra key renames and wizard title updates are correctly translated and consistent with en.json.

tests/test_ui_interactions.py (1)

233-234: The patch location is correct. HistoryService is imported locally within functions (lines 426 and 1060 of analyzer_view.py), not at module level. With local imports, patching at the source module (switchcraft.services.history_service.HistoryService) is the proper approach, as the import statement executes after the patch is applied and receives the mocked version.

Likely an incorrect or invalid review comment.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment thread src/switchcraft/assets/lang/de.json Outdated
Comment thread src/switchcraft/assets/lang/de.json Outdated
Comment thread src/switchcraft/assets/lang/de.json Outdated
Comment on lines +49 to 51
border=ft.Border.all(1, "GREY_400"),
vertical_lines=ft.border.BorderSide(1, "GREY_400"),
horizontal_lines=ft.border.BorderSide(1, "GREY_400"),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Incorrect API: ft.Border.all() should be ft.border.all().

ft.Border is a class representing border configuration, not a utility module. The correct call is ft.border.all(1, "GREY_400") (lowercase b), which is used correctly on lines 50-51 for vertical_lines and horizontal_lines. This inconsistency will cause an AttributeError at runtime.

Proposed fix
-            border=ft.Border.all(1, "GREY_400"),
+            border=ft.border.all(1, "GREY_400"),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
border=ft.Border.all(1, "GREY_400"),
vertical_lines=ft.border.BorderSide(1, "GREY_400"),
horizontal_lines=ft.border.BorderSide(1, "GREY_400"),
border=ft.border.all(1, "GREY_400"),
vertical_lines=ft.border.BorderSide(1, "GREY_400"),
horizontal_lines=ft.border.BorderSide(1, "GREY_400"),
🤖 Prompt for AI Agents
In `@src/switchcraft/gui_modern/views/addon_manager_view.py` around lines 49 - 51,
The code uses the wrong API for borders: replace the incorrect
ft.Border.all(...) call with the module-style ft.border.all(...) so it matches
the existing vertical_lines and horizontal_lines usage; update the call in the
AddonManagerView (the property named border) to use ft.border.all(1, "GREY_400")
to avoid AttributeError when constructing the control.

Comment thread src/switchcraft/gui_modern/views/group_manager_view.py
Comment thread src/switchcraft/gui_modern/views/intune_view.py Outdated
Comment thread src/switchcraft/gui_modern/views/intune_view.py Outdated
Comment thread src/switchcraft/gui_modern/views/intune_view.py
@github-actions
github-actions Bot merged commit 63c8f40 into main Jan 15, 2026
10 of 11 checks passed
@github-actions
github-actions Bot deleted the more-bug-fixes branch January 15, 2026 09:20

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (3)
src/switchcraft/gui_modern/views/script_upload_view.py (1)

179-187: Fix .ps1 reading to handle common UTF-16 / BOM encodings (current utf-8 read will break).
PowerShell scripts are often UTF-16LE (or UTF-8 with BOM); open(..., encoding="utf-8") can raise UnicodeDecodeError and block uploads.

Proposed fix
@@
-                with open(self.script_path, "r", encoding="utf-8") as f:
-                    content = f.read()
+                data = Path(self.script_path).read_bytes()
+                try:
+                    content = data.decode("utf-8-sig")
+                except UnicodeDecodeError:
+                    # Common for Windows-authored PowerShell scripts
+                    content = data.decode("utf-16")
@@
-                with open(self.detect_path, "r", encoding="utf-8") as f:
-                    det_content = f.read()
-                with open(self.remediate_path, "r", encoding="utf-8") as f:
-                    rem_content = f.read()
+                det_data = Path(self.detect_path).read_bytes()
+                rem_data = Path(self.remediate_path).read_bytes()
+                try:
+                    det_content = det_data.decode("utf-8-sig")
+                except UnicodeDecodeError:
+                    det_content = det_data.decode("utf-16")
+                try:
+                    rem_content = rem_data.decode("utf-8-sig")
+                except UnicodeDecodeError:
+                    rem_content = rem_data.decode("utf-16")

Also applies to: 318-322

src/switchcraft/gui_modern/views/winget_view.py (1)

531-571: Avoid shell=True with interpolated package IDs (command injection + quoting hazards).
Both pkg_id and paths flow into shell commands; prefer argv lists and (for temp dirs) TemporaryDirectory() to ensure cleanup on exceptions.

Proposed fix
@@
-                tmp_dir = tempfile.mkdtemp()
-                # Run winget download
-                # Note: 'winget download' requires a newer winget version, but user has it if using SwitchCraft
-                cmd = f'winget download --id {pkg_id} --dir "{tmp_dir}" --accept-source-agreements --accept-package-agreements --silent'
-                subprocess.run(cmd, shell=True, check=True)
+                with tempfile.TemporaryDirectory() as tmp_dir:
+                    cmd = [
+                        "winget", "download",
+                        "--id", pkg_id,
+                        "--dir", tmp_dir,
+                        "--accept-source-agreements",
+                        "--accept-package-agreements",
+                        "--silent",
+                    ]
+                    subprocess.run(cmd, check=True)
@@
-                files = list(Path(tmp_dir).glob("*.*"))
+                files = list(Path(tmp_dir).glob("*.*"))
@@
-                shutil.rmtree(tmp_dir)
+                # TemporaryDirectory auto-cleans
@@
-                subprocess.Popen(f'start cmd /k "{cmd}"', shell=True)
+                subprocess.Popen(
+                    ["winget", "install", "--id", pkg_id, "--silent", "--accept-package-agreements", "--accept-source-agreements"]
+                )

Also applies to: 615-625

src/switchcraft/gui_modern/views/packaging_wizard_view.py (1)

613-646: Use set_user_preference() instead of non-existent set_value(), and use standardized Graph key names.

SwitchCraftConfig.set_value() does not exist in the codebase—this will crash at runtime. Replace with set_user_preference(). Additionally, for consistency with the rest of the application, use GraphTenantId and GraphClientId instead of IntuneTenantID and IntuneClientID (the config module provides an alias mapping between them, but GraphTenantId/GraphClientId are the canonical names used in settings_view.py).

Suggested fix
# Change from:
SwitchCraftConfig.set_value("IntuneTenantID", tenant)
SwitchCraftConfig.set_value("IntuneClientID", client)

# To:
SwitchCraftConfig.set_user_preference("GraphTenantId", tenant)
SwitchCraftConfig.set_user_preference("GraphClientId", client)
🤖 Fix all issues with AI agents
In @.github/workflows/test.yml:
- Around line 14-18: Add allow-prereleases: true to the actions/setup-python@v6
step so the workflow can install Python 3.14 (pre-release); locate the "Set up
Python ${{ matrix.python-version }}" step and add the with key
"allow-prereleases: true" alongside python-version. Also, if max-parallel: 1 in
the strategy matrix is intentional to serialize runs, add a short comment
explaining why (e.g., to avoid resource conflicts); otherwise remove or increase
max-parallel to allow parallel matrix jobs.

In `@src/switchcraft/gui_modern/views/stack_manager_view.py`:
- Line 81: Replace the incorrect use of the Flet API where ft.Border.all(...) is
called: change all occurrences to ft.border.all(...) (e.g., in the widget that
sets border=ft.Border.all(1, "WHITE10") and the similar occurrence later) so the
code uses the correct ft.border.all function; ensure both instances that mirror
the change in addon_manager_view.py are updated.
- Around line 322-331: Several views still use the old dialog pattern
("page.dialog = dlg" and "dlg.open = True") while the preferred API is
page.open(dlg)/page.close(dlg); find occurrences of the old pattern (e.g.,
assignments to page.dialog and dlg.open/dlg.close in addon_manager_view,
settings_view, packaging_wizard_view, etc.) and replace them with page.open(dlg)
to show and page.close(dlg) to hide, updating any on_click callbacks or cleanup
code that currently manipulates dlg.open or page.dialog to call
self.app_page.open(dlg) / self.app_page.close(dlg) (or page.open/page.close
where self.app_page is named differently) so all views consistently use the
newer Flet dialog API.
♻️ Duplicate comments (6)
src/switchcraft/services/addon_service.py (2)

295-308: JSONDecodeError handling correctly implemented.

The nested try/except json.JSONDecodeError properly guards against malformed JSON responses from the API, addressing the previous review feedback.


310-329: Previous issues properly addressed.

The fallback logic now includes:

  • JSONDecodeError handling (lines 321-323)
  • Proper exception handler with logging and error return (lines 327-329)

Both issues from the prior review have been resolved.

src/switchcraft/services/ai_service.py (1)

22-41: Dead code: Return statement in update_context will be ignored.

The guidance message constructed in lines 25-41 is unreachable/unused since update_context is called for its side effect (updating self.context), and callers don't use its return value. This code appears intended for the ask method.

Consider removing this dead code block since the ask method (lines 43-49) already provides the stub response.

Proposed fix
         def update_context(self, data: dict):
             self.context = data
-
-            title = i18n.get("ai_addon_required_title") or "🤖 **AI Addon Required**"
-            msg = i18n.get("ai_addon_required_msg") or (
-                "The AI Assistant addon is not installed. This feature requires the AI Addon "
-                "to be installed via the Addon Manager to get intelligent responses."
-            )
-            tips_header = i18n.get("ai_tips_header") or "**In the meantime, here are some tips:**"
-
-            return (
-                f"{title}\n\n"
-                f"{msg}\n\n"
-                f"{tips_header}\n"
-                "• For MSI files: Use `/qn /norestart` for silent install\n"
-                "• For NSIS: Use `/S` (case sensitive)\n"
-                "• For Inno Setup: Use `/VERYSILENT /SUPPRESSMSGBOXES`\n"
-                "• For InstallShield: Use `/s /v\"/qn\"`\n\n"
-                f"Your question: *{self.context.get('query', 'Unknown')}*"
-            )
src/switchcraft/gui_modern/views/addon_manager_view.py (1)

50-52: Incorrect API: ft.Border.all() should be ft.border.all().

Line 50 uses ft.Border.all(1, "GREY_400") which will raise AttributeError at runtime. The correct API is ft.border.all() (lowercase b), consistent with the ft.border.BorderSide usage on lines 51-52.

Proposed fix
-            border=ft.Border.all(1, "GREY_400"),
+            border=ft.border.all(1, "GREY_400"),
src/switchcraft/gui_modern/views/analyzer_view.py (1)

828-904: Admin elevation now has an explicit platform guard.

src/switchcraft/gui_modern/views/intune_view.py (1)

393-395: _show_snack still called from background thread without thread-safety wrapper.

Line 395 calls _show_snack directly from within the _bg thread. The ViewMixin._show_snack method modifies page.snack_bar and calls page.update() directly, which violates Flet thread-safety requirements.

This was flagged in a previous review and marked as addressed, but the fix appears to be missing from the current code.

🔒️ Proposed fix
             except Exception as ex:
                 self._log(f"ERROR: {ex}")
-                self._show_snack(f"Failed: {ex}", "RED")
+                self.app_page.run_task(lambda ex=ex: self._show_snack(f"Failed: {ex}", "RED"))
🧹 Nitpick comments (17)
src/switchcraft/utils/protocol_handler.py (1)

134-157: LGTM!

Clean URL parsing with sensible defaults. The function handles edge cases well.

Optional consideration: If query parameters (?param=value) or additional path segments are needed in the future, the current implementation would need extension. For now, this simplicity is appropriate given the documented use cases.

src/switchcraft/services/addon_service.py (1)

354-378: Response connection not explicitly closed with stream=True.

When using stream=True, the underlying connection is not automatically released until all data is consumed or the response is explicitly closed. If an exception occurs during iter_content, the connection may leak.

Consider wrapping the response in a context manager to ensure proper cleanup.

♻️ Suggested improvement
         try:
             logger.info(f"Downloading {asset_name} from {asset_url}...")
-            r = requests.get(asset_url, stream=True, timeout=30)
-            r.raise_for_status()
-
-            tmp_path = None
-            try:
-                with tempfile.NamedTemporaryFile(delete=False, suffix=".zip") as tmp:
-                    for chunk in r.iter_content(chunk_size=8192):
-                        tmp.write(chunk)
-                    tmp_path = tmp.name
-
-                self.install_addon(tmp_path)
-                logger.info(f"Successfully installed {addon_id} from GitHub.")
-                result_msg = f"Installed {addon_id} ({used_tag})."
-                if warning_msg:
-                    result_msg += f" [Warning: {warning_msg}]"
-                return True, result_msg
-            finally:
-                if tmp_path and os.path.exists(tmp_path):
-                    os.remove(tmp_path)
+            with requests.get(asset_url, stream=True, timeout=30) as r:
+                r.raise_for_status()
+
+                tmp_path = None
+                try:
+                    with tempfile.NamedTemporaryFile(delete=False, suffix=".zip") as tmp:
+                        for chunk in r.iter_content(chunk_size=8192):
+                            tmp.write(chunk)
+                        tmp_path = tmp.name
+
+                    self.install_addon(tmp_path)
+                    logger.info(f"Successfully installed {addon_id} from GitHub.")
+                    result_msg = f"Installed {addon_id} ({used_tag})."
+                    if warning_msg:
+                        result_msg += f" [Warning: {warning_msg}]"
+                    return True, result_msg
+                finally:
+                    if tmp_path and Path(tmp_path).exists():
+                        Path(tmp_path).unlink()
.github/workflows/test.yml (1)

39-48: Consider aligning Python version coverage with backend tests.

The CLI core tests run only on Python 3.13, while backend tests cover both 3.13 and 3.14. If CLI compatibility with Python 3.14 is important, consider adding a matrix here as well for consistency.

If the single-version approach is intentional (e.g., CLI is stable and doesn't need pre-release testing), this is fine as-is.

src/switchcraft/gui_modern/views/stack_manager_view.py (1)

333-339: Noted: TODO for actual deployment implementation.

The _execute_deploy method currently only shows a feedback message. The TODO comment indicates actual Intune deployment logic is pending.

Would you like me to open an issue to track the implementation of the actual Intune deployment logic?

src/switchcraft/gui_modern/views/library_view.py (2)

156-172: Silent exception swallowing may hide important errors.

The inner except Exception: pass at lines 171-172 silently ignores all subdirectory scanning errors. While this prevents crashes, it could mask issues beyond permission errors (e.g., corrupted filesystem entries).

♻️ Consider logging at debug level instead of silent pass
                 try:
                     subdirs = [x for x in path.iterdir() if x.is_dir()]
                     for subdir in subdirs[:20]: # Limit subdirectory scan
                         for file in subdir.glob("*.intunewin"):
                             if file.is_file():
                                 stat = file.stat()
                                 self.all_files.append({
                                     'path': str(file),
                                     'filename': file.name,
                                     'size': stat.st_size,
                                     'modified': datetime.fromtimestamp(stat.st_mtime),
                                     'directory': str(subdir)
                                 })
-                except Exception:
-                    pass # Ignore permission errors during scan
+                except PermissionError:
+                    logger.debug(f"Permission denied scanning subdirs of {scan_dir}")
+                except Exception as ex:
+                    logger.debug(f"Error scanning subdirs of {scan_dir}: {ex}")

292-297: Tuple expression in lambda may not execute both actions reliably.

The lambda on_click=lambda e: (self.app_page.close(dlg), self._open_folder(path)) uses a tuple to chain actions. While this works in Python, if close(dlg) raises an exception, _open_folder won't execute.

♻️ Consider a dedicated handler for clarity and robustness
+        def close_and_open(e):
+            self.app_page.close(dlg)
+            self._open_folder(path)
+
         dlg = ft.AlertDialog(
             title=ft.Text(item.get('filename', 'Unknown')),
             content=ft.Column([...], tight=True, spacing=10),
             actions=[
                 ft.TextButton(i18n.get("btn_cancel") or "Close", on_click=lambda e: self.app_page.close(dlg)),
                 ft.Button(
                     i18n.get("open_folder") or "Open Folder",
                     icon=ft.Icons.FOLDER_OPEN,
-                    on_click=lambda e: (self.app_page.close(dlg), self._open_folder(path))
+                    on_click=close_and_open
                 )
             ]
         )
src/switchcraft/gui_modern/views/macos_wizard_view.py (1)

263-265: Potential issue: shlex.quote(name) in echo statement may look odd to users.

Line 263 uses echo "Downloading {shlex.quote(name)}..." which will produce output like Downloading 'My App'... with quotes visible in the terminal. This is safe but may look unusual.

♻️ Consider using APP_NAME variable for consistency
-echo "Downloading {shlex.quote(name)}..."
+echo "Downloading $APP_NAME..."

This also avoids duplicating the escaped name and relies on the already-set variable.

src/switchcraft/gui_modern/views/wingetcreate_view.py (1)

599-612: Environment variable not passed in kwargs for update command.

While the token is correctly set in env at lines 595-597, the subprocess.run call passes env=env separately from **kwargs. This works but note that kwargs doesn't include env on Windows, causing the environment to be passed differently than in _generate_new_manifest.

♻️ Consider consistent kwargs handling
                 if sys.platform == "win32":
                     startupinfo = subprocess.STARTUPINFO()
                     startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
-                    kwargs = {"startupinfo": startupinfo}
+                    kwargs = {"startupinfo": startupinfo, "env": env}
                 else:
-                    kwargs = {}
+                    kwargs = {"env": env}

                 result = subprocess.run(
                     cmd,
                     capture_output=True,
                     text=True,
                     timeout=120,
-                    env=env,
                     **kwargs
                 )

This makes the pattern consistent with _generate_new_manifest.

tests/test_ui_interactions.py (1)

30-52: Minor: Remove extra blank lines.

Lines 50-51 have unnecessary blank lines before the return statement.

♻️ Suggested fix
     elif hasattr(control, "content") and control.content:
         buttons.extend(find_buttons(control.content))
-
-
-
     return buttons
src/switchcraft/gui_modern/utils/flet_compat.py (1)

57-72: Remove the misleading positional Tabs call; fall back directly to Column.

Line 59 attempts ft.Tabs(tab_bar, length, **kwargs) with positional arguments, but Flet 0.80's Tabs constructor accepts only keyword arguments (e.g., tabs=, selected_index=, length=). This call will always fail and trigger the except block, making the intermediate try unnecessary and confusing.

Since the Column fallback (lines 63-72) is the intended outcome when TabBar-based construction is needed, remove lines 57-60 and go directly to the Column fallback to simplify the flow and avoid the misleading positional attempt.

src/switchcraft/gui_modern/views/script_upload_view.py (1)

341-423: GitHub browse: honor github_path and verify Trees API/auth scheme.

  • github_path is currently unused; either remove it or filter ps_files by prefix/exact match.
  • Please verify whether /repos/{owner}/{repo}/git/trees/{branch} works with branch names in your target GitHub repos; some setups require resolving the branch to a tree SHA first.
  • Also verify whether Authorization: token … works for the PAT types you support (some tokens prefer Bearer …).
Minimal improvement (path filter + auth header)
@@
-                headers = {}
-                if self.github_pat.value:
-                    headers["Authorization"] = f"token {self.github_pat.value}"
+                headers = {"Accept": "application/vnd.github+json"}
+                if self.github_pat.value:
+                    headers["Authorization"] = f"Bearer {self.github_pat.value}"
@@
-                ps_files = [
+                ps_files = [
                     item["path"] for item in data.get("tree", [])
                     if item["path"].endswith(".ps1") and item["type"] == "blob"
                 ]
+
+                path_filter = (self.github_path.value or "").strip().lstrip("/")
+                if path_filter:
+                    if path_filter.lower().endswith(".ps1"):
+                        ps_files = [p for p in ps_files if p == path_filter]
+                    else:
+                        prefix = path_filter.rstrip("/") + "/"
+                        ps_files = [p for p in ps_files if p.startswith(prefix)]

Also applies to: 425-553

src/switchcraft/gui_modern/views/winget_view.py (1)

241-247: Prefer i18n.get(..., count=...) over positional .format() for pluralization.
This avoids translation-placeholder mismatches (e.g., {count} vs {0}).

Proposed fix
@@
-        self.results_count.value = (i18n.get("apps_found") or "Found {0} apps").format(count) if count != 1 else (i18n.get("app_found") or "Found 1 app")
+        if count == 1:
+            self.results_count.value = i18n.get("app_found", default="Found 1 app")
+        else:
+            self.results_count.value = i18n.get("apps_found", default="Found {count} apps", count=count)
src/switchcraft/gui_modern/views/settings_view.py (1)

332-341: Don’t write Graph creds to registry/keyring on every keystroke.
on_change → frequent writes (especially set_secret(...)) can make typing laggy and causes needless secure-store churn; on_blur (or an explicit “Save”) is safer.

Proposed fix
@@
-        tenant.on_change=lambda e: SwitchCraftConfig.set_user_preference("GraphTenantId", e.control.value)
+        tenant.on_blur = lambda e: SwitchCraftConfig.set_user_preference("GraphTenantId", e.control.value)
@@
-        client.on_change=lambda e: SwitchCraftConfig.set_user_preference("GraphClientId", e.control.value)
+        client.on_blur = lambda e: SwitchCraftConfig.set_user_preference("GraphClientId", e.control.value)
@@
-        secret.on_change=lambda e: SwitchCraftConfig.set_secret("GraphClientSecret", e.control.value)
+        secret.on_blur = lambda e: SwitchCraftConfig.set_secret("GraphClientSecret", e.control.value)
src/switchcraft/gui_modern/views/packaging_wizard_view.py (2)

229-252: Consider using create_tabs(..., tabs=[Tab(content=...)]) instead of ft.Tabs(content=..., length=...).
This file reintroduces a Tabs construction style that has been problematic across environments; aligning with create_tabs() would likely be more robust and simpler (no manual body swapping).


269-317: URL download: stream consistently + update progress (avoid r.content full-buffer).
When content-length is missing, r.content loads the whole file in memory.

Proposed fix
@@
-                with requests.get(url, stream=True, timeout=30) as r:
-                    r.raise_for_status()
-                    total_length = r.headers.get('content-length')
-
-                    with open(target_path, 'wb') as f:
-                         if total_length is None: # no content length header
-                             f.write(r.content)
-                         else:
-                             dl = 0
-                             total_length = int(total_length)
-                             for data in r.iter_content(chunk_size=4096):
-                                 dl += len(data)
-                                 f.write(data)
-                                 # self.download_progress.value = dl / total_length
-                                 # self.update()
+                with requests.get(url, stream=True, timeout=30) as r:
+                    r.raise_for_status()
+                    total_length = int(r.headers.get("content-length") or 0)
+                    dl = 0
+
+                    # Indeterminate unless we can compute pct
+                    self.download_progress.value = None if total_length == 0 else 0
+                    self.update()
+
+                    with open(target_path, "wb") as f:
+                        for chunk in r.iter_content(chunk_size=8192):
+                            if not chunk:
+                                continue
+                            f.write(chunk)
+                            if total_length:
+                                dl += len(chunk)
+                                self.download_progress.value = dl / total_length
+                                self.update()
src/switchcraft/gui_modern/views/analyzer_view.py (1)

51-72: Align analyzer file types: picker allows scripts, but drop handlers only accept .exe/.msi.
Either restrict the picker back to installer types or extend the drop/analysis path to handle the extra extensions consistently.

src/switchcraft/gui_modern/views/intune_view.py (1)

466-472: Consider clearer dialog close handler.

The setattr(...) or self.app_page.update() pattern works but is unconventional. A nested function would be more readable.

♻️ Suggested improvement
     def _show_success_dialog(self, app_id):
+        def close_dialog(e):
+            dlg.open = False
+            self.app_page.update()
+
         dlg = ft.AlertDialog(
             title=ft.Row([ft.Icon(ft.Icons.CHECK_CIRCLE, color="GREEN"), ft.Text("Upload Successful")]),
             content=ft.Text(f"App ID: {app_id}\nSupersedence configured if selected."),
-            actions=[ft.TextButton("Close", on_click=lambda e: setattr(dlg, "open", False) or self.app_page.update())]
+            actions=[ft.TextButton("Close", on_click=close_dialog)]
         )
         self.app_page.open(dlg)
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 67ce854 and b02d591.

📒 Files selected for processing (25)
  • .github/workflows/test.yml
  • src/switchcraft/assets/lang/de.json
  • src/switchcraft/assets/lang/en.json
  • src/switchcraft/gui_modern/app.py
  • src/switchcraft/gui_modern/utils/flet_compat.py
  • src/switchcraft/gui_modern/utils/view_utils.py
  • src/switchcraft/gui_modern/views/addon_manager_view.py
  • src/switchcraft/gui_modern/views/analyzer_view.py
  • src/switchcraft/gui_modern/views/group_manager_view.py
  • src/switchcraft/gui_modern/views/intune_store_view.py
  • src/switchcraft/gui_modern/views/intune_view.py
  • src/switchcraft/gui_modern/views/library_view.py
  • src/switchcraft/gui_modern/views/macos_wizard_view.py
  • src/switchcraft/gui_modern/views/packaging_wizard_view.py
  • src/switchcraft/gui_modern/views/script_upload_view.py
  • src/switchcraft/gui_modern/views/settings_view.py
  • src/switchcraft/gui_modern/views/stack_manager_view.py
  • src/switchcraft/gui_modern/views/winget_view.py
  • src/switchcraft/gui_modern/views/wingetcreate_view.py
  • src/switchcraft/services/addon_service.py
  • src/switchcraft/services/ai_service.py
  • src/switchcraft/utils/protocol_handler.py
  • tests/test_navigation_integrity.py
  • tests/test_phase2.py
  • tests/test_ui_interactions.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_navigation_integrity.py
🧰 Additional context used
🧬 Code graph analysis (16)
src/switchcraft/gui_modern/views/script_upload_view.py (3)
src/switchcraft/gui_modern/utils/flet_compat.py (1)
  • create_tabs (7-81)
src/switchcraft/gui_modern/utils/view_utils.py (2)
  • ViewMixin (6-22)
  • _show_snack (9-22)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/gui_modern/views/group_manager_view.py (3)
src/switchcraft/gui_modern/nav_constants.py (1)
  • NavIndex (13-47)
src/switchcraft/gui_modern/utils/view_utils.py (2)
  • ViewMixin (6-22)
  • _show_snack (9-22)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/gui_modern/views/settings_view.py (2)
src/switchcraft/gui_modern/utils/view_utils.py (2)
  • ViewMixin (6-22)
  • _show_snack (9-22)
src/switchcraft/utils/i18n.py (2)
  • get (143-170)
  • set_language (136-141)
src/switchcraft/gui_modern/views/packaging_wizard_view.py (5)
src/switchcraft/gui_modern/utils/view_utils.py (1)
  • ViewMixin (6-22)
src/switchcraft/gui_modern/utils/file_picker_helper.py (2)
  • FilePickerHelper (5-85)
  • pick_file (12-38)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/gui_modern/views/analyzer_view.py (1)
  • on_progress (381-387)
src/switchcraft/controllers/analysis_controller.py (1)
  • analyze_file (39-194)
src/switchcraft/gui_modern/views/addon_manager_view.py (2)
src/switchcraft/gui_modern/utils/view_utils.py (1)
  • ViewMixin (6-22)
src/switchcraft/gui_modern/views/group_manager_view.py (1)
  • delete (263-277)
src/switchcraft/gui_modern/views/library_view.py (2)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/utils/config.py (1)
  • get_value (31-88)
src/switchcraft/gui_modern/views/analyzer_view.py (4)
src/switchcraft/gui_modern/utils/flet_compat.py (1)
  • create_tabs (7-81)
src/switchcraft/gui_modern/utils/view_utils.py (2)
  • ViewMixin (6-22)
  • _show_snack (9-22)
src/switchcraft/gui_modern/utils/file_picker_helper.py (2)
  • FilePickerHelper (5-85)
  • pick_file (12-38)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/gui_modern/views/stack_manager_view.py (2)
src/switchcraft/gui_modern/utils/view_utils.py (2)
  • ViewMixin (6-22)
  • _show_snack (9-22)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/services/addon_service.py (3)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/gui_modern/views/addon_manager_view.py (1)
  • delete (136-149)
src/switchcraft/gui_modern/views/group_manager_view.py (1)
  • delete (263-277)
src/switchcraft/gui_modern/views/intune_view.py (5)
src/switchcraft/services/intune_service.py (2)
  • authenticate (147-164)
  • upload_win32_app (191-340)
src/switchcraft/gui_modern/utils/file_picker_helper.py (2)
  • FilePickerHelper (5-85)
  • pick_file (12-38)
src/switchcraft/utils/config.py (1)
  • get_value (31-88)
src/switchcraft/gui_modern/utils/view_utils.py (2)
  • ViewMixin (6-22)
  • _show_snack (9-22)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/gui_modern/utils/view_utils.py (1)
src/switchcraft/debug_views.py (2)
  • open (22-22)
  • update (19-19)
src/switchcraft/services/ai_service.py (2)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft_ai/service.py (1)
  • ask (91-105)
src/switchcraft/gui_modern/views/macos_wizard_view.py (4)
src/switchcraft/gui_modern/utils/file_picker_helper.py (2)
  • FilePickerHelper (5-85)
  • pick_file (12-38)
src/switchcraft/gui_modern/utils/flet_compat.py (1)
  • create_tabs (7-81)
src/switchcraft/gui_modern/utils/view_utils.py (2)
  • ViewMixin (6-22)
  • _show_snack (9-22)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/gui_modern/views/intune_store_view.py (4)
src/switchcraft/gui_modern/nav_constants.py (1)
  • NavIndex (13-47)
src/switchcraft/gui_modern/utils/view_utils.py (2)
  • ViewMixin (6-22)
  • _show_snack (9-22)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/gui_modern/app.py (1)
  • goto_tab (773-781)
src/switchcraft/gui_modern/views/wingetcreate_view.py (3)
src/switchcraft/gui_modern/utils/flet_compat.py (1)
  • create_tabs (7-81)
src/switchcraft/gui_modern/utils/view_utils.py (2)
  • ViewMixin (6-22)
  • _show_snack (9-22)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/gui_modern/views/winget_view.py (2)
src/switchcraft/gui_modern/utils/view_utils.py (2)
  • ViewMixin (6-22)
  • _show_snack (9-22)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
⏰ 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: test-backend / test-backend (3.13)
🔇 Additional comments (71)
src/switchcraft/utils/protocol_handler.py (4)

1-20: LGTM!

Clear module documentation with usage examples and appropriate imports for the Windows registry operations.


54-96: LGTM!

Registry key creation follows the standard Windows protocol handler pattern correctly. The use of HKEY_CURRENT_USER avoids requiring admin privileges, and exception handling is comprehensive.


99-131: LGTM!

The recursive key deletion implementation is correct—using EnumKey(key, 0) repeatedly after each deletion is the standard pattern for enumerating while deleting registry subkeys.


160-173: LGTM!

Simple and effective registry key existence check with proper resource cleanup via context manager.

src/switchcraft/services/addon_service.py (4)

6-7: LGTM!

Standard library imports for temporary file handling are appropriate additions for the new GitHub installation functionality.


263-270: LGTM!

Clear method signature with sensible defaults and well-documented behavior including fallback strategy and asset naming conventions.


271-288: LGTM!

Good practice using lazy import for requests with a clear error message when unavailable, and internal import of __version__ avoids potential circular import issues.


334-351: LGTM!

Asset discovery logic is robust with defensive .get("assets", []) and clear error reporting when the expected addon zip is not found in the release.

.github/workflows/test.yml (2)

33-37: LGTM!

The test execution step correctly sets PYTHONPATH for the src layout and uses appropriate pytest flags (-v --tb=short) for readable output with concise tracebacks.


55-59: LGTM!

The CLI test step correctly targets the specific test files and sets the appropriate PYTHONPATH for the src layout.

tests/test_phase2.py (1)

28-29: LGTM! Skip conditions align with AI stub changes.

The updated skip logic correctly detects all stub indicators, including the new [AI_STUB] token introduced in the ai_service.py stub implementation, ensuring tests are properly skipped when the AI addon is not installed.

src/switchcraft/gui_modern/utils/view_utils.py (1)

6-22: LGTM! Clean mixin implementation for snackbar feedback.

The defensive approach with getattr chaining and exception handling ensures the snackbar utility degrades gracefully when the page reference is unavailable. This centralizes snackbar logic previously duplicated across multiple views.

src/switchcraft/services/ai_service.py (1)

43-49: LGTM! Stub ask method with detectable token.

The [AI_STUB] prefix enables tests to reliably detect stub mode, and the i18n integration with fallbacks ensures graceful degradation.

src/switchcraft/gui_modern/views/addon_manager_view.py (1)

11-11: LGTM! ViewMixin integration.

The class correctly inherits from both ft.Column and ViewMixin, enabling the centralized _show_snack helper.

src/switchcraft/gui_modern/views/stack_manager_view.py (1)

11-36: LGTM! Well-structured view with proper i18n integration.

The ViewMixin inheritance and consistent i18n usage with fallbacks throughout the view provide good localization support and centralized snackbar handling.

src/switchcraft/gui_modern/views/library_view.py (3)

91-92: LGTM: Lifecycle properly deferred to did_mount.

Moving data loading to did_mount is the correct pattern for Flet, ensuring the control is attached to the page before performing operations that may trigger UI updates.


94-133: Directory scanning logic is well-structured with appropriate safeguards.

Good implementation:

  • Configured output folder takes priority
  • Platform-aware default paths
  • Case-insensitive deduplication on Windows
  • Limit to 5 directories prevents slow scanning

302-315: Cross-platform folder opening correctly implemented.

The fix from the past review has been properly applied with platform detection using sys.platform and appropriate commands for each OS.

src/switchcraft/gui_modern/views/group_manager_view.py (5)

145-170: Exception handling correctly updated to catch requests library exceptions.

The fix from the past review has been properly applied. HTTPError and ConnectionError from the requests library are now caught with appropriate error messages, while the generic Exception handler provides fallback string-based detection.


317-362: Members dialog implementation is well-structured.

Good patterns observed:

  • Loading state shown with ProgressBar
  • Empty state handling for no members
  • Background threading for API calls
  • Localized strings with fallbacks

346-351: Displaying user email in member list may have privacy implications.

Line 346 displays userPrincipalName or mail in the subtitle. Depending on your compliance requirements, exposing email addresses in the UI might need consideration for GDPR/privacy policies.

Verify this is acceptable per your data handling policies. If user emails should be masked or only shown to admins, consider adding access controls or partial masking.


415-425: Potential race condition: Dialog closed before background thread completes.

In add_user, the dialog is closed immediately at line 416, but the background thread continues. If load_members() at line 422 tries to update the closed add_dlg, it could cause issues. However, load_members updates the parent dlg, not add_dlg, so this should be safe.


83-92: Header row structure is correct after fix.

The ft.Row wrapper and header = assignment are now properly in place, resolving the syntax error from the past review.

src/switchcraft/gui_modern/views/macos_wizard_view.py (3)

116-130: Tab creation correctly uses the label parameter.

The fix from the past review has been applied. Using create_tabs helper with label= instead of text= ensures compatibility with Flet 0.80.1+.


249-273: Shell escaping with shlex.quote is correctly applied.

The fix from the past review has been properly implemented. shlex.quote() is used without additional surrounding quotes, producing valid shell syntax like APP_NAME='My App' instead of the broken APP_NAME="'My App'".


206-221: Good UX: Auto-filling app name from filename.

Nice touch to infer the app name from the local file's stem and clean it up by replacing dashes/underscores with spaces and applying title case.

src/switchcraft/gui_modern/views/wingetcreate_view.py (6)

27-33: Directory creation logic is robust.

Using os.getenv('APPDATA', os.path.expanduser('~')) provides a sensible fallback for non-Windows platforms, and mkdir(parents=True, exist_ok=True) handles all edge cases.


59-74: Tab creation correctly uses the label parameter.

The fix from the past review has been applied, using create_tabs with label= for Flet 0.80.1+ compatibility.


492-515: GitHub token securely passed via environment variable.

Excellent security improvement from the past review. The token is passed via WINGET_CREATE_GITHUB_TOKEN environment variable instead of command-line argument, preventing exposure in process listings.


502-515: Platform guard correctly implemented for subprocess.

The platform check for STARTUPINFO is properly structured with kwargs dictionary, avoiding AttributeError on non-Windows platforms.


654-667: Validation function correctly structured after fix.

The duplicate platform check and indentation issues from the past review have been resolved. The platform guard and subprocess.run are now at the correct nesting level.


687-697: Cross-platform folder opening correctly implemented.

Platform detection with appropriate commands (os.startfile, open, xdg-open) matches the pattern used in library_view.py.

src/switchcraft/gui_modern/app.py (10)

17-21: Conditional winotify import is correctly guarded.

Using a try/except with WINOTIFY_AVAILABLE flag allows graceful degradation on non-Windows platforms or when the library isn't installed.


37-38: Navigation history initialized with starting index.

Initializing _navigation_history = [0] ensures the back button logic has a consistent starting state.


459-464: Dialog state check updated to avoid deprecated API.

The fix from the past review has been applied. The check now avoids referencing self.page.dialogs (removed in Flet 0.80.x) and uses a defensive check with hasattr for the custom dialog attribute.


633-635: Dynamic addon offset calculation is now robust.

Capturing first_dynamic_index = len(self.destinations) after building static destinations eliminates fragility around NavIndex.WINGET_CREATE being the last static item, as flagged in the past review.


783-804: Back navigation and visibility handling is well-implemented.

The back button handler properly manages history stack and visibility updates, with appropriate error handling for detached controls.


1089-1137: Windows toast notification implementation is comprehensive.

Good features:

  • Only triggers for new, unread notifications with notify_system=True flag
  • Prevents duplicate toasts via _last_notif_id tracking
  • Action buttons with deep links (changelog, app protocol)
  • Different audio for error notifications

1178-1179: Assets directory path resolution for Flet entry point.

The assets_dir parameter is correctly set for development mode, allowing Flet to serve assets from the correct location.


253-266: Asset path resolution handles both frozen and development modes.

Good pattern using sys.frozen check to differentiate between PyInstaller bundles and development environments, with appropriate fallback paths.


485-486: Using page.run_task for UI updates from background thread.

This is the correct pattern for Flet to ensure UI updates happen on the main thread, addressing the race condition concern from the past review.


1117-1121: The switchcraft:// protocol handler is already properly registered and handled in the codebase. The app automatically registers the protocol during first run via register_protocol_handler() (called in modern_main.py lines 251-254), which creates the necessary Windows Registry entries. The protocol URL is parsed and processed via parse_protocol_url() in src/switchcraft/utils/protocol_handler.py, and the app handles protocol invocation through command-line arguments. No action is required.

Likely an incorrect or invalid review comment.

src/switchcraft/assets/lang/de.json (3)

380-388: LGTM! Key renames and value updates are consistent.

The renamed keys (settings_entra_tenant, settings_entra_client, settings_entra_secret) correctly reflect the Microsoft Entra ID branding. The simplified wizard titles are appropriate.


450-561: LGTM! New localization keys use consistent informal "Du" form.

The extensive new keys for dashboard, stacks, script management, and Intune features use informal German ("du"/"deine"/"Prüfe") consistently, aligning with the rest of the file.


562-837: LGTM! Remaining new keys maintain informal tone.

The keys for WingetCreate, packaging wizard, detection tester, addon manager, and group management features all use the informal "Du" form consistently throughout.

src/switchcraft/assets/lang/en.json (2)

364-366: LGTM! Entra ID key renames are consistent with German localization.

The renamed settings keys correctly reflect the Microsoft Entra ID terminology, matching the German localization file.


428-837: LGTM! Comprehensive English localization expansion.

The extensive new keys provide proper English translations for all new UI features including stacks, script management, packaging wizard, detection tester, and group management. The terminology is consistent and clear.

src/switchcraft/gui_modern/utils/flet_compat.py (1)

73-78: LGTM! Safe fallback for environments without TabBar.

The final fallback correctly handles environments where TabBar doesn't exist by creating Tabs with kwargs and assigning tabs via property assignment. The None check at line 76 is a good defensive practice.

src/switchcraft/gui_modern/views/intune_store_view.py (4)

7-12: LGTM! ViewMixin integration provides _show_snack method.

The ViewMixin import and class inheritance correctly provide the _show_snack method that was previously missing. This addresses the past review comment about the AttributeError.


81-92: LGTM! Navigation uses NavIndex for consistency.

The _switch_to_settings method correctly uses NavIndex.SETTINGS_GRAPH for navigation to the Graph API settings tab, with appropriate fallbacks for different navigation patterns (go() and snackbar).


192-203: LGTM! Deploy button with placeholder feedback.

The Deploy/Package button is appropriately added with a clear placeholder message indicating the feature is coming soon. The i18n integration with fallbacks is consistent with the rest of the view.


69-73: The code is correct. ft.Button is the standard button control available in Flet 0.80.1+ (the project's minimum required version per pyproject.toml). The entire codebase consistently uses ft.Button across 17 files without any compatibility layer needed. No additional verification or compatibility check is required.

Likely an incorrect or invalid review comment.

tests/test_ui_interactions.py (5)

9-28: LGTM! Mock page fixture is well-structured.

The fixture provides all necessary mocked attributes for Flet Page including window, dialogs, snack_bar, and theme settings.


174-178: LGTM! HistoryService patch location corrected.

The patch now correctly targets switchcraft.gui_modern.views.dashboard_view.HistoryService instead of the source module, ensuring the mock is effective when DashboardView instantiates the service.


479-483: LGTM! Missing handler now properly fails the test.

The test now correctly calls pytest.fail() when the on_change handler is missing on the tenant field, addressing the previous review concern about silent test passes.


84-164: LGTM! Settings view button test is comprehensive.

The test properly mocks dependencies, exercises button discovery, and simulates click events with appropriate error handling and failure reporting.


227-230: The patch location for HistoryService is already correct. AnalyzerView imports HistoryService locally within methods (lines 431 and 1059), not at module level like DashboardView does. Since the import occurs at runtime within method scope, patching at switchcraft.services.history_service.HistoryService will correctly intercept the import when those methods execute. No change needed.

src/switchcraft/gui_modern/views/script_upload_view.py (1)

5-86: Tabbed layout refactor looks cohesive (ViewMixin + create_tabs + dynamic tab body).
Nice consolidation around ViewMixin._show_snack() and create_tabs(); the tab-body container approach keeps view code readable.

src/switchcraft/gui_modern/views/winget_view.py (1)

9-122: ViewMixin integration + pane spacing/margins are clean.
This removes duplicated snackbar plumbing and improves layout consistency.

src/switchcraft/gui_modern/views/settings_view.py (2)

16-145: Good modernization pass: ViewMixin + Button migration + i18n wiring reads consistently.

Also applies to: 147-182


693-762: Language change flow is reasonable; please verify page.open(dlg) support in your Flet runtime.
Some environments still require page.dialog = dlg; dlg.open=True; page.update().

src/switchcraft/gui_modern/views/packaging_wizard_view.py (1)

19-56: Stepper + analysis UI improvements are solid (progress callback + visible error container).

Also applies to: 327-439

src/switchcraft/gui_modern/views/analyzer_view.py (1)

33-305: URL download + cleanup_path handoff is a good, self-contained cleanup story.
The temp-dir lifecycle is clear: immediate cleanup on download failure, deferred cleanup after analysis when analysis starts.

Also applies to: 363-422

src/switchcraft/gui_modern/views/intune_view.py (8)

15-15: LGTM: ViewMixin integration

Good use of ViewMixin for consistent snackbar handling across views. This aligns with the project's pattern shown in view_utils.py.


51-66: LGTM: Tab construction uses correct parameters

The tabs now correctly use label= parameter via create_tabs from flet_compat, addressing the previous pipeline failure with Tab.__init__() got an unexpected keyword argument 'text'.


77-134: LGTM: Packager tab builder

Clean structure with proper i18n fallbacks and consistent use of FilePickerHelper for file picking. The extended allowed extensions (.msp added) aligns with the summary.


172-243: LGTM: Thread-safe UI updates

All UI mutations from background threads are properly wrapped in self.app_page.run_task(), addressing the previous thread-safety concerns. The pattern is consistently applied across connect() and search_apps().


288-323: LGTM: Copy metadata with thread-safe updates

The _copy_metadata_from_supersedence method properly marshals all UI updates back to the main thread via run_task(), including the _show_snack call at line 314.


325-329: LGTM: Thread-safe logging

The _log method now correctly wraps UI updates in run_task(), addressing the previous thread-safety concern.


369-384: LGTM: Safe subprocess invocation

The subprocess.run call now uses list arguments without shell=True, addressing the previous command injection concern. Path normalization via os.path.normpath is a good addition.


399-464: LGTM: Upload workflow with proper thread safety

The _run_upload method correctly wraps all UI updates in run_task() callbacks. The progress reporting, supersedence handling, and error cases are all properly marshaled to the main thread.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +14 to +18
strategy:
fail-fast: true
max-parallel: 1
matrix:
python-version: ["3.13", "3.14"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/test.yml | head -30

Repository: FaserF/SwitchCraft

Length of output: 896


🌐 Web query:

setup-python action Python 3.14 pre-release allow-prereleases

💡 Result:

Use actions/setup-python with python-version set to the x.y range (e.g., "3.14") and allow-prereleases: true. Example workflow step:

  • uses: actions/checkout@v4
  • uses: actions/setup-python@v6
    with:
    python-version: '3.14'
    allow-prereleases: true

This tells the action to fall back to a 3.14 pre-release when no GA 3.14 is available. [1][2]

Sources:

  • actions/setup-python release + changelog. [1]
  • setup-python docs — "Allow pre-releases" and example. [2]

Add allow-prereleases: true to the setup-python step to support Python 3.14.

Python 3.14 is in pre-release as of January 2026. The setup-python@v6 action requires the allow-prereleases: true flag to install pre-release Python versions; without it, the workflow will fail when attempting to set up Python 3.14.

Suggested fix
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v6
        with:
          python-version: ${{ matrix.python-version }}
          allow-prereleases: true

Additionally, max-parallel: 1 serializes the 2-version matrix runs. If this is intentional to prevent resource conflicts, add a comment explaining the reasoning.

🤖 Prompt for AI Agents
In @.github/workflows/test.yml around lines 14 - 18, Add allow-prereleases: true
to the actions/setup-python@v6 step so the workflow can install Python 3.14
(pre-release); locate the "Set up Python ${{ matrix.python-version }}" step and
add the with key "allow-prereleases: true" alongside python-version. Also, if
max-parallel: 1 in the strategy matrix is intentional to serialize runs, add a
short comment explaining why (e.g., to avoid resource conflicts); otherwise
remove or increase max-parallel to allow parallel matrix jobs.

Comment on lines +139 to +141
self.tenant_id = SwitchCraftConfig.get_value("IntuneTenantID", "")
self.client_id = SwitchCraftConfig.get_value("IntuneClientID", "")
self.client_secret = SwitchCraftConfig.get_secure_value("IntuneClientSecret") or ""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check the key_map definition in config.py
rg -n "key_map" -A 20 src/switchcraft/utils/config.py | head -50

Repository: FaserF/SwitchCraft

Length of output: 1745


🏁 Script executed:

# Also verify the actual lines in intune_view.py
sed -n '135,145p' src/switchcraft/gui_modern/views/intune_view.py

Repository: FaserF/SwitchCraft

Length of output: 636


🏁 Script executed:

# Let's also search for how SwitchCraftConfig.get_value works and if there's any case handling
rg -n "def get_value" -A 10 src/switchcraft/utils/config.py

Repository: FaserF/SwitchCraft

Length of output: 639


Case mismatch in config key prevents GPO alias resolution for client_id.

Line 140 uses "IntuneClientID" but SwitchCraftConfig.key_map defines the alias as "IntuneClientId" (lowercase 'd'). Python dict key lookups are case-sensitive, so the GPO alias mapping to "GraphClientId" won't be applied, preventing GPO policy from overriding the local config for this credential.

🐛 Proposed fix
         # Credentials
         self.tenant_id = SwitchCraftConfig.get_value("IntuneTenantID", "")
-        self.client_id = SwitchCraftConfig.get_value("IntuneClientID", "")
+        self.client_id = SwitchCraftConfig.get_value("IntuneClientId", "")
         self.client_secret = SwitchCraftConfig.get_secure_value("IntuneClientSecret") or ""

])
bgcolor="BLACK12",
border_radius=10,
border=ft.Border.all(1, "WHITE10")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Incorrect API: ft.Border.all() should be ft.border.all().

Same issue as in addon_manager_view.py - ft.Border.all() will raise AttributeError. This also affects line 112.

Proposed fix
-                    border=ft.Border.all(1, "WHITE10")
+                    border=ft.border.all(1, "WHITE10")

Apply the same fix at line 112.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
border=ft.Border.all(1, "WHITE10")
border=ft.border.all(1, "WHITE10")
🤖 Prompt for AI Agents
In `@src/switchcraft/gui_modern/views/stack_manager_view.py` at line 81, Replace
the incorrect use of the Flet API where ft.Border.all(...) is called: change all
occurrences to ft.border.all(...) (e.g., in the widget that sets
border=ft.Border.all(1, "WHITE10") and the similar occurrence later) so the code
uses the correct ft.border.all function; ensure both instances that mirror the
change in addon_manager_view.py are updated.

Comment on lines +322 to +331
ft.TextButton(i18n.get("btn_cancel") or "Cancel", on_click=lambda e: self.app_page.close(dlg)),
ft.Button(
i18n.get("btn_deploy") or "Deploy",
bgcolor="BLUE_700",
color="WHITE",
on_click=lambda e: self._execute_deploy(dlg)
)
]
)
self.app_page.dialog = dlg
dlg.open = True
self.app_page.update()
self.app_page.open(dlg)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find and examine addon_manager_view.py to see the dialog pattern used there
find . -name "addon_manager_view.py" -type f

Repository: FaserF/SwitchCraft

Length of output: 118


🏁 Script executed:

# Get more context around lines 322-331 in stack_manager_view.py
sed -n '315,340p' src/switchcraft/gui_modern/views/stack_manager_view.py

Repository: FaserF/SwitchCraft

Length of output: 1010


🏁 Script executed:

# Search for all page.open and page.close usage patterns
rg "page\.open\(|page\.close\(" --no-heading -B 2 -A 2

Repository: FaserF/SwitchCraft

Length of output: 10637


🏁 Script executed:

# Search for dialog property assignment patterns
rg "page\.dialog\s*=" --no-heading -B 2 -A 2

Repository: FaserF/SwitchCraft

Length of output: 5242


🌐 Web query:

Flet page.open() page.close() dialog methods API

💡 Result:

Summary — how dialog show/close works in Flet

  • Dialogs are DialogControl subclasses (AlertDialog, CupertinoAlertDialog, DatePicker, etc.) that expose an open boolean property; set open=True (or pass open=True on creation) to display them. [1][2][4]
  • page.open(control) is a helper that shows dialog/overlay controls (adds them to page.overlay or sets open). Use page.close(dialog) or set dialog.open = False to close. page.pop_dialog() closes the topmost open dialog. [1][2][3][4]
  • Dialogs fire on_dismiss when closed; modal dialogs can block outside clicks (modal=True). Many dialog controls have barrier_color, actions, and other dialog-specific props. [1][2][4]

Minimal example (Python):
import flet as ft

def main(page: ft.Page):
dlg = ft.AlertDialog(
title=ft.Text("Confirm"),
content=ft.Text("Delete item?"),
actions=[
ft.TextButton("Cancel", on_click=lambda e: page.close(dlg)),
ft.TextButton("OK", on_click=lambda e: page.close(dlg)),
],
)

page.add(ft.ElevatedButton("Show dialog", on_click=lambda e: page.open(dlg)))

ft.app(target=main)

References

  • AlertDialog / showing dialogs in Flet. [1]
  • DialogControl (open property, on_dismiss). [2]
  • BasePage.pop_dialog() and page methods. [3]
  • Flet dialogs/overlays guide with examples. [4]

Align dialog management API across the codebase.

This view uses page.open(dlg) and page.close(dlg), while other views (addon_manager_view, settings_view, packaging_wizard_view, etc.) use the older pattern (page.dialog = dlg; dlg.open = True). The newer page.open()/page.close() approach is the recommended API per Flet documentation and should be standardized across all views for consistency.

🤖 Prompt for AI Agents
In `@src/switchcraft/gui_modern/views/stack_manager_view.py` around lines 322 -
331, Several views still use the old dialog pattern ("page.dialog = dlg" and
"dlg.open = True") while the preferred API is page.open(dlg)/page.close(dlg);
find occurrences of the old pattern (e.g., assignments to page.dialog and
dlg.open/dlg.close in addon_manager_view, settings_view, packaging_wizard_view,
etc.) and replace them with page.open(dlg) to show and page.close(dlg) to hide,
updating any on_click callbacks or cleanup code that currently manipulates
dlg.open or page.dialog to call self.app_page.open(dlg) /
self.app_page.close(dlg) (or page.open/page.close where self.app_page is named
differently) so all views consistently use the newer Flet dialog API.

Comment on lines +38 to +42
else:
# Running as script - use pythonw to avoid console
python_exe = sys.executable.replace("python.exe", "pythonw.exe")
if not os.path.exists(python_exe):
python_exe = sys.executable

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Case-insensitive replacement needed for Python executable.

The replace("python.exe", "pythonw.exe") is case-sensitive, but on Windows the executable name could be Python.exe or PYTHON.EXE depending on installation. This may cause the replacement to silently fail.

Proposed fix
         else:
             # Running as script - use pythonw to avoid console
-            python_exe = sys.executable.replace("python.exe", "pythonw.exe")
+            exe_lower = sys.executable.lower()
+            if exe_lower.endswith("python.exe"):
+                python_exe = sys.executable[:-10] + "pythonw.exe"
+            else:
+                python_exe = sys.executable
-            if not os.path.exists(python_exe):
-                python_exe = sys.executable
+            if not os.path.exists(python_exe):
+                python_exe = sys.executable

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend ci-cd documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant