Skip to content

feat (change-history): add sidebar with topbar toggle#10952

Open
tunjiadeyemi wants to merge 3 commits into
swagger-api:masterfrom
tunjiadeyemi:ft/history
Open

feat (change-history): add sidebar with topbar toggle#10952
tunjiadeyemi wants to merge 3 commits into
swagger-api:masterfrom
tunjiadeyemi:ft/history

Conversation

@tunjiadeyemi

Copy link
Copy Markdown

Description

This PR adds an API Change History feature to Swagger UI so users can quickly see what changed between OpenAPI spec reloads.

Implemented:

  • New change-history core plugin with Redux state/actions/selectors
  • Local snapshot persistence in localStorage (keyed by spec URL)
  • Spec diff engine for:
    • endpoint add/remove/modify
    • parameter/request/response changes
    • schema add/remove and property-level schema diffs
    • tags/info/security changes
    • media-type changes
  • History UI moved to a right sidebar
  • History trigger moved to top bar as a history icon (next to dark mode toggle), with unseen-change badge
  • Light/dark mode styling for the sidebar and controls

Motivation and Context

When backend teams update the OpenAPI document, frontend/API consumers currently have no native way in Swagger UI to track what changed. They must manually compare specs.
This feature adds built-in change visibility by capturing snapshots and presenting a readable history timeline of changes after reloads.
Fixes #10951

How Has This Been Tested?

  • Manual testing:

    • Ran local dev server and loaded specs from local/dev sources
    • Verified baseline snapshot is created on first load
    • Reloaded modified specs and verified history entries update correctly
    • Verified history icon badge appears for unseen changes
    • Verified sidebar open/close behavior (button, backdrop, close button)
    • Verified light/dark mode color behavior for sidebar controls
  • Automated testing:

    • Ran targeted unit tests for change-history:
    • test/unit/core/plugins/change-history/fn.test.js
    • test/unit/core/plugins/change-history/container.jsx
    • test/unit/core/plugins/change-history/sidebar.jsx
    • test/unit/standalone/plugins/top-bar/ChangeHistoryToggle.jsx
      Result: all relevant tests passed

Screenshots (if appropriate):

Screenshot 2026-07-08 at 11 50 30 Screenshot 2026-07-08 at 11 50 33 Screenshot 2026-07-08 at 12 10 29

Checklist

My PR contains...

  • No code changes (src/ is unmodified: changes to documentation, CI, metadata, etc.)
  • Dependency changes (any modification to dependencies in package.json)
  • Bug fixes (non-breaking change which fixes an issue)
  • Improvements (misc. changes to existing features)
  • Features (non-breaking change which adds functionality)

My changes...

  • are breaking changes to a public API (config options, System API, major UI change, etc).
  • are breaking changes to a private API (Redux, component props, utility functions, etc.).
  • are breaking changes to a developer API (npm script behavior changes, new dev system dependencies, etc).
  • are not breaking changes.

Documentation

  • My changes do not require a change to the project documentation.
  • My changes require a change to the project documentation.
  • If yes to above: I have updated the documentation accordingly.

Automated tests

  • My changes can not or do not need to be tested.
  • My changes can and should be tested by unit and/or integration tests.
  • If yes to above: I have added tests to cover my changes.
  • If yes to above: I have taken care to cover edge cases in my tests.
  • All new and existing tests passed.

@wbizmo wbizmo left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went through this properly, checked out the branch and actually ran it, not just read the diff.

Big one: this reverts a same day bug fix

Earlier today master got a fix for a crash in getOAS3RequiredRequestBodyContentType, calling .entrySeq() on requestBody.getIn(["content"]) when a requestBody has no content field. Commit 832008a, PR #10837.

This branch was forked before that fix landed and never got rebased. So the diff here deletes the null check that fix added, and deletes all three tests that covered it (the describe("getOAS3RequiredRequestBodyContentType") block is gone from the test file).

I didn't just spot this, I reproduced it. Checked out the branch and ran the exact scenario the deleted test covered:

TypeError: Cannot read properties of undefined (reading 'entrySeq')
  at getOAS3RequiredRequestBodyContentType (src/core/plugins/spec/selectors.js:549:33)

So merging this as is brings back a crash that was already fixed and shipped, and removes the regression test that would've caught it. This alone should block the PR no matter how good the feature itself is.

dist files are hand committed

The diff includes dist/swagger-ui.js and dist/swagger-ui.js.map, a full rebuilt minified bundle. That's normally generated at release time, not something committed by hand in a feature branch. Please pull these out, they're just adding noise on top of the actual problem above.

localStorage design needs a second look

The feature snapshots the full resolved spec into localStorage on every load, keyed by spec URL, capped at 20 history entries but not capped by size.

  • writeJson() has no try/catch around localStorage.setItem. A large spec or a quota exceeded/private browsing situation will throw uncaught during spec load.
  • Full spec content sits in localStorage with no expiry beyond a manual "clear history" click. For internal APIs that's spec content parked indefinitely on whatever machine opened the docs.

Not a blocker on its own but worth deciding on before this ships.

Bottom line

Rebase onto current master and confirm the crash fix survives, pull the dist files out of the diff, and wrap the localStorage write in a try/catch. Happy to take another look once that's done, the feature idea itself is solid, this is a branch hygiene problem more than a design problem.

@tunjiadeyemi

Copy link
Copy Markdown
Author

@wbizmo awesome, i'll get to it, thanks!

- harden localStorage writes and snapshot retention
- catch quota/private-mode failures on setItem
- cap full-spec snapshots by size (512KB) and TTL (30 days) so history data cannot
grow or linger unbounded.
@tunjiadeyemi

Copy link
Copy Markdown
Author

hey @wbizmo

Addressed everything:

  1. Crash fix / rebase Rebased onto current master, so the fix commit is included.
  2. The null-safe check in getOAS3RequiredRequestBodyContentType is present again, and the describe("getOAS3RequiredRequestBodyContentType") regression tests are back. Re-ran those tests and confirmed the missing-content case no longer throws Cannot read properties of undefined (reading 'entrySeq').
  3. Dist artifacts Removed the hand-committed dist/swagger-ui.js and dist/swagger-ui.js.map from the PR so they’re no longer in the diff.
  4. Wrapped localStorage.setItem in writeJson() with a try/catch, and log a console.warn on quota / private-mode failures so spec load doesn’t crash.
  5. Added retention limits beyond entry count:
  • snapshot size cap (changeHistoryMaxSnapshotBytes, default 512KB)
  • TTL expiry (changeHistoryTtlMs, default 30 days)
  • Oversized specs skip full-spec persistence and store a small hash marker instead, so we don’t re-baseline on every reload.
    Happy for another look when you get a chance.

@wbizmo wbizmo left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went through this again properly, checked out the updated branch and re-ran everything rather than trusting the commit messages.

The rebase fixed the main issue, 832008a is now an actual ancestor of this branch, and I re-ran the crash scenario (requestBody with no content field), it resolves cleanly instead of throwing. Dist files are gone from the diff too.

The localStorage hardening in 4fd4a71 is solid: writeJson now catches setItem failures instead of throwing uncaught, and the size cap (512KB) plus 30-day TTL means history data can't grow or linger unbounded. That's a real fix, not just a try/catch slapped on top.

Ran the full suite: 885 tests, 873 passing, no regressions, nothing pre-existing broken.

Nice turnaround on all three points. No objections from me at this point.

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.

feat: Track and display OpenAPI spec changes over time

2 participants