Commit Graph
52 Commits
Author SHA1 Message Date
Serhii Dziupin 577aca0437 feat: add restore/unarchive for archived sessions
Archived sessions had no way back to the active list: the only available
action was "Delete permanently". Add restore per session (sidebar context
menu, Archive page row) and in bulk (sidebar selection bar).

The OpenCode server cannot clear time.archived over HTTP — session.update
only applies the field for a finite number, so an omitted key is a no-op
and null is silently ignored (verified against opencode 1.18.12). Restore
therefore writes time.archived = 0: every client-side reader classifies
archive state by truthiness, so 0 reads as active in the UI, the event
reducer, and the OpenCode app/TUI.

The server's time_archived IS NULL list filter still excludes such rows,
so the global session cache no longer issues an archived:false request
for its active list. Full and per-directory loads now fetch once with
the inclusive flag and split client-side via splitGlobalSessionsByArchived,
which also halves per-directory refresh requests. Directory bootstrap
keeps the server filter because live child stores must not hold archived
sessions; a restored session re-enters its live store through the
authoritative session.updated event.

unarchiveSession/unarchiveSessions follow the archiveSession contract:
wait for server confirmation before reconciling stores, runtime-guard
every reconciliation, preserve partial batch results, and fail loudly
when the server keeps the session archived instead of toasting a
successful no-op.

Closes #2346
2026-08-04 13:21:04 +03:00
Bohdan Triapitsyn 86ef96302d feat(mobile): mobile app navigation rework and beta-feedback closeout (#2561)
Navigation model rebuilt around two full-width drawers and a minimal
header (sessions / title-switcher / usage ring / workspace):

- Left sessions drawer: cross-project tree with live status indicators,
  swipe actions on sessions (rename/archive/delete) and on group headers
  (project edit / two-step close, worktree delete), reorder-only edit
  mode with collapsible project cards and draggable worktrees, app-level
  footer (connected instance, settings, pending web update).
- Right workspace drawer: Changes / Files / Terminal / Notes / MCP as
  pill tabs (inactive tabs icon-only); panes stay mounted once visited.
  The full desktop file editor serves the Files tab; read/skill tool taps
  in chat open the file there at the requested line.
- Header session switcher on title tap: 10 cross-project recents with
  live busy/attention indicators and project · branch metadata; the
  usage ring opens a metadata overlay with an explicit loading state.
- The overflow menu is gone on phones (its destinations moved into the
  drawers); iPad keeps it until its dedicated layout pass.

Correctness and continuity:

- /auth/session answers bearer-first, so a stale WebView cookie can no
  longer mask a revoked device token; cold launches classify failures
  fast and land on an explicit connect screen.
- Authoritative session snapshots raise frozen ordering baselines and
  stale live ranks — recents stay truthful after the app slept.
- Cold launches reopen the last active session per instance (persisted
  pointer, confirmed against a sessions snapshot; a user-opened draft
  clears it), with a logo hold instead of a draft flash.

Also: collapsed pill composer gains the stop control; chat tool rows
share one 36px rhythm; Task subtool rows truncate; larger bottom safe
area so the composer clears big-screen corner radii; Capacitor build
hides About/Update (store updates apply there); widgets link to the
sessions drawer with a list icon; MobileApp split into focused modules;
five mobile-surface detectors unified; translucent borders normalized to
70%; all new strings translated across the 10 locales.

iPad and foldable layouts are intentionally untouched - separate next version PR.
2026-08-01 21:16:36 +03:00
Bohdan Triapitsyn a6fb7193dc feat(ui): expose desktop session actions in header
Add a dedicated desktop header menu for the active session while keeping recent-session switching available when the sidebar is closed. Match inline rename behavior with the sidebar and expose rename, copy ID, share, export, archive, and delete actions with localized feedback.

Automatically copy newly created share links, keep share/unshare state synchronized across live and global stores, and normalize stale upstream unshare responses so the UI immediately reflects successful unsharing.

Require Markdown exports to load every available message page before formatting the conversation. Abort incomplete root exports, retain explicit child-session skip warnings, and guard complete-history pagination against failures and cursor cycles.
2026-07-31 21:02:23 +03:00
𝖎𝖚𝖑𝖎𝖎𝖆andBohdan Triapitsyn aae889b904 perf: optimize session loading and desktop startup (#2545)
* perf: optimize session loading and startup

* fix(chat): stabilize history prepend virtualization

* perf: unblock first session open from startup network contention

Opening the first session after app start waited seconds for its message
fetch. Three independent contributors, each measured via CDP network
capture and Chromium net-log against the packaged desktop app:

- The active-session watchdog fired an uncapped per-directory status poll
  and child-session discovery burst at startup, and other subsystems
  (git checks, global session pages, command/skill discovery) fanned out
  alongside it, saturating the browser's ~6 HTTP/1.1 sockets per origin.
  Add a shared background-network gate (concurrency 3) and route the
  watchdog, poll-shaped git reads (also priority: low), global session
  pages, command/skill loads, and the background update check through it.

- The packaged renderer is cross-origin to the loopback backend, so every
  API call needs a CORS preflight; a few slow OpenCode-proxied requests
  held the whole pool while preflights and interactive traffic queued
  behind them. Lift Chromium's per-host connection cap for loopback via
  ignore-connections-limit in the Electron shell.

- OpenCode initializes each directory lazily on its first request, so the
  first click paid that cost interactively. Warm the last-used directory
  and the three most recently opened projects right after OpenCode
  readiness, sequentially and best-effort, overlapping UI startup.

Validation: new background-network tests, lifecycle warmup test, focused
store/sync tests, UI type-check and lint, dead-code report, node --check
plus electron type-check/lint, and CDP first-open measurements on the
packaged app (message fetch socket queue 5.4s -> 0.03s).

* fix(ui): keep interactive git reads out of background queue

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-07-31 12:51:15 +03:00
ChangeHow 8130381f6c fix(ui): smooth desktop sidebar sticky transitions (#2528)
* fix(ui): blend vibrant sidebar transitions

* fix(ui): fade sidebar content below header

* fix(ui): smooth vibrant sidebar sticky headers

* fix(ui): scope sticky fade to desktop

* fix(ui): respect sticky header preference
2026-07-30 17:51:23 +03:00
Bohdan Triapitsyn 4b976141e0 fix(ui): back recent header only when stuck 2026-07-29 18:56:22 +03:00
Bohdan Triapitsyn 3f54f6f459 fix(ui): clear stale sticky header state 2026-07-29 18:36:05 +03:00
Bohdan Triapitsyn 1291cde5c2 feat(ui): sidebar redesign — project zones, grouping modes, full-page surfaces (#2480)
* checkpoint: flatten sidebar core (flat sessions, zones, single mode, folders flat)

* checkpoint: sidebar nav + scheduled/archive full-page surfaces, recent zone header

* checkpoint: worktrees management surface via project menu

* checkpoint: docs, i18n, validation for sidebar redesign

* checkpoint: unified row/zone geometry, recent backfill, tooltips everywhere, primary spinner

* checkpoint: branch icon marker, date moved to rich tooltip, recent back to pure time window

* checkpoint: PR state on branch markers + tooltip, no reserved right space, aligned show-more, instant tooltips

* checkpoint: reserve hover-action space so title text is not overlapped

* checkpoint: tighten hover-action reserve

* checkpoint: color-only unread emphasis to avoid title reflow

* checkpoint: drop new-subfolder action, folder actions overlay on hover

* checkpoint: fix sticky project headers (sticky on trigger div), stuck elevation

* checkpoint: full-bleed semibold zone headers, no top scroll fade

* checkpoint: headers without background tint (typography-only emphasis)

* checkpoint: recent header flush with scroll top (no pre-stick bump)

* checkpoint: shared tooltip provider with grouping (instant handoff between rows)

* checkpoint: blur pointer-click focus so hover chrome hides on mouse-leave

* checkpoint: tooltip closeDelay bridges inter-row gap

* checkpoint: nav above controls, merged view dropdown, project-scoped bulk selection, cross-worktree folders

* checkpoint: folder header tooltip with full path name

* checkpoint: true page surfaces (hidden chat, header title, close-on-select), multirun page, run-now jump, folders on top, controls row polish

* checkpoint: rename mode — dual-instance outside-click fix, no vertical shift

* checkpoint: session grouping mode toggle (by-worktree default, flat option)

* checkpoint: worktree header — hover padding reserve + delete worktree action

* checkpoint: frosted sticky header backing under desktop vibrancy

* checkpoint: align worktree sub-header with project header icon column

* checkpoint: restore worktree group DnD reorder; dense vibrancy header tint (Chromium mask+backdrop-filter)

* checkpoint: vibrancy — drop scroller mask so backdrop-filter samples rows (Chromium backdrop-root limitation)

* checkpoint: vibrancy headers use opaque sidebar tone (Electron transparent-window backdrop-filter bug)

* checkpoint: nav collapsed to one row (New session + surface icons), mirrors Add project row

* checkpoint: raise sticky zone headers above row action layers (z-20)

* checkpoint: New session as full-width CTA + single quiet toolbar row

* checkpoint: New session row back to quiet text form above the toolbar

* checkpoint: align toolbar left icon with New session icon column

* checkpoint: hoverless flat header controls (color-only hover states)

* checkpoint: sticky project headers toggle in view dropdown (default on)

* checkpoint: full-page surfaces adapted — archive directory filter panel, scheduled master-detail, multirun without duplicate title bar

* checkpoint: multirun joins mutually exclusive surface set

* checkpoint: surfaces leave via navigation only (no close/cancel buttons), robust close-on-new-session, drop dead MultiRunWindow

* checkpoint: no scheduled header description, aligned empty-worktree note, collapse/expand covers worktree groups

* checkpoint: overlay scrollbar above sticky zone headers

* checkpoint: archive delete icons reveal on hover with padding shift

* checkpoint: new worktrees surface at top of the worktree list

* checkpoint: no grab cursor on worktree headers

* checkpoint: worktrees page list-only with inline action, no worktrees in edit dialog, drop menu ellipsis

* checkpoint: worktrees page uses full content width

* checkpoint: worktrees header — 'in' instead of em dash, no description

* checkpoint: drop legacy OpenCode badge from worktree list, tooltip without OpenCode mention

* review: bound pr summary cache
2026-07-28 12:04:39 +03:00
Bohdan Triapitsyn 89f7c37d60 fix: make session ordering follow activity lifecycle
Session lists now promote a conversation when it starts working and again when it settles, instead of reacting to every streaming timestamp update. This keeps ordering responsive without bringing back the sidebar churn removed by the recent performance work.

Apply the same user-visible order across Recent, project and worktree groups, session switchers, mobile navigation, widgets, the command palette, and the desktop tray. Preserve pinned priority, freeze timestamp fallback ordering, and keep child-session activity scoped to siblings under the same parent so it never moves the root conversation.

Seed reconnect snapshots without synthetic jumps, clear ephemeral ranks on deletion and runtime changes, and cover lifecycle transitions, mixed root/child trees, metadata-only updates, and project-group ordering with regression tests.
2026-07-23 15:45:53 +03:00
Bohdan Triapitsyn 85400459e9 perf: overhaul session loading, caching, and runtime isolation (#2360)
Improve OpenChamber responsiveness under large session workloads while fixing
cache, synchronization, and persistence correctness across runtimes, projects,
directories, and worktrees.

- prioritize selected and visible sessions during bootstrap and defer
  non-critical enrichment work
- reduce redundant message loading, event processing, store publication, and
  hidden sidebar work
- prevent stale session and message requests from overwriting newer
  authoritative state
- preserve existing data when authoritative fetches fail instead of treating
  failures as successful empty responses
- scope session materialization, messages, drafts, queues, todos, pins,
  permissions, folders, tabs, Git state, and pull request data by runtime and
  directory identity
- harden runtime switching, reconnect, cleanup, mutation reconciliation, and
  persisted-state ordering
- preserve live subagent Task linkage when metadata arrives after an older
  message request or while streaming parts are suspended
- coalesce overlapping tail refreshes without losing newer refresh demand
- improve cold-session loading by moving deferrable work out of the critical
  bootstrap path
- isolate URL authentication, mobile credentials, native secrets, and other
  runtime-owned state across endpoint changes
- bound long-lived caches and remove avoidable allocations from event and
  rendering hot paths
- limit virtualization to archive collections where it improves rendering
  without disrupting active sidebar layout
- stabilize session folders, pin ordering, expanded state, and persisted
  sidebar behavior
- open skill files through the same secure editor and outside-workspace grant
  flow used by file navigation, including worktree sessions
- expand regression coverage for stale completions, runtime collisions,
  reconnect behavior, persistence races, authoritative empty results, and
  subagent refresh ordering
- document the updated synchronization, cache ownership, performance, and
  runtime-isolation invariants
2026-07-21 20:52:20 +03:00
Bohdan Triapitsyn 2b5e9a0221 feat: pass selected project when opening new session drafts
New drafts now receive the active project ID from sidebar project actions.
Project-group draft creation also forwards the selected project.
Aligns draft opening behavior across project and session selection flows.
2026-07-15 12:31:35 +03:00
Bohdan Triapitsyn b36afbf5ee perf(sidebar): index session ownership and narrow live subscriptions
Replace repeated project-by-session directory matching across sidebar hooks with a shared ownership index that resolves each unique directory once and exposes direct project and folder-scope buckets.

Gate destructive folder reconciliation on authoritative session data and topology readiness, preserve last-known worktrees after discovery failures, and retain nested-project, VS Code, active/archive dedupe, and Windows drive-root semantics.

Narrow cross-directory subscriptions to session and status slices so streaming deltas no longer trigger global aggregation. Reuse a cached session ID index for permission lineage checks instead of rebuilding it on every session switch.

On the reported 15-project, 67-worktree, 14,561-session shape, ownership indexing averages 3.81 ms versus roughly 450 ms for the cache-only hotfix.

Validation: 28 targeted tests, UI type-check, UI lint, and dead-code analysis.
2026-07-13 23:18:12 +03:00
Bohdan Triapitsyn 0daad180a1 fix(sidebar): preserve pins after partial session loads 2026-07-12 00:51:36 +03:00
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 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
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
bashrusakhandLeonid Skorobogatyy 7ee893e1e5 fix(sidebar): preserve explicit session selection on stale worktree data (#1813)
When clicking a session inside a worktree group, the layout effect in
useProjectSessionSelection could override the user's selection with the
project's first root session. This happened because projectSections
(and thus projectSessionMeta) might not yet include the worktree group
on the first render after the click.

The fix adds a guard: if currentSessionId is set but not found in the
current projectMap (stale data), the effect returns early instead of
falling through to auto-selection logic.

Fixes #1804

Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
2026-06-29 00:21:54 +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
bashrusakhandLeonid Skorobogatyy 83256ba924 fix(sidebar): preserve pinned sessions and folder refs on empty session list (#1706)
Added sessions.length === 0 guard to useSidebarPersistence.ts and
sessions.length === 0 && archivedSessions.length === 0 guard to
useSessionFolderCleanup.ts. Prevents data loss when server returns
empty list during transient failures.

Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
2026-06-23 22:01:24 +03:00
bashrusakh 59ecd86b4b perf: isolate chat streaming renders and reduce sidebar render cost (#1672)
Reworks the chat and session-sidebar render paths to cut render cascades, memory
  churn, and UI jank on large sessions and big session trees. Behavior is preserved;
  the changes are about *when* and *how much* the UI re-renders.

  ## Chat streaming
  - Freeze the streaming message's parts in the bulk turn projection during streaming,
    and re-inject live parts only in an isolated tail leaf, so a ~60/sec delta stream
    no longer re-runs the whole-session projection or re-renders unrelated rows.
    session with referential reuse of unchanged turns.
  - Memoize message rows with field-aware comparators instead of reference equality.
  - Replace the manual child-session polling in the task tool with the live SSE
    stream + a one-shot load, removing a fetch/settle state machine.

  ## History loading & scroll
  - Load an initial page fast, then prepend one older page in the background so the
    scroll container has headroom and "load older on scroll-up" fires before the user
    hits the absolute top.
  - Compensate scroll synchronously (in a layout effect, before paint) for prepends —
    including background prepends that don't originate from a user scroll — so the
    viewport stays stable instead of judder-correcting on the next frame.

  ## Markdown rendering
  - Render markdown synchronously *styled* on first paint (paragraphs, lists, code
    cards, tables, inline code) instead of raw escaped text; the async pass then only
    upgrades syntax-highlight colors. Eliminates the flash of full-width raw text.
  - Load KaTeX CSS eagerly with the main bundle instead of inside the lazy markdown
    chunk, avoiding a late stylesheet injection on first render.

  ## Sidebar
  - Hoist per-row recursive tree walks out of row comparators into per-group
    precomputed sets/keys; batch live-session lookups into a single map; add a
    group-level memo boundary.
  - Isolate rename drafts so per-keystroke typing doesn't repaint the row tree.

  ## Sync layer
  - Add a staleness guard so a slow message fetch can't repopulate a session the user
    navigated away from.
  - Throw on fetch failure for authoritative loaders so a transient blip can't read as
    an empty server response.

  ## Cleanup
  - Remove dead code (unused hooks, params, duplicated inline types) surfaced while
    reworking the above.

  ## Known issue
  - A rare, purely cosmetic first-paint width flash can still appear on large sessions;
    it has no behavioral or data impact and is tracked for a follow-up runtime trace.
2026-06-18 00:43:16 +03:00
bashrusakhandLeonid Skorobogatyy d909c43979 refactor(sidebar): remove dead directoryStatus probe artifact (#1660)
Commit 7205d7a3 replaced runtime directory probing with buildKnownSessionDirectories

filtering, but left the hook, prop, state, and UI checks as a transitional artifact.

- Delete useDirectoryStatusProbe.ts

- Remove directoryStatus state/prop from SessionSidebar, SessionGroupSection, SessionNodeItem

- Remove isMissingDirectory checks, opacity-75 styling, and disabled states

- Simplify handleSessionSelect signature

- Update sidebar DOCUMENTATION.md

Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
2026-06-15 14:45:50 +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
Tom Rochette 560e24cbc2 fix: prevent worktree modal from disappearing after opening (#1585)
The worktree dialog state was local React state in SessionSidebar,
which gets destroyed when the component unmounts (mobile drawer close,
VSCode view switch). This caused the modal to briefly appear then
disappear.

Three changes:
- Move newWorktreeDialogOpen from local state to useUIStore so it
  survives component unmount
- Guard useProjectSessionSelection layout effect to skip when the
  worktree dialog is open (prevents auto-session-selection from
  closing the sidebar)
- Remove setSessionSwitcherOpen(false) from worktree button handler
  on mobile (prevents drawer close that unmounts the sidebar)

Fixes #1414
2026-06-13 08:55:17 +03:00
Paolo InsognaandBohdan Triapitsyn b1810013e5 feat: Add delete session in the sessions menu. (#1557)
Signed-off-by: Paolo Insogna <paolo@cowtech.it>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-12 23:13:41 +03:00
Tom a0597b1065 fix: prevent cascade rollback from restoring deleted session descendants (#1555)
The OpenCode server cascade-deletes all child sessions when a parent
is removed. The client was sending individual DELETE requests for each
descendant, which returned 404 after the parent's cascade removed them.
The 404 triggered rollback in deleteSessionAction, restoring already-
deleted sessions back into the global store.

Changes:
- executeDeleteSession: only send the root session delete; the server
  cascade handles descendants.
- deleteSession / deleteSessionInDirectory: treat 404 in catch as
  success, acting as a safety net for remaining paths (e.g. sidebar
  bulk action bar when parent and child are both selected).
2026-06-12 22:54:39 +03:00
Bohdan Triapitsyn 48b0a4f292 fix: preserve chat folder assignments on reload
Prevents folder cleanup before sessions finish loading
Keeps saved chat folder memberships after desktop reload
2026-06-05 20:58:54 +03:00
Bohdan Triapitsyn 8e25bc4cce fix: keep new sessions grouped by project
Preserves session directory metadata across live updates
Keeps desktop and mobile session lists in project groups
Adds regression coverage for session grouping
2026-06-03 14:51:17 +03:00
Bohdan Triapitsyn f94d87b5fd perf(git): cache project-root resolution to stop N² polling cascade (#1398)
* perf(git): cache project-root resolution to stop N² polling cascade

Opening a workspace with many projects/worktrees fired hundreds of
`POST /api/fs/exec` requests (e.g. ~700 for 19 projects) within seconds,
dominated by repeated `git rev-parse --absolute-git-dir` /
`--git-common-dir` for the same directories.

Root cause: in `useProjectRepoStatus`, each project's `ensureStatus`
settles independently and mutates the git store, which re-derives
`projectGitBranchesKey` and re-runs `getRootBranch` for *all* projects on
every change. `getRootBranch` had no caching, so this produced an N×N
burst of uncached git plumbing calls.

Changes:
- worktreeStatus: extract `resolveProjectRoot` to module scope with a
  60s TTL cache + in-flight dedupe (root resolution is static within a
  session). Combine the two `rev-parse` queries into one subprocess.
  Add `getRootBranch(dir, { knownBranch })` fast-path that skips a
  redundant git status when the directory is its own root, while still
  resolving the primary-root branch correctly for linked worktrees.
  Export `invalidateResolvedProjectRootCache`.
- useProjectRepoStatus: replace the cascade effect with a debounced,
  diff-based pass that only resolves projects that are new or whose
  branch actually changed, passing the known branch through.
- worktreeManager: invalidate the root cache on worktree create/remove.
- Add unit tests for caching, dedupe, invalidation, rev-parse
  precedence, non-git fallback, linked-worktree resolution and the
  knownBranch fast-path.

Reduces startup from hundreds of requests to roughly one root
resolution per project.

* fix(git): clear in-flight resolves and guard write-back on cache invalidation

`invalidateResolvedProjectRootCache` cleared `resolvedRootCache` but left
`inFlightRootResolves` intact, so during a worktree topology change a
resolution already in flight could (1) be handed to callers arriving after
invalidation and (2) re-seed the cache with the pre-invalidation root when it
settled, defeating invalidation for up to the full TTL.

Drop the in-flight entry on invalidation and add an epoch guard so a resolve
that was invalidated mid-flight does not write its now-stale result back.
Add a regression test for the concurrent-invalidation scenario.

* fix(git): bound root cache and avoid early sidebar resolves
2026-05-24 13:20:55 +03:00
9af0de0056 Add mobile context notes tab (#1355)
* Add mobile context notes tab

* Fix bot comments

---------

Co-authored-by: Konstantin Zolin <zolin_ka@vk.com>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-05-23 21:48:56 +03:00
Roberto BertóandBohdan Triapitsyn 0c60fd6222 feat: rename sessions inline via double-click (#1320)
* feat: rename sessions inline via double-click

Double-clicking a session name in the sidebar or the mobile session
status bar now switches it into an inline editable input. Enter saves,
Esc cancels, and clicking elsewhere blurs the input to save. This
mirrors the VSCode/Finder rename pattern and removes the need to open
the session menu for what is a very common action.

* fix(rename): close sidebar input on empty title; drop duplicate mobile editor

Two issues from PR review:

1. handleSaveEdit (sidebar) only closed the input when editTitle.trim()
   was non-empty. Clearing the title and pressing Enter or blurring left
   the input open with no exit path other than Escape. The save handler
   now always closes the editor; an empty title is treated as a silent
   cancel (no update call).

2. ExpandedView (mobile) renders the current session twice — once in the
   sticky header, once in the session list — so a single editingSessionId
   produced two simultaneous inputs for the current session. The list row
   for the current session now suppresses its rename input; the header
   remains the single editor in that case.

* fix: refine inline session rename editing

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-05-23 21:15:15 +03:00
Bohdan Triapitsyn 51c8d52ab5 perf(ui): improve VS Code chat session switching
Improve chat session switching and history pagination, with most of the aggressive limits scoped to the VS Code webview where the freezes were observed.

Session history loading and pagination:

- Reduce the VS Code message page size to 30 records so switching sessions does not immediately hydrate large histories into the webview.

- Keep manual Load older messages in VS Code fixed at 30 records per request instead of growing the request size over time.

- Add a bounded VS Code initial-tail expansion path from 30 to 50, 80, and 120 records only when the initial page has no user-message turn boundary, preventing large final turns from rendering as an empty chat.

- Lower the normal web message page size from 200 to 150 for a mild shared optimization without adopting the aggressive VS Code limits.

- Make session pagination metadata reactive per session so ChatContainer receives cursor updates from materialization and reconnect paths without requiring a switch away and back.

- Write pagination metadata before publishing newly materialized messages so the first render sees the correct has-more state.

- Store cursor information from direct materialization and reconnect message fetches in the shared session prefetch metadata cache.

VS Code cache and memory pressure reductions:

- Use a shared per-directory session recency map so cache eviction is based on app-level recency instead of whichever useSync instance happened to run.

- Limit VS Code warm session cache retention to 4 sessions and evict heavy inactive message caches after switching away from a large session.

- Disable sidebar session prefetch in VS Code because warming extra sessions was increasing webview memory and GC pressure during navigation.

- Remove dropdown background message prefetch so opening the switcher does not start additional session materialization work.

- Drop cached session-message-record snapshots when evicting session data so stale derived records do not remain after the raw session cache is cleared.

- Add bounded LRU caching for session message record snapshots, with much smaller VS Code limits and a VS Code cap that avoids caching snapshots above 30 messages.

- Bound the turn-window model cache in VS Code and avoid caching turn models for sessions above the VS Code message-page size.

Chat render-path reductions:

- Reuse ChatContainer's already-materialized message records in plan detection instead of adding a second active-session message subscription.

- Add a no-op guard when marking session plan availability so repeated detections do not create new Map references and fan out renders.

- Add no-op guards for session switcher and dropdown open state updates to avoid unnecessary store updates and renders.

- Convert several session-specific hooks to useSyncExternalStore with empty-session no-subscribe behavior so empty IDs do not subscribe to broad store updates.

- Remount the chat viewport when the current session changes, isolating per-session viewport and list state.

- Change the virtualized message-list fallback to render only a tail window when the virtualizer has not produced rows yet, instead of rendering an entire large history.

VS Code layout and header improvements:

- Remove the broad useSessions subscription from the VS Code layout header path and subscribe only to the active session title and initial-session existence.

- Unmount the compact VS Code session sidebar when the user is in chat view instead of keeping the hidden session list mounted and subscribed.

- Compute the latest assistant model and latest context-token usage in a single reverse scan of current-session messages instead of scanning the same list twice.

- Remove switcher git-status warmup work so the switcher reads already-loaded branch labels without starting extra background git status requests.

Markdown and file-reference safeguards:

- Skip expensive syntax highlighting for very large code blocks, with a 200-line cap in VS Code and a softer 1200-line cap in web.

- Add an LRU cap to file-reference stat lookups so the cache cannot grow without bound across many rendered messages.

- Limit the number of file references annotated per render to 40 in VS Code and 200 in web to prevent large assistant outputs from spawning too many stat checks.

- Clear file-link annotations when file-reference mode is disabled so stale attributes and handlers do not remain on previously annotated nodes.

Assistant-message action and preview reductions:

- Skip preview URL scanning on VS Code, mobile, and mini-chat surfaces so assistant text and tool output are not scanned where the preview action is unavailable.

- Skip Save-as-Plan project lookup on VS Code, mini-chat, and mobile surfaces.

- Hide Save-as-Plan and Start MultiRun assistant-message actions on VS Code, mini-chat, and mobile surfaces.

- Resolve the current session directory on demand for assistant actions instead of subscribing each assistant message to the full session list.

Tool and task rendering optimizations:

- Prefer finalized task metadata summaries without fetching child-session messages when the summary is already present.

- Avoid polling or final-fetching task child sessions once a final metadata summary is available.

- Use VS Code-specific task child fetch limits of 30 records for initial, active, and idle fetches.

- Parse diff stats by scanning patch text line-by-line instead of splitting large patches into arrays.

- Count write-tool lines by scanning content instead of allocating a split array for large files.

- Avoid trimming large patch strings just to test whether they contain content.

- Memoize diff and write statistics so unchanged tool parts do not recalculate them on every render.

VS Code bridge improvements:

- Return JSON and text proxy responses through the VS Code bridge as bodyText instead of base64 so the webview avoids synchronous base64 decoding for common API responses.

- Keep binary responses on the base64 path while making bodyBase64 optional in the bridge contract.

- Strip content-length, content-encoding, and transfer-encoding headers from proxied responses because the bridge reconstructs the Response body.

Validation:

- bun run type-check

- bun run lint

- bun run vscode:build
2026-05-21 15:45:44 +03:00
vhqtvnandvhqtvn cbe6335ed9 fix(sessions): align archive-cascade count with executed list (#1298)
* fix(sessions): align archive-cascade count with executed list

When archiving a parent session, the dialog showed the total descendant
count but the actual archive request skipped already-archived
descendants — so the toast/result didn't match what the user was told.

Collect descendants once, then filter per-action: hard-delete cascades
to every descendant; archive skips already-archived ones. Dialog count
and the ID list passed to archiveSessions/deleteSessions now agree.

* fix(sessions): snapshot descendant IDs at dialog open

Address Greptile review: handleDeleteSession populated the dialog
count from a fresh collectDescendants() call, then confirmDeleteSession
re-collected at confirm time — so a background sync that mutated
childrenMap while the dialog was open could re-introduce the same
count-vs-executed mismatch this PR fixed.

Store the computed descendant IDs in deleteSessionConfirm and pass
them through to executeDeleteSession, so the executed list is always
the snapshot the user was shown. The no-dialog direct-execute path
also routes through the snapshot for consistency.

---------

Co-authored-by: vhqtvn <8930337+vhqtvn@users.noreply.github.com>
2026-05-18 18:13:43 +03:00
vhqtvnandvhqtvn 9ab90d4f1a fix(sidebar): key parent-expansion per render context (#1302)
The same session can appear in two render contexts at once — most
visibly under "Recent" and under its project's root group. Expansion
state was keyed by bare session id, so toggling the chevron in one
context flipped it in the other; the user clicked one row and a
duplicate elsewhere expanded/collapsed in sync.

Switch the expansion set to composite keys of the form
`<renderContext>:<active|archived>:<sessionId>`. SessionNodeItem
already computes this exact shape for its menu instance key
(menuInstanceKey); reuse it as expansionKey so the two render-time
instances of one session keep independent expand state. Updated:

- isExpanded reads `expandedParents.has(expansionKey)`.
- Chevron click / Enter / Space pass `expansionKey` to toggleParent
  instead of session.id.
- The memo-equality check compares the same composite key on both
  sides so it correctly re-renders when only one context's state
  changes.
- toggleParent's parameter is renamed `expansionKey` to reflect the
  new shape; the implementation is unchanged.
- The auto-expand-parent-on-subagent-navigation effect doesn't know
  which context the user will look at the parent in, so it fans the
  parentID out to all four (project|recent) × (active|archived)
  combinations.

Storage is bumped to `oc.sessions.expandedParents.v2`. Existing v1
data (bare session ids) is one-shot migrated by fanning each id
across all four contexts and rewritten under the v2 key; the v1 key
is then removed so a downgrade-then-reupgrade doesn't re-migrate.

Co-authored-by: vhqtvn <8930337+vhqtvn@users.noreply.github.com>
2026-05-18 17:57:05 +03:00
Isaac Sanchez-HawkinsandIsaac Sanchez 00c112077d fix: match root project sessions in switcher (#1274)
* fix: match root project sessions in switcher

* test: cover project path matching edge cases

---------

Co-authored-by: Isaac Sanchez <isanchez-hawkins@arize.com>
2026-05-16 16:46:05 +03:00
Bohdan Triapitsyn 60f380479a feat: add session switcher to VS Code layout
Open recent sessions from the VS Code header
Add compact session switcher mode
Limit session prefetch to the active directory
2026-05-15 20:22:01 +03:00
Bohdan Triapitsyn b6149614bd feat: add session switcher dropdown in header
Open recent sessions from chat headers
Support session switching in mini chat
Share pinned and active session state
2026-05-15 18:16:09 +03:00
Bohdan Triapitsyn 0ea573f766 refactor: redesign chat scroll system
The previous system layered three hooks (useScrollEngine, useChatScrollManager,
useChatTimelineController) with overlapping responsibilities, four parallel
ResizeObservers/MutationObservers, and six entry points to "scroll to bottom"
(force-flag combinations, persistent follow loops, materialization recovery).
This produced bugs where users could not break free of auto-follow during
streaming: scrollbar drag, keyboard scrolling and find-in-page were not
detected as user intent, and observers kept restarting the follow loop on
every DOM mutation.

The new architecture replaces the two low-level hooks with a single
useChatAutoFollow that owns scroll behaviour end to end:

- One state: 'following' or 'released'. No follow modes, no pin flags,
  no marker pixels.
- One scroll writer: a lerp loop that runs only while the session is
  streaming and state is 'following'. Idle sessions never write scrollTop
  programmatically.
- One user-intent detector: wheel up, touch drag down, keyboard
  (PageUp / Home / ArrowUp), pointerdown on the OverlayScrollbar thumb,
  and explicit releaseAutoFollow() calls all flip the state to 'released'.
- A 1.2s grace period after explicit release: re-pin will not auto-engage
  inside this window, so a small wheel up cannot snap the user back even
  while they remain near the bottom spacer.
- Re-pin and the scroll-to-bottom button share the same threshold: the
  height of the empty bottom spacer (10vh on desktop, 40px on mobile).
  Released users see the button only after they have scrolled past the
  spacer that already exists at the end of the chat.
- Save/restore of scroll position uses ratio mapping, debounced at 150ms
  on user-driven scroll events; programmatic writes are masked via a short
  window so they never persist as user positions.
- Container reattachment is detected via a useLayoutEffect probe over
  scrollRef.current. Listeners and observers re-bind when ChatViewport
  mounts after hydration or after the first message promotes a draft
  session into a real chat.
- A pending-restore queue replays restoreSnapshot once the scroll
  container appears, fixing the case where a hydrating session landed at
  the top instead of the bottom.

Removed: useScrollEngine.ts, useChatScrollManager.ts, the persistent
follow loop with its own ResizeObserver+MutationObserver pair, the
materialization-recovery .finally resume that yanked idle users to the
bottom on transient sync gaps, and the openchamber:session-reselected
event (re-select still works through the existing onSessionSelected
callback). The openchamber:chat-force-scroll-bottom event remains for
synthetic-message paths like git-message generation.

Net change: ~1300 lines removed, two hooks replaced with one, one
observer pair instead of four.
2026-05-08 14:20:16 +03:00
Bohdan Triapitsyn e892346c6b refactor: stabilize live chat sync materialization (#1132)
Canonicalize session message/part materialization across load, prefetch, reconnect, and recovery paths so OpenChamber restores session snapshots through one consistent merge flow.

Preserve live assistant streaming text when stale or delayed snapshots arrive, while still replacing optimistic user parts with confirmed server snapshots to avoid duplicated user messages.

Narrow recovery triggers to explicit incomplete snapshot signals instead of broad session-event fallbacks, reducing unnecessary session refetches during active streaming.

Keep turn windowing aligned with parented assistant replies and add regression coverage for materialization gaps, stale snapshot protection, optimistic user replacement, reconnect recovery, and turn grouping.
2026-05-07 18:57:44 +03:00
Islam NoflandBohdan Triapitsyn 8fdc1e7e55 fix: resolve sidebar stale state after worktree, folder, project, and session mutations (#1038)
* fix: update sidebar store after worktree creation

* fix: update sidebar store after worktree removal

* fix: update sidebar store with correct key in removeProjectWorktree

- Fixed key mismatch in sidebar store updates for worktree removal
- Removed worktrees now properly disappear from sidebar
- Aligned remove path with create path key resolution

* fix: resolve sidebar stale state after folder, worktree, project, and session mutations

* fix: now builds

* Fix worktree sidebar store key

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-04-27 13:42:52 +03:00
Bohdan Triapitsyn 7d7285655d Add i18n foundation and translations (#1027)
* feat: add i18n foundation

* feat: localize sessions sidebar

* Localize multirun/scheduled tasks and fix dialog dropdown interactions

* localize git sidebar surface and add zh-CN keys

* feat(ui): localize context panel, diff/plan views, and context sidebar content

* fix(config): resolve user config home via fs/home before embedded home

* localize header/chat UI and complete model/worktree panel strings

* localize worktree + github issue/pr dialog flows

* localize settings sections and split settings i18n dictionaries

* localize additional settings sections and sidebars

* localize more settings pages and dialogs

* fix settings select trigger localization

* localize tunnel settings ui surface

* localize additional settings sections

* localize keyboard shortcuts labels in settings

* localize terminal and utility dialogs surfaces

* feat(i18n): localize remaining UI strings

* Add Ukrainian locale

* Add Spanish locale

* Add Brazilian Portuguese locale

* Polish locale translations
2026-04-26 14:03:39 +03:00
Artёm 125094d371 fix: сохранил pinned sessions после reload (#943) 2026-04-21 23:13:54 +03:00
YifanandBohdan Triapitsyn bee9d19f3a feat(web): add WebSocket transport for message event streaming with SSE fallback (#764)
* feat: add websocket message stream transport

* fix: avoid false missing session directories in sidebar

* fix: re-probe project root session directories

* refactor: use button group for message stream transport

* fix: resolve chat input hook dependency warning

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-04-17 11:07:26 +03:00
jwcrystal 5ea2fed0d3 fix: hide empty archived section and folders (#872)
* fix: hide archived section and empty folders when no sessions remain

- Only push archived group in useSessionGrouping when there are archived
  sessions, preventing an empty archived section from rendering
- Hide empty folders in archived bucket via shouldKeepFolder check in
  SessionGroupSection (folders with no sessions and no content in
  children are filtered out)
- Always filter folders through shouldKeepFolder, not just during search

* perf: memoize archived folder filtering
2026-04-11 23:25:33 +03:00
Bohdan Triapitsyn 8587db89b1 perf: reduce UI render fanout and scroll jitter
- Cut broad render fanout across the app by replacing shared-store whole-object subscriptions with leaf selectors, memoizing hot chrome boundaries, and isolating disabled global providers from live session/message state. This keeps header controls, composer toolbars, side panels, and other non-hot UI surfaces from repainting on every assistant update or keystroke.

- Rework sidebar session ordering so recent, project groups, and worktree groups derive from one ordering source while avoiding streaming-time thrash. The sidebar now uses a stabilized session snapshot, preserves structural identity for unchanged rows, reads live row status/details per session, and applies a one-shot sort bump on idle->busy instead of continuously resorting during activity.

- Fix chat/input scroll instability by separating viewport-resize handling from message-growth handling, disabling conflicting native scroll anchoring, and stopping textarea autosize from collapsing on every growth keystroke. This removes the multiline typing jiggle during streaming and reduces unnecessary composer rerenders.

- Also gate voice context wiring behind voice-mode enablement and codify the learned render/scroll/order anti-patterns in AGENTS.md so future changes avoid the same classes of regressions.
2026-04-04 02:19:55 +03:00
Bohdan Triapitsyn d982171689 refactor: centralize git and pr store refresh 2026-04-03 19:47:01 +03:00
Bohdan Triapitsyn 9d78c48949 fix: keep selected session header details stable
- Prevent header flicker to Untitled during session refresh
- Resolve header session data from global sessions first
- Pass session directory hints on more session-switch paths
2026-04-03 12:38:03 +03:00
Bohdan TriapitsynandIuliia Ivashko c9e31a0e6c perf: harden sync architecture and modularize runtimes (#803)
* fix: added desktop app background throttling

* perf: add streaming debug metrics panel

- Show streaming performance metrics in the debug panel
- Auto-enable stream profiling while the panel is open
- Add JSON export for sharing UI and VS Code metrics

* perf: batch streaming updates more aggressively

- Buffer message deltas and metadata updates to cut render churn
- Skip no-op part updates before they touch the message store
- Fix the desktop debug panel shortcut binding

* perf: split streaming event handling and coalesce deltas

- Move streaming content events onto a dedicated fast path
- Defer non-critical stream side effects off the hot path
- Merge repeated message delta events before they reach the UI

* perf: isolate streaming rows from chat rerenders

- Memoize chat rows against render-relevant message changes only
- Read live assistant text directly from store to narrow streaming updates
- Split the active streaming entry from the stable message list path

* perf: streamline chat streaming and SSE proxying

- Reduce chat rerenders around the active streaming path
- Simplify server SSE forwarding to avoid duplicate proxy work

* fix: preserve the first streaming text chunk

- Show the initial text chunk immediately before batched deltas arrive
- Bypass batching for the first text or reasoning part update
- Keep later streaming updates buffered for performance

* perf: align streaming/render hot paths with opencode parity

* perf: harden turn/cache stability and stale delta suppression

* fix: stabilize chat rendering and disable timeline interactions

- Disabled timeline dialog access from shortcuts, commands, and chat input
- Reduced chat render churn by simplifying message list and turn staging behavior
- Improved session-switch stability to prevent update-depth crashes

* perf: track static message rerenders during streaming

* perf: reduce sorted-mode activity rerender fanout

* perf: reduce chat rerender fanout and add active-turn metrics

- Reduced sorted-mode rerender coupling by tightening turn context propagation
- Added a metric for static rerenders outside the active turn during streaming
- Exposed new chat render counters in the debug panel for parity tracking

* fix: keep sorted activity mounted while stream grows

* fix: stabilize session and history scroll rendering

* refactor: decouple server routes from index

* refactor: extract fs module from server index

* refactor: move opencode route ownership into module

* refactor: extract notification route registration

* refactor: extract opencode and notification runtimes from index

* refactor: extract settings runtime and complete server modularization pass

* refactor: modularize server config, skills, icons, and tunnel routes

* refactor: extract server modules from monolithic index.js

Split proxy, routes, runtime helpers, and notification emitter
into dedicated modules under packages/web/server/lib/.

* refactor: replace session/message stores with SSE-driven sync layer

Delete ~9200 lines of old architecture (useEventStream, messageStore,
sessionStore, useSessionStore, questionStore, useTodoStore, client SSE).

New sync layer: event pipeline with coalescing + 16ms flush, pure event
reducer, per-directory child stores with LRU eviction, cursor pagination,
optimistic updates, deferred timeline staging, text throttle.

Migrate all UI consumers to sync hooks (useSessionMessages,
useSessionMessageRecords, useSessionStatus, useSessionPermissions, etc).

Strip session-ui-store to UI-only state, delegate SDK ops to
session-actions with abort-if-busy, optimistic store updates, and
response merging for revert/fork/archive/delete.

Add notification-store for SSE-driven session attention tracking,
cross-directory GlobalSessionStatusStore for sidebar indicators,
client-side diff snapshot sanitization to prevent memory bloat,
and revert message filtering via useVisibleSessionMessages.

* feat: notification store, session actions, activity detection

Add notification-store.ts for SSE-driven attention tracking.
Add sanitize.ts to strip diff snapshot memory bloat.
Add session-actions.ts with optimistic revert/fork/archive/delete.
Improve useSessionActivity with incomplete-message fallback.
Delete useServerSessionStatus polling hook.

* fix: add directory param to all SDK calls, fix command/shell/abort routing

All SDK calls in session-actions.ts now pass directory parameter —
required by OpenCode server to scope session operations. Without it,
abort, commands, revert, fork, and other operations returned 500.

Add routeMessage() in session-ui-store for shell mode (session.shell),
slash commands (session.command), and normal prompts. Command lookup
checks both sync child store and useCommandsStore. Handle /compact
locally via session.summarize().

Implement getContextUsage() to restore header context usage display —
reads token counts from last assistant message in sync store.

* refactor: replace custom API proxy with http-proxy-middleware

Remove ~280 lines of custom proxy code: forwardSseRequest,
forwardGenericApiRequest, collectRequestBodyBuffer, header
manipulation, hop-by-hop filtering, SSE block buffering.

Replace with single createProxyMiddleware() call that handles
SSE streaming, large bodies, and timeouts out of the box.
Dynamic router for OpenCode port changes after restarts.
Auth headers injected via proxyReq hook.

Keep: readiness gate, Windows session merge, API prefix detection.

* perf: targeted event draft cloning to fix streaming render cascade

Event handler was eagerly cloning all state slices on every event,
breaking Zustand selector referential equality. During streaming
(~60 events/sec), this caused every subscriber to re-render regardless
of which slice actually changed.

Now only clones fields the specific event type mutates. Also extracts
StatusRowContainer to isolate high-frequency useAssistantStatus
subscription, removes dead messageStreamStatesMap subscription from
ChatContainer, and narrows useAssistantStatus to only track last
assistant message parts.

MessageList renders: 1972 → 296 per streaming session (-85%).

* fix: null safety for sync state slices

Add defensive ?? {} guards on permission, question, session_status,
and message record access. Prevents crashes when child store state
is partially initialized during bootstrap.

* perf: dedup inflight SDK calls, extract concurrency util, delay PR tracking

Extract mapWithConcurrency to shared lib/concurrency.ts. Add in-flight
dedup for loadProviders/loadAgents to prevent concurrent duplicate SDK
calls. Delay initial PR background tracking by 5s to reduce startup
CPU burst.

* fix: header session lookup across all child stores

Session title and context panel click failed when session belonged to
a different directory than the current child store. Fall back to
getAllSyncSessions() to search all initialized stores.

* chore: bump @opencode-ai/sdk to 1.3.5

* docs: add sync event handling guide

* Optimize session prefetch and improve delete/archive UX

- Add settlement delay to session prefetch to avoid race conditions on
  rapid session switches
- Reduce git diff prefetch and session cache limits for better performance
- Implement optimistic UI updates for session delete/archive operations
  with proper rollback on failure
- Wire session prefetch hook into SessionSidebar with sync integration

* Add file content cache and sync optimizations

- Wrap FilesAPI with in-memory LRU cache for file content with dual
  constraints (entry count and byte size)
- Optimize chat timeline scroll restoration using useLayoutEffect
- Preserve React references in message and part arrays to prevent
  unnecessary re-renders when prepending history
- Add session prefetch TTL cache to prevent redundant fetches
- Integrate session prefetch cache clearing with eviction flow

* Improve session sidebar error handling and add diff prefetch filtering

Load active and archived sessions independently using Promise.allSettled
to prevent one failure from blocking the other. Add retry logic to session
API calls and skip large files during diff prefetch to improve performance.

* Replace sendMessage with optimisticSend wrapper

Introduces optimistic UI updates for normal chat messages to provide
instant feedback. Messages appear immediately in the UI while the API
call executes in the background, with automatic rollback on errors.

* perf: split stores, proper optimistic send, fix revert/directory bugs

- split session-ui-store into voice/input/selection/viewport stores
  to reduce subscriber re-evaluation during streaming
- wire optimisticSend through useSync shadow Map infrastructure
  matching OpenCode's pattern (no heuristic part detection)
- port OpenCode Identifier.ascending ID format for correct sorting
- pass messageID to promptAsync to prevent duplicate messages
- fix worktree directory not propagating to session actions
  (dynamic dir() via opencodeClient.getDirectory)
- fix setCurrentSession accepting directoryHint for new sessions
- fix revert not hiding messages (session limit was 5, bumped to match loaded count)
- fix revert optimistic message removal from store
- fix load-more flicker (useLayoutEffect scroll compensation)
- add prefetch TTL cache, file content LRU cache
- add session prefetch for adjacent sessions
- add instant archive/delete (optimistic before SDK call)
- migrate legacy window.__zustand_session_store__ to session-ui-store
- add retry + independent error handling for archived sessions
- add AGENTS.md performance rules

* perf: startup optimization — dedup, caching, light git status, diff rendering gates

- defer diff prefetch to git tab open, reduce concurrency 4→2, skip >500 changed lines
- cap project git checks concurrency (2), directory status probe (3)
- dedup provider/agent loading, github auth, worktree list (in-flight + TTL caches)
- delay PR tracking 5s, cache 403 search failures per-repo
- coalesce settings PUT (200ms debounce), cache settings GET (2s TTL)
- cache canonical directory resolution (60s TTL)
- persist missing directory status to localStorage (10min TTL)
- light/heavy git status: polling skips numstat+line counting+rev-list
- large diff rendering gate (>500 lines → "render anyway" button)
- tokenization degradation for >500KB files in Pierre
- parallelize main.tsx pre-render awaits
- batch sidebar file tree expanded paths restoration (3 at a time)
- remove bare useConfigStore() subscription in AgentsPage
- sync worktree sandboxes to OpenCode SQLite DB
- fix RightSidebarTabs ternary → explicit tab matching
- defensive guards on sync state (session_status, permission, question, message)

* fix: add defensive guards on remaining sync state field accesses

guard session_status, permission, message, todo, part, config with ?? {}
in useDirectorySync selectors, session-cache, and bootstrap

* fix: add missing directory dep to useCallback in use-sync.ts

* fix: preserve diffStats when light-mode polling overwrites status

* perf: optimize startup git status polling and diff rendering

Preserves diff stats when lightweight polling updates repository status
Reduces startup overhead with smarter git polling and store updates
Adds detailed optimization and migration docs for next performance steps

* fix: keep chat diff stats stable during git status updates

Prevents lightweight git polling from dropping diff statistics
Keeps MessageList diff indicators consistent while status refreshes
Improves reliability of git-aware chat rendering

* fix: user animation replay, queued message variant, startup provider loading

- consume animation ID after first play to prevent re-animation
  on neighbor assistant message completion
- capture send config (model/agent/variant) at queue time matching
  OpenCode's FollowupDraft pattern instead of re-resolving at send time
- replace one-shot startup recovery effect with polling interval
  that retries every 2s until providers and agents load
- fix optimistic bridge to avoid re-render loop (stable ref wrappers)

* chore: update tauri to 2.10.3 and all plugins to latest

- tauri 2.9.4 → 2.10.3
- tauri-build 2.5.3 → 2.5.6
- tauri-plugin-dialog 2.4.2 → 2.6.0
- tauri-plugin-log 2.7.1 → 2.8.0
- tauri-plugin-shell 2.3.3 → 2.3.5
- tauri-plugin-updater 2 (floating) → 2.10.0 (pinned)
- @tauri-apps/api ^2.9.0 → ^2.10.1
- wry 0.53.5 → 0.54.4 (transitive)

* refactor: decouple web server index orchestration runtimes

* fix: align VS Code runtime behavior with web and reduce draft view CPU load

- Queue VS Code bridge and SSE startup requests until API readiness to avoid false bootstrap failures
- Make agent manager actions directory-aware and remove real worktrees with safer partial-failure handling
- Replace heavy logo animation path with a lightweight pulse to cut draft-session CPU usage

* fix: restore auto-selected file sending in chat input

- Send server-selected files as proper file URLs in the message payload
- Include server-backed attachments in submit flow instead of dropping them
- Restore queued-message attachments through the refactored input store

* fix: restore session model selection consistently on session switch

- Restore agent, model, and variant from the latest loaded user message for each session
- Wait for session messages before applying restored selections to avoid stale or missing state
- Remove legacy session-choice inference paths that caused overlap and instability

* fix: restore permission replies and auto-accept across sessions

- Scope permission and question replies to the target session directory so answers take effect reliably
- Make permission auto-accept immediately handle pending requests and react to new permission prompts
- Keep parent-session handling working for child-session requests through the shared response path

* feat: add reusable fuzzy branch fuzzy-search helper and dialog integration (#798)

* feat: add reusable fuzzy branch search for worktrees

* chore: drop planning docs from feature branch

* feat: make worktree branch refresh manual

* feat: add configurable session retention action

* refactor: centralize global session state in ui store

* fix: cancel debounced permission push after reply

* docs: clarify global and directory session store architecture

* docs: refine agent development rules and session activity guidance

- Clarify agent code of conduct and durable development patterns
- Add explicit shared-store rerender and live-state guidance
- Narrow session activity fallback to avoid stale working state

* chore: updated .gitignore

---------

Co-authored-by: Iuliia Ivashko <yulia.ivashko@gmail.com>
2026-03-31 18:47:00 +03:00
Bohdan TriapitsynandIuliia Ivashko 321cc7252a Major UI refresh: sidebar redesign, theme expansion, and chat performance optimizations (#706)
## Summary
Complete sidebar redesign and comprehensive UI polish pass with performance optimizations, theme system refinements, and desktop integration improvements.

## Key Changes

**Sidebar & Navigation Redesign**
- Redesigned sessions sidebar layout with unified button primitives
- Added activity sections with project grouping and improved session organization
- Refined sidebar corners, spacing, and visual hierarchy
- Removed NavRail component in favor of streamlined sidebar
- Stabilized sessions bar toggle position in fullscreen mode

**Performance Optimizations**
- Reduced chat streaming CPU usage and storage churn
- Optimized task tool polling and live timers with debouncing
- Prevented chat state races and reduced background request load
- Debounced draft writes and coalesced session reloads
- Optimized message store updates and turn tracking

**Theme & Visual System**
- Added theme-aware window corners (desktop) and border radius tokens
- Introduced glassmorphism effects on desktop sidebar
- Added backdrop blur to UI elements

**Chat Experience**
- Added session-based permission auto-accept toggle in chat input
- Polished permission shield UX with improved icon sizing and spacing
- Fixed chat scroll-to-bottom behavior and timeline tracking
- Enhanced tool output display with better path label detection
- Removed duplicate draft context details in chat header
- Added text selection menu to chat messages

**Git Improvements**
- Refreshed git history visual design with cleaner dividers
- Added remote removal action in sync selector
- Stabilized git polling to prevent excessive requests
- Improved tool output rendering for git operations

**Settings & Panels**
- Fixed mobile scrolling on settings pages
- Made outside-click settings close instantly
- Reduced settings load churn and CPU spikes
- Improved services dropdown layout and spacing
- Softened panel resize handles

**Desktop Integration**
- Synced macOS window theme with app theme
- Restored window dragging in sidebar header zones
- Fixed system window corners on macOS
- Improved header session metadata and action controls

**Button & Component Standardization**
- Unified button primitives across all components
- Standardized destructive action patterns
- Removed unused button variants (button-large, button-small)
- Aligned context tab close hit areas

---------

Co-authored-by: Iuliia Ivashko <yulia.ivashko@gmail.com>
2026-03-20 01:01:03 +02:00
Bohdan Triapitsyn fab32ca992 refactor(chat): complete turn-based pipeline and stabilize streaming, scroll, and tool UX (#629)
* refactor: add canonical turn projection pipeline for chat

Centralizes turn/activity/summary derivation, migrates render consumers to shared projection sources, and adds projector regression coverage for retries, malformed parent fallbacks, and activity segmentation.

* refactor: stream assistant text live in turn-first render path

* refactor: complete phase-3 turn timeline history/navigation

* refactor: complete phase-4 turn pipeline cutover

Remove legacy grouping adapters and lock key edge cases with executable regression tests so turn-centric rendering stays stable across retry/history/navigation flows.

* fix: align chat history loading path with opencode parity

* fix: stabilize chat streaming and reduce render churn

* feat: add blurIn streaming text animation and smooth lerp-based auto-scroll

Streaming markdown now uses Streamdown blurIn animation (150ms, ease-out) for per-word reveal
Auto-scroll during streaming uses continuous exponential smoothing (lerp) instead of restarting spring animations
One-shot scroll-to-bottom (button click) uses motion spring for natural deceleration

* fix: respect user scroll-up during streaming and re-pin on scroll back to bottom

* fix: stabilize activity stream and tool progress rendering

Activity now stays chronologically consistent and avoids duplicate reasoning/justification.
Tool rows show running state and duration earlier with smoother pending behavior.
Removed legacy trim/timestamp paths to prevent message drops and simplify Activity UI.

* fix: reverted regression of revert functionality

* fix: stabilize chat activity layout and tool progress behavior

Stopped user messages from re-animating when new user messages appear.
Made Activity and aggregated tool rows wrap inline consistently without misaligned headers/icons.
Fixed tool part matching so duration and shine no longer reset across pending/in-progress status updates.

* fix: restore activity mode behavior and stabilize tool rendering

Re-enabled Collapsed/Summary/Detailed/Changes defaults for Activity and tool expansion.
Fixed tool row wrapping/alignment, removed user fade re-animations, and disabled FadeInOnReveal animations.
Improved tool status handling to reduce timer/shine resets and made reasoning bold render as normal text.

* refactor: simplify chat tool and message rendering

Replace activity-group rendering with flat inline part rendering
Remove obsolete tool output and justification activity settings
Keep user message animation and improve edit/apply-patch file path headers

* style: narrow chat and composer columns equally

* fix: Tool status checks and abort flow regression

* fix: improve multi-file apply patch header rendering

* fix: make timeline message jump scroll to selected chat entry

Conversation Timeline clicks now reliably scroll to the selected message in chat
Timeline dialog waits for navigation result before closing to avoid silent failures
Chat message scrolling now resolves the active chat scroller consistently

* chore: remove branch-added chat tests and clean lint leftovers

Removes newly added chat and message test files from this branch
Fixes lint blockers in ProgressiveGroup, ToolPart, and messageStore
Keeps type-check, lint, and build passing after cleanup

* fix: keep composer status row closest to the input

Status/todo row now renders below queued messages and attachments.
Linked GitHub issue/PR chips stay above status so input context order is consistent.
Improves chat composer readability while assistant is working.

* fix: disable assistant header animations and polish chat status behavior

Assistant message header now renders without transition effects
Removed a lint issue from the working placeholder props
Chat status and message flow updates improve consistency during active turns

* feat: increase bottom spacer in mobile chat for better scrolling

* feat: add sorted chat mode with progressive activity rendering

Adds global chat render mode settings for sorted and live behavior
Renders non-stop assistant output in Activity as messages complete
Disables streaming text animation in sorted mode and restores classic reasoning/justification blocks

* fix: stabilize chat timeline navigation and older history loading

Prevents auto-loading older history from scroll and keeps loading behind the button
Fixes timeline jump-to-message behavior that snapped back to the bottom
Reduces chat list flicker and empty-state glitches on large session pagination

* fix: stabilize sorted activity text and hide reasoning timers

Prevented sorted justification from appearing before message completion by requiring explicit non-stop finish.
Removed duration values for sorted Thinking and Justification blocks.
Kept smooth sorted auto-follow behavior tuned to reduce bottom-jump jitter.

* refactor: simplify chat tool rendering paths and add parts docs

Centralized tool icon mapping into a shared presentation helper used by grouped and expandable rows.
Removed static-tool-specific branches from ToolPart to keep it focused on expandable tools.
Added a new DOCUMENTATION.md explaining where to change tool descriptions and rendering behavior.

* feat: enhance event stream handling for message parts

- Refactor event key building functions to improve clarity and reduce redundancy.
- Introduce `applyPartDelta` method in message store for handling delta updates to message parts.
- Update `useEventStream` to utilize the new `applyPartDelta` method for processing incoming deltas.
- Enhance text extraction logic to accommodate new part types and improve merging behavior.
- Implement logic to skip delta-driven updates for ongoing text/reasoning parts to prevent overwriting.
- Adjust server-side text merging logic to handle trimmed text comparisons and improve consistency.

* feat: enhance OpencodeService with delta handling and stale delta management

- Introduced globalSseStaleDeltas to track stale deltas.
- Added methods to generate keys for delta and updated part events.
- Updated global SSE loop to skip processing stale deltas.
- Refactored global SSE event handling to improve performance and reliability.

refactor: remove unused streaming part handling in messageStore

- Eliminated streaming part queue and associated logic.
- Simplified message merging logic by removing redundant checks.
- Streamlined message handling to improve clarity and maintainability.

chore: clean up unused streaming text normalization functions

- Removed deprecated functions related to streaming text identity and merging.
- Simplified normalizeStreamingTextPayload to focus on current functionality.

* refactor: simplify streaming event passthrough

Use SDK-native global event streaming in the UI client
Remove dead server-side SSE normalization helpers
Keep streaming payloads closer to upstream behavior

* revert: remove message memory settings UI

Drop the restored message memory settings screen
Keep session settings focused on current defaults
Preserve chat history limits as internal behavior

* feat: add compact collapsed Activity preview in sorted mode

Collapsed Activity now shows a live preview of the latest 7 entries
Older activity rows roll up behind a +N more indicator
The +N more indicator is clickable to expand the full Activity

* fix: adjusted activity changed files text prominence

* fix: make sorted mode snap to bottom when not streaming

* perf: show chat scrollbar only on user-driven scrolling

Chat scrollbar now stays hidden during automatic follow-to-bottom
Scrollbar visibility is gated by real user scroll intent
Reduced scroll overhead by removing timer-based intent tracking

* refactor: align web and vscode session activity streaming

Route VS Code SSE through SDK-based proxy flow
Restore controlled web session activity tracking and cooldowns
Keep chat activity behavior consistent across runtimes

* fix: adjusted muted foreground themes color

* fix: stabilize session activity and attention updates

Keep streamed chat state in sync as sessions complete
Restore reliable attention indicators for inactive sessions
Tighten server and UI event handling for chat updates

* feat: add chat render previews and file icon visibility setting

Added animated chat render mode previews in Chat settings.
Added a new toggle to show or hide file icons across tool rows.
Persisted chat render, activity, and mermaid modes to shared settings file.

* fix: update default GitHub OAuth client ID

Use the new OpenChamber org GitHub OAuth app ID by default.
Keep web server and VS Code auth defaults in sync.

* fix: sync chat settings across VS Code chat views

Broadcast settings changes to both sidebar and editor chat webviews.
Apply synced settings in each webview via shared settings refresh.
Ensures chat render mode updates appear immediately in editor tabs.

* fix: stop writing session folders file in VS Code workspaces

Disable disk persistence for session folders in VS Code webview runtime
Keep session folder state in VS Code/webview storage instead of project files
Preserve existing behavior for non-VS Code runtimes

* style: polish expanded tool panels and bash output readability

Removed extra borders/labels in expanded tool cards for a cleaner UI
Added softer scroll shadows and full-height decorative activity line
Switched bash command/output to code-style typography with tighter spacing

* feat: add default-open tool controls for chat activity

Added Chat settings toggles to open Bash and Edit tools by default
Default-open behavior now applies across both sorted and live activity rendering
Users can still manually collapse default-open tools, plus bash output height was reduced

* fix: align chat loading skeleton with message layout

Match skeleton width and horizontal alignment to chat message column
Add one-line tool-style skeleton rows with subtle circular placeholders
Increase top spacing so loading state no longer touches chat header

* fix: prevent blank screen when adding inline diff comments

Sanitize persisted inline comment drafts to ignore invalid data
Harden diff annotations against malformed line numbers
Fail safely when annotation updates error instead of crashing the view

* fix: streamline VS Code archived sessions sidebar

Show archived sessions in VS Code only for the active workspace directory.
Render archived sessions as a flat list in VS Code without project-root subfolders.
Hide the "Open in Side Panel" session action in VS Code.
2026-03-12 23:45:45 +02:00