Files
Jyotisha/docs/tasks/TASK-mobile-touch-and-breakpoints-20260915.md
T
Jesse_ChenandClaude Fable 5 899f955d4b docs(tasks): fold two mobile blockers into the reading-load round, brief touch targets and breakpoints
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
2026-09-15 04:02:50 +00:00

15 KiB
Raw Blame History

TASK · 消息操作触控目标 + 断点错位(BUG-695~697

  • 日期:2026-09-15
  • 基线 commitorigin/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-696CSS 的平板上限 900px 与 JS 的 1023 不一致,9011023 是混合态

JSfrontend/src/lib/sidebar-state.tssidebarViewportForWidth):

width < 768  → mobile
width < 1024 → tablet
否则          → desktop

侧栏宽度跟着它走(globals.css[data-viewport="tablet"]…--sidebar-width-tablet: 240pxdesktop 用 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) 的首页栅格规则。

结果:9011023px 之间,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 静态化、目录切抽屉 报告目录

在 761860px目录已经塌成抽屉了,正文还是桌面内边距。在 721–760:阅读区已窄、报告中心还是桌面。

另外整份样式表的宽度切点共有:430 / 480 / 620 / 639·640 / 640·641 / 720 / 760 / 767·768 / 860 / 900 / 1024。其中 639·640(会员)与 640·641(校正)是两组各自自洽的邻接对,相差 1px,不造成错位但属于噪音;430480 是两个小屏断点;620 是出生资料录入卡独有的。


3. 决策记录(产品已授权)

产品负责人 2026-09-15 确认按下述范围执行:

  1. BUG-695 修:把消息操作的触控目标做到触屏下 44×44,代价是让出垂直空间。
  2. BUG-696 / 697 修的是「错位」,不是「统一」。 本轮消除 §2.2 和 §2.3 两个混合区间,以及把噪音断点收敛。不做全表断点重构——本会话没有浏览器,全表重排的视觉回归验证不了,收益也远小于风险。
  3. 收敛的方式是约定 + 契约测试,不引 PostCSS 插件。 CSS 自定义属性不能用在媒体查询条件里,而本仓 Next.js 配置没有 postcss-custom-media。本轮不为此加构建依赖(frontend/AGENTS.md §7.7:不得顺手升级依赖)。
  4. 视觉验收是环境缺口。 本轮没有浏览器与真机,断点改动的目视确认写成 docs/testing/ 清单交给产品,不得在 PROGRESS 里写成「通过」

4. 硬红线

  1. ./node_modules/.bin/tsc --noEmit 0 错;npm run lint 0 error
  2. 测试总数不得低于开工时 origin/staging 的实测;改既有断言写「原值 / 新值 / 原因」三栏。
  3. next build/ 仍是 ○ Static;首屏 gzip ±2% 内。
  4. 不得改任何组件的 JSX 结构来迁就断点——本轮是样式与命中区的调整。唯一允许的结构改动是 BUG-695 需要的那点垂直空间,且必须在 frontend/DESIGN.md 里写明。
  5. 不得动 sidebarViewportForWidth 的 768 / 1024。它是 JS 侧真相,CSS 向它对齐,不是反过来。
  6. 不得动 adminantd + Refine[class^="ant-"] 排除项照旧)。
  7. 不得顺手升级依赖、不得引入 PostCSS 插件、不得顺手修不在本单里的 warning。
  8. 改 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 药丸重叠。
  • 按钮之间的 gap1px 放大到至少 var(--space-1),使相邻命中区不互相吞。
  • 视觉图标保持 26×26,只放大命中区与间距。桌面端(pointer: fine)保持现状不变。

1.2 保留 ::after 覆盖层那条注释的意图,但更新注释内容——它现在描述的是旧结论(「44×44 会抢走点击」),改完之后必须说明新的几何关系为什么不会抢。留着过期注释比没有注释更坏。

1.3 .markdown-code-copymin-height(pointer: coarse) 下提到 44px。做不了不阻塞,写进 PROGRESS。

验收标准

  • (pointer: coarse).message-actions button 的有效命中区 ≥ 44×44,且相邻按钮命中区不重叠、不与 follow-up 药丸重叠。
  • (pointer: fine) 下的几何与改动前逐值一致。
  • 新增 CSS 契约测试断言上述两条(本仓已有大量 globals.css 文本断言的先例,照同样方式写)。
  • 桌面端四个按钮的视觉排布肉眼无变化——这一条是真机/浏览器项,写进 docs/testing/

任务 2 · BUG-696CSS 平板上限对齐 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.1max-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: 430pxtoaster + 校正面)并入 max-width: 480px除非并入后校正面在 431–480 之间明显变坏;若是,保留 430 并就地注释理由。
  • max-width: 620px(出生资料录入卡)并入 640px

3.4globals.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-timelinepadding-inline 移动端已有覆盖(--space-4 + --assistant-content-inset),高度 64→56 内边距是为对齐头像栏,不是失误

7. 让步顺序

  1. 任务 3.2860 例外)判不清就保留 860 并注释,不要强行并入——错并会让报告目录在 768–860 之间挤在正文旁。
  2. 任务 3.3 的三条噪音收敛,任何一条并入后观感变坏就保留原值并就地注释理由,白名单相应加一行。收敛是为了可维护,不值得拿观感换。
  3. 任务 1 若让出垂直空间后消息之间的节奏明显变松,先只放大 gap 与相邻间距、命中区做到 44 宽 × 现有高,把高度留到下一轮,写进 PROGRESS。
  4. 任务 2 的 901–1023 目视若发现模型选择器在新区间反而更差,回退该条并在 PROGRESS 说明,但 .code-form 的对齐照做。
  5. 绝不让步:不得改 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 的当前最大号,可能已被别的轮次占用。