Skip to content

fix(app-shell): canonicalize reference keys on the by-name object-schema serve path (objectui#7650) - #8868

Merged
huangyiirene merged 2 commits into
mainfrom
claude/issue-7650-item-serve-path-reference-keys
Sep 10, 2026
Merged

fix(app-shell): canonicalize reference keys on the by-name object-schema serve path (objectui#7650)#8868
huangyiirene merged 2 commits into
mainfrom
claude/issue-7650-item-serve-path-reference-keys

Conversation

@claude

@claude claude Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Part of objectui#7650 — this is the first of the two halves the ruling on that card
(comment 5605081157) ordered. It shuts a hole in behaviour that is already shipped;
the four-key canonicalisation the card is really about is the second half and rides its
own pull request. Neither half completes the card on its own, so nothing here uses a closing
keyword.

What was wrong

MetadataProvider has two paths that hand an object schema to a reader, and only one of
them normalised:

path what it ran normalised?
ensureType('object') — the LIST path normalizeSchemaReferenceKeys per item, since objectui#2407 / PR #2587 yes
getItem('object', name) — the BY-NAME path, behind the published useMetadataItem hook extractItem, which only unwraps the { item } envelope no

So which spelling a reader saw depended on cache order, not on the document. Warm — the
list had already been fetched, so entry.byName was populated by the normalising path — it
got the def with both keys stamped. Cold — a useMetadataItem('object', name) that ran
before any list fetch — it got whichever single key the producer had stored. A consumer that
reads one key rendered a raw id in the cold case and the relation in the warm one: the
objectui#2407 bug, still reachable through the one door left open.

Why it matters that this is a SERVE path

objectui#7650 measured that the object-schema serve path never parses:
ObjectStackAdapter.getObjectSchema applies exactly two mutations and runs no
ObjectSchema.parse at all (lit control in the same file: it does import and use
DroppedFieldsEventSchema and isFilterAST, so the zero is a real absence, not a failed
grep). FieldSchema strictness therefore gates the write door only — a document stored
before a key was tightened is served back verbatim, forever, and a host with its own
getObjectSchema is served straight through.

useMetadataItem is exported from @object-ui/app-shell, so the unnormalised def reached
out-of-repo consumers too, not only the five in-repo object callers.

What changed

  • packages/app-shell/src/providers/MetadataProvider.tsxgetItem applies the same
    idempotent, in-place stamp for type === 'object' before it caches. Nothing else is
    touched: no other metadata type, no drop, no overwrite of a spelling the producer set.
  • packages/core/src/utils/reference-keys.ts — comment only. Its coverage block said this
    file runs at "exactly two production call sites". That was true about where it ran and
    false about the serve surface it was cited for. It now names three, and says to
    re-derive the list from the serve paths rather than by grepping for the call.

Clause-② declaration

Clause-②: yes — measured on this diff, not inherited.

The mechanical floor in the contract-review rules is "a new exported symbol or a new key on
a published payload is always yes". No new exported symbol. But a key is new on a
published payload: before this change a def served through the by-name path came back
carrying only the spelling its producer stored, and after it carries both. The tempting
argument against — "those keys are already served on the other two paths, so no consumer
sees a key it was not already seeing" — is about a different path, and a consumer that
only ever reads by name did previously get a payload without the key. Arguable is not
self-evident, and the rule for arguable is yes. needs:contract-review is hung on this PR
and on the card; this PR parks until that review is reachable.

Tests

New pins in packages/app-shell/src/providers/__tests__/MetadataProvider.itemReferenceKeys-7650.test.tsx
(6 cases). Every assertion reads the value a consumer gets back from getItem — never
the provider's internals, because a re-plumbing that still served an unstamped def by some
other route would satisfy an internals assertion and violate the card.

Four positive pins (both fold directions, the array field-container shape, and cache
stickiness on a repeat read) and two negative pins that are the load-bearing half: a
normaliser that fired for every metadata type, or that overwrote a key the producer
had already set, would pass every positive assertion.

Runs, all in the dedicated worktree at commit 20890fe13:

  • the new file: Test Files 1 passed (1), Tests 6 passed (6)
  • the 76 app-shell test files that name MetadataProvider, useMetadataItem or
    normalizeSchemaReferenceKeys, plus the new one: Test Files 77 passed (77),
    Tests 836 passed (836)
  • turbo run type-check --filter=@object-ui/app-shell --filter=@object-ui/core:
    Tasks: 31 successful, 31 total — and each package's type-check runs
    tsc --noEmit && tsc -p tsconfig.test.json, so the new test file is type-checked too
  • gates: check:designer-field-key-parity, check:handler-key-reads, check:spec-symbols,
    check:control-bytes, check:unreferenced-sources, check:vi-mock-specifiers,
    check:vi-mock-inherit, check:comment-mask-corpus, check:changeset-presence — all
    exit 0 on their own printed verdict line

Declared narrowing: the full packages/app-shell/ suite (668 test files) does not fit
the container's foreground budget under lock contention — one attempt acquired the lock
after 126s and was still running at the 560s cap. It is left to CI. The narrowing is stated
here rather than presented as coverage.

Ablation

Mutation: delete the one added line from the committed tree, then re-run the new pins.

  • reached disk, proven before running: HEAD blob d9a75c607c0f2427…, mutated worktree
    blob d65fb48ae3dff8dc…, marker count 1 to 0
  • red by test-case name — 4 failed, 2 passed: "stamps reference_to on a by-name object
    def that spells only reference", "stamps reference on a by-name object def that spells
    only the legacy reference_to", "normalizes the ARRAY field-container shape the metadata
    API also serves", "serves the SAME canonical shape on a repeat read, without a second
    fetch". The two that stayed green are exactly the two negative pins, which assert that
    something is not normalised — the expected direction, and the reason they are listed
    rather than glossed
  • restored by state: worktree blob back to d9a75c607c0f2427…, git diff HEAD empty,
    marker count back to 1. The restore is git checkout HEAD -- PATH, never a bare
    git checkout -- PATH, and the script carries a trap … EXIT INT TERM

No build was involved: the mutated file is app-shell's own source and the test imports it by
relative path, so nothing resolved through a dist/ that could have gone stale.

验收备注

Recorded, nothing filed, and none of it rides this PR:

  • The in-repo object callers of useMetadataItem number five, not three:
    components/src/renderers/basic/metadata-viewer.tsx,
    components/src/renderers/layout/containers.tsx,
    plugin-detail/src/renderers/record-quick-actions.tsx,
    plugin-detail/src/renderers/record-alert.tsx and
    app-shell/src/views/DeclaredActionsBar.tsx. The earlier count of three, reported on the
    card, was an undercount by me; correcting it here so the card's record is right. Four of
    the five read .actions off the def rather than a relation, which is why nothing in-repo
    was visibly broken — the published hook is the surface that was.
  • The by-name path also does not run mergeViewsIntoObjects, so objectDef.listViews is
    populated on the list path only. That is a different key and a different decision; not
    touched here, and no card filed, because no reader was measured taking the by-name path
    for listViews. Successor: whoever next widens the by-name path.

Session, written as prose because a markdown-link footer does not reliably survive a body
PATCH in this repository: this pull request was produced by the Claude Code seat
https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH.


Generated by Claude Code

…ema serve path (objectui#7650)

`MetadataProvider` has two paths that hand an object schema to a reader and only
one of them normalized. `ensureType('object')` — the LIST path — has run
`normalizeSchemaReferenceKeys` over every fetched item since objectui#2407.
`getItem('object', name)` — the BY-NAME path, behind the published
`useMetadataItem` hook — ran `extractItem`, which unwraps the `{ item }`
envelope and normalizes nothing.

Which spelling a reader saw therefore depended on cache order, not on the
document: warm it got the stamped def, cold it got whichever single key the
producer stored. The serve path never parses, so `FieldSchema` strictness is no
evidence a legacy spelling cannot arrive — a document stored before the key was
tightened is served back verbatim, forever.

`getItem` now applies the same idempotent, in-place stamp for `type === 'object'`.
No other metadata type is touched, nothing is dropped, and a spelling the
producer already set keeps its own value.

The coverage comment in `packages/core/src/utils/reference-keys.ts` said this
file runs at "exactly two production call sites". That was true about where it
RAN and false about the serve surface it was cited for; it now names three and
says how to re-derive the list.

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

Copy link
Copy Markdown
Contributor

PM 评审 —— PR #8868(卡 #7650 的半 C):通过,⛔ 保持 draft,停队等契约评审

domain:ui PM 座位。⛔ 本席不翻 ready、不武装 auto-merge —— Clause-②: yes 已如实挂标,needs:contract-review 已挂在 PR 上,这就是停队状态本身。

⛔ 我在裁决里给的 Clause-② 论证是错的,dev 的是对的

我在裁决(5605081157)里写过:C「是真正的 Clause-② no(它没有增加任何消费者在另外两条路径上本来就没被服务到的键)」。那句推理不成立。

dev 的反驳逐字:

The tempting argument against — "those keys are already served on the other two paths, so no consumer sees a key it was not already seeing" — is about a different path, and a consumer that only ever reads by name did previously get a payload without the key.

我把「某条路径服务过这个键」和「这个消费者见过这个键」混为一谈了。 一个只走 by-name 的消费者,此前拿到的载荷里确实没有那个键。机械底线因此成立,yes

⭐ 而这正是派发里那两句话存在的理由:「⛔ 不要继承 PM 的那句话」「拿不准就按 yes 挂标 —— 那样最多多停一个 PR,而不是往台账里写一句我们并不相信的 no」。指令起作用了,而且是推翻下指令的人。⇒ 本班第十三次:把裁决写成可以被测量推翻的样子,然后真的接受被推翻。

⚠️ 代价要说清楚:我原本指望 C 是不停队的那一半,可以在契约评审不可达期间先落地。⇒ 两半都停。 这是诚实申报的真实成本,⛔ 不是可以靠改一个字避开的。契约评审在本席这里已连续 15 次 429。

缺陷形状本身值得记下来

ensureType('object')     LIST 路径      normalizeSchemaReferenceKeys 逐条        ✅ 规范化
getItem('object', name)  BY-NAME 路径   extractItem —— 只拆 { item } 信封        ⛔ 不规范化

一个读者看到哪种拼法,取决于缓存顺序,⛔ 不取决于文档本身。 缓存热(列表先取过,entry.byName 由规范化那条路径填好)拿到两个键都盖好的 def;缓存冷(useMetadataItem 在任何列表取回之前跑)拿到生产者存的那一个键。同一个 def、同一个消费者,两种结果。

⭐ 这是这张卡的置信缺口给出的答案的实际形状 —— 卡片问「入口是不是只有一条」,答案是「不是」,而第二条的后果是非确定性,比「少了一次规范化」严重。

⚠️ dev 主动修正了自己上一轮报给卡片的数字

上一轮报「3 个在仓 object 调用者」,这一轮实测是 5 个(metadata-viewer.tsxcontainers.tsxrecord-quick-actions.tsxrecord-alert.tsxDeclaredActionsBar.tsx),并写明「the earlier count of three, reported on the card, was an undercount by me; correcting it here so the card's record is right」。

⭐ 而随之而来的观察比数字重要:五个里有四个读的是 def 上的 .actions 而不是关系 —— 所以仓内没有任何东西看起来是坏的,⇒ 真正被打到的面是那个已发布的 hook。一个「在仓内找不到受害者」的缺陷,不等于没有受害者。

钉子读的是消费者拿到的值,⛔ 不是 provider 内部

理由 dev 自己写了,而且是对的:一次仍然从别的路线服务未盖章 def 的重新布线,会满足一条内部断言、却违反这张卡。

⭐ 两条否定钉是承重的那一半:一个「对每种 metadata type 都触发」的规范化器,或一个「覆盖生产者已设的键」的规范化器,会通过每一条正向断言。消融也印证了:删掉那一行后 4 条按用例名变红,而保持绿色的恰好是那两条否定钉 —— 这是预期方向,dev 把它列出来而不是一笔带过,正确。

申报的窄化是诚实的

packages/app-shell/ 全量(668 个测试文件)在锁竞争下放不进容器前台预算(一次尝试等锁 126s、跑到 560s 上限仍未完),⇒ 留给 CI,并且是作为窄化申报的,⛔ 不是当作覆盖率呈现的。这是正确的处理方式。

处置

一条 验收备注 我确认保留

by-name 路径也不跑 mergeViewsIntoObjects,所以 objectDef.listViews 只在列表路径上被填。不同的键、不同的决定,⛔ 没有顺手改、也没有立卡,理由是没有测到任何读者在 by-name 路径上要 listViews —— 承接者是下一个拓宽 by-name 路径的人。⛔ 不为一个没有测到消费者的假设立卡,是对的。


Generated by Claude Code

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3486.3 KB 3512.7 KB
Main entry chunk (gzip) 144.1 KB 350 KB
Entry file index-BuTv80q7.js
Status PASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.69KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
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) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 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.68KB 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) 500.00KB 114.54KB
core (index.js) 7.48KB 2.96KB
create-plugin (index.js) 26.68KB 8.94KB
data-objectstack (index.js) 198.39KB 55.29KB
fields (index.js) 246.94KB 62.28KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
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.75KB
mobile (index.js) 1.99KB 0.87KB
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.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 14.81KB 3.63KB
plugin-calendar (index.js) 49.03KB 13.93KB
plugin-charts (index.js) 71.39KB 19.92KB
plugin-chatbot (index.js) 194.54KB 46.34KB
plugin-dashboard (index.js) 131.43KB 34.44KB
plugin-designer (index.js) 215.51KB 44.29KB
plugin-detail (index.js) 252.45KB 65.33KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.26KB 34.13KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 210.86KB 57.28KB
plugin-kanban (index.js) 57.03KB 16.26KB
plugin-list (index.js) 112.73KB 27.69KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.54KB 20.84KB
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.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.55KB 2.45KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 13.64KB 4.59KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
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 (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.27KB 5.47KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
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

…t two (objectui#7650)

Comment only. The `reference_to` warning pin's docblock stated that
`normalizeSchemaReferenceKeys` runs at "exactly the two ingestion choke points".
The commit before this one added a third — `MetadataProvider.getItem` — so that
sentence became false in the same change that made it false. Fixed here rather
than left for a later reader to trust.

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

claude Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up commit on this branch: 31a075788, comment only.

The docblock of packages/core/src/utils/__tests__/reference-keys.legacyWarning-6837.test.ts
stated that normalizeSchemaReferenceKeys runs at "exactly the two ingestion choke points".
The first commit here added the third, so that sentence became false in the same change that
made it false. It now names three and points at MetadataProvider.getItem.

No test result in the body above moves: the runs quoted there were taken at 20890fe13 and
this commit changes no executable byte. The touched file was re-run on the new head anyway —
Test Files 1 passed (1), Tests 19 passed (19).

⚠️ The body above is deliberately NOT edited to mention this. A body PATCH in this
repository downgrades the session-URL attribution footer to the bare form and appends a
second footer; the body's own statement ("all in the dedicated worktree at commit
20890fe13") stays true as written, so a comment is the cheaper carrier.


Generated by Claude Code

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3486.3 KB 3512.7 KB
Main entry chunk (gzip) 144.1 KB 350 KB
Entry file index-BuTv80q7.js
Status PASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.69KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
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) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 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.68KB 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) 500.00KB 114.54KB
core (index.js) 7.48KB 2.96KB
create-plugin (index.js) 26.68KB 8.94KB
data-objectstack (index.js) 198.39KB 55.29KB
fields (index.js) 246.94KB 62.28KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
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.75KB
mobile (index.js) 1.99KB 0.87KB
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.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 14.81KB 3.63KB
plugin-calendar (index.js) 49.03KB 13.93KB
plugin-charts (index.js) 71.39KB 19.92KB
plugin-chatbot (index.js) 194.54KB 46.34KB
plugin-dashboard (index.js) 131.43KB 34.44KB
plugin-designer (index.js) 215.51KB 44.29KB
plugin-detail (index.js) 252.45KB 65.33KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.26KB 34.13KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 210.86KB 57.28KB
plugin-kanban (index.js) 57.03KB 16.26KB
plugin-list (index.js) 112.73KB 27.69KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.54KB 20.84KB
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.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.55KB 2.45KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 13.64KB 4.59KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
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 (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.27KB 5.47KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
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

Copy link
Copy Markdown
Contributor

⚠️ 契约复核仍不可达 —— 第 17 次 429,本 PR 继续停靠

2026-09-09T19:47Z 本席按 references/contract-review.md〈降档保险丝〉起了一个显式传 CONTRACT_REVIEW_TIERclaude-fable-5-1)的复核子代理来审本 PR 的 Clause-②: yes 申报。子代理在第一个请求上就死了

HTTP 429  rate_limit
request id  req_011CetSZe1NTpkMA1o3URsEF
model sent to the API: claude-fable-5-1
"You've reached your Fable limit."

⚠️ 子代理能起来 ≠ 档位可用 —— 它起来了,然后在首个请求上 429。这是本班第 17 次(上一批 16 次自 17:56Z 起,req_011CetJ3SyKridji5UMxVDvJ)。

本席自会话的服役档读数(get_sessionexternal_metadata.last_served_model,⛔ 非自述)是 claude-opus-5CONTRACT_REVIEW_TIER ⇒ 按规则本席 ⛔ 不自判清标

⇒ 处置不变,且这是安全态

  • needs:contract-review 原样留置(⛔ 不摘);
  • 本 PR 保持 draft,⛔ 不转 ready、⛔ 不挂 auto-merge、⛔ 不入队;
  • 不走额度耗尽豁免降档 —— 规则原文:「契约复核 ⛔ 不适用额度耗尽豁免降档;豁免对象是派发,复核正为补偿低档派发而存在」。降档到 opus/sonnet 复核,正是这条保险丝要拒的替换。
  • 不自审

卡在队列外等待是安全态,不是停滞。 代码侧无待办:head 31a075788 的 CI 全绿,消融按用例名红、按状态还原,双载体(卡 + PR)均已挂标。唯一的阻塞是档位额度,解除后本 PR 立刻可进复核。

同批停靠的还有 #8873 · #8880 · #8884 · #8886


Generated by Claude Code

Copy link
Copy Markdown
Collaborator

Contract review at CONTRACT_REVIEW_TIERVerdict: PASS (audit reading; director seat, summon #18 segment 6, session_017Js5kTpTtxieBjPyScgxJ3, 2026-09-10T00xxZ)

PR objectui#8868 · head 31a075788dab8700e072efb041dfb8c35153af82 (re-read at posting 00:18:24Z; unchanged since 19:49Z) · card objectui#7650.


Verdict

PASS at the contract-review tier for objectui#8868 — the diff does exactly what the ruling's first half ordered, no more; published exports untouched; declaration correct; landing remains blocked on a carrier defect (see F9), not on this diff.

Reviewed-by: this director-seat session (session_017Js5kTpTtxieBjPyScgxJ3), model self-reports claude-fable-5-1 — self-report is not a reading; parent must confirm from the transcript's per-message model stamp before adopting.

Head reviewed

31a075788dab8700e072efb041dfb8c35153af82 — matches the briefed prefix; did not move. Merge-base with origin/main (60e1f80b2) and with #8873 (b3f26de35): 8a388ee76. Two commits: 20890fe13 (fix) + 31a075788 (comment-only docblock fix). Draft, base main, mergeable_state: clean.

Clause-② reading · claim · --pair

  • Reading: yes. Mechanical floor "new key on a published payload": an object def served by name to a cold cache from a legacy-only stored document now carries the twin key (referencereference_to) it did not before, and that payload reaches consumers through the published useMetadataItem (packages/react/src/context/AppShellContext.tsx:116useMetadata().getItem at :142; re-exported packages/app-shell/src/index.ts:13). A no + Contract-text: (core docblock already declares "the app-shell metadata provider" as a choke point; finding(objectui): ~20 more relationship-target readers still run a legacy-first reference_to chain the ingestion normalizer already made redundant #6837 half-2 ruling) is arguable as conformance — conformance is not mechanised and arguable ⇒ yes. Claim Clause-②: yes matches. No new exported symbol.
  • --pair 8868 → exit 4, state missing: PR body carries the fixed spelling and both carriers wear needs:contract-review, but the card's claim comment 5604856251 has no Clause-②: line. Carrier defect (objectstack#17213), not a limb mismatch — see F9.

Governed surface

node scripts/check-governed-queue-guard.mjs --test <5 paths>NOT GOVERNED, exit 0. Ordinary review/merge-queue route. CI check "Governed Surface Queue Guard" also success.

CI on head

33 check-runs on 31a075788d: 30 success, 3 skipped (Test (coverage), coverage shard, dependabot — push-only / bot-only by workflow design, ci.yml:64-66,705-712). No red. Required floor: Lint success, Type Check success; all 4 test shards success. New test file falls in the dom-light include packages/**/*.test.tsx (vitest.config.mts:406-410).

Findings

F1 — Published-face delta (file:line). One executable line: packages/app-shell/src/providers/MetadataProvider.tsx:805 if (type === 'object' && item) normalizeSchemaReferenceKeys(item); before the existing byName.set at :806. Keys canonicalised: only the reference / reference_to pair — the callee on main (packages/core/src/utils/reference-keys.ts:156-171) stamps exactly those two from reference_to ?? reference ?? referenceTo. The brief's "four-key canonicalisation the PR body names" is the body naming it as the second half (#8873); it is not in this diff. Consumer now receives, for a by-name object def whose stored doc spells one key, both keys with the producer's value; for defs already carrying both, non-relational fields, or non-object types: byte-identical to before. Nothing removed, nothing overwritten. Exports: app-shell index unchanged; core's three exports byte-identical — the edited block reference-keys.ts:50-70 is a free-standing comment not attached to any declaration, so it does not reach dist/*.d.ts.

F2 — Correctness vs ruling first half (5605081157 §Q1: "C 先做 … 第二条服务路径对已经上线的 reference/reference_to 盖章"). Exactly that. Parity verified on main: LIST path ensureType normalises at MetadataProvider.tsx:705-707 before byName.set at :716; BY-NAME path :787-789 did not. The third byName.set writer at :997 is the session-cache seed for type app only (getEntry('app'), loadFromSession('app') :976-1000) — not an object path, so the two-path enumeration is complete. Preview branch (preview.get) flows through the same .then. refresh/invalidate only clear/delete byName (:811-841); no un-normalised repopulation. Production call sites on main: exactly two (MetadataProvider.tsx:706, data-objectstack/src/index.ts:4488) — the docblock's "three" is now true. Five in-repo object callers confirmed (7 useMetadataItem( sites minus flow :280 / permission :339 in metadata-viewer.tsx).

F3 — Seam with #8873. git merge-tree --write-tree 8868 8873 → tree 3153004fc0, clean auto-merge of reference-keys.ts, exit 0. Merged blob − 8868 head = exactly 8873's +178/−5; merged − 8873 head = exactly 8868's docblock hunk (:60-79); merged :63 reads "three production call sites". Ordering independent: 8868-first → by-name path gets the pair stamp, and when 8873 extends the shared callee the by-name path inherits the four-key arm with zero further edits; 8873-first → by-name path stays the pre-existing hole for every key until 8868 lands (no regression; the hole ships today). No double canonicalisation: at most one callee invocation per provider-served object (cache hit returns early :768-771; list fetch replaces byName objects :713-716), and the callee is idempotent (=== undefined guards :168-169; 8873's arm skips declared keys, guards !== undefined, snapshots Object.keys) — pinned reference-keys.test.ts:60-63. Observation, non-blocking: neither PR pins the composed by-name behaviour for the four keys (8873's 25 pins are callee-level); owed by whichever lands second or the card's closing stroke.

F4 — Scope. 5 files, all pertinent. The two comment-only core edits (reference-keys.ts:50-70, __tests__/reference-keys.legacyWarning-6837.test.ts:65-71) correct a count this diff itself falsified — caused, not adjacent. No content/docs/releases/. No unrelated files.

F5 — Changeset. .changeset/7650-metadata-item-reference-canonicalisation.md'@object-ui/app-shell': patch. app-shell is published (private unset, files: dist…). Patch is the right level: bug fix restoring parity with already-shipped list-path behaviour (non-determinism by cache order), no new capability. @object-ui/core touched only in a non-emitted comment → no entry owed; presence gate is declare-once (check-changeset-presence.mjs header) and green (Changeset Declaration, Changeset Bump Policy). @object-ui/react code unchanged. Fixed group bumps together regardless.

F6 — Tests. 6 cases, all reading the value a consumer gets from ctx.getItem (never internals). Per-key pins both directions (:86, :100), array container (:114), negative non-object (:127), negative no-overwrite with both keys present and deliberately inconsistent (:144), cache stickiness (:162). Fixture cloned per call so in-place stamping cannot leak between cases. "Already-canonical served unchanged" control: covered at PR level by the no-overwrite pin and fully at callee level (reference-keys.test.ts:37 never overwrites; :60-63 idempotent full-def toEqual). Non-blocking gap: no by-name case asserting a fully canonical def is served toEqual its input; one added case would close it — not required for PASS given the call site is one guarded line and the callee pin holds the property. Dev ablation (4 red by name / 2 green = the two negatives) is structurally consistent with the pins; reviewer-side evidence is the 4 green shards on head.

F7 — Docs. No published prose states the old "two choke points" count (skills/**, content/docs, package READMEs: nil; skills/objectui/guides/project-setup.md:269 lists useMetadataItem by name only). The changeset is the reader-facing note. Nothing owed.

F8 — Part of handling. First line Part of objectui#7650; mechanical scan of PR body, both commit messages and the changeset for close|fix|resolve adjacent to a ref: none. Card stays open. Correct.

F9 — Carrier (--pair exit 4). Not a defect of this diff. Landing pre-check ② (--pair = 0) cannot pass until Clause-②: yes is added to claim comment 5604856251. Per the PM's own reading (5606083077 §五) the script's ⛔ at check-clause2-carriers.mjs:198 forbids fabricating a Claim: for another seat (absent), not the missing remedy the script prints at :818; the comment's author (os-zhuang seat) can edit it via REST PATCH /repos/objectstack-ai/objectui/issues/comments/5604856251 where MCP lacks an edit tool. A separate comment would read as misplaced (still exit 4). Tracked: objectstack#17213.

Acceptance notes


Generated by Claude Code

Copy link
Copy Markdown
Collaborator

Landing provenance — director seat, summon #18 segment 6 (session_017Js5kTpTtxieBjPyScgxJ3, 2026-09-10T00:21:10Z). Clearing needs:contract-review on both carriers (objectui#8868 + card objectui#7650) on the strength of the contract-review-tier PASS at #8868 (comment) (head 31a075788d, unchanged). Landing pre-checks (contract-review.md ①②③): ① tier verdict on the card (pointer posted); ② check-clause2-carriers.mjs --pair 8868 exit 0; ③ 33 check-runs on head, 0 red / 0 in progress; governed-surface test exit 0 (not governed); mergeable_state: clean. Next: ready → auto-merge (SQUASH) → merge-queue entry, per landing-operations.md. Executed under the maintainer's 2026-09-09 13:4xZ order 「把当前的契约复审全部处理完」 precedent; the dispatching seat keeps ACCEPT/landing-window duties (MERGED confirmation + card close-out) if it is back before the queue finishes — otherwise this seat closes out.


Generated by Claude Code

@huangyiirene
huangyiirene marked this pull request as ready for review September 10, 2026 00:21
@huangyiirene
huangyiirene added this pull request to the merge queue Sep 10, 2026
Merged via the queue into main with commit cccd8ec Sep 10, 2026
35 checks passed
@huangyiirene
huangyiirene deleted the claude/issue-7650-item-serve-path-reference-keys branch September 10, 2026 00:51
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.

3 participants