{
  "planVersion": "1.0.0",
  "generatedDate": "2026-06-03",
  "project": "MarkLite",
  "basedOnRequirements": "docs/optimization-requirements.json",

  "optimization_phases": [
    {
      "phase": 1,
      "name": "基础设施与代码规范",
      "priority": "P0-CRITICAL",
      "estimatedEffort": "1-2 天",
      "description": "建立代码质量基线：ESLint + Prettier + EditorConfig + ErrorBoundary + 路径别名修复。这些是后续所有优化的前提。",
      "items": ["CQ-01", "CQ-02", "CQ-03", "AR-05", "DX-07"],
      "rationale": "CQ-01 (ESLint) 是所有代码质量改进的基础；AR-05 (ErrorBoundary) 保护后续重构不会导致白屏；DX-07 (路径别名) 修复后简化后续 import。",
      "exitCriteria": [
        "npm run lint 通过且返回 0",
        "npm run format 能格式化全部源文件",
        "ErrorBoundary 包裹 App 组件",
        "TypeScript 路径别名在 vite config 中完整映射"
      ]
    },
    {
      "phase": 2,
      "name": "关键性能修复",
      "priority": "P0-CRITICAL",
      "estimatedEffort": "1-2 天",
      "description": "修复影响日常使用的核心性能瓶颈：Markdown 处理器缓存、Zustand 选择器重渲染、CodeMirror Compartment 主题切换、Preview 防抖、IndexedDB 去抖。",
      "items": ["PF-01", "PF-02", "PF-04", "PF-07", "PF-08"],
      "rationale": "PF-01 和 PF-02 是 P0-CRITICAL 级性能问题，直接影响用户感知；PF-04 的 Compartment 重构是 PF-03 (React.memo) 的前置；PF-07/08 是低风险高收益的防抖优化。",
      "exitCriteria": [
        "Markdown processor 实例复用率 > 90%",
        "编辑时仅 activeTab 相关组件重渲染（React DevTools Profiler 验证）",
        "darkMode 切换不销毁 EditorView",
        "Preview 渲染频率 < 10 次/秒（快速输入场景）",
        "IndexedDB 写入频率 < 3 次/秒（连续编辑场景）"
      ]
    },
    {
      "phase": 3,
      "name": "架构重构与代码质量",
      "priority": "P1-HIGH",
      "estimatedEffort": "2-3 天",
      "description": "组件拆分、TypeScript 类型完善、错误处理统一、消除架构反模式（DOM CustomEvent、重复设置加载）。",
      "items": ["AR-01", "AR-02", "AR-03", "AR-04", "CQ-04", "CQ-05", "PF-03"],
      "rationale": "组件拆分 (AR-01/02) 和 React.memo (PF-03) 依赖 Phase 1 的 ESLint 和 Phase 2 的 Zustand 优化；类型完善 (CQ-04) 和错误处理 (CQ-05) 可与架构重构并行。",
      "exitCriteria": [
        "App.tsx < 150 行",
        "Sidebar.tsx < 120 行",
        "所有函数参数有显式类型标注",
        "无空 catch 块，所有错误有具体描述",
        "设置加载只触发一次 IndexedDB 读取"
      ]
    },
    {
      "phase": 4,
      "name": "用户体验改进",
      "priority": "P1-HIGH",
      "estimatedEffort": "2-3 天",
      "description": "替换原生 confirm()、添加加载状态指示、改进 Toast 通知系统、增强可访问性。",
      "items": ["UX-01", "UX-02", "UX-05", "UX-07"],
      "rationale": "UX 改进依赖 Phase 3 的组件拆分（新的 ConfirmDialog、Toast 组件需要独立目录）和 ErrorBoundary（统一错误处理模式）。",
      "exitCriteria": [
        "零处原生 confirm() 调用",
        "文件打开、目录加载、Markdown 渲染有 loading 状态",
        "Toast 支持多条堆叠 + 类型区分 (success/error/warning)",
        "所有交互元素有 ARIA 属性"
      ]
    },
    {
      "phase": 5,
      "name": "开发体验与测试",
      "priority": "P2-MEDIUM",
      "estimatedEffort": "2-3 天",
      "description": "建立单元测试框架、强化类型安全、添加项目文档和 Git hooks。",
      "items": ["DX-01", "DX-02", "DX-04", "DX-05"],
      "rationale": "测试框架应在代码稳定后添加（Phase 1-4 的重构会导致测试频繁失效）；DX-02 (preload 类型安全) 依赖 CQ-04 (类型完善)。",
      "exitCriteria": [
        "Vitest + React Testing Library 可运行",
        "核心模块测试覆盖 > 60%",
        "README.md 包含完整开发指南",
        "pre-commit 钩子执行 ESLint + typecheck"
      ]
    },
    {
      "phase": 6,
      "name": "高级功能与安全加固",
      "priority": "P2-P3",
      "estimatedEffort": "3-5 天",
      "description": "代码分割、分屏模式、拖拽排序、文件搜索、安全加固 (CSP/file:// 协议)、CI/CD 配置。",
      "items": ["PF-06", "UX-03", "UX-04", "UX-06", "SE-01", "SE-02", "SE-03", "DX-06"],
      "rationale": "这些是「锦上添花」的优化，不阻塞核心功能。代码分割 (PF-06) 和分屏模式 (UX-03) 是大规模功能，需要在架构稳定后实施。",
      "exitCriteria": [
        "renderer bundle 拆分为 3+ 个 chunk",
        "分屏模式可正常编辑+预览",
        "CSP header 配置且预览正常渲染",
        "GitHub Actions CI 流水线 lint → typecheck → test → build"
      ]
    }
  ],

  "file_manifest": {
    "new_files": [
      {
        "path": "eslint.config.js",
        "phase": 1,
        "item": "CQ-01",
        "description": "ESLint v9 flat config 格式配置文件",
        "size_estimate": "small"
      },
      {
        "path": ".prettierrc",
        "phase": 1,
        "item": "CQ-02",
        "description": "Prettier 代码格式化配置",
        "size_estimate": "trivial"
      },
      {
        "path": ".prettierignore",
        "phase": 1,
        "item": "CQ-02",
        "description": "Prettier 忽略文件列表",
        "size_estimate": "trivial"
      },
      {
        "path": ".editorconfig",
        "phase": 1,
        "item": "CQ-03",
        "description": "编辑器统一行为配置",
        "size_estimate": "trivial"
      },
      {
        "path": "src/renderer/components/ErrorBoundary/ErrorBoundary.tsx",
        "phase": 1,
        "item": "AR-05",
        "description": "React ErrorBoundary 组件，显示错误信息和重启按钮",
        "size_estimate": "small"
      },
      {
        "path": "src/renderer/components/ErrorBoundary/index.ts",
        "phase": 1,
        "item": "AR-05",
        "description": "ErrorBoundary barrel export",
        "size_estimate": "trivial"
      },
      {
        "path": "src/renderer/components/AboutDialog/AboutDialog.tsx",
        "phase": 3,
        "item": "AR-01",
        "description": "从 App.tsx 提取的 About 对话框组件",
        "size_estimate": "medium"
      },
      {
        "path": "src/renderer/components/AboutDialog/index.ts",
        "phase": 3,
        "item": "AR-01",
        "description": "AboutDialog barrel export",
        "size_estimate": "trivial"
      },
      {
        "path": "src/renderer/hooks/useFileOperations.ts",
        "phase": 3,
        "item": "AR-01",
        "description": "从 App.tsx 提取的文件操作 hook（open/save/saveAs/openRecent）",
        "size_estimate": "medium"
      },
      {
        "path": "src/renderer/components/Sidebar/FileTree.tsx",
        "phase": 3,
        "item": "AR-02",
        "description": "从 Sidebar.tsx 提取的递归文件树组件",
        "size_estimate": "medium"
      },
      {
        "path": "src/renderer/hooks/useSidebarResize.ts",
        "phase": 3,
        "item": "AR-02",
        "description": "从 Sidebar.tsx 提取的 resize 逻辑 hook",
        "size_estimate": "small"
      },
      {
        "path": "src/renderer/components/ConfirmDialog/ConfirmDialog.tsx",
        "phase": 4,
        "item": "UX-01",
        "description": "自定义确认对话框组件，替代原生 confirm()",
        "size_estimate": "medium"
      },
      {
        "path": "src/renderer/components/ConfirmDialog/index.ts",
        "phase": 4,
        "item": "UX-01",
        "description": "ConfirmDialog barrel export",
        "size_estimate": "trivial"
      },
      {
        "path": "src/renderer/hooks/useConfirm.ts",
        "phase": 4,
        "item": "UX-01",
        "description": "确认对话框状态管理 hook",
        "size_estimate": "small"
      },
      {
        "path": "src/renderer/components/LoadingSpinner/LoadingSpinner.tsx",
        "phase": 4,
        "item": "UX-02",
        "description": "通用加载指示器组件",
        "size_estimate": "small"
      },
      {
        "path": "src/renderer/components/icons/toolbar-icons.tsx",
        "phase": 3,
        "item": "AR-06",
        "description": "工具栏相关 SVG 图标组件",
        "size_estimate": "small"
      },
      {
        "path": "src/renderer/components/icons/sidebar-icons.tsx",
        "phase": 3,
        "item": "AR-06",
        "description": "侧边栏相关 SVG 图标组件",
        "size_estimate": "small"
      },
      {
        "path": "src/renderer/components/icons/app-icons.tsx",
        "phase": 3,
        "item": "AR-06",
        "description": "应用级 SVG 图标组件",
        "size_estimate": "small"
      },
      {
        "path": "src/renderer/components/icons/index.ts",
        "phase": 3,
        "item": "AR-06",
        "description": "Icons barrel export（保持向后兼容）",
        "size_estimate": "trivial"
      },
      {
        "path": "src/renderer/lib/markdown-processor.ts",
        "phase": 2,
        "item": "PF-01",
        "description": "Markdown processor 缓存管理器",
        "size_estimate": "small"
      },
      {
        "path": "src/renderer/hooks/useSettingsInit.ts",
        "phase": 3,
        "item": "AR-03",
        "description": "统一设置加载 hook，一次 IndexedDB 读取分发到 store",
        "size_estimate": "small"
      },
      {
        "path": "vitest.config.ts",
        "phase": 5,
        "item": "DX-01",
        "description": "Vitest 测试框架配置",
        "size_estimate": "small"
      },
      {
        "path": "src/renderer/stores/__tests__/tabStore.test.ts",
        "phase": 5,
        "item": "DX-01",
        "description": "tabStore 单元测试",
        "size_estimate": "medium"
      },
      {
        "path": "src/renderer/lib/__tests__/fileUtils.test.ts",
        "phase": 5,
        "item": "DX-01",
        "description": "fileUtils 单元测试",
        "size_estimate": "small"
      },
      {
        "path": "src/renderer/lib/__tests__/markdown.test.ts",
        "phase": 5,
        "item": "DX-01",
        "description": "Markdown 渲染单元测试",
        "size_estimate": "medium"
      },
      {
        "path": "README.md",
        "phase": 5,
        "item": "DX-04",
        "description": "项目介绍、安装、开发、构建说明",
        "size_estimate": "medium"
      },
      {
        "path": "CONTRIBUTING.md",
        "phase": 5,
        "item": "DX-04",
        "description": "贡献指南、代码规范、PR 流程",
        "size_estimate": "small"
      },
      {
        "path": ".github/workflows/ci.yml",
        "phase": 6,
        "item": "DX-06",
        "description": "GitHub Actions CI 配置",
        "size_estimate": "small"
      }
    ],
    "modified_files": [
      {
        "path": "package.json",
        "phase": [1, 2, 5],
        "item": ["CQ-02", "DX-05", "DX-01"],
        "changes": [
          "添加 prettier、eslint-config-prettier 依赖",
          "添加 vitest、@testing-library/react、jsdom 依赖",
          "添加 husky、lint-staged 依赖",
          "添加 test、test:watch、test:coverage、format 脚本"
        ]
      },
      {
        "path": "electron.vite.config.ts",
        "phase": [1, 6],
        "item": ["DX-07", "PF-06"],
        "changes": [
          "renderer.resolve.alias 添加 @main 映射",
          "renderer.build.rollupOptions.output 添加 manualChunks 分割策略"
        ]
      },
      {
        "path": "src/renderer/main.tsx",
        "phase": 1,
        "item": "AR-05",
        "changes": [
          "用 ErrorBoundary 包裹 App 组件"
        ]
      },
      {
        "path": "src/renderer/App.tsx",
        "phase": [1, 3],
        "item": ["AR-01", "AR-05", "CQ-05"],
        "changes": [
          "提取 About 对话框到 AboutDialog 组件",
          "提取文件操作逻辑到 useFileOperations hook",
          "统一错误处理模式"
        ]
      },
      {
        "path": "src/renderer/lib/markdown.ts",
        "phase": 2,
        "item": "PF-01",
        "changes": [
          "引入 markdown-processor.ts 缓存管理器",
          "renderMarkdown 改为使用缓存的 processor 实例",
          "添加 processor 缓存失效策略（仅 filePath 变化时重建）"
        ]
      },
      {
        "path": "src/renderer/stores/tabStore.ts",
        "phase": [2, 3],
        "item": ["PF-02", "PF-08", "AR-07"],
        "changes": [
          "添加 getActiveTab() 选择器方法（返回 activeTab 对象引用）",
          "添加 getActiveTabId() 选择器方法",
          "将 setTimeout(saveToDB, 0) 替换为 debounce(saveToDB, 500)",
          "统一异步保存模式为 Zustand subscribeWithSelector middleware"
        ]
      },
      {
        "path": "src/renderer/components/Editor/Editor.tsx",
        "phase": 2,
        "item": "PF-02",
        "changes": [
          "替换 const tabs = useTabStore(s => s.tabs) 为 const activeTab = useTabStore(s => s.getActiveTab())",
          "消除 tabs.find() 调用"
        ]
      },
      {
        "path": "src/renderer/components/Preview/Preview.tsx",
        "phase": [2],
        "item": ["PF-02", "PF-07"],
        "changes": [
          "替换 tabs 订阅为 activeTab 选择器",
          "添加 150ms 防抖（useDebounce hook）",
          "使用新的 markdown processor 缓存"
        ]
      },
      {
        "path": "src/renderer/components/StatusBar/StatusBar.tsx",
        "phase": 2,
        "item": "PF-02",
        "changes": [
          "替换 tabs 订阅为 activeTab 选择器"
        ]
      },
      {
        "path": "src/renderer/components/Sidebar/Sidebar.tsx",
        "phase": [2, 3],
        "item": ["PF-02", "AR-02"],
        "changes": [
          "替换 tabs 订阅为 activeTab 选择器",
          "提取 FileTree 组件到独立文件",
          "提取 resize 逻辑到 useSidebarResize hook"
        ]
      },
      {
        "path": "src/renderer/components/Editor/useCodeMirror.ts",
        "phase": 2,
        "item": "PF-04",
        "changes": [
          "引入 Compartment API（@codemirror/state）",
          "theme 扩展放入 Compartment",
          "darkMode 变化时通过 compartment.reconfigure() 切换主题",
          "移除 EditorView 销毁重建逻辑"
        ]
      },
      {
        "path": "src/renderer/components/Toolbar/Toolbar.tsx",
        "phase": 3,
        "item": "PF-03",
        "changes": [
          "用 React.memo 包装组件导出"
        ]
      },
      {
        "path": "src/renderer/components/TabBar/TabBar.tsx",
        "phase": [3, 4],
        "item": ["PF-03", "UX-01"],
        "changes": [
          "用 React.memo 包装组件导出",
          "替换 4 处 confirm() 为自定义 ConfirmDialog"
        ]
      },
      {
        "path": "src/renderer/hooks/useUnsavedWarning.ts",
        "phase": 4,
        "item": "UX-01",
        "changes": [
          "替换 confirm() 为 electron.dialog.showMessageBox 或自定义 ConfirmDialog"
        ]
      },
      {
        "path": "src/renderer/hooks/useTheme.ts",
        "phase": 3,
        "item": "AR-03",
        "changes": [
          "移除独立的 settingsRepository.load() 调用",
          "改为从统一的 useSettingsInit hook 获取设置"
        ]
      },
      {
        "path": "src/renderer/hooks/useSettings.ts",
        "phase": 3,
        "item": "AR-03",
        "changes": [
          "移除独立的 settingsRepository.load() 调用",
          "改为从统一的 useSettingsInit hook 获取设置"
        ]
      },
      {
        "path": "src/renderer/hooks/useFileWatch.ts",
        "phase": 3,
        "item": "AR-04",
        "changes": [
          "移除 window.dispatchEvent(CustomEvent) 调用",
          "改为调用 Zustand store action 更新文件状态"
        ]
      },
      {
        "path": "src/renderer/components/Icons.tsx",
        "phase": 3,
        "item": "AR-06",
        "changes": [
          "拆分为 icons/toolbar-icons.tsx、icons/sidebar-icons.tsx、icons/app-icons.tsx",
          "原文件改为 re-export barrel 文件（向后兼容）"
        ]
      },
      {
        "path": "src/renderer/components/Toast/Toast.tsx",
        "phase": 4,
        "item": "UX-05",
        "changes": [
          "支持多条堆叠 Toast",
          "添加 success/error/warning 类型",
          "添加手动关闭按钮"
        ]
      },
      {
        "path": "src/renderer/styles/global.css",
        "phase": 4,
        "item": ["UX-01", "UX-02", "UX-05"],
        "changes": [
          "添加 ConfirmDialog 样式",
          "添加 LoadingSpinner 样式",
          "添加 Toast 堆叠和类型样式"
        ]
      },
      {
        "path": "src/preload/index.ts",
        "phase": 5,
        "item": "DX-02",
        "changes": [
          "添加 ElectronAPI 类型约束: const api: ElectronAPI = { ... }",
          "确保 contextBridge.exposeInMainWorld 返回值类型匹配"
        ]
      },
      {
        "path": "src/renderer/types/settings.ts",
        "phase": 6,
        "item": "UX-03",
        "changes": [
          "ViewMode 类型扩展为 'editor' | 'preview' | 'split'"
        ]
      },
      {
        "path": "src/renderer/index.html",
        "phase": 6,
        "item": "SE-01",
        "changes": [
          "添加 CSP meta tag 或在 main process BrowserWindow 中配置 CSP header"
        ]
      },
      {
        "path": "src/main/window-manager.ts",
        "phase": 6,
        "item": "SE-01",
        "changes": [
          "在 BrowserWindow webPreferences 中配置 CSP header",
          "确认 nodeIntegration=false, contextIsolation=true, sandbox=true"
        ]
      },
      {
        "path": "src/renderer/lib/markdown.ts",
        "phase": 6,
        "item": "SE-02",
        "changes": [
          "rehypeFixImages 中的路径校验从简单 '..' 检查改为 path.resolve + allowlist 检查"
        ]
      }
    ],
    "deleted_files": []
  },

  "technical_solutions": {
    "CQ-01_eslint_config": {
      "id": "CQ-01",
      "title": "ESLint v9 Flat Config",
      "solution": "创建 eslint.config.js，使用 ESM flat config 格式。",
      "implementation": {
        "file": "eslint.config.js",
        "format": "ESM (export default [...])",
        "config_content": {
          "imports": [
            "eslint-plugin-react-hooks",
            "eslint-plugin-react-refresh",
            "@typescript-eslint/eslint-plugin"
          ],
          "ignores": ["dist/**", "node_modules/**", "out/**"],
          "languageOptions": {
            "parser": "@typescript-eslint/parser",
            "parserOptions": {
              "ecmaVersion": "latest",
              "sourceType": "module",
              "ecmaFeatures": { "jsx": true }
            }
          },
          "rules": {
            "@typescript-eslint/no-unused-vars": "error",
            "no-console": "warn",
            "prefer-const": "error",
            "react-hooks/rules-of-hooks": "error",
            "react-hooks/exhaustive-deps": "warn",
            "react-refresh/only-export-components": ["warn", { "allowConstantExport": true }]
          }
        },
        "notes": "ESLint v9 不再支持 --ext 参数，flat config 中通过 files glob 控制。需更新 package.json 的 lint 脚本。"
      },
      "dependencies": [],
      "risk": "LOW"
    },

    "PF-01_markdown_cache": {
      "id": "PF-01",
      "title": "Markdown Processor 缓存",
      "solution": "将 unified processor 实例提取到独立模块，使用 Map 按 filePath 缓存。",
      "implementation": {
        "new_file": "src/renderer/lib/markdown-processor.ts",
        "approach": "Module-level singleton cache",
        "cache_strategy": {
          "key": "filePath (string)",
          "value": "Processor instance (unified processor with all 7 plugins)",
          "invalidation": "filePath 变化时创建新 processor，旧实例保留在 cache 中供回退",
          "max_cache_size": 10,
          "eviction": "LRU - 超过 max_cache_size 时移除最久未使用的"
        },
        "code_sketch": "const processorCache = new Map<string, Processor>();\nexport function getProcessor(filePath: string): Processor {\n  if (processorCache.has(filePath)) return processorCache.get(filePath)!;\n  const p = unified().use(remarkParse)...;\n  processorCache.set(filePath, p);\n  return p;\n}"
      },
      "dependencies": [],
      "risk": "LOW - unified processor 是无状态的，缓存安全"
    },

    "PF-02_zustand_selectors": {
      "id": "PF-02",
      "title": "Zustand 选择器优化",
      "solution": "在 tabStore 中添加 getActiveTab 派生选择器，各组件只订阅 activeTab 引用。",
      "implementation": {
        "approach": "在 store 中添加 getter 方法 + useShallow",
        "store_changes": {
          "new_methods": [
            "getActiveTab(): Tab | undefined — 从 state.tabs 和 state.activeTabId 派生",
            "getActiveTabId(): string | null — 直接返回 state.activeTabId"
          ],
          "notes": "Zustand 5 中 useShallow 来自 'zustand/react/shallow'"
        },
        "component_changes": {
          "before": "const tabs = useTabStore(s => s.tabs);\nconst activeTab = tabs.find(t => t.id === activeTabId);",
          "after": "const activeTab = useTabStore(s => s.tabs.find(t => t.id === s.activeTabId));",
          "note": "直接在选择器中 find，当 activeTab 的引用未变时不会触发重渲染。或使用 useShallow + getActiveTab 方法。"
        },
        "affected_files": [
          "src/renderer/components/Editor/Editor.tsx",
          "src/renderer/components/Preview/Preview.tsx",
          "src/renderer/components/StatusBar/StatusBar.tsx",
          "src/renderer/components/Sidebar/Sidebar.tsx"
        ]
      },
      "dependencies": ["PF-01"],
      "risk": "MEDIUM - 需验证所有订阅点，遗漏会导致功能异常"
    },

    "PF-04_codemirror_compartment": {
      "id": "PF-04",
      "title": "CodeMirror Compartment 主题切换",
      "solution": "使用 @codemirror/state 的 Compartment API 封装主题扩展，darkMode 变化时 reconfigure 而非销毁重建。",
      "implementation": {
        "file": "src/renderer/components/Editor/useCodeMirror.ts",
        "approach": "Compartment + reconfigure",
        "code_sketch": "import { Compartment } from '@codemirror/state';\nconst themeCompartment = new Compartment();\n\n// 创建时:\nconst extensions = [\n  // ... other extensions\n  themeCompartment.of(darkMode ? oneDark : [])\n];\n\n// darkMode 变化时:\nuseEffect(() => {\n  if (editorView) {\n    editorView.dispatch({\n      effects: themeCompartment.reconfigure(darkMode ? oneDark : [])\n    });\n  }\n}, [darkMode]);",
        "key_changes": [
          "移除 useEffect 中 EditorView 销毁重建逻辑",
          "theme 扩展改为 Compartment.of() 包装",
          "darkMode 变化改为 dispatch reconfigure effect",
          "保留其他扩展不变（keymap、language 等）"
        ]
      },
      "dependencies": [],
      "risk": "HIGH - 影响编辑器核心初始化流程，需仔细测试切换前后编辑器状态保留"
    },

    "PF-07_preview_debounce": {
      "id": "PF-07",
      "title": "Preview 渲染防抖",
      "solution": "创建 useDebounce hook，在 Preview.tsx 中对 content 变化进行 150ms 防抖。",
      "implementation": {
        "new_file": "src/renderer/hooks/useDebounce.ts",
        "approach": "标准 useDebounce hook + useEffect",
        "code_sketch": "function useDebounce<T>(value: T, delay: number): T {\n  const [debouncedValue, setDebouncedValue] = useState(value);\n  useEffect(() => {\n    const timer = setTimeout(() => setDebouncedValue(value), delay);\n    return () => clearTimeout(timer);\n  }, [value, delay]);\n  return debouncedValue;\n}",
        "usage_in_preview": "const content = activeTab?.content ?? '';\nconst debouncedContent = useDebounce(content, 150);\nuseEffect(() => {\n  // renderMarkdown(debouncedContent)\n}, [debouncedContent]);"
      },
      "dependencies": ["PF-01", "PF-02"],
      "risk": "LOW"
    },

    "PF-08_indexeddb_debounce": {
      "id": "PF-08",
      "title": "IndexedDB 保存去抖",
      "solution": "将 tabStore 中的 setTimeout(saveToDB, 0) 替换为 debounce(saveToDB, 500)。",
      "implementation": {
        "file": "src/renderer/stores/tabStore.ts",
        "approach": "引入 debounce 工具函数",
        "code_sketch": "// utils/debounce.ts\nexport function debounce<T extends (...args: any[]) => any>(fn: T, ms: number): T {\n  let timer: ReturnType<typeof setTimeout>;\n  return ((...args: any[]) => {\n    clearTimeout(timer);\n    timer = setTimeout(() => fn(...args), ms);\n  }) as T;\n}\n\n// tabStore.ts\nconst debouncedSaveToDB = debounce(() => saveToDB(), 500);\nconst debouncedSaveActiveTab = debounce((id: string) => tabRepository.saveActiveTabId(id), 300);",
        "affected_patterns": [
          "setTimeout(() => saveToDB(), 0) → debouncedSaveToDB()",
          "setTimeout(() => tabRepository.saveActiveTabId(activeTabId), 0) → debouncedSaveActiveTab(activeTabId)"
        ]
      },
      "dependencies": [],
      "risk": "LOW - 去抖延迟可配置，用户关闭窗口前需 flush"
    },

    "AR-05_error_boundary": {
      "id": "AR-05",
      "title": "React ErrorBoundary",
      "solution": "创建 class component ErrorBoundary，捕获渲染错误，显示友好错误页面。",
      "implementation": {
        "file": "src/renderer/components/ErrorBoundary/ErrorBoundary.tsx",
        "features": [
          "componentDidCatch + getDerivedStateFromError",
          "显示错误信息、错误栈（开发模式）",
          "「重新加载」按钮调用 location.reload() 或 window.electronAPI 重启",
          "「复制错误信息」按钮",
          "支持 fallback 自定义 UI"
        ],
        "integration": "在 main.tsx 中 <ErrorBoundary><App /></ErrorBoundary>"
      },
      "dependencies": [],
      "risk": "LOW"
    },

    "AR-01_app_split": {
      "id": "AR-01",
      "title": "App.tsx 拆分",
      "solution": "提取 AboutDialog 组件和 useFileOperations hook。",
      "implementation": {
        "extractions": [
          {
            "name": "AboutDialog",
            "source": "App.tsx 内联 JSX (~40 行)",
            "target": "src/renderer/components/AboutDialog/AboutDialog.tsx",
            "props": "{ visible: boolean; onClose: () => void; version: string }"
          },
          {
            "name": "useFileOperations",
            "source": "App.tsx 中的 handleOpenFile, handleSave, handleSaveAs, handleOpenRecent 函数",
            "target": "src/renderer/hooks/useFileOperations.ts",
            "returns": "{ handleOpenFile, handleSave, handleSaveAs, handleOpenRecent }"
          }
        ],
        "expected_result": "App.tsx 从 ~307 行减少到 ~120 行"
      },
      "dependencies": ["CQ-01", "AR-05"],
      "risk": "LOW - 提取式重构，行为不变"
    },

    "AR-03_unified_settings": {
      "id": "AR-03",
      "title": "统一设置加载",
      "solution": "创建 useSettingsInit hook，一次性从 IndexedDB 加载所有设置，分发到各 store。",
      "implementation": {
        "new_file": "src/renderer/hooks/useSettingsInit.ts",
        "approach": "单次加载 + Zustand setState 分发",
        "code_sketch": "export function useSettingsInit() {\n  const [loaded, setLoaded] = useState(false);\n  useEffect(() => {\n    settingsRepository.load().then(settings => {\n      useThemeStore.getState().applySettings(settings);\n      useEditorStore.getState().applySettings(settings);\n      setLoaded(true);\n    });\n  }, []);\n  return loaded;\n}",
        "affected_files": [
          "src/renderer/hooks/useTheme.ts — 移除独立 load()",
          "src/renderer/hooks/useSettings.ts — 移除独立 load()"
        ]
      },
      "dependencies": [],
      "risk": "MEDIUM - 设置加载时序变化可能影响首次渲染"
    },

    "UX-01_confirm_dialog": {
      "id": "UX-01",
      "title": "自定义确认对话框",
      "solution": "创建 ConfirmDialog 组件 + useConfirm hook，替代原生 confirm()。",
      "implementation": {
        "approach": "Promise-based confirm API",
        "code_sketch": "// useConfirm.ts\nexport function useConfirm() {\n  const [state, setState] = useState<{ message: string; resolve: (v: boolean) => void } | null>(null);\n  const confirm = (message: string) => new Promise<boolean>(resolve => setState({ message, resolve }));\n  const handleConfirm = () => { state?.resolve(true); setState(null); };\n  const handleCancel = () => { state?.resolve(false); setState(null); };\n  return { confirm, dialog: state ? <ConfirmDialog .../> : null };\n}",
        "affected_locations": [
          "TabBar.tsx — 4 处 confirm() 调用",
          "useUnsavedWarning.ts — 1 处 confirm() 调用"
        ]
      },
      "dependencies": ["AR-01"],
      "risk": "MEDIUM - Promise-based API 与原生 confirm() 的同步行为不同，需确保调用方正确 await"
    },

    "UX-03_split_mode": {
      "id": "UX-03",
      "title": "分屏模式",
      "solution": "扩展 ViewMode 为 'split'，布局组件根据 mode 同时渲染 Editor 和 Preview。",
      "implementation": {
        "approach": "CSS Grid/ResizeObserver 双栏布局",
        "key_changes": [
          "types/settings.ts: ViewMode = 'editor' | 'preview' | 'split'",
          "App.tsx 或新 Layout 组件: 根据 viewMode 条件渲染",
          "split 模式: 左 50% Editor + 右 50% Preview，可拖拽调整比例",
          "添加 Ctrl+3 快捷键切换到 split 模式",
          "可选：编辑器滚动同步预览滚动位置"
        ]
      },
      "dependencies": ["PF-01", "PF-02", "PF-04"],
      "risk": "HIGH - 需要同时维护两个视图实例，内存和性能需关注"
    },

    "PF-06_code_splitting": {
      "id": "PF-06",
      "title": "代码分割",
      "solution": "在 electron.vite.config.ts 中配置 Rollup manualChunks，按依赖类型分割。",
      "implementation": {
        "file": "electron.vite.config.ts",
        "approach": "manualChunks + dynamic import",
        "chunks_config": {
          "vendor-react": ["react", "react-dom"],
          "vendor-codemirror": ["@codemirror/state", "@codemirror/view", "@codemirror/commands", "@codemirror/lang-markdown", "@codemirror/language", "@codemirror/search", "@codemirror/theme-one-dark"],
          "vendor-markdown": ["unified", "remark-parse", "remark-gfm", "remark-rehype", "rehype-raw", "rehype-sanitize", "rehype-stringify", "rehype-highlight"],
          "vendor-db": ["dexie"]
        },
        "dynamic_import_targets": [
          "rehype-highlight (仅 Preview 组件需要)"
        ]
      },
      "dependencies": [],
      "risk": "MEDIUM - 代码分割后需验证 Electron 打包正确性和加载时序"
    },

    "SE-01_csp": {
      "id": "SE-01",
      "title": "CSP 配置",
      "solution": "在 main process 的 BrowserWindow 中通过 session.defaultSession.webRequest 配置 CSP header。",
      "implementation": {
        "file": "src/main/window-manager.ts",
        "approach": "session.webRequest.onHeadersReceived",
        "csp_policy": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' file: data:; font-src 'self' data:;",
        "notes": "style-src 需要 'unsafe-inline' 因为 CodeMirror 通过 JS 注入内联样式。rehype-highlight 引入 highlight.js 样式也需要考虑。"
      },
      "dependencies": ["PF-04"],
      "risk": "MEDIUM - CSP 可能阻断 CodeMirror 样式注入，需测试"
    }
  },

  "dependency_graph": {
    "description": "优化项之间的依赖关系。箭头表示 'A -> B' 意味着 B 依赖 A（A 需先完成）。",
    "edges": [
      { "from": "CQ-01", "to": "CQ-02", "reason": "Prettier 需与 ESLint 集成 (eslint-config-prettier)" },
      { "from": "CQ-01", "to": "AR-01", "reason": "组件拆分前需确保代码质量基线" },
      { "from": "CQ-01", "to": "AR-02", "reason": "组件拆分前需确保代码质量基线" },
      { "from": "CQ-01", "to": "PF-03", "reason": "React.memo 添加前需 ESLint 规则配合" },
      { "from": "CQ-01", "to": "DX-05", "reason": "lint-staged 依赖 ESLint 配置" },
      { "from": "CQ-01", "to": "DX-01", "reason": "测试代码也需遵循 ESLint 规则" },
      { "from": "CQ-04", "to": "DX-02", "reason": "preload 类型安全依赖类型完善" },
      { "from": "PF-01", "to": "PF-02", "reason": "Zustand 选择器优化涉及 Preview 组件，需配合 Markdown 缓存" },
      { "from": "PF-01", "to": "PF-07", "reason": "Preview 防抖依赖渲染管道已优化" },
      { "from": "PF-02", "to": "PF-07", "reason": "Preview 防抖依赖 activeTab 选择器" },
      { "from": "PF-02", "to": "PF-03", "reason": "React.memo 效果依赖选择器优化减少不必要更新" },
      { "from": "AR-01", "to": "UX-01", "reason": "ConfirmDialog 依赖组件拆分后的目录结构" },
      { "from": "AR-01", "to": "UX-02", "reason": "LoadingSpinner 依赖组件拆分后的目录结构" },
      { "from": "PF-01", "to": "UX-03", "reason": "分屏模式的预览面板依赖 Markdown 缓存" },
      { "from": "PF-02", "to": "UX-03", "reason": "分屏模式需要高效的 store 订阅" },
      { "from": "PF-04", "to": "UX-03", "reason": "分屏模式的编辑器依赖 Compartment 动态配置" },
      { "from": "PF-04", "to": "SE-01", "reason": "CSP 配置需在 Compartment 稳定后进行，避免相互影响" },
      { "from": "AR-05", "to": "AR-01", "reason": "ErrorBoundary 应在 App.tsx 拆分前完成，保护重构过程" }
    ],
    "parallelizable": [
      ["CQ-01", "AR-05", "DX-07"],
      ["PF-01", "PF-04", "PF-08"],
      ["CQ-04", "CQ-05", "AR-03", "AR-04"],
      ["UX-01", "UX-02", "UX-05"],
      ["DX-01", "DX-04"]
    ]
  },

  "risk_assessment": {
    "high_risk": [
      {
        "id": "PF-04",
        "title": "CodeMirror Compartment 重构",
        "risk_level": "HIGH",
        "description": "影响编辑器核心初始化流程。Compartment reconfigure 如果处理不当，可能导致编辑器状态丢失（光标位置、选区、undo 历史）。",
        "impact": "编辑器完全不可用",
        "probability": "MEDIUM",
        "mitigation": [
          "保留现有销毁重建逻辑作为 fallback（feature flag 控制）",
          "编写测试验证切换主题后光标位置和 undo 历史保留",
          "逐步迁移：先用 Compartment 切换主题，验证稳定后再扩展到其他扩展"
        ],
        "rollback": "revert useCodeMirror.ts 到使用前的版本"
      },
      {
        "id": "UX-03",
        "title": "分屏模式",
        "risk_level": "HIGH",
        "description": "需要同时维护 Editor 和 Preview 两个实例，可能增加内存占用和 CPU 消耗。滚动同步逻辑复杂。",
        "impact": "性能下降、内存溢出（大文件场景）",
        "probability": "MEDIUM",
        "mitigation": [
          "Preview 面板使用延迟加载（只在 split 模式激活时挂载）",
          "大文件场景下限制预览刷新频率（500ms debounce）",
          "考虑虚拟化预览（只渲染可视区域）"
        ],
        "rollback": "ViewMode 从 'editor' | 'preview' | 'split' 移除 'split' 值"
      }
    ],
    "medium_risk": [
      {
        "id": "PF-02",
        "title": "Zustand 选择器重构",
        "risk_level": "MEDIUM",
        "description": "选择器重构可能遗漏某些订阅点，导致某些组件在 activeTab 变化时不再更新。",
        "impact": "编辑器/预览/状态栏内容不同步",
        "probability": "LOW",
        "mitigation": [
          "使用 React DevTools Profiler 逐一验证每个受影响组件的重渲染行为",
          "逐个文件修改，每修改一个文件后手动测试",
          "编写 tabStore 选择器的单元测试"
        ],
        "rollback": "逐文件恢复原始订阅方式"
      },
      {
        "id": "SE-01",
        "title": "CSP 配置",
        "risk_level": "MEDIUM",
        "description": "CSP 配置过于严格可能阻断 CodeMirror 内联样式注入或 rehype-highlight 的样式。",
        "impact": "编辑器或预览样式丢失",
        "probability": "LOW",
        "mitigation": [
          "使用 'unsafe-inline' 允许 style-src（CodeMirror 依赖）",
          "分步启用 CSP 先 report-only 模式测试",
          "测试所有主题切换和 Markdown 语法高亮场景"
        ],
        "rollback": "移除 CSP header 配置"
      },
      {
        "id": "AR-03",
        "title": "统一设置加载",
        "risk_level": "MEDIUM",
        "description": "将两次 IndexedDB 读取合并为一次可能改变设置加载时序，导致首次渲染闪烁或主题闪烁。",
        "impact": "首次加载时短暂显示默认主题再切换到用户主题",
        "probability": "LOW",
        "mitigation": [
          "在设置加载完成前显示 loading 状态（不渲染 App）",
          "使用 init() 方式在 main.tsx 中等待设置加载完成后再渲染"
        ],
        "rollback": "恢复 useTheme 和 useSettings 各自独立加载"
      },
      {
        "id": "PF-08",
        "title": "IndexedDB 去抖",
        "risk_level": "MEDIUM",
        "description": "去抖延迟期间如果 Electron 进程被杀或用户关闭窗口，可能丢失最后一次保存。",
        "impact": "用户数据丢失（最后一次编辑）",
        "probability": "LOW",
        "mitigation": [
          "在 beforeunload / window close 事件中 flush debounce",
          "使用 navigator.sendBeacon 或 Electron app.on('before-quit') 事件触发同步保存",
          "记录 dirty flag，关闭前检查"
        ],
        "rollback": "恢复 setTimeout(fn, 0) 立即保存"
      }
    ],
    "low_risk": [
      {
        "id": "CQ-01",
        "title": "ESLint 配置",
        "risk_level": "LOW",
        "description": "纯增量添加，不影响运行时行为。",
        "mitigation": ["确保 .eslintignore 排除构建产物"]
      },
      {
        "id": "PF-01",
        "title": "Markdown 处理器缓存",
        "risk_level": "LOW",
        "description": "unified processor 是无状态管道，缓存复用安全。",
        "mitigation": ["设置 max cache size 防止内存泄漏"]
      },
      {
        "id": "PF-07",
        "title": "Preview 防抖",
        "risk_level": "LOW",
        "description": "防抖只是延迟渲染，不影响最终结果。",
        "mitigation": ["防抖时间可配置，默认 150ms"]
      },
      {
        "id": "AR-05",
        "title": "ErrorBoundary",
        "risk_level": "LOW",
        "description": "纯增量添加，只在出错时生效。",
        "mitigation": ["测试注入错误验证 ErrorBoundary 正常捕获"]
      }
    ]
  },

  "summary": {
    "total_optimization_items": 35,
    "total_new_files": 28,
    "total_modified_files": 22,
    "total_phases": 6,
    "estimated_total_effort": "12-18 天",
    "critical_path": "CQ-01 → AR-05 → AR-01 → UX-01 → DX-05",
    "parallel_speedup_potential": "约 30-40%（Phase 1 和 Phase 2 可部分并行）",
    "highest_impact_items": ["PF-02", "PF-01", "PF-04", "AR-05"],
    "recommended_execution_order": [
      "Phase 1 (Day 1-2): ESLint + Prettier + ErrorBoundary — 建立基线",
      "Phase 2 (Day 3-4): 性能修复 — 解决核心瓶颈",
      "Phase 3 (Day 5-7): 架构重构 — 提升可维护性",
      "Phase 4 (Day 8-10): UX 改进 — 提升用户体验",
      "Phase 5 (Day 11-13): 测试 + 文档 — 建立质量保障",
      "Phase 6 (Day 14-18): 高级功能 — 锦上添花"
    ]
  }
}
