diff --git "a/docs/SpeakUp-\351\234\200\346\261\202\344\270\216\350\277\233\345\272\246\344\272\244\346\216\245-2026-08-23.md" "b/docs/SpeakUp-\351\234\200\346\261\202\344\270\216\350\277\233\345\272\246\344\272\244\346\216\245-2026-08-23.md" deleted file mode 100644 index fddbad8..0000000 --- "a/docs/SpeakUp-\351\234\200\346\261\202\344\270\216\350\277\233\345\272\246\344\272\244\346\216\245-2026-08-23.md" +++ /dev/null @@ -1,228 +0,0 @@ -# SpeakUp 需求与进度交接(2026-08-23) - -这份文档记录当前需求、完成状态、待办顺序、验证口径与安全边界,供后续执行者直接接手。不要只看聊天记录;开始前仍需核对最新仓库、CI 和线上状态。 - -## 一、当前基线 - -- 仓库:`/Users/mint/workspace/learning/speakup` -- 当前分支:`refactor-enforce-source-lines` -- 当前分支基于已上线的 `master` 提交 `7bb6f8c` 创建;分支最新提交以 `git rev-parse HEAD` 为准。 -- 已上线功能 PR:[#164](https://github.com/makewheels/speakup/pull/164) -- 部署版本探针修复 PR:[#165](https://github.com/makewheels/speakup/pull/165) -- 线上版本此前已通过 `/api/version` 核对为当前 `master`,健康接口正常。 -- 工作区有一份用户自己的未跟踪文档:`docs/multica-线上开发环境.md`。不要修改、暂存或删除。 -- 本交接文档是当前任务新增的持久文档;History 视频修复 [PR #166](https://github.com/makewheels/speakup/pull/166) 已合并、由流水线部署并完成线上版本核验,功能通知工作流已成功发送。 -- CSS 拆分 [PR #167](https://github.com/makewheels/speakup/pull/167) 与定位权限修复 [PR #168](https://github.com/makewheels/speakup/pull/168) 均已合并、部署并完成线上版本核验与通知;全源码 500 物理行门禁已在当前分支完成拆分与门禁扩展,随本次 PR 提交。 - -## 二、需求总表 - -| 序号 | 需求 | 状态 | 说明 | -|---:|---|---|---| -| 1 | 删除 AI 自动笔记,改为用户选中文字手动加入笔记 | 已完成并上线 | 结果页和本人历史详情可手动摘录;公开分享页只读。AI 解释暂未实现。 | -| 2 | 结果页直接提供分享,不必先进入 History | 已完成并上线 | 手机优先系统分享,失败时回滚首次公开状态。 | -| 3 | 纠正版与标准答案分成两个隔离请求 | 已完成并上线 | 标准答案只看题目白名单上下文,不看用户原话或纠正结果。 | -| 4 | 结果页中文、换行和对齐修正 | 已完成并上线 | 中文统一为「你说的 / 纠正版 / 标准答案」;原话不分句且两端对齐,答案按句分行。 | -| 5 | 差距卡与追问输入区重做 | 已完成并上线 | 类型与标题合并;核心三行固定为「你说 / 这样说 / 为什么」;例句折叠;UI 不显示 Meaning;模型的 `chinese` 字段仍供复习内部使用;追问区加宽。 | -| 6 | 功能完成邮件通知 | 已完成基础设施 | 工作流、HTML/纯文本邮件和 Infisical OIDC 已上线;收件配置已纠正并完成无明文读回校验。 | -| 7 | 发音、重音和近音词纠正方案调研 | 调研完成,未接入 | 见 `docs/评测/发音纠正选型调研.md`;未购买、未部署、未改产品链路。 | -| 8 | `App.css` 超长及全源码 500 行门禁 | **已完成,随 [PR #169](https://github.com/makewheels/speakup/pull/169) 上线** | PR #167 已拆分 CSS;5 个超限的可执行源码/测试文件已按主题拆分(前端抽 `usePracticeRecorder` 钩子、后端测试按主题拆文件),门禁扩展到前端全源码与后端 `tests/`、`scripts/`,接入现有 CI。 | -| 9 | 网站首次打开请求地理位置 | **已完成并上线** | PR #168 已删除定位调用,自动主题改为跟随系统并清理旧纬度缓存;线上版本、健康接口与生产静态资源均已核验,通知已发送。 | -| 10 | 产品埋点、用户轨迹和统一 `trace_id` | 已决定采用 Umami 轻量统计,实施中 | 2026-08-23 用户拍板选 Umami;事件字典、隐私方案与部署见后续记录。 | -| 11 | History/历史详情视频播放器太小、缩在左上角 | **已完成并上线,视觉验收已补** | PR #166 已合并;线上版本为 `12e393c`,健康接口和生产资源均已核验。2026-08-23 独立浏览器实例完成 390/500/720 宽截图验收。 | - -## 三、History 视频播放器:已上线 - -### 现象 - -用户从 History 进入练习详情后,场景视频播放器非常小,像被限制在页面左上角;播放器控件也挤在小方框里。 - -### 已确认根因 - -- `web/src/components/SessionView.jsx` 在有作答记录时,把图片和视频都渲染为 `className="detail-hero-media"`。 -- `web/src/App.css` 中 `.detail-hero-media` 被固定为 `width: 80px; height: 80px; flex: 0 0 80px`。 -- 同一个 `SessionView` 同时用于本人历史详情和公开分享页,所以修复应同时验证两处。 -- 没有作答记录时使用的 `.session-practice-media` 已经是全宽 `16:9`,可参考其结构。 - -### 已实施修复 - -1. 只有图片的已作答记录仍保留 hero 内 `80×80` 缩略图。 -2. 有视频的已作答记录改为在 hero 下方单独渲染全宽 `16:9` 播放器,不再渲染第二份缩略视频。 -3. 视频使用 `object-fit: contain` 和黑色背景,避免裁掉原始画面;图片继续使用 `object-fit: cover`。 -4. 本人历史详情和公开分享页共用同一修复逻辑。 -5. 已新增回归测试,覆盖“有作答视频使用全宽容器”“只有图片继续使用缩略图”和“分享页视频不进入 hero 缩略框”。 - -### 已完成自动化验证 - -- 前端全量测试:25 个测试文件、280 项测试全部通过。 -- 覆盖率:Statements 88.03%、Branches 81.86%、Functions 86.11%、Lines 91.66%。 -- `pnpm run lint`:通过,0 个 error;仍有项目既存的 23 个 warning。 -- `pnpm run build`:通过。 -- 测试日志中的 jsdom `window.scrollTo` “Not implemented” 是既存非致命提示,不影响测试结果。 - -### 验收 - -- 375px、390px 手机宽度下,视频播放器占内容区完整宽度,不缩在左上角。 -- 720px 桌面上限下比例正常,不拉伸、不溢出。 -- 浅色/深色均检查;播放、暂停、进度条和全屏按钮可用。 -- 运行 `pnpm run lint`、相关测试、全量前端测试和 `pnpm run build`。 -- 当前会话的浏览器控制能力未暴露,尚未完成真实页面视觉复现;后续执行者若有浏览器能力,必须补做视觉检查,不能只凭测试宣称 UI 已验证。 -- 已完成:本地修改、测试、PR #166、CI、合并、自动部署、线上版本/健康/静态资源核验和功能邮件通知。 -- 视觉验收(2026-08-23 由接手者在独立浏览器实例补做,截图留档):390px 手机宽、500px、720px 桌面宽下,已作答视频均为内容区全宽 16:9 播放器,黑色 letterbox 不裁画面,播放/音量/全屏控件整行可用;不再缩在左上角。 - -## 四、CSS 拆分与 500 行门禁 - -### 原始问题与当前状态 - -- 原 `web/src/App.css` 有 3237 行;现已变为有序导入入口,原内容机械拆到 `web/src/styles/app/` 的 8 个文件中。 -- 8 个文件依次为 479、417、366、355、444、495、477、204 行,均不超过 500 物理行。 -- 机械拆分完成时,文件按顺序拼接后的 SHA-256 与原 `App.css` 完全一致;随后只中性化了一条旧注释,CSS 规则和顺序未变,Vite 构建产出的 CSS 资源文件名也保持不变。 -- `web/eslint.config.js` 只匹配 `**/*.{js,jsx}`;CSS 完全不受 ESLint 管理。 -- `max-lines` 虽配置为 500,但跳过空行和注释,并非严格物理行数。 -- 已新增 `web/scripts/check-style-lines.js`,对 `web/src/**/*.css` 执行严格物理行数检查,并通过 `pnpm run lint` 强制执行。 -- `PracticePage.jsx` 物理 576 行,但按现规则有效行 496,因此仍通过。 -- 已上线门禁专门覆盖 CSS;若按“可执行源码与自动化测试均不超过 500 物理行”的严格口径,当前还有 5 个文件超限:后端测试 726/552 行,前端页面及测试 576/548/530 行。 -- `docs/原型设计/` 是不参与构建和测试的设计画布,锁文件是包管理器生成物,二者不纳入源码行数门禁;其余受维护的 Python、JS、JSX、TS、TSX、CSS 源码与自动化测试都应纳入。 - -### 已实施拆分顺序 - -本次只做机械切片,保持原始级联顺序,不同时删除旧样式。由 `App.css` 按顺序导入: - -- tokens/theme -- shell/primitives -- media/recording/transcript -- corrections/chips -- login -- practice -- review -- profile -- history -- session detail -- status/loading -- feedback result -- sharing/profile -- feedback form -- free practice -- 现有 `practice-preferences.css` 继续最后加载 - -每个文件必须不超过 500 物理行。重点保护 `.detail-hero`、`.chip`、`.su-btn`、`.active`、`.loading` 等依赖后写覆盖的选择器。 - -第二步再通过浏览器 CSS Coverage 和 JSX 动态类名审计删除约 490 行疑似旧样式;不要把“拆分”和“删除”放在同一提交。 - -### 已实施门禁与后续 - -- 已新增 CSS 物理行数检查,覆盖 `web/src/**/*.css`。 -- 已将检查接入 `pnpm run lint`,因此现有前端 CI 自动执行。 -- CSS 语法/规则另接 Stylelint;不要简单把 CSS 后缀加入 ESLint 的 JS 配置。 -- 已在 `CONTRIBUTING.md` 写清本地命令、严格口径和级联顺序约束。 -- 现有 23 个 warning 尚未清零,不能直接加 `--max-warnings=0` 而不先治理。 -- [PR #167](https://github.com/makewheels/speakup/pull/167) 已通过 CI、合并、自动部署和线上静态资源核验,通知工作流已成功。 -- `refactor-enforce-source-lines` 分支已完成上述 5 个文件的拆分,并把严格物理行检查接入前后端现有 CI;未通过删空行、压缩代码或豁免存量文件绕过。 - -## 五、移除地理位置权限 - -### 已确认根因 - -- `web/src/main.jsx` 页面启动即调用 `initTheme()`。 -- `web/src/lib/theme.js` 的 `fetchLatitude()` 无条件调用 `navigator.geolocation.getCurrentPosition()`。 -- 唯一用途是根据纬度估算日出日落,从而自动切换深浅色主题。 -- 代码只读取纬度并存入本地存储,没有发现上传服务器的逻辑;但浏览器仍会弹权限框,所谓“静默尝试”并不静默。 - -### 已实施修复 - -- 已删除定位调用,不再读取或保存纬度。 -- “自动”主题已改为跟随系统 `prefers-color-scheme`;手动浅色/深色保持不变。 -- 已监听系统主题变化,自动模式下实时更新。 -- 启动时清理旧纬度缓存 key,不破坏已有主题选择。 -- 已更新首屏防闪脚本、中英文说明、CSS 注释和 `theme.test.js`。 -- 已覆盖现代浏览器与旧版 Safari 的系统主题监听接口;生产构建产物已确认不含定位 API 调用。 -- 全量前端测试、覆盖率、lint、CSS 物理行门禁和生产构建均已通过;[PR #168](https://github.com/makewheels/speakup/pull/168) 已通过 CI、合并和流水线部署。 -- 线上 `/api/version` 已核对为合并提交 `7bb6f8c`,`/api/health` 正常;生产首页和 JS 均包含系统主题规则且不含定位 API,功能通知工作流已成功。 - -## 六、埋点、用户轨迹与 Trace - -### 当前状态 - -- 线上前端包没有 PostHog、Umami、OpenReplay、HyperDX 等分析 SDK。 -- 打开网页只会留下普通 Uvicorn 访问日志;SPA 内部跳转无法形成完整轨迹。 -- 登录会更新 `users.lastLoginAt`,鉴权请求会更新 `authSessions.lastUsedAt`,但这不是事件流。 -- 练习、笔记、分享、复习和反馈会留下 MongoDB 业务事实,但不能还原每次点击和页面顺序。 -- Langfuse 只跟踪 LLM/媒体生成调用,并关联 `userId/sessionId`;没有贯穿浏览器、FastAPI、MongoDB 和 Langfuse 的统一 `trace_id`。 -- TTS 的 `X-Api-Request-Id` 是外部语音厂商请求 ID,不是 SpeakUp 自己的链路 ID。 - -### 数据库边界 - -- SpeakUp 业务数据使用 MongoDB:`users`、`authSessions`、`scenarios`、`practiceSessions`、`reviewItems`、`feedbacks`、`freeTopics`、`llmCalls` 等。 -- 文件存 OSS。 -- Langfuse 使用独立的 PostgreSQL、ClickHouse、Valkey 和对象存储。 -- 新的高频遥测不要写进 SpeakUp 业务 MongoDB。 - -### 已调研方案(尚未获准部署) - -- 若优先要“一个调试平台”统一页面体验、错误、回放、日志和后端 Trace:倾向 ClickStack/HyperDX(OpenTelemetry + ClickHouse)。 -- 若优先要轻量产品统计、页面访问、漏斗和业务事件:Umami + 独立 PostgreSQL 更轻;后端 Trace 仍需 OpenTelemetry/Tempo 或其他追踪后端。 -- 当前没有最终产品决定,不要直接安装或开回放。 - -### 隐私要求 - -- 只使用不透明内部 `userId`,不要上传手机号、邮箱、Bearer Token、授权码或分享 token。 -- 不上传录音、完整转写、笔记正文、AI 答案或请求体。 -- 保留并传播 `sourceType=human|ai_test`,避免自动化体验数据污染真人分析。 -- Session Replay 默认关闭;如后续启用,必须遮盖输入框、转写、答案、笔记和个人资料,限制采样率、保留期并提供明确用户选择。 -- 埋点不需要浏览器地理位置权限。 - -## 七、邮件通知状态与安全 - -- 工作流:`.github/workflows/feature-notify.yml` -- 发送脚本:`scripts/send_feature_email.py` -- 配置位置:Infisical `speakup-secrets / prod / notifications` -- 通用键:`EMAIL_PROVIDER`、`MAIL_FROM_NAME`、`MAIL_FROM_ADDRESS`、`FEATURE_MAIL_TO` -- SMTP 键:`SMTP_HOST`、`SMTP_PORT`、`SMTP_USERNAME`、`SMTP_PASSWORD` -- 收件配置已经纠正,读回校验为两个收件人;源码未写地址,也未因本次纠正发送测试邮件。 -- 邮箱授权码曾出现在交互记录中,应在邮箱后台轮换,然后只更新 Infisical 的 `SMTP_PASSWORD`。不要把旧值或新值写入本文档、Git、命令输出或日志。 - -## 八、发音纠正调研 - -- 资料:`docs/评测/发音纠正选型调研.md` -- 当前只有文本 ASR/纠正,不能可靠给出音素级发音、重音位置和近音词评分。 -- 建议先用专用口语评测引擎做 200–300 条真人录音对照,检查近音词、词重音、误报率、教师相关性、延迟和成本。 -- 未经明确批准,不购买套餐、不录入新密钥、不接生产。 - -## 九、继续执行的仓库纪律 - -1. 开始前: - - ```bash - cd /Users/mint/workspace/learning/speakup - git status --short - git pull --ff-only - ``` - -2. 保留用户未跟踪文档,只暂存本任务文件。 -3. Python 依赖和命令使用 `uv`;Node 使用 `pnpm`。 -4. 临时文件必须在系统临时目录中创建并注册清理,不写进用户目录或项目目录。 -5. 分支、提交、PR 标题与说明使用中文沟通,但路径、分支名和提交中不得出现执行者名称。 -6. 生产部署只能走 CI/CD;不要手动重启或手动替换生产容器。 -7. 完成状态要分别报告:本地修改、测试、提交、PR、合并、部署任务、线上版本烟测。 -8. 任何密钥、授权码、连接串和个人收件地址均不得进入 Git。 - -## 十、建议执行顺序 - -1. ~~完成当前全源码 500 物理行门禁~~ 已完成:5 个超限文件已拆分,前后端全量验证通过,随本次 PR 走 CI、合并与部署。 -2. 补做 History 视频在真实手机宽度和桌面宽度下的视觉验收;没有浏览器控制能力时明确保留此边界。 -3. 由产品明确选择埋点目标后,再写事件字典和隐私方案;未决定前不部署分析系统。 -4. 若决定接入发音评测,先购买小样本试用并用 200–300 条真人录音做供应商对照,不直接改生产链路。 -5. 在邮箱服务商侧轮换曾出现在交互记录中的旧授权码,只把新值更新到 Infisical,不进入 Git 或命令输出。 - -## 十一、完成定义 - -每一项不能只以“代码写完”结束。至少需要: - -- 相关单测通过; -- 全量 lint、测试、覆盖率与构建按风险执行; -- 视觉改动完成目标手机宽度和桌面宽度检查; -- `git diff --check` 通过; -- 只提交任务所属文件; -- PR 合并后等待 CI/CD 完成; -- `/api/version` 返回合并后的 commit SHA; -- 线上核心路径烟测通过; -- 如功能完成通知获明确授权,再通过手动邮件工作流发送人类可读的简短说明。