Commit graph

33 commits

Author SHA1 Message Date
Pascal André
fb22f6e969
feat(shell): close panels that lose their room and reveal list choices (#1144)
In a window too narrow for a docked panel, the panel floated over the
conversation and covered the messages. On phones and narrow windows, the
floating session panel also stayed open over the session the user had
just chosen, so every choice needed a second tap to see it.

A docked panel that loses its room (the window narrows, or the other panel
opens) now closes instead of floating. It returns when the room does,
unless the user opened it meanwhile; a panel closed by the user stays
closed. Minimizing a window briefly reports a tiny viewport, so without
the return every minimize would lose both panels. A panel opened without
room still floats over the conversation, as before.

Choosing a session, the shown one included, or Instance Info from the
floating session panel's list now closes that panel to show the choice;
reopened, it still marks the shown session. Back and Forward select
without closing it. Docked panels are unchanged.

Implementation: `useDrawerChrome` closes an open drawer when its docked
state turns false and marks it as awaiting room; an effect reopens it,
the session panel first and the other only if both then fit docked, and
opening a drawer clears the mark. The layout inputs are shared by the
initial layout, the docked state and the return. On touch tablets each
rotation's orientation memory decides, counting a panel closed for room
as open in the orientation it leaves. A panel closed by the layout hands
focus to its toggle when it last held focus, even when its re-render
already removed the focused control: the shell remembers which panel's
content last took focus until focus deliberately moves to the page. The
sidebar's list selection handler closes the floating session panel; the
history buttons keep using the ordinary selection path.

Validation: shell-navigation browser tests cover closing and returning
(narrowing, a simulated minimize, the other panel taking the room, a
user-closed panel staying closed, a panel opened without room floating),
the phone list, re-selection and Back/Forward behaviour, and each
panel's focus hand-off; drawer-width covers a 1024x768 tablet rotating
twice. All fail on the previous code. Help is updated in all ten
languages.
2026-10-25 21:52:03 +01:00
heunghingwan
13450749b8
feat(preview): open loopback conversation links in the web preview (#1119)
Some checks are pending
Publish help / build (push) Waiting to run
Publish help / deploy (push) Blocked by required conditions
When the frontend is reached from a browser, an agent-printed
`http://localhost:3000` link points at the browser's machine, not the
backend host that runs the dev server, so clicking it opens a dead tab.
Many development apps also break inside the opaque preview, where
`localStorage`, `sessionStorage` and `document.cookie` throw.

A plain left click on an explicit http(s) loopback link (`localhost`, a
literal `127.x.x.x`, `[::1]`, `0.0.0.0`) in session Markdown now opens it
in that session's web preview. Middle and modifier clicks, external
links and Markdown rendered without a session keep the browser default.
Interception waits until the server meta is known and stays off where
previews are unavailable, such as Remote Control while the host keeps
web previews off. Intercepted links without an author title get a
localized tooltip naming the Ctrl/⌘-click and middle-click escape hatch.

The preview runtime bridge gives opaque documents whose storage access
throws one stable in-memory `localStorage`, `sessionStorage` and
`document.cookie` object each for the current page load, with property
access and enumeration, `Max-Age` taking precedence over `Expires`, and
detection by reading alone, so working storage (a per-token preview host
opened on its own) stays native. The help in all ten locales and the
preview paragraph of architecture.md describe both.

Validation: the loopback predicate unit test, the runtime-bridge shim
tests, the Markdown loopback-link browser test, the server preview,
proxy and Remote Control suites, both typechecks and the help checks
pass; the interception and the shim were checked in Chromium against
the real preview proxy and Remote Control surface.
2026-10-25 15:46:39 +01:00
Pascal André
cfd47f3af1
docs(help): state that running one-time missions continue without the backend (#1137)
## Summary

The Missions help (Lifetime and service restarts) said one-time
coordination depends on the CodeNomad backend and told users not to
expect new dispatch while it is closed. In native task mode, the
default, a running one-time mission keeps going without CodeNomad: the
coordinator, its native subagent tasks and the mission tools all run in
OpenCode.

The **CodeNomad backend** row now says so, and lists what does wait for
the backend: assigning tasks to independent sessions and delivering
their reports to the coordinator (already admitted independent sessions
keep running in OpenCode), missions the coordinator prepares, stopping a
retired task's conversations, recovery, Mission Control, and continuing
a one-time mission after an OpenCode service restart.

## Why this is accurate

- `opencode/missions/desktop-plugin.ts`: `followPresence(..., () =>
retainMissionWork(...))` keeps the Missions tools registered after
backend presence ends while a mission is active
(`missions/lifetime.ts`).
- Native declarations, reports and briefings write the plugin's own
journal. Only the transport calls cross the authenticated loopback
bridge (`sendMissionInput`): root creation, assignment prompts and
report notifications for independent tasks, `prepare`, `observeTask`,
`interruptTask`, recovery, lifecycle and cleanup
(`missions/control.ts`).
- `desktop-plugin-presence.ts` registers the tools only with presence,
so after an OpenCode restart they return once CodeNomad is back.

## Changes

- `docs/help/<locale>/missions.md`: the **CodeNomad backend** row in
English and the nine translations, reusing each locale's existing terms.

## Validation

- `node scripts/test-help.mjs`: full multilingual check, 9/9 pass.
2026-10-25 12:42:00 +01:00
Pascal André
4b163382bc
feat(remote-control): serve web previews to paired devices (#1131)
Remote Control blocked `/previews/*`, so a phone or browser paired through
the tunnel had no web preview at all: the entry was hidden and the agent's
dev server stayed out of reach. Previews now work there, behind a host
setting that is on by default.

A preview's sandboxed document sends neither the device cookie nor a usable
`Origin`, so the Remote Control gate admits a live `/previews/<token>/…`
capability on its token alone, as local listeners already do, while the
host's Web previews switch is on. The ingress, which refused every
WebSocket, now admits preview upgrades only: `admitRemoteUpgrade()` parses
the target exactly as the preview proxy does and the manager hands the
socket to the application's path-preview upgrade handler, never to the
shared upgrade event (so `/previews/<token>/../../sidecars/…` cannot reach
another handler). Every other upgrade still gets 404.

The switch lives in Settings → Remote Access → Remote Control. Only the
host can change it (`PUT /api/remote-control/previews`); the registry
stores only the opt-out. Turning it off refuses later preview requests,
including the Referer-based root-relative asset fallback, and closes live
preview WebSockets; remote `/api/meta` then reports `previewsUnavailable`,
which hides Open web preview on paired devices.

Exposing previews through a public tunnel makes their address a bearer
credential, so path-proxied preview responses now carry
`Content-Security-Policy: sandbox` with the iframe's own flags (one shared
constant, `previews/sandbox.ts`) and `Referrer-Policy: same-origin`, on
every content type. A preview document opened outside its iframe stays an
opaque origin instead of running with CodeNomad's origin and device cookie,
and the capability path stays out of Referer headers to other sites. Such a
standalone document then behaves like the preview iframe: its root-relative
resources rely on the runtime bridge's path rewriting, without the Referer
fallback. The per-token preview.localhost host keeps its own origin and
headers.

Help (Remote Control, mobile and client overview pages, all ten locales),
dev-docs/REMOTE_CONTROL.md, architecture.md and UI_SHELL.md describe the
behavior; the switch label and caption are translated in all locales.

Validation: a Remote Control surface test drives the real application
through the ingress: a preview page and an `Origin: null` form post work
without a device cookie and carry the sandbox and referrer headers, unknown
tokens and non-preview or dot-segment upgrades get 404, a preview WebSocket
upgrades, a paired device cannot change the switch, and turning it off
closes the live WebSocket, refuses the preview and its Referer asset
fallback, and flags the metadata.
Registry, metadata and proxy tests cover persistence of the opt-out, the
flag and the headers per entry point; browser tests cover the switch and
the conversation header with previews on and off.
2026-10-24 20:25:25 +02:00
Pascal André
036a008c2c
fix(ui): restore the web preview entry on mobile, hide it through Remote Control (#1128)
Phones lost their only entry to the session web preview when the
conversation actions were reorganized: on mobile, the Full screen action
took the preview's place instead of joining it. Previews still work there
through direct (LAN) access, which serves them through the capability
iframe proxy. Remote Control, on the other hand, serves no preview content
(`/previews/*` is host-only), so its clients, desktop browsers included,
were offered a preview that could only fail to load.

The conversation header now offers the preview on every platform, inline
and in its overflow menu, with Full screen added on mobile. `/api/meta`
reports `remoteControl: true` for requests that arrived through the Remote
Control ingress, `lib/server-meta.ts` exposes it as `remoteControlClient()`,
and the shell hides the preview entry for those clients. The shell asks for
the server metadata on mount; the read is cached and shared with the other
callers, and the entry stays visible until a Remote Control answer arrives.

The Remote Control help page says, in all ten locales, that conversation
actions hide Open web preview there; dev-docs/UI_SHELL.md records the
convention.

Validation: a header-windows browser test drives the real conversation
header on a Pixel 5 profile and on desktop Chrome, with and without the
Remote Control metadata, and checks the inline buttons and the overflow
menu. It fails on the previous shell (no preview entry on the phone, and a
preview entry kept through Remote Control). meta.test.ts and the Remote
Control surface test cover the flag locally and through the real ingress
(the surface fixture's metadata gains the `host` field /api/meta needs).
The UI unit tests run by CI, the server routes, proxy and Remote Control
suites, the help checks and both typechecks pass.
2026-10-24 15:07:18 +02:00
Pascal André
39c0fa5833
chore(opencode): qualify and recommend runtime 2.1.4 (#1126)
## Summary

- Qualify OpenCode **2.1.4** (latest stable, 23 October) with the
existing locked **2.1.0 client/plugin** dependencies, and **recommend**
it.
- Keep the technical **minimum at 2.1.2**: no consumed contract changed,
so the minimum has no new reason to move.
- 2.1.2 and 2.1.3 daemons stay ready and get an optional upgrade offer.
2.1.4 is the `tested` version. Newer and custom labels keep
authenticated contract recognition.
- The reviewed-contract fast path lists the 2.1.2 minimum and the 2.1.4
target.
- The recommended version is updated in the help (all ten locales), both
READMEs, `CONTRIBUTING.md`, `dev-docs/OPENCODE_COMPATIBILITY.md` and the
architecture skill.

## Contract review (v2.1.3 → v2.1.4, 72 commits)

- **Authenticated OpenAPI:** 121 → **124 paths**, 146 → **149
operations**. No operation changed. The changes:
  - added `POST /api/experimental/client-mcp/token`;
  - added `GET /api/experimental/client-mcp` (WebSocket);
  - added `POST /api/debug/heap-dump`;
- `Config.InfoEncoded` gains the opt-in
`experimental.background_compaction`.

The session event (`V2EventEncoded`) and message schemas are unchanged.
None of the new routes is on the instance proxy allowlist, and the proxy
relays WebSockets only for SideCars and previews.
- **Published declarations:**
  - the client adds `mcp.client.token` and `debug.heapDump`;
- the schema and plugin changes are type refactors and unused-export
removals, among them `SessionID.descending`, `SessionEvent.Source` and
`PromptInput.FileAttachment.create`; `Session.Message.ID.fromEvent` now
takes any string instead of an event ID. CodeNomad uses none of these,
and every host module its native plugin bundles import
(`@opencode/plugin`, `/effect`, `/promise/adapter`, `/rpc`,
`@opencode/schema/location`, `/session`) still exists.
- **MCP elicitation forms**
([#58184](https://github.com/anomalyco/opencode/pull/58184)): an MCP
server asking for input during a tool call now creates a global-session
Form whose `form.created` event carries the server's location.
CodeNomad's live path requires that location for global Forms and shows
them as a **Project request**. Review found that the live path dropped
the location from the queued Form, so its reply lacked
`x-opencode-directory` and failed for a worktree or subdirectory until a
pending-request rescan. **#1127** fixes that; recovery already kept it.

A native probe (minimal stdio MCP server, a real Code Mode `execute`
call, CodeNomad-style reply) gives:

| Runtime | Form listed for the location | `form.created` carries the
location | Reply completes the tool |
  | --- | --- | --- | --- |
  | 2.1.3 | yes | **no** | yes |
  | 2.1.4 | yes | **yes** | yes (`action: accept`, `channel: stable`) |

On 2.1.3 such a request only appeared after a pending-request rescan; on
2.1.4 it appears live. The probe sends the location header itself, so it
checks the runtime contract; #1127 covers CodeNomad's live reply path.
- **Other changes** are covered by the native suites below:
- compaction (oldest-first shortening, ignored buffers, overflow
detection);
  - the agent's model for sessions without one;
  - V2 agent files;
  - V1 global-session import attribution;
  - MCP lifecycle fixes.
- **Live check:** this machine's shared daemon runs 2.1.4, and the
CodeNomad desktop session works through it.

## Isolated Windows qualification

Base: dev `9ac792fff`; Node **24.20.0**; exact official CLI **2.1.4**;
unchanged locked client/plugin **2.1.0**.

- **Build and checks:** production server/UI/native plugin builds,
server and root typechecks, native bundle guard.
- **Native runtime (9/9):**
  - Code Mode job ordering, plugin runtime, automation;
  - complete pruning, history, ownership, worktree and event relay;
  - loaded-only pending snapshot;
- Missions contract, family control, native and Compare/Wayfinder
trajectory.
- **Sessions (15/15):** environment, fork, aside, interrupt, lifecycle,
blank cleanup, prompt skills, provider usage/accounts/selection, service
usage, web search, plugin packages, output limit, Promise-tool failure.
- **Recurring (5/5):** all **11 recurring Mission journeys**, subagent
depth, **2 MiB** compaction, permission receipts.
- **Migrations (10/10):**
- history from **2.0.3, beta-19271, 2.0.32, 2.1.0, 2.1.1, 2.1.2 and
2.1.3** to 2.1.4;
  - Git degraded/init;
  - temporary workspaces.
- **Installation (3/3), with the 2.1.4 recommendation:**
  - fresh install of the 2.1.2 minimum;
- native **2.0.15 → 2.1.4** upgrade, after which the already-running
daemon keeps its PID and 2.0.15 version;
  - explicit OpenCode **1.18.10 → 2.1.4** replacement.
- **After the recommendation change:**
- admission, transport, update, installation and instance proxy units
**116 passed, 1 POSIX-only skipped**;
  - full server **2,471 passed, 8 skipped**, no failure or cancellation;
  - UI units **1,699/1,699**;
  - focused browser **126/126**;
  - native pruning with the real rendered UI;
  - the exact CI invocation of the Code Mode regression (without tsx);
  - ten-locale help checks (**9/9**).

This is Windows automation evidence. In CI, `latest` resolves to 2.1.4:
the compatibility job runs the native suites against the minimum and
latest on Windows, and the migration and Git-degraded certification runs
them on Ubuntu, Windows and macOS. WSL, packaged desktop,
installers/updater end-to-end and real mobile devices keep their
separate release gates.
2026-10-24 13:34:36 +02:00
Pascal André
5994944f84
feat(settings): manage desktop data profiles and drop the startup question (#1123)
Choosing a desktop data profile needed `CODENOMAD_PROFILE` or, on a first
packaged launch with several restorable profiles, a native question that
blocked startup. Settings only named a non-default profile and offered to
delete all the other profiles at once.

Settings > General > Restore previous state now ends with one folded line,
"Data profile: <name>". Unfolded, it lists every profile on the computer,
the open one first and the others by last use, each with its size, creation
date and last use. A radio chooses the profile the next launch opens (any
profile of this configuration; choosing the open one again cancels), "New
profile" creates one by name, "Restart in <name>" restarts into the choice,
and each other profile has its own delete button with a confirmation. A
launch pinned by `CODENOMAD_PROFILE`, its alias or a development run shows
the profiles without letting the user choose; deletion still works.

Without a remembered choice, a packaged launch no longer asks: it opens the
most recently used of the default, `dev` and `dev-v2` profiles (ties go in
that order) and remembers it, as it already did with zero or one candidate.
A launch that finds the selection lock held opens the detected profile
without remembering it instead of waiting.

Implementation: the existing host listing gains the open profile, creation
(folder birth time, links not followed) and last-use (newest client-state
write) dates, the remembered next-launch profile and whether the launch is
pinned. Two host operations are added on both hosts, granted to local and
Preferences windows only and serialized with list and delete: choosing the
next launch's profile (validated with the `CODENOMAD_PROFILE` grammar,
written under the selection lock, overwriting this configuration's entry,
creating a new profile's scope folder inside the lock and removing it again
if the write fails) and restarting through the graceful developer-mode
relaunch. Electron's throwaway question storage, relaunch and private
hand-offs are removed; it still sweeps leftover question folders and drops
the two hand-off variables an older build's relaunch can carry. Tauri keeps
only the invalid-profile error dialog. `CODENOMAD_DESKTOP_PROFILE` and the
server's `desktopProfile` meta field are removed, since nothing displays them.

Only a profile whose name leads back to its own folder can be chosen, so a
folder the deprecated alias made under another profile's name (such as
default-<hash>) is listed under its stored name but not offered. The
listing reports the next launch's profile only when the launch would open
it (the launch's own remembered-profile check), and Restart in <name> lists
again first and restarts only while that still names the shown profile.
Choosing with the keyboard keeps focus on the chosen radio.

Validation: UI browser tests for the manager (listing, choose and restart,
new profile and name validation, single deletion, macOS note, pinned
launches, plain browser), Electron native tests (listing dates, current,
next launch, pinned, set-next-launch outcomes, relaunch wiring, preload,
automatic choice, lock and sweep) and Tauri tests (the same cases, grants
and command wiring). Developer doc and help (10 languages) updated.
2026-10-24 09:46:30 +02:00
Pascal André
628a80a5c9
feat(settings): give Updates its own card and the data profile a Startup row (#1122)
Some checks are pending
Publish help / build (push) Waiting to run
Publish help / deploy (push) Blocked by required conditions
## Summary

- **Updates** is its own card again (About → Updates → Diagnostics →
connectivity), subtitle restored in 10 languages: latest version,
install/download + Refresh, then the **update feed** with its help and
save error last. Rows resuming after actions open with their separator.
Help speaks of the Updates card again (10 locales).
- **Data profile** moves from About to General's startup card (Restore
previous state), above Other saved profiles; still only for an explicit
non-default profile (`current-profile-settings-row.tsx`, shared by
inline settings and Preferences). Label key moved in 10 languages;
desktop help and `DESKTOP_DATA_PROFILES.md` point to the new place.
- The Clear saved startup state button keeps space above the rows'
separator.

## Validation

- `server-info`: card order, feed out of About, after the actions, with
its separator; no data profile in Info.
- `other-profiles`: running profile named above other saved profiles,
clear of the button, Electron and Tauri Preferences; default profile
never named.
- `desktop-updates`, `npm run test:help`, UI typecheck.
2026-10-24 08:44:52 +02:00
Pascal André
c82dd8fbf6
fix(notifications): show Windows system notifications and keep their permission (#1109)
Some checks are pending
Publish help / build (push) Waiting to run
Publish help / deploy (push) Blocked by required conditions
## Summary

System notifications on Windows: everything about them in one PR.

1. **Windows data profiles never showed system notifications**
(`fix(windows): show system notifications from every data profile`).
Tauri runs each data profile, such as **dev**, under its own
AppUserModelID (`…client.scope.s<hash>`), and its notifications carry
that ID; Windows shows a desktop app's notifications only under an ID it
knows, and the installer's Start menu shortcut carries the stable ID
alone. Windows recorded every notification (100 on the affected machine)
and displayed none. Every launch now registers the running ID under
`HKCU\Software\Classes\AppUserModelId\<identifier>` (`DisplayName`
CodeNomad, `IconUri` the bundled web app icon
`resources\server\public\pwa-192x192.png`, which the packaged-resource
smoke check requires; `windows_notification_identity.rs`). Electron
always uses the stable ID.
2. **The permission was lost at every restart on Windows.** The
notification plugin's page script reports *denied* there until the page
requests permission, though Windows never asks, so Settings asked again
after each restart. The check now asks the native plugin command, which
answers granted on desktop: granted once, kept.
3. **Notifications were held back whenever the window was visible**
(`fix(notifications): hold system notifications back only while
focused`). With **Notify when the app is focused** off (the default), an
open window stays visible behind other applications or on another
screen, so nothing was sent unless CodeNomad was minimized. Delivery is
now skipped only while the window is visible and focused
(`document.hasFocus()`). The setting's subtitle and the Settings help
say *focused* in all ten languages; `dev-docs/SETTINGS.md` records the
rule.

Out of scope by decision: several CodeNomad windows open on the same
project may each send the event's notification (as they already do when
the setting is on), and focus inside a browser preview counts as leaving
the page.

## Validation

- Affected machine: no profile notification showed before; after
registering its ID as above, a notification sent under it showed in the
notification centre under CodeNomad.
- `cargo test --bins -- windows_notification_identity` (writes the key,
reads back both values).
- `tests/browser/system-notification-focus.test.ts`: real routed
notification from a visible page; nothing while focused, one once focus
is lost, one while focused with the setting on; red on the previous
visibility check.
- `npm run typecheck --workspace @codenomad/ui`, `npm run test:help`.
2026-10-23 04:03:26 +02:00
Pascal André
019203e31e
feat(notifications): show Clear all and Settings as icons in the history (#1107)
## Summary

- The notification history's **Clear all** and **Settings** text buttons
become icon buttons (red trash can, gear), like the panel's close
button.
- Their labels stay as tooltips (`title`) and accessible names
(`aria-label`), with the existing translations. **Mark all as read**
keeps its text.
- Shared `window-icon-button` geometry; the existing toast-history
action classes keep hover, danger and disabled styling.

## Validation

- Screenshots of the real `ToastHistoryPanel` with two notifications,
dark and light themes, including the hover state.
- `npm run typecheck --workspace @codenomad/ui`.
2026-10-23 02:19:35 +02:00
Pascal André
7709860b61
feat(desktop): give macOS windows native traffic lights and one menu bar (#1105)
## Summary

On macOS, CodeNomad showed **two menu bars**: the system menu bar and,
inside the window, the UI title bar with the same
File/Edit/View/Window/Help menus plus Windows-style buttons at the
right.

macOS windows now follow the platform convention (as in VS Code or
Slack):
- The menus live only in the **global menu bar**.
- The **native traffic lights** sit at the left of CodeNomad's title
bar, which keeps the theme colours and shows the CodeNomad mark
(Preferences: its title) after them.
- The main window's title bar now also holds **Settings, the
notification history bell and Remote Access**, so the project tab bar
gives that space to the tabs (and drops its separator).
- Windows, Linux and browsers are unchanged.

## Implementation

- **Tauri**: new `title_bar.rs` configures local application and
Preferences windows: overlay title bar with a hidden title on macOS, no
decorations elsewhere.
- **Electron**: `titleBarStyle: "hidden"` on macOS, `frame: false`
elsewhere, for the same two windows.
- Both keep the traffic lights at their **standard place**. Tauri loses
a custom `traffic_light_position` once the window is shown and resized
(it came back to the default on the VM), so on macOS the UI bar takes
the native **28 px** title bar height instead, which centres them, and
leaves their 60 px clear in every writing direction.
- `NativeTitlebar` drops the in-window menus and window buttons on
macOS. The actions move from `InstanceTabs` into a shared `AppActions`
component, rendered by the title bar on macOS and by the tab bar
elsewhere (`appActionsInTitlebar`).
- The native close button of Preferences goes through the existing
guarded close request, like Cmd+W.
- `dev-docs/UI_SHELL.md` describes the per-platform title bar; the
Settings help says where the gear and bell are on macOS, in all ten
languages.

## Validation

- **macOS 15 test VM**, Electron app and Tauri build patched with this
change: traffic lights and mark centred in the 28 px bar in both hosts
(measured from screenshots), no in-window menus or buttons, actions at
the right end opening Preferences, Remote Access and the notification
history below the bar, Preferences title after the lights, and its red
button closing the window.
- New `tests/browser/macos-titlebar.test.ts` renders the real title bar
and tab bar as macOS and as Windows.
- `tab-chrome`, `new-instance-launcher`, `opencode-setup` and
`notification-icon-badge` browser tests (58 pass, 1 skipped), `npm run
test:help`, `npm run typecheck` pass.
2026-10-23 02:18:04 +02:00
Pascal André
01b20b5b78
chore(opencode): qualify and recommend runtime 2.1.3 (#1098)
Some checks are pending
Publish help / build (push) Waiting to run
Publish help / deploy (push) Blocked by required conditions
## Summary

- Qualify OpenCode **2.1.3** (latest stable, 22 October) with the
existing locked **2.1.0 client/plugin** dependencies, and **recommend**
it.
- Keep the technical **minimum at 2.1.2**: nothing CodeNomad consumes
changed, so the minimum has no new reason to move.
- A 2.1.2 daemon stays ready and gets an optional upgrade offer. 2.1.3
is the `tested` version. Newer and custom labels keep authenticated
contract recognition.
- The reviewed-contract fast path lists 2.1.3 beside the 2.1.2 minimum.
- The recommended version is updated in the help (all ten locales), both
READMEs, `dev-docs/OPENCODE_COMPATIBILITY.md` and the architecture
skill.

## Contract review (v2.1.2 → v2.1.3, 66 commits)

- **Authenticated OpenAPI:** 119 → **121 paths**, 144 → **146
operations**. Every consumed operation is unchanged. The only changes:
  - added `GET /api/experimental/voice/models`;
- added `POST /api/experimental/voice/session/{sessionID}/instructions`;
- an optional hosted `session.id` in the `POST
/api/experimental/voice/session` response.

  None of these is on the instance proxy allowlist, so they stay closed.
- **Published declarations:** the client adds only `voice.models` /
`voice.instructions`. The schema changes only typing (agent `color`
decoding, `Durable` as a tagged union). The plugin is unchanged.
- **Windows managed services** move their default port from **45278 to
28894** ([#57428](https://github.com/anomalyco/opencode/pull/57428)).
CodeNomad discovers the daemon through the selected CLI's `service
status` and the service registration, and assumes no port.
- Live check: this machine's shared daemon was upgraded to 2.1.3 and
restarted on 28894, and the running CodeNomad desktop kept working
through it.
- **Core:** mostly refactors, plus behaviour fixes that the native
suites below exercise:
  - compaction summary output limit;
  - `keepID` validated as a message ID;
  - MCP media kept out of attachments;
  - only changed instruction entries resent;
  - relative-directory V1 migration;
  - legacy `apply_patch` permission mapped to `edit`.

## Isolated Windows qualification

Base: dev `333d73452`, rebased on `853b14f2f` (#1097) for the final UI
unit run; Node **24.20.0**; exact official CLI **2.1.3**; unchanged
locked client/plugin **2.1.0**.

- **Build and checks:** production server/UI/native plugin builds,
server and root typechecks, native bundle guard.
- **Native runtime (9/9):**
  - Code Mode job ordering, plugin runtime, automation;
  - complete pruning, history, ownership, worktree and event relay;
  - loaded-only pending snapshot;
- Missions contract, family control, native and Compare/Wayfinder
trajectory.
- **Sessions (15/15):** environment, fork, aside, interrupt, lifecycle,
blank cleanup, prompt skills, provider usage/accounts/selection, service
usage, web search, plugin packages, output limit, Promise-tool failure.
- **Recurring (5/5):** all **11 recurring Mission journeys**, subagent
depth, **2 MiB** compaction, permission receipts.
- **Migrations (9/9):**
- history from **2.0.3, beta-19271, 2.0.32, 2.1.0, 2.1.1 and 2.1.2** to
2.1.3;
  - Git degraded/init;
  - temporary workspaces.
- **Installation (3/3), with the 2.1.3 recommendation:**
  - fresh install of the 2.1.2 minimum;
- native **2.0.15 → 2.1.3** upgrade, after which the already-running
daemon keeps its PID and 2.0.15 version;
  - explicit OpenCode **1.18.10 → 2.1.3** replacement.
- **After the recommendation change:**
  - admission/transport/update/setup units **36/36**;
  - full server **2,463 passed, 8 skipped**, no failure or cancellation;
  - UI units **1,694/1,694** (with #1097);
  - focused browser **126/126**;
  - native pruning with the real rendered UI;
  - the exact CI invocation of the Code Mode regression (without tsx);
  - ten-locale help checks.

The UI unit run on `333d73452` itself had two failures, in
`instances-pending-compaction.test.ts` (`toast.dismiss is not a
function`). They come from #1096 and also fail on unmodified `dev`;
#1097 fixes that fixture.

This is Windows automation evidence. In CI, `latest` now resolves to
2.1.3: the compatibility job runs the native suites against the minimum
and latest on Windows, and the migration and Git-degraded certification
runs them on Ubuntu, Windows and macOS. WSL, packaged desktop,
installers/updater end-to-end and real mobile devices keep their
separate release gates.
2026-10-22 16:17:20 +02:00
Pascal André
0e5dc954bc
docs(help): describe the macOS Not Opened block and the Open Anyway route (#1094)
## Problem

The **macOS quarantine** troubleshooting section said that macOS "can
report that the application is damaged". On a macOS 15.8 Intel VM, the
`1.0.0-dev-20261022` Tauri and Electron apps behave differently once
downloaded with a quarantine attribute:

- both are rejected at first launch with **"CodeNomad" Not Opened**:
Apple could not verify "CodeNomad" is free of malware… The only choices
are Done or Move to Trash;
- `spctl` rejects both bundles: Electron is only ad-hoc signed, Tauri is
unsigned, and neither is notarized.

The section also limited the **System Settings → Privacy & Security**
step to Intel Macs and did not name the actual action.

## Change

Ten locales, `docs/help/<locale>/troubleshooting.md`, with headings
unchanged:

- The introduction states that the macOS releases are not notarized. A
downloaded copy is therefore blocked at first launch with **"CodeNomad"
Not Opened**, or reported as damaged.
- Step 3: for the **Not Opened** message, **Open Anyway** for CodeNomad
in **System Settings → Privacy & Security**, confirmed with an
administrator password, is an alternative to `xattr`. The Intel-only
wording is gone.
- macOS labels stay in English in every locale, as in the existing
translations.

## Verification

- On the VM, after a blocked launch, Privacy & Security showed
**"CodeNomad" was blocked to protect your Mac.** with **Open Anyway**. I
did not click it, since it needs the user's password.
- The documented `xattr -dr com.apple.quarantine
/Applications/CodeNomad.app` let both apps start.
- `npm run test:help`: 9/9.
2026-10-22 13:49:24 +02:00
Pascal André
9f93729882
fix(opencode): explain why OpenCode 2 cannot be installed beside an older command (#1093)
## Problem

On an Ubuntu 24.04 VM, OpenCode 1.17.18 was installed by curl in
`~/.opencode/bin` and also linked as `~/.local/bin/opencode`. With that
setup, the OpenCode panel offered **no installation**, only the generic
"Select a supported executable or install @opencode/cli on the execution
host" advice.

On macOS/Linux the user npm prefix is `~/.local`, so that link occupies
OpenCode's command name exactly where OpenCode 2 would be installed
beside the older version. npm refuses to link over it, and CodeNomad
never modifies a foreign installation. The plan therefore correctly had
no prefix, but nothing told the user why.

## Fix

`sharedInstallationPlan` reports the obstacle on the foreign
installation, forwarded as `foreignInstallation.blocked`:

| `reason` | `path` | When |
|---|---|---|
| `occupied` | the occupying command | a non-npm command holds
OpenCode's name in the user npm bin directory |
| `unwritable` | the user npm prefix | the prefix, or a recognized npm
installation in it (an `@opencode/cli` launcher, a Windows npm shim or
an `opencode-ai` 1.x installation), cannot be written |

The installation notice then says how to clear it: move or rename that
command, or fix the folder's permissions, then choose **Check status and
updates**. The usual curl layout, on PATH only through
`~/.opencode/bin`, still offers **Install OpenCode 2 for CodeNomad**.
This was verified on the same VM, where the installation then succeeded.

The English messages and their nine translations, the `opencode.md` help
page in all ten locales and `dev-docs/OPENCODE_RUNTIME_SETUP.md`
describe the obstacle.

## Tests

- `legacy-installation.test.ts`: builds the exact VM layout (link in the
user npm bin directory) and checks that the plan never offers a prefix
over the foreign command and reports `occupied`. The ordinary layout
must still offer the prefix, and a read-only prefix must report
`unwritable` (POSIX, non-root).
- `opencode-setup.test.ts`: renders the occupied notice with no install
action.
- A second POSIX regression puts a verified npm OpenCode 1.x
installation in a read-only user prefix and expects `unwritable`, never
`occupied`.
- Results:
- the `opencode-update` unit tests: 51/51 on Windows (1 POSIX-only
skipped); on the Ubuntu VM as uid 1000, 48 passed, 0 failed, 4
Windows-only skipped, including both POSIX cases;
  - the `opencode-setup` browser tests: 34/34;
  - the server and UI typechecks;
  - `npm run test:help`.
2026-10-22 13:49:20 +02:00
Pascal André
1840c71054
fix(timeline): mirror every transcript block in order and keep clicks on the clicked marker (#1086)
Some checks are pending
Publish help / build (push) Waiting to run
Publish help / deploy (push) Blocked by required conditions
## What changes for the user
The timeline now matches the transcript block for block, and a click
lands exactly where it should.

- **One marker per visible block, in transcript order:** each reply,
each thinking run, each tool (or collapsed tool group), each compaction
and each system context. Markers follow Message content: a block that is
hidden in the transcript is hidden in the timeline too.
- **The clicked marker stays active** until you scroll, and its own
block, not just the start of its message, is placed at the reading
position. This also works for history that is not loaded yet.
- **While scrolling,** the active marker is the first block below the
reading line.

## Reproduced in numbers before fixing
We replayed 45 of the user's real sessions (65,241 messages, exported
read-only) into the real `SessionView`, with the outline, windows and
previews served by the real server readers. Each session was measured at
1280×800 with two presets, using 16 real pointer clicks and 8 wheel
scrolls per run. Baseline and fix used the same seeds.

| Metric | Before, Complet | Before, Balanced | After, both presets |
|---|---|---|---|
| Sessions out of sync | 43/45 | 42/45 | 0/90 runs |
| Markers / expected | 68,949 / 130,818 | 68,949 / 98,759 | 130,818 /
130,818 and 98,759 / 98,759 |
| Click: clicked marker active | 53.6% (46.0% the one above) | 57.2% |
1,440 / 1,440 |
| Click: block at reading position | 41.5% (median off by 35 px) | 82.2%
| 1,440 / 1,440 (median 0.27 px) |
| Scroll: reading-line block active | 93.1% | 92.2% | 505 / 505 moving
wheels |

What was wrong with the markers in the Complet runs:
- 29,782 tool markers were collapsed into one marker per step.
- 31,777 thinking blocks and 282 system blocks had no marker.
- There were 10,232 order inversions.

## Causes
1. Once an outline existed, the outline projection replaced the markers
of loaded messages too (`message-section.tsx`). It collapsed a step's
tools into one marker placed before its reply, and dropped system
blocks.
2. Resident segments merged thinking into replies, dropped thinking-only
steps and skipped system parts.
3. After a click or a scroll, the active marker was the message's first
marker. The message itself came from a scroll offset that rounds a row
starting just below the top to the previous message.
4. Clicks navigated to the message, not to the block.
5. Jumps into unloaded history had no row to hold while the target was
not yet rendered.

## How
- **Server (outline):** each assistant step can now carry an optional
`layout` field. It lists the step's visible parts in order: thinking
runs, reply runs and each tool, at most 64 entries. It is computed in
the same bounded SQL. Older plugin copies don't send it, and the client
then falls back to the counts: thinking, then text, then tools.
- **Markers:** `buildTimelineSegments` emits one marker per block, keyed
`messageId:type:ordinal`. Loaded messages keep their exact markers.
Unloaded steps are placed at their native position from `layout`.
- **Click and scroll:** the list's `scrollToKey(key, selector)` holds
the selected block on every frame, survives row remounts, and follows
Virtua's estimate until the row renders. `timeline-transcript-blocks.ts`
maps markers to blocks and finds the block at the reading line.
- **Saved indexes:** outline indexes saved without `layout` are
revalidated once.
- **Docs and help:** updated in `dev-docs/SESSION_HISTORY_NAVIGATION.md`
and in the help pages for all 10 locales.

## Behaviour to be aware of
- **The timeline has more markers:** about +90% with Complet and +43%
with Balanced on this sample, because every tool, thinking and system
block now has its own marker.
- **Tool icons for unloaded steps:** a step not loaded yet shows its
first tool's icon on all of its tool markers, until its window loads.
- **Plugin requirement:** the pruning plugin must ship with the new
`layout` field.

## Validation
- **New regression test:** `tests/browser/timeline-sync.test.ts` passes
4/4 and fails 4/4 on unmodified dev. Fixtures are synthetic.
- **Measurement harness:** the opt-in qualification `npm run
qualify:browser:timeline-sync`.
- **Browser suites:**

  | Suite | Result |
  |---|---|
  | history-navigation | 65 |
  | message-scroll | 128 |
  | session-rendering | 47 |
  | render-cost | 17 |
  | full-source-copy | 12 |
  | search-highlight | 11 |
  | system-messages | 10 |
  | timeline-tool-filters | pass |
| streaming-outline (no full reprojection over 64 token deltas) | pass |
  | session-search | pass |
  | transcript-presets | pass |

- **Other checks:** UI unit tests 75/75, server session-pruning 70/70,
help 9/9 and the UI typecheck pass.
- **After rebasing on dev:** the typecheck passes, along with
timeline-sync 4/4, timeline-tool-filters, full-source-copy 12/12,
usage-metrics 5/5 and streaming-outline.

Oversized files touched: `message-section.tsx` (~1,637 lines),
`virtual-follow-list.tsx` (~1,368), `message-timeline.tsx` (~508),
`message-timeline.css` (204).
2026-10-22 05:32:43 +02:00
Pascal André
1b97c7df8a
fix(transcript): jump between user messages with Alt+Up/Down, also from the composer (#1087)
Shift+Up/Down already selects text in every text field, so the user-message jump could not work from the composer, where people usually are. The defaults become Alt+Up/Down, and Cmd+Option+Up/Down on macOS, where Option+Up/Down moves to a paragraph's start or end in text fields. Neither edits text, so the jump now also works from the composer and other text fields, keeping the draft, its caret and focus; Shift+Up/Down selects text again everywhere. Saved custom bindings are unchanged.

The key guard allows text fields and leaves modified arrows where they already mean something: code editors (Monaco moves lines) and terminals, arrow-key widgets, selects and popup triggers (Alt+Down opens them, such as the new-instance launcher), fields that move a list's active option (aria-activedescendant, input[aria-controls], such as the mission picker search), non-text inputs and IME composition. The composer's / and @ picker moves its selection with every arrow key, so its root carries data-prompt-picker and the jump yields while it is open. Platform defaults live in defaultHotkeyBindings, like prompt-alternate-send. The help shortcuts table (10 locales) and HOTKEYS.md describe the new defaults.

Validation: unit tests for the platform defaults (no conflict on either platform) and the key guard; browser regressions jump with Alt from the conversation and the composer (draft, caret and focus kept; Shift+ArrowUp still selects), keep the key for an open composer picker, and, with a real MissionPicker for Alt and emulated-macOS Cmd+Option, move the picker search's option without moving the conversation (the guard checks fail without their selectors). hotkeys-settings and composer-picker-enter pass; UI own-tsconfig typecheck, root typecheck and test:help pass. Gatekeeper review (GPT-6.1 Sol): one P2 (mission picker search) fixed, then zero actionable findings at 7eb2cb081da4ebb1a04e5a7391322deaeb154dd4.

Refs #1075
2026-10-21 20:36:02 +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é
ad844d0645
feat(transcript): jump to the previous or next user message with Shift+Up/Down (#1077)
Finding "what did the agent answer when I asked…" meant scrolling or hovering the timeline marker by marker. Two new commands, Previous User Message and Next User Message, appear in the command palette and in Settings → Keyboard shortcuts, bound by default to Shift+ArrowUp and Shift+ArrowDown. From the conversation (where focus lands after a send), they place the nearest user message before or after the reading position at the top, paging its history window in when it is not resident, like a timeline click. Text fields, arrow-key widgets, IME composition and modal dialogs keep the keys, so Shift+Arrow still selects text in the composer; key repeat does not repeat the jump.

components/user-message-navigation.ts searches the timeline's markers, including the structural index beyond the resident window, and uses list order for a current message without a marker. MessageSection handles the shortcut in the capture phase, before the transcript reads arrows as a scroll gesture; the palette commands reach it through JUMP_USER_MESSAGE_EVENT. The jump requests the index even while the timeline is hidden, and a jump finding nothing resident before it arrives runs once it does. The last target stays the reference while loading or shown, since the list cannot raise a message near the end to its top. Both are forgotten in cancelWindowNavigation, which every other navigation (First/Latest, sends, paging, session changes, deactivation) passes through, and on scroll gestures, so a late index never overrides a newer navigation.

The public help lists the shortcut in all ten locales and states the 74-action catalog (it said 49); HOTKEYS.md and AGENTS.md describe the dispatch; labels are translated in every locale.

Validation: unit tests for the target search and key guard plus the catalog count (now run in CI); browser regressions for resident and windowed jumps, the composer keeping Shift+ArrowUp, the deferred jump with a hidden timeline, an explicit Latest dropping it, and repeated jumps near the end (the last three fail without their mechanism). hotkeys-settings, header-windows and history-navigation suites pass; UI own-tsconfig typecheck, root typecheck and test:help pass. Gatekeeper review (GPT-6.1 Sol): one P2 (deferred jump surviving Latest) fixed, then zero actionable findings at 7ab3f17d3cac6be694f989fde8d217c313599f9d (rebased on dev as bf6e09990ba7b65789d5789fe00d87b5e1362625; only the help pages' catalog count conflicted).

Refs #1075
2026-10-21 14:42:17 +02:00
Pascal André
1dabb5110f
docs(help): bring the user guide back in line with the current app (#1079)
docs(help): bring the user guide back in line with the current app

The help was written on 2026-10-14 and about forty user-visible changes merged since without touching it, so several pages described controls that no longer exist: Missions still listed four playbooks and a Save step for preferences that now save on each change, the depth editor kept its old names, notifications still had their former switches, and voice input pointed to a microphone button that is not in the composer.

Every English page was re-checked claim by claim against the current labels (English UI messages) and behaviour, then the nine translations were updated to match, using each locale's own UI labels. Notable corrections: Missions (Compare two models, autosaving defaults with Apply to / Global / Reset, Subsession nesting depth, prepared hand-offs, retired-task interrupts, the named causes of "Mission Control is unavailable", standalone server support); conversations and history (reasoning/tool durations, token lines adding up the cleanup selection, presets including Nothing, timeline markers following Message content, in-place reveal of long text, Open in session, focus after send, Queue message / Steer now, Retrying); settings (notification routing table and defaults, Stats, Mode, info tips, Server setting badge, localized native menus); shortcuts (72 actions, Clear shortcut, Ctrl/Cmd+K); voice (More actions entries, Save status); files (live Workspace refresh, Show 200 more); OpenCode, install, server and updates (2.1.1 recommended, OpenCode 2 beside an older installation, version card first, private loopback listener for Missions, preview releases offered once all builds are published); providers, integrations, browser, SideCars, desktop and remote pages. Translations also repaired older drift, mainly the project-tab closing passages.

The Chat settings table header (Default state, its title, item column, the thinking row and its accessible label) was still English in eight locales; it is now translated so the help can cite what users see. AGENTS.md now asks every PR changing user-visible behavior to update the affected help pages in the same PR, since CI validates structure and links but not whether the text still matches the UI; it also corrects the needs-input notification default. USER_HELP.md no longer claims native menus stay English.

Validation: npm run test:help (all ten locales, 25 pages, heading ids unchanged), a full help build, and the i18n and transcript-visibility unit tests.
2026-10-21 14:38:29 +02:00
Pascal André
f314123dd1
fix(opencode): qualify and require 2.1.2 for correct Code Mode execution (#1078)
Raise the approved external runtime minimum and recommendation to 2.1.2 after reproducing incorrect native Code Mode job ordering on 2.1.0/2.1.1. Keep the independently qualified 2.1.0 client/plugin build pins and preserve bounded contract recognition for custom/future runtimes. Add the real isolated execute regression to the minimum/latest compatibility matrix, align supported installer fixtures without weakening assertions, and update setup/help in all ten locales. Two author-side review passes found no blockers; the final focused set passes 93/93 and native Code Mode passes. Qualification includes builds/typechecks, UI/browser, native pruning/Missions/recurrence, migrations and isolated installation/upgrade. Record the broad server shutdown timeout and its two unchanged isolated passes precisely. Admin merge explicitly authorized by the maintainer without waiting for CI.
2026-10-21 14:36:51 +02:00
Pascal André
f5c5eefefc
chore(opencode): qualify and recommend runtime 2.1.1 (#1055)
Recommend the qualified external OpenCode 2.1.1 runtime while retaining minimum 2.1.0 and the unchanged locked client/plugin build. Align setup/update regression expectations and the recommended-version help tables in all ten locales; preserve authenticated contract recognition and mutation/ownership fences.

Record isolated Windows native runtime, session/account/usage, pruning UI/history, four historical migrations and all eleven recurring Mission journeys. Server units pass 2442 with seven skips, UI units 1635 and focused browser tests 125; final dev Settings/preview-offer deltas and retained-minimum controls are revalidated.

Two review passes found no actionable issue in the PR. Admission/update tests and explicit isolated 1.x-to-2.1.1 replacement pass. The user explicitly authorized administrator merge despite the unrelated Tauri Windows negative-control race test failure, the general test job still running at inspection and the required GitHub review. No stable release is published by this merge.
2026-10-21 01:16:19 +02:00
Pascal André
88532c23bd
feat(composer): queue or send now from the More actions menu, Alt+Enter by default (#1054)
The composer's More actions menu now offers the follow-up delivery Enter does not use: Queue message when Enter steers, Steer now when Enter queues. It is enabled only in normal mode while the session runs and a draft can be sent, and it goes through the same delivery resolution as the shortcut, so an idle session always sends normally.

Alt+Enter becomes the default alternate-send shortcut on Windows and Linux, with Ctrl+Shift+Enter kept as a second binding. On macOS Option+Return is the native line break in text fields, so only Cmd+Shift+Return sends there; the platform check lives once in hotkey-definitions. The file/command picker now ignores any modified Enter, so Alt+Enter never selects the highlighted item. The menu tooltip shows the effective shortcuts, the help tables note Alt+Enter is for Windows and Linux, and the menu rebuilds only when its enabled states flip.

Validation: composer-alternate-send and composer-picker-enter browser tests (including Alt+Enter with the picker open), hotkey-bindings unit tests for macOS and other defaults, usePromptKeyDown, submitPrompt, hotkeys-settings, help test and UI typecheck pass.
2026-10-21 01:05:23 +02:00
Pascal André
46208ad902
fix(settings): adopt a live window for Preferences startup state and spin the Stats refresh (#1050)
Native Preferences showed "The CodeNomad window that opened Preferences is no longer available" with disabled startup controls whenever its opener was unknown (Preferences restored at launch) or closed, even with CodeNomad windows open. Startup state (restore previous state, saved snapshot, Clear) is each window's own, so the host now adopts the most recently used local window as the opener in that case: Electron through PreferencesWindowRegistry.adoptOpener (startupStateTargetId), Tauri through set_opener (startup_state_target). The epoch rotates on adoption, so a change prepared for another window degrades to a read of the adopted one before any write. The warning and its string in all ten locales are removed; the card re-reads up to three times while the adopted window starts and stays disabled only when no local window can answer. Code contracts, AGENTS.md and the public help in all ten locales describe the opener, otherwise the most recently used window.

Stats' text Refresh button becomes the shared icon button sized like the period select, keeping its localized accessible name and tooltip; the icon spins while a read runs.

Validation: Electron unit tests for live/closed/unknown openers, adoption, epoch rotation and fenced changes; Tauri unit test for adoption and fencing; preferences-startup-state (Electron and Tauri hosts), other-profiles and service-usage browser tests; UI and Electron typechecks; help test; rustfmt check. Independent review: the first pass found that startup state is per window and that the fence no longer followed the target; the adoption fix was re-reviewed and approved, with two trivial notes applied.
2026-10-20 23:34:52 +02:00
Pascal André
7d9093de68
feat(timeline): follow Message content tool visibility (#1049)
Timeline tool markers now follow the Message content filters: a marker disappears when the tool type it shows (its icon; the outline's representative tool for indexed history) is hidden there, and the Nothing preset removes every tool marker. As with hidden tools before, the transcript's active marker and markers with an active permission stay visible, so highlighting and reveal keep following the transcript. Collapsed tools keep their marker.

Because the rail now follows those filters, the separate "Show tool calls in the timeline" preference is removed everywhere it surfaced: Settings > General, the View menu item in the renderer, Electron and Tauri hosts, the palette command, its shortcut catalog entry (72 actions, 23 initially unbound) and its strings in all ten locales and the help. Saved values are ignored; the server schema passes unknown keys through, and hosts ignore an extra field from older renderers. The never-updated expandedMessageIds timeline input is dropped. Intended default change: tool markers are visible with the Balanced preset unless their type is hidden in Message content.

Validation: new timeline-tool-filters browser test (hidden, Other-routed and collapsed tools, Nothing/Everything presets, a hidden active marker shown and highlighted); view-menu, a real Electron View menu, hotkeys-settings, transcript-presets, render-cost, history-navigation and other timeline suites (one local-environment failure that also fails on unmodified dev); related unit tests; UI typecheck; rustfmt check of view_menu.rs. Independent review approved after the active-marker fix.
2026-10-20 23:11:08 +02:00
Pascal André
04e7aae1db
feat(transcript): add a Nothing preset and a preset menu in Message content (#1043)
Transcript presets are now Nothing, Minimal, Balanced and Everything. Nothing hides every message-content row, including all registered, historical and unknown tools, thinking, diagnostics, tool inputs, tokens and system messages. Detailed is retired: saved Detailed choices load as Custom with Detailed's effective tool column (only read, skill and invalid collapsed, other tools expanded) and the same legacy thinking fallback, so existing transcripts keep their appearance.

The Message content popup gains a native select under its title with the four presets. A disabled Custom entry appears only after a manual row change, and the saved preference always drives the shown value, so refused or failed saves never leave a stale choice. Escape closes an open list before the popup. Preset recognition and patches move from Chat settings into the shared transcript-visibility module used by both surfaces; the tool-output command reuses the shared Other-tools resolution.

Preset names and descriptions were English in most locales; they are now translated in all ten, along with the accent-less French, German and Spanish card strings. The public help lists the new localized presets.

Validation: new transcript-presets browser test (popup order, choices, Custom after a manual change, pointer choice keeps the popup, Escape order, French labels) plus existing status-tokens tests; unit tests for preset round-trips, Nothing visibility and the Detailed migration; behavior-registry tests; help test; key parity across locales; UI typecheck with the pinned 2.1.0 client. Independent review approved with no P0-P2 findings; its two trivial P3 notes were applied.
2026-10-20 19:56:05 +02:00
Pascal André
7a2ee897ec
fix(projects): keep the new-tab launcher project-only (#1039)
## Summary
Remove the three bottom actions (Servers, SideCar, Full home) from the
project-tab plus launcher. Also remove its server disclosure, profile
hook/loading, local expansion state, server actions/dialog binding,
unused props/icons/CSS and four keys in all locales. This is not just
hidden chrome.

Recent search/list, Browse, Temporary and Clone remain. A normal plus
click still opens home. All long-press/context/keyboard gestures and
Escape focus remain. Home keeps remote server and SideCar functionality;
shared server setup is mounted only for its caller.

## Validation
UI typecheck passed. Real new-instance-launcher browser suite 8/8:
retained actions/gestures/focus, no server requests from launcher even
with saved profiles, home remote/SideCar controls retained.
Desktop/narrow captures inspected. git diff --check passed. No
app/daemon/state touched.

## Scope
48 files (+110/-190), largely 10-locale help and removed translations.
App.tsx remains oversized (~915 lines); only obsolete launcher props
changed.
2026-10-20 18:44:36 +02:00
Pascal André
d6a890fba7
chore(opencode): qualify 2.1.0 and adopt its native contracts (#1029)
Adopt the OpenCode 2.1 native replay and compaction contracts, align client/plugin pins and set minimum and recommended runtime to 2.1.0. Preserve authenticated contract recognition for future/custom runtimes and explicit installation/restart boundaries.

Preserve historical conversations through official projection-based pruning revisions and independently checked migrations from 2.0.3, beta-19271 and 2.0.32. Retain ownership, execution claims, stale-selection checks and transaction fences. Update setup, help and qualification evidence.

Isolated Windows native qualification and integration checks cover pruning UI, historical reimport, Compare/Wayfinder, recurring Missions, builds and typechecks. Document broad-suite limitations and outstanding platform/package/mobile acceptance; this merge does not publish CodeNomad 1.0.0.
2026-10-20 16:46:38 +02:00
Pascal André
1f132dbb32
docs(help): list Remote Control before direct access (#990)
Some checks are pending
Publish help / build (push) Waiting to run
Publish help / deploy (push) Blocked by required conditions
Reorder the shared help navigation to present Remote Control before direct access in all ten locales. Keep existing Settings behavior unchanged: Direct access starts collapsed locally and opens automatically when the server listens on the network. Update the help ordering regression. Final targeted review found zero actionable findings; all nine help tests pass, covering 25 pages across ten locales.
2026-10-17 13:27:43 +02:00
Pascal André
db6685cb55
fix(workspaces): closing a project tab closes the project in CodeNomad (#970)
Some checks failed
Publish help / build (push) Has been cancelled
Publish help / deploy (push) Has been cancelled
## Problem

Closing a project tab only hid it in that window. The backend workspace
record kept routing the folder's events, notifications, auto-accept and
pending-request recovery, invisibly, until the palette-only **Stop
Workspace** command was run on the active tab. That command still showed
the V1 message "Stop OpenCode instance? This will stop the server."
although V2 has one shared OpenCode service, and it called
`debug.location.evict`, which (per OpenCode source) rejects pending
permissions, cancels pending Forms, fails plugin RPC and kills terminals
of that directory, even for sessions started elsewhere. OpenCode loads
locations on demand and unloads idle ones itself.

## Change

- **Closing a project tab** (×, Ctrl/Cmd+W) removes that tab's backend
workspace record. CodeNomad stops following it; sessions keep running in
OpenCode and reappear when the project is reopened.
- **Another tab of the same folder** has its own record and is
unaffected.
- **The same record shown in another window**: that window's tab closes
through the existing `workspace.stopped` broadcast, with an info
notification ("closed from another window"); the initiating window shows
nothing.
- **CodeNomad never evicts OpenCode locations**: eviction, its
serialization helpers and `locationOwned` bookkeeping are removed. A
failed launch now always removes its logical record (the retention only
existed to retry a failed eviction).
- **Stop Workspace removed** (command, confirm dialog, i18n keys in all
locales).
- Unchanged: opening an open project still offers a new tab (recent row)
or switching (switch button); temporary projects keep Keep / Discard /
Cancel; window close, app quit, reload, restore and backend shutdown
delete nothing; disconnected-instance dismissal stays local.
- Delete failure: error toast, record kept and reachable from the home
page, no automatic retry.

## Docs

Help in all 10 locales (`projects`, `index`, `desktop`, `server`,
`opencode`, `missions`), `AGENTS.md`, `CONTRIBUTING.md`,
`dev-docs/architecture.md`, `packages/server/README.md` and the
architecture-guide skill now describe the new contract.

## Validation

- UI store tests (`--conditions=browser`): app-tabs,
temporary-instances, workspace-list-reconciliation-fence,
instances-restore-ownership — 30/30.
- Server: manager + shared service 54/54; event ownership and workspace
identity suites pass.
- New real-component browser regression (click ×, Ctrl+W, Cmd+W): 3/3;
project restoration (Electron/Tauri) and new-instance launcher browser
suites pass.
- `npm run test:help` 9/9 (250 pages); UI, server and Electron
typechecks.
2026-10-15 15:14:29 +02:00
Pascal André
de44f7b29c
fix(pending): name the cause, folders and sessions in the recovery warning (#965)
The "Question and permission recovery is incomplete" warning now says
which project it concerns, why recovery failed, which folders failed and
which sessions made them required, instead of a fixed sentence.

## Why
- The previous warning grouped every possible cause into one fixed text
and named none of them, including unrelated failures such as the initial
workspace load.
- Six earlier fixes each removed one measured cause, but every new cause
produced the same text, so it could only be diagnosed by live captures.
- With several projects open, it did not even say which project was
affected.

## Example
Before:
> Question and permission recovery is incomplete. Known requests are
retained; inactive worktrees are not loaded.

Now, for the live case fixed by #964:
> **CodeNomad: questions and permissions not fully recovered**
> Outside this project: prompt-replay-yield. Affected sessions: ISSUES
Codenomad.

## Change
- **Server:** every `error` directory in the pending snapshot carries a
`reason`:
- `not-owned`, `deleting`, `missing` (an owned folder that no longer
exists), `unverified` (Git/filesystem/WSL translation could not verify
it) and `changed` (ownership or repository changed during the read);
- a native placement outside the workspace is reported as `not-owned`,
and a deletion fence raised during the read as `deleting`.
- **UI, `stores/pending-recovery-cause.ts`:** a small module turns
failures into a structured cause:
- folder issues, plus client-side `timeout`, `unavailable` (with the
HTTP status when known) and `excluded` (a required folder reported as
history);
- the plugin being unavailable, repeated supersession, an initial load
failure and a generic request failure.
- **Messages:**
- folder issues are grouped by cause, named by folder basename, and
capped at three with a remainder;
- only the sessions that made a folder required (active, or holding a
request) are named;
- the title uses the same label as the project tab
(`lib/instance-label.ts`, shared with `instance-tab.tsx`).
- **Diagnostics:** the structured cause is also logged at warn level.
- **Unchanged:** the two-attempt threshold, once-per-episode
notification, re-arming on success, retained queues and deferral
handling.
- **i18n:** the generic key is replaced by the cause keys in all 10
locales.

## Tests
- Server: `pending-requests.test.ts` 40/40; every error assertion now
names its reason.
- UI:
- `instances-pending-requests` passes, including the live scenario: an
active session in a folder reported `not-owned` produces "Outside this
project: prompt-replay-yield. Affected sessions: ISSUES Codenomad.",
while an idle session in the same folder is not named. A timed-out batch
names each folder.
  - The unavailable plugin is named.
- New `pending-recovery-cause.test.ts` covers grouping, caps, the detail
variants, request classification and failure combination.
  - 39/39 together with the compaction and permission lifecycle suites.
- i18n tests and both typechecks are clean.
2026-10-15 09:17:31 +02:00
Pascal André
1a9f202420
feat(desktop): localize native menus and open About in Settings → Info (#963)
## What
- **Native menus follow the UI locale** (Electron and Tauri): top-level
menus and every item of the app, File, Edit, View, Window and Help
menus. Previously only the custom titlebar's top level and the View
panel items were translated.
- **About CodeNomad opens Settings → Info** (the About card with
version, updates and diagnostics) instead of the bare native panel. It
runs a new `open-about` command, also listed in the command palette.

## How
- Renderer translates `NATIVE_MENU_LABEL_KEYS`
(`lib/native/menu-labels.ts`) and sends them as `menuLabels` with the
existing View menu state for the focused window; older hosts ignore the
field.
- Hosts: English until a renderer reports labels; the last locale is
kept for windows without one (loading/sign-in); labels validated (known
keys, non-empty, ≤ 200 chars); `&` escaped outside macOS.
- **Electron**: rebuilds its menu when labels change; role items get
explicit labels; English defaults come from the English messages
(`menu-labels-en.ts`).
- **Tauri**: every translatable item (including predefined Edit/Window
items) is built explicitly, retained and retitled in place on the main
thread, outside our mutexes. `menu_labels.rs` defaults are checked
against the English messages by `menu-labels.test.ts`.
- About fallback when no application window can receive the action:
Electron's native panel, Tauri a short version dialog.
- Help pages now name menu items in each page's language (Help →
CodeNomad Help, Get Updates, New Window, Zoom).
- i18n: 30 `window.menu.native.*` keys + 3 `commands.openAbout.*` keys
in all ten locales; existing New Instance / Command Palette translations
reused.

## Validation
- Electron `view-menu.test.ts` + `menu-target.test.ts`: 3/3 (locale
change, per-window labels, kept locale, invalid labels, `&` escaping).
- Tauri `cargo test -- menu`: 25/25 (label resolution/validation +
existing menu tests).
- `menu-labels.test.ts`: every locale complete; Tauri defaults identical
to English messages.
- `help-command.test.ts`: 5/5 — About opens native Preferences on Info
locally, embedded Info remotely.
- `npm run test:help`: 9/9.
- Typecheck: UI only the pre-existing `help-url.test.ts` error; Electron
only pre-existing `Array.at` lib errors.
- **Not run locally**: the real-Electron menu suite
(`view-menu-electron.test.ts`, Electron not installed here) and a
running Tauri build.

`packages/tauri-app/src-tauri/src/main.rs` is already oversized (~2,700
lines); only `build_menu` and the About handler changed.
2026-10-15 00:42:23 +02:00
Pascal André
d09d1222fa
feat(ui): merge Updates into About and move connectivity last in Info (#962)
Settings → Info showed the CodeNomad version three times: as the UI version
in About, as the server version at the top of the connectivity card, and
again as the server version in the separate Updates card. The network card,
mostly useful for remote-access troubleshooting, also sat between About and
the version/update information most users look for.

About now opens with a single Version row (the server version, else the UI
version). A separate UI version row appears only when the served UI differs
from the server, the one case where both matter. The former Updates card
becomes an Updates section of About, with the same feed selector, latest
version, help text, install/download and Refresh actions, without its own
copy of the version. Diagnostics follows, and the connectivity card moves
last without its version row; its server OS, architecture, listener and
address details are unchanged. A "Version" label is added in all ten
locales and the unused Updates subtitle removed. The Updates help page now
describes a section rather than a card in every locale.

The diagnostic report, bug report environment and update behaviour are
unchanged; only the presentation moves.

Validation: a new browser case checks the card order (About, Diagnostics,
connectivity), the Updates section and feed inside About, that the version
appears once, and that a differing UI version adds its own row. Info and
desktop-update browser suites pass 26/26, diagnostics/client-OS/bug-report
unit suites 19/19, and npm run test:help 9/9. ES2022/DOM typecheck reports
only the pre-existing help-url.test.ts error from #859. Two independent
COMMENT gatekeeper rounds; the round-1 P3 (help still describing an Updates
card) is fixed and the final head reports zero actionable P0-P3 findings.
2026-10-15 00:11:02 +02:00
Pascal André
c0d53f13d4
docs(help): multilingual help site with in-app Help menu and palette entry (#859)
## Summary

A complete public CodeNomad help site in the app's ten languages,
published on GitHub Pages (https://neuralnomadsai.github.io/CodeNomad/)
and opened from the app.

- **Content:** 25 pages in 8 sections (Start, Workflows, OpenCode setup,
Remote access, Customize, Extensions, Desktop, Help), written for
OpenCode power users and covering the current version only. It includes
everything user-relevant from `README.md`, `packages/server/README.md`
(server, network, auth, TLS, Remote Control) and the user-facing
dev-docs, with flags, defaults, labels and safety boundaries checked
against source.
- **Languages:** en, fr, de, es, he (RTL), ja, ne, ru, tr, zh-Hans.
Interface labels reuse each locale's own app strings; native desktop
menu names, code, commands, flags, paths and anchors stay unchanged.
- **Site:** `scripts/build-help.mjs` renders
`docs/help/<locale>/<slug>.md` with translated chrome
(`docs/help/i18n/`), stable `{#id}` anchors identical across locales,
same-article language links with hreflang, a root language chooser, and
a first-party offline per-locale search (Ctrl/⌘+K, substring matching
for CJK, Devanagari and Hebrew, hidden without JS). No analytics or
remote resources.
- **Validation:** `npm run test:help` fails on any missing page or
string, heading drift, broken link/anchor/asset or invalid search
target. `.github/workflows/help-pages.yml` validates PRs and deploys
only from `dev`.
- **App entry points:** one `open-help` command for the palette and a
new **CodeNomad Help** item at the top of the Electron and Tauri Help
menus. It opens the help in the invoking window's UI language through
the existing external opener, falls back to the root URL for windows
without the handler, and reports failures without replay.
- Documents the strict boolean environment-variable rule from #959 and
the updater entry points from #960.

## Review

Delivered as a CodeNomad mission with independent review loops, each to
zero findings:
- English content audit (5 passes, 14 fixes) and technical gatekeeper (2
passes) before translation.
- After translation: QA, UX review, and a final multilingual gatekeeper
(35 translation/label findings fixed, then verified), plus a visual fix
for the search focus ring and a verified sync with #960.

## Validation

- `npm run test:help`: 9/9 (25 pages × 10 locales, strict, including
negative tests); `npm run build:help`: 250 pages.
- `node scripts/test-help-browser.mjs`: 44/44; Playwright: 500 page
visits at 390 and 1280 px, all locales, no overflow, no external
requests.
- UI help-command and desktop-updates browser tests 19/19; help URL unit
tests; Electron menu tests; UI and Electron typechecks; Tauri `cargo
test` 272/272.
2026-10-14 22:44:56 +02:00