Skip to content

feat(desktop): web server mirror — true 1:1 remote access via TCP reverse proxy#12000

Open
mguttmann wants to merge 10 commits intoanomalyco:devfrom
mguttmann:feat/web-server-mirror
Open

feat(desktop): web server mirror — true 1:1 remote access via TCP reverse proxy#12000
mguttmann wants to merge 10 commits intoanomalyco:devfrom
mguttmann:feat/web-server-mirror

Conversation

@mguttmann
Copy link
Copy Markdown

@mguttmann mguttmann commented Feb 3, 2026

Summary

Adds a Web Server Mirror to the desktop app — a TCP reverse proxy that exposes the desktop's existing server to the local network, giving a true 1:1 mirror of the desktop UI accessible from any browser (phone, tablet, second laptop).

Closes #11997

Why not just use opencode web?

Running opencode web spawns a completely separate server instance. While it shares the filesystem, it has its own in-memory state — meaning SSE events, WebSocket streams, and live session updates don't sync between desktop and web. You'd see stale data, miss notifications, and have to manually manage the lifecycle.

A TCP reverse proxy is the right solution: it pipes raw bytes through, so the browser talks directly to the desktop server. Same auth, same events, same state — zero duplication.

opencode web (separate server) TCP proxy (this PR)
State sync ❌ Separate in-memory state ✅ Same server instance
Live updates (SSE/WS) ❌ Don't cross instances ✅ Pass through directly
Auth ❌ Separate credentials ✅ Same Basic Auth
Lifecycle ❌ Manual start/stop ✅ Auto-start/stop with desktop
Sidebar sync ❌ None ✅ One-way (Desktop → Mirror)

How it works

Browser → http://192.168.x.x:4096
  → TCP Proxy (Rust/tokio, 0.0.0.0:4096)
    → Sidecar (127.0.0.1:<random-port>)
      → Serves bundled mirror UI + API

One-way Sidebar Sync

The mirror shows the same projects in the same order as the desktop:

  1. Desktop pushes sidebar state to PUT /mirror/sidebar on every change (debounced 300ms)
  2. Server stores it and broadcasts via SSE (mirror.sidebar.updated)
  3. Mirror reads on startup + reacts to live SSE updates

Strictly one-way (Desktop → Mirror) — no reactive loops, no conflict resolution needed.

Security: What's disabled in the mirror and why

  • Terminal is hidden — HTTP Basic Auth alone isn't sufficient to expose a shell. Anyone with the password could execute arbitrary commands on the host. Terminal remains desktop-only.
  • "Add Project" / "+" button is hidden — Opening a directory picker requires a native file dialog with OS-level permission prompts (macOS/Linux). This can only be confirmed locally, not through a remote browser. Projects are added on the desktop and automatically synced to the mirror.

Mirror UI Detection

The mirror entry point sets platform: "desktop" (same UI) but without platform.storage (no Tauri APIs):

  • Real Desktop: platform === "desktop" && storage !== undefined
  • Mirror: platform === "desktop" && storage === undefined

Authentication

HTTP Basic Auth with credential resolution:

  1. Settings UI values (user-configured)
  2. Shell env vars (OPENCODE_SERVER_USERNAME / OPENCODE_SERVER_PASSWORD)
  3. Defaults (opencode / random UUID)

Features

  • TCP reverse proxy in Rust/tokio — lightweight, handles all protocols (HTTP, SSE, WebSocket)
  • Settings UI in Settings → General → Desktop (enable/disable, port)
  • Status indicator in Servers tab (green dot, local + network URL, credentials)
  • Auto-start after sidecar is ready (if enabled), auto-stop on exit
  • Orphan cleanup — kills zombie processes on the configured port from previous runs
  • Pre-built mirror UI — Vite builds dist-mirror/, embedded as Tauri resource
  • i18n — all 15 languages with native translations

Use Cases

  • Check on running AI sessions from your phone while away from your desk
  • Use OpenCode on a second monitor/laptop over the local network
  • Run OpenCode on a remote server, connect from anywhere via browser — it keeps working autonomously
  • Quick remote access without SSH or terminal setup

Files changed (39 files, +1362 lines)

Rust (desktop core)
  • lib.rsWebMirrorState, TCP proxy, 3 Tauri commands, auto-start/stop, orphan cleanup, credential resolution
  • cli.rsprobe_shell_env() for reading env vars from login shell
TypeScript (desktop bindings)
  • bindings.ts — Tauri command bindings + types
  • index.tsx — Platform integration
App (shared UI)
  • entry-mirror.tsx — Mirror entry point (platform: "desktop" without Tauri)
  • mirror.html + vite.mirror.config.ts — Mirror build
  • platform.tsx — Type definitions
  • settings-general.tsx — Settings section
  • status-popover.tsx — Status indicator
  • layout.tsx — One-way sidebar sync
  • global-sync.tsx — SSE field for mirror sidebar
  • session-header.tsx, home.tsx, layout.tsx (pages), session.tsx — Hide terminal / add-project in mirror
Server (Hono)
  • server.tsGET/PUT /mirror/sidebar + SSE broadcast
  • flag.tsOPENCODE_WEB_DIR env flag
Build
  • predev.ts / prepare.ts — Mirror build steps
  • tauri.conf.jsonweb-ui resource
i18n (15 files)

All language files with native translations (ar, br, da, de, en, es, fr, ja, ko, no, pl, ru, th, zh, zht)

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 3, 2026

The following comment was made by an LLM, it may be inaccurate:

No duplicate PRs found

@mguttmann mguttmann force-pushed the feat/web-server-mirror branch from aa38bde to f56f7f2 Compare February 3, 2026 19:53
@mguttmann
Copy link
Copy Markdown
Author

image

@mguttmann mguttmann changed the title feat(desktop): auto-start web server mirror for remote access feat(desktop): web server mirror — true 1:1 remote access via TCP reverse proxy Feb 4, 2026
@mguttmann mguttmann force-pushed the feat/web-server-mirror branch from 91436b3 to 6f7552b Compare February 4, 2026 01:21
@mguttmann
Copy link
Copy Markdown
Author

IMG_9404

@mguttmann
Copy link
Copy Markdown
Author

IMG_9405

@rolandorojas
Copy link
Copy Markdown

Would sessions sync between the terminal and the remote client?

I work with OpenCode through the terminal but need a way to monitor and continue sessions from my phone. I've tried opencode web but it doesn't retrieve any of my current sessions.

Thanks for the great work, btw!

@mguttmann mguttmann force-pushed the feat/web-server-mirror branch 7 times, most recently from cbdf097 to e5a8041 Compare February 10, 2026 09:18
@mguttmann mguttmann force-pushed the feat/web-server-mirror branch 2 times, most recently from e54ebbf to b743cda Compare February 18, 2026 06:37
@mguttmann mguttmann force-pushed the feat/web-server-mirror branch 3 times, most recently from 8b41d2a to e9ce8fa Compare February 25, 2026 12:58
Claude Agent added 5 commits March 6, 2026 08:47
…he network

TCP reverse proxy in Rust (tokio) binds on 0.0.0.0:<port> and forwards to sidecar.
Mirror web UI bundled as Tauri resource, served via OPENCODE_WEB_DIR.
HTTP Basic Auth, sidebar sync (Desktop→Server→Mirror), terminal/add-project hidden in mirror.
Settings UI for enable/disable, port, credentials. i18n for all 15 languages.
platform.storage is undefined in both the mirror and the regular web
version. Use the mirror-specific check (platform === desktop && !storage)
so the Open Project button remains visible in web mode and e2e tests.
The platform variable was already declared at the top of the Layout
init function; remove the second declaration that caused a TypeScript
redeclaration error.
Claude Agent added 4 commits March 6, 2026 08:47
…urce retention

Align with upstream memory leak fix pattern (fc37337): use
globalThis.fetch instead of platform.fetch for local server requests.
Also add onCleanup for the debounce timer.
@mguttmann mguttmann force-pushed the feat/web-server-mirror branch from e9ce8fa to 827d651 Compare March 6, 2026 07:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(desktop): Auto-start web server mirror for remote access

2 participants