状态栏中的更新 — 设计规格¶
日期: 2026-06-02
状态: 已批准(头脑风暴)→ 可进入实施计划
分支: feat/updates-status-bar
问题 / 目标¶
自动更新界面目前是一个固定在右上角的浮动胶囊(UpdateBadge)。用户希望更新出现在底部状态栏(LogsFooter)中:一个常驻的版本指示器,外加一个“更新”标签页/面板,用于展示更新日志、stable/preview 渠道切换器以及更新/发布历史——这样更新就有了一个永久且可发现的位置,而不是一个转瞬即逝的浮动胶囊。
决策(来自头脑风暴)¶
- 位置: 在始终可见的 28px
LogsFooter栏右侧放置一个常驻版本芯片,并提供一个“更新”标签页,可将底部栏扩展为更新面板。点击芯片即可打开面板。 - 空闲行为: 芯片始终显示——处于最新状态时显示
v<current> ✓,并会在可更新 / 下载中 / 就绪 / 错误等状态之间切换。 - 数据源: GitHub Releases(实时),通过 Rust 命令获取。更新日志和“历史记录”统一为一个 Releases 列表(发布历史中正在运行的版本会标记为“current”)。这不是个人安装日志。
- 渠道切换器: 面板会提供一个可编辑的 stable/preview 切换器,与现有的“设置 → 关于”切换器共存;两者读写相同的 Rust
get_update_channel/set_update_channel,并通过 store 中保存的单个updateChannel值保持同步。
本设计所基于的现有代码¶
frontend/src/components/LogsFooter.jsx/.css— 底部栏。固定定位,z-index: 40,折叠时 28px,可展开 180–720px。标签页来自SOURCES数组,渲染为.logs-footer__pill按钮;当前标签页持久化到 localStorageomnivoice.logs.active。主体根据当前标签页渲染;Notifications 标签页是非日志自定义标签页主体的先例。设置 CSS 变量--logs-footer-height。frontend/src/components/UpdateBadge.jsx/.css— 当前的浮动胶囊,挂在App.jsx(<UpdateBadge/>)。渲染 available / downloading / ready / error 状态(idle + checking → null)。当前main分支在错误状态上增加了关闭(X)按钮、dismissUpdatestore action 和update.dismiss键——这些必须保留。frontend/src/store/updaterSlice.ts—updateStatus | updateVersion | updateNotes | updateProgress | updateError;settersetUpdateChecking/Available/Idle/Progress/Ready/Error+dismissUpdate。不持久化。 没有历史记录字段。frontend/src/utils/updater.js—isTauri()、currentChannel()(invoke('get_update_channel'))、checkForUpdate(store)(invoke('check_update',{channel}))、installUpdate(store)(invoke('install_update',{channel}),监听update://progress、relaunch())。frontend/src/utils/updateChannel.js—UPDATE_CHANNELS = ['stable','preview']、normalizeChannel()。frontend/src/pages/Settings.jsx(About 标签页)— 现有的渠道Segmented(changeChannel→set_update_channel)、端点显示、检查更新。- Rust(
src-tauri/src)— 命令get_update_channel、set_update_channel、check_update、install_update。新增:list_releases。 CHANGELOG.md存在于仓库根目录,但未在界面中展示(本设计不使用它;发布说明来自 GitHub)。
架构¶
A. 状态栏芯片 — UpdateStatusChip¶
新组件渲染在 LogsFooter 顶部栏的右侧(靠近 Discord/捐赠按钮组)。订阅 updaterSlice。状态 → 呈现:
updateStatus |
芯片显示 |
|---|---|
idle(已是最新) |
v<current> ✓(弱化/变暗) |
available |
⬆ <version> · Update(点击行在面板中安装) |
downloading |
↺ Updating <pct>% |
ready |
↺ Restart |
error |
⚠ Failed · Retry(+ 保留 dismiss) |
checking |
短暂的 … Checking(或保持之前的芯片) |
- 点击 → 打开底部栏的 Updates 标签页(
openTo('updates'))。 - 当前版本来源: 挂载时通过
@tauri-apps/api/app的getVersion()获取,并存储在updaterSlice.appVersion中(回退方案:在非 Tauri/开发环境中版本未知时隐藏芯片)。芯片仅作指示器;主要操作在面板中(可通过打开面板从芯片进入安装;重试/重启也可以内联操作,以保留当前的一键行为——详见下方已解决的开放问题)。
B. 更新面板 — UpdatesPanel¶
当 active === 'updates' 时,渲染为底部栏主体。各区块自上而下为:
- 实时行 —— 与芯片状态对应,并带有可操作控件:
- available →
Update available · <v>+ 安装(当dubStep === 'generating'时禁用,并显示与现在相同的 toast)。 - downloading → 进度条 + 百分比;ready → 重启;error → 消息 + 重试 + 关闭;idle →
Up to date · v<current>+ 立即检查。 - 渠道 ——
Segmentedstable/preview 控件绑定到 store 中的updateChannel;onChange →set_update_channel+ 重新获取 releases。与 Settings 保持同步。 - 发布列表 —— 可滚动;每一行包含:版本、日期、
prerelease标签、可展开的说明。正在运行的版本会标记为current。已处理加载中与空/离线状态(见数据部分)。
C. 数据 — list_releases Rust 命令 + releasesSlice¶
- Rust
list_releases(channel) -> Vec<ReleaseInfo>:通过现有的reqwest发起 GEThttps://api.github.com/repos/{owner}/{repo}/releases(无需认证;设置User-Agent)。映射为{ version, name, date, prerelease, notes }。Stable 渠道会过滤掉prerelease;preview 会包含它们。按最新优先排序。使用短期内存缓存(例如 5 分钟),避免频繁重复请求。 - 前端 临时
releasesSlice(不持久化):releases、releasesStatus: 'idle'|'loading'|'loaded'|'error'、loadReleases(channel)(调用该命令;失败时设置 error)。在面板首次打开以及渠道切换时懒加载。 - 离线 / 失败: 面板显示“无法加载发布列表(离线?)”+ 重试按钮。实时更新流程(
check_update/install_update)以及应用的其余部分不受影响。非 Tauri/开发环境 → releases 不可用,面板显示同样的空状态;实时更新与现在一样不执行任何操作。
D. 移除 / 移动¶
- 从
App.jsx中移除浮动的<UpdateBadge/>。 UpdateBadge的状态逻辑移入UpdateStatusChip(包括 dismiss(忽略)逻辑 + dub-busy(配音忙碌)门控)。UpdateBadge.jsx/.css退役(或重命名为 chip)。现有update.*i18n 键被复用。
状态 / 存储¶
updaterSlice:新增appVersion: string | null+setAppVersion。其余保持不变(仍然不持久化)。- 新增
updateChannelSlice(或并入updaterSlice):updateChannel: 'stable'|'preview'、setUpdateChannel(ch)(通过 Rust 写入)。设置与面板均绑定到此处 → 单一事实来源,自动同步。 - 新增
releasesSlice(瞬态):releases + status +loadReleases。
i18n(硬性规则)¶
新的 updates.* 键添加到全部 21 个语言文件(强制对等;CJK 守卫保持绿色——不能有硬编码的面向用户字符串)。尽量复用 update.* 和 about.channel_*。新键(指示性):updates.tab、updates.up_to_date、updates.check_now、updates.current、updates.releases、updates.prerelease、updates.load_error、updates.retry_load、updates.installed_version。
跨平台一致性(严格规则)¶
chip + 面板均为纯前端;list_releases 是平台无关的 Rust。该功能默认开启,在 macOS / Windows / Linux 上表现一致。没有按操作系统门控的默认行为。Updater 本身仍仅限 Tauri(在 dev/web 中为 no-op)——chip 在这些环境中降级为隐藏/仅显示版本,且在各平台上表现一致。
测试¶
- vitest:覆盖全部六种状态的 chip 状态→呈现映射;使用 mock releases 数组渲染面板(当前版本标记、按通道过滤 prerelease);通道切换调用
set_update_channel并触发重新加载;releases 加载错误 → 离线空状态;安装时的 dub-busy 门控。 - 保持现有
updater.test.js(#216 守卫)+updaterSlice.test.ts通过。 - i18n 对等测试 + CJK 守卫必须通过。
范围之外(YAGNI)¶
- 个人安装历史记录(“你在日期 Y 安装了 X”)。
- 针对单个 release 的手动下载 / 回滚 / 降级。
- 在现有纯文本/
pre-wrap处理之外,对更新说明做富 Markdown 渲染。 - 自动刷新/轮询 releases 列表(仅在打开时和通道变更时获取)。
开放问题——已解决¶
- chip 与面板在一键操作上的分工: chip 显示状态;Restart/Retry 仍可从 chip 一键操作(保留现有行为);Install 会打开面板(这是会产生实际影响的操作,且先显示更新说明更合适)。
- Changelog 与历史记录: 统一并入 Releases 列表(决定以 GitHub 作为来源)。
- 版本未知(dev/web)时的版本 chip: 隐藏。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 om/VoiceStudio