Skip to content

feat(plugins): publish the four GA object-* blocks' authoring surfaces (#4648) - #4901

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-4648-ga-authoring-surfaces
Aug 17, 2026
Merged

feat(plugins): publish the four GA object-* blocks' authoring surfaces (#4648)#4901
os-steve merged 1 commit into
mainfrom
claude/issue-4648-ga-authoring-surfaces

Conversation

@os-steve

Copy link
Copy Markdown
Collaborator

Part of #4648

Every piece of that card's ruled scope is implemented here, so this is Part of rather than a closing keyword for one reason only: the carve-out measures ten keys where the ruling enumerated five (see the flagged delta below), and the card is the right home for that one-line call. Flip it to a closing keyword on review if the delta is accepted as written.

Implements the maintainer ruling of 2026-08-16 on that card — option B (declare the keys) with option C's deprecated-alias carve-out. This is the successor step to PR #4671, which adapted the parity gates and forked this item back to the card.

Net effect: on a GA-installed tree the console registry-inputs parity gate goes from 4 failures to 0. Those four were the last of the eight #4671 inherited, so this is the piece #4639's re-point was waiting on.

What changed, and why it is not new capability

All four blocks are long-standing objectui renderers that honoured far more keys than they declared. An author — very often an AI author — who wrote one of the undeclared keys got unknown-prop from sdui-parser on a key that works, while the designer panel and the generated sdui-intrinsics.d.ts denied it existed. Publishing makes the manifest, the .d.ts, the designer and the renderer agree. No renderer behaviour changes.

Descriptions are sourced from GA's own .describe() text plus this repo's props TSDoc, and written to teach authoring — each legacy/canonical pair says which spelling to prefer and what supersedes what.

Re-derived measurement (78 keys, confirmed)

Re-derived on this branch from the resolved ComponentPropsMap[type].shape under the GA overlay, not taken from the card:

block spec keys already declared gap declared here carved out
object-form 39 19 20 20 0
object-grid 37 3 34 21 10
object-master-detail-form 14 4 10 10 0
object-metric 18 4 14 14 0
78 68 10

The card's 78 / 20 / 34 / 10 / 14 reproduce exactly.

object-form +20 — packages/plugin-form/src/index.tsx

recordId, customFields, initialValues, initialData, readOnly, modalCloseButton, contentLayout, confirmOnDiscard, submitText, cancelText, nextText, prevText, showSubmit, showCancel, showReset, submitBehavior, successMessage, navigateOnSuccess, resetOnSuccess, mobile

object-grid +21 — packages/plugin-grid/src/index.tsx (GRID_QUERY_INPUTS)

label, sort, pagination, searchableFields, data, rowHeight, frozenColumns, resizable, reorderableColumns, showColumnTypeIcons, rowColor, conditionalFormatting, grouping, aggregations, selection, rowActions, bulkActions, batchActions, bulkActionDefs, editable, singleClickEdit, navigation, operations, exportOptions

Declared on the shared const, so the view:grid alias cannot drift from the block — the invariant that const's own comment states.

object-master-detail-form +10 — packages/plugin-form/src/index.tsx

recordId, formType, fields, title, submitText, cancelText, showSubmit, initialValues, initialData, taxRateField

object-metric +14 — packages/plugin-dashboard/src/index.tsx

description, title, filter, colorVariant, variant, format, currency, prefix, suffix, invert, fallbackValue, trend, compareTo, drillDown

The carve-out — 10 keys, cited

Ruled: "object-grid's own @deprecated legacy spellings … are NOT published as new authoring surface — they get reasoned, cited exemptions so a deprecated alias is not hardened." Each gets an entry in UNPUBLISHED_EXEMPTIONS naming the canonical spelling that IS declared in its place.

carved out @deprecated tag in ObjectGridSchema GA's own describe canonical, declared here
fields "Use columns instead" "Field list fallback used when columns is absent" columns
staticData "Use data with provider: 'value' instead" "Alternate spelling of data" data
selectable "Use selection.type instead" "Legacy selection shorthand … Prefer selection" selection
pageSize "Use pagination.pageSize instead" "Flat page-size shorthand" pagination
showSearch "Use searchableFields instead" "read only when searchableFields is absent" searchableFields
showPagination "Use pagination config instead" "read only when pagination is absent" pagination
defaultSort "Use sort instead" "Legacy single-sort fallback … Prefer sort" sort
defaultFilters "Use filter instead" "Legacy base-filter fallback … Prefer filter" filter
resizableColumns "Moved to top-level resizable" "Alternate spelling of resizable" resizable
title "Use label instead" "Fallback for label" label

The gate's stated exemption bar is not amended — option B is the arm that needed no amendment, which the ruling says explicitly.

⚠️ Measurement delta the maintainer should see: the class measures TEN, not five

The ruling enumerated five (fields / staticData / selectable / pageSize / showSearch), carried over from the fork report's list. Re-deriving the class the ruling named — tagged @deprecated in ObjectGridSchema and declared by GA — measures ten. The five extra (showPagination, defaultSort, defaultFilters, resizableColumns, title) are the same class by the same test, with both authorities agreeing, so they are carved out with the enumerated five rather than published.

This is the one place this PR goes beyond the ruling's literal list, and it is deliberately in the reversible direction: trimming back to exactly five is one line each (delete the entry, declare the input), whereas publishing a key and later withdrawing it is a breaking change to published surface. Say the word and it becomes five.

One key sits just outside: batchActions is called a "Legacy alias of bulkActions" in its own TSDoc but is not tagged @deprecated, so by the ruled test it publishes. It is declared, with a description steering authors to bulkActions. Happy to move it if the intended test was prose rather than the tag.

Sequencing — and a correction to the assumed blocker

The card and the dispatch both record that "the parity gate's forward direction rejects these keys on rc.6", which is why option B was phased behind the pin bump. Measured, the forward direction never sees them. covered is Object.keys(ComponentPropsMap).filter(has inputs), and rc.6 does not carry the four blocks at all — so they are not in covered, and no forward assertion is generated for them. The declarations are simply dormant on rc.6 and fully judged on a GA tree.

Consequences, both good:

The carve-out exemptions do need the pin-aware mechanism, because they name keys rc.6 cannot resolve: the ten are added to GA_PENDING_UNPUBLISHED_KEYS, the set PR #4671 built, so every GA-pending exemption arms exactly with the installed spec judges their dormancy in both directions. Unlike #4668's five, these do not self-retire on a pin bump — no issue owns declaring them later, by design — and their comment block says so.

One bounded fix outside the declared file surface

apps/console/src/__tests__/public-block-binding-reach.test.tsxSUPERSEDES_BINDING gains customFields.

That probe mounts each public block with every declared input filled, then asserts the block asked the data layer for its objectName. Declaring object-form.customFields turned it red, correctly: ObjectForm.tsx:426 computes const hasInlineFields = schema.customFields && schema.customFields.length > 0, read at :455 under "Skip fetching if we have inline fields" — substituting a minimal in-memory object schema for the getObjectSchema(schema.objectName) call — and again at :479 to skip the record fetch. The repo's own type says the same in prose: "When used with inline field definitions (without dataSource), this becomes the primary field source."

So a non-empty customFields is the author telling the block not to fetch — precisely the criterion that put data in that set. The guard is quoted at the entry, as that set's doc requires. Note it is length-sensitive: sampleFor returns ['name'] for an array input, which is what tripped it.

Reported rather than folded in silently: this file is outside the card's declared surface, it is a direct and unavoidable consequence of the declarations, and no in-flight sibling holds it.

Evidence — both pins, at b58f664 (working tree clean)

GA readings use PR #4660's npm-pack overlay: npm pack @objectstack/spec@17.0.0 unpacked into this worktree's virtual store with the ai / zod peer links mirrored, all 34 @objectstack/spec symlinks repointed, the real suites run unmodified, rc.6 restored after. Version confirmed before each run (17.0.0-rc.6 vs 17.0.0).

reading rc.6 GA overlay
registry-inputs-spec-parity.test.ts 54 passed 62 passed (was 4 failed / 107 passed before this branch)
+ public-block-binding-reach + public-contract 81 passed (3 files) 78 passed (2 files)
apps/console full suite 50 files / 554 tests passed
plugin-form plugin-grid plugin-dashboard sdui-parser apps/console 235 files / 2258 tests — 1 failure, the customFields finding above, fixed and re-run green
app-shell/metadata-admin plugin-designer sdui-parser 183 files / 1773 passed, 1 skipped
type-checkplugin-form plugin-grid plugin-dashboard PASS, "Scope: 3 of 47 workspace projects"
type-checkapps/console PASS
eslint --quiet, 5 changed files PASS (0 errors)
check-control-bytes PASS (4371 files)
check-changeset-presence / -fixed / -no-major PASS
check-phantom-dependencies PASS

Both type-check runs had the dependency closure built first (pnpm --filter '< pkg >^...' build) — the console's unbuilt run first produced the classic TS2307 / TS2882 false red.

Reverse verification — direction predicted first, both pins, from the committed state

Ablation A — delete the object-grid.title exemption entry while leaving it pinned in GA_PENDING_UNPUBLISHED_KEYS. Predicted GA 3 / rc.6 1; observed exactly that:

GA:    × object-grid publishes every top-level key its spec props schema declares
       × every GA-pending exemption arms exactly with the installed spec, all fifteen together
       × the four GA blocks resolve their ruled split — declared vs carved out
       Tests  3 failed | 59 passed (62)

rc.6:  × every GA-pending exemption arms exactly with the installed spec, all fifteen together
       Tests  1 failed | 53 passed (54)

The rc.6 asymmetry is the mechanism proving itself: the block assertion stays green there because rc.6 carries no object-grid key to demand, which is the dormancy claimed — while the pinned-set guard still fires, so a typo cannot hide.

Ablation B — delete a declared input (object-metric.compareTo). Predicted GA red / rc.6 green:

GA:    × object-metric publishes every top-level key its spec props schema declares
       × the four GA blocks resolve their ruled split — declared vs carved out
       Tests  2 failed | 60 passed (62)

rc.6:  Tests  54 passed (54)

This is the positive control for the sequencing claim above: the declarations are genuinely invisible to rc.6 and genuinely judged on GA, so nothing here can go falsely green at the pin bump.

Both restored with git checkout < branch > -- < paths >, git status clean, re-run green on both pins.

New assertion added

the four GA blocks resolve their ruled split — declared vs carved out pins the ruling by name rather than leaving it to the derived reverse-direction loop — which goes green just as readily if a declaration is replaced by an exemption, the cheap move under time pressure and the one thing option B forbids. It asserts each carved-out key is spec-declared, un-published and cited, and that every other spec key on the four blocks is declared and carries no exemption, as an exact set difference so a key added by a later GA cannot slip through as neither.

Changeset

A real patch changeset — this changes published authoring surface on three released packages (@object-ui/plugin-form, @object-ui/plugin-grid, @object-ui/plugin-dashboard). Not an empty-frontmatter one, and no skip-changeset label: that label does not exist in this repo.

Scope

Five files: three registration sites, the console registry-inputs parity gate, and the console binding-reach probe explained above, plus the changeset. No content/docs/releases/. In-flight siblings untouched — examples/schema-catalog (#4003), packages/plugin-gantt (#4884), packages/app-shell/src/views/metadata-admin/ (#4308).


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 24.7 KB 350 KB
Entry file index-CwsSS7SZ.js
Status PASS

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 9.56KB 3.59KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 8.92KB 3.41KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 25.13KB 5.40KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.13KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.64KB 2.21KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 38.46KB 10.17KB
auth (createAuthenticatedFetch.js) 6.34KB 2.43KB
auth (index.js) 2.35KB 1.07KB
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.02KB 0.88KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 26.07KB 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.65KB 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) 498.37KB 111.05KB
core (index.js) 3.79KB 1.52KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 157.05KB 43.28KB
fields (index.js) 231.73KB 57.60KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.35KB 1.38KB
i18n (pickLocalized.js) 3.69KB 1.73KB
i18n (provider.js) 23.12KB 7.62KB
i18n (useDisplayLocale.js) 2.84KB 1.45KB
i18n (useObjectLabel.js) 27.59KB 6.63KB
i18n (useSafeTranslation.js) 7.77KB 3.13KB
layout (index.js) 38.86KB 10.83KB
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.74KB
mobile (index.js) 1.50KB 0.62KB
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.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 9.35KB 3.31KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 4.42KB 1.42KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.81KB 0.83KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 46.62KB 12.83KB
plugin-charts (index.js) 64.75KB 18.37KB
plugin-chatbot (index.js) 181.21KB 43.14KB
plugin-dashboard (index.js) 130.85KB 33.44KB
plugin-designer (index.js) 212.39KB 42.83KB
plugin-detail (index.js) 239.81KB 59.97KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 120.43KB 29.04KB
plugin-gantt (index.js) 164.10KB 39.87KB
plugin-grid (index.js) 197.59KB 53.01KB
plugin-kanban (index.js) 52.72KB 14.54KB
plugin-list (index.js) 111.23KB 26.97KB
plugin-map (index.js) 17.91KB 5.72KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 41.70KB 11.19KB
plugin-timeline (index.js) 26.68KB 7.66KB
plugin-tree (index.js) 8.50KB 2.88KB
plugin-view (index.js) 83.81KB 20.49KB
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.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 27.53KB 9.41KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.28KB 0.68KB
react (schema-input.js) 1.45KB 0.83KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 4.47KB 2.03KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-retry.js) 4.32KB 2.02KB
types (index.js) 3.05KB 1.52KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
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 (spec-report.js) 5.05KB 1.93KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 3.40KB 1.71KB
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

@os-steve
os-steve marked this pull request as ready for review August 17, 2026 03:05
@os-steve
os-steve added this pull request to the merge queue Aug 17, 2026
Merged via the queue into main with commit 375efb4 Aug 17, 2026
21 checks passed
@os-steve
os-steve deleted the claude/issue-4648-ga-authoring-surfaces branch August 17, 2026 03:05
os-steve pushed a commit that referenced this pull request Aug 17, 2026
…ost-adaptation main)

Re-points the GA pin bump onto main after the four pre-re-point adaptations
landed (#4649 / #4650 / PR #4671 / PR #4901).

package.json range lines merged cleanly (no conflicts); pnpm-lock.yaml was
REGENERATED rather than hand-merged: main's lockfile was taken verbatim and
`pnpm install` re-run with the raised ranges. The resulting lockfile diff vs
main touches only the six @objectstack/* packages.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbRmJD3iPhXjKr6vhd4Qkv
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant