docs(spec): add architecture optimization design spec
Comprehensive design for 14-week architecture overhaul: - VZustand for fine-grained reactivity - Web Worker isolation for security - XState for Hands state machine - Domain-driven directory structure Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,720 @@
|
||||
# ZCLAW 架构优化设计规格
|
||||
|
||||
> **版本**: 1.0
|
||||
> **日期**: 2026-03-21
|
||||
> **状态**: 待审核
|
||||
> **作者**: Claude + 用户协作
|
||||
|
||||
---
|
||||
|
||||
## 1. 概述
|
||||
|
||||
### 1.1 背景
|
||||
|
||||
ZCLAW 是面向中文用户的 AI Agent 桌面客户端,经过分析发现以下需要改进的领域:
|
||||
|
||||
- **安全风险**: 浏览器 eval() XSS 风险、localStorage 凭据回退
|
||||
- **性能瓶颈**: 流式更新时重建整个消息数组、无界消息数组
|
||||
- **架构问题**: 50+ lib 模块缺乏统一抽象、测试覆盖不足
|
||||
|
||||
### 1.2 目标
|
||||
|
||||
- 在 14 周内完成全面架构优化
|
||||
- 采用激进架构优先策略
|
||||
- 重点优化四个核心系统:对话、Hands、Intelligence、技能
|
||||
|
||||
### 1.3 关键决策
|
||||
|
||||
| 决策点 | 选择 | 理由 |
|
||||
|--------|------|------|
|
||||
| 状态管理 | VZustand | Proxy 细粒度响应,性能更好 |
|
||||
| 安全策略 | Web Worker 隔离 | 最安全的执行隔离方案 |
|
||||
| 整体方案 | A+C 混合 | 渐进式 + 领域驱动结合 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 架构设计
|
||||
|
||||
### 2.1 总体架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ UI Layer │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ React Components (60+) │ │
|
||||
│ │ - 按领域组织 │ │
|
||||
│ │ - 只负责展示和交互 │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ State Layer │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ VZustand Stores (基于 Proxy) │ │
|
||||
│ │ - 细粒度响应 │ │
|
||||
│ │ - 领域划分: Chat, Hands, Intelligence, Skills │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Client Layer │
|
||||
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────┐ │
|
||||
│ │ Gateway Client │ │ Intelligence Clt │ │ Worker Pool │ │
|
||||
│ │ (WebSocket) │ │ (Tauri Commands) │ │ (隔离执行) │ │
|
||||
│ └──────────────────┘ └──────────────────┘ └──────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Backend Layer │
|
||||
│ ┌──────────────────┐ ┌──────────────────────────────────────┐│
|
||||
│ │ OpenFang Kernel │ │ Tauri Rust Backend ││
|
||||
│ │ (Port 50051) │ │ - Intelligence (心跳/压缩/反思) ││
|
||||
│ │ │ │ - Memory (SQLite 持久化) ││
|
||||
│ │ │ │ - Browser (WebDriver) ││
|
||||
│ └──────────────────┘ └──────────────────────────────────────┘│
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.2 领域划分
|
||||
|
||||
```
|
||||
desktop/src/
|
||||
├── domains/ # 领域模块 (新建)
|
||||
│ ├── chat/ # 对话系统
|
||||
│ │ ├── store.ts # VZustand store
|
||||
│ │ ├── types.ts # 类型定义
|
||||
│ │ ├── api.ts # API 调用
|
||||
│ │ └── hooks.ts # React hooks
|
||||
│ ├── hands/ # 自动化系统
|
||||
│ │ ├── store.ts # 状态机 store
|
||||
│ │ ├── machine.ts # XState 状态机
|
||||
│ │ ├── executor.ts # 执行器
|
||||
│ │ └── types.ts
|
||||
│ ├── intelligence/ # 智能层
|
||||
│ │ ├── client.ts # 统一客户端
|
||||
│ │ ├── cache.ts # 缓存策略
|
||||
│ │ └── types.ts
|
||||
│ └── skills/ # 技能系统
|
||||
│ ├── store.ts
|
||||
│ ├── loader.ts # 技能加载器
|
||||
│ └── types.ts
|
||||
├── workers/ # Web Workers (新建)
|
||||
│ ├── browser-worker.ts # 浏览器隔离执行
|
||||
│ └── pool.ts # Worker 池管理
|
||||
├── shared/ # 共享模块 (新建)
|
||||
│ ├── error-handling.ts # 统一错误处理
|
||||
│ ├── logging.ts # 统一日志
|
||||
│ └── types.ts # 共享类型
|
||||
└── components/ # UI 组件 (保持)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心组件设计
|
||||
|
||||
### 3.1 VZustand 状态管理
|
||||
|
||||
**问题**: 当前 Zustand 每次更新都会触发整个订阅组件重渲染
|
||||
|
||||
**解决方案**: 使用 Proxy 实现细粒度响应
|
||||
|
||||
```typescript
|
||||
// domains/chat/store.ts
|
||||
import { proxy, useSnapshot } from 'valtio';
|
||||
|
||||
interface ChatState {
|
||||
messages: Message[];
|
||||
conversations: Conversation[];
|
||||
currentConversationId: string | null;
|
||||
isStreaming: boolean;
|
||||
|
||||
// Actions
|
||||
addMessage: (message: Message) => void;
|
||||
updateMessage: (id: string, update: Partial<Message>) => void;
|
||||
setStreaming: (streaming: boolean) => void;
|
||||
}
|
||||
|
||||
export const chatState = proxy<ChatState>({
|
||||
messages: [],
|
||||
conversations: [],
|
||||
currentConversationId: null,
|
||||
isStreaming: false,
|
||||
|
||||
addMessage: (message) => {
|
||||
chatState.messages.push(message); // 直接 mutate
|
||||
},
|
||||
|
||||
updateMessage: (id, update) => {
|
||||
const msg = chatState.messages.find(m => m.id === id);
|
||||
if (msg) Object.assign(msg, update); // 细粒度更新
|
||||
},
|
||||
|
||||
setStreaming: (streaming) => {
|
||||
chatState.isStreaming = streaming;
|
||||
}
|
||||
});
|
||||
|
||||
// Hook
|
||||
export function useChatState() {
|
||||
return useSnapshot(chatState); // 只在访问的字段变化时重渲染
|
||||
}
|
||||
|
||||
// 组件使用
|
||||
function MessageList() {
|
||||
const { messages } = useChatState(); // 只订阅 messages
|
||||
return messages.map(m => <Message key={m.id} message={m} />);
|
||||
}
|
||||
|
||||
function StreamingIndicator() {
|
||||
const { isStreaming } = useChatState(); // 只订阅 isStreaming
|
||||
return isStreaming ? <Spinner /> : null;
|
||||
}
|
||||
```
|
||||
|
||||
**预期收益**:
|
||||
- 流式更新时性能提升 70%
|
||||
- 代码更简洁 (直接 mutate)
|
||||
- 选择性渲染减少不必要的重渲染
|
||||
|
||||
### 3.2 Web Worker 隔离执行
|
||||
|
||||
**问题**: browser.eval() 在主线程执行用户输入,存在 XSS 风险
|
||||
|
||||
**解决方案**: Web Worker 完全隔离
|
||||
|
||||
```typescript
|
||||
// workers/pool.ts
|
||||
export class BrowserWorkerPool {
|
||||
private workers: Worker[] = [];
|
||||
private maxWorkers = 4;
|
||||
|
||||
async execute(script: string, args: unknown[]): Promise<unknown> {
|
||||
const worker = this.getAvailableWorker();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
worker.terminate();
|
||||
this.recycleWorker(worker);
|
||||
reject(new Error('Execution timeout (30s)'));
|
||||
}, 30000);
|
||||
|
||||
worker.onmessage = (e) => {
|
||||
clearTimeout(timeout);
|
||||
if (e.data.error) {
|
||||
reject(new ExecutionError(e.data.error));
|
||||
} else {
|
||||
resolve(e.data.result);
|
||||
}
|
||||
};
|
||||
|
||||
worker.onerror = (e) => {
|
||||
clearTimeout(timeout);
|
||||
reject(new ExecutionError(e.message));
|
||||
};
|
||||
|
||||
worker.postMessage({ type: 'eval', script, args });
|
||||
});
|
||||
}
|
||||
|
||||
private getAvailableWorker(): Worker {
|
||||
// 复用或创建新 Worker
|
||||
if (this.workers.length < this.maxWorkers) {
|
||||
const worker = new Worker(new URL('./browser-worker.ts', import.meta.url));
|
||||
this.workers.push(worker);
|
||||
return worker;
|
||||
}
|
||||
// 等待可用 Worker...
|
||||
}
|
||||
}
|
||||
|
||||
// workers/browser-worker.ts
|
||||
const ALLOWED_SCRIPTS = new Set([
|
||||
'navigate', 'click', 'type', 'screenshot', 'extract',
|
||||
'scroll', 'wait', 'select', 'hover'
|
||||
]);
|
||||
|
||||
self.onmessage = async (e) => {
|
||||
const { type, script, args } = e.data;
|
||||
|
||||
if (type === 'eval') {
|
||||
try {
|
||||
if (!ALLOWED_SCRIPTS.has(script)) {
|
||||
throw new Error(`Script not allowed: ${script}`);
|
||||
}
|
||||
|
||||
// 在受限环境中执行
|
||||
const result = await executeInSandbox(script, args);
|
||||
self.postMessage({ result });
|
||||
} catch (error) {
|
||||
self.postMessage({ error: error.message });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
async function executeInSandbox(script: string, args: unknown[]): Promise<unknown> {
|
||||
// 无 DOM 访问
|
||||
// 无 localStorage 访问
|
||||
// 只有受限的 API
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**安全保证**:
|
||||
- Worker 无法访问 DOM
|
||||
- Worker 无法访问 localStorage/cookie
|
||||
- 脚本白名单限制
|
||||
- 执行超时保护
|
||||
- 错误隔离
|
||||
|
||||
### 3.3 Hands 状态机
|
||||
|
||||
**问题**: 当前 HandStore 状态转换不清晰,难以追踪
|
||||
|
||||
**解决方案**: 使用 XState 实现状态机
|
||||
|
||||
```typescript
|
||||
// domains/hands/machine.ts
|
||||
import { createMachine, assign } from 'xstate';
|
||||
|
||||
export const handMachine = createMachine({
|
||||
id: 'hand',
|
||||
initial: 'idle',
|
||||
states: {
|
||||
idle: {
|
||||
on: {
|
||||
TRIGGER: 'validating',
|
||||
},
|
||||
},
|
||||
validating: {
|
||||
on: {
|
||||
VALID: 'checking_approval',
|
||||
INVALID: 'error',
|
||||
},
|
||||
},
|
||||
checking_approval: {
|
||||
on: {
|
||||
NEEDS_APPROVAL: 'pending_approval',
|
||||
AUTO_APPROVE: 'executing',
|
||||
},
|
||||
},
|
||||
pending_approval: {
|
||||
on: {
|
||||
APPROVE: 'executing',
|
||||
REJECT: 'cancelled',
|
||||
TIMEOUT: 'error',
|
||||
},
|
||||
},
|
||||
executing: {
|
||||
on: {
|
||||
SUCCESS: 'completed',
|
||||
ERROR: 'error',
|
||||
TIMEOUT: 'error',
|
||||
},
|
||||
},
|
||||
completed: {
|
||||
on: {
|
||||
RESET: 'idle',
|
||||
},
|
||||
},
|
||||
error: {
|
||||
on: {
|
||||
RETRY: 'validating',
|
||||
RESET: 'idle',
|
||||
},
|
||||
},
|
||||
cancelled: {
|
||||
on: {
|
||||
RESET: 'idle',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### 3.4 Intelligence 缓存策略
|
||||
|
||||
**问题**: 每次请求都需要调用 Rust 后端
|
||||
|
||||
**解决方案**: LRU 缓存 + TTL
|
||||
|
||||
```typescript
|
||||
// domains/intelligence/cache.ts
|
||||
interface CacheEntry<T> {
|
||||
value: T;
|
||||
expiresAt: number;
|
||||
}
|
||||
|
||||
export class IntelligenceCache {
|
||||
private cache = new Map<string, CacheEntry<unknown>>();
|
||||
private maxSize = 100;
|
||||
private defaultTTL = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
get<T>(key: string): T | null {
|
||||
const entry = this.cache.get(key);
|
||||
if (!entry) return null;
|
||||
|
||||
if (Date.now() > entry.expiresAt) {
|
||||
this.cache.delete(key);
|
||||
return null;
|
||||
}
|
||||
|
||||
return entry.value as T;
|
||||
}
|
||||
|
||||
set<T>(key: string, value: T, ttl = this.defaultTTL): void {
|
||||
// LRU 淘汰
|
||||
if (this.cache.size >= this.maxSize) {
|
||||
const firstKey = this.cache.keys().next().value;
|
||||
this.cache.delete(firstKey);
|
||||
}
|
||||
|
||||
this.cache.set(key, {
|
||||
value,
|
||||
expiresAt: Date.now() + ttl,
|
||||
});
|
||||
}
|
||||
|
||||
invalidate(pattern: string): void {
|
||||
for (const key of this.cache.keys()) {
|
||||
if (key.includes(pattern)) {
|
||||
this.cache.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 安全设计
|
||||
|
||||
### 4.1 凭据存储加密
|
||||
|
||||
```typescript
|
||||
// shared/secure-storage.ts
|
||||
export class SecureCredentialStorage {
|
||||
private async getEncryptionKey(): Promise<CryptoKey> {
|
||||
// 从 OS keyring 获取或派生
|
||||
const masterKey = await this.getMasterKey();
|
||||
return deriveKey(masterKey, SALT, {
|
||||
name: 'AES-GCM',
|
||||
length: 256,
|
||||
});
|
||||
}
|
||||
|
||||
async store(key: string, value: string): Promise<void> {
|
||||
const encKey = await this.getEncryptionKey();
|
||||
const iv = crypto.getRandomValues(new Uint8Array(12));
|
||||
const encrypted = await crypto.subtle.encrypt(
|
||||
{ name: 'AES-GCM', iv },
|
||||
encKey,
|
||||
new TextEncoder().encode(value)
|
||||
);
|
||||
|
||||
// 存储加密后的数据
|
||||
const stored = {
|
||||
iv: arrayToBase64(iv),
|
||||
data: arrayToBase64(new Uint8Array(encrypted)),
|
||||
};
|
||||
|
||||
if (isTauriEnv()) {
|
||||
await invoke('secure_store', { key, value: JSON.stringify(stored) });
|
||||
} else {
|
||||
localStorage.setItem(`enc_${key}`, JSON.stringify(stored));
|
||||
}
|
||||
}
|
||||
|
||||
async retrieve(key: string): Promise<string | null> {
|
||||
let stored: { iv: string; data: string };
|
||||
|
||||
if (isTauriEnv()) {
|
||||
stored = await invoke('secure_retrieve', { key });
|
||||
} else {
|
||||
const raw = localStorage.getItem(`enc_${key}`);
|
||||
if (!raw) return null;
|
||||
stored = JSON.parse(raw);
|
||||
}
|
||||
|
||||
const encKey = await this.getEncryptionKey();
|
||||
const decrypted = await crypto.subtle.decrypt(
|
||||
{ name: 'AES-GCM', iv: base64ToArray(stored.iv) },
|
||||
encKey,
|
||||
base64ToArray(stored.data)
|
||||
);
|
||||
|
||||
return new TextDecoder().decode(decrypted);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 WebSocket 安全
|
||||
|
||||
```typescript
|
||||
// shared/websocket-security.ts
|
||||
export function createSecureWebSocket(url: string): WebSocket {
|
||||
// 强制 WSS for non-localhost
|
||||
if (!url.startsWith('wss://') && !isLocalhost(url)) {
|
||||
throw new SecurityError(
|
||||
'Non-localhost connections must use WSS protocol'
|
||||
);
|
||||
}
|
||||
|
||||
const ws = new WebSocket(url);
|
||||
|
||||
// 添加消息验证
|
||||
ws.addEventListener('message', (event) => {
|
||||
const message = validateMessage(event.data);
|
||||
if (!message) {
|
||||
console.error('Invalid message format');
|
||||
ws.close(1008, 'Policy Violation');
|
||||
}
|
||||
});
|
||||
|
||||
return ws;
|
||||
}
|
||||
|
||||
function isLocalhost(url: string): boolean {
|
||||
const parsed = new URL(url);
|
||||
return ['localhost', '127.0.0.1', '::1'].includes(parsed.hostname);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 测试策略
|
||||
|
||||
### 5.1 单元测试
|
||||
|
||||
```typescript
|
||||
// domains/chat/store.test.ts
|
||||
describe('ChatState', () => {
|
||||
beforeEach(() => {
|
||||
chatState.messages = [];
|
||||
chatState.isStreaming = false;
|
||||
});
|
||||
|
||||
it('should add message', () => {
|
||||
const msg = createMockMessage();
|
||||
chatState.addMessage(msg);
|
||||
expect(chatState.messages).toHaveLength(1);
|
||||
expect(chatState.messages[0]).toEqual(msg);
|
||||
});
|
||||
|
||||
it('should update message content', () => {
|
||||
chatState.messages = [createMockMessage({ id: '1', content: 'old' })];
|
||||
chatState.updateMessage('1', { content: 'new' });
|
||||
expect(chatState.messages[0].content).toBe('new');
|
||||
});
|
||||
|
||||
it('should not trigger re-render for unrelated updates', () => {
|
||||
const { result, rerender } = renderHook(() => useChatState());
|
||||
|
||||
act(() => {
|
||||
chatState.isStreaming = true; // 不会触发 MessageList 重渲染
|
||||
});
|
||||
|
||||
// 验证只有订阅 isStreaming 的组件重渲染
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 5.2 集成测试
|
||||
|
||||
```typescript
|
||||
// domains/hands/executor.test.ts
|
||||
describe('HandExecutor', () => {
|
||||
let pool: BrowserWorkerPool;
|
||||
|
||||
beforeEach(() => {
|
||||
pool = new BrowserWorkerPool();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await pool.terminateAll();
|
||||
});
|
||||
|
||||
it('should execute allowed script', async () => {
|
||||
const result = await pool.execute('navigate', ['https://example.com']);
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
it('should reject disallowed script', async () => {
|
||||
await expect(pool.execute('eval', ['alert(1)']))
|
||||
.rejects.toThrow('Script not allowed');
|
||||
});
|
||||
|
||||
it('should timeout long execution', async () => {
|
||||
await expect(pool.execute('infiniteLoop', []))
|
||||
.rejects.toThrow('Execution timeout');
|
||||
}, 35000);
|
||||
});
|
||||
```
|
||||
|
||||
### 5.3 E2E 测试场景
|
||||
|
||||
1. **聊天流程**: 发送消息 → 接收流式响应 → 显示完整消息
|
||||
2. **Hands 触发**: 触发 Hand → 审批流程 → 执行 → 结果显示
|
||||
3. **Intelligence**: 记忆提取 → 心跳触发 → 反思生成
|
||||
4. **技能搜索**: 输入关键词 → 搜索技能 → 查看详情
|
||||
|
||||
---
|
||||
|
||||
## 6. 实施计划
|
||||
|
||||
### 6.1 Phase 1: 安全 + 测试 (2周)
|
||||
|
||||
**目标**: 建立安全基础和测试框架
|
||||
|
||||
**任务**:
|
||||
- [ ] 实现 Web Worker 隔离执行引擎
|
||||
- [ ] 实现凭据加密存储
|
||||
- [ ] 强制 WSS 连接
|
||||
- [ ] 建立测试框架 (Vitest + Playwright)
|
||||
- [ ] 设置覆盖率门禁 (60%)
|
||||
- [ ] 添加 chatStore 基础测试
|
||||
|
||||
**交付物**:
|
||||
- `workers/browser-worker.ts`
|
||||
- `workers/pool.ts`
|
||||
- `shared/secure-storage.ts`
|
||||
- 测试配置文件
|
||||
|
||||
### 6.2 Phase 2: 领域重组 (4周)
|
||||
|
||||
**目标**: 按领域重组代码,迁移到 VZustand
|
||||
|
||||
**任务**:
|
||||
- [ ] 创建 domains/ 目录结构
|
||||
- [ ] 迁移 Chat Store 到 VZustand
|
||||
- [ ] 迁移 Hands Store + 状态机
|
||||
- [ ] 迁移 Intelligence Client
|
||||
- [ ] 迁移 Skills Store
|
||||
- [ ] 提取共享模块
|
||||
- [ ] 更新导入路径
|
||||
|
||||
**交付物**:
|
||||
- `domains/chat/`
|
||||
- `domains/hands/`
|
||||
- `domains/intelligence/`
|
||||
- `domains/skills/`
|
||||
- `shared/`
|
||||
|
||||
### 6.3 Phase 3: 核心优化 (6周,并行)
|
||||
|
||||
**Track A: Chat 优化**
|
||||
- [ ] VZustand 性能优化
|
||||
- [ ] 虚拟滚动实现
|
||||
- [ ] 消息分页
|
||||
- [ ] 流式响应 AsyncGenerator
|
||||
|
||||
**Track B: Hands 优化**
|
||||
- [ ] XState 状态机完善
|
||||
- [ ] 审批流程配置化
|
||||
- [ ] Worker 隔离集成
|
||||
- [ ] 错误恢复机制
|
||||
|
||||
**Track C: Intelligence 优化**
|
||||
- [ ] Rust 后端功能完善
|
||||
- [ ] LRU 缓存实现
|
||||
- [ ] 向量检索准备
|
||||
- [ ] 性能调优
|
||||
|
||||
### 6.4 Phase 4: 集成 + 清理 (2周)
|
||||
|
||||
**目标**: 完成集成,清理旧代码
|
||||
|
||||
**任务**:
|
||||
- [ ] 跨领域集成测试
|
||||
- [ ] E2E 测试完善
|
||||
- [ ] 清理旧代码
|
||||
- [ ] 更新文档
|
||||
- [ ] 性能基准测试
|
||||
|
||||
**交付物**:
|
||||
- 完整的测试套件
|
||||
- 更新的文档
|
||||
- 性能报告
|
||||
|
||||
---
|
||||
|
||||
## 7. 验收标准
|
||||
|
||||
### 7.1 功能验收
|
||||
|
||||
- [ ] 所有现有功能正常工作
|
||||
- [ ] 无回归问题
|
||||
- [ ] 新安全功能有效
|
||||
|
||||
### 7.2 性能验收
|
||||
|
||||
| 指标 | 当前 | 目标 |
|
||||
|------|------|------|
|
||||
| 首屏加载 | ~3s | <2s |
|
||||
| 消息渲染 | 卡顿 | 60fps |
|
||||
| 1000+ 消息滚动 | 卡顿 | 流畅 |
|
||||
| 内存占用 | 无界 | <500MB |
|
||||
| Worker 执行延迟 | N/A | <100ms |
|
||||
|
||||
### 7.3 安全验收
|
||||
|
||||
- [ ] XSS 攻击测试通过
|
||||
- [ ] 凭据存储安全审计通过
|
||||
- [ ] WebSocket 安全测试通过
|
||||
- [ ] 依赖安全扫描通过
|
||||
|
||||
### 7.4 测试覆盖
|
||||
|
||||
| 模块 | 目标 |
|
||||
|------|------|
|
||||
| Chat Store | 90% |
|
||||
| Hands Store | 85% |
|
||||
| Intelligence Client | 80% |
|
||||
| Worker Pool | 85% |
|
||||
| 工具函数 | 95% |
|
||||
|
||||
---
|
||||
|
||||
## 8. 风险与缓解
|
||||
|
||||
| 风险 | 概率 | 影响 | 缓解措施 |
|
||||
|------|------|------|----------|
|
||||
| VZustand 学习曲线 | 中 | 中 | 提前 POC,编写示例 |
|
||||
| Worker 兼容性 | 低 | 高 | 渐进增强,主线程回退 |
|
||||
| 迁移导致回归 | 中 | 高 | 每阶段充分测试 |
|
||||
| 进度延期 | 中 | 中 | 预留 20% buffer |
|
||||
| 团队不熟悉 | 中 | 中 | 培训 + 文档 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 附录
|
||||
|
||||
### 9.1 技术依赖
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"valtio": "^2.0.0",
|
||||
"xstate": "^5.0.0",
|
||||
"react-window": "^2.2.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitest": "^1.0.0",
|
||||
"@playwright/test": "^1.40.0",
|
||||
"@testing-library/react": "^14.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 9.2 参考文档
|
||||
|
||||
- [VZustand 文档](https://valtio.pmnd.rs/)
|
||||
- [XState 文档](https://xstate.js.org/)
|
||||
- [Web Worker API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API)
|
||||
- [Tauri 安全最佳实践](https://tauri.app/v2/guides/security/)
|
||||
|
||||
---
|
||||
|
||||
*文档版本: 1.0 | 最后更新: 2026-03-21*
|
||||
Reference in New Issue
Block a user