* fix(ci): fix release workflow artifact upload issue - Split artifact upload into 3 separate steps (Chrome, Firefox, Source) - Update download steps to match new artifact names - Add if-no-files-found: error for better error handling - Fix Node.js 20 compatibility issue with upload-artifact@v4 glob pattern * docs: 更新 AGENTS.md,添加测试工具和翻译文件结构说明 * docs: 更新 AGENTS.md,完善 CI 步骤和存储键名格式说明 * fix: remove unnecessary animations from StorageCleaner page - Remove entrance animations (animate-in, fade-in, zoom-in) from all components - Remove scale effect (active:scale-[0.99]) from clean button - Remove transition effects (transition-all, transition-colors) from OptionItem, StorageOptionsGrid, AutoRefreshToggle - Remove bouncing animation from error icon - Remove pulsing animation from warning banner - Keep animate-spin on button loading spinner as functional indicator * fix: remove unnecessary animations from RightClickRestorer page * fix: remove all unnecessary animations from all pages - Remove dead code animations (animate-in, fade-in, slide-in, zoom-in, shake) from tailwindcss-animate plugin (not installed) - Remove decorative transition effects (transition-all, transition-colors) from all page components - Remove scale effects (active:scale-95) from buttons - Remove bounce animations (animate-bounce) from icons - Keep functional animate-spin on loading spinners as they provide essential loading feedback Affected pages: Dashboard, Timestamp, Jwt, JsonTools, Base64Converter, HtmlToMarkdown, MarkdownToHtml, QrCode, TextStatistics * docs: 添加项目文档(copilot-instructions、编码规范、各目录 README) - 新增 .github/copilot-instructions.md:Copilot 指令文件,涵盖构建命令、CI 流水线、项目架构、关键规范 - 新增 .github/CODING_STANDARDS.md:完整的代码编写规范文档(TypeScript、React、样式、错误处理、命名、测试、Hook、i18n、存储等 12 个章节) - 新增 11 个目录的 README.md:components、components/ui、config、entrypoints、i18n、lib、pages、providers、public、src、types、utils - 更新 AGENTS.md:补充页面组件模式说明和 components/ui 目录 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(CopyButton): use shadcn buttonVariants instead of duplicated variant classes Remove hand-written variantClasses/sizeClasses that duplicated shadcn's buttonVariants. Now extends ButtonProps and imports buttonVariants from components/ui/button for consistent styling. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(ErrorBoundary): use shadcn destructive tokens instead of hardcoded red colors Replace hardcoded red-200/red-50/red-600 with border-destructive, bg-destructive/5, text-destructive. Use variant='destructive' on Button. Align error log styling with PageErrorBoundary. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(TextInputArea): replace hardcoded Chinese strings with i18n t() calls Use existing translation keys for clear success, copy success, and copy error messages instead of hardcoded Chinese text. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(QrCodePreview): use shadcn Button instead of manual button styles Replace hand-written button class strings with shadcn Button component using outline and default variants. Removes ~15 lines of duplicated Tailwind classes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(GlobalSnackbar): clarify relationship with sonner toast Add note explaining when to use GlobalSnackbar (provider context, severity levels, custom positioning) vs sonner toast (simple one-off messages). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(ImageUploader): wrap handleClearFile with useCallback Prevents unnecessary re-renders by memoizing the callback, consistent with handleFileChange which already uses useCallback. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(SwitchButtonGroup): remove redundant containerPadding conditional Both branches of the ternary returned 'p-1'. Inline the constant directly into the className string. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(TopBar): add missing openExtensionPage import The function was called in handleOpenInTab but never imported from utils/chromeTabs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(TopBar): add selectedIndex boundary protection in keyboard nav Guard against out-of-bounds access when search results change during keyboard navigation. Check selectedIndex < totalItems before accessing arrays. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(RouterContainer): remove unnecessary empty-dep useMemo getEntryPointType() is a simple config getter. Replace useMemo(() => ..., []) with a direct call — the empty dep array made the memo pointless. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(i18n): add missing translation keys for error boundaries and copy messages Add keys for errorBoundary, pageErrorBoundary, router, and messages.copyEmpty in both zh and en locales. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(CopyButton): replace hardcoded Chinese strings with i18n t() calls Use useTranslation('common') for tooltip, copy empty/success/error toast messages. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(ErrorBoundary): replace hardcoded Chinese with i18n via withTranslation HOC Use react-i18next withTranslation HOC for class component to translate title, description, and refresh button text. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(PageErrorBoundary): replace hardcoded Chinese with i18n via withTranslation HOC Use react-i18next withTranslation HOC for class component to translate title, description, and retry button text. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(RouterContainer): replace hardcoded Chinese with i18n t() calls Use useTranslation('common') for 404 page title and description with entryPointType interpolation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(pages): 修复 TypeScript 错误、硬编码字符串,移除死代码 - 修复 LiveClock.tsx CopyButton size 属性类型错误 (small → sm) - 修复 ToolCard.tsx 紫色 RGB 值拼写错误 (147,51,2 purple → 147,51,232) - 替换 4 处硬编码中文为 i18n 调用 (StorageCleaner, JsonTools) - MarkdownToHtml 预览链接色改为 CSS 变量以支持暗黑模式 - 移除 Base64Converter 中已被 Base64ConverterSection 替代的死代码 (FileMode.tsx, ImageMode.tsx 及其测试文件) - 更新 README.md 移除对已删除文件的引用 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(CopyButton, LiveClock): 修改按钮大小为 'icon',移除硬编码颜色配置 * refactor(TextInputArea): 复用 CopyButton 组件替换内联复制逻辑 * fix(test): 修复 CopyButton mock 未捕获 clipboard writeText 异常 * refactor: 迁移 i18n 系统从 react-i18next 到 chrome.i18n - 移除 react-i18next、i18next 及相关依赖 - 删除旧的 i18n/ 目录和 useLazyTranslation 工具 - 新增 utils/chromeI18n.ts 类型安全 wrapper(useI18n Hook + getMessage) - 生成 public/_locales/{zh,en}/messages.json(298 个翻译 key) - 批量更新 39+ 组件文件的导入和翻译调用 - 转换翻译键格式:namespace:key → namespace_key - 修复 ErrorBoundary/PageErrorBoundary 从 withTranslation HOC 改为直接调用 getMessage - 更新 vitest.setup.ts mock 加载实际翻译文本 - 修复 11 个测试文件的断言以匹配中文翻译 - TypeScript、ESLint、547 项测试全部通过 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor: 移除语言切换按钮 chrome.i18n 语言由浏览器设置决定,不支持运行时切换,移除 TopBar 中的语言切换按钮及相关逻辑。 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor: 移除英文翻译,仅保留中文 删除 public/_locales/en/ 目录,清理 chromeI18n.ts 中未使用的语言切换相关导出。 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: 清理未使用的 i18n key(322 → 245) 移除 77 个未被源码引用的翻译 key,包括: - jsonFormat_* (17): 未接入的子模式标题和按钮 - buttons_* (15): 代码使用 common: 命名空间格式,直接 key 未引用 - qrCode_* (14): 未使用的页面标题和状态提示 - timestamp/common/storageCleaner/jsonDiff 等零散未用 key 保留了通过模板字面量动态引用的 key(storageCleaner_options_*、common_buttons_themeMode_*、jsonFormat_*InputPlaceholder 等)。 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor: reorganize directory structure into src/ Move all source code directories into src/ for cleaner project structure: - pages/, components/, utils/, config/, providers/, types/, lib/, assets/, entrypoints/ → src/ - Use WXT srcDir config to resolve @/ alias to src/ - Update tsconfig, vitest, eslint, tailwind configs - Remove scattered README.md files from subdirectories - Update documentation (AGENTS.md, CODING_STANDARDS.md, README.md) - Fix pre-existing lint error in RouterProvider.tsx Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor: remove meaningless comments Remove 50+ noise comments across the codebase: - AI-generated verbose prose (💡 emoji, '超进化', '大闸', etc.) - Comments that restate what the code obviously does - Comments about deleted code - Import-level noise comments - Overly verbose Chinese section markers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor: clean up unused code and simplify imports - Remove unused imports and variables across 35 files - Simplify component logic and remove dead code - Clean up test files by removing unnecessary setup - Streamline CI workflow configuration Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: remove HTML-Markdown conversion features (htmlToMarkdown & markdownToHtml) Removed both conversion tools and all related code: - Deleted page components: HtmlToMarkdown, MarkdownToHtml - Deleted utility functions: htmlToMarkdown.ts, markdownToHtml.ts - Deleted unit tests for both utilities - Removed feature configs and PageType entries from storage.d.ts - Removed preview mode types and StorageSchema keys - Removed all i18n translation keys from messages.json - Removed vendor-markdown chunk from wxt.config.ts - Removed marked dependency from package.json - Updated feature count in tests (11 -> 9, page order 10 -> 8) - Updated README, AGENTS.md, and copilot-instructions.md Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: redesign Dashboard layout with search and recently used tools - Add search input at top to filter tools by name/description - Add 'recently used' section showing last 3 used tools as compact chips - Add 'all tools' section header for the tool card grid - Track tool usage via new 'app/recentlyUsedTools' storage key - Update navigateTo to record recently used tools (max 3, LRU order) - Add i18n keys: dashboard_searchPlaceholder, dashboard_recentlyUsed, dashboard_allTools Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: replace ToolCard grid with compact icon+name grid on Dashboard - Replace large ToolCard layout with dense grid (3-6 columns responsive) - Each item shows icon + tool name in a compact tile - Hover highlights icon and text with smooth transitions - Remove ToolCard import from Dashboard (component retained for reuse) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: remove redundant search input from Dashboard (header already has one) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: optimize TopBar search with Cmd+K shortcut and improved styling - Add global Cmd/Ctrl+K keyboard shortcut to focus search input - Show keyboard shortcut hint (⌘K) in search input when empty - Improve input styling: rounded-lg, softer borders, focus transitions - Improve dropdown: rounded-lg, larger shadow, better spacing - Larger result icons (h-8 w-8) with rounded-lg containers - Refined history section with uppercase label and muted icon - Search icon fades in on focus via group-focus-within Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: improve search history to show recently used tools with direct navigation - Store feature keys (PageType) in history instead of translated labels - Show icon + name + description in history items (same as search results) - Click or Enter on history item navigates directly to the tool page - Remove History icon import (no longer needed) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: useI18n fallback for empty string translations from chrome.i18n * refactor: 移除设置页面 - 删除 options 入口点目录及相关文件 - 移除 wxt.config.ts 中 options_ui 配置 - 移除 TopBar 组件中的设置按钮和 onOpenOptions prop - 移除 popup/sidepanel App.tsx 中的 handleOpenOptions - 移除相关测试中的设置页面引用 - 移除 messages.json 中设置相关翻译键 - 通过 lint、typecheck、test 验证 * docs: 更新文档,移除设置页面引用 - README.md: 移除 entrypoints/options/ 目录说明 - AGENTS.md: 更新项目结构描述,移除 options/ 入口点 * docs: add comprehensive page development standards (CODING_STANDARDS.md §11) - 新增 §11 页面开发规范(10 个子节),覆盖目录结构分4级、入口组件命名统一为 Index、业务 Hook 内部结构顺序、常量/子组件/存储键/模式切换/布局约定等规范 - 新增 §11.9 页面开发检查清单(10 步逐项确认) - 更新 AGENTS.md 和 copilot-instructions.md 同步引用新规范章节 - 修正 useLazyTranslation → useI18n 与实际代码保持一致 * refactor(pages): apply UI+Hook separation and unify component naming - Dashboard, RightClickRestorer: rename page component to Index - Jwt: extract useJwt.ts hook + JwtSection.tsx sub-component (127→76 lines) - TextStatistics: extract useTextStatistics.ts hook (63→48 lines) - JsonTools: extract useJsonTools.ts hook + constants.ts (199→109 lines) - All pages now follow CODING_STANDARDS.md §11 pattern * refactor(StorageCleaner): improve style consistency and checkbox alignment - Group storage options, auto-refresh toggle, and clean button into unified card - Remove card wrappers from StorageOptionsGrid and AutoRefreshToggle (now sections within parent card) - Align OptionItem and Select All checkboxes with consistent px-3.5 padding - Distinguish indeterminate vs checked checkbox states visually - Standardize spacing (space-y-4), add select-none to page root - Fix import path for StorageCleanerConfirm to relative ./ * fix(StorageCleaner, Base64Converter): fix layout padding and checkbox alignment - StorageCleaner: add px-3.5 to grid container to prevent content sticking to border - StorageCleaner: adjust select-all bar to pl-3.5 pr-7 for checkbox alignment with grid items - Base64Converter: add px-2 to TextMode and Base64ConverterSection for consistent spacing Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore: add initial CLAUDE.md file with reference to @AGENT.md * docs: add comprehensive visual style guide Add VISUAL_STYLE_GUIDE.md to standardize UI development: - Design principles and keywords - Complete color system (CSS variables, semantic usage) - Typography hierarchy and patterns - Spacing, layout, and container specs - Border radius and shadow system - Component specs (Button, Input, Card, Dialog, etc.) - Interaction and animation guidelines - Dark mode implementation rules - Tool color palette and assignment - Code organization standards - Anti-patterns checklist - Quick reference appendix Update .gitignore to allow tracking the style guide. * docs(AGENTS): update i18n section to reflect chrome.i18n migration - Replace react-i18next docs with chrome.i18n API documentation - Update translation file paths from i18n/locales/ to public/_locales/ - Update test mock description for chromeI18n - Remove references to useLazyTranslation, vendor-i18n chunk, and snackbar Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(content): simplify content script structure - Remove messageHandler.ts (6-line pass-through facade) - Import contextMenuHandler directly from content.ts - Replace local getI18nText() with shared getMessage() from utils/chromeI18n Reduces content script files from 3 to 2 and eliminates i18n logic duplication. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(RightClickRestorer): simplify setDomain logic Replace nested if/try/catch with a single ternary expression. isUnsupportedPage() already handles invalid URLs, so new URL() is safe here. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(background): extract main world injection script Move the 62-line inline main world injection function from background.ts to a dedicated utils/rightClickInjection.ts file. This separates the RightClickRestorer's DOM patching logic from the service worker's message routing concerns. - background.ts: -62 lines, now delegates to imported function - utils/rightClickInjection.ts: new file with mainWorldInjectionScript() Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(background): simplify RELOAD_TAB with setTimeout Replace the 30-line chrome.alarms API implementation with a simple setTimeout for delayed tab reloads. Rationale: The only usage is a 1-second delay after storage cleaning. Service workers don't go idle within 1 second, so alarms' persistence benefit is unnecessary here. - Removed: alarm naming, create/clear, listener register/unregister - Added: 4-line setTimeout conditional Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(storageCleaner): extract executeScript helpers Extract two generic helpers to eliminate repetitive executeScript boilerplate: - runScript<T>(): for size/query operations (5 functions) - runCleanScript(): for cleanup operations (5 functions) Also add CLEAN_OPTION_KEYS constant shared by formatCleaningResult and isEmptyResult. - 428 lines → 379 lines (-49 lines) - Eliminates ~100 lines of duplicated try/catch/result parsing Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(base64Converter): simplify utility functions - Mark formatFileSize() as @deprecated, delegate to formatBytes - Replace manual for-loop in base64ToBytes() with Uint8Array.from() - Replace nested loops in sniffMimeFromBytes() with Array.every() 5 insertions(+), 23 deletions(-) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor: replace GlobalSnackbar with sonner toast Remove the custom 373-line GlobalSnackbar component and its Provider/Hook in favor of the already-installed sonner library. Changes: - Delete src/components/GlobalSnackbar.tsx (+ tests) - Remove SnackbarProvider from popup/App.tsx and sidepanel/App.tsx - Replace useSnackbar() calls with toast.success()/toast.error() in: - ImageUploader.tsx - useQrCode.ts - ParsePanel.tsx - LiveClock.tsx - Update test mocks to use sonner instead of GlobalSnackbar -554 lines, +43 lines (net -511 lines) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(RouterContainer): remove unnecessary useMemo and hoist constant - Replace useMemo with direct expression for animationClass - Move entryPointType to module scope (value is fixed after mount) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor: remove unnecessary useMemo calls across components Replace over-cached simple computations with direct expressions: - popup/App.tsx: getEntryPointType() and routerConfig (values are stable) - Dashboard/index.tsx: visibleSet, visibleFeatures, recentFeatures (Set creation + array filter/map are cheaper than useMemo overhead) - useTextStatistics.ts: getTextStats(text) (simple string processing) - ThemeModeProvider.tsx: contextValue object (setMode is stable via useCallback) -39 lines, +28 lines (net -11 lines) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor: unify formatBytes usage and simplify useContextMenuData 1. Remove formatSize/formatByteSize delegation functions: - storageCleaner.ts:formatSize (was just formatBytes wrapper) - textStatistics.ts:formatByteSize (was just formatBytes wrapper) - Update all callers to import formatBytes directly from @/utils/format 2. Simplify useContextMenuData hook: - Remove unnecessary useCallback wrapping - Inline checkAndConsumeData logic directly in useEffect - Eliminate callback->effect dependency cycle -69 lines, +67 lines (net -2 lines, but removes 2 indirection layers) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(storageCleaner): simplify architecture and improve type semantics - Remove RELOAD_TAB message chain, use chrome.tabs.reload() directly - Rename IndexedDB label to '站点存储' for accuracy - Introduce StorageSizeInfo type to distinguish bytes vs count - Rename totalSize to totalBytes for clarity - Merge runCleanScript into runScript to reduce duplication - Rename CleaningResult.success to overallSuccess to avoid confusion - Remove unused domain state and setDomain call Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor: replace require with import for JSON module in vitest.setup.ts * refactor: simplify Timestamp and RightClickRestorer Timestamp: - Extract shared utilities (msToUnit, dayjsFromTimestamp, ModeType) - Remove unnecessary useCallback/useMemo/React.memo - Remove extra info section (relative time, ISO 8601, UTC) - Fix missing translation key timestamp_unitS RightClickRestorer: - Remove website badge (右键已解锁 overlay) - Remove mouse penetration logic for media elements - Remove unnecessary useCallback - Remove redundant setIsLoading call Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(StorageCleanerConfirm.test.tsx): update IndexedDB label to Chinese translation --------- Co-authored-by: Ubuntu <ubuntu@localhost.localdomain> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
7.7 KiB
AGENTS.md
WXT 浏览器扩展项目 (React 19 + TypeScript)。提供时间戳转换、存储清理、JWT 解析、JSON 工具、二维码、Base64、Markdown 等测试效率工具。
核心命令
npm run dev # Chrome 开发模式 (HMR)
npm run dev:firefox # Firefox 开发模式
npm run build # Chrome 生产构建
npm run build:firefox # Firefox 生产构建
npm run zip # 打包 Chrome 扩展 (.output/*.zip)
npm run zip:firefox # 打包 Firefox 扩展
npm run lint # ESLint (--max-warnings=0)
npm run typecheck # tsc --noEmit
npm run test # vitest run (单次执行)
npm run test:watch # vitest 监视模式
npm run test:coverage # 带覆盖率的测试
运行单个测试: npx vitest run path/to/file.test.ts
验证流程
CI 步骤(严格顺序,任一步骤失败则停止并标记 CI 失败):
setup(安装依赖、wxt prepare)- 并行运行
lint、typecheck、test(三者全部通过才继续) build(仅当步骤 2 全部成功时执行)
Pre-commit hook(.husky/pre-commit 调用 lint-staged,任一步骤返回非零则终止提交):
- 代码文件 (
*.{ts,tsx,js,jsx,mjs}):运行eslint --fix --max-warnings=0 --no-warn-ignored;若失败则终止并报告错误 - 同一代码文件:运行
prettier --write - 其他文件 (
*.{json,css,scss,md}):运行prettier --write
WXT 生成文件
.wxt/目录由postinstall自动执行wxt prepare生成,包含 TypeScript 类型声明和扩展的 tsconfig。- 生产构建输出到
.output/目录。 tsconfig.json继承自./.wxt/tsconfig.json。
项目结构
src/ # 源代码根目录
config/features.tsx # 功能定义(路由 + 元数据的单一事实来源)
entrypoints/ # 扩展入口点 (popup/, sidepanel/, background.ts, content.ts)
pages/ # 功能页面组件 (懒加载)
components/ # 可复用 UI 组件
components/ui/ # shadcn/ui 基础组件 (button, dialog, select 等)
providers/ # React Context (Router, Theme 等)
hooks/ # 自定义 React Hooks
utils/ # 工具函数与服务抽象
types/ # TypeScript 类型声明
lib/ # 通用工具函数(cn、utils)
public/ # 静态资源(图标、_locales 等)
页面组件模式
典型功能页面遵循 UI + Hook 分离 模式。详见 CODING_STANDARDS.md § 11。
src/pages/FeatureName/
├── index.tsx # 页面 UI(纯展示,仅负责渲染布局)
├── useFeatureName.ts # 业务逻辑 Hook(状态管理 + 转换逻辑)
├── constants.ts # 常量定义(可选,≥3 个常量时创建)
└── __tests__/
└── index.test.tsx
- 页面入口组件统一命名为
Index,通过export default function Index()导出 - Hook 负责所有状态管理和业务逻辑,通过返回值暴露给页面
- 子组件可以独立调用
useI18n等全局 Hook - 当
index.tsx超过 150 行时,必须拆分为 UI + Hook 模式 - 复杂页面可增加
contexts/、hooks/、components/子目录
关键架构决策
路由: 不使用 React Router。通过 src/config/features.tsx 的 FEATURES 数组管理,RouterProvider 根据 PageType
渲染对应组件。支持三种渲染模式:popup(弹窗)、sidepanel(侧边栏)和 browser-tab(浏览器新标签页,通过 open_in_tab 打开)。
每种模式有独立的路由和可见页面配置(app/popupRoute、app/sidepanelRoute、app/tabRoute 等)。
存储: 所有 Chrome Storage 键必须在 src/types/storage.d.ts 的 StorageSchema 中定义,键名使用 kebab-case 格式(如 app/currentRoute)。
使用 src/utils/chromeStorage.ts 及其 Hook。Router 同时使用 chrome.storage.local 和 localStorage 做快照以消除首屏闪烁。
修改 StorageSchema 时,必须在 src/utils/chromeStorage.ts 添加版本迁移函数,并在测试中覆盖迁移场景。
通信: 使用 @webext-core/messaging,协议定义在 src/utils/messages.ts。
路径别名: @/ 映射到项目根目录 (已在 tsconfig 和 vitest.config 中配置)。
浏览器兼容: 优先使用 wxt/browser 导出的 browser 对象,而非原生 chrome API。
代码分割: wxt.config.ts 通过 manualChunksForHtmlOnly() 自动分组依赖(vendor-react、vendor-qr、vendor-dnd 等),无需手动配置。
测试环境
- 环境: jsdom
- 全局变量:
vitest/globals(describe, it, expect 等无需导入) - Setup 文件:
vitest.setup.ts自动 mock:chrome.*/browser.*API (storage, tabs, runtime, cookies 等)@/utils/chromeI18n(从public/_locales/zh/messages.json加载真实翻译)window.matchMedia
- 测试文件命名:
__tests__/*.test.{ts,tsx}或*.test.{ts,tsx} - Mock 模式: 使用
vi.mock()进行模块级 mock,避免在测试文件中重复 mock 代码 - 测试工具:
@testing-library/react+@testing-library/user-event进行组件测试
i18n (chrome.i18n)
项目使用 Chrome 扩展标准的 chrome.i18n API 进行本地化,通过 src/utils/chromeI18n.ts 提供类型安全的 React Hook 包装。
- 翻译文件:
public/_locales/{zh,en}/messages.json(Chrome 扩展标准格式) - 默认语言:
zh(在wxt.config.ts的manifest.default_locale中配置) - 使用方式:
import { useI18n } from '@/utils/chromeI18n' - 翻译键格式:
- 直接 key:
t('dashboard_title')→ 查找dashboard_title - 命名空间格式(兼容旧用法):
t('common:buttons.search')→ 查找common_buttons_search - 带命名空间参数:
useI18n(['common', 'features']),会自动尝试common_key、features_key
- 直接 key:
- 占位符支持:
t('router_notFoundDescription', { entryPointType: 'popup' }) - Hook 返回值:
{ t, i18n: { language, changeLanguage }, isLoaded } - 回退策略: 当翻译 key 未命中时,返回 key 本身(开发模式下在控制台记录 warning)
- 限制:
chrome.i18n无法动态切换语言,语言跟随浏览器设置,切换后需刷新页面
新功能开发清单
- 在
src/types/storage.d.ts添加PageType联合类型 - 在
src/config/features.tsx的FEATURES数组添加配置(指定 key、翻译键、图标、三种渲染模式的组件) - 在
src/pages/创建页面组件 (懒加载):index.tsx— UI 组件,使用useI18n获取翻译useFeatureName.ts— 业务逻辑 Hookconstants.ts— 常量(可选)
- 在
public/_locales/zh/messages.json(及en/messages.json)添加翻译 - 如需新权限,更新
wxt.config.ts的manifest.permissions - 添加对应的单元测试
代码规范
- 禁止使用
any(测试文件除外) - 未使用变量/参数: 使用
_前缀 (如_unused) - 样式: 使用 Tailwind CSS + shadcn/ui (通过
className和cn()工具) - UI 组件: 优先使用
src/components/ui/下的 shadcn/ui 组件 (button, dialog, select 等) - 图标: 使用
lucide-react图标库 - 格式: Prettier (
.prettierrc: 100 字符宽, 单引号, 尾逗号 all, LF 换行) - ESLint 使用
typescript-eslint的projectService: true(无需手动维护 project 路径)
关键外部库(非显而易见的)
@webext-core/messaging— 扩展消息通信@dnd-kit— 拖拽排序(用于页面顺序管理)qrious+qr-scanner— 二维码生成与解析dayjs— 日期处理(时间戳转换)sonner— Toast 通知(替代传统 snackbar)