Repository navigation
feat(react)!: unbind the data-source adapter from the expression scope, and point bind at the scope channel - #9369
Conversation
objectui#9308, maintainer ruling 2026-09-13, option B. b1 — `SchemaRenderer` no longer writes `data: dataSource` into the evaluator scope. Against a conformant `DataSource` adapter every `data.*` path already resolved `undefined`, so this tier published a root it could not answer. Same principle `ExpressionProvider` states and objectui#8155 / objectui#8166 already applied one tier up. b2 — `useDataScope` resolves `bind` against `usePredicateScope()`, the ambient scope a host publishes through `PredicateScopeProvider`, instead of walking the injected adapter. No published key is added; the channel already existed. Also re-aims `reportAdapterOnlyDataPredicate` at the `data` the evaluator really bound, and moves its copy off the "data is the adapter" claim. Left on the adapter it would have gone silent for exactly the hosts this change breaks and loud for the ones whose scope-channel `data` answers. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ
…d it objectui#9308 + objectui#9346. `Doc Snippet Type Check` was red on `main` with three fences that taught publishing page values through `dataSource` and reading them back under `data.*`. Under the 2026-09-13 ruling that teaching is wrong, so the Data Context passages of `content/docs/guide/schema-rendering.md` and `packages/react/README.md` move to `PredicateScopeProvider`, and the README sentence asserting that `dataSource` "is what the evaluator sees under the name `data`" — which the gate cannot see — goes with them. objectui#8021's pin is RE-DERIVED, not relaxed: leg A still reads the page on every run, leg C's wiring and leg D's expression swap to the new teaching, and a new leg E pins the adapter seam as a non-root. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ
|
| 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.3 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.51KB | 19.97KB |
| 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.58KB | 57.48KB |
| plugin-kanban (index.js) | 46.01KB | 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) | 96.00KB | 31.71KB |
| 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.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
…fence does Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ
|
| 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.3 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.51KB | 19.97KB |
| 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.58KB | 57.48KB |
| plugin-kanban (index.js) | 46.01KB | 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) | 96.00KB | 31.71KB |
| 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.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
…h through the scope channel objectui#9308 b2 — `useDataScope` resolves the ambient predicate scope, so a pin that fed the bound rows through `SchemaRendererProvider.dataSource` was measuring a channel the hook no longer reads. Both keep the adapter seam mounted, where it is now inert, and neither assertion moved. Found by narrowing the dependent packages to the tests that can reach b1 or b2 at all: 1987 candidate test files, 7 reachable, 4 failures — all in this pair. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ
|
| 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.3 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.51KB | 19.97KB |
| 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.58KB | 57.48KB |
| plugin-kanban (index.js) | 46.01KB | 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) | 96.00KB | 31.71KB |
| 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.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
Contract reviewHead judged: ⛔ First: this seat falsified TWO premises I wrote into its dispatch1. 2. "Confirm the verdict does not move for a conformant host — it should not." ⭐ Falsified, and this is the load-bearing measurement of the whole round: A present-but-undefined root and an absent root are not the same to the engine. So a ① Derived judgments
1 — enumerated rather than counted. new ExpressionEvaluator({
...predicateScope,
current_user: (predicateScope as any)?.user,
...(boundRecord && … ? { record: boundRecord } : null),
page: pageVariables,
});No 3 — I raised the shadowing hazard independently during the analysis: 4 — the reason b2 is safe, re-read per reader on this branch: 6 — the deliverable, and CI is the witness, not the seat. ② Semver
Correct, and it carries the verdict-flip. ⛔ Note the changeset was not edited by me for a spelling convention — there is none, as I had to retract to four seats this round. ③ Boundary flags — and the two questions, ruledQ2 — the verdict flip. RULING: A, as ruled. Proceed. The conservative variant (
Q1 — the published The seat hit two instructions pointing opposite ways — "fix what this round makes false" vs "⛔ do not touch
Independence⛔ For this lane a clause-② review is default-tier self-review plus the gates. The readings carry the verdict. VerdictPASS. Both carriers cleared in the same stroke as this comment.
Generated by Claude Code |
|
…ta` expression root (objectui#9379) The 2026-09-13 maintainer ruling on objectui#9308 (option B) stopped `SchemaRenderer` publishing the injected `DataSource` adapter as the expression root `data`. This guide still taught both halves: flags derived "in the dataSource object", a scope table sourcing `data` from "the `dataSource` passed to `SchemaRendererProvider`", and a trap paragraph whose recommended spelling is the one that now fails. The document is what is wrong here, not the runtime: the guide is moved onto the channel that does publish roots, `PredicateScopeProvider`, in the same shape PR #9369 used for `content/docs/guide/schema-rendering.md` and `packages/react/README.md`. - permission flags are published as an ambient scope and read by the names they were published under; - the scope table gains a `record` row (ADR-0089 D3 makes `record` the runtime-layer row root) and states that `data` is a root only when the host publishes one; - the trap paragraph is re-measured on the built evaluator: a name nothing published makes a predicate fail soft to `true` (the button is hidden for every user), while a text key hands back its own source characters. One bounded repair inside the same paragraph: the flag example derived its booleans from `check(...)`, which answers a `{ allowed, … }` object. An object is truthy, so `${!flag}` was permanently `false` and the gate showed the button to everyone. The example now publishes `can(...)`, which answers a boolean. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015h79niBMyoB1xcaQje3uiz
…ta` expression root (objectui#9379) (objectstack-ai#9669) Fixes objectstack-ai#9379 ⛔ **Governed surface — this PR stays a DRAFT.** `scripts/check-governed-queue-guard.mjs --test skills/objectui/guides/auth-permissions.md` exits **3** and prints: *"One governed path governs the WHOLE pull request … Park it as a DRAFT and leave the merge to the maintainer"*, naming `GOVERNED_APPROVERS: os-zhuang, hotlong`. No ready-flip, no queue, no auto-merge from this seat. ## What is repaired The 2026-09-13 maintainer ruling on objectui#9308 (option B) stopped `SchemaRenderer` publishing the injected `DataSource` adapter as the expression root `data`. `skills/objectui/guides/auth-permissions.md` still taught both halves of the retired wiring. The **document** is what was wrong — the runtime is right — so the guide is moved onto the channel that does publish roots, `PredicateScopeProvider`, in the same shape PR objectstack-ai#9369 used for `content/docs/guide/schema-rendering.md` and `packages/react/README.md`. - permission flags are published as an ambient **scope** and read by the names they were published under; - the scope table gains a `record` row (ADR-0089 D3 makes `record` the runtime-layer row root) and now states that `data` is a root only when the host publishes one; - the trap paragraph is re-derived rather than re-worded (see below). ### The premise, checked rather than relayed `packages/react/src/SchemaRenderer.tsx` carries the decision in its own words: *"`data` is NOT here, and the absence is the decision (objectui#9308, maintainer ruling 2026-09-13 option B)"*. Nothing in this PR asks for `dataSource` to become a real root — that would widen a published surface. Prose only; no package source is touched. ### Re-measured on the built evaluator, not remembered `packages/core/dist` (built from this branch's base, `cf601fff6`), over the guide's own gate `${!canDeleteContacts}` and its `data.`-rooted predecessor: | expression | scope | `evaluateCondition` | `evaluateExpression` | |---|---|---|---| | `${!data.canDeleteContacts}` | `{}` — no root at all | `true` | the source text `${!data.canDeleteContacts}` | | `${!data.canDeleteContacts}` | `{ data: {} }` — adapter-shaped | `true` | `true` | | `${!data.canDeleteContacts}` | `{ data: { canDeleteContacts: true } }` | `false` | `false` | | `${!canDeleteContacts}` | `{ canDeleteContacts: true }` — published as a root | `false` | `false` | | `${!canDeleteContacts}` | `{}` | `true` | the source text `${!canDeleteContacts}` | Two consequences the old paragraph could not state: a bare name **does** resolve when the host publishes it as a root (so the old "reachable only under the `data.` root" sentence is now false in its own right), and the predicate layer fails soft to `true` while the interpolation layer prints the characters you typed. Both are in the new text. ### One bounded repair in the same paragraph The flag example derived its booleans from `permissions.check(...)`, which answers a `{ allowed, … }` **object**. An object is truthy, so `${!canDeleteContacts}` was permanently `false` and the gate showed the button to **every** user — the mirror image of the trap the page warns about. The example now publishes `permissions.can(...)`, which answers a boolean, and the guide says why. Without this the repaired example would be untrue on its own terms. ## The class, re-derived (⛔ triage's "fourth" is not relayed) Instrument, run on this branch's base `cf601fff6`: every tracked `skills/**/*.md`, `content/docs/**/*.md`, `packages/*/README.md` and `README.md`; every line naming `dataSource`, judged against a ±4-line window for a teaching token (`${data`, a `data.` root, `bind`, `useDataScope`, `scope`) and for a correction token (`PredicateScopeProvider`, "not an expression root", objectui#9308, "the ADAPTER"). Raw hits were then adjudicated by hand, because the raw signal does not distinguish the class from the unrelated `PageComponentSchema.dataSource` element-data-source key. **Members of the class (5):** | file | state | |---|---| | `skills/objectui/guides/data-integration.md` | held by open PR objectstack-ai#9378 | | `skills/objectui/guides/schema-expressions.md` | held by open PR objectstack-ai#9378 | | `skills/objectui/rules/protocol.md` | held by open PR objectstack-ai#9378 | | `skills/objectui/guides/auth-permissions.md` | **this PR** | | `skills/objectui/guides/testing.md` | objectui#9380, held serial — ⛔ not touched | **⭐ A sixth candidate, reported and not folded in:** `skills/objectui/guides/page-builder.md` — its integration sequence says *"Provide `dataSource` and contextual data through renderer provider"* and every schema example on the page then reads `${data.metrics.activeUsers}` / `${data.userRole}`. It names no other channel, so a reader wires the retired one. On a governed surface every extra file widens what a human has to approve, so it is left for its own card. **Control, same instrument, same run:** `content/docs/guide/architecture.md` and `content/docs/guide/expressions.md` were surfaced by the identical `dataSource`-plus-teaching-window query and both teach the **correct** root (*"`SchemaRendererProvider`'s `dataSource` is not an expression root"*), so the instrument discriminates and the count above is a reading rather than an empty query. Adjudicated **not** in the class, also by the same run: `content/docs/guide/{ci-cd-pipeline,data-source,user-state-persistence}.md`, `content/docs/rfcs/0001-clipboard-paste.md`, `packages/{plugin-detail,plugin-list,react}/README.md` — every one of those names the adapter's own methods or the per-element `dataSource` spec key, neither of which is this class. ## Gates — each verdict is the gate's own line | gate | verdict | |---|---| | `node scripts/check-skill-examples.mjs` | exit 0 — *"Every marked skill example holds up against the built types."* `Marked: 15 ts fence(s) (floor 13), 70 json fence(s) (floor 70)`; `Semantic phase: 15 of 15 ts fence(s) judged, 0 failed`. The one `tsx` fence this PR adds is inside that 15. | | `node scripts/check-skills-paths.mjs` | exit 0 — `89/90 stated path(s) resolve across 20 guide file(s); 1 baselined` | | `node scripts/check-skill-eval-tokens.mjs` | exit 0 — *"Every must_contain token is taught by its own skill bundle."* | | `node scripts/check-changeset-presence.mjs` | exit 0 — *"No source or published contract of a released package changed in this range, so no changeset is owed."* ⇒ nothing is owed; no label is involved in that verdict. | | `node scripts/check-new-cross-file-line-citations.mjs` | exit 0 — `0 new citation(s), enforcement report-only` | | `pnpm check:control-bytes` | exit 0 — `scanned 7788 tracked text file(s)`; plus a direct `grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'` over the changed file, exit 1 (no match) | | `pnpm exec vitest run packages/components/src/__tests__/skill-guide-provider-envelope.test.tsx scripts/__tests__/check-skill-eval-tokens.test.ts scripts/__tests__/check-skill-examples.test.ts scripts/__tests__/check-skills-paths.test.ts` | exit 0 — `Test Files 4 passed (4)`, `Tests 201 passed (201)` | The four test files are the ones `node scripts/markdown-test-inputs.mjs --list` names as readers of `skills/objectui/**` and `.claude/skills/**`. ESLint is not owed by this diff: `eslint.config.js` declares no markdown surface, and `pnpm lint` is CI's repo-wide run either way. Build: `turbo run build --concurrency=2 $(node scripts/check-skill-examples.mjs --build-filter)` — `29 successful, 29 total` — so the fences above were judged against **built** `dist/*.d.ts`. ## Governed-surface size readings | reading | before | after | net | |---|---|---|---| | `skills/objectui/guides/auth-permissions.md` | 367 lines | 403 lines | **+36** | | whole published catalogue (`skills/**/*.md`) | 4604 lines | 4640 lines | **+36** | One file, one section. The added lines are the measured verdict table, the `record` row, and one `tsx` fence that the examples gate now type-checks; a second scope fence was drafted and dropped as duplicate teaching. ## Acceptance notes - `noted, not filed: skills/objectui/guides/auth-permissions.md`'s provider-composition example still nests only `SchemaRendererProvider`, with no `PredicateScopeProvider` beside it. It is not false — the adapter belongs there — but a reader copying it gets no scope. Carrier: whoever takes the `page-builder.md` card above, which needs the same nesting shown once. ## 维护者速读(草稿) **改了什么** —— 只改一份已发布技能指南 `skills/objectui/guides/auth-permissions.md` 的「表达式可见性」与「表达式作用域」两节。把权限标志的发布通道从已退休的 `SchemaRendererProvider dataSource` 改成 `PredicateScopeProvider`,作用域表补上 `record` 行,并按实测重写那段陷阱说明。⛔ 不动任何运行时代码。 **为什么改** —— 2026-09-13 您对 objectui#9308 的裁决(选项 B)已经让渲染器不再把注入的适配器发布为表达式根 `data`;这份指南两半都还在教。它按人读文档的速度持续制造错写法,并且它 **推荐** 的那个写法今天同样失败,失败方式还和它自己警告的那个不同。 **风险与代价(含回滚)** —— 纯文档,风险面是「教得对不对」,不是运行时。三道技能门禁(examples / paths / eval-tokens)各自的判定行都在上表,均为 exit 0;changeset 门禁自己判定无需 changeset。回滚 = revert 这一个 commit,无迁移、无发版影响。⚠️ 顺带修掉同段里一处独立错误:示例用 `check(...)`(返回对象,恒真)当布尔标志,改成 `can(...)`;不改它,新示例自己就是假的。 **席位意见** —— **你要做的** —— 这是受管面:PR 保持 draft,合并权在您。需要 `os-zhuang` / `hotlong` 其一的 APPROVED review,或由您直接人工合并(人工合并本身即评审记录)。另外请裁决上面那个第六个候选文件 `page-builder.md` 是否单开一卡 —— 本 PR 刻意没有把它折进来。 --- _Generated by [Claude Code](https://claude.ai/code/session_015h79niBMyoB1xcaQje3uiz)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…urce` expression root (objectstack-ai#9378) Fixes objectstack-ai#9370 ⛔ **GOVERNED SURFACE** (`skills/**`, `GOVERNED_SURFACES` `skills-catalog`). This PR parks as a **draft by design** and needs an APPROVED review from an authorized approver (`os-zhuang`, `hotlong`). ⛔ Not flipped ready, not enqueued, no auto-merge. The precedent on this exact surface is objectstack-ai#9352 / card objectstack-ai#9311, merged as `28be0786d`.⚠️ **Merge after objectstack-ai#9369, and this PR states which world it measured in.** Re-derived from my own tree at base `69aa9c017`, not assumed: `SchemaRenderer.tsx` still carries the `data: dataSource` binding, and `SchemaRendererContext.tsx` still reads `const dataSource = context?.dataSource` inside `useDataScope`. **Both halves are still LIVE on this base** — objectstack-ai#9369 is open, not merged. So these pages become true the moment objectstack-ai#9369 lands and are ahead of the code until then. Every evaluator measurement below is from that same pre-objectstack-ai#9369 tree, which is the right place to take it: the evaluator itself is what objectstack-ai#9369 stops feeding, and the evaluator's own behaviour does not move. Sequencing is a maintainer decision, not something this PR can enforce. ## What moved The maintainer ruled objectui#9308 option B on 2026-09-13: `SchemaRenderer` stops publishing the injected `DataSource` adapter as the expression root `data` (b1), and `useDataScope` — what a node's `bind` resolves through — reads the ambient scope a host publishes via `PredicateScopeProvider` instead of walking that adapter (b2). The published skill package still taught both halves. Three files, matching the shape objectstack-ai#9369 used for `content/docs/guide/schema-rendering.md` and `packages/react/README.md`: publish the host values under real names through `PredicateScopeProvider`, read them by those names, and state that `dataSource` is the **adapter** and not a root. | file | what changed | |---|---| | `guides/schema-expressions.md` | the scope table, a new COMPILED wiring example, the migration callout, the `bind` resolution prose, the bound-list comment, debugging-checklist item 3 | | `guides/data-integration.md` | the architecture diagram's channel split, "Static data" rewritten onto the scope channel as a COMPILED example, the `useDataScope` resolution sentence, the `data` root claim | | `rules/protocol.md` | both measured tables' WIRING restated, the `bind` comment and nested-path sentence, plus a callout carrying the retirement and pointing at the verdict flip | ⛔ Untouched, as the card requires: the `object-*` reader-list paragraph and the `data-table` `bind` pothole (objectui#6575 still rules `data-table` does not read `bind`). ## ⭐ The part that is not a renaming A migration note that only swaps provider names is wrong here, because the **verdict moved**. Measured by me on the built evaluator (`packages/core/dist`), with firing controls — ⛔ and the `true` on a missing root belongs to the **predicate layer**, not to the evaluator: | scope, expression | `evaluateExpression` | `evaluateCondition` | |---|---|---| | CONTROL `{ data: { status: 'draft' } }`, `${data.status}` | `"draft"` | `true` | | CONTROL same scope, `Status: ${data.status}` | `"Status: draft"` | `true` | | CONTROL same scope, `${data.nope}` — present root, absent member | `undefined` | `false` | | `{ data: {} }` — adapter-shaped, `${data.status == 'draft'}` | `false` | `false` | | `{ data: undefined }`, same predicate | `false` | `false` | | `{}` — no `data` root, `${data.status}` | `"${data.status}"` | `true` | | `{}` — no `data` root, `Status: ${data.status}` | `"Status: ${data.status}"` | `true` | | `{}` — no `data` root, `${data.status == 'draft'}` | `"${data.status == 'draft'}"` | `true` | The third control is the discriminating one: a root that is PRESENT with an absent member yields `undefined`, while a root that is MISSING yields the template's own source characters. So the two layers answer a missing root differently, and each has its own reader-visible symptom: - **predicate layer** — fails soft to `true`. A `"visible": "${data.status == 'draft'}"` authored from these pages was **hidden on every row** and is now **shown on every row**; spelled `"hidden"` it flips the other way. - **interpolation layer** — does not fail soft at all. A `content` built from a missing root paints the literal characters `${data.status}` on screen. The callout carries one column per layer and tells the reader to read both. It also states that re-publishing `data` restores the OLD always-`false` verdict rather than fixing the gate, and points row gates at `record` — the runtime-layer root under ADR-0089 D3. ## What I decided about the ~two dozen `${data.…}` examples Measured, not estimated: `schema-expressions.md` carries **21** lines spelling `${data.` (one of them is the census's own scope-table row). Repo-wide under `skills/`: 21 here, 14 in `rules/protocol.md`, 5 in `page-builder.md`, 4 in `testing.md`, 2 each in `data-integration.md` and `auth-permissions.md`, 1 in `architecture.md`, plus 4 in `evals/*.json`. **Decision: keep every one of them verbatim and make them reachable, rather than rewrite them to bare roots.** The page's new wiring example publishes a root literally named `data`, and the scope table says in a row that every `${data.*}` example on the page assumes exactly that. Three reasons this is the right divergence from objectstack-ai#9369's `content/docs` shape: 1. **A pin renders those exact spellings.** `skill-guide-provider-envelope.test.tsx`, as re-derived in objectstack-ai#9369, publishes `scope = { data: PROVIDER }` and renders `${data.customers}` / `${data.label}`. Rewriting the guide spellings would move that pin's subject from inside a second pull request — the "green alone, red together" shape. 2. **ADR-0089 D3 does not forbid the name.** `CANONICAL_ROOT_BY_LAYER` puts `data` at the metadata layer. What was retired is the renderer AUTO-publishing the adapter there, not the name. 3. **Most of those examples are about something else** — which text keys carry expressions, type preservation, the troubleshooting section. The root name is scaffolding, and churning 20 lines of scaffolding on a governed surface buys the reader nothing. ⛔ Nothing was silently rewritten: the only expression text this PR changes is the two `dataSource = {…}` comments that named the retired wiring. ## Re-measurement of the card's per-line census The card measured on the objectui#9308 branch. Re-measured against `origin/main` `69aa9c017` — **every cited line still lands on the cited text**: | cited | on `origin/main` `69aa9c017` | verdict | |---|---|---| | 112 | `| Top-level data fields | \`SchemaRendererProvider dataSource\` | …` | exact | | 113 | `| \`data\` | Alias for dataSource root | …` | exact | | 302-303 | `dataSource = { customerNames: … }`, `useDataScope("customerNames")` | exact | | 305 | `**Nested paths work:** … resolves \`dataSource.app.settings.users\`` | exact | | 393 | `// ✅ Bound data, already node-shaped: dataSource = { rows: … }` | exact | `premise_still_valid: true`. ## Enumeration beyond the census (⛔ out of scope for this PR) `--measure` judges every candidate fence, marked or not, so a page can be wrong where no gate looks. Reading the prose as well turned up two more: - **`guides/auth-permissions.md`** carries the same retired claim in its own words — a scope table row `| \`data\` | the \`dataSource\` passed to \`SchemaRendererProvider\` |`, the sentence "Keys of the `dataSource` object are reachable only under the `data.` root", and "With no host scope mounted, `data` and `page` are all you get". ⛔ Out of scope here: **PR objectstack-ai#9374 holds that file**, so an in-place edit would collide. Filed as objectstack-ai#9379. - **`guides/testing.md` Pattern 5** asserts `getByText('Secret')` for a node gated on `${userRole !== "admin"}` — a BARE root that no channel publishes, before or after the ruling. Copy the example and the assertion fails. Different defect class from this card, and independent of the ruling — it is wrong on `main` today. Filed as objectstack-ai#9380. Noted, not filed: the `${data.*}` examples in `page-builder.md`, `architecture.md`, `i18n.md`, `project-setup.md` and `evals/*.json` are in the same reachable-only-if-the-host-publishes class; they are not falsifiably wrong and the skills lane that takes the auth-permissions card is their natural carrier. ## Verification Reproduce-first, as required — the harness is trustworthy before any red or green from it is read: ```text $ node scripts/check-skill-examples.mjs --self-test # BEFORE the build EXIT=2 — PRECONDITION NOT MET: the self-test's type-check leg needs the workspace built $ ...os-verify-lock.sh -c 'turbo run build $(--build-filter) --concurrency=2' VERDICT command-exit 0 · held the lock 44s · waited 0s $ node scripts/check-skill-examples.mjs --self-test # AFTER the build EXIT=0 — ✓ 59 cases pass ``` Gate, verbatim, before and after this diff: ```text BEFORE (exit 0) Scanned 20 guide(s) under skills, .claude/skills: 121 ts/tsx/typescript fence(s), 70 json/jsonc fence(s). Marked: 14 ts fence(s) (floor 13), 70 json fence(s) (floor 70) Semantic phase: 14 of 14 ts fence(s) judged, 0 failed. JSON phase: 70 fence(s) parsed, 0 failed. Every marked skill example holds up against the built types. AFTER (exit 0) Scanned 20 guide(s) under skills, .claude/skills: 122 ts/tsx/typescript fence(s), 70 json/jsonc fence(s). Marked: 16 ts fence(s) (floor 13), 70 json fence(s) (floor 70) Semantic phase: 16 of 16 ts fence(s) judged, 0 failed. JSON phase: 70 fence(s) parsed, 0 failed. Every marked skill example holds up against the built types. ``` ⭐ The marked ts population moves **14 to 16** and the json population is unmoved at 70 — both floors are SHRINK-ONLY and neither is breached. The two new marked fences are the wiring examples, so the thing this card is about is now COMPILED against the built `dist/*.d.ts` rather than only read. Derived by hand from `package.json` plus `.github/workflows/` (this repo has no `dispatch-gates.mjs`); every one run on this tree: | gate | exit | |---|---| | `check-skill-examples.mjs --self-test` | 0 | | `check-skill-examples.mjs` | 0 | | `check-skills-paths.mjs` | 0 — 88/89 stated paths resolve, 1 pre-existing baselined | | `check-skill-eval-tokens.mjs --self-test` / `check-skill-eval-tokens.mjs` | 0 / 0 | | `check-control-bytes.mjs` | 0 — 7539 tracked text files | | `check-shell-escape-residue.mjs` | 0 — 16/16 skills documents under a declared root | | `check-new-cross-file-line-citations.mjs` | 0 — 0 new citations | | `check-changeset-presence.mjs` | 0 | | `check-doc-links.mjs` | 0 | | `check-governed-queue-guard.mjs --self-test` | 0 — 185 cases | Package tests that READ these three documents (`markdown-test-inputs.mjs --changed` names all three as test inputs, so CI runs the full shards): reported below the fold once the shared verify lock grants a turn. Line readings for the governed surface, as the contract requires: | reading | before | after | delta | |---|---|---|---| | `guides/schema-expressions.md` | 569 | 635 | +66 | | `guides/data-integration.md` | 485 | 513 | +28 | | `rules/protocol.md` | 341 | 354 | +13 | | whole published package `skills/**` | 5201 | 5308 | +107 (+2.1%) | The added lines are the correction itself: one migration callout, two now-compiled wiring examples, and the sentences that replace the retired claims. No re-wrap was used to buy lines. ## Changeset **None owed**, by measurement rather than by inheritance: 1. `node scripts/check-changeset-presence.mjs` — "Compared the working tree with `69aa9c017` (merge-base with origin/main): 3 file(s) changed, 0 of them published source of a package the release covers, 0 of them a manifest whose published contract moved … No source or published contract of a released package changed in this range, so no changeset is owed." (exit 0) 2. Independently: **0 of 43** workspace manifests mention `skills` in `files` / `exports` / `main` / `module` / `types` / `bin`. Positive control on the same scan: 38 manifests list `dist` in `files[]`. Nothing under `skills/` is shipped by any npm package. 3. The merged precedent on this surface, `28be0786d` (objectstack-ai#9352), changed one `skills/` file and carried no changeset.⚠️ One inconsistency worth a maintainer's eye rather than my guess: open PR objectstack-ai#9374, also a single `skills/` guide, DOES carry an empty-frontmatter changeset declaring "no package is released by this change". Both forms pass the gate. If the empty-frontmatter declaration is the house style for this surface, say so and I will add one. ## Governed-queue-guard verdict on this diff (verbatim) ```text $ node scripts/check-governed-queue-guard.mjs --test skills/objectui/guides/schema-expressions.md skills/objectui/guides/data-integration.md skills/objectui/rules/protocol.md ⛔ GOVERNED — 3 of 3 path(s) are on a governed surface: skills/** x3 — the published skills catalog - skills/objectui/guides/schema-expressions.md - skills/objectui/guides/data-integration.md - skills/objectui/rules/protocol.md One governed path governs the WHOLE pull request — proportion is not a question. ⛔ Do not flip it ready, enqueue it, or arm auto-merge. Park it as a DRAFT and leave the merge to the maintainer; a human merge IS the review record for a governed surface. The merge-queue run of "Governed Surface Queue Guard" refuses this diff unless an APPROVED review by an authorized approver (GOVERNED_APPROVERS: os-zhuang, hotlong) is on the pull request — on whichever commit it was left (maintainer ruling 2026-09-04). exit 3 ``` ## Clause-② — contract review `needs:contract-review` is hung on this PR in the same stroke as opening it, mirroring the card. Published `skills/**` making falsifiable contract-semantics claims about which roots a tier binds. ⛔ The governed-surface human merge does **not** substitute for it; the two stack, and neither limb is mine to clear. ## Inherited reds — ⛔ not from this PR - `Bundle Analysis` is red on `main` (arrived with objectstack-ai#9316; a maintainer decision). - `Doc Snippet Type Check` is red on `main` until objectstack-ai#9369 lands. This diff touches no document that gate scans (`content/docs` + package READMEs + root `README.md`, explicitly not `skills/**`). ## Acceptance notes - ⛔ No test was skipped, quarantined or weakened. No assertion was loosened. The marked-fence floors both hold and the ts population grew. - The card's "the pins are green while the prose beside them is wrong" warning was taken literally: the six `packages/components` pins objectstack-ai#9369 re-derived were read for what they assert about these files before a byte was changed, and every string they pin is intact — `Rule: Keys Live on the Node`, the `hoists every key onto\s+the node` regex, the absence of the two retired sentences, the `no md matches "instead of \`props.\`"` class guard, and the `"bind": "customerNames"` list example in each of the three guides. - Noted, not filed: `rules/protocol.md`'s two measured tables still cite `origin/main` `f1c27f037` as the commit they were measured on. This PR restates their WIRING, not their outcomes, and says so inline; a re-measurement on a current commit is a separate piece of work, and the `skill-guide-provider-envelope` pin is what actually holds those outcomes today. Carrier: whoever next re-derives that pin. ## 维护者速读(草稿) ### 改了什么 把发布中的 `skills/objectui` 三份指南从「`dataSource` 就是表达式根 `data`、`bind` 走 `dataSource`」改成 ruling 之后的真实通道:宿主用 `PredicateScopeProvider` 发布作用域,`dataSource` 只是取数适配器。另外给两个接线示例加上 `os:check` 标记,让它们第一次真的被编译校验。 ### 为什么改 2026-09-13 的裁决(objectui#9308 option B)已经把这两件事退役,但发布给 AI 读的技能包还在教它们。更要紧的是:这不是改名。同一条 `data.*` 门,旧接线下恒判 `false`、新接线下因为根缺失走 fail-soft 恒判 `true` —— 一个 `visible` 门从「每行都藏」变成「每行都显」。只换 provider 名字的迁移说明会让读者踩正这一脚。 ### 风险与代价(含回滚) - **排序风险(主要):** objectstack-ai#9369 还没合。在它合进去之前,这三页描述的是尚未落地的行为。⚠️ 建议排在 objectstack-ai#9369 之后合。 - 保留了全部 `${data.*}` 示例原文,只让接线示例发布一个叫 `data` 的根 —— 因为 `skill-guide-provider-envelope` 这个 pin 正在渲染这些拼写,改写它们会在另一个 PR 的盲区里挪动 pin 的被测主体。 - 回滚代价:纯文档,`git revert` 一笔即可,不影响任何已发布包(本仓 43 个 manifest 无一发布 `skills/`)。 ### 席位意见 (留空,待 `os-zhuang` / `hotlong` 定稿) ### 你要做的 1. 确认合并顺序:先 objectstack-ai#9369,再本 PR。 2. 作为受管面的人工合并方给出 APPROVED review(这一道是 `check-governed-queue-guard` 机械要求的)。 3. clause-② 的 `needs:contract-review` 需要一份书面复核记录后才清 —— ⛔ 两道保障叠加,人工合并不替代它。 4. 顺带定一句话:本仓单文件 `skills/` 改动到底要不要写空 frontmatter changeset(objectstack-ai#9352 没写、objectstack-ai#9374 写了,门禁两边都放行)。 --- _Generated by [Claude Code](https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ)_ --- _Generated by [Claude Code](https://claude.ai/code)_ --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: os-zhuang <jack@objectstack.ai>
Fixes #9308
Fixes #9346
Maintainer ruling 2026-09-13, verbatim 「同意」 on option B, both halves. Option A (a new published provider prop) and option C were not taken.
What landed
b1 —
data: dataSourceleaves the evaluator scope.packages/react/src/SchemaRenderer.tsxno longer publishes the injected adapter as an expression root.@object-ui/app-shell'sExpressionProviderstates the governing principle — every root a tier binds must be one the engine accepts AND one that tier can actually answer — and objectui#8155 (app) / objectui#8166 (data) already applied it one tier up. Against a conformantDataSourceadapter everydata.*path resolvedundefined, so this tier bound a root it could not answer.b2 —
useDataScopereadsusePredicateScope(). A node'sbindnow resolves a path in the ambient scope a host publishes throughPredicateScopeProvider— the channelExpressionProvideralready feeds — instead of walking the adapter. No published key is added; that is what makes B strictly better than A.Side effect the ruling's own appendix named:
...predicateScopewas spread beforedata: dataSource, so a host that legitimately publisheddatathrough the documented scope channel was silently overwritten by the adapter. Removing the line un-shadows it. Pinned as leg 3 of the new test.Measurements the dispatch asked for
1. The verdict DOES move — the dispatch's premise here was falsified
The dispatch predicted the verdict would not move "because
data.*already resolvedundefinedagainst an adapter". Measured on the built evaluator, that is wrong, and the distinction is present-but-undefined key vs absent key:data.status == 'draft'throwOnError{ data: undefined }falsefalsedatakey at alltruedata is not definedSo a page/component
visiblegate readingdata.*was a clean constantfalse(node hidden on every row) and is now unevaluable, which this fail-soft surface answerstrue(node shown on every row). It stops being silent in the same stroke: objectui#5454'sreportUnresolvableVisibilityPredicatenames it, in production as well as development, with a message that is true for it. That is the breaking half of this change, it is stated plainly in the changeset, and it is pinned as leg 7 ofSchemaRenderer.dataRootUnbound-9308.test.tsxrather than left to be discovered in a console.2.
reportAdapterOnlyDataPredicate— measured, then re-aimedIt does not flood: its trigger reads the object passed as
boundData, not the evaluator scope, so b1 changed its firing set for a conformant host by exactly nothing (everydata.*path was already undefined on an adapter). What b1 broke is its truth, in both directions at once:dataSourcestill answers the read, while the evaluator no longer sees it;dataanswers perfectly.⇒ Decision: keep the diagnostic, keep its severity and its dev-only gate, and hand it the
datathe evaluator actually bound (predicateScope?.data). Its copy moves off the "at the node tierdatais the DATA-SOURCE ADAPTER" claim, which this PR makes false. Reachability is now stated in the source rather than assumed: with no hostdata, adata.*predicate throws and objectui#5454 takes it, so this leg speaks only for a host that published adataroot which does not answer — the population it can still speak for.3.
celAuthoring'srowCanonAdvisory— more correct, and untouchedIts message says a wrong-layer
data.*"faults at runtime withUnknown variable: dataand the rule never fires". Before this PR the node tier contradicted that by resolvingdatato the adapter; now it agrees. ⛔ Severity unchanged (warning) — every save gate on that tier countsseverity === 'error', and promoting it would refuse predicates already in customer metadata.4.
dataTableBindDiagnostic'sdata-vs-bindprecedence question — DISSOLVEDConfirmed, and said so in the module's own docblock. While
useDataScopewalked the adapter,bindand the node'sdatawere two spellings of one idea and a reader of both would have needed a precedence rule. Nowbindresolves the ambient scope anddatais an inline array on the node: different channels, no precedence to decide. ⛔data-tablestill does not readbind(objectui#6575) and does not start.Blast radius — all nine readers re-verified per reader, not inherited
Each already falls back, so the behaviour against a conformant host is unchanged (the fallback is what was running):
components/.../list.tsxArray.isArray(boundData) ? boundData : Array.isArray(schema.items) ? schema.items : []components/.../tree-view.tsxboundData || schema.nodes || []plugin-charts/ObjectChart.tsxboundData || schema.data || fetchedDataplugin-dashboard/ObjectDataTable.tsxboundData || schema.data || fetchedDataplugin-dashboard/ObjectPivotTable.tsxboundData || schema.data || fetchedDataplugin-grid/ObjectGrid.tsxif (boundData && Array.isArray(boundData))→ else falls through togetDataConfig(schema)plugin-kanban/ObjectKanban.tsxexternal || boundData || schema.data || fetchedDataplugin-list/ObjectGallery.tsxprops.data || boundData || schema.data || fetchedData || []plugin-timeline/ObjectTimeline.tsxprops.data || boundData || fetchedDataNone treats
boundDataas its sole source, so the shape of b2 is unchanged. The card body's "five components" is wrong; nine is right.The documentation half
Doc Snippet Type Checkwas red onmainwith three fences, all teaching meaning 2 — publish page values throughdataSource, read them back underdata.*:All three move to
PredicateScopeProvider, and so does the prose around them — includingpackages/react/README.md's sentence thatdataSource"is what the evaluator sees under the namedata", which the gate cannot see and which b1 deletes outright.packages/types/src/base.ts'sbindexample comment (// → dataSource.customerNames) moves with it.The pin, written first and run red on the unmodified tree
packages/react/src/__tests__/SchemaRenderer.dataRootUnbound-9308.test.tsx, red-first reading verbatim:Legs 2 and 6 are the lit controls and pass on the unmodified tree, which is what makes the other five a reading. After b1 + b2:
Tests 7 passed (7).objectui#8021's pin was re-derived, never weakened or skipped. Leg A still READS the Data Context fence off the page on every run (it now branches over three carriers instead of two, so moving the fence back to either of the others changes what it renders); leg C's wiring and leg D's expression swapped to the new teaching; and a new leg E pins the adapter seam directly — provider
dataSourceplus${data.user.name}now renders the characters the author typed. TheFORWARDED_LOOKALIKESsweep gainedscope=so the new carrier's own look-alike is covered.Two stale claims were corrected while re-deriving neighbouring pins: three test files carried
/** The ambient scope app-shell's `ExpressionProvider` really mounts. */over a bag containingdata: {}.buildExpressionScopehas published nodatasince objectui#8166, so the comment was false before this PR and would have hidden the verdict move after it. Those bags are kept — they are now the only way the adapter-only leg is reachable at all — and re-documented as deliberate HOST publications.Verification
See the report comment on #9308 for the full command list and
VERDICTlines.Acceptance notes
skills/objectui/**now teaches a false wiring —guides/schema-expressions.md(| `data` | Alias for dataSource root |, "dataSource = { customerNames: [...] },listcallsuseDataScope("customerNames")", ""bind": "app.settings.users"resolvesdataSource.app.settings.users"),guides/data-integration.mdandrules/protocol.md. ⛔ Not touched here:skills/**is a governed surface, and one path would park this whole PR in the draft-and-wait lane while it is the PR unblocking a redmain. Filed separately; the six affected pins inpackages/componentspublish their host values through the scope channel, which keeps them measuring their own subject.formValues/datatail) is pointed the same way by this ruling but is ⛔ not ruled and ⛔ not addressed here; it remains open.data-tablenot readingbind— is untouched.维护者速读(草稿)
改了什么。 渲染器不再把宿主注入的
dataSource(适配器)当作表达式根data发布;节点的bind改为在宿主通过PredicateScopeProvider发布的环境作用域里解析。文档与 README 随之改教作用域通道。为什么改。 这是仓库自己已裁过两次的原则(objectui#8155 / objectui#8166)贯彻到最后一层:一个层只能绑定它答得出的根。面对合规适配器,
data.*恒为undefined,这一层答不出。风险与代价(含回滚)。 已在客户元数据里的
data.*可见性判据,行为从「恒隐藏」变为「恒显示」,并在控制台响亮报出。这是唯一的行为断点,已在 changeset 正文点名。回滚只需还原SchemaRenderer.tsx的一行与useDataScope的一处读取,其余为文档与钉子。席位意见。
你要做的。 确认「
data.*判据由恒隐藏翻为恒显示」这一取舍可接受;若希望改为保守方向(保留data为一个恒空对象以维持旧裁决),请说,那是另一张卡。🤖 Generated with Claude Code
https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ
Generated by Claude Code