Skip to content

fix(react): refuse a node-gate predicate parked under props by name (objectui#9108) - #9400

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-9108-props-bag-loud-refusal
Sep 13, 2026
Merged

fix(react): refuse a node-gate predicate parked under props by name (objectui#9108)#9400
os-zhuang merged 2 commits into
mainfrom
claude/issue-9108-props-bag-loud-refusal

Conversation

@os-tesla

@os-tesla os-tesla commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator

Fixes #9108

Implements the maintainer ruling of 2026-09-13 (issue comment 5653272498, verbatim 「同意」 on the domain:spec seat's recommendation): a node-gate predicate parked under the legacy props config-bag alias is refused by name, loudly — ⛔ not honoured, ⛔ not silently dropped.

PR #9144 implemented the opposite arm and is closed. Per the ruling's execution item 3 its machinery was read rather than rebuilt: the detection half (propsWithoutCanonicalKeys(schema.props, schema.properties) evaluated at the node-gate site) is lifted verbatim from that PR and re-wired from "honour" to "refuse".

The defect

SchemaRenderer hoists properties.* onto the node; nothing copies props.*, and both node gates read the post-hoist node. So a predicate that arrived under the alias was never one of the keys either gate could see — fail-open and silent by construction: a gate that never bit renders exactly like a gate that said yes.

What lands

Verdicts ⛔ none move. Nothing is hoisted, schema.KEY stays undefined for a renderer declared as ({ schema }), and every element receives the byte-identical props bag it received before. The alias is refused, not honoured.
What is new the eight node-gate predicate keys (visibleWhen / visible / visibleOn / visibility / hidden / hiddenOn / disabled / disabledOn) are named on the console when parked under props, with a migration line.
Severity / gating console.error, ⛔ no __DEV__ gate — see the stated decision below.
Rate limit one line per distinct authoring bug for the lifetime of the page (message-keyed dedupe, the objectui#6038 shape).

The key set is derived, not re-listed

NODE_GATE_PREDICATE_KEYS = VISIBILITY_CHAIN_KEYSENABLEMENT_NODE_GATE_KEYS, i.e. PREDICATE_CHAIN_KEYS minus ENABLEMENT_RENDERER_KEYS. enabled is deliberately excluded: no gate in this file consults it (the action renderers read it one layer down and negate it), so refusing it here would state, of a key this file never asks about, that a gate in this file could not see it. Its own props drop stays objectui#6708's subject.

One measured carve-out, and it is exactly one key wide

Across every renderer that reads a config bag (readsPropsBag — the five element:* readProps() sites plus view:simple), the only node-gate predicate key any of them reads out of the bag is disabled (elements.tsx: disabled={props.disabled || running}; text-input.tsx). So { "type": "element:button", "props": { "disabled": true } } really does produce a disabled button and is not refused — a refusal there would send the author looking for a defect that is not on their screen. ⛔ No visibility-chain key is read from a bag by any renderer in this repo, and neither is disabledOn, so those rows stay refused on the bag-reading family too. Both directions are pinned.

⚠️ The one stated decision, flagged for the reviewing seat

The reporter carries no __DEV__ gate. Two established postures in this tree, and this leg takes a leg of each:

  • severity from unevaluatedExpression.ts, this repo's refusal tier — its neighbour reportDroppedPropsBag warns because it reports a value that was dropped; the ruling's word here is refused, and visibilityDiagnostic.ts states the split explicitly ("not the refusal reportUnevaluatedExpressions emits");
  • always-on from the unresolvable-predicate leg that objectui#6038 took out of __DEV__ (ruling 2026-08-25, option B: "the silence is no longer an accepted property"), because a node gate that has stopped biting in production is a class-1 defect that must not sit live and undiscovered. This card is that class in its widest form — the gate never bit at all, on every render — and the ruling's own confidence gap is about exactly the population a __DEV__ gate would silence.

⇒ If the seat prefers the reportDroppedPropsBag posture instead, it is a one-line change at the call site (wrap in if (__DEV__)) plus the corresponding sentence in the changeset. Nothing else depends on it.

The governed companion

skills/objectui/rules/protocol.md said a key under props is "silently dropped" — true today, and false for these eight keys the moment this lands. Only the symptom sentence moves; the ❌ WRONG / ✅ CORRECT classification and both JSON examples are untouched.

skills/** net line count: +3 (6 added, 3 removed, one file, one hunk) — inside the dispatched ≤ +4 budget.

The production props population — asked for by the ruling, answered here

NOT MEASURED, and it is not measurable from this seat. The ruling asks whether the implementer could measure authored metadata in production. This container reaches this repository (git + repo-scoped GitHub REST) and nothing else: there is no deployment, no tenant database and no stored-document corpus in reach, and no instrument in this repo reads one. The in-repo census stands as the closed PR left it (ZERO authored props predicate bags across examples/**, content/docs/** and the skills corpus, with lit controls). ⇒ the ruling's mitigation is what shipped instead: the refusal carries the named migration line, so an author out there whose keys were silently doing nothing is told which key to move and where, rather than having half of them quietly start working.

Verification

All on final HEAD 93452d1, in the dedicated worktree. Exit codes captured by redirect before any pipe; every verdict below is quoted from the line the tool itself printed. Heavy runs went through the shared verify lock (slot objectui-9108).

step reading
new pin suite pnpm exec vitest run packages/react/src/__tests__/SchemaRenderer.propsBagLoudRefusal.test.tsxVERDICT command-exit 0, Test Files 1 passed (1) / Tests 26 passed (26)
affected package, whole pnpm exec vitest run packages/react/VERDICT command-exit 0, Test Files 84 passed (84) / Tests 1010 passed (1010)
dependency closure pnpm turbo run build --filter='@object-ui/react^...' --concurrency=2Tasks: 4 successful, 4 total
type check pnpm --filter @object-ui/react type-checkVERDICT command-exit 0. ⚠️ The FIRST attempt was a real red and is recorded as one: TS7006 on two callbacks in my own new pin, fixed in 93452d1. tsc -p packages/react/tsconfig.test.json --listFiles confirms the pin, SchemaRenderer.tsx and propsBagDiagnostic.ts are each inside the judged project (1 hit each) — the type check is not passing by excluding the file it is supposed to judge.
lint — FULL, ⛔ no narrowing claimed pnpm exec turbo run lint --concurrency=2VERDICT command-exit 0, Tasks: 47 successful, 47 total. @object-ui/react:lint356 problems (0 errors, 356 warnings); the workflow sets no --max-warnings deliberately. node scripts/check-lint-coverage.mjs46/46 packages linted, 0 with outstanding errors (0 total)
changeset node scripts/check-changeset-presence.mjs exit 0 — 2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)
the Lint job's CLI self-check pnpm check exit 0 — ✓ All checks passed
gates, all exit 0 check:control-bytes (scanned 7572 tracked text file(s)) · check:new-line-citations (0 new citation(s)) · check:unreferenced-sources · check:comment-mask-corpus (within the standing objectui#7882 residue ceiling) · check:entry-guard + --self-test (63 cases pass) · check:test-path-roots · check:vi-mock-specifiers · check:vi-mock-override-shape · check:shell-escape-residue · check:changeset-claims · check:skills-paths (88/89, 1 baselined) · check:skill-examples · check:skill-eval-tokens · check:readme-exports · check:upstream-port-parity · check:bash32-floor · check-doc-expression-carriage (report-only, exit 0 — the leg that reddened PR #9144 is green here because ⛔ no evaluate…Predicate call site moved)
plus a direct scan grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' over all five changed files — no match

Two non-zero first passes that are NOT recorded as failures, because neither ran: check:skill-examples printed PRECONDITION NOT MET (exit 2) — the example program was NOT run, and check:readme-exports failed with 73 rows every one of which reads its type entry ./dist/index.d.ts is not on disk — run pnpm build first, in packages this diff does not touch (cli, plugin-ai, plugin-gantt, plugin-map, plugin-markdown, plugin-timeline). Both exit 0 after the scoped builds they name.

The governed classification is measured, not assumed

node scripts/check-governed-queue-guard.mjs --test over all five changed paths — exit 3, naming skills/objectui/rules/protocol.md, "One governed path governs the WHOLE pull request". ⇒ this PR is parked as a draft awaiting a human merge: ⛔ not flipped ready, ⛔ not enqueued, ⛔ no auto-merge, ⛔ no seat approval.

Ablation — the pin can fail, proven on disk and restored by state

The repair's single leg (the refusal call at the node-gate site) was removed and the suite re-run.

  • mutation proven BEFORE the run, in both directions: blob c367c810a…7c8888eb2… (MOVED), LIVE marker reportRefusedPropsPredicate( 1 → 0, DEAD marker 0 → 1.
  • ablated run: Tests 12 failed | 14 passed (26) — the twelve red are exactly the refusal rows (expected [] to have a length of 1); the fourteen green are the verdict rows and the silence controls, which is the correct split and is itself the reason both halves exist in one file: an ablation of the refusal must not move a verdict.
  • restore proven BY STATE, ⛔ never by an exit code: git checkout HEAD -- ABSOLUTE_PATH (⛔ never a bare checkout), blob back to the HEAD blob c367c810a…, markers back to 1/0, git diff HEAD empty. The script carried trap … EXIT INT TERM with an absolute path from git rev-parse --show-toplevel and treats an empty hash as FAILURE.
  • No dist sits between the mutation and the run: the pin imports ../SchemaRenderer and ../utils/propsBagDiagnostic relatively, from source in the same package, so there is nothing to rebuild and no stale artefact to give a false green.

Base

Branched from da45e6be7. origin/main has since moved by one commit (#9397, app-shell) — disjoint from this file face, so ⛔ no merge was taken. objectui#9107 (the enablement half of objectui#9100) was already landed at branch point: PREDICATE_CHAIN_KEYS / ENABLEMENT_NODE_GATE_KEYS are present in the base and this change derives from them rather than re-declaring them.

维护者速读(草稿)

改了什么。 节点上的配置袋有两种拼写:properties(规范拼写)与 props(注明的遗留别名)。渲染器只把 properties.* 提升到节点上,props.* 一个都不提;而可见性/可用性这两道门只读提升后的节点。于是作者写在 props 里的谓词键(如 visible: false)一个字都没被门看到,节点照常渲染。本轮不改任何判定,只让这八个门控谓词键在 props 里出现时被点名拒绝并打印迁移指引。

为什么改。 这是最坏形态的门:失败方向朝,而且无声——「门从没咬过」与「门说了可以」在屏幕上一模一样,用户、截图、快照都找不出来,只能靠数。维护者 2026-09-13 裁决选了「拒绝」而不是「认下来」:认下来会让 8 个谓词键开始工作、其余的继续被静默丢弃,而部分能用比完全不能用更难学——作者会归纳出「props 没问题」,下一个键再撞一次,原因却离得更远了。

风险与代价(含回滚)。 渲染判定零变化,元素收到的 props 袋逐字节不变,所以对现有页面没有行为风险;唯一的新增是控制台上的一行。代价有两处:① 该行在生产环境也打(理由见上方「stated decision」),控制台会多出内容,已按「每个不同的书写错误一行、页面生命周期内不重复」限流;② skills/ 那条规则文案改了一句,需要人工合并。回滚成本极低:整个 PR 就是一次 revert,不涉及任何数据或契约迁移;若只想收回生产环境那半,把调用点包进 if (__DEV__) 即可。

席位意见。

你要做的。 ① 本 PR 触及 skills/**(受管面),按规定停在 draft 等人工合并,⛔ 不进队列、⛔ 不自动合并;请由 contract-review 档席位先看 skills/objectui/rules/protocol.md 那一个 hunk(独立可读,只动了症状句)。② 决定上方那条「生产环境也打 console.error」是否保留——保留则直接合;不保留则一行改动。③ 生产环境 props 存量无法从本席位测量,若维护者手里有线上元数据语料,建议在合并前扫一次这八个键。


Generated by Claude Code


Generated by Claude Code

A node may spell its config bag `properties` (the spec spelling) or `props`
(the annotated legacy alias). `SchemaRenderer` hoists `properties.*` onto the
node; nothing copies `props.*`, and both node gates read the post-hoist node,
so a predicate that arrived under the alias was never one of the keys either
gate could see. Fail-open and silent by construction: a gate that never bit
renders exactly like a gate that said yes.

The maintainer ruling of 2026-09-13 closed the honour arm and ruled REFUSE:
honouring the alias would have made eight predicate keys work while the rest
stayed silently dropped, and partly working is harder to learn from than not
working. So no verdict moves here. Nothing is hoisted, `schema.<KEY>` stays
undefined for a renderer declared as `({ schema })`, and every element receives
the byte-identical props bag it received before. What changes is that the eight
node-gate predicate keys are named on the console when they are parked under
`props`, with the migration that fixes them.

The reporter is `console.error` and carries no `__DEV__` gate: severity from
`unevaluatedExpression.ts`, this repo's refusal tier, and always-on from the
unresolvable-predicate leg that objectui#6038 took out of `__DEV__` because a
node gate that has stopped biting in production must not be able to sit live
and undiscovered. Rate-limited to one line per distinct authoring bug.

One measured carve-out: `disabled` on a bag-reading `element:*` node is
honoured by that renderer itself, so it is not refused there.

The governed rule book moves with it: `skills/objectui/rules/protocol.md`
said a key under `props` is "silently dropped", which becomes false for these
eight keys the moment this lands. The WRONG/CORRECT classification is
unchanged; only the symptom sentence moves.

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

Copy link
Copy Markdown
Contributor

⚠️ Console Performance Budget — gauge not trustworthy

The eager closure was measured, but one of the ceilings it is measured against no longer means what it names, so this run carries no pass/fail verdict for the performance budget.

This is not a budget violation. Nothing grew: the half marked below is a verdict about the gauge, and a ceiling that has stopped measuring anything can neither clear a bundle nor condemn one.

Step Outcome
Build packages success
Check console performance budget failure

Which half objected:

Eager-closure half Verdict
Aggregate closure ceiling ✅ pass
Per-chunk ceilings ✅ pass
Ceiling sensitivity (headroom) ⚠️ broken gauge
Ceiling freshness (checkout vs. base branch) ✅ pass

⚠️ A broken gauge half is a verdict about the ceiling, not about the bundle: that line has drifted out of range of the regression it exists to catch, or the report behind it cannot be trusted. It does not say anything grew. The Check console performance budget step log carries the ceiling and the number it was compared against.

Reason: The entry chunk measured 144.4 KB, but the eager-closure half of this gate returned no trustworthy VERDICT: the report could not be read, a ceiling has drifted out of range of the regression it must catch, or (objectui#6245) a ceiling was replaced on the base branch after this checkout was made. The step log says which. This is not a passing budget — and it is not a size regression either.

See the workflow run for details.


📦 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) 502.02KB 115.16KB
core (index.js) 8.52KB 3.41KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 211.58KB 58.68KB
fields (index.js) 247.89KB 62.50KB
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) 8.87KB 3.64KB
i18n (index.js) 5.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 32.15KB 10.49KB
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.83KB 10.95KB
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.25KB 13.99KB
plugin-charts (index.js) 71.34KB 19.90KB
plugin-chatbot (index.js) 195.34KB 46.51KB
plugin-dashboard (index.js) 131.22KB 34.59KB
plugin-designer (index.js) 215.94KB 44.33KB
plugin-detail (index.js) 253.46KB 65.85KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.77KB 34.17KB
plugin-gantt (index.js) 166.95KB 41.04KB
plugin-grid (index.js) 211.66KB 57.50KB
plugin-kanban (index.js) 46.00KB 14.30KB
plugin-list (index.js) 112.58KB 27.65KB
plugin-map (index.js) 20.64KB 6.86KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.41KB 11.93KB
plugin-timeline (index.js) 30.07KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.42KB 20.79KB
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) 99.04KB 32.62KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
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.66KB 2.50KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 14.82KB 4.99KB
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.04KB 5.36KB
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

`packages/react`'s `type-check` runs `tsc -p tsconfig.test.json`, which judges
the pin file too; `errorSpy.mock.calls` gives its callbacks no contextual type,
so the two readers were TS7006.

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

Copy link
Copy Markdown
Contributor

⚠️ Console Performance Budget — gauge not trustworthy

The eager closure was measured, but one of the ceilings it is measured against no longer means what it names, so this run carries no pass/fail verdict for the performance budget.

This is not a budget violation. Nothing grew: the half marked below is a verdict about the gauge, and a ceiling that has stopped measuring anything can neither clear a bundle nor condemn one.

Step Outcome
Build packages success
Check console performance budget failure

Which half objected:

Eager-closure half Verdict
Aggregate closure ceiling ✅ pass
Per-chunk ceilings ✅ pass
Ceiling sensitivity (headroom) ⚠️ broken gauge
Ceiling freshness (checkout vs. base branch) ✅ pass

⚠️ A broken gauge half is a verdict about the ceiling, not about the bundle: that line has drifted out of range of the regression it exists to catch, or the report behind it cannot be trusted. It does not say anything grew. The Check console performance budget step log carries the ceiling and the number it was compared against.

Reason: The entry chunk measured 144.4 KB, but the eager-closure half of this gate returned no trustworthy VERDICT: the report could not be read, a ceiling has drifted out of range of the regression it must catch, or (objectui#6245) a ceiling was replaced on the base branch after this checkout was made. The step log says which. This is not a passing budget — and it is not a size regression either.

See the workflow run for details.


📦 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) 502.02KB 115.16KB
core (index.js) 8.52KB 3.41KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 211.58KB 58.68KB
fields (index.js) 247.89KB 62.50KB
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) 8.87KB 3.64KB
i18n (index.js) 5.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 32.15KB 10.49KB
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.83KB 10.95KB
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.25KB 13.99KB
plugin-charts (index.js) 71.34KB 19.90KB
plugin-chatbot (index.js) 195.34KB 46.51KB
plugin-dashboard (index.js) 131.22KB 34.59KB
plugin-designer (index.js) 215.94KB 44.33KB
plugin-detail (index.js) 253.46KB 65.85KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.77KB 34.17KB
plugin-gantt (index.js) 166.95KB 41.04KB
plugin-grid (index.js) 211.66KB 57.50KB
plugin-kanban (index.js) 46.00KB 14.30KB
plugin-list (index.js) 112.58KB 27.65KB
plugin-map (index.js) 20.64KB 6.86KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.41KB 11.93KB
plugin-timeline (index.js) 30.07KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.42KB 20.79KB
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) 99.04KB 32.62KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
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.66KB 2.50KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 14.82KB 4.99KB
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.04KB 5.36KB
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
Collaborator Author

维护者速读(终稿)—— PR objectui#9400 · 等你合并

domain:ui 席位终稿。草稿由 dev 写,本席对照自己读过的 diff 校正,并填入席位意见。⛔ 你只需要读这一条。

改了什么

控制台里有两种写法可以给一个组件挂配置:propertiesprops。作者若把"这个节点显不显示"的开关写在 props 下面,今天它完全不起作用,而且一声不吭 —— 节点照常显示,作者以为自己关掉了。这次改动让它当场报错并点名:告诉作者这个键必须写在哪里。

⛔ 没有让 props 开始生效。裁决明确否掉了那条路,理由是:props 下有 8 个键能生效、其余继续静默失效,比全都不生效更难学 —— 作者会归纳出"props 没问题",下次在别的键上再撞一次,而那时原因离得更远了。

为什么改

协议本身就按名字拒绝 props。今天的实现比协议宽容,而这份宽容是静默的 —— 这正是最容易让 AI 批量写错元数据的形状:声明了一个运行时根本不兑现的能力。

风险与代价(含回滚)

  • 行为变化只有一处:多了一行控制台报错。 任何节点的显示/隐藏结果都没有变,元素收到的配置一字不差 —— 这一点由"消融测试"证明:把报错那条腿拿掉,12 条报错断言变红,14 条结果断言仍然全绿。
  • ⚠️ 一个需要你拍板的点:这行报错在生产环境也会出现,而且用的是 error 级别,不是 warning 依据是 2026-08-25 你对 objectui#6038 的裁决(「生产环境不再接受静默」),那条裁决我核过原文,确实如此 —— 但它当时裁的是 warning 升到 error 是这次的推断,不是那条裁决决定的。同一个作者错误一个页面周期内只打一行,所以不会刷屏。若你更想要 warning,改一行即可。
  • 回滚:整条 PR 可以直接 revert,没有数据迁移、没有存量影响。

席位意见

建议合并,并顺手确认 error 还是 warning 代码半边我逐条核过:复用了上一条被关掉的 PR 的探测逻辑而不是重写(裁决点名要求的)、拒绝的键集有实测依据(排除 enabled,因为那个文件里没有任何闸门读它)、还有一个实测出来的例外(disabled 在真读配置包的渲染器上不拒,否则就是狼来了)。

⚠️ 但技能面那个文件(skills/objectui/rules/protocol.md)本席没有复核,按规矩交给 skills 席 —— 那是对外发布的技能包,不归本车道审。它只动了一句"症状描述",净增 3 行,在我给的 4 行预算内。

你要做的

合并这条 PR(受管面,只能人工合)—— 顺带一句:error 还是 warning?


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 13, 2026 22:42
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 13, 2026
Merged via the queue into main with commit efc1c9c Sep 13, 2026
37 of 38 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-9108-props-bag-loud-refusal branch September 13, 2026 23:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

3 participants