Files
Jyotisha/docs/tasks/TASK-mobile-viewport-scroll-lock-20260917.md
T

63 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 任务书 · 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%。
### T3 顶栏右侧两枚芯片同一套尺寸(BUG-921,产品 2026-09-17「积分块有点扁」)
- 实证:手机顶栏右侧「当前盘面」与积分「✦ 557」并排,积分块看起来又宽又矮。规则出处:`globals.css:885` `.credit-button` `min-height: 44px; min-width: 64px; padding: 0 var(--space-3); font-size: var(--type-caption)`(13px);`:2964-2973` 顶栏里的 `.rectification-board-peek` 同为 `min-height: 44px` 但 `padding: var(--space-2) var(--space-4)`、`strong` 用 `--type-body-sm`(14px)、圆角与边框另一套;两枚都塞在 `.chat-panel` 的 **46px** 顶栏行里(`:855-856`),上下各剩 1px,芯片没有呼吸空间,小字 + 强制 64px 最小宽度就显得扁。
- 修法:顶栏芯片抽一个共用类(或让 `.credit-button` 与顶栏里的 `.rectification-board-peek` 共用同一组声明):同高、同圆角、同边框、同字号(14px,数字 tabular)、同内边距,`min-width` 去掉;手机(`.is-compact` / ≤767px)顶栏行从 46px 放到 52px,芯片视觉高 40px、点击热区仍 ≥44px(用透明 `::before` 扩热区或外层 padding,BUG-695 的触屏命中区规则不得退)。桌面若同样受益可一并做,写进 `DESIGN.md` 顶栏一节。
- 验收:CSS 声明断言两枚芯片高度/字号/圆角一致、`.credit-button` 无 `min-width`;`chat-header` compact 高度断言;触屏热区契约测试(BUG-695 那条)仍绿;真机清单加一条「顶栏两枚芯片等高、不扁」。
### T2 记录
- BUG-921 同上记录。
- 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),需**关掉标签页重新打开**(不是刷新)才能复位。