Files
spark-store/docs/superpowers/specs/2026-04-10-update-center-icon-fallback-design.md

230 lines
8.1 KiB
Markdown
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.
# 更新中心图标逐级回退设计
## 背景
当前更新中心的图标解析分成两段:
1. 主进程 `electron/main/backend/update-center/icons.ts` 会优先解析本地图标,解析不到时再直接返回线上图标 URL。
2. 渲染层 `src/components/update-center/UpdateCenterItem.vue` 只接收单个 `icon` 字段,图片加载失败后直接回退到默认占位图。
这个结构已经满足“本地优先”的静态选择,但不能满足新的行为要求:
1. 当本地图标成功加载时,不再请求线上图标和默认图标。
2. 当本地图标路径虽然存在、但实际加载失败时,继续尝试线上图标。
3. 当线上图标也失败时,最后才回退到默认占位图。
问题根因不是路径优先级判断错误,而是当前前后端只传递了一个最终 `icon` 值,导致前端无法在运行时根据真实加载结果继续尝试下一层来源。
## 目标
1. 更新中心图标加载顺序固定为:`localIcon -> remoteIcon -> placeholder`
2. 本地图标加载成功时,不再加载线上图标和默认图标。
3. 本地图标加载失败时,自动切换到线上图标。
4. 线上图标也失败时,才显示默认占位图。
5. 保持当前更新中心列表布局、图标尺寸和已有解析路径规则不变。
## 非目标
1. 不改动主商店、已安装列表或其他页面的图标逻辑。
2. 不增加新的网络探测请求,也不预检远程图标是否可访问。
3. 不重构现有本地图标解析算法,只调整数据结构和回退链路。
4. 不引入通用的图标来源数组或复杂图标对象。
## 方案选择
本次考虑过三种方案:
1. 后端透传 `localIcon``remoteIcon` 两个字段,前端顺序尝试。
2. 后端透传 `iconCandidates: string[]`,前端按数组顺序尝试。
3. 继续只传一个 `icon`,前端根据 `pkgname/category/arch` 自己重新拼线上图标地址。
最终选择方案 1。
原因:
1. 它刚好对应本次明确的三级回退需求,最小且直接。
2. 后端继续掌握图标来源规则,避免前端复制商店 URL 规则。
3. 相比数组方案,双字段更易读、更容易在 IPC 类型中维护。
4. 前端只负责“加载失败后切换到下一来源”,职责边界清晰。
## 设计概览
更新中心改为“主进程解析来源,渲染层控制加载顺序”的结构:
1. 主进程为每个更新项分别计算 `localIcon``remoteIcon`
2. 服务层和前端类型透传这两个字段。
3. `UpdateCenterItem.vue``localIcon -> remoteIcon -> placeholder` 的顺序逐级尝试。
4. 候选图标一旦成功加载,组件不再切换到后续来源。
## 数据结构变更
### 主进程类型
修改:`electron/main/backend/update-center/types.ts`
将:
```ts
icon?: string;
```
改为:
```ts
localIcon?: string;
remoteIcon?: string;
```
### Service Snapshot
修改:`electron/main/backend/update-center/service.ts`
更新 renderer-facing item/task 类型,并在 `toState()` 中透传:
```ts
localIcon?: string;
remoteIcon?: string;
```
### 渲染层类型
修改:`src/global/typedefinition.ts`
更新 `UpdateCenterItem``UpdateCenterTaskState`
```ts
localIcon?: string;
remoteIcon?: string;
```
## 模块边界
### `electron/main/backend/update-center/icons.ts`
保留现有职责,但返回内容从“单个最终图标”调整为“两种候选来源”:
1. `resolveDesktopIcon(pkgname)`:解析传统 deb / aptss 更新项的本地图标。
2. `resolveApmIcon(pkgname)`:解析 APM 更新项的本地图标。
3. `buildRemoteFallbackIconUrl(item)`:拼接远程商店图标地址。
4. `resolveUpdateItemIcons(item)`:组合出 `{ localIcon?, remoteIcon? }`
这里不再提前做“本地失败就直接放弃线上”的最终决策,而是把两个候选来源都准备好交给前端。
### `electron/main/backend/update-center/index.ts`
在更新项 enrichment 阶段,将:
1. 现有的单 `icon` 注入逻辑。
调整为:
1. 读取 `resolveUpdateItemIcons(item)` 的结果。
2. 仅在字段存在时把 `localIcon` / `remoteIcon` 写回更新项。
### `src/components/update-center/UpdateCenterItem.vue`
组件不再把单个 `item.icon` 当成最终地址,而是:
1.`item.localIcon``item.remoteIcon` 派生候选列表。
2. 使用当前索引决定 `img.src`
3. 失败时切到下一候选项。
4. 候选项耗尽后切到占位图。
## 详细数据流
### 主进程加载更新项
1. 更新中心主进程加载更新项。
2. 现有逻辑继续补齐 `category``arch` 等字段。
3. 图标模块为每个项分别解析:
- `localIcon`:本地图标路径。
- `remoteIcon`:线上图标 URL。
4. enrichment 后的更新项通过 service snapshot 发送到渲染层。
### 渲染层展示更新项
1. 组件收到 `item.localIcon` / `item.remoteIcon`
2. 组件构造一个有序候选列表:
- 本地路径转换为 `file://` URL。
- 远程 URL 原样使用。
3. 初始渲染第 1 个候选图标。
4. 如果 `img` 加载成功,流程结束,不再切换到下一项。
5. 如果 `img` 触发 `error`,索引递增,继续尝试下一候选图标。
6. 如果所有候选都失败,切换到占位图。
## 前端行为细节
### 候选列表生成规则
候选列表只包含存在且非空的来源:
1. `localIcon` 存在时放在第 1 位。
2. `remoteIcon` 存在时放在第 2 位。
3. 占位图不放入候选列表,而是在候选耗尽后单独回退。
这样可以避免:
1. 本地图标成功时还额外发起线上请求。
2. 图标字段为空时出现无意义的重试。
### 状态重置规则
`props.item` 变为新的更新项对象时:
1. 重置当前候选索引到第 1 项。
2. 清空“候选已耗尽”的状态。
3. 重新开始本地优先的尝试流程。
这样可确保列表复用或重新渲染时,新条目不会继承上一条目的失败状态。
### 占位图规则
保留当前组件内默认占位 SVG不改样式和尺寸。
只有在以下情况下才使用占位图:
1. `localIcon``remoteIcon` 都不存在。
2. `localIcon` 加载失败且 `remoteIcon` 不存在。
3. `localIcon``remoteIcon` 都加载失败。
## 错误处理
1. 本地图标路径不存在或不可读:允许浏览器触发加载失败,再由前端切到线上图标。
2. 远程图标返回 404、超时或其他加载错误前端切到占位图不向用户弹额外错误。
3. 后端无法推断 `category``arch`:允许 `remoteIcon` 为空,前端只尝试本地图标和占位图。
4. 任一图标来源失败都不能影响更新列表正文、状态标签和进度条显示。
## 测试方案
### 后端测试
扩展 `src/__tests__/unit/update-center/icons.test.ts`
1. 本地图标可解析时,`resolveUpdateItemIcons()` 返回 `localIcon`,并在条件满足时同时包含 `remoteIcon`
2. 本地图标缺失时,仍可返回 `remoteIcon`
3. 缺少 `category``arch` 时,不返回 `remoteIcon`
4. 两者都不可得时,返回空对象。
### 组件测试
扩展 `src/__tests__/unit/update-center/UpdateCenterItem.test.ts`
1.`localIcon` 时先渲染本地 `file://` 地址。
2. 本地图标未失败前,不切换到 `remoteIcon`
3. 本地图标触发 `error` 后切到 `remoteIcon`
4. 本地和线上都触发 `error` 后切到默认占位图。
5. 切换到新的 `item` 后,回退状态会重置。
## 风险与约束
1. 如果某些包的本地图标路径在后端看来存在,但渲染进程实际不可访问,仍会触发一次失败请求;这是预期行为,因为它正是继续尝试线上图标的触发条件。
2. 远程图标 URL 继续依赖当前商店路径规则,若个别包没有线上图标,最终仍会使用占位图。
3. 本次只调整更新中心图标链路,不同步抽象其他页面,避免扩大改动范围。
## 决策总结
1.`localIcon``remoteIcon` 替代单个 `icon` 字段。
2. 主进程负责解析来源,渲染层负责按顺序加载和失败回退。
3. 固定回退顺序为:本地图标 -> 线上图标 -> 默认占位图。
4. 本地图标成功时,不再加载线上图标和默认图标。