Skip to content

Conversation

@FaserF
Copy link
Owner

@FaserF FaserF commented Jan 15, 2026

Summary by CodeRabbit

  • New Features

    • Intune Store Browser, Entra Group Manager, Stack Manager, WingetCreate Manager, macOS Packaging Wizard, packaging wizard pre-fill, first‑run demo, in-app Notification Center, AI helper with copy‑to‑clipboard
  • Improvements

    • Expanded English/German UI texts (Du‑form), build‑date display, selectable crash details, safer startup/restart and splash/loading flows, Winget/Intune search and UX resilience
  • Documentation

    • Expanded Intune, Winget, upgrade and feature docs with screenshots and permission guidance
  • Tests

    • Many new unit tests covering startup, crash view, i18n, Intune, packaging, notifications
  • Chores

    • CI workflow permission tweak; updated ignore list (tests)

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

@FaserF FaserF self-assigned this Jan 15, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 15, 2026

Warning

Rate limit exceeded

@FaserF has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 0 minutes and 20 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between d70c5b7 and e899ba9.

📒 Files selected for processing (7)
  • src/switchcraft/assets/lang/de.json
  • src/switchcraft/assets/lang/en.json
  • src/switchcraft/gui_modern/views/settings_view.py
  • tests/test_app_startup_critical.py
  • tests/test_crash_view.py
  • tests/test_intune_service_core.py
  • tests/test_phase2.py
📝 Walkthrough

Walkthrough

Large cross-cutting update: modern UI enhancements (Intune/Winget tooling, Packaging Wizard, Notification Center), startup/crash resiliency and splash handling, AddonService instance/static API adjustments, extensive i18n keys, Intune/Winget search robustness, PyInstaller spec changes, CI workflow permission tweak, and many new unit tests.

Changes

Cohort / File(s) Summary
CI / Packaging
\.github/workflows/weekly.yml, switchcraft_legacy.spec
Adjusted trigger_release permissions (contents: write, actions: write); PyInstaller spec now uses collect_all for datas/binaries/hidden_imports and adds auto-discovery for switchcraft modules — review packaging/hook paths.
Docs & Website
README.md, docs/... (docs/FEATURES.md, docs/INTUNE*.md, docs/WINGET.md, docs/faq.md, docs/index.md, docs/upgrade.md, docs/.vitepress/config.mts)
Large content additions and asset path updates: new feature docs (Intune Store, Group/Stack Manager, WingetCreate), screenshots, permission clarifications, and site metadata/logo path changes.
i18n
src/switchcraft/assets/lang/en.json, .../de.json
Added many localization keys (build_date, AI helper, notifications, Winget/Intune metadata, restart semantics); changed several keys/values (logout/restart messages). Validate translations and Du-form checks.
Modern GUI core
src/switchcraft/gui_modern/app.py, src/switchcraft/modern_main.py, src/switchcraft/gui_modern/controls/sidebar.py
Major startup/UI lifecycle refactor: persistent splash, instance-based addon loading, first-run/demo flows, CLI flags/silent mode, per-page session storage, and added WingetCreate nav item. Review startup concurrency, splash lifecycle, and CLI handling.
Modern GUI views
src/switchcraft/gui_modern/views/* (analyzer_view.py, crash_view.py, dashboard_view.py, helper_view.py, intune_store_view.py, intune_view.py, packaging_wizard_view.py, settings_view.py, winget_view.py)
New/updated views: Intune Store list/details, packaging wizard prefill via shared session, notification drawer (mark/read/clear), AI helper (copy-to-clipboard), admin-elevation flows, safer restart/reload, and broad i18n adoption. Pay attention to new background task patterns and UI run_task fallbacks.
Legacy GUI
src/switchcraft/gui/app.py, src/switchcraft/gui/views/*
Improved init error handling, added PyInstaller crash-dump logging, and swapped several runtime addon checks to static wrapper calls.
Services
src/switchcraft/services/addon_service.py, src/switchcraft/services/ai_service.py, src/switchcraft/services/intune_service.py, src/switchcraft_ai/service.py
Added is_addon_installed_static() wrapper; richer AI-stub responses and greeting detection; IntuneService.search_apps now multi-stage with escaping and fallbacks — review search correctness and unit tests.
Winget integration
src/switchcraft_winget/utils/winget.py, src/switchcraft/gui_modern/views/winget_view.py
Increased timeouts and reordered search to PowerShell→API→CLI; enhanced logo handling and localized UI; added elevated install flows and deploy/background download handling.
Utilities
src/switchcraft/utils/logging_handler.py, src/entry.py
Updated GitHub issue URL; suppressed PyInstaller ResourceWarning in frozen mode and added short delay before exit prompt.
Tests (new/updated)
tests/* (test_*.py many files)
Many new unit tests for startup, crash view, i18n Du-form, Intune service, packaging wizard, notification service, legacy startup, session storage, and adjustments to AI-mode assertions; review CI test coverage and mocks.
Config / Ignore
.gitignore
Added test_output.ps1 to ignore list.

Sequence Diagram(s)

sequenceDiagram
actor User
participant ModernApp
participant AddonService
participant IntuneAPI as "Microsoft Graph / Intune"
participant PackagingWizard

User->>ModernApp: Open Intune Store, perform search
ModernApp->>AddonService: instance.import_addon_module("winget"/"ai")
AddonService-->>ModernApp: addon module (or None)
ModernApp->>IntuneAPI: search apps (multi-stage query)
IntuneAPI-->>ModernApp: app list
User->>ModernApp: select app
ModernApp->>ModernApp: store selected app in switchcraft_session
User->>ModernApp: navigate to Packaging Wizard
ModernApp->>PackagingWizard: open (reads switchcraft_session.pending_packaging_app)
PackagingWizard-->>User: show pre-filled packaging form
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I nibbled docs and polished views,
I hopped through Intune and winget news,
Saved a session, sparked a wizard's cheer,
Notifications chirp — the demo's near.
Tiny paws, big changes — throw a carrot here!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the primary focus of this changeset: extensive UI enhancements and modernizations across multiple views, components, and supporting infrastructure.
Docstring Coverage ✅ Passed Docstring coverage is 84.94% which is sufficient. The required threshold is 80.00%.

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


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

❤️ Share

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

@github-actions github-actions bot added documentation Improvements or additions to documentation ci-cd backend labels Jan 15, 2026
@FaserF FaserF changed the title small bug fixes & docs improvements Add modern UI enhancements for multiple views Jan 15, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 15, 2026

Note

Docstrings generation - SUCCESS
Generated docstrings for this pull request at #40

FaserF and others added 2 commits January 15, 2026 18:26
Docstrings generation was requested by @FaserF.

* #39 (comment)

The following files were modified:

* `src/switchcraft/controllers/analysis_controller.py`
* `src/switchcraft/gui/app.py`
* `src/switchcraft/gui/views/analyzer_view.py`
* `src/switchcraft/gui/views/intune_view.py`
* `src/switchcraft/gui/views/settings_view.py`
* `src/switchcraft/gui_modern/app.py`
* `src/switchcraft/gui_modern/controls/sidebar.py`
* `src/switchcraft/gui_modern/views/analyzer_view.py`
* `src/switchcraft/gui_modern/views/crash_view.py`
* `src/switchcraft/gui_modern/views/dashboard_view.py`
* `src/switchcraft/gui_modern/views/helper_view.py`
* `src/switchcraft/gui_modern/views/intune_store_view.py`
* `src/switchcraft/gui_modern/views/intune_view.py`
* `src/switchcraft/gui_modern/views/packaging_wizard_view.py`
* `src/switchcraft/gui_modern/views/settings_view.py`
* `src/switchcraft/gui_modern/views/winget_view.py`
* `src/switchcraft/modern_main.py`
* `src/switchcraft/services/addon_service.py`
* `src/switchcraft/services/ai_service.py`
* `src/switchcraft/services/intune_service.py`
* `src/switchcraft/utils/logging_handler.py`
* `src/switchcraft_ai/service.py`
* `src/switchcraft_winget/utils/winget.py`
* `tests/test_app_startup_critical.py`
* `tests/test_crash_view.py`
* `tests/test_i18n_integrity.py`
* `tests/test_intune_service_core.py`
* `tests/test_navigation_map.py`
* `tests/test_notification_service.py`
* `tests/test_packaging_wizard.py`
* `tests/test_session_storage.py`
* `tests/test_settings_language.py`
* `tests/test_template_company.py`
📝 Add docstrings to `general-improvements-and-fixes`
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

Caution

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

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

28-29: Potential inconsistency: setUp skips tests in stub mode, but tests have stub-mode branches.

The setUp method skips the entire test class when stub mode is detected (lines 28-29), yet individual test methods (lines 36-41, 47-52, etc.) contain conditional branches for stub mode responses. If setUp skips on stub detection, these stub-mode branches will never execute.

Either:

  1. Remove the skipTest in setUp to allow stub-mode testing, or
  2. Remove the stub-mode branches in individual tests if they're intentionally unreachable.
#!/bin/bash
# Check if there are other test files with similar patterns
rg -n "skipTest.*stub" tests/
src/switchcraft/assets/lang/en.json (1)

252-252: Duplicate key btn_cancel detected.

The key btn_cancel appears twice in this file (lines 252 and 855). JSON parsers will silently use the last occurrence, which can lead to confusion and maintenance issues. Remove one of the duplicate entries.

🔧 Proposed fix

Remove the duplicate entry at line 855:

     "btn_later": "Later",
-~
-    "btn_cancel": "Cancel",
-~
     "setting_changed": "Setting Changed",

Also applies to: 855-855

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

275-275: Duplicate key btn_cancel detected.

Same issue as en.json - the key btn_cancel appears twice (lines 275 and 855). Remove one of the duplicate entries.

🔧 Proposed fix

Remove the duplicate entry at line 855:

     "btn_later": "Später",
-~
-    "btn_cancel": "Abbrechen",
-~
     "setting_changed": "Einstellung geändert",

Also applies to: 855-855

🤖 Fix all issues with AI agents
In `@docs/.vitepress/config.mts`:
- Line 28: The logo path in the VitePress config (the logo property in
docs/.vitepress/config.mts) incorrectly includes the public/ prefix and will
404; update the logo value to remove /public so it points to the served root
(e.g., change '/public/screenshots/switchcraft_logo_with_Text.png' to
'/screenshots/switchcraft_logo_with_Text.png') so VitePress can find and serve
the asset.

In `@docs/index.md`:
- Line 9: Replace the GitHub raw URL used for the hero image in docs/index.md
with a site-relative path so VitePress can optimize the asset; locate the image
reference containing "switchcraft_logo_with_Text.png" and change its src to use
the relative path /public/screenshots/switchcraft_logo_with_Text.png (or simply
/screenshots/switchcraft_logo_with_Text.png depending on your VitePress static
folder setup) so the image is bundled and served by the static site generator.

In `@src/switchcraft_winget/utils/winget.py`:
- Around line 130-134: The ps_script builds a PowerShell command by
interpolating query directly (variable names: query, ps_script) which allows
command injection; fix by escaping single quotes in query before interpolation
(e.g., replace each ' with '' for PowerShell single-quoted strings) and use that
escaped value when constructing ps_script, or better, avoid string interpolation
entirely by passing the query as a safe argument to PowerShell (use a
param/ArgumentList approach) and invoke via subprocess.run (cmd) so ps_script
never contains raw unescaped user input.

In `@src/switchcraft/gui_modern/app.py`:
- Around line 1608-1611: The code checks self._view_cache using
NavIndex.ANALYZER (an enum/int) but the cache keys are stored as the string
'analyzer', so the lookup never succeeds; change the lookup to use the string
key 'analyzer' (e.g., if 'analyzer' in self._view_cache) and then retrieve
analyzer_view = self._view_cache['analyzer'] before calling
hasattr(analyzer_view, 'start_analysis') and self.page.run_task(lambda:
analyzer_view.start_analysis(tmp.name)); this ensures the cached view is found
and start_analysis is invoked.
- Around line 1586-1597: A temporary .msi file is created with
tempfile.NamedTemporaryFile(delete=False) and never removed; keep using
NamedTemporaryFile(delete=False) and pass the temp path (tmp.name) into the
existing start_analysis cleanup_path parameter so the analysis routine will
unlink the file when finished (ensure tmp.close() remains before download and
supply cleanup_path=tmp.name when calling start_analysis).

In `@src/switchcraft/gui_modern/views/dashboard_view.py`:
- Around line 88-99: The docstring for _refresh_ui is placed after a comment so
Python treats it as a no-op string literal; move the triple-quoted string
immediately below the def _refresh_ui(self): line so it becomes the method's
docstring, and delete or convert the original string literal (currently after
the "# Stats" comment) into a normal comment if needed; ensure the docstring
text remains unchanged and still describes rebuilding stats, chart, recent
activity and the swallowed update() exceptions.

In `@src/switchcraft/gui_modern/views/helper_view.py`:
- Around line 257-267: Replace the hardcoded tooltip "Send message" on the
ft.IconButton with the internationalized string used elsewhere in this file
(e.g., call the module's i18n/gettext helper) so tooltips are consistent; locate
the ft.IconButton instance rendering the send button (the one with
ft.Icons.SEND_ROUNDED and on_click=send_message) and swap the literal tooltip
value for the i18n lookup (use the same key/helper style as the copy button
nearby).
- Around line 161-167: Replace the hardcoded tooltip string on the ft.IconButton
(the COPY icon created near the ft.IconButton call that references copy_handler)
with the i18n lookup used elsewhere; specifically, call i18n.get with the same
key/fallback pattern used in this file (e.g., i18n.get("copy_message", "Copy
message")) and pass that result to the tooltip parameter so the button’s tooltip
is localized consistently.
- Around line 107-135: The copy_handler's try/except currently treats
ImportError and other pyperclip exceptions differently, skipping the Windows
'clip' fallback when pyperclip is present but pyperclip.copy() fails; change the
control flow so you first attempt to import pyperclip (catch ImportError and set
a flag), then regardless of import success attempt pyperclip.copy() inside its
own try/except and if that copy fails fall through to try the subprocess.clip
fallback (subprocess.run(['clip'], ...)) and finally the Flet fallback
(page.set_clipboard) if clip fails; ensure you log the exception from
pyperclip.copy() (e.g., via logger.warning) and only treat ImportError as a
signal that pyperclip isn't available, not as the only reason to try other
fallbacks.

In `@src/switchcraft/modern_main.py`:
- Around line 99-112: The fallback SimpleI18n.get signature must match the real
i18n.get; change SimpleI18n.get to accept arbitrary keyword args (def get(self,
key, default=None, **kwargs)) and handle formatting: if default is a string and
kwargs are provided, return default.format(**kwargs), otherwise return default,
ensuring callers that pass i18n.get(key, kw=value) won't raise.
- Around line 212-257: The CLI argument handling (help/version/factory-reset) is
currently inside main(page) which is invoked by ft.app(target=main), so calling
sys.exit() and using blocking input() will interfere with Flet's event loop;
move all that logic out of main and into the script's startup path (the if
__name__ == "__main__": block) so it runs before ft.app() is called.
Specifically, remove the block that checks sys.argv (the
help/version/factory-reset handling that uses input(),
SwitchCraftConfig.delete_all_application_data(), and sys.exit()) from main and
place equivalent pre-checks in the __main__ section, preserving behavior for
printing help/version and performing the factory reset confirmation, then call
ft.app(target=main) only after those checks complete.
🧹 Nitpick comments (39)
src/switchcraft_winget/utils/winget.py (1)

68-79: Consider distinguishing "no results" from "search errors" in fallback logic.

The log messages state "returned no results," but the underlying methods return an empty list for both actual empty results and errors (e.g., timeouts, PowerShell not installed, parsing failures). This could make debugging harder when the fallback chain activates due to errors rather than genuine absence of matches.

Consider having the search methods return a sentinel or tuple to differentiate between "searched successfully but found nothing" and "search failed," so the fallback logging can be more accurate.

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

222-235: Minor docstring typo: use Python boolean literals.

The docstring says "Returns: true if... false otherwise" but Python uses True/False.

📝 Proposed fix
         Returns:
-        	`true` if an addon with the given id is installed, `false` otherwise.
+        	True if an addon with the given id is installed, False otherwise.
src/switchcraft_ai/service.py (2)

228-234: Greeting responses use hardcoded strings instead of i18n.

The greeting responses are hardcoded in German and English, while all other responses in this method use i18n.get() for localization. For consistency and easier maintenance, consider adding i18n keys like ai_greeting_response.

♻️ Proposed refactor
         # Check for greetings first
         if self._is_greeting(query):
-            if lang == "de":
-                return "Hallo! 👋 Ich bin SwitchCraft AI, dein Paketierungs-Assistent. Wie kann ich dir helfen?"
-            else:
-                return "Hello! 👋 I'm SwitchCraft AI, your packaging assistant. How can I help you?"
+            return i18n.get("ai_greeting_response", lang=lang)

196-208: Fix docstring and consider extracting duplicate _is_greeting method.

The identical _is_greeting method exists in both src/switchcraft_ai/service.py and src/switchcraft/services/ai_service.py (lines 23-27). Extract to a shared utility module to eliminate duplication and maintain a single source of truth.

Also, fix the docstring typo: use Python's True/False (not backtick-enclosed true/false).

docs/index.md (1)

63-77: Improve alt text for better accessibility.

The alt attributes ("SwitchCraft Screenshot 2", "SwitchCraft Screenshot 3", etc.) are generic and don't describe what each screenshot displays. Replace with descriptive content that explains the actual UI shown (e.g., "SwitchCraft Intune packaging interface", "SwitchCraft application settings panel") to help users relying on screen readers understand the purpose of each screenshot.

src/switchcraft/controllers/analysis_controller.py (1)

170-172: LGTM! Instance-based AddonService usage is correct.

The change from a static call to an instance-based approach aligns with the import_addon_module method signature in addon_service.py, which is an instance method requiring self.

Consider caching the AddonService instance at the controller level (e.g., in __init__) if this method is called frequently, to avoid repeated instantiation overhead.

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

210-214: Inconsistent snack bar API usage.

The _copy_error method (lines 72-81) assigns directly to page.snack_bar and sets .open = True, whereas here you use page.show_snack_bar(...). This may work if Flet supports both patterns, but consider unifying the approach for consistency.

tests/test_notification_service.py (1)

42-53: Test relies on internal implementation details.

Directly mutating self.service.notifications[0]["read"] = True bypasses any service API. If the service provides a mark_as_read(notification_id) method, prefer using that to make tests more resilient to internal changes.

tests/test_crash_view.py (1)

81-93: Consider verifying sys.exit was called.

The test patches sys.exit but doesn't assert it was called. Adding this assertion would verify the complete reload flow executed successfully.

Suggested improvement
     def test_reload_app_calls_subprocess(self, mock_exit, mock_popen):
         """Test that reload app calls subprocess.Popen."""
         error = ValueError("Test error")
         view = CrashDumpView(self.page, error)
 
         # Test that reload_app calls subprocess.Popen
         # We don't need to mock sys.frozen since getattr handles it gracefully
         view._reload_app(self.page)
         mock_popen.assert_called_once()
         self.page.clean.assert_called_once()
         self.page.add.assert_called_once()
+        mock_exit.assert_called_once_with(0)
src/switchcraft/services/ai_service.py (2)

45-61: Consider extracting duplicated response generation logic.

The response generation logic (title, msg, tips_header, and formatted string) is duplicated between update_context and ask. Extracting this to a private helper method would improve maintainability.

Suggested refactor
+        def _build_addon_required_response(self, query: str) -> str:
+            """Build the standard addon-required guidance response."""
+            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: *{query}*"
+            )
+
         def update_context(self, data: dict):
             # ...
             if self._is_greeting(query):
                 return i18n.get("ai_stub_greeting") or "Hello! ..."
-            title = i18n.get("ai_addon_required_title") or ...
-            # ... duplicated logic ...
+            return self._build_addon_required_response(query)

         def ask(self, query):
             # ...
             if self._is_greeting(query):
                 return i18n.get("ai_stub_welcome") or "Hello! ..."
-            title = i18n.get("ai_addon_required_title") or ...
-            # ... duplicated logic ...
+            return self._build_addon_required_response(query)

Also applies to: 78-95


56-59: Hardcoded English tips may be intentional but worth noting.

The installer tips are hardcoded in English while other strings use i18n. If localization is desired for these tips, they should use i18n keys. However, since these are technical command-line switches, keeping them in English may be intentional.

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

1054-1071: Consider caching addon status for the loop.

The static method is_addon_installed_static is called once per addon in the loop. Per the relevant snippet in addon_service.py, this method internally iterates through all addons each time. For better efficiency with larger addon lists, consider fetching installed addon IDs once before the loop.

However, with only 3 addons currently, this is a minor optimization opportunity rather than a blocking issue.

tests/test_intune_service_core.py (3)

7-8: Consider using pytest with proper package configuration instead of sys.path manipulation.

While this pattern works, it's fragile and can cause issues if tests are run from different directories. Consider configuring pyproject.toml or setup.py with proper package discovery, or use pytest's conftest.py with pythonpath configuration.


28-32: Test is environment-dependent and non-deterministic.

This test's result depends on whether IntuneWinAppUtil.exe exists on the test machine. Consider using mocking consistently (like in test_is_tool_available_mock) or removing this test in favor of the mocked version.


34-39: Consider verifying the mock was called on the expected path.

The test patches Path.exists globally but doesn't verify it was called with the expected path. Adding an assertion would make the test more robust.

Suggested improvement
 `@patch`('pathlib.Path.exists')
 def test_is_tool_available_mock(self, mock_exists):
     """Test is_tool_available with mocked path."""
     mock_exists.return_value = True
     result = self.service.is_tool_available()
     self.assertTrue(result)
+    mock_exists.assert_called()
src/switchcraft/modern_main.py (1)

350-358: Redundant page.update() calls and blocking time.sleep() in UI thread.

Calling page.update() three times consecutively is redundant—only the final call has any effect. The time.sleep(0.3) blocks the Flet event loop, which can cause UI jank. Consider using page.run_task() with asyncio.sleep() for non-blocking delays if timing is essential.

Suggested simplification
 # Add loading screen FIRST - before any other operations
 page.add(loading_container)

-# Force immediate rendering - multiple updates to ensure visibility
-page.update()
-page.update()  # Second update to force render
-page.update()  # Third update to really ensure it's visible
-
-# Give Flet time to actually render the loading screen before heavy operations
-import time
-time.sleep(0.3)  # 300ms delay to ensure loading screen is rendered and visible
-page.update()  # Final update after delay
+# Single update is sufficient
+page.update()
src/switchcraft/gui/app.py (1)

148-193: Redundant AddonService imports within the same method.

AddonService is imported from the same module multiple times (lines 148-149, 160-161, 178-179, 192). Consider importing once at the top of the method.

Suggested consolidation
 def _run_background_init(self):
     try:
         self.after(0, lambda: self._update_loading("Registering Addons..."))
         from switchcraft.services.addon_service import AddonService
         AddonService.register_addons()

         # ... later uses can reference AddonService directly without re-importing ...
-        from switchcraft.services.addon_service import AddonService
         addon_service = AddonService()
         ai_mod = addon_service.import_addon_module("ai", "service")
src/switchcraft/gui_modern/views/intune_view.py (2)

386-398: Redundant conditional pattern for field assignment.

The pattern if app_data.get("key"): field.value = app_data.get("key", "") makes two dict lookups. The default "" is never used since the condition already ensures the value is truthy.

Suggested simplification
-if app_data.get("displayName"):
-    self.up_display_name.value = app_data.get("displayName", "")
-if app_data.get("description"):
-    self.up_description.value = app_data.get("description", "")
+if val := app_data.get("displayName"):
+    self.up_display_name.value = val
+if val := app_data.get("description"):
+    self.up_description.value = val

428-435: Dead code: detection.xml content is read but never used.

The code reads detection_content but then does nothing with it (the comment says "for now"). Either implement the XML parsing or remove this block to avoid confusion.

Suggested cleanup
 for file_name in file_list:
-    if 'detection.xml' in file_name.lower():
-        try:
-            detection_content = zip_ref.read(file_name).decode('utf-8')
-            # Parse XML to extract info if needed
-            # For now, we'll focus on the manifest if available
-        except Exception:
-            pass
-
     # Look for manifest or metadata JSON
     if 'manifest' in file_name.lower() or 'metadata' in file_name.lower():
src/switchcraft/gui_modern/views/settings_view.py (1)

1184-1184: Inconsistent use of instance method vs static method for addon checks.

This line uses AddonService().is_addon_installed(addon["id"]) (instance method), but other places in this file and app.py use AddonService.is_addon_installed_static(). Consider using the static method consistently for simple availability checks.

Suggested fix
-is_installed = AddonService().is_addon_installed(addon["id"])
+is_installed = AddonService.is_addon_installed_static(addon["id"])
tests/test_i18n_integrity.py (1)

134-157: Potential false positives with possessive pronoun patterns.

The patterns include Ihre, Ihren, Ihrem, Ihres (lines 137-140), but line 156 only skips exact match "Ihr". These possessive forms can also mean "their" (3rd person plural possessive) in valid informal German text, not just formal "your".

Consider either:

  1. Adding these forms to the skip list, or
  2. Documenting that these may produce false positives requiring manual review
💡 Suggested enhancement
                     # Skip "Ihr" as it can be ambiguous (could be "their" in some contexts)
                     # But flag all other capitalized forms
-                    if matched_text != "Ihr":  # "Ihr" is too ambiguous
+                    # Skip possessive forms that are ambiguous (could be "their")
+                    ambiguous_forms = {"Ihr", "Ihre", "Ihren", "Ihrem", "Ihres"}
+                    if matched_text not in ambiguous_forms:
                         violations.append(f"{key}: Contains Sie-Form: '{matched_text}'")

Alternatively, keep the current behavior but add a comment noting that these patterns prioritize catching formal language at the cost of potential false positives.

tests/test_settings_language.py (2)

57-67: Unused mock parameter in test.

The mock_get_value parameter from the @patch decorator is not used in the test body. Either remove the unnecessary patch or add assertions that verify the expected config interactions.

♻️ Proposed fix - remove unused patch
-    `@patch`('switchcraft.utils.config.SwitchCraftConfig.get_value')
-    def test_build_date_display(self, mock_get_value):
+    def test_build_date_display(self):
         """Test that build date is displayed in settings."""
         from switchcraft.gui_modern.views.settings_view import ModernSettingsView

         view = ModernSettingsView(self.page)
         build_date = view._get_build_date()

         # Build date should be a string
         self.assertIsInstance(build_date, str)
         self.assertGreater(len(build_date), 0)

45-55: Missing assertion for goto_tab call.

Unlike test_language_change_immediate, this test does not verify that goto_tab was called after the language change. Consider adding this assertion for consistency, or document why it's intentionally omitted.

💡 Suggested enhancement
     `@patch`('switchcraft.utils.config.SwitchCraftConfig.set_user_preference')
     `@patch`('switchcraft.utils.i18n.i18n.set_language')
     def test_language_change_german(self, mock_set_language, mock_set_pref):
         """Test changing language to German."""
         from switchcraft.gui_modern.views.settings_view import ModernSettingsView

         view = ModernSettingsView(self.page)
         view._on_lang_change("de")

         mock_set_pref.assert_called_once_with("Language", "de")
         mock_set_language.assert_called_once_with("de")
+        
+        # Verify view reload was triggered
+        self.page.switchcraft_app.goto_tab.assert_called_once_with(0)
src/switchcraft/gui_modern/views/winget_view.py (3)

306-310: Unnecessary try/except around string formatting.

The f-string formatting f"https://cdn.winstall.app/packages/{pkg_id.replace('.', '/')}/icon.png" cannot raise an exception since pkg_id is already verified as truthy. The try/except adds no value here.

♻️ Proposed simplification
                 if pkg_id:
-                    # Try winstall.app logo API
-                    try:
-                        logo_url = f"https://cdn.winstall.app/packages/{pkg_id.replace('.', '/')}/icon.png"
-                    except Exception:
-                        pass
+                    # winstall.app logo API
+                    logo_url = f"https://cdn.winstall.app/packages/{pkg_id.replace('.', '/')}/icon.png"

Same applies to lines 376-379.


650-652: Redundant import.

webbrowser is already imported at the top of the file (line 4). Remove this local import.

♻️ Proposed fix
     def _deploy_wau(self, info):
-        import webbrowser
         webbrowser.open("https://github.com/Romanitho/Winget-AutoUpdate")
         self._show_snack("WAU info opened in browser.")

720-761: Elevation restart logic should handle cleanup errors gracefully.

The restart logic includes cleanup steps (logging.shutdown, gc.collect, time.sleep), but if any of these fail unexpectedly, the restart process might be incomplete. Consider wrapping the entire cleanup sequence in a try block.

💡 Suggested enhancement
                 try:
                     import sys
                     import time
                     import gc
                     import logging

-                    # 1. Close all file handles and release resources
+                    # Cleanup (best effort - don't let failures block restart)
                     try:
                         logging.shutdown()
-                    except Exception:
-                        pass
-
-                    # 2. Force garbage collection
-                    gc.collect()
-
-                    # 3. Small delay to allow file handles to be released
-                    time.sleep(0.2)
+                        gc.collect()
+                        time.sleep(0.2)
+                    except Exception:
+                        pass  # Continue with restart even if cleanup fails

                     executable = sys.executable
src/switchcraft/gui_modern/app.py (3)

119-123: Duplicate splash termination logic.

The splash process is terminated twice: once at line 119-123 (after build_ui() in __init__) and again at lines 983-987 (at the end of build_ui()). The first termination attempt will succeed, making the second redundant. Consider removing one of them to avoid confusion.

Suggested fix: Remove duplicate termination

Remove the termination block at lines 119-123 since build_ui() already handles it at the end:

         self.build_ui()
-
-        # Now that UI is built, shutdown splash screen
-        if self.splash_proc:
-            try:
-                self.splash_proc.terminate()
-            except Exception:
-                pass

Also applies to: 983-987


977-980: Redundant consecutive page.update() calls.

Calling page.update() three times in succession has no additional effect—Flet batches updates, so only the first call is meaningful. The comments suggest this is for "ensuring rendering," but multiple synchronous calls don't guarantee that.

Suggested fix
-        # Force multiple updates to ensure UI is visible and rendered
-        self.page.update()
-        self.page.update()  # Second update to ensure rendering
-        self.page.update()  # Third update for good measure
+        self.page.update()

927-948: Navigation delay via time.sleep() on background thread is fragile.

Using fixed time.sleep(0.5) delays to wait for UI readiness is unreliable—different machines or load conditions may require more or less time. Consider using Flet's page.run_task() or event-based signaling instead.

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

187-194: Misleading comment: "Create a copy reference" doesn't create a copy.

The comment says "Create a copy reference for closure" but app_copy = app just creates another reference to the same object—not a copy. The code works correctly because the lambda's default argument a=app_copy captures the value at definition time, but the comment is misleading.

Suggested fix: Clarify the comment
-                # Create ListTile with direct lambda (like winget_view does)
-                # Capture app in lambda default argument to avoid closure issues
-                app_copy = app  # Create a copy reference for closure
                 tile = ft.ListTile(
                     leading=leading_widget,
                     title=ft.Text(app.get("displayName", i18n.get("unknown") or "Unknown"), selectable=True),
                     subtitle=ft.Text(app.get("publisher", ""), selectable=True),
-                    on_click=lambda e, a=app_copy: self._handle_app_click(a)
+                    on_click=lambda e, a=app: self._handle_app_click(a)  # Default arg captures value
                 )

The intermediate variable isn't needed since the default argument already handles the closure capture.


168-177: Duplicate logo extraction logic.

The same logo extraction pattern (checking largeIcon dict, converting base64 to data URL, falling back to iconUrl/logoUrl) appears in both _update_list (lines 168-177) and _show_details (lines 246-254). Consider extracting this to a helper method.

Suggested helper method
def _extract_logo_url(self, app: dict) -> str | None:
    """Extract logo URL from app metadata, handling base64 and various key names."""
    logo_url = None
    large_icon = app.get("largeIcon")
    if isinstance(large_icon, dict):
        logo_url = large_icon.get("value")
        if logo_url and not logo_url.startswith(("http://", "https://", "data:")):
            logo_url = f"data:image/png;base64,{logo_url}"
    if not logo_url:
        logo_url = app.get("iconUrl") or app.get("logoUrl")
    return logo_url

Also applies to: 246-254

tests/test_session_storage.py (1)

19-22: hasattr check on MagicMock is always True for non-spec attributes.

MagicMock auto-creates attributes on access, so hasattr(self.page, 'switchcraft_session') would return True even before assignment. However, since spec=ft.Page is used and switchcraft_session isn't part of ft.Page, the check works correctly here. The explicit assignment on line 22 ensures the dict exists regardless.

Consider simplifying to always assign:

Suggested simplification
     def setUp(self):
         self.page = MagicMock(spec=ft.Page)
-        # Initialize session storage like ModernApp does
-        if not hasattr(self.page, 'switchcraft_session'):
-            self.page.switchcraft_session = {}
+        # Initialize session storage like ModernApp does
+        self.page.switchcraft_session = {}

Since this is test setup, unconditional assignment is clearer and matches what test_packaging_wizard.py does.

tests/test_legacy_startup_critical.py (2)

14-20: Simplify redundant assertion.

self.assertTrue(True) on line 18 is a no-op. If the import succeeds without exception, the test passes implicitly.

Suggested simplification
     def test_legacy_app_imports(self):
         """Test that gui.app can be imported without errors."""
-        try:
-            import switchcraft.gui.app
-            self.assertTrue(True)
-        except Exception as e:
-            self.fail(f"Failed to import gui.app: {e}")
+        # Import will raise if there's an error; no assertion needed
+        import switchcraft.gui.app

55-73: Static method detection logic may produce false negatives.

The check on line 68 looks for AddonService.is_addon_installed( but excludes lines containing is_addon_installed_static. However, a valid call like AddonService().is_addon_installed(...) (with instantiation) would still trigger the inner check on line 70, which correctly allows it. The logic is sound but somewhat convoluted.

Consider adding a comment explaining the intent or simplifying the check to only flag the pattern AddonService.is_addon_installed( without an instance ().

tests/test_packaging_wizard.py (2)

47-54: Remove unnecessary hasattr guards.

did_mount is a standard Flet lifecycle method and upload_info is initialized in __init__. The hasattr checks add unnecessary conditional logic that could mask test failures if these attributes were accidentally removed.

Suggested simplification
-        # Simulate did_mount (which would normally be called by Flet)
-        if hasattr(view, 'did_mount'):
-            view.did_mount()
+        # Simulate did_mount (which would normally be called by Flet)
+        view.did_mount()

-        # Verify that upload_info was populated
-        if hasattr(view, 'upload_info'):
-            self.assertEqual(view.upload_info["displayName"], "Test App")
-            self.assertEqual(view.upload_info["publisher"], "Test Publisher")
+        # Verify that upload_info was populated
+        self.assertEqual(view.upload_info["displayName"], "Test App")
+        self.assertEqual(view.upload_info["publisher"], "Test Publisher")

56-73: Same hasattr pattern - consider removing guards.

Similar to the previous test, the hasattr(view, 'did_mount') check on line 69 is unnecessary since did_mount is a known method.

Suggested simplification
-        # After did_mount, session should be cleared
-        if hasattr(view, 'did_mount'):
-            view.did_mount()
+        # After did_mount, session should be cleared
+        view.did_mount()
tests/test_app_startup_critical.py (3)

1-9: Remove unused imports.

subprocess and time are imported but never used in this test file.

Suggested cleanup
 import unittest
 import sys
 import os
-import subprocess
-import time
 from pathlib import Path

127-133: Redundant exception type check.

Lines 130-131 check if "UnboundLocalError" or "NameError" appears in str(type(e)), but this code path is unreachable—these exception types are already caught by the except (UnboundLocalError, NameError) clause on line 125.

Suggested simplification
                 try:
                     main(page)
                 except (UnboundLocalError, NameError) as e:
                     self.fail(f"Startup error detected: {e}. This indicates a variable scope issue (e.g., splash_proc not declared as global).")
                 except Exception as e:
                     # Other exceptions might be expected (e.g., if ModernApp fails to initialize)
-                    # But UnboundLocalError/NameError should never happen
-                    if "UnboundLocalError" in str(type(e)) or "NameError" in str(type(e)):
-                        self.fail(f"Variable scope error detected: {e}")
-                    # Other exceptions are acceptable for this test
                     pass

137-166: Duplicate test logic for splash_proc validation.

test_splash_proc_global_declaration and test_no_unbound_local_errors both check for the same splash_proc global declaration issue using nearly identical string analysis. Consider consolidating into a single test or having one call the other's logic.

Also applies to: 194-226

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between caa8524 and d70c5b7.

⛔ Files ignored due to path filters (6)
  • docs/public/screenshots/switchcraft_logo_with_Text.png is excluded by !**/*.png
  • docs/public/screenshots/switchcraft_ui.png is excluded by !**/*.png
  • docs/public/screenshots/switchcraft_ui_2.png is excluded by !**/*.png
  • docs/public/screenshots/switchcraft_ui_3.png is excluded by !**/*.png
  • docs/public/screenshots/switchcraft_ui_4.png is excluded by !**/*.png
  • docs/public/screenshots/switchcraft_ui_5.png is excluded by !**/*.png
📒 Files selected for processing (52)
  • .github/workflows/weekly.yml
  • .gitignore
  • README.md
  • docs/.vitepress/config.mts
  • docs/FEATURES.md
  • docs/INTUNE.md
  • docs/INTUNE_SETUP.md
  • docs/WINGET.md
  • docs/faq.md
  • docs/index.md
  • docs/upgrade.md
  • src/entry.py
  • src/switchcraft/assets/lang/de.json
  • src/switchcraft/assets/lang/en.json
  • src/switchcraft/controllers/analysis_controller.py
  • src/switchcraft/gui/app.py
  • src/switchcraft/gui/views/analyzer_view.py
  • src/switchcraft/gui/views/intune_view.py
  • src/switchcraft/gui/views/settings_view.py
  • src/switchcraft/gui_modern/app.py
  • src/switchcraft/gui_modern/controls/sidebar.py
  • src/switchcraft/gui_modern/views/analyzer_view.py
  • src/switchcraft/gui_modern/views/crash_view.py
  • src/switchcraft/gui_modern/views/dashboard_view.py
  • src/switchcraft/gui_modern/views/helper_view.py
  • src/switchcraft/gui_modern/views/intune_store_view.py
  • src/switchcraft/gui_modern/views/intune_view.py
  • src/switchcraft/gui_modern/views/packaging_wizard_view.py
  • src/switchcraft/gui_modern/views/settings_view.py
  • src/switchcraft/gui_modern/views/winget_view.py
  • src/switchcraft/modern_main.py
  • src/switchcraft/services/addon_service.py
  • src/switchcraft/services/ai_service.py
  • src/switchcraft/services/intune_service.py
  • src/switchcraft/utils/logging_handler.py
  • src/switchcraft_ai/service.py
  • src/switchcraft_winget/utils/winget.py
  • switchcraft_legacy.spec
  • tests/test_app_startup_critical.py
  • tests/test_crash_view.py
  • tests/test_i18n_integrity.py
  • tests/test_intune_service_core.py
  • tests/test_legacy_startup_critical.py
  • tests/test_navigation_map.py
  • tests/test_notification_service.py
  • tests/test_packaging_wizard.py
  • tests/test_phase2.py
  • tests/test_session_storage.py
  • tests/test_settings_language.py
  • tests/test_template_company.py
  • tests/test_ui_regressions.py
  • tests/verify_build.ps1
🧰 Additional context used
🧬 Code graph analysis (21)
src/switchcraft/gui_modern/controls/sidebar.py (1)
src/switchcraft/gui_modern/nav_constants.py (1)
  • NavIndex (13-47)
tests/test_ui_regressions.py (1)
src/switchcraft/gui_modern/app.py (1)
  • ModernApp (25-1633)
src/switchcraft/controllers/analysis_controller.py (1)
src/switchcraft/services/addon_service.py (1)
  • import_addon_module (110-147)
tests/test_crash_view.py (1)
src/switchcraft/gui_modern/views/crash_view.py (4)
  • CrashDumpView (4-214)
  • _copy_error (52-83)
  • _close_app (85-129)
  • _reload_app (131-214)
tests/test_packaging_wizard.py (1)
src/switchcraft/gui_modern/views/packaging_wizard_view.py (1)
  • did_mount (60-79)
src/switchcraft/gui_modern/views/intune_view.py (3)
src/switchcraft/gui_modern/utils/view_utils.py (1)
  • _show_snack (9-20)
src/switchcraft/services/intune_service.py (2)
  • search_apps (396-446)
  • get_app_details (448-468)
src/switchcraft/gui_modern/utils/file_picker_helper.py (2)
  • FilePickerHelper (5-85)
  • pick_file (12-38)
tests/test_intune_service_core.py (1)
src/switchcraft/services/intune_service.py (3)
  • is_tool_available (33-34)
  • download_tool (36-57)
  • create_intunewin (59-143)
src/switchcraft/gui_modern/views/helper_view.py (2)
src/switchcraft/gui_modern/app.py (1)
  • goto_tab (1061-1069)
src/switchcraft/utils/i18n.py (1)
  • get (179-206)
tests/test_session_storage.py (1)
tests/test_packaging_wizard.py (1)
  • setUp (12-24)
src/switchcraft_ai/service.py (2)
src/switchcraft/services/ai_service.py (1)
  • _is_greeting (24-27)
src/switchcraft/utils/i18n.py (1)
  • get (179-206)
src/switchcraft/gui/views/analyzer_view.py (1)
src/switchcraft/services/addon_service.py (1)
  • is_addon_installed_static (238-248)
src/switchcraft/gui_modern/views/packaging_wizard_view.py (1)
src/switchcraft/gui_modern/utils/view_utils.py (2)
  • _show_snack (9-20)
  • _close_dialog (50-75)
src/switchcraft/gui_modern/views/intune_store_view.py (3)
src/switchcraft/utils/i18n.py (1)
  • get (179-206)
src/switchcraft/services/intune_service.py (1)
  • list_app_assignments (470-484)
src/switchcraft/gui_modern/utils/view_utils.py (1)
  • _show_snack (9-20)
tests/test_settings_language.py (2)
src/switchcraft/gui_modern/app.py (1)
  • goto_tab (1061-1069)
src/switchcraft/gui_modern/views/settings_view.py (3)
  • ModernSettingsView (17-1599)
  • _on_lang_change (886-1038)
  • _get_build_date (1570-1599)
tests/test_phase2.py (2)
src/switchcraft/services/ai_service.py (2)
  • ask (63-96)
  • update_context (29-61)
src/switchcraft_ai/service.py (2)
  • ask (91-105)
  • update_context (86-89)
src/switchcraft/gui/views/settings_view.py (1)
src/switchcraft/services/addon_service.py (2)
  • AddonService (21-397)
  • is_addon_installed_static (238-248)
src/switchcraft/gui_modern/app.py (5)
src/switchcraft/services/notification_service.py (3)
  • get_notifications (115-116)
  • mark_all_read (101-105)
  • clear_all (107-110)
src/switchcraft/utils/i18n.py (1)
  • get (179-206)
src/switchcraft/services/addon_service.py (1)
  • install_from_github (282-397)
src/switchcraft/gui_modern/nav_constants.py (1)
  • NavIndex (13-47)
src/switchcraft/gui_modern/utils/view_utils.py (1)
  • _show_snack (9-20)
src/switchcraft/gui_modern/views/analyzer_view.py (2)
src/switchcraft/debug_views.py (2)
  • open (22-22)
  • update (19-19)
src/switchcraft/gui_modern/utils/view_utils.py (1)
  • _show_snack (9-20)
tests/test_app_startup_critical.py (1)
src/switchcraft/modern_main.py (1)
  • main (198-506)
src/switchcraft/gui_modern/views/crash_view.py (5)
src/switchcraft/debug_views.py (2)
  • update (19-19)
  • clean (21-21)
src/switchcraft/gui_modern/views/winget_view.py (1)
  • target (210-214)
tests/test_all_views.py (1)
  • page (12-16)
tests/test_gui_views.py (1)
  • page (19-20)
src/switchcraft/gui/views/analyzer_view.py (1)
  • copy (569-572)
src/switchcraft/modern_main.py (3)
src/switchcraft/utils/i18n.py (1)
  • get (179-206)
src/switchcraft/gui/app.py (1)
  • main (1119-1204)
src/switchcraft/main.py (1)
  • main (8-72)
🪛 LanguageTool
docs/upgrade.md

[grammar] ~90-~90: Ensure spelling is correct
Context: ...ically update addons: - Automatic: Addons are checked and updated to match your S...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~91-~91: Ensure spelling is correct
Context: ... HelpAddon Manager to update addons manually ### Configuration Migration ...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~91-~91: Ensure spelling is correct
Context: ...ddon Manager** to update addons manually ### Configuration Migration SwitchCraft aut...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🪛 markdownlint-cli2 (0.18.1)
docs/FEATURES.md

94-94: Headings must start at the beginning of the line

(MD023, heading-start-left)


108-108: Headings must start at the beginning of the line

(MD023, heading-start-left)

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


themeConfig: {
logo: 'https://github.com/FaserF/SwitchCraft/raw/main/images/switchcraft_logo_with_Text.png',
logo: '/public/screenshots/switchcraft_logo_with_Text.png',
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Incorrect logo path—VitePress serves public/ contents at root.

The path /public/screenshots/switchcraft_logo_with_Text.png will result in a 404. VitePress serves files from the public/ directory at the site root, so the path should not include /public/.

🐛 Proposed fix
-        logo: '/public/screenshots/switchcraft_logo_with_Text.png',
+        logo: '/screenshots/switchcraft_logo_with_Text.png',
📝 Committable suggestion

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

Suggested change
logo: '/public/screenshots/switchcraft_logo_with_Text.png',
logo: '/screenshots/switchcraft_logo_with_Text.png',
🤖 Prompt for AI Agents
In `@docs/.vitepress/config.mts` at line 28, The logo path in the VitePress config
(the logo property in docs/.vitepress/config.mts) incorrectly includes the
public/ prefix and will 404; update the logo value to remove /public so it
points to the served root (e.g., change
'/public/screenshots/switchcraft_logo_with_Text.png' to
'/screenshots/switchcraft_logo_with_Text.png') so VitePress can find and serve
the asset.

tagline: "Analyze installers, create Intune packages, and streamline your IT workflow — all in one powerful tool."
image:
src: https://github.com/FaserF/SwitchCraft/raw/main/src/switchcraft/assets/switchcraft_logo_with_Text.png
src: https://github.com/FaserF/SwitchCraft/raw/main/docs/public/screenshots/switchcraft_logo_with_Text.png
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the hero image file exists and check the docs structure
fd -t f "switchcraft_logo_with_Text.png" docs/

echo "---"
echo "Checking docs directory structure:"
ls -la docs/

echo "---"
echo "Checking docs/public structure if it exists:"
if [ -d "docs/public" ]; then
  find docs/public -type f -name "*.png" 2>/dev/null | head -20
fi

Repository: FaserF/SwitchCraft

Length of output: 2074


🏁 Script executed:

#!/bin/bash
# Check the exact image reference in docs/index.md
grep -n "switchcraft_logo_with_Text" docs/index.md

Repository: FaserF/SwitchCraft

Length of output: 174


🏁 Script executed:

#!/bin/bash
# Search for VitePress config to understand how assets are served
fd -t f "config\.(ts|js|mts)" . | grep -E "docs|vitepress" | head -10

Repository: FaserF/SwitchCraft

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Get context around line 9 in docs/index.md
head -20 docs/index.md

Repository: FaserF/SwitchCraft

Length of output: 691


🏁 Script executed:

#!/bin/bash
# Check how other images are referenced in the file
grep -n "src:" docs/index.md | head -10

Repository: FaserF/SwitchCraft

Length of output: 174


🏁 Script executed:

#!/bin/bash
# Check if other images in the docs use relative paths or GitHub URLs
rg "!\[.*\]\(.*\)" docs/ -A 0 -B 0 | head -20

Repository: FaserF/SwitchCraft

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Check if there are any other image references using YAML syntax in docs
grep -r "src:" docs/ --include="*.md" | head -10

Repository: FaserF/SwitchCraft

Length of output: 186


Use a relative path for the hero image instead of the GitHub raw URL.

The image file exists at docs/public/screenshots/switchcraft_logo_with_Text.png, but referencing it via the GitHub raw URL bypasses VitePress asset optimization and bundling. Use a relative path like /public/screenshots/switchcraft_logo_with_Text.png for proper static site generation and better performance.

🤖 Prompt for AI Agents
In `@docs/index.md` at line 9, Replace the GitHub raw URL used for the hero image
in docs/index.md with a site-relative path so VitePress can optimize the asset;
locate the image reference containing "switchcraft_logo_with_Text.png" and
change its src to use the relative path
/public/screenshots/switchcraft_logo_with_Text.png (or simply
/screenshots/switchcraft_logo_with_Text.png depending on your VitePress static
folder setup) so the image is bundled and served by the static site generator.

Comment on lines 130 to +134
ps_script = f"Find-WinGetPackage -Query '{query}' | Select-Object Name, Id, Version, Source | ConvertTo-Json -Depth 1"
cmd = ["powershell", "-NoProfile", "-NonInteractive", "-Command", ps_script]
startupinfo = self._get_startup_info()

proc = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", errors="ignore", startupinfo=startupinfo, timeout=30)
proc = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", errors="ignore", startupinfo=startupinfo, timeout=45)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Command injection vulnerability in PowerShell query.

The query parameter is directly interpolated into the PowerShell script string without sanitization. A malicious or malformed query containing single quotes could break out of the string context and execute arbitrary PowerShell commands.

Example: query = "test'; Remove-Item -Recurse C:\\Windows -Force; echo '" would escape the string and execute destructive commands.

🔒 Proposed fix: Escape single quotes in the query
     def _search_via_powershell(self, query: str) -> List[Dict[str, str]]:
         """
         Perform a Winget package search via PowerShell and return normalized results.
         
         Parameters:
             query (str): Search query string used to find matching packages.
         
         Returns:
             List[Dict[str, str]]: A list of result dictionaries with keys 'Name', 'Id', 'Version', and 'Source'. Returns an empty list when no results are found or on error.
         """
         try:
-            ps_script = f"Find-WinGetPackage -Query '{query}' | Select-Object Name, Id, Version, Source | ConvertTo-Json -Depth 1"
+            # Escape single quotes by doubling them (PowerShell escape sequence)
+            safe_query = query.replace("'", "''")
+            ps_script = f"Find-WinGetPackage -Query '{safe_query}' | Select-Object Name, Id, Version, Source | ConvertTo-Json -Depth 1"
             cmd = ["powershell", "-NoProfile", "-NonInteractive", "-Command", ps_script]
🤖 Prompt for AI Agents
In `@src/switchcraft_winget/utils/winget.py` around lines 130 - 134, The ps_script
builds a PowerShell command by interpolating query directly (variable names:
query, ps_script) which allows command injection; fix by escaping single quotes
in query before interpolation (e.g., replace each ' with '' for PowerShell
single-quoted strings) and use that escaped value when constructing ps_script,
or better, avoid string interpolation entirely by passing the query as a safe
argument to PowerShell (use a param/ArgumentList approach) and invoke via
subprocess.run (cmd) so ps_script never contains raw unescaped user input.

Comment on lines +1586 to +1597
url = "https://www.7-zip.org/a/7z2409-x64.msi"

tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".msi")
tmp.close()

# Download
response = requests.get(url, stream=True, timeout=30)
response.raise_for_status()

with open(tmp.name, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Temporary file left on disk after demo analysis.

The temporary .msi file is created with delete=False and is never cleaned up. This will accumulate temporary files in the system temp directory over time.

Suggested fix: Pass cleanup_path to start_analysis

The start_analysis method already supports a cleanup_path parameter. Use it:

                 with open(tmp.name, 'wb') as f:
                     for chunk in response.iter_content(chunk_size=8192):
                         f.write(chunk)

                 # Navigate to analyzer if not already there
                 if hasattr(self, '_current_tab_index') and self._current_tab_index != NavIndex.ANALYZER:
                     self.goto_tab(NavIndex.ANALYZER)

                 # Wait a bit for view to load
                 import time
                 time.sleep(0.5)

                 # Trigger analysis
-                if NavIndex.ANALYZER in self._view_cache:
-                    analyzer_view = self._view_cache[NavIndex.ANALYZER]
+                if 'analyzer' in self._view_cache:
+                    analyzer_view = self._view_cache['analyzer']
                     if hasattr(analyzer_view, 'start_analysis'):
-                        self.page.run_task(lambda: analyzer_view.start_analysis(tmp.name))
+                        self.page.run_task(lambda: analyzer_view.start_analysis(tmp.name, cleanup_path=tmp.name))
🤖 Prompt for AI Agents
In `@src/switchcraft/gui_modern/app.py` around lines 1586 - 1597, A temporary .msi
file is created with tempfile.NamedTemporaryFile(delete=False) and never
removed; keep using NamedTemporaryFile(delete=False) and pass the temp path
(tmp.name) into the existing start_analysis cleanup_path parameter so the
analysis routine will unlink the file when finished (ensure tmp.close() remains
before download and supply cleanup_path=tmp.name when calling start_analysis).

Comment on lines +1608 to +1611
if NavIndex.ANALYZER in self._view_cache:
analyzer_view = self._view_cache[NavIndex.ANALYZER]
if hasattr(analyzer_view, 'start_analysis'):
self.page.run_task(lambda: analyzer_view.start_analysis(tmp.name))
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

View cache key mismatch: using NavIndex.ANALYZER instead of string 'analyzer'.

The view cache elsewhere uses string keys like 'analyzer' (e.g., line 1249-1252), but here NavIndex.ANALYZER (an integer) is used as the key. This will never find the cached view, so the analyzer won't receive the start_analysis call.

Proposed fix
-                if NavIndex.ANALYZER in self._view_cache:
-                    analyzer_view = self._view_cache[NavIndex.ANALYZER]
+                if 'analyzer' in self._view_cache:
+                    analyzer_view = self._view_cache['analyzer']
📝 Committable suggestion

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

Suggested change
if NavIndex.ANALYZER in self._view_cache:
analyzer_view = self._view_cache[NavIndex.ANALYZER]
if hasattr(analyzer_view, 'start_analysis'):
self.page.run_task(lambda: analyzer_view.start_analysis(tmp.name))
if 'analyzer' in self._view_cache:
analyzer_view = self._view_cache['analyzer']
if hasattr(analyzer_view, 'start_analysis'):
self.page.run_task(lambda: analyzer_view.start_analysis(tmp.name))
🤖 Prompt for AI Agents
In `@src/switchcraft/gui_modern/app.py` around lines 1608 - 1611, The code checks
self._view_cache using NavIndex.ANALYZER (an enum/int) but the cache keys are
stored as the string 'analyzer', so the lookup never succeeds; change the lookup
to use the string key 'analyzer' (e.g., if 'analyzer' in self._view_cache) and
then retrieve analyzer_view = self._view_cache['analyzer'] before calling
hasattr(analyzer_view, 'start_analysis') and self.page.run_task(lambda:
analyzer_view.start_analysis(tmp.name)); this ensures the cached view is found
and start_analysis is invoked.

Comment on lines 161 to 167
ft.IconButton(
ft.Icons.COPY,
icon_size=12,
icon_size=16,
icon_color="GREY_400",
tooltip="Copy",
on_click=lambda _: page.set_clipboard(text)
tooltip="Copy message",
on_click=copy_handler
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hardcoded tooltip string should use i18n for consistency.

The tooltip "Copy message" on line 165 is hardcoded in English, while other user-facing strings in this file use i18n.get() with fallbacks. Consider using a localized string for consistency.

💡 Suggested fix
                             ft.IconButton(
                                 ft.Icons.COPY,
                                 icon_size=16,
                                 icon_color="GREY_400",
-                                tooltip="Copy message",
+                                tooltip=i18n.get("copy_message_tooltip") or "Copy message",
                                 on_click=copy_handler
                             )
📝 Committable suggestion

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

Suggested change
ft.IconButton(
ft.Icons.COPY,
icon_size=12,
icon_size=16,
icon_color="GREY_400",
tooltip="Copy",
on_click=lambda _: page.set_clipboard(text)
tooltip="Copy message",
on_click=copy_handler
)
ft.IconButton(
ft.Icons.COPY,
icon_size=16,
icon_color="GREY_400",
tooltip=i18n.get("copy_message_tooltip") or "Copy message",
on_click=copy_handler
)
🤖 Prompt for AI Agents
In `@src/switchcraft/gui_modern/views/helper_view.py` around lines 161 - 167,
Replace the hardcoded tooltip string on the ft.IconButton (the COPY icon created
near the ft.IconButton call that references copy_handler) with the i18n lookup
used elsewhere; specifically, call i18n.get with the same key/fallback pattern
used in this file (e.g., i18n.get("copy_message", "Copy message")) and pass that
result to the tooltip parameter so the button’s tooltip is localized
consistently.

Comment on lines 257 to 267
ft.IconButton(
ft.Icons.SEND,
ft.Icons.SEND_ROUNDED,
on_click=send_message,
tooltip="Send",
icon_color="BLUE_400",
bgcolor="BLUE_900",
style=ft.ButtonStyle(shape=ft.RoundedRectangleBorder(radius=10))
tooltip="Send message",
icon_color="WHITE",
bgcolor="BLUE_600",
style=ft.ButtonStyle(
shape=ft.RoundedRectangleBorder(radius=10),
),
icon_size=24
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hardcoded tooltip string should use i18n for consistency.

Similar to the copy button tooltip, the "Send message" tooltip on line 260 is hardcoded. Consider using i18n for consistency with the rest of the file.

💡 Suggested fix
                 ft.IconButton(
                     ft.Icons.SEND_ROUNDED,
                     on_click=send_message,
-                    tooltip="Send message",
+                    tooltip=i18n.get("send_message_tooltip") or "Send message",
                     icon_color="WHITE",
                     bgcolor="BLUE_600",
📝 Committable suggestion

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

Suggested change
ft.IconButton(
ft.Icons.SEND,
ft.Icons.SEND_ROUNDED,
on_click=send_message,
tooltip="Send",
icon_color="BLUE_400",
bgcolor="BLUE_900",
style=ft.ButtonStyle(shape=ft.RoundedRectangleBorder(radius=10))
tooltip="Send message",
icon_color="WHITE",
bgcolor="BLUE_600",
style=ft.ButtonStyle(
shape=ft.RoundedRectangleBorder(radius=10),
),
icon_size=24
)
ft.IconButton(
ft.Icons.SEND_ROUNDED,
on_click=send_message,
tooltip=i18n.get("send_message_tooltip") or "Send message",
icon_color="WHITE",
bgcolor="BLUE_600",
style=ft.ButtonStyle(
shape=ft.RoundedRectangleBorder(radius=10),
),
icon_size=24
)
🤖 Prompt for AI Agents
In `@src/switchcraft/gui_modern/views/helper_view.py` around lines 257 - 267,
Replace the hardcoded tooltip "Send message" on the ft.IconButton with the
internationalized string used elsewhere in this file (e.g., call the module's
i18n/gettext helper) so tooltips are consistent; locate the ft.IconButton
instance rendering the send button (the one with ft.Icons.SEND_ROUNDED and
on_click=send_message) and swap the literal tooltip value for the i18n lookup
(use the same key/helper style as the copy button nearby).

Comment on lines +99 to +112
class SimpleI18n:
def get(self, key, default=None):
"""
Always return the provided default value.
Parameters:
key: The lookup key (ignored by this implementation).
default: The value to return.
Returns:
The provided `default` value.
"""
return default
i18n = SimpleI18n()
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fallback SimpleI18n.get() signature doesn't match the real i18n.get() signature.

The real i18n.get() accepts **kwargs for string formatting (per src/switchcraft/utils/i18n.py:178-205), but the fallback only accepts key and default. If any code in the crash path calls i18n.get(key, kwarg=value), it will fail.

Suggested fix
 class SimpleI18n:
-    def get(self, key, default=None):
+    def get(self, key, default=None, **kwargs):
         """
         Always return the provided default value.
         
         Parameters:
             key: The lookup key (ignored by this implementation).
             default: The value to return.
+            **kwargs: Ignored formatting arguments.
         
         Returns:
             The provided `default` value.
         """
         return default
📝 Committable suggestion

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

Suggested change
class SimpleI18n:
def get(self, key, default=None):
"""
Always return the provided default value.
Parameters:
key: The lookup key (ignored by this implementation).
default: The value to return.
Returns:
The provided `default` value.
"""
return default
i18n = SimpleI18n()
class SimpleI18n:
def get(self, key, default=None, **kwargs):
"""
Always return the provided default value.
Parameters:
key: The lookup key (ignored by this implementation).
default: The value to return.
**kwargs: Ignored formatting arguments.
Returns:
The provided `default` value.
"""
return default
i18n = SimpleI18n()
🤖 Prompt for AI Agents
In `@src/switchcraft/modern_main.py` around lines 99 - 112, The fallback
SimpleI18n.get signature must match the real i18n.get; change SimpleI18n.get to
accept arbitrary keyword args (def get(self, key, default=None, **kwargs)) and
handle formatting: if default is a string and kwargs are provided, return
default.format(**kwargs), otherwise return default, ensuring callers that pass
i18n.get(key, kw=value) won't raise.

Comment on lines +212 to +257
# --- Handle Command Line Arguments FIRST ---
import sys

# Check for help/version flags (before UI initialization)
if "--help" in sys.argv or "-h" in sys.argv or "/?" in sys.argv:
print("SwitchCraft - Packaging Assistant for IT Professionals")
print("\nUsage: SwitchCraft.exe [OPTIONS]")
print("\nOptions:")
print(" --help, -h, /? Show this help message")
print(" --version, -v Show version information")
print(" --wizard Open Packaging Wizard on startup")
print(" --analyzer, --all-in-one Open Installer Analyzer on startup")
print(" --factory-reset Delete all user data and settings (requires confirmation)")
print(" --protocol <URL> Handle protocol URL (switchcraft://...)")
print(" --silent Silent mode (minimize UI, auto-accept prompts)")
print("\nExamples:")
print(" SwitchCraft.exe --wizard")
print(" SwitchCraft.exe --analyzer")
print(" SwitchCraft.exe --factory-reset")
print(" SwitchCraft.exe switchcraft://analyzer")
sys.exit(0)

if "--version" in sys.argv or "-v" in sys.argv:
try:
from switchcraft import __version__
print(f"SwitchCraft v{__version__}")
except ImportError:
print("SwitchCraft (version unknown)")
sys.exit(0)

# Handle factory reset (before UI initialization)
if "--factory-reset" in sys.argv:
try:
from switchcraft.utils.config import SwitchCraftConfig
print("WARNING: This will delete ALL user data, settings, and secrets.")
print("Are you sure? (Type 'yes' to confirm)")
confirmation = input("> ")
if confirmation.strip().lower() == "yes":
SwitchCraftConfig.delete_all_application_data()
print("Factory reset complete.")
else:
print("Aborted.")
sys.exit(0)
except Exception as e:
print(f"Factory reset failed: {e}")
sys.exit(1)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

CLI argument handling inside Flet callback is problematic.

The main(page) function is called by ft.app(target=main), so by this point Flet is already running. Calling sys.exit(0) here may not cleanly exit, and input() on line 248 will block the Flet event loop, potentially freezing the application or causing undefined behavior.

These CLI flags should be handled in the if __name__ == "__main__": block before calling ft.app().

Suggested restructuring
 if __name__ == "__main__":
+    # Handle CLI args BEFORE starting Flet
+    if "--help" in sys.argv or "-h" in sys.argv or "/?" in sys.argv:
+        print("SwitchCraft - Packaging Assistant for IT Professionals")
+        print("\nUsage: SwitchCraft.exe [OPTIONS]")
+        # ... rest of help text ...
+        sys.exit(0)
+    
+    if "--version" in sys.argv or "-v" in sys.argv:
+        try:
+            from switchcraft import __version__
+            print(f"SwitchCraft v{__version__}")
+        except ImportError:
+            print("SwitchCraft (version unknown)")
+        sys.exit(0)
+    
+    if "--factory-reset" in sys.argv:
+        # Handle factory reset here
+        sys.exit(0)
+    
     start_splash()
+    ft.app(target=main, assets_dir="assets")

Then remove lines 212-257 from inside main().

🤖 Prompt for AI Agents
In `@src/switchcraft/modern_main.py` around lines 212 - 257, The CLI argument
handling (help/version/factory-reset) is currently inside main(page) which is
invoked by ft.app(target=main), so calling sys.exit() and using blocking input()
will interfere with Flet's event loop; move all that logic out of main and into
the script's startup path (the if __name__ == "__main__": block) so it runs
before ft.app() is called. Specifically, remove the block that checks sys.argv
(the help/version/factory-reset handling that uses input(),
SwitchCraftConfig.delete_all_application_data(), and sys.exit()) from main and
place equivalent pre-checks in the __main__ section, preserving behavior for
printing help/version and performing the factory reset confirmation, then call
ft.app(target=main) only after those checks complete.

@github-actions github-actions bot merged commit 9fe017d into main Jan 15, 2026
11 checks passed
@github-actions github-actions bot deleted the general-improvements-and-fixes branch January 15, 2026 18:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend ci-cd documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants