Files
NextEdu/docs/troubleshooting/known-issues.md
SpecialX e3d132dc1b
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
docs(known-issues): log enterprise architecture normalization work experience
2026-07-07 20:25:33 +08:00

347 lines
38 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 已知问题速查
> 索引式速查手册:场景 → 技术/规则映射
> 架构规则见 [../architecture/004_architecture_impact_map.md](../architecture/004_architecture_impact_map.md) 与 [.trae/rules/project_rules.md](../../.trae/rules/project_rules.md)
> 原始版本归档于 [../architecture/audit/archive/known-issues_v1.md](../architecture/audit/archive/known-issues_v1.md)
---
## 一、全局经验
### 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 本身而非抛错 |
### 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 设计) |
### 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 一致性 | 初始值必须可序列化且在服务端和客户端一致 |
### 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*` 格式 |
### 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 自动管理乐观状态与回滚 |
### 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` |
### 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` |
### 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> = {}` |
### 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` 是联合类型,不可混用 |
### 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` 读取 |
### 1.11 测试
| 场景 | 规则 |
| ---------------------------- | --------------------------------------------------------------------------------- |
| 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` |
| 检查 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` |
---
## 二、模块经验
### 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 细线 |
### 2.2 exams / homework / grades
| 场景 | 规则 |
| ------------- | ------------------------------------------------------------------------ |
| 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)` 分页遍历 |
### 2.4 attendance
| 场景 | 规则 |
| ----------------------- | ------------------------------------------------------------------------------------------ |
| 考勤提交乐观更新 | `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)` |
### 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` 权限点 |
### 2.7 parent
| 场景 | 规则 |
| ------------------ | ------------------------------------------------------------------- |
| 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` |
---
## 三、工作经验日志
> 按时间倒序50 条上限。AI 发现更好方案时可更新本节。
### 2026-07-07
| 时间 | 模块 | 经验 |
| ----- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 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 乐观更新改造 |
### 2026-07-04
| 时间 | 模块 | 经验 |
| ---- | ---- | -------------------------------------------------------------------------------------------- |
| 全天 | 全局 | 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` |
### 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 行) |