Skip to content

Apply and remember the ?theme= light/dark override in the docs embed#4380

Open
zenoachtig wants to merge 5 commits into
mainfrom
claude/unruffled-lalande-fa0fb0
Open

Apply and remember the ?theme= light/dark override in the docs embed#4380
zenoachtig wants to merge 5 commits into
mainfrom
claude/unruffled-lalande-fa0fb0

Conversation

@zenoachtig

Copy link
Copy Markdown
Contributor

Overview

  • Fix the docs embed ignoring ?theme=light/?theme=dark (RND-11571).
  • The embed root redirect now forwards the query string to the default tab, so the theme (and other params) aren't lost on the hop to /assistant or /page/.
  • Removed export const dynamic = 'force-static' from the dynamic-tree assistant and search pages, so the layout can actually read the forced theme (and customization overrides) from request headers instead of rendering statically with empty headers().
  • Persist the resolved forced theme to the embed's own storage (gitbook-theme-embed:<siteId>), so it's remembered across tab navigation and reloads instead of only applying while the query is present. An explicit ?theme= on a later load still overrides the remembered one; single-theme sites (which intentionally ignore the override) don't persist it.
  • Note: following the embedding iframe's CSS color-scheme is intentionally not included — the embed's dark styling is class-driven (Tailwind dark:, .dark-gated rules) and CSS has no selector for an element's used color-scheme, so it can't be done CSS-natively without reworking the design-system dark variant. ?theme= is the supported control.

Demo

Verifone-style embed on a dark-mode device — before vs after ?theme=light:

Theme remembered across tab navigation (no ?theme= on the second tab):

— Authored by Claude

zenoachtig and others added 2 commits July 8, 2026 11:56
The docs embed ignored ?theme=light/dark: the query was dropped when the
embed redirected to its default tab, and the assistant/search tabs were
forced static so the theme header was never read. Forward the query on
redirect, drop force-static from those dynamic-tree pages, and persist the
resolved forced theme to the embed's own storage so it is remembered across
tab navigation instead of only applying while the query is present.

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

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6881ec4134

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/gitbook/src/components/Embeddable/EmbeddableRootLayout.tsx Outdated
@argos-ci

argos-ci Bot commented Jul 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Argos notifications ↗︎

Build Status Details Updated (UTC)
customers-v2-cloudflare (Inspect) ⚠️ Changes detected (Review) 163 changed, 2 ignored Jul 10, 2026, 11:18 AM
customers-v2-vercel (Inspect) ⚠️ Changes detected (Review) 32 changed, 97 ignored Jul 10, 2026, 11:16 AM
v2-cloudflare (Inspect) ⚠️ Changes detected (Review) 22 changed, 11 ignored Jul 10, 2026, 11:19 AM
v2-vercel (Inspect) ⚠️ Changes detected (Review) 24 changed, 5 ignored Jul 10, 2026, 11:18 AM

Persisting theme.forcedTheme wrote the site's own default into embed storage
on every plain visit to a single-theme site, which could overwrite a
remembered explicit theme and keep winning if the site later became
multi-theme. Gate persistence on an actual URL ?theme= override and a
toggleable site. (PR #4380 review)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jul 8, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 6c2638e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
gitbook Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Comment on lines -9 to -10
export const dynamic = 'force-static';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

are we sure about that ? seems dangerous to change this

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good instinct to double-check — it's safe, and it's load-bearing for this fix:

  • This is only the dynamic-tree copy. The middleware routes here only for genuinely dynamic requests (?theme=, a customization override, adaptive/VA/contextId, or the gitbook-dynamic-route cookie). The normal cacheable case is still served by the static tree, which keeps force-static (untouched) — so nothing that was cached before stops being cached.
  • Under force-static, headers() is empty, so the layout's getThemeFromMiddleware() (and the x-gitbook-customization override) always resolved to null — the page silently dropped ?theme= and customization overrides. That's the RND-11571 bug; removing it lets those header-driven requests render correctly.
  • Those requests are effectively uncacheable anyway (contextId responses already carry max-age=0, must-revalidate), so force-static gave no caching benefit here — only wrong output.
  • Precedent: the docs tab (page/[pagePath]) in this same dynamic tree already has no force-static; this just makes assistant/search consistent with it.

If you'd still rather keep force-static, the only alternative is applying the theme purely client-side, which reintroduces a first-paint flash and still wouldn't fix the dropped customization override — so I'd recommend against it. Happy to talk it through.

🤖 Addressed by Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Zeno the human here, I'm also a bit concerned so I'll leave it out of this PR and seeing if the first-paint is a big problem or not. If we get reports on it we can reconsider, but I'd rather not touch it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

hello Zeno the human 👋🏼, I'm checking on my side the path claude is mentioning

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@zenoachtig looks like it's safe, maybe @nicolas you can confirm ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Reworked per @conico974's suggestion — the routes stay force-static. Instead of making them dynamic, the middleware now threads the forced theme into the embed route context (a new embedTheme on the encoded site data), scoped to the embed route only (the main site keeps its existing dynamic header path). The embed layouts read it from the route params — which works under static rendering — and the redirect forwards it to the default tab. The theme becomes part of the route's static cache key, so each theme is its own cached entry. force-static is restored on assistant/search. Pushed in 6cc98ad.

🤖 Addressed by Claude Code

@conico974 conico974 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is not how we should do it. Those route should never be dynamic

zenoachtig and others added 2 commits July 10, 2026 13:06
…atic

Following review: the embed routes must stay statically rendered, so reading
the theme from request headers (which are empty under force-static) doesn't
work. Instead the middleware threads the forced theme into the embed route
context (a new `embedTheme` on the encoded site data), scoped to the embed and
not the main site. The embed layouts read it from the route params, the
redirect forwards it to the default tab, and force-static is restored on the
assistant/search tabs. The theme becomes part of the route's static cache key.
The main site keeps its existing dynamic header-based theme handling.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

3 participants