docs(phase3): add core optimization implementation plan
- 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>
This commit is contained in:
397
docs/superpowers/plans/2026-03-21-phase3-core-optimization.md
Normal file
397
docs/superpowers/plans/2026-03-21-phase3-core-optimization.md
Normal file
@@ -0,0 +1,397 @@
|
||||
# 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: 安装依赖**
|
||||
|
||||
```bash
|
||||
cd g:/ZClaw_openfang/desktop && pnpm add react-window @types/react-window
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 验证安装**
|
||||
|
||||
```bash
|
||||
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 组件**
|
||||
|
||||
```typescript
|
||||
// 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 组件**
|
||||
|
||||
```typescript
|
||||
// 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 中添加:
|
||||
```typescript
|
||||
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 脚本**
|
||||
|
||||
```typescript
|
||||
// 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 池**
|
||||
|
||||
```typescript
|
||||
// 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 store
|
||||
- `desktop/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
|
||||
```
|
||||
Reference in New Issue
Block a user