|
| 1 | +# zexi/dev Fork — What's Different from Upstream |
| 2 | + |
| 3 | +This document records every intentional divergence from `anomalyco/opencode` so |
| 4 | +that after each weekly rebase the goals are clear and regressions can be caught |
| 5 | +quickly. |
| 6 | + |
| 7 | +--- |
| 8 | + |
| 9 | +## How to rebase |
| 10 | + |
| 11 | +The workflow `sync-upstream.yml` runs every Friday and rebases `zexi/dev` onto |
| 12 | +upstream automatically. After a rebase, verify the [behavioral invariants](#behavioral-invariants) below. |
| 13 | + |
| 14 | +Manual rebase if needed: |
| 15 | + |
| 16 | +```sh |
| 17 | +git fetch upstream |
| 18 | +git rebase --onto upstream/dev $(git merge-base HEAD upstream/dev) zexi/dev |
| 19 | +``` |
| 20 | + |
| 21 | +--- |
| 22 | + |
| 23 | +## Fork commits (relative to upstream/dev) |
| 24 | + |
| 25 | +### 1 · Release workflows — `f4bb5ae17` |
| 26 | + |
| 27 | +**Files:** `.github/workflows/zexi-electron.yml`, `.github/workflows/sync-upstream.yml`, `packages/desktop/electron-builder.config.ts`, `packages/script/src/index.ts` |
| 28 | + |
| 29 | +Custom CI that builds and releases signed macOS (arm64 + notarization) and |
| 30 | +Windows (x64 + Azure trusted signing) Electron packages on every push to |
| 31 | +`zexi/dev`. Releases are tagged `zexi-electron-<stamp>-<sha>`; old releases are |
| 32 | +pruned to keep the 3 most recent. |
| 33 | + |
| 34 | +The sync workflow disables all upstream workflows except these two. |
| 35 | + |
| 36 | +**Rebase risk:** Low — touches only `.github/` and build config. Conflicts |
| 37 | +typically arise if upstream renames `electron-builder.config.ts`. |
| 38 | + |
| 39 | +--- |
| 40 | + |
| 41 | +### 2 · Configure desktop local server access — `78b5d1182` |
| 42 | + |
| 43 | +**Files:** `packages/app/src/components/dialog-select-server.tsx`, `packages/app/src/components/server/server-row.tsx`, `packages/app/src/components/status-popover*.tsx`, `packages/desktop/src/main/{index,ipc,server,sidecar}.ts`, `packages/desktop/src/preload/`, `packages/opencode/src/config/server.ts`, i18n files |
| 44 | + |
| 45 | +**What it does:** Adds UI for users to configure the local Electron-embedded |
| 46 | +server's hostname, port, username, and password from within the app. Previously |
| 47 | +these could only be set via environment variables. The config is persisted in |
| 48 | +Electron's store under `localServerConfig` (see `constants.ts`). |
| 49 | + |
| 50 | +Key additions: |
| 51 | +- IPC channels: `get-local-server-config`, `set-local-server-config` |
| 52 | +- `getLocalServerConfig() / setLocalServerConfig()` in `server.ts` |
| 53 | +- Server dialog revamp in `dialog-select-server.tsx` — includes a local-server |
| 54 | + config panel when running in desktop mode |
| 55 | +- Status bar server icon (bottom-left) opens the server selection/config dialog |
| 56 | + |
| 57 | +**Rebase risk:** High — touches many app-layer files. Conflicts are likely if |
| 58 | +upstream refactors `status-popover`, `dialog-select-server`, or the desktop |
| 59 | +main/preload plumbing. |
| 60 | + |
| 61 | +--- |
| 62 | + |
| 63 | +### 3 · Sync opened projects through server events — `7aa32f066` |
| 64 | + |
| 65 | +**Files:** `packages/app/src/context/opened-projects.tsx`, `packages/opencode/src/config/projects.ts`, `packages/opencode/src/server/routes/instance/httpapi/{groups,handlers}/global.ts`, `packages/sdk/js/src/v2/gen/` |
| 66 | + |
| 67 | +**What it does:** Moves opened-project state from scattered per-component state |
| 68 | +into a single `OpenedProjectsContext` (Solid.js). The context subscribes to the |
| 69 | +`project.opened.updated` server-sent event so all windows/tabs stay in sync |
| 70 | +without polling. |
| 71 | + |
| 72 | +Adds server-side API routes under `/global` for listing, opening, closing, and |
| 73 | +reordering opened projects. Also extends the JS SDK types accordingly. |
| 74 | + |
| 75 | +**Rebase risk:** Medium — `handlers/global.ts` and the SDK generated files are |
| 76 | +common conflict points if upstream adds routes in the same files. |
| 77 | + |
| 78 | +--- |
| 79 | + |
| 80 | +### 4 · Keep opened project metadata single-sourced — `02416fa48` |
| 81 | + |
| 82 | +**Files:** `packages/opencode/src/server/shared/opened-projects.ts` (new), `packages/opencode/src/server/shared/opened-projects.sql.ts` (new), `packages/desktop/src/main/{server,ipc,constants}.ts`, `packages/app/src/context/{platform,server,opened-projects}.tsx`, `packages/app/src/pages/layout.tsx` |
| 83 | + |
| 84 | +**What it does:** Persists opened-project metadata (name, icon, commands, |
| 85 | +ordering) in the opencode SQLite database via a dedicated `OpenedProjectTable`, |
| 86 | +rather than duplicating it across Electron store and server memory. This was a |
| 87 | +follow-up fix after the upstream rebase broke the original implementation. |
| 88 | + |
| 89 | +Also stores `localServerConfig` in Electron store (previously it lived only in |
| 90 | +memory), fixing the config disappearing on restart. |
| 91 | + |
| 92 | +**Rebase risk:** Medium-High — `opened-projects.ts` is a new file owned by this |
| 93 | +fork; conflicts arise only if upstream creates a file at the same path. The |
| 94 | +migration SQL file path must stay unique. |
| 95 | + |
| 96 | +--- |
| 97 | + |
| 98 | +### 5 · Allow web UI shell without auth — `b8c154e01` |
| 99 | + |
| 100 | +**Files:** `packages/opencode/src/server/shared/public-ui.ts`, `packages/opencode/test/server/httpapi-ui.test.ts` |
| 101 | + |
| 102 | +**What it does:** Static assets (HTML shell, JS bundles, CSS, icons, favicons) |
| 103 | +are served without requiring authentication, so a remote browser can load the |
| 104 | +app UI shell before the user has entered credentials. API routes remain |
| 105 | +protected. |
| 106 | + |
| 107 | +`isPublicUIPath()` returns `true` for: |
| 108 | +- `/`, `/index.html`, `/site.webmanifest`, manifest PNGs |
| 109 | +- `/assets/*` (JS/CSS bundles) |
| 110 | +- `/favicon*`, `/apple-touch-icon*`, `/social-share.*` |
| 111 | + |
| 112 | +**Why this matters:** Without this, a remote browser hitting the server gets a |
| 113 | +401 on the very first request and cannot load anything — the user has no way to |
| 114 | +enter credentials. |
| 115 | + |
| 116 | +**Rebase risk:** Low — `public-ui.ts` is a small file. Conflict only if upstream |
| 117 | +adds its own public-path logic here. |
| 118 | + |
| 119 | +--- |
| 120 | + |
| 121 | +### 6 · Serve local web UI in dev mode, suppress auth dialog — `dd426faec` |
| 122 | + |
| 123 | +**Files:** `packages/core/src/flag/flag.ts`, `packages/desktop/src/main/server.ts`, `packages/opencode/src/server/routes/instance/httpapi/middleware/authorization.ts`, `packages/opencode/src/server/shared/ui.ts` |
| 124 | + |
| 125 | +**What it does:** |
| 126 | + |
| 127 | +**a) Bearer auth scheme** (`authorization.ts`) |
| 128 | +Changed `WWW-Authenticate` response header from `Basic` to `Bearer`. Chrome and |
| 129 | +Firefox show a native credentials popup for `Basic` on every 401, including XHR |
| 130 | +responses from the SPA. `Bearer` suppresses this popup while keeping the server |
| 131 | +fully functional — it still reads and validates `Authorization: Basic` headers |
| 132 | +sent by the desktop app. |
| 133 | + |
| 134 | +**b) Local build serving in dev mode** (`flag.ts`, `server.ts`, `ui.ts`) |
| 135 | +Added two env flags: |
| 136 | +- `OPENCODE_DEV_UI_DIR` — if set, the server serves static files from this |
| 137 | + directory (with SPA index.html fallback) instead of proxying to |
| 138 | + `https://app.opencode.ai`. |
| 139 | +- `OPENCODE_DEV_UI_URL` — overrides the upstream proxy base URL. |
| 140 | + |
| 141 | +In desktop dev mode (`!app.isPackaged`), `preferAppEnv()` automatically sets |
| 142 | +`OPENCODE_DEV_UI_DIR` to `packages/app/dist`. This means remote browsers |
| 143 | +connecting to the Electron-local server see the locally built UI (with fork |
| 144 | +customizations like the server icon) instead of the production CDN version. |
| 145 | + |
| 146 | +**Prerequisite:** `packages/app` must be built before starting Electron in dev |
| 147 | +mode: |
| 148 | +```sh |
| 149 | +cd packages/app && bun run build |
| 150 | +``` |
| 151 | + |
| 152 | +**Rebase risk:** Low for `authorization.ts` (one constant). Medium for `ui.ts` |
| 153 | +if upstream restructures `serveUIEffect` or `serveEmbeddedUIEffect`. |
| 154 | + |
| 155 | +--- |
| 156 | + |
| 157 | +## Behavioral invariants |
| 158 | + |
| 159 | +After every rebase, verify these before merging/releasing: |
| 160 | + |
| 161 | +| # | Scenario | Expected | |
| 162 | +|---|----------|----------| |
| 163 | +| 1 | Remote browser opens `http://<host>:4096/` (no credentials) | 200, loads UI shell (no browser auth popup) | |
| 164 | +| 2 | Remote browser fetches `/favicon-96x96-v3.png` | 200 (no auth required) | |
| 165 | +| 3 | SPA fetches `/global/config` without credentials | 401 with `WWW-Authenticate: Bearer …` (not `Basic`) | |
| 166 | +| 4 | Desktop app bottom-left shows server icon with current server URL | Visible, clickable | |
| 167 | +| 5 | Clicking server icon opens server config dialog | Dialog appears, shows local-server config panel | |
| 168 | +| 6 | Setting local server credentials and restarting → credentials persist | Config survives restart | |
| 169 | +| 7 | Opening a project in one browser tab → other tabs update | `project.opened.updated` event triggers sync | |
| 170 | +| 8 | Dev mode: remote browser sees fork UI (server icon in bottom left) | Not the upstream `app.opencode.ai` version | |
| 171 | + |
| 172 | +Quick automated check (run against a live local server on port 4096): |
| 173 | + |
| 174 | +```sh |
| 175 | +# Root → 200 |
| 176 | +curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:4096/ |
| 177 | +# Favicon → 200 |
| 178 | +curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:4096/favicon-96x96-v3.png |
| 179 | +# API → 401 Bearer (not Basic) |
| 180 | +curl -sI http://127.0.0.1:4096/global/config | grep -i www-authenticate |
| 181 | +``` |
| 182 | + |
| 183 | +Expected output: `200`, `200`, `www-authenticate: Bearer realm="Secure Area"`. |
0 commit comments