Uh oh!
There was an error while loading. Please reload this page.
test(sdui): check the contract in the direction that would have caught #3006 - #3013
Merged
Merged
Conversation
…#3006 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. 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 10:29
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
#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>
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.
Follow-up to #3006. The guard there only looked one way: every tag in
PUBLIC_BLOCKSmust resolve. That direction cannot distinguish "not built yet" from "built, but the contract spells it wrong" — sorecord:line_itemssat inEXPECTED_UNIMPLEMENTEDfor a release while its renderer shipped, fully configured, in plugin-form.What I found before writing the check
The obvious reverse check — every registered block must be curated — is wrong, and the data says so. The console registers 525 keys / ~274 components;
PUBLIC_BLOCKScurates 36. The rest areui:*primitives,field:*types, and plugin internals. Curation is the point; asserting against the full registry would be pure noise.Narrowing to the
record:family doesn't trivially work either — 12 blocks ship, 5 are curated. The 7 uncurated ones aren't mistakes:record:detailsrecord:highlightsrecord:related_listrecord:pathrecord:line_itemsrecord:activityrecord:alertrecord:chatterrecord:discussionrecord:historyrecord:quick_actionsrecord:reference_railThe split is exactly
inputs > 0. Curated blocks are configurable; the excluded ones have nothing an author or a model can set.The two checks
1. Every shipped
record:*block is curated, or listed with a reason. The seven live in aDELIBERATELY_UNCURATEDmap with a one-line rationale each. This is an allowlist of decisions, not of defects — registering a newrecord:*block fails the test until someone picks a side, so the vocabulary can't drift from what the platform renders. A companion assertion pins those seven at zero inputs, so one that grows a configurable surface re-opens the decision instead of inheriting the exclusion.That assertion also guards its own premise:
getMeta()reportsinputs: undefinedfor a pending lazy stub, meaning "not known yet", not "declares none". The test asserts these are eager registrations first, so going lazy fails loudly rather than passing vacuously.2. A curated tag that near-misses a registered block.
line_itemsvsrecord:line_itemsdiffer only by namespace — one of the two spellings is always a typo, never a design. The check reports the candidate rather than just "not covered".Verified against the real bug
Reverting the tag to
line_itemsfails both with exactly the right diagnosis:That second line is the sentence I needed and didn't have on #3006 — I read "not covered", concluded "not built yet", and filed it.
Full suite
726 passed | 1 skipped, type-check 35/35, lint 0 errors, changeset included.Latent issue found, not fixed here
Deduping the registry surfaced eleven
record:*blocks in plugin-detail registered as:which yields doubled
record:record:detailskeys. It does not reach the contract —getPublicConfigs()rewritestypeto the curated tag (Registry.ts:477) — so it's internal dirt, not a live bug.record:line_itemsis the one registered correctly ('line_items'+namespace+skipFallback). Documented where the deduping happens; the registrations want their own change.🤖 Generated with Claude Code
https://claude.ai/code/session_01N4mrr1ihhwnfEHFSWmGoMp
Generated by Claude Code