fix: 窄屏实测修复 —— 删抽屉、44px 命中区、触摸设备可见的次要动作
用 playwright 连本机共享 Chromium,在 390px(iPhone 14 Pro)与 320px (iPhone SE)量真实盒子。之前的窄屏适配是「照着规则写对」,实测发现 一个功能性 bug 加五处可用性问题。 ## 抽屉式侧栏遮挡底部导航(真 bug) 抽屉是 `fixed left-0 top-0 bottom-0 z-50`,铺满整个视口高度;底部导航没有 z-index。抽屉打开时点最左那一项「收件」,elementFromPoint 命中的是抽屉里的 SVG,不是导航按钮 —— 按钮在那里、尺寸也够、CSS 规则也没写错,只有命中测试 才能发现。 **删掉抽屉而不是给导航加 z-index**:抽屉装的六项(收件/发件/联系/用户/新建/ 管理)与底部导航完全重复,唯一独有的是退出登录。为一个按钮维护一套 fixed 层级加遮罩不划算,而它还附带「Esc 关不掉」「底层未锁滚」两个毛病。 退出登录移到「我的」页 —— 它与密码、密钥同属「账号自身」,而那页此前根本 没有退出入口。`NavToggle` 与 uiStore 的 navOpen/toggleNav/closeNav 一并删除。 ## 触摸命中区:新增 .tap 详情页那排工具按钮视觉高度只有 15-16px(实测「标记已读」48x16、「对话树」 54x16、「转发」42x16、「抄送」20x15),移动端下限是 44x44。 直接加 padding 会把本来就挤的头部撑散、320px 下换行,因此用居中的透明伪元素 扩大命中区:**视觉一像素不动**。只在 max-width:767px 生效 —— 桌面用鼠标精度 足够,而扩大后的命中区在密排工具栏里会互相重叠,点一个可能命中隔壁那个。 覆盖 MailView / ContactPanel / WorkCard / ModelScopePanel / AdminUsersPage / ComposePage / ThreadView / KeyPanel / QuotaPanel / Attachments / BackButton。 ## 看不见却按得动的按钮:新增 .reveal `opacity-0 group-hover:opacity-100` 在没有 hover 的设备上永远是 opacity:0, **但仍然接收点击** —— 实测联系人列表里 elementFromPoint 命中的就是那个看不见的 「归档」。一个看不见却按得动的破坏性按钮比没有按钮更糟:人以为点的是卡片, 实际归档了一条会话。 改为默认可见,只在 `(hover: hover) and (pointer: fine)` 时隐藏。 单看 hover 会把带触摸板的平板算进去。 ## 对话树 - 缩进随屏宽自适应:固定「每级 20px、上限 8 级」= 最多 160px,320px 屏还要 去掉 px-4 的 32px 与连接线 18px,卡片只剩 110px,发件人一行直接被 truncate 吃掉。窄屏改为每级 10px、上限 5 级 - 补返回出口:原先只有「关闭」。两者语义不同 —— 返回退出整个详情栏回到列表, 关闭只收起树、留在这封邮件上 ## 把实测脚本留进仓库 `web/test/manual/`(`npm run test:narrow` / `test:wide`),不进 npm test —— 要一个跑着的浏览器加一个活的 Gateway。 留着而不是用完即删,是因为结构性断言守不住「按钮实际多大、点下去命中谁」, 而这次最严重的 bug 恰好只有 elementFromPoint 能发现。helper 里两个函数专门 为此:tapTargets() 量 .tap 的真实命中区(伪元素尺寸,不是 boundingBox), hitTest() 验每个元素点下去是否命中自己。 ## 验证 - 窄屏 13 项 + 宽屏 5 项全通过。宽屏回归特意验了两件只该在窄屏生效的事: .tap 伪元素 content 为 none、没有返回按钮 - narrow-layout.test.mjs 从 20 条扩到 28 条,逐条钉住上面每个修复 - 无横向溢出:390px 与 320px 下 scrollWidth === clientWidth - 生产已部署
This commit is contained in:
@ -105,8 +105,65 @@ SSE 连接的 handler 在首次连接时从缓冲区头部开始(客户端传
|
||||
**修法**:同上,sessionrate.go 重写为调用 RateLimitCheckAndRecord。
|
||||
|
||||
### P2 — 组件级测试
|
||||
**现状**:前端无任何组件测试,前端回归只靠 lint 与构建。
|
||||
**现状**:有结构性回归(`web/test/narrow-layout.test.mjs`,28 条断言,
|
||||
读源码验形态)与一套 playwright 手工脚本,但**没有渲染组件跑断言的测试**。
|
||||
**范围**:关键组件(AddressInput 补全、PermissionPanel 决策、WorkCard 预算渲染)。
|
||||
**已有的浏览器实测**:`web/test/manual/`(`npm run test:narrow` /
|
||||
`npm run test:wide`)—— 连本机共享 Chromium 量真实盒子与命中区。
|
||||
不在 `npm test` 里,因为要一个跑着的浏览器加一个活的 Gateway。
|
||||
剩下的是把它接进 CI(需要一个 headless 环境与一个测试用 Gateway 实例)。
|
||||
|
||||
### 窄屏实测修复(2026-09-02)✅
|
||||
|
||||
用 playwright 连本机共享 Chromium,在 390px(iPhone 14 Pro)与 320px(iPhone SE)
|
||||
两档实测。**一个功能性 bug 加五处可用性问题**:
|
||||
|
||||
**抽屉式侧栏遮挡底部导航(真 bug,已删抽屉)**
|
||||
抽屉是 `fixed left-0 top-0 bottom-0 z-50`,铺满整个视口高度;底部导航没有
|
||||
z-index。抽屉打开时点最左那一项「收件」,`elementFromPoint` 命中的是抽屉里的
|
||||
SVG,不是导航按钮。
|
||||
|
||||
修法是**删掉抽屉**而不是给导航加 z-index:抽屉里六项(收件/发件/联系/用户/
|
||||
新建/管理)与底部导航完全重复,唯一独有的是退出登录。为一个按钮维护一套
|
||||
fixed 层级 + 遮罩不划算,何况它还引入了遮挡、Esc 关不掉、底层未锁滚三个问题。
|
||||
退出登录移到「我的」页 —— 它与密码、密钥同属「账号自身」,而那里此前根本
|
||||
没有退出入口。
|
||||
|
||||
**触摸命中区(新增 `.tap`)**
|
||||
详情页那排工具按钮视觉高度只有 15-16px(实测「标记已读」48x16、「对话树」
|
||||
54x16、「转发」42x16、「抄送」20x15),移动端下限是 44x44。
|
||||
|
||||
直接加 padding 会把本来就挤的头部撑散、在 320px 上换行,因此改用居中的透明
|
||||
伪元素扩大命中区:**视觉一像素不动**。只在 `max-width: 767px` 生效 ——
|
||||
桌面用鼠标精度足够,而扩大后的命中区在密排工具栏里会互相重叠。
|
||||
覆盖 MailView / ContactPanel / WorkCard / ModelScopePanel / AdminUsersPage /
|
||||
ComposePage / ThreadView / KeyPanel / QuotaPanel / Attachments / BackButton。
|
||||
|
||||
**悬停才显形的按钮在触摸设备上永远透明却按得动(新增 `.reveal`)**
|
||||
`opacity-0 group-hover:opacity-100` 在没有 hover 的设备上永远是 `opacity: 0`,
|
||||
但仍然接收点击 —— 实测联系人列表里 `elementFromPoint` 命中的就是那个看不见的
|
||||
「归档」。一个看不见却按得动的破坏性按钮比没有按钮更糟:人以为点的是卡片,
|
||||
实际归档了一条会话。
|
||||
|
||||
改为默认可见,只在 `(hover: hover) and (pointer: fine)` 时隐藏 ——
|
||||
单看 `hover` 会把带触摸板的平板算进去。
|
||||
|
||||
**对话树缩进在 320px 下把卡片压成竖条**
|
||||
固定「每级 20px、上限 8 级」= 最多 160px;320px 屏还要去掉 `px-4` 的 32px 与
|
||||
连接线 18px,卡片只剩 110px,发件人一行直接被 truncate 吃掉。
|
||||
窄屏改成每级 10px、上限 5 级。
|
||||
|
||||
**对话树窄屏没有返回出口**
|
||||
只有「关闭」。两者语义不同:返回退出整个详情栏回到列表,关闭只收起树、
|
||||
留在这封邮件上。补了 `BackButton`。
|
||||
|
||||
#### 验证
|
||||
|
||||
playwright 端到端 13 项全通过(含「底部导航六项都命中自己」、
|
||||
「工具按钮命中区 >= 44px」、四个页面无横向溢出、320px 无横向溢出);
|
||||
宽屏回归 5 项全通过(三栏并排、常驻侧栏仍有退出登录、无返回按钮、
|
||||
`.tap` 伪元素在宽屏不生效、无溢出)。
|
||||
`web/test/narrow-layout.test.mjs` 从 20 条扩到 28 条,把上述每一条都钉住。
|
||||
|
||||
### P2 — 深色主题
|
||||
**现状**:只有浅色主题,深夜使用刺眼。
|
||||
|
||||
56
docs/PLAN.md
56
docs/PLAN.md
@ -1326,13 +1326,13 @@ MVP 计划(Phase 1-6)已全部落地并在 systemd 部署态实测通过。
|
||||
transition 根本不触发(单层 rAF 在 Safari 上偶尔仍被合帧)
|
||||
- [x] `motion-reduce:transition-none` 尊重 `prefers-reduced-motion`
|
||||
- [x] 打开覆盖层时底层 `aria-hidden`,否则屏幕阅读器会读到两层内容
|
||||
- [x] 导航:竖条在窄屏退化为抽屉(60px 在手机上白占一成宽度),
|
||||
日常切换交给底部 `NarrowNav`(拇指够得到);
|
||||
抽屉带遮罩,点空白处收起
|
||||
- [x] 导航:竖条在窄屏让位给底部 `NarrowNav`(60px 在手机上白占一成宽度,
|
||||
而底部横排拇指够得到)。**曾经还有一个抽屉式侧栏,后来删掉了 —— 见 7.10.1**
|
||||
- [x] `env(safe-area-inset-bottom)`:iPhone 手势条会盖住最后一排
|
||||
- [x] **窄屏专属控件用条件渲染而非 `md:hidden`**:后者只是视觉隐藏,
|
||||
元素仍在 DOM 与 tab 序列里,宽屏用户按 Tab 会聚焦到看不见的返回按钮上。
|
||||
为此抽了 `NarrowOnly` / `BackButton` / `NavToggle` 三个组件
|
||||
为此抽了 `NarrowOnly` / `BackButton` 两个组件
|
||||
(原先还有 `NavToggle`,随抽屉一起删除)
|
||||
- [x] 列表栏 `w-full md:w-[320px]`;各页横向内边距 `px-4 md:px-6`
|
||||
(px-6 在 375px 屏上白吃 48px)
|
||||
- [x] 管理页的 3/4 列 grid 改响应式;列表行 `flex-wrap`
|
||||
@ -1342,22 +1342,58 @@ MVP 计划(Phase 1-6)已全部落地并在 systemd 部署态实测通过。
|
||||
只滑走覆盖层的话下次进列表又会弹回来
|
||||
- [x] `narrowPane` 在宽屏下**也维护**:否则从窄屏拖宽再拖回来,
|
||||
用户会发现自己回到了列表,刚打开的邮件不见了
|
||||
- [x] `web/test/narrow-layout.test.mjs`:16 条结构性断言,
|
||||
- [x] `web/test/narrow-layout.test.mjs`:结构性断言(现 28 条),
|
||||
钉住「覆盖而非分栏」「延迟卸载」「双层 rAF」「条件渲染而非 md:hidden」
|
||||
「无裸 px-6」等不变量。不做视觉快照 —— 那需要 headless 浏览器,
|
||||
且像素比对在字体差异下极脆
|
||||
「无裸 px-6」等不变量。不做像素级视觉快照 —— 字体差异下极脆
|
||||
|
||||
### 7.10.1 真机尺寸实测与修复(2026-09-02)
|
||||
|
||||
上面那些都是「照着规则写对」,实际用 playwright 连本机共享 Chromium
|
||||
在 390px(iPhone 14 Pro)与 320px(iPhone SE)量了一遍,**发现一个功能性 bug
|
||||
加五处可用性问题**。详细记录见 `docs/PHASE7-REMAINING.md`。
|
||||
|
||||
- [x] **删掉抽屉式侧栏**。它是 `fixed ... z-50` 且铺满视口高度,把底部导航
|
||||
最左那一项盖住点不到(`elementFromPoint` 命中抽屉里的 SVG)。
|
||||
修法不是给导航加 z-index 而是删掉抽屉:它装的六项与底部导航完全重复,
|
||||
唯一独有的是退出登录 —— 为一个按钮维护一套 fixed 层级 + 遮罩不划算,
|
||||
而它还附带了「Esc 关不掉」「底层未锁滚」两个毛病
|
||||
- [x] 退出登录移到「我的」页:与密码、密钥同属「账号自身」,
|
||||
而那页此前**根本没有退出入口**
|
||||
- [x] **`.tap` 工具类**:44x44 触摸命中区,用居中的透明伪元素实现,
|
||||
视觉尺寸一像素不动。实测详情页工具按钮只有 15-16px 高
|
||||
(「抄送」20x15),直接加 padding 会把头部撑散、320px 下换行。
|
||||
只在 `max-width: 767px` 生效 —— 桌面精度足够,且扩大后的命中区
|
||||
在密排工具栏里会互相重叠
|
||||
- [x] **`.reveal` 工具类**:`opacity-0 group-hover:opacity-100` 在没有 hover
|
||||
的设备上永远透明**却仍然接收点击** —— 一个看不见却按得动的「归档」
|
||||
比没有按钮更糟。改为默认可见,只在
|
||||
`(hover: hover) and (pointer: fine)` 时隐藏(单看 hover 会把带触摸板的
|
||||
平板算进去)
|
||||
- [x] 对话树缩进随屏宽自适应:固定「每级 20px、上限 8 级」在 320px 下
|
||||
把卡片压到 110px 可用宽度,发件人一行直接被 truncate 吃掉。
|
||||
窄屏改为每级 10px、上限 5 级
|
||||
- [x] 对话树补返回出口:原先只有「关闭」,而两者语义不同 ——
|
||||
返回退出整个详情栏,关闭只收起树留在这封邮件上
|
||||
|
||||
验证:`web/test/manual/`(`npm run test:narrow` / `test:wide`)
|
||||
窄屏 13 项 + 宽屏 5 项全通过;结构性断言从 20 条扩到 28 条,
|
||||
把每一条修复都钉住。
|
||||
|
||||
那套脚本刻意留在仓库里而不是用完就删:结构性断言守不住「按钮实际多大、
|
||||
点下去命中谁」,而这次最严重的 bug 恰好只有 `elementFromPoint` 能发现。
|
||||
它不进 `npm test` —— 要一个跑着的浏览器加一个活的 Gateway。
|
||||
|
||||
---
|
||||
|
||||
已知取舍,尚未处理:
|
||||
|
||||
- 前端只有 Markdown XSS 一个回归测试,没有组件级测试
|
||||
- 前端有 Markdown XSS 与窄屏结构性回归,但没有**渲染组件跑断言**的测试
|
||||
- 深色主题未做
|
||||
- 窄屏已适配(7.10),但没有真机 / headless 浏览器的视觉回归,只有结构性断言
|
||||
- 窄屏已用 playwright 在 390/320px 实测过(7.10.1),但那套脚本是临时的、
|
||||
没有进 CI;日常回归仍只有读源码的结构性断言
|
||||
- 登录限速与新建会话限速已改为 DB 事务(rate_limits 表),多实例部署不再各自计数
|
||||
- SQLite 抄送查询走 `json_each` 全表展开,无索引;单机量级下够用,
|
||||
百万级邮件时需要加物化列或换回 PostgreSQL
|
||||
- 登录限速是进程内内存计数,多实例部署时失效(MVP 单实例,暂不需要)
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user