Integration of the five reviewed feature branches for the v1.2.0 release, merged with real git 3-way merges, conflicts resolved, and verified green locally (ruff format --check, ruff check, pytest ~138 tests pass).
#17 — Stale-file detection hardened with a mtime+size fingerprint (ConfigStat/config_fingerprint). (PR #22)
#8 — Duplicate-name collision fix: closes a real silent-overwrite on drag-and-drop import; shared _import_server prompt + pure resolve_name_collision. (PR #21)
Conflict resolutions
_normalize_unicode NBSP line — kept the single out.replace(chr(0xA0), " ") form (all branches were equivalent).
bcc.py — LogViewerDialog (#6) and AboutDialog (#18/19) both inserted a class at the same anchor; kept both. Imports and MainWindow hooks merged cleanly.
tests/test_core.py — each branch appended tests at EOF; concatenated all.
Version bumped 1.1.0 → 1.2.0 in __version__, pyproject.toml, and test_dunder_version_matches_pyproject.
Supersedes the individual feature PRs #21–#25 (to be closed on merge).
Integration of the five reviewed feature branches for the **v1.2.0** release, merged with real git 3-way merges, conflicts resolved, and verified green locally (`ruff format --check`, `ruff check`, `pytest` ~138 tests pass).
Closes #6
Closes #8
Closes #9
Closes #17
Closes #18
Closes #19
## Included
- **#18 + #19** — About dialog (Help menu, repo/issues/license links) + notify-only update checker (`__version__` single source, numeric version compare, fail-quiet tokenless releases fetch, throttled once/day startup check). *(PR #25)*
- **#9** — Restart Claude Desktop button after save, gated to Desktop profiles only. *(PR #24)*
- **#6** — In-app MCP server log viewer (`server_log_path()` + read-only auto-tailing `LogViewerDialog`). *(PR #23)*
- **#17** — Stale-file detection hardened with a mtime+size fingerprint (`ConfigStat`/`config_fingerprint`). *(PR #22)*
- **#8** — Duplicate-name collision fix: closes a real silent-overwrite on drag-and-drop import; shared `_import_server` prompt + pure `resolve_name_collision`. *(PR #21)*
## Conflict resolutions
- `_normalize_unicode` NBSP line — kept the single `out.replace(chr(0xA0), " ")` form (all branches were equivalent).
- `bcc.py` — `LogViewerDialog` (#6) and `AboutDialog` (#18/19) both inserted a class at the same anchor; kept both. Imports and `MainWindow` hooks merged cleanly.
- `tests/test_core.py` — each branch appended tests at EOF; concatenated all.
- Version bumped 1.1.0 → 1.2.0 in `__version__`, `pyproject.toml`, and `test_dunder_version_matches_pyproject`.
Supersedes the individual feature PRs #21–#25 (to be closed on merge).
Bare mtime equality can miss a concurrent external write that lands
within the filesystem's mtime resolution (same-second writes), or
where the writer restores the original mtime. Add config_fingerprint()
returning a (mtime, size) ConfigStat pair; the stale-file check in
bcc.py now compares both fields instead of mtime alone. config_mtime()
is kept as-is (still used/tested independently).
The previous commit accidentally normalized the non-breaking space
(U+00A0) in _normalize_unicode's replace() call to a regular space
during a copy/paste, turning that replace() into a no-op. Use an
explicit escape instead of the literal character so it can't
be silently corrupted again.
- Add `__version__ = "1.1.0"` as the single source of truth (matches pyproject.toml).
- Add parse_version()/is_newer_version() for numeric (never lexical) version
comparison, handling v-prefix, pre-release suffixes, and malformed input.
- Add fetch_latest_release(): reads the public Gitea releases API
(anonymous, no token) and returns {version, url} or None on any failure.
Never downloads or touches a binary — metadata only.
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.
Two prior attempts to fix this line via a literal or -escaped
non-breaking space both silently reverted back to a no-op regular-space
replace during transcription. Using chr(0xA0) instead removes any
non-ASCII or backslash-escape character from the source line entirely.
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.
MainWindow._loaded_mtime -> _loaded_stat (core.ConfigStat), populated
via core.config_fingerprint() at load/save time. The save-path stale
check now compares the full fingerprint (mtime AND size) instead of
bare mtime equality.
Includes the required regression case: rewrite a file with different
content, force the original mtime back via os.utime, and assert the
fingerprint still differs (because size changed).
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.
- Add a real QMenuBar (Help -> About...).
- AboutDialog: app icon, name, version (core.__version__), and links to the
repo/issues/license opened via QDesktopServices.openUrl (system browser,
never in-app navigation). macOS unsigned-app note.
- "Check for updates" button + UpdateCheckWorker (QThread) runs
core.fetch_latest_release() off the UI thread; shows "up to date" or
"vX.Y.Z available" with a button to open the releases page. No binary
download, ever.
- Optional quiet startup auto-check, throttled to once/day via a QSettings
timestamp, off-thread, silent on failure, toggle lives in the About dialog.
All network calls are monkeypatched (urllib.request.urlopen) — no live
network in tests. Covers older/newer/equal comparisons, v-prefix,
differing-length tuples, malformed input on both sides, and
fetch_latest_release success/timeout/network-failure/malformed-response
paths.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Integration of the five reviewed feature branches for the v1.2.0 release, merged with real git 3-way merges, conflicts resolved, and verified green locally (
ruff format --check,ruff check,pytest~138 tests pass).Closes #6
Closes #8
Closes #9
Closes #17
Closes #18
Closes #19
Included
__version__single source, numeric version compare, fail-quiet tokenless releases fetch, throttled once/day startup check). (PR #25)server_log_path()+ read-only auto-tailingLogViewerDialog). (PR #23)ConfigStat/config_fingerprint). (PR #22)_import_serverprompt + pureresolve_name_collision. (PR #21)Conflict resolutions
_normalize_unicodeNBSP line — kept the singleout.replace(chr(0xA0), " ")form (all branches were equivalent).bcc.py—LogViewerDialog(#6) andAboutDialog(#18/19) both inserted a class at the same anchor; kept both. Imports andMainWindowhooks merged cleanly.tests/test_core.py— each branch appended tests at EOF; concatenated all.__version__,pyproject.toml, andtest_dunder_version_matches_pyproject.Supersedes the individual feature PRs #21–#25 (to be closed on merge).
- Add `__version__ = "1.1.0"` as the single source of truth (matches pyproject.toml). - Add parse_version()/is_newer_version() for numeric (never lexical) version comparison, handling v-prefix, pre-release suffixes, and malformed input. - Add fetch_latest_release(): reads the public Gitea releases API (anonymous, no token) and returns {version, url} or None on any failure. Never downloads or touches a binary — metadata only.