diff --git a/docs/tasks/README.md b/docs/tasks/README.md index 336cf7a5..86c1b9d7 100644 --- a/docs/tasks/README.md +++ b/docs/tasks/README.md @@ -118,6 +118,7 @@ | `TASK-unified-loading-20260902.md` | `PROGRESS-unified-loading-20260902.md` | 首页一次等待一次揭幕 | 已验收 | `482796fc`(BUG-479) | | — | — | 初始化后点首页「家庭」报运行合同未完成:本命第一步未强制计算工具、主题卡无入口钉死 | 待验收 | `codex/consultation-family-contract-incomplete-20260909`(BUG-630);走查 `docs/testing/consultation-family-contract-incomplete-20260909.md` | | `TASK-chat-reading-load-20260915.md` | — | 首页「从一个主题开始」整段下线(连同 `/api/onboarding` 的 LLM 取数与揭幕闸门);聊天回答改两层可见性:口语层默认展开、第一个 H2 起的骨架进默认收起的「完整分析」折叠块;口语层定形为「结论+2–3 要点+下一步」≤400 字。内容一字不删,只改默认可见性。⚠️ 会让 BUG-630 的走查路径失效(`guided_topic` 入口下线,枚举保留) | 待领取 | `codex/chat-reading-load-20260915` | +| `TASK-mobile-touch-and-breakpoints-20260915.md` | — | 消息操作按钮触屏命中区 27×34、相邻仅隔 1px(BUG-695);CSS 平板上限 900px 与 `sidebarViewportForWidth` 的 1024 不一致,901–1023 是混合态(BUG-696);报告域 720/760/860 三个断点互不对齐,761–860 目录已塌、正文还是桌面(BUG-697)。含断点白名单契约测试。**串行在 chat-reading-load 之后** | 待领取 | `codex/mobile-touch-and-breakpoints-20260915` | ### 个人报告 diff --git a/docs/tasks/TASK-chat-reading-load-20260915.md b/docs/tasks/TASK-chat-reading-load-20260915.md index eda027df..599ce643 100644 --- a/docs/tasks/TASK-chat-reading-load-20260915.md +++ b/docs/tasks/TASK-chat-reading-load-20260915.md @@ -1,4 +1,4 @@ -# TASK · 首页主题卡下线 + 聊天回答两层化(降低阅读负担) +# TASK · 首页主题卡下线 + 聊天回答两层化 + 两处移动端阻塞(降低阅读负担) - 日期:2026-09-15 - 基线 commit:`origin/staging` @ `ebd5ba68`(`docs: clear the timezone gap and record what actually keeps the gate red`) @@ -12,7 +12,11 @@ > 首页底下的那些问题都可以去掉;聊天过程中怎么让用户不会觉得「我现在看到字很多,我有点阅读障碍」。 -两件事分别对应任务 1 和任务 2–3。**两件事可以在同一分支同一轮做完**,互不依赖,但共用一次测试基线测量(任务 4)。 +两件事分别对应任务 1 和任务 2–3。 + +2026-09-15 的 UI 走查又带出两处**移动端**问题,同批并入(任务 2.6、任务 5):折叠层里的四列表在手机上读不了(它是折叠能不能落地的前置),以及全局输入框 14px 导致 iPhone 聚焦时整页放大。 + +全部任务可以在同一分支同一轮做完,互不依赖,共用一次测试基线测量(任务 4)。 --- @@ -48,10 +52,47 @@ 而渲染层对这一切是**完全平铺**的:`frontend/src/components/chat-message-content.tsx` → `ChatMessageContent` 把整篇 markdown 一次渲染到底,唯一的折叠是 `TechniqueAuditDisclosure`,而且 `showFoldedAudit = rows.length > 0 && split.rows.length === 0` —— 正文里已经有那张表时反而**不折**。 -### 2.3 根因一句话 +### 2.3 折叠层里的宽表在手机上读不了 + +移动端的表格卡片化重排**只认三列表**(`frontend/src/app/globals.css`,`@media (max-width: 767px)` 内): + +```css +.markdown-table table:has(thead th:nth-child(3):last-child) thead { /* 视觉隐藏表头 */ } +.markdown-table table:has(thead th:nth-child(3):last-child) tbody tr { display: grid; … } +``` + +技法审计表(技法 / 状态 / 说明)正好三列,重排正常。但 `natalSpokenReportContract` 还要求一张 **四列的 Yoga 表**(`name / formation / strength or hit-miss / domain effect`),统一参数那节也会出宽表。它们落到基础规则: + +```css +.markdown-table { overflow-x: auto; } +.markdown-table table { width: 100%; } /* ← 与上一行互相抵消 */ +.markdown-table th, .markdown-table td { word-break: keep-all; } /* ← 中文不断词 */ +``` + +`width: 100%` 让表格永远不超出容器,`overflow-x: auto` 于是永不触发;四列被压成约 85px 一柱,`keep-all` 又不许中文断词。375px 屏上要么挤成竖排单字,要么撑破容器。 + +**这正是任务 2 折叠层里的内容**——手机用户点开「完整分析」第一眼就撞上,所以它是折叠能不能落地的前置条件,并进本轮。 + +### 2.4 所有输入框 14px,iPhone 一聚焦就整页放大 + +`frontend/src/app/globals.css` 的全局 input 规则: + +```css +input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"])…, +select:not([class^="ant-"])… { min-height: 44px; …; font-size: var(--type-body-sm); } +``` + +`--type-body-sm = 14px`,且移动端媒体查询里**没有任何一处把它提上去**(`grep` 过 `@media (max-width: 767px)` 之后的全部规则)。 + +iOS Safari 对字号小于 16px 的输入框会在聚焦时自动放大页面,并且不会自己缩回去。命中的是整条注册与资料录入漏斗:登录邮箱、姓名、出生日期、出生时间、出生地搜索。 + +已经躲过的只有两处,改动不得破坏它们:`.otp-input` 是 20px,`.composer textarea` 是 `var(--type-body-md)`(16px)。 + +### 2.5 根因一句话 - 首页:一个低价值入口押着一次 LLM 调用和整个揭幕闸门。 - 聊天:内容合同要求「全量输出」,渲染层没有「默认可见性」这一层,于是全量输出 = 全量可见。 +- 移动端:宽表的响应式规则只覆盖了三列这一种形状;输入框字号沿用了正文小号,没有为触屏留出 16px 的下限。 --- @@ -186,9 +227,30 @@ export function splitSpokenAndReport(text: string): SpokenAndReportSplit; - 个人报告页走 `personal-report-document-view.tsx`,**不经过**本组件,不得受影响。 - 会话落库、复制按钮、重新生成拿到的仍是**完整原文**,不是口语层。 +**2.6 折叠层里的宽表必须在手机上可读(本轮前置,不得延后)** + +处置分两档,按列数分流,**不要动现有的三列规则**(它有测试、工作正常): + +- **三列表**:保持现状的卡片化重排,一行也不改。第三列是说明性长文本,塌成第二行读起来像图注,这是它成立的原因。 +- **四列及以上**:改为**真正可横向滚动**,而不是继续卡片化。四字段 × N 行的表格塌成卡片会变成一条极长的竖列(Yoga 表 8 行 × 4 字段 = 32 行),比横滑更难读;而且用户是主动点开「完整分析」才看到它的,横滑是可接受的代价。 + +在 `@media (max-width: 767px)` 内加: + +```css +.markdown-table table:not(:has(thead th:nth-child(3):last-child)) { + width: auto; + min-width: max-content; /* 让 .markdown-table 的 overflow-x: auto 真正生效 */ +} +``` + +并给 `.markdown-table` 加一个**可横滑的视觉提示**(右缘渐隐或 `scroll-shadow`,用既有 token,不引新库、不加 JS),否则用户不知道右边还有内容。`word-break: keep-all` 保留——横滑模式下它反而是对的。 + +同一处理必须同时覆盖:聊天里的宽表、`.technique-audit-panel`(它已有三列规则,不受影响,确认即可)。**个人报告页的表格不在本轮范围**(走 `.personal-report-*` 自己的规则)。 + **验收标准(任务 2)** - 一条本命类回答:口语层直接可读,下面一个收起的「完整分析」块;展开后章节顺序与原文完全一致,一个字不少。 +- 375px 宽视口下展开「完整分析」:三列技法审计表仍是卡片式;四列 Yoga 表可横向滑动、不挤压、不撑破容器;页面本身**不出现横向滚动条**(`.conversation` 的 `overflow-x: hidden` 必须仍然成立)。 - 闲聊类回答(无 H2)渲染与改动前逐字一致。 - onboarding 打字机消息渲染与改动前逐字一致。 - 复制该条消息,得到的是完整原文(含骨架)。 @@ -263,7 +325,8 @@ npm test 2>&1 | tail -20 # 记下 pass / fail / tests 三个数字 **4.4 文档** -- `frontend/DESIGN.md`:新增「回答的两层可见性」一节(口语层 / 折叠层的判定规则、`` 文案规则、流式期间的表现),并把首页去掉主题卡后的版式写进首页那一节。同一提交内完成。 +- `frontend/DESIGN.md`:新增「回答的两层可见性」一节(口语层 / 折叠层的判定规则、`` 文案规则、流式期间的表现),并把首页去掉主题卡后的版式写进首页那一节。**另加一条表格规则**:三列表在窄屏卡片化、四列及以上横向滚动并带滑动提示,以及表单输入 16px 下限的理由(iOS 聚焦放大)。同一提交内完成。 +- `docs/testing/chat-reading-load-20260915.md`:新建真机清单,至少含——iPhone Safari 上逐个聚焦表单输入框确认不放大;375px 下展开「完整分析」确认宽表可横滑且页面不横向滚动;折叠块在流式期间不展开、不出现转圈。 - `CHANGELOG.md`:一条日期 + 一句话标题 + 变更要点(首页主题卡下线;回答默认只展开口语层)。注明 Skill 版本**不**bump(本轮不改 Skill)。 - `docs/tasks/PROGRESS-chat-reading-load-20260915.md`:做了什么、测试三个数字(基线 / 改后)、删了哪些断言、新增哪些、偏离与原因。 - `docs/tasks/README.md`:把本任务书那行状态从「待领取」改成「执行中 / 待验收」;同时处理 §3.1 说的 BUG-630 那一行。 @@ -277,6 +340,31 @@ npm test 2>&1 | tail -20 # 记下 pass / fail / tests 三个数字 --- +### 任务 5 · 输入框字号 14 → 16(iOS 聚焦放大) + +**5.1** `frontend/src/app/globals.css` 的全局 input / select 规则,`font-size` 从 `var(--type-body-sm)`(14px) 改为 `var(--type-body-md)`(16px)。 + +- 优先做**无条件提升**(所有视口一致 16px)。`min-height: 44px` 已经在了,行高和内边距不用动,桌面端视觉变化很小。 +- 若产品在桌面端观感上不接受 16px,退而求其次只在触屏下提升: + ```css + @media (pointer: coarse) { input:not(…), select:not(…) { font-size: var(--type-body-md); } } + ``` + 两种都可以,选哪种写进 PROGRESS。**不接受的是维持 14px。** + +**5.2** 不得破坏既有的两处安全值:`.otp-input`(20px)、`.composer textarea`(`--type-body-md`)。 + +**5.3** 排除项不变:`[type="radio"]`、`[type="checkbox"]`、antd(`[class^="ant-"]` / `.ant-picker input`)——admin 是 antd + Refine,不在本轮范围。 + +**5.4** 改完复查这几个表单的换行有没有被挤坏(字号变大会改变换行点):登录邮箱 + 验证码、姓名、出生日期、出生时间、出生地搜索下拉、设置弹窗里的各字段。桌面和 375px 各看一遍。 + +**验收标准(任务 5)** + +- `grep -nE "font-size: var\(--type-body-sm\)" frontend/src/app/globals.css` 的结果里不再包含全局 input/select 规则。 +- 375px 视口下逐个聚焦上述输入框,页面不发生缩放(**真机项**:模拟器不一定复现 iOS 的放大行为,做不到就写进 `docs/testing/` 清单,不得写成「通过」)。 +- 表单在 375px 与桌面两种宽度下无换行崩坏、无溢出。 + +--- + ## 6. 让步顺序 被挡住时按这个顺序退,退到哪一步写进 PROGRESS: @@ -285,7 +373,9 @@ npm test 2>&1 | tail -20 # 记下 pass / fail / tests 三个数字 2. 任务 3 若导致 voice 合同套件大面积红且一轮内收不住,**把任务 3 整体拆到下一轮**,本轮只交任务 1 + 2 + 4。渲染层折叠独立生效,不依赖文案改动。 3. 任务 1.3 删接口若牵出 Mastra agent 注册或模型目录的连带失败,**保留 `route.ts` 与 agent 文件但断开前端调用**(前端不再请求即可拿到全部收益),把未删干净的部分写进根目录 `BLOCKED.md`。 4. `generalGuidedJyotishTopics` 若被别处引用,保留该模块,只删首页引用。 -5. **绝不让步**:骨架内容不得删减;完整原文必须仍然入库、可复制、可导出;`guided_topic` 枚举值不得从 schema 移除;不得动数据库结构。 +5. 任务 2.6 的横滑提示(渐隐 / scroll-shadow)若在深色主题下做不干净,**先只交「能横滑」**,提示留到下一轮,写进 PROGRESS。能横滑本身不让。 +6. 任务 5 若无条件 16px 在桌面端观感上被否,退到 `(pointer: coarse)` 版本。**维持 14px 不是选项。** +7. **绝不让步**:骨架内容不得删减;完整原文必须仍然入库、可复制、可导出;`guided_topic` 枚举值不得从 schema 移除;不得动数据库结构。 --- @@ -320,11 +410,12 @@ git push origin HEAD:staging # 快进推送,触发 backend-qualit - 本轮改 `frontend/src/app/page.tsx` 与 `frontend/src/components/chat-message-content.tsx`。开工前确认没有别的轮次同时在改这两个文件(`docs/tasks/README.md` 状态板上「执行中」的行)。若有,**本轮排在其后**。 - 与 `TASK-report-provenance-binding-20260915.md` 无文件重叠,可并行。 +- **`TASK-mobile-touch-and-breakpoints-20260915.md` 必须排在本轮之后**:两轮都改 `frontend/src/app/globals.css`,且那一轮的任务 2.2 依赖本轮是否已删掉主题卡。 --- ## 9. BUG 编号起点 - `origin/staging@ebd5ba68` 的 `docs/BUG_HISTORY.md` 当前最大号 = **BUG-694**。 -- 本任务书是产品改造,**不预分配 BUG 编号**。执行中若发现或引入回归,从 **BUG-695** 起连续编号,并按 `AGENTS.md` §5 在同一变更里补 `docs/BUG_HISTORY.md`。 +- 本任务书是产品改造,**不预分配 BUG 编号**。执行中若发现或引入回归,从 **BUG-698** 起连续编号(BUG-695~697 已由 `TASK-mobile-touch-and-breakpoints-20260915.md` 占用),并按 `AGENTS.md` §5 在同一变更里补 `docs/BUG_HISTORY.md`。 - 开工时请再核一次最大号——可能已有别的轮次占用。 diff --git a/docs/tasks/TASK-mobile-touch-and-breakpoints-20260915.md b/docs/tasks/TASK-mobile-touch-and-breakpoints-20260915.md new file mode 100644 index 00000000..d03fd5b2 --- /dev/null +++ b/docs/tasks/TASK-mobile-touch-and-breakpoints-20260915.md @@ -0,0 +1,259 @@ +# 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`: + +```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 确认按下述范围执行: + +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. 不得动 admin(antd + 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 药丸重叠。 +- 按钮之间的 `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. 让步顺序 + +1. 任务 3.2(860 例外)判不清就**保留 860 并注释**,不要强行并入——错并会让报告目录在 768–860 之间挤在正文旁。 +2. 任务 3.3 的三条噪音收敛,任何一条并入后观感变坏就**保留原值并就地注释理由**,白名单相应加一行。收敛是为了可维护,不值得拿观感换。 +3. 任务 1 若让出垂直空间后消息之间的节奏明显变松,**先只放大 `gap` 与相邻间距、命中区做到 44 宽 × 现有高**,把高度留到下一轮,写进 PROGRESS。 +4. 任务 2 的 901–1023 目视若发现模型选择器在新区间反而更差,**回退该条并在 PROGRESS 说明**,但 `.code-form` 的对齐照做。 +5. **绝不让步**:不得改 `sidebarViewportForWidth`;不得引 PostCSS 插件;不得为断点重构去改组件 JSX;断点白名单契约测试必须落地。 + +--- + +## 8. 开工前置命令 + +```bash +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`。 + +交付: + +```bash +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` 的当前最大号,可能已被别的轮次占用。 diff --git a/docs/testing/ios-keyboard-composer-20260915.md b/docs/testing/ios-keyboard-composer-20260915.md new file mode 100644 index 00000000..b09cd90e --- /dev/null +++ b/docs/testing/ios-keyboard-composer-20260915.md @@ -0,0 +1,55 @@ +# 真机清单 · iOS 键盘是否遮挡输入框(2026-09-15) + +**为什么需要人来测**:这一项无法从代码断定,也没有自动化替代。会话环境没有 iPhone、没有 Chrome、没有登录态。走查只能确认「风险形状成立」,不能确认「真的坏」。结论出来之前**不立修复单**。 + +## 代码侧已确认的事实 + +| 事实 | 位置 | +| --- | --- | +| 页面自身不可滚动 | `frontend/src/app/globals.css` → `html, body { width: 100%; height: 100%; overflow: hidden; }` | +| 应用外壳锁死视口高度 | `.group\/sidebar-provider[data-viewport] { height: 100dvh; overflow: hidden; }` | +| 输入框是滚动区外的粘性行 | `.composer-wrap { position: sticky; bottom: 0; }`;`.chat-panel` 用 `grid-template-rows` 把它排成独立一行 | +| 只声明了 Android 认的键盘策略 | `frontend/src/app/layout.tsx` → `viewport.interactiveWidget = "resizes-content"`。**iOS Safari 不支持这个属性,会忽略** | +| 没有任何 `visualViewport` 监听 | `grep -rn "visualViewport" frontend/src` = 0 命中 | + +这是「app shell + 锁定高度 + 粘性输入框」的典型形状。iOS 在键盘弹出时不缩小布局视口,只缩小可视视口;系统自带的 scroll-into-view 兜底在 `overflow: hidden` 的外壳里没有可滚的东西。**但 iOS 也可能自行平移可视视口把输入框顶上来**,所以必须实测。 + +## 怎么测 + +设备:iPhone。两个浏览器各测一遍——**Safari** 和**微信内置浏览器**(后者用的是 WKWebView,行为可能不同,而且很可能是真实用户的主要入口)。 + +地址:`https://staging.jyotisha.chat` + +### A. 聊天输入框 + +1. 登录,进入任意一个已有会话(不要用空首页,空首页的布局不同)。 +2. 点一下底部输入框,等键盘完全弹出。 +3. **看**:输入框整条是否还完整可见?发送按钮是否露在键盘上方? +4. 连打三行以上文字,让输入框自己长高(它 `max-height: 128px`)。**看**:长高之后是否被键盘吃掉? +5. 输入过程中,上方最后一条消息是否还看得见?还是被顶没了? +6. 收起键盘。**看**:页面有没有留下一块空白,或者整体位置偏了没还原。 + +### B. 长会话里的输入框 + +1. 找一个消息很多、需要滚动的会话,滚到中间位置(不在底部)。 +2. 点输入框。**看**:弹键盘时页面有没有突然跳到别处。 + +### C. 登录页与资料录入 + +1. 退出登录,在 `/login` 点邮箱输入框。**看**:键盘弹出后输入框和「获取验证码」按钮是否都还可见。 +2. 新账号走一遍姓名 → 出生日期 → 出生时间 → 出生地搜索。出生地搜索会弹下拉列表,**重点看**:下拉列表是否被键盘压住、能不能点到。 + +### D. 横屏 + +把手机横过来重复 A.2。横屏时键盘占比更大,最容易暴露。 + +## 怎么回报 + +每一条写「正常 / 有问题 + 一句现象 + 截图」。截图请**不要**带真实姓名、出生资料或会话内容——用一个测试账号,或者截图后把这些涂掉。 + +- 如果 A–D 全部正常:这一项关闭,记进本文件末尾,不开修复单。 +- 如果任何一条有问题:把现象贴回来,我据此出修复单。预判的修法是引入 `visualViewport` 监听把键盘高度写进一个 CSS 变量,由 `.composer-wrap` 消费;但具体怎么改要看实际是哪一种坏法。 + +## 结论 + +> 待填。测完把结论写在这里(日期 + 设备 + 浏览器 + 逐条结果),不要另开文件。