本文档是 25Maths ExamHub 项目的唯一权威规范。 无论你是人类开发者、Claude、Cursor、Copilot 还是其他 AI——都必须遵守此文档。 本文档存在于 git 仓库内,所有协作者均可读取。
- 项目: 25Maths ExamHub — 双语数学词汇 + 真题学习平台
- 仓库:
git25math/25maths-examhub - 磁盘路径:
/Users/zhuxingzhe/Project/ExamBoard/25Maths-Keywords/(注意:不是 25Maths-ExamHub) - 部署: push main → GitHub Pages → https://examhub.25maths.com
- 技术栈: Vanilla JS(无框架)+ CSS + esbuild minify + Supabase + KaTeX
- 当前版本: v5.45.0 (2026-03-19)
面向 CIE 0580 / Edexcel 4MA1 / Harrow Y7-Y11 的数学学习平台,核心功能:
| 维度 | 内容 | 规模 |
|---|---|---|
| 词汇 | 双语数学术语学习(6 种模式) | ~931 词 |
| 真题 | 历年考试真题 + MCQ 练习 | 5,822 真题 + 3,073 MCQ |
| 知识点 | 201 个 KP 六阶段学习 | 201 KP |
| 学习引擎 | FLM 掌握模型 + 衰退复查 + 错误记忆 | 8 层架构 |
| 角色 | 权限 |
|---|---|
| Guest (id='local') | 所有模式,有限 level,无云同步 |
| Student | 全部访问,云同步,作业,排行榜 |
| Teacher | 班级/学生 CRUD,作业,成绩分析 |
| Super Admin | 词汇 CRUD,反馈管理 |
FLM (Filter-Learn-Master) 是整个学习系统的核心状态机:
new → learning → uncertain → mastered
↑ ↑ ↑ |
└───────┴──────────┴───────────┘ (衰退复查失败 → 回退)
| 维度 | 转换触发 | cs 含义 | 存储 |
|---|---|---|---|
| 词汇 | 逐题 recordAnswer/recordScan |
逐题连续正确数 | localStorage (wmatch_v3) |
| KP | session-based saveKPResult |
连续成功 session (≥85%) | localStorage (wmatch_v3) |
| PP | practice: cs-based; exam: 高置信度直接 mastered | 连续正确数 | localStorage (pp_mastery) |
REFRESH_INTERVALS = [7, 14, 30]天rc(refresh count) 递增,上限MAX_RC = 2- 词汇/KP/PP 共用相同衰退机制
getSectionHealth()四维加权:vocabScore + practiceScore + knowledgeScore + ppScore
- 不要直接修改 FLM 状态转换逻辑——它经过多轮验证,牵一发动全身
- 新增学习维度必须遵循现有 FLM 接口:
recordAnswer(type, id, verdict) getStaleUnits(board)是聚合器,词汇/KP/PP 各有独立 stale 检测函数
Layer 1 课纲层 — Exam Boards / Year Groups / Sections
Layer 2 内容层 — Vocabulary / KPs / Questions / Past Papers
Layer 3 关系层 — Learning Graph (vocab ↔ KP ↔ questions via section codes)
Layer 4 状态层 — FLM / Mastery / Stale / Mistakes / Reflow
Layer 5 规划层 — Today's Plan / Section Health / Weak Groups
Layer 6 执行层 — Recovery Session / Practice / Refresh Scan
Layer 7 修复层 — Recovery Pack / Print Repair Sheet
Layer 8 反馈层 — Return by ID / Re-attempt / Status Update
showPanel(id) 在 18 个面板间切换:
panel-home, panel-deck, panel-study, panel-battle, panel-review-dash, panel-review, panel-quiz, panel-spell, panel-match, panel-preview, panel-daily, panel-practice, panel-import, panel-board, panel-stats, panel-admin, panel-homework
| 断点 | 布局 |
|---|---|
| ≥1080px | 固定侧边栏 (260px) + 主内容 |
| 640-1079px | 顶部 header + 底部导航 + 内容 |
| <640px | 紧凑模式,窄 padding |
- 无 bundler 开发: 26 个 JS 文件通过
<script>标签按顺序加载 - 生产构建:
npm run build→ esbuild 合并+压缩 - 构建脚本:
scripts/minify.sh
| Bundle | 包含文件 | 加载方式 |
|---|---|---|
app.bundle.min.js |
config + levels-loader + storage + auth + ui + mastery + syllabus + app | 立即 |
study-quiz-battle.min.js |
study + quiz + battle | 延迟 |
practice.min.js |
practice + knowledge-node + learning-graph + topic-practice | 延迟 |
recovery.min.js |
8 个 recovery/AI 模块 | 延迟 |
tools.min.js |
stats + export | 延迟 |
modes.min.js |
spell + match | 延迟 |
| 单独 lazy | homework, admin, super-admin, particles 等 | 按需 |
1. config.js — 常量、主题、全局状态、断点检测
2. levels-loader.js — 词汇数据懒加载
3. storage.js — localStorage + 云同步 + FLM + 模式完成追踪
4. auth.js — 登录/注册/Guest + 设置 + Board 选择
5. ui.js — Panel 导航、Toast、Modal、语言切换、排序
6. mastery.js — 首页仪表盘、deck 详情、模式选择
7. syllabus.js — 课纲浏览、KP 面板、Smart Path
8. app.js — 初始化、排行榜、URL 深度链接、iOS 分享恢复
- 所有 JS 文件使用
var声明(浏览器兼容性) - CSS 使用自定义属性(紫色主题:
--c-primary: #5248C9)
调用项目已有函数前,必须先 Grep 读其实现,确认:
- 参数签名(个数、类型、顺序)
- 返回值类型(对象 vs 原始类型 vs 数组)
- 副作用(是否写入 localStorage / 触发 UI)
| 函数 | 签名 | 陷阱 |
|---|---|---|
showModal(html) |
1 个 HTML 字符串 | 没有 title/callback 参数 |
getVisibleBoards() |
返回 [{id, name}] |
不是字符串数组!取 board 需 .id |
createCustomList(title) |
返回 list 对象 | 不是 id 字符串 |
wordKey(li, wid) |
uid 含小写 → W_uid,否则 → L_slug_Wid |
双路径逻辑 |
makeUid(word) |
小写+去特殊+空格转连字符 | 与 vocabulary UID 命名体系一致 |
showToast(msg) |
单参数消息字符串 | — |
t(en, zh) |
不存在于 ui.js | 需用 appLang !== 'en' ? zh : en 模式 |
- 禁止
onclick="fn()"在 innerHTML 中(XSS 风险) - 必须 使用
data-action+ 事件委托模式 - 委托绑定在面板容器上,用
_ppDelegated类标志防重复
添加任何缓存时,必须同时完成:
- 定义 invalidate 函数
- Grep 所有数据写入路径,逐一插入 invalidate 调用
- 验证:写入 → 重读 → 确认数据刷新
标准写入后失效序列:
writeS(s) / localStorage.setItem(...)
→ invalidateRecoveryPlanCache()
→ invalidateCache()
→ toast/badge 更新
→ debouncedSync()
if (typeof _hookFunction === 'function') _hookFunction(args);- 始终用
typeof守卫(hook 可能在 lazy-loaded 文件中) - 位置:数据写入之后,UI 反馈之前
把函数从一个 JS 文件迁到另一个时:
- 列出所有被调用的外部函数
- 检查每个在新加载位置是否可用
- 不可用 → 内建本地版或
typeof守卫
- 在
scripts/minify.sh的对应 bundle cat 列表中添加(注意加载顺序) - index.html 不需改(使用 bundle)
- 更新 CLAUDE.md 的 JS Load Order
- 更新本文档第五节
- 使用 CSS 变量(
--c-primary等),自动适配暗色模式 - 新样式涉及硬编码颜色 → 必须加
[data-theme="dark"]覆盖规则 - 用户可见文本必须用
t(en, zh)或等效双语包裹 - 字体栈:Bricolage Grotesque (display) + DM Sans (body) + JetBrains Mono (code) + Noto Sans SC (中文)
- 动态内容用
escapeHtml()转义 - 拼接 innerHTML 时不要信任用户输入
- 源文件:
.tex文件 →scripts/extract-vocab.py→data/JSON - 映射配置:
data/sources.json - 状态字段:
st(status),iv(interval),nr(next review),lr(last review),ok(correct),fail(error),lv(SRS 0-7),stars(0-4) - 模式完成:
modeDone对象,键为"slug:mode"(study/quiz/review/spell/match/battle)
Question {
stem: Block[] // 题干
parts: [{
content: Block[] // 小题内容
marks: number
answer: Answer // number/vector/table_input/coordinate/multiline/expression
subparts?: [...] // 层级化小题
}]
_rawBody?: string // 原始 tex 保留
}
- Block 类型:
text/table/figure/list - Answer 类型:
number/vector/table_input/coordinate/multiline/expression - 543 题含子问题结构(有 subparts 则无 marks/answer)
vocab_progress, leaderboard, schools, teachers, kw_classes, kw_class_students, kw_assignments, assignment_results, vocab_levels, notifications, feedback
- 迁移文件:
supabase/migrations/(17 个 SQL 文件) - 共享实例:与 25maths-website 共用 (ref:
jjjigohjvmyewasmmmyf)
| 模式 | 说明 | 实现文件 |
|---|---|---|
| Scan (Study) | 三按钮 FLM 循环:Know / Fuzzy / Don't know | study.js |
| Quiz | 四选一 MCQ + Daily Challenge + 分享卡 | quiz.js |
| Spell | 听写模式(Web Speech API) | spell.js |
| Match | 拖拽配对匹配 | match.js |
| Battle | 限时匹配挑战 + 计分 | battle.js |
| Daily Challenge | 每日词汇测验 + 连续打卡 | quiz.js (内置) |
| 模式 | 说明 | 实现文件 |
|---|---|---|
| Practice MCQ | 按章节分类 MCQ 练习 | practice.js |
| Knowledge Node | KP 六阶段学习(概念→词汇→MCQ→真题→综合→挑战) | knowledge-node.js |
| Topic Practice | 专题练习包 | topic-practice.js |
- 路径:
/opt/homebrew/bin/gemini -o json输出是包装结构:{ session_id, response: "<json string>", stats }response是字符串,需要二次JSON.parse(),可能含 markdown code fence- 频繁 429 限流 + ETIMEDOUT → 脚本必须幂等可断点续跑
- 调用超时设 180s(默认 120s 不够大 batch)
- 大批量任务按 section 分文件输出,重跑时跳过已存在文件
- Key 池:
~/.gemini_keys.json(多 key 轮换,最大化免费配额)
- 幂等性 — 支持断点续跑,自动跳过已完成单元
- 分批 — 大数据集按 section/batch 切分,每批独立输出
- 降级 — 前端对缺失映射数据做 graceful fallback
- 验证 — merge 脚本统计报告(数量、平均值、异常)
每次提交前(或审查时)必须检查:
-
npm run build零错误 - 每个新调用的已有函数,确认参数和返回值
- 数据流 trace — 关键路径从点击到数据到 UI 完整走一遍
- 跨模块
typeof守卫保护跨文件调用
- 事件绑定用 data-action 委托,不用 onclick
- 新 CSS 用变量或有 dark 规则
- 用户可见文本用双语包裹
- 缓存失效 — 新增写入路径触发了相关缓存清除
- 加载顺序 — minify.sh 中位置在依赖之后
- 动态内容用
escapeHtml()转义
- JSON 文件格式正确(可被
JSON.parse()解析) - LaTeX 语法正确(
$配对、花括号配对) - Block 结构符合 schema(stem/parts/answer)
- 版本号一致(config.js
APP_VERSION= CHANGELOG = ROADMAP)
CIE 分析(知识点定义) → ExamHub(词汇+真题) ↔ Play(闯关) → 主站(入口)
↑
共享 Supabase
共享用户账号
| 项目 | 路径 | 关系 |
|---|---|---|
| Play 游戏 | 25maths-games-legends |
共享 Supabase + 用户账号 |
| 主站 | 25maths-website |
入口链接,共享 Supabase |
| CIE 分析 | CIE/IGCSE_v2/analysis/ |
真题标签 + KP 数据源 |
| 视频引擎 | 多目录 | 未来集成(视频 ID 与 kpId 对齐) |
CIE 真题 PDF → 分析流水线(提取+标签) → levels.js(ExamHub) + kp-registry.ts(Play)
↓ ↓
ExamHub 真题练习 Play 关卡生成
↓ ↓
Supabase vocab_progress (共享用户数据)
- 提交消息中文概要,格式:
v{版本} {功能名} — {简述} - 多功能合并提交时,在 body 中分行列出每个功能
- commit 前必须
npm run build零错误 - push main 自动部署到 GitHub Pages
- 验证部署:
gh api repos/git25math/25maths-examhub/pages/builds
- 不要 force push
- 已 push 的 commit 不要 amend → 创建新 commit 或
push --force-with-lease - commit 前重新检查文件状态(外部可能修改了 config.js 版本号等)
- 不用
git add -A,只 stage 自己修改的文件 - 新文件必须
git add(检查git status的??未跟踪文件)
ExamHub 有两处版本号,必须保持一致:
| 位置 | 格式 | 说明 |
|---|---|---|
js/config.js |
APP_VERSION = 'v5.45.0' |
运行时版本(SW 缓存版本自动同步) |
CHANGELOG.md 顶部 |
## [5.45.0] - 日期 |
变更记录 |
ROADMAP.md 顶部 |
## v5.45.0 — 标题 |
规划状态 |
package.json的 version 字段需同步更新,以 config.jsAPP_VERSION为权威来源。
- 修改
js/config.js中APP_VERSION npm run build(自动同步 SW 缓存版本 + index.html cache-bust)- 在 CHANGELOG.md 顶部新增版本条目
- 在 ROADMAP.md 中标记已完成项
[x],新增下一版本条目 git commit→git push origin main- 验证 GitHub Pages 部署
| 不要 | 应该 |
|---|---|
onclick="fn()" 在 innerHTML |
data-action + 事件委托 |
裸 except: |
except Exception: |
| 定义 invalidate 不接入写入路径 | 同时 grep 所有 writer 并插入 |
| 依赖 lazy-loaded 文件中的函数 | typeof 守卫或本地实现 |
| 凭记忆猜函数签名 | 先 Grep/Read 确认实现 |
| build 成功就 ship | 人工 trace 关键路径 |
| 假设磁盘路径等于仓库名 | 用 25Maths-Keywords 路径 |
| 已 push 的 commit 做 amend | 新建 commit 或 force-with-lease |
| Gemini output 直接 JSON.parse | 先解包 wrapper.response |
| 大批量脚本一次性跑完 | 分 section 输出 + 幂等断点续跑 |
git add -A 提交所有 |
只 stage 自己改的文件 |
| 硬编码颜色不加 dark 规则 | 用 CSS 变量或加 dark override |
Step 1: Git 基线检查 (status + diff + log)
Step 2: 确认 APP_VERSION (grep config.js)
Step 3: Plan → Execute → Ship(见 CLAUDE.md)
Step 4: npm run build 零错误
Step 5: git commit + push
Step 6: 验证 GitHub Pages 部署
npm run build重新生成 minified 文件- CHANGELOG.md — 新增版本条目
- ROADMAP.md — 标记
[x]+ 新增下一阶段 git commit+git push origin main- 验证部署状态
- 向用户输出:版本号、变更摘要
- 更新 config.js 版本号 + CHANGELOG + ROADMAP
- 回顾对话,记录新的共识/偏好/纠正
- commit + push + 确认部署
- 输出接手指令(一句话可复制到新 session)
| 文件 | 用途 |
|---|---|
docs/CONTRIBUTING.md |
本文档 — 开发规范(AI/人类通用) |
docs/DEVELOPMENT-PLAN.md |
版本历程概览 + 下一步规划 |
docs/SYSTEM_GLOSSARY.md |
系统术语表(FLM/KP/PP 等标准定义) |
docs/learning-engine-architecture.md |
8 层学习引擎架构详解 |
docs/sync-research.md |
Supabase 同步方案 + 冲突解决 |
docs/v2-database-schema.sql |
数据库完整 schema |
CLAUDE.md |
Claude Code 启动协议 |
CHANGELOG.md |
完整版本变更记录 (v5.14.1→v5.45.0) |
ROADMAP.md |
开发路线图 |
js/config.js |
应用常量 + APP_VERSION |
js/storage.js |
FLM 状态机 + localStorage + 云同步 |
js/practice.js |
真题练习模式 |
js/knowledge-node.js |
KP 六阶段学习 |
js/learning-graph.js |
运行时关系查询层 |
scripts/minify.sh |
构建脚本(bundle 分组 + esbuild) |
scripts/extract-vocab.py |
词汇数据提取 |
data/sources.json |
.tex 文件路径映射 |
supabase/migrations/ |
17 个 SQL 迁移文件 |