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
11 KiB
11 KiB
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[]
// 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 },
],
},
]);
// 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,写在组件文件顶部,不单独导出:
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,组间空行分隔:
- React / React Router / react-query 等核心库
- 第三方 UI / 工具库(lucide-react、clsx、zod...)
@/别名导入- 相对路径导入(
./、../)
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 模式组织:
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回调仍然可用
// 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等状态
// 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回调统一分发 toastuseQuery的 API 错误通过组件内判断error状态处理,或使用全局throwOnError+ ErrorBoundary- 禁止在组件中 try/catch API 调用后自己做 UI 错误展示
// 全局 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 |
| 箭头函数括号 | 总是保留 |
命令
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 就别用 letuseTemplate: warn — 字符串拼接改用模板字符串
Quality Gate
AI 助手完成代码修改后,必须依次运行以下命令确保通过:
bun run check # Biome 格式化 + lint
bun run build # TypeScript 类型检查 + Vite 构建
任一步骤失败不允许提交。
Testing
当前阶段 不强制写测试,快速迭代优先。后续需要时优先补关键路径的单元测试(vitest)。
Environment Variables
- 禁止在业务代码中直接读取
import.meta.env.* - 所有环境变量必须通过
@/lib/env集中访问,统一提供默认值与类型安全
// 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)