Commit Graph
1961 Commits
Author SHA1 Message Date
Leonidandbashrusakh fbcf4ea2b9 fix(sidebar): prevent home-project archived session overlap crash (#2017)
* fix(sidebar): scope archived sessions to deepest project

* fix(sidebar): prefer session directory over worktree

---------

Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
2026-07-11 16:03:37 +03:00
Leonidandbashrusakh e5bba59a75 fix(worktree): restore last source branch reliably (#2030)
* fix(worktree): restore last source branch reliably

* chore: retrigger review

---------

Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
2026-07-11 16:01:30 +03:00
Leonidandbashrusakh 0d4118e87a fix(auth): clarify LAN auth and mobile guidance (#2035)
* fix(auth): clarify LAN auth and mobile guidance

* chore: retrigger PR checks

---------

Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
2026-07-11 15:23:34 +03:00
d5745aaac9 fix(sync): keep session renames stable (#2043)
* fix(sync): keep session renames stable

* fix(sync): clarify rename mirror flow

* fix(sync): clarify archive comment

---------

Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-07-11 15:19:24 +03:00
Leonidandbashrusakh 9bfc5bf0be fix(chat): enable draft auto-accept before first message (#2045)
* fix(chat): enable draft auto-accept before first message

* fix(test): use supported bun assertions

* fix(chat): apply draft auto-accept before session switch

---------

Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
2026-07-11 15:15:02 +03:00
Leonidandbashrusakh 0242765bc8 fix(session): keep pinned sessions on refresh (#2057)
* fix(session): keep pinned sessions on refresh

* fix(session): type sidebar persistence test

* fix(session): remove invalid sidebar persistence harness

---------

Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
2026-07-11 14:50:39 +03:00
Leonidandbashrusakh 17af1f3369 fix(vscode): allow Shiki module worker by adding worker-src to CSP (#2047) (#2058)
Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
2026-07-11 14:50:00 +03:00
Leonidandbashrusakh 4e591503ce fix(number-input): stepper drift on rapid clicks (closes #2053) (#2059)
* fix(number-input): stepper drift on rapid clicks (closes #2053)

The shared NumberInput stepper buttons (-/+) computed the next value from
`baseValue`, a useMemo of the controlled `value` prop. When the user
pressed - and + in rapid succession, both inline closures read the same
pre-update `baseValue` because the prop round-trip (click ->
onValueChange -> store/persistence update -> re-render) had not landed.
Net result: rapid alternation drifted or oscillated instead of returning
to the start value.

Route the stepper math through a new `committedValueRef` updated
synchronously inside `commitValue`, and re-sync the ref from `baseValue`
via a useEffect so external mutations (the reset button next to each
stepper, undo, multi-instance sync) keep the ref aligned. Keep
`baseValue` for the `disabled` predicate so the prop still gates the
buttons at the bounds.

For the same invariant, route `handleBlur`'s finite-parse branch
through `commitValue` so a typed value followed by a stepper click does
not compute from a stale ref. The empty-draft `onClear` early-return
relies on the baseValue useEffect to re-sync.

Cover the path with a new bun:test suite that drives the real onClick
closures through createRoot with a minimal document/window stub (no new
deps). Tests assert rapid --+ and +-- sequences net to the start value,
a sustained 6-click alternation does not drift, sequential clicks with a
re-render between them settle correctly, and a typed-then-stepper
sequence uses the typed base.

* test(number-input): restore DOM globals and guard empty recorded arrays

Follow-up to the stepper-drift fix on the same PR.

- installDomStub now captures the previous values of
  document/window/navigator/IS_REACT_ACT_ENVIRONMENT before overwriting
  and exposes a restore() function. withHandle calls stub.restore() in
  finally after unmount(), so the test process no longer leaks a fake
  DOM across tests.
- Replace the four 'recorded[length-1]!' non-null assertions with a
  lastCommit(handle) helper that throws a clear error if the parent
  never produced a commit. A regression that drops the first commit
  fails loudly instead of silently coercing to undefined.
- Add a short comment on the useEffect re-sync documenting the
  controlled-parent assumption (ref can briefly lead the prop if a
  parent ever rejects or debounces onValueChange; no production caller
  does today).

---------

Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
2026-07-11 14:49:19 +03:00
Leonidandbashrusakh 9c61c568aa feat(command-palette): add projects to existing fuzzy search (#2063)
* feat(command-palette): add projects to existing fuzzy search

Adds projects to the existing command palette search — same single-input
fuzzy search that already covers sessions, files, settings, and commands.
Projects are scored alongside everything else by scoreByFuzzyQuery, and
the best-matching result appears first regardless of type.

Selecting a project opens a new session draft with the project pre-selected.

Closes #976

* fix(command-palette): keep file search tied to debounced query

---------

Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
2026-07-11 14:46:41 +03:00
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
Leonidandbashrusakh bfaf62e222 fix(sidebar): add project sort modes (#2067)
* fix(sidebar): add project sort modes

* fix(sidebar): preserve manual sort order

* fix(sidebar): move sort state before render

---------

Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
2026-07-11 14:43:57 +03:00
Leonidandbashrusakh f6326e1c35 fix(chat): harden tool output rendering against non-string fields (#2011) (#2071)
React error #31 (Objects are not valid as a React child) was thrown
intermittently when a task/subagent tool returned structured data
(e.g. { TODO: '...' }) in a field that the OpenCode SDK types as a
plain string. Pathological payloads would propagate into JSX children
without runtime validation, white-screening the chat until refresh.

This change adds a single `coerceToText` helper in toolRenderers.tsx
and applies it at every vulnerable JSX expression:

- ToolPart.tsx:1807,1975  {state.error}     (typed string, can be object)
- ToolPart.tsx:1825       {q.question}      (QuestionCard input cast)
- ToolPart.tsx:1830       {opt.label}       (QuestionCard input cast)
- ToolPart.tsx:1848       task tool markdown output
- ToolPart.tsx:1898       ToolScrollableTextOutput entry
- toolRenderers.tsx       {todo.content}    x4 in renderTodoOutput

renderTodoOutput now also validates the parsed array at the boundary
(JSON.parse result is filtered to objects whose content and status are
runtime strings), so a single bad row no longer poisons the whole
tool output.

Tests: 12 new unit tests in
packages/ui/src/components/chat/message/parts/__tests__/issue-2011-react-error-31.test.ts
covering the {TODO}-key object path, circular references, and
non-string content/status on parsed todos.

Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
2026-07-11 14:42:40 +03:00
d8a904954b fix(sync): commit first message page before expansion loop (#2084) (#2086)
* fix(sync): commit first message page before expansion loop (#2084)

loadMessages committed the store only after the full expansion sequence
(50→100→150), so the hydrating skeleton stayed for 3 sequential HTTP
round-trips when a session tail had no user message boundary.

Move the store write (materialize + setState) to happen after the first
fetch. The expansion loop now commits each expanded page incrementally
instead of overwriting a page variable and committing once at the end.

The first commit is gated on hasUserMessage(page.session) || page.complete:
if the tail is assistant-only, deferring to the expansion loop keeps the
skeleton (loading state) instead of rendering an empty chat that looks
like a fresh session. Sessions with a user boundary in the first 50
messages get content after a single round-trip.

* fix(sync): address review nits for #2084

- deferred init uses page.session instead of [] so limit reflects the
  real fetched count if the expansion loop is ever a no-op
- both stale branches in commitMessagesToStore return messages: [] for
  consistency
- add isStale guard between expansion fetch and commit for defense-in-depth

* fix(sync): always commit prepend-mode pages to store (#2084)

The deferred init path (assistant-only tail) skipped commitMessagesToStore
entirely when options.before was set — prepend mode. The fetched older
messages were never written to the store, silently dropping them.

Gate the deferral on !options.before: prepend mode always commits because
messages are already rendered (no skeleton to protect) and skipping the
store write would lose the fetched page.

---------

Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-07-11 14:39:49 +03:00
Leonidandbashrusakh 37768958f3 refactor(chat): simplify baseDisplayMessages dedup — remove unnecessary reverse() (#2089)
* fix(chat): preserve chronological message order during history pagination

The baseDisplayMessages dedup loop iterated from tail to head (newest
to oldest), keeping the newer occurrence of each message ID. During
history pagination (prepend mode), the server returns older messages
that may overlap with the current view at the boundary. The tail-first
iteration discarded the older (prepended) duplicate in favor of the
newer (existing) one, breaking chronological ordering.

Change the loop to iterate head to tail (oldest to newest) so the
first occurrence of each time-sortable message ID is preserved. Remove
the now-unnecessary .reverse() call.

Fixes #2088

* test(chat): add dedup logic coverage for baseDisplayMessages

Covers message ID deduplication in baseDisplayMessages useMemo:
- First-occurrence preservation during dedup
- Input order maintenance
- Empty input, single-element, all-same-ID edge cases
- History pagination prepend scenario with overlapping IDs

---------

Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
2026-07-11 14:38:39 +03:00
Leonidandbashrusakh d01126cf3c fix(ui): pass sourceRepo to PR/issue context calls for fork workflows (#2090) (#2091)
Fixes GitHub PR/issue context endpoints returning 404 when working
from a fork because they resolved the repo from origin remote only.

- Pass sourceRepo: status?.repo ?? null to all prContext() calls in
  PullRequestSection.tsx (5 call sites)
- Pass sourceRepo: args.pr.sourceRepo ?? null / args.issue.sourceRepo
  ?? null to NewWorktreeDialog.tsx (3 call sites: prContext, issueGet,
  issueComments)
- Add status?.repo to dependency arrays to prevent stale closures

The prStatus endpoint already resolves the correct repo through the
fork network; this change wires it through to the downstream API calls.

Closes #2090

Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
2026-07-11 14:37:33 +03:00
Leonidandbashrusakh cd1ffa8b66 fix(sidebar): keep file tree expanded while refreshing root (#2092)
Replace the destructive refreshRoot() with an incremental refresh that

re-fetches the root and every expanded directory while preserving

childrenByDir. This keeps the file tree expanded instead of collapsing

it to the root on every manual refresh.

Closes #2036

Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
2026-07-11 14:34:03 +03:00
Tom RochetteandBohdan Triapitsyn 743d1c90c0 docs(agents): add step-by-step workflows with posting and label procedures (#1993)
* docs(agents): add step-by-step workflows with posting and label procedures

Add numbered step-by-step workflows to all four automation agents
(pr-review, reproduce-issue, summarize, triage), each with an explicit
comment-posting sub-procedure: draft once, post via gh, capture result,
verify by reading comments back only, and retry once on failure.

pr-review also gains a Labels section that applies confidence:* and
risk:* labels matching the review scores, removing stale labels first
to avoid stacking. merge-conflict:true is left to its dedicated action.

triage renames its label-selection steps to Category 1-5 to avoid
colliding with the new workflow step numbering.

* fix(agents): avoid duplicate comments after ambiguous posts

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-07-11 11:22:00 +03:00
Bohdan Triapitsyn 0a89f05d07 feat: mark embedded session chat as desktop surface
Adds a surface=desktop URL param to embedded session chat links
Uses the surface override to classify embedded chat as desktop
Covers the new URL parameter in tests
2026-07-11 10:48:20 +03:00
Bohdan Triapitsyn 5a91c08413 fix: treat embedded session chat as desktop surface
Prevents narrow embedded session chat panels from being misdetected as mobile
Uses the ocPanel=session-chat query parameter to पहचान desktop-like layout
Keeps device detection aligned with other desktop shell runtimes
2026-07-11 09:40:35 +03:00
Bohdan Triapitsyn 33cbd0b836 fix(mobile): pad Android app below the status bar on Android 15
Android 15 enforces edge-to-edge and ignores the StatusBar overlay:false
inset the app relied on, while every --oc-safe-area-* CSS definition was
gated behind iOS-only conditions. Read the Capacitor-injected
--safe-area-inset-* vars (with env() fallback) on the Android native
shell so the header, top toasts, and connect screen clear the status
bar; both sources report 0 where the native inset still applies.
2026-07-10 20:25:48 +03:00
Bohdan Triapitsyn e9102df5f7 release v1.15.0 2026-07-10 19:41:49 +03:00
Bohdan Triapitsyn 3c0fb49c46 chore: add changelog entry for workspace-relative filenames in Editor Integration 2026-07-10 19:15:41 +03:00
Bohdan Triapitsyn 6ec1797583 feat(cli): make connect-url --relay a full anywhere pairing link
- --relay links now carry both routes: direct LAN plus relay fallback,
  matching the UI's Anywhere pairing; devices prefer the direct route
- pairing sessions created by the CLI are marked with usesRelay, and the
  server reconciles relay demand on a timer, so a headless instance
  brings the relay up on its own after connect-url --relay
- warn with LAN_UNREACHABLE when the link's direct route points at
  loopback and other devices cannot use it
- document the --relay flow and the --lan binding caveat in Connect a
  Device and Remote Instances across all locales
2026-07-10 18:29:15 +03:00
Ibrahim Khan de92b8fef4 fix(vscode): keep relative path in selection attachment filename (#1923)
The "Add to Context" command and the active-editor pin-selection suggestion both create selection attachments but used the basename only (e.g. assist.ts:47). OpenCode synthesizes its Read call from that filename, so the directory was lost and the model could read or edit the wrong file when names collide.

Use the workspace-relative path (asRelativePath(uri, false)) in both paths so the filename carries the directory and the two paths produce identical filenames, restoring attachment dedup.

Fixes #1914
2026-07-10 16:39:55 +03:00
Bohdan Triapitsyn 7ea974d89b docs: centralize device connection guides, add private relay docs
- new Connect a Device page: one-time QR pairing, transport choices, device management
- new Private Relay page: E2EE guarantees, demand-driven lifecycle, relay vs tunnel
- rewrite mobile page around the native iOS/Android apps (TestFlight + APK)
- update remote-instances, security, tunnels, and remote-access troubleshooting to point at the new pairing flow
- translate everything across all 8 locales and update the sidebar
2026-07-10 15:30:50 +03:00
Bohdan Triapitsyn b3fa19fe3e feat: add navigable JSON summaries for tool output
Tool JSON output now starts with a compact navigable summary view.
Expandable tool output includes quick open-file and diff actions for changed files.
Reasoning headers strip stray HTML comments, and navigation tools stay compact.
2026-07-10 14:51:33 +03:00
Bohdan Triapitsyn 6c2e657511 chore: draft unreleased changelog, bump opencode sdk to 1.17.18
Changelog leads with the private relay and the native mobile apps (TestFlight
beta + Android APK links), followed by pairing v2 and the device management,
desktop multi-transport, and chat items; VS Code changelog gets the shared
chat-render entries.
2026-07-10 13:42:52 +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 cc4de243c6 fix(chat): sticky user headers float mid-list in the virtualized timeline
The tanstack rows sit in a wrapper offset with transform: translateY(), and a
transformed ancestor becomes the sticky containing block — turn headers stuck
to the wrapper's overscan-dependent top edge instead of the scroll container,
floating over the previous turn. Offset the wrapper with padding-top instead:
identical geometry, sticky computes against the scroll container again, and
the padding only changes when the virtual window shifts, not per scroll frame.
2026-07-10 02:28:31 +03:00
Bohdan Triapitsyn 848f552767 fix(ios): actually hide the iOS 26 scroll edge effect
The compile fix replaced the direct topEdgeEffect API with KVC casting the
effect to UIView — but UIScrollEdgeEffect is not a UIView, so the cast
silently returned nil and the system's dark edge band stayed visible behind
the status bar. Keep the KVC (compiles with pre-26 SDKs) but toggle the
ObjC 'hidden' key on the effect as a plain NSObject, guarded by respondsTo.
2026-07-10 02:20:41 +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
Bohdan Triapitsyn e74834a739 feat(mobile): redesign connect screen and instances sheet
- Connect screen leads with Scan QR code plus a plain-words hint of where the
  code lives; manual URL entry is collapsed behind Connect by address (expanded
  automatically on web where scanning is unavailable); saved connections show a
  per-row connecting spinner
- Instances sheet is list-first: the active instance shows a live status dot
  and transport (Connected - Local network / Private relay), rows connect on
  tap with an inline spinner, and the add/edit form hides behind Scan QR code /
  Add by address
- Deleting the last instance returns to the connect screen: without a runtime
  endpoint the native app no longer bootstraps against the webview's own origin
  (which faked a successful connection), and the connect screen renders
  regardless of a stale isConnected flag
2026-07-10 01:44:42 +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
FanFan4204 57ebaedada fix(server): allow x-opencode-directory-encoding header in CORS (#1825)
PR #1673 added sanitizeHeadersForBrowser in the fetch bridge to handle
non-ISO-8859-1 directory paths by encoding the value and attaching a
x-opencode-directory-encoding: uri header. The server-side decoder was
already in place (08b86613). However, the CORS Access-Control-Allow-Headers
list on the Express server was not updated to include this new header.

When a user opens a directory with CJK or other non-Latin-1 characters
(e.g. D:\文件), the browser sends a CORS preflight OPTIONS request with
x-opencode-directory-encoding in the Access-Control-Request-Headers.
The preflight fails because the server does not list it as allowed,
blocking all subsequent API requests with 'Failed to fetch'.

Add X-OpenCode-Directory-Encoding to the Access-Control-Allow-Headers
response header for openchamber-ui://app packaged client origin.
2026-07-09 10:29:34 +03:00
Bohdan Triapitsyn ac93a52e21 feat: iPad split layout for the Capacitor app (#2104)
* fix: open mobile model/agent panels on tablet-width Capacitor shells and keep composer taps from dismissing the keyboard

* feat: add iPadOS-style split layout to the Capacitor app

- classify the Capacitor shell as mobile in device detection so shared
  surfaces (draft starters, panels) stop falling into tablet branches
- add isIPadApp() and useOrientation() helpers
- iPad: persistent full-height sessions sidebar (mobile sessions surface
  inline), Changes/Files in a right sidebar with header shortcut toggles
- animate sidebar open/close like the desktop sidebars and add
  finger-sized drag-resize with persisted widths
- anchor the overflow menu and the usage/metadata popover next to their
  header buttons regardless of open sidebars

* fix: re-anchor metadata popover on layout shifts and untangle sidebar toggle updates

- recompute the iPad metadata popover anchor via a ResizeObserver on its
  wrapper so sidebar toggles/resizes while it is open cannot leave it
  misplaced
- move the portrait right-panel close out of the setIpadSidebarOpen
  updater into plain sequential state updates
2026-07-08 21:52:18 +03:00
CarsonandBohdan Triapitsyn 3d32ac7989 feat(chat): add Mermaid diagram zoom controls (#2100)
* feat(chat): add mermaid diagram zoom controls

* fix(chat): handle malformed mermaid data urls

* fix(chat): preserve mermaid load error stack

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-07-08 20:43:23 +03:00
Bohdan Triapitsyn dfed121bf1 fix: defer markdown code line number sync during streaming
Avoids syncing code line numbers while markdown is still streaming
Keeps code block wrapping and line numbers stable after render
Updates code block layout to support deferred gutter insertion
2026-07-08 20:06:33 +03:00
Bohdan Triapitsyn 6d0977fc4e fix: accept looser unified diff patches
Normalizes bare ---/+++ headers and paths before rendering
Repairs loosely formatted hunk bodies for patch display
Recounts hunk ranges so diff headers stay accurate
2026-07-08 15:10:40 +03:00
Bohdan Triapitsyn f49f3a0d88 feat: add synced code block line numbers
Adds line-number gutters for markdown code blocks
Keeps gutter heights in sync when wrapping or resizing changes
Applies wrap styles directly to pre and code for better overflow handling
2026-07-08 14:59:11 +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 42e470cefa fix: keep browser tab session navigation stable
Keep the browser pane loaded URL separate from the in-frame current URL so SPA navigation updates the address bar and history without remounting the iframe or resetting the Electron webview src.

Preserve parsed ?session= route params during initial URL normalization and pass a directory hint when applying deep links, preventing embedded OpenChamber sessions from collapsing back to / while bootstrap catches up.
2026-07-07 20:58:54 +03:00
Bohdan Triapitsyn 44f44da212 ci: add friendly android release asset names 2026-07-07 20:38:05 +03:00
Bohdan Triapitsyn d447952344 ci: allow manual mobile release tag uploads 2026-07-07 20:26:20 +03:00
Bohdan Triapitsyn 7a508f1b14 ci: add mobile platform release toggles 2026-07-07 20:20:47 +03:00
Bohdan Triapitsyn 2f4eb1d112 fix: simplify context raw message rows 2026-07-07 20:05:54 +03:00