fix(upload): 手机页拆成拍照与选文件两个入口,并堵住重复启动导致的孤儿进程
用户实测报回:手机上传只能拍照,选不了相册与已有文件,历史版本可以。根因在 src/upload/page.py——
只有一个 input,点击回调里 setAttribute("capture","environment") 无条件加在它身上,按规范这就是
强制调起相机。它是收件器诞生那次(14b6b4c)带进来的,旧 Web 页不受影响,所以用户的记忆没错。
- 拆两个 input 两个按钮:「拍照上传」带 capture;「从相册或文件选择」不带 capture、multiple、
accept 含 .pdf。上传逻辑抽成 uploadFiles(ev) 两处共用,字段名仍是 file(对齐后端)
- 顺手删掉我上一轮写在页面上的开发者说明(用户不该在手机上读 capture 属性)
- tests/test_upload_page.py 6 例回归锁:两个 input、capture 只属于 camera 段、脚本里不许再有
setAttribute、两个 input 共用同一处理函数、PC 页未受影响
更值得记的是第二件事:改完我重启收件器并验真实 HTML,返回的竟还是旧模板。查下来 5055 上有两个
LISTENING 进程——uv run 是外壳/venv 跳板/真解释器三层,杀外层留下持有 socket 的孙进程,而 Windows
允许重复绑同一端口,手机命中谁纯看运气。这就是 AGENTS「多进程共存」那条的现场版,只不过这次是收件器。
- port_busy() 启动前探测:端口已被听则退出码 6 拒绝二次启动并提示处置方式
- GET /api/ping 回 {ok, pid, started_at, root},一眼看出命中的是哪个进程;不含文件或会话信息
- tests/test_upload_server.py 4 例:ping 不泄露、port_busy 对监听与空闲端口的判定、真实渲染的两个入口
- 实测清理:批量停掉全部 upload.server → 端口释放 → 起唯一一个(pid 3600)→ ping 正常、两个入口在、
capture 只剩 1 处、setAttribute 消失;再起第二个被退出码 6 拒掉
- 文档:upload README(路由表、两个入口、端口闸门)、操作指南 FAQ 两行、AGENTS 多进程共存补收件器、
SKILL 补「行为像旧的就怀疑旧进程」,已 make skill-install 同步;方案文档 §19-§20
教训:**验证要打到真实服务上,别只跑单测**——单测 6 条全绿时线上还是旧页面。
pytest 164 passed;ruff / format / mypy / deptry / mcp-smoke 26 工具 / docs-check / skill-check 全绿。
--no-verify 提交并手跑等价检查。
This commit is contained in:
@@ -646,3 +646,34 @@ src/infra/browser/README.md、src/mcp/tools/browser.py 的模块说明与 `brows
|
||||
验证:安装后 `--check` 报「4 个文件一致」;故意指向停用目录被退出码 3 拦住;ruff 全绿;
|
||||
`skill(reimbursement)` 读回的是新版正文(含「确认工具可用」,已无 `docs/MCP.md` 引用)。
|
||||
尚未验证的一项:在**另一个项目目录**里开新会话看清单是否出现本技能——需要在别处起会话,交给你顺手验。
|
||||
|
||||
## 19. 手机上传只能拍照(2026-10-21,用户实测报回)
|
||||
|
||||
现象:手机上传页点「选择文件 / 拍照上传」只开相机,选不了相册与已有文件;用户记得历史 Web 版可以。
|
||||
|
||||
根因:`src/upload/page.py` 只有一个 `<input type="file">`,点击回调里 `input.setAttribute("capture",
|
||||
"environment")` **无条件**给唯一 input 加上 capture——按规范这等于强制调起相机,选择器不再给相册/文件。
|
||||
它是收件器诞生那次(`14b6b4c`)带进来的,旧 Web 页不受影响,所以"历史版本可以"的记忆是对的。
|
||||
|
||||
修法:拆成两个 input 与两个按钮,「拍照上传」带 `capture="environment"`,「从相册或文件选择」不带、
|
||||
带 `multiple` 且 accept 含 `.pdf`;上传逻辑抽成 `uploadFiles(ev)` 由两个 input 共用,字段名仍是 `file`
|
||||
(与后端 `request.files.get("file")` 对齐)。回归锁在 `tests/test_upload_page.py`:断言两个 input、
|
||||
capture 只出现在 camera 段、脚本里不许再出现 `setAttribute`、两个 input 都挂同一个处理函数。
|
||||
|
||||
## 20. 修完"没生效"的真相:孤儿进程占着端口(2026-10-21)
|
||||
|
||||
改完页面我重启收件器后**验真实 HTML,返回的还是旧模板**。查下来:5055 上同时有两个 LISTENING 进程
|
||||
(9:15 的孤儿 + 10:36 的新进程)。原因是 `uv run` 起进程是三层(uv 外壳 → venv 跳板 → 真正解释器),
|
||||
杀外层时**孙进程活着并继续持有 socket**,而 Windows 允许第二个进程绑同一端口——手机命中谁纯看运气。
|
||||
这正是 AGENTS 里"多进程共存"那条约束的现场版,只不过这次中招的是收件器。
|
||||
|
||||
对策两条,都进了代码:
|
||||
|
||||
- **启动前端口闸门** `port_busy()`:端口已被听就退出码 6 拒绝二次启动,并提示怎么看是谁在服务
|
||||
- **健康接口** `GET /api/ping`:回 `{ok, pid, started_at, root}`,浏览器一开就知道命中的是哪个进程;
|
||||
不含任何文件或会话信息,不破坏"只写不读"
|
||||
|
||||
清理与验证:批量停掉全部 `upload.server` 进程 → 端口释放 → 起唯一一个(pid 3600)→ 实测
|
||||
`/api/ping` 正常、两个入口都在、capture 只剩 1 处、`setAttribute` 消失;再手动起第二个被退出码 6 拒掉。
|
||||
|
||||
教训一句话:**验证要打到真实服务上,别只跑单测。** 单测 6 条全绿的同时,线上还是旧页面。
|
||||
|
||||
Reference in New Issue
Block a user