Skip to content

feat(react,app-shell,plugin-detail,components): data-invalidation bus — refresh data, don't rebuild UI (#2269) - #2274

Merged
os-zhuang merged 1 commit into
mainfrom
feat/invalidation-bus-2269
Jul 5, 2026
Merged

feat(react,app-shell,plugin-detail,components): data-invalidation bus — refresh data, don't rebuild UI (#2269)#2274
os-zhuang merged 1 commit into
mainfrom
feat/invalidation-bus-2269

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

概述

落地 #2269P1(失效总线,核心)+ P2(AGENTS.md 状态规则)+ P3(URL 参数注册表)。一句话:"刷数据"不再等于"炸 UI" —— 两个 key= 重挂载模式被删除,记录详情/子表/计数徽章全部改为总线驱动的原地重取。

架构(基于探索发现的既有先例,不另起炉灶)

代码库已有两半套机制:DataSource.onMutation(MutationEvent,列表视图已订阅)和 related-changed 窗口事件 / RelatedCountStore。本 PR 把它们合成一个 seam:

写路径 读路径
dataSource.create/update/delete ──┐
(MutationEvent, 自动) ├─→ notifyDataChanged({objectName, recordId?})
server action / flow / approval ──┤ │ (bus, @object-ui/react)
(raw HTTP, 手动 notify) │ ├─→ useDataInvalidation(obj, id) → 各读者原地 refetch
undo/redo (op 自带精确 scope) ─────┘ ├─→ 旧 window 事件桥(RelatedList 零改动兼容)
└─→ RelatedCountStore.invalidate(徽章重probe)

变更

  • @object-ui/react:notifyDataChanged / useDataInvalidation(读端 nonce)/ subscribeDataChanges / useMutationInvalidationBridge(dataSource)(console 在 AppContent 挂一次)。
  • 删除两个重挂载 key:AppContent<RecordDetailView key={refreshKey}>RecordDetailView<DetailView key={actionRefreshKey}>。schema 路径的 pageRecord fetch 和 legacy DetailView 自取 effect 订阅总线原地重取;9 处 action 成功后的 key bump 全部换成精确 scope 的 notifyDataChanged;undo/redo 用 UndoableOperation 自带的 objectName/recordId。滚动、折叠分组、tab、进行中的就地编辑从此在保存/action/undo 后全部幸存
  • RelatedCountStore 接总线 + 修一个既有隐性 bug:原 getSnapshot 返回同一个 Map 引用 → emit() 从不触发订阅者重渲染(徽章更新全靠碰巧的父级渲染),失效后还会永久停留在旧值。快照改为单调 version,新增 useRelatedCountVersion() 驱动 probe effect 在失效后重新拉取
  • P3:app-shell/src/urlParams.ts —— 保留参数单点注册(recordId/form/formObject/formLink/tab/palette/shortcuts,含 push-vs-replace 语义表),7 个文件的字面量全部迁入。
  • P2:AGENTS.md 戒律 feat: add live playground for interactive schema demonstration #8 —— UI 状态三分类(可寻址→URL;偏好→存储;真瞬态→组件)+ "refresh data, don't rebuild UI"。

验证

浏览器端到端(showcase,StrictMode):

  • ✅ Related tab 上编辑 account 保存 → 名字原地更新、tab 保持(此前 refreshKey 整树重建)
  • ✅ 子表新建 contact → 子表经桥自动刷新(手动 notify 已删)、父路由不动
  • ✅ Related 徽章 11 → 12(失效 → version 驱动重 probe → 精确计数;此前会卡旧值)

单测 +10(总线匹配语义 × 桥接 × 退订);4 包全量 1679 通过;type-check 32/32;changeset(fixed group minor)。

Closes#2269 · Refs #2257 · #2267 · framework#2604 · ADR-0054

🤖 Generated with Claude Code

https://claude.ai/code/session_019zKcAtbtxuF9SXYgSzjk9v


Generated by Claude Code

… — refresh data, don't rebuild UI (#2269)
P1 of #2269, plus the P2/P3 guardrails:
- @object-ui/react: notifyDataChanged / useDataInvalidation /
subscribeDataChanges + useMutationInvalidationBridge(dataSource), which
fans every dataSource write (MutationEvent — the bus list views already
use) onto one seam. The bus also dispatches the legacy
objectui:related-changed window event so pre-bus listeners keep working;
notifyRelatedChanged is now a thin alias for writes that bypass the
dataSource (row actions).
- DELETED both refetch-by-remount keys: key={refreshKey} on
RecordDetailView (AppContent) and key={actionRefreshKey} on DetailView.
The schema-path pageRecord fetch and DetailView's self-fetch subscribe
to the bus and refetch IN PLACE; all nine action-success bumps became
precisely-scoped notifyDataChanged calls; undo/redo use the
UndoableOperation's own objectName/recordId.
- RelatedCountStore wired to the bus + snapshot fixed: getSnapshot
returned the same Map reference, so emit() never re-rendered
subscribers (pre-existing latent bug — badges only updated by luck) and
invalidation left them stale. Snapshot is now a monotonic version;
useRelatedCountVersion() drives the probe effect's re-fetch.
- P3: app-shell/src/urlParams.ts — single registry of reserved console
URL params (recordId/form/formObject/formLink/tab/palette/shortcuts);
all literals migrated.
- P2: AGENTS.md Commandment #8 — UI-state classification (addressable →
URL; preference → storage; ephemeral → component) + 'refresh data,
don't rebuild UI'.
Browser-verified (showcase, StrictMode): account edit-save on the
Related tab → name updates in place, tab kept; child create → subtable
refetches via the bridge alone (manual notify removed) and the Related
badge re-probes 11 → 12. Unit: +10 bus tests; suites 1679 green;
type-check 32/32.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019zKcAtbtxuF9SXYgSzjk9v
@vercel

vercelBot commented Jul 5, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectuiIgnoredIgnoredJul 5, 2026 8:37am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)59.5 KB350 KB
Entry fileindex-CO34Ckhj.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)7.51KB2.72KB
app-shell (runtime-config.js)4.72KB1.69KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)4.11KB1.65KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)18.34KB3.80KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)4.79KB1.88KB
auth (LoginForm.js)9.55KB3.36KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.63KB2.15KB
auth (SocialSignInButtons.js)8.89KB3.61KB
auth (UserMenu.js)3.40KB1.22KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)26.07KB6.30KB
auth (createAuthenticatedFetch.js)3.93KB1.55KB
auth (index.js)1.75KB0.76KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)4.29KB0.82KB
auth (useIsWorkspaceAdmin.js)1.61KB0.85KB
collaboration (CommentThread.js)18.38KB4.49KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)3.65KB1.42KB
collaboration (PresenceProvider.js)2.42KB0.96KB
collaboration (index.js)1.25KB0.53KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)437.44KB94.00KB
core (index.js)1.65KB0.59KB
create-plugin (index.js)9.28KB2.98KB
data-objectstack (index.js)109.66KB27.05KB
fields (index.js)185.40KB45.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.32KB1.77KB
i18n (index.js)2.46KB0.96KB
i18n (pickLocalized.js)1.31KB0.67KB
i18n (provider.js)5.37KB1.72KB
i18n (useObjectLabel.js)21.15KB4.68KB
i18n (useSafeTranslation.js)2.68KB0.98KB
layout (index.js)36.92KB10.17KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.74KB
mobile (index.js)1.50KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)4.42KB1.27KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.71KB0.42KB
mobile (useResponsiveConfig.js)1.36KB0.63KB
mobile (useSpecGesture.js)1.77KB0.77KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)5.09KB1.84KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)3.46KB1.03KB
permissions (evaluator.js)4.00KB1.23KB
permissions (index.js)0.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
permissions (usePermissions.js)1.50KB0.70KB
plugin-ai (index.js)15.71KB3.79KB
plugin-calendar (index.js)45.10KB12.33KB
plugin-charts (index.js)46.18KB13.02KB
plugin-chatbot (index.js)173.06KB41.28KB
plugin-dashboard (index.js)108.28KB26.86KB
plugin-designer (index.js)213.56KB42.96KB
plugin-detail (index.js)203.75KB49.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)102.48KB24.90KB
plugin-gantt (index.js)136.67KB33.88KB
plugin-grid (index.js)165.48KB43.77KB
plugin-kanban (index.js)48.15KB12.94KB
plugin-list (index.js)97.89KB23.09KB
plugin-map (index.js)16.78KB5.23KB
plugin-markdown (index.js)13.65KB4.67KB
plugin-report (index.js)37.83KB9.97KB
plugin-timeline (index.js)25.37KB7.20KB
plugin-tree (index.js)8.21KB2.76KB
plugin-view (index.js)85.78KB20.89KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.55KB0.67KB
providers (UploadProvider.js)11.71KB3.53KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)3.19KB1.38KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)18.23KB5.97KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)0.79KB0.43KB
sdui-parser (codegen.js)4.09KB1.74KB
sdui-parser (index.js)2.16KB0.94KB
sdui-parser (parse.js)10.04KB2.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)4.69KB1.48KB
tenant (TenantContext.js)0.31KB0.25KB
tenant (TenantGuard.js)1.04KB0.43KB
tenant (TenantProvider.js)2.76KB0.98KB
tenant (TenantScopedQuery.js)0.77KB0.44KB
tenant (index.js)0.75KB0.38KB
tenant (resolver.js)2.64KB0.76KB
tenant (useTenant.js)0.50KB0.32KB
tenant (useTenantBranding.js)0.62KB0.39KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)0.77KB0.41KB
types (disclosure.js)0.20KB0.18KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (index.js)1.54KB0.68KB
types (layout.js)0.20KB0.18KB
types (mobile.js)0.20KB0.18KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.26KB1.96KB
types (tenant.js)0.20KB0.18KB
types (theme.js)0.20KB0.18KB
types (ui-action.js)0.75KB0.46KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-zhuang
os-zhuang marked this pull request as ready for review July 5, 2026 08:52
@os-zhuang
os-zhuang merged commit 6fffd3d into mainJul 5, 2026
10 checks passed
@os-zhuang
os-zhuang deleted the feat/invalidation-bus-2269 branch July 5, 2026 08:52
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationpackage: componentspackage: reactplugintests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

长期治理:客户端失效总线取代 key 重挂载(refreshKey/actionRefreshKey)+ UI 状态分类规则 + URL 参数注册表(#2257 后续)

2 participants

@os-zhuang@claude