d7c4bc7c8e
- 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
339 lines
11 KiB
Markdown
339 lines
11 KiB
Markdown
## 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-merge(CVA 变体模式)
|
||
- **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 Code(src/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
|
||
|
||
## Formatter(Biome)
|
||
项目使用 **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`)
|