docs(known-issues): log enterprise architecture normalization work experience
Some checks failed
CI / scheduled-backup (push) Failing after 1m32s
CI / backup-verify (push) Has been skipped
CI / weekly-dr-drill (push) Failing after 0s
CI / build-deploy (push) Has been cancelled
CI / security-scan (push) Has been cancelled
Lighthouse CI / lighthouse (push) Has been cancelled

This commit is contained in:
SpecialX
2026-07-07 20:25:33 +08:00
parent dbb124bf17
commit e3d132dc1b
2 changed files with 226 additions and 223 deletions

View File

@@ -10,158 +10,158 @@
### 1.1 i18n
| 场景 | 规则 |
|------|------|
| key 命名 | 禁止包含 `.`,不同字段用驼峰,同字段多角色变体用嵌套对象 |
| 动态 key 调用 | `t()` 参数为动态变量时必须包含完整嵌套路径(`t(\`type.${type}\`)` 而非 `t(type)` |
| ICU 参数 | 单花括号 `{level}`,多参数直接并列 `{count} 题 · {subject}` |
| Namespace 与文件名一致性 | namespace 必须与文件名严格一致 |
| 翻译文件对称性 | zh-CN 与 en 必须同步新增/删除 key |
| 硬编码中文 | 所有用户可见文本必须走 i18nAI Prompt 中文常量允许) |
| i18n dead 节清理 | 删除组件时同步清理对应的 i18n key |
| 动态键翻译守卫 | 缺失 key 时降级返回 key 本身而非抛错 |
| 场景 | 规则 |
| ------------------------ | -------------------------------------------------------------------------------- |
| key 命名 | 禁止包含 `.`,不同字段用驼峰,同字段多角色变体用嵌套对象 |
| 动态 key 调用 | `t()` 参数为动态变量时必须包含完整嵌套路径(`t(\`type.${type}\`)`而非`t(type)` |
| ICU 参数 | 单花括号 `{level}`,多参数直接并列 `{count} 题 · {subject}` |
| Namespace 与文件名一致性 | namespace 必须与文件名严格一致 |
| 翻译文件对称性 | zh-CN 与 en 必须同步新增/删除 key |
| 硬编码中文 | 所有用户可见文本必须走 i18nAI Prompt 中文常量允许) |
| i18n dead 节清理 | 删除组件时同步清理对应的 i18n key |
| 动态键翻译守卫 | 缺失 key 时降级返回 key 本身而非抛错 |
### 1.2 Next.js 配置与运行时
| 场景 | 技术/规则 |
|------|----------|
| Node.js 服务端驱动 | `serverExternalPackages: ["mysql2"]` 标记为外部包 |
| 输出模式 | `output: "standalone"` |
| Tailwind v4 排除非源码 | `globals.css` 中 `@source not "../../docs";` 防止 docs 字符串被扫描成类 |
| 可选依赖 | 动态 import + `/* webpackIgnore: true */`(如 `@upstash/redis` |
| 环境变量驱动加载 | `if (env.DRIVER === "redis") { const { Redis } = await import(...) }` |
| next-auth useSession SSR | 客户端用 `useSession()` 时需 `SessionProvider` + 等待 `status === "authenticated"` |
| useSearchParams Hydration | 包裹 `<Suspense>` 或在 useEffect 中读取 |
| Edge Runtime 限制 | proxy.ts 不能导入 Node.js 模块,用 Web Crypto API |
| React StrictMode 与 Server Action | StrictMode 双调用不影响 Server Actionidempotent 设计) |
| 场景 | 技术/规则 |
| --------------------------------- | ---------------------------------------------------------------------------------- |
| Node.js 服务端驱动 | `serverExternalPackages: ["mysql2"]` 标记为外部包 |
| 输出模式 | `output: "standalone"` |
| Tailwind v4 排除非源码 | `globals.css` 中 `@source not "../../docs";` 防止 docs 字符串被扫描成类 |
| 可选依赖 | 动态 import + `/* webpackIgnore: true */`(如 `@upstash/redis` |
| 环境变量驱动加载 | `if (env.DRIVER === "redis") { const { Redis } = await import(...) }` |
| next-auth useSession SSR | 客户端用 `useSession()` 时需 `SessionProvider` + 等待 `status === "authenticated"` |
| useSearchParams Hydration | 包裹 `<Suspense>` 或在 useEffect 中读取 |
| Edge Runtime 限制 | proxy.ts 不能导入 Node.js 模块,用 Web Crypto API |
| React StrictMode 与 Server Action | StrictMode 双调用不影响 Server Actionidempotent 设计) |
### 1.3 Server/Client Component 边界
| 场景 | 规则 |
|------|------|
| "use server" 文件 | 只能导出 `async function`;常量放 `types.ts`/`constants.ts`;同步函数放 `lib/`;禁止值 re-export类型 re-export 允许 |
| Client Component 依赖隔离 | 不得 import server-only 文件;`import { db }` 的文件不得被客户端 import |
| Server Component 传递函数 | 禁止向 Client Component 传递函数,改传纯数据 + 在客户端调用 Server Action |
| Server Component 传递纯数据 | 序列化日期用 ISO string不用 Date 对象 |
| useState SSR/CSR 一致性 | 初始值必须可序列化且在服务端和客户端一致 |
| 场景 | 规则 |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| "use server" 文件 | 只能导出 `async function`;常量放 `types.ts`/`constants.ts`;同步函数放 `lib/`;禁止值 re-export类型 re-export 允许 |
| Client Component 依赖隔离 | 不得 import server-only 文件;`import { db }` 的文件不得被客户端 import |
| Server Component 传递函数 | 禁止向 Client Component 传递函数,改传纯数据 + 在客户端调用 Server Action |
| Server Component 传递纯数据 | 序列化日期用 ISO string不用 Date 对象 |
| useState SSR/CSR 一致性 | 初始值必须可序列化且在服务端和客户端一致 |
### 1.4 数据库与 Drizzle
| 场景 | 规则 |
|------|------|
| schema.ts 修改后 | 必须 `npm run db:push` 或 `npm run db:migrate` 同步数据库 |
| `Unknown column` 报错 | 先 `SHOW COLUMNS FROM 表名` 对比实际结构与 schema.ts |
| 迁移文件管理 | 必须录入 `_journal.json`;编号唯一不可复用;`__drizzle_migrations` 表必须记录 hash |
| 手动补迁移流程 | 创建 `.sql` → 更新 journal → 计算 sha256 插入 migrations 表 → 验证 `No new migrations to apply` |
| 字段映射 | TS 属性名与 DB 列名不同时,验证 SQL 用 DB 列名ORM 查询用 TS 属性名 |
| 子查询字段 alias | Drizzle 子查询字段必须显式 alias否则类型推断失败 |
| FULLTEXT 索引声明 | drizzle-kit 无法生成,迁移文件手动追加 `CREATE FULLTEXT INDEX` + 同步 snapshot |
| MATCH AGAINST BOOLEAN MODE | 转义特殊字符 `+ - < > ( ) ~ * " '`,转 `+word1* +word2*` 格式 |
| 场景 | 规则 |
| -------------------------- | ----------------------------------------------------------------------------------------------- |
| schema.ts 修改后 | 必须 `npm run db:push` 或 `npm run db:migrate` 同步数据库 |
| `Unknown column` 报错 | 先 `SHOW COLUMNS FROM 表名` 对比实际结构与 schema.ts |
| 迁移文件管理 | 必须录入 `_journal.json`;编号唯一不可复用;`__drizzle_migrations` 表必须记录 hash |
| 手动补迁移流程 | 创建 `.sql` → 更新 journal → 计算 sha256 插入 migrations 表 → 验证 `No new migrations to apply` |
| 字段映射 | TS 属性名与 DB 列名不同时,验证 SQL 用 DB 列名ORM 查询用 TS 属性名 |
| 子查询字段 alias | Drizzle 子查询字段必须显式 alias否则类型推断失败 |
| FULLTEXT 索引声明 | drizzle-kit 无法生成,迁移文件手动追加 `CREATE FULLTEXT INDEX` + 同步 snapshot |
| MATCH AGAINST BOOLEAN MODE | 转义特殊字符 `+ - < > ( ) ~ * " '`,转 `+word1* +word2*` 格式 |
### 1.5 状态管理
| 场景 | 技术 |
|------|------|
| 5 层状态模型 | L1 URL(nuqs) · L2 Server(TanStack Query) · L3 Client(Zustand slice) · L4 Global UI(Zustand ui-store + ModalRoot) · L5 Form(react-hook-form + zodResolver) |
| Zustand selector | 单字段 selector 优于 `useShallow` 多字段包装 |
| useOptimistic | 乐观更新(消息星标、成绩编辑、考勤提交),`addOptimistic` 必须在 transition 或 action 内调用 |
| useOptimistic 不适用场景 | 需要服务端返回判断成功的操作(如撤回校验时间窗口) |
| React 19 useTransition | 配合 useOptimistic 自动管理乐观状态与回滚 |
| 场景 | 技术 |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 5 层状态模型 | L1 URL(nuqs) · L2 Server(TanStack Query) · L3 Client(Zustand slice) · L4 Global UI(Zustand ui-store + ModalRoot) · L5 Form(react-hook-form + zodResolver) |
| Zustand selector | 单字段 selector 优于 `useShallow` 多字段包装 |
| useOptimistic | 乐观更新(消息星标、成绩编辑、考勤提交),`addOptimistic` 必须在 transition 或 action 内调用 |
| useOptimistic 不适用场景 | 需要服务端返回判断成功的操作(如撤回校验时间窗口) |
| React 19 useTransition | 配合 useOptimistic 自动管理乐观状态与回滚 |
### 1.6 性能优化
| 场景 | 技术 |
|------|------|
| 批量 INSERT | `db.insert().values([...])` 单次写入 |
| 批量 UPDATE | `sql.join` + `CASE WHEN` 单次执行 |
| 递归删除 | 批量收集后代 ID + `inArray` 单次删除,禁止递归逐个 |
| 动态导入重 UI 库 | `xxx-inner.tsx` + `xxx.tsx` lazy wrappertiptap / @xyflow/react / AI SDK |
| ReactFlowProvider | 留在外层同步渲染,保证 lazy 子组件 hook 可用 |
| 请求级去重 | React `cache()` 包装auth 相关 4 函数) |
| 跨请求缓存 | 不用 `unstable_cache`(权限数据易变) |
| 高 LIMIT 调低 | 非分页场景默认 LIMIT ≤ 100 |
| 内存 filter 改 SQL | `inArray(field, ids)` 下推 |
| 统计走 SQL 聚合 | `COUNT(*)`/`SUM(CASE WHEN ...)` 替代拉全表内存循环 |
| chart props 模块级常量 | 12 个 recharts 图表提取 `CHART_MARGIN` |
| 高频子组件 React.memo | `AssignmentCard` / `StatusBadge` / `EmptyState` |
| 虚拟化大表 | `@tanstack/react-virtual` |
| 场景 | 技术 |
| ---------------------- | --------------------------------------------------------------------------- |
| 批量 INSERT | `db.insert().values([...])` 单次写入 |
| 批量 UPDATE | `sql.join` + `CASE WHEN` 单次执行 |
| 递归删除 | 批量收集后代 ID + `inArray` 单次删除,禁止递归逐个 |
| 动态导入重 UI 库 | `xxx-inner.tsx` + `xxx.tsx` lazy wrappertiptap / @xyflow/react / AI SDK |
| ReactFlowProvider | 留在外层同步渲染,保证 lazy 子组件 hook 可用 |
| 请求级去重 | React `cache()` 包装auth 相关 4 函数) |
| 跨请求缓存 | 不用 `unstable_cache`(权限数据易变) |
| 高 LIMIT 调低 | 非分页场景默认 LIMIT ≤ 100 |
| 内存 filter 改 SQL | `inArray(field, ids)` 下推 |
| 统计走 SQL 聚合 | `COUNT(*)`/`SUM(CASE WHEN ...)` 替代拉全表内存循环 |
| chart props 模块级常量 | 12 个 recharts 图表提取 `CHART_MARGIN` |
| 高频子组件 React.memo | `AssignmentCard` / `StatusBadge` / `EmptyState` |
| 虚拟化大表 | `@tanstack/react-virtual` |
### 1.7 设计令牌
| 场景 | 规则 |
|------|------|
| 禁止硬编码颜色 | TSX/TS/CSS 中不得出现 `#hex`,用 `hsl(var(--*))` 或 Tailwind 类 |
| 禁止硬编码字体 | 不得出现 `'Inter'`/`'Fraunces'`/`'JetBrains Mono'` 字面量 |
| 禁止硬编码字号 | 不得出现 `font-size: Npx`,用 `var(--font-size-1~9)` |
| 禁止 Tailwind 任意值 | 不得使用 `w-[Npx]`/`h-[Npx]`,映射到 `--space-*` |
| 豁免场景 | PWA manifest、邮件 HTML 内联样式、图表 SVG 固定画布、loading.tsx 骨架 |
| 豁免注释格式 | `// eslint-disable-next-line no-restricted-syntax -- <reason>` |
| 白名单文件 | `primitive.css`、`email-channel.ts`、`manifest.ts` |
| 场景 | 规则 |
| -------------------- | --------------------------------------------------------------------- |
| 禁止硬编码颜色 | TSX/TS/CSS 中不得出现 `#hex`,用 `hsl(var(--*))` 或 Tailwind 类 |
| 禁止硬编码字体 | 不得出现 `'Inter'`/`'Fraunces'`/`'JetBrains Mono'` 字面量 |
| 禁止硬编码字号 | 不得出现 `font-size: Npx`,用 `var(--font-size-1~9)` |
| 禁止 Tailwind 任意值 | 不得使用 `w-[Npx]`/`h-[Npx]`,映射到 `--space-*` |
| 豁免场景 | PWA manifest、邮件 HTML 内联样式、图表 SVG 固定画布、loading.tsx 骨架 |
| 豁免注释格式 | `// eslint-disable-next-line no-restricted-syntax -- <reason>` |
| 白名单文件 | `primitive.css`、`email-channel.ts`、`manifest.ts` |
### 1.8 TypeScript
| 场景 | 规则 |
|------|------|
| 禁止 `any` | 未知类型用 `unknown` + 类型守卫 |
| 禁止 `as` 断言 | 除非从 `unknown` 转换或测试中(需注释原因) |
| 类型守卫优先 | JSON.parse 结果经 `unknown` 中转 + 守卫localStorage/URL param/HTTP 响应需守卫 |
| 模块私有类型守卫 | 放 `lib/type-guards.ts`,导出 `isX` / `toX` |
| 函数返回值 | 必须显式标注,特别是 `Promise<T>` |
| 仅类型导入 | `import type` |
| 可选链后禁止 `!` | `a?.b!` 禁止 |
| 非空断言 `!` | 数组取值后判空再访问;`split` 结果用 `??` 兜底 |
| 空对象赋值 | 显式标注 `const empty: Record<string, string> = {}` |
| 场景 | 规则 |
| ---------------- | ------------------------------------------------------------------------------- |
| 禁止 `any` | 未知类型用 `unknown` + 类型守卫 |
| 禁止 `as` 断言 | 除非从 `unknown` 转换或测试中(需注释原因) |
| 类型守卫优先 | JSON.parse 结果经 `unknown` 中转 + 守卫localStorage/URL param/HTTP 响应需守卫 |
| 模块私有类型守卫 | 放 `lib/type-guards.ts`,导出 `isX` / `toX` |
| 函数返回值 | 必须显式标注,特别是 `Promise<T>` |
| 仅类型导入 | `import type` |
| 可选链后禁止 `!` | `a?.b!` 禁止 |
| 非空断言 `!` | 数组取值后判空再访问;`split` 结果用 `??` 兜底 |
| 空对象赋值 | 显式标注 `const empty: Record<string, string> = {}` |
### 1.9 API 路由
| 场景 | 规则 |
|------|------|
| 响应信封 | `{ success, message?, errorCode?, data? }` 与 `ActionState<T>` 对齐 |
| 错误→状态码 | PermissionDeniedError→403 / NotFoundError→404 / ValidationError→400 / BusinessError→400 / 其他→500 |
| 统一异常处理 | `withApiErrorHandler(handler)` HOF 消除重复 try/catch |
| Action 转 API | `apiFromAction(result, options?)` 通过 `errorCode` 驱动状态码 |
| SSE 头 | `Content-Type: text/event-stream` + `Cache-Control: no-cache, no-transform` + `Connection: keep-alive` |
| SSE 错误格式 | `{ type: "error", message }` |
| SSE 结束信号 | `[DONE]` |
| JSDoc 注释 | 禁止 `**/` 序列(破坏注释解析) |
| Permissions 类型 | `Permissions` 是常量对象,`Permission` 是联合类型,不可混用 |
| 场景 | 规则 |
| ---------------- | ------------------------------------------------------------------------------------------------------ |
| 响应信封 | `{ success, message?, errorCode?, data? }` 与 `ActionState<T>` 对齐 |
| 错误→状态码 | PermissionDeniedError→403 / NotFoundError→404 / ValidationError→400 / BusinessError→400 / 其他→500 |
| 统一异常处理 | `withApiErrorHandler(handler)` HOF 消除重复 try/catch |
| Action 转 API | `apiFromAction(result, options?)` 通过 `errorCode` 驱动状态码 |
| SSE 头 | `Content-Type: text/event-stream` + `Cache-Control: no-cache, no-transform` + `Connection: keep-alive` |
| SSE 错误格式 | `{ type: "error", message }` |
| SSE 结束信号 | `[DONE]` |
| JSDoc 注释 | 禁止 `**/` 序列(破坏注释解析) |
| Permissions 类型 | `Permissions` 是常量对象,`Permission` 是联合类型,不可混用 |
### 1.10 日志系统
| 场景 | 规则 |
|------|------|
| 服务端日志 | `createModuleLogger("module-name")` + pino禁止 `console.*` |
| 日志参数顺序 | `log.error({ err: error, userId }, "操作描述")` |
| 错误对象字段名 | `err`(不是 `error` |
| 模块级 logger 命名 | kebab-case`audit` / `exams` / `client-error` |
| ESLint no-console | 服务端强制,客户端 track-event 改为 no-op |
| 客户端错误上报 | 通过 track-event 上报,不导入服务端 logger |
| Request ID | proxy.ts 注入 + `withRequestContext` 包装 Server Action + `getRequestContext().requestId` 读取 |
| 场景 | 规则 |
| ------------------ | ---------------------------------------------------------------------------------------------- |
| 服务端日志 | `createModuleLogger("module-name")` + pino禁止 `console.*` |
| 日志参数顺序 | `log.error({ err: error, userId }, "操作描述")` |
| 错误对象字段名 | `err`(不是 `error` |
| 模块级 logger 命名 | kebab-case`audit` / `exams` / `client-error` |
| ESLint no-console | 服务端强制,客户端 track-event 改为 no-op |
| 客户端错误上报 | 通过 track-event 上报,不导入服务端 logger |
| Request ID | proxy.ts 注入 + `withRequestContext` 包装 Server Action + `getRequestContext().requestId` 读取 |
### 1.11 测试
| 场景 | 规则 |
|------|------|
| Vitest + Next.js 16 缓存测试 | server-only alias 到空 stub`cache()` 包装函数测试需 mock |
| Playwright 检测 | 检查 HTTP 状态码 + pageerror循环外注册一次+ 可见错误元素,不检查 HTML content |
| testTimeout | ts-morph 解析慢arch-scan 测试设置 60000ms |
| 场景 | 规则 |
| ---------------------------- | --------------------------------------------------------------------------------- |
| Vitest + Next.js 16 缓存测试 | server-only alias 到空 stub`cache()` 包装函数测试需 mock |
| Playwright 检测 | 检查 HTTP 状态码 + pageerror循环外注册一次+ 可见错误元素,不检查 HTML content |
| testTimeout | ts-morph 解析慢arch-scan 测试设置 60000ms |
### 1.12 ESLint 与验证命令
| 场景 | 命令/规则 |
|------|----------|
| 类型检查 | `npx tsc --noEmit` |
| Lint | `npm run lint` |
| 开发服务器 | `npm run dev` |
| 生产构建 | `npm run build` |
| 同步 schema | `npm run db:push` |
| 执行迁移 | `npm run db:migrate` |
| 验证 schema 一致性 | `node scripts/verify-modules-schema.mjs` |
| 场景 | 命令/规则 |
| -------------------- | ---------------------------------------------------------------------- |
| 类型检查 | `npx tsc --noEmit` |
| Lint | `npm run lint` |
| 开发服务器 | `npm run dev` |
| 生产构建 | `npm run build` |
| 同步 schema | `npm run db:push` |
| 执行迁移 | `npm run db:migrate` |
| 验证 schema 一致性 | `node scripts/verify-modules-schema.mjs` |
| 检查 i18n key 含 `.` | Grep `^\s*"[a-zA-Z_]+\.[a-zA-Z_\.]+"\s*:` glob `**/messages/**/*.json` |
| arch.db 扫描 | `npm run arch:scan` |
| arch.db 查询 | `npm run arch:query -- <command>` |
| ESLint 零警告 | 所有修改文件必须 0 errors 0 warnings |
| 自定义规则加载 | Windows 路径用 `path.join`,不用 `path.posix.join` |
| arch.db 扫描 | `npm run arch:scan` |
| arch.db 查询 | `npm run arch:query -- <command>` |
| ESLint 零警告 | 所有修改文件必须 0 errors 0 warnings |
| 自定义规则加载 | Windows 路径用 `path.join`,不用 `path.posix.join` |
---
@@ -169,97 +169,97 @@
### 2.1 lesson-preparation备课
| 场景 | 规则 |
|------|------|
| Zustand store 拆 slice | mutation 方法包装 history`pushHistory` + `set``updateNodePosition` 不推 historyhydrate/replaceDoc 清空历史history 栈上限 `MAX_HISTORY` |
| 撤销/重做快捷键 | `metaKey \|\| ctrlKey` 区分 MacShift+Z 或 Y 重做;`preventDefault` |
| 自动保存失败 UI | toast.error + 断网时不触发 + 监听 online/offline + saveError 显示重试按钮 |
| 发布前预览 3 步 | `select → preview → confirm` 状态机;总分 useMemo确认页警告文案 |
| Tiptap Image 扩展 | `inline: false, allowBase64: false``editor.commands.setImage`;不自建上传组件 |
| Tiptap SSR | `immediatelyRender: false` 避免 hydration mismatch |
| 附件表 Date 转换 | data-access 返回 Dateservice 转换为 ISO string |
| 打印视图 | `print:hidden` / `print:static` / `break-inside-avoid` / `print:fixed` |
| 素材库 picker | 复用 `use-file-upload` + 通过 service 调用 + `FileTargetType` 扩展 |
| Server Action form action 适配 | `duplicateLessonPlanFormAction(formData)` 内部调用 + `redirect()` |
| 知识点按需加载 | 对话框 Tab 内 useEffect 调用,不首屏 Promise.all |
| AI 模块纯服务端 | `import "server-only"` + Zod 验证 + 失败降级返回空 |
| 版本对比/一致性校验 | 纯函数 `diffDocuments` / `checkConsistency` / `computeCurriculumCoverage` |
| 移动端切换 | `useMediaQuery` 在 readonly-view避免服务端 useState 默认值不一致 |
| 类型扩展 | 新增可选字段 `stage?` / `differentiation?`,不破坏旧数据 |
| chapterId null | `kp.chapterId ?? null``string \| undefined` → `string \| null` |
| React Flow 移除 | 改为三列布局sidebar + paper area + detail panel纸感界面 |
| 师生互动节点 | 渲染为对话 + '师'/'生' 角色标签 + 虚线分隔 |
| 节点展开/收起 | 左侧栏、右侧面板、右键菜单三处入口一致 |
| 右键菜单 | 节点操作(展开/收起、移动、复制、删除)+ AI 辅助(生成内容、优化表达、差异化建议、分层提问) |
| 排版差异 | Fraunces 16px 主文(近黑)+ Inter 13.5px 节点(暖灰)+ 左 2px 细线 |
| 场景 | 规则 |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| Zustand store 拆 slice | mutation 方法包装 history`pushHistory` + `set``updateNodePosition` 不推 historyhydrate/replaceDoc 清空历史history 栈上限 `MAX_HISTORY` |
| 撤销/重做快捷键 | `metaKey \|\| ctrlKey` 区分 MacShift+Z 或 Y 重做;`preventDefault` |
| 自动保存失败 UI | toast.error + 断网时不触发 + 监听 online/offline + saveError 显示重试按钮 |
| 发布前预览 3 步 | `select → preview → confirm` 状态机;总分 useMemo确认页警告文案 |
| Tiptap Image 扩展 | `inline: false, allowBase64: false``editor.commands.setImage`;不自建上传组件 |
| Tiptap SSR | `immediatelyRender: false` 避免 hydration mismatch |
| 附件表 Date 转换 | data-access 返回 Dateservice 转换为 ISO string |
| 打印视图 | `print:hidden` / `print:static` / `break-inside-avoid` / `print:fixed` |
| 素材库 picker | 复用 `use-file-upload` + 通过 service 调用 + `FileTargetType` 扩展 |
| Server Action form action 适配 | `duplicateLessonPlanFormAction(formData)` 内部调用 + `redirect()` |
| 知识点按需加载 | 对话框 Tab 内 useEffect 调用,不首屏 Promise.all |
| AI 模块纯服务端 | `import "server-only"` + Zod 验证 + 失败降级返回空 |
| 版本对比/一致性校验 | 纯函数 `diffDocuments` / `checkConsistency` / `computeCurriculumCoverage` |
| 移动端切换 | `useMediaQuery` 在 readonly-view避免服务端 useState 默认值不一致 |
| 类型扩展 | 新增可选字段 `stage?` / `differentiation?`,不破坏旧数据 |
| chapterId null | `kp.chapterId ?? null``string \| undefined` → `string \| null` |
| React Flow 移除 | 改为三列布局sidebar + paper area + detail panel纸感界面 |
| 师生互动节点 | 渲染为对话 + '师'/'生' 角色标签 + 虚线分隔 |
| 节点展开/收起 | 左侧栏、右侧面板、右键菜单三处入口一致 |
| 右键菜单 | 节点操作(展开/收起、移动、复制、删除)+ AI 辅助(生成内容、优化表达、差异化建议、分层提问) |
| 排版差异 | Fraunces 16px 主文(近黑)+ Inter 13.5px 节点(暖灰)+ 左 2px 细线 |
### 2.2 exams / homework / grades
| 场景 | 规则 |
|------|------|
| AI 出题 | `exams/ai-pipeline.ts` + `createAiChatCompletion` + JSON 解析 + Zod 校验 |
| 作业化考试 | `sourceExamId` 外键关联;`homeworkSubmissions` + `homeworkAnswers` 表 |
| 批改事务 | `db.transaction` 包裹多步写 |
| 成绩批量更新 | `useOptimistic<GradeRecordListItem[], GradeRecordListItem>` |
| 场景 | 规则 |
| ------------- | ------------------------------------------------------------------------ |
| AI 出题 | `exams/ai-pipeline.ts` + `createAiChatCompletion` + JSON 解析 + Zod 校验 |
| 作业化考试 | `sourceExamId` 外键关联;`homeworkSubmissions` + `homeworkAnswers` 表 |
| 批改事务 | `db.transaction` 包裹多步写 |
| 成绩批量更新 | `useOptimistic<GradeRecordListItem[], GradeRecordListItem>` |
| FULLTEXT 检索 | questions 表 `content_text` STORED 生成列 + `MATCH AGAINST BOOLEAN MODE` |
### 2.3 messaging / notifications
| 场景 | 规则 |
|------|------|
| 消息星标乐观更新 | `useOptimistic<boolean>` + `useTransition` + `addOptimisticStarred` |
| 消息列表星标 Map | `useOptimistic<Map<string, boolean>>`key=messageId |
| 撤回不乐观更新 | 需服务端返回判断 2 分钟窗口 |
| 通知批量化 | `createNotifications(items)` 单次 INSERTin_app 批量 + 其他渠道并行 |
| 通知偏好 | 已从 messaging 迁移至 notifications |
| fan-out 分页 | `getAllUserIds(limit=1000, offset)` 分页遍历 |
| 场景 | 规则 |
| ---------------- | -------------------------------------------------------------------- |
| 消息星标乐观更新 | `useOptimistic<boolean>` + `useTransition` + `addOptimisticStarred` |
| 消息列表星标 Map | `useOptimistic<Map<string, boolean>>`key=messageId |
| 撤回不乐观更新 | 需服务端返回判断 2 分钟窗口 |
| 通知批量化 | `createNotifications(items)` 单次 INSERTin_app 批量 + 其他渠道并行 |
| 通知偏好 | 已从 messaging 迁移至 notifications |
| fan-out 分页 | `getAllUserIds(limit=1000, offset)` 分页遍历 |
### 2.4 attendance
| 场景 | 规则 |
|------|------|
| 考勤提交乐观更新 | `useOptimistic<boolean>` 保留 form action 模式(`useFormStatus` 依赖 form action promise |
| 不用 useTransition 包裹 | 会导致 form action 立即返回、`useFormStatus` 不可靠 |
| 场景 | 规则 |
| ----------------------- | ------------------------------------------------------------------------------------------ |
| 考勤提交乐观更新 | `useOptimistic<boolean>` 保留 form action 模式(`useFormStatus` 依赖 form action promise |
| 不用 useTransition 包裹 | 会导致 form action 立即返回、`useFormStatus` 不可靠 |
### 2.5 school / classes / scheduling
| 场景 | 规则 |
|------|------|
| school data-access 拆分 | `data-access-{departments,grades,subjects,classrooms,semesters,schools}.ts` + barrel |
| 角色判断迁出 data-access | `lib/school-permissions.ts` 导出 `getSchoolsForUser(userId)` |
| promoteGrades 事务 | `db.transaction` 包裹 select + update 循环 |
| classSchedule 写入口 | 统一到 scheduling 模块 |
| 跨模块批量接口 | `getGradeNamesByIds(gradeIds): Promise<Map<string,string>>` |
| A-06 JOIN 改批量查询 | 移除 `.leftJoin(textbooks, ...)`,改 `getTextbookTitlesByIds(ids)` + Map 合并 |
| 单条查询用单 ID 接口 | `getClassNameById(classId)` 替代 `db.select().from(classes)` |
| 场景 | 规则 |
| ------------------------ | ------------------------------------------------------------------------------------ |
| school data-access 拆分 | `data-access-{departments,grades,subjects,classrooms,semesters,schools}.ts` + barrel |
| 角色判断迁出 data-access | `lib/school-permissions.ts` 导出 `getSchoolsForUser(userId)` |
| promoteGrades 事务 | `db.transaction` 包裹 select + update 循环 |
| classSchedule 写入口 | 统一到 scheduling 模块 |
| 跨模块批量接口 | `getGradeNamesByIds(gradeIds): Promise<Map<string,string>>` |
| A-06 JOIN 改批量查询 | 移除 `.leftJoin(textbooks, ...)`,改 `getTextbookTitlesByIds(ids)` + Map 合并 |
| 单条查询用单 ID 接口 | `getClassNameById(classId)` 替代 `db.select().from(classes)` |
### 2.6 audit / rbac
| 场景 | 规则 |
|------|------|
| 破坏性操作用专属权限 | `AUDIT_LOG_PURGE` ≠ `AUDIT_LOG_READ` |
| 权限点注册全链路 | `permissions.ts` → `ROLE_PERMISSIONS_SEED` → `permission-bitmap.ts` → `permission-catalog.ts` → i18n `rbac.json` 五处同步 |
| 审计日志保留管理 | `AUDIT_RETENTION_MANAGE` 权限点 |
| 场景 | 规则 |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| 破坏性操作用专属权限 | `AUDIT_LOG_PURGE` ≠ `AUDIT_LOG_READ` |
| 权限点注册全链路 | `permissions.ts` → `ROLE_PERMISSIONS_SEED` → `permission-bitmap.ts` → `permission-catalog.ts` → i18n `rbac.json` 五处同步 |
| 审计日志保留管理 | `AUDIT_RETENTION_MANAGE` 权限点 |
### 2.7 parent
| 场景 | 规则 |
|------|------|
| parent actions.ts | app/ 必须调用 `@/modules/parent/actions`,不直接 import data-access |
| 路由权限校验 | 同时校验 `parentId` + `studentId` 防止信息泄露 |
| dashboard 数据获取 | `Promise.all` / `Promise.allSettled` 并行 |
| dashboard 组件 | 用 Server Component除非需要 `useMemo` |
| 场景 | 规则 |
| ------------------ | ------------------------------------------------------------------- |
| parent actions.ts | app/ 必须调用 `@/modules/parent/actions`,不直接 import data-access |
| 路由权限校验 | 同时校验 `parentId` + `studentId` 防止信息泄露 |
| dashboard 数据获取 | `Promise.all` / `Promise.allSettled` 并行 |
| dashboard 组件 | 用 Server Component除非需要 `useMemo` |
### 2.8 shared
| 场景 | 规则 |
|------|------|
| session.ts 单一入口 | shared/lib/* 不再静态 import @/auth |
| dynamic import 打破循环 | `session.ts` 内部 `dynamic import("@/auth")` |
| 重复 helper 提取 | `serializeDate` / `downloadBase64File` / `getInitials` / `isRecord` / `extractQuestionPreview` |
| ROLE_NAMES 常量 | 替代 DB 查询中硬编码角色名 |
| api-response 工具 | `apiSuccess` / `apiError` / `apiFromAction` / `withApiErrorHandler` |
| sse 工具 | `formatSseEvent` / `createSseResponse` / `createSseError` |
| 场景 | 规则 |
| ----------------------- | ---------------------------------------------------------------------------------------------- |
| session.ts 单一入口 | shared/lib/\* 不再静态 import @/auth |
| dynamic import 打破循环 | `session.ts` 内部 `dynamic import("@/auth")` |
| 重复 helper 提取 | `serializeDate` / `downloadBase64File` / `getInitials` / `isRecord` / `extractQuestionPreview` |
| ROLE_NAMES 常量 | 替代 DB 查询中硬编码角色名 |
| api-response 工具 | `apiSuccess` / `apiError` / `apiFromAction` / `withApiErrorHandler` |
| sse 工具 | `formatSseEvent` / `createSseResponse` / `createSseError` |
---
@@ -269,75 +269,78 @@
### 2026-07-07
| 时间 | 模块 | 经验 |
|------|------|------|
| 21:30 | 全局 | 004 架构文档 V3 重构:从"影响地图"升级为"架构设计文档"14 章节 912 行。核心 mermaid 图:系统边界图 + 三层架构图 + 模块依赖全景图35 模块分层)+ 认证 sequenceDiagram + 权限系统架构图 + 数据访问分层图 + 5 层状态模型图 + 跨模块协作 sequenceDiagram + 考试全生命周期 sequenceDiagram跨 4 模块)+ 令牌分层架构图 + API 路由分类图 + 错误→状态码映射图 + userId/permission 影响链。模块细节移至各模块 README004 聚焦架构层面:技术栈选型/分层规则/依赖深度统计/循环依赖处理/权限 3 道防线/缓存策略/状态层选用规则/聚合层模式/事件驱动闭环/硬性约束/性能原则/安全原则/架构决策记录 |
| 20:56 | 全模块 | 35 个模块 README 重写为完整架构文档:每个含 mermaid 架构图graph TB 分层)+ 2-3 个 流程图sequenceDiagram/flowchart+ 技术栈表格 + 文件结构(行数来自 arch.db+ 依赖关系双向表 + 关键约束 + 架构决策表。标杆 textbooks/README.md 251 行。arch:query 提供真实数据(模块统计/文件清单/依赖关系/技术标签)。并行子代理执行时 arch:scan 可能因竞争报 FOREIGN KEY 错误,串行执行正常 |
| 12:56 | onboarding/parent/messaging | data-access 审计 Phase 0 P0 修复完成6 项onboarding 加 server-only + DB 操作下沉parent 新建 actions.ts6 个 Server Actionmessaging data-access.ts1089 行)拆分为 5 子文件 + barrel |
| 12:27 | 全局 | data-access 审计 v1 完成86 文件 ~30 actions5 维度 4 严重级别,生成 230 issues17 P0/48 P1/105 P2/60 P35 子代理并行审查 |
| 10:45 | 全局 | cacheFn 迁移审查完成302 cacheFn 块全部正确放置Raw + Wrapper 配对9 处 POS-ERR 为 JSDoc/`};` 结尾的误报 |
| 09:49 | 全局 | V5 状态管理重构 Phase 4-5 完成5 层状态模型URL/Server/Client/UI/Form+ notify 替换规则 + ModalRoot 命令式 registry |
| 08:13 | lesson-preparation/messaging | `as` 断言违规修复11 个 `isXxxBlockData` 类型守卫 + `BLOCK_DATA_GUARDS` registry替换 14 处 `as` 断言 |
| 时间 | 模块 | 经验 |
| ----- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 22:30 | 全局 | 企业级架构规范化 P2 完成CI 新增 Unit test 阶段;创建 /api/health 端点 + Dockerfile HEALTHCHECK安装 @next/bundle-analyzer`npm run analyze` 触发noUncheckedIndexedAccess 评估 419 错误,推迟到独立迭代(记入 tech-debt TD-P1-001 |
| 22:10 | 全局 | 企业级架构规范化 P1 完成:创建 LICENSE专有协议/CHANGELOG/CONTRIBUTING/SECURITY 大仓文档;安装 husky + lint-staged + commitlintpre-commit 运行 eslint+prettiercommit-msg 校验 conventional commits 格式;填充 tech-debt.mdP0/P1/P2 分级 + 已解决项) |
| 21:50 | 全局 | 企业级架构规范化 P0 完成arch:scan @public JSDoc 标记豁免机制12 个登录前/公开/内部工具 Action递归 CTE 识别间接调用链requireAiPermission 等辅助函数);修复 9 个真违规parent 5 个 requireAuth→requirePermission + settings 1 个显式校验 + 3 个 @public 豁免);拆分 5 个超长文件schema.ts 2245→29+27子文件、invalidation-map.ts 1195→50+6子文件、messaging/actions.ts 973→47+5子文件、textbooks/data-access.ts 907→15+6子文件、questions/data-access.ts 828→48+4子文件违规数 32→0 |
| 21:30 | 全局 | 004 架构文档 V3 重构:从"影响地图"升级为"架构设计文档"14 章节 912 行。核心 mermaid 图:系统边界图 + 三层架构图 + 模块依赖全景图35 模块分层)+ 认证 sequenceDiagram + 权限系统架构图 + 数据访问分层图 + 5 层状态模型图 + 跨模块协作 sequenceDiagram + 考试全生命周期 sequenceDiagram跨 4 模块)+ 令牌分层架构图 + API 路由分类图 + 错误→状态码映射图 + userId/permission 影响链。模块细节移至各模块 README004 聚焦架构层面:技术栈选型/分层规则/依赖深度统计/循环依赖处理/权限 3 道防线/缓存策略/状态层选用规则/聚合层模式/事件驱动闭环/硬性约束/性能原则/安全原则/架构决策记录 |
| 20:56 | 全模块 | 35 个模块 README 重写为完整架构文档:每个含 mermaid 架构图graph TB 分层)+ 2-3 个 流程图sequenceDiagram/flowchart+ 技术栈表格 + 文件结构(行数来自 arch.db+ 依赖关系双向表 + 关键约束 + 架构决策表。标杆 textbooks/README.md 251 行。arch:query 提供真实数据(模块统计/文件清单/依赖关系/技术标签)。并行子代理执行时 arch:scan 可能因竞争报 FOREIGN KEY 错误,串行执行正常 |
| 12:56 | onboarding/parent/messaging | data-access 审计 Phase 0 P0 修复完成6 项onboarding 加 server-only + DB 操作下沉parent 新建 actions.ts6 个 Server Actionmessaging data-access.ts1089 行)拆分为 5 子文件 + barrel |
| 12:27 | 全局 | data-access 审计 v1 完成86 文件 ~30 actions5 维度 4 严重级别,生成 230 issues17 P0/48 P1/105 P2/60 P35 子代理并行审查 |
| 10:45 | 全局 | cacheFn 迁移审查完成302 cacheFn 块全部正确放置Raw + Wrapper 配对9 处 POS-ERR 为 JSDoc/`};` 结尾的误报 |
| 09:49 | 全局 | V5 状态管理重构 Phase 4-5 完成5 层状态模型URL/Server/Client/UI/Form+ notify 替换规则 + ModalRoot 命令式 registry |
| 08:13 | lesson-preparation/messaging | `as` 断言违规修复11 个 `isXxxBlockData` 类型守卫 + `BLOCK_DATA_GUARDS` registry替换 14 处 `as` 断言 |
### 2026-07-06
| 时间 | 模块 | 经验 |
|------|------|------|
| 时间 | 模块 | 经验 |
| ----- | ---- | ------------------------------------------------------------------------------------------------- |
| 23:50 | 全局 | 文档体系重设计计划制定arch.db 扫描器 + 004 瘦身 + 005 废弃 + known-issues 精简 + 35 模块 README |
### 2026-07-05
| 时间 | 模块 | 经验 |
|------|------|------|
| 全天 | 全局 | V5 状态管理重构 Phase 1-3 完成notify 替换为 zustand + ModalRootuseDialogState 评估结论(不采用) |
| 全天 | 全局 | 性能预算重构Phase 1 配置基线 + Phase 2 Bundle 预算 + Phase 3 数据层优化 + Phase 4 组件渲染优化 |
| 全天 | questions/search | FULLTEXT 索引 + MATCH AGAINST BOOLEAN MODE 替代 LIKE '%xxx%' |
| 全天 | notifications | fan-out 批量化:`getAllUserIds` 分页 + `createNotifications` 批量 INSERT |
| 全天 | 全局 | 高 LIMIT 调低attendance 5000→100、adaptive-practice 1000→100、questions 1000→100 |
| 全天 | messaging/grades/attendance | useOptimistic + useTransition 乐观更新改造 |
| 时间 | 模块 | 经验 |
| ---- | --------------------------- | ---------------------------------------------------------------------------------------------------- |
| 全天 | 全局 | V5 状态管理重构 Phase 1-3 完成notify 替换为 zustand + ModalRootuseDialogState 评估结论(不采用) |
| 全天 | 全局 | 性能预算重构Phase 1 配置基线 + Phase 2 Bundle 预算 + Phase 3 数据层优化 + Phase 4 组件渲染优化 |
| 全天 | questions/search | FULLTEXT 索引 + MATCH AGAINST BOOLEAN MODE 替代 LIKE '%xxx%' |
| 全天 | notifications | fan-out 批量化:`getAllUserIds` 分页 + `createNotifications` 批量 INSERT |
| 全天 | 全局 | 高 LIMIT 调低attendance 5000→100、adaptive-practice 1000→100、questions 1000→100 |
| 全天 | messaging/grades/attendance | useOptimistic + useTransition 乐观更新改造 |
### 2026-07-04
| 时间 | 模块 | 经验 |
|------|------|------|
| 全天 | 全局 | API 规范化重构:响应信封 + 错误→状态码映射 + SSE 工具 + withApiErrorHandler HOF |
| 全天 | 全局 | 设计令牌专项重构双层令牌架构Primitive + Semantic+ ESLint 强制约束 |
| 时间 | 模块 | 经验 |
| ---- | ---- | -------------------------------------------------------------------------------------------- |
| 全天 | 全局 | API 规范化重构:响应信封 + 错误→状态码映射 + SSE 工具 + withApiErrorHandler HOF |
| 全天 | 全局 | 设计令牌专项重构双层令牌架构Primitive + Semantic+ ESLint 强制约束 |
| 全天 | 全局 | PWA 支持manifest.ts + viewport + 触控友好panOnDrag/zoomOnPinch/zoomOnDoubleClick=false |
### 2026-07-02
| 时间 | 模块 | 经验 |
|------|------|------|
| 时间 | 模块 | 经验 |
| ---- | ------------------ | ------------------------------------------------------------------------------------- |
| 全天 | lesson-preparation | V5 备课模块场景缺口修复Zustand slice + history + 自动保存 + Tiptap Image + 打印视图 |
### 2026-06-26
| 时间 | 模块 | 经验 |
|------|------|------|
| 时间 | 模块 | 经验 |
| ---- | ------- | ------------------------------------------------------------------------------------------ |
| 全天 | student | 学生端路由权限校验修复:`homework:submit` → `CLASS_READ`;新增 `loading.tsx` + `error.tsx` |
| 全天 | classes | teacher/classes/* 路由权限校验修复:统一 `CLASS_READ` |
| 全天 | classes | teacher/classes/\* 路由权限校验修复:统一 `CLASS_READ` |
### 2026-06-24
| 时间 | 模块 | 经验 |
|------|------|------|
| 时间 | 模块 | 经验 |
| ---- | ------ | ------------------------------------------------------------------------ |
| 全天 | grades | 成绩录入支持按试卷录入:`getExamsForGradeEntry` / `getExamForGradeEntry` |
| 全天 | shared | `ROLE_NAMES` 常量替代 DB 硬编码角色名;`extractQuestionPreview` 共享工具 |
### 2026-06-23
| 时间 | 模块 | 经验 |
|------|------|------|
| 时间 | 模块 | 经验 |
| ---- | ------ | ------------------------------------------------------------------------------------------------------------------- |
| 全天 | shared | Action 错误处理统一:`handleActionError` / `safeActionCall` / `BusinessError` / `NotFoundError` / `ValidationError` |
### 2026-06-22
| 时间 | 模块 | 经验 |
|------|------|------|
| 时间 | 模块 | 经验 |
| ---- | ------- | -------------------------------- |
| 全天 | classes | teacher/classes 路由权限审计修复 |
### 2026-06-17
| 时间 | 模块 | 经验 |
|------|------|------|
| 时间 | 模块 | 经验 |
| ---- | ---- | ------------------------------------------------------------------ |
| 全天 | 全局 | 004 架构影响地图首次编写4227 行,后于 2026-07-07 瘦身至 458 行) |

Binary file not shown.