feat: Restart Claude Desktop convenience button (#9) #24

Closed
the_og wants to merge 0 commits from feat/9-restart-claude-desktop into main
Owner

Closes #9

Adds a "Restart Claude Desktop" button to the post-save confirmation, so a saved config takes effect without a manual quit/relaunch.

bcc_core.py

  • restart_claude_desktop() — platform-abstracted restart, scoped strictly to Claude Desktop (never Claude Code):
    • macOS: pkill -x "Claude" then open -a Claude
    • Windows: taskkill /IM Claude.exe /F then relaunch via the Start-menu shortcut (%APPDATA%\Microsoft\Windows\Start Menu\Programs\Claude.lnk)
    • Linux: pkill claude then a detached Popen(["claude"], ...) relaunch
    • The kill step is best-effort and never raises — a process that wasn't running is not a failure. Only a failed relaunch is reported as success=False. Returns a RestartResult(success, detail) NamedTuple.
  • profile_targets_claude_desktop(profile) — distinguishes a Claude Desktop profile (claude_desktop_config.json) from Claude Code (~/.claude.json or the legacy ~/.claude/settings.json), used to gate the button.

bcc.py

  • The status row now includes a "Restart Claude Desktop" button, hidden by default.
  • Shown only after a successful save (both the normal overwrite path and the stale-file "merge" path), and only when core.profile_targets_claude_desktop(self.current_profile) is true — never for Claude Code.
  • Hides again on further edits (_mark_dirty) or when switching/reloading a profile, so it never lingers stale.
  • Clicking it calls core.restart_claude_desktop(), appends the result detail to the status line on success, or shows a warning dialog on failure.

tests/test_core.py

  • profile_targets_claude_desktop: true for a claude_desktop_config.json profile, false for ~/.claude.json and the legacy settings.json profile.
  • restart_claude_desktop: per-platform command sequence (macOS/Windows/Linux) verified via monkeypatch on subprocess.run/subprocess.Popen and sys.platform — nothing is actually killed or launched. Also covers: a non-zero pkill/taskkill exit (nothing to kill) is NOT a failure; a failed relaunch IS reported as a failure; a missing pkill binary (OSError) doesn't propagate.

Test count: 92 → 103 (11 new tests). ruff check, ruff format --check, and pytest all pass.

Caveat: one pre-existing, unrelated line in bcc_core.py (_normalize_unicode's non-breaking-space literal) was transcribed as the equivalent "\xa0" escape instead of the literal U+00A0 character during the push — functionally identical (Python parses \xa0 to the same character), verified via ruff/pytest, and outside this issue's scope. Flagging for visibility rather than risking further transcription churn on an unrelated line.

Closes #9 Adds a "Restart Claude Desktop" button to the post-save confirmation, so a saved config takes effect without a manual quit/relaunch. **bcc_core.py** - `restart_claude_desktop()` — platform-abstracted restart, scoped strictly to Claude Desktop (never Claude Code): - macOS: `pkill -x "Claude"` then `open -a Claude` - Windows: `taskkill /IM Claude.exe /F` then relaunch via the Start-menu shortcut (`%APPDATA%\Microsoft\Windows\Start Menu\Programs\Claude.lnk`) - Linux: `pkill claude` then a detached `Popen(["claude"], ...)` relaunch - The kill step is best-effort and never raises — a process that wasn't running is not a failure. Only a failed relaunch is reported as `success=False`. Returns a `RestartResult(success, detail)` NamedTuple. - `profile_targets_claude_desktop(profile)` — distinguishes a Claude Desktop profile (`claude_desktop_config.json`) from Claude Code (`~/.claude.json` or the legacy `~/.claude/settings.json`), used to gate the button. **bcc.py** - The status row now includes a "Restart Claude Desktop" button, hidden by default. - Shown only after a successful save (both the normal overwrite path and the stale-file "merge" path), and only when `core.profile_targets_claude_desktop(self.current_profile)` is true — never for Claude Code. - Hides again on further edits (`_mark_dirty`) or when switching/reloading a profile, so it never lingers stale. - Clicking it calls `core.restart_claude_desktop()`, appends the result detail to the status line on success, or shows a warning dialog on failure. **tests/test_core.py** - `profile_targets_claude_desktop`: true for a `claude_desktop_config.json` profile, false for `~/.claude.json` and the legacy settings.json profile. - `restart_claude_desktop`: per-platform command sequence (macOS/Windows/Linux) verified via `monkeypatch` on `subprocess.run`/`subprocess.Popen` and `sys.platform` — nothing is actually killed or launched. Also covers: a non-zero pkill/taskkill exit (nothing to kill) is NOT a failure; a failed relaunch IS reported as a failure; a missing `pkill` binary (OSError) doesn't propagate. Test count: 92 → 103 (11 new tests). `ruff check`, `ruff format --check`, and `pytest` all pass. **Caveat:** one pre-existing, unrelated line in `bcc_core.py` (`_normalize_unicode`'s non-breaking-space literal) was transcribed as the equivalent `"\xa0"` escape instead of the literal U+00A0 character during the push — functionally identical (Python parses `\xa0` to the same character), verified via `ruff`/`pytest`, and outside this issue's scope. Flagging for visibility rather than risking further transcription churn on an unrelated line.
the_og added the P1 label 2026-07-07 20:43:22 -04:00
the_og added 3 commits 2026-07-07 20:43:23 -04:00
Platform-abstracted restart: macOS uses pkill -x Claude + open -a Claude,
Windows uses taskkill + relaunch via the Start-menu shortcut, Linux uses
pkill claude + a detached Popen relaunch. Not finding a running process is
not an error -- only a failed relaunch is reported as failure.

Also adds profile_targets_claude_desktop() to distinguish Claude Desktop
profiles (claude_desktop_config.json) from Claude Code profiles, used to
gate the restart button in the GUI.
After a successful save, shows a 'Restart Claude Desktop' button next to
the status line. Only shown when the saved profile targets Claude Desktop
(core.profile_targets_claude_desktop) -- never for Claude Code, which has
no GUI process to bounce. Clicking it calls core.restart_claude_desktop()
and reports success/failure. The button hides again on further edits or
when switching/reloading profiles.
test: cover restart_claude_desktop() and profile_targets_claude_desktop() (#9)
CI / Lint (ruff) (pull_request) Successful in 6s
CI / Tests (py3.10) (pull_request) Successful in 8s
CI / Tests (py3.12) (pull_request) Successful in 8s
16961a5cc8
Mocks subprocess.run/Popen and patches sys.platform per-OS branch (darwin,
win32, linux) -- never actually kills or launches anything. Covers: correct
command sequence per platform, pkill/taskkill exiting non-zero (nothing to
kill) is NOT treated as failure, a failed relaunch IS reported as failure,
and profile_targets_claude_desktop() correctly distinguishes Claude Desktop
configs from Claude Code / legacy settings.json profiles.
Author
Owner

Supervisor review (Cowork): approve. CI run #159 green; isolated diff. restart_claude_desktop() is platform-abstracted, treats "not running" as success, and the button is correctly gated to Claude Desktop profiles only (profile_targets_claude_desktop). Tests mock subprocess per-platform — nothing is actually killed/launched. Same benign NBSP-line rewrite as the others (resolve at merge). Ready for v1.2.0.

Supervisor review (Cowork): **approve.** CI run #159 green; isolated diff. `restart_claude_desktop()` is platform-abstracted, treats "not running" as success, and the button is correctly gated to Claude Desktop profiles only (`profile_targets_claude_desktop`). Tests mock subprocess per-platform — nothing is actually killed/launched. Same benign NBSP-line rewrite as the others (resolve at merge). Ready for v1.2.0.
Author
Owner

Superseded by #26 (release: v1.2.0), which merged this change into main as part of the integrated release. Closing.

Superseded by #26 (release: v1.2.0), which merged this change into `main` as part of the integrated release. Closing.
the_og closed this pull request 2026-07-08 11:35:45 -04:00
Some checks are pending
CI / Lint (ruff) (pull_request) Successful in 6s
CI / Tests (py3.10) (pull_request) Successful in 8s
CI / Tests (py3.12) (pull_request) Successful in 8s

Pull request closed

Sign in to join this conversation.