Commit Graph
100 Commits
Author SHA1 Message Date
Bohdan Triapitsyn 4e00446adb fix(settings): allow overlay scrollbars in settings 2026-09-07 23:27:42 +03:00
Bohdan Triapitsyn 0753add360 Merge main into bohdan/dev after BTW composer integration 2026-09-07 23:18:17 +03:00
Bohdan Triapitsyn 2ec0238e5c feat: add an always-show scrollbars preference
Adds a device-local setting to keep overlay scrollbars visible.
Surfaces the setting in visual settings and settings search.
Updates scrollbar behavior and tests for the new preference.
2026-09-07 23:16:36 +03:00
Bohdan Triapitsynandalvins82 7b206b1014 fix(chat): release thinking follow when dragging the scrollbar
Retain the capped reasoning implementation and adopt the scroll-position release behavior from #3394. Cover pause, resume, and observer cleanup on the mounted component.

Co-authored-by: alvins82 <alvins82@gmail.com>
2026-09-07 20:38:00 +03:00
Bohdan Triapitsyn f9d6b5c479 fix(updater): verify native host version and report restart failures
Complete #3227 by keeping browser completion polls on the native updater, checking the requested version, and preserving retry access after a failed restart.
2026-09-07 20:38:00 +03:00
Bohdan Triapitsyn c82aa7879b fix(git): honor standalone credential-helper permission
Complete the createGit option handling introduced in #3383.
2026-09-07 20:37:47 +03:00
Bohdan Triapitsyn cffd903f56 fix(sync): reject obsolete interrupted-turn recovery responses
Follow up #3396 with runtime, SDK, and request ownership guards across hydration and reconnect.
2026-09-07 20:37:47 +03:00
Bohdan Triapitsyn 45bc61f8f9 fix(network): apply connection timeout in server and extension hosts
Complete the runtime entrypoints from #3404 without changing address-family selection.
2026-09-07 20:37:46 +03:00
Bohdan Triapitsyn ab7ee73992 fix(vscode): validate Ollama quota responses consistently
Share the Ollama request and parser between credential validation and quota refresh so unparseable pages cannot produce successful empty usage. Reject redirects and bound requests with a timeout while preserving both plan formats.

Tested with 94 quota tests, VS Code type-check and ESLint, and the extension build. Reviewed dead-code output; existing anti-slop findings remain outside the changed code.
2026-09-07 19:41:40 +03:00
Bohdan Triapitsyn c9bef8bc05 style: increase chat text and heading contrast
Boosted assistant message text opacity for better readability
Styled markdown headings with a slightly stronger foreground color
2026-09-07 19:26:23 +03:00
Bohdan Triapitsyn 0449d65921 chore: share the project setup and actions through .openchamber/project.json
Track the worktree setup command, the DevHMR project action and the plans
directory so they follow the repository; screenshots stay ignored.
2026-09-07 17:58:03 +03:00
Bohdan Triapitsyn ec089b34e8 fix(chat): keep streaming thinking inside its capped scroll box
A streaming Thinking block grew without a height cap and only moved into
the capped, scrollable box once it finished, so a long thought pushed the
whole timeline down while it streamed. The cap was dropped in June because
the box then pinned to its own end on every tick and captured the wheel,
so the chat could not be scrolled while thinking streamed.

The box is capped in every state now and follows its own end only while
streaming and only until the reader wheels or drags upward inside it;
returning to its end re-arms the follow. It is marked as a nested scroller,
so an upward wheel scrolls the box first and reaches the chat once the box
sits at its top. Growth is observed on the content box because markdown
commits asynchronously.

Testing: reasoning block tests extended for the capped, nested box while
streaming; ui type-check and lint; web build. Live reasoning stream not
exercised in a browser.
2026-09-07 17:58:03 +03:00
Bohdan Triapitsyn 13bc0a68e2 fix(chat): give the sorted Activity block the live timeline's row rhythm
The Activity block stacked its tool rows with an extra 0.375rem gap on top
of each row's own padding, while the live timeline stacks the same rows
with nothing between them. Both views now share the 36px row step.

Testing: measured both render modes on one session in headless Chrome;
ui type-check and lint.
2026-09-07 17:58:03 +03:00
Bohdan Triapitsyn 78e3ab9744 fix(chat): keep the dirty-branch tooltip quiet for an auto-opened draft
At cold launch the container opens a new-session draft as a placeholder
until the last session restores. The draft's dirty-directory warning
announced itself by opening its tooltip for five seconds, so on mobile the
tooltip appeared alone over an almost empty screen and vanished with the
draft once the session came back.

The draft state now records that the app opened it automatically, and the
warning does not flash for such a draft. The icon still shows and the
tooltip stays reachable by hover or long press; a draft the user opens
flashes as before.

Testing: ui type-check and lint; session store and composer tests; web
build.
2026-09-07 17:58:03 +03:00
Bohdan Triapitsyn 4915658de1 fix(sync): replace every optimistic part of a just-sent message
The server echoes a sent message part by part. The reducer replaced an
optimistic part with its server echo only while the FIRST part of the
message was still optimistic, so once the text echo had landed the file
echo no longer qualified and was appended instead: an attached image
showed twice until the next page fetch rewrote the parts.

Any remaining optimistic part of the same type is now a replacement
candidate. The scan only runs when a part with a new id arrives, which is
once per part during assistant streaming.

Testing: reducer test extended past the first echo to the file echo; ui
type-check and lint; verified the event order against a live OpenCode
(message, text part, file part within ~70ms of the accepted request).
2026-09-07 17:58:03 +03:00
Bohdan Triapitsyn e3b0088c60 refactor(chat): drop the anchored end space after sending a message
Sending used to park the new user message near the top of the viewport by
reserving blank space below it and holding the viewport there while the
reply streamed in. The reserved space showed up on its own at times and the
hold fought other corrections; with a reliable end-follow and pin in place
the effect no longer pays for its machinery.

The anchoring mode, its arm/claim/position/settle lifecycle, the reserved
end space passed to the list and the anchored-turn geometry are removed.
Sending from the live edge is now an instant return to the end, and the
reply is kept in view by ordinary end-follow; sending from mid-history with
auto-follow off still leaves the viewport untouched.

Testing: scroll module tests trimmed to the remaining geometry; ui
type-check, lint and dead-code report; web build.
2026-09-07 17:58:02 +03:00
Bohdan Triapitsyn 34c8887fc1 feat(chat): tighten markdown typography and unify text selection colour
Chat prose read looser than the rest of the app: 15px body with a single
line-height for everything, headings barely larger than the text, list
bullets drawn as dashes with a 2em gutter, no vertical rhythm for lists or
quotes, and inline code that took a syntax colour from the theme.

Body text is 14px with a whole-pixel line height (22.75px rounded to 23px
so baselines stay on the pixel grid). Every block carries the same 0.65rem
margin on both sides, so any two neighbours are one spacing apart and the
outer blocks add nothing. Headings step 20/18/16/14px at weight 600 with
more air above than below. Lists use native markers that cycle by depth
with a 1.25rem gutter and 0.25rem between items; task-list checkboxes
replace the marker. Blockquotes get their left rule back. Headings, list
markers and inline code render in the text colour; inline code is a muted
chip one step smaller than the prose. The inlineCode theme tokens are
removed from the built-in themes, the generator and the VS Code adapter;
custom themes that still carry them are simply ignored.

The remaining semantic sizes shrink by the same step (code 12px, labels and
meta 13px, settings title 17px), in VS Code proportionally.

Text selection in chat used the browser default in dark themes while the
comment overlay painted the theme's row-selection token, so the colour
changed the moment Comment was clicked; that token is also nearly the page
background in several light themes and opaque in monokai, where the overlay
hid the text. Native selection, file-preview selection and the overlay now
share one translucent accent tint (primary at 30%), which stays visible over
every built-in theme's background.

Testing: ui type-check and lint; all theme JSON re-parsed; screenshots of
prose, lists and the selection/comment states in headless Chrome; contrast
of the new selection tint computed against every theme's background and
text.
2026-09-07 17:58:02 +03:00
Bohdan Triapitsyn 37f5021715 fix(chat): keep a pinned reader on the end through panel and window resizes
Opening a panel or resizing the window while pinned to the end of an idle
session bounced the timeline and left it above the end with the pin
released. Every row re-wraps on a width change and the list's total
content length lags a frame behind the rows, so anything scrolling to the
end from it (the list's own maintainScrollAtEnd, scrollHeight) landed on a
blank tail or short of the real end; the idle branch then released the pin
instead of recovering.

An idle session no longer hands end-keeping to the list at all: the hook's
same-frame pin owns it, and during a width resize it holds the measured
bottom of the last real row plus the list footer, for streaming readers
too. The footer size was missing from that measurement (the list does not
expose it through getState) and left the viewport short by the tail
spacer; it now arrives through onMetricsChange. Size compensation applies
only to a reader who left the end.

The follow re-arm band is half a viewport instead of 40px: leaving the end
is decided by a real gesture only, so the band now only decides how close
a reader must come back before follow re-arms.

Testing: scroll module tests updated (footer in the real end, half-viewport
band with a 40px floor); ui type-check and lint; measured in headless
Chrome that scrollTop equals the maximum on every frame of a panel toggle,
a height change and a combined resize, where the previous build ended 122px
and 462px above the end.
2026-09-07 17:58:02 +03:00
Bohdan Triapitsyn 85c4320825 Settings storage with scopes, and project setup that can live in the repository (#3413)
* refactor(settings): settings registry and intent-gated writes

Problem: every setting lived in a flat document with ten hand-maintained
key lists that had drifted (three keys the server silently dropped, five
it kept that nothing read), and three code paths wrote to the server
without a person changing anything: the theme persist effect on mount,
bootstrap seeding of server-missing keys, and the auto-save echoing
values just adopted from the server.

Approach: one registry (packages/ui/src/lib/settings/registry.ts) names
every key with its scope (instance / profile / device), a boundary parser
and its store binding; DesktopSettings, the sanitizer, the mirror, the
apply step and the auto-save derive from it. A generated JSON snapshot
carries the key list to the server and the VS Code bridge. Writes carry
intent: the theme context writes only from its user-facing setters, a
missing server key leaves the local store alone instead of resetting it,
updateDesktopSettings drops values the server already holds, and the
auto-savers treat values applied from the server as a new baseline.

Testing: bun test packages/ui (registry + persistence suites cover zero
writes on load, dedup, toggle-back cancellation, failed-save retry, and
snapshot freshness); tsc for every workspace.

* refactor(ui): read and write settings through the shared path only

Problem: fourteen pages and stores fetched /api/config/settings on their
own and re-parsed the raw document by hand, so the registry could not
guard them and two of them treated a failed load as an empty list.

Approach: loadDesktopSettings() and updateDesktopSettings() (which now
resolves { ok }) replace every direct call; SkillsCatalogPage and
AddCatalogDialog refuse to write the catalog list until it is known.

Testing: bun test packages/ui (403 files), eslint on the changed files.

* refactor(server): validate settings writes against the registry snapshot

Problem: the server whitelist was the only guard on PUT /api/config/settings
and had drifted from the client; dead keys were still persisted.

Approach: settings-helpers.js drops any key the generated registry
snapshot does not list as persistable and strips secret keys from
responses; the dead keys (markdownDisplayMode, toolCallExpansion,
typographySizes, expandedEditorToolbar, gitProviderId/gitModelId) are
gone; the profile keys that were client-only now round-trip. A drift
test requires a valid sample for every persistable registry key.

Testing: vitest run in packages/web (182 files), including the packed
tarball import.

* refactor(vscode): gate bridge settings writes by the registry

Problem: the extension host wrote any key the webview sent straight into
settings.json, and commit-message generation read the dead
gitProviderId/gitModelId pair instead of the small-model setting.

Approach: filterPersistableSettingsChanges applies the registry snapshot
before the file write; chooseBridgeGitGenerationModel honours
smallModelUseDefault/smallModelOverride ahead of the zen fallback.

Testing: bun test packages/vscode (37 files), tsc, build:extension.

* feat(settings): split the user's profile into preferences.json

Problem: one flat settings.json held instance facts, the user's
preferences and device state together, so device state travelled between
installs and the profile had no document of its own to sync from.

Approach: the server keeps one merged document for clients but routes
each key by registry scope on disk (settings-files.js): profile keys go to
preferences.json as { value, updatedAt } entries stamped when the value
changes, everything else stays in settings.json, device keys are dropped
from writes. A missing preferences.json is seeded once from settings.json,
which is left intact; an unreadable one is a failure that pauses profile
writes and never gets overwritten. Server modules that read a profile key
off the disk use the merged sync read. Electron main reads the theme mode
from both files and now owns the splash colours, handed over the
window-theme IPC instead of the settings document. Clients stop sending
device keys, seed them once from a pre-split document, and persist
inputBarOffset locally. The PWA manifest keys are instance facts.

Testing: vitest in packages/web (seed, split write, timestamp retention,
unreadable file), bun test in packages/ui and packages/electron, tsc for
every workspace.

* feat(vscode): write the profile to preferences.json from the extension host

Problem: the extension host writes the shared settings files directly and
had to follow the server's split, and its file writes reported success on
failure.

Approach: settings-files.ts mirrors the server's format and split rules
(seed once, unreadable preferences.json is a failure); persistSettings
routes profile keys to preferences.json and the rest to settings.json,
and the atomic writers now throw so a failed save reaches the webview.
Clearing a key now actually removes it from the owning file.

Testing: bun test packages/vscode (38 files), tsc, build:extension.

* feat(settings): store the per-surface profile fields by surface kind

Problem: theme, chat-layout switches and typography sizes are one value
for every client of an instance, so the phone and the desktop cannot
disagree without a hard-coded runtime branch.

Approach: every settings request carries the client's surface kind in the
x-openchamber-surface header (web, desktop, vscode, mobile — the phone app
and the hosted mobile shell are one kind). For the registry's perSurface
keys the store writes a changed value under fields[key].surfaces[kind] in
preferences.json and never touches the base from a surface; reads resolve
the kind's own value, then the base, then nothing. Writes without a
surface (migrations, the seed) set the base. The VS Code host is always
vscode; Electron main resolves desktop for the native window theme. The
Settings UI is unchanged.

Testing: vitest in packages/web (surface write/read, no base copy, unknown
surface falls back to base), bun test in packages/vscode and packages/ui,
tsc for every workspace, build:extension.

* fix(settings): keep a legacy copy of the profile in settings.json

The first write after the split rewrote settings.json with the instance
part only, and that write happens on startup (relay reconcile). A build
from before the split reads only settings.json, so rolling back would
have lost every preference: theme, default model, all of it.

Every write now stores the profile's base values in settings.json next
to the instance part (`legacySettingsDocumentOf`), on the server and in
the VS Code extension host alike. Current builds ignore the copy because
preferences.json wins in the merged read. When preferences.json is
unreadable the copy already on disk is kept rather than dropped.

Testing: settings-runtime tests updated for the copy; full web suite
(182 files), VS Code tests and extension build, tsc clean. Verified live
on a scratch OPENCHAMBER_DATA_DIR: all 136 keys survive startup, theme
changes land per surface, plain keys land in the base.

* feat(settings): make the UI password and tunnel preset tokens write-only

GET /api/config/settings returned desktopUiPassword and the managed
remote tunnel preset tokens to every authenticated client, including
paired phones and the VS Code webview that never need them.

Both keys are now `secret` in the registry: accepted on write, withheld
from reads. The server answers with a hasDesktopUiPassword flag; the
desktop network page shows "Password set" and sends a value only when
the user types a new one or presses "Remove password" (an empty string
clears it and turns LAN access off). The tunnel page already learned
token presence from the status endpoint. The VS Code bridge strips
secret keys from what it hands the webview while still merging them
from disk on write.

Testing: registry, i18n parity, server settings, VS Code gate tests and
tsc; workspace type-check. Verified against a scratch server: GET
carries the flag and no password, PUT with '' clears, PUT with a value
sets. The desktop-only page itself awaits the owner's run.

* fix(settings): send the surface kind as a query parameter, not a header

The packaged desktop shell (openchamber-ui://app) and the phone app are
cross-origin to the OpenChamber server, so the x-openchamber-surface
header turned every settings request into a CORS preflight the server
did not allow. Settings looked reset and every save reported "Save
failed" without reaching persistSettings. An older remote instance would
refuse the header the same way even with the allow-list fixed.

The client now sends ?surface=<kind>, which keeps the request
CORS-simple on every server version; the server reads the query
parameter and still honours the header. The header is also in the CORS
allow-list for completeness.

Testing: workspace type-check, persistence and registry tests, server
opencode tests. On a scratch server: PUT with ?surface=vscode lands
under surfaces.vscode, GET without or with an unknown surface serves the
base, the header fallback resolves. Confirmed in the owner's rebuilt
desktop and on the phone.

* refactor(settings): drop the show-password toggle from the desktop network page

With the password write-only, the field only ever holds a value the user
is typing right now; the reveal toggle and its strings are gone from
every locale.

* refactor(projects): serve project setup through the server, drop the legacy migration

The shared UI read and wrote ~/.config/openchamber/projects/<id>.json
itself: it resolved the home directory, composed the path, and used the
Files API, which only desktop and VS Code have natively and which cannot
see a remote instance's file at all. It also still carried the months-old
migration from <repo>/.openchamber/openchamber.json, which deleted files in
the folder the upcoming shared project config will use.

The client-owned keys (worktree setup commands, project actions, draft
starters) now live behind GET/PUT /api/projects/:projectId/config.
project-setup.js sanitizes and builds the view; the project-config runtime
merges a patch under the same cross-process lock the scheduled-task writers
hold, so unknown and server-owned keys survive. A wrongly shaped key is a
400, not a silent drop. openchamberConfig.ts keeps its exported functions
and is now an HTTP client. The VS Code webview handles the route locally
and bridges to the extension host, which owns the file with a TS mirror of
the sanitizers.

Testing: server tests for sanitizers, round trip, lock, and invalid patch;
client tests against a mocked route; VS Code sanitizer and bridge tests;
workspace type-check, both VS Code builds, UI isolated suite (409 files),
server projects and project-context suites. Live GET/PUT against a
running server with the owner's real project config.

* feat(projects): read the team's shared config and merge it with the personal one

A project can now carry <repo>/.openchamber/project.json (version 1:
setupWorktree, setupWorktreeWait, projectActions, draftStarters,
plansDir). The server finds the checkout from the path-derived project
id, parses the file, and answers GET /api/projects/:id/config with one
merged view: what runs at the top level, plus shared and personal blocks
so a page can edit the personal file without copying a teammate's entry
into it.

Merge rules: shared setup commands run first (a personal
setupWorktreeMode of "replace" uses the personal list only); the
personal wait flag wins when set; actions union by id with a personal
action replacing the shared one and personal hiddenSharedActionIds
dropping shared ones; starters union by type:name; the primary action is
personal only. A shared file that exists but cannot be parsed, or that
names a plansDir outside the repo, is reported as invalid with a reason
and never treated as "no shared setup". Nothing writes the repo file yet.

Client: getProjectSetup exposes the view; the existing helpers return
effective values, while the Projects page sections and the draft
starters hook edit the personal block only. Shared entries show a quiet
"shared" mark in the actions dropdown and read-only lists above the
editable ones on the Projects page; shared starter chips have no remove
handle. The VS Code extension host mirrors the parser and merge.

Testing: server tests for the parser, plansDir guard, merge table, id
round trip, and a runtime test against a temp checkout; client tests
against a mocked route; VS Code sanitizer, merge, and bridge tests; the
section test covers the shared row; locale parity; workspace type-check;
UI isolated suite (409 files). Live: GET against a temp repo with a
shared file and with a broken one.

* feat(projects): ask before the team's shared commands run, once per set of commands

Shared setup commands and shared actions come from a file a git pull can
change, and they run on the machine of whoever pulls. The first time one
would run, a dialog now shows exactly what would run and asks: "Trust and
run" or "Not this time". A "trust" answer is recorded in the personal
config against a SHA-256 of the executable parts (setup commands and each
action's id, command, and runIn; renames and icons do not count), so a
pull that changes a command brings the prompt back. Nothing asks when the
shared file has nothing that executes.

Worktree creation (session creator, new-worktree dialog, session store,
multi-run launcher, agent-manager empty state) resolves its commands
through the prompt; "not this time" runs only the user's own commands.
The actions dropdown asks before a shared action runs. The Projects page
shows "Trusted on this instance" with a "Reset trust" button next to the
shared actions. The dialog is mounted beside the app-link confirmation on
every shell. The VS Code extension host mirrors the hash and the record.

Testing: server tests for hash stability, ordering, and the trusted flag,
plus a runtime test that changes the shared file and sees trust drop;
client tests for the confirmation store (ask, trust, skip, replace mode,
newer request, failed record, reset); VS Code mirror tests; the actions
button, new-worktree dialog, and issue-2039 tests updated for the trust
path; locale parity; workspace type-check; UI isolated suite (410 files).

* feat(projects): share and unshare setup with the team from the Projects page

The repo file <repo>/.openchamber/project.json is now written by the app,
and only when the user shares something: nothing appears in a repository
until then. PUT /api/projects/:id/config/shared replaces the keys it
names over the current file, writes it pretty-printed with version first
and only the keys that carry something, removes the file (and an empty
.openchamber folder) when nothing is left, refuses a missing checkout or
a plansDir outside the repo, and records trust for the writer, who has
seen what they shared.

On the Projects page, actions and setup commands get "Share with team"
and "Make personal"; shared actions can be hidden for this user; a
checkbox switches to "Use only my setup commands". Project starter chips
get share and make-personal hover buttons. A new "Shared config" block
shows the file's path and status, the shared plans folder, and the trust
status with "Reset trust". A share is a repo write followed by a personal
write; a failure after the first leaves the item visible once, as
personal. The VS Code extension host mirrors the writer.

Testing: server tests for the patch, serialization, emptiness, the write
and removal round trip, the writer's trust record, and the refusals;
client test for the shared route; VS Code bridge test for write and
removal; locale parity; workspace type-check; UI isolated suite (410
files). Live on a scratch server: share, invalid plansDir (400), unshare
to removal of file and folder.

* feat(projects): list, edit, and move plans in the team's shared plans folder

When the shared config names a plansDir, every markdown file in that
folder is a plan on the Plans tab: listed after the user's own plans,
marked shared, addressed as shared:<file>, read and edited in place
(the raw document is written verbatim, so a plan another tool wrote
keeps its shape), and deletable. Share moves one of the user's plans
into the folder; make personal moves it back under a new id; a name
collision gets a numeric suffix. Sharing is refused, with a hint in the
panel, until a shared plans folder is set in Project settings. This
answers the request to read plans from an existing folder such as
docs/plans.

Server: the project-context runtime takes resolveSharedPlansDir from the
project-config runtime; readContext reports sharedPlansDir; POST
.../plans/:id/share and /unshare. Client: movePlan in the context store,
a shared badge and a share / make-personal button per plan row. Session
attachments reference plan ids, so an attached plan that moves has to be
attached again.

Testing: runtime tests for listing, foreign markdown titles, id
traversal, in-place update and delete, share and unshare with a
collision, and the refusal without a folder; HTTP route tests; store and
locale parity tests; workspace type-check; full web suite (183 files);
UI isolated suite (410 files). Live on a scratch server against a temp
repo: list, share, read, unshare.

* fix(server): make OPENCHAMBER_DATA_DIR move every folder, not just the flat files

The variable is documented as the OpenChamber data directory, but only
settings, preferences, auth, and push files followed it; projects,
themes, speech models, and the chats default stayed under
~/.config/openchamber. A second instance started with a custom
directory therefore read and wrote the default instance's project
configs.

Every folder now hangs off the one root. An instance that already used
a custom directory gets projects, themes, and speech-models copied in
once at startup; copied, not moved, so a second instance beside the
default one cannot strip it, and nothing is merged into a folder that
already exists. Existing managed chats are not copied, as with
OPENCHAMBER_CHATS_DIR.

Testing: migration tests for copy-once, no-merge, and same-root no-op;
full web suite; a scratch server with an empty data dir copied the real
project configs and kept its writes in the copy.

* fix(projects): keep a plan's id when it moves into or out of the repository folder

A plan moved into the repository plans folder used to be listed under a
new shared:<file> id, so a session that had attached it lost the
attachment. The manifest entry now stays with a `shared` flag that says
which folder holds the file; the id survives both directions. Only a
plan that never had an entry (one written by another tool) gets an id
when it is brought in. A personal file and a repository file may share
a name because they live in different folders.

Testing: runtime tests for share and unshare with a stable id, reading
and editing the moved plan, the suffix on a name collision, and the
adoption of a foreign file.

* feat(projects): default repository plans folder, "move to repository" wording, tooltips

Plans now have a repository folder without any setup: .openchamber/plans
by default. A custom plansDir replaces the default outright (only that
folder is read and written; moving files between the two is the user's
job), and the field's placeholder and hint say so. The move buttons on
plans are therefore always available.

The word "share" is gone from the UI: it read like publishing, while
the action stores an item in the repository so everyone who pulls it
gets it. Labels are "Move to repository" / "Move to my settings", the
badge is "In repo", the block is "Repository config", and every button
on the Projects page carries a tooltip that says what happens (the
"Move to repository" button explains that edits save first while the
form is dirty). The trust status with "reset trust" moved from the
repository block into the Worktree section next to the commands it
guards; the plan row's badge sits beside the title.

Testing: locale parity, section test, workspace type-check, UI isolated
suite (410 files), full web suite.

* fix(projects): leave the icon key out of the repository file when an action has none

Actions without an icon were written as "icon": null into
.openchamber/project.json. The key is now omitted; readers already fall
back to the play icon. Server and VS Code serializers, tests updated.

* docs: describe the repository config file and how items move into it

A new page in every locale: what stays personal and what can move into
the repository, the .openchamber/project.json format with an example
and every key explained (setup commands, actions with the supported icon
names, starters, plansDir), the merge rules, the trust prompt, and plans
in the repository. Linked from the sidebar and from Project Actions.
Translations written by hand.
2026-09-07 17:50:55 +03:00
Bohdan Triapitsyn ff75dc9bd5 fix(terminal): unset NODE_CHANNEL_FD for PTY shells on every POSIX host
The PTY runtime exported an empty NODE_CHANNEL_FD to override the daemon's
IPC descriptor, because bun-pty merges the native environ back into the
child and a JS-only delete does not stick. Node CLIs launched from the
shell (opencode, claude) then printed "warn: Failed to parse IPC channel
number ''" on exit.

The Linux-only env -u ARGV0 wrapper now applies on macOS and Linux and
also unsets NODE_CHANNEL_FD, so the variable is gone instead of empty.

Testing: runtime and inherited-env tests updated for the POSIX wrapper;
verified in the running app that exiting opencode no longer prints the
warning.
2026-09-07 12:18:22 +03:00
Bohdan Triapitsyn 39fa8c1917 feat(terminal): replace ghostty-web with an in-repo libghostty-vt adapter
The terminal ran on the ghostty-web npm package plus a hand-written patch,
and every rendering bug (recycled rows, duplicated reflow fragments, prompt
artifacts) had to be worked around from outside. The emulator now is the
official libghostty-vt C ABI compiled to WebAssembly, driven by a browser
adapter ported from T3 Code (MIT, notice in LICENSE-T3CODE) and owned in
packages/ui/src/lib/ghostty. The artifact is reproducible with
scripts/build-libghostty-wasm.sh, including a workaround for Zig 0.15.2 on
macOS 27 SDKs.

On top of the port: one WASM instance per page with every tab kept mounted
and hidden tabs paused; history replayed at the PTY size it was drawn for;
shells spawned only after the first fitted grid so zsh never prints the
PROMPT_SP marker; box drawing, block elements and Powerline arrows drawn
procedurally to the exact cell so TUI borders and block logos have no gaps
between rows; a software-rasterized canvas so Gecko renders every tab's text
with the same smoothing; the symbols-only Nerd Font bundled instead of a CDN
fetch; touch selection and scrolling driven through the surface API; a copy
button in the tab strip for touch hosts; localized aria labels.

Testing: bun tests run the real WASM (reflow, palette, replay isolation,
recycled rows, box glyph geometry); viewport and view tests use a surface
double; verified in Chromium and Zen (windowed and headless) for crisp text,
new tabs, panel reopen, resize and box glyph rendering; package type-check,
oxlint/eslint on new files, web build.
2026-09-07 12:18:22 +03:00
Bohdan Triapitsyn 3132d1361a fix(sessions): keep missing-worktree relocation manual
Remove automatic moves on session activation, terminal failures, and archive restoration while preserving manual moves and worktree deletion.

Replace directory listing probes with a stat-only endpoint using Node built-ins, including an isolated module-load regression test for packaged desktop.

Validation: focused session, worktree, filesystem, localization, and bridge tests; workspace type-check and lint; web and VS Code builds. Desktop startup and behavior verified by the maintainer.
2026-09-07 02:10:06 +03:00
Bohdan Triapitsyn eb6f7b0904 fix(terminal): update ghostty-web to a build that clears recycled rows
ghostty-web 0.4.0 hands rows that scroll into view out of recycled WASM
page memory without clearing them, so after a tab or project switch the
new emulator showed the previous terminal's text (upstream #138). The fix
landed only in prereleases, so pin 0.4.0-next.20 and carry the local
block-glyph rendering patch over to the new dist file.

Verified in a production build: creating an emulator after disposing a
full one no longer exposes its rows, and switching between two projects
with live output in each keeps every terminal's content to itself.
2026-09-06 22:57:43 +03:00
Bohdan Triapitsyn c2f36fb5e7 fix(terminal): replay snapshot history at the PTY size it was drawn for
Opening the terminal panel sometimes showed stray fragments on the prompt
row: zsh's end-of-line mark and pieces of the prompt path. The shell had
laid its output out for one PTY width, but the client replayed that
history into an emulator of another width (an early size estimate, a
remount, or a renderer rebuild after fonts loaded). ghostty-web's reflow
then left fragments the shell's SIGWINCH redraw never clears.

The server now reports the PTY cols/rows in every snapshot, the transport
carries them through projections and accepted resizes, and the viewport
replays a sized snapshot chunk at that size before returning to the
fitted size. The container-based size estimate only seeds newly spawned
shells and is no longer sent to a running PTY.

Tests cover the sized replay, the store chunk size, the transport
projection, and the server snapshot; verified in a production build by
reloading with the panel open and switching tabs at a changed width.
2026-09-06 22:57:43 +03:00
Bohdan Triapitsyn b0282b2720 fix(quota): validate Hyper credentials and clean up balance labels
Reject invalid credentials while preserving valid token fallback, parse balances with existing boundary helpers, and keep credit values free of untranslated unit text. Inject auth and HTTP dependencies for focused tests in both runtimes.

Validated web quota and registry tests (35 passed), VS Code quota tests (70 passed), both package type checks and lint, extension build, and changed-line anti-slop checks. Reviewed dead-code output. Live Hyper validation was not run because no API key is available.
2026-09-05 23:26:35 +03:00
Bohdan Triapitsyn 7b42208b8c release v1.22.2 2026-09-05 21:39:04 +03:00
Bohdan Triapitsyn 0d8545ed6b chore: update unreleased changelog 2026-09-05 21:36:01 +03:00
Bohdan Triapitsyn 7308b90670 chore: update unreleased changelog with recent chat and file fixes 2026-09-05 20:28:18 +03:00
Bohdan Triapitsyn f46fb718c5 fix(chat): recall the current session's prompts by default; tidy the six merged PRs
Input history (#3035) shipped with "All projects" as the default scope and
only recorded prompts sent after the upgrade, so ArrowUp showed other
sessions' prompts and, once switched to "Current session", nothing at all.
Default to the current session and merge the visible transcript's prompts
with the persisted bucket. Existing sessions recall as they did before
#3035, while new prompts keep their attachments and stay recallable after
a revert hides them from the transcript.

Cleanup across #1855, #2297, #3072, #3178, #3035 and #3135: drop the
duplicate poll guards in the file content poller, the zod schema the
VS Code package cannot depend on, a copied file-URL helper and stray
whitespace; move the Enter-to-send strings into the settings namespace;
document OPENCHAMBER_CHATS_DIR, resolve the chats root once on the server
and warm it alongside the other bootstrap calls.
2026-09-05 20:16:14 +03:00
Bohdan Triapitsyn 29480383cc fix(oc-dev): use imported existsSync for the legacy changelog check
Claude-Session: https://claude.ai/code/session_013rV6NKKjFb6pDB2Zh7TtsH
2026-09-05 19:10:29 +03:00
Bohdan Triapitsyn 391ea57731 docs: update unreleased changelog title 2026-09-05 17:58:04 +03:00
Bohdan Triapitsyn fe9119abb5 docs(changelog): VS Code notes for the next release, no banner in the extension changelog
Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
2026-09-05 17:29:32 +03:00
Bohdan Triapitsyn 08d5d766cf docs(skills): rename changelog-authoring to update-changelog
Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
2026-09-05 17:17:40 +03:00
Bohdan Triapitsyn 3d2bcf7ed6 chore(changelog): retire CHANGELOG.md as a dependency
The update dialog (server and desktop) now reads changelog/index.json and
renders title, intro and groups; the release workflow builds the GitHub
Release body and name from changelog/<version>.md; oc-dev, the issue-intake
agent, AGENTS.md and the changelog skill no longer point at the file.

CHANGELOG.md stays as a legacy copy for installs up to 1.22.1, which fetch
it for update notes. The generator refreshes it while it exists and never
recreates it, so deleting it after 2026-09-19 retires it for good.

Generated outputs hold released versions only, so editing unreleased.md
never makes them stale: agents write that file and nothing else, and
oc-dev create-release does the generation.

Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
2026-09-05 17:02:28 +03:00
Bohdan Triapitsyn c3ae9b1d91 docs(changelog): title the unreleased notes
Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
2026-09-05 16:46:07 +03:00
Bohdan Triapitsyn a1bc1368ae docs(changelog): title every release and require one
Every release file now carries a title: two to six plain words naming the
change a user would remember it for. The generator refuses a release without
one, promote carries the title from unreleased.md, and the unreleased
template opens with a title line. The changelog-authoring skill and the
format README describe how to write it.

Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
2026-09-05 16:45:29 +03:00
Bohdan Triapitsyn 475dc5ecb1 docs(changelog): title 1.22.1 after its OpenCode Go highlight
The grouped notes put the OpenCode Go header change under Improvements, so
the site's headline fallback (first bold bullet) picked Message queue. The
explicit title keeps the release's most important change on the index.

Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
2026-09-05 16:27:45 +03:00
Bohdan Triapitsyn fe662874df fix(ui): show changelog groups as labels in the update dialog
Release notes now carry New, Improvements, Fixes, and Misc headings.
Inside the update dialog they render as small uppercase labels over
their lists instead of full-size document headings.

Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
2026-09-05 16:17:29 +03:00
Bohdan Triapitsyn 9f78de9e89 docs(changelog): rewrite the last thirty releases for readers
Releases 1.13.1 through 1.22.1 are regrouped by what each change is
(New, Improvements, Fixes, Misc) and reworded in the voice the
changelog-authoring skill asks for: one or two plain sentences per
bullet, the symptom or the capability named, no mechanism. Packed
bullets were split, every fact and every contributor credit kept.
Older releases keep the mechanical grouping.

Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
2026-09-05 15:58:34 +03:00
Bohdan Triapitsyn d9b9c8edab feat(changelog): one source file per release, generated outputs
Release notes now live in changelog/<version>.md (front matter with
version and date, then ## App and ## VS Code sections grouped into
New, Improvements, Fixes, Misc) plus changelog/unreleased.md for what
has not shipped. `bun run changelog:build` renders CHANGELOG.md,
packages/vscode/CHANGELOG.md, and changelog/index.json from them;
`changelog:check` fails when the outputs are behind and runs in CI and
in release:prepare. `oc-dev create-release` promotes unreleased.md to
the versioned file dated today and rebuilds.

The existing history was split mechanically: every bullet kept, sorted
into groups by keyword, five hand-typed headers with one-digit days
normalised to YYYY-MM-DD (the update dialog matched none of them). The
generated files keep today's release headers, which the update dialog,
the release workflow, and the website match by regex.

Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
2026-09-05 15:58:23 +03:00
Bohdan Triapitsyn bc2513f410 docs(changelog): group release notes and rewrite the authoring skill
Release sections now carry New, Improvements, Fixes, and Misc groups,
and the bullets are written for a user who never opened the code: one
or two plain sentences naming what they see. The changelog-authoring
skill makes that shape the primary rule, with a worked example, a
five-second reader test, and checks per bullet.

Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
2026-09-05 15:45:22 +03:00
Bohdan Triapitsyn c3a83b3181 fix(vscode): default to the VS Code display language before a saved locale
The splash and the UI bundle both started in English on a fresh install
even when VS Code ran in a supported language, because only OpenChamber's
own saved locale was consulted. The HTML now exposes VS Code's display
language; the splash and detectInitialLocale use it until the user picks
a locale, which still wins.

Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
2026-09-05 15:08:15 +03:00
Bohdan Triapitsyn 319cf9f17a fix(mobile): label the pill's running-state action as queue
While a turn runs, the collapsed pill showed a plain Send button for a
draft that the expanded composer shows as Queue with a rotated icon, and
the pill routed it through the primary action. The pill now queues, with
the same label and icon as the expanded composer.

Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
2026-09-05 15:08:15 +03:00
Bohdan Triapitsyn 1e0d7abb6e fix(sidebar): poll project actions only while one is running
The sidebar indicator kept the terminal listing loop going for as long as
the sidebar was visible, in every client, whether or not anything was
running. It now lists once on mount, to pick up runs another client
started, and keeps the loop only while a project action is known to be
running anywhere; an idle sidebar costs no polling.

Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
2026-09-05 15:08:15 +03:00
Bohdan Triapitsyn f4eaa70eaa fix(docker): ship patched dependencies in the runtime image
The runtime stage copied node_modules from the deps stage, but patch-package
runs in the builder stage, so a patched server-side dependency reached the
image unpatched; only the bundled dist carried the fix. Both node_modules
copies now come from builder.

Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
2026-09-05 15:08:15 +03:00
Bohdan Triapitsyn 99c9f99a3d test(vscode): assert bundle keys by presence
The bundle files are parsed as string maps, so a missing key is the only
failure the test can see; checking presence says that directly.

Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
2026-09-05 14:50:08 +03:00
Bohdan Triapitsyn 03e14b61cd fix(terminal): key project action state by one canonical directory
The terminal store keyed its directories by trimming trailing slashes,
while the new sidebar activity indicator and the whole-server session
grouping keyed the same folder with the project-action normalizer, which
also rewrites backslashes. On Windows that split one project into two
namespaces: the sidebar reconciled server sessions under "C:/repo" while
the terminal panel and the project actions button worked under "C:\repo",
so the running-action indicator never lit up, adopted tabs were duplicated
and a stop recorded in one namespace did not guard reconciliation in the
other. The project actions button also read the store map directly with a
path normalized somewhere else, missing its own directory entry.

All terminal directory keys now come from normalizeTerminalDirectory in
lib/pathNormalization, and store reads go through getDirectoryState.
2026-09-05 14:48:25 +03:00
Bohdan Triapitsyn 74b8c77d49 fix(chat): keep the diff preview bound off a surrogate pair
The 256 KiB budget for an oversized tool diff preview counts UTF-16 units, so
the cut can land between the two halves of an astral character - an emoji in a
patched string, for instance. The preview then ended in a lone surrogate that
renders as the replacement glyph.

Step back one unit when the boundary splits a pair.
2026-09-05 14:48:17 +03:00
Bohdan Triapitsyn e24d2b2cbd fix(ui): guard the remaining comment inputs against IME composition
The IME fix covered the diff/file comment input and the browser annotation
overlay, but two other places where a user writes a comment still acted on
the Enter that confirms an IME candidate: the chat quote comment in the text
selection menu, and the in-place comment editor on a composer context chip.
Confirming a candidate there attached or committed the half-typed reading and
closed the input. Escape, which abandons a candidate, had the same problem.

Both handlers now return early on a composing keystroke, and a single test
asserts the guard across every comment input so the next one added does not
quietly skip it.
2026-09-05 14:48:17 +03:00
Bohdan Triapitsyn 904334de88 fix(vscode): compare workspace folders through the shared path helper
The add-folder dedup normalized the drive letter to uppercase on one side and compared it with Uri.fsPath, which VS Code lowercases again. On Windows an already-open folder therefore never matched and was re-added. Uses pathsEqualWithNormalizedDriveLetter so both sides normalize.
2026-09-05 14:47:10 +03:00
Bohdan Triapitsyn 6c3e51956a fix(vscode): complete the Turkish extension bundles
The inline-comment feature added its manifest and runtime strings to English and French only, so Turkish users saw English command titles, thread labels and warnings. Adds the ten missing keys and a test that fails whenever a locale bundle drifts from the English key set or its placeholders.
2026-09-05 14:47:10 +03:00
Bohdan Triapitsyn 9eb8a92169 fix(cli): probe bun.exe under BUN_INSTALL on Windows
isBunInstalled() spawned <BUN_INSTALL>/bin/bun, which does not exist on
Windows where the installer ships bun.exe. spawnSync adds no extension to
an explicit path, so the probe failed and the CLI quietly started the
server under Node even with Bun installed. The probe now names bun.exe
on win32.

Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
2026-09-05 14:46:06 +03:00
Bohdan Triapitsyn 65019492e5 fix(sync): guard child-session discovery against overlapping pulls
Child discovery now pages through the whole session list, so one pull can
take longer than the 15s watchdog interval. Two overlapping pulls each read
the store before either commits, so both appended the same newly found child
and the directory store ended up with duplicate rows. Keep one pull per
directory in flight, mirroring the status-poll guard.
2026-09-05 14:45:35 +03:00
Bohdan Triapitsyn f02dfbd4cb fix(web): tidy the XDG config root resolution
The web server read XDG_CONFIG_HOME through a redundant typeof guard on a
value that is already string|undefined, which tripped the anti-slop lint on
its own new line and diverged from the VS Code helper. Both now read the
same way. The VS Code provider test also still hard-coded ~/.config/opencode,
so it silently stopped asserting whenever XDG_CONFIG_HOME was set; it now
uses the shared constant, like the web test already does.
2026-09-05 14:45:20 +03:00
Bohdan Triapitsyn e43541d38b fix(tooling): resolve Bun in the full dev launcher
dev-web-full.mjs is started by node and spawned bare `bun`, so on Windows
machines where npm exposes only bun.cmd both children died with ENOENT and
`dev:web:full` never started. It now uses the resolver dev-web-hmr.mjs got,
and hides the console window like the other Node-spawned children.
2026-09-05 14:45:20 +03:00
Bohdan Triapitsyn ac23e23db7 docs(providers): name the supported adapters in the web module doc
The web route accepts the OpenAI Chat Completions, OpenAI Responses and
Anthropic Messages adapters, but the module doc still described the write
path as OpenAI-compatible only. The VS Code doc already names all three.
2026-09-05 14:44:55 +03:00
Bohdan Triapitsyn b7a63af1b9 test(providers): cover legacy alias migration with several entries
The legacy `providers` block is deleted whole when its last entry migrates
to `provider`. Nothing covered the case where other legacy entries remain,
so a regression there would silently drop unrelated providers. Adds the
case to both the web server and VS Code parity suites.
2026-09-05 14:44:55 +03:00
Bohdan Triapitsyn 671dce7213 fix(vscode): send an editor comment to the sidebar chat instead of opening a tab
A comment went to the active session tab, and when there was none it opened
a new one, even while the user was chatting in the sidebar. That is not how
the other capture flows behave. The comment now takes the same route as
Add to Context: the active session tab when one exists, otherwise the
sidebar, revealed if it is closed.

Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
2026-09-05 14:25:00 +03:00
Bohdan Triapitsyn 60a4dccb32 fix(vscode): file an editor comment under the session it was delivered to
A comment written with no session tab open opens one, and that panel knows
its directory long before it has loaded the session list and selected its
session. The webview filed the draft on the first snapshot that had a
directory, with the session key falling back to "draft" because no session
was current yet. The panel's composer reads the session's key, so the chip
never appeared, while the editor thread saw the draft in the store snapshot
and reported it attached.

A session panel now stamps its session on every comment it delivers, and
the webview waits until it actually shows that session before filing. The
sidebar files on its current session or an open new-session draft, and no
longer falls back to "draft" merely because nothing is selected yet. The
resolver is a pure module with tests.

Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
2026-09-05 14:18:51 +03:00
Bohdan Triapitsyn 4f1f9e6650 feat(sidebar): show running project actions by directory 2026-09-05 14:12:07 +03:00
Bohdan Triapitsyn 7a3244bab2 test(web): remove two timing races that hung CI for a minute each
Both failures showed up as "Test timed out in 30000ms" followed by "Hook
timed out in 30000ms" on loaded PR runners, in files nobody had touched.

relay host-client: the scripted client dialed its WebSocket before awaiting
its WebCrypto setup and only then attached the open/message listeners. On a
loaded runner the loopback socket opened while key generation was still
queued on the threadpool, the open event found no listener, no hello was
sent, and the client waited forever. The socket is now dialed after the key
material is ready, in the same tick as the listeners. The fake relay also
terminates leftover sockets on stop so a failure is reported once, not
twice.

walkthrough routes: each test slept 20 ms and assumed the request had
reached the route by then. The tests now wait until the service has been
asked to generate one more time than before the request, and afterEach
closes idle keep-alive connections so server.close() cannot hang on a
response a failed test never received.

Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
2026-09-05 12:40:07 +03:00
Bohdan Triapitsyn d323b51a0a fix(queue): deliver a queued slash command with context as a prompt
OpenCode's command route accepts file parts only. The server queue was
attaching captured context as text parts to POST /session/:id/command, so
a slash command queued with a comment, quote, or PR diff was rejected with
400 and retried forever.

A command queued without context still takes the command route with its
files. One queued with context now takes the prompt route the way the
composer does: the command's template is expanded with its arguments, a
skill keeps its text and gets the explicit skill-invocation instruction,
and the context rides along.

Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
2026-09-05 12:27:55 +03:00
Bohdan Triapitsyn 0b39efb0ae chore: bump @opencode-ai/sdk to 1.18.29 2026-09-05 12:11:20 +03:00
Bohdan Triapitsyn d37ce34a2e fix(terminal): reconcile project action executions across clients (#3362) 2026-09-05 12:05:39 +03:00
Bohdan Triapitsyn 58dfc789a2 fix(queue): clear the queue card after the last queued message is sent
When the server delivered the last item of a session's queue it deleted
the session entry and broadcast the update with an empty directory. The
UI keys its projection by directory, could not build the key, and dropped
the event, so the delivered message stayed in the "Queued messages" card
until the next full hydration.

The server now remembers a session's directory beyond the emptying of its
queue so that broadcast names it. The UI additionally treats an empty
session without a directory as "this session's queue is done" for every
projection keyed under that session id, which covers clients talking to
an older server.

Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
2026-09-05 11:21:39 +03:00
Bohdan Triapitsyn 7c9fdd1ee5 fix: complete goal resume and comment input follow-ups (#3361) 2026-09-05 10:53:34 +03:00
Bohdan Triapitsyn 260bfe666b fix: complete config path and review follow-ups (#3348) 2026-09-05 02:19:59 +03:00
Bohdan Triapitsyn 07c4053f42 Merge pull request #3330 from hehuaiyu/fix/vscode-custom-provider-protocol-parity
fix(vscode): support custom provider protocols
2026-09-05 01:36:12 +03:00
Bohdan Triapitsyn 214d9a916b Merge main and preserve custom provider protocols 2026-09-05 01:29:16 +03:00
Bohdan Triapitsyn f742775be4 feat: streamline release preparation and changelog checks
Exports the release package list so release prep stages the same versioned files as the bump script.
Validates that CHANGELOG.md contains the target release section before preparing a release.
Updates the suggested release steps to commit only release files and push the tag explicitly.
2026-09-05 01:26:10 +03:00
Bohdan Triapitsyn 74a065fd10 Merge pull request #3332 from hehuaiyu/fix/windows-bun-shim-resolution
fix(tooling): resolve Bun executable on Windows
2026-09-05 01:24:34 +03:00
Bohdan Triapitsyn d9f7bc199e Merge pull request #3329 from hehuaiyu/fix/preserve-custom-provider-model-metadata
fix(providers): preserve custom model metadata on edit
2026-09-05 01:24:23 +03:00
Bohdan Triapitsyn 05e2903815 release v1.22.1 2026-09-04 23:51:03 +03:00
Bohdan Triapitsyn f42988c9bf chore: bump @opencode-ai/sdk to 1.18.28 2026-09-04 23:43:54 +03:00
Bohdan Triapitsyn d6f0f2f23c feat(settings): retire the third-party plugin integrations
Settings → Integrations offered install cards for the Claude Code and
Cursor provider plugins. They are gone: the section, its plugin catalog,
its own i18n module and tests, the settings search entries, the page
keywords, and the two sprite icons only it used. The page now holds the
built-in GitHub and Linear cards, so it is hidden in VS Code where neither
applies; its title and description live in the settings dictionaries.

Docs follow: the Integrations page in every locale now documents GitHub
(pointing at its own page) and Linear in full, the GitHub page names
Settings → Integrations as the place to connect and covers linking an
issue or PR to a message, and the Providers pages no longer promise Claude
or Cursor subscriptions.

Claude-Session: https://claude.ai/code/session_01HB9wdLQoZX2vfyDjwv6Rso
2026-09-04 23:34:15 +03:00
Bohdan Triapitsyn 34bf631157 feat(queue): queue a message with everything the composer had attached
Queueing captured only the text and files. Context chips (inline comments,
terminal selections, browser annotations, PR comments and checks, quotes,
linked issue/PR/Linear references, pending synthetic parts) stayed in the
composer and only left with the next manual send, so a queued message the
server delivered went out without them and the chips rode an unrelated
message later.

A queued message now carries what the composer would have sent: the text
with its agent mention stripped and file mentions resolved into
attachments, the attached context as structured parts, and the skill
instruction derived from the text. The server delivers those parts in the
composer's order, the VS Code auto-send does the same, and editing a queued
message puts the chips and linked references back. A failed queue restores
the composer completely. Snapshots and broadcasts omit the captured
context like attachment payloads; a take returns it.

Claude-Session: https://claude.ai/code/session_01HB9wdLQoZX2vfyDjwv6Rso
2026-09-04 22:56:27 +03:00
Bohdan Triapitsyn ce5c0c068e feat(mcp): reconnect failed MCP servers in managed OpenCode
OpenCode marks an MCP server failed when it does not come up at startup
or when a live connection drops, and never retries. A new managed plugin
reconnects those servers with a per-server backoff (1s doubling to a 30s
cap), wakes early on mcp.tools.changed, and stops on dispose. Only the
failed state is retried; disabled and auth states stay untouched.

The OPENCODE_CONFIG_CONTENT merge that agent-tool and system-prompt each
carried is now one shared helper so the three managed plugins compose.

Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
2026-09-04 22:56:27 +03:00
Bohdan Triapitsyn caac24ac3d Merge pull request #3320 from openchamber/fix/ui-thinking-effort-draft-project-rename
Various session UI fixes: drafts, rename, worktree creation, thinking effort
2026-09-04 20:00:42 +03:00
Bohdan Triapitsyn f81bc26fe5 docs: clarify uncommitted changes warning text 2026-09-04 19:54:16 +03:00
Bohdan Triapitsyn b4a38061bc fix(ui): route answer worktrees from source session 2026-09-04 19:46:21 +03:00
Bohdan Triapitsyn f160f3aac4 Merge remote-tracking branch 'origin/main' into fix/ui-thinking-effort-draft-project-rename 2026-09-04 18:58:08 +03:00
Bohdan Triapitsyn 8a58054551 docs: refresh README wording and product messaging 2026-09-04 14:22:49 +03:00
Bohdan Triapitsyn 07fa83cc72 feat(queue): deliver queued messages from the server
Messages queued while a session is busy used to live in the browser tab and
were sent by that tab once the session went idle, so closing the tab (or
losing the connection) stranded them. The web server now owns the queue:
it persists to <data-dir>/message-queue.json, watches session.status on the
global event hub, re-verifies idleness against OpenCode before sending, and
delivers the head of the queue via prompt_async (or /command for slash
commands) with the model, agent, variant, attachments, and agent mention
captured at queue time. Failed sends stay queued and retry with backoff; a
user abort holds delivery briefly; every change is broadcast so all clients
see one queue.

The shared UI store becomes a projection of the server queue outside VS
Code (hydrate on connect, apply broadcasts, optimistic mutations settled on
the server's copy, one-time upload of locally queued messages from older
builds). Edit / send-now take the full message back from the server. A
UI-driven auto-review run asks the server to hold that session's queue.
VS Code keeps its local queue and foreground auto-send.

Claude-Session: https://claude.ai/code/session_01HB9wdLQoZX2vfyDjwv6Rso
2026-09-04 14:08:08 +03:00
Bohdan Triapitsyn 8afec51480 fix: switch new installs to OpenChamber themes
New installs now default to OpenChamber light and dark themes.
Existing Flexoki theme preferences continue to be preserved during migration.
2026-09-04 12:51:49 +03:00
Bohdan Triapitsyn 40f5ed73c9 Merge pull request #3274 from kydorn/fix/theme-save-echo-clobber
fix(ui): stop settings save echoes from clobbering theme preferences
2026-09-03 13:01:27 +03:00
Bohdan Triapitsyn 2a0672e3a6 fix(ui): preserve VS Code themes during settings broadcasts 2026-09-03 12:41:59 +03:00
Bohdan Triapitsyn 85bf0a99de fix: identify OpenCode Go requests by session 2026-09-03 11:49:35 +03:00
Bohdan Triapitsyn 76128b615f feat: add Fixel font support
Adds Fixel Text as a new UI font option
Allows font sources to load from direct URLs
Keeps existing fontsource-based loading working
2026-09-03 11:49:35 +03:00
Bohdan Triapitsyn 0107abb32d fix: order concurrent instance probes and honour the probe budget
Startup warm-up, opening the switcher and the refresh button can all probe at
once, and a relay host working through tunnel retries takes an order of
magnitude longer than a loopback one — so a slow older run landed last and
replaced a fresh "ok" with its own stale "unreachable". Each host now records
which run owns its status; a status from the switch flow outranks any probe
still running for it.

Each relay attempt is also capped by what is left of the 15s budget rather than
the full per-request timeout, so an attempt started just under the deadline can
no longer run the whole 8s past it.
2026-09-03 11:49:35 +03:00
Bohdan Triapitsyn f0ced9d361 fix: reject instance-served responses that outlive their instance
MCP status and skills were cleared on a switch but their in-flight requests
were not, so a response for the previous instance could still write itself over
the new one's — the same race the quota store already guards. Both now carry a
generation.

Usage also claimed an instance as loaded before it had answered, so a load that
failed on a cold or briefly unreachable instance was never attempted again, and
the previous instance's display mode and provider selection survived a switch —
which decided what the new instance was even asked for.
2026-09-03 11:49:34 +03:00
Bohdan Triapitsyn 17fd46d4e5 fix: keep instance statuses between switcher opens
The switcher held reachability in component state and replaced the whole map at
the end of a probe run. It ran once per open before the config had loaded — with
Local as the only host — so that pass wiped every other instance's status and
each open started on "Checking", including for the instance the app was
connected to and actively talking to.

Statuses move to their own module: startup warms them so the switcher opens on
real values, a re-probe replaces each value in place as it lands rather than
blanking them first, and stale entries are dropped against the loaded config
instead of a partial host list. The connected instance never reads "Checking" —
the live connection already answers what the probe would ask.
2026-09-03 11:49:34 +03:00
Bohdan Triapitsyn 8f1e0c8b5f fix: retry the relay instance probe before reporting it unreachable
The tunnel rejects everything waiting on its channel the moment one connect
attempt fails, even though it has already scheduled the next one with backoff.
That is right for app traffic, which retries for itself, but it made the
one-shot probe report a durable red "Unreachable" for a host that answers when
the user presses refresh a second later — a cold start is exactly when that
first attempt loses.

The probe now spans the tunnel's own reconnects within a 15s budget, and ends
immediately on a terminal tunnel state (auth failed, duplicate client, limit),
which waiting cannot resolve.
2026-09-03 11:49:34 +03:00
Bohdan Triapitsyn 6fa2cb66d0 fix: scope instance-served state to the connected instance
Linear and GitHub logins, quotas, MCP status, skills and agent memory are
served by whichever instance is connected, but each was cached globally or
by directory alone — which two instances can share. Switching instances left
the previous instance's answers on screen and its Linear login usable against
a runtime that has no Linear.

Reset them all through runtimeEndpointReset, each store guarding its in-flight
requests with a generation so a response for the previous instance cannot land
in the new one. The Linear team filter is now persisted per instance: a team
belongs to one workspace, so carrying it across filtered the new instance's
issue list down to nothing.

Usage also waits for the instance to report itself initialised before loading.
Providers report themselves as configured only once the instance can read their
credentials, so a fetch fired at mount answered "nothing configured" for every
provider and cached it — which is why Usage stayed missing from the work-status
panel until Settings -> Usage forced a fresh fetch.
2026-09-03 11:49:34 +03:00
Bohdan Triapitsyn e8ba8e996f fix: show full desktop instance label in header 2026-09-03 11:49:34 +03:00
Bohdan Triapitsyn a87f068256 fix: support dev server previews over relay 2026-09-03 11:49:34 +03:00
Bohdan Triapitsyn da6ab52a65 feat: add exe.dev usage tracking 2026-09-03 11:49:34 +03:00
Bohdan Triapitsyn 62a18f07a0 feat: add exe.dev provider logo 2026-09-03 11:49:34 +03:00
Bohdan Triapitsyn 29ca970362 fix: refresh terminal renderer after fonts load 2026-09-03 11:49:34 +03:00
Bohdan Triapitsyn 6c4923b8c8 fix: support Bun-only remote dev deployments 2026-09-03 11:49:34 +03:00
Bohdan Triapitsyn dfb3c614c8 fix: authenticate terminal WebSockets synchronously on Bun 2026-09-03 11:49:34 +03:00