{
  "review_date": "2026-07-06",
  "reviewer": "CatPaw (苏浅玥) — Follow-up Audit & Fix",
  "project": "MarkLite",
  "overall_score": 96,
  "verdict": "pass",
  "previous_review": {
    "review_date": "2026-06-03",
    "overall_score": 82,
    "verdict": "needs-fix"
  },
  "score": {
    "code_quality": {
      "score": 95,
      "weight": 0.25,
      "details": {
        "typescript": {
          "status": "pass",
          "errors": 0,
          "fixes_applied": [
            "useMilkdown.ts:73 — Replaced state.selection.constructor.create with TextSelection.create from @milkdown/prose/state",
            "useMilkdown.ts:42-56 — Fixed dead-code bug: Backspace handler was unreachable because PAIRS['Backspace'] check returned false first",
            "package.json — Pinned all @milkdown/* packages to exact 7.21.1, added 5 phantom dependency plugins as direct deps, eliminating @milkdown/ctx 7.21.1/7.21.2 version duplication",
            "useFolderOperations.ts — openFolderDialog() return type fixed (previously resolved by prior commit)",
            "types/ipc.ts — Duplicate electronAPI declaration removed (previously resolved by prior commit)"
          ]
        },
        "eslint": {
          "status": "pass",
          "errors": 0,
          "warnings": 0,
          "fixes_applied": [
            "OutlinePanel.tsx — Extracted parseHeadings() and Heading interface to outlineUtils.ts to satisfy react-refresh/only-export-components",
            "useStatusBarItems.tsx — Extracted 5 StatusBar component definitions to StatusBarItems.tsx to satisfy react-refresh/only-export-components",
            "tabStore.test.ts:55 — Unused switchToTab variable removed (previously resolved by prior commit)",
            "Editor.tsx / Preview.tsx — react-hooks/exhaustive-deps handled with eslint-disable comments (previously resolved by prior commit)"
          ]
        },
        "npm_warnings": {
          "status": "pass",
          "fixes_applied": [
            "package.json — Added allowScripts config for electron and esbuild postinstall scripts via npm approve-scripts"
          ]
        },
        "tests": {
          "status": "pass",
          "total_files": 6,
          "total_tests": 96,
          "all_passed": true
        }
      }
    },
    "performance_optimization": {
      "score": 95,
      "weight": 0.2,
      "details": {
        "markdown_cache": {
          "status": "pass",
          "implementation": "LRU cache with MAX_CACHE_SIZE=10",
          "features": ["filePath-based cache key", "LRU eviction", "null key handling"]
        },
        "zustand_selectors": {
          "status": "pass",
          "implementation": "Fine-grained selectors throughout",
          "example": "useTabStore(s => s.tabs) instead of full store"
        },
        "codemirror_compartment": {
          "status": "pass",
          "implementation": "Compartment.reconfigure() for theme switching",
          "benefit": "No EditorView reconstruction on theme change"
        },
        "debounce": {
          "status": "pass",
          "implementations": [
            "Preview: 150ms debounce on markdown rendering",
            "TabStore: 500ms debounce on DB persistence"
          ]
        }
      }
    },
    "architecture_refactoring": {
      "score": 92,
      "weight": 0.2,
      "details": {
        "component_split": {
          "status": "pass",
          "extractions": [
            "FileTree extracted from Sidebar",
            "useFolderOperations hook from Sidebar",
            "EditorToolbar from Editor",
            "Toast with SingleToast sub-component",
            "parseHeadings + Heading extracted to outlineUtils.ts",
            "StatusBar item components extracted to StatusBarItems.tsx"
          ]
        },
        "type_safety": {
          "status": "pass",
          "positives": [
            "Shared types properly defined",
            "IpcInvokeMap type mapping implemented",
            "Generic tryAsync pattern with proper typing",
            "ElectronAPI centralized in types/ipc.ts, preload.d.ts imports from it",
            "All @milkdown/* packages pinned to exact versions to prevent type duplication"
          ]
        },
        "error_handling": {
          "status": "pass",
          "implementation": [
            "AppError class with code and cause",
            "logError() for logging",
            "getErrorMessage() for user-facing messages",
            "tryAsync() wrapper returning [result, error] tuple",
            "ErrorBoundary for React component errors"
          ]
        }
      }
    },
    "user_experience": {
      "score": 90,
      "weight": 0.2,
      "details": {
        "confirm_dialog": {
          "status": "pass",
          "features": [
            "Promise-based API via useConfirm hook",
            "Focus management (save/restore)",
            "ESC key handling",
            "Background scroll lock",
            "ARIA: role=alertdialog, aria-modal, aria-labelledby, aria-describedby",
            "Variant support (danger/warning/info)"
          ]
        },
        "loading_states": {
          "status": "pass",
          "features": [
            "LoadingSpinner with size variants (small/medium/large)",
            "Overlay mode support",
            "Global loading state via editorStore.loadingStates",
            "StatusBar integration with contextual labels",
            "ARIA: role=status, aria-label, aria-busy"
          ]
        },
        "toast": {
          "status": "pass",
          "features": [
            "Multi-toast stacking (max 5)",
            "Type differentiation with icons",
            "Auto-dismiss with configurable duration",
            "Enter/exit animations",
            "Close button",
            "ARIA: role=alert, aria-live=assertive"
          ]
        },
        "accessibility": {
          "status": "pass",
          "features": [
            "File tree: role=tree, role=treeitem, aria-expanded, aria-selected",
            "Keyboard navigation: Enter/Space on tree items",
            "Sidebar: aria-label on sections",
            "StatusBar: role=status, aria-live=polite",
            "Preview: aria-live=polite, aria-busy during render",
            "Resize handle: role=separator, aria-orientation, tabIndex"
          ]
        }
      }
    },
    "developer_experience": {
      "score": 92,
      "weight": 0.15,
      "details": {
        "test_framework": {
          "status": "pass",
          "setup": {
            "framework": "Vitest 4.1.8",
            "environment": "jsdom",
            "setup_file": "@testing-library/jest-dom/vitest",
            "coverage_provider": "v8",
            "coverage_targets": ["stores", "lib", "hooks"]
          },
          "test_files": [
            "tabStore.test.ts (22 tests)",
            "editorStore.test.ts (12 tests)",
            "fileUtils.test.ts (22 tests)",
            "markdown.test.ts (16 tests)",
            "errorHandler.test.ts (12 tests)",
            "useDocStats.test.ts (12 tests)"
          ]
        },
        "git_hooks": {
          "status": "pass",
          "setup": {
            "tool": "Husky 9.1.7",
            "pre_commit": "lint-staged",
            "lint_staged": {
              "ts_tsx": ["eslint --fix", "tsc --noEmit --pretty"],
              "json_css_md": ["prettier --write"]
            }
          }
        },
        "documentation": {
          "status": "pass",
          "files": ["CONTRIBUTING.md", "README.md", "DESIGN.md", "DEVSETUP.md"]
        }
      }
    }
  },
  "issues_resolved": [
    {
      "severity": "critical",
      "category": "typescript",
      "file": "src/renderer/components/Editor/useMilkdown.ts",
      "lines": [73],
      "description": "state.selection.constructor.create — Property 'create' does not exist on type 'Function'",
      "fix": "Imported TextSelection from @milkdown/prose/state, replaced with TextSelection.create(tr.doc, from + 1)",
      "status": "fixed"
    },
    {
      "severity": "critical",
      "category": "typescript",
      "file": "src/renderer/components/Editor/useMilkdown.ts",
      "lines": [173, 190, 193, 194],
      "description": "Milkdown plugin type incompatibility: @milkdown/ctx 7.21.2 (top-level) vs 7.21.1 (nested in plugin packages) caused Ctx #private field mismatch",
      "fix": "Pinned all @milkdown/* packages to exact 7.21.1, added 5 phantom-dependency plugins as direct deps, reinstalled to eliminate nested @milkdown/ctx copy",
      "status": "fixed"
    },
    {
      "severity": "critical",
      "category": "typescript",
      "file": "src/renderer/hooks/useFolderOperations.ts",
      "lines": [20, 21, 24, 27],
      "description": "openFolderDialog() return type mismatch",
      "fix": "IPC handler changed to return string | null, type definitions aligned (resolved in prior commit)",
      "status": "fixed"
    },
    {
      "severity": "critical",
      "category": "typescript",
      "file": "src/renderer/types/ipc.ts",
      "lines": [54, 58],
      "description": "Duplicate Window.electronAPI declaration",
      "fix": "Removed duplicate from ipc.ts, preload.d.ts is source of truth (resolved in prior commit)",
      "status": "fixed"
    },
    {
      "severity": "error",
      "category": "eslint",
      "file": "src/renderer/stores/__tests__/tabStore.test.ts",
      "lines": [55],
      "description": "'switchToTab' is destructured but never used in test",
      "fix": "Test updated (resolved in prior commit)",
      "status": "fixed"
    },
    {
      "severity": "warning",
      "category": "eslint/react-refresh",
      "file": "src/renderer/components/OutlinePanel/OutlinePanel.tsx",
      "lines": [19],
      "description": "Fast refresh only works when a file only exports components",
      "fix": "Extracted parseHeadings() and Heading interface to outlineUtils.ts",
      "status": "fixed"
    },
    {
      "severity": "warning",
      "category": "eslint/react-refresh",
      "file": "src/renderer/hooks/useStatusBarItems.tsx",
      "lines": [10, 20, 42, 59, 64],
      "description": "5 react-refresh/only-export-components warnings — component definitions mixed with hook export",
      "fix": "Extracted 5 StatusBar item components to StatusBarItems.tsx",
      "status": "fixed"
    },
    {
      "severity": "warning",
      "category": "eslint/react-hooks",
      "file": "src/renderer/components/Editor/Editor.tsx",
      "lines": [43, 55],
      "description": "useEffect hooks have missing dependencies",
      "fix": "Added eslint-disable comments with explanation (resolved in prior commit)",
      "status": "fixed"
    },
    {
      "severity": "warning",
      "category": "eslint/react-hooks",
      "file": "src/renderer/components/Preview/Preview.tsx",
      "lines": [53],
      "description": "useEffect missing 'activeTab' dependency",
      "fix": "Correctly using optional chaining in deps array (resolved in prior commit)",
      "status": "fixed"
    },
    {
      "severity": "warning",
      "category": "npm",
      "file": "package.json",
      "lines": [],
      "description": "npm allow-scripts warning: electron and esbuild postinstall scripts not covered",
      "fix": "Added allowScripts config via npm approve-scripts electron esbuild",
      "status": "fixed"
    }
  ],
  "strengths": [
    "Excellent LRU cache implementation for Markdown processor with proper eviction strategy",
    "CodeMirror Compartment pattern for efficient theme switching without EditorView reconstruction",
    "Comprehensive Zustand selector usage preventing unnecessary re-renders",
    "Well-designed ConfirmDialog with full accessibility support (focus trap, ESC, ARIA)",
    "LoadingSpinner component with multiple sizes, overlay mode, and proper ARIA attributes",
    "Toast system supports stacking, types, animations, and accessibility",
    "Unified error handling with AppError, logError, getErrorMessage, tryAsync patterns",
    "Good component extraction (FileTree, EditorToolbar, useFolderOperations, outlineUtils, StatusBarItems)",
    "96 unit tests covering core modules with proper mocking",
    "Debounce implementation for both preview rendering and DB persistence",
    "Git hooks with lint-staged for automated code quality checks",
    "Comprehensive documentation (CONTRIBUTING.md, DESIGN.md, README.md)",
    "All @milkdown/* packages pinned to exact versions preventing type duplication",
    "Type-safe IPC architecture with centralized ElectronAPI definition",
    "npm allow-scripts configured for secure postinstall script management"
  ],
  "summary": "MarkLite has been fully audited and all issues resolved. TypeScript compilation passes with 0 errors, ESLint passes with 0 errors and 0 warnings, and all 96 tests pass. Key fixes include: (1) @milkdown/ctx version deduplication by pinning all Milkdown packages to exact 7.21.1, (2) TextSelection.create type fix in useMilkdown.ts, (3) Backspace auto-pair dead-code bug fix, (4) react-refresh warnings resolved by extracting utilities and components to separate files, (5) npm allow-scripts configuration for electron and esbuild. The project is production-ready."
}
