Repository navigation
fix(memory): bound renderer caches and avoid WebKit scrollbar allocations - #1097
Open
kogekiplay wants to merge 6 commits into
Open
kogekiplay wants to merge 6 commits into
kogekiplay wants to merge 6 commits into
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved workspace-search timeouts, redundant traversals, and shared blocking can return empty results and stall searches and mention suggestions for over a minute.
7 open issues
Fallback traversal resets the failed-build marker indefinitely · New Empty-query file search bypasses traversal limits · New Superseded searches repeat full traversals under the global mutex · New File search delays all suggestion groups from rendering · New Closing the dialog unnecessarily invalidates the shared index · New Search timeout is too short for cold workspace traversals · New Overflow highlight requests never retry after engine readiness · New
What changed in this PR
Reduces Codeg’s renderer memory use by moving workspace search into shared native storage and consolidating syntax-highlighting caches.
Changes:
- Adds budgeted native indexing, capped search results and full-traversal fallback.
- Migrates mention, dialog and canvas searches to the bounded API in desktop and server modes.
- Shares a lazy Shiki engine and bounded token cache, with regression coverage.
| File | Description |
|---|---|
src/lib/code-highlighter.ts |
Adds shared highlighting, cache budgets and plaintext fallback. |
src/lib/code-highlighter.test.ts |
Covers cache bounds, streaming, coalescing and subscriptions. |
src/lib/api.ts |
Exposes workspace-search preparation and query APIs. |
src/hooks/use-file-tree.ts |
Fetches bounded, debounced matches and rejects stale responses. |
src/hooks/use-file-tree.test.ts |
Covers debounce, workspace changes and refresh behavior. |
src/components/message/completed-turn-streaming-mode.test.tsx |
Updates highlighting mocks to the shared service. |
src/components/conversations/search-command-dialog.tsx |
Uses query-driven search only while open. |
src/components/chat/composer/use-reference-search.ts |
Fetches bounded native file matches for mentions. |
src/components/chat/composer/use-reference-search.test.ts |
Covers native preparation and selective file fetching. |
src/components/canvas/add-node-menu.tsx |
Requests capped, files-only search results. |
src/components/ai-elements/streamdown-plugins.ts |
Loads the shared highlighting plugin lazily. |
src/components/ai-elements/streamdown-plugins.test.ts |
Updates shared-plugin loading mocks. |
src/components/ai-elements/reasoning.test.tsx |
Updates reasoning-view highlighting mocks. |
src/components/ai-elements/message.test.tsx |
Updates message-view highlighting mocks. |
src/components/ai-elements/code-block.tsx |
Replaces independent caches with shared highlighting subscriptions. |
src-tauri/src/web/router.rs |
Registers workspace-search HTTP routes. |
src-tauri/src/web/handlers/folders.rs |
Adds search and preparation HTTP handlers. |
src-tauri/src/lib.rs |
Registers desktop workspace-search commands. |
src-tauri/src/commands/workspace_file_search.rs |
Implements compact indexing, ranking, budgets and regression tests. |
src-tauri/src/commands/mod.rs |
Exposes the workspace-search module. |
src-tauri/src/commands/folders.rs |
Refactors traversal into reusable visitors with early termination. |
Review effort: Max
Comment on lines
+388
to
+395
| if fresh && !refresh { | ||
| if limit == 0 { | ||
| return Vec::new(); | ||
| } | ||
| if let Some(index) = cache.as_ref().and_then(|c| c.index.as_ref()) { | ||
| return index.search(query, limit, files_only); | ||
| } | ||
| } |
Comment on lines
+448
to
+449
| if query.trim().is_empty() { | ||
| return tokio::task::spawn_blocking(move || { |
| .await | ||
| .map_err(|e| AppCommandError::task_execution_failed(e.to_string())); | ||
| } | ||
| let mut cache = CACHE.lock().await; |
Comment on lines
+404
to
+405
| const [files, sessions, commits] = await Promise.all([ | ||
| filesPromise, |
| const reset = useCallback(() => { | ||
| loadedForPathRef.current = null | ||
| generation.current += 1 | ||
| refresh.current = true |
Comment on lines
+5045
to
+5049
| return getTransport().call("search_workspace_files", { | ||
| path, | ||
| query, | ||
| ...options, | ||
| }) |
Comment on lines
+159
to
+160
| if (pending.size >= HIGHLIGHT_LIMITS.inFlight) { | ||
| return rawCodeTokens(options.code) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Web Content can grow into multiple GiB while the live JavaScript heap remains small. Repeated scrollbar updates trigger retained native WebKit allocations in OverlayScrollbars' container-relative transforms; renderer-wide workspace listings and independent highlighting caches add further retained data.
This change addresses those paths together:
ScrollTimelinealone is insufficient because the CSS fallback also usescqw/cqh. Scope the workaround to WebKit, including iOS browser shells, across all four package entrypoints; Chromium retains the native path. Preserve dragging, track clicks, RTL, resizing and normal animations.Native memory measurements on macOS 27.2 (26B123), using isolated system WKWebViews and synthetic content:
calc()transformsMeasurement retention limitation: the completed one-shot probe was accidentally submitted as a launchd keepalive job; its automatic restart replaced the raw series after the 30-minute run exited with status 0. The table uses summaries retained before completion, with physical summaries through 29.2 minutes and a successful runtime health check at 29 min 50 s. It does not claim a complete preserved 30-minute trace. Both probe jobs have been stopped.
The patched run continuously checks that handles move, DOM count remains constant and no scrollbar
ScrollTimelineanimations exist. System WebKit geometry checks pass 16 combinations of axis, LTR/RTL, resize and scroll progress (maximum position error 0.234375 px); real WebKit input checks pass all eight drag/track-click cases. These results isolate the scrollbar allocation path. A follow-up build based on the officialv0.42.1tag, with all PR commits applied, was installed as0.42.1+memory.3. The app bundle and live service version were verified. A 30-minute application observation retained all 120 physical-footprint samples and kept the same main/Web Content process identities throughout: Web Content started at 263.3 MiB, ended at 589.2 MiB, and peaked at 604.5 MiB in the periodic samples; macOS reported a lifetime peak of 661.0 MiB. The main process ended at 194.6 MiB. A live workspace search returned the exact deep match among two results in 0.249 s, transferring 241 bytes.The application run did not reproduce the previous multi-GiB runaway, and memory repeatedly fell after temporary peaks. It does not establish zero growth: Web Content medians for minutes 10–15 / 15–20 / 20–25 / 25–30 were 557.0 / 548.8 / 579.0 / 587.9 MiB, and the fit after minute 10 was approximately +3.55 MiB/min under continued interactive use. Native WebKit allocated bytes were approximately 231.7 MiB at minute 15, 232.4 MiB at minute 27, and 239.5 MiB near minute 30. These results support a substantial improvement while leaving a smaller upward drift to distinguish from normal workload/caching. The full application trace is preserved locally; private app data and heap captures are not published.
The earlier 5.3-million-entry workspace probe retained approximately 116 MiB in the compact index. Warm filename searches took 0.17–0.54 seconds and transferred approximately 0.1–13 KB, versus approximately 925 MiB for the previous full renderer listing. Cold discovery still takes a full walk (64–104 seconds in those release probes). The existing ignore rules and authorized linked-root behavior are preserved in desktop and server modes.
Validation:
cargo checkpassed. Desktop Clippy (--all-targets --features test-utils) and server Clippy passed with-D warnings.Copilot review follow-up:
6a0f496acovers failed-build TTL, empty-query admission, obsolete traversal cancellation, progressive mention groups, local reset versus index refresh, and HTTP deadlines.d68c4849covers retrying mounted highlighting subscribers after saturation. Tests exercise each reported condition, including out-of-order cancellation and keeping consumers independent.