diff --git a/.github/workflows/help-pages.yml b/.github/workflows/help-pages.yml
new file mode 100644
index 000000000..3350d5ef7
--- /dev/null
+++ b/.github/workflows/help-pages.yml
@@ -0,0 +1,60 @@
+name: Publish help
+
+on:
+ push:
+ branches: [dev]
+ paths:
+ - 'docs/help/**'
+ - 'docs/screenshots/workspace-0.20.png'
+ - 'images/CodeNomad-Icon.png'
+ - 'scripts/build-help.mjs'
+ - 'scripts/test-help.mjs'
+ - 'package-lock.json'
+ - '.github/workflows/help-pages.yml'
+ pull_request:
+ paths:
+ - 'docs/help/**'
+ - 'docs/screenshots/workspace-0.20.png'
+ - 'images/CodeNomad-Icon.png'
+ - 'scripts/build-help.mjs'
+ - 'scripts/test-help.mjs'
+ - 'package-lock.json'
+ - '.github/workflows/help-pages.yml'
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ build:
+ runs-on: ubuntu-24.04
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: .node-version
+ cache: npm
+ - run: npm ci --ignore-scripts
+ - run: node --test scripts/test-help.mjs
+ - run: node scripts/build-help.mjs
+ - uses: actions/upload-pages-artifact@v3
+ with:
+ path: dist/help
+
+ deploy:
+ if: github.ref == 'refs/heads/dev' && github.event_name != 'pull_request'
+ needs: build
+ runs-on: ubuntu-24.04
+ permissions:
+ pages: write
+ id-token: write
+ concurrency:
+ group: help-pages
+ cancel-in-progress: false
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ steps:
+ - uses: actions/configure-pages@v5
+ - uses: actions/deploy-pages@v4
+ id: deployment
diff --git a/AGENTS.md b/AGENTS.md
index 24545d716..a067f10b1 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -94,6 +94,8 @@
## Multi-Language Support (i18n)
+- The separate public user guide lives in `docs/help/` and is intentionally English-only for now. `scripts/build-help.mjs` renders its Markdown using the existing `marked` dependency; `docs/help/help.css` owns the standalone site's square, responsive chrome. It does not import the application runtime or add an app locale. Validate it with `npm run test:help`; GitHub Pages publishes `dist/help` through `.github/workflows/help-pages.yml`.
+
The UI uses a small custom i18n layer (no ICU/messageformat). When building features, never hardcode user-visible strings.
- **Runtime API:** use `useI18n()` in components (`const { t } = useI18n();`) and `tGlobal(...)` in stores/non-component code.
diff --git a/README.md b/README.md
index 422dc6014..bf98db61f 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,8 @@ CodeNomad is a **desktop and browser workspace for OpenCode V2** — built for d
*The 0.20 interface, rendered with demonstration data. [Download the latest stable release](https://github.com/NeuralNomadsAI/CodeNomad/releases/latest).*
+[User help](https://neuralnomadsai.github.io/CodeNomad/) · [Read the guide on GitHub](docs/help/index.md) — a short English guide to current CodeNomad and OpenCode V2.
+
---
## Features
diff --git a/dev-docs/USER_HELP.md b/dev-docs/USER_HELP.md
new file mode 100644
index 000000000..f9225cb4c
--- /dev/null
+++ b/dev-docs/USER_HELP.md
@@ -0,0 +1,44 @@
+# Public user help
+
+The English guide in `docs/help/` covers current CodeNomad with OpenCode V2,
+not the old runtime or every upstream configuration option. Assume readers
+already know OpenCode: document CodeNomad's controls, workflow differences,
+and state/scope boundaries instead of teaching agents, prompts, Git, or MCP.
+Keep pages short, use current English interface labels, and link to the
+official V2 docs for native configuration details.
+
+## Build and check
+
+```sh
+npm ci --ignore-scripts
+npm run test:help
+npm run build:help
+```
+
+Output is `dist/help/`. Serve that directory with any static HTTP server to
+preview it; no app backend, credentials, or OpenCode service is needed. The
+builder uses the existing `marked` dependency, copies local screenshots/logo,
+and generates relative links that work under a GitHub project-site path.
+The site has no client JavaScript, remote fonts, analytics, or runtime requests.
+
+## Publish on GitHub Pages
+
+In **Repository Settings → Pages → Build and deployment**, select **GitHub
+Actions** as the source. The repository currently has no Pages site; this is a
+one-time administrator setup, not something the workflow silently enables.
+Allow the `github-pages` environment to deploy from `dev` if its protection
+rules require a branch allowlist.
+
+After merging the workflow into `dev`, it builds and publishes changes to the
+help sources. It can also be started manually from `dev`. Pull requests build
+and check the site but never deploy it. Deployment needs `pages: write` and
+`id-token: write`; the build job only reads repository contents.
+
+Expected URL: **https://neuralnomadsai.github.io/CodeNomad/**. No separate
+repository, paid hosting, domain, or additional deployment secret is needed.
+The site follows `dev` (the current implementation); it is not an archive of
+versioned guides. Update the help alongside user-visible behavior changes.
+
+The desktop Help link should use this URL once deployment is available. Native
+Help menu items are maintained in both desktop hosts; the updater work from
+issue #606 remains separate from documentation hosting.
diff --git a/docs/help/conversations.md b/docs/help/conversations.md
new file mode 100644
index 000000000..489668d41
--- /dev/null
+++ b/docs/help/conversations.md
@@ -0,0 +1,32 @@
+# Conversations
+
+The controls around your OpenCode sessions: composer context, pending requests, and full-history navigation.
+
+## Composer controls
+
+**Agent**, **model**, and **thinking** selectors live in the composer footer, alongside the session's worktree selector. Resize the composer without changing its draft; long drafts scroll within it.
+
+The composer remains available during execution. Subsequent submissions may enter the session's pending queue; **Stop** targets the current execution.
+
+## Context attachments
+
+- **`@`** searches project files, agents, and skills available in the session's Location. Selecting a skill attaches a removable badge rather than inserting its instructions into your draft.
+- The **attachment** action, drop, and paste use files from your client device. Project file references resolve on the execution host.
+- **`/`** lists the Location's commands. **`/btw`** opens a temporary side-question window without replacing the main conversation.
+
+## Requests across conversations
+
+Pending questions and permissions are edited **above the composer**, not in transcript cards. Request drafts survive queue refreshes and session navigation.
+
+The request area includes the open session, its descendants, and other conversations. Origin labels identify the source; **View conversation** navigates there. External requests start compact and can be answered in place. Arrival does not replace the request being edited or switch your session. Use previous/next controls when several requests are pending.
+
+CodeNomad's **Yolo mode** applies to the session family: it automatically approves permissions, not questions or other Forms.
+
+## History navigation
+
+- **Search** queries full history, not just mounted transcript rows.
+- **Timeline** markers jump to messages and tool activity. Hover/focus previews let you inspect a point without loading a whole transcript section. At narrow conversation widths, the rail is hidden and header actions move into overflow.
+- **Message content visibility** controls reasoning and tool presentation without changing stored history.
+- **Fork** continues from a history point in a separate session.
+
+Subsessions appear beneath their parent in normal browsing. Session search uses flat results, with an option to include subsessions. View closure, session deletion, and history pruning are separate operations; the latter two can remove stored content.
diff --git a/docs/help/files-and-tools.md b/docs/help/files-and-tools.md
new file mode 100644
index 000000000..29628959c
--- /dev/null
+++ b/docs/help/files-and-tools.md
@@ -0,0 +1,37 @@
+# Files and tools
+
+The right panel combines workspace browsing, Git inspection, and project services. Readers open centrally, above the composer.
+
+## Workspace files
+
+In **Files → Workspace**, expand folders and select a row. Use its **eye** action to open the central file reader; selecting a row alone does not open it.
+
+Text files, including Markdown source, are editable. Use **Save** or **Ctrl/Cmd+S** to write changes. If the file changed on disk, CodeNomad asks you to resolve that conflict rather than silently replacing it. Images and binary previews are read-only.
+
+Close the reader with its **X** to return to the conversation.
+
+## Review Git changes
+
+Switch **Files** to **Changes** to inspect staged and unstaged files. Open a diff with the eye action, stage or unstage files, and use the commit controls at the top of the staged section.
+
+Switch to **Commits** to browse repository history and inspect historical diffs. Diffs are read-only; use the layout switch to choose unified or split views.
+
+## Check Status
+
+The **Status** panel shows project services and background activity, including **MCP Servers** and **Plugins**.
+
+- **MCP Servers** exposes connection state and activation controls.
+- **Plugins** have separate **Global** and **Project** activation switches. Global changes can affect other projects; a project rule can override the global choice.
+- Background shell activity is distinct from an interactive terminal and from the agent's current reply.
+
+## Preview a web app
+
+Open the session's **browser preview** from the conversation toolbar. It is attached to that session; the agent can target the visible preview through CodeNomad's browser tools. Opening it does not start a development server.
+
+Agent-driven native browser control is available in Electron and **Windows Tauri**. Other Tauri platforms use an iframe preview; that is not equivalent to native browser automation. Some sites also prevent iframe embedding.
+
+## Add a SideCar
+
+**SideCars** are tabs for web tools running on the CodeNomad server host, such as an editor or terminal service.
+
+Start the tool separately, then add it in **Settings → SideCars** with its name, port, and protocol. The port refers to the **server host**, not necessarily the device displaying CodeNomad. Match the prefix mode to the tool's base-path support; see the [SideCar examples](https://github.com/NeuralNomadsAI/CodeNomad#sidecars).
diff --git a/docs/help/getting-started.md b/docs/help/getting-started.md
new file mode 100644
index 000000000..2e7660c89
--- /dev/null
+++ b/docs/help/getting-started.md
@@ -0,0 +1,31 @@
+# Connect OpenCode
+
+Use your existing OpenCode V2 setup from a local desktop host or through a CodeNomad server.
+
+## Local desktop
+
+Download [the current desktop build](https://github.com/NeuralNomadsAI/CodeNomad/releases/latest), then select your OpenCode executable in setup. CodeNomad prefers executables on `PATH`; **Settings → OpenCode** exposes the selected CLI and the running shared service separately. An installation action is available if you need it. Desktop builds bundle Node.js/npm.
+
+Open the repository or folder whose sessions you want to access. CodeNomad uses directory ownership to determine which sessions belong to that project; see [projects and worktrees](projects.md).
+
+## Backend versus execution host
+
+Git must be available in the **CodeNomad backend's `PATH`**, even when OpenCode executes inside WSL. Git installed only in WSL does not satisfy a Windows backend. Restart the backend after changing its environment.
+
+With Git unavailable, folder-only sessions remain usable, but Git/worktree features and cross-checkout discovery are unavailable.
+
+## Browser and remote desktop
+
+Run CodeNomad on the host containing your projects and OpenCode setup. The standalone server requires Node.js 24 LTS/npm:
+
+```sh
+npx @neuralnomads/codenomad --password "your-password" --launch
+```
+
+Use the printed connection URL in a browser or CodeNomad's remote connection screen. The server binds to loopback by default; use the [server guide](https://github.com/NeuralNomadsAI/CodeNomad/blob/dev/packages/server/README.md#remote-access-binding-rules) for remote binding, authentication, and TLS options.
+
+Project paths, commands, and SideCar ports refer to the **server host**, not the client displaying CodeNomad. Device attachments are the exception: they come from the client device.
+
+## CLI updates are not service restarts
+
+Updating OpenCode through CodeNomad changes the installed executable, not the running service. Restart explicitly when you want to activate it; this can interrupt work in all connected clients. Closing CodeNomad leaves the service running.
diff --git a/docs/help/help.css b/docs/help/help.css
new file mode 100644
index 000000000..7a53207c4
--- /dev/null
+++ b/docs/help/help.css
@@ -0,0 +1,93 @@
+:root {
+ color-scheme: light dark;
+ --canvas: #edf0f4;
+ --panel: #e0e6ed;
+ --text: #283a4a;
+ --muted: #526373;
+ --accent: #375c9b;
+ --border: #bdc9d6;
+ --code: #dce3ec;
+ font-family: "Segoe UI", system-ui, sans-serif;
+ line-height: 1.65;
+ background: var(--canvas);
+ color: var(--text);
+}
+@media (prefers-color-scheme: dark) {
+ :root {
+ --canvas: #272f40;
+ --panel: #333e52;
+ --text: #dce3ef;
+ --muted: #adbed4;
+ --accent: #c6b58c;
+ --border: #53647c;
+ --code: #333e52;
+ }
+}
+* { box-sizing: border-box; }
+body { margin: 0; }
+a { color: var(--accent); text-underline-offset: .2em; }
+a:hover { text-decoration-thickness: 2px; }
+:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
+.skip-link { position: fixed; top: -100px; left: 1rem; padding: .5rem 1rem; background: var(--canvas); z-index: 3; }
+.skip-link:focus { top: 1rem; }
+.site-header { display: flex; justify-content: space-between; align-items: center; gap: 1rem; padding: 1rem 2rem; border-bottom: 1px solid var(--border); }
+.brand { display: flex; align-items: center; gap: .7rem; color: var(--text); font-size: 1.25rem; font-weight: 650; text-decoration: none; }
+.brand span { font-size: .875rem; font-weight: 400; color: var(--muted); border-left: 1px solid var(--border); padding-left: .7rem; }
+.site-header nav { display: flex; gap: 1.5rem; font-size: .875rem; }
+.layout { display: grid; grid-template-columns: 230px minmax(0, 760px) 190px; gap: 3rem; max-width: 1400px; margin: auto; padding: 2.5rem 2rem; }
+.sidebar, .outline { position: sticky; top: 2rem; align-self: start; max-height: calc(100vh - 4rem); overflow: auto; font-size: .875rem; }
+.eyebrow { margin: 0 0 .75rem; text-transform: uppercase; letter-spacing: .1em; font-size: .75rem; color: var(--muted); font-family: ui-monospace, monospace; }
+.sidebar nav { display: grid; gap: .2rem; }
+.sidebar nav a { padding: .6rem .75rem; color: var(--text); text-decoration: none; border-left: 3px solid transparent; }
+.sidebar nav a:hover { background: var(--panel); }
+.sidebar nav a[aria-current] { background: var(--panel); border-color: var(--accent); font-weight: 600; }
+.scope { color: var(--muted); border-top: 1px solid var(--border); margin-top: 1.5rem; padding: 1rem .75rem; font-size: .8rem; }
+main { min-width: 0; }
+main:focus { outline: none; }
+h1, h2, h3 { font-family: "Segoe UI", system-ui, sans-serif; line-height: 1.25; font-weight: 650; scroll-margin-top: 1.5rem; }
+h1 { font-size: clamp(2rem, 3vw, 2.8rem); letter-spacing: -.035em; margin: 0 0 1.2rem; }
+h2 { font-size: 1.4rem; margin: 2.5rem 0 1rem; }
+h3 { font-size: 1.1rem; margin-top: 2rem; }
+article > p:first-of-type { font-size: 1.15rem; color: var(--muted); }
+p, ul, ol { margin: 0 0 1.2rem; }
+ul, ol { padding-left: 1.5rem; }
+li { margin: .4rem 0; }
+article img { max-width: 100%; height: auto; display: block; border: 1px solid var(--border); margin: 1.5rem 0; }
+strong { font-weight: 650; }
+code, kbd, pre { font-family: "Cascadia Code", "SFMono-Regular", Consolas, monospace; font-size: .875em; }
+code { background: var(--code); padding: .15em .3em; overflow-wrap: anywhere; }
+pre { background: var(--code); border-left: 3px solid var(--accent); padding: 1rem; overflow-x: auto; }
+pre code { padding: 0; overflow-wrap: normal; }
+kbd { border: 1px solid var(--border); padding: .1em .3em; }
+table { border-collapse: collapse; width: 100%; font-size: .95rem; margin: 1.5rem 0; }
+th, td { border-bottom: 1px solid var(--border); padding: .65rem .5rem; text-align: left; overflow-wrap: anywhere; }
+th { font-weight: 600; }
+blockquote { border-left: 3px solid var(--accent); margin: 1.5rem 0; padding-left: 1rem; color: var(--muted); }
+.outline { color: var(--muted); font-size: .8rem; }
+summary { cursor: pointer; color: var(--text); font-weight: 600; }
+.outline ul { list-style: none; padding: .5rem 0; margin-bottom: 1rem; }
+.outline a { color: var(--muted); text-decoration: none; display: block; padding: .2rem 0; }
+.outline a:hover { color: var(--accent); }
+footer { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 1rem; margin-top: 3rem; padding-top: 1rem; border-top: 1px solid var(--border); font-size: .8rem; }
+@media (max-width: 1150px) {
+ .layout { grid-template-columns: 210px minmax(0, 760px); gap: 2rem; }
+ .outline { display: none; }
+}
+@media (max-width: 720px) {
+ .site-header { padding: 1rem; gap: .5rem; }
+ .brand { font-size: 1rem; gap: .4rem; }
+ .brand span { padding-left: .4rem; }
+ .site-header nav { gap: .75rem; font-size: .8rem; }
+ .layout { display: block; padding: 1rem; }
+ .sidebar { position: static; max-height: none; overflow: visible; border-bottom: 1px solid var(--border); margin-bottom: 2rem; padding-bottom: 1rem; }
+ .sidebar nav { display: flex; flex-wrap: wrap; gap: .25rem; }
+ .sidebar nav a { padding: .45rem .6rem; }
+ .scope { border: 0; margin: .75rem 0 0; padding: 0; }
+ .scope br { display: none; }
+ td:last-child { width: 42%; }
+}
+@media print {
+ .site-header, .sidebar, .outline, footer, .skip-link { display: none; }
+ .layout { display: block; padding: 0; }
+ :root { --canvas: white; --text: black; --muted: #333; --accent: #222; --code: #eee; }
+}
diff --git a/docs/help/index.md b/docs/help/index.md
new file mode 100644
index 000000000..2fa56bf4e
--- /dev/null
+++ b/docs/help/index.md
@@ -0,0 +1,26 @@
+# CodeNomad help
+
+An OpenCode workspace, across projects, sessions, and worktrees.
+
+This guide assumes you already use **OpenCode V2**. It covers CodeNomad's interface and workflow, not the basics of agents or OpenCode configuration.
+
+
+
+## Find the control you need
+
+| I want to… | Read |
+|---|---|
+| Connect CodeNomad to my OpenCode setup | [Connect OpenCode](getting-started.md) |
+| Navigate history, attach context, handle requests across sessions | [Conversations](conversations.md) |
+| Move a session family between checkouts | [Projects and worktrees](projects.md) |
+| Use the file reader, Git views, browser preview, and SideCars | [Files and tools](files-and-tools.md) |
+| Control model visibility, accounts, and Global/Project settings | [Settings](settings.md) |
+| Diagnose a CodeNomad-specific problem | [Troubleshooting](troubleshooting.md) |
+
+## What stays shared
+
+CodeNomad uses the shared OpenCode service. Sessions and history remain available to compatible OpenCode clients; tabs, drafts, and layout are restored separately per CodeNomad window. Closing a tab or window does **not** stop the service.
+
+## Reference
+
+For native configuration, use the [OpenCode V2 documentation](https://opencode.ai/v2/docs/). For hosting options, use the [CodeNomad server guide](https://github.com/NeuralNomadsAI/CodeNomad/blob/dev/packages/server/README.md).
diff --git a/docs/help/projects.md b/docs/help/projects.md
new file mode 100644
index 000000000..cae529f18
--- /dev/null
+++ b/docs/help/projects.md
@@ -0,0 +1,31 @@
+# Projects and worktrees
+
+CodeNomad groups an opened repository and its registered Git worktrees into one project. Session placement still follows its execution directory.
+
+## Switch projects and sessions
+
+Use project tabs to switch folders and the session list to switch conversations. Selecting another conversation does not move its files or change its working directory.
+
+The session list normally shows parent sessions and their subsessions. Search and filters let you find sessions directly, including subsessions when that option is enabled.
+
+Desktop windows can show different projects or sessions. Tabs, drafts, and layout are restored per window; session history belongs to the shared OpenCode service.
+
+## Use a Git worktree
+
+In the composer's worktree selector, select an existing checkout or choose **Create and use worktree**. Both actions move the session and its subsessions to the destination.
+
+Uncommitted file changes stay in the original checkout; they are not copied to the destination. Creating a worktree outside this selector, for example with Git, does not by itself move a CodeNomad session.
+
+CodeNomad's default worktree location is **`.codenomad/worktrees`** inside the main project checkout. The Files panel can browse another worktree without moving your session or checking out a branch.
+
+## Know where execution happens
+
+Prompts and session commands run in the session's working directory on its execution host. A remote desktop client or browser is only the interface: your project files and tools live on the server host.
+
+On Windows, a configured WSL OpenCode executable runs inside its selected distribution. Keep its tools and paths available there; Git must also be available to the CodeNomad backend for repository features.
+
+## Close without stopping work
+
+Closing a project tab or CodeNomad window detaches that view. It does not shut down the shared OpenCode service.
+
+**Stop**, workspace-stop actions, and shared-service restarts have different scopes. Read the confirmation before interrupting work beyond the selected session.
diff --git a/docs/help/settings.md b/docs/help/settings.md
new file mode 100644
index 000000000..5e183669d
--- /dev/null
+++ b/docs/help/settings.md
@@ -0,0 +1,39 @@
+# Settings
+
+Open **Settings** for CodeNomad's display preferences and native configuration controls. On a local desktop, this opens a separate **Preferences** window.
+
+## Providers and models
+
+**Settings → Providers** separates **Models** from **Web search**.
+
+**Manage models** filters which models CodeNomad shows in selectors. **Active account** switches the provider credential when multiple accounts are available; environment-based connections are read-only. **Connect** adds a connection through the provider's native flow.
+
+Provider credentials are shared by projects using the same OpenCode service. Changing the active account can affect other clients connected to that service.
+
+## Web search
+
+In the Web search group, connect a search provider, then set **Default search** or **For the current project**. A project override takes precedence over the global choice. Search credentials are global; connecting a provider does not by itself select it for every project.
+
+## OpenCode
+
+**Settings → OpenCode** shows the selected executable and running service. You can install or update the CLI, check status, and use explicit service controls.
+
+- **Update** changes the installed executable.
+- **Restart shared service** replaces the running OpenCode process and can interrupt work in every connected client.
+- **Reload OpenCode configuration** is different from restarting. It rebuilds loaded Locations and can cancel pending requests and close terminals/background commands. Use it deliberately, not as a routine refresh.
+
+Follow the compatibility information shown by the current CodeNomad release. A newer installed CLI and an older running service can coexist until you restart it.
+
+## Appearance and interaction
+
+Choose a light, dark, or automatic appearance and a palette for each mode. You can also adjust language, font sizing, notifications, and conversation content visibility.
+
+**Ctrl/Cmd+Shift+P** opens the **Command Palette**. **Settings → General → Enter to submit** controls composer submission; shortcut hints reflect the active behavior.
+
+## Environment variables
+
+CodeNomad applies the profile's environment variables before each prompt or session command on the execution host. Editing them does not restart the service or change a session until the next send. The client device's environment is not substituted for a remote host's.
+
+## Voice and speech
+
+Configure voice input and text-to-speech in their settings sections. Microphone use requires device/browser permission. Availability depends on the selected service and your setup.
diff --git a/docs/help/troubleshooting.md b/docs/help/troubleshooting.md
new file mode 100644
index 000000000..2d10416ee
--- /dev/null
+++ b/docs/help/troubleshooting.md
@@ -0,0 +1,45 @@
+# Troubleshooting
+
+Checks for the CodeNomad interface and its connection to OpenCode. Shared-service restarts affect other connected clients too.
+
+## OpenCode will not connect
+
+Open **Settings → OpenCode** and check the selected executable, running-service status, and compatibility details. Use **Check status and updates**; install or select a supported OpenCode V2 executable if needed.
+
+If an update is installed but the old service is still running, use the explicit restart action when it is safe to interrupt shared work. A configuration reload is not an executable upgrade.
+
+## No model is available
+
+If a model works in OpenCode but is missing in CodeNomad, check **Settings → Providers → Manage models** for selector visibility, then check the active account. **Models** and **Web search** are separate groups.
+
+## Git or worktrees are unavailable
+
+Check Git in the CodeNomad **backend process's `PATH`**, not only in the agent's shell. Restart CodeNomad or its standalone server after changing that environment.
+
+Git inside WSL alone does not satisfy a Windows backend. Until Git is available, folder-only conversations can still work, but repository and worktree features cannot.
+
+## A reply seems stuck
+
+Pending requests are in the area **above the composer**, including requests from subsessions and other conversations. Check that area rather than looking for an editable transcript form. **Status** shows background activity; message-content filters can hide reasoning and tool details.
+
+Use **Stop** if you want to interrupt the execution. If sending failed, verify the current state before trying again—do not assume the server rejected it before doing any work.
+
+If the interface itself freezes, [report it](https://github.com/NeuralNomadsAI/CodeNomad/issues) with your CodeNomad version, OpenCode version, OS, desktop host (Tauri/Electron) or browser, and whether the server is remote. Include repeatable steps where possible, but remove credentials and private project content.
+
+## Remote login or HTTPS fails
+
+The remote URL and login belong to the **CodeNomad server**, not directly to the OpenCode service. Check its reachability and authentication independently of provider credentials.
+
+Only trust a certificate warning after verifying that the server is yours. For remote hosts, prefer a trusted certificate or secure tunnel. See the [server authentication and TLS guide](https://github.com/NeuralNomadsAI/CodeNomad/blob/dev/packages/server/README.md).
+
+## Desktop-specific startup issues
+
+For an unnotarized macOS download that Gatekeeper marks as damaged, use the documented [macOS steps](https://github.com/NeuralNomadsAI/CodeNomad#troubleshooting) only after verifying the download's source.
+
+On Linux with Wayland/NVIDIA, a Tauri WebKitGTK startup failure may be a graphics issue. The [same troubleshooting section](https://github.com/NeuralNomadsAI/CodeNomad#troubleshooting) lists the workaround; Electron is an alternative host. The Tauri Debian package is currently qualified on Ubuntu 24.04, not every Debian-based distribution.
+
+## Go deeper
+
+- [OpenCode V2 documentation](https://opencode.ai/v2/docs/) for agents, commands, skills, and tool configuration.
+- [CodeNomad server guide](https://github.com/NeuralNomadsAI/CodeNomad/blob/dev/packages/server/README.md) for hosting and connection options.
+- [CodeNomad issues](https://github.com/NeuralNomadsAI/CodeNomad/issues) for bugs and feature requests.
diff --git a/package.json b/package.json
index c3e4b863c..b6f86f5f7 100644
--- a/package.json
+++ b/package.json
@@ -22,7 +22,9 @@
"build:mac-x64": "npm run build:mac-x64 --workspace @neuralnomads/codenomad-electron-app",
"build:binaries": "npm run build:binaries --workspace @neuralnomads/codenomad-electron-app",
"typecheck": "npm run typecheck --workspace @codenomad/ui && npm run typecheck --workspace @neuralnomads/codenomad-electron-app",
- "bumpVersion": "node ./scripts/bump-version.js"
+ "bumpVersion": "node ./scripts/bump-version.js",
+ "build:help": "node scripts/build-help.mjs",
+ "test:help": "node --test scripts/test-help.mjs"
},
"dependencies": {
"7zip-bin": "^5.2.0",
diff --git a/scripts/build-help.mjs b/scripts/build-help.mjs
new file mode 100644
index 000000000..19ef79b12
--- /dev/null
+++ b/scripts/build-help.mjs
@@ -0,0 +1,79 @@
+import { cp, mkdir, readFile, writeFile } from "node:fs/promises"
+import { fileURLToPath } from "node:url"
+import { resolve } from "node:path"
+import { Marked } from "marked"
+
+const root = fileURLToPath(new URL("../", import.meta.url))
+const source = resolve(root, "docs/help")
+const output = resolve(process.argv[2] ?? resolve(root, "dist/help"))
+const pages = [
+ ["index", "Overview"],
+ ["getting-started", "Connect OpenCode"],
+ ["conversations", "Conversations"],
+ ["projects", "Projects and worktrees"],
+ ["files-and-tools", "Files and tools"],
+ ["settings", "Settings"],
+ ["troubleshooting", "Troubleshooting"],
+]
+const repo = "https://github.com/NeuralNomadsAI/CodeNomad"
+const escape = (value) => value.replace(/[&<>"']/g, (char) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[char])
+
+await mkdir(resolve(output, "assets"), { recursive: true })
+await Promise.all([
+ cp(resolve(source, "help.css"), resolve(output, "assets/help.css")),
+ cp(resolve(root, "docs/screenshots/workspace-0.20.png"), resolve(output, "assets/workspace.png")),
+ cp(resolve(root, "images/CodeNomad-Icon.png"), resolve(output, "assets/logo.png")),
+ writeFile(resolve(output, ".nojekyll"), ""),
+])
+
+for (const [name, label] of pages) {
+ const headings = []
+ const used = new Map()
+ const markdown = new Marked({ renderer: {
+ heading(text, level, raw) {
+ const slug = raw.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")
+ const count = used.get(slug) ?? 0
+ used.set(slug, count + 1)
+ const id = count ? `${slug}-${count}` : slug
+ if (level === 2) headings.push({ text: raw, id })
+ return ``
+ },
+ } })
+ const content = markdown.parse(await readFile(resolve(source, `${name}.md`), "utf8"))
+ const nav = pages.map(([id, title]) => `${title}`).join("\n")
+ const toc = headings.map(({ text, id }) => `