Skip to content

Commit f8b3c71

Browse files
ZexiZexi
authored andcommitted
docs: add FORK.md describing fork changes and behavioral invariants
1 parent dd426fa commit f8b3c71

1 file changed

Lines changed: 183 additions & 0 deletions

File tree

FORK.md

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
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

Comments
 (0)