Files
appwebks/AGENTS.md
T
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

11 KiB
Raw Blame History

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 ModecreateBrowserRouter + 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 组件文件:PascalCaseButton.tsxUserList.tsx
  • Hooks / utils / types 文件:camelCaseuseAuth.tsformatDate.ts
  • 页面文件夹:PascalCaseUserProfile/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,组间空行分隔:

  1. React / React Router / react-query 等核心库
  2. 第三方 UI / 工具库(lucide-react、clsx、zod...
  3. @/ 别名导入
  4. 相对路径导入(./../
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 文件。项目只保留一个全局入口 CSSsrc/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 hooksuseQuery / 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 Codesrc/client/

  • 视为只读,禁止手动修改任何 src/client/ 下的文件
  • 需要变更时修改 openapi.jsongenapi.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 回调统一分发 toast
  • useQuery 的 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 Commitsfeat / fix / refactor / docs / test / chore
  • 原子提交:一个 commit 只做一件事
  • 禁止 force push 到 main

FormatterBiome

项目使用 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 就别用 let
  • useTemplate: 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