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

8.0 KiB
Raw Permalink Blame History

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 常量,消除类型断言

// 在组件外部定义(文件顶部 import 之后)
const DEFAULT_VALIDATION_STATE: ValidationState = 'valid';

第 121 行改为:

state: validationCache.get(path) ?? DEFAULT_VALIDATION_STATE,
  • Step 2: 环境变量展开加 20 并发上限

第 88-96 行,在 toExpand 之后加批次限制:

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 双重触发

问题:validationCacheexpandedCache 在依赖数组中,setState 后触发 effect 再次执行(空跑一轮)。

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

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

验证 effect 改为:

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 同理:

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
// 第 1 行
import { useState, useEffect, useMemo, useCallback, useRef } from 'react';
  • Step 5: 编译 + 测试
npx tsc --noEmit && npx vitest run
  • Step 6: Commit
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)

// 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 到文件顶部,消除重复字符串
// 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 行),更新路径引用:

// 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
cd src-tauri && cargo check && cargo clippy -- -D warnings
  • Step 4: Commit
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 行之后)加:

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

第 95 行改为:

const file = e.dataTransfer.files[i] as TauriFile;
if (file.path) useAppStore.getState().addPath(file.path, activeTab === 'user' ? TargetType.USER : TargetType.SYSTEM);
  • Step 2: 编译检查
npx tsc --noEmit
  • Step 3: Commit
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 行添加注释

// 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: 编译检查
npx tsc --noEmit
  • Step 3: Commit
git add src/store/app-store.ts
git commit -m "docs: undo/redo 添加注释说明为何内联 isModified 而非调用 markDirty()"

执行顺序

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

全部完成后运行完整验证:

npx tsc --noEmit && npx vitest run && cd src-tauri && cargo check && cargo clippy -- -D warnings