fix(ui): restore mobile scroll and theme layout
Independent Staging Quality Gate / validate (push) Successful in 13m32s
Independent Staging Quality Gate / publish (push) Successful in 9m8s

This commit is contained in:
Jesse_Chen
2026-08-15 16:25:39 +08:00
parent b0a1174eae
commit 48a6f43b64
4 changed files with 60 additions and 42 deletions
+16
View File
@@ -3273,3 +3273,19 @@
- 防复发:任何 revision state 快路径都必须验证可读性,并保留容器镜像 revision 的只读回退,不得仅以路径存在作为可消费条件。
- 相关记录:BUG-083、BUG-192
- 修复版本:本次 staging 集成提交(精确 SHA 以远端 staging 与部署结果为准)
## BUG-194 | 移动端出生资料空状态无法滚动且主题问题被压成窄列
- 状态:resolved(待 staging 精确 SHA 验收)
- 首次发现:2026-08-15
- 最近更新:2026-08-15
- 影响面:首页空会话出生资料填写、移动端内部滚动容器、首页主题问题入口。
- 用户现象:手机上填写出生资料时无法继续向下滚动,底部字段与“继续”按钮不能进入视口;首页十个主题问题在桌面横向挤成窄条,中文几乎逐字换行。
- 触发条件:宽度不超过 767px 的空会话首页内容高于可视区域;或桌面显示完整主题问题集合。
- 根因:早期移动端 `.conversation.is-empty { display: block; }` 声明位于后续桌面 `.conversation.is-empty { display: grid; place-items: center; }` 之前,被级联顺序覆盖;外层页面又固定为隐藏溢出,只能依赖该内部容器滚动。主题区同时使用单排 flex 并让所有问题共享整体边框,十项内容被压缩到不可读宽度。
- 修复:把移动端空状态覆盖移动到桌面 Grid 规则之后,恢复独立纵向滚动、iOS 惯性滚动,并为动态视口高度增加 `100vh` 回退和 `100dvh`;主题入口改为带独立边框和圆角的响应式 Grid,桌面、平板、手机分别为 3、2、1 列,同时沿用按压态底色与仅桌面 hover 箭头反馈。
- 验证:移动端滚动与主题布局契约测试、首页入口回归、目标 ESLint 和 `git diff --check` 通过;真实浏览器在 1440、820、390 宽度下无水平或文字溢出,390×844 与模拟键盘高度 390×500 均可滚动到底部并显示“继续”。
- 防复发:移动端覆盖必须位于桌面空状态 Grid 之后;测试锁定滚动容器、`vh`/`dvh` 顺序、iOS 惯性滚动和主题区 3/2/1 列断点,并禁止恢复为单排 flex 压缩布局。
- 相关记录:BUG-042、BUG-048、BUG-140
- 复发自:无
- 修复版本:本次 staging 集成提交(精确 SHA 以远端 `staging` 核对结果为准)
+24 -41
View File
@@ -272,7 +272,6 @@ button:disabled { cursor: default; opacity: .45; }
}
@media (max-width: 767px) {
.conversation.is-empty { display: block; }
.brand-row { padding: 0 4px; }
.brand-mark { width: 26px; height: 26px; }
.session-nav { overflow: hidden; }
@@ -327,7 +326,7 @@ button:disabled { cursor: default; opacity: .45; }
.app-loading-error { background: var(--color-canvas-soft); }
.app-loading-error .app-loading-content { padding: 28px; border: 1px solid var(--color-border); background: var(--color-canvas); border-color: var(--color-border); border-radius: var(--radius-lg); box-shadow: var(--shadow-elevated); }
.group\/sidebar-provider[data-viewport] { height: 100dvh; min-height: 0; overflow: hidden; }
.group\/sidebar-provider[data-viewport] { height: 100vh; height: 100dvh; min-height: 0; overflow: hidden; }
.chat-app { width: 100%; height: 100%; min-height: 0; overflow: hidden; display: grid; background: var(--color-canvas); grid-template-columns: minmax(0, 1fr); }
.sidebar { position: relative; width: 100%; height: 100%; min-width: 0; min-height: 0; overflow: hidden; display: flex; flex-direction: column; border-right: 1px solid var(--sidebar-border); color: var(--sidebar-foreground); padding: var(--space-5) var(--space-3) 0; background: var(--sidebar-background); backdrop-filter: saturate(130%) blur(20px); }
[data-sidebar="header"] { flex: 0 0 auto; }
@@ -910,7 +909,13 @@ input:not([class^="ant-"]):not([class*=" ant-"]):not(.ant-picker input):disabled
[data-sidebar="rail"] { display: none; }
.chat-panel { grid-template-rows: calc(64px + env(safe-area-inset-top)) minmax(0, 1fr) auto; }
.chat-header { gap: 10px; padding: max(0px, env(safe-area-inset-top)) var(--space-3) 0; }
.conversation.is-empty { padding: var(--space-5) var(--space-4); }
.conversation.is-empty {
display: block;
place-items: initial;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
padding: var(--space-5) var(--space-4);
}
.welcome { padding: var(--space-6) 0 var(--space-10); }
.welcome > .onboarding-message:first-child .message-bubble p { font-size: var(--type-display-sm); }
.starter-list { grid-template-columns: 1fr; grid-template-rows: auto; }
@@ -1218,43 +1223,36 @@ input:not([class^="ant-"]):not([class*=" ant-"]):not(.ant-picker input):disabled
}
.starter-theme-accordion {
display: flex;
min-width: 0;
overflow: hidden;
border: 1px solid var(--color-border);
border-radius: var(--radius-xl);
background: var(--color-canvas-soft);
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: var(--space-3);
}
.starter-theme-card {
position: relative;
min-width: 0;
min-height: 170px;
flex: 1 1 0;
min-height: 156px;
display: grid;
grid-template-columns: minmax(0, 1fr) 20px;
align-items: end;
gap: var(--space-3);
padding: var(--space-5);
border: 0;
border-right: 1px solid var(--color-border);
background: transparent;
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
background: var(--color-canvas-soft);
cursor: pointer;
text-align: left;
transition: background-color 150ms ease-out;
}
.starter-theme-card:last-child {
border-right: 0;
transition: border-color 180ms ease-out, background-color 180ms ease-out, box-shadow 180ms ease-out, transform 180ms var(--ease-out);
}
.starter-theme-card:not(:disabled):active {
background: color-mix(in srgb, var(--color-action-soft) 56%, var(--color-canvas));
}
.starter-theme-card:not(:disabled):active .starter-arrow {
color: var(--color-action);
transform: translate(2px, -2px);
.starter-theme-card:focus-visible {
border-color: color-mix(in srgb, var(--color-action) 38%, var(--color-border));
box-shadow: var(--shadow-soft);
}
@media (hover: hover) {
@@ -1278,7 +1276,7 @@ input:not([class^="ant-"]):not([class*=" ant-"]):not(.ant-picker input):disabled
}
.starter-content span {
max-width: 245px;
max-width: none;
overflow: visible;
color: var(--color-ink);
font-family: var(--font-display);
@@ -1334,21 +1332,11 @@ input:not([class^="ant-"]):not([class*=" ant-"]):not(.ant-picker input):disabled
}
.starter-theme-accordion {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.starter-theme-card {
min-height: 136px;
border-bottom: 1px solid var(--color-border);
}
.starter-theme-card:nth-child(2n) {
border-right: 0;
}
.starter-theme-card:nth-last-child(-n + 2) {
border-bottom: 0;
min-height: 142px;
}
}
@@ -1394,17 +1382,12 @@ input:not([class^="ant-"]):not([class*=" ant-"]):not(.ant-picker input):disabled
}
.starter-theme-accordion {
display: grid;
grid-template-columns: 1fr;
gap: var(--space-3);
}
.starter-theme-card {
min-height: 116px;
border-right: 0;
border-bottom: 1px solid var(--color-border);
}
.starter-theme-card:last-child {
border-bottom: 0;
min-height: 112px;
}
.starter-content span {
@@ -5,6 +5,12 @@ import test from "node:test";
const css = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8");
test("mobile rectification welcome content starts at the scroll origin", () => {
assert.match(css, /@media \(max-width: 767px\)[\s\S]*?\.conversation\.is-empty\s*\{[^}]*display:\s*block/);
const centeredGrid = css.indexOf(".conversation.is-empty { display: grid");
const mobileOverride = css.indexOf(".conversation.is-empty {\n display: block", centeredGrid);
assert.ok(centeredGrid >= 0, "desktop empty state should remain centered");
assert.ok(mobileOverride > centeredGrid, "mobile display:block must be declared after the desktop grid rule");
assert.match(css, /\.conversation\s*\{[^}]*min-height:\s*0[^}]*overflow-y:\s*auto/);
assert.match(css.slice(mobileOverride), /\.conversation\.is-empty\s*\{[^}]*-webkit-overflow-scrolling:\s*touch/);
assert.match(css, /\.group\\\/sidebar-provider\[data-viewport\]\s*\{[^}]*height:\s*100vh;[^}]*height:\s*100dvh/);
});
@@ -0,0 +1,13 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
const css = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8");
test("starter questions use readable responsive cards instead of a compressed flex row", () => {
assert.match(css, /\.starter-theme-accordion \{[^}]*display: grid;[^}]*grid-template-columns: repeat\(3, minmax\(0, 1fr\)\);/);
assert.match(css, /@media \(min-width: 768px\) and \(max-width: 900px\)[\s\S]*?\.starter-theme-accordion \{[^}]*grid-template-columns: repeat\(2, minmax\(0, 1fr\)\);/);
assert.match(css, /@media \(max-width: 767px\)[\s\S]*?\.starter-theme-accordion \{[^}]*grid-template-columns: 1fr;/);
assert.match(css, /\.starter-theme-card \{[^}]*min-height: 156px;[^}]*border: 1px solid var\(--color-border\);[^}]*border-radius: var\(--radius-lg\);/);
assert.doesNotMatch(css, /\.starter-theme-accordion \{[^}]*display: flex;/);
});