UI API Decoupling
Core Boundary
- Official OpenCode API calls use
@opencode-ai/sdk/v2 through opencodeClient.
- OpenChamber-owned HTTP capabilities use
RuntimeAPIs where runtime-specific behavior exists, otherwise explicit OpenChamber routes through runtimeFetch.
- Browser/realtime consumers use shared runtime URL/socket helpers.
- Shared UI never hardcodes localhost, ports, API origins, credentials, or one runtime's transport assumptions.
- Treat runtime adapters as the imperative shell: they own transport, auth, serialization, and platform mechanics. Shared feature code receives trusted contracts and owns domain decisions.
Classify First
| Need |
Correct path |
| Official OpenCode endpoint |
opencodeClient or its SDK client |
| SDK gap for official OpenCode |
Narrow documented wrapper in opencodeClient preserving request fidelity |
| OpenChamber HTTP route |
runtimeFetch('/api/...') |
| Runtime-owned capability |
Extend RuntimeAPIs and implement each applicable runtime |
| Browser-owned authenticated URL |
Runtime URL resolver and scoped URL auth |
| SSE/WebSocket |
Owning realtime transport; also load relay-transport |
Load References By Task
| Task |
Required reference |
| Iframes, downloads, raw images, object URLs, URL tokens |
references/browser-assets-and-auth.md |
| Adding runtime capabilities, VS Code behavior, Electron privilege/security, unsupported runtime behavior |
references/runtime-parity.md |
| Locating implementations, route registration, runtime switching, or focused tests |
references/implementation-map.md |
Load every matching reference before editing.
Mandatory Rules
- Do not bypass the SDK for official OpenCode APIs. Preserve SDK-generated method, body, headers, query, auth, and abort signal.
- Keep OpenChamber routes explicit. Register them before the generic OpenCode proxy.
- Use runtime APIs for runtime-owned capabilities. Components consume hooks/providers, not runtime globals.
- Resolve runtime state at call time. Do not cache runtime base URLs, resolver output, credentials, or SDK clients across endpoint switches.
- Let transport own auth. HTTP uses runtime bearer handling; browser/realtime URLs use scoped short-lived URL auth where headers are impossible.
- Never put long-lived client credentials in URLs. Do not manually append URL tokens.
- Define runtime parity explicitly. Shared UI needs deliberate web, Electron, VS Code, hosted-mobile, and Capacitor behavior or stable unsupported responses.
- Authoritative fetches must signal failure. Do not convert failure into a valid empty value that callers use to clear state.
- Keep privileges at the native/runtime boundary. UI visibility and prompts are not authorization.
- Confirm trust-boundary mutations. Host imports, credential writes, privileged deep links, and runtime switching require explicit user intent.
- Parse at the boundary. Treat external, persisted, bridge, IPC, and network payloads as unknown until a schema, parser, or narrow constructor produces the trusted type consumed by shared code. Do not validate fields and then continue passing the raw payload.
- Model the real contract. Prefer precise result/state unions and required dependencies over loose strings, boolean combinations, optional callback bags,
any, or repeated casts. Make unsupported runtime behavior and failure distinct from valid empty success.
- Keep adapters deep and bridges thin. Hide meaningful protocol or platform mechanics behind an intention-revealing runtime operation; do not add pass-through layers that only rename SDK, fetch, or bridge calls.
HTTP Decision Rules
Pass route paths directly to runtimeFetch:
await runtimeFetch('/health');
await runtimeFetch('/api/config/settings');
await runtimeFetch('/api/fs/raw', { query: { path } });
Do not immediately fetch a URL produced by getRuntimeUrlResolver(). Use the resolver only when the browser/realtime API itself consumes the URL:
const imageSrc = getRuntimeUrlResolver().authenticatedAsset('/api/fs/raw?path=diagram.png');
const eventUrl = getRuntimeUrlResolver().sse('/api/event');
Plain fetch is reserved for intentional external origins that are not the active OpenChamber/OpenCode runtime.
Runtime Switch Safety
Review runtime base URL, auth, SDK clients, terminal/realtime transports, stores, session memory, and caches. Key caches by runtime identity where IDs, paths, or URLs can collide. Reset or reconnect affected state through the established runtime-switch flow.
Re-parse values obtained after a switch at their owning boundary. A type established for one runtime response does not make cached raw data from another runtime trustworthy.
Common Anti-Patterns
| Avoid |
Use |
Raw feature fetch to official OpenCode |
SDK wrapper/client |
| Component reads runtime globals |
useRuntimeAPIs() / provider |
| Hardcoded runtime URL |
runtimeFetch or runtime URL resolver |
| Browser URL containing bearer/client token |
Scoped URL-auth helper |
| Web-only shared route |
Explicit VS Code/mobile decision |
Returning [] after authoritative fetch failure |
Throw or distinct failure result |
Rebuilding SDK Request from URL only |
Preserve original request body/headers/signal |
| Component validates unknown JSON then passes it onward |
Adapter parses once and returns a trusted contract |
| Boolean/nullable combinations for exclusive outcomes |
Discriminated result or state union |
Verification
- Official calls use SDK paths or documented SDK-gap wrappers.
- OpenChamber routes win before generic proxy fallback.
- Request fidelity, auth, abort, query, and body behavior are tested.
- Browser/realtime auth uses narrow allowlists and scoped tokens.
- Every applicable runtime has implementation or explicit unsupported behavior.
- Runtime switching cannot reuse stale endpoint/auth/cache state.
- Privileged Electron/extension behavior is enforced outside the renderer.
- Focused transport, bridge, proxy, auth, and runtime tests pass; static type/lint checks alone are insufficient.
1---2name: ui-api-decoupling3description: Use when creating or modifying OpenChamber shared UI data access, OpenCode SDK calls, `RuntimeAPIs`, runtime fetch/auth/URLs, authenticated browser assets, bridges/proxies, runtime switching, or server API routes.4---5
6# UI API Decoupling
7
8## Core Boundary
9
10- Official OpenCode API calls use `@opencode-ai/sdk/v2` through `opencodeClient`.
11- OpenChamber-owned HTTP capabilities use `RuntimeAPIs` where runtime-specific behavior exists, otherwise explicit OpenChamber routes through `runtimeFetch`.
12- Browser/realtime consumers use shared runtime URL/socket helpers.
13- Shared UI never hardcodes localhost, ports, API origins, credentials, or one runtime's transport assumptions.
14- Treat runtime adapters as the imperative shell: they own transport, auth, serialization, and platform mechanics. Shared feature code receives trusted contracts and owns domain decisions.
15
16## Classify First
17
18| Need | Correct path |
19|---|---|
20| Official OpenCode endpoint | `opencodeClient` or its SDK client |
21| SDK gap for official OpenCode | Narrow documented wrapper in `opencodeClient` preserving request fidelity |
22| OpenChamber HTTP route | `runtimeFetch('/api/...')` |
23| Runtime-owned capability | Extend `RuntimeAPIs` and implement each applicable runtime |
24| Browser-owned authenticated URL | Runtime URL resolver and scoped URL auth |
25| SSE/WebSocket | Owning realtime transport; also load `relay-transport` |
26
27## Load References By Task
28
29| Task | Required reference |
30|---|---|
31| Iframes, downloads, raw images, object URLs, URL tokens | `references/browser-assets-and-auth.md` |
32| Adding runtime capabilities, VS Code behavior, Electron privilege/security, unsupported runtime behavior | `references/runtime-parity.md` |
33| Locating implementations, route registration, runtime switching, or focused tests | `references/implementation-map.md` |
34
35Load every matching reference before editing.
36
37## Mandatory Rules
38
391. **Do not bypass the SDK for official OpenCode APIs.** Preserve SDK-generated method, body, headers, query, auth, and abort signal.
402. **Keep OpenChamber routes explicit.** Register them before the generic OpenCode proxy.
413. **Use runtime APIs for runtime-owned capabilities.** Components consume hooks/providers, not runtime globals.
424. **Resolve runtime state at call time.** Do not cache runtime base URLs, resolver output, credentials, or SDK clients across endpoint switches.
435. **Let transport own auth.** HTTP uses runtime bearer handling; browser/realtime URLs use scoped short-lived URL auth where headers are impossible.
446. **Never put long-lived client credentials in URLs.** Do not manually append URL tokens.
457. **Define runtime parity explicitly.** Shared UI needs deliberate web, Electron, VS Code, hosted-mobile, and Capacitor behavior or stable unsupported responses.
468. **Authoritative fetches must signal failure.** Do not convert failure into a valid empty value that callers use to clear state.
479. **Keep privileges at the native/runtime boundary.** UI visibility and prompts are not authorization.
4810. **Confirm trust-boundary mutations.** Host imports, credential writes, privileged deep links, and runtime switching require explicit user intent.
4911. **Parse at the boundary.** Treat external, persisted, bridge, IPC, and network payloads as unknown until a schema, parser, or narrow constructor produces the trusted type consumed by shared code. Do not validate fields and then continue passing the raw payload.
5012. **Model the real contract.** Prefer precise result/state unions and required dependencies over loose strings, boolean combinations, optional callback bags, `any`, or repeated casts. Make unsupported runtime behavior and failure distinct from valid empty success.
5113. **Keep adapters deep and bridges thin.** Hide meaningful protocol or platform mechanics behind an intention-revealing runtime operation; do not add pass-through layers that only rename SDK, fetch, or bridge calls.
52
53## HTTP Decision Rules
54
55Pass route paths directly to `runtimeFetch`:
56
57```ts
58await runtimeFetch('/health');
59await runtimeFetch('/api/config/settings');
60await runtimeFetch('/api/fs/raw', { query: { path } });
61```
62
63Do not immediately fetch a URL produced by `getRuntimeUrlResolver()`. Use the resolver only when the browser/realtime API itself consumes the URL:
64
65```ts
66const imageSrc = getRuntimeUrlResolver().authenticatedAsset('/api/fs/raw?path=diagram.png');
67const eventUrl = getRuntimeUrlResolver().sse('/api/event');
68```
69
70Plain `fetch` is reserved for intentional external origins that are not the active OpenChamber/OpenCode runtime.
71
72## Runtime Switch Safety
73
74Review runtime base URL, auth, SDK clients, terminal/realtime transports, stores, session memory, and caches. Key caches by runtime identity where IDs, paths, or URLs can collide. Reset or reconnect affected state through the established runtime-switch flow.
75
76Re-parse values obtained after a switch at their owning boundary. A type established for one runtime response does not make cached raw data from another runtime trustworthy.
77
78## Common Anti-Patterns
79
80| Avoid | Use |
81|---|---|
82| Raw feature `fetch` to official OpenCode | SDK wrapper/client |
83| Component reads runtime globals | `useRuntimeAPIs()` / provider |
84| Hardcoded runtime URL | `runtimeFetch` or runtime URL resolver |
85| Browser URL containing bearer/client token | Scoped URL-auth helper |
86| Web-only shared route | Explicit VS Code/mobile decision |
87| Returning `[]` after authoritative fetch failure | Throw or distinct failure result |
88| Rebuilding SDK `Request` from URL only | Preserve original request body/headers/signal |
89| Component validates unknown JSON then passes it onward | Adapter parses once and returns a trusted contract |
90| Boolean/nullable combinations for exclusive outcomes | Discriminated result or state union |
91
92## Verification
93
94- Official calls use SDK paths or documented SDK-gap wrappers.
95- OpenChamber routes win before generic proxy fallback.
96- Request fidelity, auth, abort, query, and body behavior are tested.
97- Browser/realtime auth uses narrow allowlists and scoped tokens.
98- Every applicable runtime has implementation or explicit unsupported behavior.
99- Runtime switching cannot reuse stale endpoint/auth/cache state.
100- Privileged Electron/extension behavior is enforced outside the renderer.
101- Focused transport, bridge, proxy, auth, and runtime tests pass; static type/lint checks alone are insufficient.