下载桌面应用时切换页面(例如从「环境总览」进入 Provider 或 Profile 页),下载进度条消失。返回原页面后也不会恢复,进度条不再出现,直到下载结束都没有任何可见反馈。
底层下载并没有中断 —— 安装在 Go 侧执行,与前端组件的生命周期无关。丢的只是 UI。
根因不是共享状态丢失
TaskCenterProvider 挂在 router 之上(frontend/src/App.tsx:84-88),所以它的 progress map 在路由切换时确实存活,onInstallOutput 订阅也不会重建。这部分设计是对的,注释里也写明了意图。
真正的问题是渲染进度条的条件用了组件本地 state:
// frontend/src/components/DesktopAppSection.tsx:154
{pending === "install" || pending === "installer" ? <DownloadProgress target={desktopApp.id} pending /> : null}
pending 是 useState(:28)。切页 → DesktopAppSection 卸载 → pending 归零。回到该页时组件重新挂载,pending 初始为 "",条件不成立,进度条不渲染 —— 尽管 TaskCenterContext 里那个 target 的 progress 条目一直都在。
DownloadProgress 本身有能力独立显示:它从 context 读 progress[target],只有在既无进度又非 pending 时才返回 null(DownloadProgress.tsx:23)。也就是说数据齐备,只是调用方用本地 flag 把它挡住了。
第二个后果:finally 永不执行
更麻烦的是 run() 的 finally(DesktopAppSection.tsx:59-62):
} finally {
setPending("");
if (downloading) resetProgress(desktopApp.id);
}
组件卸载后 promise 仍在跑,但 setPending / resetProgress 作用在已卸载的组件上。于是:
- 完成通知丢失 ——
setNotice("{name} 安装完成") 落在已卸载组件上,用户永远看不到「安装完成」。
- 失败信息丢失 —— 同理,
setFailure 的错误信息也不会显示,安装失败表现为「什么都没发生」。
resetProgress 不执行 —— 该 target 的进度条目留在 context 里。下次进入页面若恰好触发 pending,可能先闪一下上次残留的进度。
也就是说切页不只是「看不到进度」,而是整个操作的结果反馈都没了。
影响范围不止桌面应用
同一个 pending && 模式在运行时下载里也有:
| 位置 |
条件 |
DesktopAppSection.tsx:154 |
pending === "install" || pending === "installer" |
RuntimeSection.tsx:114 |
pending === runtime.id |
RuntimePrompt.tsx:86 |
pending |
RuntimeSection.tsx:53-67 的 install() 有完全相同的 finally 结构,所以运行时下载切页后同样静默。
唯一不受影响的是 ActivationPage.tsx:202,它无条件渲染:
{runtimeDownloads.map(({ id }) => <DownloadProgress key={id} target={id} pending />)}
这正好说明修法已经在仓库里存在了 —— 让渲染条件只依赖共享 state,不依赖本地 flag。
复现
- 打开环境总览,点桌面 Agent 的「安装」或「更新」
- 进度条出现后,切到 Provider 或 Profile 页
- 切回环境总览:进度条不见了,直到安装结束都没有任何提示,「安装完成」也不会出现
顺带
任务中心(左下角)在切页时保留日志,因为它读的是同一个 provider 的 log。所以下载期间唯一还能看到点东西的地方是任务中心的命令输出 —— 但下载进度是 kind === "progress" 事件,只进 progress map,不进 log,任务中心里看不到。用户切页后确实没有任何进度可见。
相关代码
frontend/src/components/DesktopAppSection.tsx:28、:59-62、:154
frontend/src/components/DownloadProgress.tsx:16-23
frontend/src/state/TaskCenterContext.tsx:59-99
frontend/src/components/RuntimeSection.tsx:53-67、:114
frontend/src/components/RuntimePrompt.tsx:86
frontend/src/pages/ActivationPage.tsx:202(未受影响的对照写法)
下载桌面应用时切换页面(例如从「环境总览」进入 Provider 或 Profile 页),下载进度条消失。返回原页面后也不会恢复,进度条不再出现,直到下载结束都没有任何可见反馈。
底层下载并没有中断 —— 安装在 Go 侧执行,与前端组件的生命周期无关。丢的只是 UI。
根因不是共享状态丢失
TaskCenterProvider挂在 router 之上(frontend/src/App.tsx:84-88),所以它的progressmap 在路由切换时确实存活,onInstallOutput订阅也不会重建。这部分设计是对的,注释里也写明了意图。真正的问题是渲染进度条的条件用了组件本地 state:
pending是useState(:28)。切页 →DesktopAppSection卸载 →pending归零。回到该页时组件重新挂载,pending初始为"",条件不成立,进度条不渲染 —— 尽管TaskCenterContext里那个 target 的progress条目一直都在。DownloadProgress本身有能力独立显示:它从 context 读progress[target],只有在既无进度又非 pending 时才返回 null(DownloadProgress.tsx:23)。也就是说数据齐备,只是调用方用本地 flag 把它挡住了。第二个后果:
finally永不执行更麻烦的是
run()的finally(DesktopAppSection.tsx:59-62):组件卸载后 promise 仍在跑,但
setPending/resetProgress作用在已卸载的组件上。于是:setNotice("{name} 安装完成")落在已卸载组件上,用户永远看不到「安装完成」。setFailure的错误信息也不会显示,安装失败表现为「什么都没发生」。resetProgress不执行 —— 该 target 的进度条目留在 context 里。下次进入页面若恰好触发 pending,可能先闪一下上次残留的进度。也就是说切页不只是「看不到进度」,而是整个操作的结果反馈都没了。
影响范围不止桌面应用
同一个
pending &&模式在运行时下载里也有:DesktopAppSection.tsx:154pending === "install" || pending === "installer"RuntimeSection.tsx:114pending === runtime.idRuntimePrompt.tsx:86pendingRuntimeSection.tsx:53-67的install()有完全相同的finally结构,所以运行时下载切页后同样静默。唯一不受影响的是
ActivationPage.tsx:202,它无条件渲染:这正好说明修法已经在仓库里存在了 —— 让渲染条件只依赖共享 state,不依赖本地 flag。
复现
顺带
任务中心(左下角)在切页时保留日志,因为它读的是同一个 provider 的
log。所以下载期间唯一还能看到点东西的地方是任务中心的命令输出 —— 但下载进度是kind === "progress"事件,只进progressmap,不进log,任务中心里看不到。用户切页后确实没有任何进度可见。相关代码
frontend/src/components/DesktopAppSection.tsx:28、:59-62、:154frontend/src/components/DownloadProgress.tsx:16-23frontend/src/state/TaskCenterContext.tsx:59-99frontend/src/components/RuntimeSection.tsx:53-67、:114frontend/src/components/RuntimePrompt.tsx:86frontend/src/pages/ActivationPage.tsx:202(未受影响的对照写法)