- Track A: Chat virtual scrolling with react-window - Track B: Hands Web Worker isolation for security - Track C: Intelligence caching (already completed) Plan includes: - File structure and task breakdown - Code examples for each component - Verification checklist - Integration requirements Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
10 KiB
ZCLAW 架构优化 - Phase 3: 核心优化 实施计划
For agentic workers: REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 实现核心性能优化 - 虚拟滚动、Web Worker 隔离、缓存策略
Architecture: 三条并行轨道,可独立实施和验证
Tech Stack: React, react-window, Web Worker, Valtio
Spec Reference: docs/superpowers/specs/2026-03-21-architecture-optimization-design.md
Duration: 6 周 (24 人日)
并行轨道概览
┌─────────────────────────────────────────────────────────────────────────┐
│ Phase 3: 核心优化 (并行) │
├─────────────────────┬─────────────────────┬─────────────────────────────┤
│ Track A: Chat │ Track B: Hands │ Track C: Intelligence │
│ 虚拟滚动优化 │ Web Worker 隔离 │ 缓存策略 ✅ │
├─────────────────────┼─────────────────────┼─────────────────────────────┤
│ • react-window │ • browser-worker.ts │ • IntelligenceCache │
│ • 消息分页 │ • worker-pool.ts │ • TTL + LRU │
│ • 惰性加载 │ • 安全执行隔离 │ • 命中率统计 │
│ • 首屏 3x 提升 │ • XSS 防护 │ • 70% 响应提升 │
└─────────────────────┴─────────────────────┴─────────────────────────────┘
Track C (Intelligence 缓存) 已在 Phase 2.5 完成
Track A: Chat 虚拟滚动优化
目标
| 指标 | 当前 | 目标 |
|---|---|---|
| 首屏渲染 | ~2s | <500ms |
| 1000+ 消息滚动 | 卡顿 | 60fps |
| 内存占用 | 无限增长 | 限制 100 条 |
文件结构
desktop/src/
├── components/ChatArea/
│ ├── MessageList.tsx # 修改: 使用虚拟滚动
│ ├── VirtualMessageList.tsx # 新建: react-window 封装
│ └── MessageItem.tsx # 新建: 单条消息组件
└── domains/chat/
└── hooks.ts # 修改: 添加分页 hooks
Task A.1: 安装 react-window
Files:
-
Modify:
desktop/package.json -
Step 1: 安装依赖
cd g:/ZClaw_openfang/desktop && pnpm add react-window @types/react-window
- Step 2: 验证安装
pnpm list react-window
Task A.2: 创建 VirtualMessageList 组件
Files:
-
Create:
desktop/src/components/ChatArea/VirtualMessageList.tsx -
Create:
desktop/src/components/ChatArea/MessageItem.tsx -
Step 1: 创建 MessageItem 组件
// desktop/src/components/ChatArea/MessageItem.tsx
import { memo } from 'react';
import type { Message } from '@/domains/chat';
interface MessageItemProps {
message: Message;
style?: React.CSSProperties;
}
export const MessageItem = memo(function MessageItem({ message, style }: MessageItemProps) {
return (
<div style={style} className={`message message-${message.role}`}>
<div className="message-content">{message.content}</div>
{message.streaming && <span className="streaming-indicator">...</span>}
</div>
);
});
- Step 2: 创建 VirtualMessageList 组件
// desktop/src/components/ChatArea/VirtualMessageList.tsx
import { useRef, useCallback, useEffect } from 'react';
import { FixedSizeList as List } from 'react-window';
import { MessageItem } from './MessageItem';
import type { Message } from '@/domains/chat';
interface VirtualMessageListProps {
messages: Message[];
height: number;
width: number;
onScrollToEnd?: () => void;
}
export function VirtualMessageList({
messages,
height,
width,
onScrollToEnd,
}: VirtualMessageListProps) {
const listRef = useRef<List>(null);
// Auto-scroll to bottom when new message arrives
useEffect(() => {
if (listRef.current && messages.length > 0) {
listRef.current.scrollToItem(messages.length - 1, 'end');
}
}, [messages.length]);
const Row = useCallback(
({ index, style }: { index: number; style: React.CSSProperties }) => (
<MessageItem message={messages[index]} style={style} />
),
[messages]
);
return (
<List
ref={listRef}
height={height}
width={width}
itemCount={messages.length}
itemSize={80} // Approximate message height
overscanCount={5}
>
{Row}
</List>
);
}
Task A.3: 更新 MessageList 使用虚拟滚动
Files:
-
Modify:
desktop/src/components/ChatArea/MessageList.tsx -
Step 1: 替换实现
将现有的全量渲染替换为 VirtualMessageList。
Task A.4: 添加消息分页支持
Files:
-
Modify:
desktop/src/domains/chat/store.ts -
Modify:
desktop/src/domains/chat/hooks.ts -
Step 1: 添加分页状态
在 chatStore 中添加:
pageSize: 50,
currentPage: 0,
hasMore: true,
loadMoreMessages: async () => { /* ... */ },
Track B: Hands Web Worker 隔离
目标
| 指标 | 当前 | 目标 |
|---|---|---|
| 执行安全性 | eval() XSS 风险 | 完全隔离 |
| 错误隔离 | 主线程崩溃 | Worker 隔离 |
| 超时控制 | 无 | 30s 强制终止 |
文件结构
desktop/src/
├── workers/
│ ├── browser-worker.ts # 新建: 浏览器执行 Worker
│ └── worker-pool.ts # 新建: Worker 池管理
├── lib/
│ └── browser-executor.ts # 修改: 使用 Worker
└── domains/hands/
└── store.ts # 修改: 集成 Worker 执行
Task B.1: 创建 Browser Worker
Files:
-
Create:
desktop/src/workers/browser-worker.ts -
Step 1: 创建 Worker 脚本
// desktop/src/workers/browser-worker.ts
/// <reference lib="webworker" />
const ctx = self as DedicatedWorkerGlobalScope;
interface ExecuteRequest {
type: 'execute';
id: string;
script: string;
args: unknown[];
timeout: number;
}
interface ExecuteResponse {
type: 'result' | 'error';
id: string;
result?: unknown;
error?: string;
}
ctx.onmessage = async (e: MessageEvent<ExecuteRequest>) => {
const { type, id, script, args, timeout } = e.data;
if (type !== 'execute') return;
const timeoutId = setTimeout(() => {
ctx.postMessage({
type: 'error',
id,
error: 'Execution timeout',
} as ExecuteResponse);
}, timeout);
try {
// Safe execution without DOM access
const fn = new Function('args', script);
const result = await fn(args);
clearTimeout(timeoutId);
ctx.postMessage({ type: 'result', id, result } as ExecuteResponse);
} catch (err) {
clearTimeout(timeoutId);
ctx.postMessage({
type: 'error',
id,
error: err instanceof Error ? err.message : String(err),
} as ExecuteResponse);
}
};
ctx.postMessage({ type: 'ready' });
Task B.2: 创建 Worker Pool
Files:
-
Create:
desktop/src/workers/worker-pool.ts -
Step 1: 创建 Worker 池
// desktop/src/workers/worker-pool.ts
export class BrowserWorkerPool {
private workers: Worker[] = [];
private available: Worker[] = [];
private maxWorkers: number;
constructor(maxWorkers = 4) {
this.maxWorkers = maxWorkers;
}
private createWorker(): Worker {
return new Worker(
new URL('./browser-worker.ts', import.meta.url),
{ type: 'module' }
);
}
async execute(script: string, args: unknown[], timeout = 30000): Promise<unknown> {
const worker = this.available.pop() || this.createWorker();
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
worker.terminate();
reject(new Error('Execution timeout'));
}, timeout);
const handler = (e: MessageEvent) => {
if (e.data.type === 'result') {
clearTimeout(timeoutId);
worker.removeEventListener('message', handler);
this.available.push(worker);
resolve(e.data.result);
} else if (e.data.type === 'error') {
clearTimeout(timeoutId);
worker.removeEventListener('message', handler);
this.available.push(worker);
reject(new Error(e.data.error));
}
};
worker.addEventListener('message', handler);
worker.postMessage({
type: 'execute',
id: Date.now().toString(),
script,
args,
timeout,
});
});
}
terminateAll(): void {
this.workers.forEach(w => w.terminate());
this.workers = [];
this.available = [];
}
}
export const browserWorkerPool = new BrowserWorkerPool();
Task B.3: 集成到 Hands Domain
Files:
-
Modify:
desktop/src/domains/hands/store.ts -
Step 1: 使用 Worker 执行
在 triggerHand action 中使用 browserWorkerPool.execute() 替代直接 eval()。
Track C: Intelligence 缓存策略 ✅ 已完成
已在 Phase 2.5 实现:
desktop/src/domains/intelligence/cache.ts- LRU + TTL 缓存desktop/src/domains/intelligence/store.ts- 带缓存的 Valtio storedesktop/src/domains/intelligence/hooks.ts- React hooks
验证清单
Track A 验证
- 首屏渲染 < 500ms
- 1000+ 消息滚动 60fps
- 消息正确显示
- 流式消息正常更新
Track B 验证
- Worker 正常创建和销毁
- 超时正确终止
- 错误正确隔离
- XSS 攻击被阻止
集成验证
- 所有 TypeScript 编译通过
- 现有测试通过
- E2E 核心流程正常
提交规范
feat(chat): add virtual scrolling with react-window
feat(hands): add Web Worker isolation for browser execution
perf(intelligence): add LRU cache with TTL support