Repository navigation
fix(app-shell,fields): valid package-sheet header markup; select field controlled from its first render (objectui#11804) - #11879
Merged
objectstack-fleet[bot] merged 3 commits intoOct 8, 2026
Conversation
…rolled from first render
The package sheet's "Pending changes" header was a <p> holding the
draft-count Badge, which renders a <div>; React logged a DOM-nesting
error whenever a package with drafts opened the sheet. The header is
now a div with the same classes.
SelectField handed Radix an undefined value for a field the create
form had not seeded, so the user's first pick flipped the Select from
uncontrolled to controlled. A host that hands the widget an onChange
now gets a controlled Select from the first render ('' = nothing
chosen); a host with no onChange stays uncontrolled. Saved values are
unchanged.
Claude-Session: https://claude.ai/code/session_01DrKzdPdyLLBW3qpZ4vtk7z
Co-authored-by: Claude <noreply@anthropic.com>
… package-sheet pin So a regression's first failure shows React's own nesting error. Claude-Session: https://claude.ai/code/session_01DrKzdPdyLLBW3qpZ4vtk7z Co-authored-by: Claude <noreply@anthropic.com>
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
This was referenced Oct 8, 2026
objectstack-fleet
Bot
deleted the
claude/issue-11804-react-markup-warnings
branch
October 8, 2026 04:23
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 free
to 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.
Fixes #11804
Clause-②: no
Both React warnings from the 2026-10-07 Studio QA pass are fixed at the component that emits them. Each emitting site was measured before any edit (happy-dom renders through the real components, with
console.error/console.warncaptured). Both PM hypotheses were partly wrong; the corrections are below.What was measured first
The
divinside ap(H1, corrected). The sheet'sSheetDescriptionis not the cause: it holds only the package id as text. The cause is the Pending changes header inPackageDetailSheet(PackagesPage.tsx). It was apholding the draft-countBadge, and theBadgeprimitive (packages/components/src/ui/badge.tsx, left untouched) renders adiv. React logged "In HTML, div cannot be a descendant of p" (with its companion "p cannot contain a nested div") only for a package with pending drafts. The same sheet with no drafts logged nothing. Studio's Package info & settings menu item opens this samePackageDetailSheet, so console and Studio share the one site.The Select flip (H2, corrected). The emitting widget is the single-value branch of
SelectField(@object-ui/fields). It is registered asfield:select, which is the form type the object create form gives everyselectfield. The widget passed the form'sundefinedstraight to Radix'sSelectfor a field the record did not seed, so theSelectstarted uncontrolled. The flip was not caused by defaults loading. A field seeded with adefaultValuewas controlled from its first render. Opening the form logged nothing. The warning fired on the user's first pick in a select field that has no default. Measured throughObjectFormin create mode, on an account-shaped object withindustry(no default) andstatus(defaultValue: 'prospect'):industry: "Select is changing from uncontrolled to controlled";{"status":"prospect","name":"Acme","industry":"finance"}.Other selects with the same shape, as read on this branch:
RecordPickerDialog's filter select andGridField's cell select already pass''.InlineFieldInputpasses'', andActionParamDialogpasses?? null.SelectFieldalready mapsnullto''.FieldEditWidget(the grid's inline editor) passes the raw value. It goes throughSelectField, so this fix covers it.selectbranch (BuiltinSelectControlinform.tsx) is reached only by a baretype: 'select'form field, never by the object create form. It flips the same way on a first pick (measured). It is left alone here, see Acceptance notes.The fix
PackagesPage.tsx: the headerpbecomes adivwith the same classes. Tailwind's preflight zeroes ap's margins, so the spacing does not change.SelectField.tsx: a host that hands the widget anonChangegets a controlledSelectfrom the first render.undefinednow maps to'', asnullalready did. Radix renders''andundefinedthe same way (the placeholder, and an empty hidden native select), so nothing changes on screen. The saved value does not change either. Only the control's value changes: the host's own state still holdsundefined,onValueChangeis still the host'sonChange, and the cascade clear and the read-only display still read the raw value.onChangestill getsundefinedand stays uncontrolled. That host is a bare SDUIfield:selectnode:SchemaRendererhands it neither a value nor anonChange. If its value were mapped to'', the control would freeze on the placeholder after a pick, because nothing would ever hand the pick back. The comment this change replaces named exactly that risk, and the guard keeps it out..changeset/11804-react-markup-warnings.md: a patch for@object-ui/app-shelland@object-ui/fields.No export, prop,
@object-ui/typesmember, accepted input orpackages/i18nkey changes.Pins
PackagesPage.pendingHeaderMarkup-11804.test.tsxuses the realPackageDetailSheetwith two drafts. It checks that the header and its count badge render, that React logs no nesting error, and that nopin the document holds a block element. With no drafts, there is no header and no error.SelectField.controlledFromFirstRender-11804.test.tsxhas three hosts:field:selectfields. Picking one logs no flip and the trigger shows the pick. On submit, the picked field is'silver'and the untouched one isundefined, never''.onChange. Going fromundefinedto a value logs no flip. The empty trigger still shows the placeholder, and mounting does not callonChange.onChange. The control stays uncontrolled and still shows the pick.Reverse check (run on committed
0fefd45, all mutation legs restored and proven)Each mutation was applied by
ablation-replace.mjs: the anchor hit once and the blob hash changed. After every leg the blob hash matchedHEADagain andgit diff HEADwas empty.pundefinedbranch (the pre-fix behaviour)undefinedto''unconditionally (noonChangeguard)The first M2 attempt was a no-op and is void. Its replacement text was a substring of the anchor, so
ablation-replacerefused the leg and ran nothing. It was re-run as a delete leg, with the result shown above.Gates (local, on
0fefd45;origin/maincef0eeemerged in as1e4bf56)pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' buildexited 0. That is 29 packages, and it covers the@object-ui/fields^...closure.pnpm --filter @object-ui/fields type-checkandpnpm --filter @object-ui/app-shell type-checkexited 0. Both chaintsconfig.test.json, and--listFilesOnlyshows each one includes its new pin.pnpm exec vitest run --maxWorkers=2 packages/fields/plus 61 other test files exited 0: 302 files passed, 1 skipped; 4780 tests passed, 7 skipped. The 61 files are every test that namesSelectField,select-trigger-orfield:select, plus everyPackagesPagetest.pnpm check:control-bytes,check:new-line-citations,check:changeset-claims,check:pending-changeset-literals,check:test-path-roots,check:unreferenced-sourcesandcheck:vi-mock-specifiersall exited 0. So didnode scripts/check-changeset-presence.mjsandnode scripts/check-changeset-no-major.mjs.eslint --no-inline-configon the four touched.tsxfiles gave 0 errors (warnings only, the repo's usualno-explicit-any/ hook-dependency shapes). This is a narrowed run. The repo-widepnpm lintbelongs to CI.Acceptance notes
selectbranch (BuiltinSelectControl,packages/components/src/renderers/form/form.tsx) passestoControlValue(value), which isundefinedfor an unseeded field. A first pick on a baretype: 'select'form field logs the same flip (measured in happy-dom through the realformrenderer). The fix would be the same shape,?? ''at that call site and not in the sharedtoControlValue, which the SDUIselectrenderer andradio-groupread as "absent" for theirdefaultValue. It is left out because it is not the ruled site, the object create form never reaches it, and it would add@object-ui/componentsto this change's verification surface. No public door or real producer was measured for it, so it is noted here rather than filed.StudioDesignSurface.tsxwas only read. Nothing underpackages/components/src/ui/**changed.Generated by Claude Code