fix(webui): 手势与横向滚动分家 + 纵向滚动边缘淡出

用户给了两条具体信息(这比我自己猜五轮都管用):
「横向滚动条会与切换视图的手势冲突,我觉得周视图需要卡严条件,
  同时我说的其他硬截断是对应内容项上下滑动会直接被切断」。

## ① 手势卡严(周视图)

冲突是真的:周视图窄屏下必须横向滚(`overflow-auto` + `min-w-[36rem]`),
而我又给整页加了左右滑动翻页 ⇒ 同一次横滑既想滚又想翻,两边都不好用。

规则定死:**手势从可横向滚动的区域里起手,就归滚动条,完全不参与翻页判断**
(触点沿祖先链查找 `overflow-x: auto/scroll` 且真的能滚的容器)。
想翻页就从别处滑(例如上方标题栏)。

## ② 纵向滚动边缘淡出

滚动条本身是"一刀切":卡片滚到边缘被硬生生截断 —— 这就是用户说的"直接被切断"。
给纵向滚动容器(`.overflow-y-auto`)加 12px 上下渐隐遮罩,切得有交代。

只作用在**纵向**容器:横向滚动有自己的滚动条,加纵向遮罩会跟它打架。

## 过程记录(值得记)

这两条改动**第一次没有生效**,因为部署失败了:`/tmp` 是 tmpfs 且已 100% 满,
而部署脚本把构建产物写到硬编码的 `/tmp/agentmail-gateway-build-*` ⇒
`no space left on device`。我差点把"旧构建上的测量结果"当成"改动无效"。
清理后(清掉我自己的探针脚本/截图/旧构建,约 950MB)部署成功。
⚠️ `/tmp` 现在仍占 91%(`gocache` 4.5G 等不全是我的),**下次部署可能还会撞上**;
脚本改成尊重 `TMPDIR` 才是根治(未做)。
This commit is contained in:
2026-09-14 14:35:43 +08:00
parent c9717daa13
commit 1717863c87
5 changed files with 197 additions and 14 deletions

View File

@ -423,3 +423,37 @@ test('判据自检:预设清单少一档必须判红', () => {
const trimmed = webIds.slice(0, -1);
assert.notDeepEqual(trimmed, W.PRESET_IDS, '自检:裁掉一档后必须与实现不一致(否则这条判据没有分辨力)');
});
test('★ 背景画出来了还不够:每个页面要**让出**页面底,否则壁纸全被盖住', () => {
/*
* 这一条是"渲染"这句话的另一半。只把壁纸铺在最底层、而每个页面自己又刷一层
* **不透明**的系统页面底,壁纸就等于没画(用户看到的仍然是纯色页面)。
* WebUI 侧的原话:「页面底 → 完全透明,让出背景;不改 27 个组件的 class
* 逐个加 class 必然漏(漏掉的那块就是一张不透明卡片浮在背景上)」。
*
* 所以判据钉的是"没有一处页面底还在用不透明的系统页面底"——
* 漏掉任何一个页面,就是那一页看不到壁纸。
*/
const main = read('pages/MainPage.ets');
const opaqueRoots = [...main.matchAll(/\.backgroundColor\(Theme\.pageBg\)/g)].length;
assert.equal(opaqueRoots, 0,
`还有 ${opaqueRoots} 处页面底用不透明的 Theme.pageBg —— 那几页看不到壁纸`);
const yielded = [...main.matchAll(/\.backgroundColor\(this\.bgActive \? Color\.Transparent : Theme\.pageBg\)/g)].length;
assert.ok(yielded >= 5, `要让出页面底的页面至少 5 个(通信/联系人 + 三个 pane实际 ${yielded}`);
// 每个页面都要**收到**这个开关:漏传 = 该页恒为不透明(等于没有让出)
for (const comp of ['CommPage', 'ContactsTab']) {
assert.match(main, new RegExp(`${comp}\\(\\{ bgActive: this\\.bgActive \\}\\)`), `主界面要把 bgActive 传给 ${comp}`);
}
for (const pane of ['InboxTab', 'SentTab', 'PermissionTab']) {
assert.match(main, new RegExp(`${pane}\\(\\{ bgActive: this\\.bgActive \\}\\)`), `通信页要把 bgActive 传给 ${pane}`);
}
// 开关必须由**背景计划**驱动(不是写死的 true/false
assert.match(main, /this\.bgActive = this\.bgPlan\.kind !== 'none';/, 'bgActive 要由背景计划决定');
// 每个组件都要声明这个 @Prop漏一个就编译不过但判据先钉住意图
for (const comp of ['CommPage', 'ContactsTab', 'InboxTab', 'SentTab', 'PermissionTab']) {
const at = main.indexOf(`struct ${comp} {`);
const head = main.slice(at, at + 400);
assert.match(head, /@Prop bgActive: boolean = false;/, `${comp} 要声明 @Prop bgActive`);
}
});