AppImage exports ARGV0 into the process environment. zsh treats that as
argv[0] for every external command, which broke Python venv detection in
the integrated terminal and managed OpenCode sessions.
Clear ARGV0 in Electron before login-shell probing, refuse to re-apply it
from shell snapshots, and strip it from terminal PTY and managed OpenCode
launch environments.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
Restrict in-place skill rename to managed skill directories, require
frontmatter name to match before moving, roll back/reject with tests,
hide rename in the UI for unmanaged paths, and drop unused toast keys.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
On narrow mobile widths the chat status row rendered the edited-files
summary, diff totals, todo trigger, and chevron in a single flex line.
The left side has several fixed-width parts (file count, +/- counts,
chevron) so it could not shrink past the right side and the right-side
controls visually overlapped the truncated 'changed in workspace' label.
Fold the existing 24rem hide rule for status-row__changed-label into the
same 30rem container query that already hides status-row__active-todo,
and lower the breakpoint from 38rem to 30rem so the secondary label
remains visible on a wider range of mobile widths while the two
non-collapsible sides stop competing for space.
Validation:
- bun run type-check (all workspaces, exit 0)
- bun run lint (all workspaces, exit 0)
- mobile HMR route (HTTP 200)
- visual: before/after screenshots at ~360-400px width
performConfigRefresh passed client-directory-first path into
invalidateSkillsLoadCache, missing the active-project cache key used by
loadSkills after the repository-local skills discovery fix.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
Derive the effective OpenCode config layer (custom > project > user) from
provider sources and send it on PUT /api/provider so project/custom edits
update that layer instead of creating a global user override. Resolve
OPENCODE_CONFIG at call time and add UI/web/VS Code coverage for scoped
upserts.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
Rename skills by moving the skill directory and updating frontmatter
name instead of recreate-with-stub-description, which wiped the body
and supporting files.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
path-open-utils.mjs and its smoke test are byte-for-byte identical to
his PR #1335 (opened 2026-05-19, review findings addressed the same
day, community-verified on CachyOS but never given a maintainer
review), and linux-app-discovery.mjs retains most of that PR's
implementation. The [1.17.0] changelog entry credited the PR that
carried this work forward but omitted the original author.
A guided explanation is only useful in a language the reader reads, so the
panel header gets a language picker alongside the model one, defaulting to
the interface language. Like the model, it is request state rather than a
setting: the language travels with the read and the generation, and the one
a walkthrough was written in is stored with it, so reopening a review
describes what is there instead of what a fresh one would be.
Only prose is translated. Hunk aliases resolve back to hunk ids and
icon/importance are validated against fixed English values, so a translated
one would be dropped by the normalizer — silently losing an anchor or a
style. Identifiers and paths stay as they appear in the code.
The language is part of the cache key, and a read now asks the cache for the
exact request it was given before falling back to the pointer. Without that
the panel answered a request to switch languages with the text it already
had, leaving the other language unused in the cache.
Alongside it:
- The answer budget is derived from the resolved model instead of a flat 24k.
That number was the same for a 64k-context model and for one that admits to
384k output tokens, and on the latter it was the only reason generation
failed: the model spent the whole allowance reasoning and returned nothing.
It is now min(96k, max(24k, a quarter of the context)) capped by the
catalog's output limit, decided once so the input reserve and the request
cannot drift apart.
- A read no longer offers Cancel. It is a few hundred milliseconds of git with
nothing to cancel, and the button flickered on every model or language
change. When the panel is showing a fallback, a banner names what is on
screen versus what was asked for — only once the read has settled.
- The header keeps one 32px control height and drops its labels below 680px
instead of squeezing them to two letters and an ellipsis.
Docs and module documentation updated in every locale.
Use the real status-warning tokens (--status-warning-bg did not exist, so
the card rendered without a fill), drop the decorative icons, and fold the
conflict count into the title as a single full-message key per locale.
The operation description now wraps instead of truncating, and both the
conflict and ready-to-continue states share the same action layout.
Follow-up to #2574 and f95f1ab18, which guarded the archive path. The
delete path had the same two defects and worse consequences.
`useSessionUIStore.deleteSession`/`deleteSessions` declared an `options`
parameter and discarded it on both paths, so a caller-supplied runtime
key was a silent no-op. `SessionDialogs.tsx:416` already passes options
today and they never reach the action.
The delete path also never rechecked the runtime. Session IDs are not
unique across runtimes, so a response produced by a previous runtime
could commit `finalizeConfirmedSessionDeletion` against the runtime the
user switched to: evicting an unrelated session from the live and global
stores and calling `cleanupPersistedSessionState`, which erases queued
messages, todos, folder membership, inline-comment drafts, chat draft,
and pins. That is user data loss, not stale cache.
`cleanupPersistedSessionState` already rejects an identity whose runtime
is no longer active, but `finalizeConfirmedSessionDeletion` defeated that
check by passing the live `getRuntimeKey()` at commit time, comparing a
value with itself. It now forwards the captured key.
Adopt the default-on shape from f95f1ab18: `expectedRuntimeKey` defaults
to the active runtime in `deleteSession`, `deleteSessionInDirectory` and
the new canonical `deleteSessions` action, and is rechecked before the
request and before every reconciliation. A `404` still means "already
deleted" and commits cleanup, but only while the captured runtime is
active; after a switch it describes the wrong runtime and the action
reports failure instead of committing.
Also documents the throw contract of `patchSessionMetadata`, a
non-blocking nit raised by the review bot on #2574.
The tablet ran the phone layout with a half-finished iPad draft on top: two
custom sidebars, a leftover overflow menu, split Files/Changes header buttons,
and phone-width sheets stretched across a 13" screen. This brings it onto the
phone's navigation model and keeps only the differences a large screen earns.
- Sessions are a persistent resizable left sidebar; the overflow menu is gone
and its destinations moved into that sidebar's footer (connected instance,
settings, pending web update) and into the workspace drawer.
- The workspace (Changes / Files / Terminal / Notes / MCP) is the phone's
drawer everywhere: a resizable right sidebar where the screen can host one
(up to 900px) and the full-cover drawer otherwise, with its mounted panes —
an open diff, an edited file, an attached terminal — surviving rotation.
- Header dropdowns are anchored popovers: the recents switcher mirrors the
usage overlay on the left, and its trigger is sized to the title rather than
to the free width.
- App-level pages (settings, instances, update, an opened plan) render as
centered dialogs instead of covering the screen.
- Overlays center on the chat column through published insets, so the model
and directory pickers no longer sit off-centre; the directory picker also
stops overriding the shared width clamp.
- Wide chat layout applies to mobile surfaces, where a tablet chat column is
finally wide enough for the setting to mean anything.
The layout gate is a live size class rather than a device check, so Android
tablets and foldables are covered by the same code:
- `enabled` when the shortest viewport side is at le
sw600dp). The short side is what makes this a size question instead of a
device question — a phone reports ~360-430 whichev
unfolded book foldable ~600+, and folding shut drops back under it. iPads
also answer on identity, since iPadOS hands out od
- `roomyForPanels` when landscape and at least 1000px wide, which is what it
takes to host the sidebar, the panel and a readabl
foldables miss it in BOTH orientations — their long side is barely wider
than a tablet's short one — so they keep the portr
Every consumer re-decides instead of remembering wha
open sidebar closes if the device folds shut under it. iPad behaviour is
unchanged: its landscape widths all clear the panel
ones do not, exactly as the previous orientation check did.
Hardware keyboards are read natively. iOS reports them through GCKeyboard,
published to the web layer at document start and kep
disconnect and foregrounding; the layer stops inferring once that answers. A
single early publish was not enough — the connect no
already-attached keyboard fires before the page exists, and GameController can
populate late — so the state is re-published across
resume. With a keyboard attached the draft screen keeps its starter chips and
the composer never collapses; tablets skip the colla
Runtimes with no native answer fall back to inferring it from the keyboard
bridge, and only ever conclude "hardware" from silen
Also: sidebar rows no longer sit on a differently ti
footer is no longer clipped by an over-tall content box, the resize handles
moved above the panes' own overlays so they can actu
now-unreachable overflow menu, fullscreen terminal/MCP/notes surfaces and their
locale key are deleted.
Device behaviour is unverified — the tablet layout,
keyboard bridge and the foldable size class have not been exercised on
hardware, and the 600/1000 thresholds are derived fr
rather than measured on a foldable.
A diff is ordered by file path, which is almost never the order in which a
change makes sense. This adds a Walkthrough surface that reorders it: the model
groups related hunks into stops, explains what each group changes about
behavior, and orders the stops so each builds on the last. It explains and
orders; judging code stays with the existing Review action.
Reviews uncommitted work (all, staged, unstaged), a branch against its base, or
a pull request. Generation is always user-initiated — nothing runs on a timer,
on a file change, or as a side effect of opening a panel.
Invariants worth preserving:
- Hunk identity is derived on the server and only there. Ids are content
hashes, so an anchor that no longer resolves is proof the code it described
changed, and staleness needs no heuristics. The client matches ids to ids and
never recomputes them; two implementations would have to agree forever.
- The digest is never truncated. A diff that does not fit the model's context
is refused with an actionable reason, because a walkthrough written against
half a diff reads as confident and is wrong.
- Nothing disappears. Lockfiles and other generated output are excluded from
the model's input by name — never by size — and everything no stop covers is
listed at the end, so "have I seen all of it" stays answerable.
- Cost is explicit. Results are content-addressed, so returning the working
tree to an earlier state costs nothing; generation outlives its request, so a
refresh detaches the client rather than discarding paid-for work, and only an
explicit cancel stops it.
Supporting changes to shared modules:
- git: expose the existing getRangeDiff as GET /api/git
listUntrackedPaths and getUntrackedDiffs. The latter resolve the repository
once for a batch instead of per file, taking a panel
~340ms on an 80-file working tree.
- small-model: structured output across four wire forma
and abort signal, and an onOverflow policy so an oversized prompt fails
loudly instead of being silently clipped. A provider
remembered so the prompt-side fallback goes first next time.
- models.dev metadata: surface structured_output as tri
false blocks a model, a missing field does not, because the catalog omits it
for roughly half of all models.
Desktop and tablet only: VS Code serves Git through its
these routes, and the mobile shell does not consume the surface registry.
Docs: packages/docs walkthrough page in English and all eight locales.
Skills listing ignored the active project when the OpenCode client
directory was unset, so project `.agents/skills` files were created but
never shown. Prefer the active project path (matching Commands/Agents)
and soft-fall back to it on skill API routes when directory is omitted.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
Gate Edit/incomplete-auth on config-layer sources so catalog providers
are not treated as editable custom overrides. Snapshot form initial
values when Edit is clicked and ignore same-id initialValues identity
changes so parent re-renders cannot wipe in-progress edits.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
`useSessionUIStore.archiveSessions` declared an `options` parameter and
discarded it, so any caller passing a captured runtime key got a silent
no-op. The archive path also never rechecked the runtime, letting a
response produced by a previous runtime reconcile the live and global
session stores of the runtime the user had switched to.
Move the batch to a canonical `archiveSessions()` action, add an optional
`expectedRuntimeKey` to `archiveSession()`, `patchSessionMetadata()`, and
`cleanupReviewMetadataBeforeDelete()`, and recheck that key before every
store reconciliation. A guarded batch stops at the first observed runtime
change: server-confirmed sessions stay in `archivedIds` and every
unconfirmed ID is returned in `failedIds`, so existing partial-failure
feedback stays truthful. Callers that pass no key keep prior behavior.
Type the store option as `ArchiveSessionsOptions` instead of
`Record<string, unknown>`, since the loose type allowed the drop.
Add coverage for the archived-only narrowing: mixed pages return archived
records only, active pages stay unfiltered, a page that is full upstream
but fully filtered out keeps paginating, onPage receives only accepted
records, and id dedupe still stops a repeating page.
Document the OpenCode `archived` flag semantics in the owning store
documentation.
OpenCode's `archived` flag drops the `time_archived IS NULL` condition
instead of selecting archived rows, so the archived list came back with
active sessions mixed in. Filter at the boundary and keep pagination
progress measured on the raw response.
Require an API key or {env:VAR} on client and server, add edit/prefill for
existing custom providers, save auth before config, and surface incomplete
auth plus disconnect after partial failures. Add VS Code parity tests and
drop the unused allProvidersConnected locale key.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>