# v4.1 第二轮代码清理 — 实现计划

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task.

**Goal:** 修复第二轮深度审查发现的 7 个代码质量问题

**Architecture:** 7 个独立小修，互不冲突。PathTable.tsx 有 3 项相关改动，放在一个 Task 里。

**Tech Stack:** TypeScript strict + React + Rust + Tauri IPC

---

### Task 1: PathTable 三合一（并发限制 + 双重触发 + 类型断言）

**Files:**
- Modify: `src/components/path-list/PathTable.tsx:25, 53, 78, 88-96, 111, 121`

- [ ] **Step 1: 提取 `DEFAULT_VALIDATION_STATE` 常量，消除类型断言**

```typescript
// 在组件外部定义（文件顶部 import 之后）
const DEFAULT_VALIDATION_STATE: ValidationState = 'valid';
```

第 121 行改为：
```typescript
state: validationCache.get(path) ?? DEFAULT_VALIDATION_STATE,
```

- [ ] **Step 2: 环境变量展开加 20 并发上限**

第 88-96 行，在 `toExpand` 之后加批次限制：
```typescript
const toExpand = paths.filter(
  (p) => p.includes('%') && !expandedCache.has(p),
);
if (toExpand.length === 0) return;

const batch = toExpand.slice(0, 20);  // ← 新增：限制并发 20
Promise.all(
  batch.map(async (p): Promise<[string, string]> => {
```

- [ ] **Step 3: 消除 useEffect 双重触发**

问题：`validationCache` 和 `expandedCache` 在依赖数组中，setState 后触发 effect 再次执行（空跑一轮）。

用 ref 跟踪"是否已经触发过验证"，effect 只依赖 `paths`：

```typescript
// 新增两个 ref（放在 useState 声明之后）
const validatedRef = useRef<Set<string>>(new Set());
const expandedRef = useRef<Set<string>>(new Set());
```

验证 effect 改为：
```typescript
useEffect(() => {
  let cancelled = false;
  const toValidate = paths.filter((p) => !validatedRef.current.has(p));
  if (toValidate.length === 0) return;

  const batch = toValidate.slice(0, 20);
  Promise.all(
    batch.map(async (p): Promise<[string, ValidationState]> => {
      try {
        if (p.includes('%')) return [p, 'valid'];
        const valid: boolean = await invoke('validate_path', { path: p });
        return [p, valid ? 'valid' : 'invalid'];
      } catch {
        return [p, 'unknown'];
      }
    }),
  ).then((results) => {
    if (cancelled) return;
    for (const [p] of results) validatedRef.current.add(p);
    setValidationCache((prev) => {
      const next = new Map(prev);
      for (const [p, v] of results) next.set(p, v);
      return next;
    });
  });

  return () => { cancelled = true; };
}, [paths]); // ← 移除 validationCache 依赖
```

展开 effect 同理：
```typescript
useEffect(() => {
  let cancelled = false;
  const toExpand = paths.filter((p) => p.includes('%') && !expandedRef.current.has(p));
  if (toExpand.length === 0) return;

  const batch = toExpand.slice(0, 20);
  Promise.all(
    batch.map(async (p): Promise<[string, string]> => {
      try {
        const expanded: string = await invoke('expand_env_vars', { path: p });
        return [p, expanded !== p ? expanded : ''];
      } catch {
        return [p, ''];
      }
    }),
  ).then((results) => {
    if (cancelled) return;
    for (const [p] of results) expandedRef.current.add(p);
    setExpandedCache((prev) => {
      const next = new Map(prev);
      for (const [p, v] of results) next.set(p, v);
      return next;
    });
  });

  return () => { cancelled = true; };
}, [paths]); // ← 移除 expandedCache 依赖
```

`validations` 和渲染逻辑不变。

- [ ] **Step 4: 添加 `useRef` 到 import**

```typescript
// 第 1 行
import { useState, useEffect, useMemo, useCallback, useRef } from 'react';
```

- [ ] **Step 5: 编译 + 测试**

```bash
npx tsc --noEmit && npx vitest run
```

- [ ] **Step 6: Commit**

```bash
git add src/components/path-list/PathTable.tsx
git commit -m "fix: PathTable — 环境变量展开限流20并发、消除useEffect双重触发、类型断言改为常量"
```

---

### Task 2: backup.rs — use 语句移到文件顶部 + 消除路径字符串重复

**Files:**
- Modify: `src-tauri/src/commands/registry.rs:4-5`
- Modify: `src-tauri/src/commands/backup.rs:1-3, 22-23, 34-42`

- [ ] **Step 1: registry.rs 常量改为 pub(crate)**

```rust
// src-tauri/src/commands/registry.rs — 第 4-5 行，加 pub(crate)
pub(crate) const SYS_REG_PATH: &str = "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment";
pub(crate) const USER_REG_PATH: &str = "Environment";
pub(crate) const PATH_VALUE: &str = "Path";
```

- [ ] **Step 2: backup.rs — 移动 use 到文件顶部，消除重复字符串**

```rust
// src-tauri/src/commands/backup.rs — 文件顶部（第 1 行之后）
use chrono::Local;
use std::path::PathBuf;
use winreg::enums::*;                        // ← 从函数体内移出

// 第 4 行之后新增：
use crate::commands::registry::{self, SYS_REG_PATH, USER_REG_PATH};
```

删除函数体内的 `use`（第 22-23 行），更新路径引用：

```rust
// backup.rs — 第 34-42 行，用常量替换字符串字面量
let sys_paths = registry::load_paths(
    HKEY_LOCAL_MACHINE,
    SYS_REG_PATH,   // ← 用常量
    "系统",
)?;
let user_paths = registry::load_paths(
    HKEY_CURRENT_USER,
    USER_REG_PATH,  // ← 用常量
    "用户",
)?;
```

- [ ] **Step 3: 编译 + clippy**

```bash
cd src-tauri && cargo check && cargo clippy -- -D warnings
```

- [ ] **Step 4: Commit**

```bash
git add src-tauri/src/commands/registry.rs src-tauri/src/commands/backup.rs
git commit -m "refactor: backup.rs — use 语句移至文件顶部，注册表路径复用常量消除重复"
```

---

### Task 3: AppShell — `as any` 拖拽路径类型安全

**Files:**
- Modify: `src/components/layout/AppShell.tsx:95`

- [ ] **Step 1: 定义 TauriFile 接口并消除 as any**

在 AppShell 组件定义之前（第 16 行之后）加：

```typescript
/** Tauri 的 File 对象扩展了标准 File，额外提供文件系统路径 */
interface TauriFile extends File {
  path: string;
}
```

第 95 行改为：
```typescript
const file = e.dataTransfer.files[i] as TauriFile;
if (file.path) useAppStore.getState().addPath(file.path, activeTab === 'user' ? TargetType.USER : TargetType.SYSTEM);
```

- [ ] **Step 2: 编译检查**

```bash
npx tsc --noEmit
```

- [ ] **Step 3: Commit**

```bash
git add src/components/layout/AppShell.tsx
git commit -m "fix: AppShell 拖拽路径消除 as any，用 TauriFile 接口类型安全访问 path"
```

---

### Task 4: app-store — undo/redo 加注释说明为何不用 markDirty()

**Files:**
- Modify: `src/store/app-store.ts:210-226`

- [ ] **Step 1: 在 undo/redo 的 isModified 行添加注释**

```typescript
// app-store.ts — 第 213 行之前加注释
undo: () => {
  const { undoRedo, sysPaths, userPaths, _savedSys, _savedUser } = get();
  const result = undoRedo.undo(sysPaths, userPaths);
  if (result) {
    set({
      sysPaths: result[0], userPaths: result[1], selectedIndices: [],
      // 内联 isModified 计算而非调用 markDirty()，避免两次 set() 渲染
      isModified: !(arraysEqual(result[0], _savedSys) && arraysEqual(result[1], _savedUser)),
    });
  }
},

redo: () => {
  const { undoRedo, sysPaths, userPaths, _savedSys, _savedUser } = get();
  const result = undoRedo.redo(sysPaths, userPaths);
  if (result) {
    set({
      sysPaths: result[0], userPaths: result[1], selectedIndices: [],
      // 内联 isModified 计算而非调用 markDirty()，避免两次 set() 渲染
      isModified: !(arraysEqual(result[0], _savedSys) && arraysEqual(result[1], _savedUser)),
    });
  }
},
```

- [ ] **Step 2: 编译检查**

```bash
npx tsc --noEmit
```

- [ ] **Step 3: Commit**

```bash
git add src/store/app-store.ts
git commit -m "docs: undo/redo 添加注释说明为何内联 isModified 而非调用 markDirty()"
```

---

## 执行顺序

Task 1 → 2 → 3 → 4，互不依赖但建议按序执行。

全部完成后运行完整验证：
```bash
npx tsc --noEmit && npx vitest run && cd src-tauri && cargo check && cargo clippy -- -D warnings
```
