Commit Graph
1799 Commits
Author SHA1 Message Date
Cursor AgentandSerhii Dziupin 05dd270a9a perf(ui): lazy-load html-to-image and snapDOM
Both are only needed on explicit user actions (export message as image,
capture preview annotation screenshot). Defer them with dynamic import so
~140 KB leaves the eager app-shell graph.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-06-26 11:26:11 +00:00
Bohdan Triapitsyn 4a37b9a005 fix: clean up stale file tree paths on startup
Removes missing expanded folders from persisted Files state
Prevents repeated 404 noise for stale file tree paths
Avoids startup SDK race when restoring sessions
2026-06-26 12:47:01 +03:00
8c1a24089d fix(worktree): gate sessions on bootstrap readiness (#1762)
Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-26 12:16:42 +03:00
Bohdan Triapitsyn b0e476ab7c fix: prevent sidebar worktree reorder snap-back (#1827)
Invalidates cached group ordering when reorder state changes
Keeps dragged worktree groups in the new position immediately after drop
Validated with UI package type-check
2026-06-25 17:11:37 +03:00
Tom Rochette 9a2012c94e fix(chat-input): dismiss open question prompt when sending a message (#1740)
Sending a message while a question prompt was open left the prompt
lingering, blocked the send, or collided with the still-blocked agent
turn. Two root causes:

useSessionActivity treated pending permissions as idle but not pending
questions, so the send button became Stop during a question and Enter
queued/collided instead of sending. handleSubmit also never dismissed
the open question, stranding the session in a half-answered state.

The send path now dismisses open questions for the session subtree
(optimistic local clear so the card vanishes instantly, plus a formal
question.reject) and queues the message. The queued-message auto-send
hook then delivers it as the next turn once the rejected turn winds
down and the session returns to idle. Queueing avoids aborting the
turn, which surfaced an unwanted "running turn was stopped" notice.

Regression tests cover the no-op, subtree dismissal (root + subagent
child), and QuestionNotFoundError paths.
2026-06-25 10:55:35 +03:00
Bohdan Triapitsyn c97d5c79c1 fix: prioritize session sidebar status indicators
Prevents pinned icons from crowding live indicators
Shows chevrons on hover when status or pin takes priority
2026-06-24 18:45:26 +03:00
bashrusakhandLeonid Skorobogatyy 569342b3c2 fix(markdown): preserve user code block characters (#1750)
Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
2026-06-24 18:33:50 +03:00
Bohdan Triapitsyn 8c551c40aa fix(chat): stop double scroll write on prepend that resonates into oscillation
When older history is prepended while the viewport is pinned to the bottom, the
timeline controller wrote the re-pin manually (scrollTop += delta). That write
is not flagged as programmatic, so useChatAutoFollow's scroll handler treated it
as movement and issued its own correcting scroll — a redundant up/down move on
every prepend. On most setups it settles after one move, but with different
virtualizer measurement/timing it never converges, producing the reported
infinite up/down scroll glitch.

When pinned, delegate the prepend re-pin to auto-follow's goToBottom('instant'):
a single authoritative write to the bottom that IS marked programmatic, so
auto-follow ignores it instead of fighting it. The released case (user reading
back through history) is unchanged and still preserves the read position.

This also covers the on-open history auto-load (loadEarlierIfPinnedViewport-
Underfilled), which only runs while pinned, so its prepends now go through the
single writer too.
2026-06-24 18:29:55 +03:00
076e9331ec fix(agents): send null to clear temperature/topP overrides on update (#1718)
When clearing temperature or topP on an existing agent, the UI sent
undefined which JSON.stringify drops, so the server never received the
clear command. Now sends null to properly remove the override in
opencode.json.

Changed updateAgent to use 'field' in config pattern for temperature
and top_p, matching the existing prompt handling.

Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-24 17:08:22 +03:00
Tom Rochette ecc70fa15d feat(debug): add fetch requests-in-flight tracker with age percentiles
Adds a 'Requests' tab to the debug panel (Ctrl/Cmd+Shift+D) that wraps
window.fetch while the panel is open to track every request as in-flight
from call to promise settle, sampled once per second over a 5-minute
rolling window.

Charts:
- in-flight request count over time (current + peak)
- p50/p90/p99/max age distribution of currently in-flight requests,
  with the legend below the chart and a y-axis max label for scale

Tracking is fully gated behind the panel: closing it stops the sampling
interval, unwraps window.fetch, and drops all state (zero overhead when
not debugging). i18n keys added to all 9 locales.
2026-06-24 05:24:48 +00:00
bashrusakhandLeonid Skorobogatyy e44efa97d6 feat(agents): expose thinking variant configuration in agent settings (#1715)
* feat(agents): expose thinking variant configuration in agent settings

Fix #1425: add variant field to agent config UI so users can configure
thinking/reasoning depth per agent without editing opencode.json.

Changes:
- Added variant to AgentConfig and AgentDraft types in useAgentsStore
- Pass variant in createAgent and updateAgent API calls
- Support null for temperature, top_p, and variant to clear overrides
- Added variant input field in AgentsPage 'Model & Parameters' section
- Added variant to settings search registry
- Added i18n strings for variant field in all 8 non-English locales

The variant field maps to provider-specific parameters (e.g. Anthropic
high/max variant, OpenAI reasoning effort). Users can enter any string
value; the SDK passes it through to the model provider.

Clearing temperature/topP/variant now sends null to the server instead
of omitting the field, which properly removes the override in
opencode.json.

* fix(sync): preserve tool state.time in materialization merge

* chore: trigger re-review

* fix(agents): use thinking variant selector in settings

* fix(agents): preserve thinking variant values

---------

Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
2026-06-23 23:22:59 +03:00
Bohdan Triapitsyn 3d3674d4dd fix: restore arrow-up message history navigation
Lets ArrowUp recall previous messages when the cursor is at the start
Keeps autocomplete guards for history navigation
Restores prior chat input behavior
2026-06-23 23:21:09 +03:00
Bohdan Triapitsyn d47a892376 fix: sync before pushing git commits
Makes Commit & Sync fetch and pull before push when needed
Prevents stale git status from showing already up to date
Adds regression coverage for git status cache invalidation
2026-06-23 23:16:06 +03:00
bashrusakhandLeonid Skorobogatyy 3db91721cb fix(providers): use correct endpoint for provider disconnect (#1714)
Fix #1462: handleDisconnectProvider called the SDK auth.remove() which
only clears auth credentials from auth.json. Cloud providers configured
in user/project/custom config files were not removed and reappeared
after reload. Now calls DELETE /api/provider/:id/auth?scope=all which
removes the provider from all config sources.

Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
2026-06-23 22:38:49 +03:00
bashrusakhandLeonid Skorobogatyy 6e68015389 fix(agents): use isPrimaryMode consistently across all agent pickers (#1713)
* fix(agents): use isPrimaryMode filter for agent picker

Fix #1527: agent picker filtered by mode !== 'subagent' which missed
agents with unexpected mode values. Now uses isPrimaryMode() which
only includes 'primary', 'all', undefined, and null — the semantically
correct set of agents that should appear in the picker.

* fix(agents): use isPrimaryMode consistently across all agent pickers

Updated AgentSelector.tsx to use isPrimaryMode instead of mode !== 'subagent'.
Removed duplicate isPrimaryMode definition from useConfigStore.ts and
imported the shared helper from mobileControlsUtils.

---------

Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
2026-06-23 22:34:51 +03:00
bashrusakhandLeonid Skorobogatyy ac0f173655 fix(chat): preserve tool duration across session switches (#1712)
* fix(chat): preserve tool duration across session switches

Fix #1636: ToolPart.tsx reset pinnedTime to empty on unmount/remount,
causing LiveDuration to not render on first paint. Now initializes
pinnedTime from server-provided time?.start/time?.end in the useState
initializer, eliminating the one-frame gap.

* fix(sync): preserve tool state.time in materialization merge

---------

Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
2026-06-23 22:30:04 +03:00
bashrusakhandLeonid Skorobogatyy 57cef1b278 fix(sidebar): correct expansion-key format for virtualizer buffer (#1711)
* fix(sidebar): increase virtualizer buffer for expanded parents

Fix #1530: archive sub-session layout broken because the virtualizer
used a fixed 28px height estimate per row. Expanded parents with inline
children are much taller. Now dynamically increases bufferSize when
expanded parents are present.

* fix(sidebar): correct expansion-key format for virtualizer buffer

The expansion key was using raw sessionId instead of the scoped format
'project:{archived|active}:{sessionId}'. This made hasExpandedParent
always false, so bufferSize never increased. Also removed dead
hasSessionSearchQuery branch.

---------

Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
2026-06-23 22:24:11 +03:00
bashrusakhandLeonid Skorobogatyy a25fc4c25a fix(sync): reflect share status from global store after cancel (#1709)
* fix(sync): reflect share status from global store after cancel

Fix #1551: unshareSession() called updateLiveSession() which silently
fails when the child store doesn't exist. The sidebar rendered from the
child store first, showing stale share data. Now overlays the global
session's share field at merge points.

* fix(sync): extract shared mergeLiveSessionWithGlobalSession helper

Extracted the share-field overlay into a single shared helper in
useGlobalSessionsStore.ts. All 3 merge sites now use the helper
instead of duplicating the overlay logic.

* test(sync): add unit tests for mergeLiveSessionWithGlobalSession helper

---------

Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
2026-06-23 22:18:44 +03:00
bashrusakhandLeonid Skorobogatyy 83256ba924 fix(sidebar): preserve pinned sessions and folder refs on empty session list (#1706)
Added sessions.length === 0 guard to useSidebarPersistence.ts and
sessions.length === 0 && archivedSessions.length === 0 guard to
useSessionFolderCleanup.ts. Prevents data loss when server returns
empty list during transient failures.

Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
2026-06-23 22:01:24 +03:00
Gokul GK 12835c7646 fix: refresh skills catalog after settings updates (#1681) 2026-06-23 21:10:22 +03:00
Leonid Skorobogatyy 20f2a2635b chore(worktree): drop dead otherBranches labels after search guard
The mobile and desktop (cmdk) pickers in NewWorktreeDialog now hide
non-matching branches during search via a !hasExistingBranchQuery /
!hasSourceBranchQuery outer guard. That makes the inner heading
ternaries (which switched between 'localBranches' and
'otherLocalBranches' depending on the search state) unreachable: they
always resolve to the non-query label, and the
'hasExistingBranchQuery && <CommandSeparator />' lines were dead code.

This commit:

- Replaces 8 heading ternaries with static non-query labels
  (mobile and desktop, existing/source, local/remote).
- Drops the two unreachable <CommandSeparator /> renders in the
  otherLocal blocks.
- Simplifies the otherRemote separator conditions by removing the
  always-false '|| hasExistingBranchQuery' / '|| hasSourceBranchQuery'
  terms.
- Removes the now-unused 'session.newWorktree.otherLocalBranches' and
  'session.newWorktree.otherRemoteBranches' keys from all 9 locales.

Behavior is unchanged; this is dead-branch cleanup only.
2026-06-24 03:51:56 +11:00
Leonid Skorobogatyy 1ad470cead fix(worktree): hide non-matching branches during search 2026-06-24 03:26:26 +11:00
Nicolas CharpentierandBohdan Triapitsyn b87de3c5b2 fix: ignore pasted @ for file mentions (#1649)
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-23 14:07:09 +03:00
weixiang1862andBohdan Triapitsyn 3f4ad2a3e8 fix: preserve settings default thinking variant when switching agents (#1639)
* fix: preserve settings default thinking variant when switching agents

When a user sets a default thinking variant (e.g. 'high') in settings and
switches between plan and build agents in a session, the variant was reset
to 'default' (undefined) instead of respecting the settings default.

Root cause: two code paths failed to fall back to settingsDefaultVariant:

1. ModelControls variant sync effect: when no per-session+agent+model
   variant was saved, the effect set currentVariant to undefined instead
   of falling back to settingsDefaultVariant.

2. setAgent in useConfigStore: when the target agent had a configured
   model, the variant was always passed as undefined to
   applyResolvedModelSelection, ignoring both the saved per-session
   variant and the settings default.

Fix both paths to resolve variants in priority order:
saved variant > settingsDefaultVariant > undefined.

* fix: preserve agent variant fallback order

* fix: apply historical session variant on restore

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-23 13:35:36 +03:00
weixiang1862 56ca5bcf74 fix(mobile): subagent chevron overlaps session title on mobile (#1582)
`mobile.css` applies `min-width: 36px; min-height: 36px` to all `[role="button]` elements on mobile devices for touch targets, enlarging the subagent chevron from `14×14px to 36×36px`. This extends the chevron box 20px past the content edge, visually overlapping the session title. Add inline `minWidth/minHeight: 14` to pin the chevron size.
2026-06-23 11:30:02 +03:00
Bohdan Triapitsyn 4d63278efd feat: add SSH commit signing to git identities
Configure commit signing per Git identity
Apply SSH signing settings automatically
Support signing in web and VS Code
2026-06-18 23:51:40 +03:00
Bohdan Triapitsyn e4cfb628fe fix(diff): keep header controls and horizontal scroll within the panel when line wrap is off
In the changes/diff view, an unwrapped diff's intrinsic width leaked up the
flex chain: the flex-1 column holding the scroll area lacked min-width:0, so its
min-content (the widest line) stretched it — and every nested w-full element,
including the .pierre-diff-wrapper (overflow-x-auto) and the file header — grew
to the content width. That pushed the header's action controls past the narrow
viewport and left overflow-x-auto with nothing to scroll.

Add min-w-0 to the diff layout's flex items so the chain stays at viewport
width: long lines now scroll horizontally inside the diff, and the file header
controls stay visible.
2026-06-18 02:17:44 +03:00
Bohdan Triapitsyn 57c5808ef2 fix(files): refresh URL auth token proactively for asset previews
The oc_url_token has a ~50s effective lifetime and was only fetched once at
preview mount, so HTML/image/PDF previews cycled to 'authentication required'
when it expired and nothing forced a re-render with a fresh token.

Add a consumer-gated proactive refresh in runtime-auth: while at least one
url-token consumer is active, a single scheduler mints a fresh token just
before the skew window and swaps it in atomically (the previous token stays
valid until the new one lands — no empty-token window for other consumers).
acquire/release manage the consumer count; subscribe fires only on a real
token replacement.

FilesView consumes this via a shared useAssetAuthRefresh hook (replacing three
near-duplicate effects) and remounts the iframe/img only when the token
actually changes, not on a blind interval.
2026-06-18 01:24:37 +03:00
bashrusakh 59ecd86b4b perf: isolate chat streaming renders and reduce sidebar render cost (#1672)
Reworks the chat and session-sidebar render paths to cut render cascades, memory
  churn, and UI jank on large sessions and big session trees. Behavior is preserved;
  the changes are about *when* and *how much* the UI re-renders.

  ## Chat streaming
  - Freeze the streaming message's parts in the bulk turn projection during streaming,
    and re-inject live parts only in an isolated tail leaf, so a ~60/sec delta stream
    no longer re-runs the whole-session projection or re-renders unrelated rows.
    session with referential reuse of unchanged turns.
  - Memoize message rows with field-aware comparators instead of reference equality.
  - Replace the manual child-session polling in the task tool with the live SSE
    stream + a one-shot load, removing a fetch/settle state machine.

  ## History loading & scroll
  - Load an initial page fast, then prepend one older page in the background so the
    scroll container has headroom and "load older on scroll-up" fires before the user
    hits the absolute top.
  - Compensate scroll synchronously (in a layout effect, before paint) for prepends —
    including background prepends that don't originate from a user scroll — so the
    viewport stays stable instead of judder-correcting on the next frame.

  ## Markdown rendering
  - Render markdown synchronously *styled* on first paint (paragraphs, lists, code
    cards, tables, inline code) instead of raw escaped text; the async pass then only
    upgrades syntax-highlight colors. Eliminates the flash of full-width raw text.
  - Load KaTeX CSS eagerly with the main bundle instead of inside the lazy markdown
    chunk, avoiding a late stylesheet injection on first render.

  ## Sidebar
  - Hoist per-row recursive tree walks out of row comparators into per-group
    precomputed sets/keys; batch live-session lookups into a single map; add a
    group-level memo boundary.
  - Isolate rename drafts so per-keystroke typing doesn't repaint the row tree.

  ## Sync layer
  - Add a staleness guard so a slow message fetch can't repopulate a session the user
    navigated away from.
  - Throw on fetch failure for authoritative loaders so a transient blip can't read as
    an empty server response.

  ## Cleanup
  - Remove dead code (unused hooks, params, duplicated inline types) surfaced while
    reworking the above.

  ## Known issue
  - A rare, purely cosmetic first-paint width flash can still appear on large sessions;
    it has no behavioral or data impact and is tracked for a follow-up runtime trace.
2026-06-18 00:43:16 +03:00
Bohdan Triapitsyn 253094cb1d fix: stabilize history diff loading and comments
Prevents History file loading from getting stuck
Disables inline comments in History diffs
Keeps review comments available in regular diff views
2026-06-17 02:02:01 +03:00
Bohdan Triapitsyn a99eba1ca6 fix(markdown): currency-safe math delimiters
Single-dollar $...$ inline math collided with currency text ($50,
US$ 680, "$50M to $72M"), parsing money as math and corrupting it.
Drop single-dollar inline math; keep $$...$$ display math and add
\(...\) inline and \[...\] display via marked tokenizers (caught at
lex time so they survive backslash escaping and stay code-safe).

Also gate renderMathExpressions on a cheap $-presence check so the
split + regex passes are skipped for the non-math majority of blocks.
2026-06-17 00:33:39 +03:00
Bohdan Triapitsyn 0fef61e25f fix: sync context panel iframe themes
Keeps embedded sessions aligned with parent theme
Prevents iframe reloads on theme changes
Supports theme hotkeys from focused iframes
2026-06-16 23:33:29 +03:00
Bohdan Triapitsyn a2a1cedf8d perf: streamline provider and agent startup loading #185
Avoids loading the full provider catalog on startup
Prewarms project config in the background
Prevents duplicate worktree-scoped config requests
2026-06-16 19:23:38 +03:00
Bohdan Triapitsyn 9f266a351c fix: preload commands and skills when draft session opens so pinned starters appear immediately
Draft starters from commands and skills now resolve on mount without needing to open the add dialog
Uses existing TTL-cached and deduped loaders, so no extra cost if already loaded
2026-06-16 15:33:33 +03:00
Bohdan Triapitsyn 69e534c457 refactor: fixed mobile session button for android
Replaces custom pointer-capture touch logic with touch-action: manipulation
Aligns with the pattern used across other mobile surfaces
2026-06-16 14:46:55 +03:00
Bohdan Triapitsyn f3aff42b8a feat: show context usage as circular progress
Replaces static context icons with live circular progress indicators
Applies consistent context progress in desktop, mobile, VS Code, and mini-chat headers
Keeps usage coloring tied to existing status thresholds
2026-06-16 14:34:02 +03:00
Bohdan Triapitsyn 3ba93c111e fix: show clear toast when agent definition is missing
Surface missing agent definitions during delete
Avoid hiding delete errors behind generic failures
Add localized toast copy
2026-06-16 14:33:08 +03:00
9199798a14 perf(right-sidebar): gate live effects, memoize lookups, always-mount tabs (#1674)
* perf(right-sidebar): gate live effects, memoize lookups, always-mount tabs

Performance fixes for the right sidebar (git/files/context tabs).

== Correctness / leak fixes (P0)

* RightSidebar: drop dead useEffect that re-nulled refs the resize
  handler already nulled; collapse the redundant width/minWidth/maxWidth
  triple into width + the existing --oc-right-sidebar-width variable.
* useUIStore: clamp setRightSidebarWidth to [MIN, MAX]; simplify
  setRightSidebarOpen (22 lines -> 12).
* RightSidebarTabs: useRightSidebarGitSync now takes the right tab and
  main tab and only polls when the right git tab is the visible consumer
  and the browser is online + visible. Replaces a global poll that
  fired for the lifetime of the open sidebar.
* GitView: commit-files fetch refactored to cancelled + Promise.all
  (was a per-hash loop that could setState after unmount); getRemoteUrl
  and refreshRemotes gated on cancelled/mountedRef; new module-scoped
  mountedRef guards setIsSettingIdentity from firing after unmount.
* GitView + useGitmojiList: extract gitmoji fetch/cache into a hook
  with module-level inflight promise + subscribers Set; stale-while-
  revalidate from localStorage; ensureLoaded() for call-site-initiated
  hydration; cancelled flag on setIsLoading to avoid the React
  setState-after-unmount race.
* ProjectNotesTodoPanel: 400 ms notes debounce now cancels on blur
  (was double-saving); persistProjectData chained per project through
  a module-level Map<projectId, Promise> so a fast todo toggle racing
  the debounced save no longer hits the server in parallel; resize
  auto-adjust guards against same-value pings.

== Render fanout (P1)

* RightSidebarTabs: all three tab content components are now always
  mounted with the hidden attribute. State and cache survive tab
  switches. When activeMainTab === 'git' (or 'context') the matching
  right tab is filtered out of the tab strip and a redirect effect
  snaps any persisted-but-now-hidden right tab to 'files'. onSelect is
  now a type-guarded handler instead of `as RightTab`.
* GitView: 13 separate useGitStore action selectors collapsed into one
  useShallow block (one re-evaluation per store change instead of 13).
* GitView: new isGitViewActive flag (true when this instance is the
  visible consumer) gates the 7 live effects — load identities, fetch
  remote URL, refresh remotes, ensureAll, sessionEvents.onGitRefreshHint,
  worktree bootstrap poll, default-identity auto-apply. Hidden
  GitView instances no longer run these.
* GitView: gitViewSnapshots module-level Map is now backed by an
  LRU wrapper (cap 20) so per-directory draft snapshots cannot leak
  across hundreds of project switches. Removed the dead `unique.set`
  dedup in changeEntries — GitStatus.files is already unique by path.
* SidebarFilesTree: statusByPath Map<path, FileStatus> and
  badgeByDir Map<dirPath, { modified, added }> are precomputed once
  per gitStatus change. Tree render is O(1) per node instead of O(N)
  per node via the previous per-row find/scan. badgeByDir walks each
  file's path segments and increments counters for every ancestor
  dir, so total cost is O(N + total_dirs_in_files) per gitStatus
  change.
* SidebarFilesTree: FileRow wrapped in React.memo with a custom
  comparator. Context-menu open state moved INTO FileRow as local
  state — opening a menu in one row no longer re-renders siblings.
* SidebarFilesTree: loadDirectory accepts an isCancelled predicate;
  the batch-load effect for expandedPaths passes a stable predicate
  so per-dir fetches stop touching state once the effect tears down.
* SidebarFilesTree: module-level fileTreeCacheByRoot Map (LRU,
  cap 8 roots) hydrates childrenByDir / loadErrorsByDir /
  loadedDirsRef on mount or root change. Mirror effects write state
  back to the cache. Survives close-and-reopen of the right sidebar;
  populated entries are dropped on unmount only when they had no
  data.

== Result

Net diff: 6 files modified, 1 new (useGitmojiList.ts), 682 insertions,
283 deletions. Existing test suite baseline preserved (537 pass / 58
fail / 1 error) — no new regressions. The 58 pre-existing failures are
in unrelated chat/streaming tests and were verified via git stash on
the same branch.

Architecture assumptions, verified by manual review:
- P1.1's redirect effect snaps rightSidebarTab to 'files' whenever
  activeMainTab === 'git', so the right and main GitView instances
  are mutually exclusive — isGitViewActive cannot be true for both.
- The 7 gated effects plus the useRightSidebar GitSync poll cover all
  cases where git state should advance: visible consumer fetches; the
  poll keeps the store warm when only the right git tab is visible.
- The aborted loadDirectory predicate is sufficient because
  inFlightDirsRef and loadedDirsRef dedup at the call site before
  any network IO is initiated.

* fix(sidebar): always clean up inFlightDirsRef regardless of cancellation

* refactor(sidebar): deduplicate RIGHT_SIDEBAR_MIN/MAX_WIDTH constants, export from useUIStore

* docs: split right sidebar perf plan into standalone file, clean up merged master status from chat plan

* fix(git): gate GitView effects by instance visibility

---------

Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-16 13:52:05 +03:00
Bohdan Triapitsyn e982bd9388 fix: prevent agent deletion from disabling built-ins
Stop delete from creating disable overrides
Delete only the selected agent scope
Keep web and VS Code behavior aligned
2026-06-16 13:38:24 +03:00
Tom RochetteandBohdan Triapitsyn e402cd75f5 feat(scheduled-tasks): add cron syntax support to task editor dialog (#1593)
Adds a 'Cron' schedule type option to the scheduled task editor, allowing
users to create and edit cron-based schedules through the UI.

- Add cron expression input with inline validation (cron-parser)
- Show next 4 upcoming run times as a preview
- Provide clickable example chips (every 5min, hourly, Monday 9am, etc.)
- Preserve cron expressions when editing existing cron tasks
- Add cron.ts utility module for validation and next-run computation
- Add i18n keys across all 8 locale files

Closes #1586

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-16 11:03:42 +03:00
Bohdan Triapitsyn e904abda04 feat(editor): Shiki highlighting for code files in PlanView and SkillsPage
Reuse the file-editor Shiki extension for the PlanView and SkillsPage editors,
gated to non-markdown files. Markdown sources keep the lezer highlighter (its
markdown-aware styling is better for editing and there's no Shiki view to match).

- PlanView: code files opened through it get Shiki colors; plan .md stays lezer.
- SkillsPage: code supporting files get Shiki colors; SKILL.md stays lezer.
2026-06-16 01:08:54 +03:00
Bohdan Triapitsyn 782982cdd3 feat(editor): Shiki syntax highlighting in the file editor (CodeMirror)
Bring the CodeMirror file editor up to the same rich highlighting as the Shiki
file view, so toggling edit <-> view is visually consistent. lezer collapses far
more tokens than TextMate (import/from/const are all "keyword"), so a theme
remap can't reach parity — instead, project real Shiki tokens onto decorations.

- Worker: add highlightTokens — tokenize with an arbitrary registered TextMate
  theme and return per-line styled runs with offsets. The theme object ships to
  the worker once per name; later calls send only the name.
- New shikiHighlight CodeMirror extension: a StateField of mark decorations
  built from worker tokens. Re-tokenizes on a short idle (off the keystroke
  path) and maps decorations through edits so colors persist while typing.
- flexokiTheme: add { syntaxColors: false } to keep only the editor UI theme,
  so the lezer highlighter doesn't compete with the Shiki decorations.
- FilesView: enable Shiki highlighting (same language resolver as the file view
  → identical language) and drop lezer token colors when it's active. lezer
  language stays on for indentation/folding/brackets.
2026-06-16 01:08:54 +03:00
Bohdan Triapitsyn c22e1cbb15 refactor(chat): remove dead syntaxTheme plumbing
The Prism syntaxTheme prop is no longer read after code highlighting moved to
the Shiki worker. Remove the now-dead prop threading and its source.

- Drop syntaxTheme from interfaces, destructures, prop passes, and React.memo
  comparators across ChatMessage, MessageBody, ProgressiveGroup, ToolPart,
  TurnActivity, ToolOutputDialog, and ChatInput.
- Drop the unused _syntaxTheme param from renderWebSearchOutput.
- Remove the dead generateSyntaxTheme usages (ChatMessage memo, PlanView
  unassigned memo) and delete the now-unimported syntaxThemeGenerator module.
2026-06-16 01:07:43 +03:00
Bohdan Triapitsyn e41e5bac91 perf(code): replace react-syntax-highlighter and prismjs with the Shiki worker
Route all non-markdown code highlighting through the off-main-thread Shiki
worker, removing react-syntax-highlighter and prismjs entirely.

- Extend the worker with highlightLines: tokenize a whole block once and return
  per-line inner HTML, so per-line layouts (diffs, gutters, virtualization) make
  one worker call instead of one highlighter per line.
- Add shared WorkerHighlightedCode (whole-block) and useWorkerHighlightedLines
  (per-line) primitives. Colors resolve via the --md-syntax-* CSS variables, so
  theme changes never re-highlight.
- Migrate all 12 react-syntax-highlighter call sites: PermissionCard,
  ToolPart, ContextSidebarTab, ToolOutputDialog (whole block) and
  DiffPreview/WritePreview (per line).
- Migrate VirtualizedCodeBlock off prismjs to the worker, keeping virtua
  virtualization; whole-block tokenization also restores cross-line syntax
  context that per-line highlighting lost.
- Drop react-syntax-highlighter (+types) from ui and web, prismjs (+types) from
  ui, and the orphaned create-element type shim.
2026-06-16 01:07:43 +03:00
Bohdan Triapitsyn 464c4ac0ca perf(markdown): move code highlighting off the main thread into a Shiki worker
Tokenize closed code blocks in a dedicated Shiki Web Worker instead of calling
the shared highlighter synchronously on the UI thread. This removes the
one-shot main-thread highlight stall when a code fence closes on a large block.

Streaming behavior is unchanged: the open (streaming) fence still renders as
plain text and is highlighted once on close. On any worker failure the block
keeps its escaped plain code — highlighting never falls back onto the main
thread.

- Add markdownShikiThemeDefinition (dependency-free CSS-variable theme) so the
  worker can use the theme without pulling in @pierre/diffs / React.
- Add markdown-worker-protocol, markdown-shiki.worker, and the main-thread
  markdown-worker client.
- Route highlightCodeBlocks through the worker; keep the size/VSCode line guard
  and mermaid skip on the main thread.
- Add shiki as a direct dependency (was transitive via @pierre/diffs).
2026-06-16 01:07:43 +03:00
Bohdan Triapitsyn 9ea557d23e feat(markdown): open mermaid fullscreen preview on diagram click in chat
Thread onShowPopup from MessageBody through AssistantTextPart into the
markdown renderer so clicking a rendered mermaid diagram in assistant
messages opens the existing pan/zoom fullscreen preview dialog.
2026-06-15 18:23:59 +03:00
Bohdan Triapitsyn 4d506fba4e perf(markdown): rewrite rendering on marked + Shiki + morphdom
Replace the react-markdown/Prism component tree with an HTML-string
pipeline (marked -> KaTeX -> Shiki -> DOMPurify -> DOM decorators) patched
into the DOM via morphdom, with per-block reconciliation and a paced
streaming reveal. Cuts streaming CPU versus the previous renderer while
preserving file-reference links, mermaid diagrams, table export, and
agent/skill/favicon link handling. Public MarkdownRenderer/
SimpleMarkdownRenderer props are unchanged (drop-in).
2026-06-15 18:17:47 +03:00
Bohdan Triapitsyn 7a0abfe6c4 fix: keep context panel resize handle above content
Prevents changed file rows from visually overlapping the resize border
Preserves existing spacing in the changes list
2026-06-15 15:29:19 +03:00
Bohdan Triapitsyn ba0a755040 fix: sync pending changes with active directory
Uses the effective directory for composer git status
Prevents stale pending changes after commit or push
Keeps the dropdown aligned with the git panel
2026-06-15 15:26:08 +03:00
Bohdan Triapitsyn 41160ed5b2 fix: refresh pending changes from chat input
Keeps pending changes state fresh even when the bar is hidden
Updates the composer when workspace changes appear or disappear
Removes duplicate refresh handling from the pending changes bar
2026-06-15 15:10:12 +03:00