Commit Graph
261 Commits
Author SHA1 Message Date
e0229917f8 feat(settings): editor font size for chat input and code editor (#1325) (#2065)
* feat(settings): add editor font size setting for chat input and code editor

Adds an 'Editor font size' control in Settings > Appearance that sets an
absolute px font size for the chat input textarea and the in-app
CodeMirror editor. Mirrors the existing terminalFontSize lifecycle.

- New store field editorFontSize (default 13, clamp 9-32, step 1) in
  useUIStore with narrow selectors at each consumer.
- Persistence wired through appearanceAutoSave, desktop + runtime API
  types, and persistence.ts read/normalize.
- Settings UI row (NumberInput) with reset to 13, VisibleSetting union
  entry, OpenChamberPage registration, and search index entry
  appearance.editor-font-size.
- Applied as a post-zoom absolute override on the chat input textarea
  and on the CodeMirror theme's content rule, leaving gutter/line-number
  chrome at its existing hardcoded sizes (matches terminal scope).
- All 10 locales translated (en, es, fr, ja, ko, pl, pt-BR, uk, zh-CN,
  zh-TW); no English placeholders in non-English dictionaries.

Refs #1325

* fix(codemirror): use unitless lineHeight so it scales with editor font size

The & rule in the CodeMirror theme set lineHeight to 1.5rem (~24px),
which does not scale when editorFontSize is increased (e.g., 28-32px).
This causes overlapping lines at larger font sizes.

Change to unitless 1.5, which scales proportionally with whatever fontSize
resolves to (dynamic prop or --text-code fallback). Matches browser best
practice for proportional leading.

Review comment: https://github.com/openchamber/openchamber/pull/2065

---------

Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-07-11 14:45:27 +03:00
achcyano 4296efb64d feat(electron): add Windows startup and system tray support (#2112)
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.
2026-07-10 12:40:48 +03:00
Bohdan Triapitsyn 51e6ae7e3f feat(desktop): multi-transport hosts with relay fallback, card-style services dropdown
- 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
2026-07-10 12:24:50 +03:00
Bohdan Triapitsyn ba32518b88 feat(desktop): live status in the servers list and a cleaner section header
- Each saved server row shows live reachability (Connected · Nms ping /
  Unreachable / Auth required) with a status dot, probed once per list change
  through the shared HTTP/relay probe (relay probing moved to desktopHosts as
  probeRelayDesktopHost, reused by the host switcher)
- Section header: one short description, Import Link promoted to the primary
  action; the token-storage note moved into the Add Server dialog next to the
  token field it describes, and the dialog got its own description
2026-07-10 03:36:10 +03:00
Bohdan Triapitsyn 26e88355e1 fix(desktop): relay host status, display, and server-side LAN candidate
- Probe relay hosts through a throwaway E2EE tunnel in the host switcher
  instead of an HTTP probe against the relay:// pseudo-URL, which always
  reported Unreachable
- Show 'via OpenChamber Relay' for relay hosts in the switcher and the
  servers list instead of the raw relay:// pseudo-URL; hide the URL-centric
  edit action for relay hosts (saving it would drop the tunnel descriptor)
- Pairing LAN candidate prefers the address the requesting client actually
  reached the server on; interface scanning could pick an unroutable virtual
  bridge (docker0), producing links whose LAN leg silently failed and forced
  devices onto the relay
2026-07-10 03:22:57 +03:00
Bohdan Triapitsyn 3184afafdf feat(pairing): auto-close the QR/link dialog once the device connects
The pairing session is single-use, so it leaving the pending list (polled
every 5s) means it was redeemed — close the dialog and toast success. Armed
only after the pairing has been seen in the pending list, so the stale list
at result-phase open can't blink the dialog shut; expired/cancelled sessions
close it silently. Pending-list polling now preserves the previous list on a
transient fetch failure instead of blanking it (which would also have faked
the redeem signal).
2026-07-10 02:01:00 +03:00
Iuliia Ivashko 91a95bfdaa feat: pairing v2 — one-tap trusted devices over LAN and private relay (#2103)
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
2026-07-10 00:12:33 +03:00
a1aae30e66 Share project edit form; add per-project default model (#2015)
* feat(chat): migrate history list to @tanstack/react-virtual with deterministic mobile history loading

- Replace virtua with @tanstack/react-virtual for chat history on all
  surfaces: bottom anchoring (anchorTo: end), key-stable prepend
  preservation, and native iOS touch/momentum deferral live in the core
- Patch virtual-core to clamp the render range to real scroll bounds
  during transient adjustments (OpenCode upstream parity)
- Rows render in normal flow inside a translated wrapper so sticky user
  headers keep working; measurement snapshots cached per session
- Pre-write container height in scrollToFn so the browser cannot clamp
  anchor corrections to the stale height; hold the prepend anchor for up
  to 180 frames on mobile while fresh rows settle (cancelled by user
  input; desktop relies on core anchoring alone)
- Adaptive row-size estimate from per-session measured averages; disable
  reveal fade-in for virtualized history rows
- Mobile loads older history only through an explicit localized top
  button: no scroll-position trigger and no post-mount background
  prepend, so every insert happens from a resting state; a quiet-window
  hold defers any stray prepend commit while a touch gesture is active
- Desktop/VS Code keep the seamless scroll-up trigger and progressive
  background prepend

* Share project edit form between settings and sidebar dialog

Extract ProjectIdentityFields and useProjectIdentityForm so the projects
settings page and sidebar Edit dialog share the same layout and behavior.
Rename the project menu action from Rename to Edit, and add per-project
default model selection for new chats with persistence and draft-session
resolution ahead of global defaults.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* Unify project edit UI with shared ProjectIdentityEditor shell

Wrap header, fields, and inline Save changes button in one editor
component used identically by settings projects page and sidebar
dialog. Remove dialog-specific footer, title, and padding so both
surfaces render the same layout.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* Include Actions and Worktree sections in project Edit dialog

Extract ProjectSettingsPanel with the full settings=projects content
(identity, actions, worktree) and render it from both the settings page
and sidebar Edit dialog. Keep the dialog open after identity save so
users can configure actions and worktrees without reopening.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* Narrow project Edit dialog to modal-appropriate width

Use max-w-2xl instead of max-w-4xl so the popup does not inherit the
full settings page width.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* Unify project settings subsections and auto-save all fields

- Add shared ProjectSettingsSubsection with consistent titles and dividers
- Auto-save identity, actions, and worktree setup commands (debounced)
- Remove Save changes and Save Actions buttons
- Split worktree into Worktree and Existing worktrees subsections
- Align controls to shared max width across all subsections

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* Harden project settings auto-save error handling

- Only update worktree setup snapshot after successful save; toast on failure
- Toast when actions auto-save is blocked by validation for >1s

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* Show toast when project identity auto-save fails

Wrap onSave in try/catch and surface settings.projects.page.toast.saveFailed
so rejected parent callbacks are not silently swallowed.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* Fix clearing project default model from settings

Send null instead of undefined when no default model is selected so
updateProjectMeta enters the defaultModel branch and deletes the field.
Apply consistently in prepareSaveData, ProjectsPage, and SessionSidebar.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-07-09 13:54:05 +03:00
Bohdan Triapitsyn 8b7448bcf0 feat: add code block line wrap toggle
Adds a chat code block wrap toggle in markdown code block headers
Persists and restores the setting across desktop/web settings
Adds localized labels and OpenChamber search entry for the new option
2026-07-08 14:46:44 +03:00
Bohdan Triapitsyn 859b4529da feat: add private relay for end-to-end-encrypted remote access (#2087)
Adds OpenChamber Relay — an opt-in way to reach an instance from a phone,
browser, or another desktop from anywhere, with no open inbound ports, no
tunnel, and no shared LAN. The instance dials outbound to a relay; all app
traffic (HTTP, the event stream, terminal, dictation) is multiplexed and
encrypted through a single connection per client, so the relay only ever
forwards opaque ciphertext.

Transport
- End-to-end-encrypted channel over WebCrypto (ECDH P-256 -> HKDF ->
  AES-256-GCM) with a capability-negotiated handshake and a small
  HTTP/SSE/WebSocket multiplexing protocol. A byte-compatible JS host mirror
  is cross-checked by tests.
- Host: outbound connection manager, per-client tunnel dispatcher to the local
  server over loopback, reuse of the existing instance identity key, and
  management routes. Disabled by default; explicit opt-in.
- Client: plugs into the existing runtime layer (runtime-fetch/-url/-switch/
  -auth, event pipeline, terminal, dictation) so features work over the relay
  unchanged; direct-URL and Electron realtime-proxy paths are untouched.

Pairing & UX
- Relay section in Settings -> Remote Instances (live status, QR/link pairing,
  revocation via the existing client-token list) and the mobile connect flow.
- Frame batching and idle-gated keepalive keep tunnel message volume low
  without affecting streaming smoothness.

Security
- The tunnel is transport only; the server authenticates every tunneled
  request exactly as for a direct remote client.
  fragments only. The relay stores no keys, tokens, or payloads.

Operability
- The endpoint can be pinned to a self-hosted rel
  paired clients inherit it from the offer automatically.
- Relay module DOCUMENTATION.md and a relay-trans
  invariants that future WebSocket/streaming changes must follow.

The relay transport is complete and tested; the UI for enabling and pairing
is gated behind openchamber_relay_gate and stays
2026-07-08 03:44:02 +03:00
Bohdan Triapitsyn 182fc89b8d feat: split session recap and suggestion settings 2026-07-06 22:54:31 +03:00
Bohdan Triapitsyn 28f0736d69 feat: small-model utility calls on existing OpenCode providers (#2049)
Adds a server-side "small model" capability: direct, cheap LLM calls that
reuse the user's existing OpenCode provider logins — the mechanism OpenCode
uses internally for titles and summaries but does not expose through the
SDK or plugins. Zero new dependencies; plain fetch with per-provider wire
formats, credentials never leave the server.

Core (packages/web/server/lib/small-model):
- Resolution mirrors OpenCode's session scoping: explicit settings override
  → small_model from the OpenCode config → family scan within the session's
  provider → the session's own model. The global provider scan only serves
  callers without a session context, and background callers forbid it
  entirely (restrictToPreferredProvider), so conversation content never
  reaches a provider the user didn't pick — explicit choices excepted.
- Per-provider auth replicating OpenCode's plugin loaders: GitHub Copilot
  (device token as bearer, no exchange), ChatGPT plan via the codex
  Responses API (single-flight OAuth refresh written back to auth.json),
  Anthropic messages, Google generateContent, generic OpenAI-compatible.
- OpenCode's free models (opencode/big-pickle, *-free) are never called
  directly; unauthenticated providers are skipped by design.
- Prompt clamping to the model's catalog context limit; thinking disabled
  where a wire switch exists (Z.AI/GLM, MiniMax-M3, Gemini Flash); robust
  content parsing with a clear error when a thinking model spends its whol
  budget on reasoning.
- Settings → Sessions gains a Small Model group: use-default checkbox plus
  an override picker limited to authenticated providers, persisted with
  web/desktop/VS Code sanitization parity.

Consumers:
- Session assist: a server-side watcher on the global SSE hub generates a
  short recap and one suggested follow-up after a session idles quietly fo
  a minute, stored on session metadata (openchamber.assist). Freshness is
  keyed to the last assistant message id, so new activity invalidates the
  payload everywhere with no extra writes. The chat shows the recap under
  the last message after five quiet minutes and the suggestion as a
  dismissible chip above the composer (tap fills the input, never sends).
  Gated by a new Chat setting (default on) that is a hard generation
  switch. Language is anchored to the conversation itself, with a
  script-mismatch guard against model/backend language hallucination.
- TTS: a third input mode, summarized — long replies are condensed to
  spoken prose before playback on any TTS engine.
- Git: commit-message and PR generation moved off the active chat session
  onto the small model fed with real diffs and the commit list (bodies
  included), with a session-transport fallback for free-model-only setups.
- Notes: Add to notes distills long selections into 1-3 dense sentences
  preserving exact identifiers, with verbatim fallback on failure.

Fixes along the way:
- The global event watcher now starts unconditionally; it was gated behind
  the desktop-notify env, leaving the server-side event hub dead in
  packaged apps.
- OpenCode re-emits message.updated for old user messages after idle; the
  watcher no longer mistakes those for new activity.
- Session metadata merges from a fresh read right before the PATCH, so
  writes made during the generation window (suggestion dismissals, review
  links) are preserved; the assist runtime stops during graceful shutdown.
2026-07-05 23:19:10 +03:00
Bohdan Triapitsyn 997116c736 fix(opencode): robust Windows CLI discovery and launch across surfaces
Launching: the VS Code extension spawned .cmd shims with shell:true,
which builds an unquoted command line — a path like
"C:\Program Files\nodejs\opencode.cmd" broke with "'C:\Program' is not
recognized". It now spawns cmd.exe directly with the shim path as its
own argv element (the web server's existing cmd-wrapper pattern), and the
web server's final launch-spec fallback routes raw .cmd/.bat through the
same wrapper instead of handing them to spawn.

Configured paths: wrapping quote pairs (Windows "Copy as path" pastes)
are now stripped everywhere a binary path enters — the VS Code setting
and shared settings.json, env vars on both surfaces, the server's
directory-path normalization, and the desktop settings input.

Discovery: added the system-wide npm prefix (Program Files\nodejs) and
scoop's .exe shim to the Windows candidates, Linuxbrew on Linux, and the
where-probe now runs with windowsHide. The macOS desktop-app exclusion
also covers the OpenCode Dev/Beta app bundles.
2026-07-05 10:39:09 +03:00
Bohdan Triapitsyn de1b85ac56 feat(voice): first-class voice input and local TTS across web, desktop, and mobile (#2018)
Complete rebuild of voice input on a server-authoritative streaming
architecture, replacing the legacy Web Speech / whole-blob / WASM engines
and the dead voice-agent layer (~4k lines removed).

Speech-to-text (dictation):
- Client streams 16 kHz mono PCM16 chunks over /api/dictation/ws with
  seq/ack ordering; buffered audio is retained and replayed on reconnect
- Server transcribes and streams live partial transcripts back;
  segments auto-commit every ~15s with silence suppression and adaptive
  finalization timeouts
- Local provider (default, zero config): sherpa-onnx models in a forked
  worker process — auto-download with progress, staged extraction with
  verification, corrupt-model auto-recovery, idle shutdown after 5 min
- Model catalog with settings picker (accuracy/speed ratings, sizes,
  download/delete): Parakeet TDT v2 (English) and v3 (25 European
  languages, auto-detected), Whisper base and tiny (multilingual, light)
- OpenAI-compatible provider for any Whisper endpoint
- Composer overlay with live transcript, volume meter, timer, and
  cancel / insert / insert-and-send actions; failed transcriptions keep
  their audio for retry or accepting the partial text as-is
- Configurable keyboard shortcut (default mod+alt+v) toggles dictation;
  Enter confirms and Escape cancels while recording
- Overlay is pixel-aligned with the composer (measured footer height,
  matching paddings/typography/gaps) — no layout shift when toggling

Text-to-speech:
- Local Kokoro provider (English, 11 voices) synthesized in the same
  worker via /api/dictation/tts/speak, managed by the shared model
  pipeline; sentence-pipelined playback keeps time-to-first-audio at
  ~1 sentence regardless of message length, and stop cancels in-flight
  synthesis
- Sanitizer keeps inline-code content (strips backticks only), reads
  interword slashes aloud, and removes only absolute file paths

Settings:
- Voice page unified: a single read-aloud toggle owns all playback
  options (the confusing "Enable Voice Mode" is gone); a new "Enable
  voice input" toggle (default on, persisted to settings.json) hides
  the composer mic entirely when disabled

Mobile and transport:
- iOS/Android microphone permissions added (dictation was previously
  impossible on mobile)
- Fixed Android WebSocket upgrades: the Capacitor WebView origin
  (https://localhost) was missing from the packaged-client allowlist,
  403-ing every WS connection — root cause of the old mobile SSE lock,
  which is now removed for all transports

Security and conventions:
- All HTTP routes sit behind the global /api auth gate; the WS upgrade
  explicitly validates the UI session and origin, with oc_url_token
  narrowly allowlisted and covered by tests; the dictation socket mints
  a fresh URL token before connecting
- Routes register before the generic OpenCode proxy; the client goes
  through runtimeFetch/getRuntimeUrlResolver, and runtime switches
  reset the dictation socket
- VS Code deliberately reports dictation as unavailable (no server
  process in that runtime)

CI: workflow Node bumped 20 -> 22 to match the repo engines and fix
better-sqlite3 installs broken by node-gyp@latest on Node 20.

New dependency: sherpa-onnx-node (prebuilt N-API; macOS/Linux x64+arm64,
Windows x64 — Windows-on-ARM falls back to the OpenAI-compatible provider)
2026-07-04 02:48:07 +03:00
Bohdan Triapitsyn a62ec20ff6 fix: enable connection link generation for desktop app 2026-07-01 19:11:05 +03:00
Bohdan Triapitsyn bc4a7d358a feat(desktop): add keep-awake setting 2026-07-01 19:07:20 +03:00
Bohdan Triapitsyn 61a4a23add feat: native iOS & Android mobile apps (Capacitor) (#1954)
* feat(mobile): add Capacitor native shell

* docs: add serve-sim workflow guidance

* docs(mobile): add implementation handoff

* chore(mobile): clean up generated defaults

* feat(mobile): add connection onboarding

* feat(mobile): manage saved instances

* feat(mobile): refine connection management UI

* chore(mobile): upgrade Capacitor 8

* fix(mobile): reliable saved-instance auth with secure token storage

- store client tokens in the OS secure store (iOS Keychain / Android Keystore)
  per instance URL via direct native plugin calls; keep only token-less metadata
  in localStorage. Bound every secure call so a stalled bridge can't hang unlock.
- bypass the secure-storage JS wrapper's lazy platform load (which stalled in the
  webview) by calling internalSetItem/internalGetItem/internalRemoveItem directly.
- harden the shared connect/unlock controller (health + session + progressive
  password) and drop the heavy pre-connect hydration that stalled no-token hosts.
- await token persistence before switching runtime endpoints (no fire-and-forget).
- sync native iOS/Android projects + Keyboard/StatusBar config for Capacitor 8.

* fix(mobile): keep UI stable across connection churn (no transport hardcoding)

The "reload every ~10s" was a UX bug, not a transport one:
- MobileSurfaceShell received a fresh inline onClose each parent render, so any
  re-render (e.g. an SSE/WS event) re-ran the focus effect and refocused the first
  element — stealing focus from the active input and collapsing the keyboard
  mid-edit. onClose now lives in a ref so the focus/keydown effect depends only on
  `open`. Fixes all sheets (Instances/Files/Changes/Settings).
- Gate the mobile shell on connectionPhase, not the live isConnected flag, so a
  transient reconnect keeps MobileShell mounted instead of flashing the loader.
- Instances form: populate fields imperatively on edit/cancel/save instead of via
  an effect keyed on the derived connection, so list churn can't wipe input.

Transport stays on `auto` (WS-first with SSE fallback) — no hardcoded override, so
WS-only Quick Tunnels and SSE-capable proxies both keep working.

* feat(mobile): add native QR pairing-code scanner

Wire the connection onboarding + Instances scan buttons to a real native
scanner via @capacitor-mlkit/barcode-scanning, which registers as the
BarcodeScanner plugin the existing mobileQrScan helper already resolves at
runtime. Add NSCameraUsageDescription and bump the iOS deployment target to
15.5 (GoogleMLKit 8 requirement).

* fix(cli): repair connect-url host resolution

Define the missing isWildcardBindHost helper that connect-url called but was
never declared, which crashed any link generation that reached host
resolution. Also treat a full http(s) --host value as a public server URL so
'--host https://example.com' produces a correct link instead of
'http://https://example.com:port'.

* fix(mobile): make input follow the keyboard across all surfaces

Switch the native Capacitor Keyboard plugin to resize: 'none' and drive the
layout from an --oc-keyboard-inset CSS variable set on keyboardWillShow, which
fires at the start of the iOS keyboard animation. A transition tuned to the
native keyboard curve/duration (0.25s, cubic-bezier(0.38, 0.7, 0.125, 1)) makes
the layout rise together with the keyboard instead of snapping into place after
the built-in 'native' resize finished (~1.5s lag).

The inset is consumed by every surface that can hold a focused input:
- chat shell shrinks its height;
- portal sheets/overlays raise their bottom edge;
- the full-screen connect/login view caps its height so it actually scrolls
  (and is now generally scrollable for long saved-connection lists).

* feat(mobile): rounder chat composer + native bottom safe area

Round the mobile chat composer corners a touch more (1rem), and reserve a small
app-level bottom safe area for the native shell via the --oc-app-bottom-safe
token so controls clear the phone's rounded hardware corners. The reservation
folds into the keyboard inset (no gap above the keyboard), and the composer's
own bottom padding tightens while the keyboard is open.

* fix(mobile): remove iOS 26 dark status-bar band; polish composer

The dark band behind the status bar in system Dark Mode was iOS 26's automatic
scroll edge effect (Liquid Glass) dimming the WebView's top edge beneath the
status bar — appearance-coloured, so it tracked the system theme regardless of
the in-app theme. Hide it via UIScrollView.topEdgeEffect/bottomEdgeEffect on the
WebView's scroll view (iOS 26+), and make the WebView non-opaque so the themed
web background shows under the overlaid status bar.

Also: re-assert the status-bar overlay on resume, paint the document canvas with
the theme background in the native shell, round the composer corners to 1.5rem,
and enlarge the app-level bottom safe area so controls clear the rounded corners.

* feat(mobile): logo splash until first paint is final (no FOUT / layout shift)

Cold start flashed the fallback font and then reflowed once the real font and
persisted appearance prefs landed, and text jumped a frame after mount because the
mobile typography classes were applied from a hook effect. Fix it on three fronts:

- apply device classes (device-mobile / mobile-pointer) synchronously in
  renderMobileApp before the first React paint, so mobile --text-* sizes are in
  effect from the start;
- hold a logo splash (useFontsReady) until the UI web font has loaded;
- gate that splash on appBootReady too, resolved once async appearance/typography
  preferences are applied, plus a double rAF so styles commit before reveal.

All under a 2.5s safety timeout so a slow/offline CDN can't block startup.

* feat(mobile): native local notifications; APNs implemented but frozen

The native app now delivers agent ready/error/question/permission events as iOS
(and Android) Local Notifications: a native notifications API backed by
@capacitor/local-notifications replaces the Web Notifications API (which doesn't
display in a WKWebView), driven by the notification SSE stream now subscribed in
the mobile app. Tapping a notification opens its session. Also fix the settings
toggle, which treated the Capacitor app as a browser and gated 'Enable
Notifications' on the absent Web Notification permission, leaving it un-toggleable.

Remote APNs push is implemented end-to-end (dependency-free HTTP/2 + ES256 JWT
server runtime, token routes, client registration, iOS native config) but kept
dormant: config-gated so it never fires, client registration not wired, and the
aps-environment entitlement / background mode removed so the app builds with no
Apple push setup. It will be reused once OpenChamber ships its own encrypted
relay so users don't each configure APNs. See notifications/APNS.md.

WKWebView can't use web push (unlike an installed PWA), so true
background-when-suspended delivery on native requires APNs via that relay.

* feat(mobile): APNs relay-mode background push

Deliver native iOS background push through the central relay: the server posts
device tokens + generic, model-based text to api.openchamber.dev/v1/push/send
(default), which holds the single APNs key and signs+sends; dead tokens (410)
are dropped from the per-session store. Direct APNs (HTTP/2 + ES256 JWT) stays
as a fallback when OPENCHAMBER_PUSH_RELAY_DISABLED=true. The mobile push payload
is generic only (model + scenario) so no session content crosses the relay.

Re-enable the client token registration (useNativePushRegistration) and the
aps-environment entitlement (alert pushes need no background mode). Wired into
the same fanout as web push; focus-suppressed and only when tokens exist.

* fix(mobile): APNs-only native notifications, generic templates, no foreground

Make APNs the single notification channel for the native app and fix delivery:

- Remove local notifications entirely (the @capacitor/local-notifications plugin
  and the SSE-driven path). A WKWebView can't tell foreground from background
  (document.hasFocus() is unreliable), so local notifications leaked while the app
  was open; the in-app dispatch is no-op'd on native.
- Stop gating APNs on UI visibility — a backgrounded WebView can't report 'hidden'
  before iOS suspends it, which dropped background push. Instead always send and let
  iOS suppress the foreground banner (PushNotifications presentationOptions: []).
- Fix a ReferenceError (out-of-scope 'variables') that crashed maybeSendPushForTrigger
  before any push was sent.
- Mobile push text is generic: a scenario title ('Agent response is ready' / 'needs
  your input' / 'needs permission' / 'hit an error') + the session name, no model or
  message content.
- Hide the focus toggle, templates, and test button in mobile notification settings.

* feat(push): sign relay requests + bind tokens per server

Each OpenChamber server now auto-generates an ECDSA P-256 keypair (persisted in settings,
like the VAPID keys) and uses it to:
- bind every newly-seen device token to the server on the relay
  (POST /v1/push/register-token, signed), and
- sign every push send (publicKeyJwk + ts + signature over ts.sortedTokens.title).

The relay derives serverId = SHA-256(publicKey), verifies the signature + timestamp, and
only delivers to tokens bound to that server. Result: a leaked device token alone can no
longer be used to push to a device — the sender also needs the server's private key. Stays
zero-config (the keypair generates on first use). Drops the soft PUSH_RELAY_TOKEN bearer.

* docs(push): describe relay data-confidentiality model

Document that the push payload is not application-encrypted (TLS-in-transit only), what the
relay and Apple can see (generic scenario title + session name, plus token/sessionId), that
the signature is authentication rather than encryption, and what an end-to-end encrypted
payload would require.

* fix: invalid skill description

* feat(push): app-icon badge for native notifications

Send an absolute aps.badge with each native push = the count of distinct
collapse-ids (tag) pushed since the app was last foregrounded, mirroring the
lock-screen banner stack. Cleared server-side on user engagement (session view,
message-sent, visibility beacon) and on-device via sceneDidBecomeActive.

* feat(mobile): auto-connect last instance on launch + notification deep-links

Cold launch silently reconnects to the most-recent saved instance (when reachable
and a token is saved), holding the splash instead of flashing the connect screen;
falls back to the connect screen when there's no saved instance, it's unreachable,
or it needs a re-login. Notification-tap deep-links are now captured unconditionally
(even before connect / on cold launch) and applied once the app is ready, so a tap
opens the target session instead of being lost on the login screen.

* fix(mobile): resolve theme background before first paint on cold launch

The mobile shell entry (mobile.html) had no pre-paint theme step, so a cold
launch flashed the WebView's default light canvas, then the baked
design-system default (.dark { --background: #151313 }) via body.bg-background,
before React's theme system injected the real theme vars. Add a blocking script
that resolves dark/light from the persisted theme + system preference and sets
--background (plus color-scheme and the element background) inline on the root,
so the very first paint matches the resolved theme. Falls back to the default
flexoki backgrounds when no theme has been persisted yet.

* feat(mobile): openchamber:// deep-link foundation + arm64 simulator build

Add a typed deep-link vocabulary (deepLinks.ts: parse/build + DeepLinkIntent)
and a single native navigation layer (deepLinkNavigation.ts) that handles both
the openchamber:// URL scheme (App.appUrlOpen — widgets, Live Activities,
external links) and notification taps, normalising each into an intent. Session
and new-session resolve against the store; shell surfaces (sessions/settings/
views/changes) register handlers. Cold-launch intents stash until the app is
ready. Replaces the push-only useNativePushDeepLink and keeps backwards
compatibility with bare sessionId payloads.

Register the openchamber:// scheme in Info.plist.

Dev tooling: with-mobile-env now honours xcode-select (-p) instead of hardcoding
Xcode.app, so an Xcode beta is used. build:ios:simulator runs a new
ios-sim-build script that temporarily drops the MLKit barcode-scanning pod
(no arm64-simulator slice) so the app builds an arm64 binary installable on
Apple Silicon simulators, then restores the Podfile + Pods for device builds.
QR scanning already degrades cleanly when the native plugin is absent.

* feat(mobile): iOS home/lock/Control Center widgets + push-driven refresh

Add a Widget Extension (OpenChamberWidget) and a Notification Service Extension
(OpenChamberNotificationService), wired into the Xcode project, sharing an App
Group with the app.

Widgets:
- Overview (medium): recent sessions with read/unread dots + four quick actions
  (new, status, instances, settings).
- Sessions (large): session list with per-session project label, attention count
  and a new-session button in the header.
- Quick Actions (small): New chat pill + status/instances.
- Lock Screen (accessoryCircular x2): brand logo to new session, attention counter.
- Control Center control: brand logo (custom SF Symbol) to new session.

Data: the app writes a session-overview snapshot (attention count + recent
sessions with project labels) to the App Group on scene activate/resign; the NSE
refreshes it from each push (aps.badge + sessionId) so widgets update even when
the app is closed (needs aps mutable-content, added to the server + relay).

Deep links: add openchamber://status (session status panel) and reuse
view/instances; all widget taps route through the existing deep-link channel.

* feat(mobile): large Sessions widget lists 6 sessions with project labels

* feat(mobile): edge-swipe to switch sessions with directional slide+fade

* fix(mobile): keep widgets in sync via reload-on-change + periodic refresh

Widgets sharing the app's WidgetKit reload budget refreshed unevenly, leaving the
large Sessions widget stale (no unread dot / attention count) while medium updated.
Drop the per-call updatedAt from the snapshot, only write + reloadAllTimelines when
the session overview actually changed (so we don't burn the budget on every scene
activate/resign), and give each widget a periodic timeline refresh so a missed
reload self-corrects.

* feat(mobile): Android support — chrome fixes, SSE lock, icon, QR scan

Cosmetics:
- Status bar: on Android inset the WebView below the bar (overlay:false) and
  paint it with the resolved theme background + correct content Style, since
  Android doesn't feed env(safe-area-inset-top) to CSS.
- Keyboard: skip the manual --oc-keyboard-inset on Android (the window resizes
  natively, so applying it double-counted and floated the composer); declare
  windowSoftInputMode=adjustResize and disable the shell height transition on
  Android so the header no longer bounces on keyboard open.

Transport: lock Capacitor apps to SSE — native WebSocket streaming is unreliable
on Android (events only arrive once a run finishes). Forced in sync-context and
the other options are disabled in the Chat settings UI.

Push: gate APNs registration to iOS only; on Android @capacitor/push-notifications
register() needs Firebase/FCM (not configured) and crashes at launch.

QR pairing: declare CAMERA permission + the ML Kit barcode_ui dependency, and
install/await the Google barcode scanner module (with a post-install retry) before
scanning so the first scan works without a manual retry.

Icon: Android adaptive launcher icon generated from the cube logo (full-bleed
white background, no edge artifact on One UI). Source assets under mobile/assets.

Tooling: adb-based android-device.mjs + android:* scripts for device deploy.

* feat(notifications): presence-aware push routing (don't spam the phone)

Only push to a device when the notification would otherwise be missed there. A
notification is suppressed on devices where the user is already present.

- Tag every client's visibility beacon and web-push subscription with a platform
  ('ios' | 'android' | 'vscode' | 'desktop' | 'web') via getClientPlatform().
- Server tracks visibility per client (keyed by oc_ui_session) with the platform,
  and exposes isAnyInteractiveClientVisible() = any visible non-mobile client.
- Native push (APNs) and mobile PWA web-push are now suppressed when an
  interactive (desktop/web/vscode) client is visible — it already shows the
  in-app notification. Gated on the desktop's visibility (reliable), never the
  phone's own (a backgrounded WKWebView can't report "hidden").
- Desktop/web web-push keeps the any-visible gate (a visible client absorbs it).
- Skipping APNs also skips the badge increment so it doesn't drift.

Fixes the case where every session on a shared instance pushed to the phone even
while the user was actively working on desktop.

* feat(mobile): Android FCM push notifications

Enable native background push on Android via Firebase Cloud Messaging, in parallel
with the existing iOS APNs path.

- Add google-services.json + declare POST_NOTIFICATIONS (Android 13+). The Google
  Services Gradle plugin is applied when the file is present, so register() returns
  an FCM token instead of crashing.
- Un-gate native push registration to iOS OR Android, and tag the registered token
  with its platform ('ios' | 'android') so the relay routes it to APNs vs FCM.
- Server stores the platform per device token and binds it to the relay (platform
  included in the signed register message).
- Notification small icon: monochrome cube silhouette with a mark on the top face,
  set as the FCM default_notification_icon so the status-bar icon reads as the logo.

Relay-side FCM sending ships in openchamber-website.

* docs(mobile): refresh HANDOFF with current state, dev/deploy process, and CI gap

* chore(mobile): iOS store-review prerequisites (privacy manifest, encryption flag)

- Add the app's PrivacyInfo.xcprivacy (no tracking; required-reason UserDefaults for the App
  Group snapshot shared with the widget + notification service extension) and wire it into the
  App target's resources — Apple requires an app-level privacy manifest.
- Set ITSAppUsesNonExemptEncryption=false to skip the per-build export-compliance prompt.
- HANDOFF: add a store-review-readiness checklist (in-repo vs release-time console/infra items).

Verified: plist lint, xcodebuild parse, and an iOS simulator build with PrivacyInfo.xcprivacy
bundled into App.app.

* refactor(mobile): dedupe capacitor detection + make beacon guard explicit

Addresses non-blocking PR review notes:
- Consolidate the repeated Capacitor-native check (mobileConnections, deepLinkNavigation,
  usePushVisibilityBeacon each redefined it) onto the single isCapacitorApp() in lib/platform.
- usePushVisibilityBeacon now guards on isWebRuntime() OR isCapacitorApp() instead of relying on
  isWebRuntime() being true for Capacitor, so the beacon can't silently stop if that changes.
2026-07-01 09:55:41 +03:00
Bohdan Triapitsyn 359c73fcf3 feat(desktop): support remote runtime headers 2026-06-30 00:30:48 +03:00
Bohdan Triapitsyn e904a43b5a fix(settings): move Follow-up behavior radio group next to the other choice settings
It was rendered in the middle of the checkbox list, where a labeled radio
group reads as out of place. Move it into the radio/choice cluster, right
after Diff Layout, and drop it from the checkbox section's visibility gate.
2026-06-29 01:37:36 +03:00
f13f6d5540 feat(#1766): support OpenCode steer delivery / follow-up behavior settings (#1781)
* feat: support OpenCode steer delivery / follow-up behavior settings

Implements issue #1766 — steer delivery mode for mid-turn message
insertion, replacing the old boolean queue-mode toggle with a tri-state
follow-up behavior setting (Steer / Queue / Send immediately).

- Plumbing: threaded optional delivery: 'steer' through sendMessage
  -> routeMessage -> opencodeClient.sendMessage -> promptAsync
- Store: messageQueueStore stores followUpBehavior; migration from
  legacy queueModeEnabled persisted state
- Settings: Chat -> Follow-up behavior shows three radio options
  using existing settings UI patterns
- Composer: when session is busy, a floating queue button remains;
  force-sending a queued message (via chip click) uses delivery: 'steer'
  during a busy session; Steer button intentionally omitted — steer is
  available via the two-gesture path (Enter to queue -> chip to steer)
- Keyboard: queue mode = Enter queues, Ctrl+Enter sends; otherwise
  Enter sends, Ctrl+Enter queues
- Persistence: DesktopSettings, web settings payload, and server-side
  sanitizer handle the new key with legacy fallback
- i18n: follow-up behavior section and option labels in all 9 locales
  plus new chat.chatInput.actions.queue label
- Search: settings registry updated from chat.queue-mode to
  chat.follow-up-behavior

Validation: type-check passes (no new errors), lint clean.

* fix(#1766): make steer mode actually steer

The followUpBehavior === 'steer' branch in handlePrimaryAction and the
keyboard handler was a no-op — both fell into the else branch and sent
without the delivery: 'steer' flag, so selecting 'Steer (insert into
the running turn)' in settings produced identical behavior to 'Send
immediately'.

- handlePrimaryAction: when steer mode is selected and the session is
  busy, call handleSubmit({ delivery: 'steer' }) directly
- Keyboard handler: in steer mode, Enter steers and Ctrl+Enter sends
  immediately (consistent with queue mode where Ctrl+Enter bypasses
  the special handling)

Also removes the unused chat.chatInput.actions.queue i18n key from all
9 locales (it was a dead key after the Steer button was removed from
the composer).

Validation: type-check clean, lint clean.

* refactor(#1766): flatten nested ternary in followUpBehavior resolution

Replace nested ternary with explicit if/else chain per project code style
(CONTRIBUTING.md). Import FollowUpBehavior type explicitly for the new
let declaration.

* feat(chat): drop redundant 'immediate' follow-up mode, keep Queue + Steer

'Immediate' was wire-identical to 'Steer' on a busy session: OpenCode only
supports delivery 'steer' | 'queue' and defaults to 'steer', so an immediate
send (no delivery flag) already steered into the running turn. The three-mode
UI therefore exposed two settings that did the same thing.

Collapse to two modes — Queue (unchanged: client-side queue with edit/reorder)
and Steer. Any persisted/legacy 'immediate' (and legacy queueModeEnabled=false)
now maps to 'steer', preserving prior behavior. Removes the immediate option,
its keyboard branch, the i18n label across all locales, and narrows the
followUpBehavior union to 'steer' | 'queue'.

---------

Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-29 01:28:20 +03:00
c184ddf185 fix(worktree): subagent sessions kept when deleting worktree group from sidebar (#1806)
* fix(worktree): include sessions when deleting worktree group from sidebar

allGroupSessions was guarded by group.isArchivedBucket, returning [] for
active worktree groups. This caused the 'delete worktree' button in the
sidebar to send an empty session list — SessionDialogs only removed the
git worktree directory and skipped archiving any sessions, leaving them
orphaned.

Remove the guard so all sessions (including recursive children /
subagent sessions) are collected regardless of archived state.

* fix(sessions): delete all descendants on hard-delete instead of relying on server cascade

The previous code sent only the root session ID and assumed the server
would cascade-delete all children. If the cascade failed, children were
left orphaned. Delete root + descendants individually; 404 responses
from already-cascade-deleted children are treated as success.

* fix(sessions): clear worktree metadata when deleting a session

Deleted sessions kept their worktree attachment in both
session-worktree-store and session-ui-store. Clean it up on successful
deletion and on 404 (already deleted).

* fix(worktree): search subagent sessions across all directories before delete

WorktreeSectionContent and BranchPickerDialog used useSessions(), which
is scoped to the current sync directory. Subagent sessions created in
other worktrees/project roots were missed and left orphaned. Search
across active + archived global sessions when collecting descendants.

---------

Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-29 00:42:13 +03:00
Bohdan Triapitsyn 84d7303346 feat(desktop): add macOS dock badge for chats with unseen activity
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.
2026-06-28 01:07:52 +03:00
Serhii DziupinandSerhii Dziupin f4e90ca232 fix(agents): stop falsely reporting saved agent edits on external OpenCode; make model-selector shortcut customizable (#1839)
* feat(shortcuts): make 'Open model selector' shortcut customizable

Lets users remap the model selector shortcut (e.g. to Ctrl+M) via
Settings > OpenChamber > Shortcuts, matching OpenCode's quick
model-switch keybinding workflow.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* fix(agents): surface manual-restart needed when on external OpenCode

Agent prompt/permission/settings edits are written to disk, but an
external OpenCode server (skip-start or auto-detected on the default
port) is not owned by OpenChamber and is only health-probed on config
change, so it keeps serving its startup-cached config until restarted.
The API previously claimed a successful reload, so the UI silently
reverted the edit to the stale/default value on refresh.

Now refreshOpenCodeAfterConfigChange reports whether a real reload
happened; agent routes return requiresManualRestart for external mode;
and the agents UI keeps the saved values and warns the user to restart
their OpenCode server instead of showing a false success. Managed mode
behavior is unchanged (process is restarted and reload is live).

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

---------

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-06-26 19:37:45 +03:00
Serhii DziupinandSerhii Dziupin 3e3cd82a47 feat: inherit model-picker reorder/accordion persistence and shift-delete from otto-ui (#1833)
* feat(model-picker): drag-to-reorder providers and persist accordion state

Ports two model/provider picker QoL features from otto-ui:
- Persist collapsed state of picker sections (favorites, recent, each
  provider) via a new persisted zustand store so collapse survives
  remounts and reloads, shared across every picker surface.
- Desktop drag-to-reorder of provider sections (whole header as the
  mouse activator, 8px threshold so a plain click still toggles
  collapse), with the order persisted in useUIStore.providerOrder and
  applied across ModelControls, ModelMultiSelect and ModelSelector.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* feat(sessions): shift-click quick action hard-deletes thread without prompt

Ports the shift-quick-delete feature from otto-ui. The sidebar quick
action (normally archive) becomes a no-prompt hard delete while Shift is
held: the icon switches to a trash bin, the affordance turns destructive,
and the click bypasses the confirmation dialog via a new skipConfirm
source flag. A shared useShiftKeyHeld hook (single window listener set,
useSyncExternalStore) keeps only the small action button re-rendering on
Shift state changes, and resets on window blur so the affordance can't
get stuck after alt-tab.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

---------

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-06-26 19:36:47 +03:00
00821700de chore: remove dead code (59 unused files + ~125 unused exports) (#1835)
* chore: remove dead/unreferenced files across ui, vscode

Remove 59 unused source files (components, hooks, lib utils, stores,
barrels, and orphaned vscode github modules) that are not imported by
any entry-reachable code. Also drop a stale test mock for the removed
execCommands module.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* refactor: remove unused exported symbols (types, functions, consts, hooks)

Remove exported symbols whose identifier is referenced nowhere in the
repository (verified via repo-wide search), across ui types/contracts,
lib utilities, sync layer, stores, and components. Also drop the few
imports/private helpers orphaned by these removals.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* refactor: remove more unused exports (desktop, shortcuts, worktree, vscode)

Continue removing repo-wide unreferenced exported functions, consts and
types across lib/desktop, shortcuts, worktreeSessionCreator, sync, and
vscode gitService, with cascading orphaned helpers/imports cleaned up.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* chore: add dead-code cleanup tooling

* refactor: checkpoint dead-code cleanup

* refactor: remove dead-code suppressions

---------

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-26 19:27:53 +03:00
8c1a24089d fix(worktree): gate sessions on bootstrap readiness (#1762)
Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-26 12:16:42 +03:00
076e9331ec fix(agents): send null to clear temperature/topP overrides on update (#1718)
When clearing temperature or topP on an existing agent, the UI sent
undefined which JSON.stringify drops, so the server never received the
clear command. Now sends null to properly remove the override in
opencode.json.

Changed updateAgent to use 'field' in config pattern for temperature
and top_p, matching the existing prompt handling.

Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-24 17:08:22 +03:00
bashrusakhandLeonid Skorobogatyy e44efa97d6 feat(agents): expose thinking variant configuration in agent settings (#1715)
* feat(agents): expose thinking variant configuration in agent settings

Fix #1425: add variant field to agent config UI so users can configure
thinking/reasoning depth per agent without editing opencode.json.

Changes:
- Added variant to AgentConfig and AgentDraft types in useAgentsStore
- Pass variant in createAgent and updateAgent API calls
- Support null for temperature, top_p, and variant to clear overrides
- Added variant input field in AgentsPage 'Model & Parameters' section
- Added variant to settings search registry
- Added i18n strings for variant field in all 8 non-English locales

The variant field maps to provider-specific parameters (e.g. Anthropic
high/max variant, OpenAI reasoning effort). Users can enter any string
value; the SDK passes it through to the model provider.

Clearing temperature/topP/variant now sends null to the server instead
of omitting the field, which properly removes the override in
opencode.json.

* fix(sync): preserve tool state.time in materialization merge

* chore: trigger re-review

* fix(agents): use thinking variant selector in settings

* fix(agents): preserve thinking variant values

---------

Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
2026-06-23 23:22:59 +03:00
bashrusakhandLeonid Skorobogatyy 3db91721cb fix(providers): use correct endpoint for provider disconnect (#1714)
Fix #1462: handleDisconnectProvider called the SDK auth.remove() which
only clears auth credentials from auth.json. Cloud providers configured
in user/project/custom config files were not removed and reappeared
after reload. Now calls DELETE /api/provider/:id/auth?scope=all which
removes the provider from all config sources.

Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
2026-06-23 22:38:49 +03:00
Gokul GK 12835c7646 fix: refresh skills catalog after settings updates (#1681) 2026-06-23 21:10:22 +03:00
Bohdan Triapitsyn 4d63278efd feat: add SSH commit signing to git identities
Configure commit signing per Git identity
Apply SSH signing settings automatically
Support signing in web and VS Code
2026-06-18 23:51:40 +03:00
Bohdan Triapitsyn a2a1cedf8d perf: streamline provider and agent startup loading #185
Avoids loading the full provider catalog on startup
Prewarms project config in the background
Prevents duplicate worktree-scoped config requests
2026-06-16 19:23:38 +03:00
Bohdan Triapitsyn 3ba93c111e fix: show clear toast when agent definition is missing
Surface missing agent definitions during delete
Avoid hiding delete errors behind generic failures
Add localized toast copy
2026-06-16 14:33:08 +03:00
Bohdan Triapitsyn e982bd9388 fix: prevent agent deletion from disabling built-ins
Stop delete from creating disable overrides
Delete only the selected agent scope
Keep web and VS Code behavior aligned
2026-06-16 13:38:24 +03:00
Bohdan Triapitsyn e904abda04 feat(editor): Shiki highlighting for code files in PlanView and SkillsPage
Reuse the file-editor Shiki extension for the PlanView and SkillsPage editors,
gated to non-markdown files. Markdown sources keep the lezer highlighter (its
markdown-aware styling is better for editing and there's no Shiki view to match).

- PlanView: code files opened through it get Shiki colors; plan .md stays lezer.
- SkillsPage: code supporting files get Shiki colors; SKILL.md stays lezer.
2026-06-16 01:08:54 +03:00
Bohdan Triapitsyn a45376d585 perf: migrate chat rendering to virtua (#1651)
* refactor: migrate chat history virtualization to virtua

* refactor: render loaded chat history directly

* refactor: finish virtua migration

* perf: defer tool body rendering

* perf: queue deferred tool body mounts

* perf: quiet and defer markdown file probes

* perf: defer markdown code highlighting

* perf: stabilize markdown plugin lists

* perf: defer mermaid markdown rendering

* perf: delay markdown file reference annotation

* perf: attach markdown table listeners on demand

* perf: trim markdown render overhead
2026-06-15 03:29:40 +03:00
Bohdan Triapitsyn cf8eac966e Refine stacked diff view 2026-06-13 22:30:03 +03:00
Roberto BertóandBohdan Triapitsyn 15813b61c3 feat: add opt-in docked editor toolbar (#1562)
* feat: add opt-in docked editor toolbar

* fix: align docked editor toolbar actions

* fix: move docked toolbar setting to navigation

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-13 00:01:07 +03:00
Bohdan Triapitsyn 106b31a407 Harden remote API security boundaries 2026-06-12 18:24:07 +03:00
youzini abda2bc42b fix(mcp): import opencode mcp config snippets (#1532) 2026-06-11 21:55:38 +03:00
Bohdan Triapitsyn f26950fa4e fix: treat gh CLI token as GitHub account 2026-06-11 19:30:19 +03:00
Bohdan Triapitsyn 465732858f fix: clarify GitHub token settings 2026-06-11 18:59:58 +03:00
Tom Rochette 33e614c76b Fallback to gh CLI credentials if available (#1515)
Adds `gh` CLI as a GitHub credential fallback for users who already have
`gh auth login` configured locally. OpenChamber-owned OAuth credentials
remain the primary source of truth; the `gh` token is only used when no
stored OpenChamber GitHub access token exists and the fallback is not
disabled.

The fallback is implemented as a credential provider only: GitHub features
continue to use the existing Octokit/GitHub API paths for issues, pull
requests, checks, merges, and related operations. The PR does not replace
those endpoints with `gh issue` or `gh pr` CLI commands.

Server changes:
- Add `gh-cli-credential.js` to read `gh auth token` with a bounded timeout.
- Cache the `gh` token lookup for 30 seconds, including negative results,
  to avoid repeated subprocess spawning on status/polling paths.
- Hide the subprocess window on Windows via `windowsHide: true`.
- Clear the gh CLI token cache when the fallback setting changes.
- Update `getOctokitOrNull()` to prefer stored OpenChamber OAuth tokens and
  fall back to the `gh` token only when enabled.
- Add `ghCliDisabled` persistence in the existing settings file with atomic
  writes and `0o600` file permissions.
- Add `POST /api/github/auth/gh-cli` to enable or disable the fallback.
- Extend `/api/github/auth/status` with `ghCli` metadata: availability,
  disabled state, active state, and active user when applicable.

UI/runtime changes:
- Extend `GitHubAuthStatus` and `GitHubAPI` with gh CLI fallback metadata
  and toggle support.
- Add web RuntimeAPI support for toggling the gh CLI fallback through
  `runtimeFetch`, preserving active runtime/remote target behavior.
- Add deterministic VS Code unsupported handling for the gh CLI toggle.
- Update GitHub Settings to show gh CLI availability and active status.
- When gh CLI is the active auth source, show it in the connected account
  card and offer Disable instead of Disconnect.
- Keep Add Account available so users can still connect an OpenChamber OAuth
  account, which then takes priority over gh CLI.
- Add localized gh CLI settings strings across supported settings locales.

Fixes addressed during review:
- Removed unreachable UI branches in the inactive gh CLI card.
- Avoided duplicate and repeated `gh auth token` subprocess calls.
- Hardened settings file permissions for the new persisted flag.
- Routed the gh CLI toggle through the RuntimeAPI/runtimeFetch path instead
  of direct browser `fetch`.
- Added targeted tests for hidden subprocess options and negative-result
  cache behavior.
- Fixed a VS Code webview Response body typing issue that blocked type-check.
2026-06-11 18:43:41 +03:00
Bohdan Triapitsyn e36085d898 feat: add collapsible user message setting 2026-06-11 01:26:50 +03:00
Pascal AndréandBohdan Triapitsyn 49a1424e5f feat: add complete French localization (#1482)
* feat: add French locale runtime

Add French to OpenChamber's shared i18n runtime, dictionaries, and parity tests so the existing language picker can load a complete fr locale across shared UI surfaces.

* fix: localize shared UI formatting

Remove remaining shared UI locale hardcodings so dates, numbers, and first-party helper copy follow the active app locale instead of leaking English on French surfaces.

* feat: localize VS Code French surfaces

Localize VS Code bootstrap, native runtime messages, panel titles, and manifest contribution strings so French users get consistent first-party copy across the extension experience.

* fix: TASK-2026-05-30-008 correct French review findings

Fix broken French relative-time and weekday strings reported on PR #1482 and restore proper import order in quota utils without broadening scope.

* fix: TASK-2026-05-30-008 address final PR review comments

Capture the localized More Info label once in the VS Code CLI-missing flow and replace the remaining inline French-only utility strings with dictionary-driven copy plus required locale keys.

* fix: TASK-2026-05-30-008 normalize French glossary

Correct glossary-level French terminology on the live PR branch, keeping canonical technical terms like PR, worktree, stash, HEAD, Mermaid, Markdown, remote, and session while replacing misleading literal translations.

* fix: TASK-2026-05-30-008 refine French terminology pass

Clean up remaining glossary mistakes on the French PR branch, especially around Mermaid, Markdown, PR, worktree, stash, branch, remote, and commit terminology, while keeping behavior unchanged.

* fix: TASK-2026-05-30-008 clean remaining French false friends

Correct the SOCKS5 mistranslation and a final small set of obvious false-friend technical nouns on the French branch without changing behavior.

* fix: TASK-2026-05-30-008 correct French glossary terms

Replace remaining false-friend translations in the French UI dictionaries and normalize technical labels for the French PR branch.

* fix: TASK-2026-05-30-008 remove remaining French Mermaid false friend

Replace the last confirmed Sirène translation with Mermaid and re-run the requested blacklist and build verification on the PR branch.

* fix: TASK-2026-05-30-008 enforce French glossary policy

Keep skill/PR/worktree/remote terminology developer-credible in French and remove remaining machine-translated Git and settings copy.

* fix: TASK-2026-05-30-008 keep prompt terminology in French

Replace remaining technical invite translations with prompt wording across scheduled tasks, multi-run, prompt templates, and Magic Prompts.

* fix: TASK-2026-05-30-008 finalize French terminology cleanup

Polish remaining worktree/remote wording, remove visible metadata leakage, and correct final Git and settings labels on the French PR branch.

* fix: TASK-2026-05-30-008 polish final French strings

Correct the last aria-like artifacts and awkward worktree/remote/GitHub URL phrasing in the French dictionaries.

* fix: TASK-2026-05-30-008 normalize final French glossary framing

Tighten the last worktree/remote/checkout wording and fix remaining French grammar around canonical technical terms.

* fix: TASK-2026-05-30-008 align final developer glossary wording

Normalize the last French framing around canonical developer terms like worktree, remote, prompt, and checkout.

* fix: TASK-2026-05-30-008 harmonize final French sentence framing

Replace the last raw franglais around checkout, remote, worktree, and prompt-facing labels with more natural French framing while keeping the chosen technical terms.

* fix: TASK-2026-05-30-008 add compact relative date keys

Replace French-specific prefix stripping in compact session date labels with dedicated i18n keys across locale dictionaries, preserving existing compact label output while making French wording robust.

* docs: add French documentation

* docs: mention French locale folder

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-10 20:19:30 +03:00
Bohdan Triapitsyn 12c8cbf544 feat: add right-click menus across sidebar rows
Open row actions at the pointer position
Keep three-dot menus working separately
Reuse shared menu styling for consistent visuals
2026-06-10 15:12:10 +03:00
Bohdan Triapitsyn 7a18e79bdb fix: improve Settings search results
Adds Window transparency to Settings search
Keeps Settings search group headers in normal case
2026-06-10 14:11:13 +03:00
Bohdan Triapitsyn 079e3a9bd6 feat(settings): add item search (#1592)
Adds item-level search inside Settings so users can find concrete settings like provider auth, agent mode, terminal font size, tunnel options, notification events, and similar controls instead of only filtering top-level pages.
Groups search results by Settings page and shows localized labels plus optional descriptions where useful.
Supports keyboard navigation with Arrow Up/Down, Enter, and Escape, matching the existing autocomplete interaction style.
Opens the correct Settings page or split-page draft state before scrolling to the matching control.
Highlights the matched setting with a subtle token-based background so users can see where they landed without an aggressive outline.
Adds explicit data-settings-item anchors across Settings pages and a centralized search registry with runtime/mobile availability guards.
Updates Settings UI skill guidance so future Settings changes keep search registry entries, anchors, localization, and availability guards in sync.
2026-06-10 12:15:15 +03:00
Bohdan Triapitsyn eff6f46ad9 feat: improve mobile UX (#1591)
Added a mobile MCP overlay so MCP tools can be opened and managed from the mobile UI without relying on desktop-only dropdown behavior.
Improved mobile session panel touch handling so tapping the status/session area opens the right panel reliably on phones and tablets.
Cleaned up mobile usage provider metadata by removing duplicate rows, hiding unset providers, and showing provider logos consistently.
Added eager loading for provider logos used in mobile usage views to avoid delayed or missing icons when the panel opens.
Refined the mobile update and about flows in OpenChamber settings so release/update information is easier to read on small screens.
Adjusted related layout, header, VS Code layout, command palette, and settings text/localization details needed for the mobile polish.
2026-06-10 12:00:10 +03:00
Bohdan Triapitsyn 3541bc63f9 feat(desktop): macOS vibrancy for the left sidebar with a toggle
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).
2026-06-09 18:38:01 +03:00