🐛 更新页与安装页补齐骨架屏与异步中间态,失败不再被渲染成成功 - #1721
Open
CodFrm wants to merge 3 commits into
Open
Conversation
页面此前无从判断服务端到底做了什么:openUpdatePageByUUID 在命中静默更新时 不开安装页却同样返回 true,用户点完脚本名只看到转一圈、什么都没发生; IGNORE 分支根本没有返回值,页面只能 fire-and-forget。 - openUpdatePageByUUID / openUpdatePage 返回 "opened" | "silent" | "failed" - IGNORE 逐条回报结果。忽略写的是脚本自身的 ignoreVersion,与检查缓存无关, 因此缓存随 Service Worker 回收后忽略照样生效,这里如实回报而不是谎报失效 - checkScriptUpdate 的结果收敛成 TCheckScriptUpdateResult 并用 reason 区分 「已有检查在跑」与真正的失败,页面才能分别提示
从批量更新页点脚本名进来的必然是「更新」,加载屏却把上下文 chip 写死成 「脚本安装」,几百毫秒后再闪成「脚本更新」;描述写着「正在从来源下载」, 但这条入口的代码 Service Worker 早已备好,根本不下载。 - 状态屏按来路分档,未确知场景不渲染 chip(不猜),并补一条与就绪态操作栏 等高的底部占位,避免就绪瞬间内容区高度再跳一次 - 暂存代码被定时清理回收时落到专属终态,出口换成「重新检查更新」—— 原来的「重试」在这个最常见的失败原因下重试多少次都是同一结果 - Monaco 实例就绪前渲染代码骨架,替代此前 340px 的纯空白 - toggleWatch / rejectExternalAccess 补忙态,install 加重入守卫: 这两个动作全程不置忙态,连点会发出两次安装/两次决定
取数失败时记录仍是空的,页面直接走到空态,把一次加载失败渲染成 「所有脚本均为最新(已检查 0 个脚本)」这条与事实相反的成功终态; 点「检查更新」到服务端广播回来之间页面完全静止,期间可以连点。 - 取数失败落错误终态:等宽 detail 框 + 重试 / 脚本列表出口 - 主动检查由本地 pending 立刻接管忙态,并把服务端的「正忙」「结果够新已跳过」 「通道异常」三条回执分别说出来;跳过时就地清掉待反馈标记, 否则会在下一次后台检查完成时冒出一条用户没点过的 toast - 忽略复用与更新相同的行级阶段(working → success → 退场),不再 fire-and-forget - 批量进行中互斥(行内勾选、两个批量按钮、全部恢复),避免两条进度互相覆盖; 被「结果失效」中断时保留已完成条数,不把汇总抹掉 - 骨架补齐工具条(桌面)与顶部选择栏/底部操作栏(移动)占位,消除数据到达时的 布局跳动,并加 role="status" / aria-busy;空态下重新检查不再整页闪回骨架 - 脚本名改用 aria-disabled + onClick 早退:disabled 会让浏览器不派发指针事件, 正好在名字被截断、最需要看全名时把 tooltip 一起关掉,键盘触发后焦点还会掉到 body
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.
Checklist / 检查清单
背景
更新页与安装页这条链路上有多处「异步动作没有中间态」和「失败被渲染成成功」的问题:
loadRecord没有 catch,失败时记录仍是空数组,页面走到空态渲染「所有脚本均为最新(已检查 0 个脚本)」,唯一出口「重新检查」还会同样失败。checking只跟随 Service Worker 广播,往返回来之前按钮不禁用、可连点;服务端回的「正忙」与「结果够新已跳过」被直接丢弃,后者还会让userCheckPendingRef残留,在下一次后台检查完成时冒出一条用户没点过的 toast。loading_desc写「正在从来源下载」,但 uuid 入口的代码 Service Worker 早已写进 OPFS/TempStorage,根本不下载。这条入口最常见的失败是暂存条目被 30 分钟定时清理回收,而给出的出口是「重试」——重试多少次都是同一结果。toggleWatch/rejectExternalAccess全程不置忙态,InstallActions的busy判据因此始终为假,连点会发两次安装 / 两次决定。本次改动
三个提交按面拆分:
Service Worker(
4b0afe6c)openUpdatePageByUUID/openUpdatePage由boolean改为"opened" | "silent" | "failed"。命中静默更新时不开安装页却同样返回 true,页面无从区分,用户点完脚本名只看到转一圈、什么都没发生。checkScriptUpdate的结果收敛成TCheckScriptUpdateResult,并用reason: "busy"区分「已有检查在跑」与真正的失败。安装页(
a87c144b)toggleWatch/rejectExternalAccess补忙态,install加submittingRef重入守卫(UI 的 disabled 只作第一道防线——下拉菜单项在 phase 翻转前已展开时仍能被选中)。批量更新页(
dc399245)BatchProgress.interrupted),「结果已失效」提示条加role="alert"与内联「重新检查」。role="status"+aria-busy;空态下重新检查保留空态 + 顶部进度条,不再整页闪回骨架。aria-disabled+ onClick 早退,spinner 位改为常驻等宽空槽。实现考虑
为什么忽略不走「结果已失效」。 初稿设计里,缓存随 Service Worker 回收后点忽略应提示「结果已失效」。回源核实后否掉了:
scriptDAO.update写的是脚本自身的ignoreVersion(src/app/repo/repo.ts:314),与scriptUpdateCheck.cacheFull无关——忽略一直是生效的,缺的只是回执和刷新广播。因此改成逐条回执:成功即由页面乐观收起该行,失败才停在失败态并保留重试;只有 UPDATE 才需要record_expired,因为它真的依赖缓存里的newCode。为什么用
aria-disabled而不是disabled。 浏览器不向disabled表单控件派发指针事件,Radix Tooltip 靠onPointerMove/onPointerLeave开关,转圈期间「完整脚本名」tooltip 会失效——而这正是名字被截断、用户最想看全名的时刻;键盘用户按 Enter 后焦点还会掉到<body>。防连点本来就由同步 ref 守卫承担,不依赖disabled。为什么错误捕获放在
loadRecord外面。 在 async 函数里catch到的setState无法被证明发生在await之后(被调用方同步抛出时它就是同步 setState),react-hooks/set-state-in-effect会因此报错。改成在调用侧.catch()收口,promise 回调必然是异步的。骨架的判据。
loading || (checking && empty && checktime === 0):首屏必然要骨架;此外只有「从未检查过」才用骨架,已经给出过空态之后再点检查,保留空态 + 顶部进度条即可,不要整页闪回骨架再闪回来。已知限制
fix/batchupdate-remove-autoclose的改动主题重叠,留给那条分支处理。keepAlive定时器从不清理、远程图标无onError回退。建议审查重点
runIgnores与runUpdates共用rowStatesRef/scheduleRowExit,两者交叉时行状态是否仍然自洽。checking: checking || pendingCheck的合成忙态:pendingCheck在服务端响应回来时解除,此时「检查完成」广播可能尚未到达。openUpdatePageByUUID的三态返回是否覆盖了所有调用方(requestCheckUpdate忽略返回值)。关联
叠在 #1719 之上(base 选
fix/batchupdate-open-detail而非main,否则会把 #1719 的提交一并算进本 PR 的 diff)。#1719 合入后本 PR 的 base 需改回main。验证
均在独占状态下执行(该套件对默认 850ms testTimeout 敏感,与其他任务并发跑会大量误超时):
npx vitest run→Test Files 364 passed (364)/Tests 4556 passed (4556),退出码 0。基线
c73a876f同样方式跑 →364 passed/4516 passed,即本 PR 净增 40 个用例、零回归。npx eslint src/pages/batchupdate src/pages/install src/app/service/service_worker tests/mocks/CodeEditor.tsx→ 0 error。npx tsc --noEmit -p tsconfig.json→ 0 error。node scripts/check-i18n.mjs→ 通过(新增 19 个 key 均已补齐 10 个语言包)。未做浏览器实机验证;行为改动均由上述单元测试覆盖(新增用例含:取数失败落错误终态、busy/跳过/通道异常三条回执、忽略的行级阶段与逐条回执、批量互斥与中断保留计数、骨架占位与
aria-busy、aria-disabled不再是disabled、静默更新回报silent、安装页加载分档与过期终态、代码骨架、提交忙态与防重入)。