- 定位线上故障根因:单序列页点「开始分析」时会先逐个串行上传文件,该循环无超时、无进度、无错误暴露,任一请求挂住就永久转圈,
/api/analyze根本没发出(服务器端两个会话只落地部分文件、且没有任何分析日志,可佐证)。 - 重写
uploadFiles:客户端先按后端同样的base_i.ext约定给重名文件去重(消除并发下后端file.exists去重的竞态)、首个文件串行上传以建立会话、其余按并发 4 上传、按下标回填保证返回顺序与入参一致、单请求 60 秒超时后中止并返回带文件名的可读错误(并提示改用 ZIP 一次性上传)。 - 修正失败判定:plumber 把
success序列化成[false],而数组本身是真值,旧代码会把后端失败当成功继续往下走;改为解包后再判断。 - 单序列页与多序列页在上传阶段显示「上传 i/N…」,不再只有一个没有说明的转圈。
- 「已上传 N 个文件」改为「已选择 N 个文件」——该文案是本地选中数,此前会让人误以为文件已经传上去了,是这次误判为「转发故障」的一环。
- 新增
frontend/src/lib/__tests__/upload.test.ts(8 例):并发乱序完成仍保序、首请求不带会话头且其余复用、同名去重、进度回调递增到 total、请求卡住按超时中止并报出文件名、success:[false]判为失败、中途失败短路不再发起后续请求、空列表返回错误。 npx vitest run:16 个测试文件 / 51 例全部通过。npx tsc --noEmit通过;npm run build通过。- 浏览器实测(本地 dev server + stub 上传响应):按钮依次显示「上传 0/9…→上传 1/9…→上传 5/9…」;请求发起时序为 0ms / 982ms×4 / 1982ms×4,确认首个串行、其余并发 4;首请求无
x-session-id、其余均带后端返回的会话 id。 - 未验证:60 秒默认超时没有在真实慢网络上跑过(单测用 20ms 覆盖同一分支)。
- 顺带发现未修:
frontend/src/app/multi/page.tsx:140有一处既有 eslintreact-hooks/set-state-in-effect报错,与本轮改动无关(stash 后复现),未处理。
Changed files:
frontend/src/lib/api.ts: 重写uploadFiles(去重 / 建会话 / 并发 / 保序 / 超时 / 进度回调 / 失败判定),analyzeBatch增加可选上传进度回调参数。frontend/src/app/page.tsx: 上传阶段按钮显示「上传 i/N…」。frontend/src/app/multi/page.tsx: 同上。frontend/src/components/FileUploader.tsx: 「已上传」文案改为「已选择」。frontend/src/lib/__tests__/upload.test.ts: 新增,上传逻辑单测。
Rollback:
- 本轮改动尚未提交,回滚点为当前 HEAD
c5dac80:git checkout -- frontend/src/lib/api.ts frontend/src/app/page.tsx frontend/src/app/multi/page.tsx frontend/src/components/FileUploader.tsx && rm frontend/src/lib/__tests__/upload.test.ts
承接上一条:上一条的「逐文件并发上传」方案已被本条取代(同一轮工作内的方案升级,保留上一条记录不改写)。 选型依据:实测 ab1 压缩率 52.6%(209 个文件 45MB→24MB),且 30MB 单请求经公网域名 200 OK / 5.7s, 中间层没有 body 上限,所以"打成一个包传"这条路可行且收益更大。
- 前端上传改为:本地按去重后的文件名打包成一个 ZIP(fflate,level 6),单请求 POST 到已有的
/api/upload/zip。几十次往返压成一次,慢网络下不再"断一次就整批卡死"。 - 上传改用 XHR 而非 fetch,拿到真实的字节级上传进度;按钮依次显示「打包 i/N…」→「上传 xx%…」(封顶 99%,留给服务端解压)。
- 超时判据从"单请求总时长"改为"传输停滞":60 秒内没有任何字节发出才中止并报「上传停滞」。慢但仍在传的连接不会被误杀,而线上那种传到一半彻底不动的情况会被立刻暴露。
- 返回的文件名以本地清单为准(入参顺序),不用服务端
list.files的字典序;并校验服务端解压清单与上传清单一致,不一致直接报错,避免错位的文件名流进指派表。 - 后端
start_api.R的 postroute hook 增加请求级访问日志:[api] METHOD PATH?QUERY status=xxx NNms session=xxx。此前任何一层都没有请求日志,故障只能靠/tmp里的文件数倒推。
frontend/src/lib/__tests__/upload.test.ts重写为 13 例:单请求且打到/api/upload/zip、请求体是真 ZIP 且条目名正确、服务端返回字典序时仍按入参顺序返回、同名去重、打包/上传两段进度、停滞中止、success:[false]判失败、解压清单不一致报错、非 JSON 响应报可读错误、空列表报错,外加uploadStageLabel三例。npx vitest run:16 文件 / 56 例全过;npx tsc --noEmit通过;npm run build通过。Rscript -e 'parse("start_api.R")'语法通过。- 浏览器真实回归(本地 dev → 生产
/api/upload/zip):4 个故意非字典序命名的文件上传成功,服务端 session 目录落地a-/b-/m-/z-sample.ab1四个原名文件(无_i改名),确认"以本地清单为准"的映射成立;随后分析按预期返回「所有文件分析失败」(测试文件是假字节),错误横幅正常显示、转圈正常结束。测试会话已从服务器删除。 - 进度文案实测序列:
打包 4/4… → 上传 16% → 33% → 50% → 66% → 83% → 99% → 结束。 - 未验证:60 秒停滞阈值未在真实劣化网络上跑过(单测用 30ms 覆盖同一分支)。
Changed files:
frontend/src/lib/api.ts:uploadFiles改为打包 ZIP + XHR 单请求上传,新增postZipUpload、UploadProgress、uploadStageLabel。frontend/src/app/page.tsx/frontend/src/app/multi/page.tsx: 按钮显示打包/上传两段进度。frontend/src/components/FileUploader.tsx: 「已上传」文案改「已选择」。frontend/src/lib/__tests__/upload.test.ts: 重写单测。frontend/package.json/package-lock.json: 新增依赖 fflate 0.8.3。start_api.R: postroute 增加请求级访问日志(未声明 value 形参,不影响响应体)。
Rollback:
- 回滚点
c5dac80(本次改动之前的 main)。git revert <merge/commit>后服务器git pull+cd frontend && npm run build+sudo systemctl restart editr-frontend.service editr-api.service。
- 核对现有
Non_Target_Edit_Rate_at_Top_Position的真实计算口径,确认其表示“总非参考碱基比例”,包含预期编辑碱基信号。 - 确定向后兼容方案:保留旧字段,新增目标位点与最高编辑位点的非预期替换率和编辑产物纯度,并新增目标位点总非参考碱基比例。
- 完成
/metrics指标说明页的页面结构、字段范围、兼容策略、测试要求与 Sanger 数据解释边界设计。
- 对照
R/analysis.R、R/data_processing.R、server.R、前端类型与顶栏结构完成只读一致性检查。 - 使用仓库内置 AB1 样本核验四碱基比例之和、总非参考比例、非预期替换率和编辑产物纯度公式。
- 设计文档自检:无
TBD/TODO占位;新增字段、空值规则、兼容路径和验收标准一致。
Changed files:
docs/superpowers/specs/2026-07-31-edit-metrics-reference-design.md: 新增编辑质量指标与指标说明页书面设计。progress.md: 追加本轮设计与核验记录。
Rollback:
- 删除
docs/superpowers/specs/2026-07-31-edit-metrics-reference-design.md,并回退progress.md末尾本条记录。
- 将已批准的编辑质量指标设计拆解为后端公式、前端说明页、文档、全量验证和生产部署五个可独立验证的实施任务。
- 明确测试驱动顺序、五个新增字段的接口名称、旧字段兼容要求以及部署后的 API/页面/公式冒烟检查。
- 对照书面设计逐项检查计划覆盖:公式、空值规则、旧 Shiny 路径、API/Excel/归档、前端类型、
/metrics、导航、文档、完整测试及部署均有对应步骤。 - 检查计划中的字段名称在前后任务间一致,无占位步骤。
Changed files:
docs/superpowers/plans/2026-07-31-edit-metrics-reference.md: 新增完整实施与部署计划。progress.md: 追加本轮实施计划记录。
Rollback:
- 删除
docs/superpowers/plans/2026-07-31-edit-metrics-reference.md,并回退progress.md末尾本条记录。
- 保留历史字段
Non_Target_Edit_Rate_at_Top_Position及其现有口径,明确它表示最高编辑位点的“总非参考碱基比例”,包含预期编辑信号。 - 新增目标位点的总非参考比例、非预期替换率、编辑产物纯度,以及最高编辑位点的非预期替换率和编辑产物纯度,共 5 个 summary 字段;所需碱基比例缺失或分母不合法时返回空值。
- 新增前端
/metrics页面与顶栏入口,集中展示核心术语、summary/raw 字段、计算公式、C→T 示例和 Sanger 数据解释边界。 - 新增稳定指标契约文档,并在 README 增加入口说明。
- 修复新增导航入口后在窄屏被挤成竖排的问题;小屏保持导航单行、隐藏版本号,指标表格继续独立横向滚动。
Rscript run_tests.R:20/20 通过,包含新增tests/test_edit_quality_metrics.R。cd frontend && npm test:17 个测试文件 / 60 例全部通过。cd frontend && npx tsc --noEmit:通过。cd frontend && npm run build:通过,/metrics作为静态页面生成。cd frontend && npx eslint src/app/metrics/page.tsx src/components/ui/TopBar.tsx src/lib/metric-definitions.ts src/lib/__tests__/metric-definitions.test.ts:通过。- 浏览器实测桌面与 390×844 窄屏:字段、公式和示例完整显示;页面无整体横向溢出,表格容器可横向滚动;四个导航项保持单行;控制台无 warning/error。
- 完整
npm run lint仍有 4 个既有错误和 1 个既有警告,均位于本轮未修改文件,未扩大范围处理。
Changed files:
R/data_processing.R: 计算并输出 5 个新增编辑质量 summary 字段。server.R/ui.R: 旧 Shiny 结果表同步展示新增字段。tests/test_edit_quality_metrics.R: 覆盖目标位点、最高编辑位点、零分母和缺失输入。frontend/src/lib/types.ts: 补充新增 summary 字段类型。frontend/src/lib/metric-definitions.ts: 集中维护指标名称、公式、语义和注意事项。frontend/src/app/metrics/page.tsx: 新增指标说明页。frontend/src/components/ui/TopBar.tsx: 增加指标说明入口并修复窄屏导航。frontend/src/lib/__tests__/metric-definitions.test.ts: 校验字段文档、导航入口和窄屏样式约束。docs/metrics.md: 新增稳定指标计算契约。README.md: 增加指标说明页与契约文档入口。
Rollback:
- 在合并后的主分支执行
git revert aa2f238..HEAD,重新构建并重启前后端服务;本轮未修改数据库结构或历史数据。
- 将生产服务器
frontend/package-lock.json的既有未提交改动保存到可恢复的stash@{0},说明为codex-predeploy-package-lock-2026-07-31,未覆盖服务器现场。 - 生产仓库从
e2e6a26快进到28f31cb,按 systemd 使用的 Node 20.20.2 环境重新安装依赖并完成 Next.js 生产构建。 - 首次
npm ci因 npm audit 网络连接进入CLOSE-WAIT且日志停止增长而中止;改用npm ci --no-audit --no-fund后按 lockfile 安装成功。 - 重启
editr-api.service与editr-frontend.service,并确认两项服务持续为active。
GET http://127.0.0.1:8001/api/health:返回status=ok、version=2.0.0、db_ready=true。GET http://127.0.0.1:3000/metrics:页面包含Edit_Product_Purity_at_Target_Position。- 生产服务器执行
/home/wang/.local/envs/editr2/bin/Rscript tests/test_edit_quality_metrics.R:通过。 - 浏览器访问生产
/metrics:顶栏入口、符号约定、摘要字段和计算示例正常渲染,控制台无 warning/error。 - 生产仓库 HEAD 为
28f31cb0cf560a6c8e27d8b6e22cf8c2b6f4228a,部署后工作区干净。
Changed files:
progress.md: 追加生产部署、验证和服务器 stash 记录。
Rollback:
- 在主分支回退
e2e6a26..28f31cb的功能提交并推送,生产服务器拉取后重新执行前端构建与两项服务重启;服务器原锁文件改动仍可从stash@{0}恢复。
- 用户反馈 ZIP 上传返回 HTTP 500、记录页面为空后,立即停止进一步重启和数据操作,先只读核对 API/前端日志、SQLite 与归档目录。
- 确认持久化数据未丢失:SQLite 中仍有 15 条归档记录,归档目录约 157MB,最近记录时间仍为 2026-07-29。
- 定位直接故障为生产前端运行目录缺少
node_modules/next,Next.js 代理请求时报Cannot find module 'next/dist/compiled/http-proxy';因此 ZIP 上传和归档列表等经/api/*rewrite 的请求统一返回 500。 - 停止前端服务后重新执行完整依赖安装,显式确认
next/dist/compiled/http-proxy可解析,再完成生产构建并启动前端服务;R API 与数据库、归档目录均未改动。
- 经前端代理请求
GET /api/health:返回status=ok、db_ready=true。 - 经前端代理请求
GET /api/archive/list:返回成功且共 15 条记录。 - 经前端代理实际上传仅含测试文件的微型 ZIP:
POST /api/upload/zip返回 200、成功解压smoke.ab1;测试会话随后清理。 - 浏览器打开生产
/report:15 条归档记录重新正常显示,包含 2026-07-29 最新记录。 editr-api.service与editr-frontend.service均为active。- 修复后日志未再出现
Cannot find module 'next/dist/compiled/http-proxy'。
Changed files:
progress.md: 追加生产代理故障、数据核验和恢复记录。
Rollback:
- 本轮未修改代码、数据库或归档文件;若需回退依赖恢复动作,仅停止前端服务并按当前
package-lock.json重新安装即可。