Files
testing-tool/AGENTS.md
T
LingandRX f9821553b3 chore: merge develop into main (#56)
* 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>
2026-05-29 23:00:21 +08:00

154 lines
7.7 KiB
Markdown
Raw Permalink 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.
# AGENTS.md
WXT 浏览器扩展项目 (React 19 + TypeScript)。提供时间戳转换、存储清理、JWT 解析、JSON 工具、二维码、Base64、Markdown 等测试效率工具。
## 核心命令
```bash
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 失败):
1. `setup`(安装依赖、`wxt prepare`
2. 并行运行 `lint``typecheck``test`(三者全部通过才继续)
3. `build`(仅当步骤 2 全部成功时执行)
Pre-commit hook`.husky/pre-commit` 调用 `lint-staged`,任一步骤返回非零则终止提交):
1. 代码文件 (`*.{ts,tsx,js,jsx,mjs}`):运行 `eslint --fix --max-warnings=0 --no-warn-ignored`;若失败则终止并报告错误
2. 同一代码文件:运行 `prettier --write`
3. 其他文件 (`*.{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](./.github/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`
- **占位符支持**: `t('router_notFoundDescription', { entryPointType: 'popup' })`
- **Hook 返回值**: `{ t, i18n: { language, changeLanguage }, isLoaded }`
- **回退策略**: 当翻译 key 未命中时,返回 key 本身(开发模式下在控制台记录 warning)
- **限制**: `chrome.i18n` 无法动态切换语言,语言跟随浏览器设置,切换后需刷新页面
## 新功能开发清单
1.`src/types/storage.d.ts` 添加 `PageType` 联合类型
2.`src/config/features.tsx``FEATURES` 数组添加配置(指定 key、翻译键、图标、三种渲染模式的组件)
3.`src/pages/` 创建页面组件 (懒加载)
- `index.tsx` — UI 组件,使用 `useI18n` 获取翻译
- `useFeatureName.ts` — 业务逻辑 Hook
- `constants.ts` — 常量(可选)
4.`public/_locales/zh/messages.json`(及 `en/messages.json`)添加翻译
5. 如需新权限,更新 `wxt.config.ts``manifest.permissions`
6. 添加对应的单元测试
## 代码规范
- 禁止使用 `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