2026-09-15 的 UI 走查(web + 移动端)结果落盘。 并入 TASK-chat-reading-load:折叠层里的四列表在窄屏没有重排规则 (767 的卡片化只认三列,width:100% 又让 overflow-x 永不触发),它是 折叠能不能落地的前置;全局 input/select 是 14px,iOS 聚焦会把整页放大, 命中整条注册与资料录入漏斗。 新单 TASK-mobile-touch-and-breakpoints(BUG-695~697):消息操作按钮 命中区 27×34 且相邻只隔 1px;CSS 平板上限 900px 与 sidebar-state.ts 的 1024 不一致,901–1023 是混合态;报告域 720/760/860 三刀互不对齐, 761–860 目录已塌、正文还是桌面。含断点白名单契约测试作为防复发。 另加 docs/testing/ios-keyboard-composer-20260915.md:html/body overflow hidden + 100dvh 外壳 + sticky 输入框 + interactiveWidget 只有 Android 认, 是键盘遮挡的风险形状,但代码断不了真假,交真机确认后再决定是否立单。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155nFCgCHtoA7jhSDGmZmMu
15 KiB
TASK · 消息操作触控目标 + 断点错位(BUG-695~697)
- 日期:2026-09-15
- 基线 commit:
origin/staging@41473261 - 执行分支:
codex/mobile-touch-and-breakpoints-20260915 - 工作树:
.worktrees/mobile-touch-and-breakpoints-20260915 - 串行依赖:必须排在
TASK-chat-reading-load-20260915.md之后。 两轮都改frontend/src/app/globals.css,不得并行。开工前确认前一轮已合入staging,并以合入后的 staging 为基线,而不是41473261。
1. 来源
2026-09-15 的 UI 走查(web + 移动端)在首页与聊天两件事之外,另外查实三处。其中两处已并入 TASK-chat-reading-load-20260915.md(折叠层宽表、输入框 14px),本单处理剩下的两类。
走查同时确认了一批做得好、本轮不要动的东西,列在 §6,避免执行方顺手"优化"掉。
2. 事故实证
行号会漂移,定位以符号名为准。核对于 origin/staging@41473261。
2.1 BUG-695|消息操作按钮触控目标 27×34,相邻只隔 1px
frontend/src/app/globals.css:
.message-actions { gap: 1px; margin: -4px 0 8px 42px; }
.message-actions button { width: 26px; height: 26px; flex: 0 0 26px; }
/* Visual icon stays 26px. Neighbours sit 27px centre-to-centre (26px + 1px gap)
and follow-ups start after an 8px bottom margin, so a 44×44 overlay would
steal clicks. This is the largest non-overlapping hit: 27×34. */
.message-actions button::after { width: 27px; height: 34px; }
复制 / 点赞 / 点踩 / 重新生成四个按钮并排,中心间距 27px。WCAG 2.5.8 的 24×24 下限过了,但离触屏推荐的 44×44 差很远;在手机上点赞很容易点成点踩。
注释本身写清楚了这是被布局逼出来的:往下 8px 就是 follow-up 药丸,44×44 的覆盖层会抢走它们的点击。所以这不是「把 27 改成 44」一行能解决的,得先给这一行让出空间。
同类但优先级低得多的还有一处:.markdown-code-copy { min-height: 28px }。本产品的回答里代码块极少,顺手一起处理,做不了不阻塞。
2.2 BUG-696|CSS 的平板上限 900px 与 JS 的 1023 不一致,901–1023 是混合态
JS(frontend/src/lib/sidebar-state.ts → sidebarViewportForWidth):
width < 768 → mobile
width < 1024 → tablet
否则 → desktop
侧栏宽度跟着它走(globals.css 的 [data-viewport="tablet"]… 用 --sidebar-width-tablet: 240px,desktop 用 288px)。
但 CSS 里另有两处用 max-width: 900px 当平板上限:
.code-form { grid-template-columns: 1fr 1fr; }.model-selector-trigger { max-width: 100%; grid-column: 1; grid-row: 1; }
以及两处 (min-width: 768px) and (max-width: 900px) 的首页栅格规则。
结果:901–1023px 之间,data-viewport 还是 tablet(侧栏 240px 窄版),但所有 max-width: 900px 的规则已经失效,模型选择器和首页栅格回到桌面排法。这个区间正好是 iPad 横屏(1024 刚好落在 desktop 侧)、桌面窗口拖到一半、以及不少 Android 平板。
2.3 BUG-697|报告域三个断点互不对齐,761–860 是混合态
| 断点 | 规则 | 文件位置 |
|---|---|---|
max-width: 720px |
.report-center-shell 内边距、.report-center-hero 单栏、按钮满宽 |
报告中心 |
max-width: 760px |
.personal-report-reader-body 内边距、.personal-report-document 内边距、封面单栏 |
报告阅读区 |
max-width: 860px |
.personal-report-md-layout 单栏、.personal-report-toc 静态化、目录切抽屉 |
报告目录 |
在 761–860px:目录已经塌成抽屉了,正文还是桌面内边距。在 721–760:阅读区已窄、报告中心还是桌面。
另外整份样式表的宽度切点共有:430 / 480 / 620 / 639·640 / 640·641 / 720 / 760 / 767·768 / 860 / 900 / 1024。其中 639·640(会员)与 640·641(校正)是两组各自自洽的邻接对,相差 1px,不造成错位但属于噪音;430 与 480 是两个小屏断点;620 是出生资料录入卡独有的。
3. 决策记录(产品已授权)
产品负责人 2026-09-15 确认按下述范围执行:
- BUG-695 修:把消息操作的触控目标做到触屏下 44×44,代价是让出垂直空间。
- BUG-696 / 697 修的是「错位」,不是「统一」。 本轮只消除 §2.2 和 §2.3 两个混合区间,以及把噪音断点收敛。不做全表断点重构——本会话没有浏览器,全表重排的视觉回归验证不了,收益也远小于风险。
- 收敛的方式是约定 + 契约测试,不引 PostCSS 插件。 CSS 自定义属性不能用在媒体查询条件里,而本仓 Next.js 配置没有
postcss-custom-media。本轮不为此加构建依赖(frontend/AGENTS.md§7.7:不得顺手升级依赖)。 - 视觉验收是环境缺口。 本轮没有浏览器与真机,断点改动的目视确认写成
docs/testing/清单交给产品,不得在 PROGRESS 里写成「通过」。
4. 硬红线
./node_modules/.bin/tsc --noEmit0 错;npm run lint0 error。- 测试总数不得低于开工时
origin/staging的实测;改既有断言写「原值 / 新值 / 原因」三栏。 next build后/仍是○ Static;首屏 gzip ±2% 内。- 不得改任何组件的 JSX 结构来迁就断点——本轮是样式与命中区的调整。唯一允许的结构改动是 BUG-695 需要的那点垂直空间,且必须在
frontend/DESIGN.md里写明。 - 不得动
sidebarViewportForWidth的 768 / 1024。它是 JS 侧真相,CSS 向它对齐,不是反过来。 - 不得动 admin(antd + Refine,
[class^="ant-"]排除项照旧)。 - 不得顺手升级依赖、不得引入 PostCSS 插件、不得顺手修不在本单里的 warning。
- 改 UI 的同一提交内更新
frontend/DESIGN.md。
5. 任务分解
任务 1 · BUG-695:消息操作触控目标
1.1 在触屏下(@media (pointer: coarse))把 .message-actions button 的命中区做到 44×44,并让出它需要的空间:
- 加大
.message-actions的下外边距(现为8px),或加大.conversation-follow-ups的上外边距,使 44px 的覆盖层不与 follow-up 药丸重叠。 - 按钮之间的
gap从1px放大到至少var(--space-1),使相邻命中区不互相吞。 - 视觉图标保持 26×26,只放大命中区与间距。桌面端(
pointer: fine)保持现状不变。
1.2 保留 ::after 覆盖层那条注释的意图,但更新注释内容——它现在描述的是旧结论(「44×44 会抢走点击」),改完之后必须说明新的几何关系为什么不会抢。留着过期注释比没有注释更坏。
1.3 .markdown-code-copy 的 min-height 在 (pointer: coarse) 下提到 44px。做不了不阻塞,写进 PROGRESS。
验收标准
(pointer: coarse)下.message-actions button的有效命中区 ≥ 44×44,且相邻按钮命中区不重叠、不与 follow-up 药丸重叠。(pointer: fine)下的几何与改动前逐值一致。- 新增 CSS 契约测试断言上述两条(本仓已有大量
globals.css文本断言的先例,照同样方式写)。 - 桌面端四个按钮的视觉排布肉眼无变化——这一条是真机/浏览器项,写进
docs/testing/。
任务 2 · BUG-696:CSS 平板上限对齐 JS 的 1024
2.1 把下列 max-width: 900px 改为 max-width: 1023px,使 CSS 的平板区间与 sidebarViewportForWidth 的 [768, 1024) 完全重合:
.code-form { grid-template-columns: 1fr 1fr; }.model-selector-trigger { max-width: 100%; grid-column: 1; grid-row: 1; }
2.2 两处 (min-width: 768px) and (max-width: 900px) 的首页栅格规则同样改为 and (max-width: 1023px)。
⚠️ 其中一处是 .starter-list/.starter-hero 相关。如果前一轮(TASK-chat-reading-load-20260915)已经删掉了主题卡,先确认这些选择器是否还有作用域;已成孤儿的规则直接删除,不要改了断点再留着。
2.3 改完检查 901–1023 区间内这几处的排版是否合理:模型选择器(是否还挤)、首页两张产品卡(单栏还是双栏)、兑换码表单。这是浏览器项,写进清单。
验收标准
grep -n "max-width: 900px" frontend/src/app/globals.css无结果。- 新增契约测试:断言
globals.css中不出现900px作为宽度断点,且sidebar-state.ts的阈值仍是768/1024。 - 901 / 1023 / 1024 三个宽度下的目视确认写进
docs/testing/。
任务 3 · BUG-697:报告域三断点收敛为一个
3.1 把 max-width: 720px(报告中心)与 max-width: 760px(报告阅读区)合并到 max-width: 767px,与全局移动断点对齐。两组规则内容不变,只改条件并合并到同一个块里(或保持两块但条件一致,择一,写进 PROGRESS)。
3.2 max-width: 860px(报告目录塌成抽屉)保留为有理由的例外,但必须在 globals.css 里就地加注释说明理由——目录是三栏布局的第三栏,它需要比正文更早塌陷,860 是内容宽度决定的,不是设备宽度。没有理由就一并并入 1023。执行方按实际布局判断并在 PROGRESS 里给结论。
3.3 噪音收敛(纯整理,行为不得变):
max-width: 639px+min-width: 640px(会员)与max-width: 640px+min-width: 641px(校正)统一为 640/641 这一组,即会员那对改成max-width: 640px/min-width: 641px。差 1px,没有真实用户影响,只为让白名单更短。max-width: 430px(toaster + 校正面)并入max-width: 480px,除非并入后校正面在 431–480 之间明显变坏;若是,保留 430 并就地注释理由。max-width: 620px(出生资料录入卡)并入640px。
3.4 在 globals.css 顶部新增一段注释,声明允许出现的宽度断点白名单及各自含义:
480 小屏手机
640/641 内容级栅格塌陷(会员方案卡、校正候选卡、录入卡)
767/768 移动 / 平板,与 sidebar-state.ts 对齐
860 报告目录(第三栏,内容宽度决定)——若 3.2 判定无理由则删除本行
1023/1024 平板 / 桌面,与 sidebar-state.ts 对齐
验收标准
globals.css里出现的宽度断点值,全部在 3.4 的白名单内。- 新增契约测试:从
globals.css抽出所有(max-width: Npx)/(min-width: Npx)的 N,断言集合等于白名单。这条测试是本单最重要的防复发措施——它让下一个人加断点时必须先改白名单。 - 761 / 800 / 860 三个宽度下报告页的目视确认写进
docs/testing/。
任务 4 · 测试与文档
4.1 开工先量基线,写进 PROGRESS:
cd .worktrees/mobile-touch-and-breakpoints-20260915/frontend
npm ci
npm test 2>&1 | tail -20 # pass / fail / tests 三个数字
4.2 新增测试至少覆盖:任务 1 的命中区几何、任务 2 的 900px 消失 + JS 阈值不变、任务 3 的断点白名单。
4.3 文档:
frontend/DESIGN.md:新增「断点」一节,写白名单与各自含义,并写明 CSS 必须向sidebar-state.ts对齐而不是反过来;触控目标一节写明「触屏 44×44、桌面视觉 26×26」的双轨规则。docs/BUG_HISTORY.md:新增 BUG-695 / 696 / 697 三条,字段齐全(状态、现象、触发条件、根因、修复、验证、防复发、关联记录、修复版本)。视觉部分只有清单没有真机证据时,状态写resolved但在「验证」里如实写明「代码级契约测试通过,浏览器级验收见docs/testing/」。docs/testing/mobile-touch-and-breakpoints-20260915.md:把任务 1.3 / 2.3 / 3.x 里标了「浏览器项 / 真机项」的条目写成可照做的清单(给出具体宽度、具体页面、看什么)。CHANGELOG.md:一条日期 + 一句话标题 + 要点。docs/tasks/PROGRESS-mobile-touch-and-breakpoints-20260915.md。
6. 走查确认「已经做得好」的部分——本轮不得改动
执行方容易顺手"优化"掉这些,逐条列明:
| 项 | 现状 | 不要动的理由 |
|---|---|---|
env(safe-area-inset-*) |
侧栏、chat-header、composer、auth 面板全覆盖 | 刘海与 home bar 已适配正确 |
min-width: 0 纪律 |
全表贯彻,无固定大宽度 | 这是页面不横向溢出的原因 |
dvh |
15 处,vh 仅 3 处做降级兜底 |
已是正确形态 |
@media (hover: hover) 6 块 |
全部只承担颜色 / 位移装饰 | 没有 hover-only 的功能,触屏无损 |
| 44px 触控纪律 | 侧栏汉堡、会话菜单、关闭、额度、模型选择器、follow-up 药丸、composer 发送全是 44 | 只有 .message-actions 与 .markdown-code-copy 破例,即本单任务 1 |
| 深色主题 | 32 对对比度矩阵 + 契约测试 | 加任何颜色都要过那张矩阵 |
prefers-reduced-motion / prefers-contrast / forced-colors / print |
均有 | 完整 |
.rectification-timeline 的 padding-inline |
移动端已有覆盖(--space-4 + --assistant-content-inset),高度 64→56 |
内边距是为对齐头像栏,不是失误 |
7. 让步顺序
- 任务 3.2(860 例外)判不清就保留 860 并注释,不要强行并入——错并会让报告目录在 768–860 之间挤在正文旁。
- 任务 3.3 的三条噪音收敛,任何一条并入后观感变坏就保留原值并就地注释理由,白名单相应加一行。收敛是为了可维护,不值得拿观感换。
- 任务 1 若让出垂直空间后消息之间的节奏明显变松,先只放大
gap与相邻间距、命中区做到 44 宽 × 现有高,把高度留到下一轮,写进 PROGRESS。 - 任务 2 的 901–1023 目视若发现模型选择器在新区间反而更差,回退该条并在 PROGRESS 说明,但
.code-form的对齐照做。 - 绝不让步:不得改
sidebarViewportForWidth;不得引 PostCSS 插件;不得为断点重构去改组件 JSX;断点白名单契约测试必须落地。
8. 开工前置命令
cd /workspace/Jyotisha
git status -sb | head -1
git fetch origin --prune
# 确认前置轮次已合入
git log --oneline origin/staging -5 # 应能看到 chat-reading-load 的实现提交
git worktree add -b codex/mobile-touch-and-breakpoints-20260915 \
.worktrees/mobile-touch-and-breakpoints-20260915 origin/staging
cd .worktrees/mobile-touch-and-breakpoints-20260915/frontend
npm ci
npm test 2>&1 | tail -20
./node_modules/.bin/tsc --noEmit
npm run lint
纯前端改动,不要求 scripts/pre_work_check.py。
交付:
git push origin HEAD:staging
推完核对远端 SHA。
9. BUG 编号
- 本单占用 BUG-695、BUG-696、BUG-697。
TASK-chat-reading-load-20260915.md若在执行中需要新编号,从 BUG-698 起。- 开工时仍需核对
docs/BUG_HISTORY.md的当前最大号,可能已被别的轮次占用。