* feat(electron): add Linux AppImage releases
* ci: cache Linux OpenCode CLI artifacts
* fix(ci): await Linux release inventory check
* fix(electron): add frameless window controls on Linux desktop
Linux AppImages were created without native WM decorations and without
in-app controls, leaving users unable to close the window with a mouse.
Treat Linux like Windows: frameless BrowserWindow plus the existing
WindowsWindowControls header buttons and app-menu entry. macOS keeps
hidden title bar with traffic lights unchanged.
Shared usesFramelessElectronChrome() helper drives main window, mini
chat, header insets, and titlebar controls.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* feat(desktop): add configurable window controls position by OS
Add desktopWindowControlsPosition setting (auto/left/right) with OS-aware
defaults: Linux left, Windows right. Wire frameless chrome controls in
Header, TitlebarLeftControls, and MiniChatLayout, plus a Sessions settings
control for Windows and Linux desktop shells.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* fix(desktop): address Linux AppImage release review findings
Propagate updater capability errors to the UI, treat missing
latest-linux.yml feeds as no-update, stop installed-apps IPC spam on
Linux, document FUSE/AppImage limits, add CHANGELOG entry, migrate
remaining btriapitsyn URLs, and run Electron Linux unit tests on PRs.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
---------
Co-authored-by: jibanez-staticduo <staticduo@gmail.com>
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
Candidates refresh (server + mobile + desktop clients):
- GET /api/client-auth/connection/candidates returns the server's current
LAN URLs, relay candidate, and serverId for already-paired devices
- /health and /api/version expose serverId so clients can verify a learned
address belongs to the expected server before sending their bearer token
- mobile: refresh saved candidates over the live transport after every
connect/wake, hot-switch relay->LAN when a fresh address is reachable;
serverId gate on direct probes; token no longer sent to /health
- desktop: refresh stored host apiUrl after a relay connect and hot-switch
back to direct; electron probe verifies serverId before authenticated fetch
Fixes found while debugging a dead pairing:
- settings: strict reader that throws on corrupt/unreadable file instead of
returning {}; relay signing/encryption key generation is now gated on it,
so a swallowed read failure can no longer mint a new server identity and
orphan every paired device (loud log when a keypair IS generated)
- SessionAuthGate: bounded auto-retry for transient session-check failures
(initial request racing the relay tunnel's first WS attempt, startup 5xx)
Add Windows launch-at-login with background startup support and extend the
native tray integration to Windows.
Add a Windows-only setting to minimize or close the main window to the
system tray, persist it through desktop settings, and expose it in Settings
search and all locale dictionaries.
Keep tray state synchronized with live sessions on both macOS and Windows,
while preserving the existing macOS behavior.
- A saved host now keeps every transport its pairing link carried: direct URL
plus the relay descriptor, with one token for both (the mobile connection
model). Switching tries the direct leg and falls back to the E2EE tunnel;
list probes report Connected · Relay when only the tunnel reaches the host;
relaunch restore picks direct first
- Host switching trusts the dropdown's fresh probe instead of re-probing on
click (no doubled latency, no transient Unreachable flashes); statuses are
written once with the final outcome, survive the dropdown closing via a
last-known cache, and an unprobed host reads Checking — never Unknown
- Open-in-new-window works for relay hosts: a new IPC command boots the local
UI with the host id injected and the renderer picks the transport; the app
render holds on the relay restore so the splash shows instead of a transient
auth screen (10s safety valve)
- Relay host control socket gained protocol-level keepalive: a missed pong
window terminates and reconnects, so the relay can no longer hold a ghost
registration that leaves every client tunnel hanging; the desktop relay
probe also hard-times-out at 8s instead of hanging status flows
- Services dropdown restyled with mobile-style cards: per-provider usage
cards, per-host instance cards with a selected highlight and a toned
status line, MCP servers grouped in a card
Reworks how devices connect to an OpenChamber server, end to end.
Pairing v2:
- One-time pairing links/QR codes (openchamber://connect?v=2) carrying a set of transport candidates (LAN/tunnel/relay) and a single-use secret redeemed server-side; no tokens embedded in links
- Add-a-device dialog written for first-time users: intent-based transport choice (Anywhere / Home network only / This computer only) with plain-language descriptions, transparent fallback checkboxes, server-authoritative LAN detection, high-res QR dialog
- Private relay folded into pairing as a transport candidate with a demand-driven lifecycle (enables when a relay device is paired, disables when none remain)
Multi-transport devices:
- A saved device holds all its transports and one token; mobile re-probes on connect, resume, and network change and hot-switches LAN<->relay seamlessly (no re-pairing, no remount, session preserved)
- Desktop can import relay pairing links, switch to relay hosts through the E2EE tunnel, and restore a relay default host after relaunch
Device management:
- Device list (web + desktop) shows live per-device connectivity with the active transport (Connected - Local network / Relay) and platform badges (iOS/Android/macOS/Windows/Linux)
- One physical device = one record: stable per-install dedupe keys across pairing and password re-login; typed pairing label names the device, paired devices name the connection by the issuing server hostname
- Trusted desktop-local client manages all devices (list, revoke, clear revoked); relay host reaps dead client sockets after 3 missed keepalives
Android:
- LAN transport unblocked (cleartext + mixed content, mirroring iOS ATS exceptions); resume re-probe retries through network flux and silently auto-reconnects from a disconnected state
The client-create gate added in 1.13.9 rejects client tokens without the
desktop-local kind, but the kind was only attached when the runtime
origin exactly matched the injected local origin — an empty (same-origin)
api base, loopback aliases, and the embedded server addressed via a LAN
interface (0.0.0.0 binds) all minted untagged tokens, which then hit 403
and surfaced as "Local — Auth required" plus the unreachable-server
screen. The renderer now treats same-origin and loopback targets as
local, the Electron main additionally matches any of the machine's own
interface addresses on the local server's port, and a deduped kind-tagged
mint migrates away legacy same-label tokens that predate client kinds.
The client-create gate itself is unchanged.
Show a count of chats (root sessions) with unseen activity on the macOS
dock icon. The count is computed in the existing tray snapshot (full
cross-project list, not the capped tray view; a subtask's unseen rolls up
to its root only when subtask notifications are enabled) and pushed to the
main process over the existing desktop_tray_update IPC, which calls
app.setBadgeCount (0 clears it). The badge clears as sessions are marked
seen on window focus.
Add a Dock badge toggle in Appearance settings (default on, persisted,
darwin desktop only), localized across all dictionaries, with a matching
settings-search entry whose availability mirrors the render guard exactly.
OpenChamber spawns the OpenCode server as an external child binary (detached
on Unix), so a hard crash, SIGKILL, or Ctrl+C of the host before graceful
teardown could leave it running. Orphaned servers then accumulate and contend
on the shared SQLite DB, causing severe startup slowdowns.
Add a per-process registry plus a startup reaper, mirroring the pattern
OpenCode's own CLI daemon uses for its detached server:
- One file per spawned process at
~/.config/openchamber/managed-opencode/<pid>.json. Per-process files avoid
the read-modify-write clobber race between concurrent runtimes/windows that a
single shared file would suffer.
- On spawn, record the child (pid, owner pid, port, binary, host runtime).
- On graceful close/restart, delete the record.
- On startup, reap only our own, verified, genuinely-orphaned processes:
recorded by us AND still a live `opencode serve` on the recorded port AND
whose spawner is provably gone (reparented to pid 1, or recorded owner dead).
It never touches a process a live instance is using, the user's standalone
server, the official desktop app, or the TUI.
Wire it into every runtime that spawns the server:
- web/desktop via the OpenCode lifecycle (register on spawn, unregister on
close/restart, reap at startup). The restart-for-config-change flow inherits
this automatically through the same kill/spawn paths.
- VS Code carries a parity implementation (it does not bundle the web package)
that reads/writes the same registry directory and uses the same algorithm.
- Tag the actual host runtime (desktop/web/ssh-remote/vscode) for observability.
Also tighten teardown so the registry stays accurate and orphans die promptly
instead of only on the next start:
- The web server now also handles SIGHUP and SIGUSR2 (terminal close and the
nodemon restart used by dev:server:watch / dev:web:hmr).
- Electron now installs SIGINT/SIGTERM/SIGHUP handlers that run the same
background teardown as a normal quit, covering Ctrl+C on electron:dev.
External OpenCode servers (OPENCODE_SKIP_START) are intentionally excluded: we
never manage or kill processes we did not spawn.
The global event-stream WebSocket opened before a valid oc_url_token was
minted, so the upgrade failed auth ("no valid credentials available") in
packaged builds with a UI password. The resulting reconnect storm churned
the sync store and made session status flicker busy<->idle. Await the URL
auth token before connecting (a WS upgrade can't send a bearer header like
SSE does) and drop a rejected token on pre-ready close so the next attempt
re-mints a fresh one.
Also harden /session/status reconciliation: the watchdog poll is now
monotonic (only confirms/raises active status, never blindly lowers a
busy/retry session to idle on a transient or misscoped snapshot). Idle is
applied only by the authoritative reconnect/escalation resync, which trusts
the live server snapshot as the source of truth. Add a Help -> Toggle
Developer Tools menu item so production builds can open the console.
Packaged desktop showed no sessions in 1.12.4. Root cause: the sanitized
session-list proxy path added in #1538 forwarded the renderer's
"authorization" header (the OpenChamber UI client token) to the managed
OpenCode upstream alongside the managed "Authorization" credential.
OpenCode does not recognize UI client tokens, so every session-list
request answered 401 — only in the packaged app, because only its
renderer (openchamber-ui:// origin) attaches a bearer token; dev web and
dev Electron run same-origin without one. The legacy http-proxy path
overwrote the header correctly, which is why everything except session
lists kept working.
Proxy fix:
- proxy-headers: filter the client "authorization" header out of
forwarded request headers; the OpenCode upstream must only ever see
its own managed credentials. Covered by tests.
Desktop cwd:
- electron: launch the managed OpenCode CLI from the user home instead
of app userData, matching upstream desktop behavior. userData-as-cwd
made OpenCode treat the app-data folder as a separate empty workspace.
Home directory poisoning loop:
- directoryPersistence: stop replaying localStorage homeDirectory
through synchronizeHomeDirectory on boot/auth resync. The persisted
value is only a boot-time cache; replaying it re-wrote stale values
(e.g. a project path) into desktop settings on every start, overriding
the authoritative /api/fs/home resolution.
- persistence: never overwrite an injected window.__OPENCHAMBER_HOME__
with a persisted value.
- useDirectoryStore: host switches happen in place (no reload), so
re-resolve home from the new runtime's /api/fs/home on endpoint
change instead of keeping the previous host's value.
- opencode client: only short-circuit to the injected desktop home when
the active runtime is local; remote runtimes ask /api/fs/home.
Settings hygiene:
- persistSettings: log field names only — change payloads can carry
credentials (UI password, client tokens, tunnel tokens) that must not
reach the log file; drop step-by-step log chatter.
- validateProjectEntries: only stat project paths when the incoming
update actually touches the projects list, not on every settings save.
- remove the write-only approvedDirectories setting everywhere and add
a migration that strips the stale key from persisted settings.
Tests:
- usePluginsStore.test: register an own runtime-fetch module mock so the
suite is independent of process-global mock.module leakage from other
files, and restore globalThis.fetch after the suite.
- persistence.test: clean up the window global created for the suite.
Start managed OpenCode from the app data directory instead of the home folder
Prevent unnecessary Desktop, Documents, Downloads, and Music access prompts
Add coverage for configured OpenCode working directory
Enrich each session row in the macOS tray menu and refine its layout.
- Add a "project · branch" sublabel to every session row, resolved from the
session directory: project-root sessions map to their project + live/cached
git branch; worktree sessions map back to their parent project and use the
worktree's branch. Branch resolution falls back live VCS → git store → worktree
metadata, with normalized directory keys.
- Replace the inline text status glyph with a native left-aligned status icon
(vertically centred across the title + sublabel). Idle rows use a transparent
placeholder so every row shares the same gutter and both text lines align.
- Status icons use the app icon set: pulse (busy), check (unread), error-warning
(error), loop-right (retry); all rendered as tinted template images.
- Drop the unread count "(n)" from the row label — the check icon already
signals it and the number wasn't self-explanatory.
- Show the first 8 sessions inline; the rest stay in the overflow submenu.
- Subscribe the tray to the projects, worktree and git stores so subtitles stay
current.
Add an always-visible macOS status bar (tray) item that surfaces OpenChamber's
live state and acts as a quick launcher, plus a series of related desktop UX
fixes around mini-chat, window routing, notifications and shortcuts.
Tray (new):
- Monochrome template cube glyph that adapts to the menu bar light/dark.
- Icon-driven activity indicator: a smooth, eased, infinite "breathing" fill
while sessions are busy; a static filled cube when finished sessions are left
unread; a plain outline when idle. Text counters next to the icon only for
actionable states (pending approvals, errors).
- Menu lists active sessions (status glyph, branch, unread count) with overflow
rolled into a submenu; pending permission/question approvals with inline
Allow once / Allow always / Deny; quick actions (New Session, New Mini Chat,
Show OpenChamber, Quit). Header shows the active instance name
("Local OpenChamber" or the remote host label) for multi-window clarity.
- Session list sourced from the global (cross-project) sessions store, sorted by
last-updated, independent of which directories are currently open; live
status/unread/branch merged in from directory sync stores where available.
Sub-session (multi-run) activity rolls up to the parent row.
- Event-driven updates (global store + directory stores + notifications +
registry) with a short debounce; polling kept only as a slow safety net.
Tray/window routing:
- Opening a session from the tray targets the surface the user was last on: if a
mini-chat is active it switches that existing window to the session in place
(no new window); otherwise the main window (revealed without a reload).
- app.activate (dock click) restores the last-focused/minimized window instead
of spawning a new main window; only creates one when nothing is left.
- "Open in main window" and tray session-open now create the main window when
none exists, queuing the session as a pending deep-link so it opens once the
fresh renderer is ready.
Mini chat:
- New Mini Chat is now a customizable shortcut, exposed in Settings > Shortcuts,
in the File menu (hint only, renderer owns the binding), and in the tray.
- Themed splash backdrop on window open to remove the white flash / flicker;
dismissed once content is ready, leaving the content's single cube logo.
- Mini-chat can switch sessions in place via openchamber:open-session.
Notifications:
- The active/selected session only counts as "seen" when the window is focused,
so turns completing while the app is backgrounded raise an unread marker;
refocusing the window clears it.
Add native macOS vibrancy behind the left sidebar (the only translucent
surface; header/chat/right sidebar stay opaque), plus a setting to turn it off.
- Window created with vibrancy applied after first show (avoids the cold-launch
no-composite quirk); minimize/restore suppress the frost during the genie
animation. Renderer frosts the sidebar via --sidebar-vibrancy-overlay once
data-oc-vibrancy[-ready] are set; project-actions pill matches when open.
- data-oc-vibrancy-ready defaults are set in cssGenerator (DOM guaranteed),
not the preload (document-start race left the sidebar un-frosted on launch).
- prefers-reduced-transparency falls back to solid surfaces.
- Appearance settings (macOS desktop only): a checkbox to enable/disable
vibrancy, persisted to settings.json and applied via a Save & restart button
(vibrancy is a window-creation option, so it needs a relaunch).
Load native Windows app icons for open-in menu
Open Explorer and Terminal to the selected project directory
Resolve Windows Terminal icon from installed app assets
Exit the desktop app without waiting on background cleanup
Kill managed OpenCode by process group with a port fallback
Make OpenCode shutdown reuse the active shutdown promise
Electron updater now uses Electron release metadata only
Removed legacy Tauri package and migration workflow
Replaced Tauri shim usage with the desktop bridge
Add a packaged-client runtime boundary so the shared UI can talk to local,
desktop, remote, and VS Code runtimes through the right transport instead of
assuming one same-origin web server.
Centralize OpenChamber-owned API access behind RuntimeAPIs, runtimeFetch, and
runtime URL helpers, while keeping official OpenCode traffic on the SDK path.
Support runtime switching, remote host selection, desktop client credentials,
and headless connection links for pairing packaged clients with remote
OpenChamber servers.
Harden the new auth model by moving long-lived client tokens out of browser
URLs, introducing short-lived scoped URL tokens for browser-owned transports,
restricting URL-token access to explicit readable/realtime routes, and making
client-token management session-scoped or self-scoped as appropriate.
Update browser-owned assets and preview proxy flows to work with the split
runtime model, including authenticated project icons, preview token propagation,
CSP-safe preview bridge injection, and preview proxy auth that survives
short-lived URL-token expiry.
Tighten Electron security boundaries for packaged clients by gating privileged
preload state to trusted origins and requiring explicit confirmation before
connect deep-links import or switch remote runtimes.
Also refresh agent guidance and project skills so future runtime/API, auth,
preview, UI, CLI, settings, locale, and drag-to-reorder work follows the new
architecture.
* fix: make upstream sync actions target the selected remote
Ensure fetch and pull actually honor upstream selection so fork maintenance works from the Git sidebar, and surface upstream branch status alongside the primary origin-tracking indicators.
* feat: add Windows Electron desktop foundation
* fix(electron): stabilize Windows desktop packaging
* fix(electron): stabilize Windows desktop chrome
Use native Windows titlebar behavior with an Alt-accessible hidden menu, and harden Windows dev command launching so the desktop app follows platform conventions.
* fix(electron): stabilize Windows dev startup
* fix(electron): clarify desktop artifact names
* fix(electron): harden Windows desktop release and launch
* fix(electron): address Windows release review
* fix(electron): point updater and release links to org repo
* Fix Windows settings persistence fallback
* Fix Windows Electron dev startup
* Add Windows Electron window controls
* Fix Windows Electron install and opencode launch
* fix: resolve git status for repositories without upstream
Fixes repository detection stuck on Checking repository
Handles git status when no upstream is configured
Adds regression coverage for git status loading
* Add Windows app menu button
* fix: preserve file editor line endings
* ci: add desktop release smoke workflow
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
Add launch-at-startup support across the Electron desktop app and the web CLI.
Electron now supports macOS launch-at-login through the native login item API. Login launches start OpenChamber in the background without opening a window, while Dock activation, deep links, and second-instance launches still open or focus the normal app window. The desktop Settings UI now exposes a localized launch-at-login toggle in Desktop Network Access.
The web CLI now includes `openchamber startup status|enable|disable`, backed by native user services:
- macOS: launchd LaunchAgent
- Linux: systemd --user service
- Windows: Task Scheduler
Startup services run `openchamber serve --foreground` so the OS service manager owns process lifetime and restarts. Foreground service updates now defer restarts to the service manager instead of spawning duplicate CLI restarts.
Startup services snapshot useful environment variables by default so provider tokens, PATH, SSH agent settings, and OpenCode configuration survive login/reboot starts. The snapshot avoids shell/session-only state, uses systemd-compatible env quoting on Linux, and avoids unused env artifacts on macOS.
Also adds localized docs for startup services and environment variables.
Restart OpenCode after successful updates so the new version is active
Open native About menu into the app About dialog
Update desktop View menu actions for the new layout
* fix: remove max-h-80 cap on quick notes textarea so resized height is respected
* feat: add drag & drop reordering to project todo items
* feat: make todo panel resizable with density-aware sizing
* feat: open plan import file picker at project root
* feat: persist quick notes and todo panel sizes across sessions
* refactor(ui): scale content height with padding in projectnotestodopanel
* Update packages/ui/src/components/session/ProjectNotesTodoPanel.tsx
Signed-off-by: Erman HAVUÇ <ermanhavuc@gmail.com>
* fix(ui): harden context panel resizing and import
---------
Signed-off-by: Erman HAVUÇ <ermanhavuc@gmail.com>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
When notificationMode is 'hidden-only', the isWindowFocused check now
happens before summarization and template resolution, not after. This
prevents costly Zen API calls for notifications that would be skipped
anyway, and eliminates stale notifications arriving after the user has
already read the response and switched away.
* fix(electron): point issue links to current repo
* fix(electron): point updater links to org repo
* fix(electron): align release publish owner
---------
Co-authored-by: Isaac Sanchez <isanchez-hawkins@arize.com>
Add dedicated Electron Mini Chat windows for focused chat sessions without the full desktop shell. Mini Chat can open existing sessions or draft sessions, supports pinning above other windows, transfers sessions or drafts back to the main window, and deduplicates existing-session windows.
Expose Mini Chat entry points from the main header, session sidebar, command palette, and `mod+alt+n`. Add a dedicated Vite entry and React runtime so the compact surface can stay isolated from full-app chrome while still sharing chat, sync, theme, locale, model, agent, and worktree behavior.
Keep Mini Chat behavior scoped to the compact surface:
- limit assistant/user message actions to the appropriate Mini Chat set
- hide workspace changed-files UI in Mini Chat
- keep draft worktree selection and streaming directory state in sync
- mark sessions viewed while they are open in Mini Chat
- support Mini Chat-specific keyboard shortcuts for input focus, model selection, thinking variant cycling, favorite model cycling, and opening new Mini Chat drafts
Harden Electron integration by gating Mini Chat controls on desktop IPC availability, restricting pin/unpin IPC to Mini Chat windows, and only closing Mini Chat after the main window handoff succeeds.
Merge file picker into command palette. Single Cmd+P entry searches
files, sessions, settings pages and commands; groups re-order by best
fuzzy score per source. Sessions show branch labels; git status is
lazily fetched for all session directories on open.
Drop QuickOpenDialog and Cmd+K shortcut.
* feat: embedded preview proxy for local dev servers
Add a same-origin server proxy under /api/preview/proxy/:id and
matching UI surfaces so local dev servers (Vite, Next, etc.) can be
embedded inside OpenChamber.
Server (packages/web/server):
- New lib/preview/proxy-runtime.js: cookie-gated HTTP+WebSocket proxy
to loopback hosts only, with TTL'd targets and SSRF allowlist.
- index.js wires the runtime alongside terminal/event-stream.
UI (packages/ui):
- ContextPanel preview tab with iframe, reload, and open-in-browser.
- Inline html code-block preview in MarkdownRenderer.
- Terminal auto-detects loopback URLs and offers to open them.
- i18n keys across en, es, pt-BR, uk, zh-CN.
* perf(preview): cache proxy targets across PreviewPane remounts
Module-scoped Map keyed by upstream URL so tab switches and component
remounts within the same page session reuse the existing proxy
registration instead of POSTing a fresh target each time.
In-memory only by design: the server holds the target map in memory
and the auth cookie is HttpOnly + scoped to the proxy id, so a stale
persisted entry would 404 after a server restart. Entries are evicted
on registration error and on a 30s safety margin before TTL expiry.
* feat(preview): surface dev-server-down state with retry overlay
Iframes don't expose HTTP status to the parent, so when the proxy
returns a 502 (upstream dev server is offline) the iframe just renders
the raw JSON error body. Probe the proxy URL out-of-band with HEAD
(falling back to GET on 404/405) and replace the iframe with a
friendly 'Dev server is not responding' overlay + retry button when
the upstream is unreachable.
Re-probes on reload, on URL change, and on proxy re-registration.
* feat(preview): strip frame-busting response headers
Many dev servers (Next.js, others) send X-Frame-Options: SAMEORIGIN
and/or a CSP with frame-ancestors that block embedding inside the
OpenChamber iframe. The proxy is same-origin and already
authenticated per-target, so embedding is otherwise safe.
- Drop X-Frame-Options outright on proxied responses.
- Surgically remove only the frame-ancestors directive from
Content-Security-Policy and Content-Security-Policy-Report-Only,
preserving every other directive. Drops the header entirely if no
directives remain.
- Verified end-to-end: upstream sending both headers comes through
with X-Frame-Options removed, CSP retaining default-src/script-src
but no frame-ancestors, and unrelated headers untouched.
* docs(preview): design for remote-host relay agent
Design-only doc for the next phase of the embedded preview feature:
when OpenChamber runs remotely (cloud/shared/tunnel) and the user's
dev server runs on their local machine. Covers architecture (local
agent + outbound control WebSocket + server dispatch), pairing flow,
wire protocol, security model, failure modes, open questions, and
implementation milestones. No code changes.
* feat(preview): auto-open preview pane for loopback URLs in chat
Detect http(s) loopback URLs in incoming assistant messages and open the
preview pane automatically, deduped per (session, url) pair so re-renders
or repeated mentions do not steal focus. Add an inline Preview button
next to loopback links in chat markdown as a manual fallback when the
auto-open was dismissed or the URL appeared in an older message.
- url.ts: isLoopbackHttpUrl / extractLoopbackUrls helpers
- ChatContainer: module-level dedupe Set + effect on active session tail
- MarkdownRendererImpl: optional onPreviewLoopback in main renderer only
(SimpleMarkdownRenderer for tool diffs is intentionally untouched)
- Reuses existing terminalView.preview.open i18n keys
* feat: preview enhancements, dev shutdown, and reliability fixes
Add preview start/stop UI in ContextPanel/Header, improve URL detection (Python HTTP server logs, trailing punctuation, IPv6 loopback), fix proxy path filtering to avoid disrupting non-preview WebSockets. Add dev-only /api/system/dev-shutdown endpoint and Header button to terminate local dev processes and orphaned preview servers. Improve terminal cleanup with process group killing, event pipeline reconnect backoff. Update file read APIs with optional flag and cache control. Add /api/system/free-port endpoint, detectDevServer.ts utility, and preview/shutdown i18n strings for 5 languages.
* fix: harden preview support
* fix: keep terminal toolbar interactive
* fix: keep expanded terminal below header
* fix: keep preview iframe under proxy path
* fix: respect project action preview urls
* fix: rewrite preview asset urls
* feat: capture preview console logs
* feat: annotate preview elements
* feat: attach preview annotation screenshots
* fix: improve proxied preview hmr
* feat: refine preview action UX
* fix: address preview review feedback
* fix: show auto-discover preview wait state
---------
Co-authored-by: William Biggers <will@Williams-MacBook-Pro.local>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
* fix: reconnect SSE immediately on OS wake-from-sleep
When the desktop app resumes from OS sleep, TCP connections are dead
but timers were paused during sleep so the heartbeat watchdog doesn't
fire until ~30s after wake.
Add Electron powerMonitor.resume → renderer notification → event-pipeline
immediate abort, cutting reconnection delay from ~30s to ~0ms.
Changes:
- electron/main.mjs: import powerMonitor, emit openchamber:system-resume
to all renderer windows on OS resume
- ui/sync/event-pipeline.ts: listen for openchamber:system-resume, set
attemptAbortReason and abort the active SSE/WS attempt to trigger
immediate reconnection with retryDelayMs=0 and lastEventId preservation
* fix: reconnect SSE immediately on OS wake-from-sleep
When the desktop app resumes from OS sleep, TCP connections are dead
but timers were paused during sleep so the heartbeat watchdog doesn't
fire until ~30s after wake.
Add Electron powerMonitor.resume → renderer notification → event-pipeline
immediate abort, cutting reconnection delay from ~30s to ~0ms.
Changes:
- electron/main.mjs: import powerMonitor, emit openchamber:system-resume
to all renderer windows on OS resume
- ui/sync/event-pipeline.ts: listen for openchamber:system-resume via
globalThis.window, set attemptAbortReason and abort the active SSE/WS
attempt to trigger immediate reconnection with retryDelayMs=0 and
lastEventId preservation
- Test: event-pipeline-resume.test.js verifies abort → reconnect flow