feat(rjsf): suppress the duplicative top-level object title in RJSF forms#1601
Conversation
…itle The canonical @meshery/schemas form schemas carry a title on their root object (e.g. the import-design schema's "Import Design"). Rendered inside a titled modal the root title is drawn a second time — a duplicate heading in the default theme, or a collapsed accordion in the downstream custom RJSF templates that users must expand before seeing any fields. Introduce the standard, UI-schema-driven fix: hideRootObjectTitle() merges `ui:options.label = false` into the root of the UI schema, so RJSF's ObjectField hands an empty title (and no description) to whatever ObjectFieldTemplate is registered and the child fields render directly. The canonical JSON schema is consumed unmodified. RJSFFormModal applies it by default (the modal header already names the form); RJSFFormWrapper exposes it via the opt-in hideRootTitle prop. Adds unit + RJSF-derivation tests and documents the pattern in the schemas readme. Signed-off-by: Lee Calcote <leecalcote@gmail.com>
There was a problem hiding this comment.
Code Review
This pull request introduces a mechanism to suppress the root object title and description in React JSON Schema Form (RJSF) forms, preventing duplicate headers when forms are rendered inside titled surfaces like modals. This is achieved by adding a hideRootTitle prop to RJSFFormWrapper and RJSFFormModal, supported by a new utility function hideRootObjectTitle that sets ui:options.label = false on the UI schema. Comprehensive tests and documentation in readme.md are also added. Feedback suggests making the type check for ui:options in hideRootObjectTitle more robust by explicitly ensuring it is not an array.
| const existing = uiSchema?.['ui:options']; | ||
| const existingOptions = | ||
| typeof existing === 'object' && existing !== null ? (existing as Record<string, unknown>) : {}; |
There was a problem hiding this comment.
In JavaScript, typeof [] is 'object'. If uiSchema['ui:options'] is somehow configured as an array, typeof existing === 'object' && existing !== null will evaluate to true, and spreading it in { ...existingOptions, label: false } might lead to unexpected behavior. Adding !Array.isArray(existing) makes this check more robust and defensive.
const existing = uiSchema?.['ui:options'];
const existingOptions =
typeof existing === 'object' && existing !== null && !Array.isArray(existing)
? (existing as Record<string, unknown>)
: {};There was a problem hiding this comment.
Good call — applied in 989b483. The ui:options merge now guards with !Array.isArray(existing), so a malformed array option falls back to {} cleanly instead of being spread into index keys. Added a regression test for that case.
There was a problem hiding this comment.
Pull request overview
This PR standardizes how Sistent (and downstream consumers) render canonical @meshery/schemas RJSF forms inside titled surfaces (especially modals) without showing a duplicative, often-collapsed root object “header”, by applying the UI-schema lever ui:options.label = false at the root.
Changes:
- Added
hideRootObjectTitle(uiSchema)helper to non-mutatively mergeui:options.label=falseat the UI schema root. - Added
hideRootTitleprop toRJSFFormModal(defaulttrue) andRJSFFormWrapper(defaultfalse) to apply the helper automatically. - Added docs + unit/E2E-style tests to lock in merge behavior and verify RJSF root title suppression.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| src/schemas/readme.md | Documents the standard pattern and how to apply it via modal, wrapper, or helper. |
| src/custom/RJSFFormWrapper/RJSFFormWrapper.tsx | Adds hideRootTitle prop and applies hideRootObjectTitle when enabled. |
| src/custom/RJSFFormWrapper/RJSFFormModal.tsx | Adds hideRootTitle prop (default true) and forwards it to the wrapper. |
| src/custom/RJSFFormWrapper/index.ts | Re-exports hideRootObjectTitle from the module barrel. |
| src/custom/RJSFFormWrapper/hideRootObjectTitle.ts | Introduces the helper that merges ui:options.label=false without mutating input. |
| src/testing/hideRootObjectTitle.test.tsx | Adds unit tests for merge/no-mutation and an RJSF render assertion for root title suppression. |
Address review feedback on #1601: typeof [] === 'object', so guard the ui:options merge with !Array.isArray(...) to fall back cleanly when the option is malformed. Adds a regression test. Signed-off-by: Lee Calcote <leecalcote@gmail.com>
Description
The RJSF form schemas we consume from
@meshery/schemascarry a human‑readabletitleon their root object — e.g. the import‑design schema's roottitle: "Import Design". When that form is rendered inside a surface that already names it (most commonly a modal whose header shows the very same title), RJSF draws the root title a second time, directly above the fields:We want the root object's contents shown directly, with the duplicative title gone — while still consuming the canonical UI schema verbatim (no hand‑edited fork, no mutation of the JSON schema). This establishes a reusable standard for that situation across Sistent and its downstream consumers (Meshery, Meshery Cloud, Meshery Extensions).
The standard pattern
Set
ui:options.label = falseon the root of the UI schema. Per@rjsf/core'sObjectField, whenlabel === falsethe title handed to theObjectFieldTemplateis forced to''(and the description toundefined), so every template — the default@rjsf/muione and the custom collapsible templates used downstream — skips its header and renders the child fields inline. Because the lever is the UI schema and not the JSON schema, validation,required, and conditionalallOfbranches stay untouched.What changed
hideRootObjectTitle(uiSchema)— new exported helper that mergesui:options.label = falseinto the root of a UI schema. Returns a shallow copy (never mutates input); preservesui:order, per‑field overrides, and any existingui:options.RJSFFormModal— newhideRootTitleprop, defaulting totrue: the modal header already names the form, so the root object title is suppressed automatically. This fixes the Import Design modal out of the box.RJSFFormWrapper— samehideRootTitleprop, defaulting tofalseso the general‑purpose wrapper is unchanged unless a consumer opts in.src/schemas/readme.mddocuments the pattern and the three ways to apply it.How it was verified
hideRootObjectTitleunit tests (merge correctness, preservation, no‑mutation).@rjsf/corewith a recordingObjectFieldTemplateand asserts the root title RJSF derives is"Import Design"by default and""afterhideRootObjectTitle(with the child fields still present).jest: 12 suites / 295 tests),eslintclean on changed files,tsupbuild succeeds and the new symbol/prop appear in both the runtime (dist/index.mjs,dist/index.js) and type (dist/index.d.ts) outputs.Notes for reviewers
Downstream PRs in
meshery/mesheryandlayer5io/meshery-cloudadopt the same UI‑schema‑driven approach in their own RJSF stacks (replacing a schema‑mutatinghideTitlehack). They inline a local mirror ofhideRootObjectTitleuntil they consume a release of Sistent that exports it.