Skip to content

perf: store refactor + robustness fixes (#11-#17)#4

Open
GoDiao wants to merge 37 commits into
masterfrom
perf/store-refactor-and-robustness
Open

perf: store refactor + robustness fixes (#11-#17)#4
GoDiao wants to merge 37 commits into
masterfrom
perf/store-refactor-and-robustness

Conversation

@GoDiao

@GoDiao GoDiao commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Summary

Follow-up branch to docs/perf-optimization-plan-v2.md. Implements the seven trade-off fixes (#11#17) flagged during v2 review:

Frontend only (desktop/src/**). No Rust changes, no schema/API changes.

Commit map

# Commit Fix # Files
1 0cf1e3f .gitignore housekeeping (untrack .omc/ runtime state)
2 ba17fc2 11,16 MermaidRenderer.tsx, MarkdownRenderer.tsx
3 d511eda 12,15 terminalRuntime.ts, tabStore.ts, uiStore.ts, uiStore.test.ts, GeneralSettings.tsx
4 dc605db 13 sessionStore.ts
5 423a827 14 useScheduledTaskDesktopNotifications.ts + test
6 9e75d80 17 Sidebar.tsx, StatusBar.tsx, ActiveSession.tsx
7 fcf013c docs docs/perf-store-refactor-and-robustness.md

tabStore.ts is 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 on master (verified via git stash baseline run). Net +2 files greener, +1 passing test.
  • pnpm lint: ~70 historical TS6133 / TS2352 errors in Settings.tsx / AboutSettings.tsx / desktopRuntime.test.ts. All pre-date this branch. None introduced here.

Verification done

  • bun tauri dev boots cleanly; sidecar /health returns {"status":"ok"}, /api/sessions returns valid JSON, CORS preflight 204 with proper Access-Control-Allow-Origin.
  • Baseline test run via git stash confirms test failures are historical, not regressions.
  • .omc/ no longer tracked; .gitignore already had the entry on line 25.

See docs/perf-store-refactor-and-robustness.md for full per-fix detail.

Test plan

GoDiao added 30 commits May 29, 2026 03:04
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)
合入性能优化分支全部实现成果:
- 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
GoDiao added 7 commits June 12, 2026 21:42
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.
Document scope, commit map, per-fix summary, test delta, and verification
status for the seven trade-off fixes (#11#17) implemented on this
branch as a follow-up to docs/perf-optimization-plan-v2.md.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant