Skip to content

Many new features and UX improvements - #30

Merged
github-actions[bot] merged 4 commits into
mainfrom
new-features
Jan 13, 2026
Merged

github-actions[bot] merged 4 commits into
mainfrom
new-features

Conversation

@FaserF

@FaserF FaserF commented Jan 13, 2026 •

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Community Database for sharing installer switches with automatic lookup
    • Dashboard with analytics and recent activity
    • End-to-end Packaging Wizard and macOS installer script generator
    • Detection Tester for Intune rules, Library, Project Stacks, Addon & Script upload UIs
    • Enhanced Intune integration (script/app upload, group management)
    • In-app notifications with read/unread management
  • Documentation

    • README and FEATURES updated with contribution guide and new features
  • Tests

    • Expanded modern GUI import coverage

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

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

coderabbitai Bot commented Jan 13, 2026 •

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Adds community-contributed switch submission tooling (issue template, processing workflow, DB, and CLI script), integrates CommunityDB lookups into analysis, expands the modern GUI with many new views and UI features, refactors Addon and Notification services, and extends Intune service APIs and model fields.

Changes

Cohort / File(s) Summary
GitHub Automation
\.github/ISSUE_TEMPLATE/add_switch.yml`, `.github/workflows/process_switch_issue.yml``
New issue form for submitting silent installer switches and a workflow that runs scripts/process_issue_ops.py to parse submissions and create PRs.
Community Database & Processing
\src/switchcraft/data/community/switches.json`, `src/switchcraft/services/community_db_service.py`, `scripts/process_issue_ops.py``
Introduces a JSON-backed community switches DB, service for hash/name lookups, and a script to parse issue forms, validate/deduplicate, and append entries.
Analysis & Model Updates
\src/switchcraft/analyzers/msi.py`, `src/switchcraft/models.py`, `src/switchcraft/controllers/analysis_controller.py``
MSI analyzer now captures product_code; InstallerInfo gains product_code and install_path; AnalysisController performs a Community DB lookup (phase 3.5) and adds community_match flag and potential install_switches population.
Modern App Core & Home View
\src/switchcraft/gui_modern/app.py`, `src/switchcraft/gui_modern/views/home_view.py`, `src/switchcraft/gui_modern/controls/skeleton.py``
App integrates NotificationService and AddonService, adds notification UI, drag/drop behavior, fade transitions; Home view refactored to class-based cards; new SkeletonContainer loading control.
New GUI Views — Packaging / Testing / Dashboard / Library
\src/switchcraft/gui_modern/views/packaging_wizard_view.py`, `src/switchcraft/gui_modern/views/detection_tester_view.py`, `src/switchcraft/gui_modern/views/dashboard_view.py`, `src/switchcraft/gui_modern/views/library_view.py`, `src/switchcraft/gui_modern/views/macos_wizard_view.py``
Adds PackagingWizard (multi-step analyze/package/upload), DetectionTester (local detection checks), Dashboard, Library, and macOS wizard views with significant new UI and asynchronous flows.
New GUI Views — Addons, Scripts, Stacks, Groups
\src/switchcraft/gui_modern/views/addon_manager_view.py`, `src/switchcraft/gui_modern/views/script_upload_view.py`, `src/switchcraft/gui_modern/views/stack_manager_view.py`, `src/switchcraft/gui_modern/views/group_manager_view.py``
Addon manager, script upload, stack manager, and group manager UI components with install/delete, upload, persistence, and Intune group operations.
Views Export & Tests
\src/switchcraft/gui_modern/views/init.py`, `tests/test_modern_gui.py`, `pyproject.toml``
Re-exports many view classes; test updated to dynamically import expanded view list; pytest path adjusted to include src.
Services Refactor & Intune Expansion
\src/switchcraft/services/addon_service.py`, `src/switchcraft/services/notification_service.py`, `src/switchcraft/services/intune_service.py``
AddonService converted to instance-based filesystem-driven API; NotificationService replaced with singleton in-memory store and listener API; IntuneService gains numerous upload and group-related methods (scripts, macOS, mobile LOB, supersedence, group CRUD).
Addon/Universal Resilience & Utilities
\src/switchcraft/analyzers/universal.py`, `src/switchcraft/gui_modern/views/history_view.py`, `src/switchcraft/utils/updater.py`, `src/switchcraft_winget/utils/winget.py`, `src/switchcraft/modern_main.py`, `src/switchcraft/analyzers/msi.py``
Guarded addon import with a stub UniversalAnalyzer fallback; narrowed bare excepts to Exception in a few places; small imports/linters and minor parsing/assignment tweaks.
Documentation
\README.md`, `docs/FEATURES.md``
README and FEATURES updated with Community Database, Packaging Wizard, Dashboard, Project Stacks, Library, and contributing instructions for switches.

Sequence Diagram(s)

sequenceDiagram
    participant User as User / Issue Creator
    participant GitHub as GitHub Issues
    participant Workflow as GitHub Actions
    participant Script as process_issue_ops.py
    participant DB as switches.json
    participant PR as Create PR action

    User->>GitHub: Submit issue using add_switch form
    GitHub->>Workflow: Trigger on issue opened/labeled (automation)
    Workflow->>Workflow: Checkout repo, setup Python
    Workflow->>Script: Run script with issue body path
    Script->>Script: Parse form fields, validate required fields
    Script->>DB: Load existing switches.json
    Script->>DB: Check for duplicates and append new entry
    Script->>Workflow: Exit status indicates success
    Workflow->>PR: Create pull request with changes
    PR->>User: PR created referencing issue
Loading
sequenceDiagram
    participant Analyzer as AnalysisController
    participant MSI as MSI Analyzer
    participant DB as CommunityDBService
    participant Local as switches.json
    participant Result as AnalysisResult

    Analyzer->>MSI: Extract MSI properties from installer
    MSI-->>Analyzer: Return properties (including product_code)
    Analyzer->>DB: Request lookup by file hash or name
    DB->>Local: Read/consult switches.json
    Local-->>DB: Return matching switch entries (if any)
    DB-->>Analyzer: Return community switches or null
    alt Community match found
        Analyzer->>Result: Populate install_switches and set community_match=true
    else No match
        Analyzer->>Result: Leave install_switches empty and community_match=false
    end
    Analyzer-->>Result: Return final AnalysisResult
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰
I hopped in the code with a twitch and a twitch,
Collected your switches, a collaborative stitch,
New dashboards to sparkle, wizards that spin,
Addons and stacks let the craft truly begin,
Merge the PR, nibble carrots — let SwitchCraft win! 🎉

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Many new features and UX improvements' is generic and vague, using non-descriptive terms that do not clearly convey the specific primary changes in the changeset. Consider using a more specific title that highlights the main feature or change, such as 'Add community database integration and GUI enhancements' or 'Introduce community switch submission workflow and notification system'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c8c9425 and 394df89.

📒 Files selected for processing (30)
  • .github/ISSUE_TEMPLATE/add_switch.yml
  • .github/workflows/process_switch_issue.yml
  • README.md
  • pyproject.toml
  • scripts/process_issue_ops.py
  • src/switchcraft/analyzers/universal.py
  • src/switchcraft/controllers/analysis_controller.py
  • src/switchcraft/gui_modern/app.py
  • src/switchcraft/gui_modern/controls/skeleton.py
  • src/switchcraft/gui_modern/views/__init__.py
  • src/switchcraft/gui_modern/views/addon_manager_view.py
  • src/switchcraft/gui_modern/views/dashboard_view.py
  • src/switchcraft/gui_modern/views/detection_tester_view.py
  • src/switchcraft/gui_modern/views/group_manager_view.py
  • src/switchcraft/gui_modern/views/history_view.py
  • src/switchcraft/gui_modern/views/home_view.py
  • src/switchcraft/gui_modern/views/intune_store_view.py
  • src/switchcraft/gui_modern/views/library_view.py
  • src/switchcraft/gui_modern/views/macos_wizard_view.py
  • src/switchcraft/gui_modern/views/packaging_wizard_view.py
  • src/switchcraft/gui_modern/views/script_upload_view.py
  • src/switchcraft/gui_modern/views/stack_manager_view.py
  • src/switchcraft/models.py
  • src/switchcraft/modern_main.py
  • src/switchcraft/services/addon_service.py
  • src/switchcraft/services/community_db_service.py
  • src/switchcraft/services/intune_service.py
  • src/switchcraft/utils/updater.py
  • src/switchcraft_winget/utils/winget.py
  • tests/test_modern_gui.py

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

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 12

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

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/views/home_view.py (1)

59-82: Card navigation indices don't match app.py navigation rail destinations.

The card indices here don't align with the navigation rail in app.py:

  • Line 60: Analyzer card uses index 1, but Analyzer is at index 2 in app.py
  • Line 61: AI Helper uses index 2, but Helper/Generate is at index 3
  • Line 62: Winget uses index 3, but Winget/Apps is at index 1
  • Line 74: Stacks uses index 10, but Stacks is at index 12 in app.py
  • Line 80: Settings uses index 7, but Settings is at index 9

This will cause cards to navigate to wrong tabs.

🤖 Fix all issues with AI agents
In @.github/workflows/process_switch_issue.yml:
- Around line 25-26: The workflow step named "Extract Issue Body" directly
interpolates github.event.issue.body into the shell which allows command
injection; instead, pass the issue body through an environment variable (set
ISSUE_BODY to github.event.issue.body) and write it to issue_body.txt using a
safe, quoted print (e.g., use printf '%s' or similar with the quoted ISSUE_BODY)
so the contents are treated as data, not executable shell commands.

In @src/switchcraft/data/community/switches.json:
- Around line 1-12: CommunityDBService is reading from
data/community_switches.json while process_issue_ops.py writes to
src/switchcraft/data/community/switches.json, causing script-added entries to
never be seen; update either CommunityDBService to read from
"src/switchcraft/data/community/switches.json" or change process_issue_ops.py to
write to "data/community_switches.json" so both use the identical canonical
path, and ensure the write routine in process_issue_ops.py that creates new
entries also sets the contributor field (use issue author if available or a
sensible default like "SwitchCraftBot") to match the schema shown in the seed
file.

In @src/switchcraft/gui_modern/app.py:
- Around line 107-112: The class assigns self.window_event to
self.page.on_window_event but never defines it; add a method named
window_event(self, event) on the same class that matches the GUI framework's
window event handler signature, inspects the incoming event (e.g., close/closing
events), implements the intended behavior (e.g., prompt/confirm close, set
self.page.window_prevent_close = False to allow closing, or call any existing
close/cleanup routines), and ensure any references to
self.page.window_prevent_close or cleanup logic used in setup are invoked from
this method so no AttributeError occurs when a window event fires.

In @src/switchcraft/gui_modern/views/home_view.py:
- Around line 16-26: The lambda in _create_card currently references an
undefined name on_navigate; change it to use the instance attribute
self.on_navigate so the button calls the correct handler. Locate the
ElevatedButton in _create_card and replace the on_click lambda to reference
self.on_navigate (and guard it the same way, e.g., call
self.on_navigate(target_idx) if self.on_navigate else None) so the closure uses
the bound method set in __init__.

In @src/switchcraft/gui_modern/views/macos_wizard_view.py:
- Around line 84-90: The file has mis-indented blocks that will prevent import;
correct the indentation so control blocks align properly (e.g., in method
_generate_script ensure the "if not url or not name:" block and its body lines
(self._show_snack and return) are indented one level inside the method), and fix
similar mis-indentations in other methods referenced (adjust the blocks inside
the methods that contain the snack/show or return statements around the app
name/url handling and any subsequent nested blocks so they are indented
consistently under their def). Locate and fix indentation for the methods named
_generate_script and the nearby methods that contain the snack/return blocks
(ensure all conditional and loop bodies are indented exactly one level under
their respective def or control statement).

In @src/switchcraft/gui_modern/views/packaging_wizard_view.py:
- Around line 1-13: The module is missing an import for subprocess which causes
a NameError when _sign_script calls subprocess.run(); add "import subprocess" to
the top-level imports in this file so _sign_script can invoke subprocess.run()
without error, ensuring the import is placed alongside the other stdlib imports
(e.g., threading, tempfile) to keep imports organized.
- Around line 216-248: The function currently returns early with return
ft.Tabs(...), making subsequent code unreachable and leaving tabs undefined;
replace the early return by assigning that Tabs object to a local variable
(e.g., tabs = ft.Tabs(...)) before creating self.autopilot_btn and then return
the final ft.Column that includes self.mode_radio, tabs, and the autopilot
button wired to self._run_autopilot; ensure you reference the same local_content
and url_content when building the Tabs and keep the ft.Column return as the
single return at the end of the function.

In @src/switchcraft/gui_modern/views/script_upload_view.py:
- Around line 77-90: The credential-check block in _upload_ps_script has extra
leading indentation before the if-body lines causing a syntax/indentation error;
remove the extra space so the two statements under "if not all([tenant, client,
secret]):" (the calls to self._show_snack(...) and the subsequent return) are
indented exactly one level inside the if, and apply the same fix to the
analogous credential-validation block around lines 169-182 (align the
_show_snack and return to the if body).

In @src/switchcraft/models.py:
- Around line 9-13: Remove the duplicate product_version field declaration in
the dataclass (the repeated "product_version: Optional[str] = None") so only one
product_version attribute remains; locate the duplicate occurrence near the
other fields (product_version, manufacturer, product_code, install_path) in the
class in src/switchcraft/models.py and delete the redundant line, leaving a
single product_version definition and verifying no other duplicated field names
exist.

In @src/switchcraft/services/addon_service.py:
- Around line 83-122: The install_addon method currently calls
z.extractall(target) which is vulnerable to Zip Slip; replace the blind extract
with per-member path validation: iterate over each ZipInfo from z.infolist(),
compute the final path by joining target with the member name and resolving it
(use Path(...) .resolve()), and ensure the resolved path starts with
target.resolve() to reject absolute or ../ escapes; only extract members that
pass this check (create parent dirs as needed and write the file), and log/raise
an error if any entry is invalid. Reference install_addon, z.extractall,
z.infolist(), target, and manifest.json when locating the code to change.

In @src/switchcraft/services/intune_service.py:
- Around line 630-648: There are two definitions of search_apps which causes the
later one to override the earlier; consolidate by keeping a single
implementation: either merge the behavior of the direct API caller into the
original search_apps that delegates to list_apps, or rename the second
implementation (e.g., search_apps_direct) and update any call sites that should
use it (packaging_wizard_view.py, intune_store_view.py) to avoid silent
override; ensure the final search_apps signature matches callers, remove the
duplicate definition, and run tests/linters to confirm no remaining references
to the removed symbol.
- Around line 500-604: The upload_mobile_lob_app method dereferences app_info
without a null check; change the signature or start of upload_mobile_lob_app to
ensure app_info is a dict (e.g., app_info = app_info or {}) so all
app_info.get(...) calls in default_info are safe, and implement the merge block
(currently pass) to update default_info with sanitized keys from app_info
(whitelist allowed fields like displayName, description, publisher, productCode,
productVersion, installCommandLine) while removing incompatible keys (e.g., any
keys not in that whitelist); also modify the commit payload for the file commit
in upload_mobile_lob_app so that fileEncryptionInfo is omitted entirely when
unencrypted instead of sending {"fileEncryptionInfo": None} (only include
fileEncryptionInfo when you have a non-null value).
🟠 Major comments (16)
src/switchcraft/gui_modern/views/detection_tester_view.py-6-7 (1)

6-7: Guard Windows-specific imports for cross-platform compatibility.

Top-level imports of winreg and win32api will cause ImportError on non-Windows platforms, preventing the entire module from loading. While this is a Windows-specific tool, graceful handling would improve maintainability.

🐛 Proposed fix with lazy imports or platform check
 import flet as ft
 import logging
 import subprocess
 import os
+import sys
 from pathlib import Path
-import winreg
-import win32api
+
+if sys.platform == "win32":
+    import winreg
+    import win32api
+else:
+    winreg = None
+    win32api = None

 logger = logging.getLogger(__name__)

Then add a check at the start of methods using these modules:

def _check_registry(self, key_path, value_name, expected_value):
    if winreg is None:
        return False, "Registry checks only available on Windows"
    # ... rest of method
src/switchcraft/gui_modern/views/detection_tester_view.py-260-298 (1)

260-298: Ensure temp file cleanup on all exit paths.

If subprocess.run raises an exception, the temp file created at line 261 won't be deleted. Use try/finally to guarantee cleanup.

🐛 Proposed fix
     def _check_script(self, script_content):
         # Run script in temp file
         import tempfile
+        temp_path = None
         try:
             with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix=".ps1") as f:
                 f.write(script_content)
                 temp_path = f.name

             cmd = ["powershell.exe", "-ExecutionPolicy", "Bypass", "-File", temp_path]

             startupinfo = subprocess.STARTUPINFO()
             startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW

             res = subprocess.run(cmd, capture_output=True, text=True, startupinfo=startupinfo)

-            os.remove(temp_path)
-
             stdout = res.stdout.strip()
             stderr = res.stderr.strip()
             exit_code = res.returncode

             if exit_code == 0:
                 if stdout:
                      return True, f"Detected (Exit 0 + Stdout): {stdout[:100]}..."
                 else:
                      return False, "Not Detected (Exit 0 but Empty Stdout)"
             else:
                 return False, f"Not Detected (Exit Code {exit_code}). Err: {stderr[:100]}"

         except Exception as e:
             return False, f"Script Execution Error: {e}"
+        finally:
+            if temp_path and os.path.exists(temp_path):
+                os.remove(temp_path)
scripts/process_issue_ops.py-66-72 (1)

66-72: Exit code 0 on duplicate will create an empty PR.

When a duplicate entry is found, the script exits with code 0, which signals success to GitHub Actions. The workflow will then proceed to create a PR with no changes. Exit with a non-zero code to fail the workflow gracefully, or use GitHub Actions outputs to conditionally skip PR creation.

🐛 Proposed fix
     # Check for duplicate (naive check)
     for entry in db:
         if (entry.get("app_name") == new_entry.get("app_name") and
             entry.get("version") == new_entry.get("version")):
             print("Entry already exists for this version.")
-            # We might want to update it instead? For now, skip.
-            sys.exit(0)
+            sys.exit(1)
scripts/process_issue_ops.py-14-16 (1)

14-16: Regex incorrectly captures subsequent headers when a field is empty.

The pattern (.+?) requires at least one character, causing it to match across empty fields. When a field is truly empty (e.g., ### Application Name\n\n### Version), the regex captures the next header and its value instead—e.g., "Application Name": "### Version\n\n1.0.0" instead of an empty string. This corrupts the parsed data.

The pattern needs to handle optional values. Consider using (.*) with a modified lookahead or restructure the regex to explicitly match empty lines followed by the next ###.

src/switchcraft/gui_modern/app.py-168-194 (1)

168-194: Navigation index comments are inconsistent with actual positions.

The inline comments indicate index numbers that don't match the actual list positions. For example:

  • Line 169: "History" is at index 8, but comment says "# 8 History"
  • Line 173: "Settings" comment says "# 7 Settings" but it's at index 9
  • Lines 175-188: Comments show indices 8, 9, 10, 11, 14, 15, 16 but actual indices are 10-16

This inconsistency will cause navigation bugs when goto_tab or card clicks use hardcoded indices.

src/switchcraft/services/addon_service.py-69-81 (1)

69-81: Dynamic code execution from untrusted addons allows arbitrary code execution.

The load_addon_view method uses importlib to execute arbitrary Python code from addon files. The addon manager UI explicitly allows users to upload custom ZIP files via file picker, and the install_addon method performs no signature verification or integrity checks. While ADDONS.md documents a warning to use only trusted sources, this is insufficient—there are no technical safeguards preventing execution of malicious addon code.

Implement addon signing with signature verification, or add sandboxing to restrict addon capabilities in future iterations. For now, the upload feature should prominently warn users that addons execute with full app privileges.

src/switchcraft/gui_modern/views/group_manager_view.py-10-23 (1)

10-23: Initialize and gate self.token before create/delete (avoid race/AttributeError).
Create/delete assume self.token exists and is valid; users can trigger dialogs before _load_data() finishes or succeeds.

Proposed fix
 class GroupManagerView(ft.Column):
     def __init__(self, page: ft.Page):
@@
         self.filtered_groups = []
+        self.token = None
@@
     def _load_data(self):
@@
         def _bg():
             try:
                 self.token = self.intune_service.authenticate(tenant, client, secret)
@@
             finally:
@@

     def _show_create_dialog(self, e):
+        if not self.token:
+            self._show_snack("Load groups / authenticate first", ft.Colors.RED)
+            return
@@
     def _confirm_delete(self, e):
         if not self.selected_group: return
+        if not self.token:
+            self._show_snack("Load groups / authenticate first", ft.Colors.RED)
+            return

Also applies to: 83-109, 152-184, 185-216

src/switchcraft/gui_modern/views/stack_manager_view.py-69-82 (1)

69-82: Handle JSON read/write failures explicitly (avoid crashing UI callbacks).
Right now _save_stacks() can raise and break the UI, and _load_stacks() silently hides corruption without leaving diagnostics.

Proposed fix
 def _load_stacks(self):
     if not self.stacks_file.exists():
         return {}
     try:
-        with open(self.stacks_file, "r") as f:
-            return json.load(f)
-    except Exception:
-        return {}
+        with open(self.stacks_file, "r", encoding="utf-8") as f:
+            data = json.load(f)
+        # minimal schema validation
+        if not isinstance(data, dict):
+            raise ValueError("stacks.json must be a JSON object")
+        for k, v in data.items():
+            if not isinstance(k, str) or not isinstance(v, list):
+                raise ValueError("stacks.json must map string -> list")
+        return data
+    except Exception as ex:
+        logger.warning("Failed to load stacks DB; starting empty: %s", ex)
+        return {}

 def _save_stacks(self):
     self.stacks_file.parent.mkdir(parents=True, exist_ok=True)
-    with open(self.stacks_file, "w") as f:
-        json.dump(self.stacks, f, indent=4)
+    try:
+        with open(self.stacks_file, "w", encoding="utf-8") as f:
+            json.dump(self.stacks, f, indent=4)
+    except Exception as ex:
+        logger.error("Failed to save stacks DB: %s", ex)
+        raise

Also applies to: 83-86

src/switchcraft/services/notification_service.py-8-70 (1)

8-70: Add synchronization + make timestamp serializable (threaded UI elsewhere makes this risky).
This service is very likely to be called across threads in this PR, but it has no locking; also datetime.now() is timezone-naive and not JSON-serializable.

Proposed fix
 import logging
 import uuid
-from datetime import datetime
+from datetime import datetime, timezone
+import threading
 from typing import List, Dict, Callable
@@
 class NotificationService:
     _instance = None
     _listeners: List[Callable] = []
@@
     def __new__(cls):
         if cls._instance is None:
             cls._instance = super(NotificationService, cls).__new__(cls)
             cls._instance.notifications = []
+            cls._instance._lock = threading.RLock()
         return cls._instance

-    def add_notification(self, title: str, message: str, type: str = "info"):
+    def add_notification(self, title: str, message: str, type: str = "info"):
@@
-        notif = {
+        notif = {
             "id": str(uuid.uuid4()),
             "title": title,
             "message": message,
             "type": type,
-            "timestamp": datetime.now(),
+            "timestamp": datetime.now(timezone.utc).isoformat(),
             "read": False
         }
-        self.notifications.insert(0, notif)
-        self._notify_listeners()
+        with self._lock:
+            self.notifications.insert(0, notif)
+        self._notify_listeners()
         return notif
@@
     def _notify_listeners(self):
-        for callback in self._listeners:
+        # Copy to avoid issues if listeners are added/removed during callbacks
+        for callback in list(self._listeners):
             try:
                 callback()
             except Exception as e:
                 logger.error(f"Error in notification listener: {e}")
src/switchcraft/services/community_db_service.py-39-46 (1)

39-46: Add error handling to _get_hash() to make hash lookup best-effort (graceful fallback on unreadable files).

The method can raise exceptions on file access errors, preventing the fallback to name-based lookup in analysis_controller.py. Return None on hash calculation failure so the caller can proceed to alternative strategies.

Proposed fix
 def _get_hash(self, filepath):
-    h = hashlib.sha256()
-    with open(filepath, "rb") as f:
-        while chunk := f.read(8192):
-            h.update(chunk)
-    return h.hexdigest()
+    try:
+        h = hashlib.sha256()
+        with open(filepath, "rb") as f:
+            while chunk := f.read(8192):
+                h.update(chunk)
+        return h.hexdigest()
+    except Exception as ex:
+        logger.debug("Failed to hash %s: %s", filepath, ex)
+        return None
 def get_switches_by_hash(self, file_path):
     """Calculate hash and lookup."""
     if not Path(file_path).exists():
         return None
 
     sha256 = self._get_hash(file_path)
+    if not sha256:
+        return None
     return self.db.get("hash_map", {}).get(sha256)

Also applies to: 56-61

src/switchcraft/gui_modern/views/script_upload_view.py-95-117 (1)

95-117: Refactor to use Flet's thread-safe UI update pattern.

Both upload flows (lines 95-117 and 187-212) directly mutate controls and call self.update() from a raw threading.Thread, which violates Flet's threading guidelines. Flet documentation explicitly recommends either:

  1. Async tasks (preferred): Use async/await with page.run_task() for background I/O
  2. Thread-safe execution: Use page.run_thread() and marshal UI updates back via page.run_task() to avoid concurrent page.update() calls

Calling update() directly from arbitrary threads can cause intermittent errors. Refactor both locations to use one of the documented patterns.

src/switchcraft/gui_modern/views/addon_manager_view.py-63-78 (1)

63-78: Use page.run_task() or page.run_thread() instead of plain threading.Thread() for UI updates.

Flet enforces a single-threaded async UI model—mutating control state (e.g., DataTable.rows) and calling update() from arbitrary worker threads is unsafe and can fail intermittently. Both _load_data() and _update_table() violate this constraint.

Move background work to a task scheduled via the page's event loop so that all control mutations and update() calls happen on the UI thread. Alternatively, collect results in the worker, then use page.run_task() to perform the mutation and update on the main context.

This pattern also appears in _install() (lines 109–118).

src/switchcraft/gui_modern/views/group_manager_view.py-95-108 (1)

95-108: Refactor to marshal UI updates from background thread to page event loop.

The code calls _update_table() and self.update() from a background thread spawned with threading.Thread(). Flet requires all UI mutations (including DataTable.rows modifications and Control.update() calls) to occur on the page event loop. Direct thread-based updates can cause race conditions and intermittent errors.

Use page.run_task() or page.run_thread() with proper result marshalling instead. Pattern: background work updates the data model; schedule the UI update on the page via page.run_task() so that _update_table() and update() execute in the page context.

Also applies to: 110-126

src/switchcraft/gui_modern/views/macos_wizard_view.py-147-181 (1)

147-181: Replace daemon thread with Flet's page.run_thread() and marshal UI updates back to the page event loop.

The code directly mutates control properties and calls self.update() from a background thread, which is unsafe in Flet. Flet's UI state is managed on the page event loop; arbitrary thread access causes intermittent errors and crashes.

Use page.run_thread() for the blocking Intune operation and schedule UI updates back to the page via page.run_task():

async def _upload_bg():
    try:
        token = await page.run_thread(
            self.intune_service.authenticate,
            tenant, client, secret
        )
        await page.run_thread(
            self.intune_service.upload_macos_shell_script,
            token,
            f"Install {self.app_name.value}",
            f"Auto-generated installer for {self.app_name.value}",
            self.preview_field.value
        )
        self.status_txt.value = "Success! Script Uploaded to MacOS > Shell Scripts."
        self.status_txt.color = ft.Colors.GREEN
    except Exception as ex:
        self.status_txt.value = f"Error: {ex}"
        self.status_txt.color = ft.Colors.RED
    finally:
        self.upload_btn.disabled = False
        self.update()

# In _upload_to_intune:
self.status_txt.value = "Uploading..."
self.upload_btn.disabled = True
self.update()
page.run_task(_upload_bg)
src/switchcraft/services/intune_service.py-605-629 (1)

605-629: Payload has undocumented field and is missing required property; fix before shipping.

The endpoint and @odata.type are correct per Microsoft Graph API, but the payload has issues:

  • "isSupersedence": True is not a documented field in the Graph API spec and will likely be rejected or ignored.
  • "targetType" field is missing; the API expects this to be set to "parent" or "child".

Remove isSupersedence and add "targetType": "parent" (or "child" depending on your relationship model).

Also improve exception handling by using raise instead of raise e to preserve the original traceback:

Fix traceback preservation
         except Exception as e:
             logger.error(f"Failed to add supersedence: {e}")
-            raise e
+            raise
src/switchcraft/services/intune_service.py-663-688 (1)

663-688: Major: Mutable default arg + payload configuration breaks M365 group creation.

  1. Mutable default argument (group_types=[]): While the current code doesn't mutate it, this is a Python anti-pattern; use group_types=None with a default assignment instead.

  2. API payload inconsistency for M365 groups: The docstring documents group_types=["Unified"] for M365 groups, but the code always sets mailEnabled=False and securityEnabled=True regardless of the group type. Per Microsoft Graph API requirements, M365 (Unified) groups require mailEnabled=true and securityEnabled=false. The current payload will fail or produce incorrect results when called with ["Unified"].

Fix: Conditionally set mailEnabled and securityEnabled based on whether "Unified" is in group_types:

Proposed fix
-    def create_group(self, token, name, description, group_types=[]):
+    def create_group(self, token, name, description, group_types=None):
         """
         Creates a new group.
         group_types: ["Unified"] for M365, [] for Security.
         """
+        group_types = group_types or []
+        is_m365 = "Unified" in group_types
+
         headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
         url = "https://graph.microsoft.com/v1.0/groups"
 
         payload = {
             "displayName": name,
             "description": description,
-            "mailEnabled": False,
-            "securityEnabled": True,
+            "mailEnabled": True if is_m365 else False,
+            "securityEnabled": False if is_m365 else True,
             "mailNickname": name.replace(" ", "").lower(),
             "groupTypes": group_types
         }
🟡 Minor comments (10)
src/switchcraft/gui_modern/views/detection_tester_view.py-236-247 (1)

236-247: Version comparison may behave unexpectedly with different segment counts.

List comparison treats [1, 0] < [1, 0, 0], so "1.0" >= "1.0.0.0" would return False. Consider normalizing version lists to equal length by padding with zeros.

💡 Suggested improvement
             def parse_ver(v_str):
-                return [int(x) for x in v_str.split('.')]
+                parts = [int(x) for x in v_str.split('.')]
+                # Normalize to 4 segments for consistent comparison
+                while len(parts) < 4:
+                    parts.append(0)
+                return parts
src/switchcraft/controllers/analysis_controller.py-137-140 (1)

137-140: Type mismatch: get_switches_by_name expects a filename string, not a Path.

Looking at CommunityDBService.get_switches_by_name(), it expects a filename string and uses Path(filename).stem.lower() internally. Passing a Path object works due to implicit string conversion, but get_switches_by_hash already handles the path, so passing path (a Path object) to get_switches_by_name will use the full path as the stem, not just the filename.

🐛 Proposed fix
                 db_switches = db.get_switches_by_hash(path)
                 if not db_switches:
                     # Fallback to name
-                    db_switches = db.get_switches_by_name(path)
+                    db_switches = db.get_switches_by_name(path.name)
src/switchcraft/gui_modern/views/library_view.py-36-45 (1)

36-45: Status filter dropdown is wired but filtering logic is disabled.

The filter_dd dropdown is visible and wired to _on_filter_change, which updates self.status_filter, but the actual filtering logic in _refresh_grid (lines 84-88) is commented out. This creates a confusing UX where the dropdown appears functional but does nothing.

Consider either implementing the status filtering or hiding the dropdown until it's ready.

Also applies to: 84-88

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

204-204: Typo: "Download form Web" should be "Download from Web".

📝 Proposed fix
-                ft.Text("Download form Web", size=24, weight=ft.FontWeight.BOLD),
+                ft.Text("Download from Web", size=24, weight=ft.FontWeight.BOLD),
src/switchcraft/gui_modern/views/packaging_wizard_view.py-510-515 (1)

510-515: Duplicate self.txt_desc in UI layout.

self.txt_desc is added twice to the Column controls (lines 513-514), causing the description field to render twice.

🧹 Proposed fix
             self.txt_app_name,
             self.txt_publisher,
             self.txt_desc,
-            self.txt_desc,
             ft.Divider(),
src/switchcraft/gui_modern/views/packaging_wizard_view.py-28-33 (1)

28-33: Remove duplicate assignments.

self.package_path (lines 28-29) and self.signing_cert (lines 31-32) are each assigned twice consecutively. These appear to be copy-paste errors.

🧹 Proposed fix
         self.generated_script_path = None
         self.package_path = None
-        self.package_path = None
         self.upload_info = {}
         self.signing_cert = SwitchCraftConfig.get_value("SigningCertThumbprint")
-        self.signing_cert = SwitchCraftConfig.get_value("SigningCertThumbprint")
         self.packaging_mode = "win32" # win32 or lob
src/switchcraft/gui_modern/views/packaging_wizard_view.py-609-612 (1)

609-612: Remove duplicate stub method definition.

_run_upload is defined twice. This stub definition (lines 609-611) is immediately shadowed by the full implementation below. Remove the stub to avoid confusion.

🧹 Proposed fix
-    def _run_upload(self, e):
-        # Override to include supersedence logic at the end
-        pass # Replaced by below logic via tool
-
     def _run_upload(self, e):
src/switchcraft/gui_modern/views/addon_manager_view.py-120-138 (1)

120-138: Handle “delete returned False” as a user-visible failure.
AddonService.delete_addon() can return False; the UI always shows success.

Proposed fix
 def delete(e):
     aid = self.selected_addon['id']
     def _bg():
         try:
-            self.addon_service.delete_addon(aid)
-            self._show_snack("Addon deleted.", ft.Colors.GREEN)
+            ok = self.addon_service.delete_addon(aid)
+            if not ok:
+                self._show_snack("Addon not found (nothing deleted).", ft.Colors.RED)
+                return
+            self._show_snack("Addon deleted.", ft.Colors.GREEN)
             self.selected_addon = None
             self.app_page.close_dialog()
             self._load_data()
src/switchcraft/gui_modern/views/stack_manager_view.py-124-139 (1)

124-139: Guard _select_stack() against stale/invalid names.
If the stack name is missing (e.g., race between delete/select or corrupted JSON), self.stacks[name] will throw.

Proposed fix
 def _select_stack(self, name):
     self.current_stack = name
-    items = self.stacks[name]
+    items = self.stacks.get(name)
+    if items is None:
+        self.current_stack = None
+        self.stack_content_list.controls.clear()
+        self._show_snack("Stack not found", ft.Colors.RED)
+        self.update()
+        return
src/switchcraft/services/intune_service.py-689-700 (1)

689-700: Change raise e to bare raise for cleaner exception re-raising. In Python 3, using bare raise in an except block re-raises the exception while preserving the original traceback. This is the preferred style over raise e.

Proposed fix
         except Exception as e:
             logger.error(f"Failed to delete group: {e}")
-            raise e
+            raise
🧹 Nitpick comments (17)
README.md (1)

66-66: Consider using an absolute GitHub URL.

The relative link ../../issues may not render correctly in all contexts where the README is displayed (e.g., documentation sites, package registries, or mirrors).

🔗 Proposed fix
-1. Go to the [Issues](../../issues) tab.
+1. Go to the [Issues](https://github.com/FaserF/SwitchCraft/issues) tab.
src/switchcraft/gui_modern/controls/skeleton.py (1)

46-51: Consider using a single persistent thread instead of recursive spawning.

The current pattern spawns a new daemon thread every 0.8 seconds. While functional, this creates many short-lived threads over the component's lifetime. A single persistent loop thread would be more efficient.

♻️ Proposed refactor using a single thread
     def did_mount(self):
-        self._animate()
+        threading.Thread(target=self._animation_loop, daemon=True).start()

     def will_unmount(self):
         self.aborted = True

-    def _animate(self):
+    def _animation_loop(self):
+        while not self.aborted:
+            self._toggle_opacity()
+            time.sleep(0.8)
+
+    def _toggle_opacity(self):
         if self.aborted:
             return
-
-        # Simple pulsing opacity animation
         self.opacity = 0.5 if self.opacity == 1.0 else 1.0
         self.update()
-
-        # Schedule next frame
-        if not self.aborted:
-            # ... comments ...
-
-            def _loop():
-                time.sleep(0.8)
-                if not self.aborted:
-                    self._animate()
-
-            threading.Thread(target=_loop, daemon=True).start()
tests/test_modern_gui.py (1)

42-61: The dynamic import approach is cleaner; consider improving error handling.

The refactoring to use importlib is a good improvement for maintainability. However, importlib.import_module() raises ImportError on failure rather than returning None, so the assertion assert module is not None provides no additional value.

♻️ Suggested improvement with better error reporting
 def test_view_imports():
     """Ensure all view modules can be imported."""
     view_names = [
         "home_view",
         "analyzer_view",
         "helper_view",
         "winget_view",
         "intune_view",
         "intune_store_view",
         "history_view",
         "settings_view",
         "packaging_wizard_view",
         "detection_tester_view",
         "stack_manager_view",
         "dashboard_view",
         "library_view",
     ]
     for view_name in view_names:
-        module = importlib.import_module(f"switchcraft.gui_modern.views.{view_name}")
-        assert module is not None
+        try:
+            module = importlib.import_module(f"switchcraft.gui_modern.views.{view_name}")
+        except ImportError as e:
+            pytest.fail(f"Failed to import {view_name}: {e}")
src/switchcraft/controllers/analysis_controller.py (2)

142-151: Unfinished merge logic leaves potential enhancements undocumented.

The else branch contains comments about potential merge strategies but does nothing. Consider either:

  1. Implementing a merge/comparison strategy
  2. Removing the else block entirely
  3. Adding a TODO comment or logging that alternatives were found
♻️ Option: Log when community alternatives exist
                 if db_switches:
                     if not info.install_switches:
                         info.install_switches = db_switches
                         community_match = True
                     else:
-                        # Merge? Or just flag that we found alternatives?
-                        # For now, let's append if completely different?
-                        # Simpler: If analyzer found nothing, use DB.
-                        # If analyzer found something, maybe trust analyzer?
-                        pass
+                        # Analyzer found switches; log community alternatives for reference
+                        logger.debug(f"Community DB has alternative switches: {db_switches}")

131-135: Consider caching CommunityDBService instance.

CommunityDBService is instantiated on every analysis call. If this involves file I/O (loading JSON), it could be cached as an instance variable in __init__ or as a module-level singleton.

♻️ Proposed refactor
 class AnalysisController:
     """
     Shared controller for handling the analysis workflow.
     Used by both Classic (Tkinter) and Modern (Flet) UIs.
     """

     def __init__(self, ai_service=None):
         self.ai_service = ai_service
+        self._community_db = CommunityDBService()

Then in analyze_file:

-            db = CommunityDBService()
+            db = self._community_db
scripts/process_issue_ops.py (1)

63-64: Use actual timestamp and add contributor field.

The added_at field uses a placeholder string. Additionally, the example entry in switches.json includes a contributor field that this script doesn't populate.

♻️ Proposed fix
+from datetime import datetime, timezone
+
 def update_db(new_entry):
     # ... existing code ...

     # Add metadata
-    new_entry["added_at"] = "pending-merge" # In real action, could use current time
+    new_entry["added_at"] = datetime.now(timezone.utc).isoformat()
+    new_entry["contributor"] = os.environ.get("GITHUB_ACTOR", "unknown")

In the workflow, ensure GITHUB_ACTOR is available (it's set by default in GitHub Actions).

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

1-29: LGTM - Clean dashboard view initialization.

The class structure and mock data setup are appropriate for a prototype dashboard. The layout composition with stats row and a two-column chart/activity section is well-organized.

Note: logger is imported (line 4) but not used anywhere in this file.

.github/ISSUE_TEMPLATE/add_switch.yml (1)

1-76: Issue template is well-structured.

The template covers all necessary fields for switch submissions and aligns well with the automation workflow that processes these issues. The dropdown for installer types provides good coverage of common frameworks.

Minor observation: The notes textarea (lines 72-76) is missing an explicit validations block. While it defaults to required: false, adding it explicitly would maintain consistency with other fields.

Optional: Add explicit validation for notes field
   - type: textarea
     id: notes
     attributes:
       label: Additional Notes
       description: Any other details or caveats?
+    validations:
+      required: false
src/switchcraft/gui_modern/views/library_view.py (2)

103-106: Avoid bare except: clause.

Using a bare except: catches all exceptions including KeyboardInterrupt and SystemExit, which can hide bugs and make debugging difficult. Catch specific exceptions instead.

Suggested fix
         try:
             dt = datetime.fromisoformat(ts).strftime("%Y-%m-%d")
-        except:
+        except (ValueError, TypeError):
             dt = ts

119-124: Complex inline lambda with side effects is hard to read.

The hover handler uses a chained setattr(...) or e.control.update() pattern which relies on setattr returning None to execute update(). While functional, this is non-idiomatic and harder to maintain.

Suggested refactor
-            on_hover=lambda e: setattr(e.control, "bgcolor", ft.Colors.with_opacity(0.2, ft.Colors.WHITE) if e.data == "true" else ft.Colors.with_opacity(0.1, ft.Colors.WHITE)) or e.control.update()
+            on_hover=self._on_tile_hover
         )
+
+    def _on_tile_hover(self, e):
+        e.control.bgcolor = (
+            ft.Colors.with_opacity(0.2, ft.Colors.WHITE)
+            if e.data == "true"
+            else ft.Colors.with_opacity(0.1, ft.Colors.WHITE)
+        )
+        e.control.update()
src/switchcraft/gui_modern/app.py (2)

92-93: Duplicate import of time module.

The time module is already imported at line 10. This redundant import should be removed.

Suggested fix
         # Perform deferred UI building.
         # User requested visible loading screen. Since Flet execution here is linear during init,
         # we add a small sleep to ensure the "Loading..." ring is perceived.
-        import time
         time.sleep(1.5)

511-514: Lambda captures drawer variable correctly but clear_all() and close() in list may have unintended behavior.

The expression lambda _: [self.notification_service.clear_all(), self.page.close(drawer)] uses a list to execute multiple statements. While this works, it's unconventional and the return value (a list) is discarded. Consider using a proper handler method.

Suggested refactor
+        def clear_and_close(_):
+            self.notification_service.clear_all()
+            self.page.close(drawer)
+
         drawer = ft.NavigationDrawer(
             controls=[
                 ft.Container(height=12),
                 ft.Row([
                     ft.Text("Notifications", size=20, weight=ft.FontWeight.BOLD),
-                    ft.TextButton("Clear All", on_click=lambda _: [self.notification_service.clear_all(), self.page.close(drawer)])
+                    ft.TextButton("Clear All", on_click=clear_and_close)
                 ], alignment=ft.MainAxisAlignment.SPACE_BETWEEN, run_spacing=10),
src/switchcraft/services/addon_service.py (1)

57-57: Bare except: pass silently swallows all errors.

Lines 57 and 134 use bare except: pass which hides all exceptions including critical ones. This makes debugging difficult and can mask serious issues.

Suggested fix for line 57
                  try:
                      with open(d / "manifest.json") as f:
                          data = json.load(f)
                          if data.get("id") == addon_id:
                              addon_path = d
                              manifest_data = data
                              break
-                 except: pass
+                 except (OSError, json.JSONDecodeError) as e:
+                     logger.warning(f"Skipping invalid addon at {d}: {e}")

Also applies to: 134-134

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

270-271: Add timeout to prevent indefinite hangs.

requests.get() has no timeout, which can block the thread indefinitely if the server doesn't respond.

♻️ Proposed fix
-                with requests.get(url, stream=True) as r:
+                with requests.get(url, stream=True, timeout=60) as r:
src/switchcraft/services/intune_service.py (3)

404-433: Make script base64 encoding robust to bytes input + validate run_as_account values.
If UI passes bytes (or already-base64), .encode('utf-8') will throw / double-encode.

Proposed hardening
-        # Script content must be base64 encoded
-        encoded_script = base64.b64encode(script_content.encode('utf-8')).decode('utf-8')
+        # Script content must be base64 encoded (Graph expects base64 string)
+        if isinstance(script_content, bytes):
+            raw = script_content
+        else:
+            raw = str(script_content).encode("utf-8")
+        encoded_script = base64.b64encode(raw).decode("ascii")
+
+        if run_as_account not in {"system", "user"}:
+            raise ValueError("run_as_account must be 'system' or 'user'")

434-466: Same base64 robustness needed for remediation scripts; avoid raise e.
Prefer raise to preserve original traceback context.

Proposed tweak
-        enc_detection = base64.b64encode(detection_content.encode('utf-8')).decode('utf-8')
-        enc_remediation = base64.b64encode(remediation_content.encode('utf-8')).decode('utf-8')
+        det_raw = detection_content if isinstance(detection_content, bytes) else str(detection_content).encode("utf-8")
+        rem_raw = remediation_content if isinstance(remediation_content, bytes) else str(remediation_content).encode("utf-8")
+        enc_detection = base64.b64encode(det_raw).decode("ascii")
+        enc_remediation = base64.b64encode(rem_raw).decode("ascii")
@@
-        except Exception as e:
+        except Exception as e:
             # Check for license errors common with Remediations
             logger.error(f"Failed to upload Remediation: {e}")
-            raise e
+            raise

649-662: list_groups: build filters via params and handle pagination.
String-concatenating $filter is brittle (escaping) and Graph often paginates with @odata.nextLink.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c4f7da1 and c8c9425.

📒 Files selected for processing (27)
  • .github/ISSUE_TEMPLATE/add_switch.yml
  • .github/workflows/process_switch_issue.yml
  • README.md
  • docs/FEATURES.md
  • scripts/process_issue_ops.py
  • src/switchcraft/analyzers/msi.py
  • src/switchcraft/controllers/analysis_controller.py
  • src/switchcraft/data/community/switches.json
  • src/switchcraft/gui_modern/app.py
  • src/switchcraft/gui_modern/controls/skeleton.py
  • src/switchcraft/gui_modern/views/__init__.py
  • src/switchcraft/gui_modern/views/addon_manager_view.py
  • src/switchcraft/gui_modern/views/dashboard_view.py
  • src/switchcraft/gui_modern/views/detection_tester_view.py
  • src/switchcraft/gui_modern/views/group_manager_view.py
  • src/switchcraft/gui_modern/views/home_view.py
  • src/switchcraft/gui_modern/views/library_view.py
  • src/switchcraft/gui_modern/views/macos_wizard_view.py
  • src/switchcraft/gui_modern/views/packaging_wizard_view.py
  • src/switchcraft/gui_modern/views/script_upload_view.py
  • src/switchcraft/gui_modern/views/stack_manager_view.py
  • src/switchcraft/models.py
  • src/switchcraft/services/addon_service.py
  • src/switchcraft/services/community_db_service.py
  • src/switchcraft/services/intune_service.py
  • src/switchcraft/services/notification_service.py
  • tests/test_modern_gui.py
🧰 Additional context used
🧬 Code graph analysis (9)
src/switchcraft/gui_modern/views/detection_tester_view.py (3)
tests/test_modern_gui.py (1)
  • update (32-33)
src/switchcraft/gui_modern/views/addon_manager_view.py (1)
  • delete (126-137)
src/switchcraft/gui_modern/views/group_manager_view.py (1)
  • delete (191-202)
src/switchcraft/gui_modern/views/home_view.py (2)
src/switchcraft/gui_modern/views/macos_wizard_view.py (1)
  • _build_content (28-82)
src/switchcraft/utils/i18n.py (1)
  • get (143-170)
src/switchcraft/gui_modern/views/dashboard_view.py (1)
src/switchcraft/debug_views.py (2)
  • height (30-30)
  • width (28-28)
src/switchcraft/gui_modern/views/addon_manager_view.py (2)
src/switchcraft/services/addon_service.py (3)
  • list_addons (18-37)
  • install_addon (83-122)
  • delete_addon (124-135)
src/switchcraft/gui_modern/utils/file_picker_helper.py (2)
  • FilePickerHelper (5-85)
  • pick_file (12-38)
src/switchcraft/gui_modern/views/library_view.py (1)
src/switchcraft/services/history_service.py (3)
  • HistoryService (9-63)
  • get_history (22-35)
  • clear (55-56)
src/switchcraft/controllers/analysis_controller.py (2)
src/switchcraft/services/community_db_service.py (3)
  • CommunityDBService (9-61)
  • get_switches_by_hash (39-45)
  • get_switches_by_name (47-54)
src/switchcraft/models.py (1)
  • InstallerInfo (5-35)
src/switchcraft/gui_modern/views/macos_wizard_view.py (2)
src/switchcraft/services/intune_service.py (3)
  • IntuneService (16-700)
  • authenticate (147-164)
  • upload_macos_shell_script (467-498)
src/switchcraft/utils/config.py (2)
  • get_value (31-88)
  • get_secure_value (219-273)
src/switchcraft/services/intune_service.py (1)
src/switchcraft/gui_modern/views/group_manager_view.py (1)
  • delete (191-202)
src/switchcraft/gui_modern/views/__init__.py (12)
src/switchcraft/gui_modern/views/home_view.py (1)
  • ModernHomeView (4-84)
src/switchcraft/gui_modern/views/analyzer_view.py (1)
  • ModernAnalyzerView (21-693)
src/switchcraft/gui_modern/views/helper_view.py (1)
  • ModernHelperView (9-112)
src/switchcraft/gui_modern/views/winget_view.py (1)
  • ModernWingetView (12-616)
src/switchcraft/gui_modern/views/intune_view.py (1)
  • ModernIntuneView (10-180)
src/switchcraft/gui_modern/views/intune_store_view.py (1)
  • ModernIntuneStoreView (9-188)
src/switchcraft/gui_modern/views/history_view.py (1)
  • ModernHistoryView (7-95)
src/switchcraft/gui_modern/views/settings_view.py (1)
  • ModernSettingsView (14-931)
src/switchcraft/gui_modern/views/packaging_wizard_view.py (1)
  • PackagingWizardView (16-860)
src/switchcraft/gui_modern/views/detection_tester_view.py (1)
  • DetectionTesterView (12-313)
src/switchcraft/gui_modern/views/dashboard_view.py (1)
  • DashboardView (7-115)
src/switchcraft/gui_modern/views/library_view.py (1)
  • LibraryView (8-124)
🪛 actionlint (1.7.10)
.github/workflows/process_switch_issue.yml

26-26: "github.event.issue.body" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks for more details

(expression)

🔇 Additional comments (15)
README.md (3)

37-42: Excellent feature documentation!

The new feature descriptions are clear, concise, and well-integrated into the existing structure. Each feature has an actionable description that helps users understand its value.


75-101: Well-organized release documentation!

The reorganized "Release Artifacts & Variants" section with the Editions Overview and File Guide tables significantly improves clarity. The tables are well-formatted, the information is comprehensive, and the pros/cons format helps users make informed decisions about which variant to download.


71-71: The automation system described in the README is already implemented and functional. The process_switch_issue.yml workflow automatically:

  1. Triggers when issues are labeled with "automation"
  2. Validates required fields (application name, version, silent switch)
  3. Checks for duplicate entries
  4. Creates a pull request via the peter-evans/create-pull-request action

No action needed—the claim in the README is accurate.

Likely an incorrect or invalid review comment.

docs/FEATURES.md (1)

11-14: LGTM!

The new feature entries are well-documented and consistent with the existing format. They accurately reflect the new views and services introduced in this PR.

src/switchcraft/analyzers/msi.py (1)

43-43: LGTM!

The assignment properly captures the MSI ProductCode into the new product_code field on InstallerInfo, enabling downstream usage for uninstall string construction.

src/switchcraft/gui_modern/controls/skeleton.py (1)

20-24: LGTM on lifecycle management.

The did_mount and will_unmount hooks properly manage the animation lifecycle using the aborted flag to signal thread termination.

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

12-85: Well-structured view initialization.

The UI layout is clean with appropriate field visibility management and consistent patterns for the detection type selector. The component follows the established view patterns in the codebase.

.github/workflows/process_switch_issue.yml (1)

28-48: Workflow structure looks good.

The workflow correctly:

  • Filters on the 'automation' label
  • Sets appropriate permissions for contents, PRs, and issues
  • Uses current action versions (v4, v5, v6)
  • Configures PR to close the source issue on merge

Note: The workflow relies on the Python script exiting with non-zero on failure, which it does correctly via sys.exit(1).

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

186-193: LGTM!

The community_match flag is correctly integrated into the AnalysisResult return, enabling downstream code to identify when switches came from the community database.

scripts/process_issue_ops.py (1)

81-97: LGTM on the main entry point.

The main block correctly handles argument parsing, file reading, and error cases with appropriate exit codes.

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

31-52: Stat cards look good.

The stats row and card helper methods are clean and reusable. The use of parameterized _stat_card avoids code duplication.


54-115: Chart and activity sections are well-structured.

The mock BarChart configuration and recent activity ListView are appropriate for a dashboard prototype. The layout uses proper Flet patterns.

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

1-13: LGTM!

The re-exports correctly expose all new view classes. Minor observation: newer views (PackagingWizardView, DetectionTesterView, etc.) lack the Modern prefix used by older views—consider aligning naming for consistency if this is a concern.

src/switchcraft/services/intune_service.py (2)

6-6: base64 import is fine; ensure encoding is ASCII-safe and input-type tolerant in callers.
Right now the new methods assume script_content is str; consider accepting bytes too (see later comments).


467-499: Minor code style improvement: use bare raise instead of raise e.

The hardcoded executionFrequency and retryCount fields are required by the Microsoft Graph API for deviceShellScript creation and cannot be safely omitted. Removing them would cause API rejection, not prevent it. If parametrization is desired for future flexibility, add these as optional function parameters with sensible defaults rather than removing them.

The comment about "PT15M" should clarify it's a valid ISO 8601 duration (15 minutes).

Likely an incorrect or invalid review comment.

Comment thread .github/workflows/process_switch_issue.yml Outdated
Comment thread src/switchcraft/data/community/switches.json
Comment thread src/switchcraft/gui_modern/app.py
Comment thread src/switchcraft/gui_modern/views/home_view.py
Comment thread src/switchcraft/gui_modern/views/macos_wizard_view.py Outdated
Comment thread src/switchcraft/gui_modern/views/script_upload_view.py
Comment thread src/switchcraft/models.py
Comment thread src/switchcraft/services/addon_service.py
Comment thread src/switchcraft/services/intune_service.py
Comment thread src/switchcraft/services/intune_service.py Outdated
@github-actions
github-actions Bot merged commit 01d85bf into main Jan 13, 2026
1 check passed
@github-actions
github-actions Bot deleted the new-features branch January 13, 2026 01:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend ci-cd documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant