Repository navigation
feat: 浮窗无障碍、macOS 外观同步与降低透明度响应 - #70
Merged
Merged
Conversation
红绿灯图标原先只在 group:hover 时显出,Tab 聚焦能看到 focus ring 却看不到图标——三个按钮对键盘用户就是三个纯色圆点。补 :focus-visible 规则,与 hover 同等对待。 同时补齐状态语义:折叠按钮加 aria-expanded,宽度按钮加 aria-pressed, 折叠态状态点旁加 sr-only 的 role="status" 文本。此前按钮只有 aria-label 写明动作,屏幕阅读器读不到当前是展开还是折叠、是标准宽还是阅读宽, 折叠态的状态也完全靠颜色传达。 新增 TrafficLights 的 6 条无障碍契约测试,覆盖以上各点及图标对辅助 技术隐藏、容器保留 data-no-drag。
prefers-reduced-transparency: reduce 时把阅读衬底提到接近不透明、 玻璃层同步加厚并去掉 backdrop-filter。用户开启该设置说明其明确要求 更实的底色,此时玻璃质感次要,可读性与视觉舒适度优先。 深色配方写在同一个 @media 块内并用 :root.dark 提权,避免写成块外 独立规则后在正常模式下也覆盖 --popup-reading-bg。 注意本规则只作用于 CSS 层;OS 原生材质(Acrylic / Liquid Glass)是否 降级由系统自行决定,webview 无法控制。
此前 set_popup_theme 在 macOS 上是个空实现:前端确实把 `.dark` 同步 过来了,但这条 IPC 什么也没做,浮窗的原生材质明暗只能跟着系统走。 应用主题与系统主题相反时(应用深色 + 系统浅色),CSS 是深的、材质是 浅的,玻璃观感全毁。 改用单窗口 NSWindow::setAppearance,而不是 tauri::Window::set_theme —— 后者在 macOS 上落到 [NSApplication setAppearance:],是应用级的,会连带 改掉设置页与历史窗口的原生外观。AppKit 调用放在 run_on_main_thread 内。 新增 followSystem 参数区分「跟随系统」与显式深浅:system 档位必须 setAppearance(nil),不能按当前明暗钉死 —— 钉死会同时锁住 webview 的 prefers-color-scheme,此后系统主题变化浮窗再也检测不到。 前端同步键从 dark 改为 `dark|followSystem` 组合:用户把「深色」改成 「跟随系统」而系统正好是深色时,`.dark` 不变但语义变了,只比对 dark 会漏掉这次切换。同时订阅 configStore 补上「偏好变化但不改 class」的路径。 appearance_choice 抽成纯函数并加 4 条单测,覆盖六种主题组合与 followSystem 的优先级。
只改 CSS 压不住窗口背后的 Acrylic / Liquid Glass —— 那层由系统合成, 边缘反光与亮度调制照旧,用户开了「降低透明度」仍会觉得浮窗半透明。 新增 set_popup_transparency 命令:reduce 生效时 set_effects(None) 走 tauri 的 clear_effects 把原生材质整个撤掉,关闭时重新施加。恢复路径 复用 translation_flow::popup_effects()(改为 pub(crate)),材质参数 只有那一个来源,不另抄一份。 实测 Windows:backdrop_type 在 reduce 时 3(Acrylic) → 1(无 backdrop), 恢复后回到 3,可逆且无需重启。 同时把深色 reduce 的阅读衬底从 0.96 降到 0.88、玻璃层 0.82→0.74: 0.96 在深色桌面上几乎就是纯黑,与「偏淡」的整体取向相反。0.88 在 纯白/中灰/纯黑/高饱和蓝/高饱和黄五类背景下,白字对比度均 ≥11.8:1, 远超 4.5:1 阈值,降淡没有牺牲可读性。
宽度切换目前只有两档(标准 400 / 阅读 520),单个按钮在两组状态间 来回切,对应 WAI-ARIA 的 toggle button 模式 —— aria-pressed 表达的 正是「按钮当前是否处于按下态」,不是「从一组 radio 里选了哪个」。 补一条测试锁定这个语义选择:断言宽度按钮没有 role="radio"、没有 aria-checked、容器上没有 radiogroup。将来档位增加到 3 个以上时这条 测试会失败,提醒改成 radiogroup + 多个 radio + aria-checked,而不是 继续往 aria-pressed 上堆语义。
标题栏(语言方向标签)与操作栏(复制按钮、快捷键提示)不在阅读衬底 的覆盖范围内,它们的可读性只由 .popup-container 的 alpha 保证。此前 container 留在 0.72 / 0.74,在纯白或高饱和黄桌面上 secondary 文字只有 2.8–4.0:1,tertiary 低至 2.5:1 —— reduce 模式恰恰是用户要求「看得更 清楚」的时候,这里反而更糊。 把 container 提到与阅读衬底同级(浅 0.94 / 深 0.88),并按 DECISIONS D7 的顺序先调文字色:深色 tertiary #98989D → #C7C7CC,浅色 #6E6E73 → #69696E。两处都只在 @media (prefers-reduced-transparency: reduce) 内 覆盖,normal 模式的层次关系不受影响。 实测六类背景(纯白/纯黑/中灰/高饱和蓝/高饱和黄/纹理浅灰)× 三个文字 层级,深色最差 6.53:1、浅色最差 4.78:1,全部高于 4.5:1。
文字色覆盖此前写在 :root 上,而 globals.css 被四个窗口共用。暗色 减少透明模式下 tertiary (#C7C7CC) 比 secondary (#C0C0C5) 更亮,导致 历史列表与设置页的元信息比它们修饰的二级标签更抢眼 —— 层级反转, 且这两个窗口是不透明的,与浮窗的对比度问题没有因果关系。 改为只覆盖 .popup-container:自定义属性继承,浮窗全部后代照常生效。 同时撤掉 reduce 下的光学层(::before 顶部高光、::after 内圈亮边)。 原生材质撤掉后它们仍叠在实心衬底上,观感是「玻璃样式贴到实心板上」。 暗色衬底从 0.88 提到 0.92:无材质无模糊时 0.88 在亮色壁纸下会透出 可察觉的亮度,对比度达标不等于观感成立。实测六类背景 × 三个文字 层级最差 7.46:1(深)/ 4.78:1(浅),仍全部达标。
1. 配置未加载时不再猜 followSystem。config 初值为 null,而该 effect 属于子组件、先于父组件的 getConfig() 落地执行 —— 直接比较会得到 undefined === "system" → false,把「未知」误判成「非 system」, 在 macOS 上把 appearance 钉死成 Aqua。getConfig 若失败(调用方只 console.error),这个错误判断会永久生效:CSS 按 system 渲染成深色, 原生 appearance 却是浅色。改为与 useTheme 同一语义:未知就跳过。 2. 去重值改为 IPC 成功后再提交。此前在调用前就写 lastKey / last, 一次失败(主线程投递失败、窗口未就绪)即永久失配,2 秒轮询再也 兜不了底 —— 而轮询的注释正是声明它用来兜底。 3. 增加 prefers-reduced-transparency 的能力探测日志。WKWebView 至今 不支持该媒体查询(WebKit bug 175497,Safari 26.0 特性清单亦无此项), 不支持时整条 reduce 链路静默空操作 —— 无日志、无降级,排查时看不出 是「用户没开」还是「平台不支持」。
宽度按钮的 aria-label 此前随档位翻转(wide ? "标准宽度" : "阅读宽度"),
而 aria-pressed 同步表达状态。按下态配「阅读宽度」读出来是
「阅读宽度,已按下」—— 用户无法分辨这是当前状态还是点下去会变成什么。
折叠按钮同理("展开"/"折叠" 配 aria-expanded)。
约定统一为:label = 动作(点下去会发生什么),aria-* = 当前状态。
字形互换动画原先靠 svg 的 key={title} 触发,title 稳定后会丢失,
因此把动画键拆成独立的 glyphKey,与 label 解耦。
测试同步更新:原来那条用例的名字承诺了「不参与 aria-pressed 播报」,
实际只断言了 disabled —— 收窄为它真正验证的内容,另加一条锁定
label 不随状态翻转的契约。
apply_popup_appearance 里「拿不到窗口指针 / 指针为空 / retain 失败 / 类型不符」四条早退全部静默返回。D9 的动机就是让外观与材质决策可观测, 而 set_effects 内层是 `let _ =`,日志是唯一线索 —— 静默 return 后 排查「macOS 外观没跟上主题」完全看不出断在哪个环节,四种原因表现一样。 四条各补一条 warn,并给 NSAppearanceName 取不到的情况也加上日志。 set_popup_transparency 的日志原本写「原生材质已清空 / 已恢复」,但这 断言的是本函数证明不了的结论:tauri 的 set_effects 恒返回 Ok,上面那句 map_err 只可能捕获主线程投递失败。改为陈述「投递了撤除/恢复请求」, 与 log_material_decision 的措辞一致。 补充 Cargo.toml 的 feature 注释:列出的五项都在 objc2-app-kit 0.3.2 的 default 里,保留是为了把本 crate 实际用到的 AppKit 能力写进清单。
WKWebView 至今不支持 prefers-reduced-transparency(WebKit bug 175497, Safari 26.0 特性清单亦无此项),macOS 上媒体查询恒为 false,系统级 「降低透明度」偏好根本读不到 —— 整条 reduce 链路静默空操作。这个配置项 给这类平台一个确定性的回退。 布尔键的解析是手写的三处 match(get / 缓存 / 载入),不在 KEY_DESCRIPTORS 表里,新增时漏掉任何一处都是静默的。新增测试同时锁住 三条路径,并顺带覆盖「重启后回退默认值」这个最容易漏的分支。 默认 false:不改变现有行为,用户不主动开就与之前完全一致。
此前 reduce 只有 @media (prefers-reduced-transparency: reduce) 一条 路径。WKWebView 不支持该媒体查询,macOS 上等于整条链路不存在。 现在两条触发源取「或」: 1. 系统级无障碍偏好(媒体查询) 2. 应用内开关(config.reduce_transparency → :root.reduce-transparency) 两者叠加而非互斥:系统偏好命中时永远生效,开关只是额外多一条途径。 CSS 里同一份配方写了两遍。触发条件在 CSS 中无法合并成一个「或」—— @media 与选择器没有合成语法,@container style() 在目标 WebView 上 支持面不够。用 JS 在媒体查询命中时补挂 class 能消掉一份,但那会让 系统偏好的响应依赖脚本,脚本出错时无障碍设置就失效了。宁可重复。 跨窗口同步用独立事件 reduce-transparency-changed 而不是塞进 theme-changed:浮窗是长期驻留窗口(关闭只 hide),读的是自己那份 configStore,不广播的话用户点完开关看不到任何变化,要等整体重载。 端到端实测(release 构建 + CDP 读计算值): 基线 bg rgba(255,255,255,0.1) blur(30px)… tertiary #6E6E73 光学层有 开关开 bg rgba(255,255,255,0.94) none tertiary #69696E 光学层无 开关关 回到基线 深色+开 bg rgba(28,28,30,0.92) none tertiary #C7C7CC 光学层无
设置页「行为」分区加一行开关,与其它布尔项走同一套草稿模型:改动计入 未保存状态,点保存才持久化。hint 写明系统级偏好始终优先,此项用于系统 偏好无法被读取的平台(macOS)。 浮窗的材质同步改为读两个来源取「或」:媒体查询命中,或配置开关打开。 两个都要,因为 macOS 上媒体查询恒为 false,只看它的话系统偏好读不到。 应用内开关变化不会触发媒体查询的 change 事件,所以额外订阅 configStore。 能力探测日志的措辞同步更新:不再说「减少透明效果不会生效」(现在开关 仍然可用),改为说明「系统偏好读不到,应用内开关仍可用」。
CI 在 aarch64-apple-darwin 上报 E0505:闭包是 move 的,直接捕获 `window` 会让 `window.run_on_main_thread(...)` 的借用与闭包内的 所有权转移冲突。Windows 上这段代码不参与编译,本地 clippy 看不见, 只有 macOS 目标能暴露。 改为先 clone 一份句柄给闭包。WebviewWindow 的 clone 只是引用计数递增, 代价可忽略。 验证:用复刻 tauri 签名的 mock 做了正负两组检查 —— 修复前的写法精确 复现 CI 的 E0505,修复后通过。
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
概述
浮窗无障碍与系统级显示偏好的响应。分三块:
NSWindow::setAppearance,不碰应用级为什么需要第 3 块的应用内开关
prefers-reduced-transparency在 WKWebView 上不支持:css/at-rules/media.json:safari.version_added: false,impl_url指向 WebKit bug 175497WKWebView 与 Safari 同引擎,所以 macOS 上该媒体查询恒为
false。这不只是 CSS 白写 ——setPopupTransparency在 macOS 上一次都不会被调用,原生材质永远不会被撤,整条链路静默空操作。因此新增
reduce_transparency配置项,两条触发源取「或」:@media (prefers-reduced-transparency: reduce)config.reduce_transparency→:root.reduce-transparency系统偏好命中时永远生效,开关只是额外多一条途径 —— 不是用应用设置覆盖系统设置。
关键实现
macOS 外观同步:
tauri::set_theme在 macOS 上是应用级([NSApplication setAppearance:]),会连带改掉设置页与历史窗口。改为取ns_window()后单窗口setAppearance。follow_system = true时必须setAppearance(nil)而非钉死当前明暗 —— 钉死会同时钉死 webview 的prefers-color-scheme,此后系统主题变化再也检测不到。跨窗口同步:浮窗是长期驻留窗口(关闭只
hide),读自己那份configStore。新增reduce-transparency-changed事件而非塞进theme-changed—— 两者语义不同(配色 vs 材质与衬底),合并会让任一方增加字段时误触另一方。对比度:reduce 撤掉原生材质后无 blur,衬底成为唯一视觉载体。原
containeralpha 0.72/0.74 时,标题栏与操作栏(不在阅读衬底覆盖内)的 secondary 只有 2.8–4.0:1。提到 0.94/0.92 并局部调整 tertiary 后,六类背景 × 三个文字层级最差 7.46:1(深)/ 4.78:1(浅)。已知取舍
CSS 配方写了两遍。 触发条件在 CSS 中无法合并成一个「或」——
@media与选择器没有合成语法,@container style()在目标 WebView 上支持面不够。用 JS 在媒体查询命中时补挂 class 能消掉一份,但那会让系统偏好的响应依赖脚本,脚本出错时无障碍设置就失效。宁可重复,两处都已标注「改配方时必须同步」。文字色覆盖收窄到
.popup-container。 若用:root,暗色 reduce 下 tertiary#C7C7CC(199) 比 secondary#C0C0C5(192) 更亮,会造成历史列表与设置页的层级反转 —— 而那两个窗口是不透明的,与浮窗的对比度问题没有因果关系。验证
npx tsc --noEmitnpm testcargo test --libcargo clippy -D warningscargo fmt --checknpm run build端到端实测(release 构建 + CDP 读计算值,走真实 IPC 而非直接改 DOM):
rgba(255,255,255,0.1)blur(30px)…#6E6E73rgba(255,255,255,0.94)none#69696Ergba(28,28,30,0.92)none#C7C7CC未验证
macOS 侧的原生材质撤除与 appearance 同步。 全部验证在 Windows 完成,macOS 只能确认编译与逻辑正确。按项目既有口径(DECISIONS D5),macOS 视觉结论只认真机截图,此处记未验证,不写成通过。