Restore detailed diagnostics for GitHub bot OpenCode SDK failures by logging nested error cause metadata before wrapping session list, create, and command failures.
This keeps the migrated GitHub automation behavior aligned with the original branch and improves production debugging for undici, fetch, and local OpenCode loopback failures without changing command execution behavior.
Validation: npm run typecheck --prefix packages/server; npm test --prefix packages/server -- "src/integrations/github/__tests__/*.test.ts"
Restore the fuller GitHub bot operating guidance so headless runs have clear workflow, continuation, and scope instructions for issue comments, PR openings, and review-comment events.
Add runner-level coverage around accepted issue-comment dispatch and same-thread supersession. The job runner now accepts a narrow operations seam so tests can exercise workspace setup, worktree context, OpenCode command arguments, and abort behavior without real GitHub, git, or OpenCode services.
Update the server test script to discover nested __tests__ files and unref delayed workspace cleanup timers so test and short-lived process exits are not held open by background cleanup scheduling.
Validation: npm run test --workspace @neuralnomads/codenomad; npm run typecheck --workspace @neuralnomads/codenomad; npm run build --workspace @codenomad/codenomad-opencode-plugin; git diff --check.
Restore the no-timeout Undici dispatcher for GitHub automation OpenCode calls so long-running session.command requests do not regress to default header/body timeouts. The custom fetch also normalizes Request objects and preserves duplex handling for streamed bodies.
Return explicit publish_pr validation failures as HTTP 400 responses with ok=false payloads so the plugin requester treats invalid directories, missing branches, and dirty worktrees as tool failures instead of successful responses.
Add the server test script from the original GitHub bot branch to make the tsx test suite discoverable and runnable through npm.
Validation: npx tsx --test packages/server/src/integrations/github/__tests__/*.test.ts packages/server/src/workspaces/__tests__/git-worktrees.test.ts; npm run typecheck --workspace @neuralnomads/codenomad; npm run build --workspace @codenomad/codenomad-opencode-plugin; npm run test --workspace @neuralnomads/codenomad
Add the CodeNomadBot GitHub App integration on top of the current dev branch. The server now validates GitHub webhooks, applies explicit policy rules, prepares managed issue/PR worktrees, launches OpenCode in GitHub mode, and exposes scoped GitHub operations through the plugin route.
Wire the OpenCode plugin to expose a GitHub-only tool and command templates when CODENOMAD_MODE=github. Add GitHub App authentication, installation-token git helpers, bot signature handling, payload sanitization, latest-comment-wins job cancellation, and documentation for setup and operations.
Also harden managed worktree creation by reusing valid existing worktrees, pruning stale registrations, and replacing orphaned directories. Add migrated webhook helper tests and worktree recovery coverage.
Validation: npm run typecheck --workspace @neuralnomads/codenomad; npm run build --workspace @codenomad/codenomad-opencode-plugin; npx tsx --test packages/server/src/integrations/github/__tests__/*.test.ts packages/server/src/workspaces/__tests__/git-worktrees.test.ts
## Summary
- add per-provider model visibility controls to Settings > Providers and
the floating provider dialog
- persist exact hidden model IDs as CodeNomad presentation preferences
- keep large plugin catalogs manageable with search and Show all / Hide
all actions
## Behavior
- all current and newly reported models remain visible by default
- unchecked models are hidden only from model picker lists
- active/default models remain usable even when hidden
- favorites remain persisted and return when their models are shown
again
- the same management UI is available from Settings and the floating
model-picker dialog
This intentionally mirrors OpenChamber/OpenCode Desktop client-side
hiding. It does not rewrite OpenCode configuration or change model
execution/default semantics.
## Validation
- npm run typecheck
- node --import tsx --test packages/ui/src/lib/model-visibility.test.ts
- npm run build --workspace @codenomad/ui
- Gatekeeper reviews: PASS
Closes#636
## Summary
- validate the authenticated OpenCode process configuration before
publishing a workspace as ready
- stop and remove instances that report invalid configuration
- show localized configuration diagnostics, affected paths, validation
issues, and typo suggestions in the existing launch-error dialog
## Existing coverage
PR #195 already handles binary spawn and early-exit failures. PR #582
surfaces session-list loading failures. This change closes the remaining
case where OpenCode reports healthy while configuration-dependent
endpoints fail.
## Validation
- `bun test ./packages/server/src/workspaces/manager.test.ts`
- `node --import tsx --test packages/ui/src/lib/launch-errors.test.ts`
- server and UI typechecks
- UI build
- manual reproduction with OpenCode 1.18.5
- independent Gatekeeper reviews: PASS
Closes#508
## Summary
- retry an ephemeral port when an automatic listener receives Windows
`EACCES`
- keep explicitly configured ports strict
- keep non-Windows `EACCES` behavior unchanged
## Context
CodeNomad already treats 9898 and 9899 as preferred rather than
mandatory ports. The fallback previously handled only `EADDRINUSE`, so a
Windows port reservation could terminate Tauri startup before the
operating system was asked for an available port.
The specific WinNAT attribution in the issue remains unverified, but the
observable startup failure is fixed regardless of which Windows
reservation produced `EACCES`.
## Validation
- focused listener retry test passes
- server typecheck passes
- server suite: 256 passed, 4 skipped, 1 pre-existing Windows-only
`git-worktrees.test.ts` fixture failure
- autonomous gatekeeper review will be published as a PR comment after
opening
Fixes#627
## Summary
PR #561 introduced `createInstanceClient` so server modules would stop
hand-assembling the OpenCode loopback URL and use the generated SDK
client. Three call sites adopted it; the background-process completion
prompt was the remaining holdout — it still hand-built
`http://127.0.0.1:{port}/session/{id}/prompt_async` with manual
auth/content-type wiring. This PR routes it through the factory +
`client.session.promptAsync`, and consolidates the loopback host
constant.
## Why
- **Consistency** — the last hand-built OpenCode-instance loopback URL
(explicitly flagged in #561 as a future adoption target) now uses the
same version-correct SDK path as the permission replier, yolo metadata,
and the opencode updater.
- **Correctness** — the old call sent no directory at all;
`notify.directory` was stored but never used (dead data). The factory
now scopes the prompt to the session's own directory, which for a
worktree/subdirectory session is the correct project context (a normal
session coincides with the workspace root, so no change there).
## What changed
**Migration** — `background-processes/manager.ts`:
`sendCompletionPrompt` → `createInstanceClient(...)` +
`client.session.promptAsync({sessionID, parts:[{type:"text", text,
synthetic:true}]}, {throwOnError:true})`. Synthetic `<system-message>`
text + `synthetic:true` preserved. Side effect: the call now carries the
factory's 10s loopback timeout (the old fetch had none; the failure is
swallowed + warn-logged, so finalization is unaffected).
**Factory** — `workspaces/instance-client.ts`: new optional `directory`
override in `InstanceClientOptions` (defaults to workspace root); adopts
shared `LOOPBACK_HOST`. Fetch wrapper unchanged from `dev`.
**Consolidation** — new `workspaces/loopback.ts` exporting
`LOOPBACK_HOST = "127.0.0.1"`, adopted by `instance-client.ts`,
`instance-events.ts`, and the workspace manager's health + TCP probes
(`manager.ts`). (Other `127.0.0.1` uses — remote-access proxy, sidecars
— are different concerns, left alone.)
**Tests**
- `workspaces/instance-client.test.ts` (8 cases): null-when-no-port,
loopback host/port targeting, auth header attach/omit, directory scoping
present/absent, explicit directory override, timeout aborts stuck call.
- `background-processes/manager.test.ts` (2 cases, first test for this
manager): drives the real lifecycle (spawn + exit) against a mocked
transport; asserts the `prompt_async` POST
URL/body/authorization/`x-opencode-directory` (= session dir, distinct
from workspace root), and that a failed prompt is swallowed +
warn-logged without aborting finalization.
## Behavior parity
| Aspect | Before | After |
|---|---|---|
| Route / body | `/session/{id}/prompt_async`,
`{parts:[{type,text,synthetic}]}` | **same** (via SDK) |
| Auth header | manual | via factory |
| Directory scoping | none | session's `notify.directory` |
| Loopback timeout | none | 10s (factory default) |
| Error handling | throw on non-2xx → caught + warn-logged |
`throwOnError` → caught + warn-logged (**same**) |
## Testing
10 new tests pass. The background-process integration test is stable
12/12 under `--test-concurrency=4` and is mutation-killed if the
`directory` override or `throwOnError` is removed. Full server suite:
255 pass / 1 fail — the single failure is pre-existing
(`git-clone.test.ts` Chinese-locale git message, confirmed failing
identically on `dev`). Server typecheck clean.
## Risk / rollback
Additive refactor; the factory is production-proven by #561's consumers.
Rollback = revert the single commit.
## Notes
- Per the file-length guideline:
`packages/server/src/background-processes/manager.ts` is ~684 lines
(above the 500 warn threshold, under the 800 limit); this PR shrank it
by ~16 lines net.
## Summary
- prevent Linux workspace startup from failing when process identity
discovery exceeds its one-second command deadline
- capture the launched process and its existing process-group members
without spawning helper commands for every `/proc` entry
- preserve the leader-exit cleanup guarantees introduced by #602
- read immutable Linux process start ticks correctly and tolerate
multiline task names
## Root cause
Workspace startup captures an immutable process identity before
accepting the OpenCode runtime. The Linux implementation scanned every
process and launched several `cat`, `cut`, `sed`, `basename`, and
`dirname` helpers per entry. On the affected Mint host, that synchronous
shell command exceeded its one-second timeout. Identity capture then
failed closed and stopped the newly launched OpenCode process; cleanup
retried the same expensive probes and produced the repeated `spawnSync
sh ETIMEDOUT` errors.
The identity parser also used `$20`, which POSIX shells interpret as
`$2` followed by `0`, rather than the twentieth positional field. This
did not cause the startup timeout but weakened immutable process
matching and is corrected here.
## Safety
- launch discovery still retains every already-started member of the
observed process group
- guarded cleanup remains identity- and launch-token-based; no
unverified PID fallback is introduced
- WSL, macOS, and Windows paths retain their existing platform-specific
probes
## Validation
- server TypeScript typecheck
- focused process identity and runtime suite: 23 passed, 1 Linux-only
test skipped on Windows
- real WSL `/proc` probe: correct start identity, 2 group members, 4 ms
- broader workspace suite: 79 passed, 3 platform skips
- `git diff --check`
The broader workspace run still has the unrelated existing Windows
fixture failure in `git-worktrees.test.ts` (`undefined` instead of
`main`).
Closes#624
## Summary
- count only fuzzy-qualified paths toward the 8000 workspace search
candidate cap
- scope the bounded cache to the current query and entry type
- stop recursive directory-link cycles without hiding legitimate alias
paths
- expand the @file picker to its parent width and horizontally scroll
long paths
## Validation
- 6 focused filesystem search/cache tests pass
- server typecheck passes
- workspace UI/Electron typecheck passes
- UI production build passes
- gatekeeper round 3: zero findings
- full server suite: 241 pass, 3 skip; the unrelated existing Windows
git-worktrees fixture fails to parse its mocked branch output
Closes#503
## Summary
- Populate the remote login username input with the configured default
value instead of displaying it only as a placeholder.
- Disable mobile capitalization, autocorrection, and spellchecking for
username and password inputs.
- Add a route-level regression test for the rendered username value and
mobile credential attributes.
## Validation
- node --import tsx --test
packages/server/src/server/routes/auth.test.ts
- npm run typecheck --workspace @neuralnomads/codenomad
- node packages/server/scripts/copy-auth-pages.mjs
Closes#616
## Summary
- reorganize Settings into focused General, Chat, Notifications, Speech,
Remote Access, OpenCode, Providers, SideCars, Config files, Advanced,
and Info sections
- add independent expanded, collapsed, and hidden controls for Thinking,
Tool output, Diagnostics, Tool inputs, and Token usage
- embed the shared Provider manager directly in Settings while retaining
the model-selector dialog entry point
- move environment, session cleanup, sub-agent idle markers, and Tauri
transport controls into Advanced
## Reliability
- scope provider loading and authentication operations to the current
ready instance
- clean up OAuth callbacks and popups when the instance or view changes
- preserve failed disconnect state and prevent conflicting provider
operations
- improve focus management and live status/error announcements
## Validation
- npm run typecheck
- npm run build:ui
- 144 runnable UI tests from the settings review cycle
- git diff --check
- independent settings and accessibility gatekeepers: SHIP
Closes#609
## Summary
- Persist server-owned Yolo state through OpenCode session metadata so
it survives workspace and CodeNomad restarts.
- Restore expanded and collapsed parent/subsession state from the
desktop client snapshot.
- Serialize CodeNomad metadata mutations so Yolo and worktree metadata
cannot overwrite each other.
## Yolo persistence
- Stores state under metadata.codenomad.yolo with enabled and
rootSessionId fields.
- Accepts a persisted marker only when it is owned by that session,
preventing copied fork metadata from enabling an unrelated family.
- Hydrates the project session tree before processing permission events
and queues events while hydration is pending.
- Keeps the server authoritative for family inheritance, permission
replies, toggles, and cleanup.
- Uses get/merge/update writes that preserve worktreeSlug and
third-party metadata.
- Serializes writes by session across CodeNomad instances and routes
worktree slug updates through the same server-owned path.
- Reconciles late ancestry and repeated root migrations without
re-enabling a family after a concurrent disable.
## Expansion restoration
- Captures bounded, deduplicated expanded session IDs per workspace in
the local desktop snapshot.
- Restores explicit expanded and collapsed state while preserving IDs
that are temporarily unavailable during startup reconciliation.
- Gives live user changes, deletions, and loaded-session authority
precedence over preserved state.
- Cleans expansion state when sessions or instances are removed.
- Keeps the active child visible when loading legacy snapshots that
predate expansion persistence.
## Validation
- 39 focused server permission and metadata tests pass.
- 66 focused client snapshot and restoration tests pass.
- Server and UI TypeScript typechecks pass.
- git diff --check passes.
## Limits
- Project hydration follows the existing 10,000-session list ceiling.
- Snapshot expansion persistence is capped at 256 session IDs.
Closes#607
Depends on anomalyco/opencode#23068.
## Summary
- keep the Status tab visible while provider usage refreshes in the
background every minute
- simplify Yolo controls, and rename the quota subsection to Usage
- render the provider name prominently and each quota as one discreet
text row plus a progress bar
- show Included for subscription-model cost and remove the supplemental
Codex credit-balance row
- shorten the session-header Yolo badge while preserving its accessible
label
## Provider safety
- remove the Claude Pro/Max OAuth quota adapter because it's not usable
anymore
## Refresh behavior
- replace the suspense-aware provider resource with contained
signal-based polling
- preserve the last successful result during background refresh failures
- ignore stale requests after the active session or provider changes
## Validation
- 8 targeted server usage tests
- server typecheck
- UI typecheck
- production UI build
- git diff checks
- focused regression review of polling, failures, and session changes
Closes#605
## Summary
- add an OpenCode update card to the OpenCode settings section
- show the installed version and a versioned update button only when npm
advertises a newer release
- run the upgrade through the generated OpenCode SDK against a ready
instance using the configured binary
- keep running instances uninterrupted; newly started instances use the
installed version
- localize loading, unavailable-instance, success, and failure states
across all supported locales
## Server behavior
- resolve the selected binary with the existing binary probe
- check opencode-ai/latest with a 10-second timeout and a five-minute
cache
- require a ready workspace using that exact binary before enabling the
upgrade
- invoke client.global.upgrade({ target }) rather than assembling an
OpenCode API request manually
- return stable error codes without exposing server details
## Validation
- pm run typecheck in packages/server
- pm run typecheck in packages/ui
- ode --import tsx --test src/opencode-update/service.test.ts
- pm run build in packages/ui
- pm run build in packages/server
- git diff --check
<img width="1034" height="688" alt="image"
src="https://github.com/user-attachments/assets/a71a01c4-d3e2-46f6-a333-a408c058255c"
/>
<img width="1018" height="360" alt="image"
src="https://github.com/user-attachments/assets/983a87a9-6103-48d7-8bcf-e10c5ce6a105"
/>
## Summary
- Add provider quota usage to an expanded-by-default subsection in the
session Status panel.
- Resolve the active provider and model automatically from the current
session.
- Display localized quota windows, reset times, values, and color-coded
progress bars inside the same bordered rounded container used by
neighboring subsections.
## Server implementation
- Add GET /api/usage/:providerId with optional modelId filtering.
- Add a 60-second in-memory cache and deduplicate concurrent provider
requests.
- Read credentials only on the server and never return secrets through
the API.
- Support Claude, Codex, GitHub Copilot, Google/Gemini/Antigravity,
Kimi, NanoGPT, OpenRouter, z.ai, Zhipu, MiniMax global/CN, Cursor,
Ollama Cloud, Wafer, and OpenCode Go.
- Use existing OpenCode and Antigravity access tokens for Google calls;
optional environment-provided OAuth client values are required only for
token refresh.
## UI behavior
- Keep Provider usage open by default while allowing it to collapse like
the other Status subsections.
- Show loading, unsupported, not configured, unavailable, and no-session
states.
- Localize all user-visible strings across the nine existing locales.
## Attribution
- Include the MIT notice for the OpenChamber usage-provider
implementation used as the reference.
## Validation
- Server typecheck
- UI typecheck
- 7 targeted usage tests
- Production UI build
- Server build and npm package-content verification
- Tauri release build without bundling
- git diff checks
<img width="484" height="444" alt="image"
src="https://github.com/user-attachments/assets/69afbaf8-4c9a-4cb3-b316-e5788e416141"
/>
## Summary
- Restore the primary desktop client's window bounds, zoom, workspace
tabs, active sessions, drafts, bounded attachments, scroll positions,
and panel layout across launches.
- Keep additional Electron and Tauri processes independent: they still
start normally but do not read or write the shared native snapshot.
- Add translated settings to disable startup restoration or clear the
saved state.
## Implementation
- Persist a versioned, bounded snapshot atomically in Electron and
Tauri, preserving unknown future envelopes until an explicit clear.
- Elect one primary process with stale-owner recovery and process
identity checks, then protect renderer access with per-document
capability tokens.
- Flush renderer state before close, quit, reload, and app-owned
navigation with bounded failure handling and fresh token rotation.
- Reconcile partial or timed-out workspace hydration without losing
unsent prompt state or resurrecting explicitly cleared drafts,
attachments, sessions, or tabs.
- Correlate restore-created workspaces so cancellation and delayed SSE
events cannot produce ghost tabs.
- Harden workspace launch cancellation and cross-platform process
cleanup so failed starts cannot orphan detached POSIX, WSL, or Windows
children; incomplete shutdown exits nonzero.
The restored shell state remains local to the desktop client. Durable
semantic session properties continue to use OpenCode session metadata
separately.
## Validation
pm run typecheck --workspace @neuralnomads/codenomad
- Electron native suite: 43 passed
- Tauri suite: 54 passed
- Focused UI restore, reconciliation, attachment, authority, and race
suites passed
- Focused server workspace runtime, manager, process identity, route,
and shutdown suites passed
- pm run build
- pm run build:tauri
- git diff --check
- 16-pass adversarial review loop completed with final No findings
- extensivly optimized, tested and used day by day
## Platform notes
- Native menu reloads and app-owned navigation have awaited durability
guarantees. Browser-engine crashes or forced process termination remain
inherently best effort.
- Process cleanup uses identity-guarded platform adapters and fails
conservatively when target ownership cannot be proven.
## Summary
Voice input (speech-to-text / STT) and voice output (text-to-speech /
TTS) currently share a single API key, base URL, and provider
configuration. Users cannot use different providers for each direction —
for example, Groq for transcription (STT) and OpenAI for synthesis
(TTS).
This PR adds a `separateProviders` toggle to the speech settings. When
**off** (default), behavior is unchanged — existing configs work as-is.
When **on**, STT and TTS each get their own `apiKey`, `baseUrl`, and
`model` fields, allowing independent OpenAI-compatible endpoints per
direction.
## Changes
### Server
- **`api-types.ts`**: Added `separateProviders`, `sttConfigured`,
`ttsConfigured`, `sttBaseUrl`, `ttsBaseUrl` to
`SpeechCapabilitiesResponse`
- **`speech/service.ts`**: Extended Zod schema with `separateProviders`
+ nested `stt`/`tts` sub-objects. Split `createProvider()` into
direction-aware `createSttProvider()`/`createTtsProvider()` with
`resolveSttSettings()`/`resolveTtsSettings()` resolvers that fall back
to shared values when per-direction fields are absent
- **`settings/public-config.ts`**: Extended `sanitizeServerOwner` to
strip per-direction `stt.apiKey`/`tts.apiKey` and set
`stt.hasApiKey`/`tts.hasApiKey` booleans (same pattern as the shared
`apiKey`)
- **`OpenAICompatibleSpeechProvider`**: Completely unchanged — it
receives the same flat `NormalizedSpeechSettings` it always has
### UI
- **`stores/preferences.tsx`**: Extended `SpeechSettings` type with
`separateProviders`, `stt`, `tts` sub-objects. Updated
`normalizeSpeechSettings()` and `updateSpeechSettings()` to handle
per-direction patches
- **`components/settings/speech-settings-card.tsx`**: Added toggle at
the top of the card. When enabled, shows two sections (Input/STT and
Output/TTS) with their own API Key, Base URL, and Model fields. Playback
Mode and TTS Format remain shared. Also includes "Test input" button
alongside existing "Test playback"
- **`lib/audio-utils.ts`**: Extracted shared `blobToBase64`,
`createMediaRecorder`, `stopTracks` utilities (used by both
`usePromptVoiceInput` and `useTranscriptionTest`)
- **`lib/hooks/use-transcription-test.ts`**: New hook for testing STT
transcription from the settings card
- **`components/prompt-input/usePromptVoiceInput.ts`**: Uses
`sttConfigured` instead of combined `configured`
- **`stores/conversation-speech.ts`**, **`lib/hooks/use-speech.ts`**,
**`speech-settings-card.tsx`**: TTS consumers use `ttsConfigured`
instead of combined `configured`
- **`styles/components/settings-screen.css`**: Added
`settings-card-section-header`/`settings-card-section-title` styles
- **i18n**: Added new keys per locale across all 9 locales (en, es, ja,
zh-Hans, fr, de, he, ne, ru)
## Backward Compatibility
- `separateProviders` defaults to `false` — existing configs work
unchanged
- The `configured` field remains in capabilities response as
`(sttConfigured || ttsConfigured)` so any consumer not yet updated still
works
- `NormalizedSpeechSettings` (the provider-facing flat interface) is
unchanged
## Testing
- 13 unit tests covering direction-aware resolution (shared mode,
separate mode, partial config, fallback chains, model overrides) and
config sanitization
- All tests pass: `npx tsx --test
packages/server/src/speech/service.test.ts
packages/server/src/settings/public-config.test.ts`
- Server typecheck: clean
- UI typecheck: clean
- UI build: succeeds (no duplicate i18n keys)
detect realpath when open workspace
try avoid path issue in windows
- d:\xxx
- D:\xxx
- symbol link
now they are same path when try detect exists workspace
---------
Co-authored-by: Pascal André <pascalandr@gmail.com>
## Summary
Yolo (permission auto-accept) currently lives entirely in the UI. Each
browser keeps its own toggle in `localStorage` and auto-replies to
permission requests over a 4-hop path (`OpenCode → server SSE → UI →
server proxy → OpenCode`). The server — which already sits on the event
stream that carries every `permission.asked` — does none of the work.
This PR makes the **server authoritative**: it owns the toggle state,
resolves family-root inheritance, and performs the auto-reply in-process
via loopback using the same `"once"` semantics the UI used to send. The
UI becomes a pure view: it toggles via REST and mirrors state from a
`yolo.stateChanged` SSE event.
## Why
- **Correctness**: the server already consumes the instance SSE stream
(`InstanceEventBridge`); auto-accepting there is the natural choke point
instead of bouncing to the UI and back.
- **Multi-client**: previously each browser had independent
`localStorage` state and never synced. Toggles now broadcast to all
connected clients in real time.
- **Headless**: Yolo keeps auto-accepting even when no UI is connected
(useful for long autonomous runs).
- **Latency**: drops from 4 hops to a single in-process loopback call.
## What changed
**Server (new, authoritative)**
- `permissions/auto-accept-store.ts` — in-memory state keyed by family
root. Faithful port of `resolvePermissionAutoAcceptFamilyRoot`:
fork/`revert` sessions root at themselves; enabling any member enables
the whole family.
- `permissions/auto-accept-manager.ts` — subscribes to `instance.event`,
builds the session tree from `session.created/updated/deleted`
(`properties.info`), intercepts `permission.v2.asked` /
`permission.asked`, dedupes in-flight replies, emits `yolo.stateChanged`
/ `yolo.autoAccepted`, clears per-instance state on
`workspace.stopped/error`.
- `permissions/opencode-replier.ts` — default replier calling OpenCode
directly (`getInstancePort` + auth header), mirroring
`background-processes/manager.ts`.
- `server/routes/yolo.ts` — `GET/POST
/workspaces/:id/yolo/sessions/:sid[/toggle]`, following existing route
conventions.
- `api-types.ts` / `events/bus.ts` — `YoloStateResponse` + the two new
event types registered in `onEvent` so they flow over `/api/events`.
**UI (pure view)**
- `permission-auto-accept.ts` — removed `localStorage`, persistence, and
drain logic; now a runtime (non-persisted) projection.
`resolvePermissionAutoAcceptFamilyRoot` is **retained as a display aid**
so the badge still lights up for child/sub-sessions of an enabled family
(preserving the inheritance UX).
- `instances.ts` — toggle calls REST (optimistic, reconciled on success,
reverted on failure); subscribes to `yolo.stateChanged`;
`ensureYoloStateSynced` backfills the active session's state on first
connect (deduped per session, reset on SSE reconnect so it re-syncs
after a server restart).
- `session-events.ts` — removed the three
`drainAutoAcceptPermissionsForInstance` hooks (the server drains now).
- `api-client.ts` — `getYoloState` / `toggleYolo`.
## Behavior parity
| Aspect | Before | After |
|---|---|---|
| Reply semantics | `"once"` | `"once"` (unchanged) |
| Inheritance | whole family (root + non-fork descendants) | **same** |
| Fork isolation | `revert` session is its own root | **same** |
| Persistence | UI `localStorage` | none (intentional, see Notes) |
| Multi-client sync | ❌ independent per browser | ✅ real-time via SSE |
| Works with UI closed | ❌ | ✅ |
| Auto-reply path | 4 hops | 1 in-process hop |
## Testing
32 unit tests added (`node:test`), all passing. Server + UI typecheck
clean.
- **Store (16)**: inheritance (parent/child/sibling), fork isolation,
cyclic parent chains, late parent discovery, `revert` re-rooting,
per-instance independence, tree maintenance.
- **Manager (13)**: real `properties.info` event shapes, v2 vs legacy
reply, in-flight dedup + retry-after-resolve, `yolo.stateChanged`
emission, `session.deleted` keeps toggle, `workspace.stopped` clears
state, `stop()` unsubscribes.
- **UI (3)**: retained `resolvePermissionAutoAcceptFamilyRoot`
display-projection tests.
## Notes for reviewers
- **No persistence is intentional** for this milestone — server restart
resets all Yolo state (matches the agreed scope). The UI mirror
self-heals via SSE reconnect + `ensureYoloStateSynced`. Persistence can
be layered on later (e.g. into `~/.config/codenomad/config.json`)
without touching the manager.
- **Family-root resolution lives in two places on purpose**: the server
resolves it to decide whether to auto-reply; the UI resolves the same
pure function to render the badge instantly (synchronous memo). Both are
faithful ports; no network round-trip is added for display.
- **`properties.info` nesting**: OpenCode wraps session records under
`properties.info` for `session.*` events (permission events are flat).
The manager handles both and the tests use the real nested shape to
guard against regressions.
- `getYoloState` / `yolo.autoAccepted` are wired but `yolo.autoAccepted`
is not yet consumed by the UI — it's available on the wire for future
observability (e.g. an audit log / toast).
## Risk / rollback
The change is additive on the server and the UI gracefully degrades to
the SSE mirror. If the server lacks the new routes (mixed-version), the
UI's optimistic toggle still flips locally and `toggleYolo` failures are
logged + reverted, so no hard breakage.
---------
Co-authored-by: Pascal André <pascalandr@gmail.com>
## Summary
- Adds editable workspace display names for recent folders.
- Shows the saved workspace name in the recent folder list and instance
tab, including the launching workspace state.
- Preserves workspace names when recent folders are relaunched and keeps
basename fallback for unnamed folders.
Fixes#510
## Validation
- npm run typecheck --workspace @codenomad/ui
- npm run typecheck --workspace @neuralnomads/codenomad
## Summary
This fixes#518 by moving CodeNomad worktree execution from
directory-header routing to OpenCode experimental workspace routing.
OpenCode changed existing-session routing so session routes can prefer
the session's stored directory over `x-opencode-directory`. That made
the old CodeNomad worktree proxy model unreliable for sessions that
should execute in a worktree. This PR switches CodeNomad to resolve an
OpenCode workspace ID for each CodeNomad worktree and pass that
workspace ID on worktree-scoped OpenCode calls.
## What changed
- Start OpenCode with `OPENCODE_EXPERIMENTAL_WORKSPACES=true`.
- Change the OpenCode server base URL from
`/workspaces/:id/worktrees/root/instance` to `/workspaces/:id/instance`.
- Add a root OpenCode client helper in
`packages/ui/src/stores/opencode-client.ts`.
- Add OpenCode workspace sync/cache helpers in
`packages/ui/src/stores/opencode-workspaces.ts`.
- Sync OpenCode workspaces after CodeNomad worktree hydration and after
worktree create/delete flows.
- Map CodeNomad worktree slugs/directories to OpenCode `workspace.id`
values discovered by `experimental.workspace.syncList` and
`experimental.workspace.list`.
- Replace all OpenCode worktree clients with the root client plus
explicit `workspace` payloads where the active session/worktree requires
it.
- Route session, permission/question, file browser reads, SDK git
status, and prompt/action calls through root OpenCode client + workspace
ID.
- Keep CodeNomad local git worktree server APIs intact; those still need
filesystem directories for local git operations.
## Review fix
- Fixed right-panel file saves so they no longer write to the root
workspace after reading from a selected worktree.
- The existing CodeNomad file-content API now accepts an optional
`worktree` query parameter.
- Right-panel saves pass `worktreeSlugForViewer()`, and the server
resolves that slug to the same worktree directory used by local git
worktree APIs before writing.
- Root saves continue to use the original root workspace path.
## Removed old routing
- Removed `/workspaces/:id/worktrees/:slug/instance` OpenCode proxy
routes.
- Removed directory override proxy support using `/__dir/<encoded>`.
- Removed proxy injection of `x-opencode-directory`.
- Removed the remaining background-process completion prompt
`x-opencode-directory` header.
- Removed `getOrCreateWorktreeClient`,
`getOrCreateWorktreeClientWithDirectoryOverride`, and worktree proxy
path helpers from the UI worktree store.
- Simplified `sdkManager.createClient` because clients are no longer
keyed by worktree slug.
## Why this fixes#518
Worktree sessions can now stay visible under the root project session
listing while worktree execution is selected through OpenCode's
workspace routing model. CodeNomad no longer depends on
`x-opencode-directory` to override existing session directories, so
sessions should not disappear from the root-directory list or execute in
the wrong directory because of stale session directory fallback
behavior.
Fixes#518
## Validation
- `npm run typecheck --workspace @codenomad/ui`
- `npm run typecheck --workspace @neuralnomads/codenomad`
- `git diff --check`
## Notes
Some touched files are already oversized and were not refactored as part
of this migration: `packages/server/src/server/http-server.ts`,
`packages/ui/src/stores/instances.ts`,
`packages/ui/src/stores/session-api.ts`,
`packages/ui/src/stores/session-state.ts`,
`packages/ui/src/components/instance/shell/right-panel/RightPanel.tsx`,
`packages/ui/src/stores/session-events.ts`, and
`packages/server/src/background-processes/manager.ts`.
## Summary
- Upgrade `@opencode-ai/sdk` to `1.15.13` and adapt UI types for the
current session/diff API surface.
- Add a session metadata store helper that safely updates OpenCode
session metadata via read-merge-full-replace while preserving
non-CodeNomad metadata.
- Migrate CodeNomad worktree assignments from
`.codenomad/worktreeMap.json` into `metadata.codenomad.worktreeSlug`,
with legacy fallback, stale-entry pruning, and server-side deletion of
the legacy map once empty.
- Add `.codenomad/background_processes/` to CodeNomad-managed
`.git/info/exclude` entries.
## Details
- New sessions and worktree reassignment now persist explicit session
worktree assignments in OpenCode session metadata under:
`metadata.codenomad.worktreeSlug`
- Worktree resolution now prefers session metadata, then falls back to
legacy `parentSessionWorktreeSlug`, then `root`.
- Legacy migration runs after session/map loading; after the full
session list is known, missing legacy session IDs are treated as stale
and pruned.
- The UI no longer uses `defaultWorktreeSlug`; the fallback is `root`.
- `writeWorktreeMap` deletes `.codenomad/worktreeMap.json` server-side
when no legacy parent-session mappings remain.
## Validation
- `npm run typecheck --workspace @codenomad/ui`
- `npm run typecheck --workspace @neuralnomads/codenomad`
## Summary
Fixes#468 and #470. The quick-start examples crashed on first run
without a password, and the browser self-signed certificate warning was
not documented anywhere a new user would see it.
## Changes
- Add `--password` to all npx quick-start examples (main README + server
README)
- Document the three ways to configure auth: `--password`, env var,
`auth.json`
- Show `auth.json` schema so users understand the expected format
- Add browser warning note to self-signed certificates section with
step-by-step instructions for Chrome/Brave and Firefox
- Mention `--https=false --http=true` as an alternative for local-only
use
## Validation
- Reviewed rendered markdown structure
- Verified auth.json schema matches AuthFile interface in auth-store.ts
## Summary
- Add an allowlisted server API for editing global config files,
starting with OpenCode global config.
- Add a Config Files settings section using the existing Monaco editor
with save, reload, dirty-state handling, and responsive layout.
- Improve compact settings navigation with a full-width section
selector, settings icon, and close action in the compact toolbar.
## Validation
- npm run typecheck in packages/server
- npm run typecheck in packages/ui
- npm run build in packages/ui
## Notes
- Existing Vite warnings about virtua JSX import source and large chunks
remain unrelated.
## Summary
- Expands Linux `--launch` browser discovery for Brave and Vivaldi
package variants (`brave-browser-stable`, `brave`, `vivaldi-stable`).
- Adds snap path candidates for Brave and Vivaldi plus Flatpak launch
commands.
- Adds `xdg-open` as the final fallback when direct browser app-mode
launch candidates are unavailable.
## Validation
- Attempted `npm run typecheck --workspace @neuralnomads/codenomad`, but
it failed before checking this change because the worktree is missing
the `node` type definitions (`TS2688: Cannot find type definition file
for 'node'`).
Fixes#469
--
Yours,
[CodeNomadBot](https://github.com/NeuralNomadsAI/CodeNomad)
---------
Co-authored-by: Shantur Rathore <i@shantur.com>
## Summary
- Rename the OpenCode config template into a versioned npm-packable
CodeNomad plugin package.
- Build and package the plugin through the server bundle, with
Electron/Tauri carrying it via existing server resources.
- Replace OPENCODE_CONFIG_DIR injection with JSONC-aware
OPENCODE_CONFIG_CONTENT merging that appends the CodeNomad plugin while
preserving user config.
## Validation
- npm run build --workspace @codenomad/codenomad-opencode-plugin
- npm run prepare-plugin --workspace @neuralnomads/codenomad
- npm run typecheck --workspace @neuralnomads/codenomad
- npm run typecheck --workspace @neuralnomads/codenomad-electron-app
- node --import tsx --test \"src/opencode-plugin.test.ts\"
\"src/workspaces/__tests__/spawn.test.ts\"
## Notes
- Production plugin loading uses an explicit npm file alias for the
packaged tarball.
- Dev loading still references the TypeScript plugin entry directly.
---------
Co-authored-by: Pascal André <pascalandr@gmail.com>
## Summary
- Reuses the directory browser for web file attachment selection and
removes the separate filesystem browser dialog.
- Reads web-selected files through the filesystem browser API so
selection respects unrestricted and configured workspace roots.
- Keeps Electron/Tauri picker selection on the FileList ingestion path
so picked files behave like dropped files with byte-backed attachments.
## Validation
- npm run typecheck --workspace @codenomad/ui
- npm run typecheck --workspace @neuralnomads/codenomad
- npm run typecheck --workspace @neuralnomads/codenomad-electron-app
- cargo check
- git diff --check
## Notes
- Follow-up to #417; this branch currently includes the commits from
that PR plus the attachment picker parity fix.
---------
Co-authored-by: Pascal André <pascalandr@gmail.com>
## Summary
- Add ephemeral per-session web preview mode for opening arbitrary
HTTP(S) URLs inside the session workspace without persisting preview
state across reloads.
- Reuse the SideCar iframe shell by extracting a shared BrowserFrame
with navigation, refresh, path entry, viewport presets, and no-injection
element comment targeting.
- Add authenticated preview proxy routes for HTTP and WebSocket traffic,
sharing lower-level proxy forwarding with SideCars to avoid duplicating
proxy code.
- Localize all new preview and viewport strings across English, Spanish,
French, Hebrew, Japanese, Russian, and Simplified Chinese.
## User-facing behavior
- Users can open a web preview from the session toolbar and toggle back
and forth between chat and preview mode.
- Preview mode replaces only the message stream/timeline area; the
prompt input and attachments stay available below the preview.
- Comment mode lets users select elements in the same-origin proxied
iframe and append structured page/element references to the prompt
draft.
- The generated prompt quotes the page/element reference while leaving
the user's actual comment as normal prompt text.
- Viewport selection supports responsive, desktop, tablet
portrait/landscape, and mobile portrait/landscape canvases, with fixed
sizes isolated inside the iframe scroll container.
## Implementation notes
- Preview sessions are in-memory and keyed by short-lived tokens exposed
through `/previews/:token`.
- Preview proxy responses strip frame-blocking headers and rewrite
same-origin redirect locations back under the preview route.
- SideCarView now composes the shared BrowserFrame, preserving existing
SideCar behavior while gaining shared viewport controls.
- Parent-side iframe DOM access powers hover highlighting and element
metadata extraction, avoiding HTML/script injection for the first
implementation.
## Validation
- `npm run typecheck --workspace @codenomad/ui`
- `npm run typecheck --workspace @neuralnomads/codenomad`
## Notes
- Existing unrelated local changes to `package.json` and
`package-lock.json` were intentionally left out of this PR.
- The initial proxy does not yet rewrite all HTML/CSS asset references;
pages with root-relative assets may need follow-up URL rewriting.
## Summary
- Adds a server endpoint to clone a Git repository into a selected local
destination.
- Adds a folder selection action/dialog for repository URL, destination
folder, and optional destination cleanup.
- Opens the cloned folder as the workspace after clone succeeds.
Fixes#253
## Validation
- npm run typecheck --workspace @codenomad/ui
- npm run typecheck --workspace @neuralnomads/codenomad
- npm run build --workspace @codenomad/ui
When --unrestricted-root is enabled, filesystem browsing now starts from the configured workspace root instead of defaulting to the user's home directory. This makes CLI_WORKSPACE_ROOT and --workspace-root define the initial location while preserving unrestricted navigation to absolute paths outside that root.
The implementation updates unrestricted path resolution so empty and dot paths resolve to the configured root, and aligns unrestricted metadata so rootPath consistently represents that configured root. The Windows drives pseudo-root now reports the same rootPath as other unrestricted listings, keeping the metadata contract uniform across platforms.
Add FileSystemBrowser tests covering unrestricted default browsing, dot-path handling, outside-root navigation, default folder creation, and Windows drive pseudo-root metadata via a platform override. Also make the existing workspace search cache refresh test use deterministic timestamps so the full server test suite remains reliable.
Fixes#321
## Summary
- expose OPENCODE_SERVER_BASE_URL in the spawned OpenCode workspace
environment
- point it at the current workspace's stable CodeNomad proxy URL
- propagate the new variable through WSL launches alongside the existing
auth env vars
## Why
Tools and plugins already receive the auth credentials needed to call
the current workspace instance, but they still need an extra lookup to
discover the correct URL.
The proxy URL is already known before runtime startup, so exposing it
directly removes that lookup without changing OpenCode's port selection
flow.
## What Changed
- packages/server/src/workspaces/opencode-auth.ts: added the
OPENCODE_SERVER_BASE_URL env var constant
- packages/server/src/workspaces/manager.ts: injects
OPENCODE_SERVER_BASE_URL into the workspace runtime environment using
CODENOMAD_BASE_URL + proxyPath
- packages/server/src/workspaces/__tests__/spawn.test.ts: updates WSL
env propagation coverage for the new variable
## Validation
- npx tsx --test
"packages/server/src/workspaces/__tests__/spawn.test.ts"
## Summary
- Adds a `--upgrade [version]` CLI flag that upgrades the global
CodeNomad CLI server package and exits.
- Uses `bun add --global` for the package upgrade path and includes
server-side tests.
- Rebased onto the latest `dev` because we do not have permission to
push to the original fork branch.
## Credits
- Original PR: #363
- Original author: Pascal André (@pascalandr)
## Testing
- Not run; this PR only recreates the rebased branch from #363.
---------
Co-authored-by: Pascal André <pascalandr@gmail.com>
Fixes#202
## Summary
- keep the default `root` worktree directory pointed at the folder the
user opened
- continue using the git repo root only for git/worktree discovery
- add a targeted regression test for opening a repo subfolder as the
workspace
## Why
When a workspace is opened from a subfolder inside a git repo, CodeNomad
currently maps the `root` worktree to the repo root. That causes proxied
OpenCode requests to run with the repo root directory and miss an
`opencode.json` that lives in the selected subfolder.
## Validation
- inspected the attached `config-issue.zip` from #202
- confirmed `resolveRepoRoot(proj-1)` still returns the git root while
`listWorktrees()` now returns `root.directory = proj-1`
- `npx tsx --test
"packages/server/src/workspaces/__tests__/git-worktrees.test.ts"`
- `npm run typecheck --workspace @neuralnomads/codenomad`
## Summary
- Adds editable path entry directly inside the folder browser dialog
while keeping browse-first behavior.
- Removes the multi-root workspace picker changes from the source
implementation.
- Refines responsive controls so mobile shows the path field first, then
New Folder and Open actions together.
## Credits
- Based on the work and request flow from #350. Thanks to the original
requester and contributor there for the folder picker path input idea.
## Verification
- npm run typecheck --workspace @neuralnomads/codenomad
- npm run typecheck --workspace @codenomad/ui
---------
Co-authored-by: Pascal André <pascalandr@gmail.com>
## Summary
- revert the Bun standalone desktop packaging path and restore the
server's original `dist/bin.js` bootstrap flow
- add a managed Node runtime for Electron and Tauri that downloads only
the current platform/arch artifact into `~/.config/codenomad`
- update desktop startup and packaging scripts so packaged apps use the
managed runtime consistently, and clean up Electron's expected
navigation-abort log noise
## Testing
- npm run typecheck --workspace @neuralnomads/codenomad-electron-app
- cargo check
- npm run build --workspace @neuralnomads/codenomad
- npm run build:mac --workspace @neuralnomads/codenomad-electron-app
- launch
`packages/electron-app/release/mac-arm64/CodeNomad.app/Contents/MacOS/CodeNomad`
and verify the packaged server reaches ready with the managed Node
runtime
## Summary
- package `packages/server` as a standalone desktop executable so
Electron and Tauri no longer depend on a system-installed Node runtime
in production
- align Electron and Tauri startup logic around launching the packaged
server, resolving binaries from the user shell, and bundling the same
server resources into both desktop apps
- replace the workspace instance proxy path that used
`@fastify/reply-from` with a direct streaming proxy so packaged
standalone builds can talk to spawned `opencode` instances correctly
## Why
Desktop production builds were still depending on a user-provided Node
runtime to launch `packages/server`, which made packaging less
self-contained and created different behavior across machines. While
moving to a standalone server executable, we also found that
Bun-compiled standalone builds could start `opencode` successfully but
failed when proxying requests to those instances through `reply-from`.
The goal of this change is to make desktop production startup
self-contained, keep Electron and Tauri behavior aligned, and restore
correct communication with local `opencode` instances in packaged
builds.
## What Changed
- added a standalone build path for `packages/server` and bundle
`codenomad-server` into desktop resources
- updated Electron production startup to resolve and launch the
standalone server executable
- updated Tauri production startup to resolve and launch the standalone
server executable with matching cwd and shell behavior
- added runtime path helpers so the packaged server can reliably find
its bundled UI, auth templates, config template, and package metadata
- improved bare binary resolution so commands like `opencode` can be
resolved from the user's login shell environment
- upgraded the server stack to newer Fastify-compatible packages needed
for the standalone/runtime work
- replaced the workspace instance proxy implementation with a direct
streaming proxy for requests to spawned `opencode` instances
- updated Electron and Tauri build/prebuild scripts to generate and
package the standalone server, while also repairing missing
platform-specific optional binaries during packaging
## Benefits
- desktop production builds no longer require Node to be installed on
the user's system
- Electron and Tauri now use the same packaged server model in
production, reducing platform drift
- packaged desktop apps can successfully create workspaces, launch
`opencode`, and proxy health/session traffic to those instances
- the server bundle is more self-contained and resilient to different
launch environments
- desktop packaging is more predictable because the required server
executable is built and bundled as part of the app build flow
## Summary
- support Windows validation and launch of OpenCode binaries stored
under WSL UNC paths like \\wsl.localhost\...
- harden the existing manual directory browser so absolute, UNC, and WSL
paths can be pasted and navigated reliably
- harden WSL env/path propagation, UNC workspace handling, runtime
shutdown, and add targeted tests
Partially addresses #5.
## Testing
- node --test --import tsx src/workspaces/__tests__/spawn.test.ts
- npm run typecheck --workspace @neuralnomads/codenomad
- npm run typecheck --workspace @codenomad/ui
## Summary
- add a server-backed HTTPS proxy flow for Tauri remote windows so
self-signed remote HTTPS works with the local CLI TLS assets and desktop
auth/cookie handling
- manage remote proxy sessions through `packages/server` with
per-session bootstrap, local-only cleanup, and explicit session
lifecycle handling
- support the Tauri desktop flow across environments, including packaged
Windows builds, `tauri dev`, and updated Linux/macOS handling for the
new local HTTPS proxy path
## Testing
- `npm run build --workspace @neuralnomads/codenomad`
- `cargo check`
- `npm run build --workspace @codenomad/tauri-app`
- Windows smoke test for concurrent remote proxy bootstrap sessions
- Windows manual validation of packaged Tauri remote connection flow
## Notes
- Windows was validated end-to-end.
- Linux and macOS code paths were updated for the new proxy flow, but
runtime validation on those platforms is still pending.
---------
Co-authored-by: Shantur Rathore <i@shantur.com>
# Git Changes PR Review Context
Fixes: #310
## Purpose of this document
This document is intended to give a PR reviewer or gatekeeper enough
neutral context to review the Git Changes feature series accurately.
## BEFORE/AFTER SNAPSHOT:
<img width="835" height="1163" alt="image"
src="https://github.com/user-attachments/assets/463d6f8c-1a6b-4cf0-8ab8-44a92c534ca5"
/>
It distinguishes:
1. the intended scope of the work
2. implementation choices that were deliberate
3. behaviors that were explicitly tested and accepted during development
4. remaining follow-up areas that were not part of the required intent
It should not be treated as a request to approve the PR automatically.
It exists to reduce false-positive review findings caused by missing
context.
---
## High-level scope
The work in this series refactors and extends the existing `Git Changes`
tab in the right panel.
The intended feature scope includes:
1. grouped staged / unstaged change presentation
2. correct section-aware diff loading
3. per-file stage / unstage controls
4. commit message compose box and commit action for staged changes
5. prompt-context insertion from the Git diff viewer
6. auto-refresh behavior that reduces dependence on the manual refresh
button
This work is intentionally implemented inside the existing Git Changes
vertical slice rather than as a new SCM subsystem.
---
## Files and areas intentionally changed
### Server / API surface
The following server areas were intentionally extended:
1. `packages/server/src/api-types.ts`
2. `packages/server/src/events/bus.ts`
3. `packages/server/src/server/http-server.ts`
4. `packages/server/src/server/routes/workspaces.ts`
5. `packages/server/src/workspaces/git-status.ts`
6. `packages/server/src/workspaces/git-mutations.ts`
7. `packages/server/src/workspaces/worktree-directory.ts`
8. `packages/server/src/workspaces/instance-events.ts`
### UI surface
The following UI areas were intentionally extended:
1. `packages/ui/src/components/file-viewer/monaco-diff-viewer.tsx`
2. `packages/ui/src/components/instance/instance-shell2.tsx`
3.
`packages/ui/src/components/instance/shell/right-panel/RightPanel.tsx`
4.
`packages/ui/src/components/instance/shell/right-panel/git-changes-model.ts`
5.
`packages/ui/src/components/instance/shell/right-panel/tabs/GitChangesTab.tsx`
6. `packages/ui/src/components/instance/shell/right-panel/types.ts`
7. `packages/ui/src/components/instance/shell/storage.ts`
8. `packages/ui/src/components/prompt-input.tsx`
9. `packages/ui/src/components/prompt-input/types.ts`
10. `packages/ui/src/components/session/session-view.tsx`
11. `packages/ui/src/lib/api-client.ts`
12. `packages/ui/src/lib/i18n/messages/*/instance.ts`
13. `packages/ui/src/styles/panels/right-panel.css`
---
## Intentional product and architecture decisions
The following outcomes were deliberate and should not be flagged as
issues merely because they exist.
### Git status / diff architecture
1. The UI does not rely only on the proxied OpenCode `file.status()`
payload.
2. CodeNomad adds server-backed worktree Git status and diff endpoints
to expose staged / unstaged semantics correctly.
3. Server-backed worktree mutation endpoints were added for:
- stage
- unstage
- commit
4. The existing event bus / SSE channel is reused for Git invalidation,
instead of adding a bespoke invalidation route.
### Git Changes UI structure
1. The file list is grouped into:
- `Staged Changes`
- `Changes`
2. Both sections are collapsible.
3. Section open state is persisted.
4. The same file may appear in both sections when Git state genuinely
requires that.
5. Rows are filename-first, with parent path as secondary text.
6. Rows are intentionally compact compared to the original flat list.
### Diff behavior
1. Diff loading is section-aware.
2. Deleted files are supported in grouped mode.
3. Binary files are treated as non-line-oriented in the diff viewer.
4. Binary diffs suppress line-based prompt-context affordances.
### Stage / unstage / commit workflow
1. Stage and unstage are per-file row actions.
2. Bulk stage-all / unstage-all was intentionally not added.
3. The commit compose box is intentionally rendered inside the `Staged
Changes` section.
4. The commit button is intentionally overlaid inside the commit input
area.
5. The current commit compose flow is minimal by design:
- no push
- no amend flow
- no branch management
### Prompt-context insertion
1. Prompt insertion is intentionally an HTML comment marker, not a full
diff payload.
2. The expected inserted form is:
`<!-- Git change context: <path> lines X-Y -->`
3. The trigger UI is intentionally a seam/gutter action in the Monaco
diff viewer, not a toolbar button.
### Row action reveal behavior
1. Stage / unstage row actions are intentionally hover-revealed on
hover-capable layouts.
2. The row action reveal intentionally uses:
- delayed hide
- slight stats fade/shift
- compact idle width
3. On non-hover layouts, the action remains visible for reliability.
### Auto-refresh behavior
The accepted refresh model is intentionally hybrid:
1. refresh on Git Changes tab activation
2. 20-second polling only while the Git Changes tab is active
3. immediate invalidation from completed raw tool events for:
- `write`
- `edit`
- `apply_patch`
This hybrid model is intentional. Polling remains as a fallback even
after tool-event invalidation.
---
## Behaviors explicitly tested during development
The following behaviors were explicitly exercised during development and
used to guide fixes.
### Grouped staged / unstaged behavior
1. files appear in the correct staged / unstaged sections
2. section collapse / expand works
3. collapse state persists
4. line counts are section-specific
### Diff behavior
1. staged diff loads differently from unstaged diff
2. deleted-file handling was verified and corrected
3. binary-file rendering was corrected to avoid line-oriented behavior
4. untracked binary files no longer report fake text line counts
### Mutation behavior
1. per-file stage works from `Changes`
2. per-file unstage works from `Staged Changes`
3. stage / unstage selection remapping was exercised and corrected
4. unborn-repo unstage behavior was explicitly hardened
### Prompt-context behavior
1. selected line / range insertion was tested
2. button placement in the Monaco seam/gutter was iterated and verified
### Auto-refresh behavior
1. tab-activation refresh was tested
2. 20-second active-tab polling was tested
3. raw completed tool invalidation was tested in the running UI for:
- `write`
- `edit`
- `apply_patch`
4. stale async overwrite and stale selection restoration bugs were found
and fixed through review/testing
---
## Review findings that were investigated and are no longer intended
blocker topics
The following areas were previously raised by strict reviews and then
either fixed or determined to be acceptable within scope.
### Fixed in the current series
1. duplicate stage / unstage firing
2. stale diff response overwriting newer selection
3. passive refresh restoring a stale selection
4. instance-wide invalidation overreach
5. selected diff staying stale after tool invalidation
6. worktree-switch status races
7. unhandled rejection risk from async invalidation publication
8. queued invalidation intent being lost during in-flight refresh
9. `git-diff` path traversal / absolute path boundary issue
### Investigated and considered non-blocking within current intent
1. split add/delete presentation for tracked rename behavior
- this was compared against VS Code behavior during manual testing
- no stage/unstage corruption was observed in the tested flow
- this is currently treated as a representation tradeoff, not a proven
blocker
---
## Remaining non-blocker follow-up areas
The following are still reasonable follow-up topics, but they were not
part of the required blocker-fix scope.
1. normalize directory-to-worktree matching more aggressively on Windows
so tool invalidation works more reliably from nested directories or
path-format variations
2. improve keyboard discoverability of hover-revealed stage / unstage
actions
3. reserve textarea space for the overlaid commit button if the overlay
tradeoff is reconsidered
4. reduce size/complexity in:
- `RightPanel.tsx`
- `right-panel.css`
5. tighten raw SSE tool-event parsing into a more explicit helper if
that event bridge grows further
These follow-ups should not be interpreted as evidence that the core
implementation is incomplete unless a reviewer finds a new concrete
failure.
---
## Suggested review focus
If a gatekeeper or reviewer is evaluating this PR, the most useful focus
areas are:
1. whether staged / unstaged behavior is correct for normal Git
workflows
2. whether the new server worktree Git endpoints remain narrowly scoped
3. whether auto-refresh remains bounded to the active Git Changes
context
4. whether the explicit fixes for stale async behavior and invalidation
races are sufficient
5. whether any unintentional server boundary broadening or state
corruption remains
Less useful review topics, unless tied to a concrete failure, are:
1. preference disagreements with accepted prompt insertion format
2. preference disagreements with the overlaid commit button placement
3. preference disagreements with keeping polling fallback alongside tool
invalidation
4. objections to server-backed Git endpoints purely because they add
surface area
---
## Summary
This series intentionally evolves the existing Git Changes tab into a
more complete source-control workflow for:
1. grouped staged / unstaged inspection
2. section-aware diffs
3. per-file staging and unstaging
4. commit composition for staged changes
5. prompt-context insertion from Git diffs
6. bounded auto-refresh for both passive viewing and agent-driven file
mutations
The intended review standard is to find concrete correctness, layering,
or maintenance problems that remain after this series — not to re-argue
the already accepted product choices listed above.
---------
Co-authored-by: Shantur Rathore <i@shantur.com>