docs(tasks): iPhone 键盘收起/刷新后整页上移任务书(BUG-920)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
73d3c99ccd
commit
6ca28e060b
@@ -105,6 +105,7 @@
|
||||
| `TASK-rectification-dead-d9-choice-20260916.md` | `PROGRESS-rectification-dead-d9-choice-20260916.md` | 真机(6aabbe38):删卡流程已通,但**P0** D9 感情题没有自己的探针,`stampChoiceSchemaWithProbe` 退到最高增益探针,盖上了 **D24 学业六星座对照探针**(BUG-375 (1) 复发):答「没有」按 D24 计分,04:48–04:59 收窄不可信;GET 投影又匹配不上 → 无卡(BUG-912);打字答「没有」后平局直接出交付卡待回放定性(BUG-913);`NAKSHATRA_TRAITS` 整对塞进一个 option,卡头把相反两极列成共同点(BUG-914)。**串行在 year-focus-overlay 验收之后** | **验收未通过(P0)** | `227a7571`+`8d7dfbf0`:不借探针、一列一句、BUG-913 定性均过;但自建探针只在盖戳时注入内存 state,生产答题路径读持久化 state → `stale_probe` 报错、GET 无卡、idle 立刻 superseded(生产路径回放实证)。修复单见下一行 |
|
||||
| `TASK-rectification-dead-d9-choice-fix-20260916.md` | `PROGRESS-rectification-dead-d9-choice-fix-20260916.md` | **验收修复单**:自建分盘探针照 `withNakshatraBoundaryProbe` 模式在每个读 state 的地方(盖戳 / 答题 / `previousInferenceFromReceipt` / GET 投影 / idle 过期判定 / 计划承接)从 receipt 确定性重建(BUG-915);测试改走生产路径、删手工塞探针的 fixture;顺带 host 前置挪到会话校验之后、登记 phase(BUG-916);死卡不得配 `collect_waiting` 占位(BUG-917);**产品决策 1c**:风格题默认不作全额计分(沿用 09-09 ±1 拍板),无事件探针就走下一条线。**staging 现状:风格题答不了,先跳过**。§1d 已记 **产品决策 (b)** | 已验收 | `5113d457`(BUG-915~917,产品拍板 (b) 风格题不计分)。Claude 独立验收:生产路径测试(receipt 不含自建探针)答题 applied / GET 出卡 / idle 不误伤;合并候选门禁 tsc 0 / lint 0 error / npm test 3427 条 31 红与基线逐条相同 / `/` Static / 首屏 gzip 620,107→620,193(+0.01%)/ pytest 63 绿 / 快速门 Python 798 绿。真机清单 `docs/testing/rectification-dead-d9-choice-fix-20260916.md` |
|
||||
| `TASK-rectification-mobile-timeline-readout-20260917.md` | `PROGRESS-rectification-mobile-timeline-readout-20260917.md` | 手机截图:时间轴读数第四项被裁成「已…」(nowrap + inset 内边距,BUG-918);「跳到最新」浮层压住选项 C(BUG-919)。灰卡与「再说一件」矛盾归修复单 BUG-915/917 | 已验收 | `e8e98bbd`(合入时重放为本分支提交,BUG-918/919)。时间轴相关 71 条 + 全量同上;手机上隐藏「已对照 N 件」符合 DESIGN §10;桌面是否也移除待产品拍板。真机清单 `docs/testing/rectification-mobile-timeline-readout-20260917.md` |
|
||||
| `TASK-mobile-viewport-scroll-lock-20260917.md` | `PROGRESS-mobile-viewport-scroll-lock-20260917.md` | iPhone 上键盘收起 / 刷新后整页上移、顶栏点不到(BUG-920):iOS 不支持 `interactive-widget`,键盘弹出时 Safari 滚动 window,`html/body overflow: hidden` 让用户拉不回来,reload 又还原 `scrollY`;代码里无任何 window 级复位。补 `ViewportScrollLock`(`scrollRestoration=manual` + `visualViewport` 复位)。与今天两单无关,既有缺陷 | 待领取 | — |
|
||||
|
||||
### 聊天主链路与首页
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# 任务书 · iPhone 上键盘收起 / 刷新后整页上移,顶栏点不到(2026-09-17)
|
||||
|
||||
## 0. 基线
|
||||
|
||||
- 基线 commit:`73d3c99c`(staging 已部署)。分支:`codex/mobile-viewport-scroll-lock-20260917`。
|
||||
- 纯前端;不动 `page.tsx`(新逻辑放 `frontend/src/components/` 的一个客户端组件,从 `layout.tsx` 挂载)。
|
||||
- BUG 段:**BUG-920 起**(基线最大 BUG-919)。
|
||||
|
||||
## 1. 事故实证(产品负责人 2026-09-17 09:34 iPhone Safari 截图,staging `73d3c99c`)
|
||||
|
||||
- 刷新校正页后,顶栏(侧栏按钮 / 「9月17日 · 生时校正」/ 当前盘面 / 点数)和时间轴都不在屏幕里,第一条可见内容是消息正文;输入框下面空出约 260px 的白底,浏览器工具栏之上什么都没有。产品原话:「点不到 tab 栏,没找到入口,整体上移了」。
|
||||
- 空白高度 ≈ 整页上移的距离:整个应用壳被 `window` 级滚动推上去了,不是壳内某个容器的问题。
|
||||
|
||||
## 2. 根因(代码可证部分 + iOS 行为推断,执行方需真机确认)
|
||||
|
||||
1. `globals.css:322` `html, body { height: 100%; overflow: hidden; }`,应用壳靠 `.chat-app` / `.chat-panel` 的 `height: 100%` 撑满;**没有任何代码处理 `window.scrollY`**:`git grep` `visualViewport` / `scrollRestoration` / `window.scrollTo` 在 `frontend/src` 0 命中(`use-billing-panel.ts:195` 的 `scrollIntoView` 是容器内滚动)。
|
||||
2. iOS Safari 不支持 `viewport` 的 `interactive-widget=resizes-content`(`layout.tsx:26` 只对 Chrome Android 生效)。iOS 上键盘弹出时**布局视口不变、Safari 直接滚动 window** 让聚焦的输入框露出来;`overflow: hidden` 挡不住这次程序滚动,却挡住了用户手指把页面拉回来。键盘收起后 `window.scrollY` 留在原地 → 顶栏在屏幕上方、底部空白。
|
||||
3. 刷新不能自愈:Safari 对同 URL 的 reload 默认恢复滚动位置(`history.scrollRestoration === "auto"`),`scrollY` 被原样还原,所以「刷新想测试」看到的还是上移后的页面。
|
||||
4. 与今天上线的两单无关:BUG-919 只改了 `.message-list` 底部留白和 `.conversation` 的 `scroll-padding-block-end`,都在滚动容器内部;BUG-918 只改时间轴内边距。这条是既有缺陷,今天因为在手机上连续输入才被看到。
|
||||
|
||||
## 3. 决策记录
|
||||
|
||||
- 产品:手机上任何时候顶栏都必须能点到;不接受「用户自己想办法滚回去」。
|
||||
- 不改 `html, body` 的 `overflow: hidden`(桌面与滚动锚定依赖它),只补 window 级复位。
|
||||
|
||||
## 4. 任务分解
|
||||
|
||||
### T1 window 级滚动复位(BUG-920)
|
||||
|
||||
- 新客户端组件 `frontend/src/components/viewport-scroll-lock.tsx`,在 `layout.tsx` 与 `StaleClientRecovery` 并列挂载,只在 `pointer: coarse` 或 `visualViewport` 存在时启用:
|
||||
- 挂载时 `history.scrollRestoration = "manual"`,并 `window.scrollTo(0, 0)`;
|
||||
- 监听 `window.visualViewport` 的 `resize` 与 `scroll`、`window` 的 `pageshow` / `orientationchange`、`document` 的 `focusout`:当 `visualViewport.height` 回到 ≥ `window.innerHeight - 1`(键盘已收)且 `window.scrollY > 0` 时 `window.scrollTo(0, 0)`;键盘打开期间(`visualViewport.height < innerHeight - 100`)不动,让 Safari 把输入框露出来。
|
||||
- 卸载时移除监听。不用 `setInterval`。
|
||||
- 验收:源码契约测试(挂载点、`scrollRestoration = "manual"`、监听事件集合、键盘打开期间不复位的判定);jsdom 下模拟 `visualViewport` 对象触发 resize 断言 `scrollTo(0,0)` 被调用一次;`tsc` / lint / `npm test` 失败清单同基线;`/` Static;gzip ±2%。
|
||||
|
||||
### T2 记录
|
||||
|
||||
- BUG-920(状态按真机结果:本机无 iOS 只能写 `resolved` 附「真机待核」或 `investigating`,不得编)、CHANGELOG、`frontend/DESIGN.md` 移动端一节加一句「window 级滚动由 ViewportScrollLock 复位」、`docs/testing/mobile-viewport-scroll-lock-20260917.md`:iPhone Safari 上 (a) 点输入框弹键盘→收起→顶栏仍可点;(b) 弹键盘状态下刷新→顶栏在顶;(c) 横竖屏切换后无空白带;(d) Android Chrome 不受影响。
|
||||
|
||||
## 5. 让步顺序
|
||||
|
||||
1. 若 `focusout` 时机早于键盘收起动画导致复位被 Safari 再次覆盖,改为在 `visualViewport.resize` 上做,`focusout` 只做兜底延时 300ms 一次。
|
||||
2. T2 不可让步。
|
||||
|
||||
## 6. 开工前置命令
|
||||
|
||||
```bash
|
||||
git fetch origin --prune
|
||||
git worktree add -b codex/mobile-viewport-scroll-lock-20260917 .worktrees/mobile-viewport-scroll-lock-20260917 origin/staging
|
||||
cd .worktrees/mobile-viewport-scroll-lock-20260917/frontend && ./node_modules/.bin/tsc --noEmit && npm run lint && npm test 2>&1 | tail -20
|
||||
```
|
||||
|
||||
## 7. 环境缺口
|
||||
|
||||
- 本机无 iOS / Chrome;根因第 2、3 条是按 iOS Safari 已知行为推断,执行方与产品在真机上按 §4 T2 清单确认。产品临时绕过:键盘收起后把页面往下拉一下不会有效(overflow hidden),需**关掉标签页重新打开**(不是刷新)才能复位。
|
||||
Reference in New Issue
Block a user