d4c29a1bf4
* 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 异常 --------- Co-authored-by: Ubuntu <ubuntu@localhost.localdomain> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
147 lines
7.0 KiB
Markdown
147 lines
7.0 KiB
Markdown
# 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`。
|
||
|
||
## 项目结构
|
||
|
||
```
|
||
config/features.tsx # 功能定义(路由 + 元数据的单一事实来源)
|
||
entrypoints/ # 扩展入口点 (popup/, options/, 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 类型声明
|
||
i18n/locales/{zh,en}/ # 国际化资源 (common.json, features.json 及各功能独立 JSON)
|
||
```
|
||
|
||
### 页面组件模式
|
||
|
||
典型功能页面遵循 **UI + Hook 分离** 模式:
|
||
|
||
```
|
||
pages/FeatureName/
|
||
├── index.tsx # 页面 UI(纯展示,使用 shadcn/ui 组件)
|
||
├── useFeatureName.ts # 业务逻辑 Hook(状态管理 + 转换逻辑)
|
||
└── constants.ts # 常量定义
|
||
```
|
||
|
||
- 页面组件调用 `useLazyTranslation('featureName')` 获取翻译函数
|
||
- Hook 负责所有状态管理和业务逻辑,通过返回值暴露给页面
|
||
- 子组件可进一步拆分(如 `LiveClock.tsx`、`ResultView.tsx`)
|
||
|
||
## 关键架构决策
|
||
|
||
**路由**: 不使用 React Router。通过 `config/features.tsx` 的 `FEATURES` 数组管理,`RouterProvider` 根据 `PageType`
|
||
渲染对应组件。支持三种渲染模式:popup(弹窗)、sidepanel(侧边栏)和 browser-tab(浏览器新标签页,通过 `open_in_tab` 打开)。
|
||
每种模式有独立的路由和可见页面配置(`app/popupRoute`、`app/sidepanelRoute`、`app/tabRoute` 等)。
|
||
|
||
**存储**: 所有 Chrome Storage 键必须在 `types/storage.d.ts` 的 `StorageSchema` 中定义,键名使用 kebab-case 格式(如 `app/currentRoute`)。
|
||
使用 `utils/chromeStorage.ts` 及其 Hook。Router 同时使用 `chrome.storage.local` 和 `localStorage` 做快照以消除首屏闪烁。
|
||
修改 StorageSchema 时,必须在 `utils/chromeStorage.ts` 添加版本迁移函数,并在测试中覆盖迁移场景。
|
||
|
||
**通信**: 使用 `@webext-core/messaging`,协议定义在 `utils/messages.ts`。
|
||
|
||
**路径别名**: `@/` 映射到项目根目录 (已在 tsconfig 和 vitest.config 中配置)。
|
||
|
||
**浏览器兼容**: 优先使用 `wxt/browser` 导出的 `browser` 对象,而非原生 `chrome` API。
|
||
|
||
**代码分割**: `wxt.config.ts` 通过 `manualChunksForHtmlOnly()` 自动分组依赖(vendor-react、vendor-i18n、vendor-qr 等),无需手动配置。
|
||
|
||
## 测试环境
|
||
|
||
- 环境: jsdom
|
||
- 全局变量: `vitest/globals` (describe, it, expect 等无需导入)
|
||
- Setup 文件: `vitest.setup.ts` 自动 mock:
|
||
- `chrome.*` / `browser.*` API (storage, tabs, runtime, cookies 等)
|
||
- `react-i18next` (返回 key 作为翻译)
|
||
- `@/utils/useLazyTranslation` (返回 `ns:key` 格式翻译)
|
||
- `window.matchMedia`
|
||
- 测试文件命名: `__tests__/*.test.{ts,tsx}` 或 `*.test.{ts,tsx}`
|
||
- Mock 模式: 使用 `vi.mock()` 进行模块级 mock,避免在测试文件中重复 mock 代码
|
||
- 测试工具: `@testing-library/react` + `@testing-library/user-event` 进行组件测试
|
||
|
||
## i18n
|
||
|
||
- 命名空间: `common` (默认), `features`
|
||
- 翻译键格式: `namespace:key` (如 `features:timestamp.title`)
|
||
- 语言: `zh` (默认), `en`
|
||
- 翻译文件结构:
|
||
- `i18n/locales/{zh,en}/common.json` - 全局通用翻译
|
||
- `i18n/locales/{zh,en}/features.json` - 功能模块标题和描述
|
||
- `i18n/locales/{zh,en}/{功能名}.json` - 各功能独立翻译(如 timestamp.json, storageCleaner.json 等)
|
||
- 添加新翻译: 编辑 `i18n/locales/{zh,en}/{common,features}.json` 及对应功能独立 JSON
|
||
- 使用 `useLazyTranslation` hook 加载功能独立翻译,返回 `ns:key` 格式
|
||
- 回退策略: 当翻译 key 在目标语言缺失时,回退到默认语言 `zh`;若默认语言也缺失,返回占位格式 `namespace:key` 并在开发模式下记录 warning
|
||
|
||
## 新功能开发清单
|
||
|
||
1. 在 `types/storage.d.ts` 添加 `PageType` 联合类型
|
||
2. 在 `config/features.tsx` 的 `FEATURES` 数组添加配置(指定 key、翻译键、图标、三种渲染模式的组件)
|
||
3. 在 `pages/` 创建页面组件 (懒加载):
|
||
- `index.tsx` — UI 组件,使用 `useLazyTranslation` 获取翻译
|
||
- `useFeatureName.ts` — 业务逻辑 Hook
|
||
- `constants.ts` — 常量(可选)
|
||
4. 在 `i18n/locales/{zh,en}/features.json` 添加翻译(复杂功能可新建独立 JSON)
|
||
5. 如需新权限,更新 `wxt.config.ts` 的 `manifest.permissions`
|
||
6. 添加对应的单元测试
|
||
|
||
## 代码规范
|
||
|
||
- 禁止使用 `any` (测试文件除外)
|
||
- 未使用变量/参数: 使用 `_` 前缀 (如 `_unused`)
|
||
- 样式: 使用 Tailwind CSS + shadcn/ui (通过 `className` 和 `cn()` 工具)
|
||
- UI 组件: 优先使用 `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` — 拖拽排序(用于页面顺序管理)
|
||
- `marked` — Markdown 解析
|
||
- `qrious` + `qr-scanner` — 二维码生成与解析
|
||
- `dayjs` — 日期处理(时间戳转换)
|
||
- `sonner` — Toast 通知(替代传统 snackbar)
|