|
|
f6feb7c0df
|
test(webui): 「滑动硬截断」诊断脚本(自带浏览器,不依赖共享实例)
用户:「webui 大片界面存在滑动硬截断」。
## 现状:这一轮我**没能复现**
量法是"内容溢出但没有可滚动祖先"⇒ 候选 0 个;换成用户视角四问
(有没有可滚动容器 / 能不能滚到底 / 末尾有没有被切 / 页面自己溢不溢出)后:
390×700 通信 ✅ 日历 ✅ 联系人 ✅ 我的 ✅
1400×700 通信 ✅ 日历 ✅ 联系人 ✅ 我的 ✅
(造了 12 个会话、正文都很长;验完已归档)
即按这四种量法都测不到硬截断。**长正文详情页**那一块我的探针超时没跑完 ⇒
**未判定**,不能算"没问题"。
## 这次的交付物
`client/electron/test/manual/scroll-cut-verify.mjs`:把上面这套量法固化下来,
并且**自带浏览器**(不连共享 9222)—— 共享实例被历次被中断的运行留下僵尸上下文后,
`connectOverCDP` 会一直超时(我这个会话里已经遇到三次),诊断脚本必须在
"共享实例状态未知"时也能跑。
## 我需要的(否则只能继续猜)
请指一下**具体页面 + 具体手势/位置**:哪一屏、滚轮还是触摸拖动、硬截断出现在
列表末尾还是详情正文?也可以直接看截图。我上一轮"窄屏显示不全"也是同样情况 ——
我量不出来、但你说有,那多半是我量的维度不对。
|
2026-09-14 13:43:11 +08:00 |
|
|
|
9aa702b8cc
|
fix(webui): 导航改"按主题的玻璃"(浅色白玻璃+深字)+ 去掉整屏闪的入场动画
用户两句话:
「那你为什么不把白字换成黑字或者自动反色或者描边呢?」
「部分动画十分不合理,会导致页面大范围的闪动,且不能让人自然的把注意力集中在
将要出现的页面上」
## ① 导航:他说得对,我之前是用错误的方式解决对比度
这个应用是**浅色底 + 深字**,导航却是全页唯一一块黑的 —— 那才是"割裂"。我上一轮
为了"白字对比度"把它做成深玻璃,等于**把一块地方永久压黑**来回避问题。
现在导航底色/文字全部走令牌,按主题切换:
浅色:白玻璃 rgb(255 255 255 / .72) + 深字 #475569 → 对比度 7.48:1
深色:深玻璃 rgb(15 23 42 / .72) + 亮字 #94a3b8 → 自动反色
组件侧改成 `.nav-rail` / `.nav-item[data-active]` 令牌类(Sidebar 与 NarrowNav 同一套)。
同时删掉壁纸模式里写死的深玻璃规则 —— 那条正是"为什么还是黑色"。
## ② 动画:整面板入场删掉
先是从"所有面板一起动"改成"只动主内容区",试下来仍然不对:页面级淡入会把
**已经在那儿的框架**也一起暗一下,观感还是闪。所以这一档整体删掉,只保留局部、
有明确语义的动效(日历翻页、菜单展开)。实测切视图时 `animatedOnSwitch = 0`。
骨架不动,注意力自然落在变化的那块内容上。
## ③ 一条我自己的误判(值得记下)
深色主题下我量到导航文字对比度只有 2.36,一度以为是变量/级联的问题,还写死了一份
深色字面值。**那是误判**:`.nav-item` 有 `transition: color .15s`,我在切主题后
**立刻**读 computed color,拿到的是过渡的**起点**(浅色值)。决定性证据是连内联
`style.color` 都"改不动"它 —— 级联不可能这样,只可能是还在过渡中。等 400ms 再量就正常。
写死的那份已撤掉,并在 CSS 里留下说明;新判据 `test/manual/nav-contrast-verify.mjs`
用 WCAG 比值量对比度(不是"颜色是不是黑的"),每次读之前等 400ms。
背景套件 32 条全绿(含"导航走令牌 + 两套令牌都在")。
|
2026-09-14 11:57:41 +08:00 |
|
|
|
2b6eb97bc9
|
test(webui): 横竖屏判据跟上新契约(横屏=侧边导航、列表固定 320)
`rotate-verify.mjs` 10/10。途中判据自己错了两次,都记在这里:
1. `content` 选择器取 `.narrow-shell > *:not(.narrow-nav)` —— 横屏改用侧边导航后,
第一个子元素变成了 60px 侧栏 ⇒ 把侧栏当内容区来判断排布,报出一条假失败。
改成同时排除 `.bg-chrome-900`。
2. 原先只断言"排布换了",没断言"导航在哪边" ⇒ 用户后续追问的侧边栏
根本没被判据覆盖。现在补:横屏侧栏必须 60px 且在左侧、底部导航必须不存在;
竖屏反过来(底部导航存在、无侧栏)。
|
2026-09-14 11:36:50 +08:00 |
|
|
|
d11e6df9a8
|
feat(webui): 日历翻页过渡动画(方向跟随手势/按钮)
用户:「日历滑动页面为什么没有切换动画?」。
## 做法
`shift()` 里记下方向(手势与"上一页/下一页"按钮**都走这一个函数** ⇒ 方向只有一个来源),
容器用 **key + 声明的动画类**:
<div key={`${anchor.getTime()}-${scale}`} className={`flex-1 min-h-0 flex ${slideClass}`}>
## 为什么不是"命令式加类"
第一版用 `el.classList.add()`(与视图切换动画同一套写法),**类根本没进 DOM**。
原因:切月时 `loading` 会把网格整块换成加载态、再换回来,命令式加上的类会被
React 的重渲染与那次重挂载抹掉。key 变化 ⇒ 新元素自带动画类出现 ⇒ 必然重放。
## 顺带修掉一个守卫 bug
`firstRender` 守卫原先只在 `el` 存在时才消费。挂载时 `loading=true` ⇒ 容器还没渲染
⇒ 守卫一直留着 ⇒ **用户第一次真正翻页的动画被吞掉**(实测:点第一下不动、第二下才动)。
现在无条件消费。
## 判据
`test/manual/calendar-swipe-verify.mjs` 增加三条(与滑动手势同一条线索):
反向对照"刚进页面不跑动画"(animationName=none)、翻页时 `cal-slide-next` +
`cal-in-next` 且时长 >0、反向翻页是 `cal-slide-prev`(方向正确性)。
**实测**:静止 none → 点下一页 `cal-slide-next`/`cal-in-next` 0.2s → 上一页 `cal-slide-prev`/`cal-in-prev`。
|
2026-09-14 11:27:37 +08:00 |
|
|
|
8a9bc58433
|
feat(webui): 横竖屏自适应(紧凑双栏)+ 旋屏重算高度
用户:「为什么窄屏的横屏和竖屏没有自适应」。
## 根因
断点是**纯宽度**的:`(max-width: 1023px)`。手机竖屏 390 与横屏 844 **都落在同一档**
⇒ 布局一个像素都不变,横屏多出来的 ~450px 全浪费在空白上。看起来就是"没自适应"。
## 改动
- 新增 `useCompactTwoPane()`:`(min-width: 700px) and (max-height: 560px)`
—— 仍是紧凑外壳(悬浮底部导航),但内容区从**覆盖式**改成**列表 + 详情并排**。
用高度而不是 `landscape` 判:桌面窗口压扁、分屏、软键盘弹起都会命中,
而它们要的是同一件事(别浪费横向空间)。
- App:`compactTwoPane && hasList` 时走并排分支,其余情况仍是 NarrowStack 覆盖式。
- `--app-height` 增加 `orientationchange` 监听并延后一帧重算(iOS 上部分版本的
resize 不跟着方向变化走;立刻读会拿到旧高度)。
## 判据
`test/manual/rotate-verify.mjs` 8 条,含**可逆性**与前置对照:
竖屏覆盖式 → 横屏并排(真的换了 class)→ 旋回竖屏恢复覆盖式;
三态的 `--app-height` 分别为 844 / 390 / 844(不是旧值);横屏无横向溢出、
底部导航仍完整可见且浮着(left=20)。实测 **8/8**。
同一轮还补上了 `test/manual/calendar-swipe-verify.mjs`(日历滑动,4 条)。
|
2026-09-14 11:13:24 +08:00 |
|
|
|
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 |
|
|
|
76ce201c3a
|
feat(webui): 导航合并 + 悬浮玻璃 + 页面动画 + 控件档透明度 + 圆角无条件生效
用户四封信(同一线索)提的六件事,都在这里:
## ① 导航合并(「导航栏的内容有点多了」)
收件箱/发件箱/授权 → 一项「通信」,进去由**内部页签**区分(CommTabs);
新建 → 「通信」页内**悬浮圆形加号**(ComposeFab);导航只剩 通信/日历/联系人;
管理 → 合进「我的」,管理员在页面**最下面**看得到(非管理员不渲染,不是禁用)。
`viewMode` 仍是原来那三个值(几十处调用点不用改),新增 `commTab` 记住上次用的子页签;
通信项的徽标 = 未读 + 待决策(授权信息不能因为合并而消失)。
## ② 窄屏底部导航:悬浮玻璃(「还是固定底部延伸,没有玻璃效果,也没有悬浮」)
原来是 `border-t bg-chrome-900` 通栏贴底。改成:左右/离底各留 10px、圆角、
深色半透明 + `blur(18px)`、投影,安全区并入下外边距。
## ③ 页面动画(「页面极度缺少动画,所有页面都是直接出现」)
在 `<html>` 上挂一个短命类触发外壳直接子面板的入场动画。**没有用 key 重挂载**:
面板是 flex 链上的一环,套 wrapper 会改掉 flex 传递(窄屏覆盖层最先坏)。
并且尊重 `prefers-reduced-motion`(关了就一点都不动)。
## ④ 控件档透明度(「复选框和地址猜测项…他们才是真正需要拉低透明度的地方」)
新增第三档 `--bg-glass-control`(0.5/0.55):授权多选胶囊、地址建议菜单。
三档递减:正文面 0.88 > 嵌套 0.82 > 控件 0.5。
## ⑤ 打底 vs 透明(上一封「该打底的地方透了、该透的空白处糊了」)
正文面回到 0.88(读得清优先)、空白/页面底透明**且不模糊**、面板模糊降到 10px。
## ⑥ 圆角与玻璃**无条件**生效(「大面积缺失圆角与玻璃效果…都是硬截断」)
根因:`.app-shell` 的留缝/圆角/投影原先全写在 `html[data-bg='on']` 里
⇒ **没开壁纸的账号**看到的是硬边不透明面板。现在几何下沉为无条件基线,
壁纸相关的加强仍叠加在上面。窄屏内容面板同样浮起来。
## 判据
- `test/nav-merge.test.mjs` 8 条(含自检:重构前的写法必须判红)——
改完跑老套件 254 条**全绿却一条都没测导航结构**,结构改动必须自带判据。
- `test/manual/nav-restructure-verify.mjs`:真浏览器 9 条(导航项数、页签切换真的换内容、
加号 56×56 且 `elementFromPoint` 命中自己、我的页底部管理入口在退出登录之后、
控件档 α=0.5 对正文面 α=0.88 的反向对照)。
- `test/manual/narrow-glass-verify.mjs` 6 条:悬浮几何(三边留缝 10px、圆角 14px、
缝隙处命中的是页面底 ⇒ 真的浮着)、玻璃(α=0.82 + blur18)、触摸高度最小 49px、
动画真的在跑(静止时 `animationName=none`,切换后 =pane-in)且 reduced-motion 下不动。
- `test/background.test.mjs`:把三条**过时判据**(早前那版"越透越好")按现行契约重写 ——
旧判据留着只会把我拽回错误方向。30 条全绿。
## 顺带
窄屏探针原先只调视口、**没模拟触屏**,于是 `(hover: hover)` 仍为真,
把「悬停才显形的次要动作」误报成透明 —— 差点去"修"一个本来就对的规则。
已改成 hasTouch + isMobile + DPR 的触屏上下文,且收尾只关自己的 context
(CDP 连的是共享 Chromium,`browser.close()` 会把别人的标签页一起关掉)。
|
2026-09-14 10:35:05 +08:00 |
|
|
|
ababe4ae56
|
fix(webui): 壁纸被不透明层盖住 —— 玻璃不再层层相乘 + 浅色表面类全部接管
用户报的:「webui 目前在壁纸底上叠了太多不透明层,导致壁纸效果很差,几乎看不出来」,
追问后补充:「不只是玻璃,而是很多界面是不透明的」。两句都成立,是两个叠加的原因。
## ① 玻璃层层相乘
每层面板都吃同一个不透明度 a,两层就是 1-(1-a)²。a=0.82 时两层 0.97、三层 0.995
—— 壁纸在数学上被吃掉,而且每层各做一次 16px 模糊,图案被糊成灰块。
改法:玻璃只出现一次(外层 0.62 + 18px 模糊;嵌套层只留 0.3 色调且不再模糊;
第三层透明)。同时把遮罩 24%→12%、照片自身模糊 8→4px(那张被洗白的锅之一)。
## ② 14 个浅色表面类根本没被接管("很多界面是不透明的"就是这条)
旧规则只接管 white / gray-50 / slate-100,而源码里在用的是
gray-100(24 处)、blue-50(17)、red-50(13)、blue-100(8)、gray-200(8)、green-50(8)…
—— 全是实心的,正好盖住壁纸。
清单改为**从源码用法生成**(`scripts/gen-background-takeover.mjs` → 生成的 CSS,
构建前自动跑),因为手写清单必然烂;判据保证"源码里出现的浅色表面类必须都被接管",
并明确排除页面底(gray-50/slate-100 必须保持**全透明**,不能被改成半透明)。
按钮/徽标(*-600/700、chrome-600/700)**故意保持不透明**:小控件可读性优先
(index.css 里原有注释记着实测 4.46:1 的教训)。
## 判据
- `test/background.test.mjs` 23 条(新增 8 条):玻璃算式(两层 ≤0.85)、外层 ≤0.70、
嵌套规则存在、**判据自检**(旧值 0.82 必须算得出 >0.95)、表面类覆盖、页面底不得进清单、
清单非空跑。犯过一次错:第一版把判据追加在 `process.exit()` **之后** ⇒ 根本没执行,
从"测试数没变"才发现。
- `test/manual/wallpaper-layers-verify.mjs`(真实渲染,自带无头 Chromium):
用**纯红壁纸读绿通道**测有效不透明度(纯色下 backdrop-filter 不影响读数)。
面板 p95:**修复前 82% → 修复后 62%**;反向对照(改回旧值)能分辨 ≥10 个百分点;
深色照片面上面板仍是浅底;关掉壁纸的同坐标对照更实。5/5。
过程中作废了两个指标:只看绿通道会把深色元素误判成"盖死";"面积占比"类阈值
(Δ≥8/≥40)在 0.97 时仍会蹭过门槛,饱和而无分辨力 —— 同坐标比值才可信。
## 交付
已部署(网关内嵌 WebUI 重建):线上 CSS 已含 `--bg-glass-inner`、嵌套规则与 14 个接管类。
|
2026-09-14 08:09:22 +08:00 |
|
|
|
35f71d5144
|
test(webui): 全流程演练的两处判据修正(同主题请求的定位、工作区目录)
第三次跑通 **37/37(0 失败 0 无法判定)**,含真 Agent 闭环:写信带附件 →
pi 请求 bash 授权 → **在界面点同意** → 决策落库 → pi 回信把附件原样带回
(sha256 一致)→ 回信出现在收件箱。
两处都是**判据/前置条件**的问题,不是产品缺陷,但都会伪装成产品故障:
1. 授权页上同一 Agent 的请求主题**一模一样**("是否允许执行 bash?")。原先按主题
`.first()` 选行,于是点到了**别的会话那条旧请求**上 —— 网关回 `expired` 警告,
界面看起来"点了但没生效"。现在按**本轮会话别名**定位分组容器
(`header.locator('xpath=..')`,DOM 探针确认头与请求行同容器),且**只在折叠时**
才点头按钮(已展开时再点会把它折起来,那正是第二次又落到别人行上的原因)。
2. **工作区寻址里的 path 必须是已存在的目录**,否则桥按设计回退到自己的兜底目录
(`~/.pi/mail-sessions/<会话>`),产物就落在别处而不是我指定的目录。脚本现在
先 mkdir —— zcode 那次也栽在同一条规则上。
顺带记录一个**产品层面值得决策**的现象:pi 的 worker 池上限是 3,而**"等人点头"的
worker 占着池子**。我上一轮的误点让 3 个 worker 全卡在等决策上 → 池满 → 新邮件排队
(设计如此:满载排队不丢信),于是我 300s 等不到回信。清掉卡住的请求后队列**立即
排空**、排队那封信被取出并起了一轮。也就是说:人在开会时,同一个 Agent 接不了新活。
是否把等待授权的 worker 挪出池子(或单独给额度),需要你定。
|
2026-09-13 12:06:40 +08:00 |
|
|
|
f243355bd4
|
test(webui): 全流程演练(真浏览器 + 真后端 + 真 Agent 闭环)
test/manual/webui-full-flow.mjs —— 37 项判据,实测 37/37 全绿、0 失败
0 无法判定。覆盖:登录(含错密码负向对照)→ 收件箱 → 打开详情 → 写信带
附件 → **真 Agent 闭环**(Agent 请求 bash 授权 → 在界面批准 → 决策落库 →
Agent 继续 → 回信把附件原样带回,sha256 一致)→ 授权页 → 多账号添加/切换
→ 主题与背景(含自定义图片)刷新后保持 → 窄屏 → 全程无预期外 4xx/5xx。
判据要点(这轮踩过的坑都在里面):
- 授权邮件**不在收件箱列表里**(MailList 明确把 permission_request 滤到
「授权」导航项)——在收件箱里找它必然"找不到同意按钮",看着像 UI 缺陷。
- 决策按钮必须用**精确文本**:`has-text("同意")` 会命中"已同意"/"一直同意",
`.first()` 可能点到别处,表现是"点了但库里没有任何决策"(像后端失灵)。
- 一次「同意」只放行**一条**命令,多步任务会一条接一条地问(实测 pi 连问
两次)——所以脚本首次用「同意」、之后用「一直同意」,两种选项都验到。
- 未登录时前端要问一次 `/auth/me`,那时 401 是**正常回答**;登录之后再 401
才是会话丢失。判据按时间点区分,而不是无条件放过这个端点。
- 故意用错密码造的 401 也要断言"确实产生了",否则"被拒"那条可能是假绿。
顺带清掉本轮留下的测试数据:6 条待决权限决策为拒绝(剩 0),13 个测试会话
归档 12 个(余下 1 个属他人会话,服务端 403「无权归档他人的会话」——这是
对的,没绕过去)。
|
2026-09-13 09:30:44 +08:00 |
|
|
|
085e6384a0
|
test(electron): 多账号真实验收脚本(真起打包产物 + 两个真实账号)
test/manual/multi-account-verify.mjs:预置 accounts.json 后起打包产物,
判据落在**网络层**而不是"列表里有多少行" —— 收件箱按会话分组且默认折叠,
DOM 行数 ≠ 邮件数,用行数当判据只会得到一条随折叠状态变化的假绿/假红。
21 项判据,实测 21/21:
- 聚合 = 每个可用账号各一次收件箱请求、**各带自己的令牌**(抓请求头确认没串号)
- 徽标的存在与否是单/聚合视图的确定性差异(单账号视图不该有徽标)
- 切到单账号只问那一个账号、且带的是它的令牌
- 添加流程:无效密钥当场拒绝(不写进列表)、有效密钥写入并落盘
- 落盘 accounts.json 权限 600、内容与界面一致
★ 修一处**判据自伤**:无效密钥那步故意发 401,最初我把它算成"页面错误"导致
假红;改成"断言这个 401 确实是本步造出来的"(直接过滤 401 会把真实的鉴权故障
一起放过去)。
|
2026-09-13 06:38:58 +08:00 |
|
|
|
addde97600
|
feat(electron): 多账号第一纵切 —— 账号存储/选择器/聚合收件箱
按 docs/MULTI-ACCOUNT-PLAN.md 实现客户端多账号的前半段(SSE 多连接与
写信账号切换留作下一轮)。
- `src/lib/accounts.ts`:纯逻辑(地址规范化、身份判重、默认账号、聚合合并),
16 条测试钉住每条判据(含反向对照)。
- 持久化在主进程:`userData/accounts.json`,**原子写**(临时文件 + rename)+
0600。不落 localStorage:那份存储渲染层任何脚本都可读,且 file:// 与
http:// 是两套。无 IPC 时(浏览器)退到 localStorage 并在界面**如实写明**。
- 取信:单账号走原路径(逐字节不变);聚合时**每账号各一次请求、各带自己的
令牌**(`fetchWithAuth`,不碰认证单例,避免并发串号)。
- ★ 只合并**同一网关**的账号:跨网关的邮件混进列表后点开会去问当前账号的
服务器(404,或 mail_id 撞上就打开了别人的信)。如实排除 + 列表上方说明。
- ★ 部分失败可见:某账号取不到时给出账号名与原因 —— 静默丢掉它会让聚合列表
少一整份邮件而界面看起来完全正常。
- `API_BASE` 改为 `let`(切换账号要换网关),api 层不得缓存它
(`client.ts` 的 `const BASE` 快照已改成每次读)。
- UI:列表头下拉(≥2 个可用账号才出现「全部邮箱」)+ 账号徽标 + 账号页
「多账号」一段(添加前调 /auth/me 验证,401 当场拒绝,不写进列表)。
- 测试:vitest 230 通过(原 222 + 新 8)、`test/lib/accounts.test.mjs` 16 通过、
typecheck 通过。新增 `test/manual/multi-account-verify.mjs`(真起打包产物 +
两个真实账号,判据落在网络层:聚合必须每账号各一次请求且各带自己的令牌)。
|
2026-09-13 06:16:59 +08:00 |
|
|
|
8b2206ed53
|
fix(electron): Phase 3 验收抓到的两个静默缺陷 —— 白屏与登录
Phase 3(写信 + 附件 + 权限面板)的验收脚本第一次跑就把这两件事翻出来了,
两个都**表现正常**:进程活着、窗口标题对、接口能通,只有结果不对。
## 1. 打包后的应用是白屏(vite 的 base 缺省值)
`vite.config.ts` 没设 `base`,Vite 按默认的 `/` 生成 `src="/assets/index-xxx.js"`。
同一份 dist 有两个宿主:网关在 `/` 下伺服它(Web 正常),Electron 用 `loadFile()`
从 **file:///…/dist/index.html** 加载它 —— 绝对路径在那儿解析成
`file:///assets/index-xxx.js`(不存在),**JS 根本没加载**。
现场:`#root` 里一个子节点都没有。没有报错对话框,控制台里只有一条不起眼的
资源加载失败。而当时所有既有检查都是绿的:`npm run build` 成功、deb 元数据检查、
asar 内容清点(**它们只看文件在不在,不看文件引用什么**)。
修法:`base: './'` —— 两边都对(Web 在 /index.html 里 `./assets/x.js` → `/assets/x.js`;
Electron 在 dist/index.html 里 → `dist/assets/x.js`)。
## 2. 桌面壳用账号密码登录是断的,而且静默失败
账号密码登录靠 `SameSite=Lax` 的会话 Cookie,而桌面壳的页面是 `file://`
(**不透明源**)—— Chromium 按第三方上下文处理它,Cookie **不予存储**。
实测现场:`POST /auth/login` 返 **200**、响应体能读出用户名,但 `document.cookie`
是空的,紧接着的 `/auth/me` 返 **401**;界面停在登录页,看起来像「密码错了」,
而同样的账号密码用 curl 登录是成功的。所以这不是凭据问题。
修法:桌面壳里**不再给账号密码表**(一个必然失败的按钮比没有更糟),改成粘贴
**用户密钥**(`Authorization: Bearer`,桌面端本来就该这么用):
- preload 显式声明 `__AGENTMAIL_SHELL__ = 'desktop'`(宿主契约,而不是让渲染层
sniff 协议;顺带让 jsdom 里可测 —— 那里的 `location.protocol` 不可重写)
- 新增 `authStore.loginWithKey`:成功后才留下令牌,失败**还原**(否则之后每个请求
都会带上这个坏 key 并 401,而人看到的是「重输一次也不行」)
- 顺手修了 label 与 input 没有关联(`htmlFor`/`id`)—— 无障碍缺陷,也让测试能按标签查
## 验收
- 结构性守卫进 `npm test`(`test/packaging.test.mjs`,不需要浏览器):base 必须是
相对路径、产物里不能有绝对资源引用、**安装包里的 dist 与当前构建一致**
(前端改了没重打包时,装上去的人看到的是旧界面,两边不一致却谁都不报错)。
判据自检过:把 base 改回 `/` 或把产物改回绝对路径,各自都能让对应那条变红。
- 组件测试 6 条(两种壳的形态、密钥成功/失败、空密钥不可提交)。
- `test/manual/desktop-phase3-verify.mjs`:真起打包好的应用(xvfb + CDP),
一条贯穿的链 —— 用桌面 UI 写信带附件 → 外部核验信与附件真到了网关 →
这封信触发 zcode 的真实授权请求 → 在桌面**授权面板**里点同意 →
外部核验 **Agent 真的执行了**(标记文件出现)。第二次跑 14/14 全绿。
- 客户端全量 222/222;网关换新产物后 Web 依旧正常(相对路径在 `/` 下同样成立,
实测渲染出收件箱、无控制台错误),并真发一封邮件确认回信到达。
## 判据自己的错(记一笔)
第一次跑时「附件真的挂在信上」报红,而库里那 41 字节的附件**明明挂在信上** ——
我把端点写成了 `/me/mail/{id}`(不存在,404),正确是 `/mail/{id}`。
判据用错端点时以「附件是空的」现形,看起来像功能 bug。
另:`pkill -f 'agentmail-web'` 会把**自己这条命令**也杀掉(命令行里含同样的字符串),
表现是「脚本没有任何输出、退出码 143」。改用端口定位(`ss -tlnp | grep :9223`)。
|
2026-09-12 20:25:00 +08:00 |
|
|
|
c19eea5e3c
|
feat(webui): 真正动可见层的现代化 —— 字号、层次、间距、分隔线
# 起因:上一轮的「现代化」基本不算现代化
用户指出「我说的是 webui 现代化」。回看上一轮,我交付的其实是**底层改进**:
圆角加大一档、自定义滚动条、焦点环、过渡、reduce-motion、令牌与可访问性。
这些都对,但**可见变化几乎只有圆角** —— 界面看起来还是老样子。
实测数据确认了「老」在哪:
text-xs(12px) 172 处 ← 被当正文用
text-[10px] 84 处
text-[11px] 68 处
text-[9px] 19 处 ← 现代显示器上基本读不了
text-sm(14px) 69 处
text-base(16px) 4 处
57 处 border-b + 21 处 border-r,其中 67 条是 border-gray-200 的硬灰线
阴影:全站共 10 处,且全是 Tailwind 系统默认档;
index.css 里 --shadow-1/2/3 三个语义令牌**定义了但零处使用**
所以真正的病因是三条:**字太小、层次为零、硬线切分**。
# 改动
## 1. 字号体系抬一档(tailwind.config.js)
不用 Tailwind 默认档,重定为:
3xs 11px(角标下限,取代 9/10px 魔法数字)
2xs 12px(元信息,取代 11px)
xs 13px(次要正文,原 12px —— 拿它当正文的地方自动变舒适)
sm 14px(正文)
base 15px
并把 171 处裸 px 类名(text-[9px]/[10px]/[11px])统一换成令牌 ——
顺带消除魔法数字。行高一起给:小档位 1.35/1.45,正文 1.55,
只放大字号不放行高会把密排列表顶得很难看。
## 2. 把层次接出来(原本是死代码)
tailwind.config.js 新增 boxShadow 映射 `--shadow-1/2/3` + 新增
`--shadow-panel`(横向偏移 + 大扩散,竖向几乎不偏移,否则全高面板像浮在半空)。
用于:列表面板(lg:shadow-panel,**同时去掉 border-r 硬线**)、
登录/初始化卡片(shadow-sm → shadow-2 + 去硬边框)、
地址自动补全下拉(shadow-lg → shadow-2)、窄屏滑入详情面板
(shadow-2xl → shadow-3 + 去 border-l)、主题分段控件的选中滑块。
深色下层次比浅色更难感知,所以 --shadow-panel 在深色里更实一些;
深色里靠边框分组几乎看不见,层次是**唯一**有效的分组手段。
## 3. 分隔线软化(改令牌而不是改 67 处类名)
`--c-gray-200` 浅色 229 231 235 → 234 236 241,深色 44 49 59 → 39 43 52。
改在令牌上,67 条边框 + 8 处底色一次性生效且不会漏。
**刻意没有一起调 gray-300**:它同时是滚动条滑块色,调淡会让滑块更难看见。
## 4. 配比放宽(列表行的呼吸感)
MailList:行内距 px-3 py-2.5 → px-3.5 py-3,列表 gap space-y-0.5 → space-y-1,
表头 py-3 → py-3.5。未读主题字重 medium → semibold,已读 gray-500 → gray-600。
## 5. 量出来的两个真实对比度缺陷(不是估算)
新增 `test/manual/modernization-verify.mjs`,用真实渲染做四条判据。
它量出浅色下两个 WCAG AA 不达标(阈值 4.5:1):
- 会话别名 `text-blue-500` 白底 3.68:1(别名在 mail list / thread / mailview
共 4 处,都是 11px 小字)→ 改 blue-600/700,达 5.17:1
- 时间戳 `text-gray-400` 压在选中行淡蓝底 `bg-blue-50` 上 4.44:1
第二个的**根因是调色板缺一档**:浅色下 `--c-gray-400` 与 `--c-gray-500`
完全相同(都是 107 114 128),于是「比次要文字再深一档的中间色」根本不存在,
时间戳无处可退。拉开 gray-500 → 90 98 112(5.65:1),并把 5 个列表组件的
行内元信息(19 处)从 gray-400 提到 gray-500。
# 验证
- typecheck 干净
- 前端全量 `npm test` EXIT=0(markdown-xss / narrow-layout / theme 30 /
background 15 / vitest 216)
- **真实渲染** `modernization-verify.mjs`:浅色 8/8、深色 8/8,判据含
最小字号 ≥ 11px(改造前 9px)、邮件正文 ≥ 14px、列表面板真有 box-shadow、
gray-200 是软化值、40 处正文对比度全部达标
# 我自己的三处错(都被这次的度量拦下)
1. **判据量错对象**:第一版拿「收件箱列表」要求 40% 元素 ≥13px,量出 39.7%
判失败 —— 而收件箱本质是元信息密集区,发件人/时间/别名本来就该小。
改成量真正该达标的**邮件正文**(≥14px)。
2. **探针忽略 alpha**:`parseRgb` 把 `rgba(239,246,255,0.4)` 的 alpha 丢掉当实色,
于是把淡蓝底当纯蓝算出 4.44:1 的假缺陷。改为按画家算法合成整条背景链。
3. **config 注释换算写错**:3xs 注释写 10px,0.6875rem 其实是 11px。
|
2026-09-12 09:54:31 +08:00 |
|
|
|
84c1d749cd
|
feat(webui): 自定义背景 + 外观现代化;修正实心按钮白字在深色下的对比度
# 自定义背景(新功能)
三选一:不设 / 预设渐变 / 自定义图片,另加压暗与模糊两条滑杆。
**预设的色值全部复用现有调色板变量**,因此自动随主题变化 —— 那一组
(50–300)在深色下本来就是暗的(见 .dark 与 theme.test.mjs 第 19 条),
于是浅色得到柔和 pastel、深色得到低沉暗调,不需要维护两套渐变,也不会
出现「深色模式下原样落下浅色渐变」这类绕过主题变量的错误。
图片路径的关键取舍:
- **先压缩再存**。手机直出照片 4–8MB,而 localStorage 配额约 5MB,直接写会抛
异常,用户看到的是「选了图片没反应」。等比缩到最长边 2560px、转 JPEG;
仍超限则再缩一档;再不行就**明确拒绝并说明原因**(不是静默失败)。
- 失败一律返回 `{ok:false, reason}` 并渲染成 `role="alert"`。
# 背景层为什么不放进主题 store
主题(light/dark/system)是必须全局一致的语义;背景是纯装饰偏好,取值空间
与主题毫无关系。混在一起会让「跟随系统」的实现被背景字段淹没。
# 背景层实现在 CSS,不改 27 个组件
按 Tailwind 生成的实际类名统一接管:背景开启时让出不透明的页面底
(body / bg-gray-50 / bg-slate-100 → 透明),并把卡片(bg-white)与框架
(bg-chrome-800/900)变成半透明 + 背景模糊。
逐个组件加 class 必然漏 —— 漏掉的那块就是一张不透明卡片浮在背景上。
这段 CSS **刻意放在所有 @layer 之外**:它要覆盖的正是 utilities 生成的
`.bg-white`,写进 @layer components 会被 utilities 压过去(静默失效),
而分层 CSS 恒输给未分层 CSS,这是唯一稳定可靠的位置。
**chrome-600/700 刻意保持不透明**:它们不是大面板,而是导航项与 15px 的
计数徽标。真实渲染量得半透明会把徽标上的数字压到 4.46:1,低于 AA 4.5 ——
小控件的可读性优先于装饰效果(已用脚本量出,见下)。
# 「跟随系统」的可见性
三态本来就已实现(system 为默认值 + matchMedia 监听)。这次做的是让它可被
发现与信任:选择器改成分段控件(role=radiogroup + aria-checked),说明文案
写清「跟随系统会随系统的深色开关自动切换」,并保留单选按钮入口的
「当前跟随系统:深色/浅色」提示。
# 外观现代化
- **圆角整体调大一档**(默认 0.25→0.5rem)。原值是几年前的紧凑风格,
在宽屏桌面应用上偏硬。只改比例尺,200 处圆角一次性刷新,不产生
「新组件大圆角、旧组件小圆角」的断层。
- 语义化圆角令牌:`rounded-card` / `rounded-control`(数值档位答的是「多大」,
这两个名字答的是「用在哪」)。
- 自定义滚动条(桌面应用里常驻可见,系统默认样式偏旧)。
- 键盘焦点环(`:focus-visible`,仅键盘导航时出现;可访问性硬要求)。
- 交互元素统一过渡;并尊重 `prefers-reduced-motion`。
# 顺带修正两处真实问题(都由真实渲染量出,不是估算)
1. **实心按钮白字在深色下 4.46:1,低于 AA**。
深色 `--c-on-accent` 是「近白」244 246 250(为了不刺眼),而结构检查第 23
条只拿**浅色**的纯白 255 去算 → 4.83 通过。**测试存在盲区**:
同一个实心底,白字换暗一点点就越过 AA 线。导航未读徽标「12」正是这个组合。
两处都修:把第 23 条改成**两种模式的 on-accent 都算**(闭合盲区),
并把深色 on-accent 抬到 250 250 252(4.65:1,仍非纯白,保留原初衷)。
2. **theme.test.mjs 切颜色块的方式很脆**:它用 `indexOf('.dark')` 切片,于是在
:root 的注释里写一句带点的选择器写法就会把浅色块提前截断(我加注释时
真的踩到了,第 8 条假失败)。更危险的是反向情形:块被截短后变量集合变小,
「覆盖齐全」这类断言可能**真空通过**。改为所有块切分都基于**剥注释后**的文本。
# 测试
- 新增 `test/background.test.mjs`(15 条结构检查):遮罩两主题各一份、
背景层必须负 z-index(0 会盖住界面)、背景开启时必须让出页面底、
玻璃化只在 data-bg=on 下、悬停态一并接管、预设复用调色板变量、
图片上限与失败原因存在、尊重 reduced-motion 等。
- 新增 `test/stores/background.test.ts`(16 条):脏数据归一化(未知预设、
kind=image 却无图、越界数值)、CSS 变量写入与清理成对(残留 --bg-image 会
让「关掉背景」后仍显示旧图)、localStorage 抛异常不打断操作。
- 新增 `test/manual/background-verify.mjs`:连真实 Chromium 验收**渲染结果**
(背景层是否真的可见、玻璃化的计算样式、正文在背景之上是否仍达 WCAG AA、
自动模式在**不刷新**页面时跟随系统切换、显式选择不被系统覆盖)。
它拦住了上面两个真问题,也拦住了我自己两次写错的判据。
# 验证
- typecheck 干净
- 主题 30/30、背景 15/15、vitest 216/216(新增 16)
- 真实渲染验收 23/23(AGENTMAIL_DIST 注入本地构建 + 活 Gateway,未部署即验收)
- 截图对照:浅色/深色 + 极光背景,面板玻璃化与层次均符合预期
|
2026-09-12 08:02:30 +08:00 |
|
|
|
f9d757b5e5
|
chore: directory migration - gateway→server, web→client/electron
|
2026-09-08 19:16:35 +08:00 |
|