feat(webui): 通信二级页签与列表头一体 + 沉浸式(PWA 全屏)+ 日历滑动验收脚本

用户三条(同一线索):「通信页面的二级页面与其他位置极其割裂」、
「不支持沉浸式网页」、「日历页面还不支持左右滑动手势」。

## ① 二级页签不再割裂

页签原先是**带 shadow 的白色胶囊**浮在面板上,看起来像硬贴上去的另一套控件。
改成**下划线页签**:与列表头同一内边距、同一条下边框,选中态用蓝色下划线 +
`-mb-px` 压住分隔线(否则会出现"两条线"的接缝)。

## ② 沉浸式

根因不是 viewport(`viewport-fit=cover` 早就有了,安全区也接了
`env(safe-area-inset-*)`),而是**没有 Web App Manifest**:手机上"添加到主屏幕"后
打开仍然是带地址栏的网页。现在加了 `manifest.webmanifest`(`display: standalone`)
+ iOS 的 `apple-mobile-web-app-capable` / `black-translucent`(状态栏内容叠在页面上)。

manifest 放在**根路径**而不是 /assets/ 下:它里面的 `start_url`/`scope` 是相对
manifest 自己的 URL 解析的,挂在 /assets/ 下就得写 "../"。静态只挂了 `/` 与 `/assets/*`,
所以显式加了一条路由(并从 embed 读,而不是读磁盘 —— 前端产物必须与应用同源同版本)。
图标由项目唯一图标源生成 192/512(尺寸与声明一致,我用 struct 读文件头核对过)。

**实测**:`/manifest.webmanifest` → HTTP 200 `application/manifest+json`。

## ③ 日历滑动:补上真正的验收脚本

`test/manual/calendar-swipe-verify.mjs` 四条,含**反向对照**(纵向拖动不得翻页)
与前置断言。写它时又踩了一次自己的坑:标题真实格式是「2026 年 9 月」(数字与"年月"
之间有空格),我第一版正则按无空格写 ⇒ 匹配不到 ⇒ 三个值全是 null,
**看起来像"滑动没生效",其实是探针瞎了**。所以脚本里第一条就是"标题读得到"。

实测:9 月 →左滑→ 10 月 →右滑→ 9 月,纵向拖动不动。

## 顺带

把我为验收造的测试数据**归档**(不是删除):10 个 `/tmp/scrollprobe-*` 独立会话 +
12 封"滚动验收/窄屏验收样例"邮件。
This commit is contained in:
2026-09-14 11:07:03 +08:00
parent 50ee10a522
commit 0a4b98144c
8 changed files with 177 additions and 4 deletions

View File

@ -32,7 +32,12 @@ export default function CommTabs() {
return (
<div
data-testid="comm-tabs"
className="shrink-0 flex items-stretch gap-0.5 px-1.5 pt-1.5 border-b border-gray-200"
/*
* 与列表头同一套观感(同内边距、同下边框),页签用**下划线**而不是浮动白胶囊。
* 用户2026-09-14「通信页面的二级页面与其他位置极其割裂」——
* 之前的白胶囊带 shadow 浮在面板上,看起来是硬贴上去的另一套控件。
*/
className="shrink-0 flex items-stretch gap-1 px-3 border-b border-gray-200"
role="tablist"
aria-label="通信"
>
@ -46,10 +51,10 @@ export default function CommTabs() {
role="tab"
aria-selected={on}
onClick={() => setCommTab(key)}
className={`relative px-3 py-1.5 text-xs font-medium rounded-t-md transition-colors ${
className={`relative -mb-px px-3 py-3 text-xs font-medium border-b-2 transition-colors ${
on
? 'bg-white text-gray-900 shadow-sm'
: 'text-gray-500 hover:text-gray-800 hover:bg-white/60'
? 'border-blue-600 text-gray-900'
: 'border-transparent text-gray-500 hover:text-gray-800 hover:border-gray-300'
}`}
>
{label}