Let the API docs follow the system theme

The app has a dark theme. /api/docs was the one page that stayed white when the
rest of the site went dark, which is exactly the page somebody has open beside
the app while they work.

Redoc does not read prefers-color-scheme, and the <redoc spec-url> element it
was using takes no theme at all - so the page now initialises through
Redoc.init, which does. Light keeps the default look; dark names the colours
Redoc otherwise derives from the background and gets wrong: body text, the
sidebar, and the right-hand sample panel, which came out dark-on-dark.

It re-initialises on a live scheme change rather than restyling, because Redoc
bakes its colours into styled-components at init - a CSS override reaches the
chrome and misses the content. The listener has the addListener fallback for the
older MediaQueryList, since a shop-floor kiosk browser is exactly where this
gets read.

Nothing new is loaded: same vendored bundle, same spec, no font, no CDN. A test
pins that, because theming is the kind of change that quietly introduces one.

The try-it page is left light. Swagger UI 5 ships no dark theme, so it would
mean vendoring a community stylesheet that fights upstream on every bump - and
it is a development tool that is off in production anyway.
This commit is contained in:
cproudlock
2026-09-19 12:36:15 -04:00
parent 76f085e224
commit be1eb9b366
2 changed files with 88 additions and 5 deletions

View File

@@ -247,3 +247,37 @@ def test_the_spec_declares_the_mount_it_is_served_from(enabled_client):
# and duplicating it into every path would double it under a subpath.
assert '/api/apitokens' in mounted['paths']
assert '/shopdb/api/apitokens' not in mounted['paths']
# ---------------------------------------------------------------------------
# The read-only page follows the OS theme
# ---------------------------------------------------------------------------
def test_the_redoc_page_follows_the_system_theme(client):
"""The app has a dark theme; /api/docs was the one page that stayed white.
Redoc ignores prefers-color-scheme and the <redoc spec-url> element takes no
theme, so the page has to go through Redoc.init - which is also why this
test pins the mechanism and not just the CSS. A future edit that puts the
element back would lose dark mode without changing a single colour.
"""
page = client.get('/api/docs/').get_data(as_text=True)
assert 'Redoc.init' in page
assert '<redoc spec-url' not in page, 'the element takes no theme'
assert '@media (prefers-color-scheme: dark)' in page
assert 'var DARK=' in page and 'var LIGHT=' in page
# Re-render on a live scheme change, with the fallback for the older
# MediaQueryList that a kiosk browser may still have.
assert 'addEventListener("change",draw)' in page
assert 'q.addListener(draw)' in page
def test_the_docs_page_still_loads_nothing_from_another_origin(client):
"""Theming is the kind of change that quietly introduces a font or a CDN."""
import re as _re
page = client.get('/api/docs/').get_data(as_text=True)
assert not _re.findall(r'(?:src|href)\s*=\s*[\'"](?:https?:)?//', page)