Commit graph

7 commits

Author SHA1 Message Date
Pascal André
87ae7050c6
fix(tauri): ship a single desktop entry in the Linux AppImage (#1179)
## Problem

Every Tauri Linux build since 2026-10-28 fails in "Package and verify
Tauri AppImage" with `Expected exactly one desktop entry in the Tauri
AppImage, found 2`. This blocks dev pre-releases and releases.

## Cause

Tauri's AppImage bundler copies `bundle.linux.appimage.files` into
`usr/share/applications` next to its generated `CodeNomad.desktop`,
symlinks `CodeNomad.desktop` at the AppDir root, then runs linuxdeploy
without a desktop-file argument (tauri-bundler `linuxdeploy.rs`).
linuxdeploy logs `No desktop file specified, using first desktop file
found` and deploys whichever entry the directory listing returns first.
When that is the hidden alias `ai.neuralnomads.codenomad.client.desktop`
(`NoDisplay=true`, added in #334), it lands at the root beside Tauri's
entry. The 2026-10-27 build picked `CodeNomad.desktop`; today's builds
pick the alias. No repository change between them touches packaging.

## Fix

- Remove the alias from `bundle.linux.appimage.files` only. Inside an
AppImage, files under `usr/share/applications` are not registered with
the desktop; integration tools install the root entry. deb/rpm keep the
alias for GTK app id association.
- Document the constraint in `dev-docs/TAURI_UPDATER.md`.

## Validation

- This PR's Tauri Linux packaging job extracts the AppImage and requires
exactly one root desktop entry.
2026-10-28 17:41:19 +01:00
Pascal André
2eb1fb1797
fix(tauri): stop F11 from freezing the Linux app (#1143)
Pressing F11 froze the Tauri app on Linux under X11 or XWayland: GNOME
reported "CodeNomad is not responding" and the window never went
fullscreen. The released 1.0.0 AppImage froze the same way, so this
predates the AppImage display-stack fix (#1138), which keeps GTK on X11
for this very shortcut.

A backtrace of the frozen process showed a deadlock between two
threads:
- global-hotkey's X11 thread received F11 and ran the plugin handler on
  that thread. toggle_fullscreen_window then asked the main thread
  whether the window was focused (is_focused) and waited.
- The key grab itself moves X11 focus. Its focus events reached the main
  thread, where update_fullscreen_shortcut re-registered F11 and waited
  for the X11 thread to confirm.

The handler now queues the toggle on the main thread
(run_on_main_thread) and returns, so the shortcut thread is free to
answer the registration.

Two leaks from #1138 are closed on the way:
- prefer_x11_display exported GDK_BACKEND=x11,wayland, which every child
  inherited: the backend, the shared OpenCode service it may start, agent
  shells, editors and the browser opened for links. Their GUI programs
  then ran under XWayland. CodeNomad now sets its own GTK's preference
  with gdk_set_allowed_backends, and children keep the session's choice.
  A GDK_BACKEND set by the session still wins.
- Children that leave an AppImage's mount started in AppRun's working
  directory inside it (PWD=/tmp/.mount_*/usr for the browser opened by a
  link). use_host_environment now starts them in / while ours is inside
  the mount; callers that pick a directory (backend, editors) still do.

dev-docs/HOTKEYS.md records that a global-shortcut handler only queues
work on the main thread; dev-docs/TAURI_UPDATER.md describes the GTK
backend preference and the working directory.

Validation on Ubuntu 24.04 GNOME Wayland, with the #1141 CI AppImage
repacked around a binary built from this branch:
- After focusing the window, about ten F11 presses toggle fullscreen in
  and out. The main thread stays in its event loop (poll), where the
  CI build and the 1.0.0 release deadlock on the first press
  (gdb backtraces).
- The window is still an XWayland client. The backend and the OpenCode
  service carry neither GDK_BACKEND nor mount paths.
- The help link opens Firefox with / as working directory, PWD=/ and no
  GDK_BACKEND.
- The appimage_environment and appimage_exit tests pass, and the Linux
  release binary builds.
2026-10-25 21:25:16 +01:00
Pascal André
a6c19d19d7
fix(tauri): leave the display stack to the host in the Linux AppImage (#1138)
The Tauri AppImage showed a black window on up-to-date Linux systems
(#1136: Arch, sway, AMD, Mesa 26.2). WebKit aborted with "Could not create
surfaceless EGL display: EGL_BAD_ALLOC", and no WebKit or GDK variable
helped.

Tauri CLI 2.11.5's linuxdeploy copied the build runner's display stack
into the image: Ubuntu 24.04's libwayland-client, -cursor, -egl and
-server, libxkbcommon, libxcb-render and -shm, libXau and libXdmcp.
AppRun puts usr/lib first on the library path, so the host's Mesa EGL
loaded the stale libwayland-client and failed even on the surfaceless
platform. This is tauri-apps/tauri#15976, fixed upstream in CLI 2.12.1.
On a current Arch userland (Mesa 26.2.5, wayland 1.26), `eglinfo -p
surfaceless` initializes with the host libraries and fails with the
1.0.0 image's bundled ones, or with its libwayland-client alone.

Packaging:
- The Tauri CLI moves to ^2.12.4.
- The Linux job defines APPIMAGE_HOST_LIBRARIES once (wayland,
  xkbcommon, xcb, X11, Xau, Xdmcp, EGL, GL, gbm, drm). It is passed to
  linuxdeploy as LINUXDEPLOY_EXCLUDED_LIBRARIES, because 2.12.4's own
  excludelist still deploys GDK's display libraries.
- The AppImage verification step rejects an image that bundles any of
  them.

Runtime, because the 2.12 GTK hook leaves GDK_BACKEND unset and exports
mount-scoped module paths:
- main() sets GDK_BACKEND=x11,wayland unless the session sets it. This
  keeps the XWayland windowing that the F11 X11 key grab needs, with a
  Wayland fallback.
- appimage_environment.rs drops variables inside the mount (byte-wise,
  plus the lossy spelling of the Node-serialized shell snapshot) and the
  mount's path-list entries. GIO_MODULE_DIR, unlike GIO_EXTRA_MODULES,
  replaces the host's module directory. It applies to children that
  outlive the mount: the backend (and through it the shared OpenCode
  service), the editors CodeNomad opens, and link handlers.
- Links open through external_open.rs. On Linux it uses the opener
  plugin's launchers with the host environment, detached; elsewhere it
  delegates to the plugin. The renderer reaches it through a new
  open_external_url command (http, https, mailto). The windows hold that
  command instead of opener:allow-open-url.
- The plugin's injected click handler is off. Its replacement,
  lib/native/external-link-clicks.ts, takes the same decisions in the
  application and Preferences windows and in trusted same-origin SideCar
  documents. Opaque previews and panel extensions stay fenced.
- The unused @tauri-apps/plugin-opener npm dependency is dropped.

Validation:
- Ubuntu 24.04: the Linux crate compiles, and the appimage_* tests
  (path lists, non-UTF-8 entries and mounts, lossy snapshot) and the
  capability test pass.
- The inventory check accepts GTK, WebKit and GLib, and rejects flat,
  multiarch, dangling-symlink and GLES copies.
- Browser tests drive the real link handler (target="_blank", Ctrl,
  Shift, nested, mailto; plain, Alt, tel and custom schemes untouched)
  and the real SideCarView and preview frames in a stubbed Tauri window.
  Each fails without its fix.
- The UI own-tsconfig and root typechecks pass.
2026-10-25 15:45:34 +01:00
Pascal André
9d9fc8d235
fix(tauri): stop AppImage helpers before exit so WebKit never crashes on quit (#1091)
## Problem

Quitting the Linux **Tauri AppImage** can show Ubuntu's "Sorry, Ubuntu
24.04 has experienced an internal error" dialog. apport records a
`WebKitWebProcess` crash with **SIGBUS**.

The AppImage runtime unmounts the bundle as soon as the main process
exits. WebKit's helper processes, started from that mount, can still be
running for a moment: their next page fault in the vanished mount raises
SIGBUS.

- Ubuntu 24.04 VM, `1.0.0-dev-20261022-1840c710` AppImage: 2 crashes in
3 quits.
- The same build extracted with `--appimage-extract` (no mount) never
crashed.
- Stable releases shipped Tauri Linux as a `.deb`, so the problem comes
with the AppImage distribution.

## Fix

On `RunEvent::Exit`, which covers ordinary quits and updater restarts,
an AppImage launch (`APPIMAGE` + `APPDIR` set) stops its direct child
processes whose executable lies under the mount, before the process
exits. The WebKit helpers are direct children of the main process. The
new module is `appimage_exit.rs`:

- each child is pinned with a **pidfd**, then checked again as a child
of this process running from the mount, so a process that reused a PID
is never signalled;
- `SIGTERM`, then pidfd polling up to 1.5 s, then `SIGKILL` and a final
0.5 s;
- children running from elsewhere (the shared OpenCode service) keep
running;
- `.deb`/Electron, Windows and macOS are unchanged; kernels without
`pidfd_open` keep the previous behaviour.

`dev-docs/TAURI_UPDATER.md` documents the exit contract and its
validation.

## Validation

- **Linux unit tests** (new): `/proc/<pid>/stat` parsing with command
names containing spaces and parentheses, and a real child started from a
temporary "mount" that is stopped while a child started elsewhere keeps
running. They pass on Ubuntu 24.04.
- **Real AppImages** on the Ubuntu 24.04 VM, repacked with appimagetool
around the released AppDir, once with the original binary and once with
the fixed one:

| Trial | Original binary | Fixed binary |
|---|---|---|
| Secondary `WebKitWebProcess` frozen (`SIGSTOP`) before closing the
window | survives the main process, i.e. outlives the mount | stopped
before the main process exits |
| Normal quit | 0.06 s, no leftover | 0.08 s, no leftover, no crash
report |

  The shared OpenCode service kept running throughout.
- `cargo check -p codenomad-tauri` passes on Windows.
- Running the whole crate suite on the VM, only the cross-host election
tests (they need the full checkout with `tsx`) and the zsh test (zsh not
installed) failed. Linux CI has both.
2026-10-22 12:36:48 +02:00
Pascal André
7a1fc90310
docs(agents): keep AGENTS.md to universal rules and agent docs to the current state (#1082)
AGENTS.md loaded about 33,000 tokens into every agent session, subagent and reviewer, mostly per-feature contracts, and the agent documentation around it had accumulated duplicates, dated logs and history of removed designs. This PR makes AGENTS.md a short set of rules that apply everywhere plus an index of area documents, and leaves AGENTS.md, dev-docs and the repository skills describing only the current state.

Three commits, reviewable separately:

1. **Move (lossless).** Every per-area rule moved verbatim from AGENTS.md into its area document (existing ones, or six new ones: UI_CONVENTIONS, UI_SHELL, COMPOSER, TRANSCRIPT, SETTINGS, PROJECTS). A script split the old file into 208 units, required one destination for each and checked all 199 content units verbatim at their destination. AGENTS.md went from 134,000 to 16,000 characters.
2. **Trim.** A per-area ponytail review removed rules stated two or three times (keeping the more precise copy), dated validation logs, per-version qualification sections below the current minimum, abandoned designs and self-references; seven documents whose surviving rules live elsewhere were deleted. It also fixed statements the code contradicts (locale registration, the Mission play/recovery condition, plugin package updates, cleanup progress UI, timeline index persistence, automation restart/targets, stale pins and minimums).
3. **Current state only.** The new AGENTS.md rule: no history (removed features and their names, former behavior, provenance, dated logs) and no denials; correct a contradicted statement at its source. DEVELOPER_MODE.md is merged into BROWSER_AUTOMATION.md and every Developer Mode mention is gone (including the README and the browser help page in ten locales). Names that only made sense against older designs are renamed with every reference updated, MISSIONS_ENVIRONMENT_ADMISSION is folded into SESSION_ENVIRONMENT, and INDEX lists every document. The server npm README now states the real OpenCode minimum (2.1.2).

AGENTS.md is now about 14,000 characters (roughly 3,500 tokens); AGENTS.md, dev-docs and the vendor README went from about 11,200 lines to under 4,700.

Validation: every relative Markdown link and every cited dev-docs path resolves; no reference to a renamed or deleted document remains; `npm run test:help` passes for all ten locales.

Review: three independent GPT-6.1 Sol (high) reviews by area found binding rules lost in the trim and current-state passes; 25 were restored as present-tense facts or prohibitions and four contradicting statements corrected, each checked against code. A Fable 5.1 gatekeeper review of the whole PR passed; its follow-up routes session recovery from the index, points to dev-docs/INDEX.md and documents the family identity read.
2026-10-21 16:59:43 +02:00
Pascal André
4e0b62bc7f
build: ship the Linux deb from Electron and the Windows installer without a ZIP (#955)
## Summary

One download per need, with fewer duplicate or unused assets:

| | Before | After |
|---|---|---|
| **Linux Electron** | portable `tar.gz` | **`.deb`** |
| **Linux Tauri** | `.deb` + AppImage | **AppImage** (self-updating) |
| **Windows Tauri** | ZIP wrapping the installer + the same installer as
`.exe` | **`.exe`** only |
| **Updater signatures** | `latest.json` + separate `.sig` assets |
**`latest.json` only** |

### Linux: Electron deb, Tauri AppImage
- The Electron deb installs as root, so electron-builder's post-install
script gives Chromium's `chrome-sandbox` the `root:root` ownership and
`4755` mode that a user-extracted archive cannot. Electron starts **with
its sandbox** and without any manual step. This fixes #541 properly.
- The package is named `codenomad` and **conflicts with / replaces** the
former Tauri deb (`code-nomad`). Both ship
`/usr/share/applications/CodeNomad.desktop`, so without this, installing
over an existing Tauri deb would fail on the shared file.
- The deb declares Electron's full runtime closure. electron-builder's
default `Depends` omit `libgbm1` and ALSA, which previously arrived only
through Recommends. ALSA is declared as `libasound2t64 | libasound2`,
because `libasound2` is a multi-provider virtual package on Ubuntu
24.04.
- Debian versions: dev builds (`<stable>-dev-<date>-<sha>`) become
`<stable>+dev.<date>.<HHMMSS commit time>.<sha>`, so they install as
upgrades after their stable base and in order on the same day; other
prereleases keep electron-builder's `~`, which sorts before the release.
The workflow rewrites the control `Version` with `dpkg-deb` when it
differs; PR and stable builds rewrite a copy to a synthetic dev version
and check that fields, files, modes, owners and control members are
preserved.
- Tauri Linux builds only the AppImage (`tauri build --bundles
appimage`).
- No unused electron-updater metadata is generated (`publish: null`).

### Windows: installer only
- The Tauri NSIS installer is published directly as
`CodeNomad-Tauri-windows-x64-<version>.exe`. It is also the signed
updater payload, so signed builds upload it once.
- The ZIP that wrapped the same installer is no longer published.
- The WinGet automation defaults now point to the `.exe`.

### Updater signatures
- The updater reads each signature from `latest.json`, never from a
`.sig` asset. Staging still verifies every signature and its trusted
comment and records it in the receipt, and publication still requires
every payload's uploaded digest to match.
- Releases no longer carry `.sig` assets, and staging deletes any left
on a rebuilt tag by older builds.

Rebuilt tags also delete obsolete Electron `tar.gz`, Tauri deb and
Windows ZIP assets.

### Docs
- **README:** shows the deb and AppImage commands. The AppImage uses the
static type2 runtime, so it needs no `libfuse2`;
`--appimage-extract-and-run` covers environments without FUSE. The
Wayland/NVIDIA command now targets the AppImage.
- **BUILD.md, `TAURI_UPDATER.md` and the WinGet guide** describe the new
formats.

## Migration note

Installing the Electron deb over a Tauri deb switches the app from Tauri
to Electron:
- `code-nomad` and `codenomad-tauri` are removed, and the launcher
becomes `CodeNomad`;
- server configuration is shared, but desktop window state is not
migrated.

Tauri deb users never had in-app updates, so the release notes should
say this. The AppImage is their Tauri option.

## Validation

- **Electron deb packaging (CI):** checks package, conflicts, replaces
and version metadata, the contents, the post-install `chmod 4755` step,
the resources/asar smoke checks and `ldd`.
- **Electron deb install (CI):** installs on Ubuntu 24.04 **without
recommends**, so `ldd` proves the declared `Depends` are enough. It
installs over a stand-in `code-nomad` package, then checks that the old
package was replaced, the owner of the desktop entry,
`/usr/bin/CodeNomad`, `chrome-sandbox` `root:root 4755` and shared
libraries.
- **Version mapping:** the mapping, the ordering against stable, later
releases and same-day builds, and both repack branches were checked with
dpkg 1.22.6 on Ubuntu 24.04. The repack keeps the control fields and
root ownership.
- **Dependencies:** every declared dependency resolves on Ubuntu 24.04,
either as a real package or through its t64 provider.
- **Updater scripts:** `updater-artifacts.test.mjs` passes, including
signature-in-manifest publication with no staged `.sig`.
- **Workflow:** the YAML parses.
- **Independent review:** no blocker. The major finding (missing deb
dependencies) and the minor ones (dev version ordering, stale BUILD.md
and README command, unchecked exit codes, unused updater metadata) are
addressed.

## Follow-ups outside this PR

- **WinGet:** the `WINGET_WINDOWS_ASSET_NAME_TEMPLATE` repository
variable still pins the `.zip` name and overrides the new default.
Before the next stable release:
  - update it to `.exe`, or delete it;
- check that the automated `winget-pkgs` PR declares a direct `nullsoft`
installer without the old ZIP fields, and correct it if not.

Until then, a stable release's WinGet job waits for the ZIP and fails
without side effects.
- **Ubuntu desktop trial:** test the Electron deb on a real Ubuntu
desktop.
2026-10-14 16:45:49 +02:00
Pascal André
dac236c8fd
feat(tauri): signed cross-platform updates with stable and preview feeds (#786)
Desktop Tauri builds now update themselves in place on Windows (signed NSIS), macOS x64/arm64 (signed .app.tar.gz) and Linux x64 (signed AppImage), and Linux gains AppImage distribution. The application no longer shells out to `winget upgrade`; WinGet remains a distribution channel.

Updates follow the same two feeds as the server notifications: `stable` offers GitHub's latest final release when it is newer; `preview` (dev + release) offers the newest publication of any kind, ordered by publication because dev labels reuse their base version. The saved `server.updateFeed` selects the feed (otherwise the build label). Only releases carrying their signed `latest.json` are installable, so releases still building are never offered.

Native code owns URLs, keys and installers: renderers pass only the feed and, after an explicit "Install and restart", the accepted version. The manifest must announce exactly the selected version and `requireSignedVersion` binds it to the signature. Full verification precedes shutdown; installation and restart are admitted atomically with an all-window flush; timeouts, cancellation and failed cleanup discard the installer without replay. Failures belong to the installing window, survive reloads and are reported once; concurrent checks report "busy". Menu actions reach only the focused local application document. The vendored tauri-plugin-updater 2.12.0 runs its Windows exit callback only after a successful installer launch.

Publication stages four verified payloads (signed version required) and publishes each release's `latest.json` only after validating every receipt and uploaded digest; rebuilt tags drop a stale manifest first. The signing private key reaches only `tauri build` steps. Unsigned builds and PR runs publish nothing. Debian/RPM and unsupported formats use the release-page fallback. The Tauri CLI minimum is 2.11.5.

Validated by eight rounds of independent review (zero findings on both native/CI and renderer/docs), 268 native tests, script and browser regressions, real signed update trials on Windows and Linux (from loopback endpoints, before feed selection), and published-package trials on Windows, Linux and macOS x64. A production-signed dev prerelease, macOS signed updates and draft restoration across an update remain to be exercised.
2026-10-14 12:18:36 +02:00