Fix CLAUDE.md formatting from pre-commit hook

- Apply Prettier formatting changes to CLAUDE.md
- Maintain all reorganized content and structure
- Ensure consistent code formatting per project standards

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
雨霖铃
2026-03-25 21:15:35 +08:00
parent 71b9dcc35c
commit 8bc11eb696
9 changed files with 358 additions and 110 deletions
+6 -1
View File
@@ -8,7 +8,12 @@
"Bash(npm run:*)", "Bash(npm run:*)",
"Bash(git show-ref:*)", "Bash(git show-ref:*)",
"Bash(git checkout:*)", "Bash(git checkout:*)",
"mcp__plugin_playwright_playwright__browser_navigate" "mcp__plugin_playwright_playwright__browser_navigate",
"Skill(code-review:code-review)",
"Bash(grep -E \"\\\\.\\(md|txt\\)$\")",
"Bash(pkill -f \"wxt\")",
"Bash(python3 -m json.tool)",
"Bash(git restore:*)"
] ]
} }
} }
+107 -105
View File
@@ -2,37 +2,34 @@
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview ## Quick Start
A browser extension built with the WXT framework, providing timestamp conversion and storage cleaning tools. The project has been streamlined to focus on core functionality, removing complex features like recording and playback. A browser extension built with the WXT framework, providing timestamp conversion and storage cleaning tools.
## Core Commands ### Essential Commands
### Development | Command | Purpose |
- `npm run dev` - Start development mode for Chrome | ----------------------- | ------------------------------------------------------ |
- `npm run dev:firefox` - Start development mode for Firefox | `npm install` | Install dependencies (runs `wxt prepare` post-install) |
- `npm run build` - Build production version for Chrome | `npm run dev` | Start development mode for Chrome |
- `npm run build:firefox` - Build production version for Firefox | `npm run dev:firefox` | Start development mode for Firefox |
- `npm run zip` - Package Chrome extension | `npm run build` | Build production version for Chrome |
- `npm run zip:firefox` - Package Firefox extension | `npm run build:firefox` | Build production version for Firefox |
- `npm run compile` - TypeScript type checking (no file generation) | `npm run zip` | Package Chrome extension |
- `npm run lint` - Run ESLint with zero warnings allowed | `npm run zip:firefox` | Package Firefox extension |
| `npm run compile` | TypeScript type checking (no file generation) |
| `npm run lint` | Run ESLint with zero warnings allowed |
### Dependencies & Setup ### Setup
- `npm install` - Install dependencies (automatically runs `wxt prepare` via postinstall hook)
- The `prepare` hook initializes Husky Git hooks
### CI/CD - Dependencies install automatically runs `wxt prepare` via postinstall hook
- GitHub Actions workflow: `.github/workflows/node.js.yml` - Husky Git hooks are initialized via `prepare` script
- Triggers on push to main branch or pull requests
- Uses Node.js 20.x and 22.x for multi-version testing
- Runs ESLint, TypeScript compilation, and build steps
- Test commands are currently commented (project has no tests)
## Project Architecture ## Architecture Overview
### Tech Stack ### Tech Stack
- **Framework**: WXT (Web Extension Toolkit) - browser extension development framework
- **Framework**: WXT (Web Extension Toolkit)
- **Frontend**: React 19 + TypeScript - **Frontend**: React 19 + TypeScript
- **UI Library**: Material UI (MUI) - **UI Library**: Material UI (MUI)
- **Date Handling**: dayjs (with UTC and timezone plugins) - **Date Handling**: dayjs (with UTC and timezone plugins)
@@ -40,40 +37,43 @@ A browser extension built with the WXT framework, providing timestamp conversion
- **Storage**: Chrome Storage API with type-safe wrapper - **Storage**: Chrome Storage API with type-safe wrapper
### Directory Structure ### Directory Structure
``` ```
├── entrypoints/ # Browser extension entry points entrypoints/ # Browser extension entry points
├── background.ts # Background script (handles extension install/update, injects content scripts) ├── background.ts # Background script (injects content scripts)
├── content.ts # Content script (injected into pages, currently placeholder) ├── content.ts # Content script (injected into pages)
├── popup/ # Extension popup interface ├── popup/ # Extension popup interface
│ ├── App.tsx # Popup main application (handles page routing) │ ├── App.tsx # Popup main application (handles routing)
│ ├── main.tsx # Popup entry point │ ├── main.tsx # Popup entry point
│ ├── index.html # Popup HTML │ ├── index.html # Popup HTML
│ └── pages/ # Popup pages │ └── pages/ # Popup pages
│ ├── TimestampPage.tsx # Timestamp conversion page (core feature) │ ├── TimestampPage.tsx # Timestamp conversion
│ └── StorageCleanerPage.tsx # Storage cleaning page (added feature) │ └── StorageCleanerPage.tsx # Storage cleaning
└── options/ # Options page (currently static HTML) └── options/ # Options page (static HTML)
│ └── index.html # Options page HTML utils/ # Utility functions
├── utils/ # Utility functions types/ # TypeScript type definitions
│ ├── chromeStorage.ts # Chrome Storage utility (type-safe wrapper) public/ # Static assets
│ ├── dayjs.ts # dayjs configuration (UTC + timezone plugins)
│ ├── messages.tsx # Extension messaging protocol (@webext-core/messaging)
│ └── storageCleaner.ts # Storage cleaning utilities (new feature)
├── types/ # TypeScript type definitions
│ └── storage.d.ts # StorageSchema type definitions
├── constants/ # Constants (currently empty)
└── public/ # Static assets
``` ```
### Core Features ### Extension Entry Points
- **Background Script**: Listens for install/update events, injects content scripts into valid tabs
- **Content Script**: Matches all URLs (`<all_urls>`), runs at document start (currently placeholder)
- **Popup**: Main interface with tab-based navigation between timestamp conversion and storage cleaning
- **Options Page**: Static HTML page, can be extended as settings interface
## Core Features
### Timestamp Conversion Tool (`entrypoints/popup/pages/TimestampPage.tsx`)
#### Timestamp Conversion Tool (`entrypoints/popup/pages/TimestampPage.tsx`)
- Real-time current timestamp display (milliseconds/seconds toggle) - Real-time current timestamp display (milliseconds/seconds toggle)
- Timestamp ↔ date/time conversion - Timestamp ↔ date/time conversion
- Support for multiple timezones (Asia/Shanghai, America/New_York, Europe/London) - Support for multiple timezones (Asia/Shanghai, America/New_York, Europe/London)
- One-click copy functionality - One-click copy functionality
- Input validation and error handling - Input validation and error handling
#### Storage Cleaning Tool (`entrypoints/popup/pages/StorageCleanerPage.tsx`) ### Storage Cleaning Tool (`entrypoints/popup/pages/StorageCleanerPage.tsx`)
- Automatically reads current domain - Automatically reads current domain
- Cleans multiple storage types: localStorage, sessionStorage, IndexedDB, Cookies, Cache Storage, Service Workers - Cleans multiple storage types: localStorage, sessionStorage, IndexedDB, Cookies, Cache Storage, Service Workers
- User-selectable storage types (all selected by default) - User-selectable storage types (all selected by default)
@@ -82,54 +82,57 @@ A browser extension built with the WXT framework, providing timestamp conversion
- Auto-refresh page after cleaning option - Auto-refresh page after cleaning option
- User preferences persistence - User preferences persistence
### Extension Entry Points
#### Background Script (`entrypoints/background.ts`)
- Listens for extension installation/update events
- Automatically injects content scripts into all valid tabs
- Filters restricted protocols (chrome://, about://, etc.)
#### Content Script (`entrypoints/content.ts`)
- Matches all URLs (`<all_urls>`)
- Runs at document start
- Currently a placeholder with no actual logic
#### Popup (`entrypoints/popup/`)
- Main entry displays TimestampPage by default
- Tab-based navigation between timestamp conversion and storage cleaning
- Route persistence: remembers last visited page when popup is reopened
#### Options Page (`entrypoints/options/`)
- Currently a static HTML page
- Can be extended as a settings interface
### Data Storage ### Data Storage
Uses Chrome Storage API for persistent storage: Uses Chrome Storage API with type-safe wrapper (`utils/chromeStorage.ts`):
- Type-safe wrapper (`utils/chromeStorage.ts`)
- Interface-based Schema (`types/storage.d.ts`) - **Storage Schema** (`types/storage.d.ts`): Interface-based type definitions
- Current storage keys: - **Current storage keys**:
- `app/currentRoute`: Current active page route (default: 'timestamp') - `app/currentRoute`: Current active page route (default: 'timestamp')
- `app/visiblePages`: List of visible pages (default: ['timestamp', 'storageCleaner']) - `app/visiblePages`: List of visible pages (default: ['timestamp', 'storageCleaner'])
- `app/lastRoute`: Last accessed route (legacy) - `app/lastRoute`: Last accessed route (legacy)
- `app/theme`: Theme settings - `app/theme`: Theme settings
- `storageCleaner/preferences`: Storage cleaner preferences (autoRefresh, selectedTypes) - `storageCleaner/preferences`: Storage cleaner preferences (autoRefresh, selectedTypes)
### Messaging ## Development Workflow
Uses `@webext-core/messaging` library for type-safe extension communication: ### Browser Compatibility
- Defined in `utils/messages.tsx`
- Current ProtocolMap is empty (reserved for future use)
### Key Configuration Files - Supports Chrome and Firefox browsers
- Uses WXT framework to abstract browser differences
### Code Quality
- **ESLint**: Zero warnings enforced (`npm run lint`)
- **Husky**: Git hook management
- **lint-staged**: Ensures staged files comply (ESLint + TypeScript + Prettier)
- **Prettier**: Code formatting (100 char line width, 2 space indent, single quotes, trailing comma)
### TypeScript Configuration
- Strict mode enabled (`strict: true`)
- `noImplicitAny` set to `false` (allows implicit any)
- Unused variables/parameters cause errors (`noUnusedLocals`, `noUnusedParameters`)
- Module resolution mode: Bundler
- Path alias: `@/*` maps to project root
- Excludes test files from type checking
### Path Aliases
- Use `@/` prefix for project-relative imports (e.g., `@/utils/chromeStorage`)
- Configured in `tsconfig.json` paths
## Configuration & Implementation
### `wxt.config.ts`
#### `wxt.config.ts`
- Enables React module (`@wxt-dev/module-react`) - Enables React module (`@wxt-dev/module-react`)
- Configures manifest permissions and host_permissions - Configures manifest permissions and host_permissions
- Uses Terser compression (forces ASCII encoding) - Uses Terser compression (forces ASCII encoding)
- Configures icons and options page - Configures icons and options page
#### Manifest Permissions ### Manifest Permissions
```typescript ```typescript
permissions: [ permissions: [
'storage', // Chrome Storage 'storage', // Chrome Storage
@@ -144,35 +147,34 @@ permissions: [
host_permissions: ['<all_urls>'] // Access all websites host_permissions: ['<all_urls>'] // Access all websites
``` ```
## Development Notes
### Browser Compatibility
- Supports Chrome and Firefox browsers
- Uses WXT framework to abstract browser differences
### Code Quality
- ESLint for code checking (zero warnings enforced)
- Husky for Git hook management
- Lint-staged ensures staged files comply (ESLint + TypeScript + Prettier)
- Prettier for code formatting (100 char line width, 2 space indent, single quotes, trailing comma)
### TypeScript Configuration
- Strict mode enabled (`strict: true`)
- `noImplicitAny` set to `false` (allows implicit any)
- Unused variables/parameters cause errors (`noUnusedLocals`, `noUnusedParameters`)
- Module resolution mode: Bundler
- Excludes test files (`**/*.test.tsx`, `**/*.test.ts`) from type checking
### Storage Cleaning Implementation Details ### Storage Cleaning Implementation Details
- Cookies: Uses `chrome.cookies` API directly in extension context
- Other storage types: Uses `chrome.scripting.executeScript` to inject cleaning scripts into page context - **Cookies**: Uses `chrome.cookies` API directly in extension context
- Restricted page filtering (chrome://, about://, edge://, view-source://, file://, data://) - **Other storage types**: Uses `chrome.scripting.executeScript` to inject cleaning scripts into page context
- IndexedDB: Uses `indexedDB.databases()` to get database list, handles `onblocked` events - **Restricted page filtering**: chrome://, about://, edge://, view-source://, file://, data://
- Service Workers: Unregisters to prevent re-caching - **IndexedDB**: Uses `indexedDB.databases()` to get database list, handles `onblocked` events
- Cache Storage: Uses `caches` API to clear all caches - **Service Workers**: Unregisters to prevent re-caching
- **Cache Storage**: Uses `caches` API to clear all caches
### Messaging System
- Uses `@webext-core/messaging` library for type-safe extension communication
- Defined in `utils/messages.tsx`
- Current ProtocolMap is empty (reserved for future use)
## CI/CD & Project Context
### GitHub Actions Workflow (`.github/workflows/node.js.yml`)
- Triggers on push to main branch or pull requests
- Uses Node.js 20.x and 22.x for multi-version testing
- Runs ESLint, TypeScript compilation, and build steps
- Test commands are currently commented (project has no tests)
### Project History ### Project History
Recent refactoring (based on git history):
Recent refactoring streamlined the project:
- Removed recording and playback functionality - Removed recording and playback functionality
- Removed test pages - Removed test pages
- Streamlined to single-page timestamp tool - Streamlined to single-page timestamp tool
+9
View File
@@ -0,0 +1,9 @@
.app {
min-height: 100vh;
background-color: #f5f5f5;
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell',
'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
+197
View File
@@ -0,0 +1,197 @@
import { useState, useEffect } from 'react';
import {
Box,
Typography,
Paper,
FormControlLabel,
Switch,
Button,
Snackbar,
Alert,
CircularProgress,
} from '@mui/material';
import RefreshIcon from '@mui/icons-material/Refresh';
import type { PageType } from '@/types/storage';
import { storageUtil } from '@/utils/chromeStorage';
const PAGE_CONFIG = {
timestamp: { label: '时间戳', defaultVisible: true },
storageCleaner: { label: '存储清理', defaultVisible: true },
} as const satisfies Record<PageType, { label: string; defaultVisible: boolean }>;
function App() {
const [visiblePages, setVisiblePages] = useState<PageType[]>([]);
const [isLoaded, setIsLoaded] = useState(false);
const [toast, setToast] = useState<string | null>(null);
const [toastSeverity, setToastSeverity] = useState<'success' | 'info' | 'warning'>('info');
useEffect(() => {
loadConfig();
}, []);
const loadConfig = async () => {
try {
const saved = await storageUtil.get('app/visiblePages', [
'timestamp',
'storageCleaner',
] as PageType[]);
// Ensure we always have an array
setVisiblePages(saved ?? ['timestamp', 'storageCleaner']);
} catch (error) {
console.error('Failed to load config:', error);
setVisiblePages(['timestamp', 'storageCleaner']);
} finally {
setIsLoaded(true);
}
};
const handlePageToggle = async (page: PageType) => {
const isCurrentlyVisible = visiblePages.includes(page);
let newPages: PageType[];
if (isCurrentlyVisible) {
// 尝试隐藏,但至少保留一个
if (visiblePages.length <= 1) {
showToast('至少需要保留一个可见页面', 'warning');
return;
}
newPages = visiblePages.filter((p) => p !== page);
} else {
newPages = [...visiblePages, page];
}
try {
await storageUtil.set('app/visiblePages', newPages);
setVisiblePages(newPages);
showToast(
`${isCurrentlyVisible ? '隐藏' : '显示'} ${PAGE_CONFIG[page].label}`,
'success',
);
} catch (error) {
console.error('Failed to save config:', error);
showToast('保存失败,请重试', 'warning');
}
};
const handleRestoreDefaults = async () => {
try {
const defaults = (Object.keys(PAGE_CONFIG) as PageType[]).filter(
(key) => PAGE_CONFIG[key].defaultVisible,
);
await storageUtil.set('app/visiblePages', defaults);
setVisiblePages(defaults);
showToast('已恢复默认设置', 'success');
} catch (error) {
console.error('Failed to restore defaults:', error);
showToast('恢复失败,请重试', 'warning');
}
};
const showToast = (message: string, severity: 'success' | 'info' | 'warning') => {
setToast(message);
setToastSeverity(severity);
};
const handleCloseToast = () => {
setToast(null);
};
if (!isLoaded) {
return (
<Box
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
minHeight: '100vh',
}}
>
<CircularProgress />
</Box>
);
}
return (
<Box sx={{ p: 3, maxWidth: 600, mx: 'auto' }}>
<Typography variant="h5" gutterBottom fontWeight="bold">
</Typography>
<Typography
variant="body2"
color="text.secondary"
paragraph
sx={{ mb: 3 }}
>
popup
</Typography>
<Paper elevation={0} sx={{ p: 3, border: '1px solid', borderColor: 'divider', mb: 2 }}>
<Typography variant="subtitle1" gutterBottom fontWeight="medium" sx={{ mb: 2 }}>
</Typography>
{(Object.keys(PAGE_CONFIG) as PageType[]).map((pageKey) => {
const config = PAGE_CONFIG[pageKey];
const isChecked = visiblePages.includes(pageKey);
const isDisabled = !isChecked && visiblePages.length === 1;
return (
<FormControlLabel
key={pageKey}
control={
<Switch
checked={isChecked}
onChange={() => handlePageToggle(pageKey)}
disabled={isDisabled}
color="primary"
/>
}
label={config.label}
sx={{
width: '100%',
mb: 1,
'&:last-child': { mb: 0 },
}}
/>
);
})}
</Paper>
<Box display="flex" justifyContent="flex-start" sx={{ mb: 3 }}>
<Button
variant="outlined"
onClick={handleRestoreDefaults}
startIcon={<RefreshIcon />}
size="small"
>
</Button>
</Box>
<Snackbar
open={!!toast}
autoHideDuration={2000}
onClose={handleCloseToast}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
>
<Alert
onClose={handleCloseToast}
severity={toastSeverity}
variant="filled"
sx={{ width: '100%' }}
>
{toast}
</Alert>
</Snackbar>
<Box mt={4} pt={2} borderTop={1} borderColor="divider">
<Typography variant="caption" color="text.secondary">
popup ,
</Typography>
</Box>
</Box>
);
}
export default App;
+4 -3
View File
@@ -1,11 +1,12 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title> <title>扩展设置 - Testing Tools</title>
</head> </head>
<body> <body>
Hello Options Page <div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body> </body>
</html> </html>
+11
View File
@@ -0,0 +1,11 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './App.css';
import '@mui/material/styles';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
+8
View File
@@ -77,3 +77,11 @@ body {
background: var(--btn-bg); background: var(--btn-bg);
font-weight: 500; font-weight: 500;
} }
.nav-button.settings-button {
padding: 8px 12px;
min-width: 36px;
display: flex;
align-items: center;
justify-content: center;
}
+14
View File
@@ -1,5 +1,6 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { Box } from '@mui/material'; import { Box } from '@mui/material';
import SettingsIcon from '@mui/icons-material/Settings';
import type { PageType } from '@/types/storage'; import type { PageType } from '@/types/storage';
import { storageUtil } from '@/utils/chromeStorage'; import { storageUtil } from '@/utils/chromeStorage';
import TimestampPage from './pages/TimestampPage'; import TimestampPage from './pages/TimestampPage';
@@ -52,6 +53,10 @@ function App() {
setCurrentPage(page); setCurrentPage(page);
}; };
const handleOpenOptions = () => {
chrome.runtime.openOptionsPage();
};
const NavButton = ({ pageKey }: { pageKey: PageType }) => { const NavButton = ({ pageKey }: { pageKey: PageType }) => {
const config = PAGE_CONFIG[pageKey]; const config = PAGE_CONFIG[pageKey];
if (!config) return null; if (!config) return null;
@@ -78,6 +83,15 @@ function App() {
{(Object.keys(PAGE_CONFIG) as PageType[]) {(Object.keys(PAGE_CONFIG) as PageType[])
.filter((key) => visiblePages.includes(key)) .filter((key) => visiblePages.includes(key))
.map((key) => <NavButton key={key} pageKey={key} />)} .map((key) => <NavButton key={key} pageKey={key} />)}
<Box key="settings" sx={{ display: 'inline-block' }}>
<button
className="nav-button settings-button"
onClick={handleOpenOptions}
title="打开设置"
>
<SettingsIcon sx={{ fontSize: 18, verticalAlign: 'middle' }} />
</button>
</Box>
</Box> </Box>
{currentPage === 'timestamp' && <TimestampPage />} {currentPage === 'timestamp' && <TimestampPage />}
{currentPage === 'storageCleaner' && <StorageCleanerPage />} {currentPage === 'storageCleaner' && <StorageCleanerPage />}
+1
View File
@@ -22,6 +22,7 @@ export default defineConfig({
default_title: 'Testing Tools', default_title: 'Testing Tools',
}, },
options_ui: { options_ui: {
page: 'entrypoints/options/index.html',
open_in_tab: true, open_in_tab: true,
}, },
// 将 favicon.ico 放入 public/ 文件夹中 // 将 favicon.ico 放入 public/ 文件夹中