Skip to content

fix(theme): stop duplicating branch properties in nested anyOf/oneOf renders#1549

Merged
sserrata merged 1 commit into
mainfrom
fix-anyof-nested-oneof-property-duplication
Jul 10, 2026
Merged

fix(theme): stop duplicating branch properties in nested anyOf/oneOf renders#1549
sserrata merged 1 commit into
mainfrom
fix-anyof-nested-oneof-property-duplication

Conversation

@sserrata

Copy link
Copy Markdown
Member

Summary

When an outer schema shapes as {properties, anyOf: [{oneOf: [...]}, ...]}, foldSiblingsIntoBranches merges the outer properties into each anyOf branch, giving each branch its own inner oneOf alongside merged properties. AnyOneOf then delegates that branch to SchemaNode, which folds again into each inner variant — but AnyOneOf was also rendering <Properties> directly for the branch, so the merged fields showed up twice per selected inner variant tab.

Gate the direct <Properties> render on the branch having no nested oneOf/anyOf/allOf. When a nested composition is present, the delegated SchemaNode already folds those properties into every variant, so rendering them inline as well is redundant.

Repro

Downstream discovered by pan.dev PR #1342 on the POST /ethernet-interfaces request schema. The spec shape:

type: object
properties:
  name: {...}
  default_value: {...}
  # ...5 more base props
anyOf:
  - oneOf:  # interface-type variants
      - title: aggregate_group
        properties: { aggregate_group: {...} }
      - title: tap
        properties: { tap: {...} }
      - title: layer2
        properties: { layer2: {...} }
      - title: layer3
        properties: { layer3: {...} }
  - oneOf:  # container-scope variants
      - title: folder
        ...

Before this fix: name, default_value, comment, link_speed, … render once at the outer object tab and again inside each aggregate_group / tap / layer2 / layer3 variant tab.

After this fix: each field appears exactly once — inside the variant tab where the merge lives, matching pre-5.1.0 behavior.

The existing demo fixture /tests/any-of-with-nested-one-of-and-properties (added in demo/examples/tests/anyOf.yaml) exercises this exact shape and demonstrates the fix visually on the demo site.

Test plan

  • yarn jest packages/docusaurus-theme-openapi-docs/src/theme/Schema/normalize.test.ts — all 30 tests pass, including the new regression test that documents the two-level fold invariant the render layer relies on.
  • yarn build in the demo — clean.
  • Visual check on the built demo at /tests/any-of-with-nested-one-of-and-properties: base properties should render once per variant tab, not twice.

Fixes the duplication observed in PaloAltoNetworks/pan.dev#1342.

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown

Size Change: 0 B

Total Size: 2.36 MB

ℹ️ View Unchanged
Filename Size
demo/.docusaurus/codeTranslations.json 2 B
demo/.docusaurus/docusaurus.config.mjs 16.4 kB
demo/.docusaurus/globalData.json 82 kB
demo/.docusaurus/i18n.json 372 B
demo/.docusaurus/registry.js 119 kB
demo/.docusaurus/routes.js 112 kB
demo/.docusaurus/routesChunkNames.json 46.7 kB
demo/.docusaurus/site-metadata.json 1.58 kB
demo/build/assets/css/styles.********.css 173 kB
demo/build/assets/js/main.********.js 697 kB
demo/build/assets/js/runtime~main.********.js 26.8 kB
demo/build/index.html 99.5 kB
demo/build/petstore/add-pet/index.html 30 kB
demo/build/petstore/create-user/index.html 24.7 kB
demo/build/petstore/create-users-with-array-input/index.html 24.8 kB
demo/build/petstore/create-users-with-list-input/index.html 24.8 kB
demo/build/petstore/delete-order/index.html 24.5 kB
demo/build/petstore/delete-pet/index.html 24.8 kB
demo/build/petstore/delete-user/index.html 25 kB
demo/build/petstore/find-pets-by-status/index.html 25.5 kB
demo/build/petstore/find-pets-by-tags/index.html 26.2 kB
demo/build/petstore/get-inventory/index.html 23.8 kB
demo/build/petstore/get-order-by-id/index.html 24.8 kB
demo/build/petstore/get-pet-by-id/index.html 25.6 kB
demo/build/petstore/get-user-by-name/index.html 25.1 kB
demo/build/petstore/login-user/index.html 25.6 kB
demo/build/petstore/logout-user/index.html 24.4 kB
demo/build/petstore/new-pet/index.html 25 kB
demo/build/petstore/pet/index.html 23.2 kB
demo/build/petstore/place-order/index.html 24 kB
demo/build/petstore/schemas/apiresponse/index.html 25.2 kB
demo/build/petstore/schemas/cat/index.html 39.4 kB
demo/build/petstore/schemas/category/index.html 26.3 kB
demo/build/petstore/schemas/dog/index.html 39.6 kB
demo/build/petstore/schemas/honeybee/index.html 39.6 kB
demo/build/petstore/schemas/id/index.html 23.4 kB
demo/build/petstore/schemas/order/index.html 27.4 kB
demo/build/petstore/schemas/pet/index.html 39.2 kB
demo/build/petstore/schemas/tag/index.html 24.7 kB
demo/build/petstore/schemas/user/index.html 41.1 kB
demo/build/petstore/store/index.html 22.2 kB
demo/build/petstore/subscribe-to-the-store-events/index.html 30.9 kB
demo/build/petstore/swagger-petstore-yaml/index.html 30.9 kB
demo/build/petstore/update-pet-with-form/index.html 25 kB
demo/build/petstore/update-pet/index.html 25.4 kB
demo/build/petstore/update-user/index.html 25 kB
demo/build/petstore/upload-file/index.html 24.8 kB
demo/build/petstore/user/index.html 22.9 kB

compressed-size-action

@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown

Visit the preview URL for this PR (updated for commit cdff976):

https://docusaurus-openapi-36b86--pr1549-yyuij7om.web.app

(expires Fri, 17 Jul 2026 18:28:36 GMT)

🔥 via Firebase Hosting GitHub Action 🌎

Sign: bf293780ee827f578864d92193b8c2866acd459f

…renders

When an outer schema shapes as `{properties, anyOf: [{oneOf: [...]}, ...]}`,
`foldSiblingsIntoBranches` merges the outer properties into each anyOf
branch, giving each branch its own inner `oneOf` alongside merged
properties. AnyOneOf then delegates that branch to SchemaNode, which
folds *again* into each inner variant — but AnyOneOf was also rendering
<Properties> directly for the branch, so the merged fields showed up
twice per selected inner variant tab.

Gate the direct <Properties> render on the branch having no nested
oneOf/anyOf/allOf. When a nested composition is present, the delegated
SchemaNode already folds those properties into every variant, so
rendering them inline as well is redundant.

Repro (see demo `/tests/any-of-with-nested-one-of-and-properties`):
the ethernet-interfaces-style shape rendered `name`, `default_value`,
etc. once at the outer object tab and again inside each aggregate_group
/ tap / layer2 / layer3 variant tab. After the fix each field appears
exactly once — inside the variant tab where the merge lives.

Regression covered by a Jest test asserting that a two-level fold
(outer anyOf branch, inner oneOf variant) leaves the merged
properties on the innermost variant only, matching the invariant the
render layer now relies on.
@sserrata sserrata force-pushed the fix-anyof-nested-oneof-property-duplication branch from 0d29cad to cdff976 Compare July 10, 2026 18:24
@github-actions

Copy link
Copy Markdown

📸 Visual Diff Report

Snapshot comparison against https://docusaurus-openapi.tryingpan.dev/

Total ✅ Match ❌ Diff ⏭ Skip
222 221 1 0
❌ 1 page(s) with visual regressions

/tests/any-of-with-nested-one-of-and-properties-at-same-level

Prod Preview Diff
prod screenshot for /tests/any-of-with-nested-one-of-and-properties-at-same-level preview screenshot for /tests/any-of-with-nested-one-of-and-properties-at-same-level pixel diff for /tests/any-of-with-nested-one-of-and-properties-at-same-level

Updated at 2026-07-10T18:36:03.074Z · run 29114452272

@sserrata sserrata merged commit 88c7ec7 into main Jul 10, 2026
12 checks passed
@sserrata sserrata deleted the fix-anyof-nested-oneof-property-duplication branch July 10, 2026 18:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant