Files
MailUI4Agents/client/electron/index.html
JianFeeeee 0a4b98144c 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 封"滚动验收/窄屏验收样例"邮件。
2026-09-14 11:07:03 +08:00

68 lines
3.2 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, interactive-widget=resizes-content" />
<!--
图标放在 public/assets/ 而不是 public/ 根下Gateway 只把 /assets/* 和 /
挂给了静态处理器(见 server/cmd/server/main.go 的静态段),放在根下会 404。
Vite 原样拷贝 public/ 到 dist/,所以开发态与内嵌后 URL 都是 /assets/…。
品牌源文件与其生成脚本client/electron/src/icons/。
-->
<link rel="icon" type="image/svg+xml" href="/assets/agentmail.svg" />
<link rel="alternate icon" type="image/x-icon" href="/assets/favicon.ico" />
<link rel="apple-touch-icon" href="/assets/apple-touch-icon.png" />
<meta name="theme-color" content="#2563eb" />
<!--
沉浸式(用户 2026-09-14「不支持沉浸式网页」
原先没有 manifest也没有 iOS 全屏 meta ⇒ 在手机上「添加到主屏幕」后打开,
看到的仍然是**带地址栏的网页**。加上 standalone + black-translucent
(状态栏内容叠在页面上)之后才是真正的全屏;安全区由 viewport-fit=cover
与 .safe-frame / .narrow-nav 的 env(safe-area-inset-*) 负责,不会被刘海挡住。
-->
<link rel="manifest" href="./manifest.webmanifest" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="AgentMail" />
<title>AgentMail</title>
<script>
/*
* 首帧防闪屏。
*
* JS bundle 有几百 KB从 HTML 解析完到 React 挂载之间有一段空窗 ——
* 那段时间里页面是 body 的默认底色。深色偏好的用户会被闪一下白屏,
* 而且是每次刷新都闪。
*
* 这段脚本必须**内联且同步**:外链或 defer 都会晚于首次绘制。
* 逻辑与 themeStore 的 readStored/resolveTheme 一致,
* 改那边的键名或取值时这里要同步。
*
* 引号别「顺手统一」成双引号test/theme.test.mjs 逐字符断言这一段里是
* classList.add('dark')(单引号)。曾经被 prettier 改写过一次,直接让测试变红。
*/
(function () {
try {
var p = localStorage.getItem('agentmail.theme') || 'system';
var dark =
p === 'dark' ||
(p === 'system' &&
window.matchMedia &&
window.matchMedia('(prefers-color-scheme: dark)').matches);
if (dark) {
document.documentElement.classList.add('dark');
document.documentElement.style.colorScheme = 'dark';
}
} catch (e) {
/* localStorage 不可用(隐私模式)时退回浅色,不阻塞渲染 */
}
})();
</script>
</head>
<body class="bg-gray-50 text-gray-900 antialiased">
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>