feat(#101): live hot-reload of external sidecar/config changes #103

Merged
the_og merged 1 commits from feat/101 into main 2026-08-13 00:55:04 -04:00
Owner

Closes #101.

Live hot-reload so external changes to a server's sidecar (ssh-mcp's config.toml) or to BCC's own loaded config surface without a restart. Detection (#91/#93) previously ran only at profile load / server selection.

Core (pure, unit-tested — CI has no PySide6)

  • sidecar_watch_paths() — the external paths worth watching for a server: the resolved sidecar file, its directory (so create/delete and atomic-rename replaces register), and the wrong-path/doc file. Order-stable, de-duplicated.
  • sidecar_state_fingerprint() — a hashable snapshot folding the #91 sidecar status + #93 permission status, so the GUI can tell whether the observable state actually changed and skip a redundant refresh.
  • sidecar_state_changed() — explicit named equality for that decision.

GUI (thin wiring, smoke-tested headlessly with QT_QPA_PLATFORM=offscreen)

  • QFileSystemWatcher over the selection's sidecar path(s) + BCC's own config; debounced 300 ms.
  • Re-arm on every event — an atomic-rename replace drops the inode from the watcher, so wanted paths are re-added before the next check.
  • Focus-in fallback via changeEvent(ActivationChange) — covers what watchers miss (atomic replaces, not-yet-created files).
  • recheck_advisories() only recomputes warning labels; it never touches field values, so a live reload cannot clobber unsaved edits.
  • An external edit to BCC's own config shows a non-destructive Reload banner (never a silent overwrite); reload confirms-on-dirty via the existing path.

GUI click-through (needs a human)

  1. Add an ssh-mcp server (npx -y ssh-mcp --host=h --user=u), select it.
  2. In a terminal, mkdir -p ~/Library/Application\ Support/ssh-mcp && printf '[server]\nhost="h"\n' > ~/Library/Application\ Support/ssh-mcp/config.toml (macOS path). The "arguments are inert" advisory should appear within ~1s, no restart.
  3. chmod 644 that file → the permission advisory appears; chmod 600 → it clears.
  4. Edit BCC's config JSON in another editor and save → the Reload banner appears; click Reload (prompts if you have unsaved edits).
  5. Delete the config.toml → advisory clears. Switch focus away and back to confirm the focus-in fallback also refreshes.

Base: main. First of a stack — #102 (sidecar editor) is branched off this one.

🤖 Generated with Claude Code

Closes #101. Live hot-reload so external changes to a server's sidecar (ssh-mcp's `config.toml`) or to BCC's own loaded config surface **without a restart**. Detection (#91/#93) previously ran only at profile load / server selection. ### Core (pure, unit-tested — CI has no PySide6) - `sidecar_watch_paths()` — the external paths worth watching for a server: the resolved sidecar file, its **directory** (so create/delete and atomic-rename replaces register), and the wrong-path/doc file. Order-stable, de-duplicated. - `sidecar_state_fingerprint()` — a hashable snapshot folding the #91 sidecar status + #93 permission status, so the GUI can tell whether the *observable* state actually changed and skip a redundant refresh. - `sidecar_state_changed()` — explicit named equality for that decision. ### GUI (thin wiring, smoke-tested headlessly with `QT_QPA_PLATFORM=offscreen`) - `QFileSystemWatcher` over the selection's sidecar path(s) + BCC's own config; **debounced 300 ms**. - **Re-arm on every event** — an atomic-rename replace drops the inode from the watcher, so wanted paths are re-added before the next check. - **Focus-in fallback** via `changeEvent(ActivationChange)` — covers what watchers miss (atomic replaces, not-yet-created files). - `recheck_advisories()` only recomputes warning labels; it never touches field values, so a live reload **cannot clobber unsaved edits**. - An external edit to BCC's own config shows a **non-destructive Reload banner** (never a silent overwrite); reload confirms-on-dirty via the existing path. ### GUI click-through (needs a human) 1. Add an ssh-mcp server (`npx -y ssh-mcp --host=h --user=u`), select it. 2. In a terminal, `mkdir -p ~/Library/Application\ Support/ssh-mcp && printf '[server]\nhost="h"\n' > ~/Library/Application\ Support/ssh-mcp/config.toml` (macOS path). The **"arguments are inert"** advisory should appear within ~1s, no restart. 3. `chmod 644` that file → the **permission** advisory appears; `chmod 600` → it clears. 4. Edit BCC's config JSON in another editor and save → the **Reload banner** appears; click Reload (prompts if you have unsaved edits). 5. Delete the `config.toml` → advisory clears. Switch focus away and back to confirm the focus-in fallback also refreshes. Base: `main`. First of a stack — #102 (sidecar editor) is branched off this one. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
the_og added 1 commit 2026-08-13 00:12:52 -04:00
feat(#101): live hot-reload of external sidecar/config changes
CI / Lint (ruff) (pull_request) Successful in 13s
CI / Tests (py3.12 / windows-latest) (pull_request) Successful in 25s
CI / Tests (py3.10 / ubuntu-latest) (pull_request) Successful in 20s
CI / Tests (py3.12 / ubuntu-latest) (pull_request) Successful in 13s
CI / Tests (py3.13 / ubuntu-latest) (pull_request) Successful in 12s
CI / Catalog signature (pull_request) Successful in 8s
ded1eef2dd
Detection (#91/#93) previously ran only at profile load / server
selection, so a config.toml created or chmod-ed while BCC was running
stayed invisible until a restart. Make the view react on its own.

Core (pure, unit-tested — CI has no PySide6):
- sidecar_watch_paths(): the external paths worth watching for a server
  — the resolved sidecar file, its directory (so create/delete and
  atomic-rename replaces register), and the wrong-path/doc file — order-
  stable and de-duplicated.
- sidecar_state_fingerprint(): a hashable snapshot folding the #91
  sidecar status and #93 permission status, so the GUI can tell whether
  the *observable* state actually changed and skip a redundant refresh.
- sidecar_state_changed(): explicit, named equality for that decision.

GUI (thin wiring, smoke-tested headlessly):
- QFileSystemWatcher over the selection's sidecar path(s) + BCC's own
  loaded config; debounced (300 ms) so a burst of writes doesn't thrash.
- Re-arm on every event: an atomic-rename replace drops the inode from
  the watcher, so wanted paths are re-added before the next check.
- Focus-in fallback via changeEvent(ActivationChange) — always works
  where watchers miss (atomic replaces, not-yet-created files).
- recheck_advisories() only recomputes warning labels from the form +
  filesystem; it never touches field values, so a live reload cannot
  clobber unsaved edits.
- An external edit to BCC's own config surfaces a non-destructive
  Reload banner (never a silent overwrite); confirm-on-dirty reuses the
  existing load path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
the_og merged commit ece8c99f79 into main 2026-08-13 00:55:04 -04:00
the_og deleted branch feat/101 2026-08-13 00:55:04 -04:00
Sign in to join this conversation.