fix(react): refuse a node-gate predicate parked under props by name (objectui#9108) - #9400
Conversation
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
|
| 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) | |
| 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. TheCheck console performance budgetstep 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
|
| 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) | |
| 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. TheCheck console performance budgetstep 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
维护者速读(终稿)—— PR objectui#9400 · 等你合并
改了什么控制台里有两种写法可以给一个组件挂配置: ⛔ 没有让 为什么改协议本身就按名字拒绝 风险与代价(含回滚)
席位意见建议合并,并顺手确认
你要做的合并这条 PR(受管面,只能人工合)—— 顺带一句: Generated by Claude Code |
Fixes #9108
Implements the maintainer ruling of 2026-09-13 (issue comment
5653272498, verbatim 「同意」 on thedomain:specseat's recommendation): a node-gate predicate parked under the legacypropsconfig-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
SchemaRendererhoistsproperties.*onto the node; nothing copiesprops.*, 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
schema.KEYstays 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.visibleWhen/visible/visibleOn/visibility/hidden/hiddenOn/disabled/disabledOn) are named on the console when parked underprops, with a migration line.console.error, ⛔ no__DEV__gate — see the stated decision below.The key set is derived, not re-listed
NODE_GATE_PREDICATE_KEYS=VISIBILITY_CHAIN_KEYS∪ENABLEMENT_NODE_GATE_KEYS, i.e.PREDICATE_CHAIN_KEYSminusENABLEMENT_RENDERER_KEYS.enabledis 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 ownpropsdrop 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 fiveelement:*readProps()sites plusview:simple), the only node-gate predicate key any of them reads out of the bag isdisabled(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 isdisabledOn, so those rows stay refused on the bag-reading family too. Both directions are pinned.The reporter carries no
__DEV__gate. Two established postures in this tree, and this leg takes a leg of each:unevaluatedExpression.ts, this repo's refusal tier — its neighbourreportDroppedPropsBagwarns because it reports a value that was dropped; the ruling's word here is refused, andvisibilityDiagnostic.tsstates the split explicitly ("not the refusalreportUnevaluatedExpressionsemits");__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
reportDroppedPropsBagposture instead, it is a one-line change at the call site (wrap inif (__DEV__)) plus the corresponding sentence in the changeset. Nothing else depends on it.The governed companion
skills/objectui/rules/protocol.mdsaid a key underpropsis "silently dropped" — true today, and false for these eight keys the moment this lands. Only the symptom sentence moves; the❌ WRONG/✅ CORRECTclassification 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
propspopulation — 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
propspredicate bags acrossexamples/**,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 (slotobjectui-9108).pnpm exec vitest run packages/react/src/__tests__/SchemaRenderer.propsBagLoudRefusal.test.tsx—VERDICT command-exit 0,Test Files 1 passed (1)/Tests 26 passed (26)pnpm exec vitest run packages/react/—VERDICT command-exit 0,Test Files 84 passed (84)/Tests 1010 passed (1010)pnpm turbo run build --filter='@object-ui/react^...' --concurrency=2—Tasks: 4 successful, 4 totalpnpm --filter @object-ui/react type-check—VERDICT command-exit 0.93452d1.tsc -p packages/react/tsconfig.test.json --listFilesconfirms the pin,SchemaRenderer.tsxandpropsBagDiagnostic.tsare each inside the judged project (1 hit each) — the type check is not passing by excluding the file it is supposed to judge.pnpm exec turbo run lint --concurrency=2—VERDICT command-exit 0,Tasks: 47 successful, 47 total.@object-ui/react:lint—356 problems (0 errors, 356 warnings); the workflow sets no--max-warningsdeliberately.node scripts/check-lint-coverage.mjs—46/46 packages linted, 0 with outstanding errors (0 total)node scripts/check-changeset-presence.mjsexit 0 —2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)Lintjob's CLI self-checkpnpm checkexit 0 —✓ All checks passedcheck: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 ⛔ noevaluate…Predicatecall site moved)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-examplesprintedPRECONDITION NOT MET (exit 2) — the example program was NOT run, andcheck:readme-exportsfailed with 73 rows every one of which readsits 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 --testover all five changed paths — exit 3, namingskills/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.
c367c810a…→7c8888eb2…(MOVED), LIVE markerreportRefusedPropsPredicate(1 → 0, DEAD marker 0 → 1.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.git checkout HEAD -- ABSOLUTE_PATH(⛔ never a bare checkout), blob back to the HEAD blobc367c810a…, markers back to 1/0,git diff HEADempty. The script carriedtrap … EXIT INT TERMwith an absolute path fromgit rev-parse --show-topleveland treats an empty hash as FAILURE.distsits between the mutation and the run: the pin imports../SchemaRendererand../utils/propsBagDiagnosticrelatively, 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/mainhas 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_KEYSare 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