Commit Graph
100 Commits
Author SHA1 Message Date
Bohdan Triapitsyn 5d554eaddd fix(opencode): never auto-attach to a pre-existing OpenCode instance
A blind probe of the default port 4096 made the desktop hijack a user's
separately-running OpenCode (e.g. the OpenCode desktop app): it attached as
an external server instead of starting its own. That coupled OpenChamber's
lifecycle to the foreign instance and broke initialization against an
unexpected server version/config.

Attaching to an external OpenCode now requires explicit opt-in via env
(OPENCODE_HOST / OPENCODE_PORT / OPENCODE_SKIP_START). Without that, we always
start our own managed instance on a freshly-allocated port.
2026-06-29 00:02:03 +03:00
Bohdan Triapitsyn 9f720c66af fix(github): stop PR-status requests from starving startup connection pool
Watching N worktrees fired N PR-status requests at once (startWatching
called refresh() directly, bypassing the batch limiter). Each request can
take 20s+ under GitHub secondary-rate-limiting, and N of them saturate the
browser's ~6 HTTP/1.1 connections per origin, starving the critical path
(bootstrap session.status, diffs, sending messages) until they finish — the
UI appeared frozen for ~20s on startup.

- Gate all PR-status network calls through a global concurrency semaphore
  (max 2), so free sockets always remain for critical traffic.
- Bound resolveGitHubPrStatus with a 12s timeout so a slow request fails
  fast instead of holding a socket; the client keeps its last-known status.
- Reuse already-fetched repo metadata for the default branch instead of a
  redundant repos.get, reducing serial GitHub calls (less rate-limiting).
2026-06-28 23:41:14 +03:00
Bohdan Triapitsyn f040ea85e6 chore: remove temp design documents 2026-06-28 22:28:22 +03:00
Bohdan Triapitsyn 3faddbeae8 release v1.13.7 2026-06-28 13:43:33 +03:00
Bohdan Triapitsyn bf1d0d3a75 feat(mobile): polish composer model/agent controls and selection overlay
Redesign the mobile composer model and agent buttons as borderless, full-bleed
labels that hug their content, truncate with an ellipsis when space is tight,
and show the provider logo inline before the model name. Tighten the footer
action buttons (sessions / attach / auto-accept) so they sit close together,
with a small left inset on the group. In the mobile model selection overlay,
make the thinking-variant control text-only with a chevron, vertically center
the variant and favorite controls in each row, and place the provider logo
inline with the model name.
2026-06-28 13:33:55 +03:00
Bohdan Triapitsyn 1178c4e4f5 fix(providers): keep add-provider flow selected during sync 2026-06-28 13:05:00 +03:00
Bohdan Triapitsyn c5eab5ac1e Revert "fix(ui): actually shrink typography classes on mobile viewports (#1791)"
This reverts commit 1ac8fbad70.
2026-06-28 12:33:10 +03:00
Bohdan Triapitsyn 8b5acbf415 fix(chat): improve mobile history loading and virtualization fidelity
Give touch surfaces a larger, viewport-relative head start for loading older
history so an in-flight fetch completes before the finger reaches the top.
Raise the mobile virtualizer overscan so fast flings stay populated instead of
leaving blank gaps, and drop the fixed itemSize hint so virtua auto-estimates
row heights from measured sizes instead of a flat constant.
2026-06-28 12:27:38 +03:00
Bohdan Triapitsyn b2598fa45e fix(chat): stop scroll twitch and message skipping with expanded tools
Gate passive auto-follow on active (working/settling) state so idle layout
churn from virtualizer re-measurement no longer re-pins the viewport to the
bottom. Render default-open tool bodies synchronously on mount so the
virtualizer measures the real row height up front instead of growing a frame
later and lurching scroll past several messages.
2026-06-28 12:01:26 +03:00
Bohdan Triapitsyn b72230bc24 fix: restore update command helpers (#1857)
Exported package-manager helpers used by openchamber update
Added regression coverage for the update-available path
2026-06-28 09:58:45 +03:00
Bohdan Triapitsyn 5034ad27a8 release v1.13.6 2026-06-28 02:00:17 +03:00
Bohdan Triapitsyn 4d4b3aa9d9 fix: improve context panel session visibility
Marks active embedded chat sessions as seen only while focused
Shows real session titles for context panel chat tabs
Names review sessions after the implementation session
2026-06-28 01:39:15 +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
Bohdan Triapitsyn 00efd8cc4b fix: mark context panel chat sessions as seen
Clears unread state when a chat session is active in the context panel
Keeps embedded chat presence in sync while the window is focused
2026-06-28 01:05:44 +03:00
Bohdan Triapitsyn 1b67aa3c21 fix: prevent duplicate preview URL auth tokens
Stops embedded browser URLs from accumulating repeated oc_url_token params
Strips preview-only query params before persisting browser navigation
Adds regression coverage for replacing URL auth tokens
2026-06-28 00:10:35 +03:00
Bohdan Triapitsyn 757c704956 fix(chat): rebuild auto-follow on a single instant writer to kill scroll twitch
Replace the RAF easing follow loop + settle burst with an always-on
instant-follow model: while pinned, the content ResizeObserver re-pins to
the bottom synchronously (scrollTop = scrollHeight, before paint) and is
the only writer of scrollTop. A position+TTL auto marker distinguishes our
own programmatic writes from genuine user scrolling, so a scroll event that
lands at the just-written bottom never trips a false release.

This removes the feedback loop where the easing animation, growing content,
and the user's own scroll all fought for scrollTop in the same frame -- the
infinite twitch when scrolling down during streaming, and the jiggle on
send / from the queue. The public hook interface is unchanged; all
consumers keep working untouched.
2026-06-27 23:45:34 +03:00
Bohdan Triapitsyn 7f45963050 release v1.13.5 2026-06-27 09:49:38 +03:00
Bohdan Triapitsyn 5cc37d0c33 fix: restore CLI validation and test baseline (#1857)
Fixed lazy CLI helper imports for tunnel flows
Restored update command version detection
Made web test and dead-code commands run reliably
2026-06-27 09:45:34 +03:00
Bohdan Triapitsyn 38c9ff77c9 fix: restore CLI and quota provider startup (#1857)
Export ngrok tunnel capabilities for CLI startup
Import Clack spinner/progress helpers used in interactive CLI paths
Restore Google quota provider registry exports
2026-06-27 09:19:24 +03:00
Bohdan Triapitsyn 1cec2f3b1a release v1.13.4 2026-06-27 01:50:10 +03:00
Bohdan Triapitsyn 3c3abd2afe fix(chat): keep historical session entry an instant snap to bottom, not a smooth scroll
Entering a historical session sometimes showed a smooth scroll from a mid
position instead of landing instantly at the bottom. The single-writer change
had startFollowLoop stop the settle burst, so a content-measurement
ResizeObserver tick during restore would downgrade the authoritative instant
pin into an easing follow loop that scrolled in from the partially-measured
position.

- startFollowLoop now yields to an active settle burst instead of stopping it.
  The asymmetry is intentional: the settle burst is the authoritative instant
  pin (session restore / goToBottom 'instant') and must not be preempted by the
  easing loop. startSettleBurst still stops the follow loop, so the two never
  write scrollTop in the same frame.
- tickFollow snaps deltas larger than a full viewport (discrete jumps: late
  history measurement, session entry, a big block in one commit) instead of
  easing them; only small streaming-sized deltas ease.
- restoreSnapshot mirrors goToBottom('instant') — instant write + settle burst,
  no startFollowLoop.
2026-06-27 01:34:27 +03:00
Bohdan Triapitsyn 9547e3a0d2 fix(i18n): localize bootstrap messages 2026-06-27 01:03:32 +03:00
Bohdan Triapitsyn b384deb812 fix(chat): stop scroll jiggle and double-scroll on send while pinned
Two scroll owners were writing the chat container's scrollTop concurrently
during pinned content growth and on send, fighting frame-to-frame and
producing the reported flicker/jiggle (after a pause, from the queue, on user
interruptions) plus a visible double scroll on a normal user send.

Enforce a single-writer invariant in useChatAutoFollow:
- The easing follow loop and the instant settle burst now mutually exclude:
  starting one stops the other, so they can never write scrollTop in the same
  frame. The isFollowingProgrammatically flag (which suppresses the overlay
  scrollbar) is owned by whichever loop is active and cleared only when both
  are idle, including the settle burst's natural 280ms end.

Stop the redundant re-pin storm in useChatTimelineController:
- While pinned, route goToBottom('instant') only for a prepend (history loaded
  above), not on every bottom append / streaming part. Normal growth is owned
  by the follow loop (kicked by the content ResizeObserver and chunk handlers).

Remove the double movement on send:
- Add scrollToBottomOnSend: when already following, just (re)kick the follow
  loop for a single smooth movement instead of also firing an instant
  goToBottom that raced the ResizeObserver-driven loop. When released (scrolled
  up), keep the instant jump to the just-sent message.
2026-06-27 00:06:19 +03:00
Bohdan Triapitsyn fc9146eb7b chore: clean up dead exports after merges 2026-06-26 19:35:25 +03:00
Bohdan Triapitsyn 1f549e4525 feat: add automatic review loop (#1840) 2026-06-26 19:29:44 +03:00
Bohdan Triapitsyn 45df19c3b2 Refactor web CLI into focused modules (#1837) 2026-06-26 19:28:44 +03:00
Bohdan Triapitsyn 4a37b9a005 fix: clean up stale file tree paths on startup
Removes missing expanded folders from persisted Files state
Prevents repeated 404 noise for stale file tree paths
Avoids startup SDK race when restoring sessions
2026-06-26 12:47:01 +03:00
Bohdan Triapitsyn b0e476ab7c fix: prevent sidebar worktree reorder snap-back (#1827)
Invalidates cached group ordering when reorder state changes
Keeps dragged worktree groups in the new position immediately after drop
Validated with UI package type-check
2026-06-25 17:11:37 +03:00
Bohdan Triapitsyn f4646a5268 fix: preserve target session thinking variant in review handoffs
Uses the linked target session's last model choice for follow-up review transfers
Prevents reviewer thinking settings from leaking into implementer follow-ups
2026-06-25 12:25:41 +03:00
Bohdan Triapitsyn b173fd3f09 fix: align CLI lifecycle detection with live port checks (#1830)
Prevents status/stop/restart from deleting valid PID registries on transient probe failures
Adds explicit stop recovery for unresponsive PID-file instances
Covers unmanaged, stale PID, host, and desktop edge cases with CLI tests
2026-06-25 12:00:09 +03:00
Bohdan Triapitsyn 4a068fca86 release v1.13.3 2026-06-24 19:15:26 +03:00
Bohdan Triapitsyn c97d5c79c1 fix: prioritize session sidebar status indicators
Prevents pinned icons from crowding live indicators
Shows chevrons on hover when status or pin takes priority
2026-06-24 18:45:26 +03:00
Bohdan Triapitsyn 8c551c40aa fix(chat): stop double scroll write on prepend that resonates into oscillation
When older history is prepended while the viewport is pinned to the bottom, the
timeline controller wrote the re-pin manually (scrollTop += delta). That write
is not flagged as programmatic, so useChatAutoFollow's scroll handler treated it
as movement and issued its own correcting scroll — a redundant up/down move on
every prepend. On most setups it settles after one move, but with different
virtualizer measurement/timing it never converges, producing the reported
infinite up/down scroll glitch.

When pinned, delegate the prepend re-pin to auto-follow's goToBottom('instant'):
a single authoritative write to the bottom that IS marked programmatic, so
auto-follow ignores it instead of fighting it. The released case (user reading
back through history) is unchanged and still preserves the read position.

This also covers the on-open history auto-load (loadEarlierIfPinnedViewport-
Underfilled), which only runs while pinned, so its prepends now go through the
single writer too.
2026-06-24 18:29:55 +03:00
Bohdan Triapitsyn f2d4a7833d fix(chat): keep session switch pinned to bottom without backward jump
Release auto-follow based on position (the user has left the near-bottom zone)
instead of scroll-delta direction. The old `currentTop < previousTop` check
treated the tiny scrollTop clamp the browser applies when the composer grows —
which keeps you at the bottom — as a user scroll-up and released follow, so
content finishing loading then drifted the view backward.

Also always return to the bottom on session switch, dropping the saved-ratio
restore: it had a low success rate and, by landing 'released' partway up,
produced the same visible backward jump as content finished loading.

overflow-anchor is already disabled on the chat scroll container, so no
delta-threshold workaround is needed; this is a net simplification.
2026-06-24 18:04:59 +03:00
Bohdan Triapitsyn 1558364b49 fix(cli): verify process identity when validating server pid files
After an ungraceful shutdown removePidFile never runs, so a stale
run/openchamber-<port>.pid outlives the process. The kernel can recycle that
PID to an unrelated process, and a liveness-only `process.kill(pid, 0)` check
then reports OpenChamber as "already running" and aborts startup — an infinite
crashloop under systemd Restart=always while the port is actually free
(issue #1721).

Verify identity, not just liveness, but only where it belongs:

- Add isOpenchamberProcessRunning(pid) = liveness + command-line identity, and
  use it ONLY at the two sites that validate a PID read from a pid file (the
  "already running" guard and the stale pid-file cleanup sweep). isProcessRunning
  stays liveness-only for PIDs we know are ours (a freshly spawned daemon child,
  processes we are stopping), so those paths cannot get a false negative.
- Identity works on Linux (/proc/<pid>/cmdline) and macOS (ps -o command=); on
  Windows or where the command line can't be read it falls back to liveness, so
  behaviour is unchanged there with no false negatives.
- Match the "openchamber" install-path segment (present for both @openchamber/web
  and a source checkout, foreground and daemon entrypoints alike) so a recycled
  stranger such as npm-cli.js or agentmemory is not mistaken for us.
- Clear the stale pid file once its recorded PID is no longer our process.

Adds unit tests for isOpenchamberCmdline and isOpenchamberProcessRunning,
covering the recycled-PID cases and a live non-OpenChamber process.
2026-06-24 17:23:20 +03:00
Bohdan Triapitsyn 2ff5428c69 feat(opencode): never leave orphaned OpenCode server processes
OpenChamber spawns the OpenCode server as an external child binary (detached
on Unix), so a hard crash, SIGKILL, or Ctrl+C of the host before graceful
teardown could leave it running. Orphaned servers then accumulate and contend
on the shared SQLite DB, causing severe startup slowdowns.

Add a per-process registry plus a startup reaper, mirroring the pattern
OpenCode's own CLI daemon uses for its detached server:

- One file per spawned process at
  ~/.config/openchamber/managed-opencode/<pid>.json. Per-process files avoid
  the read-modify-write clobber race between concurrent runtimes/windows that a
  single shared file would suffer.
- On spawn, record the child (pid, owner pid, port, binary, host runtime).
- On graceful close/restart, delete the record.
- On startup, reap only our own, verified, genuinely-orphaned processes:
  recorded by us AND still a live `opencode serve` on the recorded port AND
  whose spawner is provably gone (reparented to pid 1, or recorded owner dead).
  It never touches a process a live instance is using, the user's standalone
  server, the official desktop app, or the TUI.

Wire it into every runtime that spawns the server:

- web/desktop via the OpenCode lifecycle (register on spawn, unregister on
  close/restart, reap at startup). The restart-for-config-change flow inherits
  this automatically through the same kill/spawn paths.
- VS Code carries a parity implementation (it does not bundle the web package)
  that reads/writes the same registry directory and uses the same algorithm.
- Tag the actual host runtime (desktop/web/ssh-remote/vscode) for observability.

Also tighten teardown so the registry stays accurate and orphans die promptly
instead of only on the next start:

- The web server now also handles SIGHUP and SIGUSR2 (terminal close and the
  nodemon restart used by dev:server:watch / dev:web:hmr).
- Electron now installs SIGINT/SIGTERM/SIGHUP handlers that run the same
  background teardown as a normal quit, covering Ctrl+C on electron:dev.

External OpenCode servers (OPENCODE_SKIP_START) are intentionally excluded: we
never manage or kill processes we did not spawn.
2026-06-24 16:51:17 +03:00
Bohdan Triapitsyn a9dfd32347 fix: avoid stale project binding for new sessions
Keeps implicit new sessions tied to the current directory
Prevents unmatched directories from inheriting the active project
Adds regression coverage for draft project selection
2026-06-24 10:56:54 +03:00
Bohdan Triapitsyn 604bb97258 refactor(files): use runtime fetch query options 2026-06-24 00:43:27 +03:00
Bohdan Triapitsyn 7f8e04d22f fix(session): prefer current directory for implicit drafts 2026-06-24 00:43:19 +03:00
Bohdan Triapitsyn 08b866136e fix(server): normalize encoded directory headers 2026-06-24 00:43:11 +03:00
Bohdan Triapitsyn c3cf914fda fix(runtime): avoid encoding latin1 directory headers 2026-06-24 00:43:03 +03:00
Bohdan Triapitsyn 37aec95f37 chore: bump opencode sdk 2026-06-24 00:42:56 +03:00
Bohdan Triapitsyn 3d3674d4dd fix: restore arrow-up message history navigation
Lets ArrowUp recall previous messages when the cursor is at the start
Keeps autocomplete guards for history navigation
Restores prior chat input behavior
2026-06-23 23:21:09 +03:00
Bohdan Triapitsyn d47a892376 fix: sync before pushing git commits
Makes Commit & Sync fetch and pull before push when needed
Prevents stale git status from showing already up to date
Adds regression coverage for git status cache invalidation
2026-06-23 23:16:06 +03:00
Bohdan Triapitsyn efdfbf3ce2 fix: improve PR review UX guidance
Adds behavioral contract checks for user-facing changes
Discourages raw schema-driven UI defaults in reviews
Applies guidance to automated review workflow prompts
2026-06-23 23:16:06 +03:00
Bohdan Triapitsyn 2fd86db6a7 fix(auth): trim opencode server username 2026-06-23 21:52:51 +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 07e6935a3e docs: add vacation notice to README
Added vacation notice for Jun 18-28
2026-06-18 02:26:40 +03:00
Bohdan Triapitsyn 68cebd09a6 release v1.13.2 2026-06-18 02:20:56 +03:00
Bohdan Triapitsyn e4cfb628fe fix(diff): keep header controls and horizontal scroll within the panel when line wrap is off
In the changes/diff view, an unwrapped diff's intrinsic width leaked up the
flex chain: the flex-1 column holding the scroll area lacked min-width:0, so its
min-content (the widest line) stretched it — and every nested w-full element,
including the .pierre-diff-wrapper (overflow-x-auto) and the file header — grew
to the content width. That pushed the header's action controls past the narrow
viewport and left overflow-x-auto with nothing to scroll.

Add min-w-0 to the diff layout's flex items so the chain stays at viewport
width: long lines now scroll horizontally inside the diff, and the file header
controls stay visible.
2026-06-18 02:17:44 +03:00
Bohdan Triapitsyn 57c5808ef2 fix(files): refresh URL auth token proactively for asset previews
The oc_url_token has a ~50s effective lifetime and was only fetched once at
preview mount, so HTML/image/PDF previews cycled to 'authentication required'
when it expired and nothing forced a re-render with a fresh token.

Add a consumer-gated proactive refresh in runtime-auth: while at least one
url-token consumer is active, a single scheduler mints a fresh token just
before the skew window and swaps it in atomically (the previous token stays
valid until the new one lands — no empty-token window for other consumers).
acquire/release manage the consumer count; subscribe fires only on a real
token replacement.

FilesView consumes this via a shared useAssetAuthRefresh hook (replacing three
near-duplicate effects) and remounts the iframe/img only when the token
actually changes, not on a blind interval.
2026-06-18 01:24:37 +03:00
Bohdan Triapitsyn 08a851f902 fix(markdown): restore paragraph spacing in assistant messages
Wire the unused --markdown-paragraph-spacing token to .markdown-content p so
adjacent paragraphs no longer collapse into a single visual line (Tailwind
preflight had zeroed the default <p> margins).

The renderer wraps each block in a display:contents [data-md-block] element, so
the message-level last-child margin nullifiers target the wrapper, not the
paragraph. Drop the trailing margin on the last paragraph of the last block
directly so messages don't gain extra bottom space. Keep tool-card and
reasoning markdown compact.
2026-06-18 01:00:05 +03:00
Bohdan Triapitsyn 077a766f94 perf: make OpenCode config defaults non-blocking
Removes startup blocking on OpenCode config defaults
Preserves manual and directory-specific model selections
Adds regression coverage for config races
2026-06-17 11:21:43 +03:00
Bohdan Triapitsyn 6b11211968 release v1.13.1 2026-06-17 02:06:57 +03:00
Bohdan Triapitsyn 253094cb1d fix: stabilize history diff loading and comments
Prevents History file loading from getting stuck
Disables inline comments in History diffs
Keeps review comments available in regular diff views
2026-06-17 02:02:01 +03:00
Bohdan Triapitsyn ada6d417a0 chore: updated changelog with unreleased points 2026-06-17 01:45:24 +03:00
Bohdan Triapitsyn 69a303ab00 fix: prevent search indexing of self-hosted instances
Adds noindex headers to all server responses
Adds robots.txt to disallow crawlers
2026-06-17 01:43:13 +03:00
Bohdan Triapitsyn 7fe58c5c45 fix: harden installer version detection
Require Node.js 22 to match project runtime requirements
Handle malformed or failing node version output safely
Improve install success guidance and PATH diagnostics
2026-06-17 01:41:57 +03:00
Bohdan Triapitsyn 71172181ef chore: add unreleased changelog entries for recent fixes and perf improvements 2026-06-17 01:08:54 +03:00
Bohdan Triapitsyn a99eba1ca6 fix(markdown): currency-safe math delimiters
Single-dollar $...$ inline math collided with currency text ($50,
US$ 680, "$50M to $72M"), parsing money as math and corrupting it.
Drop single-dollar inline math; keep $$...$$ display math and add
\(...\) inline and \[...\] display via marked tokenizers (caught at
lex time so they survive backslash escaping and stay code-safe).

Also gate renderMathExpressions on a cheap $-presence check so the
split + regex passes are skipped for the non-math majority of blocks.
2026-06-17 00:33:39 +03:00
Bohdan Triapitsyn b6f7f3a478 docs: update security contact 2026-06-16 23:40:00 +03:00
Bohdan Triapitsyn 0fef61e25f fix: sync context panel iframe themes
Keeps embedded sessions aligned with parent theme
Prevents iframe reloads on theme changes
Supports theme hotkeys from focused iframes
2026-06-16 23:33:29 +03:00
Bohdan Triapitsyn 91e8e94961 fix: route Electron dev auth through Vite proxy
Fixes password-protected Electron dev startup
Avoids exposing desktop tokens to the HMR UI
2026-06-16 23:33:15 +03:00
Bohdan Triapitsyn ab020886e2 fix: sync embedded chat theme with parent panel
Keeps iframe sessions aligned with the parent theme
Prevents system theme detection from forcing dark mode
2026-06-16 19:35:15 +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 fe99c455ae fix: prevent session folder rerender loop #1461
Avoids redundant folder store updates
Prevents startup crashes with many sessions
Keeps invalid folder moves from mutating state
2026-06-16 15:55:35 +03:00
Bohdan Triapitsyn 9f266a351c fix: preload commands and skills when draft session opens so pinned starters appear immediately
Draft starters from commands and skills now resolve on mount without needing to open the add dialog
Uses existing TTL-cached and deduped loaders, so no extra cost if already loaded
2026-06-16 15:33:33 +03:00
Bohdan Triapitsyn 91de51d1a5 fix: deduplicate desktop notifications and tighten notification text extraction
Desktop notifications no longer duplicate when native delivery succeeds
Reasoning chain-of-thought is excluded from notification body text
Untyped message parts are ignored in notification text extraction
2026-06-16 15:21:27 +03:00
Bohdan Triapitsyn 6ea8fb357d chore: add unreleased changelog entries 2026-06-16 14:56:40 +03:00
Bohdan Triapitsyn 69e534c457 refactor: fixed mobile session button for android
Replaces custom pointer-capture touch logic with touch-action: manipulation
Aligns with the pattern used across other mobile surfaces
2026-06-16 14:46:55 +03:00
Bohdan Triapitsyn f3aff42b8a feat: show context usage as circular progress
Replaces static context icons with live circular progress indicators
Applies consistent context progress in desktop, mobile, VS Code, and mini-chat headers
Keeps usage coloring tied to existing status thresholds
2026-06-16 14:34:02 +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 8f1da2f728 fix: stabilize session diagnostics and Windows session loading
Fix duplicated health probe URL in diagnostics
Share session list proxy handling across platforms
Avoid repeated hanging session requests on Windows
2026-06-16 14:05:44 +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 782982cdd3 feat(editor): Shiki syntax highlighting in the file editor (CodeMirror)
Bring the CodeMirror file editor up to the same rich highlighting as the Shiki
file view, so toggling edit <-> view is visually consistent. lezer collapses far
more tokens than TextMate (import/from/const are all "keyword"), so a theme
remap can't reach parity — instead, project real Shiki tokens onto decorations.

- Worker: add highlightTokens — tokenize with an arbitrary registered TextMate
  theme and return per-line styled runs with offsets. The theme object ships to
  the worker once per name; later calls send only the name.
- New shikiHighlight CodeMirror extension: a StateField of mark decorations
  built from worker tokens. Re-tokenizes on a short idle (off the keystroke
  path) and maps decorations through edits so colors persist while typing.
- flexokiTheme: add { syntaxColors: false } to keep only the editor UI theme,
  so the lezer highlighter doesn't compete with the Shiki decorations.
- FilesView: enable Shiki highlighting (same language resolver as the file view
  → identical language) and drop lezer token colors when it's active. lezer
  language stays on for indentation/folding/brackets.
2026-06-16 01:08:54 +03:00
Bohdan Triapitsyn c22e1cbb15 refactor(chat): remove dead syntaxTheme plumbing
The Prism syntaxTheme prop is no longer read after code highlighting moved to
the Shiki worker. Remove the now-dead prop threading and its source.

- Drop syntaxTheme from interfaces, destructures, prop passes, and React.memo
  comparators across ChatMessage, MessageBody, ProgressiveGroup, ToolPart,
  TurnActivity, ToolOutputDialog, and ChatInput.
- Drop the unused _syntaxTheme param from renderWebSearchOutput.
- Remove the dead generateSyntaxTheme usages (ChatMessage memo, PlanView
  unassigned memo) and delete the now-unimported syntaxThemeGenerator module.
2026-06-16 01:07:43 +03:00
Bohdan Triapitsyn e41e5bac91 perf(code): replace react-syntax-highlighter and prismjs with the Shiki worker
Route all non-markdown code highlighting through the off-main-thread Shiki
worker, removing react-syntax-highlighter and prismjs entirely.

- Extend the worker with highlightLines: tokenize a whole block once and return
  per-line inner HTML, so per-line layouts (diffs, gutters, virtualization) make
  one worker call instead of one highlighter per line.
- Add shared WorkerHighlightedCode (whole-block) and useWorkerHighlightedLines
  (per-line) primitives. Colors resolve via the --md-syntax-* CSS variables, so
  theme changes never re-highlight.
- Migrate all 12 react-syntax-highlighter call sites: PermissionCard,
  ToolPart, ContextSidebarTab, ToolOutputDialog (whole block) and
  DiffPreview/WritePreview (per line).
- Migrate VirtualizedCodeBlock off prismjs to the worker, keeping virtua
  virtualization; whole-block tokenization also restores cross-line syntax
  context that per-line highlighting lost.
- Drop react-syntax-highlighter (+types) from ui and web, prismjs (+types) from
  ui, and the orphaned create-element type shim.
2026-06-16 01:07:43 +03:00
Bohdan Triapitsyn 464c4ac0ca perf(markdown): move code highlighting off the main thread into a Shiki worker
Tokenize closed code blocks in a dedicated Shiki Web Worker instead of calling
the shared highlighter synchronously on the UI thread. This removes the
one-shot main-thread highlight stall when a code fence closes on a large block.

Streaming behavior is unchanged: the open (streaming) fence still renders as
plain text and is highlighted once on close. On any worker failure the block
keeps its escaped plain code — highlighting never falls back onto the main
thread.

- Add markdownShikiThemeDefinition (dependency-free CSS-variable theme) so the
  worker can use the theme without pulling in @pierre/diffs / React.
- Add markdown-worker-protocol, markdown-shiki.worker, and the main-thread
  markdown-worker client.
- Route highlightCodeBlocks through the worker; keep the size/VSCode line guard
  and mermaid skip on the main thread.
- Add shiki as a direct dependency (was transitive via @pierre/diffs).
2026-06-16 01:07:43 +03:00
Bohdan Triapitsyn 9ea557d23e feat(markdown): open mermaid fullscreen preview on diagram click in chat
Thread onShowPopup from MessageBody through AssistantTextPart into the
markdown renderer so clicking a rendered mermaid diagram in assistant
messages opens the existing pan/zoom fullscreen preview dialog.
2026-06-15 18:23:59 +03:00
Bohdan Triapitsyn 4d506fba4e perf(markdown): rewrite rendering on marked + Shiki + morphdom
Replace the react-markdown/Prism component tree with an HTML-string
pipeline (marked -> KaTeX -> Shiki -> DOMPurify -> DOM decorators) patched
into the DOM via morphdom, with per-block reconciliation and a paced
streaming reveal. Cuts streaming CPU versus the previous renderer while
preserving file-reference links, mermaid diagrams, table export, and
agent/skill/favicon link handling. Public MarkdownRenderer/
SimpleMarkdownRenderer props are unchanged (drop-in).
2026-06-15 18:17:47 +03:00
Bohdan Triapitsyn 7290c4aa51 ci: pin Windows release runner to 2022 2026-06-15 15:51:37 +03:00
Bohdan Triapitsyn 4927a3164f release v1.13.0 2026-06-15 15:35:11 +03:00
Bohdan Triapitsyn 7a0abfe6c4 fix: keep context panel resize handle above content
Prevents changed file rows from visually overlapping the resize border
Preserves existing spacing in the changes list
2026-06-15 15:29:19 +03:00
Bohdan Triapitsyn ba0a755040 fix: sync pending changes with active directory
Uses the effective directory for composer git status
Prevents stale pending changes after commit or push
Keeps the dropdown aligned with the git panel
2026-06-15 15:26:08 +03:00
Bohdan Triapitsyn 41160ed5b2 fix: refresh pending changes from chat input
Keeps pending changes state fresh even when the bar is hidden
Updates the composer when workspace changes appear or disappear
Removes duplicate refresh handling from the pending changes bar
2026-06-15 15:10:12 +03:00
Bohdan Triapitsyn 313f04e916 fix: keep Windows header menu aligned
Moved the Windows app menu into the fixed titlebar controls
Kept sidebar controls stable when opening and closing the sidebar
Documented longer validation timeouts for workspace checks
2026-06-15 14:52:43 +03:00
Bohdan Triapitsyn a73090f396 Deduplicate desktop notifications 2026-06-15 14:02:16 +03:00
Bohdan Triapitsyn f321562abd chore: bump @opencode-ai/sdk to ^1.17.7 and update changelogs
Upgraded @opencode-ai/sdk dependency from ^1.17.0 to ^1.17.7 across all packages
Added unreleased changelog entries for VSCode startup parity, mobile tool card fix, and files workspace directory fix
Refined VSCode changelog to remove inaccurate project-level actions note
2026-06-15 13:24:39 +03:00
Bohdan Triapitsyn c73ab9cbd5 feat(vscode): startup parity + workspace-grouped session list (#1658)
* perf(vscode): gate API readiness and coalesce duplicate startup reads

Bring the VS Code bridge runtime to parity with the web startup
optimizations (PR #1650), which were web/desktop-only.

waitForApiUrl now hands out the OpenCode API URL only once the manager
reports 'connected', instead of as soon as getApiUrl() exposes
server.url. The URL is available the moment the process is spawned —
before waitForReady confirms it can serve and during a workspace-switch
restart (stale port) — so URL-presence alone let the bridge forward to a
not-yet-ready OpenCode and surface 502s. Gating on connected status
mirrors the web proxy's isOpenCodeReady hold. Also fail fast on 'error'
status so a missing CLI doesn't burn the full 30s timeout.

Coalesce concurrent identical GET reads (config/path/agents/agent/
project/command) at the bridge proxy so the single OpenCode process
serves them once. On cold start the webview's sync bootstrap and config
store fire these reads in parallel with no shared dedup; this is the
extension-host analog of the runtimeFetch coalescer. Shared reads carry
no AbortController so one caller's abort can't strand the others, and the
entry clears as soon as it settles (never serves stale).

* perf(vscode): fade the startup splash once mounted + connected, not on live fetch

The webview's initial-loading overlay held until a successful live
/api/config/providers AND /api/agent fetch completed. After the cache
hydration work those live reads are the slowest cold-start tail — the UI
underneath already paints pickers and the sidebar from cache and refreshes
in the background — so gating the splash on them kept it spinning long
after the app was usable.

Fade the overlay as soon as the UI is mounted and OpenCode is connected.
Per-widget loaders convey any remaining background refresh, matching how
web/desktop (which have no such splash) already behave. Removes the now
-obsolete bootstrapProvidersReady/AgentsReady/Failed tracking and
recordBootstrapFetch. Connection error/disconnected splash messages are
unchanged.

* fix(vscode): include captured OpenCode output in spawn-timeout error

When the managed OpenCode server fails to emit its 'listening' line within
the start timeout, the error discarded everything the process printed to
stdout/stderr — so the status report showed a bare 'Timeout waiting for
server to start' with no clue whether the process hung, crashed silently,
or printed a config/auth error. The exit path already includes the output;
the timeout path now does too (or notes that nothing was printed).

* feat(vscode): workspace-grouped session list with working folders, pinning, and archived toggle

Replace the flat multi-workspace session list with the grouped project view,
using each open VS Code workspace folder as a header (no per-worktree
subgroups). This restores native folder and pin support, which the flat list
silently dropped, and fixes the clipped left padding on session rows.

- Group sessions strictly by open workspace; funnel all non-archived sessions
  into the workspace's group so they no longer fall into the archived bucket.
- Keep the project/group/folder + buttons but make them open a draft in the
  correct workspace and navigate to chat; hide the project actions (...) menu,
  which isn't relevant in VS Code.
- Force the minimal single-line row layout (the second metadata row is
  redundant under workspace headers) and drop the per-row tooltip.
- Add a show/hide archived toggle next to the archive-all control, since the
  VS Code header has no display-mode menu.
- Size the hover action reveal so the timestamp clears the row buttons.
2026-06-15 13:02:26 +03:00
Bohdan Triapitsyn 8919d33636 fix: prevent descender clipping and show tool icon when collapsed on mobile
- Bump mobile line-height on tool/reasoning rows from leading-4 to leading-5
  so descenders (g, y, p) are no longer clipped by truncate overflow
- Show the tool icon (not the chevron) for collapsed expandable tools on
  mobile, matching reasoning rows; chevron now appears only when expanded
2026-06-15 12:54:47 +03:00
Bohdan Triapitsyn e276dc8a5d fix: resolve runtime URLs from injected desktop API base
Uses the injected desktop API base URL at call time
Fixes packaged desktop WebSocket URL generation
Adds regression coverage for injected runtime URLs
2026-06-15 11:45:31 +03:00
Bohdan Triapitsyn 5773297ecf chore: updated unreleased changelog 2026-06-15 10:45:18 +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 e372c8d8cb perf: instant startup via cache hydration + decoupled readiness (#1650)
* perf(startup): hydrate providers/agents from cache (stale-while-revalidate)

Persist last-known provider/agent snapshots instead of stripping them, so the
model/agent pickers paint instantly on cold start. Freshness is preserved by the
background refresh in initializeApp() and activateDirectory() (which overwrite on
success) and by the existing provider/agent config-change subscriptions, so the
prior stale-provider regression stays fixed without blanking the UI during fetch.

* perf(startup): cache directory session list for instant sidebar

Persist a capped slice of each directory's session list and seed the child store
from it on creation, so the sidebar paints chats immediately on cold start.
Bootstrap phase-3 loadSessions overwrites with the fresh list; its empty-list
race guard preserves the seeded sessions during OpenCode warmup.

* perf(startup): hold API requests through OpenCode warmup instead of 503

The readiness gate returned 503 the instant OpenCode wasn't ready, pushing the
client into an exponential-backoff retry loop (500ms -> 1s -> ...) that wasted
seconds of cold-start time and could fail bootstrap outright. Now hold the
request and poll readiness up to a bounded window so the first call succeeds as
soon as OpenCode is up (typically sub-second); still 503 fast past the window so
a genuinely-down server doesn't hang. Adds coverage for both paths.

* perf(startup): surface cached providers/agents in pickers (optimistic readiness)

The model/agent pickers gated purely on isInitialized, so they showed
"Loading…" for the entire init round-trip even when provider/agent data was
already hydrated from cache — making the persisted-cache work invisible. Treat
the pickers as ready as soon as cached providers are present (stale-while-
revalidate), so they paint last-known models/agents instantly and refresh in the
background. First-ever launch (no cache) still shows Loading until init.

* perf(startup): don't abort directory bootstrap on transient phase-1 failure

A failed initial path.get OR session.status aborted the whole directory
bootstrap, stranding it in loading and skipping phase 2/3 (session load).
session.status is live data the event pipeline keeps current, and path.get is
tolerable once a project is resolved from global state. Now only a total
failure (or path.get failing with no resolved project) aborts, so the sidebar
and chat keep advancing and loading sessions through warmup hiccups.

* perf(startup): don't bootstrap directories from archived sidebar rows

Each sidebar session row called useDirectoryStore(dir), which defaulted to
bootstrap:true and triggered a full directory bootstrap. Archived sessions point
at dozens of (often deleted) worktrees, so on startup this fired a session-list
fetch + 6x2s empty-retry storm per dead directory (the logs the user saw). The
store ref there is only read on-demand via getState() in export handlers, never
subscribed, so archived rows don't need it bootstrapped. Add a { bootstrap }
option to useDirectoryStore and skip bootstrap for archived rows; active rows
still bootstrap so live cross-directory session/status keeps aggregating.

* perf(startup): stop empty-session bootstrap retry storm on web/desktop

The post-bootstrap retry re-ran the full directory bootstrap 6x2s whenever the
session list came back empty, on the theory that empty meant OpenCode wasn't
ready. But loadSessions already retries transient failures twice over
(listGlobalSessionPages throws on 5xx and retries internally), so on web/desktop
an empty result is authoritative — the directory genuinely has no sessions (e.g.
deleted worktrees referenced only by archived sessions). That produced the
dozens of '[bootstrap] sessions empty ... 6 attempts; giving up' log storms.
Gate the retry to VS Code, where the bridge can return an empty 200 during
warmup that the inner retries can't catch.

* perf(startup): scope provider/agent config to project (worktrees inherit)

Providers/agents/defaults are project-level, but were keyed per directory, so a
worktree fetched and cached its own snapshot — duplicating the parent project's
load (the trace showed initializeApp loading the worktree and activateDirectory
loading the project concurrently, ~8s of redundant background work).

- resolveConfigDirectory() maps a worktree to its owning project; loadProviders
  /loadAgents/activateDirectory now key by it, so a worktree reuses one shared
  project snapshot. activateDirectory resolves up-front so activeDirectoryKey and
  the snapshot key always match (picker stays consistent); the OpenCode working
  directory is unaffected.
- Add a 30s runtime freshness guard so the stale-while-revalidate background
  refresh skips re-fetching config that was just loaded (initializeApp then
  activateDirectory for the same project), and to avoid churn on rapid project
  switches. Config-change invalidation clears the snapshot, which bypasses the
  guard, so freshness never masks a needed refresh.

* fix(sidebar): default archived sessions to hidden to avoid startup flash

useSessionDisplayStore defaulted showArchivedSessions to true, so on startup
archived sessions rendered by default and then vanished once the persisted
preference rehydrated to hidden — a visible flash. Default to hidden so the
pre-hydration state is the quiet one; users who opted into showing archived keep
their persisted true (default change doesn't override persisted state).

* perf(startup): persist worktree->project mapping to kill cold double-load

The worktree->project map (availableWorktreesByProject) is populated by async git
discovery, so it isn't ready when initializeApp runs — a worktree's first config
load couldn't resolve to its project and duplicated the project's provider/agent
load, saturating OpenCode during cold start (the source of the slow first
createSession/send the user observed). Cache resolved worktree->project mappings
to localStorage so resolveConfigDirectory resolves synchronously at init on
subsequent launches; the project is loaded once and activateDirectory hits the
freshness guard. worktree->project is immutable so a cached entry is safe; live
resolution still populates/corrects the cache.

* perf(startup): persist worktree map for instant sidebar + first-launch keying

Worktree discovery is async (git), so availableWorktreesByProject was empty at
startup: the sidebar worktree list appeared late, and useConfigStore couldn't
resolve a worktree to its project on the first launch (causing the cold
worktree+project double-load). Persist the discovered worktree map to
localStorage and seed it synchronously on store init (stale-while-revalidate:
discovery refreshes in the background via the existing setState, which now
write-through persists). The sidebar paints worktrees instantly and
resolveConfigDirectory resolves the project from the very first launch.

* perf(startup): coalesce concurrent duplicate OpenCode reads in runtimeFetch

On cold start the sync bootstrap and the config store independently fire the same
idempotent reads (providers, config, path, agents, project) concurrently with no
shared dedup, saturating the single OpenCode process and delaying work queued
behind it (e.g. createSession). Coalesce genuinely-concurrent identical GETs to
those read endpoints at the transport layer so OpenCode does the work once; each
caller receives an independent response clone. Tightly scoped: GET only,
allowlisted read paths, never event streams, never a signal-bearing request (so
one caller's abort can't cancel the shared fetch). Entries clear on settle, so it
only shares overlapping in-flight requests — never a stale response.

* perf(startup): cache git branches so the draft branch selector paints instantly

The branch selector above the composer was the slowest-loading element: it's
gated behind a cold 'git branch' fetch (useGitStore, not persisted). Cache the
per-directory branch list to localStorage and seed the store on init (with
isGitRepo:true so the selector's gate passes), and write the cache on every
successful fetchBranches. The ChatInput draft-branch effect now refreshes on
staleness (>30s) rather than mere absence, so seeded branches show immediately
and still refresh in the background without a spinner — no stale-forever
regression. Only the branch list is cached; status/log/diff are untouched.
2026-06-15 03:16:34 +03:00
Bohdan Triapitsyn 928b7ff1d6 revert: restore stable chat history scrolling 2026-06-14 23:06:03 +03:00
Bohdan Triapitsyn 296177357b perf: speed up model/agent readiness on the initial draft
Load startup config under the owning project's directory key (resolving from a
worktree directory when needed) so the auto-opened draft, which activates the
project, finds a ready providers/agents snapshot instead of triggering a second
load. Also dedupe app.agents: listAgents now takes the directory directly and
shares an in-flight request, so the config store and agents store no longer
issue duplicate agent fetches at startup.
2026-06-14 22:04:32 +03:00
Bohdan Triapitsyn 9111611bdc fix: start draft sessions from default model/agent and honor OpenCode default_agent
A new draft session inherited the previous session's model/agent instead of
resetting to defaults, because opening a draft restored the directory snapshot
without re-applying the startup default cascade. When the prior session ran in
a worktree, defaults were resolved against the worktree directory's provider
list, which omits project/global-scoped providers, so the default agent's model
fell back to opencode/big-pickle.

Resolve the default agent/model via a shared cascade (settings default ->
OpenCode default_agent -> build -> first), resolve the model from the agent's
pinned model/variant or OpenCode's config model, and activate the project's
config (not the worktree's) when opening a draft.
2026-06-14 21:36:05 +03:00
Bohdan Triapitsyn 9f06224151 fix: authenticate event-stream WebSocket before connecting
The global event-stream WebSocket opened before a valid oc_url_token was
minted, so the upgrade failed auth ("no valid credentials available") in
packaged builds with a UI password. The resulting reconnect storm churned
the sync store and made session status flicker busy<->idle. Await the URL
auth token before connecting (a WS upgrade can't send a bearer header like
SSE does) and drop a rejected token on pre-ready close so the next attempt
re-mints a fresh one.

Also harden /session/status reconciliation: the watchdog poll is now
monotonic (only confirms/raises active status, never blindly lowers a
busy/retry session to idle on a transient or misscoped snapshot). Idle is
applied only by the authoritative reconnect/escalation resync, which trusts
the live server snapshot as the source of truth. Add a Help -> Toggle
Developer Tools menu item so production builds can open the console.
2026-06-14 19:48:07 +03:00
Bohdan Triapitsyn 94ca3fda04 Add diff review flow dialog 2026-06-14 16:55:58 +03:00