* 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>
6.5 KiB
Copilot 指令
基于 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 # TypeScript 类型检查(tsc --noEmit)
npm run test # 运行全部单元测试(vitest run)
npm run test:watch # Vitest 监视模式
npm run test:coverage # 带覆盖率的测试
运行单个测试文件:npx vitest run path/to/file.test.ts
修改 package.json 后需运行 npm install(会自动触发 postinstall → wxt prepare 重新生成 .wxt/ 类型声明)。
CI 流水线(GitHub Actions)
严格顺序门控,任一步骤失败则终止:
- setup — 安装依赖,缓存
node_modules - lint、typecheck、test — 三者并行运行,全部通过才继续
- build — Chrome + Firefox 矩阵构建(仅当步骤 2 全部通过时执行)
Pre-commit 钩子(.husky/pre-commit → lint-staged):
- 代码文件(
*.{ts,tsx,js,jsx,mjs}):运行eslint --fix --max-warnings=0 - 同一代码文件:运行
prettier --write - 其他文件(
*.{json,css,scss,md}):运行prettier --write
项目架构
路由(不使用 React Router)
路由完全通过 config/features.tsx 中的 FEATURES 数组管理。每个功能定义一个 key(类型为 types/storage.d.ts 中的 PageType)和三个懒加载组件,分别对应 popup、sidepanel、tab 三种渲染模式。providers/RouterProvider.tsx 中的 RouterProvider 根据存储状态渲染当前页面。
存在三套独立的路由作用域:app/popupRoute、app/sidepanelRoute、app/tabRoute,各自维护独立的可见页面列表和页面排序。
存储
所有 Chrome Storage 键必须在 types/storage.d.ts 的 StorageSchema 中声明,键名使用 kebab-case 格式(如 app/currentRoute)。使用 utils/chromeStorage.ts 中的类型安全封装(storageUtil.get/set/remove)。
Router 同时使用 chrome.storage.local 持久化和 localStorage 快照来消除首屏闪烁。
扩展通信
使用 @webext-core/messaging。通信协议在 utils/messages.ts 中通过 ProtocolMap 定义。使用该模块导出的 sendMessage / onMessage,不要直接使用原生 chrome.runtime.sendMessage。
页面组件模式
功能页面遵循 UI + Hook 分离 模式:
pages/FeatureName/
├── index.tsx # UI 组件(纯展示,使用 shadcn/ui 组件)
├── useFeatureName.ts # 业务逻辑 Hook(状态管理 + 转换逻辑)
└── constants.ts # 常量定义(可选)
- 页面组件调用
useLazyTranslation('featureName')获取翻译函数 - Hook 负责所有状态管理,通过返回值暴露给页面
- 子组件可进一步拆分(如
LiveClock.tsx、ResultView.tsx)
新功能开发清单
- 在
types/storage.d.ts的PageType联合类型中添加新成员 - 在
config/features.tsx的FEATURES数组中添加配置(key、翻译键、图标、三种渲染模式组件) - 在
pages/目录创建页面组件(懒加载):index.tsx— 使用useLazyTranslation的 UI 组件useFeatureName.ts— 业务逻辑 Hookconstants.ts— 常量(可选)
- 在
i18n/locales/{zh,en}/features.json添加翻译(复杂功能可新建独立 JSON 文件) - 如需新权限,更新
wxt.config.ts的manifest.permissions - 添加对应的单元测试
关键规范
完整的代码编写规范详见 CODING_STANDARDS.md。
浏览器 API
始终使用 wxt/browser 导出的 browser 对象,而非原生 chrome API,以确保跨浏览器兼容性。
路径别名
@/ 映射到项目根目录(已在 tsconfig 和 vitest.config 中配置)。跨目录导入使用 @/ 绝对别名,同目录导入使用 ./ 相对路径。
UI 组件
- 使用
components/ui/下的 shadcn/ui 组件(button、dialog、select、input 等) - 图标:
lucide-react - 样式:Tailwind CSS +
@/lib/utils中的cn()工具函数(clsx + tailwind-merge) - 主题:使用 shadcn/ui 语义化 token(
bg-background、text-foreground、border-border等),禁止硬编码颜色
代码分割
wxt.config.ts 通过 manualChunksForHtmlOnly() 自动分组 vendor 依赖(vendor-react、vendor-i18n、vendor-qr、vendor-dnd、vendor-markdown),无需手动配置。
代码风格
- 禁止使用
any(测试文件除外) - 未使用的变量/参数:使用
_前缀(如_unused) - Prettier:100 字符宽、单引号、尾逗号 all、LF 换行
- ESLint 使用
typescript-eslint的projectService: true - 导出模式:页面组件 default export,工具函数/Hook 命名导出,UI 组件 forwardRef + 命名导出
测试
- 环境: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} - 使用
vi.mock()进行模块级 mock;避免重复 mockvitest.setup.ts中已有的内容 - 测试工具:
@testing-library/react+@testing-library/user-event
国际化(i18n)
- 命名空间:
common(默认)、features - 翻译键格式:
namespace:key(如features:timestamp.title) - 语言:
zh(默认)、en - 翻译文件:
i18n/locales/{zh,en}/{common,features}.json+ 各功能独立 JSON 文件 - 使用
useLazyTranslationHook 加载功能专属翻译 - 回退策略:缺失的翻译键回退到
zh,若仍缺失则返回占位格式namespace:key
WXT 生成文件
.wxt/目录由postinstall(wxt prepare)自动生成,包含 TypeScript 类型声明和扩展 tsconfig- 生产构建输出到
.output/目录 tsconfig.json继承自./.wxt/tsconfig.json