Commit Graph
288 Commits
Author SHA1 Message Date
ricautomationandBohdan Triapitsyn 324cf49755 feat: Latex support (#929)
* feat: implement LaTeX math rendering via KaTeX

- Add katex, remark-math, and rehype-katex dependencies
- Integrate KaTeX CSS with light/dark theme color overrides
- Wire remark-math and rehype-katex plugins into MarkdownRenderer
- Support inline ($...$) and display ($$...$$) math delimiters
- Add plan and task documentation

* fix(katex): display inline error messages for invalid LaTeX

- Configure rehypeKatex with throwOnError: false to render errors inline
- Use destructive color var for error text
- Update feature documentation with verification results

* chore: remove latex PR artifacts and hardcoded color fallback

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-04-16 23:30:52 +03:00
jwcrystalandBohdan Triapitsyn fccf4bad32 feat: session worktree isolation (#913)
* feat: add session-worktree contract types and canonicalizeWorktreeState API

- Add SessionWorktreeAttachment type and worktree metadata fields (worktreeRoot,
  worktreeStatus, headState, worktreeSource) to session/worktree types
- Add GitAPI.validateWorktreeDirectory() and canonicalizeWorktreeState() methods
  with full HTTP delegation chain (gitApiHttp → routes.js → service.js)
- Add canonicalizeWorktreeState() implementation that resolves worktreeRoot,
  headState (branch/detached/unborn), attentionReason (merge/rebase/etc), and
  worktreeStatus (ready/missing/invalid/not-a-repo) for a given directory
- Add validateWorktreeDirectory() to check whether a cwd is inside a worktreeRoot
- Add session-worktree-contract.ts: pure functions for resolving session worktree
  state, formatting badges, and building repair actions
- Add session-worktree-store.ts: authoritative Zustand store for session-to-worktree
  attachments, replacing session-ui-store as the source of truth for worktree binding
- Add unit tests for contract functions and store operations

* feat: canonicalize worktree metadata producers

- worktreeManager.listProjectWorktrees: derive headState (branch/detached/unborn)
  from worktree list entry instead of relying on external state, and populate
  all Phase 1 canonical fields (worktreeRoot, worktreeStatus, worktreeSource)
  for each discovered worktree entry
- worktreeManager.createWorktree: include all Phase 1 canonical fields
  (worktreeRoot, worktreeStatus, headState, worktreeSource) in returned metadata
- useDetectedWorktreeRoot: populate fallback canonical fields so that
  sessions without store-based metadata still have worktreeRoot/worktreeStatus/
  headState/worktreeSource when resolved through the fallback path

* feat: route sessions through authoritative worktree attachments

- session-ui-store: import session-worktree-store as the authoritative source
  for session↔worktree attachment state
- setWorktreeMetadata: mirror all writes to session-worktree-store so that
  session-worktree-store.attachments is always the authoritative record;
  local worktreeMetadata map is kept for backward-compatible reads
- Add session-ui-store.test.js with unit tests covering: valid cwd routing,
  degraded fallback, created-for-session attachments, legacy upgrade recovery,
  missing/not-a-repo status handling

* feat: clarify session worktree targets

- session-worktree-contract: extend buildSessionTargetOptions to accept
  pendingBootstrapDirectory and mark pending worktrees with pending=true;
  extend SessionTargetOption to include optional pending flag
- ChatInput: replace manual worktree branch options construction with
  buildSessionTargetOptions; add  prefix for pending bootstrap worktrees
- Add test for pending bootstrap worktree distinction

* feat: show worktree-backed session state

- Header: read worktree attachment from authoritative session-worktree-store
  and render needs-attention/degraded/missing badge with alert icon next to
  current session info when session has degraded/missing/invalid state
- GitView: show 'Worktree features are unavailable' message when session has
  missing worktree status and open-without-worktree-features repair action

* feat: enforce safe mutations for attached worktrees

- session-worktree-contract: add getMutationBlockingReasons helper that returns
  blocking reasons (missing/invalid/attention state) for high-risk mutations
- GitView: gate handleCheckoutBranch, handleCreateBranch, and handleRenameBranch
  with getMutationBlockingReasons; block with explicit toast message when
  worktree is missing, invalid, or has an in-progress git operation
- session-worktree-contract.test: add 7 tests covering mutation blocking for
  missing/invalid/attention states (merge/rebase/cherry-pick)

* feat: implement session worktree isolation

This adds a shared session↔worktree contract that makes session switching
worktree-backed. Sessions attached to different worktrees keep stable branch
context without shared-directory auto-checkout.

Commits:
- feat: add session-worktree contract types and canonicalizeWorktreeState API
- feat: canonicalize worktree metadata producers
- feat: route sessions through authoritative worktree attachments
- feat: clarify session worktree targets
- feat: show worktree-backed session state
- feat: enforce safe mutations for attached worktrees

* feat: make authoritative attachment first-priority source for session directory resolution

Phase A: resolveSessionDirectory, getDirectoryForSession, hooks read
authoritative attachment before falling back to worktreeMetadata.

Phase B: createSession canonicalizes and writes attachment on creation;
setCurrentSession recovers legacy/missing attachments via async
canonicalization.

* feat: make authoritative attachment the primary branch source in Header/GitView

Phase C: Header branch label and GitView project root now read from
authoritative SessionWorktreeAttachment first, falling back to live git
and legacy sources only when attachment is absent, degraded, or legacy.

Adds getAttachmentBranchLabel() helper with 7 tests.

* feat: add runtime parity for validateWorktreeDirectory and canonicalizeWorktreeState

Phase D: Web runtime API, VS Code bridge, and VS Code gitService now
expose validateWorktreeDirectory and canonicalizeWorktreeState, matching
the server-side implementations. All three runtimes (web, desktop, VS Code)
can now delegate worktree canonicalization without HTTP fallback.

* feat: add dirty-tree blocking to mutation safety gates

getMutationBlockingReasons now accepts an optional gitStatus param
and blocks branch mutations when the tree has uncommitted changes.
GitView passes live status to all three blocking call sites.
5 new tests covering dirty, clean, null, combined, and no-file-count cases.

* refactor: revert branch label to live-git-first, remove getAttachmentBranchLabel

Live git is the correct source for branch labels in all scenarios:
dedicated worktree sessions have identical live/attachment branches,
and shared-directory sessions must show the real current branch.

Attachment remains authoritative for worktreeRoot, cwd, degraded/
missing/repair status, and mutation blocking.

* chore: remove session worktree isolation plan doc

* refactor: simplify session worktree isolation implementation

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-04-16 20:13:59 +03:00
Bohdan Triapitsyn 16ec1f605e fix(chat): show blocking requests in sub-session threads too 2026-04-16 19:16:32 +03:00
jwcrystalandBohdan Triapitsyn bb1d522838 fix(task): prevent subagent silent failures in session resolution and polling lifecycle (#903)
* fix(task): prevent subagent silent failures in session resolution and polling lifecycle

Two failure points fixed:

1. Fallback session resolution window too narrow (3s):
   - resolveFallbackTaskSessionId now accepts hasRetried boolean
   - First attempt uses 3s window (avoids binding wrong sessions)
   - Subsequent attempts widen to 8s (handles late-appearing child sessions)
   - Uses useState + useEffect instead of side effects in Zustand selector

2. Polling stops before child results are captured:
   - When child session goes idle before parent sees it active, polling
     would stop without fetching results
   - Added final-fetch-before-stop: a one-shot delayed fetch that runs
     after the settle grace period, ensuring child results are captured
   - Uses taskFinalFetchDoneRef to guarantee exactly one final fetch
   - Preserves existing happy path (active child → normal settle timer)

* fix(task): serialize final fetch after polling stops

Move the subagent final-fetch into a dedicated effect that runs only after
polling has stopped, avoiding races between polling writes and final-fetch
writes to the child sync store.

Also retry safely on final-fetch failure by reopening polling instead of
marking the final fetch as done before the request succeeds.

* feat(task): distinguish child session errors from normal idle

When a subagent terminates with an error, abort, timeout, or failure,
the parent session could not tell it apart from a normal completion.

Changes:
- event-reducer.ts: session.error now stores { type: 'error' } instead of
  { type: 'idle' }, so consumers can distinguish failed from completed sessions
- useSessionActivity.ts: add 'error' phase to SessionActivityPhase and
  isError flag to SessionActivityResult; error phase is non-active (like idle)
  but distinguishable via isError
- ToolPart.tsx: pass childSessionError to TaskToolSummary and show
  'Subagent session ended with an error.' instead of the generic
  'No subagent session id on task metadata.' when the child errored

Other consumers of session_status that only check 'busy'/'idle' are
unaffected — 'error' falls through to existing idle-like behavior.

* Revert "feat(task): distinguish child session errors from normal idle"

This reverts commit b3cc749bde16ed8fc55e4f304dcc455484335fba.

* fix(task): delay fallback retry window widening

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-04-14 20:17:52 +03:00
jwcrystalandBohdan Triapitsyn 5584537da5 fix: question tool content disappears after refresh (#879) (#909)
* fix: show question content in ToolPart instead of 'Awaiting response...' after refresh

Previously, when the question tool was pending/running or completed
without parseable output, the ToolPart fell through to a generic
'Awaiting response...' message. After a page refresh or app restart,
this made questions appear empty even though the tool state still
contained the question input data.

Now the ToolPart reads question text, headers, and options from the
tool state's input field, ensuring question content persists across
refreshes regardless of QuestionCard store availability.

Fixes #879

* fix: restore QuestionCard after refresh and pause working status during active questions

Two fixes for question tool UX:

1. ChatContainer: sessionIsWorking now returns false when there are
   active questions (same as it already did for permissions). This
   prevents the status row from showing 'Asking question...' and
   instead shows the QuestionCard.

2. sync-context: resyncDirectoryAfterReconnect now re-fetches
   pending questions via listPendingQuestions(). Previously only
   sessions and messages were re-fetched on SSE reconnect, so
   questions asked during disconnection were lost, causing
   QuestionCard to disappear after page refresh.

Refs #879

* fix: hide assistant working status while questions are pending

The assistant status hook only special-cased pending permissions, so
question tools still surfaced 'Asking question...' after refresh even
when the UI was already waiting on a QuestionCard response.

Treat pending questions like other blocking requests by clearing the
working indicator until the user answers.

Refs #879

* fix: merge question/permission stores instead of full replace on bootstrap and reconnect

The root cause of QuestionCard disappearing after refresh was a race
condition between SSE events and HTTP bootstrap. Bootstrap and reconnect
both did full replacement of state.question, wiping SSE-delivered data
that arrived between the HTTP call initiation and response arrival.

Changes:
- bootstrap.ts: question and permission stores now use merge semantics.
  Only sessions present in the API response are overwritten. Sessions
  absent from the response are left untouched (they may hold SSE data).
- sync-context.tsx: reconnect question resync uses the same merge pattern.
  No longer clears question entries for sessions not in the API response.
- bootstrap.ts: sdk.question.list() now passes directory parameter to
  scope the query correctly.

This ensures SSE-delivered question data survives the bootstrap window,
while still allowing the API response to be authoritative for sessions
it covers.

Refs #879

* fix: prune stale pending requests after reconnect

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-04-14 20:02:07 +03:00
Bohdan Triapitsyn 01ca471776 fix: render JSON code block quotes correctly 2026-04-14 19:40:36 +03:00
Alexander BusseandAlexander Busse 055b6f6af0 feat(tts): add OpenAI-compatible custom server TTS provider with configurable model, pitch, and volume (#859)
Co-authored-by: Alexander Busse <alex@ableph.net>
2026-04-12 10:33:15 +03:00
Bohdan Triapitsyn 844052e599 fix: restore desktop startup and align tool previews with SDK updates
Add reflect-metadata bootstrap so desktop sidecar no longer crashes on startup.
Update SDK v1.4 compatibility for model variant and diff payload handling.
Unify write/edit/apply patch expanded previews and hide write success output noise.
2026-04-12 00:40:49 +03:00
Gyumin Lee e6ac6badde fix: restore variant restoration for OpenCode 1.4.0 (#871)
OpenCode 1.4.0 moved `UserMessage.variant` from the top-level to
`UserMessage.model.variant`. OpenChamber still read the legacy location,
so variant selection silently stopped being restored on sessions served
by OpenCode 1.4.0+.

Read `model.variant` first and fall back to the legacy top-level field
so both pre-1.4 and 1.4+ OpenCode servers keep working.
2026-04-11 23:50:13 +03:00
jwcrystal b91a72c74b fix(chat): replace hover bridge with padding to unblock desktop interactions (#885)
* fix(chat): replace hover bridge with padding to unblock desktop interactions

PR #826 removed pointer-events-none from the hover bridge div to fix
short user message revert button hover, but the transparent 44px
full-width bridge blocked all interactions (clicks, text selection)
with messages below it on Tauri desktop where the rendering difference
made the overlay consume pointer events across the entire message area.

Replacing the bridge div approach with pb-12 padding on the message
bubble container (max-w-[85%]) when sticky inline hover is active.
This extends the hover hit area downward within the bubble's own
padding, maintaining continuous hover path from bubble to action
buttons without any overlay that could block sibling elements.

The padding approach works because:
- The 48px bottom padding extends max-w-[85%]'s hover area to cover
  the gap between bubble bottom and the absolute-positioned buttons
- Hovering the padding counts as hovering group/user-shell (parent)
- No absolute-positioned invisible layer means nothing blocks the
  next message's interactive content

* fix(chat): reduce hover padding from pb-12 to pb-5 for tighter spacing
2026-04-11 23:27:47 +03:00
jwcrystal 4fd1d68eb9 fix(chat): allow revert button hover on short user messages (#826) 2026-04-08 10:44:17 +03:00
Bohdan Triapitsyn 46d166e205 fix: drop stale chat memo boundaries
Remove remaining chat row and body memo comparators that were still keeping finished background session responses blank or stale in the UI.
2026-04-07 21:48:56 +03:00
Bohdan Triapitsyn 952132a08d fix: remove desktop translucency to reduce macOS compositing cost 2026-04-07 21:46:38 +03:00
Bohdan Triapitsyn 605e4bf42c fix: stop stale chat renders after session switches
Roll back aggressive chat reuse paths that froze message parts, tool state, and completed assistant bodies when sessions were inactive or backgrounded.
2026-04-07 20:33:55 +03:00
Bohdan Triapitsyn db19f83f46 fix: restore remote OpenCode providers and startup errors
- Fix OpenChamber proxying to the correct OpenCode host for remote VPS setups
- Show a clear empty-state error when OpenCode is not reachable
- Harden session event routing for early or mismatched directory events
2026-04-07 01:17:12 +03:00
Bohdan Triapitsyn 8de1884f5c refactor: remove plan polling, add message-driven detection via usePlanDetection hook 2026-04-06 20:44:13 +03:00
Bohdan Triapitsyn a516650f96 fix: stabilize chat auto-scroll and bottom resume
- Unify send and session resumes around the latest chat tail
- Keep smooth follow active during assistant message growth
Remove staged chat rendering from the main scroll path
2026-04-06 20:18:20 +03:00
Bohdan Triapitsyn 9254ec0783 fix desktop notifications and chat scroll stability 2026-04-06 17:36:08 +03:00
Bohdan Triapitsyn e42471ee7b chore: update workspace dependencies and chat behavior
- Adjust package metadata and lockfiles across the workspace
- Refine chat sidebar and auto-scroll behavior in the UI
- Update PWA install handling alongside runtime package changes
2026-04-06 13:04:36 +03:00
Bohdan Triapitsyn 4db1e6baad perf: reduce active-turn chat rerenders during streaming
- Keep older assistant messages in the active turn stable while new content streams
- Prevent existing tool rows from rerendering when new tool activity is appended
- Tighten message and tool memo comparisons to isolate render work to changed rows
2026-04-06 00:43:19 +03:00
Bohdan Triapitsyn 4a3b7ec5c9 fix: restore smooth chat auto-scroll and live compaction updates
- Keep assistant auto-scroll smooth during active responses without periodic snaps
- Restore live `/compact` rendering and streamed compaction summaries without refresh
- Preserve global event handling while routing session and message updates efficiently
2026-04-05 23:01:12 +03:00
Bohdan Triapitsyn f2f57ca281 fix: restore live compaction updates in chat
- Show `/compact` command and streamed compaction summary live without refresh
- Handle versioned sync event types so message and part updates are processed
- Route session/message events via indexed lookup to avoid hot-path store scans
2026-04-05 21:22:17 +03:00
Bohdan Triapitsyn dfc61d7671 refactor: replace chat markdown renderer
- Replace Streamdown-based chat markdown rendering with the new renderer path
- Preserve OpenChamber table, code block, and mermaid presentation
- Remove leftover Streamdown dependencies and compatibility hooks
2026-04-05 19:24:23 +03:00
Bohdan Triapitsyn 3bf9947388 perf: reduce status row streaming churn
Replace live assistant-part status subscriptions with coarse session phase, retry, permission, and abort state so the composing row no longer rerenders on every text delta.
2026-04-05 17:29:14 +03:00
Bohdan Triapitsyn c88e68e5f7 perf: reduce chat streaming rerenders
- Keep historical chat messages stable during streaming
- Isolate active stream state to the current message path
- Reduce chat container churn from sync message updates
2026-04-05 16:56:05 +03:00
Bohdan Triapitsyn e56f2f99cf perf: freeze detached chat history updates
Stop historical chat view from waking on live tail deltas by suspending detached part updates, narrowing scroll manager dependencies, and hardening sync-context HMR behavior during dev.
2026-04-05 15:58:06 +03:00
Bohdan Triapitsyn 4663abd6f3 refactor: isolate chat hot paths and lazy server watchers
Split static history from live tail, stabilize chat data identities, and reduce scroll-shell churn so old viewport content stops waking on stream deltas. Also defer watcher startup and health polling so idle runtime work better matches actual usage.
2026-04-05 15:36:11 +03:00
Iuliia Ivashko fc44465c00 fix(chat): render LSP diagnostics in tool output (#837) 2026-04-04 11:15:32 +03:00
Bohdan Triapitsyn 8587db89b1 perf: reduce UI render fanout and scroll jitter
- Cut broad render fanout across the app by replacing shared-store whole-object subscriptions with leaf selectors, memoizing hot chrome boundaries, and isolating disabled global providers from live session/message state. This keeps header controls, composer toolbars, side panels, and other non-hot UI surfaces from repainting on every assistant update or keystroke.

- Rework sidebar session ordering so recent, project groups, and worktree groups derive from one ordering source while avoiding streaming-time thrash. The sidebar now uses a stabilized session snapshot, preserves structural identity for unchanged rows, reads live row status/details per session, and applies a one-shot sort bump on idle->busy instead of continuously resorting during activity.

- Fix chat/input scroll instability by separating viewport-resize handling from message-growth handling, disabling conflicting native scroll anchoring, and stopping textarea autosize from collapsing on every growth keystroke. This removes the multiline typing jiggle during streaming and reduces unnecessary composer rerenders.

- Also gate voice context wiring behind voice-mode enablement and codify the learned render/scroll/order anti-patterns in AGENTS.md so future changes avoid the same classes of regressions.
2026-04-04 02:19:55 +03:00
Bohdan Triapitsyn d982171689 refactor: centralize git and pr store refresh 2026-04-03 19:47:01 +03:00
Bohdan Triapitsyn 246464a325 perf: remove animated logo from idle and loading surfaces 2026-04-03 16:44:31 +03:00
Bohdan Triapitsyn 5045d21821 perf: reduce idle desktop load by gating plan-mode checks
- Add startup feature flag for plan mode from OPENCODE_EXPERIMENTAL* env vars.
- Stop recurring app health polling and keep a one-time startup check.
- Skip plan-mode synthetic parsing and plan tab/file work when flag is off.
2026-04-03 13:59:10 +03:00
Bohdan Triapitsyn a36b11d54e fix: limit PWA input spacing behavior to mobile
- Prevent desktop chat input from inheriting mobile bottom safe-area gap
- Apply chat input safe-area classes only on mobile
- Scope standalone PWA safe-area and blur overlay CSS to mobile runtime
2026-04-03 12:45:00 +03:00
Bohdan Triapitsyn 9d78c48949 fix: keep selected session header details stable
- Prevent header flicker to Untitled during session refresh
- Resolve header session data from global sessions first
- Pass session directory hints on more session-switch paths
2026-04-03 12:38:03 +03:00
jwcrystalandBohdan Triapitsyn e63450ae2c fix: resync session state after SSE reconnect to prevent stuck subagent UI (#817)
* fix: resync session state after SSE reconnect to prevent stuck subagent UI

When a subagent completes while the page is in the background (common on
mobile PWA and desktop webview), the final SSE events are lost. The UI
then stays stuck on 'Waiting for subagent activity...' because:

- part.state.status never transitions to 'completed'
- session_status is never updated to 'idle'
- activeLatched remains true indefinitely

Fix:
- Add onReconnect callback to event pipeline, fired after SSE reconnect
- Add pageshow listener for bfcache restores (mobile PWA back-forward)
- On reconnect, re-fetch session list for directories with active sessions
- Pass explicit directory to useSessionActivity in ToolPart for subagents
  to ensure the correct child store is queried

Closes #810

* fix(chat): resolve pending subagent task binding before metadata arrives

* fix: restore subagent activity and tool visibility after reconnect

- Resyncs session status and child session data after SSE reconnect
- Ensures child task tool messages are read from the correct directory
- Prevents stale assistant fallback from keeping sessions stuck as active

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-04-03 00:29:49 +03:00
Nguyễn Ngô Thượng d68bec491c fix(chat): improve error message display with better styling and word-break (#767)
- Add error icon and colored background/border for error messages
- Add break-all and max-w-full to prevent long error text overflow
- CSS: word-break and overflow-wrap for inline code in error messages
2026-04-01 18:36:47 +03:00
Nguyễn Ngô ThượngandBohdan Triapitsyn 6180f388e8 feat(json): add interactive JSON tree viewer with collapse/expand and rainbow colors (#786)
- Add JsonTreeViewer component with collapse/expand for nested objects and arrays
- Add rainbow colors by depth level using CSS color-mix() with syntax theme tokens
- Add virtualization support (@tanstack/react-virtual) for large JSON files (>200 nodes)
- Add JsonTreeView wrapper with Expand All / Collapse All toolbar
- Integrate into FilesView: tree/text toggle button, JSON file detection
- Integrate into ToolPart: auto-detect JSON tool outputs, render as tree
- Integrate into ToolOutputDialog: JSON tree view in expanded dialog
- Add jsonTreeUtils.ts: parse, flatten, path utilities

No new dependencies - uses existing @tanstack/react-virtual.
Colors derived from existing syntax.* theme tokens - works across all themes.

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-04-01 17:30:28 +03:00
Dave Otero 1af2b9468f feat(chat): add arrow key navigation for thinking mode in model selector (#769)
* feat(chat): add arrow key navigation for thinking mode in model selector

Adds keyboard arrow key navigation for thinking mode selection within the model picker, enabling a fully keyboard-driven workflow for model and variant selection.

Changes:
- Model Selector: Added ←/→ arrow key handling to cycle thinking variants while picker is open
- Introduced transient state to track per-model variant selections during picker lifecycle
- Updated metadata display logic to show thinking mode only after adjustment with arrow keys
- Added contextual footer hints that appear only when highlighted model supports thinking variants
- Modified Enter key behavior to apply both selected model and pending thinking variant together
- Help Dialog: Added in-picker navigation shortcuts (↑↓ navigate, ←→ adjust thinking)
- Documentation: Updated CHANGELOG.md files with feature entry

Behavior:
- Navigate models with ↑↓, adjust thinking with ←→, press Enter to apply both
- Footer hints update dynamically based on highlighted model capabilities
- Thinking display persists only for models adjusted with arrow keys
- No-op for models without variants (clean, no feedback)

Testing:
- Type-check, lint, and build all pass
- Manual testing verified on dev server
2026-04-01 16:17:04 +03:00
Bohdan Triapitsyn 81c41388ac refactor: simplify file reference interactions by removing unused validation logic 2026-04-01 12:25:58 +03:00
YifanandBohdan Triapitsyn 14fc741cc9 feat(fs): add stat API for markdown file validation (#774)
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-04-01 10:18:05 +03:00
kalac2232and郭皓楠 b59040ff18 feat: escape HTML in user messages (#782)
- Add HTML escaping for user messages in Markdown mode
- Fix agent mention link rendering order issue
- Plain mode remains unchanged (uses React elements)

Tested:
- User messages: <div> now displays as &lt;div&gt;
- Agent mentions render correctly as clickable links in Markdown mode

Co-authored-by: 郭皓楠 <guohaonan@MacBook-Neo.local>
2026-04-01 09:49:58 +03:00
Bohdan TriapitsynandIuliia Ivashko c9e31a0e6c perf: harden sync architecture and modularize runtimes (#803)
* fix: added desktop app background throttling

* perf: add streaming debug metrics panel

- Show streaming performance metrics in the debug panel
- Auto-enable stream profiling while the panel is open
- Add JSON export for sharing UI and VS Code metrics

* perf: batch streaming updates more aggressively

- Buffer message deltas and metadata updates to cut render churn
- Skip no-op part updates before they touch the message store
- Fix the desktop debug panel shortcut binding

* perf: split streaming event handling and coalesce deltas

- Move streaming content events onto a dedicated fast path
- Defer non-critical stream side effects off the hot path
- Merge repeated message delta events before they reach the UI

* perf: isolate streaming rows from chat rerenders

- Memoize chat rows against render-relevant message changes only
- Read live assistant text directly from store to narrow streaming updates
- Split the active streaming entry from the stable message list path

* perf: streamline chat streaming and SSE proxying

- Reduce chat rerenders around the active streaming path
- Simplify server SSE forwarding to avoid duplicate proxy work

* fix: preserve the first streaming text chunk

- Show the initial text chunk immediately before batched deltas arrive
- Bypass batching for the first text or reasoning part update
- Keep later streaming updates buffered for performance

* perf: align streaming/render hot paths with opencode parity

* perf: harden turn/cache stability and stale delta suppression

* fix: stabilize chat rendering and disable timeline interactions

- Disabled timeline dialog access from shortcuts, commands, and chat input
- Reduced chat render churn by simplifying message list and turn staging behavior
- Improved session-switch stability to prevent update-depth crashes

* perf: track static message rerenders during streaming

* perf: reduce sorted-mode activity rerender fanout

* perf: reduce chat rerender fanout and add active-turn metrics

- Reduced sorted-mode rerender coupling by tightening turn context propagation
- Added a metric for static rerenders outside the active turn during streaming
- Exposed new chat render counters in the debug panel for parity tracking

* fix: keep sorted activity mounted while stream grows

* fix: stabilize session and history scroll rendering

* refactor: decouple server routes from index

* refactor: extract fs module from server index

* refactor: move opencode route ownership into module

* refactor: extract notification route registration

* refactor: extract opencode and notification runtimes from index

* refactor: extract settings runtime and complete server modularization pass

* refactor: modularize server config, skills, icons, and tunnel routes

* refactor: extract server modules from monolithic index.js

Split proxy, routes, runtime helpers, and notification emitter
into dedicated modules under packages/web/server/lib/.

* refactor: replace session/message stores with SSE-driven sync layer

Delete ~9200 lines of old architecture (useEventStream, messageStore,
sessionStore, useSessionStore, questionStore, useTodoStore, client SSE).

New sync layer: event pipeline with coalescing + 16ms flush, pure event
reducer, per-directory child stores with LRU eviction, cursor pagination,
optimistic updates, deferred timeline staging, text throttle.

Migrate all UI consumers to sync hooks (useSessionMessages,
useSessionMessageRecords, useSessionStatus, useSessionPermissions, etc).

Strip session-ui-store to UI-only state, delegate SDK ops to
session-actions with abort-if-busy, optimistic store updates, and
response merging for revert/fork/archive/delete.

Add notification-store for SSE-driven session attention tracking,
cross-directory GlobalSessionStatusStore for sidebar indicators,
client-side diff snapshot sanitization to prevent memory bloat,
and revert message filtering via useVisibleSessionMessages.

* feat: notification store, session actions, activity detection

Add notification-store.ts for SSE-driven attention tracking.
Add sanitize.ts to strip diff snapshot memory bloat.
Add session-actions.ts with optimistic revert/fork/archive/delete.
Improve useSessionActivity with incomplete-message fallback.
Delete useServerSessionStatus polling hook.

* fix: add directory param to all SDK calls, fix command/shell/abort routing

All SDK calls in session-actions.ts now pass directory parameter —
required by OpenCode server to scope session operations. Without it,
abort, commands, revert, fork, and other operations returned 500.

Add routeMessage() in session-ui-store for shell mode (session.shell),
slash commands (session.command), and normal prompts. Command lookup
checks both sync child store and useCommandsStore. Handle /compact
locally via session.summarize().

Implement getContextUsage() to restore header context usage display —
reads token counts from last assistant message in sync store.

* refactor: replace custom API proxy with http-proxy-middleware

Remove ~280 lines of custom proxy code: forwardSseRequest,
forwardGenericApiRequest, collectRequestBodyBuffer, header
manipulation, hop-by-hop filtering, SSE block buffering.

Replace with single createProxyMiddleware() call that handles
SSE streaming, large bodies, and timeouts out of the box.
Dynamic router for OpenCode port changes after restarts.
Auth headers injected via proxyReq hook.

Keep: readiness gate, Windows session merge, API prefix detection.

* perf: targeted event draft cloning to fix streaming render cascade

Event handler was eagerly cloning all state slices on every event,
breaking Zustand selector referential equality. During streaming
(~60 events/sec), this caused every subscriber to re-render regardless
of which slice actually changed.

Now only clones fields the specific event type mutates. Also extracts
StatusRowContainer to isolate high-frequency useAssistantStatus
subscription, removes dead messageStreamStatesMap subscription from
ChatContainer, and narrows useAssistantStatus to only track last
assistant message parts.

MessageList renders: 1972 → 296 per streaming session (-85%).

* fix: null safety for sync state slices

Add defensive ?? {} guards on permission, question, session_status,
and message record access. Prevents crashes when child store state
is partially initialized during bootstrap.

* perf: dedup inflight SDK calls, extract concurrency util, delay PR tracking

Extract mapWithConcurrency to shared lib/concurrency.ts. Add in-flight
dedup for loadProviders/loadAgents to prevent concurrent duplicate SDK
calls. Delay initial PR background tracking by 5s to reduce startup
CPU burst.

* fix: header session lookup across all child stores

Session title and context panel click failed when session belonged to
a different directory than the current child store. Fall back to
getAllSyncSessions() to search all initialized stores.

* chore: bump @opencode-ai/sdk to 1.3.5

* docs: add sync event handling guide

* Optimize session prefetch and improve delete/archive UX

- Add settlement delay to session prefetch to avoid race conditions on
  rapid session switches
- Reduce git diff prefetch and session cache limits for better performance
- Implement optimistic UI updates for session delete/archive operations
  with proper rollback on failure
- Wire session prefetch hook into SessionSidebar with sync integration

* Add file content cache and sync optimizations

- Wrap FilesAPI with in-memory LRU cache for file content with dual
  constraints (entry count and byte size)
- Optimize chat timeline scroll restoration using useLayoutEffect
- Preserve React references in message and part arrays to prevent
  unnecessary re-renders when prepending history
- Add session prefetch TTL cache to prevent redundant fetches
- Integrate session prefetch cache clearing with eviction flow

* Improve session sidebar error handling and add diff prefetch filtering

Load active and archived sessions independently using Promise.allSettled
to prevent one failure from blocking the other. Add retry logic to session
API calls and skip large files during diff prefetch to improve performance.

* Replace sendMessage with optimisticSend wrapper

Introduces optimistic UI updates for normal chat messages to provide
instant feedback. Messages appear immediately in the UI while the API
call executes in the background, with automatic rollback on errors.

* perf: split stores, proper optimistic send, fix revert/directory bugs

- split session-ui-store into voice/input/selection/viewport stores
  to reduce subscriber re-evaluation during streaming
- wire optimisticSend through useSync shadow Map infrastructure
  matching OpenCode's pattern (no heuristic part detection)
- port OpenCode Identifier.ascending ID format for correct sorting
- pass messageID to promptAsync to prevent duplicate messages
- fix worktree directory not propagating to session actions
  (dynamic dir() via opencodeClient.getDirectory)
- fix setCurrentSession accepting directoryHint for new sessions
- fix revert not hiding messages (session limit was 5, bumped to match loaded count)
- fix revert optimistic message removal from store
- fix load-more flicker (useLayoutEffect scroll compensation)
- add prefetch TTL cache, file content LRU cache
- add session prefetch for adjacent sessions
- add instant archive/delete (optimistic before SDK call)
- migrate legacy window.__zustand_session_store__ to session-ui-store
- add retry + independent error handling for archived sessions
- add AGENTS.md performance rules

* perf: startup optimization — dedup, caching, light git status, diff rendering gates

- defer diff prefetch to git tab open, reduce concurrency 4→2, skip >500 changed lines
- cap project git checks concurrency (2), directory status probe (3)
- dedup provider/agent loading, github auth, worktree list (in-flight + TTL caches)
- delay PR tracking 5s, cache 403 search failures per-repo
- coalesce settings PUT (200ms debounce), cache settings GET (2s TTL)
- cache canonical directory resolution (60s TTL)
- persist missing directory status to localStorage (10min TTL)
- light/heavy git status: polling skips numstat+line counting+rev-list
- large diff rendering gate (>500 lines → "render anyway" button)
- tokenization degradation for >500KB files in Pierre
- parallelize main.tsx pre-render awaits
- batch sidebar file tree expanded paths restoration (3 at a time)
- remove bare useConfigStore() subscription in AgentsPage
- sync worktree sandboxes to OpenCode SQLite DB
- fix RightSidebarTabs ternary → explicit tab matching
- defensive guards on sync state (session_status, permission, question, message)

* fix: add defensive guards on remaining sync state field accesses

guard session_status, permission, message, todo, part, config with ?? {}
in useDirectorySync selectors, session-cache, and bootstrap

* fix: add missing directory dep to useCallback in use-sync.ts

* fix: preserve diffStats when light-mode polling overwrites status

* perf: optimize startup git status polling and diff rendering

Preserves diff stats when lightweight polling updates repository status
Reduces startup overhead with smarter git polling and store updates
Adds detailed optimization and migration docs for next performance steps

* fix: keep chat diff stats stable during git status updates

Prevents lightweight git polling from dropping diff statistics
Keeps MessageList diff indicators consistent while status refreshes
Improves reliability of git-aware chat rendering

* fix: user animation replay, queued message variant, startup provider loading

- consume animation ID after first play to prevent re-animation
  on neighbor assistant message completion
- capture send config (model/agent/variant) at queue time matching
  OpenCode's FollowupDraft pattern instead of re-resolving at send time
- replace one-shot startup recovery effect with polling interval
  that retries every 2s until providers and agents load
- fix optimistic bridge to avoid re-render loop (stable ref wrappers)

* chore: update tauri to 2.10.3 and all plugins to latest

- tauri 2.9.4 → 2.10.3
- tauri-build 2.5.3 → 2.5.6
- tauri-plugin-dialog 2.4.2 → 2.6.0
- tauri-plugin-log 2.7.1 → 2.8.0
- tauri-plugin-shell 2.3.3 → 2.3.5
- tauri-plugin-updater 2 (floating) → 2.10.0 (pinned)
- @tauri-apps/api ^2.9.0 → ^2.10.1
- wry 0.53.5 → 0.54.4 (transitive)

* refactor: decouple web server index orchestration runtimes

* fix: align VS Code runtime behavior with web and reduce draft view CPU load

- Queue VS Code bridge and SSE startup requests until API readiness to avoid false bootstrap failures
- Make agent manager actions directory-aware and remove real worktrees with safer partial-failure handling
- Replace heavy logo animation path with a lightweight pulse to cut draft-session CPU usage

* fix: restore auto-selected file sending in chat input

- Send server-selected files as proper file URLs in the message payload
- Include server-backed attachments in submit flow instead of dropping them
- Restore queued-message attachments through the refactored input store

* fix: restore session model selection consistently on session switch

- Restore agent, model, and variant from the latest loaded user message for each session
- Wait for session messages before applying restored selections to avoid stale or missing state
- Remove legacy session-choice inference paths that caused overlap and instability

* fix: restore permission replies and auto-accept across sessions

- Scope permission and question replies to the target session directory so answers take effect reliably
- Make permission auto-accept immediately handle pending requests and react to new permission prompts
- Keep parent-session handling working for child-session requests through the shared response path

* feat: add reusable fuzzy branch fuzzy-search helper and dialog integration (#798)

* feat: add reusable fuzzy branch search for worktrees

* chore: drop planning docs from feature branch

* feat: make worktree branch refresh manual

* feat: add configurable session retention action

* refactor: centralize global session state in ui store

* fix: cancel debounced permission push after reply

* docs: clarify global and directory session store architecture

* docs: refine agent development rules and session activity guidance

- Clarify agent code of conduct and durable development patterns
- Add explicit shared-store rerender and live-state guidance
- Narrow session activity fallback to avoid stale working state

* chore: updated .gitignore

---------

Co-authored-by: Iuliia Ivashko <yulia.ivashko@gmail.com>
2026-03-31 18:47:00 +03:00
Bohdan Triapitsyn 1231fd773e feat: improve VS Code dev flow and stabilize sidebar/chat behavior (#754)
* fix: improve session sidebar tooltip and truncation behavior

- Keep new-draft tooltip anchored to its trigger button
- Fix minimal-mode worktree/group header text truncation
- Tune minimal-mode right padding to reduce early label clipping

* fix: render reasoning through markdown pipeline

- Use Streamdown rendering for reasoning in live chat mode
- Remove italic styling from reasoning text
- Render expanded reasoning content with MarkdownRenderer

* chore: remove legacy electron dependencies

- Removed unused Electron packages from root and UI manifests
- Deleted obsolete Electron context menu type declaration
- Regenerated lockfile after dependency cleanup

* fix: handle non-repository folders in git status API

- Prevent 500 errors when status is requested outside a valid Git repo
- Improve repository detection using `git rev-parse --git-dir`
- Reduce noisy server logs for expected non-repo status checks

* fix unloaded session chat layout flicker

* fix: reduce noisy TTS status polling

Cache and dedupe TTS status requests, and only check provider availability when the related voice features are enabled so disabled voice setups stay quiet.

* perf: throttle background PR git status refreshes

* fix: improve VS Code Explorer file drop mentions in chat

- Add Explorer context action to insert selected files as @mentions.
- Handle Explorer drag-and-drop to prefill @file mentions instead of attachments.
- Prevent duplicate plain-path text when dropping multiple files.

* fix: deduplicate recent sessions in VS Code sidebar

- Hide sessions from main list when already shown in recent
- Apply dedup only in VS Code runtime
- Keep session search behavior unchanged

* feat: add true HMR dev flow for VS Code extension

- Load VS Code webview from Vite dev server with React refresh preamble
- Add `vscode:dev` runner that starts watchers and opens Extension Development Host
- Update VS Code dev docs and scripts to use the new HMR startup flow

* feat: polish VS Code session sidebar and attachment UX

- Add resizable sessions sidebar in VS Code layout
- Tighten session list spacing and hover behavior in VS Code
- Remove bulk file/image attach success toasts while keeping error toasts
2026-03-23 23:51:55 +02:00
Bohdan Triapitsyn 53c2a0d919 feat: instant draft-first worktree creation and multi-run launcher redesign (#741)
## Summary

- **Instant worktree creation from chat draft**: selecting "+ New worktree" in the draft branch selector immediately creates a session draft and bootstraps the worktree in the background — no modal interruption
- **Redesigned multi-run launcher**: compact 2-column grid layout in a right-sized dialog with scroll shadow, sticky footer, tooltips replacing verbose descriptions, and project icons in the selector
- **Branch selector aligned across surfaces**: multi-run and agent manager branch pickers now use the shared git store and match NewWorktreeDialog behavior (same default resolution cascade, no synthetic HEAD option, all branches shown)
- **Opaque model multi-select dropdown**: fixes text bleed-through on translucent backgrounds by compositing `--surface-elevated` over `--surface-background`
- **"+ New" inline button in sidebar worktree headers** for faster worktree creation

## Why

Worktree creation was behind modal flow that interrupted the user's train of thought. The draft-first approach lets users start typing immediately while the worktree bootstraps. The multi-run launcher had an oversized form layout with redundant explanations, and its branch picker behaved differently from the main worktree dialog - causing confusion about which branches were available and what the default was.
2026-03-22 22:31:29 +02:00
Bohdan Triapitsyn 7356090e3d fix: improve cross-runtime session UX and platform config handling (#725)
* fix: make textarea focus highlight render inside

Apply inset focus ring to shared textarea component
Prevent focus border from appearing clipped near container edges

* fix: build desktop sidecar with target-matched architecture

Map Tauri target triples to Bun compile targets
Pass explicit Bun compile target for sidecar builds
Prevent x86_64 releases from shipping arm64 sidecar binaries

* fix: allow Windows git custom binary paths

Enable safe use of resolved custom git executable paths
Prevent git status failures when path contains restricted characters
Keep default behavior unchanged for plain git invocations

* fix: allow toggling diff line wrap on mobile

Stops forcing wrapped lines in mobile diff view
Line-wrap button now reflects and applies user preference

* fix: align VS Code managed server env with shell settings

Import login-shell environment variables before starting managed OpenCode
Apply Windows and Unix shell snapshot resolution for parity
Improve proxy-dependent provider connectivity in VS Code extension

* fix: respect user scope when adding MCP servers

Prevent user-scope MCP entries from being written to project config
Keep project writes only for explicit project scope

* fix: show linked GitHub issues and PRs as user message attachments

Preserve synthetic issue/PR context parts during message filtering.
Convert synthetic GitHub context JSON into attachment-style user parts.
Open issue/PR attachment links via shared external URL helper.

* fix: restore and polish project notes in sessions sidebar

Restored the Notes button in the left sessions sidebar header
Improved notes panel layout with wider dialog, larger notes area, and project name in the header
Refined todo rows with inline expand/collapse text and stable action/checkbox alignment

* fix: hide sidebar footer actions in VS Code runtime

Remove Settings, About, and Shortcuts buttons from the sessions sidebar footer in VS Code
Keep update button behavior unchanged across runtimes

* fix: normalize Windows paths for VS Code session loading

Canonicalize drive-letter casing in session path normalization
Align VS Code workspace path persistence with the same Windows path format
Normalize client directory context before API calls to keep session filtering consistent

* fix: open linked GitHub attachments with shared URL helper

Use runtime-aware external URL opening for issue/PR attachment links.
Keep GitHub attachment labels readable without altering normal file name rendering.

* fix: keep user MCP config writes out of project files

Respect user scope when selecting config write target
Prevent MCP user entries from being written to project opencode.json

* fix: prevent project menu from overlapping new session button

Align project menu positioning for non-git and git project rows
Avoid kebab-menu and plus-button overlap in sessions sidebar
2026-03-20 18:58:13 +02:00
jwcrystalandBohdan Triapitsyn bf61ccedc7 fix: external links in desktop app - context menu and open behavior (#716)
* fix: allow native context menu on links in chat messages

The desktop app was blocking the context menu on all elements except
specific allowlisted ones (terminal, input, textarea, etc.). This
prevented users from right-clicking on HTTP links in chat messages
to access the 'Open Link' option.

Added 'a' (anchor) tag to the allowlist to restore native context
menu functionality for links.

Fixes #708

* fix: use tauri.shell.open for external links in desktop app

- Add global window.open override to init_script that routes HTTP/HTTPS
  URLs through tauri.shell.open() instead of window.open()
- Add openExternalUrl utility that prefers tauri.shell.open with window.open fallback
- Add openExternalUrl to MarkdownRenderer for link safety.onLinkCheck
- Replace window.open with openExternalUrl in ProvidersPage for OAuth URLs

Fixes #708

* fix: unify external link opening across desktop and UI

Added a shared URL opener that only allows http/https links.
Replaced duplicated Tauri/window link-open logic in key UI sections.
Removed fragile desktop window.open override and markdown external-link modal behavior.

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-03-20 13:11:45 +02:00
Bohdan TriapitsynandIuliia Ivashko 321cc7252a Major UI refresh: sidebar redesign, theme expansion, and chat performance optimizations (#706)
## Summary
Complete sidebar redesign and comprehensive UI polish pass with performance optimizations, theme system refinements, and desktop integration improvements.

## Key Changes

**Sidebar & Navigation Redesign**
- Redesigned sessions sidebar layout with unified button primitives
- Added activity sections with project grouping and improved session organization
- Refined sidebar corners, spacing, and visual hierarchy
- Removed NavRail component in favor of streamlined sidebar
- Stabilized sessions bar toggle position in fullscreen mode

**Performance Optimizations**
- Reduced chat streaming CPU usage and storage churn
- Optimized task tool polling and live timers with debouncing
- Prevented chat state races and reduced background request load
- Debounced draft writes and coalesced session reloads
- Optimized message store updates and turn tracking

**Theme & Visual System**
- Added theme-aware window corners (desktop) and border radius tokens
- Introduced glassmorphism effects on desktop sidebar
- Added backdrop blur to UI elements

**Chat Experience**
- Added session-based permission auto-accept toggle in chat input
- Polished permission shield UX with improved icon sizing and spacing
- Fixed chat scroll-to-bottom behavior and timeline tracking
- Enhanced tool output display with better path label detection
- Removed duplicate draft context details in chat header
- Added text selection menu to chat messages

**Git Improvements**
- Refreshed git history visual design with cleaner dividers
- Added remote removal action in sync selector
- Stabilized git polling to prevent excessive requests
- Improved tool output rendering for git operations

**Settings & Panels**
- Fixed mobile scrolling on settings pages
- Made outside-click settings close instantly
- Reduced settings load churn and CPU spikes
- Improved services dropdown layout and spacing
- Softened panel resize handles

**Desktop Integration**
- Synced macOS window theme with app theme
- Restored window dragging in sidebar header zones
- Fixed system window corners on macOS
- Improved header session metadata and action controls

**Button & Component Standardization**
- Unified button primitives across all components
- Standardized destructive action patterns
- Removed unused button variants (button-large, button-small)
- Aligned context tab close hit areas

---------

Co-authored-by: Iuliia Ivashko <yulia.ivashko@gmail.com>
2026-03-20 01:01:03 +02:00
Bohdan Triapitsyn 3123de5f43 fix: improve Windows UX and stabilize chat/session behavior across runtimes (#693)
* fix: preserve unsent prompt when adding editor context in VS Code

* fix: append Add to chat selections as markdown blocks with stable spacing

Convert selected assistant content to markdown before appending
Wrap each Add to chat selection in an `md` fenced block
Preserve multiline composer formatting across repeated appends

* fix: normalize persisted Windows paths to prevent identity mismatches

* fix: hide Windows subprocess console popups across server tasks

Hide OpenCode startup and shell command child windows in the web server
Apply windowsHide to cloudflared and skills-catalog git subprocesses
Cover remaining git service exec paths that could surface console windows

* fix: restore chat auto re-pin when reaching bottom

Re-pin now triggers when scrolling back into the bottom zone, not only via the button.
Upward user scroll intent still unpins immediately and is not overridden by re-pin.
Unified bottom/re-pin threshold logic to reduce sensitivity mismatches.

* fix: restore chat scroll release on mobile during streaming

Restores pinned-scroll release on touch scroll up so mobile users can leave auto-follow while streaming.
Improves re-pin behavior near bottom to avoid sticky or inconsistent pin states.
Includes related chat UI and dependency updates in the same change set.

* fix: hide daemon startup probe consoles on Windows

* fix: prevent pinned scroll tug-of-war during streaming

* fix: prefer git.exe to avoid Windows diff popup flashes

* fix: prefer git.exe discovery in Windows git flows

* fix: avoid where probes in Windows git resolution

* fix: avoid update-check subprocess flashes on Windows

* fix: normalize read file path labels

* feat: add OpenChamber defaults and improve theme ports

Add new OpenChamber light and dark themes
Regenerate imported themes with stronger surface mapping
Set OpenChamber themes as the default top options

* fix: stabilize chat pin and unpin behavior during streaming

Restores reliable unpin on upward wheel and touch gestures while auto-follow is active.
Prevents immediate re-pin while the user is actively scrolling upward near the bottom.
Keeps smooth follow-to-bottom behavior while reducing scroll tug-of-war.

* fix: suppress Windows command popups in VSCode runtime processes

Hide spawned git and server process windows in VS Code runtime
Extend hidden-window handling to server port cleanup and reveal commands
Keep behavior unchanged on non-Windows platforms
2026-03-17 13:18:54 +02:00
Henry Moran 8f6f1c8284 fix(ui): wrap "Add to chat" selections in md fenced blocks (#641) (#684)
packages/ui/src/components/chat/message/TextSelectionMenu.tsx
  - What: Wrap selectedText in ```md fenced block before appending to composer.
  - Why: Selections were appended as raw inline text with no visual distinction.

packages/ui/src/components/chat/ChatInput.tsx
  - What: Replace trimEnd()/trim() + single-space join with raw prev + \n\n separator in append branch.
  - Why: Existing composer newlines were being destroyed and multiple appends ran together.

packages/ui/src/components/session/ProjectNotesTodoPanel.tsx
  - What: Wrap todoText in ```md fenced block before appending to composer.
  - Why: Todo text sent to current session should have the same fenced formatting.
2026-03-17 02:45:10 +02:00
Gyumin Lee edf0a197a8 perf: batch mobile keyboard viewport handlers with rAF and debounce (#660)
Reduce layout thrashing during virtual keyboard open/close on mobile by
batching resize and visualViewport event handlers to once per animation
frame. No visual or behavioral changes — desktop is unaffected.

- MainLayout: rAF-batch visualViewport resize/scroll and window resize
  listeners (was firing 10-20x per frame during keyboard animation)
- Header: rAF-batch ResizeObserver and resize/orientationchange callbacks
- ChatContainer: rAF-batch ResizeObserver and resize fallback callbacks
- useDeviceInfo: 150ms debounce on resize listener (device type does not
  change during keyboard transitions)
2026-03-15 23:18:59 +02:00