Uh oh!
There was an error while loading. Please reload this page.
fix(console): System Hub 计数失败回落 null 而非冒充 0,单卡隔离 (#3679) - #3686
Merged
Conversation
…ookup fails (#3679) Each count on the System Hub caught its own failure with an empty page, so a 500 / 401 / 403 / offline rendered the same confident `0` as a table that really is empty. The most reachable case is a permission denial on ONE object: an admin who may open the hub but cannot read `sys_audit_log` was shown "0 entries". Each `.catch` now resolves to `null`, which flows into `counts` and hits the badge's existing `count !== null` branch — the card shows no number instead of a wrong one. The catch stays on each call rather than moving out around the `Promise.all`, so one object's failure blanks only its own card; measured, not assumed (see the PR's reverse verification). 404 is unchanged: the adapter resolves unregistered objects as an empty page by design, so those still count `0`. The outer `catch` is left alone — it is reachable only by a synchronous throw, where the counts are still at their initial `null` anyway. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
yinlianghui
marked this pull request as ready for review
August 7, 2026 21:15
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 8, 2026
Merged
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 freeto 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.
Fixes#3679
先复核前提(#3680 的三行矩阵,逐条对
origin/main@074ec53d6重测)issue 的三条论断全部成立,没有一条过期:
packages/data-objectstack/src/index.ts:1147的is404Error(err)命中后把资源记进missingResources并return { data: [], total: 0 }(find()开头:1117还有一条同名短路),给的是 resolve 不是 reject。这是契约,本 PR 一个字节没碰。.catch(() =({ data: [] }))实际覆盖的只有非 404:适配器对非 404 一律throw err(:1152)。catch { /* Keep nulls on failure */ }确实进不去:每个 promise 自带.catch,Promise.all不可能 reject。另外核实了 issue 没说、但决定「复活什么」的一条:
null的渲染分支是活的,不是死的。徽章写作{card.count !== null && (Badge…)},而 Applications / Metadata / Settings / Profile 等卡片本来就是count: null且不显示徽章 —— 也就是说「不显示徽章 = 不知道」这个形态页面现在就在用,本 PR 只是让错误态也走进去,没有发明任何新 UI。(若这条不成立,派发要求停手报告;它成立。)修法:逐 promise 回
null,而不是让外层接手派发把两条路留给实测取舍,实测结论:外层接手做不到逐卡,必须逐 promise。
外层 catch 是全军覆没式的 —— 一个 reject 就让五张卡全部回
null。而 issue 点名的最易复现场景恰恰是单个对象上的权限拒绝:一个能进 System Hub、但对sys_audit_log没有读权限的管理员。让 Audit Log 那张卡失去数字是对的;顺带把 Users / Organizations / Positions 三张已经答复成功的卡的真实数字一起抹掉,是把一个局部故障放大成全局故障。所以.catch留在每个调用上,只是把返回值从「空页」换成null。这不是推理,是量出来的 —— 见下方逆向验证的扰动 B。
0是一个主张(后端答复了,答案是「没有」);null是唯一能说「不知道」的形态。修前后行为矩阵
.catch未触发00(不变 —— 契约,不是错误).catch未触发00(不变).catch→null0,邻卡正常.catch→null0第一、二行是 #3655 / #3670-permissions 的账,⛔ 未碰;#3680 落的 Permissions 恒 0 的 MEASUREMENT 钉子原样保留。
外层那个 catch:照实说,没动它,也没为它写测试
派发允许「删逐 promise catch 让外层接手」,上面已说明为什么不走。剩下的问题是外层 catch 本身要不要一并改成显式
setCounts(全 null)。没改,理由是能被诚实钉住的只有它现在的样子:dataSource.find同步抛出(.catch挂在返回的 promise 上,rejection 到不了外层)。null—— 所以「所有卡都没有数字」这条断言无论外层 catch 做什么都会绿,包括什么都不做。那是「因为什么都没产生所以绿」,不是「因为逻辑对所以绿」。fetchCounts以dataSource记忆化,AdapterProvider的 adapter 每次挂载只产生一次身份变化(useState(externalAdapter ?? null)→ effect 里setAdapter,依赖[externalAdapter]),所以今天这条路根本走不到,想钉住它得改掉测试里「adapter 是稳定单例」的脚手架。所以修的是结果被写下的那一处(
setCounts),测试也钉在那里;外层 catch 只把注释订正为它真实的可达条件。测试文件里留了一段说明,免得下一个读者以为这里漏了一条用例。逆向验证(两个扰动,方向都先预测后运行)
扰动 A —— 把逐 promise 的
.catch退回({ data: [] })(即复现缺陷)预测:4 红 | 9 绿(本文件 4 红 5 绿 + metadataCards 4 绿)。红的应是新增那 4 条错误态用例,方向是又见确定的 0;而这 4 条用例内部的「邻卡仍显示真实数字」「真实空集仍是 0」那些断言在两个方向下都绿 —— 它们钉的是不该动的东西,不是这次改动的证据,这点照实预测,不假装它们会跟着红。
实测:
Tests 4 failed | 5 passed (9),逐条吻合。 失败现场直接把缺陷打印出来:扰动 B —— 删掉逐 promise 的
.catch,让外层接手(派发点名的另一条修法)这一扰动才是「逐 promise vs 外层」的实测取舍,方向和 A 不同,所以单独预测:
预测:3 红 | 6 绿,且红法不是断言 diff 而是等待锚超时 —— 前三条用例都以一张「答复成功」的邻卡徽章作为 settle 锚(
2 organizations/3 users),外层接手后这些邻卡连同失败的那张一起没了数字,锚永远等不到。第 4 条(五个全失败)两种形态下结果相同,应保持绿。实测:
Tests 3 failed | 6 passed (9),逐条吻合:「答复成功的邻卡失去了它的数字」正是选择逐 promise 的理由,现在是量出来的。
测试
SystemHubPage.counts.test.tsx新增 4 条(#3680 落的 6 条中,5 条原样未动):a 500 on one object blanks that card instead of collapsing it into 0blanks only the card that failed and leaves its neighbours their real countssys_audit_log403 → 该卡无徽章;其余四张仍是3 users/2 organizations/4 positions/0 permissions(单卡隔离)keeps 0 for the two things that really are zero, and blanks only the failure0 entries、未注册对象0 permissions、403 无徽章shows no counts at all when every lookup fails, and still renders the hub关于那条被重写的 MEASUREMENT:派发写的是测试「只加不改」,但 #3680 落的 6 条里有一条 ——
MEASUREMENT: a non-404 failure is collapsed into 0 as well, with no error affordance—— 钉的正是本单要修的行为,它自己的注释也写明「changing the error handling is a separate class of work, filed as objectui#3679」。留着它必红,故按其原意重写(同 fixture、期望反转、迁进新 describe),并在原 MEASUREMENT 段落注明「原本三条,第三条已完成并搬走」。另外两条 MEASUREMENT 钉的是 #3655 的缺口,未动。从仓库根跑,重活走共享 flock +
--max-old-space-size=4096+--maxWorkers=2:pnpm exec vitest run --project '@object-ui/console'→ 27 files / 245 tests passed(fix(console): System Hub 的 Organizations 计数改查框架真名 sys_organization,Permissions 半只钉不修 (#3670) #3680 记录的 242 + 新增 4 - 重写 1)pnpm --workspace-concurrency=2 --filter @object-ui/console type-check→ 通过(新树里先--filter '@object-ui/console^...' build建好依赖)pnpm --workspace-concurrency=2 --filter @object-ui/console lint→ 0 errors / 190 warnings,与 fix(console): System Hub 的 Organizations 计数改查框架真名 sys_organization,Permissions 半只钉不修 (#3670) #3680 记录的数目一致。SystemHubPage.tsx上那条react-hooks告警(useEffect里同步 setState)是既有的 —— 已把该文件切回origin/main版本单跑 lint 复核,同一条告警在同一行、总数同为 190,本次零新增。node scripts/check-control-bytes.mjs→ OK;check-changeset-no-major.mjs/check-changeset-fixed.mjs→ OKgrep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]'零命中消费半径清扫:全仓
hub-card-*testid 的产出者只有SystemHubPage(本文件)与无关的DeveloperHubPage;同目录SystemHubPage.metadataCards.test.tsx不断言计数,已随全量跑绿。计数徽章文案(0 users等)在packages/**的唯一命中是AssignedUsersSection.tsx里的一句注释,与本页无关。文件面
apps/console/src/pages/system/SystemHubPage.tsx—— 五个.catch回null+countOrUnknown辅助函数 + 说明为什么是逐 promise、为什么 404 不走这里、外层 catch 真实可达条件apps/console/src/pages/system/__tests__/SystemHubPage.counts.test.tsx—— 新增 describe(4 条)+ 重写上述那条 MEASUREMENT.changeset/system-hub-count-error-state-3679.md—— patch(用户可见:错误态不再冒充0)⛔ 未触碰:
packages/data-objectstack的missingResources/is404Error设计、卡片href、Permissions 的对象名与其 MEASUREMENT 钉、其他任何文件。越界发现(只报不改,已单独立单、未认领)
.catch(() => ({ data: [] }))把 500 / 401 / 403 / 断网都渲染成一个确定的0(404 根本到不了它) #3679 正文的修法方向 1 里那句「必要时补一句错误文案」正是它,本单派发面明确划在界外(「复活现存设计,不发明新 UI」),故另立。🤖 Generated with Claude Code
https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
Generated by Claude Code