Repository navigation
🐛 更新页与安装页补齐骨架屏与异步中间态,失败不再被渲染成成功 - #1721
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
|
沒空去理會這些UI問題 最初的batch update設計是 它不是一個普通的 要求batch update -> 檢查所有腳本有沒有update -> 彈出結果 而是先按時機在背景帶delay慢慢檢查一下所有腳本有沒有update -> 有update就保存一下 -> 因此如果有近期的檢查,batch update不會重新檢查所有腳本,而是最近的結果 這樣的設計是平衡了更新頻率,同時檢查的網絡請求密度,用戶實際體驗 @CodFrm你用agent改設計時請先考慮原本的設計理念 我看到很多關於 batchupdate的agent pr 老實說能動就別搞了 |
原来如此,但我仍然坚持任何更新都要先人工审核一遍,防止出现任何预期外的意外状况。 但用户要不要更新,也不应该由程序来定义用户什么时候完成判断,这是对用户极大的不尊重。
@cyfung1031 我这里没有使用繁体及其文法习惯来写(主要我也不知道你是哪里的……),希望对你没有影响。 |
主要是手机打字没做简体字转换。我看得懂但打不到 |
|
主要是我担心 Mishasama 所指出的 检查结果 如何显示 等UI/UX问题都可以改动 但 CodFrm的ScriptCat设计是不想搞太多用户设定 |
|
@cyfung1031 这个pr只是调整一些用户体验的UI/UX,没有动任何的业务逻辑 autoclose 在另外那个pr中,可以在那里讨论 |
* 🐛 批量更新页打开更新详情复用检查缓存并挡住重复点击 点击脚本名查看更新时,openUpdatePageByUUID 会重新 fetch 一次脚本代码, 而这份新版代码在检查更新阶段已经存进 scriptUpdateCheck 的记录缓存里 (行内「更新」按钮装的就是它)。用户因此要为每次点击白等一次网络往返, 期间页面又没有任何反馈,连点几下就会开出多个安装页。 - SW: 拆出 prepareUpdateOrInstallPage,openUpdatePage 命中缓存代码时 跳过 fetchScriptBody;openUpdatePageByUUID 改为回报 boolean - 页面: 打开期间行内转圈并同步挡住重复点击,失败弹 toast, 点击脚本名同样取消自动关闭倒计时 * 🐛 更新页与安装页补齐骨架屏与异步中间态,失败不再被渲染成成功 (#1721) * 🐛 打开更新详情区分静默更新,忽略动作补齐逐条回执 页面此前无从判断服务端到底做了什么: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
* 🐛 restore ScriptCat registration health checks (#1724) * 🐛 修复 @run-at context-menu:设置覆写不生效、菜单注册不上、脚本体自己的菜单被屏蔽 (#1718) * 🐛 修复设置面板覆写运行时机在重新注册后失效 restoreJSCodeFromCompiledResource 用脚本自带 metadata 选择编译分支, 而设置面板改运行时机/early-start 只写 selfMetadata,导致全量重新注册 (扩展更新、切换启用脚本、改黑名单等)后覆写被丢弃:context-menu 脚本 恢复自动执行且不注册菜单项,early-start 退化为普通注入。 pushValueUpdate 判断 early-start 时同样只看自带 metadata,覆写而来的 early-start 脚本在 GM 值变更后不会重新编译,预注入代码里的值会过期。 close #1649 * 🐛 修复 GM API 权限校验忽略用户覆写的运行时机 GMApi.parseRequest 直接把 scriptDAO 里的原始 Script 放进 GMApiRequest, metadata 没有合并 selfMetadata。PermissionVerify 对 context-menu 脚本的 GM_registerMenuCommand 免 @grant 豁免因此判不出来,浏览器里表现为 verify error {"api":"GM_registerMenuCommand","error":"permission not requested"}, 菜单项注册不上 —— 即 #1649 里「上下文菜单中没有出现执行选项」。 真实浏览器验证记录见 e2e/scratch/run-at-override/report.md(未入库)。 * 🐛 context-menu 包装不再屏蔽脚本体自己的 GM_registerMenuCommand @run-at context-menu 的包装把脚本体塞进菜单回调时,回调开头把 GM_registerMenuCommand 连同 window./GM. 上的引用一起置为 undefined。 于是任何在脚本体里注册菜单的脚本,点菜单执行就会 TypeError: GM_registerMenuCommand is not a function 当场中断, 它自己的菜单项也永远注册不上——用户看到的是「GM_registerMenu 的菜单显示不出来」。 该置空还会污染页面 window 与该脚本的 GM 物件,且是持久的。 去掉这行,脚本体里的菜单注册照常工作。代价是脚本体每次被点执行都会重新注册 一次,内部条目累积(显示层按 groupKey 去重,不会出现重复菜单项,但同名项的 回调会被触发多次)。 真实浏览器验证记录见 e2e/scratch/ctx-menu-{display,fix}/(未入库)。 * ✨ 统一 example/tests 测试结果与人工验证反馈 (#1717) * ✨ 统一 example/tests 测试结果与人工验证反馈 * 🔒 固定 sctest CDN 引用到框架提交 * 🎨 统一 userscript 诊断面板与测试描述 * 🔒 固定 sctest CDN 引用到最新框架提交 * Update sctest.js * 🔒 固定 sctest CDN 引用到最新框架提交 * ✨ 增强统一测试诊断与面板反馈 * 🔒 固定 sctest CDN 引用到最终框架提交 * ✨ 优化 sctest 诊断面板与 frame 反馈 * 🔒 固定 sctest CDN 依赖版本 * ♿ 优化 sctest 面板可访问性反馈 * 🔒 重新固定 sctest CDN 引用 * 🎨 优化 sctest 面板指标与布局 * 🔒 固定指标优化后的 sctest CDN 引用 * 🐛 固定 sctest 耗时显示宽度 * ✅ 加固 UI 异步断言与 E2E 保存结果观察 (#1727) * test: stabilize heavy network rules UI cases * test: isolate UI files and await observable state * ✅ 保留 UI 测试原有隔离配置 * ✅ harden async test observations * 🐛 align E2E save expectations with failure cases * 🔍 tighten test guard binding and toast observation * 📄 补充 Agent 自主操作边界、指令冲突裁决与人类可读写作规范 (#1728) 现有 agent 文档规定了改动的质量门槛,但缺三层:agent 在两次人工决策之间可以自行做什么、 指令冲突如何裁决、以及写给人看的东西该怎么写。静态审查的依据: 整套文档没有一处把 agent 写成决策者(`decide` 的主语全是分类表或原则),因而产出「建议生成器」; `material` 作为门槛术语被引用九次却从未定义,且在 pull-request.md 内有两种含义; 测试失败例外在 AGENTS.md 概括成两条而 owner 定义了六种;路由表是一次性分类; 人工指令能覆盖什么没有成文;以及全套文档没有任何一条关于行文的规范, 而 pull-request.md 提供的九级标题骨架会被当成表格来填。 本次补齐:范围内自行决策、交还决定须指名归属与阻塞点、不写可查证却不查的保留意见、 指令冲突裁决与人工指令覆盖边界、连续路由、`material` 定义、测试失败例外改交 owner 裁决、 自主操作边界、不稳定结果报告口径、面向人类读者的写作原则、文档集自身的指令预算。 PR 模板补一条不渲染注释;pull-request.md 明确其结构是待考虑项而非待填表格。 Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * 🐛 批量更新页打开更新详情复用检查缓存并挡住重复点击 (#1719) * 🐛 批量更新页打开更新详情复用检查缓存并挡住重复点击 点击脚本名查看更新时,openUpdatePageByUUID 会重新 fetch 一次脚本代码, 而这份新版代码在检查更新阶段已经存进 scriptUpdateCheck 的记录缓存里 (行内「更新」按钮装的就是它)。用户因此要为每次点击白等一次网络往返, 期间页面又没有任何反馈,连点几下就会开出多个安装页。 - SW: 拆出 prepareUpdateOrInstallPage,openUpdatePage 命中缓存代码时 跳过 fetchScriptBody;openUpdatePageByUUID 改为回报 boolean - 页面: 打开期间行内转圈并同步挡住重复点击,失败弹 toast, 点击脚本名同样取消自动关闭倒计时 * 🐛 更新页与安装页补齐骨架屏与异步中间态,失败不再被渲染成成功 (#1721) * 🐛 打开更新详情区分静默更新,忽略动作补齐逐条回执 页面此前无从判断服务端到底做了什么: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 --------- Co-authored-by: wangyizhi <yz@ggnb.top> Co-authored-by: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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、安装页加载分档与过期终态、代码骨架、提交忙态与防重入)。