Skip to content

Repository files navigation

11408 沉浸时钟

单用户公网沉浸式学习计时器:选择科目 → 开始/暂停/继续/结束 → 北京时间日时间轴 + 总控只读 API。

新接手者先读:docs/接手提示词-沉浸时钟.md(含第一条提示词)与 docs/DESIGN.md(设计系统单一事实来源);历史事实与 P0 契约见 docs/交接手册-沉浸时钟-2026-08-20.md

边界

  • 本项目只记录时间执行数据。计时、科目选择和时间轴不能推断学习完成、正确率、覆盖或掌握。
  • 真实作答、错因、闭卷提取、订正和间隔复习由根工作区 study-ledger 独占,本项目不写任何学习事件。
  • 数据库 UTC 存储;自然日、显示与汇总一律 Asia/Shanghai(固定 +8)。

结构

shared/      领域层:状态机、日切、汇总、类型(provider-neutral,零云依赖)
server/      Hono API:路由/鉴权/幂等/限流 + SQLite 适配器(Storage 接口可替换)
web/         React SPA:三态时钟 + 日时间轴 + 浅深主题
migrations/  纯 SQL migration(SQLite/D1 交集语法)
e2e/         Playwright 端到端测试
docs/        设计与契约文档

本地开发

npm install
npm run test            # shared + server 单测/集成
npm run build -w web    # 前端产物到 web/dist
npm run start -w server # http://127.0.0.1:4517(静态前端 + API)
npm run test:e2e        # Playwright(自动起服务,端口 4390)

环境变量:CLOCK_DATA_DIR(数据目录,默认 ./data)、CLOCK_DB_PATHCLOCK_PORTNODE_ENV=production 启用 Secure cookie 与 HSTS。

鉴权

  • 默认进入只读监督态(2026-08-23):任何人打开即是大屏只读视图(时钟/时间轴/近 7 天回顾可看),计时操作与写端点全部封死;顶栏锁图标输入 6 位 PIN 解锁为 owner(可读写)。监督场景(给别人看进度)无需交出密码。
  • 首次访问设置 owner 密码(PBKDF2-SHA256,10 万次迭代,Web Crypto 跨运行时);登录后 HttpOnly + SameSite=Lax cookie,7 天有效。
  • 写安全纵深:即使绕过 UI,所有写端点服务端仍强制 requireOwner;只读态纯前端对齐,不替代服务端鉴权。
  • 总控只读凭据:POST /api/v1/credentials(owner)生成 clk_… token,仅显示一次;POST /api/v1/credentials/:id/revoke 撤销;支持轮换(新建+撤销)。

总控只读 API(v1)

端点 说明
GET /api/v1/health 健康检查
GET /api/v1/subjects 7 科目固定表
GET /api/v1/state 实时状态:是否在计时、今日累计、本段秒数(公开只读)
GET /api/v1/snapshot Web 内部原子刷新:同次 state + 当天 sessions(公开只读)
GET /api/v1/sessions?date=YYYY-MM-DD 兼容单日会话与段(含运行中)
GET /api/v1/sessions?from=YYYY-MM-DD&to=YYYY-MM-DD 最多 31 个北京日的跨日会话事实;支持科目/408/状态/备注过滤
GET /api/v1/daily-summary?date=YYYY-MM-DD&timezone=Asia%2FShanghai 日报口径汇总,支持 ETag/If-None-Match
GET /api/v1/daily-summaries?from=YYYY-MM-DD&to=YYYY-MM-DD&timezone=Asia%2FShanghai 最多 31 日逐日汇总 + 范围按科/聚合分布,支持 ETag
GET /api/v1/export/events.jsonl owner-only 事件导出(可重放重建一切)

写路径(owner cookie):POST /api/v1/sessions(start)、/:id/pause|resume|stop|switch|void|retime|adjust-startPATCH /:id/note。所有会话写操作必须携带 Idempotency-Key(8–64 字符);同键重试回放原状态码与原响应体,并返回 Idempotent-Replay: true。auth/credentials 端点是连接与凭据管理,不要求幂等键,由限流保护。

契约见 docs/openapi.yaml

可靠性要点

  • 服务端时间是事实来源;前端用单调时钟(performance.now)平滑显示,任何响应都重新校准。
  • 同一用户最多一个活动会话(部分唯一索引);多标签页第二个 start 收到 409 并跟随。
  • 刷新/休眠/后台节流不丢不重:净时长 = Σ(服务端段端点差)。
  • 会话跨北京时间 00:00 不拆分;日报按窗口裁剪入账。
  • 作废/修正保留事件链与 manual_adjustment 审计,不抹历史。
  • 每日备份:Cron Triggers(北京 23:00)把 events.jsonl(与导出端点同格式,可重放)与 sessions.jsonl 写入 R2 clock-11408-backup,滚动保留 30 天(server/src/backup.ts)。
  • 海螺:客户端只缓存最近成功展示结果;服务端 D1 先以“已完成时间线 revision + 模型 + 窗口”命中共享建议,命中仅轻量读取活动态,不重扫历史。缓存到下一个输入变化边界失效,最晚不跨下一个北京时间自然日;租约合并并行生成,动态 API 响应仍禁止 CDN 长缓存。

迁移

  • Cloudflare(当前生产形态):server/ 同一 Hono 代码经 Workers 入口(server/dist/worker.mjs)+ D1 适配器(migrations 已用 SQL 交集);前端静态由 Worker Static Assets asset-first 托管(仅 /api/* run_worker_first,SPA fallback 用 not_found_handling,静态安全头用 web/public/_headers),不使用 Pages。这样 hash JS/CSS/字体不消耗 Worker invocation,详见 docs/Cloudflare-配额审计-2026-08-26.md
  • CloudBase:云函数 Node 运行时适配 Hono;数据库 adapter 换 MySQL 方言。
  • 迁移前用 GET /api/v1/export/events.jsonl 全量导出重放对账。

安全清单

CSP(script-src 'self',防闪白脚本已外置,无内联脚本)/ X-Frame-Options: DENY / nosniff / no-referrer / 生产 HSTS,统一覆盖 API 与静态资源的所有响应(含 404/500,实现见 server/src/headers.ts);写请求 Origin 同源校验(CSRF);登录与 API 限流(客户端 IP 优先取 Cloudflare 边缘写入、不可伪造的 CF-Connecting-IPX-Forwarded-For 仅作非边缘环境降级);日志不含 token/cookie/备注全文;公网错误不泄漏栈与路径。

About

私密、准确、可自托管的工作计时器 · A private-by-default, self-hosted work timer

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages