* fix(ui): update session-switch-resync test to current handleEvent/setSessionTodos signatures
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* perf(build): split Shiki grammars/themes, CodeMirror legacy modes, and @pierre/diffs into on-demand chunks
Merging @shikijs/langs into one manual vendor chunk made the first language
request download every grammar (7.4 MB raw / 1 MB gzip). Letting Rollup split
these packages per dynamically imported module downloads only the languages,
themes, and modes actually used — matching how the worker build already
behaves. @pierre/diffs is split the same way so its pure patch parser (used by
the eager tool renderer) no longer drags the Shiki-importing render stack into
the startup graph.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* perf(ui): restore lazy heavy views and stop eager settings-graph loading
- MainLayout: DiffView/FilesView/GitView/PlanView return to lazyWithChunkRecovery
(they were silently made static in 2031e3b4 while their Suspense wrappers
remained), keeping the CodeMirror and @pierre/diffs stacks out of startup.
- ContextPanel: same lazy treatment for its Diff/Files/Git/Plan/Walkthrough
tabs, with null Suspense fallbacks.
- CommandPalette imported getSettingsNavIcon from SettingsView, statically
pulling the entire settings surface (SkillsPage -> CodeMirrorEditor -> vim
mode, theme registry -> @pierre/diffs) into the eager graph; the helper now
lives in lib/settings/metadata.
- The windowed SettingsWindow mounts only after its first open: rendering the
lazy component closed made React fetch the SettingsView chunk graph at
startup.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* perf(ui): keep @pierre/diffs + Shiki out of the eager chat graph and defer diff worker warmup
- DiffWorkerProvider no longer statically imports @pierre/diffs/worker or the
theme registry, and no longer spawns 3 workers plus a main-thread shared
highlighter during mount. Pools are created on demand through a dynamic
module load, warmed via requestIdleCallback after startup settles, and
useWorkerPool notifies consumers when a pool becomes available.
- ToolPart's rich diff preview moves to lazily loaded ToolPartDiffPreview;
the plain-text patch (PlainDiffFallback) renders while the chunk loads,
mirroring the existing error fallback. Theme registration happens during
render inside the lazy module so PatchDiff never renders unregistered ids.
- ChatInput mounts its lazy ToolOutputDialog only after the first attachment
preview opens instead of fetching the dialog chunk on the draft screen.
- getMarkdownSyntaxVars moves to a pierre-free markdownSyntaxVars module so
eager code-rendering consumers stop importing the registration module.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* perf(web): load ghostty-web and Nerd Fonts on first terminal use
- ghostty-web (638 KB raw JS + WASM VT) is dynamically imported when a
terminal actually mounts; TerminalView stays eagerly importable for the
bottom dock.
- The ~2 MB of CDN Nerd Fonts are no longer preloaded and force-loaded on
every cold start. index.html exposes an idempotent
__openchamberEnsureNerdFonts hook; TerminalViewport requests it on mount
and waits up to 2s so a cached font is in place before the glyph atlas is
built, while a cold CDN fetch never blocks the terminal. Runtimes without
the hook (VS Code, mobile) resolve immediately, matching their existing
fallback-font behavior.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
---------
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
The spinner ran a CSS animation on every active row for the whole turn,
repainting a composited layer at frame rate. Rows now carry a static dot —
primary while running, info while unread — and the metadata slot on the right
shows how long the turn has been going, updating once per second in the dot's
colour. The counter is the motion the spinner used to provide, at 1 fps.
Collapsed groups, folders and projects take the dot only, since one counter
cannot speak for several running turns.
Elapsed time is measured client-side because SessionStatus carries no
timestamps, and starts are persisted so a reload resumes the same count. Two
rules keep that honest. Only a liveness stamp — refreshed while a session is
observed active, stamped as the page hides, and compared against the page's
navigation start so a slow bootstrap is not charged to the absence — and a 90s
adoption window may expire a record; a snapshot that cannot yet see a session
is not evidence its turn ended. And a busy event is never read as a turn
boundary, because OpenCode republishes busy at every step of the agent loop, so
after a reload one of those repeats normally beats the first status snapshot.
Idle and error events do end a turn, and retire the record with it.
Snapshot reconciliation walks the running turns and asks whether the snapshot
covers each one, rather than being handed everything it covers: only a live
start can settle, so the pass scales with timing work instead of with the
directory's session list, and allocates nothing per poll.
Also applied to the mobile sessions sheet and session switcher. The shared
duration ticker moves to hooks/ now that it has a second consumer.
Keep the shortcut in Help/Settings only; the floating selection chip
should stay quiet so the Add to Chat action is easier to scan.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
Bind mod+l to append the current selection into the chat composer
(Cursor-style), and move session sidebar toggle to mod+alt+l so desktop
menus stay in sync. Closes#208.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
Navigation model rebuilt around two full-width drawers and a minimal
header (sessions / title-switcher / usage ring / workspace):
- Left sessions drawer: cross-project tree with live status indicators,
swipe actions on sessions (rename/archive/delete) and on group headers
(project edit / two-step close, worktree delete), reorder-only edit
mode with collapsible project cards and draggable worktrees, app-level
footer (connected instance, settings, pending web update).
- Right workspace drawer: Changes / Files / Terminal / Notes / MCP as
pill tabs (inactive tabs icon-only); panes stay mounted once visited.
The full desktop file editor serves the Files tab; read/skill tool taps
in chat open the file there at the requested line.
- Header session switcher on title tap: 10 cross-project recents with
live busy/attention indicators and project · branch metadata; the
usage ring opens a metadata overlay with an explicit loading state.
- The overflow menu is gone on phones (its destinations moved into the
drawers); iPad keeps it until its dedicated layout pass.
Correctness and continuity:
- /auth/session answers bearer-first, so a stale WebView cookie can no
longer mask a revoked device token; cold launches classify failures
fast and land on an explicit connect screen.
- Authoritative session snapshots raise frozen ordering baselines and
stale live ranks — recents stay truthful after the app slept.
- Cold launches reopen the last active session per instance (persisted
pointer, confirmed against a sessions snapshot; a user-opened draft
clears it), with a logo hold instead of a draft flash.
Also: collapsed pill composer gains the stop control; chat tool rows
share one 36px rhythm; Task subtool rows truncate; larger bottom safe
area so the composer clears big-screen corner radii; Capacitor build
hides About/Update (store updates apply there); widgets link to the
sessions drawer with a list icon; MobileApp split into focused modules;
five mobile-surface detectors unified; translucent borders normalized to
70%; all new strings translated across the 10 locales.
iPad and foldable layouts are intentionally untouched - separate next version PR.
* feat(ui): add context surface registry and rail switcher
* feat(ui): move git and project notes into context surfaces, embed editor file tree
* feat(ui): replace right sidebar with context surfaces, per-surface panel widths
* refactor(ui): retire legacy main-tab overlays and right-sidebar state
* feat(ui): rail polish, right-docked file tree, terminal surface
* feat(ui): move terminal into context surface, per-surface tab closing, editor empty state
* feat(ui): tune default rail order and activity dot
* fix(ui): keep context panel controls anchored during width animations
* feat(ui): lazy-follow context panel resize with window-level drag tracking
* feat(ui): panel dividers, right-dock tree icon, muted outline folder icons
* feat(ui): restructure git view into changes-first surface with standalone PR surface
- Remove commit/update/pr tabs; git view is always changes + commit
- Promote pull request to its own rail surface with shared repo context
- Move update-branch and re-integrate flows into separate dialogs
- Add PR status chip and repo actions menu to the git header row
- Seed new PR-status entries from resolved sibling remotes to avoid
a false "checking status" state when the PR is already known
- History/graph dialog refresh button, fingerprint global identity icon,
muted outline folder icons follow-ups
* feat(ui): progressive-disclosure PR surface with live checks and pinned chat context
- Segment the PR surface into Overview / Checks / Comments pill tabs with
live badges; merge controls move to the status row
- Live checks segment: progress bar, per-run rows with workflow names,
elapsed timers, expandable failures, auto-refresh while pending
- PR comments and failed checks pin as chat-context drafts (like terminal
selections) instead of sending an immediate message; works on new-session
drafts too
- Shared prContext cache client+server, ETag conditional requests in the
octokit wrapper (304s bypass rate limits), extended checks aggregate
(inProgress/queued/startedAt)
- Resolve gh-CLI auth login for merge-permission checks
- Full-width description editor with matched control heights
* fix(ui): single source of truth for PR checks and status readers
- Derive the checks aggregate from the visible run list and sync it into
the PR-status store so bar, badges, header, and git-view chip agree
- Route PR body hydration through the shared context cache
- Git-view PR chip reads the freshest entry across remote keys
* fix(github): freshness stamps prevent stale cache responses from regressing PR state
- pr/status and pulls/context responses carry a server-side fetchedAt that
survives cache serves
- The status store rejects responses older than the held snapshot (only
clearing the loading flag), and the checks sync adopts the context's
stamp so stale status polls cannot flip fresher derived checks
- Regression test for the stale-response guard
* perf(github): repo-level pull-list cache collapses per-branch PR resolution
- One pulls.list per repo per state per 45s answers every branch (10
worktrees = 1 call, not 10 query fans); in-flight fetches coalesce
- A complete repo list makes a no-PR miss authoritative, skipping the
per-owner head queries AND the Search API fallback (the 30/min killer)
- force refresh bypasses the repo list cache; PR create/merge/ready
invalidate it
* perf(github): back off Search API misses per repo+branch
A branch without a PR re-searched on every poll; with >100 closed PRs the
list miss is never authoritative, so the search fallback still ran and
burned the 30/min search quota. Remember misses for 10 minutes; PR
creation clears remembered misses for the repo.
* fix(github): dedupe re-run check runs to the latest per (app, name)
listForRef returns the superseded completed run alongside its re-run;
GitHub's UI shows only the latest per name. Mirror that in both pr/status
and pulls/context so counts and run lists match github.com.
* fix(ui): address review findings on registry test, surface docs, and PR-context keys
- Rail-order test asserts against the registry itself (was stale after the
'pr' surface landed and failed)
- surfaces DOCUMENTATION.md describes actual behavior: has-content surfaces
hide until content exists; only multi-instance/terminal panes are
keep-alive, singleton surfaces remount and restore from stores
- PR-context cache keys are runtime-scoped JSON tuples; invalidation
compares the directory exactly instead of by string prefix (+ test)
* fix(ui): wrap long unbreakable tokens in check-run details
Annotation messages with long SHAs/URLs overflowed the panel; break-words
on annotation title/message/rawDetails and output summary/text, and the
expanded run body clips instead of widening the panel.
* fix(ui): busy state for context-attach buttons and honest attach labels
- 'Attach failed checks' / 'Attach all to chat' show a spinner and disable
while the context request runs (previously nothing happened for seconds)
- Action labels/tooltips reworded from send-to-agent to attach-to-chat
semantics across all locales
* fix(i18n): Ukrainian attach wording uses 'прикріпити' with proper cases
* fix(ui): runtime-scope PR-view remote caches, correct surfaces doc on preview
- Remote/remote-url caches in PullRequestView are keyed by runtime +
directory so a backend switch never serves another runtime's remotes
- surfaces DOCUMENTATION.md: preview is not keep-alive; preview tabs
remount on switch like singleton surfaces
* fix(ui): rail active color, clearer collapse icon, remove dead bottom-terminal dock
Design-review feedback on the context panel:
- Context rail: icons enlarged 16px -> 18px; the active surface is now
highlighted with the primary color only (no background, no scale
animation), replacing the previous scale-up effect that read as a
resize rather than a selected state.
- Files tree: the icon-only 'collapse all folders' toolbar button now
uses collapse-vertical instead of contract-up-down, which was easily
mistaken for a close button. The labelled 'Collapse all' dropdown item
in the session sidebar keeps its icon since text removes the ambiguity.
- Terminal: removed the leftover bottom-dock expand/close buttons that
rendered in the context-panel terminal but controlled a dock that no
longer exists (nothing toggles it anymore), so the expand button
appeared to do nothing and duplicated the panel-header fullscreen
control. Cleaned up the entire inert layer with it: four useUIStore
fields (isBottomTerminalOpen/Expanded, bottomTerminalHeight,
hasManuallyResizedBottomTerminal), five actions, their persistence,
the MainLayout resize listener that only served the dock height, the
dock-driven refit effect in TerminalView, and the
terminalView.bottomDock.* keys across all 10 locale dictionaries.
Validated: ui type-check and lint clean; messages parity test (2 pass)
and useUIStore contextPanel test (13 pass) green; icon sprite
regenerated via icons:generate.
* refactor: use PR visual state for git header icon
Derives the pull request icon color from a single visual state
Covers merged, closed, draft, blocked, and open PR states
Removes conditional class handling from the git header icon
Add a shared OpenChamber control service with two thin adapters — a native
`openchamber` tool injected into managed OpenCode, and new CLI commands — so
users can manage parallel sessions, worktrees, and scheduled tasks
conversationally through agents or from the terminal.
Control plane:
- New openchamber-control service owning a fixed action contract:
projects.list, models.list, session list/create/send/fork/status/messages,
and schedule list/create/run/delete/toggle. Session and worktree deletion
and project registration are deliberately not exposed.
- New openchamber-sessions module owning create/worktree/prompt orchestration,
Goal Mode dispatch, wait semantics (initial idle never counts as completion;
timeout and cancellation are failures), and explicit partial-failure results.
- Scheduled-task logic extracted into a service shared by routes, CLI, and the
agent tool.
Agent tool:
- Managed OpenCode gets a materialized plugin registering one typed tool with
a loopback-only callback, per-child ephemeral bearer (timing-safe, never
persisted or logged), and abort propagation into the service.
- The ~1.5k-token schema applies progressive disclosure: short descriptions,
server-side validation returning actionable usage errors, and intent
guardrails — created sessions/tasks are user-facing work (not age
self-delegation); worktree/goal/agent/variant/wait are omit-by-default;
dispatches produce no completion notification, and later result r
to session.messages, which now returns the authoritative sessionStatus.
- session.create without a user-named model picks from favorites/re
send/fork omit the selection and the service reuses the target session's
last user-message model, agent, and variant before falling back t
- An "Agent control tool" setting (default on, Save + Reload to apply)
disables plugin injection entirely.
CLI:
- New `openchamber session`, `schedule`, `projects`, and `models` commands
with automatic instance targeting, --wait/--timeout/--last-assist
worktree flags, and Goal Mode, preserving interactive, non-TTY, --quiet,
and --json contracts. The control HTTP timeout derives from the w
instead of the 4-second default.
UI:
- New built-in "Schedule a Task" starter (/schedule-task) running a
dialogue that defines a task and offers to create it via the tool after
explicit confirmation; Craft a Goal and Feature Planning gain the
handoff offer, and guided starters reserve the question tool for concrete
option choices. Localized in all 10 locales, migrated into custom
starter lists, hidden on VS Code.
- Sidebar shows CLI/agent-created sessions live via the control eve
- openchamber tool calls render with per-action titles and metadata.
* preserve attachments in UI and materializer
* add support for non-image attachments
* preserve attachments in UI and materializer
* improve attachment rendering and state synchronization
* simplify attachment filtering and key handling
* prevent rendering tool attachments without URLs
* require `f.url` in `imageAttachments` to match gallery indices
Improve OpenChamber responsiveness under large session workloads while fixing
cache, synchronization, and persistence correctness across runtimes, projects,
directories, and worktrees.
- prioritize selected and visible sessions during bootstrap and defer
non-critical enrichment work
- reduce redundant message loading, event processing, store publication, and
hidden sidebar work
- prevent stale session and message requests from overwriting newer
authoritative state
- preserve existing data when authoritative fetches fail instead of treating
failures as successful empty responses
- scope session materialization, messages, drafts, queues, todos, pins,
permissions, folders, tabs, Git state, and pull request data by runtime and
directory identity
- harden runtime switching, reconnect, cleanup, mutation reconciliation, and
persisted-state ordering
- preserve live subagent Task linkage when metadata arrives after an older
message request or while streaming parts are suspended
- coalesce overlapping tail refreshes without losing newer refresh demand
- improve cold-session loading by moving deferrable work out of the critical
bootstrap path
- isolate URL authentication, mobile credentials, native secrets, and other
runtime-owned state across endpoint changes
- bound long-lived caches and remove avoidable allocations from event and
rendering hot paths
- limit virtualization to archive collections where it improves rendering
without disrupting active sidebar layout
- stabilize session folders, pin ordering, expanded state, and persisted
sidebar behavior
- open skill files through the same secure editor and outside-workspace grant
flow used by file navigation, including worktree sessions
- expand regression coverage for stale completions, runtime collisions,
reconnect behavior, persistence races, authoritative empty results, and
subagent refresh ordering
- document the updated synchronization, cache ownership, performance, and
runtime-isolation invariants
- Working indicator shows provider icon and model name with the live
status (e.g. 'Fable 5 is reading file'), localized via
chat.statusRow.modelStatus
- Pin auto-follow to the exact fractional scroll maximum and re-pin on
every passive follow instead of skipping within tolerance
- Round message text line-height to whole pixels so streamed content
grows on the pixel grid; kills the 1px vertical jitter of
bottom-anchored rows during streaming
- Remove assistant message headers; show provider icon, model, agent,
thinking variant, duration and time in the turn footer (metadata left,
hover-revealed actions right)
- Merge turns started by hidden user messages (subagent nudges) into the
previous turn so Activity, footer and spacing stay continuous
- Treat compaction summary text (info.summary) as justification activity
in sorted mode and skip it when picking the turn summary
- Interleave activity segments with standalone tool rows so Agent Task
sits chronologically between activity sections
Replace the legacy terminal flow with a shared authenticated WebSocket
runtime used across web, desktop, relay, and mobile surfaces.
- introduce the v3 terminal protocol with scoped attachments, snapshots,
ordered output, bounded replay history, reconnects, and explicit lifecycle
- harden PTY creation, restart, resize, close, force-kill, idle cleanup,
shell selection, login mode, environment sanitization, and appearance sync
- add runtime-aware terminal APIs with relay authentication and Electron parity
- add a fullscreen mobile terminal workspace with touch scrolling,
long-press selection, safe-area controls, quick keys, and Ctrl/Alt input
- add terminal selection attachments, preview detection, project actions,
shell settings, and localized UI
- harden Ghostty rendering, resize recovery, Unicode handling, block
characters, line height, and stale-row behavior
- remove the obsolete terminal SSE path and update reverse-proxy guidance
- expand terminal runtime, transport, input, selection, and store coverage
- avoid duplicate web builds when preparing mobile assets in root CI builds
Add pin and unpin actions for user and assistant text messages, with clear compaction-survival labels, localized tooltips, status-info active styling, and VS Code gating where the server runtime is unavailable.
Persist pinned message IDs, creation timestamps, and roles under the OpenChamber session metadata namespace using fresh-read merge updates so goal, review, and other metadata remain intact.
Introduce a server runtime that reacts to OpenCode's dedicated session.compacted event, fetches pinned messages by ID, extracts and chronologically orders their text parts, and injects them as hidden synthetic context through prompt_async. The restoration prompt tells the agent to use the context silently while work remains and limits idle summaries to one short paragraph.
Track the last handled compaction summary to avoid replay duplication, tolerate individually missing pinned messages, integrate runtime shutdown, document ownership and limitations, and cover metadata round trips plus compaction injection behavior with focused tests.
Use the Task part's state.metadata.sessionId as the only live identity contract for child sessions. Remove timestamp, status, and ordering heuristics that could misassociate concurrent subagents or repeatedly scan directory sessions while parts stream.
Extract Task metadata parsing and child-summary projection into a focused model with cached projections for unchanged message records. Preserve output and part-level metadata parsing only for legacy persisted records, and keep standalone Task rows visible when sorted activity groups are rendered.
Validated with focused Task and turn projection tests, UI type-check, lint, and dead-code analysis.
* feat(chat): prompt navigator list preview with prompt filtering
The hover preview is now an interactive scrolling mini-list of prompts:
rows render as bordered two-line cards, the highlighted row stays inside
a center dead zone and the list glides only near the window edges, wheel
steps the highlight, and the panel stays open when the pointer moves into
it so a click can be corrected inside the list.
Rail entries are filtered to real prompts: previews are built from
normalized user display parts (synthetic context stripped), fully
synthetic user messages are excluded, and shell-mode messages show their
extracted command via the shared shell bridge helpers.
* fix(chat): render shell command status transitions
The injected /shell text part carries live state in shellAction, which
the render-relevant part comparator ignored — a running→completed update
reached the store but never re-rendered the message row until the next
send. Compare shellAction command/output/status for text parts.
* fix(sync): stream shell bridge part updates while running
Streaming suspension keeps part updates out of the static message records
while an assistant message streams, relying on the live streaming-tail
path to render it. Shell-mode bridge messages are hidden from the
timeline and rendered inside the user row, so they have no live path —
suspension froze their output chunks and left the card without a Show
output action until the run finished. Exempt shell bridges (single bash
tool part parented to a synthetic shell-marker user message) from
suspension; their updates arrive at command-output pace, not delta pace.
* feat(chat): syntax-highlight shell command card
Render the shell-mode command and its output through the shared
WorkerHighlightedCode (Shiki) with bash grammar, matching the bash tool
part presentation, instead of plain pre blocks.
React error #31 (Objects are not valid as a React child) was thrown
intermittently when a task/subagent tool returned structured data
(e.g. { TODO: '...' }) in a field that the OpenCode SDK types as a
plain string. Pathological payloads would propagate into JSX children
without runtime validation, white-screening the chat until refresh.
This change adds a single `coerceToText` helper in toolRenderers.tsx
and applies it at every vulnerable JSX expression:
- ToolPart.tsx:1807,1975 {state.error} (typed string, can be object)
- ToolPart.tsx:1825 {q.question} (QuestionCard input cast)
- ToolPart.tsx:1830 {opt.label} (QuestionCard input cast)
- ToolPart.tsx:1848 task tool markdown output
- ToolPart.tsx:1898 ToolScrollableTextOutput entry
- toolRenderers.tsx {todo.content} x4 in renderTodoOutput
renderTodoOutput now also validates the parsed array at the boundary
(JSON.parse result is filtered to objects whose content and status are
runtime strings), so a single bad row no longer poisons the whole
tool output.
Tests: 12 new unit tests in
packages/ui/src/components/chat/message/parts/__tests__/issue-2011-react-error-31.test.ts
covering the {TODO}-key object path, circular references, and
non-string content/status on parsed todos.
Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
Tool JSON output now starts with a compact navigable summary view.
Expandable tool output includes quick open-file and diff actions for changed files.
Reasoning headers strip stray HTML comments, and navigation tools stay compact.
Normalizes bare ---/+++ headers and paths before rendering
Repairs loosely formatted hunk bodies for patch display
Recounts hunk ranges so diff headers stay accurate
Adds a Last turn scope to DiffView that renders OpenCode snapshot diffs from the latest user message summary without re-fetching git contents. The view hides Review in that mode and carries the selected diff scope through main and context-panel navigation.
Connects latest-turn changed-file chips in chat to the snapshot diff view on desktop and mobile, while keeping older turn chips static/read-only to avoid misleading affordances and extra subscriptions. Updates localized labels and empty states plus changelog.
Validation: bun run type-check (packages/ui); bun run lint (packages/ui).
Keeps the textarea reference available during mobile viewport adjustments
Ensures the composer scrolls back into view after keyboard interactions
Updates text selection menu dependencies to include the current session
Adds a server-side "small model" capability: direct, cheap LLM calls that
reuse the user's existing OpenCode provider logins — the mechanism OpenCode
uses internally for titles and summaries but does not expose through the
SDK or plugins. Zero new dependencies; plain fetch with per-provider wire
formats, credentials never leave the server.
Core (packages/web/server/lib/small-model):
- Resolution mirrors OpenCode's session scoping: explicit settings override
→ small_model from the OpenCode config → family scan within the session's
provider → the session's own model. The global provider scan only serves
callers without a session context, and background callers forbid it
entirely (restrictToPreferredProvider), so conversation content never
reaches a provider the user didn't pick — explicit choices excepted.
- Per-provider auth replicating OpenCode's plugin loaders: GitHub Copilot
(device token as bearer, no exchange), ChatGPT plan via the codex
Responses API (single-flight OAuth refresh written back to auth.json),
Anthropic messages, Google generateContent, generic OpenAI-compatible.
- OpenCode's free models (opencode/big-pickle, *-free) are never called
directly; unauthenticated providers are skipped by design.
- Prompt clamping to the model's catalog context limit; thinking disabled
where a wire switch exists (Z.AI/GLM, MiniMax-M3, Gemini Flash); robust
content parsing with a clear error when a thinking model spends its whol
budget on reasoning.
- Settings → Sessions gains a Small Model group: use-default checkbox plus
an override picker limited to authenticated providers, persisted with
web/desktop/VS Code sanitization parity.
Consumers:
- Session assist: a server-side watcher on the global SSE hub generates a
short recap and one suggested follow-up after a session idles quietly fo
a minute, stored on session metadata (openchamber.assist). Freshness is
keyed to the last assistant message id, so new activity invalidates the
payload everywhere with no extra writes. The chat shows the recap under
the last message after five quiet minutes and the suggestion as a
dismissible chip above the composer (tap fills the input, never sends).
Gated by a new Chat setting (default on) that is a hard generation
switch. Language is anchored to the conversation itself, with a
script-mismatch guard against model/backend language hallucination.
- TTS: a third input mode, summarized — long replies are condensed to
spoken prose before playback on any TTS engine.
- Git: commit-message and PR generation moved off the active chat session
onto the small model fed with real diffs and the commit list (bodies
included), with a session-transport fallback for free-model-only setups.
- Notes: Add to notes distills long selections into 1-3 dense sentences
preserving exact identifiers, with verbatim fallback on failure.
Fixes along the way:
- The global event watcher now starts unconditionally; it was gated behind
the desktop-notify env, leaving the server-side event hub dead in
packaged apps.
- OpenCode re-emits message.updated for old user messages after idle; the
watcher no longer mistakes those for new activity.
- Session metadata merges from a fresh read right before the PATCH, so
writes made during the generation window (suggestion dismissals, review
links) are preserved; the assist runtime stops during graceful shutdown.
- Migrate sidebar session groups, git changes panel, virtualized code
blocks, and JSON tree viewer from virtua to @tanstack/react-virtual;
virtua remains only inside the Pierre diff viewer integration
- Sidebar: preserve scroll position when virtualization enables
mid-session (enable only once the ancestor scroll element is resolved,
seed initial offset from its live scrollTop, render plain rows for the
single pre-paint frame); disable native scroll anchoring on the
sessions scroller; keep row spacing identical between plain and
virtualized modes; absolute row positioning so variable-height rows
cannot drift past the container
- Chat: expand tool/thinking blocks downward by only adjusting scroll
for rows growing above the viewport; raise the desktop history-load
lead to 1.5 viewports so prepends land above the visible area
- Git changes: compute the prefetch window from the first visible row,
skipping overscan rows above the viewport
- Sidebar rows: make the whole highlighted row area clickable, guarded
against double-firing from interactive children
Render a streaming Thinking block inline instead of inside a capped,
independently-scrollable max-height box (the cap now applies only to finished
thinking, for compact review). The nested scroll box was capturing the wheel and
auto-pinning to its own bottom, so the chat could not be scrolled while thinking
streamed. With it gone the chat's own auto-follow owns the scroll.
Two auto-follow refinements make that solid:
- Direction-aware bottom-zone re-engage: scrolling UP into the bottom spacer zone
no longer re-arms follow (which the next growth would yank back). Follow resumes
only when the user arrives at the bottom by scrolling down, is already
following, or is at the true bottom. Kills the dead-zone fight near the bottom.
- Animation guard: while a Thinking block COLLAPSE animation runs, transient
geometry / trailing async scroll events are treated as our own and never
trigger a false release. Genuine user gestures still release instantly.
Gate passive auto-follow on active (working/settling) state so idle layout
churn from virtualizer re-measurement no longer re-pins the viewport to the
bottom. Render default-open tool bodies synchronously on mount so the
virtualizer measures the real row height up front instead of growing a frame
later and lurching scroll past several messages.