Commit Graph

243 Commits

Author SHA1 Message Date
SpecialX
33ebb9a652 feat(portal-shell): questions + textbooks 模块 3 页迁移(教师域 §9.1 B2)
§9.1 line 630-631 教师域:
- /shell/teacher/questions (列表,1 页)
- /shell/teacher/textbooks + /shell/teacher/textbooks/[id] (列表+详情,2 页)
契约:🟡 混合
- question(id)  真实单查(schema 第 775-778 行确认)
- textbook(id)  真实单查
- 列表查询  schema 无 → MSW 兜底 + @contract-pending
- textbookChapters(textbookId)  schema 无 → MSW 兜底

新增文件:
- src/lib/api/questions.ts (5 hooks)
- src/lib/api/textbooks.ts (5 hooks)
- src/lib/api/operations/{questions,textbooks}.graphql.ts (10 documents)
- src/features/teacher/questions/ (clients + transformations + tests)
- src/features/teacher/textbooks/ (clients + transformations + tests)
- src/app/shell/teacher/{questions,textbooks}/ (3 page.tsx + 2 loading + 2 error)

修改文件:
- src/lib/api/teacher.ts + operations/teacher.graphql.ts
  → 重命名 legacy widget API 以解决命名冲突:
    Question → QuestionBankItem
    Textbook → LegacyTextbook
    Chapter → LegacyChapter
    TextbookFilter → LegacyTextbookFilter
    useTextbooks → useLegacyTextbooks
    GET_QUESTIONS_DOC → GET_QUESTION_BANK_DOC
    GET_TEXTBOOKS_DOC → GET_LEGACY_TEXTBOOKS_DOC
- src/widgets/teacher/{question-bank,textbook-manager}/index.tsx
  → 更新引用为重命名后的 legacy API
- src/mocks/graphql-data.ts
  → 添加 questions/textbooks mock + GetQuestionBank/GetLegacyTextbooks handler
- src/messages/{zh-CN,en}.json (questions + textbooks i18n)
- src/lib/api/{index,operations/index}.ts (导出 questions + textbooks)
- src/shared/lib/route-permissions.ts (questions + textbooks 路由权限)
- scripts/check-page-count.ts (baseline 37 → 40)

DoD 验收(§11.3 11 项):
- typecheck 0 errors
- lint 0 errors
- vitest 469 tests passed (新增 64 tests)
- lint:tokens 0 errors
- check:pages 40 PASS
- route-permissions 已声明
- 三态齐备
- @contract-pending + MSW 兜底
- i18n zh-CN + en 同步

关联:ARCHITECTURE.md §5.3 / §5.4 / §5.5 / §9.1 / §10 P2 / §11.3 / §11.4
契约工单:docs/architecture/issues/contracts/core-edu_contract.md
2026-07-22 20:29:59 +08:00
SpecialX
8ab4fae9d5 feat(portal-shell): lesson-plans 模块 6 页迁移(教师域 §9.1 B2)
§9.1 line 629 教师域 lesson-plans 行:
- /lesson-plans (列表) / /new (表单) / /library (教案库)
- /calendar (日历) / /heatmap (热力图) / /[planId]/edit (工作台)
契约:全  schema 无 → MSW 兜底 + @contract-pending

新增文件:
- src/lib/api/lesson-plans.ts (8 hooks)
- src/lib/api/operations/lesson-plans.graphql.ts (8 documents)
- src/features/teacher/lesson-plans/ (clients + transformations + tests)
- src/app/shell/teacher/lesson-plans/ (6 page.tsx + loading.tsx + error.tsx)

修改文件:
- src/mocks/graphql-data.ts (mock 数据 + handler cases)
- src/messages/{zh-CN,en}.json (lessonPlans i18n 命名空间)
- src/lib/api/{index,operations/index}.ts (导出 lesson-plans)
- src/shared/lib/route-permissions.ts (lesson-plans 路由权限声明)
- scripts/check-page-count.ts (baseline 31 → 37)

DoD 验收(§11.3 11 项):
- typecheck 0 errors
- lint 0 errors
- vitest 405 tests passed (新增 48 tests)
- lint:tokens 0 errors
- check:pages 37 PASS
- route-permissions 已声明
- 三态(loading/error/empty)齐备
- @contract-pending + MSW 兜底
- i18n zh-CN + en 同步

关联:ARCHITECTURE.md §5.3 / §5.4 / §9.1 / §10 P2 / §11.3 / §11.4
契约工单:docs/architecture/issues/contracts/core-edu_contract.md
2026-07-22 19:59:22 +08:00
SpecialX
2f8f3f3855 feat(portal-shell): grades 模块 5 页迁移(教师域 §9.1 B2)
§9.1 B2 grades 行:/grades、/entry、/analytics、/stats、/report-card
契约:grade(id)  真实查询;列表/分析/统计/成绩单 → MSW 兜底

新增文件:
- src/lib/api/grades.ts (6 hooks)
- src/lib/api/operations/grades.graphql.ts (6 documents)
- src/features/teacher/grades/ (7 files: transformations + tests + 5 clients)
- src/app/shell/teacher/grades/ (5 page.tsx + loading.tsx + error.tsx)

修改文件:
- src/mocks/graphql-data.ts (5 mock 常量 + 6 handler cases)
- src/messages/{zh-CN,en}.json (grades i18n)
- src/lib/api/{index,operations/index}.ts (导出 grades)
- scripts/check-page-count.ts (baseline 26 → 31)

DoD 验收(§11.3 11 项):
- typecheck 0 errors
- lint 0 errors
- vitest 357 tests passed
- lint:tokens 0 errors
- check:pages 31 PASS

关联:ARCHITECTURE.md §5.3 / §5.4 / §9.1 / §10 P2 / §11.3 / §11.4
2026-07-22 18:41:55 +08:00
SpecialX
081cb5fbc3 feat(portal-shell): homework 模块 7 页迁移(教师域 §9.1 B2)
§9.1 教师域 homework 模块完整迁移(继 exams 之后第二个 B2 模块):

7 页路由结构(与旧 teacher-portal 同构):
- /shell/teacher/homework:列表页(?classId/status/q 筛选)
- /shell/teacher/homework/new:布置作业表单页
- /shell/teacher/homework/[id]:详情 + 内联批改(含提交列表 + recordGrade 表单)
- /shell/teacher/homework/submissions:跨作业提交评审列表
- /shell/teacher/homework/submissions/[submissionId]:单份提交批改 + AI 建议 + 上下份导航
- /shell/teacher/homework/submissions/[submissionId]/scan-grading:扫描批改工作台(三栏)
- /shell/teacher/homework/assignments/[id]/submissions:按作业批量批改 + 统计 + AI 批量评分

数据契约(混合):
-  homework(id: ID!) 真实查询(schema 已就绪,详情页用)
-  列表/mutation/submissions/grading/aiBatchGrading 全部 @contract-pending MSW 兜底
  · 9 个 hook 走 MSW,待后端补齐 mutation 后切换真实 fetcher

§11.3 DoD 11 项验收:
1. route-permissions:EXACT + PREFIX 表 /shell/teacher/homework 已配置
2. 页面模板:list/new 用 ListPageShell/FormPageShell;detail/grading 用 DetailPageShell;
   scan-grading 用 WorkbenchPageShell(三栏,未使用 emptyNode)
3. 三态:loading(Skeleton)/error(errorNode 或 errorSummary)/empty(emptyNode) 全实现
4. lib/api hooks:homework.ts 10 个 hooks(useHomework 真实 + 9 个 MSW)
5. @contract-pending MSW:graphql-data.ts 扩展 6 块 mock + 10 个 switch case
6. i18n:homework 节点扩展 8 个分区共 130+ keys(list/detail/new/submissions/grading/
   scan/assignment/error)中英对齐
7. lint:0 errors(4 warnings 在 __generated__)
8. lint:tokens:0 errors
9. notify:mutation 反馈走 @/shared/lib/notify(非 sonner 直引)
10. vitest:transformations 纯函数单测齐全,全量 323/323 通过(新增 ~50 测试)
11. typecheck:0 errors(noUncheckedIndexedAccess 安全访问)

附带修复:
- 修复 2 处遗留 broken link:
  · widgets/sidebar/quick-actions: /homework/new → /shell/teacher/homework/new
  · widgets/topbar/global-search: /homework → /shell/teacher/homework
- scripts/check-page-count.ts baseline 同步 13 → 26(与 exams 6 + homework 7 一致)

剩余模块:grades(5)+lesson-plans(6)+questions(1)+textbooks(2)+attendance(4)+classes(3)+
students(1)+course-plans(2)+elective(3)+error-book(1)+diagnostic(2)+analytics(2)+ai-*(3)+
knowledge-graph(1)+practice(1)+schedule-changes(1)+leave(1) 共 39 页。
2026-07-22 18:11:15 +08:00
SpecialX
dca25fc42f feat(portal-shell): exams 三子页面迁移(analytics/build/edit)
§9.1 教师域 exams 模块补完(继 d066da5 列表/详情/表单后):
- /shell/teacher/exams/[id]/analytics:详情页(图表)- 混合契约
  · 基础统计  assignmentAnalysis(data-ana 子图,schema 已就绪)
  · 扩展字段(排名/每题正确率) MSW 兜底(@contract-pending)
  · 含 Summary/Distribution/QuestionAccuracy/Rankings 四区
- /shell/teacher/exams/[id]/build:工作台页(组卷)@contract-pending
  · 三栏:题库候选 / 已选题目 / 预览
  · 支持搜索/类型/难度筛选,添加/移除/上移/下移/改分
- /shell/teacher/exams/[id]/edit:工作台页(富文本试卷)@contract-pending
  · contentEditable + 工具栏(B/I/U/H1-H3/列表)
  · 右栏试卷属性面板

§11.3 DoD 11 项验收:
1. route-permissions:PREFIX 表 /shell/teacher/exams/ 已覆盖
2. 页面模板:analytics 用 DetailPageShell;build/edit 用 WorkbenchPageShell
3. 三态:loading/error/empty 均实现(workbench 用 errorNode 合并 empty)
4. lib/api hooks:useExamAnalytics/useExamBuild/useQuestionsLibrary/
   useSaveExamBuild/useExamRichEditor/useSaveExamRichContent 6 个
5. @contract-pending MSW 模式:graphql-data.ts 扩展 6 个 case
6. i18n:analytics(19 keys)+build(28 keys)+edit(13 keys) 中英对齐
7. lint:0 errors(4 warnings 在 __generated__)
8. lint:tokens:0 errors
9. notify:success/error/warning 走 @/shared/lib/notify(非 sonner 直引)
10. vitest:transformations 新增 10 函数 22 测试,全量 273/273 通过
11. typecheck:0 errors(noUncheckedIndexedAccess 安全 swap 写法)

剩余:proctoring 标注"二期 WS"按 §9.1 暂缓。
2026-07-22 17:38:51 +08:00
SpecialX
d066da563f feat(portal-shell): 教师域考试管理页面迁移(P2)
按 ARCHITECTURE.md §9.1/§10 P2 要求,迁移教师域 exams 模块:
- 列表页 /shell/teacher/exams(ListPageShell + URL 状态 + 客户端二次筛选)
- 详情页 /shell/teacher/exams/[id](DetailPageShell + 真实 exam(id) 查询)
- 新建页 /shell/teacher/exams/new(FormPageShell + MSW 兜底)
- 纯函数 transformations.ts + 19 个 vitest 单测
- @contract-pending:exams(classId) 列表查询、createExam mutation 走 MSW
- 三态 UI(loading/error/empty)+ 路由级 loading.tsx/error.tsx
- i18n:zh-CN/en 双语补全,无硬编码中文
- MSW handlers 支持 variables 透传

§11.3 DoD 验收:
- lint: 0 errors(4 个 __generated__ 预存警告)
- typecheck: 0 errors
- test: 250/250 passed(含 19 个新增 transformations 测试)
- lint:tokens: 0 errors
2026-07-22 17:02:05 +08:00
SpecialX
843c3c0144 docs(docs): sync known-issues with P1-7/P1-8 learnings 2026-07-22 16:13:03 +08:00
SpecialX
92f24e2e91 docs(portal-shell): align P1-7 acceptance criteria with actual scope 2026-07-22 16:10:52 +08:00
SpecialX
7c511e74bd feat(portal-shell): add CI structural checks for routes, pages, codegen (P1-8)
ARCHITECTURE.md §10 P1-8: three structural checks wired into CI to
prevent regressions identified in the §1.3 audit.

Scripts (apps/portal-shell/scripts/):
- check-route-table.ts: scans src/app/shell/**/page.tsx, parses
  route-permissions.ts (EXACT/PREFIX/DASHBOARD/PUBLIC_ROUTES), fails
  if any actual /shell/* route is unregistered. Reports ghost entries
  (EXACT declarations without page.tsx) as informational.
- check-page-count.ts: asserts total page.tsx >= 13 and per-category
  minimums (dashboards/login/root/forbidden/catch-all/dev-templates).
- check-codegen.ts: runs pnpm run codegen, fails if any output with
  skipDocumentsValidation:false has operations referencing non-existent
  schema fields (currently enforces dashboard-types.ts output from P1-7).

npm scripts: check:routes / check:pages / check:codegen / check:all

CI: .github/workflows/ci.yml quality-ts job — new "Portal-shell
structural checks (P1-8)" step between typecheck and test.

Acceptance (ARCHITECTURE.md §10 P1-8 — "CI 对预埋违规报红"):
- Route violation: planted /shell/test-violation/page.tsx → check:routes
  exits 1 with "unregistered route" error; reverted → PASS
- Codegen violation: planted non_existent_field in GetTeacherDashboard →
  check:codegen exits 1 with "Cannot query field" error; reverted → PASS
- Page count: baseline=13, deleting any page.tsx triggers FAIL
- Clean state: all 3 checks PASS (10 routes, 28 EXACT, 24 ghost entries
  informational, 13 pages, codegen 3 outputs SUCCESS)

Refs: ARCHITECTURE.md §5.3, §10 P1-8, §11.6, §11.7 红线 #5
2026-07-22 15:57:58 +08:00
SpecialX
0beeff6329 feat(portal-shell): restore codegen typescript-operations for data-ana domain (P1-7)
ARCHITECTURE.md §10 P1-7: dashboard domain's 6 operations strictly
match the schema, so disable skipDocumentsValidation for that output
and restore per-operation type generation.

Changes:
- codegen.yml: add dashboard-types.ts output (typescript +
  typescript-operations plugins, skipDocumentsValidation: false);
  move documents config into each generates entry
- dashboard.ts: remove 14 handwritten interfaces and 6 internal query
  type aliases; derive types via NonNullable<GetXxxQuery['xxx']> so
  the public hook API shape stays unchanged
- admin/student/teacher page.tsx: add ?? "--" / ?? 0 null guards on
  StatCard value props to match schema nullable semantics (parent page
  already uses toFixed chain, no change needed)

Acceptance (ARCHITECTURE.md §10 P1-7):
- codegen 3 outputs all SUCCESS
- tsc 0 errors / eslint 0 errors / vitest 231 passed / next build ok
- 6 operations strictly match schema with 0 errors

Refs: ARCHITECTURE.md §5.3 data layer / §10 P1-7
2026-07-22 15:37:06 +08:00
SpecialX
a28a6bd6ea feat(portal-shell): clean widget design tokens and fix lint:tokens (P1-6)
516 mechanical replacements across 25 widget files:
- spacing xs/sm/md/lg/xl to numeric 1/2/3/4/6
- text-heading-3 to text-lg font-semibold
- bg-danger to bg-destructive
- border border dedup

Fix .eslintrc.tokens.js to use typescript-eslint parser (was importing
uninstalled @typescript-eslint/parser). lint:tokens now passes.
2026-07-22 15:07:38 +08:00
SpecialX
9358372657 feat(portal-shell): add MSW mock layer with production bundle exclusion (P1-5)
MSW v2.7.0 fallback layer covering dashboard/users/exams/grades domains.
NEXT_PUBLIC_MSW=1 enables browser Service Worker + SSR route handler mock
responses without backend. Production build excludes all mock data via
Turbopack resolveAlias redirecting @/mocks to empty stub.

Acceptance: build bundle (client+server) verified clean of mock strings;
typecheck/lint/vitest (231 tests) all pass.
2026-07-22 14:48:30 +08:00
SpecialX
da05c9107a feat(portal-shell): integrate next-intl + merge messages from teacher-portal (P1-4)
- Add next-intl v4.13.2 with cookie-based locale (no i18n routing)
- Create src/i18n/request.ts reading NEXT_LOCALE cookie
- Merge zh-CN/en messages from teacher-portal + add shell.dev.templates namespace
- Wrap next.config.js with withNextIntl plugin (Turbopack resolveAlias)
- Refactor RootLayout to async + NextIntlClientProvider + getLocale/getMessages
- Replace ThemeI18nProvider with ThemeProvider (theme-only, i18n removed)
- Remove locale/setLocale from PluginStore
- Rework locale-switcher to useLocale/useTranslations + router.refresh
- Update dev/templates page to use getTranslations (Server Component)
- Fix WorkbenchPageShell test (loading prop + center instead of children)

Verified: locale switch via NEXT_LOCALE cookie changes <html lang> and messages
zh-CN: 保存/取消/切换侧栏 | en: Save/Cancel/Toggle sidebar
typecheck 0 errors | lint 0 errors 2 warnings (generated) | vitest 231 passed
2026-07-22 13:40:44 +08:00
SpecialX
994441c2dc feat(portal-shell): add page templates quartet (list/detail/form/workbench) (P1-3)
P1-3 验收通过:4 个页面模板 + 4 个 dev 示例页 + 三态规范。

新增文件:
- src/shared/components/page-templates/
  - list-page.tsx:ListPageShell + ListPageSkeleton
  - detail-page.tsx:DetailPageShell + DetailSection + DetailField + DetailPageSkeleton
  - form-page.tsx:FormPageShell + FormPageSkeleton
  - workbench-page.tsx:WorkbenchPageShell + WorkbenchPanel + WorkbenchPageSkeleton
  - index.ts:barrel 导出
- src/app/shell/dev/templates/
  - page.tsx:索引页(4 个模板入口)
  - list/page.tsx:列表页示例(支持 ?state=loading|empty|success)
  - detail/page.tsx:详情页示例
  - form/page.tsx:表单页示例
  - workbench/page.tsx:工作台页示例
- src/shared/components/__tests__/page-templates.test.tsx:19 个单测

修改文件:
- src/shared/lib/route-permissions.ts:新增 PREFIX /shell/dev/(空 config = 仅校验登录)
- ARCHITECTURE.md:P1-3 状态回填  + 验收证据

路径命名修正:
- 原 ARCHITECTURE.md 写 /shell/_dev/templates/*,但 Next.js 将下划线开头的
  文件夹视为"私有文件夹"(不参与路由),实测被 [[...route]] catch-all 兜底接管。
- 改用 dev 命名后,显式路由优先匹配,catch-all 不再触发。

三态规范验证:
- GET /shell/dev/templates/list?state=loading → 200,含 animate-pulse 骨架
- GET /shell/dev/templates/list?state=empty → 200,含"暂无数据"空态
- GET /shell/dev/templates/list(默认 success)→ 200,含表格数据

质量校验:
- tsc --noEmit 通过
- eslint(新/改文件)通过
- vitest run 全量 21 test files / 231 tests 全部通过(212 原有 + 19 新增)

Refs: apps/portal-shell/ARCHITECTURE.md §7.3 页面四种类型与模板、
      §7.4 页面级数据获取模式、§11.3 每页硬性清单(DoD)三态规范
2026-07-22 13:02:11 +08:00
SpecialX
03e3ec4f60 fix(portal-shell): wrap sidebar in client component to respect RSC boundary
P1-1 regression introduced by layout.tsx RSC refactor: navigation.ts
exports `icon: LucideIcon` (function refs) which cannot cross the RSC
boundary from a Server Component to a Client Component.

Fix:
- Introduce ShellSidebar (Client Component) that owns the navigation
  filtering + icon refs entirely on the client side.
- layout.tsx (RSC) now only passes serializable strings (`role` and
  `permsBitmap`) to ShellSidebar; no function references cross the
  boundary.

Error before fix:
  Error: Functions cannot be passed directly to Client Components
  unless you explicitly expose it by marking it with "use server".
  {$$typeof: ..., render: function LayoutDashboard}

Refs: apps/portal-shell/ARCHITECTURE.md §7.2 AppFrame, §10 P1-1,
      §11.7 red line #5 (fail-closed identity).
2026-07-22 12:47:00 +08:00
SpecialX
98058eb16b feat(portal-shell): wire dashboards to real data-ana queries (P1-2)
- add dashboard.graphql.ts with 6 real aggregate queries
  (teacherDashboard / studentDashboard / parentDashboard /
   adminDashboard / warnings / errorBookStats), snake_case aligned
- add dashboard.ts with 6 hooks + full domain model types
- add 4 role dashboard pages (teacher/student/parent/admin)
  using DashboardShell + StatCard + DashboardSection with
  loading / error / success tri-state
- update [[...route]]/page.tsx to redirect /shell -> /shell/{role}
- retire 6 fake contract queries and hooks (grades/homeworks/
  schedule/attendance/exams/announcements) and mark widget
  placeholders as migrated
- update universal.test.ts to drop retired hook tests
- mark ARCHITECTURE.md P1-2 as completed with acceptance evidence
2026-07-22 12:33:22 +08:00
SpecialX
f92fdf8efe feat(portal-shell): wire AppFrame + navigation + permission filter (P1-1)
实现 ARCHITECTURE.md §10 P1-1:
- 新增 src/shared/lib/navigation.ts:27 项静态导航注册表,
  按 teacher/student/parent/admin 四角色分区,group 字段用于角色过滤
- 新增 src/app/shell/layout.tsx:RSC AppFrame,从 headers() 读取
  middleware 注入的身份头,batchCheckRoutePermission 按位图二次过滤
- 新增 src/shared/components/layout/user-menu.tsx:顶部用户菜单,
  显示 userId + role,登出 POST /api/auth/logout
- 新增 src/shared/lib/__tests__/navigation.test.ts:P1-1 验收单测
  (7 用例:href 登记一致性 + 4 角色隔离 + 权限检查)
- 修改 src/shared/lib/route-permissions.ts:补全 7 个列表页根路由
  的 EXACT 登记(/shell/admin/announcements、/shell/admin/classes、
  /shell/teacher/exams 等),与 PREFIX 表互补避免 catch-all 拒绝

验收:
- vitest run navigation → 7/7 passed
- tsc --noEmit 通过;eslint(5 文件)通过

回填 ARCHITECTURE.md §10 P1-1 状态为 ,附验收证据。

关联:ARCHITECTURE.md §7.2 AppFrame / §10 P1-1
2026-07-22 12:17:16 +08:00
SpecialX
dc13a2afb4 docs(portal-shell): backfill ARCHITECTURE.md §10 P0 completion status
- §10 P0 表格新增"状态"列,全部标记 
- 新增"P0 验收证据(2026-07-22)"小节,逐条附命令/文件引用
- 文档头部状态行从"待实施"改为
  "P0 已完成 + P1 进行中"
- 验收依据:
  P0-1 login API 401 实测
  P0-2 middleware + forbidden page 代码
  P0-3 localStorage 仅匹配删除注释
  P0-4 getDefaultConfig 实现
  P0-5 instrumentation register() 实现
  P0-6 vitest 27/27 通过
  P0-7 eslint 0 errors
  P0-8 git status 干净
2026-07-22 11:42:02 +08:00
SpecialX
cfb7b005fd feat(portal-shell): v2.1 P0 auth + middleware + login + graphql proxy
- 新增 ARCHITECTURE.md v3.0:portal-shell 架构权威文档
  涵盖 §3.4 V3-A2/A3 认证链、§4 GraphQL 联邦、§5 安全、
  §6 部署、§10 P0-P3 验收清单
- 新增 middleware.ts:认证 + 路由门禁
  httpOnly cookie edu_session(JWT)读取
  DEV_MODE 合成 dev-user/teacher 身份(NODE_ENV!=production &&
  NEXT_PUBLIC_DEV_MODE=true)
  生产模式 jose JWKS RS256 验签(iss/aud 校验)
  路由权限位图注入 x-user-id/x-user-role/x-user-permissions 头
  /shell/** 强制 checkRoutePermission,拒绝跳 /shell/forbidden
- 新增 instrumentation.ts:生产环境 DEV_MODE 强制 false
  防止生产环境误开 DEV_MODE 合成身份
- 新增 app/api/auth/login/route.ts + logout/route.ts
  登录走 api-gateway /v1/iam/login
  设置 httpOnly + Secure + SameSite=Strict cookie
- 新增 app/api/graphql/route.ts:同域 GraphQL 代理
  转发到 apollo-router,注入 router-authorization 头
- 新增 app/login/page.tsx + login-form.tsx
  zod 表单校验,next 参数支持
- 新增 app/shell/forbidden/page.tsx:403 页面
- 更新 route-permissions.ts:补全 P0 路由权限映射
- 更新 permission-bitmap.ts(shared-ts):位图编码/解码
- 更新 apollo-client.ts:DEV_MODE APQ 关闭,错误处理
- 更新 config-fetcher.ts:config-service 直连降级
- 更新 ApolloProvider.tsx:SSR/RSC 兼容
- 更新 eslint.config.js:design-tokens/no-hardcoded-fonts
  白名单调整
2026-07-22 11:35:36 +08:00
SpecialX
682f323bad chore(infra): docker dev-apps stack + apollo-router v1.45 compat
- 新增 docker-compose.dev-apps.yml:7 个应用服务容器化
  (iam/classes/core-edu/content/msg/push-gateway/api-gateway)
  + apollo-router override
- 新增 apollo-router dev-apps-entrypoint.sh / dev-apps-supergraph.yaml
  仅等待 5 个子图(iam/core-edu/content/msg/config-service)
  避免等待未启动的 ai/data-ana
- router.yaml:移除 v1.45 不兼容键
  (require_manifest/manifest_path/max_cost/max_batch_size)
  移除 ELv2 受限特性(persisted_queries/limits.max_depth)
  自托管无 GraphOS license
- docker-compose.yml:config-service 添加 NODE_ENV=production
  修复 pino-pretty devDep 误用
  apollo-router 添加 APOLLO_PERSISTED_QUERIES env
- services/{classes,core-edu,content,config-service}/Dockerfile
  重写为 repo 根 context + shared-ts/shared-proto 工作区依赖
  + --ignore-scripts
  修复 ERR_PNPM_WORKSPACE_PKG_NOT_FOUND 与 ERR_PNPM_IGNORED_BUILDS
- api-gateway healthcheck:wget --spider 改为 wget -O /dev/null
  修复 busybox 对 200+body 误报 exit 8
2026-07-22 11:35:02 +08:00
SpecialX
f586a0b19e feat(portal-shell): v2.0 P1-P4 token migration + unit tests + prod endpoint + e2e
P1: 31 widget 旧纸感令牌批量迁移到 shadcn 标准(1104 次替换)
- bg-paper→bg-background / bg-surface→bg-card / text-ink→text-foreground
- 保留 button.tsx 中 bg-accent(shadcn 标准 hover 语义令牌)

P2: v2.0 新增组件单元测试补齐(5 文件 81 用例)
- permission-bitmap: 24 用例(含 GRADE_READ 重复去重)
- route-permissions: 26 用例(4 张表优先级 + AND/OR 语义)
- notify: 12 用例(sonner toast 双重性质 vi.hoisted mock)
- use-error-report: 9 用例(jsdom Blob vi.stubGlobal mock)
- plugin-boundary: 10 用例(错误边界 + 骨架变体)

P3: 错误上报端点生产替换(后端 /api/v1/log)
- api-gateway: internal/log/handler.go(slog 结构化日志,64KB 限制,204 返回)
- main.go: 注册 POST /api/v1/log 路由
- useErrorReport: 环境感知端点(prod→/api/v1/log,dev→/api/log)

P4: E2E 测试(3 文件 30 用例)
- streaming: 4 用例(React 19 use() + Suspense,act 包裹 render)
- error-boundaries: 6 用例(三级错误边界层级 L1/L2/L3)
- security-boundaries: 20 用例(L1 角色门禁 + L2 权限点 + L3 数据范围)
- vitest setup: IS_REACT_ACT_ENVIRONMENT + jest-dom matchers

验证:typecheck 0 错误 / lint 0 错误 / build 6 路由 / 206 测试全部通过
2026-07-17 16:49:00 +08:00
SpecialX
9cedf0c437 feat(portal-shell): v2.0 P0 shadcn standardization + security + streaming + error handling
- shadcn/ui 标准化:废弃纸感令牌,统一 bg-background/text-foreground 等
- Tailwind v4 + @theme inline,移除 tailwind.config.js
- React 19 use() + Suspense 流式渲染,首屏骨架秒出
- 三级错误边界:Route → Section → Widget 层层兜底
- 错误上报:useErrorReport → sendBeacon → /api/log mock 端点
- 三层安全边界:L1 角色门禁 / L2 权限点门禁 / L3 数据范围
- 权限位图 base36 压缩:67 权限点 → ~14 字符,JWT 体积减少 ≥ 99%
- notify 统一 Toast 封装,禁止业务直接 import sonner
- PluginBoundary 替代 PluginLoader(错误边界 + Suspense + Skeleton 三件套)

验证:typecheck 0 错误 / lint 0 错误 / build 6 路由生成成功
2026-07-17 16:10:05 +08:00
SpecialX
f7e52b5b7f docs(portal-shell): update README to v1.1 with data layer and GraphQL hardening
- 版本 1.0 -> 1.1,日期 2026-07-17
- 新增 §13 数据访问层与 GraphQL 安全栈(6 子节)
- 更新 §5/§9.6/§10/§11/§12/附录 A/B/C
- 修正 004 §16.5 测试数(admin 31->4,sidebar 5->9)
- arch:scan 通过(TS 20 模块/4803 符号)
2026-07-17 13:47:40 +08:00
SpecialX
f8db1bfe80 docs(docs): add portal-shell data layer and GraphQL hardening known-issues
§1.11 Apollo Router 新增 4 条全局经验:
- PQ Manifest + require_manifest 配置
- limits 深度/成本/批量限制
- introspection 环境变量控制
- entrypoint.sh manifest 校验

§2.17 portal-shell 新增 15 条模块经验:
- Widget 内联 gql 废弃 + 4 层数据访问分层
- graphql-codegen skipDocumentsValidation
- useNotifications 命名冲突
- APQ + PQ Manifest 生成
- Windows ESM 动态 import 路径
- apollo-router PQ manifest 挂载 + 安全限制
- Resolver @RequirePermission 字段级守卫
- TS interface 不满足 Record 约束
- parent.test.tsx 可选链
- PowerShell heredoc / commitlint 限制
2026-07-17 13:39:16 +08:00
SpecialX
bbb43a210f docs(docs): sync 004 with portal-shell data layer and GraphQL hardening
新增 §11.7 portal-shell 前端数据访问层 + GraphQL 安全栈:
- §11.7.1 四层数据访问分层(Widget → API → Operations → Hook)
- §11.7.2 GraphQL 安全栈(APQ + PQ Manifest + 深度/成本限制)
- §11.7.3 Resolver 权限守卫审计与补齐

新增 §16.5 portal-shell 数据抽象与 GraphQL 加固子阶段(M1-M4 完成)
新增 ADR-042(前端数据访问四层分层)、ADR-043(PQ Manifest + APQ 安全加固)
更新 §16.4 关联 spec 文档列表
2026-07-17 13:38:16 +08:00
SpecialX
9bee920e4d test(portal-shell): add security stack tests for PQ manifest and APQ
Covers v2.1 M3 安全加固验证:
- PQ Manifest 完整性(6 cases):DocumentNode 校验、sha256 稳定性、确定性、唯一性、manifest 文件有效性、hash 一致性
- Query depth limit(2 cases):11 层嵌套构造、合法查询构造(实际拒绝由 apollo-router limits.max_depth=10 执行)
- APQ behavior(2 cases):默认启用、NEXT_PUBLIC_APOLLO_APQ=false 关闭

测试结果:95/95 passed (85 原有 + 10 新增)
2026-07-17 13:35:37 +08:00
SpecialX
caa90eba85 feat(infra): enable apollo-router APQ + manifest + depth/cost limits
Task 14 of portal-shell data abstraction plan (M3 security).

router.yaml:
- persisted_queries: enabled + require_manifest (env-controlled)
- limits: max_depth=10, max_cost=1000, max_batch_size=5
- introspection: env-controlled (prod=false, dev=true)

docker-compose.yml:
- Mount portal-shell/public/pq-manifest.json as read-only volume
- Add APOLLO_REQUIRE_PQ_MANIFEST + APOLLO_ROUTER_INTROSPECTION env vars
- Dev defaults preserve current behavior (manifest optional)

entrypoint.sh:
- Pre-start check: if require_manifest=true, fail fast when manifest missing
- Dev mode: warn but continue when manifest absent

Production enables strict mode via env:
  APOLLO_REQUIRE_PQ_MANIFEST=true
  APOLLO_ROUTER_INTROSPECTION=false
2026-07-17 13:32:46 +08:00
SpecialX
b30d43f983 feat(portal-shell): enable Apollo Client APQ + PQ manifest generator
Task 12-13 of portal-shell data abstraction plan (M3 security).

APQ (Automatic Persisted Queries):
- Add createPersistedQueryLink with sha256 to apollo-client.ts
- Production: client sends only query hash, not plaintext query
- Dev: NEXT_PUBLIC_APOLLO_APQ=false to disable for debugging
- Prevents attackers from crafting arbitrary queries via DevTools

PQ Manifest generator:
- New scripts/generate-pq-manifest.ts iterates operations barrel
- Outputs public/pq-manifest.json (sha256 -> query text whitelist)
- prebuild hook: codegen + generate-pq-manifest before next build
- 51 queries currently registered

- crypto-hash dependency added
- typecheck + lint (0 errors) + test (85/85) verified
2026-07-17 13:31:11 +08:00
SpecialX
1b5781bf42 fix: add missing @RequirePermission decorators
Adds @RequirePermission to 19 TS GraphQL resolvers across 5
subgraphs (iam, config-service, core-edu, content, msg) per
audit report §6.1. Maps: iam user/role -> IAM_USER_READ;
config-service 5 queries -> CONFIG_USER; core-edu classInfo ->
CLASS_READ, exam -> EXAM_READ, grade -> GRADE_READ, homework
-> HOMEWORK_READ, datascope visibleGrades/visibleExams ->
GRADE_READ/EXAM_READ; content chapter/knowledgePoint/question/
textbook -> CONTENT_*_READ; msg notifications ->
MSG_NOTIFICATION_READ, template -> MSG_NOTIFICATION_MANAGE.
Federation resolveReference left unguarded. Python subgraphs
(data-ana, ai) deferred to follow-up infrastructure work.
2026-07-17 13:26:58 +08:00
SpecialX
315b954998 docs(docs): add GraphQL @auth audit report
Audits 50 resolvers across 8 Apollo Federation subgraphs (iam,
config-service, classes, core-edu, content, msg, data-ana, ai).
Coverage: 18 guarded, 32 missing (36%). TS subgraphs: 35 total,
16 guarded, 19 missing (45.7%). Python subgraphs: 15 total, 2
guarded, 13 missing (13.3%). Documents AuthMiddleware /graphql
coverage gaps and Python resolver permission infrastructure as
follow-up items.
2026-07-17 13:20:30 +08:00
SpecialX
2910a90271 feat(portal-shell): extract domain API layer and migrate 31 widgets
Task 4-10 of portal-shell data abstraction plan (M1-M2).

Add 7 domain API modules under src/lib/api/ (parent/admin/teacher/
student/universal/sidebar/topbar), each exposing semantic hooks that
wrap useWidgetQuery/useWidgetMutation and return flattened domain
models. Widget code now imports from @/lib/api instead of inlining
gql literals.

- 31 widgets migrated (gql literal count in widgets: 0)
- 7 test files (85 cases, all passing)
- topbar.useNotifications renamed to useNotificationBell to avoid
  barrel export collision with universal.useNotifications
- typecheck + lint (0 errors) + test (85/85) verified
2026-07-17 13:07:24 +08:00
SpecialX
f623dcf4a7 feat(portal-shell): extract gql documents to operations layer
M1 Task 3: 从 31 widget 抽取 51 个 gql 文档到 7 个 operations 文件

- universal(7) + sidebar(3) + topbar(3) + teacher(6) + student(8) + parent(4) + admin(20) = 51 DOC

- operations/index.ts barrel 统一出口

- codegen.yml 启用 documents + skipDocumentsValidation(services 子图字段待补齐)

- 生成 types.ts (28KB) + operations.ts (10KB)
2026-07-17 12:37:36 +08:00
SpecialX
7c234947e1 feat(portal-shell): add graphql-codegen configuration
M1 Task 2: 配置 graphql-codegen 与 federation schema 预处理

- codegen.yml: schema 从 combined-schema.graphql 读取(federation 已剥离)

- scripts/normalize-schema.ts: 把 7 个子图的 extend type Query 合并为 type Query

- package.json: 新增 codegen/codegen:watch scripts + 4 个 codegen deps + tsx

- .gitignore: 忽略 src/lib/api/__generated__/

- documents 配置暂注释,Task 3 创建 operations 文件后启用
2026-07-17 12:12:37 +08:00
SpecialX
989603e318 feat(portal-shell): add lib/api skeleton with errors and types
M1 Task 1: 创建 4 层数据抽象层的骨架文件

- errors.ts: ApiError + GraphQLErrorCode 枚举

- types.ts: Pagination / PaginatedResult / UseQueryResult / UseMutationResult

- internal.ts: normalizeError 把 ApolloError 转为 ApiError

- index.ts: barrel 出口
2026-07-17 12:06:01 +08:00
SpecialX
117c89396d docs(portal-shell): add implementation plan for data abstraction & GraphQL hardening
- Plan: 20 tasks across M1-M4 phases

- Spec: fix useWidgetMutation destructure (object, not array)
2026-07-17 12:03:03 +08:00
SpecialX
135efa5ee5 docs(portal-shell): add data abstraction and GraphQL hardening spec
设计 portal-shell 数据抽象层与 GraphQL 安全加固方案:

- 4 层数据访问分层(Widget -> API -> Operations -> Hook)

- 31 个 widget 全量迁移到 lib/api/ 抽象层

- graphql-codegen 集成,消除手写类型

- Apollo Router 持久化查询(APQ + manifest)防查询探测

- 深度/复杂度限制(max_depth=10, max_cost=1000)

- 8 个子图字段级 @auth 审计与补齐

关联:portal-shell spec v2.1、004 §16、project_rules §3.8/§4
2026-07-17 11:56:35 +08:00
SpecialX
80e6468d62 docs(docs): sync 004 to v2.1 with apollo federation and portal-shell 2026-07-15 08:32:12 +08:00
SpecialX
83b6919185 feat(api-gateway): retire teacher/student/parent-bff, route GraphQL to apollo-router
v2.1 M9+M10: BFF layer and old portals retired.

- api-gateway: remove TeacherBffURL/StudentBffURL/ParentBffURL config

- api-gateway: add ApolloRouterURL config and /api/graphql route

- api-gateway: /api/admin/graphql now proxies to apollo-router

- api-gateway: health checks now ping apollo-router instead of BFF

- deploy.yml: replace teacher-bff service block with apollo-router

- deploy.yml: add config-service service block (M3 dependency)

- deploy.yml: remove teacher-portal and admin-portal service blocks

- source code in services/{teacher,student,parent}-bff/ and apps/*-portal/ retained for rollback
2026-07-15 08:14:24 +08:00
SpecialX
514e26ebb4 feat(portal-shell): implement portal-shell with apollo-router integration
M8: portal-shell unified frontend shell (Modular Monolith + micro-kernel).

- Apollo Client -> apollo-router (port 4010, RSC prefetch)

- 5 layouts: classic/focus/split/triple/canvas

- Registry + PluginLoader (dynamic import ssr:false)

- 3-layer props merge, Zustand PluginStore

- 4 widgets: grades/notification-bell/user-menu/class-selector

- config-service: new pluginConfig GraphQL resolver

- apollo-router: CORS + header propagation for portal-shell

- docker-compose.yml: portal-shell service block
2026-07-15 08:06:09 +08:00
SpecialX
47e950c664 feat(ai): temporal worker for lesson plan workflow
- deploy temporal server (postgresql + auto-setup + ui) in docker-compose
- new temporal/ module: workflow + activities + worker manager
- convert lesson plan 4-step orchestration to temporal workflow
- activities wrap existing analyze/recommend/generate/prepare_review steps
- worker injects failover_chain/content_client/data_ana_client via module globals
- start() uses temporal client.start_workflow, falls back to asyncio in dev
- register temporal ports 7233/8085 in port-allocation

Implements M6.5 of v2.1 migration plan (ADR-030).
2026-07-15 02:34:34 +08:00
SpecialX
ce5aeec955 refactor(ai): stateless workflow store with redis-only mode
- remove in-memory fallback (ADR-028: ai must be stateless)
- Redis is the only state store, shared across instances
- Redis unavailable raises RedisError instead of degrading to memory
- key prefix workflow -> ai:workflow (spec 4.3)
- TTL 24h -> 1h (spec 4.3: long-lived state managed by Temporal)

Implements M6 of v2.1 migration plan.
2026-07-15 02:22:28 +08:00
SpecialX
1423a0b8eb feat(core-edu): dataspcope @requires with scopetoken resolution
- new DataScopeResolver extends iam UserDataScope via @extends
- visibleGrades @requires(studentScopeToken) + visibleExams @requires(classScopeToken)
- resolve ScopeToken via Redis sMembers to actual ID list (ADR-041)
- add findByStudentIds/findByClassIds/findAll to repositories
- ALL token = full visibility (admin), skip WHERE IN filter

Implements M4 of v2.1 migration plan.
2026-07-15 02:20:15 +08:00
SpecialX
1a5fa78fa6 feat(config-service): split config-service from iam for plugin/layout config
- new NestJS service on port 3011/gRPC 50059 (ADR-026)
- owns 6 config_ tables (plugin/role-mapping/role-layout/layout-tpl/user-override/outbox)
- GraphQL Federation 2 subgraph with DataLoader + RouterAuthGuard
- gRPC ConfigService + admin REST CRUD + user REST API
- three-layer merge: registry.defaultProps + roleMapping.widget_props + userOverride.props
- Redis cache with 5min TTL
- registered in apollo-router supergraph + docker-compose + port-allocation

Implements M3 of v2.1 migration plan.
2026-07-15 02:13:03 +08:00
SpecialX
163bff6666 feat(infra): apollo router deployment and supergraph composition
M2: Apollo Router replaces BFF manual aggregation (ADR-037)

- Dockerfile: self-contained with rover + router, auto-compose on startup

- router.yaml: CORS, Router-Authorization header injection, traffic shaping

- supergraph.yaml: 6 subgraphs (iam/core-edu/content/msg/ai/data-ana)

- docker-compose: apollo-router on port 3000, depends on all subgraphs

- ROUTER_AUTH_SECRET added to secrets.example.env

- port-allocation.md: apollo-router registered on port 3000
2026-07-15 01:32:14 +08:00
SpecialX
6af1aa0d82 feat(push-gateway,msg): redis pubsub backplane for real-time notifications
M7: ADR-040 Redis Pub/Sub as state routing backplane

- push-gateway: remove Kafka consumer, add SSE endpoint

- SSE: subscribe to Redis user:{userId}:notify on connect

- msg: publish notifications to Redis Pub/Sub instead of HTTP push

- docker-compose: remove Kafka env from push-gateway
2026-07-15 01:28:55 +08:00
SpecialX
a75527be80 feat(content): eager invalidation and optimistic lock for cqrs
M4: ADR-038 Eager Invalidation + ADR-039 Optimistic Lock

- EagerInvalidationService: Redis DEL after MySQL commit

- Cache key conventions for textbook/chapter/kp/question

- Version header check (If-Match/X-Expected-Version) on write endpoints

- 409 Conflict on version mismatch

- All write endpoints return updatedAt timestamp
2026-07-15 01:28:20 +08:00
SpecialX
47a062606f refactor(shared-ts,iam,core-edu,content,msg): remove outbox polling publisher
M8: Debezium CDC now handles outbox table to Kafka (ADR-032)

- Remove OutboxPublisher class from shared-ts

- Remove publisher from iam/core-edu/content/msg lifecycle and modules

- OutboxService retained for transactional outbox table writes

- Debezium monitors binlog and pushes to Kafka automatically
2026-07-15 01:27:45 +08:00
SpecialX
a3f4fd013e feat(data-ana): python graphql federation subgraph with strawberry
- strawberry-graphql[asgi] dependency added

- 13 Federation 2 types: ClassPerformance/StudentWeakness/Dashboards/Mastery/ErrorBook

- 11 Query resolvers delegate to existing analytics/mastery services

- RouterAuthMiddleware validates Router-Authorization header on /graphql

- GraphQL endpoint mounted at /graphql alongside existing REST endpoints
2026-07-15 00:57:02 +08:00
SpecialX
1dcdcf23fd feat(ai): python graphql federation subgraph with strawberry
- strawberry-graphql[asgi] dependency added

- GeneratedReport and LessonPlanStatus @key types with resolve_reference

- RouterAuthMiddleware validates Router-Authorization header on /graphql

- GraphQL endpoint mounted at /graphql in FastAPI app

- WorkflowStateStore injected for lesson plan status resolution
2026-07-15 00:56:37 +08:00
SpecialX
35aa56537d chore(ai): add proto-generated graphql schema reference
Python service needs strawberry-graphql federation implementation
2026-07-15 00:20:21 +08:00