The installed desktop app polls `desktop-latest/latest.json`. Previously
every push to `main` (autotag -> v*.devN -> desktop.yml dispatch) rebuilt
and republished `desktop-latest` as a DEV prerelease, so stable users were
auto-updated onto unvetted dev builds, and any manual stable mirror was
clobbered on the next merge.
Split the streams so the app's channel only ever serves vetted stable:
- Dev/rolling builds (v* autotag + manual workflow_dispatch) now publish to
a new `desktop-edge` pre-release. The shipped app does not poll edge, so
dev builds never auto-install onto stable users.
- Stable `desktop-v*` builds publish the user-facing release as before, then
a new `refresh-stable-channel` job copies that release's signed
`latest.json` into `desktop-latest` (mirror; URLs already point at the
desktop-v* assets). Cut a `desktop-v*` tag to ship an update.
- `clean-release` now targets `desktop-edge`; `desktop-latest` is never
wiped by CI.
No app/tauri.conf.json change — the updater endpoint stays `desktop-latest`.
Doc updated to describe the now-implemented stable/edge split.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The uv-sync failure handling added in #398 was inline in the async
`boot_backend` GUI path, so its logic — exit-code rendering and the
stderr "tail" extraction — could only be verified by running the
desktop app. This extracts the pure logic into three free functions
and adds unit tests, so the message formatting is now covered by
`cargo test` with no GUI / webview runtime needed.
Extracted:
- `uv_sync_stderr_tail(stderr, max_chars)` — last N chars of stderr,
trimmed, on char boundaries. The original inline version used
`.rev().take(N).collect().chars().rev().collect()`; the new version
is `skip(count - N)` which is clearer and equally UTF-8-safe (matters
because Windows consoles emit non-ASCII cp9xx bytes — a byte slice
could split a codepoint).
- `format_uv_sync_failure(root, exit_code, stderr)` — the non-zero-exit
message. Now renders a missing exit code (signal-terminated process)
as "unknown" instead of a misleading "-1".
- `format_uv_sync_spawn_error(root, uv_bin, err)` — the can't-spawn
message.
`boot_backend` now calls these instead of formatting inline.
Tests (7, in `#[cfg(test)] mod tests`):
- tail returns whole string when shorter than the limit
- tail keeps the END (the actionable line), not the spinner-noise start
- tail trims surrounding whitespace
- tail never splits a multi-byte codepoint (500×"é", limit 100 → exactly
100 chars, all "é")
- failure message includes exit code + stderr tail + the actionable
"run uv sync manually" hint
- missing exit code renders as "exit unknown", never "exit -1"
- spawn-error names the uv binary path and repo root
Verified the logic standalone via `rustc --test` (7/7 pass). In CI they
run via `cargo test` in desktop.yml's `validate` job, which already
builds the Tauri crate with the webkit deps and runs on every PR that
touches `frontend/**` — so this changes the existing `cargo check` step
to `cargo test` (a superset: same build coverage, plus the tests).
This doesn't verify the GUI *behavior* (that still needs a human running
the app, or the windows-latest empirical path) — but the error-message
logic that was previously untestable now has automated coverage.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The `test` job runs only on ubuntu-latest, so the Windows-specific
branches added for #373 (RAM detection via GlobalMemoryStatusEx) and
#293 (cp9xx → UTF-8 stdout reconfigure) were never *executed* in CI —
only unit-tested with mocks on Linux. `tests/hardware/test_hardware_profiles.py::test_total_ram_gb_windows`
has existed all along but is `skipif(sys.platform != "win32")`, so it
silently skipped on every run.
This adds a `test-windows` job that runs on a real Windows runner
(free for public repos) and:
1. Verifies `_total_ram_gb()` returns > 0 on actual Windows — executes
the real `ctypes.windll.kernel32.GlobalMemoryStatusEx` path (#373).
Runs as a pure-Python step BEFORE any Rust build, so a flaky
toolchain install can't mask the result.
2. Runs `tests/hardware/test_hardware_profiles.py` (the now-unskipped
Windows RAM test) and `tests/cli/test_cli.py` (the
`test_windows_reconfigures_stdout_to_utf8` test from #293).
3. Builds + imports the `openjarvis_rust` PyO3 extension on Windows —
the only CI job that does so. The extension is mandatory at runtime
(`_rust_bridge.py` hard-errors without it), yet nothing else
verified it compiles/imports on Windows. desktop.yml builds the
Tauri app's Rust, not this extension.
4. Smoke-tests `jarvis --version`.
Scoped to the platform-relevant test files (not the full 6700-test
suite) so the job stays fast; the slow part is the Rust build, which
doubles as Windows-extension-build coverage.
All `run:` steps are static commands with no `github.event.*`
interpolation — no workflow-injection surface.
Note: #331 (desktop "did not become healthy" — uv sync error
surfacing) is GUI-triggered Tauri boot logic and isn't covered here;
its only automatable surface is string formatting, and testing it
needs the heavy webview build. Left as a possible follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tauri's build script enforces strict SemVer
(`MAJOR.MINOR.PATCH[-pre][+build]`) on `tauri.conf.json > version`, but
the autotag scheme introduced in #358 emits PEP 440 dev releases like
`1.0.2.dev661` — PEP 440 separates dev with `.`, SemVer requires `-`.
First post-#358 desktop run failed with:
tauri.conf.json > version must be a semver string
PyPI requires PEP 440; Tauri requires SemVer. They genuinely don't
agree on `.devN`. Translate just for the Tauri bundle:
1.0.2.dev661 -> 1.0.2-dev.661 (valid SemVer prerelease)
1.0.2 -> 1.0.2 (passthrough)
1.0.0-rc.1 -> 1.0.0-rc.1 (passthrough)
Tag, git history, PyPI wheel, and the updater's `latest.json` all keep
the PEP 440 form. Only the embedded bundle version uses SemVer, and
the comparison the updater does is bundle-vs-latest.json (both SemVer
now) so the upgrade path stays consistent.
Failing run for reference: 26118619500
Co-authored-by: krypticmouse <herumbshandilya123@gmail.com>
The frontend bundling step in pypi-publish.yml assumed Vite produced
`frontend/dist/`, but vite.config.ts is configured with
`outDir: '../src/openjarvis/server/static'` and `emptyOutDir: true` —
Vite writes straight to the package's static dir and clears stale
assets itself. The `rm -rf dist; cp -r dist/.` ceremony was dead code
that would have deleted the build output had the assertion not
short-circuited it first.
First post-#358 autotag run failed at this step with
`frontend/dist/index.html missing or empty after build` (build was
fine; the assertion checked the wrong path).
Fix: drop the rm/cp dance and assert the actual output location.
Co-authored-by: krypticmouse <herumbshandilya123@gmail.com>
Removes the pull_request trigger so the workflow only runs on explicit
@claude mentions or manual workflow_dispatch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The project had two overlapping directories: desktop/ (Tauri Rust backend +
stale React components) and frontend/ (real React app + dead Tauri stub).
This consolidates everything under frontend/:
- Move desktop/src-tauri/ → frontend/src-tauri/ (the real 1,720-line Rust
backend with Ollama sidecar, backend lifecycle, cloud keys, overlay, etc.)
- Preserve 9 old desktop React components in frontend/src/components/Desktop/
(excluded from TS build — APIs have drifted, kept for future integration)
- Delete the old frontend/src-tauri/ stub (246 lines, never compiled)
- Delete desktop/ entirely
- Fix tauri.conf.json frontendDist path (../../frontend/dist → ../dist)
- Update CI workflow, bump script, .gitignore, and docs paths
- Rename setup/ → Setup/ for consistent PascalCase component directories
Ahead of the 2026-06-02 forced-Node-24 deadline, upgrade the remaining
javascript actions flagged in the workflow annotations:
- actions/setup-python v5 → v6
- actions/setup-node v4 → v6 (skipping v5)
- actions/cache v4 → v5
- actions/deploy-pages v4 → v5
- actions/upload-pages-artifact v3 → v5.0.0
upload-pages-artifact has no rolling @v5 major tag — only the specific
v5.0.0 release — so it gets an explicit pin, same as astral-sh/setup-uv.
Not touched: swatinem/rust-cache@v2 (still on v2 majors, not flagged),
dtolnay/rust-toolchain@stable (composite), tauri-apps/tauri-action@v0
(composite), anthropics/claude-code-action@v1 (still latest major).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
astral-sh/setup-uv does not publish a rolling @v8 major tag — only the
specific v8.0.0 release. Previous commit's @v8 reference failed to
resolve and broke CI/Docs/PyPI-publish runs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- openjarvis.__version__ now comes from importlib.metadata.version("openjarvis")
instead of a hardcoded string, so it stays in sync with pyproject.toml on every
release. Tests assert against openjarvis.__version__ rather than a literal.
- Bump actions/checkout@v4 → @v6 and astral-sh/setup-uv@v4 → @v8 across every
workflow ahead of the 2026-06-02 Node.js 20 deprecation on GitHub Actions runners.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The clone tracking workflow needs to push directly to main, but branch
protection rules block GITHUB_TOKEN pushes. Using the PAT for checkout
allows the push to succeed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Move download/clone/star badges from docs/index.md to README.md
- Use TRAFFIC_TOKEN secret instead of GITHUB_TOKEN for Traffic API
(requires admin-level access that GITHUB_TOKEN doesn't provide)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Closes#182 — the claude-code-action attempts OIDC token exchange when
no github_token is provided, which fails without the Anthropic GitHub
App installed. Passing secrets.GITHUB_TOKEN skips the OIDC flow.
The gemma_cpp live tests require local model weights and env vars
(GEMMA_CPP_MODEL_PATH, etc.) that are not available in CI, causing
4 failures since the gemma-cpp-engine PR was merged. Add marker
filters to the pytest invocation so live and cloud tests are skipped.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
macos-latest now resolves to macOS 15 (Sequoia), which removed the
SetFile command from Xcode Command Line Tools. Tauri's bundle_dmg.sh
relies on SetFile to set custom icon attributes on the DMG volume,
causing the Desktop Build & Release workflow to fail on the macOS
runner.
Pin to macos-14 (Apple Silicon M1, Sonoma) which still includes
SetFile and supports universal binary builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three issues fixed:
1. The desktop-latest release accumulated stale artifacts across builds
because tauri-action adds new files without removing old ones. Users
could download an outdated DMG. Added a clean-release job that wipes
all existing assets before the build matrix runs.
2. The frontend hardcoded DESKTOP_API to port 8000 but the Tauri backend
starts the server on port 8222. Added a get_api_base Tauri command so
the frontend fetches the port from the Rust backend at startup,
keeping JARVIS_PORT as the single source of truth.
3. frontend/package.json version was 1.0.0 while desktop and pyproject
are 0.1.0, causing duplicate artifact names in the release.