Files
PathEditor/docs/superpowers/plans/2026-05-26-v4.1-round2-cleanup-plan.md
T

295 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```