chore: snapshot before P0 security phase (backup point)

This commit is contained in:
SpecialX
2026-07-07 19:12:33 +08:00
parent 3f68f3eb09
commit 747344bfe3
130 changed files with 18879 additions and 6615 deletions

View File

@@ -69,7 +69,7 @@
| 文档 | 归档原因 |
|------|---------|
| [002 RBAC 重构方案](architecture/002_rbac_refactoring.md) | 描述修复前的安全隐患,当前所有 Server Action 已接入 `requirePermission()` |
| [002 角色路由 RFC](architecture/002_role_based_routing.md) | 2025-12-23 提案,当前角色域路由已全部实现 |
| [002b 角色路由 RFC](architecture/002b_role_based_routing.md) | 2025-12-23 提案,当前角色域路由已全部实现 |
| [003 UI 重构计划](architecture/003_ui_refactoring_plan.md) | 2026-06-16 重构计划,当前已执行完毕 |
### 设计历史文档

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,10 @@
# 历史审查报告归档
> 本目录为只读归档,不再更新。
> 有价值的内容已提取到模块 README 和 known-issues.md。
## 归档文件
- 005_architecture_data.json已废弃由 arch.db 替代)
- 60+ 份模块审查报告(历史参考)
- data-access-audit-v1 系列文件(数据访问层审查)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,16 @@
# 路线图索引
> 本目录存放项目长远规划与架构事实004分离。
## 文档清单
| 文档 | 用途 |
|------|------|
| [tech-debt.md](./tech-debt.md) | 技术债清单 |
| [decoupling.md](./decoupling.md) | 解耦路线图 |
| [pending-features.md](./pending-features.md) | 待开发功能 |
## 维护规则
- 规划实现后从本目录删除,迁入 004 架构事实或 git 历史
- 不含架构事实,不含经验(经验查 known-issues.md

View File

@@ -0,0 +1,7 @@
# 待开发功能
> 从 004 各模块"未完成项"迁入。
## 待开发功能清单
(迁入后填充)

View File

@@ -0,0 +1,11 @@
# 技术债清单
> 从 004 第三部分"已知架构问题和技术债"迁入。
## 待解决项
(从 004 迁入后填充)
## 已解决项
已解决项删除git 历史已记录)

View File

@@ -194,7 +194,11 @@ export function UserCard({ user, onSelect, children }: UserCardProps): JSX.Eleme
### 4.1 配置tsconfig.json
当前项目配置需升级以符合规范。**目标配置**
当前项目配置已基本符合规范(`target: ES2022``noImplicitReturns: true``noFallthroughCasesInSwitch: true`)。
**待办**`noUncheckedIndexedAccess` 当前为 `false`(数组/对象索引不返回 `T | undefined`),逐步迁移后启用。具体配置见 `tsconfig.json`
**目标配置**
```json
{
@@ -221,12 +225,6 @@ export function UserCard({ user, onSelect, children }: UserCardProps): JSX.Eleme
}
```
**当前差异**(需逐步升级):
- `target`: `ES2017``ES2022`
- 缺失 `noUncheckedIndexedAccess`(数组/对象索引返回 `T | undefined`
- 缺失 `noImplicitReturns`(函数所有分支必须返回)
- 缺失 `noFallthroughCasesInSwitch`
### 4.2 类型规则
1. **禁止 `any`**:未知类型用 `unknown` 并做类型守卫。若极特殊情况必须使用,需 `// eslint-disable-next-line @typescript-eslint/no-explicit-any` 并注释原因
@@ -366,23 +364,22 @@ import { cn } from "@/shared/lib/utils";
### 6.3 设计令牌配置
本项目在 `src/app/globals.css` 中使用 CSS 变量定义设计令牌:
本项目在 `src/app/styles/tokens/` 中使用 CSS 变量定义设计令牌(分层架构)
```css
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--destructive: 0 84.2% 60.2%;
--border: 240 5.9% 90%;
--radius: 0.5rem;
/* ... */
}
```
- `primitive.css`: 原始色板/字号/间距/阴影Layer 1业务代码不直接引用
- `semantic-light.css` + `semantic-dark.css`: 语义令牌Layer 2业务代码唯一引用入口
- `lesson-preparation.css`: `--lp-*` 令牌(明暗双份,模块命名空间)
- `tailwind-theme.css`: `@theme inline` 将 Semantic 令牌暴露为 Tailwind 类
- `index.css`: 入口文件(汇总 @import
**所有视觉设计决策**(颜色、字号、间距)必须体现在设计令牌中,组件中不使用硬编码值。
**ESLint 强制约束**
- `no-restricted-syntax`: 禁止 `#hex` 颜色字面量
- `design-tokens/no-hardcoded-fonts`: 禁止 `'Inter'`/`'Fraunces'`/`'JetBrains Mono'` 字面量(单词边界匹配,不影响 `Interval`/`Interactive` 等标识符)
- 白名单:`primitive.css`(令牌定义)、`email-channel.ts`(邮件 HTML`manifest.ts`PWA
- 任意值豁免需注释:`// eslint-disable-next-line no-restricted-syntax -- <reason>`
---
## 七、数据获取与状态管理
@@ -400,7 +397,7 @@ import { cn } from "@/shared/lib/utils";
**规则**
- 服务端数据获取通过模块的 `data-access.ts` 函数
- 客户端动态数据统一使用 **TanStack Query v5+****禁止在 `useEffect` 中手写 fetch**
- 缓存策略:服务端请求必须显式设置 `next.revalidate`使用 `unstable_cache`,并注释缓存时长理由
- 缓存策略:服务端请求使用 `cacheFn`(封装 React `cache()` + 自定义缓存层),详见 `shared/lib/cache/`;不使用 `unstable_cache`(权限数据易变,跨请求缓存风险高于收益)
### 7.2 Server Actions
@@ -470,18 +467,23 @@ export async function createExamAction(
### 7.3 状态管理
| 场景 | 方案 |
|------|------|
| 局部 UI 状态 | `useState` / `useReducer` |
| 跨组件共享(小范围) | React Context |
| 跨组件共享(大范围) | Zustand轻量、可选择订阅 |
| 全局状态 | 仅存放真正全局必要数据(认证信息、主题、通知列表) |
| URL 状态 | `nuqs`(已集成) |
本项目采用 **5 层状态模型**
| 层级 | 场景 | 方案 |
|------|------|------|
| L1 URL | 可分享、可刷新的状态(分页、筛选、排序) | `nuqs` |
| L2 Server | 服务端数据 | TanStack Query |
| L3 Client Business | 客户端业务状态 | Zustand slice |
| L4 Global UI | 全局 UI 状态(弹窗、主题) | Zustand ui-store + ModalRoot |
| L5 Form | 表单状态 | react-hook-form + zodResolver |
**规则**
- Context 拆分:一个 Context 只负责一类数据,避免无关状态变化引发不必要的渲染
- 业务数据一律通过路由参数或 TanStack Query 获取,**不存入全局状态**
- Zustand 的 `persist` 中间件必须处理版本迁移和敏感数据加密
- 优先使用细粒度 Zustand selectors单字段 selector而非 `useShallow` 多字段包装
- 乐观更新使用 React 19 `useOptimistic` + `useTransition`,替代手动 `isPending` 状态
- `useOptimistic``addOptimistic` 必须在 transition 或 action 内调用form action 也算 action
---
@@ -700,52 +702,19 @@ test(checkout): cover discount edge cases
## 十五、统一工具配置
### 15.1 ESLint(当前配置 + 建议增强)
### 15.1 ESLint
**当前配置**`eslint.config.mjs`
**当前配置**`eslint.config.mjs`已实现以下规则
```javascript
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
- `no-restricted-syntax`: 禁止 `#hex` 颜色字面量
- `design-tokens/no-hardcoded-fonts`: 禁止 `'Inter'`/`'Fraunces'`/`'JetBrains Mono'` 字面量(单词边界匹配,不影响 `Interval`/`Interactive` 等标识符)
- 白名单:`primitive.css`(令牌定义)、`email-channel.ts`(邮件 HTML`manifest.ts`PWA
- `@typescript-eslint/no-explicit-any`: error
- `react/react-in-jsx-scope`: off
- `react/function-component-definition`: function-declaration
- `import/order`: 强制分组排序
const eslintConfig = defineConfig([
...nextVitals,
...nextTs,
{
rules: {
"react-hooks/incompatible-library": "off",
},
},
// ...
]);
export default eslintConfig;
```
**建议增强**(待逐步集成):
```javascript
{
extends: [
"next/core-web-vitals",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:jsx-a11y/recommended",
"prettier"
],
rules: {
"@typescript-eslint/no-explicit-any": "error",
"react/react-in-jsx-scope": "off",
"react/function-component-definition": [2, { "namedComponents": "function-declaration" }],
"import/order": ["error", {
"groups": ["builtin", "external", "internal", "parent", "sibling", "index", "type"],
"newlines-between": "always"
}]
}
}
```
(具体配置见 `eslint.config.mjs`
### 15.2 Prettier

File diff suppressed because it is too large Load Diff