On macOS trackpads, a light touch on a file/folder row can start a
native HTML5 drag after ~4px of movement, and Chromium then swallows
the resulting click. Track the drag start position and, on dragend,
treat a micro-drag (dropEffect 'none' and under 8px of travel) as the
click the gesture was meant to be.
Reconciled with main's OS file drag-drop upload handlers on the same
rows: kept onDrop/onDragOver/hasExternalFiles for external file
uploads, and wired the new click-recovery logic alongside it on both
the file tree row and the search-results row. Dropped the row's
cursor-grab/active:cursor-grabbing classes, since these rows are not
meant to read as draggable to the user.
Closes#2368
Merge main and reduce the change to the defect that reproduces: the server's
synchronous login-shell probes (env snapshot and command -v for opencode,
node, bun) ran with no timeout, so a slow or interactive rc file held startup
until it returned — on macOS that is what made a brew-installed opencode look
undetected from a Dock launch. Every probe now carries the same 5s bound the
Electron shell probe already uses and falls through on overrun; the known
install locations already include both Homebrew prefixes.
The non-login command -v fast path and the reproduction script are dropped:
a plain sh inherits the same PATH the resolver has already walked.
Closes#1720
Merge main and reshape the follow opt-out to the gestures the timeline was
missing: a middle-button press starts the platform autoscroll pan (the only
scroll gesture on wheel-less mice and tablets with a pointer), Shift+Space
scrolls up from the keyboard, and an upward wheel over a nested scroller that
still has room above stays with that scroller instead of releasing the chat.
The grace re-pin timer and scroll-direction tracking are dropped: returning to
within the end band already re-arms follow, and the mode machine is built
without timers on purpose. Pause/Break never move the viewport and are not
gestures.
Closes#1640
The list's animated maintainScrollAtEnd is single-flight and re-pins only
within a tenth of the viewport, so in a narrow viewport (VS Code sidebar) each
revealed block — several screens tall there — left the reader a second behind
and multiple screens above the live edge. The timeline hook now follows growth
itself: glide when within a viewport of the end, otherwise jump to one screen
above the end and glide the rest. Measured at 420x640: time spent >40px behind
dropped from 45% to 11%, max distance from 1682px to 382px.
Claude-Session: https://claude.ai/code/session_017TK5JAYDfT3Fotc23UEg98
Two findings from the openchamber-bot review at 478f1e9c:
1. Pending selections silently swallowed the Open-in-Finder pick
(prev #4). handleOpenInFinder flows into the batch-first branch of
finalizeSelection, so with checkboxes ticked the OS pick was
ignored and the selections were added instead. Clear selectedPaths
before finalizeSelection so the Finder-sourced target is honored.
2. Within-batch dedup was missing in the VS Code branch (nit from
the previous review). A path repeated within one batch hit
addWorkspaceFolder twice. Mirror the non-VS Code contract with
a seen Set; add a regression test asserting the host is called
once per unique path.
Chat sessions without a project were dropped from the mobile session tree
because no registered project owned their directory. The sheet now partitions
sessions like the desktop sidebar and shows Chats as a collapsible section above
the project tree, with the same rows, swipe actions and paging; search results
label them "Chats" instead of the raw directory name.
Claude-Session: https://claude.ai/code/session_017TK5JAYDfT3Fotc23UEg98
With "Follow new content while streaming" disabled, sending from mid-history
no longer anchors the new message at the top of the viewport; the reader stays
where they were and the scroll-to-bottom pill leads to the new turn. Sending
from the live edge still parks the message as before. The setting's info text
now describes this in every locale.
Claude-Session: https://claude.ai/code/session_017TK5JAYDfT3Fotc23UEg98
Chromium scrolls overflow:hidden ancestors when a textarea caret moves out
of view (PageUp/PageDown in the prompt box, long prompts), shifting the whole
app up and hiding the title bar with no way to scroll back. Snap html/body/#root
back to zero on any root scroll event in the web, desktop, VS Code and mini-chat
apps.
Claude-Session: https://claude.ai/code/session_017TK5JAYDfT3Fotc23UEg98
The VS Code app mounts VSCodeLayout, which exposes sessions, chat, and settings but not the shared GitView or its branch selector. The remote-tracking checkout path added for that selector therefore had no user-reachable caller in the extension.
Restore the existing VS Code checkout implementation and remove the VS Code changelog claim. The web runtime keeps the fix because its Git view exposes the branch selector.
Two non-blockers from the openchamber-bot review at b2ab4af15:
1. VS Code batch add reported a misleading failure. addProjects
returned [] unconditionally for the VS Code runtime because
addWorkspaceFolder is reached only by addProject. Iterate addProject
per path so valid selections succeed and the host is called once
per selection. addProjects is now async (returns Promise<ProjectEntry[]>);
the call site in DirectoryExplorerDialog awaits it; existing
tests in useProjectsStore.test.ts updated to await.
2. Turkish locale (tr.ts) was missing the two new keys that other
11 dictionaries received: actions.addSelected and browse.selectForAdd.
Add both with real Turkish translations: "Seçilenleri ekle" and
"Eklemek için seç".
Two nits from the openchamber-bot review at 65668f14:
- The disconnect handler carried a stale comment that referenced a
"removed:false payload gating" feature the rebase removed; the
helper below it does not do that gating. Drop the comment.
- The Hide button onClick ran setAuthPanelDismissedForId inside the
setShowAuthPanel updater (idempotent today, impure under StrictMode
double-invoke). Move both calls outside the updater.
Also rewrite the [Unreleased] changelog bullet to describe the
user-visible change (Connected + models visible for options.apiKey
providers) instead of internal mechanics ("source refetch + optimistic
auth mark"), and broaden the wording from "after saving" to cover
OAuth, custom-provider, and disconnect paths.
CodeMirror collapses a CRLF pair into one line break, so the document is
shorter than the string it was given. The composer derived the caret from
the JS string length, which put it past the end of the document and made
dispatch throw `RangeError: Selection points outside of document`.
Because the exception fires before the transaction applies, the document
never updates, the un-normalized text stays in React state, and the draft
persists as-is: every later visit to the session restores it and crashes
again, with no way out from the UI.
Derive the caret from the change set instead, in the controlled writeback
and in the imperative insert/replace handles.
fixes#3013
# Conflicts:
# CHANGELOG.md
# packages/vscode/CHANGELOG.md
Two blockers from the openchamber-bot review:
1. Space swallowed in the path input. The handler at
handleKeyDown was calling preventDefault on every Space,
turning paths with spaces into no-op strokes and toggling the
highlighted row instead. Gate the toggle on hasTrailingPathSeparator
(query) so Space is a literal character when the user is typing
a path or filter and only acts as a selection toggle when they
have navigated into a directory.
2. Batch path was unreachable when the filter had no exact match.
With checkboxes ticked and a typed filter that has no exact match,
shouldCreateTarget evaluated true and the primary action (Add
selected) called createDirectory for the typed text instead of
adding the selections. Move the batch branch above
shouldCreateSelection so explicit selections always win over the
single-target create path. Drop the trailing else-if (now
unreachable) which also lost { asProject: true }.
Since opencode 1.18.x, `POST /global/upgrade` requires a `target` semver in
the body. OpenChamber sent an empty object, so every "Update OpenCode" click
came back 400. The rejection arrives as `{name, data:{message}}`, which has
no `error` field, so the user was left with the bare status text: "Bad
Request".
Resolve the target from the latest release — the same lookup the upgrade
prompt already uses to decide there is anything to offer — and fail with an
explicit code when it cannot be resolved, rather than sending a body opencode
is guaranteed to reject. Read the upstream rejection message so a refused
upgrade explains itself.
The VS Code extension carries its own copy of this flow and had the same two
defects; both are fixed there.
fixes#3121
The previous fix added optionsApiKey to providerHasCredentials but
wired it through only one of the two call sites in ProvidersPage.tsx.
The auto-open effect at line 339 still omitted it, so a config-defined
provider whose only credential is options.apiKey would get the auth
panel force-opened on every selection while the summary beside it
said Connected; the dismissal resets on provider switch, so the
panel re-opens each time.
Restore the isEditableCustomProvider exemption that main's
requiresProviderAuth helper carried into both authStatusIncomplete
and shouldShowModelsSection. A keyless local custom provider (LM
Studio / Ollama style) regressed from 'models visible, no banner' to
'Credentials missing' with the models section hidden.
Rewire requiresProviderAuth (its only production consumer was lost
in the rebase) by delegating authStatusIncomplete to it. The helper
already encodes sourcesLoaded && !hasCredentials && !isEditableCustom
Provider, so the call site is one line and the contract matches main.
shouldShowModelsSection now accepts an optional isEditableCustom
Provider flag that lifts the credential gate for editable providers,
matching the exemption the ProvidersPage.test.ts:22 fixture asserts.
Tests: 15/15 pass. Was 14 in the previous commit; added one test
for the editable custom exemption.
The previous review flagged that providerHasCredentials misclassifies
working config-defined providers: Provider.key is only set by upstream
when exactly one declared env var resolves or an api-type auth.json
entry exists. Config providers only get options, so provider.<id>.
options.apiKey never reaches key. Result: a provider whose key is
embedded in opencode.json showed 'Credentials missing', lost the
Models section, and forced the auth panel open.
Add optionsApiKey to ProviderCredentialInput and check it in
providerHasCredentials alongside key and authSourceExists. This
matches what main's requiresProviderAuth helper used to do and
honors the OpenChamber docs contract that options.apiKey counts as
a usable login (walkthrough/DOCUMENTATION.md:134).
Wire the new field through ProvidersPage.tsx using a typed
indirection: the SDK Provider type does not yet expose options
publicly, so the read site casts the object to the known shape.
This keeps the call site type-safe without waiting for an SDK
update.
- shouldAutoOpenAuthPanel: re-introduce isEditableCustomProvider exemption
(was dropped when requiresProviderAuth was replaced). Custom providers are
editable directly in the form and must not be force-opened into the auth
panel on a stale sources snapshot.
- handleSaveCustomProvider + handleDisconnectProvider: route through
applyConfigReloadOrRecordDeferred so an externally managed OpenCode that
throws requiresManualRestart records deferred-restart guidance instead of
toasting a misleading 'mutation failed' for a write that already persisted.
- handleOAuthConnected: call markAuthWriteSucceeded so the page does not stick
on a stale 'Credentials missing' summary while the providers refresh lands
(OAuth previously only updated the deferred-restart payload).
- Sources effect deps: add settingsDirectory back so a directory switch while
the provider id is unchanged refetches the source snapshot (was swapped for
providerSourcesRevision in the prior rebase).
- Cleanup: drop dead oauthCodes state and copyTextToClipboard import; restore
the requiresOpenCodeRestartAfterOAuth import alignment.
13/13 ProvidersPage.test.ts still green.
Stop treating Provider.env length as credentials, refetch provider
sources after OAuth/API key writes, and respect an explicit auth-panel
Hide so OAuth-only providers do not stick on a stale "Credentials
missing" / empty-models state after a successful login.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
The variable was referenced at line 484 (the single-target create branch)
but never declared, so every non-clone add path (Add button, Cmd+Enter,
Open in Finder, mobile add) threw a ReferenceError that surfaced as a
'Failed to select directory' toast. Add the missing local declaration
matching the condition the original review intended: !isCloneMode &&
shouldCreateTarget && the target path equals the user-typed path.
The checkbox button calls togglePathSelection() on click but does not stop
event propagation, so the click bubbles to the parent row's onClick which
calls executeRow() -> browseToEntry(). With a mouse, every checkbox click
would (a) toggle the selection, (b) navigate into the directory, and (c)
the navigation effect would clear selectedPaths. The primary interaction
of the multi-select feature was unusable.
The existing handleQuickAdd helper avoids this exact bug by calling
event.stopPropagation() inside its onClick handler. Apply the same
pattern to the new togglePathSelection onClick.
Refs openchamber-bot review on #2877.