Repository navigation
More bug fixes for modern UI - #35
Conversation
📝 WalkthroughWalkthroughCentralizes 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
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
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
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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. Comment |
There was a problem hiding this comment.
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 forwinregmodule.The
winregmodule is lazily imported and may beNoneif the import failed (line 9-16). Accessingwinreg.HKEY_LOCAL_MACHINEon line 175 without a null check will raiseAttributeErrorifwinregisNone.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 forwin32apimodule.Similar to
winreg,win32apiis lazily imported and could beNone. Line 230 callswin32api.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.importorskipis ineffective due to decorator ordering.The
@patch("winreg.OpenKey")decorator on line 95 executes before the test body runs. Ifwinregis unavailable, the patch will fail withModuleNotFoundErrorbeforepytest.importorskipon 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 isft.border_radius.only(...)(lowercase).BorderRadiusis a class, whileborder_radiusis the helper module with the.only()method. This will raise anAttributeErrorat 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: Uselabelinstead oftextforft.Tabparameter.This issue was previously identified and causes the CI pipeline failure. The
ft.Tabcomponent useslabelas the parameter name, nottext.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: Replacetextwithlabelparameter inft.Tab()constructor.This issue was previously flagged: Flet 0.80.1+ uses
labelinstead oftextfor 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.startfileis Windows-only and will raiseAttributeErroron 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-onlysubprocess.STARTUPINFOlacks platform guard.This issue was previously flagged.
subprocess.STARTUPINFO()andSTARTF_USESHOWWINDOWare Windows-only and will raiseAttributeErroron 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.startfileis Windows-only.This will raise
AttributeErroron macOS/Linux. Apply the same cross-platform fix pattern suggested forlibrary_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.Tabsconstruction here usescontent=ft.TabBar(tabs=[...], on_change=...)with alengthparameter, which is an unusual pattern. Additionally, a past review flagged thatlabel=should be used instead oftext=forft.Tab. The current code useslabel=which is correct, but the overall Tabs structure with nested TabBar may cause issues.Flet Tabs component API ft.Tabs with TabBar content parametersrc/switchcraft/gui_modern/views/intune_view.py (3)
36-53: Pipeline failure:Tab.__init__()got unexpected keyword argumenttext.The past review flagged this issue and it appears to still be present. The
ft.Tabconstructor in recent Flet versions doesn't accepttextas a keyword argument - use positional argument orlabel=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.runwithshell=Trueposes command injection risk.This issue was flagged in a past review and is still present. If
output_filecontains 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: Bareexcept: passswallows 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.execvrestart pattern will fail on Windows with PyInstaller builds.This issue was flagged in a past review. The
os.execvapproach doesn't work properly on Windows, especially with PyInstaller. The codebase has a working pattern incrash_view.pythat usessubprocess.Popeninstead.🔒️ 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_installbackground thread directly modifies dialog content (content.controls,dlg.actions) and callsdlg.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 sanitizingnameto 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_urlfunction returns asubkey for URLs likeswitchcraft://settings/updates, but the action handling only checks the top-levelaction. For example,switchcraft://settings/updateswould 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_drawermethod is private, while other similar protocol actions use the publicapp.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.runcall has notimeoutparameter. 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.TimeoutExpiredin 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 leveragingi18n.get()'s native**kwargsformatting support (as shown in the relevant snippet fromi18n.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_deploymethod only shows a snack message but doesn't perform actual deployment. While this is acceptable for scaffolding, consider adding a# TODOcomment 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}buti18n.getsupports 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,
ctypesis imported inside the function but used later at line 598 without re-importing after theexceptblock scope ends - this works becausectypesis 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 Falsesrc/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:
- Accepts any HTTP/HTTPS URL without domain validation
- Downloads executable files directly
- 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 existingloggerinstead. 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:timeis imported but never used.The
timemodule 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_selectshould be removed or implemented.The method body is just
passwith 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 - 21uses a hardcoded value. IfNavIndexis updated or new navigation items are added, this offset could become incorrect. Consider deriving this fromNavIndexconstants.♻️ 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_STARTOr better, add a constant to
nav_constants.py:class NavIndex: # ... existing constants ... DYNAMIC_ADDONS_START = 21tests/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_viewsdictionary contains only class types, never tuples. Thisisinstance(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 usingpytest -vorcapsysinstead of print statements.Print statements work but can clutter test output. Use pytest's verbose mode (
-v) orcapsysfixture for cleaner debugging output that integrates with pytest's reporting.
160-163: Consider usingelifor a dictionary for tab index assertions.Multiple independent
ifstatements all execute even when only one can match. Usingelifor 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 duplicatepage.openassignment.
page.openis 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.logcreates side effects and may cause issues in CI/parallel test runs. Consider using pytest'scapfdfixture or Python'sloggingmodule configured with pytest'scaplog.♻️ 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 importingLibraryViewat 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-levelfind_buttons.This local helper is similar to the module-level
find_buttonsbut addsTextFieldsupport. Consider extendingfind_buttonswith a parameter for additional control types, or create a second module-level helper.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (25)
.github/workflows/tests-modern.yml.gitignoresrc/switchcraft/gui_modern/app.pysrc/switchcraft/gui_modern/views/addon_manager_view.pysrc/switchcraft/gui_modern/views/analyzer_view.pysrc/switchcraft/gui_modern/views/dashboard_view.pysrc/switchcraft/gui_modern/views/detection_tester_view.pysrc/switchcraft/gui_modern/views/group_manager_view.pysrc/switchcraft/gui_modern/views/helper_view.pysrc/switchcraft/gui_modern/views/intune_store_view.pysrc/switchcraft/gui_modern/views/intune_view.pysrc/switchcraft/gui_modern/views/library_view.pysrc/switchcraft/gui_modern/views/macos_wizard_view.pysrc/switchcraft/gui_modern/views/packaging_wizard_view.pysrc/switchcraft/gui_modern/views/script_upload_view.pysrc/switchcraft/gui_modern/views/settings_view.pysrc/switchcraft/gui_modern/views/stack_manager_view.pysrc/switchcraft/gui_modern/views/winget_view.pysrc/switchcraft/gui_modern/views/wingetcreate_view.pysrc/switchcraft/modern_main.pytests/test_addons.pytests/test_full_coverage.pytests/test_navigation.pytests/test_navigation_map.pytests/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_pathattribute 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
FilePickerHelperfor 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_ERRORand handled gracefully downstream.
92-107: LGTM!The guard at line 94 correctly addresses the previous review concern about
NameErrorwhen imports fail. The protocol URL parsing logic handles both--protocol <url>and directswitchcraft://argument formats with appropriate exception handling.
348-370: Button styling is consistent and appropriate.The use of
ft.Buttonwith explicitButtonStylefor 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: Verifyft.Buttonproperty compatibility.Same concern as in
detection_tester_view.py:bgcolorandcolormay need to be passed viaft.ButtonStylerather than as direct properties onft.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_btncorrectly initializes withdisabled=Trueand bindson_click=self._confirm_delete. Samebgcolor/colorproperty 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
NavIndexenables type-safe navigation constants throughout the view.
32-36: Button component updated consistently.The "Go to Settings" button uses
ft.Buttonwith 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_GRAPHinstead of a magic number improves maintainability. The fallback logic searching forapp_refwithgoto_tabis a reasonable defensive pattern.src/switchcraft/gui_modern/views/detection_tester_view.py (1)
67-73: No changes needed—ft.Buttonsupportsbgcolorandcolordirectly.The migration from
ft.ElevatedButtontoft.Buttonis valid. Flet'sft.Buttonacceptsbgcolorandcoloras direct control properties; they are not required to be passed viaft.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_uimethod 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 viai18n.get()with sensible fallbacks.
185-213: LGTM!The
_refresh_stack_listmethod correctly handles the empty state with a localized placeholder and renders stack items with proper count display and delete actions. The lambda closure patternlambda e, n=name:correctly captures the loop variable.src/switchcraft/gui_modern/views/intune_store_view.py (4)
7-7: LGTM!The
NavIndeximport enables centralized navigation index management, which improves maintainability.
80-92: LGTM!The
_switch_to_settingsmethod correctly checks forgoto_tabavailability and usesNavIndex.SETTINGS_GRAPHfor direct navigation to the Graph settings tab. The fallback topage.go("/settings")provides graceful degradation.
163-169: LGTM!Metadata field labels are properly localized with sensible fallbacks.
191-213: Previously flagged issue resolved -_show_snackmethod added.The
_show_snackhelper method has been added, resolving the previously identifiedAttributeError. 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
NavIndeximport aligns with the broader navigation refactoring in this PR.
93-125: LGTM - Good directory scanning strategy.The
_get_scan_directoriesmethod 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_datamethod 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 usingAPPDATAwith a fallback to home directory.
438-538: Good manifest generation flow with comprehensive error handling.The
_generate_new_manifestmethod is well-implemented:
- Validates required input (URLs)
- Builds the command dynamically from form fields
- Handles
FileNotFoundErrorwith helpful installation instructions- Handles
TimeoutExpiredgracefully- 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.Buttonwith explicitbgcolorandcolorstyling 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_snackimplementation is consistent with other views likeintune_store_view.pyandmacos_wizard_view.py.src/switchcraft/gui_modern/views/settings_view.py (3)
743-773: Graph connection test implementation looks correct.The
_test_graph_connectionmethod 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_notificationmethod properly instantiatesNotificationServiceand 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_AVAILABLEflag 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_visibilitymethods properly manage navigation history and back button visibility. TheRuntimeErrorcatch in_update_back_btn_visibilityappropriately 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
categoriesattribute is always initialized inHoverSidebar.__init__at construction time, and the bounds check at line 864 (if 0 <= cat_index < len(self.sidebar.categories):) already preventsIndexError. NoAttributeErrorrisk exists forcategoriessince 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.
| if tenant_field.on_change: | ||
| log("SUCCESS: 'Entra Tenant ID' has on_change handler.") | ||
| else: | ||
| log("CRITICAL: 'Entra Tenant ID' missing on_change handler!") |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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_tabmethod at lines 890-1021 routes toNavIndex.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_foldermethod 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.Popeninstead ofos.execv, detects PyInstaller frozen state withgetattr(sys, 'frozen', False), and usesos._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_pathis only passed tostart_analysison 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: + passtests/test_ui_interactions.py (2)
150-155: Patch location is incorrect for HistoryService.
HistoryServiceis imported at module level indashboard_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 foron_changehandler check.The test logs "CRITICAL" when
on_changeis 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:toexcept 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: Replacetextwithlabelparameter inft.Tab()constructor.This issue was previously identified. Flet 0.80.1+ uses
labelinstead oftextfor 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_changehandler or data association to identify which scripts are selected. When implementing_import_github_scriptsand_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) andi18n.get("key", default="default")(lines 164-168). Thedefault=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:tempfileandrequests.These modules are imported but not used anywhere in the file.
♻️ Proposed fix
import logging import threading -import tempfile -import requests from pathlib import Pathsrc/switchcraft/gui_modern/views/library_view.py (1)
104-114: Windows-specific paths in cross-platform code.The hardcoded
C:/TempandC:/IntuneWinpaths are Windows-specific. While theexists()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-specificos.startfileusage.
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 ofpage.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 pageAlso 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. Usecapfdfixture 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.Buttonwithbgcolor,color, andheightmay not be valid.Standard Flet button controls (
ft.ElevatedButton,ft.TextButton) use thestyleparameter withButtonStylefor customization, not directbgcolor/colorproperties. 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=Truewas removed, which is good. However, the current formatf'/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
📒 Files selected for processing (23)
.github/workflows/test.yml.gitignoresrc/switchcraft/assets/lang/de.jsonsrc/switchcraft/gui_modern/app.pysrc/switchcraft/gui_modern/utils/flet_compat.pysrc/switchcraft/gui_modern/views/analyzer_view.pysrc/switchcraft/gui_modern/views/category_view.pysrc/switchcraft/gui_modern/views/crash_view.pysrc/switchcraft/gui_modern/views/dashboard_view.pysrc/switchcraft/gui_modern/views/group_manager_view.pysrc/switchcraft/gui_modern/views/intune_store_view.pysrc/switchcraft/gui_modern/views/intune_view.pysrc/switchcraft/gui_modern/views/library_view.pysrc/switchcraft/gui_modern/views/macos_wizard_view.pysrc/switchcraft/gui_modern/views/script_upload_view.pysrc/switchcraft/gui_modern/views/settings_view.pysrc/switchcraft/gui_modern/views/stack_manager_view.pysrc/switchcraft/gui_modern/views/wingetcreate_view.pysrc/switchcraft/services/addon_service.pysrc/switchcraft/services/ai_service.pytests/test_i18n_integrity.pytests/test_navigation.pytests/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_tabsfromflet_compatensures 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
textinstead oflabelinft.Tab()constructor. This has been correctly addressed - all tabs now uselabel=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_snackhelper 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=Trueandpadding=20provides 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 = Truetopage.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_GRAPHinstead of the hardcoded index9is 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_PROCESSflag,close_fds=Truefor handle inheritance prevention, and CWD handling for PyInstaller bundles are all appropriate fixes for robust application restart. The conditionalcreationflagsensures 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.NamedTemporaryFilewithdelete=Falsefor controlled cleanup, and thefinallyblock 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 .vspip 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_GRAPHfor 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_snackhelper 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_rowand 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.Tabcomponents now correctly use thelabelparameter instead oftext, addressing the previous CI failure.
205-220: LGTM - Local file picker implementation.The
_pick_local_filemethod correctly usesFilePickerHelper, 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_AVAILABLEflag 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://notificationsprotocol is registered automatically on first run inmodern_main.py(lines 251–254) via theregister_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, andcreate_tabsare appropriate for the URL download and tabbed UI features.
171-185: Tab creation now uses compatibility helper.The
create_tabshelper fromflet_compatabstracts 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_pathparameter andfinallyblock 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 olderdialogproperty pattern when needed.tests/test_ui_interactions.py (2)
30-58: Good extraction offind_buttonsto module level.The helper function is now defined once at module level, eliminating duplication across tests. The dynamic button type detection with
hasattrchecks 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_*tosettings_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_hookapproach 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_tabsfromflet_compatfor cross-version Flet compatibility is appropriate.
50-65: Previous issue addressed: Tab keyword argument fixed.The
text=keyword argument issue has been resolved by usinglabel=and thecreate_tabscompatibility 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
sysfor platform detection.
25-31: LGTM!Good cross-platform handling with
APPDATAfallback to home directory, and safe directory creation withexist_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
kwargsapproach 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_snackinwinget_view.py.
311-323:ft.Buttonis 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 supportsft.Button. The control exists and functions properly in this version; it has not been removed. The codebase usesft.Button70+ 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.
There was a problem hiding this comment.
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 causesNameErroron non-Windows.This issue was previously flagged but remains unfixed. The nested
if sys.platform == "win32":(lines 644-645) meanssubprocess.runonly executes on Windows. On other platforms,resultis never defined, causingNameErrorat 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.stderrsrc/switchcraft/gui_modern/views/intune_view.py (1)
217-235: Thread safety pattern inconsistent with connect handler.While the
connecthandler (lines 172-202) correctly usesrun_task()to marshal UI updates,search_apps(lines 217-235),_copy_metadata_from_supersedence(lines 288-311), and_run_upload(lines 408-438) still callself.update()directly from background threads. Apply the same pattern used inconnectfor 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
requestsis acceptable for optional dependencies. The hardcoded repository values are appropriate for this single-repo use case.Consider catching
ImportErrorforrequeststo provide a friendlier error message if the dependency isn't installed, though this is optional ifrequestsis 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
ValueErrorwhile line 299 catchesjson.JSONDecodeError. WhileJSONDecodeErroris a subclass ofValueError, 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:
Line 133-134:
HTTPErrorcatches all HTTP errors (400, 401, 404, 500, etc.), not just 403 permission errors. A 404 or 500 would incorrectly display as a permission error.Line 139-140:
ConnectionErrorindicates network connectivity issues (DNS failure, connection refused), not authentication failures. The comment and error message are misleading.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_urlstate variable appears unused.The
dmg_urlattribute is initialized but never referenced elsewhere in the class. The URL is read directly fromself.url_field.valuein_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 importSwitchCraftConfig.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 SwitchCraftConfigsrc/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: Movesysimport to module level.
sysis 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 includespytestas a test dependency. The subsequentpip install pytest pytest-covmay be redundant. Consider consolidating by either addingpytest-covto 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] extrassrc/switchcraft/gui_modern/views/intune_store_view.py (3)
80-92: Empty fallback branch does nothing.The
elseblock at lines 87-92 contains onlypasswith a comment. If neithergoto_tabnorgois 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 useorpattern. For consistency and to leverage i18n's built-in fallback mechanism, prefer usingdefault=throughout.
207-213: Silent exception swallowing may hide issues.The
_show_snackmethod catches all exceptions with a barepass. While this prevents crashes, it also hides legitimate errors. Consider logging warnings for debugging purposes, consistent with the pattern inintune_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 extractingfind_all_buttons_and_inputsto module level.This helper is similar to
find_buttonsbut includes TextFields. To improve maintainability and enable reuse, consider extracting it to module level alongsidefind_buttons.src/switchcraft/gui_modern/app.py (1)
250-252: Redundant import statement.
import osat line 251 is redundant sinceosis already imported at line 2.♻️ Remove redundant import
# Set window icon paths - import os import syssrc/switchcraft/gui_modern/views/analyzer_view.py (7)
171-185: Verifycreate_tabsargument order matches expected signature.The
create_tabshelper function signature expectstabsas the first positional argument:def create_tabs(tabs, **kwargs). However, this call passestabsas 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 withstream=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-specificctypes.windllcalls. 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 variableresult_path- consider removing or renaming.The
result_pathvariable captures the return value fromcreate_intunewin(), but it's never used. According to theIntuneService, this returns the tool's output text, not the file path. The code correctly searches for the.intunewinfile 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
📒 Files selected for processing (17)
.github/workflows/test.ymlsrc/switchcraft/assets/lang/de.jsonsrc/switchcraft/assets/lang/en.jsonsrc/switchcraft/gui_modern/app.pysrc/switchcraft/gui_modern/utils/flet_compat.pysrc/switchcraft/gui_modern/views/analyzer_view.pysrc/switchcraft/gui_modern/views/dashboard_view.pysrc/switchcraft/gui_modern/views/group_manager_view.pysrc/switchcraft/gui_modern/views/intune_store_view.pysrc/switchcraft/gui_modern/views/intune_view.pysrc/switchcraft/gui_modern/views/library_view.pysrc/switchcraft/gui_modern/views/macos_wizard_view.pysrc/switchcraft/gui_modern/views/script_upload_view.pysrc/switchcraft/gui_modern/views/wingetcreate_view.pysrc/switchcraft/services/addon_service.pytests/test_i18n_integrity.pytests/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_githubmethod.
291-304: JSON parsing error handling properly implemented.The explicit
json.JSONDecodeErrorcatch 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_databeingNoneat line 327 provides appropriate defensive coding.
349-374: Download and cleanup logic implemented correctly.
- Streaming download with
stream=Trueis appropriate for potentially large files- The 30-second timeout correctly applies to connection establishment
- Temporary file cleanup in
finallyblock ensures cleanup even on failure- Using
os.path.existsbeforeos.removeprevents errors if the file wasn't createdsrc/switchcraft/gui_modern/views/group_manager_view.py (4)
1-10: LGTM!The imports are well-organized.
NavIndexis correctly imported for navigation constants, andrequestsis appropriately imported for handling HTTP-specific exceptions in the error handling logic.
33-37: LGTM!The
ft.Buttonreplacement 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 theft.Buttoncomponent. The background thread for deletion is appropriately daemonized.
289-304: LGTM!The navigation correctly uses
NavIndex.SETTINGS_GRAPHfor 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
shlexfor shell escaping andcreate_tabshelper for Flet compatibility.
35-56: LGTM!Clean UI initialization with proper i18n fallbacks and layout structure.
107-132: LGTM!Good use of
create_tabshelper for Flet compatibility and correctlabelparameter forft.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 thelabelparameter, which addresses the previous review feedback about the Flet API change.
497-510: Platform guard correctly implemented.The subprocess
startupinfohandling now properly checkssys.platformand 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.pyandpackaging_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.Buttonis 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 likeFilledButton. 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_repomethod spawns a background thread that directly modifies UI controls (self.github_script_list,self.github_status) and callsself.update()from the thread (line 539). Flet recommends marshaling UI updates to the main event loop usingpage.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_foldermethod 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 viarun_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_folderhandler now usessubprocess.run(['explorer', f'/select,{safe_path}'])with list arguments instead ofshell=True, addressing the previous command injection concern.
449-455: Good: Exception now logged instead of silently swallowed.The
_show_snackmethod now logs failures withlogger.warning()instead of using a bareexcept: 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)expectstabsas the first positional argument, but callers (e.g.,script_upload_view.pyline 63) pass it as a keyword argument viatabs=[...]within**kwargs. This causestabsto beNone(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=Trueinstead 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 fromitem.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_pagefixture properly mocks the essential Flet Page attributes needed for UI testing.
30-58: LGTM - Module-level helper extraction.The
find_buttonshelper 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.failcall 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 atswitchcraft.gui_modern.views.analyzer_view.HistoryServicewould 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_viewhelper 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_systemflag 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_dirtoft.runensures 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
requestsandtempfilesupports the new URL download feature, and usingcreate_tabsfromflet_compatproperly 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=Truefor 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.
| length = len(tabs) if tabs else 0 | ||
| return ft.Tabs(content=ft.TabBar(tabs=tabs), length=length, **kwargs) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n src/switchcraft/gui_modern/utils/flet_compat.pyRepository: 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:
- 1: https://docs.flet.dev/controls/tabs/?utm_source=openai
- 2: https://flet.dev/docs/controls/tabs/?utm_source=openai
- 3: https://flet.dev/docs/controls/tabs/?utm_source=openai
- 4: https://docs.flet.dev/controls/tabbarview/?utm_source=openai
- 5: https://flet.dev/docs/controls/tabs/?utm_source=openai
🌐 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.
There was a problem hiding this comment.
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:
pytest.importorskip("winreg")is redundant since theskipifdecorator already ensures this test only runs on Windows wherewinregis available.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—extractedtab_contentsis never used.Lines 29-34 iterate over
tabsto extract.contentintotab_contents, but this list is never passed to the constructedTabsorTabBarView. Additionally, line 52 returnsft.Tabs(content=ft.TabBar(tabs=tabs), ...)which according to Flet 0.80.1 API is incomplete—thecontentproperty requires a single Control containing bothTabBar(headers) andTabBarView(body), not justTabBar.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)}"producesFILENAME="'installer.dmg'"- Line 257:
DOWNLOAD_URL="{shlex.quote(url)}"producesDOWNLOAD_URL="'https://...'"- Line 271:
APP_NAME="{shlex.quote(name)}"producesAPP_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_manifestcorrectly passes the token via environment variable (lines 491-495),_update_manifeststill uses--tokenas 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) usetext=. Thecreate_tabshelper fromflet_compat.pyhandles the compatibility, but for consistency with the codebase, consider usingtext=.
🧹 Nitpick comments (21)
.github/workflows/test.yml (2)
12-18: Remove unusedosmatrix variable or use it inruns-on.The
matrix.osis defined but never referenced—runs-onis hardcoded towindows-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 pytestOr, 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: returnwhich will also trigger on whitespace-only input. Considerif not query.strip(): returnfor 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- thereplace(":", "/")would produce[email protected]/owner/repowhich lackshttps://- URLs with trailing paths like
https://github.com/owner/repo/tree/mainThe 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_snackis 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: passThen views can use
show_snack(self.app_page, msg, color)instead of duplicating the method.
26-32:get_manifest_dircreates 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
--wizardand--analyzerarguments instead ofNavIndexconstants. 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: passpattern 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 transitionstests/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 usingapp.first_dynamic_indexinstead.♻️ 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_stackmethod checks ifvalis 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_deploymethod 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 URLcomment 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.
ModernHomeViewis 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
📒 Files selected for processing (26)
.github/workflows/test.ymlsrc/switchcraft/assets/lang/de.jsonsrc/switchcraft/assets/lang/en.jsonsrc/switchcraft/gui_modern/app.pysrc/switchcraft/gui_modern/utils/flet_compat.pysrc/switchcraft/gui_modern/views/addon_manager_view.pysrc/switchcraft/gui_modern/views/analyzer_view.pysrc/switchcraft/gui_modern/views/group_manager_view.pysrc/switchcraft/gui_modern/views/helper_view.pysrc/switchcraft/gui_modern/views/history_view.pysrc/switchcraft/gui_modern/views/intune_store_view.pysrc/switchcraft/gui_modern/views/intune_view.pysrc/switchcraft/gui_modern/views/library_view.pysrc/switchcraft/gui_modern/views/macos_wizard_view.pysrc/switchcraft/gui_modern/views/script_upload_view.pysrc/switchcraft/gui_modern/views/stack_manager_view.pysrc/switchcraft/gui_modern/views/wingetcreate_view.pysrc/switchcraft/services/addon_service.pysrc/switchcraft/services/ai_service.pysrc/switchcraft/services/intune_service.pytests/test_full_coverage.pytests/test_modern_layout.pytests/test_navigation_integrity.pytests/test_ui_interactions.pytests/test_ui_regressions.pytests/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 correctPYTHONPATHconfiguration 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
sysimport is correctly added to support thesys.platformcheck in the skipif decorator.tests/test_ui_regressions.py (1)
13-13: LGTM! Consistent mock setup forpage.open.The addition of
page.open = MagicMock()across all test cases ensures the mockft.Pageobject has theopenmethod available, which is required byModernAppand 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.ElevatedButtontoft.Buttonaligns 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
JSONDecodeErrorfor 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
finallyblock (lines 372-374)src/switchcraft/gui_modern/views/group_manager_view.py (1)
143-168: Good improvement: Properrequestsexception handling.The exception handlers now correctly use
requests.exceptions.HTTPErrorandrequests.exceptions.ConnectionErrorinstead of Python built-ins. The fallback string-based detection in the genericExceptionhandler (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_tabshelper 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_tabsfor Flet compatibility and handles tab switching cleanly.
205-220: Good UX: Auto-fill app name from filename.The
_pick_local_filemethod 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
STARTUPINFOis 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_tabsfor Flet compatibility with appropriate i18n fallbacks.src/switchcraft/gui_modern/app.py (5)
460-463: Dialog state check is now defensive butself.page.dialogis app-managed.The condition
hasattr(self.page, "dialog") and dlg != self.page.dialogis 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_handlerproperly 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_viewhelper properly catches exceptions during view initialization and displays aCrashDumpViewwith 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_AVAILABLEand notification flags- Tracks
_last_notif_idto 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 runThe protocol handler is fully implemented in
src/switchcraft/utils/protocol_handler.pyand automatically registered during startup viais_protocol_registered()andregister_protocol_handler()checks inmodern_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_updateto avoid notification side effects- Patch
flet.Control.updateto prevent "Control must be added to page first" errorsThis allows testing navigation logic without a real Flet runtime.
src/switchcraft/gui_modern/views/intune_store_view.py (2)
206-212: LGTM!_show_snackimplementation 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 flaggedAttributeErrorissue.
80-91: Navigation fallback logic is well-structured.The three-tier approach (NavIndex-based → routing → snackbar fallback) provides good resilience. Using
NavIndex.SETTINGS_GRAPHcorrectly 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_uimethod cleanly separates concerns with left panel for stack list and right panel for stack contents. The consistent use ofi18n.get()with fallback defaults ensures graceful degradation.tests/test_modern_layout.py (1)
8-66: LGTM! Consistent mocking ofpage.openacross 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_foldermethod now correctly handles Windows (os.startfile), macOS (open), and Linux (xdg-open). This resolves the previously flaggedAttributeErrorissue 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
finallyblock when download fails (lines 291-297). Theanalysis_startedflag ensures cleanup only happens on failure, not when analysis takes over.
406-415: LGTM! Cleanup path handling in analysis completion.The
finallyblock properly cleans up both directory and file paths using appropriate methods (shutil.rmtreefor directories,os.removefor files).
823-866: LGTM! Platform guard added for Windows-specific admin elevation.The code now explicitly checks
sys.platform == "win32"before usingctypes.windllAPIs (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. Thesetattr(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_*tosettings_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.
| border=ft.Border.all(1, "GREY_400"), | ||
| vertical_lines=ft.border.BorderSide(1, "GREY_400"), | ||
| horizontal_lines=ft.border.BorderSide(1, "GREY_400"), |
There was a problem hiding this comment.
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.
| 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.
There was a problem hiding this comment.
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.ps1reading to handle common UTF-16 / BOM encodings (currentutf-8read will break).
PowerShell scripts are often UTF-16LE (or UTF-8 with BOM);open(..., encoding="utf-8")can raiseUnicodeDecodeErrorand 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: Avoidshell=Truewith interpolated package IDs (command injection + quoting hazards).
Bothpkg_idand 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: Useset_user_preference()instead of non-existentset_value(), and use standardized Graph key names.
SwitchCraftConfig.set_value()does not exist in the codebase—this will crash at runtime. Replace withset_user_preference(). Additionally, for consistency with the rest of the application, useGraphTenantIdandGraphClientIdinstead ofIntuneTenantIDandIntuneClientID(the config module provides an alias mapping between them, butGraphTenantId/GraphClientIdare the canonical names used insettings_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.JSONDecodeErrorproperly 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 inupdate_contextwill be ignored.The guidance message constructed in lines 25-41 is unreachable/unused since
update_contextis called for its side effect (updatingself.context), and callers don't use its return value. This code appears intended for theaskmethod.Consider removing this dead code block since the
askmethod (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 beft.border.all().Line 50 uses
ft.Border.all(1, "GREY_400")which will raiseAttributeErrorat runtime. The correct API isft.border.all()(lowercaseb), consistent with theft.border.BorderSideusage 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_snackstill called from background thread without thread-safety wrapper.Line 395 calls
_show_snackdirectly from within the_bgthread. TheViewMixin._show_snackmethod modifiespage.snack_barand callspage.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 withstream=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 duringiter_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_deploymethod 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: passat 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, ifclose(dlg)raises an exception,_open_folderwon'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 likeDownloading '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
envat lines 595-597, thesubprocess.runcall passesenv=envseparately from**kwargs. This works but note thatkwargsdoesn't includeenvon 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 buttonssrc/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: honorgithub_pathand verify Trees API/auth scheme.
github_pathis currently unused; either remove it or filterps_filesby 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 preferBearer …).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: Preferi18n.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 (especiallyset_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 usingcreate_tabs(..., tabs=[Tab(content=...)])instead offt.Tabs(content=..., length=...).
This file reintroduces a Tabs construction style that has been problematic across environments; aligning withcreate_tabs()would likely be more robust and simpler (no manual body swapping).
269-317: URL download: stream consistently + update progress (avoidr.contentfull-buffer).
Whencontent-lengthis missing,r.contentloads 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
📒 Files selected for processing (25)
.github/workflows/test.ymlsrc/switchcraft/assets/lang/de.jsonsrc/switchcraft/assets/lang/en.jsonsrc/switchcraft/gui_modern/app.pysrc/switchcraft/gui_modern/utils/flet_compat.pysrc/switchcraft/gui_modern/utils/view_utils.pysrc/switchcraft/gui_modern/views/addon_manager_view.pysrc/switchcraft/gui_modern/views/analyzer_view.pysrc/switchcraft/gui_modern/views/group_manager_view.pysrc/switchcraft/gui_modern/views/intune_store_view.pysrc/switchcraft/gui_modern/views/intune_view.pysrc/switchcraft/gui_modern/views/library_view.pysrc/switchcraft/gui_modern/views/macos_wizard_view.pysrc/switchcraft/gui_modern/views/packaging_wizard_view.pysrc/switchcraft/gui_modern/views/script_upload_view.pysrc/switchcraft/gui_modern/views/settings_view.pysrc/switchcraft/gui_modern/views/stack_manager_view.pysrc/switchcraft/gui_modern/views/winget_view.pysrc/switchcraft/gui_modern/views/wingetcreate_view.pysrc/switchcraft/services/addon_service.pysrc/switchcraft/services/ai_service.pysrc/switchcraft/utils/protocol_handler.pytests/test_navigation_integrity.pytests/test_phase2.pytests/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_USERavoids 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
requestswith 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
PYTHONPATHfor 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
PYTHONPATHfor 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 theai_service.pystub 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
getattrchaining 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! Stubaskmethod 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.ColumnandViewMixin, enabling the centralized_show_snackhelper.src/switchcraft/gui_modern/views/stack_manager_view.py (1)
11-36: LGTM! Well-structured view with proper i18n integration.The
ViewMixininheritance 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 todid_mount.Moving data loading to
did_mountis 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.platformand 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
userPrincipalNameorVerify 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. Ifload_members()at line 422 tries to update the closedadd_dlg, it could cause issues. However,load_membersupdates the parentdlg, notadd_dlg, so this should be safe.
83-92: Header row structure is correct after fix.The
ft.Rowwrapper andheader =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 thelabelparameter.The fix from the past review has been applied. Using
create_tabshelper withlabel=instead oftext=ensures compatibility with Flet 0.80.1+.
249-273: Shell escaping withshlex.quoteis correctly applied.The fix from the past review has been properly implemented.
shlex.quote()is used without additional surrounding quotes, producing valid shell syntax likeAPP_NAME='My App'instead of the brokenAPP_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, andmkdir(parents=True, exist_ok=True)handles all edge cases.
59-74: Tab creation correctly uses thelabelparameter.The fix from the past review has been applied, using
create_tabswithlabel=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_TOKENenvironment variable instead of command-line argument, preventing exposure in process listings.
502-515: Platform guard correctly implemented for subprocess.The platform check for
STARTUPINFOis properly structured withkwargsdictionary, avoidingAttributeErroron 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.runare 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 inlibrary_view.py.src/switchcraft/gui_modern/app.py (10)
17-21: Conditional winotify import is correctly guarded.Using a try/except with
WINOTIFY_AVAILABLEflag 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 withhasattrfor the customdialogattribute.
633-635: Dynamic addon offset calculation is now robust.Capturing
first_dynamic_index = len(self.destinations)after building static destinations eliminates fragility aroundNavIndex.WINGET_CREATEbeing 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=Trueflag- Prevents duplicate toasts via
_last_notif_idtracking- 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_dirparameter 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.frozencheck to differentiate between PyInstaller bundles and development environments, with appropriate fallback paths.
485-486: Usingpage.run_taskfor 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: Theswitchcraft://protocol handler is already properly registered and handled in the codebase. The app automatically registers the protocol during first run viaregister_protocol_handler()(called inmodern_main.pylines 251-254), which creates the necessary Windows Registry entries. The protocol URL is parsed and processed viaparse_protocol_url()insrc/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_snackmethod that was previously missing. This addresses the past review comment about the AttributeError.
81-92: LGTM! Navigation uses NavIndex for consistency.The
_switch_to_settingsmethod correctly usesNavIndex.SETTINGS_GRAPHfor 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.Buttonis the standard button control available in Flet 0.80.1+ (the project's minimum required version perpyproject.toml). The entire codebase consistently usesft.Buttonacross 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.HistoryServiceinstead 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 theon_changehandler 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 atswitchcraft.services.history_service.HistoryServicewill 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 aroundViewMixin._show_snack()andcreate_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 verifypage.open(dlg)support in your Flet runtime.
Some environments still requirepage.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_pathhandoff 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 integrationGood use of
ViewMixinfor consistent snackbar handling across views. This aligns with the project's pattern shown inview_utils.py.
51-66: LGTM: Tab construction uses correct parametersThe tabs now correctly use
label=parameter viacreate_tabsfromflet_compat, addressing the previous pipeline failure withTab.__init__() got an unexpected keyword argument 'text'.
77-134: LGTM: Packager tab builderClean structure with proper i18n fallbacks and consistent use of
FilePickerHelperfor file picking. The extended allowed extensions (.mspadded) aligns with the summary.
172-243: LGTM: Thread-safe UI updatesAll 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 acrossconnect()andsearch_apps().
288-323: LGTM: Copy metadata with thread-safe updatesThe
_copy_metadata_from_supersedencemethod properly marshals all UI updates back to the main thread viarun_task(), including the_show_snackcall at line 314.
325-329: LGTM: Thread-safe loggingThe
_logmethod now correctly wraps UI updates inrun_task(), addressing the previous thread-safety concern.
369-384: LGTM: Safe subprocess invocationThe
subprocess.runcall now uses list arguments withoutshell=True, addressing the previous command injection concern. Path normalization viaos.path.normpathis a good addition.
399-464: LGTM: Upload workflow with proper thread safetyThe
_run_uploadmethod correctly wraps all UI updates inrun_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.
| strategy: | ||
| fail-fast: true | ||
| max-parallel: 1 | ||
| matrix: | ||
| python-version: ["3.13", "3.14"] |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n .github/workflows/test.yml | head -30Repository: 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: trueAdditionally, 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.
| self.tenant_id = SwitchCraftConfig.get_value("IntuneTenantID", "") | ||
| self.client_id = SwitchCraftConfig.get_value("IntuneClientID", "") | ||
| self.client_secret = SwitchCraftConfig.get_secure_value("IntuneClientSecret") or "" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check the key_map definition in config.py
rg -n "key_map" -A 20 src/switchcraft/utils/config.py | head -50Repository: 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.pyRepository: 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.pyRepository: 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") |
There was a problem hiding this comment.
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.
| 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.
| 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) |
There was a problem hiding this comment.
🧩 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 fRepository: 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.pyRepository: 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 2Repository: FaserF/SwitchCraft
Length of output: 10637
🏁 Script executed:
# Search for dialog property assignment patterns
rg "page\.dialog\s*=" --no-heading -B 2 -A 2Repository: 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.
| 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 |
There was a problem hiding this comment.
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
Summary by CodeRabbit
New Features
Enhancements
Tests
Chores
✏️ Tip: You can customize this high-level summary in your review settings.