Adds the user-facing half of the server catalog: a searchable, signed
catalog browser wired to the existing paste/import path.
bcc_core.py (pure, GUI-free, per the design comment on #10):
- CATALOG_CATEGORY_GROUPS / catalog_category_group(): collapses the
raw taxonomy to the 7 UI chips (unknown categories fall back to
Other, never drop an entry).
- catalog_entry_matches_query() / filter_catalog_entries() /
catalog_entries_in_group(): search-as-you-type + category chip
filtering over catalog entries.
- format_freshness_hint(): last_release ISO date -> "Last updated N
months/years ago", '' when missing/unparseable/future.
- first_unfilled_focus_target(): finds the first <PLACEHOLDER> arg or
blank env_required key so the GUI can focus it after Add.
- load_bundled_catalog_entries(): reads+verifies+validates the
bundled catalog.json/.sig pair, returns servers or an EMPTY list on
ANY failure -- the load-bearing guarantee behind the dialog's empty
state.
- Bug fix: catalog_entry_to_paste_json() only copied config.env,
ignoring env_required -- the field where 9 of the 19 seed entries
(postgres, github, notion, obsidian, brave-search, tavily,
home-assistant, n8n, grafana) actually declare their secret var
names. Add would have silently added these servers with no env
field for the user to fill in. Now env_required keys are seeded as
empty-string placeholders unless config.env already sets them.
bcc.py:
- BrowseCatalogDialog: search box, category chips, list, detail pane
(description/notes/homepage/freshness hint/verbatim command
preview), per-tier action (Add for basic, Open setup docs for
link-only). Every catalog-derived string goes through plain_label()
(Qt.PlainText + html.escape, matching catalog_console.py's
approach) or an inherently-plain QPlainTextEdit for the command
preview -- catalog.json takes community PRs, so every field is
attacker-influenceable.
- "Browse catalog…" button next to Paste JSON.
- ServerEditor.focus_target(): selects the first unfilled arg line or
opens the env-table cell editor for the first blank env row.
- Save-time placeholder guard: warns (Yes/No, defaults No) when any
server still has an unfilled <PLACEHOLDER>; never blocks a
deliberate save, never saves one unnoticed.
bcc.spec: bundle data/catalog.json.sig alongside catalog.json -- the
signature file was missing from datas, so a frozen build would have
had a catalog.json with no matching .sig for resolve_catalog() to
verify against.
tests/test_core.py: +82 tests covering category-group mapping,
catalog search/filter, freshness-hint formatting (including the
month/year rounding boundary), first_unfilled_focus_target, the
catalog_entry_to_paste_json env_required fix (incl. a regression
against the real shipped postgres entry), and
load_bundled_catalog_entries under valid/tampered/wrong-key/missing-
file/invalid-but-signed conditions plus an end-to-end check against
the real data/catalog.json + .sig (19 entries).
GUI code (BrowseCatalogDialog, ServerEditor.focus_target,
MainWindow.browse_catalog) is unavoidably untested here -- PySide6
cannot import in this sandbox (missing libEGL/system GL libs) -- but
all the logic it depends on is pushed into bcc_core.py and covered
above.
Sets live in the config under _bccServerSets (bcc-owned, ignored by
Claude, travels with the file). Apply enables exactly the set's members
and parks the rest; vanished members are reported, not fatal. GUI row:
set combo + Apply + Save set… + delete.
Closes#52
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
First release actually shipping the v1.2.0 feature set (the v1.2.0
tag's release run was cancelled and produced no assets) plus the
audit fixes #32-#40 and the Windows process-tree kill (#13).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Popen.kill() only terminated the direct child, so runner-style commands
(npx -> node -> server) leaked the real server process on every Windows
spawn test. taskkill /PID <pid> /T /F walks the descendant tree. Also
sets CREATE_NO_WINDOW on the spawned test process so the windowed exe
doesn't flash a console per test.
Closes#13
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
python3 is not a command on a stock Windows install; caught by the new
windows-latest CI job. Probe 'python' there and accept warn (found on
augmented PATH) as proof of resolution.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The status bar is rewritten on every action, so the appended MSIX
warning vanished on first interaction. A dedicated warn banner under
the top bar stays visible.
Closes#35
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The self-hosted Windows runner's PowerShell execution policy rejects
setup-python's install script, so Windows mirrors release.yml: py -3.12
+ venv (the version the release binaries ship with). Linux keeps the
full 3.10/3.12/3.13 setup-python matrix.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Linux (and any non-desktop platform): refuse instead of 'pkill claude',
which substring-matched running Claude Code CLI sessions and relaunched
the CLI, not a desktop app. New restart_supported() gates the button.
- macOS: wait (<=5s) for the old instance to exit before 'open -a Claude'
so the relaunch can't re-activate the dying process. Runs off the UI
thread via a RestartWorker.
- Windows: verify the Start-menu shortcut exists BEFORE taskkill, so an
MSIX/Store install is never killed without a relaunch path.
Closes#33
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Closing the About dialog mid-check GC'd the dialog and the running
QThread with it -> 'QThread: Destroyed while thread is still running'.
A class-level keepalive set now holds each worker until finished;
stale results to a destroyed receiver are dropped by Qt.
Closes#32
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>