perf: store refactor + robustness fixes (#11-#17)#4
Open
GoDiao wants to merge 37 commits into
Open
Conversation
Comprehensive audit covering architecture, core logic, Rust layer, frontend rendering, and quick fixes with verified evidence and priority recommendations.
5-phase plan with concrete code changes, file locations, verification steps, dependency graph, and rollback strategy. Estimated 23-34 days.
- PTY read buffer: 8KB → 32KB (lib.rs:972) - Cap allUserMessages to 3 entries (handler.ts) - Team member polling: add in-flight guard (teamStore.ts) - terminal_environment(): cache with OnceLock (lib.rs) - Remove unused reqwest dependency (Cargo.toml)
Convert 25+ static imports in src/tools.ts to lazy require() calls with caching. Tools are loaded on first use instead of at module evaluation time, reducing cold start latency.
- Window state save: add 500ms debounce (lib.rs) - Sidecar startup: move to background thread, emit server-ready event (lib.rs) - Terminal sessions: replace Mutex<HashMap> with DashMap (lib.rs) - Add dashmap dependency (Cargo.toml)
- Create useElapsedTimer hook (desktop/src/hooks/useElapsedTimer.ts) - Update StreamingIndicator to use hook instead of Zustand state - Remove elapsedTimer field from PerSessionState - Remove all elapsedTimer clearInterval/setInterval calls from chatStore - Clean up test files
- 3.1: Move elapsed timer to useElapsedTimer hook (out of Zustand) - 3.2: Granular selectors in MessageList (replace full sessionState object) - 3.3: Markdown parsing with useDeferredValue for async rendering
Add in-memory metadata cache to sessionService.listSessions() with file mtime-based invalidation. Eliminates redundant JSONL reads on repeated list requests.
Mark Phase 1-4 as completed with commit references. Note Phase 4.2 is infeasible due to explicit ordering dependencies.
Phase 5 (communication layer refactor) requires modifying CLI subprocess code which may not be in the current repo. Defer until ROI is evaluated.
- Create PipeTransport (stdin/stdout) for direct CLI communication - Add pipe:// protocol support to getTransportForUrl - Enable via DREAMCODER_USE_PIPE_TRANSPORT env var - Server reads CLI stdout as SDK messages directly - Eliminates one WebSocket hop for streaming deltas
Phase 5.1 (pipe transport) is now complete. Phase 5.2 (binary protocol) deferred pending ROI evaluation.
Binary protocol has low ROI: messages are small, JSON overhead is minimal on modern hardware, and binary format increases debugging complexity. Defer unless profiling shows measurable bottleneck.
DashMap requires Send + Sync on values, but TerminalSession contains dyn MasterPty + Send which is not Sync. Revert to Mutex<HashMap> and fix related compilation errors.
TerminalSession contains dyn MasterPty + Send which does not satisfy Sync trait bound required by DashMap. Reverted to Mutex<HashMap>.
- scripts/benchmark.ts: automated benchmark comparing dev vs perf branches - Module import time, session cache, markdown parsing, state updates, transport - docs/perf-optimization-results.md: detailed results with measurements - Session cache: 24.8x improvement (2.02s → 81ms for 594 files) - Timer external hook: 1.6x fewer re-renders - Pipe transport: +49% per-msg overhead but eliminates double-hop
Include benchmark results, related docs list, and how to run benchmarks so the branch purpose is immediately clear when revisiting.
Each optimization now has a one-line description so the branch purpose is self-documenting without opening other files.
Sync with dev changes: - UIA Tree mode for text-only Computer Use - Midnight theme (globals.css + settings) - Sidecar cleanup (bridge/, assistant/, bootstrap/ removal) - Computer Use mode persistence + race condition fix - sessionService.ts: resolve with dev deletion (perf changes were mirrored from sidecar/ to src/ server side only)
# Conflicts: # README.md
合入性能优化分支全部实现成果: - Phase 1: 快修 6 项(PTY 缓冲、allUserMessages 上限、teamStore 去重、 terminal_environment OnceLock、reqwest 移除、tools lazy import) - Phase 2: Rust 层优化(窗口防抖 500ms、sidecar 异步启动) - Phase 3: 前端优化(elapsed timer 外置、granular selectors、 Markdown useDeferredValue) - Phase 4: 会话元数据缓存(mtime-based,2s→81ms) - Phase 5.1: CLI↔Server pipe transport(实验性,环境变量门控) - DashMap 尝试后回滚(TerminalSession 不满足 Sync)
…ynamic katex/mermaid)
- vite.config.ts: split heavy vendor deps into separate chunks
(mermaid, katex, shiki, xterm, diff, prism, qrcode, marked, dompurify, react)
- Settings.tsx: lazy-load all 15 settings tab components via React.lazy + Suspense
- MarkdownRenderer.tsx: dynamic import('katex'), only loads when math blocks present
- MermaidRenderer.tsx: dynamic import('mermaid'), only loads when diagram rendered
Bundle impact (baseline → after):
- chunks 418 → 383
- total_gzip 3209 KB → 3187 KB
- main entry 143 KB → 7 KB (95% reduction; mermaid/katex/shiki now lazy)
Also adds benchmark infrastructure under scripts/benchmark/ and the v2 plan
document under docs/perf-optimization-plan-v2.md.
- Rust: pending_utf8 (256), candidates (4), children (4) pre-allocated - TS: 14 console.warn calls guarded by import.meta.env.DEV via devWarn()
- uiStore.setSidebarWidth: debounce localStorage writes (300ms) during drag (~60×/s → 1× per drag end) - teamStore.handleTeamCreated: replace 4 fire-and-forget setTimeouts with retryWithBackoff (1s/2s/4s), stops at first success - useScheduledTaskDesktopNotifications: tasks.list() short-circuits when no desktop-enabled tasks, polling backs off 30s → 5min - ActiveSession TASK_POLL_INTERVAL_MS: 1s → 3s Net effect: idle desktop drops ~10× API requests / minute.
- Add sessionsById: Map<string, SessionListItem> kept in sync with the existing sessions array on every mutation - Export useSessionById selector hook; consumers can opt in to the O(1) lookup over .find() at their leisure - Single source of truth: fetchSessions builds Map directly from dedupe pass; create/branch/delete/rename rebuild via helper ChatStore sessions Map refactor deferred (larger change, separate batch).
…robe Frontend: - tabStore: add liveTerminalIds[] with MAX_LIVE_TERMINALS = 5. New touchTerminal() promotes a terminal to MRU and evicts the head when over cap, calling destroyTerminalRuntime() on the loser. Terminals are touched on open/activate; closeTab removes them. - ContentRouter: render only live terminals (was: every terminal tab). Caps PTY + xterm.js memory growth for power users with many tabs. Rust: - login_shell_environment: read stdout via OS EOF (no busy-poll) and run the child on a dedicated thread instead of spinning try_wait + thread::sleep(25ms) on the caller. OnceLock still caches the result, so the cost is paid once per process — the win is removing the up-to-2s blocking budget from whichever sync Tauri command first triggers it (typically terminal_spawn).
ActiveSession.test.tsx: bump advanceTimersByTimeAsync from 2200ms to 8900ms to match the throttled 3000ms TASK_POLL_INTERVAL_MS (Batch C). useScheduledTaskDesktopNotifications.test.tsx: the Batch C short-circuit skips getRecentRuns when no task has the desktop notification channel, so test should wait for the task list call and assert getRecentRuns was never issued.
…s doc - store-e2e.ts: getSessionById hot path (Array.find vs Map.get, 5-100 sessions) - tti.ts: puppeteer-core first-load TTI (cold/warm DCL, load, JS bytes) - tauri-rss.ts: sidecar/desktop exe size + RSS probe (needs running Tauri) - Results: Map.get 5-10x faster than Array.find; TTI cold +156ms but main entry -95%; Tauri RSS pending GUI workflow
The .omc/ directory contains runtime state from oh-my-claudecode (session logs, hud cache, mission state) that the .gitignore already excluded. This removes the historically-tracked entries so future stash/merge/rebase operations stop conflicting on these auto-generated files.
…se dedup - Wrap Mermaid dynamic import() in try/catch with fallback error UI so a failed chunk load surfaces a readable message instead of an infinite loading spinner. - Add in-flight Promise dedup + persistent error state for KaTeX so concurrent math renders share a single load attempt and a previously failed load surfaces inline rather than retrying forever.
- Add `isTerminalProcessActive()` helper in terminalRuntime to detect running/starting PTY processes. - tabStore eviction now rotates active terminals to the tail of the LRU and only evicts inactive heads, so long-running commands aren't killed by automatic LRU shedding when more terminals open. - Replace hardcoded `MAX_LIVE_TERMINALS = 5` with `getMaxLiveTerminals()` reading from uiStore (`maxLiveTerminals`). - uiStore: add `MAX_LIVE_TERMINALS_DEFAULT = 5`, `MAX_LIVE_TERMINALS_OPTIONS = [3, 5, 10, 0]`, persisted setter, and `setMaxLiveTerminals()`. - GeneralSettings: expose the new cap in Settings UI. - Test updates for uiStore theme cycling now cover all 6 themes with colorScheme assertions (incl. midnight = dark).
Remove `sessionsById` from store state to eliminate dual source of truth that had to be kept in sync on every list mutation. Replace with a module-level memoized cache that rebuilds the Map only when the sessions array reference changes, plus a `useSessionById(id)` hook for components that need O(1) lookups. This prevents subtle staleness bugs where the array and the Map could diverge during partial updates, and removes ~30 lines of bookkeeping in every mutator.
The scheduled-task notification poller previously swallowed all errors,
leaving users unaware when notifications stopped flowing (e.g. backend
restart, network blip, sidecar crash).
Add a consecutive failure counter with a toastedFailure dedup flag:
after 3 consecutive poll failures, a single warning toast is shown
("定时任务通知轮询失败...") and not repeated until polling recovers.
A successful poll resets both counter and dedup flag.
Test added covering the consecutive-failure threshold + dedup behavior.
Sidebar, StatusBar, and ActiveSession previously rebuilt their own session-id maps or scanned the full sessions array to resolve a single session by id. They now use the new `useSessionById(id)` hook (added in #13), which subscribes to the memoized id->session Map and avoids both per-render Map construction and full-list iteration. - Sidebar: drop local `sessionsById` Map; `pendingBatchDeleteSessions` now resolves names via `sessions.find()` (only invoked at confirm time, not on every render). - StatusBar: read project path via `useSessionById(activeTabId)`. - ActiveSession: same migration; remove unused `SessionListItem` import.
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.
Summary
Follow-up branch to
docs/perf-optimization-plan-v2.md. Implements the seven trade-off fixes (#11–#17) flagged during v2 review:MAX_LIVE_TERMINALSis configurable from SettingssessionStoresingle source of truth (drop dualsessionsByIdstate, replace with module-level memoized cache +useSessionByIdhook)Sidebar,StatusBar,ActiveSessionconsumers onto the newuseSessionByIdhookFrontend only (
desktop/src/**). No Rust changes, no schema/API changes.Commit map
.gitignorehousekeeping (untrack.omc/runtime state)MermaidRenderer.tsx,MarkdownRenderer.tsxterminalRuntime.ts,tabStore.ts,uiStore.ts,uiStore.test.ts,GeneralSettings.tsxsessionStore.tsuseScheduledTaskDesktopNotifications.ts+ testSidebar.tsx,StatusBar.tsx,ActiveSession.tsxdocs/perf-store-refactor-and-robustness.mdtabStore.tsis bundled with #15 because the active-process-aware eviction (#12) and the configurable cap (#15) both rewrite the same eviction block; splitting them would have required surgical hunk surgery without semantic gain.Test status
pnpm test: 16 failing test files vs baseline 18 onmaster(verified viagit stashbaseline run). Net +2 files greener, +1 passing test.pnpm lint: ~70 historical TS6133 / TS2352 errors inSettings.tsx/AboutSettings.tsx/desktopRuntime.test.ts. All pre-date this branch. None introduced here.Verification done
bun tauri devboots cleanly; sidecar/healthreturns{"status":"ok"},/api/sessionsreturns valid JSON, CORS preflight 204 with properAccess-Control-Allow-Origin.git stashconfirms test failures are historical, not regressions..omc/no longer tracked;.gitignorealready had the entry on line 25.See
docs/perf-store-refactor-and-robustness.mdfor full per-fix detail.Test plan
tail -fin one — verify it survives LRU pressure ([help wanted] One-click MCP marketplace (registry-driven install) #12)