Uh oh!
There was an error while loading. Please reload this page.
fix(sdui): the curated contract lists record:line_items, the tag that actually resolves - #3006
Merged
Merged
Conversation
… actually resolves PUBLIC_BLOCKS carried `line_items` — the bare tag. @object-ui/plugin-form registers the block as `record:line_items` with `skipFallback: true`, which exists precisely so the bare name is NOT claimed, so that key never existed and the curated entry could never resolve. Its four siblings in the list are all `record:`-prefixed and plugin-form's own comment says "Register record:line_items"; the bare spelling was a slip. The effect was a block that has shipped all along — a full renderer, a label, five declared inputs — being absent from the public contract, from the JSX type surface, from the generated manifest, and from every kind:'react' page's scope. It read as an unimplemented aspirational entry, which is how it was recorded when #2979 added the contract-coverage guard. With the tag corrected the contract has no gaps left: all 36 curated tags resolve in the console, record:line_items among them with its full inputs. The guard's known-unimplemented list is now empty and stays asserted, so the next entry that cannot resolve surfaces instead of being explained away. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N4mrr1ihhwnfEHFSWmGoMp
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
os-zhuang
marked this pull request as ready for review
July 30, 2026 09:50
Uh oh!
There was an error while loading. Please reload this page.
os-zhuang added a commit
that referenced
this pull request
Jul 30, 2026
…#3006 (#3013) The console guard only looked one way: every tag in PUBLIC_BLOCKS must resolve. That direction cannot tell "not built yet" from "built, but the contract spells it wrong" — so record:line_items was filed as a known gap for a release while its renderer shipped, fully configured, in plugin-form. Two checks close the other direction. Every shipped record:* block is curated, or listed with a reason. Seven are deliberately out, each declaring zero inputs — nothing for an author or a model to configure. A new record:* registration now fails until someone decides which side it belongs on, so the vocabulary cannot quietly drift from what the platform can render. A companion assertion pins those seven at zero inputs (and at being eager registrations, so a lazy stub's "not known yet" cannot make it vacuous), letting one that grows a configurable surface re-open the decision instead of inheriting the exclusion. A curated tag that near-misses a registered block. `line_items` vs `record:line_items` differ only by namespace; one of the two spellings is always a typo. The check reports the candidate — "also try record:line_items" — rather than just "not covered". Both were verified against the real bug: reverting the tag to `line_items` fails them with exactly that diagnosis. Deduping the registry surfaced a second, latent issue — eleven record:* blocks in plugin-detail are registered as register('record:x', …, {namespace:'record'}), prefixing an already-prefixed name and yielding doubled record:record:x keys. It does not reach the contract (getPublicConfigs rewrites type to the curated tag), so this change only documents it where the deduping happens; the registrations are left for a separate change. Claude-Session: https://claude.ai/code/session_01N4mrr1ihhwnfEHFSWmGoMp Co-authored-by: Claude <noreply@anthropic.com>
os-zhuang added a commit
that referenced
this pull request
Jul 30, 2026
#3023) Eleven blocks in plugin-detail were registered as register('record:x', …, { namespace: 'record' }) — an already-prefixed name handed to a registry that prefixes it again. Each landed at record:record:x, and the key authors actually resolved, record:x, was the un-namespaced FALLBACK rather than the intended registration. The registry carried 23 keys for 12 components. Nothing failed, which is why it survived: getPublicConfigs() rewrites `type` to the curated tag, so the doubled name never reached the contract, the manifest, or the JSX surface. It was visible only when enumerating the registry directly — which is what #3013's reverse check does. Registering the bare name is what makes `namespace` correct, and skipFallback: true is what keeps the fallback from claiming that bare name globally. Without it these would take over `details`, `path`, `history`, `alert` … as top-level tags; `alert` is the live case, owned by ui:. Every block stays reachable exactly as record:<name>, and 23 keys become 12. record:line_items needed no change — it was the one already registered this way, which is what made #3006's near-miss possible in the first place. Two console assertions hold the shape: no key carries a doubled prefix, and no record:* block owns the bare spelling of its own name. Both were verified against the old form: restoring one registration fails them with the phantom key named. Claude-Session: https://claude.ai/code/session_01N4mrr1ihhwnfEHFSWmGoMp Co-authored-by: Claude <noreply@anthropic.com>
os-zhuang added a commit
that referenced
this pull request
Jul 30, 2026
…ks, and curate six (#3027) Seven record:* blocks shipped with renderers that read props but declared no inputs. That combination is the worst of both: the renderer honours `limit`, `severity`, `location` …, while every authoring surface — the designer panel, the AI vocabulary, the generated manifest — reports the block takes no configuration. #3013 recorded them as deliberately uncurated for exactly that reason. The declarations mirror what each renderer actually reads: record:activity 11 from RecordActivityComponentProps record:chatter 5 from RecordChatterComponentProps record:discussion 5 (same renderer, same inputs) record:alert 8 severity, title, body, visible, icon, action, dismissible, dismissKey record:quick_actions 7 actionNames, requiredPermissions, location, align, inline, variant, size record:history 3 limit, emptyText, unknownUserText record:reference_rail 1 hideEmpty inputs describe what an AUTHOR writes, which is a subset of what the renderer reads. `entries`, `loading` and resolved `actions` are injected by the host shell off RecordContext; declaring them would invite a model to hand-write the data the page is supposed to fetch. `aria` is omitted for the reason it is omitted on record:details — an accessibility escape hatch, not a layout choice. `location` takes its enum from the spec's ACTION_LOCATIONS rather than restating it, per #3019. Six of the seven join PUBLIC_BLOCKS: configurable and absent from the contract is the state #3006 was about. The contract goes 36 -> 42 tags, all resolving. record:chatter stays out — same renderer as record:discussion under a Salesforce-familiar name, kept for schemas already in the wild. Two spellings of one block is ambiguity an authoring model cannot resolve, so the vocabulary carries the spec's name. A test compares the two input lists, so the day they diverge the exclusion stops being justified and fails. A companion assertion requires every curated record:* tag to declare inputs. A curated tag with none reads as "takes no configuration" when the renderer in fact reads props — the same gap #3006 opened, pointed the other way. Claude-Session: https://claude.ai/code/session_01N4mrr1ihhwnfEHFSWmGoMp Co-authored-by: Claude <noreply@anthropic.com>
os-zhuang added a commit
that referenced
this pull request
Jul 30, 2026
…he public contract (#3069) The AI-authoring vocabulary and the Studio page designer disagreed by thirteen blocks: PUBLIC_BLOCKS carried one page: tag and one element: tag while the designer palette — and @objectstack/spec's page schema — offered the whole families. A block a human can drag in Studio was invisible to a model writing the same page, which is the #3006 state at 10x the scale. Fifteen tags join the contract (42 -> 57), every one shipping a renderer with declared inputs (#3065): page: tabs, card, accordion, section, footer, sidebar element: text, number, button, definition-list, repeater action: button, group, menu, icon Five stay out, each with its reason recorded and guarded: action:bar (record:quick_actions covers the record action strip; the spec blesses the other four), element:image (duplicates the curated `image` — one spelling per concept), and element:record_picker / element:text_input / element:metadata_viewer (mirroring the Studio palette's own PALETTE_EXCLUSIONS, so the two vocabularies stay out for the same reasons rather than by coincidence). The console's reverse-coverage guard now sweeps all four semantic namespaces instead of record: alone — checking only the namespace you just fixed is exactly how the last 22 doubled keys went unnoticed (#3037). A new prop-less allowlist (element:divider, page:section, page:footer, page:sidebar) keeps "declares no inputs" a pinned decision in both directions: those four must stay at zero, everything else curated must declare a surface. Verified by mutation: dropping action:menu from the list fails the sweep with exactly that tag named. Claude-Session: https://claude.ai/code/session_01N4mrr1ihhwnfEHFSWmGoMp Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
line_itemswas never unimplemented@object-ui/plugin-formregisters it — full renderer, label, five declaredinputs:skipFallback: trueexists precisely to stop the bare name being claimed.PUBLIC_BLOCKSlisted the bareline_items, so the curated entry resolved against a key that by design never exists.Three things say the bare spelling was a slip, not an intent:
record:details,record:highlights,record:related_list,record:path;record:line_itemsis what app-shell's own test fixtures use.What it cost
A block that has shipped all along was absent from the public contract, the JSX type surface, the generated
sdui.manifest.json, and everykind:'react'page's scope. An author writing<RecordLineItems>got aReferenceError; one writing<record:line_items>in akind:'html'page got the tag rejected.It presented as an aspirational entry, which is exactly how #2979's contract-coverage guard recorded it —
EXPECTED_UNIMPLEMENTED = ['line_items']. The guard did its job (it made the gap visible and reviewable); I misread what the gap was.Verified
All 36 curated tags now resolve in the console.
EXPECTED_UNIMPLEMENTEDis now[]and stays asserted, so the next entry that cannot resolve surfaces rather than getting explained away as aspirational.716 files passed | 1 skipped,8373 tests passed | 24 skipped.lint+type-checkfor@object-ui/coreand@object-ui/console: 38/38 tasks successful.changeset:check: clean. Changeset included.If you still want it removed
One line in
packages/core/src/registry/public-blocks.tsplus moving the tag out ofEXPECTED_COVEREDin the console guard. Worth deciding on the merits now that it's clear the choice is "should this shipped block be part of the AI-authoring vocabulary", not "should we stop advertising something that doesn't exist".🤖 Generated with Claude Code
https://claude.ai/code/session_01N4mrr1ihhwnfEHFSWmGoMp
Generated by Claude Code