Skip to content

fix(console): System Hub 的 Organizations 计数改查框架真名 sys_organization,Permissions 半只钉不修 (#3670) - #3680

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-3670-hub-card-counts
Aug 7, 2026
Merged

fix(console): System Hub 的 Organizations 计数改查框架真名 sys_organization,Permissions 半只钉不修 (#3670)#3680
yinlianghui merged 1 commit into
mainfrom
claude/issue-3670-hub-card-counts

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes-part-of #3670(Organizations 半已交付;Permissions 半按有界授权停手,等维护者在 #3655 裁决 A/B/C 后另行落地 —— 详见下文「为什么 Permissions 留着不动」)

先复核:issue 的症状成立,机制归因需订正

issue 说「.catch 吞掉 404,所以计数落成 0」。症状完全成立,机制不是这样 —— 这条不是措辞挑剔,它决定了本 PR 该改哪一行、不该改哪一行:

  • 对象不存在时后端答 404 + code: 'OBJECT_NOT_FOUND',而 ObjectStackAdapter.find() 自己先吃掉了它:packages/data-objectstack/src/index.tsis404Error(err) 命中后把资源记进 missingResources(后续同名调用直接短路)并 return { data: [], total: 0 }。这是有意设计,注释原话是 callers treat empty data as "feature unavailable"。
  • 也就是说 404 从来没到达页面那一层的 .catch;它给的是 resolve 不是 reject。页面拿到一页空数据,data.length 得 0,渲染出一个完全正常的徽章。

结论不变而且更刺眼:错名的失败是静默的,不是被 .catch 掩盖的,靠加错误处理也不会让它变响 —— 唯一的修法就是把名字改对。因此本 PR 只改名字,.catch 一个字节没动(那是另一个类,见文末 #3679)。

对象名核实表(逐个对框架源码核对,不是猜的)

框架侧 checkout ../objectstack @ b4872a868;计数处五个名字全量列出,不只 issue 点名的两个:

卡片 查询用的对象名 框架里有没有 依据 处置
Users sys_user packages/platform-objects/src/identity/sys-user.object.ts:17 不动
Organizations sys_org 没有 全仓 grep 作为对象名零命中;真名 sys_organization,packages/platform-objects/src/identity/sys-organization.object.ts:14 改为 sys_organization
Positions sys_position packages/plugins/plugin-security/src/objects/sys-position.object.ts:16 不动
Permissions sys_permission 没有 全仓精确 grep(不含 _set)只命中 sys-capability.object.ts:20 的一句注释,无对象定义 不动,只钉
Audit Log sys_audit_log packages/plugins/plugin-audit/src/objects/sys-audit-log.object.ts:17 不动

没有第三个错名。issue 对 sys_user / sys_position 正确的判断经核实成立,并把它没点名的 sys_audit_log 一并核过。

Organizations 这一条的现实后果:单组织部署里 sys_organization 至少有一行,所以那张卡片显示的 0 在任何部署上都是错的,而且长得和「还没建组织」一模一样。

为什么 Permissions 留着不动

框架把这张卡片叫的东西拆成了两个对象,两边证据都硬(考证出自已合并的 PR #3673,本 PR 未推翻):

  • sys_capability —— 其 docblock 明写「Named sys_capability (not sys_permission as the ADR loosely floats)」,而 sys_permission 正是这里今天在用的名字。血缘指向它。
  • sys_permission_set —— 权限文档称其为唯一的 capability 容器,是带管理员 CRUD 的授予容器,和卡片描述 "Manage permission rules and assignments" 对得上。功能指向它。

任选其一,此后每一个看到这个数字的人都会以为平台确认了这就是「Permissions」——一个维护者没有做过的选择被一次改名静默定型。#3655 已把它挂上 needs-user-decision,三选一是 A sys_permission_set / B sys_capability / C 连同这张 bespoke 卡片一起退场(SystemHubPage 自己的 docblock 已标 @deprecated)。裁决落地前,这张卡片继续读 0 —— 但这个缺口现在在测试里显式钉住,不再读起来像漏了一行。

.catch 行为矩阵(只测量,不修)

同一个 0 背后有三种语义完全不同的事,修前修后都不可分:

后端实际发生的事 适配器 页面 .catch 屏幕 本 PR 前 本 PR 后
对象不存在(404 OBJECT_NOT_FOUND) 吃掉,resolve 空 未触发 0 Organizations + Permissions 两张 只剩 Permissions 一张
对象存在但确实没有记录 resolve 空 未触发 0 同(不变)
500 / 401 / 403 / 断网 rethrow 吃掉 0 同(不变)

第三行是这个 .catch 实际覆盖的唯一一类,和 issue 的归因正相反。它没被本 PR 碰,只被测量并另立 #3679。顺带记录:fetchCounts 外层那个 catch { /* Keep nulls on failure */ } 因为每个 promise 自带 .catch 已不可能被 Promise.all 触发,那条「保留 null 不显示徽章」的降级路径实际是死的 —— 而它恰好是唯一能把「不知道」和「0」区分开的形态。这一条也写进了 #3679

逆向验证(先预测,后运行)

预测:把对象名回退成 sys_org 后,3 红 3 绿。红的应是——(1) Organizations 计数用例(查询发向 sys_org,fixture 里没有这个对象,适配器 resolve 空 → 徽章回到 0 organizations);(2) 对象名审计用例(调用清单第二项变成 sys_org,「框架不认识的名字」从 1 个变 2 个);(3) 非 404 失败的 MEASUREMENT 用例 —— 它顺带断言了邻居卡片仍显示真实计数 2 organizations,所以也会跟着红,这条要照实预测而不是假装它无关。绿的应是另外三条:三个正确名字的用例、Permissions 恒 0 的钉子、「空对象与不存在对象徽章相同」的钉子 —— 它们都不经过 Organizations 的计数。

实测:6 passed -> 3 failed | 3 passed,失败清单与预测逐条吻合,失败现场直接打印出修前的屏幕与调用清单:

× counts Organizations through sys_organization, the name the framework registers
× asks for exactly five names, and only one of them is missing from the framework
× MEASUREMENT: a non-404 failure is collapsed into 0 as well, with no error affordance

TestingLibraryElementError: Unable to find an element with the text: 2 organizations
  data-testid="hub-card-organizations"
  ...
      0
       
      organizations

AssertionError: expected [ 'sys_user', 'sys_org', …(3) ] to deeply equal [ Array(5) ]
-   "sys_organization",
+   "sys_org",

(把审计用例的等待锚从 Organizations 卡片换到 Users 卡片,就是为了让它红在调用清单的 diff 上,而不是红在别处的徽章上。)

测试

从仓库根跑,重活走共享 flock + --max-old-space-size=4096 + --maxWorkers=2:

消费半径清扫:SystemHubPage 只在 apps/console 内被引用(AppContent.tsx 的路由声明 + pages/settings/SettingsHub.tsx),packages/** 里对它的五处提及全是注释,无 import;全仓 hub-card-* testid 的另一处产出者是 DeveloperHubPage,与本文件无关。同目录既有的 SystemHubPage.metadataCards.test.tsx(#3660 落的)不断言计数,已跑绿确认不受影响。

文件面

  • apps/console/src/pages/system/SystemHubPage.tsx —— 一行对象名 + 一段说明为什么这里的错名是静默的、以及 Permissions 为什么留着
  • apps/console/src/pages/system/__tests__/SystemHubPage.counts.test.tsx —— 新增(与既有的 metadataCards 测试同目录同形态:真组件 + 真 @object-ui/components,只 mock useAdapter / useIsWorkspaceAdmin)。适配器 stub 按真实契约建模:不存在的对象 resolve 空而不是 reject,所以测试跑在和缺陷同一种静默里
  • .changeset/system-hub-org-count-3670.md —— patch

⛔ 未触碰:卡片 href(#3668 刚落)、AppContent.catch / 错误处理、框架侧任何对象定义、以及同目录里 #3672 记录的孤儿文件 systemObjects.ts(它内嵌的对象定义同样写着 sys_org / sys_permission,但全仓零引用,属于那一单)。

越界发现(只报不改,已单独立单、未认领)


🤖 Generated with Claude Code

https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt


Generated by Claude Code

…3670)

`sys_org` 在框架里不存在。缺失对象不是响亮的失败:后端答 404
OBJECT_NOT_FOUND,而 ObjectStackAdapter.find() 有意把它吸收成
{ data: [], total: 0 }(资源记进 missingResources 后续短路),于是卡片
渲染出一个与「确实没有」不可分的 `0` —— 而单组织部署里
sys_organization 至少有一行。每个 find 上的 .catch 从来没看到过这个
404,它只覆盖非 404 的 reject。

另外三个计数对象名(sys_user / sys_position / sys_audit_log)已逐个
核对框架对象注册表,拼写正确,未改。

Permissions 卡片刻意不修,仍读 0:它查的 sys_permission 框架同样没有,
而框架把该面拆成 sys_capability(血缘)与 sys_permission_set(功能),
任选其一都会把卡片静默绑到维护者没有选过的面上 —— 该裁决开在 #3655。
这个缺口由测试里的 MEASUREMENT 用例钉住,而不是悄悄改指。

.catch 对非 404 失败(500/401/403/断网)同样折叠成 0,只测量记录,
已另立 #3679。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectui Ignored Ignored Aug 7, 2026 8:15pm

Request Review

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 28.1 KB 350 KB
Entry file index-zt3zmKA8.js
Status PASS

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 8.66KB 3.13KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 7.57KB 2.97KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 22.10KB 4.37KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.13KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.64KB 2.21KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 35.76KB 9.11KB
auth (createAuthenticatedFetch.js) 4.37KB 1.69KB
auth (index.js) 2.35KB 1.07KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 4.91KB 0.87KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 26.07KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.65KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 480.72KB 105.64KB
core (index.js) 2.96KB 1.13KB
create-plugin (index.js) 9.28KB 2.98KB
data-objectstack (index.js) 137.51KB 35.11KB
fields (index.js) 230.87KB 56.83KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 2.65KB 1.06KB
i18n (pickLocalized.js) 1.70KB 0.83KB
i18n (provider.js) 9.48KB 3.27KB
i18n (useObjectLabel.js) 26.14KB 6.07KB
i18n (useSafeTranslation.js) 4.52KB 1.96KB
layout (index.js) 38.53KB 10.71KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.74KB
mobile (index.js) 1.50KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 8.75KB 3.06KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.41KB 1.44KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.55KB 0.71KB
plugin-ai (index.js) 15.71KB 3.79KB
plugin-calendar (index.js) 44.98KB 12.37KB
plugin-charts (index.js) 61.04KB 17.31KB
plugin-chatbot (index.js) 180.09KB 42.72KB
plugin-dashboard (index.js) 115.50KB 29.96KB
plugin-designer (index.js) 210.51KB 42.51KB
plugin-detail (index.js) 232.79KB 57.42KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 112.10KB 27.10KB
plugin-gantt (index.js) 162.55KB 39.57KB
plugin-grid (index.js) 186.61KB 49.34KB
plugin-kanban (index.js) 48.30KB 13.28KB
plugin-list (index.js) 105.12KB 25.48KB
plugin-map (index.js) 16.81KB 5.24KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 40.58KB 10.58KB
plugin-timeline (index.js) 25.76KB 7.33KB
plugin-tree (index.js) 8.50KB 2.88KB
plugin-view (index.js) 84.03KB 20.55KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.67KB 2.37KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 19.28KB 6.38KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.02KB 0.55KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 4.47KB 2.03KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-retry.js) 4.32KB 2.02KB
types (index.js) 2.71KB 1.34KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.05KB 1.93KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 3.40KB 1.71KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@yinlianghui
yinlianghui marked this pull request as ready for review August 7, 2026 20:23
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 7, 2026
Merged via the queue into main with commit 278f57c Aug 7, 2026
19 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-3670-hub-card-counts branch August 7, 2026 20:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants