docs: record undergraduate release checks and diagnostic feedback review

This commit is contained in:
Codex
2026-10-15 10:41:31 +08:00
parent 7299f4d8b6
commit cc75b626aa
4 changed files with 116 additions and 4 deletions
+2
View File
@@ -1,5 +1,7 @@
# TPSchedule # TPSchedule
0.7.0补充:考试页查询类型与卡片保持16像素间距;反馈问题和建议可自选附带最近10分钟的登录/读取/提醒诊断日志。默认不发送,不含密码、Cookie或课表内容,单独作为邮件文本附件。服务端需按[日志附件更新说明](server/feedback/diagnostic-log-update.md)手动更新;真实学校登录及SMTP未由本次离线测试代替验证。玻璃取样偏移仍在调查,未纳入本次修复结论。
学生课表 APP,Flutter 实现,支持电子科技大学研究生和本科生系统。Android 0.7.0+18,Android 7.0 及以上。 学生课表 APP,Flutter 实现,支持电子科技大学研究生和本科生系统。Android 0.7.0+18,Android 7.0 及以上。
使用用户提供的原始 SVG 图标,蓝色渐变、黄色日历框及白色笔画保持一致;启动画面使用相同配色。0.5.1修复重复启动页闪烁:初始化期间保留原生启动屏,数据就绪后直接显示课表首帧,不再叠加Flutter品牌页或淡出动画。Android12+ 原生启动屏使用同色纯蓝背景及图标,旧版采用蓝色渐变。 使用用户提供的原始 SVG 图标,蓝色渐变、黄色日历框及白色笔画保持一致;启动画面使用相同配色。0.5.1修复重复启动页闪烁:初始化期间保留原生启动屏,数据就绪后直接显示课表首帧,不再叠加Flutter品牌页或淡出动画。Android12+ 原生启动屏使用同色纯蓝背景及图标,旧版采用蓝色渐变。
+74
View File
@@ -0,0 +1,74 @@
# Final whole-branch review — undergraduate integration
Executor resolution (2026-10-15): all three Important findings and the record-type rendering failure (regraded Important) were reproduced RED then fixed GREEN; final Flutter suite249/249. Original independent report retained below. Live SSO remains unverified; all artifact checks and decisions are in tpschedule-0.7.0-validation.md.
Review range: `3f28e0a1b68a6a86d23c545ca143efb8e9431787..674df1ceb306fce13947c722e28b59188e8375ad`.
Date: 2026-10-15. This resumes the interrupted final review; it is not a second review or fix pass. Source, index, HEAD and branch state were not changed by this reviewer. Only this requested ignored review artifact was written inside the checkout. Temporary probes used the sandbox temp directory.
## Strengths
- Undergraduate imports have a separate connector, explicit manual timetable capture, and an all-types exam merge; the graduate workflow remains separately dispatched.
- School-qualified terms, stable persisted school IDs, period-12 source validation, per-page byte limits, narrow navigation origins, and inherited atomic persistence are coherent safeguards.
- Timetable cancellation subtraction and arrangement grouping preserve teachers, rooms and weeks. Undergraduate exam imports preserve the current timetable and date settings. Source labels and 11/12-row selection use saved data.
- The implementation includes focused parser, session, controller, widget and service regressions. The executor reports 241 Flutter tests, analysis and both Node probes green, native sample counts of 18 timetable arrangements and 4 examinations, and Android offline-flow checks. These are executor evidence, not independently rerun suite results in this review.
## Issues
### Critical
None identified in the reviewed range.
### Important
1. **Invalidate completed exam results when the browser starts a new document.**
- Location: `mobile/lib/features/school/undergraduate/session.dart:110–115`; related save guard at `mobile/lib/features/school/undergraduate/auth_page.dart:64–70`.
- `onPageStarted` clears only `pendingDraft`; in exam mode it leaves both `pendingExams` and `_generation` unchanged. `_complete` also stays true, so subsequent `_loaded` callbacks return before detecting an authentication change.
- Reproduction: finish an exam batch, then have a navigation to a different document (for example an authentication redirect) start before the deferred/queued save executes, or navigate after a failed save while the result remains pending. The captured generation and pending result identity still match. The page's `canApply` guard therefore accepts the obsolete exam result, and `session.saved(generation: oldGeneration)` also returns true. A fresh navigation or account transition cannot invalidate this completed result as required by the spec.
- Fix: separate document validity from intentional within-batch navigation, or explicitly invalidate completed/pending results on subsequent document starts; include document validity in the save guard. Keep normal type-to-type navigation working. Add a regression with a completed result, blocked controller queue, then new navigation before queue release.
- Evidence: direct control-flow inspection. A temporary external Flutter reproduction was prepared but did not execute assertions because the sandbox denied copying the compiled test artifact; no runtime pass is claimed for this issue.
2. **Preserve independently saved undergraduate exams when importing a new graduate timetable term.**
- Location: `mobile/lib/app/app_controller.dart:172–174`.
- The `clearExams` predicate checks the incoming timetable's school, but never checks the saved exam schedule's school. Whenever a graduate timetable changes term, it deletes the saved exam schedule even if that schedule is undergraduate data intentionally independent of the timetable.
- Reproduction: start with graduate timetable term 73; successfully import undergraduate exams for term 503; import graduate timetable term 72. `_differentTerm` is true and the incoming school is graduate, so `copyWith(clearExams: true)` persists removal of the undergraduate results and their successful query choices. The user requested a timetable update, not deletion of the independent exam query.
- Fix: preserve undergraduate exam schedules regardless of incoming timetable school; retain legacy graduate clearing only for graduate exam schedules to which that rule applies. Add this direction to the existing controller test, which currently tests only importing an undergraduate timetable after saving undergraduate exams.
- Evidence: direct evaluation of the persistence predicate and `copyWith` path. Same external Flutter test-run limitation as issue 1.
3. **Reject unconsumed activity/index syntax instead of silently importing a partial timetable.**
- Location: `mobile/lib/features/school/undergraduate/activity_data.dart:48–54` (also activity matching at lines 20–24).
- Completeness is measured by counting only already-recognized `index = ...;` and `tableN.activities[index][...] = activity;` statements. Unsupported additional activity writes are not included in either count, so they can silently disappear while the recognized subset passes validation. This violates the explicit unknown-index rejection and old-data-preservation requirement.
- Minimal reproduction: use a valid TaskActivity and the usual assignment at index `0*unitCount+0`, then append `table0.activities[1*unitCount+1][table0.activities[1*unitCount+1].length] = activity;`. The parser returns one activity with indices `{0}` and no error, dropping the Tuesday lesson. The timetable adapter has no subsequent full-script consumption validation; such a successful draft can replace a complete stored timetable and change reminders.
- Fix: tokenize/validate the complete relevant activity block, or detect every TaskActivity declaration and activities write and reject any unconsumed form. Supporting arbitrary JavaScript is unnecessary; rejecting unsupported syntax is sufficient. Cover a recognized statement followed by an unrecognized extra assignment, not only an unknown expression inside a recognized `index =` assignment.
- Evidence: an independent temporary Dart probe against the actual `parseActivityData` implementation ran successfully and printed `Parsed activities: 1; indices: {0}; direct second assignment silently omitted`. No private student data was used.
### Minor
4. **Validate the optional record-level exam type before accepting stored graduate records.**
- Location: `mobile/lib/domain/exam_schedule.dart:61` and `:116–120`; consumer `mobile/lib/features/exams/exams_page.dart:190` in the reviewed revision.
- `ExamRecord.fromJson` accepts any integer `examTypeId`. The schedule-level membership check runs only for undergraduate schedules, while the examination page calls `UndergraduateExamType.fromId` for every non-null record type, regardless of school.
- Reproduction: deserialize a graduate/default-school schedule with otherwise valid record fields and `examTypeId: 99`. Loading succeeds; opening its examination card throws `FormatException: 考试类型无效`. A damaged optional field therefore bypasses the intended invalid-save/backup recovery path and instead crashes the page rendering. This is lower likelihood because the current graduate adapter does not emit this field.
- Fix: reject non-null type IDs outside 1–3 at the model boundary, and/or require the field to be absent for graduate data. Keep old records with no field compatible.
- Evidence: independent Dart probe loaded type 99 successfully and reproduced the enum conversion exception used by the page.
## Verification and limitations
- Read the production diff, spec, plan, progress ledger, relevant existing tests, and validation record. Inspected the exact code paths above.
- Two pure-Dart reproduction cases ran against the implementation: unsupported activity assignment omission and invalid record-type acceptance/render conversion.
- Three additional external temporary Flutter probes were attempted for the controller, completed-session invalidation and full timetable adapter. Flutter failed before assertions with an output.dill/listener.dart.dill copy permission error in the sandbox temp directory. The process was stopped. This is a test-environment limitation, not a product test failure, and those probes are not counted as executed regressions.
- The semicolon-omission parser candidate was considered and rejected as a finding: the tested form threw a FormatException rather than silently importing. The confirmed parser finding above uses an unrecognized extra assignment instead.
## Recommendations
Fix issues 1–3 before shipping, adding narrow regressions for their triggers. Issue 4 is a small model-boundary fix consistent with the corrupt-data requirement. Run the relevant session/controller/parser/model tests and the usual final checks after fixes. Rebuild artifacts if shipped source changes. No second independent review pass is requested by this report.
## Declined to judge
- Actual undergraduate SSO, CAPTCHA and live school callback behavior: no authenticated school session was available; offline fixtures and fake WebView callbacks cannot establish successful real authentication. This remains a documented release validation boundary, not a claimed pass.
- Query-label/card spacing and the glass offset/shader changes made after `674df1c`: explicitly separate current user requests, outside this fixed review range, being handled by the executor.
- Frozen registration materials, private signing keys and current APK signature/ZIP contents: this is the requested source-branch safety review; artifact verification is recorded by the executor. No private key or original student HTML was opened or printed by this reviewer.
## Assessment
**Ready to merge: With fixes.**
The core separation and persistence design follows the plan, but document invalidation, cross-school exam retention and fail-closed activity parsing have reproducible gaps that can save obsolete data or remove valid data. Resolve the three Important findings before delivering the updated build; the Minor finding is also straightforward to address.
+16 -4
View File
@@ -11,10 +11,11 @@
- 本科12节,第12节22:00–22:45;研究生及示例11节。七天同屏,教师和教室完整显示,相同节数课程条等长;课程详情保留课程代码和备注。提醒和日历结束时间覆盖第12节。 - 本科12节,第12节22:00–22:45;研究生及示例11节。七天同屏,教师和教室完整显示,相同节数课程条等长;课程详情保留课程代码和备注。提醒和日历结束时间覆盖第12节。
- 旧保存格式仍为schemaVersion1,新增字段兼容默认研究生;学校/学期损坏和本科第12节来源缺失拒绝保存或恢复。页面变动/取消时在保存队列内部再检查有效性,避免旧回调写入。 - 旧保存格式仍为schemaVersion1,新增字段兼容默认研究生;学校/学期损坏和本科第12节来源缺失拒绝保存或恢复。页面变动/取消时在保存队列内部再检查有效性,避免旧回调写入。
- Android实际检查发现网页加载前的一次状态探测可能读到旧空白文档;已补回归并改为加载完成后探测,再重建发布和调试包。 - Android实际检查发现网页加载前的一次状态探测可能读到旧空白文档;已补回归并改为加载完成后探测,再重建发布和调试包。
- 考试页“查询类型”与首张卡片增加16逻辑像素间隔。反馈问题和建议新增默认关闭的近10分钟日志附件,不占3个媒体附件名额。日志包含学校认证跳转、页面识别、读取/解析/保存和提醒状态,使用固定页面类别、错误类别及数字状态,不含密码、Cookie、原始地址、HTML、课程和反馈正文;重试冻结附件。重启时恢复仍在窗口内的记录,最多500条、256 KiB。
## 检查结果 ## 检查结果
- 完整Flutter套件241项通过;flutter analyze无问题。研究生及本科两个Node页面捕获脚本测试通过。 - 完整Flutter套件249项通过;flutter analyze无问题。研究生及本科两个Node页面捕获脚本测试通过。反馈服务54项测试通过,覆盖日志与3个媒体附件共存、无效/超大日志拒绝及幂等重试。
- 原本科课表附件解析10门课程、18段上课安排,3段包含第12节;逐节逐周对照保留全部有效安排,停课周扣除。原考试附件4条属于503,外层默认523没有污染实际学期。仓库仅保存脱敏等价样例。 - 原本科课表附件解析10门课程、18段上课安排,3段包含第12节;逐节逐周对照保留全部有效安排,停课周扣除。原考试附件4条属于503,外层默认523没有污染实际学期。仓库仅保存脱敏等价样例。
- 核心回归覆盖人工登录等待超过120秒、单项45秒/整批120秒超时、最后一项失败、整批重新认证、关闭页面、过期回调、保存失败、旧研究生导入、来源损坏、第12节提醒/日历、换周动画、完整教师、课程条长度及慢启动。 - 核心回归覆盖人工登录等待超过120秒、单项45秒/整批120秒超时、最后一项失败、整批重新认证、关闭页面、过期回调、保存失败、旧研究生导入、来源损坏、第12节提醒/日历、换周动画、完整教师、课程条长度及慢启动。
- Android16/API36模拟器覆盖安装后11条原课程仍在。随后通过安卓文件选择器导入脱敏本科HTML:预览18条、提示新学期开始日、设置2026-09-07后显示12行及22:00–22:45;多教师完整显示。历史503考试导入4条,课表523、18条课程及开始日保持;考试选项取消不改成功选项,重启保持学校与数据。 - Android16/API36模拟器覆盖安装后11条原课程仍在。随后通过安卓文件选择器导入脱敏本科HTML:预览18条、提示新学期开始日、设置2026-09-07后显示12行及22:00–22:45;多教师完整显示。历史503考试导入4条,课表523、18条课程及开始日保持;考试选项取消不改成功选项,重启保持学校与数据。
@@ -23,12 +24,23 @@
## 交付与实际边界 ## 交付与实际边界
发布APK:59991887字节,SHA256 `D813F9FB6D33F4DE9A1116DF35EE66BBF8475E09DD1D536C50DFE017DFDBD1BE`。调试包、源码包及截图在dist中;保留TPSchedule-0.6.2.apk。 发布APK:60024655字节,SHA256 `591E3961399D788C56D12154AC02F3AEC6FDFFB178D1AFD6B47E41DA01918FB2`。调试包、源码包及截图在dist中;保留TPSchedule-0.6.2.apk。
截图含学校列表、本科浏览器入口、考试选项、学期开始日提示、本科课表、12节及考试列表。真实本科账号、验证码、学校在线回调与自动查询尚未用本人账号验证;模拟器仅核对入口、离线数据与界面,不代表认证成功。需学生在学校原网页完成这部分验证,不要求在聊天中提供密码。 截图含学校列表、本科浏览器入口、考试选项、学期开始日提示、本科课表、12节及考试列表。真实本科账号、验证码、学校在线回调与自动查询尚未用本人账号验证;模拟器仅核对入口、离线数据与界面,不代表认证成功。需学生在学校原网页完成这部分验证,不要求在聊天中提供密码。
0.5.4登记冻结、V1.0材料、原签名以及反馈服务器部署文件均保持。反馈服务器仍由用户手动部署。 0.5.4登记冻结、V1.0材料和原签名保持。反馈服务新增可选日志字段,需要用户按 `server/feedback/diagnostic-log-update.md` 手动更新服务;原SMTP/域名/反代/密钥及数据卷保持,没有连接服务器或实发邮件。
玻璃偏移实验在Android软件兼容渲染下发现父图层平移后采样异常,尝试尚未验证通过;实验已撤回,本版保留既有玻璃实现,不能宣称玻璃偏移修复。后续原生模拟器进程退出,最后安装包只确认覆盖安装/冷启动成功,未把该次退出归因于真机APP崩溃。原本科文件导入流程的完整原生验证发生在先前构建,最终补充更改由249项测试、静态检查及重新构建覆盖。
日志使用有限的结构化记录,不等同于完整系统logcat。写入过程中若进程强制终止,上一段日志可能损坏而被跳过;当前没有保证进程崩溃时的持久性。
## 实现决策 ## 实现决策
保存接口增加可选页面有效性检查,并在串行保存队列内部执行;仅在界面检查会留下排队期间页面已变动的窗口。若误判会要求重新导入,原有调用保持兼容。最终独立审查结果记录于实现进度及后续交付记录。 1. 保存接口增加可选页面有效性检查,并在串行保存队列内部执行;仅在界面检查会留下排队期间页面已变动的窗口。若误判会要求重新导入,原有调用保持兼容。
2. 将无效可选考试类型的审查意见从Minor提高为Important:损坏字段会绕过恢复检查并导致考试页抛错。严格拒绝的代价是手工改坏的字段不能继续加载。
3. 真实SSO和验证码保持待本人验证;不能用模拟网页断言在线成功,代价是学生仍需在真实学校系统验证。
4. 按用户追加要求在本次加入考试间距和诊断反馈,原计划不新增服务端功能的范围由新请求扩展;代价是可选日志需要手动更新服务端,原无日志协议仍兼容。
5. 玻璃实验未通过后保留原实现,代价是已报告的偏移仍待进一步修复。
6. 签名、源码ZIP、冻结材料由执行者本地验证;审查者只看源码,代价是这些交付检查未由独立人员重复。
独立审查记录见 `tpschedule-0.7.0-review.md`;三项Important和一项升级后的Important全部有RED→GREEN回归。完成后新网页令待保存考试失效、研究生更新保留独立本科考试、未知活动写入拒绝整批、可选考试类型在模型层校验。新反馈功能另见 `tpschedule-diagnostic-review.md`,错误原因过于笼统和探测异常未记日志的P2已用登录诊断回归修复。没有延期的已分级Minor。
+24
View File
@@ -0,0 +1,24 @@
# Focused diagnostic feedback review
Executor resolution (2026-10-15): structured static failure causes and numeric HTTP/WebView status are now recorded; graduate probe exceptions produce a type-only breadcrumb. Login diagnostic regression was observed RED→GREEN; final Flutter suite249/249 and backend54/54. Original independent report follows.
Scope: new diagnostic logging and feedback multipart changes only, compared with HEAD. Undergraduate final-review fixes and glass changes were excluded. Read-only inspection; no deployment or private page content accessed. Existing test additions inspected; no redundant test run while the implementer is running the feature suite.
## Important finding
[P2] Preserve a structured reason for terminal school-read failures.
Location: `mobile/lib/features/school/school_session.dart:272-278` (and equivalent `mobile/lib/features/school/undergraduate/session.dart:219-225`). All callers currently produce `school_read_failed` with the same `code: flow_failure`. Minimal trigger: let the graduate main-frame HTTP request fail versus let the same target time out; both logs contain the identical failure event even though the UI already distinguishes those causes. A 403/500, a main-frame WebView resource error, a rejected navigation and a parsing/read timeout cannot be separated in the feedback attachment. This materially limits the stated import-stall diagnostic purpose. Pass bounded static reason codes through `_failure`, plus numeric HTTP/WebView error codes where applicable, without recording URLs or exception messages.
Closely related coverage gap: `SchoolSession._loaded` still silently swallows the outer JavaScript/evidence exception (`school_session.dart:497-499`). If `runJavaScriptReturningResult` repeatedly throws while the term selection flow is waiting, `finding` remains false and `_scheduleProbe` can exhaust its retries without a `school_evidence`, parse-rejected, or terminal-failure breadcrumb. Record a bounded probe-failed event (exception type only) in that catch so the attached log identifies this stall path.
## Other areas checked
- Checkbox starts unchecked; logs are sent only in the opted-in problem submission.
- Live callers never pass credentials, cookie values, raw URLs/HTML, feedback text or course content. URL classification emits only a fixed page category.
- Cache of `_pendingSubmission` preserves immutable log bytes and the same submission ID across retry/status checking.
- Media validation remains independently limited to three files; backend allows one extra diagnostic part and limits it to 256 KiB.
- Normal generated entries are ASCII and limited to 500 rows with bounded event/field strings, so the exported payload stays below 256 KiB.
- Startup attaches persistence without awaiting it; logger file failures are swallowed and cannot fail app startup.
Non-blocking observation: persistence rewrites the destination directly on each accepted event, so termination during a write can leave a truncated log and lose the previous window. Atomic temporary-file replacement would improve crash resilience, but I have not treated this as a release blocker absent an explicit crash-durability guarantee.