Part of #8 (Phase 2).
Problem / Goal
zazu/app-4 subclasses DocsUI::Shell and copy-pastes the private #topbar verbatim to change ONE href (brand link / → /docs) — flagged fragile in their own comments — and injects hand-written dark-mode Rouge CSS because code_theme supports exactly one theme while the switcher offers light AND dark themes (code blocks are unreadable in one of them on every site that offers both).
Goal: both needs become config knobs; the app-4 Shell subclass shrinks to nothing (once the in-flight nonce fix on fix/csp-nonce-shell merges).
Context (read these first)
app/components/docs_ui/shell.rb:127-138 — #topbar, the hardcoded a(href: "/").
app/components/docs_ui/code.rb:71-77 — highlight_css emits ONE theme scoped to .code-highlight.
lib/docs_kit/configuration.rb — knob conventions (attr + default + resolver).
~/Code/zazu/app-4/app/views/docs/shell.rb — the workaround being deleted.
- daisyUI's built-in theme list — which names are dark (for the default).
Decision
c.brand_href — default "/"; Shell#topbar reads it. One-line change, kills the copy-paste subclass.
c.code_theme_dark — default nil (current single-theme behavior, fully backwards compatible). When set, DocsUI::Code#highlight_css additionally emits the dark theme's CSS scoped under [data-theme=X] .code-highlight for each theme name in c.dark_themes.
c.dark_themes — default: the built-in daisyUI dark theme names (%w[dark synthwave halloween forest black luxury dracula business night coffee dim sunset abyss], frozen constant Configuration::DEFAULT_DARK_THEMES), intersected with c.themes at render time so only shipped themes generate CSS. Overridable for custom themes (app-4's zazu-dark).
- Resolver
code_theme_dark_class mirrors the existing code_theme_class (String or Class).
Alternatives rejected: prefers-color-scheme media queries (theme is user-selected via data-theme, not OS-derived); auto-detecting darkness from daisyUI CSS variables at render time (Ruby can't see the compiled CSS — a static default list + override is honest); making code_theme accept a {light:, dark:} hash (breaks the existing String contract; a second knob is simpler and compatible).
Implementation steps (TDD)
- Specs first:
spec/docs_kit/configuration_spec.rb — defaults (brand_href "/"; code_theme_dark nil; dark_themes list frozen + overridable); code_theme_dark_class resolves String/Class/nil. spec/docs_ui/shell_spec.rb — brand link href follows config. spec/docs_ui/code_spec.rb — with code_theme_dark set and themes: %w[light dark], output contains [data-theme=dark] .code-highlight rules and no rules for non-shipped dark themes; with it nil, output byte-identical to today (regression guard).
- Implement the three knobs + resolver in
lib/docs_kit/configuration.rb; thread through shell.rb and code.rb.
- Configure the dogfood site (
docs/config/initializers/docs_kit.rb) with a dark pair (e.g. Rouge::Themes::Github light / Monokai dark) so it's visibly dogfooded across its 9 themes.
- README: document all three knobs in the Configure section; note the theme-sync invariant is unaffected (Rouge CSS is inline, not Tailwind).
Verification gates
bundle exec rspec — green; Configuration 100% covered.
bundle exec rubocop — no offenses.
- Dogfood: switch light↔dark themes on
/docs/languages — code blocks restyle with the theme (JS-free CSS scoping, no flash).
- Backwards compat: nil
code_theme_dark output unchanged (spec-asserted).
Out of scope
Part of #8 (Phase 2).
Problem / Goal
zazu/app-4 subclasses
DocsUI::Shelland copy-pastes the private#topbarverbatim to change ONE href (brand link/→/docs) — flagged fragile in their own comments — and injects hand-written dark-mode Rouge CSS becausecode_themesupports exactly one theme while the switcher offers light AND dark themes (code blocks are unreadable in one of them on every site that offers both).Goal: both needs become config knobs; the app-4 Shell subclass shrinks to nothing (once the in-flight nonce fix on
fix/csp-nonce-shellmerges).Context (read these first)
app/components/docs_ui/shell.rb:127-138—#topbar, the hardcodeda(href: "/").app/components/docs_ui/code.rb:71-77—highlight_cssemits ONE theme scoped to.code-highlight.lib/docs_kit/configuration.rb— knob conventions (attr + default + resolver).~/Code/zazu/app-4/app/views/docs/shell.rb— the workaround being deleted.Decision
c.brand_href— default"/";Shell#topbarreads it. One-line change, kills the copy-paste subclass.c.code_theme_dark— defaultnil(current single-theme behavior, fully backwards compatible). When set,DocsUI::Code#highlight_cssadditionally emits the dark theme's CSS scoped under[data-theme=X] .code-highlightfor each theme name inc.dark_themes.c.dark_themes— default: the built-in daisyUI dark theme names (%w[dark synthwave halloween forest black luxury dracula business night coffee dim sunset abyss], frozen constantConfiguration::DEFAULT_DARK_THEMES), intersected withc.themesat render time so only shipped themes generate CSS. Overridable for custom themes (app-4'szazu-dark).code_theme_dark_classmirrors the existingcode_theme_class(String or Class).Alternatives rejected:
prefers-color-schememedia queries (theme is user-selected viadata-theme, not OS-derived); auto-detecting darkness from daisyUI CSS variables at render time (Ruby can't see the compiled CSS — a static default list + override is honest); makingcode_themeaccept a{light:, dark:}hash (breaks the existing String contract; a second knob is simpler and compatible).Implementation steps (TDD)
spec/docs_kit/configuration_spec.rb— defaults (brand_href"/";code_theme_darknil;dark_themeslist frozen + overridable);code_theme_dark_classresolves String/Class/nil.spec/docs_ui/shell_spec.rb— brand link href follows config.spec/docs_ui/code_spec.rb— withcode_theme_darkset andthemes: %w[light dark], output contains[data-theme=dark] .code-highlightrules and no rules for non-shipped dark themes; with it nil, output byte-identical to today (regression guard).lib/docs_kit/configuration.rb; thread throughshell.rbandcode.rb.docs/config/initializers/docs_kit.rb) with a dark pair (e.g.Rouge::Themes::Githublight /Monokaidark) so it's visibly dogfooded across its 9 themes.Verification gates
bundle exec rspec— green;Configuration100% covered.bundle exec rubocop— no offenses./docs/languages— code blocks restyle with the theme (JS-free CSS scoping, no flash).code_theme_darkoutput unchanged (spec-asserted).Out of scope
fix/csp-nonce-shell, issue DocsUI::Shell inline scripts are not nonce-aware — blocked under a nonce-based CSP #2).feat/configurable-icon-library, issue Make DocsUI::Icon's icon library configurable (currently hardcoded to RailsIcons default_library) #3).DocsUI::Code.