Skip to content

Redesign docs theme and apply sentence case throughout - #378

Merged
bgolat merged 5 commits into
mainfrom
claude/docs-design-casing-82328e
Sep 10, 2026
Merged

bgolat merged 5 commits into
mainfrom
claude/docs-design-casing-82328e

Conversation

@bgolat

@bgolat bgolat commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Rebuilds the docs theme around a flat, hairline-ruled visual language driven by the Defined Networking brand palette, and normalizes casing across all 31 docs.

Design system

  • Brand-anchored color ramps. Purple 50 is literally #5d22dd and gray 55 is brand slate, so the palette is verifiably the brand's rather than an approximation. Semantic tokens sit on top: a four-tier surface system (background / surface / surface-2 / border), text, accent, and shape, defined for both themes.
  • Type. Plus Jakarta Sans for headings, labels, and UI; Inter for body copy.
  • No top bar on desktop. The logo, search, and color-mode toggle move into the sidebar. Below 997px the navbar stays — it carries the hamburger drawer, which is the only navigation on small screens.
  • Flatter index cards that title themselves from the doc rather than the sidebar item, so shortened nav labels don't leak into the index pages.

Casing

  • Sentence case for headings, frontmatter titles, category labels, and navbar/footer items. Case-only heading edits preserve anchor slugs, and the build is configured to throw on broken anchors — so a passing build validates every cross-reference.
  • Prose normalized only where the repo already had a dominant convention: lighthouse (was 42 capitalized vs 237 lowercase) and certificate authority (4 vs 18). Fenced code blocks were skipped.
  • Config pills carry literal YAML values, so the uppercase transform is gone and Default: False is corrected to false in 14 places — FALSE misrepresented a case-sensitive value.

Accessibility

Audited every text node against its effective background and fixed 14 WCAG AA contrast failures:

Issue Before After
Muted text (systemic) 3.19–3.49:1 4.65–5.6:1
Code comments 3.66–3.71:1 4.81–5.1:1
Admonition headings (×4) 2.91–3.99:1 ≥4.5:1

Also raised mobile touch targets — the drawer close button was 21×21, under the 24×24 minimum in WCAG 2.2 SC 2.5.8. Desktop keeps its tighter rhythm. Sidebar labels are no longer clamped to two lines by theme-classic, which was silently truncating the longest guide titles.

Other

  • Open Graph image: 990KB → 176KB. It's a photographic starfield, close to worst case for PNG. Re-encoded as JPEG q85 and compared against the original before committing. Renamed so social platforms re-fetch rather than serving the cached original.
  • Two dependencies promoted to direct: @types/react and @docusaurus/plugin-content-docs. Under pnpm's strict layout neither was resolvable for types, which broke pnpm typecheck on every swizzled theme component. This pre-dated the branch — typecheck now passes for the first time.

Reviewer notes

  • The package.json / lockfile change is the least expected part of a design PR — see above for why it's needed.
  • Four new files under src/theme/ are swizzled Docusaurus components. DocSidebar/Desktop and DocCard are full ejects; both were unavoidable (the card renders its emoji as a text node inside the heading, which no wrapper or CSS can remove). Each carries a comment explaining why.
  • Verified: build, typecheck, format:check, and test all pass. Both light and dark themes checked across six pages, plus a 375px mobile viewport.
  • Not covered: non-text contrast for UI borders (hairline borders measure 1.27–1.7:1 against the 3:1 guideline — a consequence of the flat aesthetic, worth a separate decision), screen-reader testing, and tablet breakpoints.

🤖 Generated with Claude Code

Rebuild the theme around a flat, hairline-ruled visual language driven by
Defined Networking's brand palette, and normalize casing across the docs.

Design system
- Anchor the colour ramps on the exact brand values, so purple 50 is
  literally #5d22dd and gray 55 is brand slate. Add semantic tokens for a
  four-tier surface system (background/surface/surface-2/border), text,
  accent and shape, defined for both themes.
- Plus Jakarta Sans for headings, labels and UI; Inter for body copy.
- Move the logo, search and colour-mode toggle into the sidebar and drop
  the top bar on desktop. Below 997px the navbar stays, since it carries
  the hamburger drawer that is the only navigation on small screens.
- Replace DocCard with a flatter card that titles itself from the doc
  rather than the sidebar label, so shortened nav labels do not leak into
  the index pages.

Casing
- Sentence case for headings, frontmatter titles, category labels and
  navbar/footer items. Case-only heading edits preserve anchor slugs, and
  the build throws on broken anchors, so the passing build validates every
  cross-reference.
- Normalize prose where the repo already had a dominant convention:
  lighthouse (was 42 capitalized vs 237 lower) and certificate authority
  (4 vs 18), skipping fenced code blocks.
- Config pills carry literal YAML values, so drop the uppercase transform
  and correct "Default: False" to "false" in 14 places.

Accessibility
- Fix 14 WCAG AA contrast failures found by auditing every text node
  against its effective background. The systemic one was muted text at
  3.19-3.49:1; gray-42 is the lightest stop clearing 4.5:1 on all light
  surfaces. Also code comments and four admonition headings.
- Raise mobile touch targets: the drawer close button was 21x21, under the
  24x24 minimum in WCAG 2.2 SC 2.5.8. Desktop keeps its tighter rhythm.
- Unclamp sidebar labels, which theme-classic truncates to two lines.

Other
- Re-encode the Open Graph image as JPEG: 990KB to 176KB. It is a
  photographic starfield, close to worst case for PNG.
- Add @types/react and @docusaurus/plugin-content-docs as direct
  dependencies. Under pnpm's strict layout neither was resolvable for
  types, which broke typecheck on every swizzled theme component.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 8, 2026 •

Copy link
Copy Markdown

Deploying docs-nebula with  Cloudflare Pages  Cloudflare Pages

Latest commit: ba5edec
Status: ✅  Deploy successful!
Preview URL: https://de58b4a5.docs-nebula.pages.dev
Branch Preview URL: https://claude-docs-design-casing-82.docs-nebula.pages.dev

View logs

The TOC column arrived unlabelled, fenced by a continuous left rule that
looked identical wherever you were on the page, and its links had no
padding, so there was no hover target.

- Add an "On this page" heading in a <nav> landmark. Upstream renders a
  bare list in a plain <div>, so assistive tech had nothing to jump to and
  sighted readers had nothing naming the column. aria-labelledby points at
  the visible heading rather than repeating the string in an aria-label.
- Drop the continuous rule. The current section is marked by the type
  going accent-coloured and semibold, leaving the column to just its words.
- Move padding onto the links so they have a real hover target, and add a
  hover state.
- Make the wrapper sticky rather than the list, so the heading stays put
  while a long list scrolls beneath it.

Contrast verified in both themes: label 14.6-15.7:1, inactive links
5.4-5.9:1, active 6.8-7.3:1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@bgolat
bgolat marked this pull request as ready for review September 8, 2026 18:53
@bgolat
bgolat requested review from jasikpark and johnmaguire and removed request for jasikpark September 8, 2026 18:53

@jasikpark jasikpark left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed on the Cloudflare preview (b478aee3) at 1280×800 and 375×812, comparing against prod where behaviour changed.

The casing pass is clean: every heading edit is case-only and onBrokenAnchors: 'throw' validated the anchors, the intro.md link-text fix is right, the @docusaurus/plugin-content-docs promotion is justified by the DocCard import, and the OG image rename is complete. The TOC <nav aria-labelledby> landmark and the Prism comment-contrast bump are real accessibility wins.

Three regressions I could reproduce on the preview, each with a line comment:

  1. Wide tables clip on mobile instead of scrolling (components.css).
  2. ⌘K opens two DocSearch modals on desktop (a second SearchBar is mounted in the sidebar).
  3. Non-doc pages (the 404, any future src/pages route) lose all chrome on desktop, since the navbar is hidden globally but the sidebar only exists on doc pages.

Smaller suggestions and nits inline, all labelled.

— Caleb + Cache

Comment thread src/css/components.css Outdated
Comment on lines +570 to +576
.markdown table {
display: table;
width: 100%;
border-collapse: collapse;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
overflow: hidden;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

issue (blocking): display: table + overflow: hidden replaces Infima's table { display: block; overflow: auto }, which is what lets a wide table scroll inside the content column instead of pushing the whole page sideways. On the preview at 375px, /docs/guides/unsafe_routes/ has a document scrollWidth of 509 vs 360 on prod, and the right edge of the first table is clipped.

Suggest keeping display: block; overflow: auto — border-radius still clips a scrolling block's corners, so the rounded border survives.

Comment thread src/css/components.css
Comment on lines +53 to +60
@media (min-width: 997px) {
html:root {
--ifm-navbar-height: 0px;
}

.navbar {
display: none;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

issue (blocking): this hides the navbar at ≥997px on every page, but the sidebar that replaces it only exists on doc pages. On the preview, /docs/this-page-does-not-exist/ renders with no logo, no search and no colour-mode toggle — only the footer links. Any future src/pages route hits the same.

Suggest scoping the hide to pages that actually have a sidebar, e.g. #__docusaurus:has(.theme-doc-sidebar-container) .navbar.

<div className={styles.header}>
<Logo className={styles.brand} imageClassName={styles.brandImage} titleClassName={styles.brandTitle} />
<div className={styles.search}>
<SearchBar />

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

issue (blocking): this is a second mounted SearchBar. The navbar's implicit one is still in the DOM (only display: none), and each instance registers its own ⌘K listener. On the preview at 1280px, Meta+K opens two .DocSearch-Modal nodes at once.

Keep exactly one instance mounted — either drop the navbar's (swizzle Navbar/Content to skip the implicit search item) or don't render one here.

Comment thread src/theme/DocSidebar/Desktop/index.tsx Outdated
Comment on lines +33 to +38
const { label, href, to, ...rest } = item as { label?: string; href?: string; to?: string };
if (!label || (!href && !to)) {
return [];
}
return [
<Link key={label} className={styles.footerLink} {...(href ? { href } : { to })} {...rest}>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

suggestion: ...rest forwards every navbar-item config key onto the anchor, so position="right" lands in the DOM as an unknown attribute (visible on the preview's footer link). Pick the ones you want (rel, target, className) explicitly.

Comment thread docs/config/stats.mdx
## stats.message_metrics

<Pill className="mb-24">Default: False</Pill>
<Pill className="mb-24">Default: false</Pill>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

suggestion: two pills further down this file (L67 DEFAULT: nebula, L74 DEFAULT: tcp) still use the all-caps label. With text-transform: uppercase gone from Pill, they now render as literal caps next to sentence-case Default: false everywhere else. The no-transform class on them is dead now too.

Comment thread src/css/base.css Outdated
--font-sans: 'Inter', var(--font-fallback);
/* Plus Jakarta Sans carries headings and labels */
--font-display: 'Plus Jakarta Sans', var(--font-sans);
--font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

suggestion: 'JetBrains Mono' heads the stack but is never loaded, so code blocks fall through to ui-monospace. Either add it to the font loading or drop it from the stack so the declared font matches what renders.

max-height: calc(100vh - 2rem);
}

.container > div[class*='tableOfContents'] {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

suggestion (non-blocking): [class*='tableOfContents'] matches an upstream CSS-module hash, so it will stop matching silently on the next theme-classic bump. Same pattern in components.css ([class*='linkLabel'], [class*='docCardListItem']). Where a stable theme-* class exists, prefer it; where not, a class set from the swizzled component is more robust.

Comment thread src/css/base.css Outdated
--sidebar-bg: var(--surface-1);
--field-bg: var(--surface-2);

--accent: var(--dn-color-purple-75, hsl(var(--dn-color-purple-hs), 75%));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nitpick (non-blocking): --dn-color-purple-75 doesn't exist (the ramp is 10–90 in tens plus 95), so this always resolves to the fallback. Same for --dn-color-purple-45 in theme.css L10. Either add the stops or write the hsl() directly.

Comment thread src/components/Pill/Pill.module.css Outdated
border-radius: var(--radius-pill);
padding: 5px 10px;

&:global(.no-transform),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nitpick (non-blocking): with text-transform: uppercase gone, this .no-transform block has nothing to undo (and the selector is duplicated). Safe to delete along with the no-transform usages in stats.mdx.

Comment thread src/theme/DocCard/index.tsx Outdated
*
* Ejected rather than wrapped because the upstream card renders its icon as a
* text node inside the heading (`{icon} {title}`), which no wrapper or CSS can
* remove. The same generic page emoji on every entry carried no information.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nitpick (non-blocking): "carried no information" describes the diff rather than the code; it reads fine in the commit message but will look odd to a reader who never saw the emoji. The sentence before it already carries the constraint (icon is a text node in the heading), so this one can go.

bgolat and others added 3 commits September 10, 2026 08:05
All three reported by @jasikpark on #378 and reproduced locally.

Wide tables pushed the page sideways on mobile. `display: table` replaced
Infima's `display: block; overflow: auto`, which is what keeps a wide
table scrolling inside the content column. At a 343px column the table now
clamps to the column, scrolls internally, and the document no longer
overflows; border-radius still clips the scrolling block, so the rounded
border survives.

Non-doc pages lost all chrome on desktop. The navbar was hidden at ≥997px
everywhere, but the sidebar that replaces it only exists on doc pages, so
the 404 rendered with no logo, search or colour-mode toggle. The hide is
now scoped to pages that actually have a sidebar. Where :has() is
unsupported the rule simply does not apply, leaving the navbar visible —
redundant chrome rather than a broken page.

Cmd+K opened two DocSearch modals. Hiding the navbar with CSS left its
SearchBar mounted, and each instance binds its own listener. The navbar's
copy is now suppressed while the rail renders one.

Deciding that needed a signal neither component had on its own: the navbar
renders outside DocsSidebarProvider, so useDocsSidebar throws there, and
the route is no help either — the docs plugin owns /docs/**, so a 404
under that prefix reports the same route context as a real doc, and
suppressing on the route left the 404 with no search at all. A small
context published from Root carries the fact from the rail, which knows
it, to the navbar, which needs it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Follow-up to #378 review by @jasikpark. Non-blocking items 1-7; the
Google Fonts question and the CSS-module hash selectors are left for a
separate decision.

- Two pills in stats.mdx still read DEFAULT: nebula / DEFAULT: tcp in
  caps, next to sentence-case Default: false everywhere else. Their
  no-transform class had nothing left to undo, so drop it and the now-dead
  rule in Pill.module.css with it.
- "Certificate Authority" in the cert v2 guide was missed by the
  sentence-case pass: the regex required a lowercase character before the
  match, and this one follows "v2 ".
- --dn-color-purple-75 and --dn-color-purple-45 are not stops on the ramp
  (it runs in tens plus 95), so both always resolved to their fallback.
  Write the hsl() directly rather than implying a stop exists.
- 'JetBrains Mono' headed the mono stack but was never loaded, so code
  already rendered in ui-monospace. Declare what actually renders.
- The sidebar footer links spread the whole navbar item config onto the
  anchor, putting `position` into the DOM as an invalid attribute. Take
  only href/to/rel/target.
- Drop a DocCard comment that described the diff rather than the code.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nothing renders at 800 — the heaviest weight in the built CSS is 700, and
--label-weight is 600 — so the face was downloaded and never used.

Staying on Google Fonts for now; self-hosting via @fontsource is a
separate call.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@bgolat

bgolat commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

Thanks Caleb — all three blockers reproduced exactly as described, including your scrollWidth numbers. Fixed in 08b0a6f, suggestions 1–7 in 8e720bb, the font weight in ba5edec.

Blockers

Tables — restored display: block; overflow: auto. You were right that border-radius still clips a scrolling block, so the rounded border survives. Verified at a 343px column: the table clamps to the column, scrolls internally (content 492px), and the document no longer overflows.

Worth flagging that I'd claimed "no horizontal overflow on mobile" in my own audit. That was wrong — I tested viewing-nebula-logs, which has no wide table, and reported it as a general result. Good catch.

Non-doc pages — took your suggested scoping, #__docusaurus:has(.theme-doc-sidebar-container) .navbar. The 404 has logo, search and toggle back. Where :has() isn't supported the rule just doesn't apply, leaving the navbar visible — redundant chrome rather than a broken page.

Duplicate search — one DocSearch-Button in the DOM, one modal on ⌘K.

This one didn't land where I expected, and it's why the diff now carries a small context provider. Both routes I tried first were dead ends:

  • useDocsSidebar() throws outside DocsSidebarProvider, and the navbar renders outside it — SSG failed on all 35 paths.
  • Route context looked promising, but it's identical on a real doc and on a 404: the docs plugin owns /docs/**, so /docs/this-page-does-not-exist/ reports docusaurus-plugin-content-docs too. Suppressing on the route would have shipped your exact bug back, with the 404 having no search at all.

So the rail publishes its own presence through a context provided at Root — the only ancestor common to both trees. Heavier than I'd like for the problem, but it's the only signal that distinguishes "a rail is rendering search" from "this path merely starts with /docs/". Open to a lighter approach if you see one.

Suggestions

Done: the two all-caps pills and the dead no-transform rule, the Certificate Authority miss (my regex required a lowercase char before the match, and that one follows v2 ), the phantom --dn-color-purple-75 / -45 stops, the unloaded JetBrains Mono, the ...rest spread (now href/to/rel/target only — confirmed position="right" is gone from the built HTML and rel="noopener" survived), and the DocCard comment.

Deferred

Google Fonts — staying for now, Brian's call. Dropped the unused 800 though: heaviest weight in the built CSS is 700, so that face was downloading and never rendering.

Hash selectors — agreed on the risk, and that silent failure is the bad part. I'd rather pair it with the pending 3.9.2 → 3.10.2 bump on its own branch, since that's exactly where a break would surface. Happy to do it here instead if you'd prefer it not linger.

Not covered

Viewport emulation stopped working in my browser partway through, so the table fix is verified by constraining the container rather than at a true 375px. The mechanism is the one that matters and the sub-997px path was confirmed at 980px, but a glance at the preview on ba5edec at real mobile width would be worth it.

🤖 Generated with Claude Code

@bgolat
bgolat requested a review from jasikpark September 10, 2026 16:00

@jasikpark jasikpark left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-reviewed at ba5edec. The three blocking regressions are resolved: wide tables scroll correctly on mobile, desktop DocSearch mounts only once, and non-doc pages retain their navbar chrome. The follow-up cleanup also addresses the earlier inline suggestions. No further findings—approving.

@bgolat
bgolat merged commit bb67f6b into main Sep 10, 2026
4 checks passed
@bgolat
bgolat deleted the claude/docs-design-casing-82328e branch September 10, 2026 19:32
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.

2 participants