mirror of
https://github.com/NeuralNomadsAI/CodeNomad.git
synced 2026-10-26 00:01:57 +00:00
33 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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. |
||
|
|
13450749b8
|
feat(preview): open loopback conversation links in the web preview (#1119)
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. |
||
|
|
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. |
||
|
|
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. |
||
|
|
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. |
||
|
|
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 `
|
||
|
|
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. |
||
|
|
628a80a5c9
|
feat(settings): give Updates its own card and the data profile a Startup row (#1122)
## 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. |
||
|
|
c82dd8fbf6
|
fix(notifications): show Windows system notifications and keep their permission (#1109)
## 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`. |
||
|
|
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`. |
||
|
|
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. |
||
|
|
01b20b5b78
|
chore(opencode): qualify and recommend runtime 2.1.3 (#1098)
## 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 `
|
||
|
|
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. |
||
|
|
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`. |
||
|
|
1840c71054
|
fix(timeline): mirror every transcript block in order and keep clicks on the clicked marker (#1086)
## 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). |
||
|
|
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 |
||
|
|
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. |
||
|
|
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 |
||
|
|
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. |
||
|
|
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. |
||
|
|
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. |
||
|
|
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. |
||
|
|
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. |
||
|
|
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. |
||
|
|
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. |
||
|
|
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. |
||
|
|
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. |
||
|
|
1f132dbb32
|
docs(help): list Remote Control before direct access (#990)
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. |
||
|
|
db6685cb55
|
fix(workspaces): closing a project tab closes the project in CodeNomad (#970)
## 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.
|
||
|
|
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. |
||
|
|
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. |
||
|
|
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. |
||
|
|
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. |