Files
zhenyi d7c4bc7c8e feat(schemas): add repository settings and user restore token fields
- Add allow_forking, allow_merge_commit, allow_rebase_merge, allow_squash_merge fields to repo schemas
- Add delete_branch_on_merge field to repository models and schemas
- Add has_issues, has_pull_requests, has_wiki, homepage fields to repo schemas
- Add topics array field to repository schemas and models
- Add restore_token_expires_at and restore_token_hash fields to user schemas
- Remove UserAvatarResponse and UploadUserAvatarParams schemas completely
- Update CreateRepoParams and UpdateRepoParams with new repository settings
- Modify CreateTemplateParams and UpdateTemplateParams with notification template fields
- Remove description from SetBranchProtectionParams schema
- Delete App.css and auth.css files completely
- Update App.tsx with routing migration notes
2026-06-11 23:12:24 +08:00

339 lines
11 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.
## Language
Always respond in Chinese (中文). Use the user's language for all conversations and explanations. Code, commands, and technical terms can remain in English.
页面内容(UI 文案、标签、提示、错误信息)默认使用 **英语**。仅当用户明确要求时才做多语言。
# Project Guidelines — appwebks
## Tech Stack
- **Runtime**: Bun (packageManager, scripts, lockfile)
- **Framework**: React 19, TypeScript 6.0, Vite 8.0
- **Styling**: Tailwind CSS 4.3 + clsx + tailwind-mergeCVA 变体模式)
- **State**: @tanstack/react-query v5(服务端状态)+ URL search/path params(客户端状态)
- **Routing**: react-router-dom v7
- **HTTP**: ky(优先)+ axios
- **Validation**: zod v4
- **Icons**: lucide-react
## Project Structure
```
src/
pages/ # 页面组件,每个 page 一个文件夹
components/ # 共享组件,按功能域分文件夹
hooks/ # 自定义 hooks(含 react-query hooks
client/ # 自动生成 API 代码(只读!)
lib/ # 工具函数、常量
types/ # 共享类型定义
```
## Routing
使用 React Router v7 **Data Mode**`createBrowserRouter` + Route Object 配置。
- 路由配置集中在 `src/routes.ts`,通过 `createBrowserRouter` 组装
- 每个路由模块(Component + loader + action)为独立文件,放在 `src/pages/`
- 路由文件不容纳 JSX,只 export RouteObject[]
```ts
// src/routes.ts — 路由配置,只组装不渲染
import { createBrowserRouter } from 'react-router';
import { RepoListPage } from '@/pages/RepoList/RepoListPage';
import { RepoDetailPage } from '@/pages/RepoDetail/RepoDetailPage';
import { RootLayout } from '@/pages/RootLayout';
export const router = createBrowserRouter([
{
path: '/',
Component: RootLayout,
children: [
{ index: true, Component: RepoListPage },
{ path: 'repo/:id', Component: RepoDetailPage },
],
},
]);
```
```tsx
// src/pages/RepoList/RepoListPage.tsx — 路由模块
export function RepoListPage() {
const { data } = useRepoList();
return <div>...</div>;
}
```
## Code Style
### File Naming
- React 组件文件:**PascalCase**`Button.tsx``UserList.tsx`
- Hooks / utils / types 文件:**camelCase**`useAuth.ts``formatDate.ts`
- 页面文件夹:**PascalCase**`UserProfile/``RepoList/`
### Component Conventions
- 组件用 **function 声明**,不用 arrow function
- Props 类型用 **interface**,写在组件文件顶部,不单独导出:
```tsx
interface ButtonProps {
size?: 'sm' | 'md' | 'lg';
variant?: 'primary' | 'secondary';
children: React.ReactNode;
}
export function Button({ size = 'md', variant = 'primary', children }: ButtonProps) {
// ...
}
```
- 原生 HTML 元素 Props 用 `React.ComponentProps<'button'>` 派生
- 单文件不超过 **200 行**,函数不超过 **50 行**
- 复杂逻辑抽成 hooks
### Import Order
Biome 的 `organizeImports` 会自动按以下顺序排列 import,组间空行分隔:
1. React / React Router / react-query 等核心库
2. 第三方 UI / 工具库(lucide-react、clsx、zod...
3. `@/` 别名导入
4. 相对路径导入(`./`、`../`
```tsx
import { useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { Link } from 'react-router-dom';
import { Search, Plus } from 'lucide-react';
import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
import { repoService } from '@/client/services/RepoService';
import { Button } from '@/components/ui/Button';
import { useRepoList } from './useRepoList';
```
## Styling
### Tailwind + CVA
用 `clsx` + `tailwind-merge` 管理组件变体,按 CVA 模式组织:
```tsx
import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
function cn(...inputs: (string | undefined | false | null)[]) {
return twMerge(clsx(inputs));
}
const variants = {
size: {
sm: 'px-2 py-1 text-sm',
md: 'px-4 py-2 text-base',
lg: 'px-6 py-3 text-lg',
},
variant: {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300',
},
} as const;
interface ButtonProps {
size?: keyof typeof variants.size;
variant?: keyof typeof variants.variant;
}
export function Button({ size = 'md', variant = 'primary', ... }: ButtonProps) {
return <button className={cn(variants.size[size], variants.variant[variant])} ... />;
}
```
- **禁止新增任何 `.css` 文件**。项目只保留一个全局入口 CSS(`src/index.css`),仅用于:
- `@import "tailwindcss"`
- CSS 自定义属性(主题变量)
- 全局 reset
- 组件样式**必须**使用 Tailwind 原子类,不得引用局部 CSS 文件
- 禁止 `@apply` 抽取类名
- `cn()` 函数放在 `@/lib/cn.ts` 中统一导入
## State Management
### URL-Driven State
- UI 状态(分页、筛选、排序、tab 切换)**必须**编码到 URL search/path params
- 用 `useSearchParams()` 读写,组件内部 `useState` 最小化
- 好处:可分享、可书签、浏览器前进后退自然工作
### Server State (react-query)
- 所有 API 调用必须通过 react-query hooks`useQuery` / `useMutation`
- 禁止在组件中直接调用 `src/client/` 的服务方法
- Query key 约定:`['domain', 'action', ...params]`,如 `['repos', 'list', { page, q }]`
**注意**TanStack Query v5 已移除 `useQuery` 上的 `onError` / `onSuccess` / `onSettled` 回调:
- `useQuery` 错误处理:组件内检查 `error` 状态,或全局通过 `QueryClient.setDefaultOptions` 配置
- `useMutation` 错误处理:`onError` 回调仍然可用
```tsx
// useQuery — 错误在 error 字段上处理
import { useQuery } from '@tanstack/react-query';
const { data, error, isLoading } = useQuery({
queryKey: ['repos', 'list', { page }],
queryFn: () => repoService.listRepos({ page }),
});
// useMutation — onError 仍然可用
import { useMutation } from '@tanstack/react-query';
import { toast } from 'sonner';
const mutation = useMutation({
mutationFn: (params) => repoService.createRepo(params),
onError: (error) => toast.error(error.message),
});
```
## API Layer
### Generated Codesrc/client/
- **视为只读**,禁止手动修改任何 `src/client/` 下的文件
- 需要变更时修改 `openapi.json` 或 `genapi.js`,然后运行 `bun run genapi` 重新生成
### Custom Hooks 封装
每个业务域在 `src/hooks/` 下创建对应的 react-query hook 文件:
```
src/hooks/
useRepoList.ts # useQuery: repo list + filters
useRepoDetail.ts # useQuery: single repo
useCreateRepo.ts # useMutation: create repo
useBranchList.ts # useQuery: branch list
```
hook 职责:
- 组装 queryKey(如 `['repos', 'list', { page, q }]`
- 调用 `src/client/` 服务方法
- 转换入参(筛选条件 → API 参数)、归一化错误
- **默认返回 React Query 原始结果**:不隐藏 `isPending` / `isFetching` / `error` / `refetch` 等状态
```tsx
// src/hooks/useRepoList.ts — 典型范式
import { useQuery, keepPreviousData } from '@tanstack/react-query';
import { repoService } from '@/client/services/RepoService';
export function useRepoList(filters: RepoFilters) {
return useQuery({
queryKey: ['repos', 'list', filters],
queryFn: ({ signal }) => repoService.listRepos({ ...filters }, { signal }),
placeholderData: keepPreviousData,
});
}
// 组件侧直接解构:const { data, isPending, isFetching, error, refetch } = useRepoList(filters);
```
### Error Handling
- 全局 **ErrorBoundary** 捕获 React 渲染错误
- `useMutation` 的 API 错误通过 `onError` 回调统一分发 toast
- `useQuery` 的 API 错误通过组件内判断 `error` 状态处理,或使用全局 `throwOnError` + ErrorBoundary
- 禁止在组件中 try/catch API 调用后自己做 UI 错误展示
```tsx
// 全局 QueryClient 配置(在 App 入口)
import { QueryClient } from '@tanstack/react-query';
import { toast } from 'sonner';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
throwOnError: true, // Query 抛错 → ErrorBoundary 捕获
},
mutations: {
onError: (error) => toast.error(error.message),
},
},
});
```
## Git
### Branching
- **Trunk-based**:直接在 `main` 上做小而频繁的提交
- 不做长期 feature 分支,未完成功能用 feature flag 或路由守卫控制
- 需要时可以从 main 拉短期分支,但合入后立即删除
### Commits
- **Conventional Commits**`feat` / `fix` / `refactor` / `docs` / `test` / `chore`
- 原子提交:一个 commit 只做一件事
- 禁止 force push 到 main
## FormatterBiome
项目使用 **Biome** 作为唯一的格式化 & lint 工具。配置见 `biome.json`。
### 格式化规则
| 配置项 | 值 |
|--------|-----|
| 缩进 | 2 空格 |
| 引号 | 单引号(JS/TS),双引号(JSX) |
| 分号 | 总是 |
| 尾逗号 | 总是 |
| 行宽 | 100 |
| 换行符 | LF |
| 箭头函数括号 | 总是保留 |
### 命令
```bash
bun run format # 格式化 src/ 下所有文件
bun run lint # 仅 lint 检查,不修改
bun run check # 格式化 + lint,写入修复
```
### Lint 规则
- 启用 **recommended** 规则集
- `noUnusedVariables`: error — 禁止未使用变量
- `useHookAtTopLevel`: error — React hooks 必须在顶层调用
- `noParameterAssign`: error — 禁止给参数重新赋值
- `useConst`: error — 能用 const 就别用 let
- `useTemplate`: warn — 字符串拼接改用模板字符串
## Quality Gate
AI 助手完成代码修改后,**必须依次运行以下命令**确保通过:
```bash
bun run check # Biome 格式化 + lint
bun run build # TypeScript 类型检查 + Vite 构建
```
任一步骤失败不允许提交。
## Testing
当前阶段 **不强制写测试**,快速迭代优先。后续需要时优先补关键路径的单元测试(vitest)。
## Environment Variables
- **禁止**在业务代码中直接读取 `import.meta.env.*`
- 所有环境变量必须通过 `@/lib/env` 集中访问,统一提供默认值与类型安全
```ts
// src/lib/env.ts
export const env = {
API_BASE_URL: import.meta.env.VITE_API_BASE_URL ?? '/api',
DEV: import.meta.env.DEV,
MODE: import.meta.env.MODE,
} as const;
```
## Import Discipline
- **禁止**页面组件直接导入 `@/client/**`
- 只有 `src/hooks/**` 或 `src/features/**/api/**` 可以导入 generated client
- 页面通过自定义 hook 访问 API 数据,不接触底层 client
```
✅ src/hooks/useRepoList.ts → import { RepoService } from '@/client/services/RepoService'
✅ src/features/repos/api.ts → import { RepoService } from '@/client/services/RepoService'
❌ src/pages/RepoList.tsx → import { RepoService } from '@/client/services/RepoService'
```
## Safety
- 禁止硬编码 secrets / API keys / tokens
- 所有用户输入必须验证(zod schema)
- 错误必须显式处理,禁止 silent failure
- 禁止使用 `// ── xxxx ──────────` 分隔注释
## Reference
- `hooks.txt` — 项目已安装依赖的完整 Hooks 清单
- `biome.json` — Biome 格式化 / Lint 配置
- `openapi.json` — API Schema(用于生成 `src/client/`
- `genapi.js` — API 代码生成脚本(`bun run genapi`