Commit Graph
59 Commits
Author SHA1 Message Date
Leonidandbashrusakh c1f19b1158 test(sync): preserve question bootstrap and reducer invariants (#3439)
* test(sync): preserve question bootstrap and reducer invariants

Port the main-independent test assets from the superseded hybrid V2
question PRs (#3266/#3267/#3288) onto current main's V1-only
architecture:

- listPendingQuestions: unscoped + per-directory fan-out, merge with
  id-dedupe (first wins), failure throws instead of empty success,
  malformed-item skip, true-empty success (client.questions.test.ts)
- question reducer invariants: idempotent upsert-by-id, replace-not-
  first-wins, removal by session/request pair, duplicate-terminal no-op,
  late-asked re-registration (no tombstone)
- bootstrap deferred-phase question merge: signature-based replace and
  disappearance-deletion, in-flight-change preservation (stale guard),
  retry-then-merge on transient failure

Tests only: zero production changes, zero V2 SDK calls, zero fallback
logic, zero raw V2 event aliases.

* test(questions): clarify V1 merge id-filter test title

---------

Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
2026-09-09 17:44:11 +03:00
Leonidandalvins82 c5086e0250 fix(session-goal): bound length recovery after truncation (#3278)
Thanks to alvins82 for tracing the truncation failure and to bashrusakh for adding bounded recovery and error precedence. We will finish the explicit Resume behavior in the same batch.

Closes #3255

Co-authored-by: alvins82 <alvins82@gmail.com>
2026-09-05 10:46:18 +03:00
Leonid c8d8d4a384 fix(sync): paginate child session discovery to avoid >200 truncation (#3271)
Thanks for fixing the child-session discovery cutoff and covering the paginated discovery path with a provider-level regression test.
2026-09-05 10:46:09 +03:00
Leonid ec86d738e2 fix(vscode): stop gating list-derived permission auto-accept on the V2 preflight (#3318)
Thanks for fixing both permission reconciliation paths. The fetchPermission documentation follow-up is maintainer-owned; #3259 remains open.
2026-09-05 02:14:17 +03:00
Leonid 2d4e920fbd refactor(client): remove dead legacy file/command wrappers (#3290)
Thanks for removing the unused wrappers and keeping the live file and command APIs intact.
2026-09-05 02:14:07 +03:00
bashrusakh a894a80c0d fix(agents): open duplicate editor on mobile 2026-08-25 07:37:02 +11:00
bashrusakh 04835308c2 fix(vscode): pair thinking label with selection foreground when highlighted 2026-08-20 12:00:02 +11:00
bashrusakh 8cf51db649 docs(changelog): credit contributor in model picker entries 2026-08-20 12:00:01 +11:00
bashrusakh f1b9308394 fix(vscode): pair selection foreground with highlight in model picker 2026-08-20 12:00:01 +11:00
bashrusakh 177dbe486d fix(sync): clear dismissed questions deterministically after reject/reply 2026-08-20 00:17:24 +11:00
bashrusakh 599dafcd8c fix(vscode): add project adds the chosen folder to the workspace 2026-08-19 10:38:54 +11:00
bashrusakh 3feacad69b fix(server): keep env-provided OPENCODE_BINARY when settings clear the override 2026-08-19 10:38:53 +11:00
Leonidandbashrusakh 200c02cd00 fix(terminal): auto-focus hidden input overlay on mobile web (#2319) (#2329)
On mobile web/PWA, useTouchTerminalInput is true, which gated the
autoFocus prop behind !useTouchTerminalInput, causing autoFocus to be
false and the hidden input overlay to never receive focus. The user
had to manually tap the terminal to trigger focus.

Remove the !useTouchTerminalInput condition so autoFocus works on
mobile the same way it does on desktop: when the terminal becomes
visible, focusTerminalInput() -> focusHiddenInput() is called,
positioning and focusing the hidden textarea/input portal that
captures virtual keyboard events.

Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
2026-07-21 09:45:54 +03:00
f5b4a267c0 fix(sessions): route new sessions to the correct project when server omits directory (#1637, #2270) (#2272)
* fix(sessions): route new sessions to the correct project when server omits directory

createSession() passed the server response's `directory` field to
setCurrentSession as `directoryHint`. When the server response omitted
the field (a common path), `directoryHint` was `null` and
setCurrentSession fell back to `opencodeClient.getDirectory()`, which
could still hold a stale directory from a different project.

That fallback caused:
- #1637: clicking `+` on one project while browsing another created
  a session grouped under the wrong project.
- #2270: in a parent Git repo with multiple child projects (some
  without their own Git), sessions from a child with its own Git were
  grouped under a sibling child without Git.

Capture the effective directory passed to the SDK and reuse it as the
fallback for the server response. This guarantees `setCurrentSession`
and `registerSessionDirectory` always see the directory the user
explicitly selected for the new session.

Add regression tests covering both reported topologies and the
no-override / no-server-directory preservation path.

* fix(sessions): prefer registered project paths

---------

Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-07-17 13:14:22 +03:00
bashrusakh 5004712801 fix(sync): route directory-less todo updates 2026-07-15 19:47:54 +11:00
Leonidandbashrusakh e1fc3882e3 fix(chat): restore editor font size styling in chat input (#2199)
Commit 5c6546245 removed the editorFontSize reading and inline font-size
styling from ChatInput, calling it 'unused'. However this code was the
core of the editor font size feature (feat/e0229917f) — it applies the
user's chosen font size to the chat input textarea.

Without it, changing Settings > Appearance > Editor font size has no
effect on the chat input textarea. The CodeMirror editor (FilesView,
PlanView) was unaffected because its font size path was not touched.

This reverts the ChatInput portion of 5c6546245, restoring:
- useUIStore selector for editorFontSize
- inline fontSize style on the textarea element

Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
2026-07-14 14:27:48 +03:00
9624d4b6f6 fix(ui,server): normalize Windows drive letter casing for consistent path resolution (#2154)
* fix(ui,server): normalize Windows drive letter casing for consistent path resolution

Provider settings were failing to persist for specific projects on Windows
because path normalization was inconsistent across the codebase. Some
normalizePath functions uppercased the Windows drive letter (c:\ -> C:\)
and others did not, causing:
  - directoryScoped cache misses (different keys for the same directory)
  - broken model selection in the affected project
  - lost conversation history (sessions could not match their project)
  - false cache hits in resolveConfigDirectory on undefined inputs

This change extracts a single shared normalizePath utility and uses it
from the 5 client sites that were missing the drive letter normalization.
The server-side normalizePathForPersistence is updated to uppercase the
drive letter both before and after safeRealpathSync, so the persisted
path is consistent even when realpath returns a symlink/junction with
a lowercase drive letter on some Windows environments.

Fixes #2109

* test(ui,server): add coverage for Windows path normalization

Address review feedback on #2154:
- Add Windows-platform test for normalizePathForPersistence covering
  drive letter uppercase on input and after realpath resolution
- Add dedicated test suite for the shared normalizePath utility
- Defensive fix: normalizePath now returns null for paths that consist
  only of slashes (\\, ///), matching the documented contract

Refs #2154

* fix(server): scope drive casing normalization to Windows

---------

Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-07-13 01:43:35 +03:00
Leonidandbashrusakh 002c7a70ad fix(ui): dispatch queued messages when session is already idle (#1978)
Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
2026-07-11 16:14:44 +03:00
Leonidandbashrusakh 01a52eccab SDK v1.17.12: session.permission — programmatic create/fetch, more reliable auto-accept (#1982)
* docs: add SDK v1.17.12 migration plan — phase 4 (session.permission)

* feat(permissions): verify pending permission before auto-accept via SDK v1.17.12

Adds createPermission() and fetchPermission() wrappers on OpencodeService
for the new v2.session.permission endpoints (OpenCode SDK 1.17.12).

fetchPermission() is used by the auto-accept sweep in
resyncBlockingRequestsForDirectory to verify a permission is still
pending before replying. The auto-accept flow now skips permissions
that are already resolved, returning a null from fetchPermission()
rather than blindly calling respondToPermission on a stale entry.

createPermission() is exposed for future programmatic permission
creation; the V1 list/reply path used by the UI is unchanged.

The plan doc at plans/opencode-v1.17.12-sdk/ was rebased onto
origin/main in the prior commit to keep the PR diff focused on
this change.

Closes #1972

* fix(permissions): drop confirmed-resolved permissions from auto-accept resync

fetchPermission() now returns a tagged FetchPermissionResult so the
auto-accept loop can distinguish a server-confirmed 404 (the
permission is no longer pending) from a fetch failure (network error
or pre-v1.17.12 server). Previously both cases collapsed to null, so
a permission the server had already answered would still appear in
the resync output and trigger a spurious 'Permission needed' toast.

The auto-accept loop in resyncBlockingRequestsForDirectory now tracks
both accepted and resolved permissions, then drops both from the
'grouped' map before it falls through to the toast path. On a
pre-v1.17.12 server (no V2 endpoint) the call still returns
'unknown' and the permission stays in the resync output so the user
can answer manually — fail-closed, no false-resolved signals.

Adds a focused unit test for fetchPermission (4 cases: 200 ok, 404
resolved, 500 unknown, network throw) mocking the V2 SDK client
shape.

---------

Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
2026-07-11 16:11:13 +03:00
Leonidandbashrusakh 6d7ea82d86 perf(worktree): skip unchanged store updates and content-aware persist (#1992)
* perf(worktree): skip unchanged store updates and content-aware persist

- Add content-aware equality check before setState in all three discovery
  loops (SessionSidebar, ElectronMiniChatApp, MobileApp). Compares
  Map size and per-entry length + element references — avoids triggering
  16+ subscriber re-renders when discovery finds the same worktrees.
- Add content-hash guard to persistWorktreeMap subscription with try-catch.
  Avoids redundant localStorage writes when the Map reference changed but
  the content is identical. Serialization errors are caught and skipped.

Contributes to #1990

* perf(worktree): extract shared worktreeMapsEqual, fix comparison, avoid double serialization

- Extract worktreeMapsEqual() into worktreeManager.ts as a shared utility
  comparing worktree maps by path (not reference identity). This replaces
  the inline reference-comparison logic in all three discovery loops
  (SessionSidebar, ElectronMiniChatApp, MobileApp) that was ineffective
  because readStableProjectWorktrees creates new object instances on
  each call after cache expiry, making item !== value[i] always true.
- Pass pre-serialized JSON to persistWorktreeMap to avoid double
  JSON.stringify on every persist. The subscriber already computes the
  serialized string for the content-hash check; pass it through instead
  of re-serializing inside persistWorktreeMap.
- Deduplicate 3 copies of the same comparison logic into the shared util.

* refactor(worktree): make worktreeMapsEqual generic over path-bearing type

The helper's equality contract is element-wise path comparison,
not anything specific to WorktreeMetadata. Generifying on
`T extends { path: string }` documents the contract at the type
level and keeps it reusable for any future map-of-arrays shape
that has a path field. Call sites stay compatible since
WorktreeMetadata has a required `path: string`.

No runtime change.

* refactor(worktree-store): clarify persist hash name and signature

Drop the optional preSerialized parameter from persistWorktreeMap —
its only caller (the subscriber) already builds the serialized
string for the content-compare, so the dual-path body is dead code.
persistWorktreeMap now takes the serialized string directly.

Rename lastPersistedWorktreeHash → lastPersistedWorktreeSerialized
(the variable holds the full JSON string, not a hash) and drop the
try/catch around JSON.stringify: it cannot realistically throw on
Map.entries() of WorktreeMetadata (no circular refs, no BigInt, no
custom toJSON). The try/catch around setItem stays — it can throw
on quota errors.

No behavior change in the success path.

* docs(worktree): trim repeated call-site comments

Replace the 5-line explanation block (copy-pasted in all three
discovery loops) with a one-liner that points at the worktreeMapsEqual
JSDoc. The '16+ subscribers' framing is also dropped — the helper
itself is general-purpose and the precise number was fuzzy.

* fix(worktree): compare branch in worktreeMapsEqual to avoid stale sidebar label

The helper compared entries by path only. An external git checkout
between discoveries changes branch (and the derived label /
headState) while path stays the same, so the helper returned true
and the store update was skipped — leaving a stale branch label in
the sidebar until the next worktree create/remove or project switch,
since there is no periodic worktree-list refresh.

Compare branch in the inner loop alongside path. Tighten the generic
constraint to T extends { path: string; branch: string } so the
contract is documented at the type level.

worktreeStatus is intentionally NOT compared: status transitions go
through setStoredWorktreeStatus, which writes a fresh Map reference
that the persist subscriber picks up directly. Adding worktreeStatus
to the contract would also force the sidebar to detect status changes
that the persist path already handles, and would couple this helper
to a field whose semantics differ from the discovery path.

Fixes the staleness concern raised by openchamber-bot in PR #1992.

* test(worktree): cover worktreeMapsEqual edge cases

Documents the helper's equality contract and guards against
regressions in the path+branch comparison. Eight cases:

- two empty maps
- identical entries (path and branch match in order)
- same path, different branch — the F1 regression case
- different paths at the same index
- per-project array length mismatch
- project-key count mismatch
- positional reorder (helper is order-sensitive)
- non-first-entry branch difference (subset detection)

All 10 tests in the file pass (2 existing + 8 new).

* ci: retrigger checks

* test(worktree): add benchmark for worktreeMapsEqual and persist path

Documents the actual cost of the PR #1992 optimizations on representative
sizes (1-1000 worktrees per project, 1-50 projects), so future contributors
can reproduce the numbers and detect regressions in the equality helper or
the persist subscriber.

Run with: `bun run packages/ui/src/lib/worktrees/worktreeManager.bench.ts`

Measured on V8 (one example run):
- worktreeMapsEqual early-exit (50×20 with first project differing):
  412 ns/op vs 33,034 ns/op full sweep — ~80x speedup when any project
  actually changed.
- F1 path+branch overhead vs path-only (10×50): +2.3 µs (+15.8%) on a
  full sweep; on the early-exit path the F1 cost is irrelevant.
- Stringify dedup in persistWorktreeMap subscriber: 67% saved (552 µs
  per persist on 10×50). This is the main absolute win of the PR.
- Content-compare guard: 19-29 ns/op, free relative to the stringify it
  gates.

Bench file is standalone (import.meta.main guard) — does not run as part
of `bun test`, does not import React, does not touch localStorage.

---------

Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
2026-07-11 16:06:58 +03:00
Leonidandbashrusakh 76c3bb5fd9 fix(agents): preserve YAML frontmatter fields when saving agent settings via UI (#2004)
Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
2026-07-11 16:05:16 +03:00
Leonidandbashrusakh 420582984e fix(sync): keep pending questions answerable after restart (#2005)
Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
2026-07-11 16:04:41 +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 e5bba59a75 fix(worktree): restore last source branch reliably (#2030)
* fix(worktree): restore last source branch reliably

* chore: retrigger review

---------

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

* chore: retrigger PR checks

---------

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

* fix(sync): clarify rename mirror flow

* fix(sync): clarify archive comment

---------

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

* fix(test): use supported bun assertions

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

---------

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

* fix(session): type sidebar persistence test

* fix(session): remove invalid sidebar persistence harness

---------

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

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

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

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

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

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

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

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

---------

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

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

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

Closes #976

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

---------

Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
2026-07-11 14:46:41 +03:00
e0229917f8 feat(settings): editor font size for chat input and code editor (#1325) (#2065)
* feat(settings): add editor font size setting for chat input and code editor

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

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

Refs #1325

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

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

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

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

---------

Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-07-11 14:45:27 +03:00
Leonidandbashrusakh bfaf62e222 fix(sidebar): add project sort modes (#2067)
* fix(sidebar): add project sort modes

* fix(sidebar): preserve manual sort order

* fix(sidebar): move sort state before render

---------

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

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

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

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

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

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

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

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

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

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

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

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

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

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

---------

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

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

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

Fixes #2088

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

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

---------

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

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

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

Closes #2090

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

re-fetches the root and every expanded directory while preserving

childrenByDir. This keeps the file tree expanded instead of collapsing

it to the root on every manual refresh.

Closes #2036

Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
2026-07-11 14:34:03 +03:00
Leonidandbashrusakh ec61cf3573 fix(auth): narrow mobile auth fallback (#2046)
Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
2026-07-05 23:33:23 +03:00
f13f6d5540 feat(#1766): support OpenCode steer delivery / follow-up behavior settings (#1781)
* feat: support OpenCode steer delivery / follow-up behavior settings

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

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

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

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

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

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

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

Validation: type-check clean, lint clean.

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

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

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

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

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

---------

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

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

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

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

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

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

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

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

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

---------

Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-29 00:42:13 +03:00
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
bashrusakh c6c9338aca fix(web): make managed process reaper async 2026-06-27 05:50:54 +11:00
8c1a24089d fix(worktree): gate sessions on bootstrap readiness (#1762)
Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-26 12:16:42 +03:00
bashrusakhandLeonid Skorobogatyy 03e6f789a4 fix(git): materialize draft session for generate (#1761)
* fix(git): materialize draft session for generate

* fix(sync): remove redundant draft session side effects

---------

Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
2026-06-26 11:52:39 +03:00
bashrusakhandLeonid Skorobogatyy 569342b3c2 fix(markdown): preserve user code block characters (#1750)
Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
2026-06-24 18:33:50 +03:00
076e9331ec fix(agents): send null to clear temperature/topP overrides on update (#1718)
When clearing temperature or topP on an existing agent, the UI sent
undefined which JSON.stringify drops, so the server never received the
clear command. Now sends null to properly remove the override in
opencode.json.

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

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

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

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

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

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

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

* chore: trigger re-review

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

* fix(agents): preserve thinking variant values

---------

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

Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
2026-06-23 22:38:49 +03:00
bashrusakhandLeonid Skorobogatyy 6e68015389 fix(agents): use isPrimaryMode consistently across all agent pickers (#1713)
* fix(agents): use isPrimaryMode filter for agent picker

Fix #1527: agent picker filtered by mode !== 'subagent' which missed
agents with unexpected mode values. Now uses isPrimaryMode() which
only includes 'primary', 'all', undefined, and null — the semantically
correct set of agents that should appear in the picker.

* fix(agents): use isPrimaryMode consistently across all agent pickers

Updated AgentSelector.tsx to use isPrimaryMode instead of mode !== 'subagent'.
Removed duplicate isPrimaryMode definition from useConfigStore.ts and
imported the shared helper from mobileControlsUtils.

---------

Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
2026-06-23 22:34:51 +03:00
bashrusakhandLeonid Skorobogatyy ac0f173655 fix(chat): preserve tool duration across session switches (#1712)
* fix(chat): preserve tool duration across session switches

Fix #1636: ToolPart.tsx reset pinnedTime to empty on unmount/remount,
causing LiveDuration to not render on first paint. Now initializes
pinnedTime from server-provided time?.start/time?.end in the useState
initializer, eliminating the one-frame gap.

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

---------

Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
2026-06-23 22:30:04 +03:00
bashrusakhandLeonid Skorobogatyy 57cef1b278 fix(sidebar): correct expansion-key format for virtualizer buffer (#1711)
* fix(sidebar): increase virtualizer buffer for expanded parents

Fix #1530: archive sub-session layout broken because the virtualizer
used a fixed 28px height estimate per row. Expanded parents with inline
children are much taller. Now dynamically increases bufferSize when
expanded parents are present.

* fix(sidebar): correct expansion-key format for virtualizer buffer

The expansion key was using raw sessionId instead of the scoped format
'project:{archived|active}:{sessionId}'. This made hasExpandedParent
always false, so bufferSize never increased. Also removed dead
hasSessionSearchQuery branch.

---------

Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
2026-06-23 22:24:11 +03:00
bashrusakhandLeonid Skorobogatyy a25fc4c25a fix(sync): reflect share status from global store after cancel (#1709)
* fix(sync): reflect share status from global store after cancel

Fix #1551: unshareSession() called updateLiveSession() which silently
fails when the child store doesn't exist. The sidebar rendered from the
child store first, showing stale share data. Now overlays the global
session's share field at merge points.

* fix(sync): extract shared mergeLiveSessionWithGlobalSession helper

Extracted the share-field overlay into a single shared helper in
useGlobalSessionsStore.ts. All 3 merge sites now use the helper
instead of duplicating the overlay logic.

* test(sync): add unit tests for mergeLiveSessionWithGlobalSession helper

---------

Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
2026-06-23 22:18:44 +03:00
5f3ef320d2 fix(session): bind new sessions to selected project (#1708)
* fix(session): bind new sessions to selected project

Fix #1521: openNewSessionDraft() always used currentDirectory even when
the user selected a different project. Now prefers the selected project's
path when no explicit directory is provided.

* test(session): add unit test for openNewSessionDraft project binding

---------

Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-23 22:14:33 +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
bashrusakhandLeonid Skorobogatyy 4feddfa810 fix(auth): honor OPENCODE_SERVER_USERNAME env var for basic auth (#1705)
Fix #1685: the Basic auth header for the OpenCode server was hardcoded
to use the username 'opencode', ignoring OPENCODE_SERVER_USERNAME. Users
who set a custom username got 401 errors because the server expected a
different credential.

Both call sites (web server auth-state-runtime.js and VS Code
extension opencode.ts) now read process.env.OPENCODE_SERVER_USERNAME
with a fallback to 'opencode' to preserve prior behavior.

Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
2026-06-23 21:50:14 +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
9199798a14 perf(right-sidebar): gate live effects, memoize lookups, always-mount tabs (#1674)
* perf(right-sidebar): gate live effects, memoize lookups, always-mount tabs

Performance fixes for the right sidebar (git/files/context tabs).

== Correctness / leak fixes (P0)

* RightSidebar: drop dead useEffect that re-nulled refs the resize
  handler already nulled; collapse the redundant width/minWidth/maxWidth
  triple into width + the existing --oc-right-sidebar-width variable.
* useUIStore: clamp setRightSidebarWidth to [MIN, MAX]; simplify
  setRightSidebarOpen (22 lines -> 12).
* RightSidebarTabs: useRightSidebarGitSync now takes the right tab and
  main tab and only polls when the right git tab is the visible consumer
  and the browser is online + visible. Replaces a global poll that
  fired for the lifetime of the open sidebar.
* GitView: commit-files fetch refactored to cancelled + Promise.all
  (was a per-hash loop that could setState after unmount); getRemoteUrl
  and refreshRemotes gated on cancelled/mountedRef; new module-scoped
  mountedRef guards setIsSettingIdentity from firing after unmount.
* GitView + useGitmojiList: extract gitmoji fetch/cache into a hook
  with module-level inflight promise + subscribers Set; stale-while-
  revalidate from localStorage; ensureLoaded() for call-site-initiated
  hydration; cancelled flag on setIsLoading to avoid the React
  setState-after-unmount race.
* ProjectNotesTodoPanel: 400 ms notes debounce now cancels on blur
  (was double-saving); persistProjectData chained per project through
  a module-level Map<projectId, Promise> so a fast todo toggle racing
  the debounced save no longer hits the server in parallel; resize
  auto-adjust guards against same-value pings.

== Render fanout (P1)

* RightSidebarTabs: all three tab content components are now always
  mounted with the hidden attribute. State and cache survive tab
  switches. When activeMainTab === 'git' (or 'context') the matching
  right tab is filtered out of the tab strip and a redirect effect
  snaps any persisted-but-now-hidden right tab to 'files'. onSelect is
  now a type-guarded handler instead of `as RightTab`.
* GitView: 13 separate useGitStore action selectors collapsed into one
  useShallow block (one re-evaluation per store change instead of 13).
* GitView: new isGitViewActive flag (true when this instance is the
  visible consumer) gates the 7 live effects — load identities, fetch
  remote URL, refresh remotes, ensureAll, sessionEvents.onGitRefreshHint,
  worktree bootstrap poll, default-identity auto-apply. Hidden
  GitView instances no longer run these.
* GitView: gitViewSnapshots module-level Map is now backed by an
  LRU wrapper (cap 20) so per-directory draft snapshots cannot leak
  across hundreds of project switches. Removed the dead `unique.set`
  dedup in changeEntries — GitStatus.files is already unique by path.
* SidebarFilesTree: statusByPath Map<path, FileStatus> and
  badgeByDir Map<dirPath, { modified, added }> are precomputed once
  per gitStatus change. Tree render is O(1) per node instead of O(N)
  per node via the previous per-row find/scan. badgeByDir walks each
  file's path segments and increments counters for every ancestor
  dir, so total cost is O(N + total_dirs_in_files) per gitStatus
  change.
* SidebarFilesTree: FileRow wrapped in React.memo with a custom
  comparator. Context-menu open state moved INTO FileRow as local
  state — opening a menu in one row no longer re-renders siblings.
* SidebarFilesTree: loadDirectory accepts an isCancelled predicate;
  the batch-load effect for expandedPaths passes a stable predicate
  so per-dir fetches stop touching state once the effect tears down.
* SidebarFilesTree: module-level fileTreeCacheByRoot Map (LRU,
  cap 8 roots) hydrates childrenByDir / loadErrorsByDir /
  loadedDirsRef on mount or root change. Mirror effects write state
  back to the cache. Survives close-and-reopen of the right sidebar;
  populated entries are dropped on unmount only when they had no
  data.

== Result

Net diff: 6 files modified, 1 new (useGitmojiList.ts), 682 insertions,
283 deletions. Existing test suite baseline preserved (537 pass / 58
fail / 1 error) — no new regressions. The 58 pre-existing failures are
in unrelated chat/streaming tests and were verified via git stash on
the same branch.

Architecture assumptions, verified by manual review:
- P1.1's redirect effect snaps rightSidebarTab to 'files' whenever
  activeMainTab === 'git', so the right and main GitView instances
  are mutually exclusive — isGitViewActive cannot be true for both.
- The 7 gated effects plus the useRightSidebar GitSync poll cover all
  cases where git state should advance: visible consumer fetches; the
  poll keeps the store warm when only the right git tab is visible.
- The aborted loadDirectory predicate is sufficient because
  inFlightDirsRef and loadedDirsRef dedup at the call site before
  any network IO is initiated.

* fix(sidebar): always clean up inFlightDirsRef regardless of cancellation

* refactor(sidebar): deduplicate RIGHT_SIDEBAR_MIN/MAX_WIDTH constants, export from useUIStore

* docs: split right sidebar perf plan into standalone file, clean up merged master status from chat plan

* fix(git): gate GitView effects by instance visibility

---------

Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-16 13:52:05 +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