Customisation Research · OpenChamber 2.5.2 · 27 October 2026

OpenChamber, A to Z: what the SDK can change, what only React source can change

Short answer: yes, you can change every line — it is MIT and the entire React app ships in the repo. But the smarter question is which changes keep working after an upstream update, and which ones turn your fork into a permanent maintenance job.

Source: openchamber/openchamber @ main SDK API v1 · app 2.5.2 Layers: config → extension → source

🎯 TL;DR — the real answer

Three sentences:

  1. Yes, A-to-Z is possible. Nothing is hidden or compiled-only in a proprietary way: the React UI (4,388 files), the Express server, the Electron shell, the Capacitor mobile app and the VS Code extension are all in the repository under MIT.
  2. But "can" is not "should". Roughly 60–70% of everyday customisation — colours, panels, menus, slash commands, tool rendering, integrations, status widgets, file editors, git hosts — is achievable with the extension SDK and JSON config without touching React.
  3. The remaining 30–40% — layout shell, composer, timeline visuals, settings pages, onboarding, auth/relay, mobile and VS Code behaviour — requires editing React/JS source and accepting merge work with every upstream release.
The one-sentence decision rule

If the change is "add something next to what exists", do it as an extension. If the change is "make what already exists look or behave differently", it is a React edit. There is no third option, because extensions live in a sandboxed iframe and never touch the host DOM.

🧭 The three customisation layers

LAYER 0 — CONFIG & THEME (no build, no fork) ┌──────────────────────────────────────────────────────────────┐ │ theme JSON · VS Code theme import · app settings · │ │ AGENTS.md · skills · MCP servers · models · prompts · │ │ project actions · schedules · OpenCode plugins │ └──────────────────────────────────────────────────────────────┘ │ deploy: drop a file / change a setting / restart ▼ LAYER 1 — EXTENSION SDK (built bundle, no React, no host access) ┌──────────────────────────────────────────────────────────────┐ │ rail panel · full page · dialog · status section · file │ │ editor · chat/session actions · slash commands · custom tool │ │ renderers · integrations · local service · browser provider │ │ · git-host provider · docked live surface · popovers │ └──────────────────────────────────────────────────────────────┘ │ deploy: zip / folder / git URL → Settings → Extensions ▼ LAYER 2 — REACT / SERVER SOURCE (full fork) ┌──────────────────────────────────────────────────────────────┐ │ app shell · sidebar · header · composer · chat timeline · │ │ settings pages · diff view · terminal · preview · multi-run │ │ · auth · relay · tunnels · Express routes · mobile · VS Code │ └──────────────────────────────────────────────────────────────┘ │ deploy: rebuild UI bundle + server, ship your own app

Each layer is a different contract with different maintenance cost. Choose the lowest layer that can express your change.

📋 A-to-Z capability matrix

Legend: SDK no React edit · PART partially, or needs a small core patch · CODE React/server source required.

Visual identity

ChangeHowReality
Colours, light + darkSDKTheme JSON in ~/.config/openchamber/themes/, reload in Settings. No rebuild.
Import an existing VS Code themeSDKBuilt-in importer from Open VSX or a local .json.
Typography scale, radius, spacingPARTTheme covers colours and fonts; sizes/spacing live in design-system.css and Tailwind classes.
Logo, wordmark, app iconCODEAssets and manifest icons; also PWA icons and Electron packaging.
Make your theme the default for everyoneCODEAdd it to packages/ui/src/lib/theme/themes/ and the preset index.

Layout and navigation

ChangeHowReality
Add a panel on the right railSDKcontributes.panel + sandboxed iframe.
Add a full-screen page (board, dashboard)SDKcontributes.page; user opens it from the Extension pages menu.
Add an entry to the + menu / message menu / session menuSDKcontributes.attach and contributes.actions.
Add a widget inside the Work Status panelSDKcontributes.statusSection, height capped at 320 px.
Dock your own controls beside a live surfaceSDKcontributes.service.surface + panel.dock.
Move the sidebar, change rail order, resize panesCODEShell components under packages/ui/src/components/layout.
Add or remove built-in nav sectionsCODESame layout tree; no contribution slot exists for core nav.
Mobile / VS Code layoutCODESeparate shells; extensions do not even load there.

Chat and agent interaction

ChangeHowReality
Slash commands in the composerSDKcontributes.commands + onResolve; up to 8. Names already used by the app are ignored.
How a tool call renders (icon, title, table/markdown/code)SDKcontributes.tools, declarative manifest, no code, up to 16.
Send prompts, start sessions, create worktreesSDKPermission-gated: prompt, sessions.
Read/write project files, or paths outside itSDKfiles and filesystem capabilities with explicit paths.
One-off text generation with the Small ModelSDKmodel capability → host.generate().
Change how message bubbles, reasoning, or markdown lookCODETimeline components and streamdown configuration.
Change composer behaviour, attachments, voiceCODEComposer components; the SDK can prefill text, not restyle the box.
Add a permission/approval rulePARTRules live in OpenCode config; the UI to manage them is core.

Data, integrations and services

ChangeHowReality
Connect an external service with a token/OAuthSDKcontributes.integration; token never reaches the iframe.
Persist your own dataSDKhost.storage on the server, or device-scoped storage.
Run a local process / CLISDKcontributes.service, unsandboxed, user-approved.
Act as the agent's browserSDKprovides: ["browser"].
Add a git host (issues/PRs/worktrees)SDKprovides: ["source-control"].
Custom file editor for a file typeSDKcontributes.fileEditors, replaces the built-in editor for matched files.
New settings page or new core settingCODESettings registry is internal; extensions only get declared integration fields.
New Express API routeCODEpackages/web/server/index.js and server/lib/*.

Platform and deployment

ChangeHowReality
Tunnels, reverse proxy, remote accessSDKAlready built: Cloudflare/ngrok/Tailscale, private relay, proxy docs.
Auth method (WebAuthn / password / headers)PARTConfigure it; changing the flow is server/lib/ui-auth.
Branded desktop app + installerCODEpackages/electron, icon and packaging config.
Branded mobile appCODEpackages/mobile, Capacitor project, store signing.
Host on Cloudflare / any Node hostSDKIt is an Express server serving a static bundle.

🧩 What the SDK can do (full contribution surface)

These are the only ways an extension can enter the app. Each is declared in package.json under openchamber.contributes and validated on disk at install time.

ContributionWhat it gives youNotes
panelIcon on the right rail + an iframe panelkebab-case id, Remixicon name or package SVG
pageFull-screen page from the Extension pages menuThe extension cannot open it itself
attachDialog or panel opened from the + menu"dialog" / "panel" / custom entry
backgroundHidden sandbox for actions and slash commands20 s limit per invocation
actionsEntries on messages and sessionsUp to 8, optionally mode: "background"
commandsSlash commands in the chat boxUp to 8, name collisions ignored
toolsCustom rendering for tool callsDeclarative only: icon, title template, text/json/markdown/code/table
statusSectionWidget in Work StatusFrame or host-drawn from a service endpoint
fileEditorsCustom editor for matching filesCmd/Ctrl+S routed through host
integrationSettings card, token/OAuth/Linear accountSecrets stay on the server
serviceLocal process, browser or source-control provider, live surfaceRuns with the user's full access
filesystemAllow-listed paths outside the projectExplicit globs, user-approved

Host API you can call

connectHost() exposes a permission-checked RPC, not a DOM API: onReady, onDirectory, onSession, onSessionLifecycle, onConnection, onSettings, onItem, onAction, onResolve, onFileOpen, onFileSnapshot, onFileSaved, onStatusControl, onPopoverClosed, plus methods such as toast, compose, attach, prompt, startSession, openSession, listProjects, listWorktrees, listSessions, storage.get/set/delete/keys, generate, setBadge, setHeight, openCommit, openUrl, openPopover, request, oauthStart, readFile, writeFile, listDir, stat.

Theme tokens are pushed into the iframe on every onReady, so your extension can look native — including syntax colours — without importing app CSS.

This is a real, documented contract

@openchamber/sdk is published to npm, versioned with the app (apiVersion: 1, wire envelope v: 1), ships hosts/guest types and a UI kit, and has six official examples. It is not a private hook.

🚫 What the SDK cannot do

This list matters more than the previous one, because these are the requests that look possible and are not:

⚛️ What needs React source edits

Everything here is a normal component edit, but it is your fork now. The app is TypeScript + React 19 + Tailwind 4, built with Vite 7.

AreaWhere it livesCost to change
App shell, sidebar, rail, headerpackages/ui/src/components/layout/Medium — affects every screen
Chat timeline, bubbles, reasoning, markdownpackages/ui/src/components/chat/Medium–high — most surface area
Composer, attachments, voicepackages/ui/src/components/chat/ composer filesMedium
Settings pages and registrypackages/ui/src/lib/settings/registry.ts + components/sections/Low per page, grows with pages
Theme engine, presets, exportpackages/ui/src/lib/theme/Low — well isolated
Extension host (frames, grants, protocol)packages/ui/src/lib/guests/ + components/layout/PluginPane.tsxHigh — security-sensitive
Server, proxy, auth, tunnelspackages/web/server/index.js (≈137 KB) + server/lib/*Medium — plain ESM JavaScript, not TS
Desktop packagingpackages/electronMedium
Mobile shellpackages/mobile (Capacitor)High — native build + store signing
VS Code webviewpackages/vscodeMedium
Two facts that surprise people

The backend is JavaScript, not TypeScript. packages/web/server holds hand-written ESM .js files, including a 137 KB index.js. TypeScript lives in packages/ui, packages/sdk and packages/electron.

One UI build feeds five surfaces. packages/ui is consumed by web, Electron, mobile and the VS Code webview — so a component change propagates everywhere, and must be tested everywhere you ship.

Yes, literally every file is editable

There is no generated UI code you are forbidden to touch, no closed binary in the frontend path, and no obfuscation. The practical ceiling is not permission, it is:

⏱️ Effort and maintenance cost

Ranges assume you know React and TypeScript; they are estimates, not measurements.

GoalLayerFirst versionUpstream merge pain
Full recolour, light + darkTheme JSON1–4 hNone
Rail panel from your APIExtension1–2 daysNone
Custom tool rendering in chatExtension (manifest only)1–2 hNone
Slash command that attaches a ticketExtensionHalf a dayNone
Status widget with a local serviceExtension2–3 daysNone
Git-host or browser providerExtension service1–2 weeksLow
New settings pageReact source1–2 daysMedium
Reskin the chat timelineReact source1–2 weeksHigh
New app shell / navigation modelReact source2–4 weeksHigh
Branded desktop installerElectron source3–5 daysMedium
Branded iOS/Android appCapacitor source2–4 weeksHigh

🛠️ Recommended workflow

1. Write the change down as a sentence

"Add a Deploy panel that shows CI status and can start a deploy." → extension. "Make the sidebar 40 px wider and move sessions to the left." → React edit. The sentence selects the layer.

2. Exhaust layer 0 before writing anything

Themes, settings, project actions, magic prompts, OpenCode config, skills and MCP servers cover a surprising amount of "customisation" with zero maintenance.

3. Build the extension against the documented slots

my-ext/
├── package.json        # openchamber.contributes block
├── panel/index.html    # sandboxed page
├── panel/main.js       # built IIFE — the app never compiles TS
└── icon.svg            # optional, package-local

bunx openchamber-guest-bundle panel/main.ts panel/main.js
# then: Settings → Extensions → add folder / zip / git URL

Install from a folder while developing, so edit → rebuild → reload is instant. Switch to a pinned git URL for distribution.

4. Keep the fork surgical

5. Choose your distribution

You wantShip
Only config + extensionsYour themes/ folder, extension zips, a settings note — no fork at all
A branded web workspaceForked packages/ui + packages/web, built bundle, Node/Cloudflare host
A desktop productForked Electron package with your icon, signing and updater
A mobile productForked Capacitor project, native builds, store accounts

⚠️ Hard limits and risks

LimitConsequence for you
Extensions are sandboxed iframesNo host styling, no DOM access, no shared React state. "Make the app look like X" is never an extension.
No runtime compilationYou must build IIFE JS yourself; TypeScript sources in the package fail install.
Extensions load on web + desktop onlyA customisation built as an extension is absent on mobile and VS Code.
Directory permissions are coarseFilesystem access is all-or-nothing per declared path, not per file per call.
Services run unsandboxedA service extension is a real local process with the user's access — a trust decision, and a security review point.
Background actions are short-livedLong jobs need a service or an external API, not a background frame.
SDK version tracks the app@openchamber/sdk@2.5.2 is the contract of 2.5.2. Preview builds exist but are not compatibility promises.
Upstream is activeCore patches will conflict. Theme JSON and extensions will not.
Practical ceiling, stated plainly

With zero React edits you can build a genuinely different product surface: your own panels, pages, dashboards, menus, commands, integrations, tool renderers and even a git host — all themed natively. You cannot change the shell you live in: the sidebar, the chat, the composer, the settings pages. That is the line. Cross it only for changes you are willing to maintain.

📚 Sources