Files
zclaw_openfang/docs/superpowers/plans/2026-03-21-phase3-core-optimization.md
iven 0d4fa96b82
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
refactor: 统一项目名称从OpenFang到ZCLAW
重构所有代码和文档中的项目名称,将OpenFang统一更新为ZCLAW。包括:
- 配置文件中的项目名称
- 代码注释和文档引用
- 环境变量和路径
- 类型定义和接口名称
- 测试用例和模拟数据

同时优化部分代码结构,移除未使用的模块,并更新相关依赖项。
2026-03-27 07:36:03 +08:00

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_zclaw/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 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