Files
nj/docs/superpowers/specs/2026-06-02-classroom-pilot-readiness-design.md

18 KiB
Raw Blame History

暖记课堂试点就绪 — 功能补全设计规格

版本: 1.1 (审查修订) 日期: 2026-06-02 基线: main (7e928ae) 目标: 四角色闭环 + 跨角色链路全部打通,达到真实课堂试点就绪 审计报告: docs/verification/full-system-audit-report.md


1. 产品定位

1.1 核心认知

暖记是纯 C 端个人日记产品,不是学校管理系统:

  • 孩子是主动用户 — 产品价值在于"孩子愿不愿意每天打开写日记"
  • 家长是守门人 — 14 岁以下必须家长授权,家长是法律上的责任主体
  • 老师是可选增强层 — 班级功能让日记变得更有趣,但不是必需品
  • 管理员是平台运营 — 管理内容和用户,不面向具体学校

1.2 角色优先级

孩子(绝对核心)> 家长(合规+决策)> 老师(增强)> 管理员(运营)

1.3 成功标准

试点阶段回答一个核心问题:孩子会不会自发地、持续地使用暖记写日记?

辅助指标:

  • 家长是否感到安全PIPL 合规 + 数据管理权)
  • 老师是否愿意持续参与(布置主题 + 写评语的流程是否顺畅)
  • 管理员能否有效运营(审核内容 + 管理资源)

2. 四角色旅程地图

2.1 孩子旅程(绝对核心)

打开 app → 写日记(手写/贴纸/照片/心情)
  → 保存 → 回看历史 → 感受到"我在成长"
  → [可选] 加入班级 → 分享给同学 → 收到老师鼓励 → 更想写了
步骤 当前状态 缺口
注册/登录
首页(今日概览/近期日记)
写日记(手写+贴纸+照片+文字)
再次打开编辑已有日记 P0: editor 打开已有日记=空白
保存(本地+云端) SyncEngine 已接入
心情记录/查看
日历回看
搜索历史日记 P2: Isar FTS 未实现(当前用客户端过滤)
[可选] 加入班级
[可选] 分享到班级
[可选] 收到老师点评 ⚠️ 需验证通知→查看闭环

关键缺口:再次编辑 + 搜索

2.2 家长旅程(守门人)

注册 → 关联孩子账号 → 查看孩子日记 → 查看心情趋势
  → 感到安心 → [可选] 管理数据权限/导出
步骤 当前状态 缺口
注册+关联孩子 多孩子选择器已修
查看孩子日记 ⚠️ 数据流未端到端验证
查看心情趋势 ⚠️ MoodBloc 未验证
查看老师评语 未验证是否展示给家长
数据管理权(查阅/更正/删除/导出) ⚠️ UI 已存在parent_page.dart需端到端验证+补全文件下载

关键缺口PIPL 数据权利端到端验证 + 导出文件下载补全

2.3 老师旅程(可选增强)

注册(普通用户) → 创建班级 → 获得班级码 → 布置主题
  → 查看学生日记墙 → 写评语/鼓励
步骤 当前状态 缺口
注册
创建班级
布置主题 P2 已修 classId
查看班级日记墙 服务端过滤已修
写评语/鼓励 ⚠️ API 完整UI 需端到端验证
管理班级(编辑/停用/重置码) H6 缺失C端可延后

关键缺口:点评闭环需端到端验证

2.4 管理员旅程(平台运营)

步骤 当前状态 缺口
登录管理端
用户/角色管理 基座继承
班级管理 ⚠️ C1: API 不匹配
日记审阅 95% 完成
贴纸包管理 H7: 只有只读
主题管理 ⚠️ H8: 缺编辑/停用
菜单完整性 多个功能无入口

3. 跨角色链路

3.1 必须打通的三条链路

链路 1合规刚需: 孩子 → 家长
  孩子≠写日记 → 家长≠能查看 + 管理数据权利

链路 2动力增强: 孩子 → 老师 → 孩子
  老师≠布置主题 → 孩子≠看到主题→写日记→分享 → 老师≠写评语 → 孩子≠下次看到鼓励

链路 3平台运营: 老师管理端
  管理员≠审核内容 + 管理班级/主题 + 管理贴纸资源

4. 架构决策

4.1 决策 1编辑器加载已有数据 → 方案 A: LoadJournal Event

问题: EditorPage 的 _EditorStack 从不加载 journalId 对应的数据,编辑已有日记时页面空白。

方案: 在 EditorBloc 添加 LoadJournal event从 JournalRepository 读取日记数据,还原 strokes + elements + mood + tags 到 EditorState。

实现要点:

// editor_event.dart
class LoadJournal extends EditorEvent {
  final String journalId;
  const LoadJournal(this.journalId);
}

// editor_bloc.dart — 新增 event handler
Future<void> _onLoadJournal(LoadJournal event, Emitter<EditorState> emit) async {
  final journal = await repo.getJournal(event.journalId);
  if (journal == null) return;

  // 加载元素
  final elements = await repo.getElements(event.journalId);

  // 查找 handwriting_ref 元素,反序列化为 Stroke 列表
  final strokes = await _loadStrokes(repo, event.journalId);

  emit(state.copyWith(
    title: journal.title,
    selectedMood: journal.mood,
    tags: journal.tags,
    strokes: strokes,
    elements: elements,
    lastSavedAt: journal.updatedAt,
  ));
}

触发时机: _EditorStack.initState 中,当 widget.journalId != null 时 dispatch LoadJournal

笔画反序列化: Stroke.fromJson() 工厂方法已实现stroke_model.dart:97-110无需额外开发。只需验证序列化/反序列化的一致性。

约束:

  • EditorBloc 当前不接受 JournalRepository 作为依赖。需要修改构造函数注入 JournalRepository,或在 LoadJournal event 中传入所需数据。
  • 推荐:在 event 中传入 JournalEntry + List<JournalElement>,由 EditorPage 的外层 widget 负责加载后传入。

4.2 决策 2PIPL 数据管理权 → 方案 B: 验证+补全现有实现

问题: PIPL 要求家长有查阅、更正、删除、导出孩子数据的权利。

现状(审查修正): parent_page.dart 已实现大部分 PIPL 功能:

  • 孩子选择器(多孩子切换)
  • 查看孩子日记列表ParentJournalsLoaded state
  • 导出数据ParentDataExported state + UI
  • 删除数据 + 确认对话框parent_page.dart:160-200
  • PIPL 合规说明文本parent_page.dart:1113-1150
  • ParentBloc 已有完整事件ParentLoadChildren, ParentViewJournals, ParentExportData, ParentDeleteData

方案: 不需要新建页面。在现有 /parent 页面上验证和补全缺口

需要补全的具体缺口:

  1. 导出文件下载 — 当前导出可能仅显示 JSON 预览,需确认是否支持文件下载到本地
  2. 端到端数据流验证 — 确认 ParentBloc 事件链完整触发,后端返回正确数据
  3. 法律说明文本完善 — 确认 30 天账号注销政策文本存在且准确

后端 API实际路径已验证

  • GET /diary/parent/journals?child_id=... — 查看孩子日记query parameter非 path parameter
  • GET /diary/parent/export?child_id=... — 导出数据GET 方法,非 POST
  • DELETE /diary/parent/data + JSON body {child_id: ...} — 删除数据

⚠️ 注意以上路径与审计报告中的描述不同。Flutter 端 ParentBloc 已使用正确路径。

4.3 决策 3师生点评闭环 → 方案 B: 轮询拉取

问题: 老师写评语后学生看不到。SSE 有端口配置问题,且 Flutter Web 对 SSE 支持有限。

方案: 学生打开日记时从后端拉取该日记的评论列表并展示。不做实时推送Phase 2 再完善 SSE。

实现要点:

// 在日记详情/查看页面加载时
final comments = await apiClient.get('/diary/journals/$journalId/comments');

// 展示评论列表(在日记内容下方)
// - 老师评语特殊样式(头像+标签)
// - 未读评论标记(对比本地最后查看时间)

展示位置: EditorPage 顶栏添加评语图标,点击弹出评论列表。仅在 journalId != null查看已有日记时显示。

BLoC 架构决策: 评论列表使用独立 FutureBuilder widget,不纳入 EditorBloc state。

  • 理由:评论是只读展示,不需要复杂的 state 管理(加载/错误/空三态 FutureBuilder 足够)
  • 评论数据通过 context.read<ApiClient>().get(...) 直接获取
  • 避免给已复杂的 EditorBloc 增加更多职责

老师写评语位置: 班级日记墙页面class diary wall在学生日记卡片上添加评语按钮。

  • 文件:app/lib/features/class_/views/ 相关页面
  • 通过 ClassBloc 已有的 _onCommentCreate 事件处理
  • 需验证:评语提交后日记墙上的评语计数是否更新

约束:

  • 后端 API 已有:GET /diary/journals/:id/commentsPOST /diary/journals/:id/comments
  • 评论需要区分"老师评语"和"同学评论"(通过角色判断)
  • 内容安全过滤:审查确认 comment_service.rs 已集成 ContentSafetyServiceH4 实际已完成)

5. 实施路线图

Phase 1孩子核心闭环~2-3 天)

验证标准: 孩子能完成"写日记→保存→再打开→继续编辑→搜索找到→日历回看" 测试要求: flutter test 通过 + EditorBloc LoadJournal 单元测试

# 任务 文件 优先级 依赖
1 EditorBloc 添加 LoadJournal event + state 还原逻辑(注入 JournalEntry + List<JournalElement> editor_bloc.dart, editor_event.dart, editor_state.dart P0
2 Stroke.fromJson() 已实现,需验证序列化/反序列化一致性 stroke_model.dart
3 EditorPage _EditorStack.initState 触发 LoadJournal外层 widget 先加载数据再传入 event editor_page.dart P0 1
4 EditorBloc LoadJournal 单元测试 test/features/editor/bloc/editor_bloc_test.dart P0 1
5 搜索功能验证当前客户端过滤可用性确认Isar FTS 延后) search_bloc.dart P2
6 H5 catch_ 异常处理class_bloc.dart 6 处 catch(_)+5 处无日志 catch(e) class_bloc.dart, weekly_page.dart, monthly_page.dart, profile_page.dart, parent_page.dart P3

Phase 2家长合规闭环~1-2 天)

验证标准: 家长能"关联孩子→查看日记→导出数据文件→删除数据+确认" 测试要求: ParentBloc 端到端数据流验证通过

# 任务 文件 优先级 依赖
7 端到端验证ParentBloc 事件链(加载孩子→查看日记→导出→删除) parent_bloc.dart, parent_page.dart P0
8 补全:导出功能文件下载(确认 JSON 预览→实际文件下载) parent_page.dart P1 7
9 补全PIPL 法律说明文本完善30 天注销政策) parent_page.dart P1 7
10 端到端验证:家长查看老师评语(确认评论展示给家长) parent_page.dart P1 7

Phase 3师生互动闭环~2-3 天)

验证标准: 老师能"布置主题→学生看到→写日记分享→老师写评语→学生下次打开看到鼓励" 测试要求: ClassBloc 评论提交测试通过 + 手动端到端验证

# 任务 文件 优先级 依赖
11 EditorPage 顶栏添加评语图标 + FutureBuilder 评论弹出列表(独立 widget不纳入 EditorBloc editor_page.dart, comment_list_sheet.dart(新建) P0
12 班级日记墙:学生日记卡片添加评语按钮 + 提交框 class_ 相关 views/ P0
13 H4: 内容安全过滤接入评论 审查确认 comment_service.rs 已集成
14 端到端验证:老师布置→学生写→老师评→学生看 全链路手动验证 P0 11, 12

Phase 4管理端补全 + 全系统验证(~3-4 天)

验证标准: 管理员能"管理班级/审核内容/管理贴纸主题",四角色 × 跨角色链路全部通过 测试要求: cargo test 通过 + 管理端 pnpm build 无错误 + 全系统 E2E 场景走通

# 任务 文件 优先级 依赖
15 C1: list_all_classes handler 已实现class_handler.rs + 管理端 classApi.listAll()
16 管理端菜单补全(贴纸/主题/统计入口) routeConfig.ts, MainLayout.tsx P1
17 H7: 贴纸 CRUD后端 handler + 管理端 UI sticker_handler.rs, StickerPackList.tsx P2
18 H8: 主题编辑/停用 topic_handler.rs, TopicList.tsx P2
19 四角色 × 跨角色 端到端全面验证 P0 1-18

总时间线

Week 1: Phase 1 (孩子闭环) + Phase 2 开始
Week 2: Phase 2 (家长闭环) + Phase 3 (师生闭环)
Week 3: Phase 4 (管理端) + 全系统端到端验证

6. 风险评估

风险 概率 影响 缓解措施
笔画序列化/反序列化格式不匹配 Phase 1 阻塞 Stroke.fromJson 已实现Day 1 做一致性验证
管理端 React 改动涉及基座代码 Phase 4 延期 只改 pages/diary/ 和路由配置,不动基座
家长端数据流实际未连通API 调用失败) Phase 2 阻塞 Phase 2 启动先用 curl 验证 API再验证 ParentBloc

7. 审计报告状态追踪

已完成(审查确认,无需开发)

ID 问题 状态 验证
C4 SyncEngine 接入 EditorPage + app.dart 已完成 app.dart:60-62, editor_page.dart:126-133
H1 EditorPage authorId 从 AuthBloc 获取 已完成 editor_page.dart:59-64
Stroke.fromJson() 笔画反序列化 已存在 stroke_model.dart:97-110
C1 list_all_classes handler 已存在 class_handler.rs:208-220, lib.rs 注册
H4 内容安全过滤接入评论 已存在 comment_service.rs 已集成
PIPL 家长数据管理 UI ⚠️ 大部分已存在 parent_page.dart 有完整 UI需验证数据流

本设计覆盖的修复

ID 问题 对应任务
M4 编辑器不加载已有数据 任务 #1, #3, #4
PIPL 家长数据权利端到端验证+补全 任务 #7, #8, #9, #10
点评闭环 师生评论展示+写入闭环 任务 #11, #12, #14
H5 catch_ 异常处理 任务 #6
H7 贴纸 CRUD 任务 #17
H8 主题编辑/停用 任务 #18
管理端 菜单补全 任务 #16

明确延后到 Phase 2 的功能

功能 理由
SSE 实时通知 轮询已满足试点需求
班级管理(编辑/停用/重置码) C 端产品,班级是可选增强
BLoC 统一迁移 不影响功能,代码风格问题
成就系统管理端页面 试点阶段不紧急
心情统计管理端页面 试点阶段不紧急
Discover 页面数据接入 全 mock需单独设计

8. 验证方案

8.1 编译验证(每个 Phase 提交前必须通过)

# 后端
cd g:/nj && cargo check && cargo test

# Flutter
cd g:/nj/app && flutter analyze && flutter test

# 管理端
cd g:/nj/apps/web && pnpm build

8.2 Phase 1 验证:孩子核心闭环

自动化测试:

cd g:/nj/app
flutter test test/features/editor/bloc/editor_bloc_test.dart  # LoadJournal event 测试

手动验证:

  1. 创建日记(手写+贴纸+心情)→ 保存 → 返回首页
  2. 点击刚创建的日记 → 编辑器正确加载:标题、笔画、贴纸元素、心情
  3. 继续编辑 → 添加新内容 → 再次保存 → 验证更新成功
  4. 搜索关键词 → 找到对应日记
  5. 日历点击某天 → 显示该天的日记

8.3 Phase 2 验证:家长合规闭环

手动验证:

  1. 家长账号登录 → 关联孩子 → 查看孩子日记列表(确认列表非空,内容正确)
  2. 导出数据 → 验证文件可下载、JSON 格式正确、包含日记内容
  3. 删除单篇日记 → 确认对话框 → 验证日记从列表消失 + 后端 soft-delete
  4. 验证 PIPL 法律说明文本显示正确

8.4 Phase 3 验证:师生互动闭环

自动化测试:

cd g:/nj/app
flutter test test/features/class_/bloc/class_bloc_test.dart  # 评论提交测试(如存在)

手动验证:

  1. 老师账号 → 创建班级 → 布置主题
  2. 学生账号 → 看到主题 → 写日记 → 分享到班级
  3. 老师账号 → 查看日记墙 → 点击评语按钮 → 输入评语 → 提交成功
  4. 学生账号 → 打开该日记 → 点击顶栏评语图标 → 看到老师评语

8.5 Phase 4 验证:管理端 + 全系统

编译验证:

cd g:/nj && cargo test          # 后端测试通过
cd g:/nj/app && flutter test    # Flutter 测试通过
cd g:/nj/apps/web && pnpm build # 管理端构建成功

手动验证:

  1. 管理端 → 菜单完整性:所有暖记功能有入口(班级/日记/贴纸/主题)
  2. 管理端 → 贴纸管理 → 创建/编辑/删除贴纸
  3. 管理端 → 主题管理 → 停用主题 → 学生端不再看到

8.6 全系统端到端验证Phase 4 任务 #19

完整走通以下场景(四角色 × 跨角色):

  1. 学生闭环:新用户注册 → 写第一篇日记 → 分享到班级 → 收到老师评语 → 再次编辑
  2. 老师闭环:创建班级 → 布置主题 → 查看学生作品 → 写评语
  3. 家长闭环:关联孩子 → 查看日记+评语 → 导出数据
  4. 管理员闭环:审核内容 → 管理班级 → 管理贴纸/主题 → 查看统计

设计规格版本 1.0 | 2026-06-02 | 基于 main (7e928ae)