Add drop-in theme system with retro, grimoire, and midnight themes#156
Open
EnriqueCanals wants to merge 17 commits into
Open
Add drop-in theme system with retro, grimoire, and midnight themes#156EnriqueCanals wants to merge 17 commits into
EnriqueCanals wants to merge 17 commits into
Conversation
Adds the plumbing for an opt-in theme system while leaving the default
look and behavior unchanged. A theme is selected via the ABBEY_THEME env
var (or `Rails.application.config.theme`) and contributes three things:
* View overrides: any file under `app/views/themes/<theme>/` overrides
its same-named default counterpart (including layouts and partials)
via Rails' view path prepending. Implemented in
`app/controllers/concerns/theming.rb` and included in
ApplicationController as a no-op for the default theme.
* Extra stylesheets: files under `app/assets/stylesheets/themes/` are
excluded from the existing `:app` Propshaft bulk-include so they
cannot leak into the default build, and are loaded explicitly via
the new `theme_stylesheets` helper only when their theme is active.
* Theme-aware markdown rendering: themes listed in
`Rails.application.config.themes_using_minimal_renderer` get the new
`MinimalMarkdownRender`, which emits semantic HTML without inline
Tailwind utility classes — so themes can style content from a
wrapper scope (e.g. `.prose-retro`) rather than fighting the
renderer. The default theme keeps the existing `MarkdownRender`.
No new gems, no Tailwind config changes, no default-theme regressions:
when `ABBEY_THEME=default` (the default) the rendered HTML, asset list
and markdown output are byte-for-byte identical to before this commit.
Co-authored-by: Cursor <cursoragent@cursor.com>
Two pure-CSS stylesheets that ship with the new `retro` theme and are
loaded by the theme system on demand:
* `themes/retro.css` — Memphis design palette (neon pink/cyan/yellow
/mint/purple/coral on paper or CRT-black), neo-brutalist cards,
pixel-display headings (`Press Start 2P`), terminal-mono body
(`VT323` / `Space Grotesk`), animated marquee, blinking cursor,
drifting confetti background, subtle CRT scanlines, and a fully
themed `.prose-retro` block for rendered markdown (h1-h3 with hard
text-shadows, wavy underlines, highlighted `<strong>`, terminal
`<pre>` blocks, dotted SVG `<hr>`, etc.).
* `themes/retro-highlight.css` — Rouge syntax highlighting palette
that pairs with the terminal aesthetic (neon-on-CRT-black).
Every selector is gated on `.theme-retro` (set on `<html>` by the retro
layout), so even if the file is loaded outside the theme it produces no
visible effect. No Tailwind processing is required — the file is served
directly by Propshaft.
Co-authored-by: Cursor <cursoragent@cursor.com>
Per-theme view overrides under `app/views/themes/retro/` covering every
template the default theme ships:
* `layouts/application.html.erb` — sets `theme-retro` on `<html>`,
preloads Google Fonts for `Press Start 2P` / `VT323` /
`Space Grotesk`, adds a pixelated 4-square SVG favicon and pink
theme-color, and loads the extra theme stylesheets via
`theme_stylesheets`.
* `shared/_navigation.html.erb` — color-block logo, blinking-cursor
site title, retro tagline, and Memphis-styled nav buttons (Home,
About, Presentations, Projects, Links, Papers) with hard shadows
and a wiggle animation on the active page.
* `shared/_footer.html.erb` — caution-tape marquee + terminal
command-line copyright bar with blinking cursor.
* `shared/_admin_navigation.html.erb` — BBS-sysop command line:
yellow `SYSOP>` prompt, `ONLINE▮` indicator, and the same admin
actions (New Post / New Page / New Link / Feeds / Read / Sign out)
as the default bar (selectors `.fixed.top-0`, "New Post",
"Sign out" preserved so system tests pass under either theme).
* `shared/_tags.html.erb` — color-cycling tag pills picked from the
Memphis palette based on the tag name hash.
* `blog/index.html.erb`, `blog/show.html.erb`,
`blog/index_by_tag.html.erb` — Memphis cards with color-cycled
shadows, rotated date "stickers", glitch-on-hover titles, and a
retro `READ MORE` button. Post body uses the `.prose-retro`
wrapper from `themes/retro.css`.
* `pages/show.html.erb` — page-as-Memphis-card with a mint rotated
slug tag.
* `links/index.html.erb`, `links/_link.html.erb` — banner header
plus per-link Memphis cards.
* `papers/index.html.erb`, `papers/_paper.html.erb` — retro variants
of the new Papers feature, with PDF preview thumbs framed in hard
shadows.
All variants preserve the user-facing copy and DOM selectors checked
by the existing Playwright system tests, so enabling the theme does
not regress `test:system`.
Co-authored-by: Cursor <cursoragent@cursor.com>
`test/integration/themes_test.rb` covers the four guarantees the
theme system makes:
* default theme does NOT emit `theme-retro` or load any
`themes/...` stylesheets;
* retro theme DOES set `theme-retro` on `<html>`, loads
`themes/retro` + `themes/retro-highlight`, and preserves the
DOM contract checked by system tests (header h1, footer, all
six nav links);
* `Post.markdown_renderer` follows
`Rails.application.config.theme` (MarkdownRender for default,
MinimalMarkdownRender for retro);
* `theme_stylesheets` helper is empty for the default theme and
populated for retro.
Existing tests still pass under both themes:
bin/rails test # 21 tests, 85 asserts
bin/rails test test/system/{navigation,blog_public,pages,links}_test.rb
ABBEY_THEME=retro bin/rails test test/system/... # same suite, all green
Co-authored-by: Cursor <cursoragent@cursor.com>
Adds a Themes section to the README explaining:
* how to switch themes via ABBEY_THEME or
config/initializers/themes.rb,
* what the built-in `default` and `retro` themes are,
* how the theme system overrides views, ships extra stylesheets,
and selects a markdown renderer,
* how to author a new theme (drop a CSS file under
`app/assets/stylesheets/themes/<name>.css` and set ABBEY_THEME).
Also adds a one-line feature entry to the top-level features list.
Co-authored-by: Cursor <cursoragent@cursor.com>
Extends the theming system introduced for the retro theme with a second built-in named theme: "grimoire" — retro hacker dark fantasy. Like retro, grimoire opts into MinimalMarkdownRender so it can style markdown content entirely from a wrapper class scope. No behavioural change for ABBEY_THEME=default or ABBEY_THEME=retro. README updated to document grimoire alongside retro in the themes table. Co-authored-by: Cursor <cursoragent@cursor.com>
Pure-CSS theme files served by Propshaft and loaded only when Rails.application.config.theme == "grimoire" (via the existing theme_stylesheets helper). All selectors are scoped under `.theme-grimoire` so loading these files in any other context is a no-op. Visual language: - Light mode: aged parchment (#ebd9b3) with subtle noise grain + corner foxing; blood-red (#8b1d27) ink accents; gold (#c8a44d) hairlines - Dark mode: starless void (#07080d) with drifting ember/arcane dust; golden text; phosphor green (#57f287) for code/cursor accents; subtle CRT scanlines - Matrix-minimal typography: JetBrains Mono 800 uppercase for display headings, Inter for body, IBM Plex Mono for inline code/meta - Components: tome cards (double gold/ink border + corner sigils), spell-btn (raised paper/metal action button), wax-seal tag pills (deterministic blood/arcane/ember/phosphor/gold/ichor color cycle), icon-rune (square icon button), summoning circle, marquee, rune-divider - Markdown prose (.prose-grimoire): chunky monospace drop cap with thin underline (phosphor green in dark mode), terminal-style code blocks with "~/grimoire/spells $ cast" header, // EOF // hr, wax-seal tag pills, scoped table styling - Konami code easter egg overlay (.incant-overlay/.incant-title) - Custom scrollbar (dark mode only) The companion grimoire-highlight.css is a Rouge theme: phosphor green base, gold sigils for keywords, blood-red strings, ember comments — all scoped under `.theme-grimoire` so it can't clobber the default highlight theme. Co-authored-by: Cursor <cursoragent@cursor.com>
Drops a full set of view overrides under app/views/themes/grimoire/ that
the theming system prepends to the view lookup path when grimoire is
active:
layouts/application.html.erb — html.theme-grimoire wrapper,
pixel-runic favicon, theme-only
Google Fonts (Inter + JetBrains
Mono + IBM Plex Mono),
cookie-based dark-mode JS that
survives Turbo Drive navigation
(re-applies on turbo:load and
turbo:render, listeners guarded
against double-registration),
and a Konami code easter egg
that unlocks a "necromancer"
overlay
shared/_navigation.html.erb — terminal masthead
(`root@grimoire:~$ cat ./codex_of`),
Roman-numeral table-of-contents
nav, RSS + theme-toggle icon-runes
shared/_footer.html.erb — animated summoning circle SVG
(counter-rotating ring + pentagram
+ center ember), scrolling
marquee of dev-zine tokens,
terminal command-line copyright
shared/_admin_navigation.html.erb — adept's console
(`:transcribe / :inscribe / :bind
/ :auguries / :scry / :depart`)
shared/_tags.html.erb — wax-seal tag pills (deterministic
color cycle by tag name hash)
blog/index.html.erb,
blog/show.html.erb,
blog/index_by_tag.html.erb — tome cards with "Folio · Anno
<Roman>" date stickers, "open
spellbook" CTAs, // LOG // and
// FILTER // rune dividers
pages/show.html.erb — single-page tome with
"✦ Chapter · <slug> ✦" header
and `exit 0` // EOF // sign-off
links/index.html.erb, _link.html.erb — "Forbidden Tomes" reliquary
header, link cards rendered as
tomes with ✦ icon
papers/index.html.erb, _paper.html.erb — "Treatises" header with
"Codex · PDF" stickers
All views consume the pure-CSS classes defined in
app/assets/stylesheets/themes/grimoire.css (tome, spell-btn, wax-seal,
h-blackletter, h-engraved, rune-divider, prose-grimoire, etc.) plus
.theme-grimoire-scoped utility classes that mirror Tailwind syntax
(bg-grim-*, text-grim-*, font-plex, tracking-[Xem], leading-[X], …).
This means grimoire — like retro — needs zero changes to the Tailwind
config to render correctly: it owns its own visual surface and only
takes effect when ABBEY_THEME=grimoire is active.
Co-authored-by: Cursor <cursoragent@cursor.com>
Adds parallel coverage for the grimoire opt-in theme so that future
theming changes can't quietly regress it:
- default theme renders the original layout: also asserts no
`theme-grimoire` class and no `themes/grimoire` stylesheets leak in
- new test: grimoire theme prepends its view path and loads its theme
stylesheets; nav still contains all the required labels
- renderer test: grimoire (like retro) uses MinimalMarkdownRender
- theme_stylesheets helper test: returns themes/grimoire and
themes/grimoire-highlight when grimoire is active
All 5 tests pass with 68 assertions.
Co-authored-by: Cursor <cursoragent@cursor.com>
Replaces hand-rolled `.theme-X .bg-foo-bar` utility-class declarations (plus matching `.dark:`, `.hover:`, `.group-hover:`, opacity-modifier and arbitrary-value mirrors) with Tailwind v4 `@theme` tokens that generate the same utilities natively. Addresses the PR description's noted trade-off: "The retro theme uses pure CSS rather than extending the Tailwind config — deliberate, so the default build stays unchanged." It turns out we can have both: the theme tokens live in `@theme` so Tailwind generates the utilities, and the default build stays unchanged because the default theme's views never reference the new utility classes (so Tailwind's content-aware extraction never emits them in the default theme's bundle — only the CSS variables on `:root`, which are inert without matching classes). What moved into `app/assets/tailwind/application.css` under `@theme`: * Memphis palette (--color-memphis-pink/cyan/yellow/mint/purple/coral/...) * Grimoire palette (--color-grim-blood/gold/ember/phosphor/arcane/...) * Retro hard-shadows (--shadow-retro, --shadow-retro-lg, --shadow-retro-pink/...) * Theme-specific font tokens (--font-display/blackletter/engraved/plex/manuscript) * Shared animations + @Keyframes (--animate-blink/wiggle/pop-in/marquee) What dropped out of `themes/retro.css` (626 -> 539 lines) and `themes/grimoire.css` (819 -> 754 lines): * `.theme-X .bg-/text-/border-/shadow-/font-...` declarations — Tailwind generates them from the --color-* / --shadow-* / --font-* tokens * `.theme-X.dark .dark\:foo-bar` mirrors — the `dark:` variant works because <html> already carries both `theme-X` and `dark` classes, and the existing `darkMode: 'class'` setting in tailwind.config.js applies * `.hover\:foo-bar`, `.group:hover .group-hover\:foo-bar`, opacity mirrors (`text-foo\/40`), and ~30 arbitrary-value escapes in grimoire.css (`text-\[0\.62rem\]`, `tracking-\[0\.18em\]`) — Tailwind v4 generates all of these natively from the registered tokens * Hand-written @Keyframes that duplicated --animate-* registrations What stayed in the per-theme CSS files: * theme-root environmental styles (body backdrops, scanlines, scrollbar) * 4-line CSS-variable rebinding inside `.theme-X { --font-sans: ...; ... }` so utilities like `font-mono` / `font-sans` / `font-display` resolve to the theme's font stack inside the theme scope without affecting the default theme * all component classes (`.card-retro`, `.btn-retro`, `.tome`, `.spell-btn`, `.wax-seal`, `.h-display`, `.h-blackletter`, `.prose-retro`, `.prose-grimoire`, ...) — now consuming `var(--color-memphis-pink)` etc. internally so the @theme tokens are the single source of truth for design values * theme-local @Keyframes (retro-drift, retro-glitch, grimoire-sigil-spin, grimoire-mist, grimoire-incant) that aren't surfaced as utilities Default theme is still byte-for-byte unchanged. Tests still pass: $ bin/rails test # 22 runs, 115 assertions, 0 failures $ bin/rails test test/integration/themes_test.rb # 5 runs, 68 assertions, 0 failures $ bin/rails tailwindcss:build # clean Future themes are now much smaller: drop tokens into `@theme`, rebind `--font-*` for typography, ship a body backdrop + component classes — no more growing per-theme boilerplate every time a new color or shadow is needed. Co-authored-by: Cursor <cursoragent@cursor.com>
Reorganize Abbey's theming so each theme is one self-contained folder
under app/themes/<name>/ and gets dropped in / removed independently.
Zero central edits to add a new theme.
Folder consolidation (git mv preserves history):
app/views/themes/retro/ -> app/themes/retro/views/
app/views/themes/grimoire/ -> app/themes/grimoire/views/
app/assets/stylesheets/themes/retro.css -> app/themes/retro/assets/retro.css
app/assets/stylesheets/themes/retro-highlight.css
-> app/themes/retro/assets/retro-highlight.css
(same for grimoire)
New Abbey::Theme registry (lib/abbey/theme.rb, autoloaded via
config.autoload_lib). Themes declare their metadata in app/themes/<name>/
theme.rb manifests:
Abbey::Theme.register(:retro) do |t|
t.display_name = "Retro (Memphis / 8-bit / CRT)"
t.html_class = "theme-retro"
t.body_class = "min-h-screen flex flex-col font-sans ..."
t.markdown_renderer = :minimal
t.theme_color_light = "#fff8ef"
t.theme_color_dark = "#0a0e1a"
t.fonts = ["https://fonts.googleapis.com/css2?..."]
t.favicon_svg = "<svg ...>"
end
The registry exposes a sentinel DefaultTheme so callers never have to
nil-check; Abbey::Theme.active always returns a Theme.
Boot wiring (config/initializers/themes.rb):
- Honor ABBEY_THEME env var (Rails.application.config.theme).
- Abbey::Theme.load_all! scans app/themes/*/theme.rb (uses Kernel#load
so manifests are re-evaluatable for tests and dev reload).
- Register each theme's assets/ folder with Propshaft so
stylesheet_link_tag "<name>" / "<name>-highlight" resolves.
Consumer updates:
- Theming concern reads from Abbey::Theme.active (still per-request).
- ApplicationHelper#theme_stylesheets enumerates the active theme's
manifest-declared stylesheets, with a legacy themes/<name> fallback.
- Rendering#markdown_renderer delegates to Abbey::Theme.active.
Tests:
- test/lib/abbey/theme_test.rb (11 cases) covers .register, .active,
DefaultTheme, env-var override, .discover, .load_all!, #stylesheets.
- test/integration/themes_test.rb updated to match new logical asset
paths (themes are now served at /assets/retro[-...].css, not
/assets/themes/retro[-...].css).
Verified: rails test (37 runs, 207 assertions, 0 failures), rubocop
clean, ABBEY_THEME=retro bin/rails runner smoke test reports the
expected registry, view path, asset path, stylesheets, and renderer.
Co-authored-by: Cursor <cursoragent@cursor.com>
Each theme now ships its own Tailwind input file at
app/themes/<name>/assets/tailwind.css. The default Abbey bundle
(app/assets/tailwind/application.css) compiles to tailwind.css with
`@source not "../../themes"` so theme views never leak utilities into
the default bundle — regardless of how many themes the project ships,
the default bundle stays byte-for-byte unchanged.
Per-theme bundles:
app/themes/retro/assets/tailwind.css -> tailwind-retro.css
(memphis palette, retro
shadows, theme animations)
app/themes/grimoire/assets/tailwind.css -> tailwind-grimoire.css
(grim palette, theme fonts,
theme animations)
Each theme's input declares its own `@source "../views"` so Tailwind
scans only that theme's view files and tree-shakes accordingly.
Build pipeline (lib/tasks/themes.rake):
- themes:tailwind:build compiles every theme bundle.
- themes:tailwind:watch spawns one watcher per theme (used by
Procfile.dev so `bin/dev` picks up changes
in any theme's tailwind.css live).
- themes:tailwind:clobber removes every compiled theme bundle.
`Rake::Task["tailwindcss:build"].enhance` chains the per-theme build so
plain `bin/rails tailwindcss:build` and `assets:precompile` both produce
the full set. test:prepare picks it up automatically too.
Helper update: ApplicationHelper#theme_stylesheets emits
`tailwind-<active>` first (so theme tokens land before component CSS
that consumes them), then the theme's component stylesheets.
New test/integration/theme_bundle_isolation_test.rb (5 cases) asserts:
* default tailwind.css contains no --color-memphis-*, --color-grim-*,
--shadow-retro-*, .bg-memphis-pink, .bg-grim-void, .shadow-retro-lg.
* tailwind-retro.css does contain its tokens + utilities.
* tailwind-grimoire.css does contain its tokens + utilities.
* tailwind-retro.css does not leak Grimoire tokens (themes are
isolated from each other too, not just from the default).
Verified: rails test (42 runs, 239 assertions, 0 failures), rubocop
clean, manual `bin/rails tailwindcss:build` emits tailwind.css,
tailwind-retro.css, and tailwind-grimoire.css with the expected
contents.
Co-authored-by: Cursor <cursoragent@cursor.com>
Extract every theme's <head>/<body>/wrapper boilerplate into one
canonical partial driven by the active Abbey::Theme manifest. Each
theme's layout collapses from ~50-120 lines to 3, and the default
layout drops from 47 lines to 3 as well:
<%= render "layouts/abbey_chrome" do %>
<%= yield %>
<% end %>
The chrome partial (app/views/layouts/_abbey_chrome.html.erb, 75
lines) emits the full DOCTYPE/html/head/body shell, reading everything
configurable from the manifest:
* html_class -> <html class="theme-retro">
* dark_html_class / -> dark-mode-conditional html classes
light_html_class
* body_class -> <body class="...">
* main_class -> <main class="...">
* theme_color_light + -> media-aware <meta name="theme-color">
theme_color_dark
* favicon_svg -> inline data:image/svg+xml favicon
* fonts (Array<URL>) -> Google Fonts preconnect + <link> per font
Manifest API gained dark_html_class / light_html_class / main_class
fields (with sensible defaults: "dark", nil, container-mx-auto). The
DefaultTheme sentinel sets them to the exact values the original
default layout used, so the default HTML stays functionally identical.
Per-theme JS that needs to vary (e.g. grimoire's Turbo-safe dark mode
+ Konami easter egg) moves to shared/_dark_mode_script.html.erb,
overridable per-theme via the normal view-path-prepend mechanism. The
default partial ships the simple cookie toggle.
Bug fix surfaced during this work: ApplicationHelper#app_stylesheets_
paths used to exclude only `themes/*` logical paths. With per-theme
assets registered under their own Propshaft load_paths in Phase 1+2,
the per-theme tailwind-<name>.css and theme component CSS were leaking
into the default :app bundle bulk inclusion. Now we filter explicitly
against every registered theme's contributed paths (cached per-helper-
instance) — no theme assets in the default :app stylesheet_link_tag.
Tests:
* test/integration/themes_test.rb adds:
- "chrome partial drives <head> entirely from theme manifest"
verifies html_class/body_class/main_class/theme-color/font/favicon
from manifest land in the rendered <head>.
- default theme test extended to assert no /assets/tailwind-retro
or /assets/tailwind-grimoire links leak into the default bundle.
Verified: rails test (43 runs, 264 assertions, 0 failures), rubocop
clean. erb_lint shows only the same pre-existing _footer.html.erb
findings present before this branch.
Co-authored-by: Cursor <cursoragent@cursor.com>
Scaffold a new drop-in theme with one command:
bin/rails g abbey:theme aurora
# full skeleton: theme.rb, assets/tailwind.css, all 12 view files
# (layouts/_, shared/*, blog/*, pages/_, links/*, papers/*), README.
bin/rails g abbey:theme spark --minimal
# bare minimum: theme.rb + assets/tailwind.css + 3-line layout.
# Use case: pure recolor that inherits Abbey's default chrome.
bin/rails g abbey:theme neon --from=retro
# clones retro's views/ and non-tailwind assets as a starting point.
# Use case: serious visual override that wants a structural head
# start rather than 12 empty stubs.
Generator (lib/generators/abbey/theme/theme_generator.rb):
* Validates name with /\A[a-z][a-z0-9_]*\z/ (after NamedBase#underscore
normalization). Rejects obvious bad input ("9foo", "with.dots",
"Has Spaces", etc).
* Refuses to overwrite an existing app/themes/<name>/.
* Honors destination_root throughout (relative paths only) so
Rails::Generators::TestCase isolates cleanly.
* `--from` copies entry-by-entry into the existing scaffolded views/
so the clone merges instead of nesting `views/views/`.
* Prints next-steps after scaffolding.
Templates (lib/generators/abbey/theme/templates/):
* theme.rb.tt — manifest stub with sensible defaults + commented
examples for favicon/fonts.
* tailwind.css.tt — @import + @source "../views" + a starter @theme
palette (--color-<name>-bg/fg/accent/muted).
* layouts/application.html.erb.tt — 3-line shell calling abbey_chrome.
* views/shared/{_navigation,_footer,_admin_navigation,_tags}.html.erb.tt
* views/blog/{index,show,index_by_tag}.html.erb.tt
* views/{pages,links,papers}/...html.erb.tt
* README.md.tt — author notes with the standard activation
command + folder reference.
Tests (test/generators/abbey/theme_generator_test.rb, 6 cases):
* full scaffold puts the right files in the right places
* --minimal skips the view tree
* --from clones from source and merges into the scaffolded layouts dir
* invalid names produce stderr errors (Thor catches Thor::Error, so
we capture(:stderr) rather than assert_raises)
* refuses to overwrite
* generated theme.rb is valid Ruby and registers with Abbey::Theme
Also: .gitignore the generator test scratch directory at test/tmp/
(Rails::Generators::TestCase destination).
Verified: rails test (49 runs, 328 assertions, 0 failures), rubocop
clean, manual smoke test (`bin/rails g abbey:theme midnight --minimal`
+ `ABBEY_THEME=midnight bin/rails runner ...`) confirms the generated
theme registers correctly with the expected attributes.
Co-authored-by: Cursor <cursoragent@cursor.com>
Three artifacts so a community contributor can ship a theme without reverse-engineering the codebase: docs/THEMES.md — end-to-end authoring guide. Concepts, quick-start 30-second recolor, folder layout, manifest walkthrough, Tailwind entry point recipe, view overrides, markdown renderer selection, per-theme JavaScript, dark-mode mechanics, common patterns (recolor, custom typography, favicon, syntax highlighting), and gotchas. docs/THEMES_API.md — exhaustive manifest field reference + registry API. Tables for every Abbey::Theme writable attribute (defaults, descriptions), registry class methods, filesystem conventions, boot flow, view helpers, generator flags, rake tasks, and a versioning commitment for the manifest stability boundary. app/themes/midnight/ — sample drop-in theme demonstrating the "30-second recolor" claim from the docs. ~80 lines total across theme.rb (30) + assets/tailwind.css (50) + 3-line layout shell + a README that walks through what it shows. Deep slate palette with warm amber accents, Inter for body / JetBrains Mono for code (loaded via manifest's t.fonts), inline SVG crescent-moon favicon, custom prose styling that overrides Tailwind Typography's --tw-prose-* vars for the dark background. Ships zero view overrides; inherits Abbey's default chrome and templates entirely. Forking starting point: bin/rails g abbey:theme yourname --from=midnight Top-level README.md Themes section rewritten: replaces the old file-based authoring instructions with a folder-based drop-in description, lists midnight as a built-in, and points contributors at docs/THEMES.md + docs/THEMES_API.md. Test: test/integration/theme_bundle_isolation_test.rb adds an isolation assertion for midnight (tokens land in tailwind-midnight.css, don't leak into the default tailwind.css). 50 runs, 334 assertions, 0 failures. Co-authored-by: Cursor <cursoragent@cursor.com>
The shared chrome header comment accidentally closed early on `%>` and rendered template text at the top of every page. Dark mode now uses a shared script core that syncs the dark_mode cookie to the full html class set (base + light/dark variants), runs before stylesheets in <head>, and re-applies on turbo:load/turbo:render so back/forward navigation stays in sync. Co-authored-by: Cursor <cursoragent@cursor.com>
Per-theme tailwind.css entry points were missing darkMode: class config, so dark:bg-* utilities compiled against prefers-color-scheme instead of the `.dark` class our cookie toggle sets. Add @custom-variant dark and @import the shared tailwind.config.js to retro, grimoire, midnight, and the theme generator template, with a regression test. Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Supersedes #152. This PR replaces the original opt-in theme proposal with a drop-in theme system: a community theme is one self-contained folder under
app/themes/<name>/, activated viaABBEY_THEME=<name>, with zero edits to central application files.Why a new PR instead of #152
#152 introduced opt-in theming, but themes still required edits across 5+ central files (
themes.rb,application_helper.rb, layouts, central Tailwind config, etc.), duplicated ~50–120 lines of layout boilerplate per theme, and leaked per-theme Tailwind tokens into the default bundle.This PR delivers the same user-facing goal — opt-in themes that leave the default site unchanged — with a structure designed for community contributions:
app/themes/<name>/{theme.rb, assets/, views/}_abbey_chrome.html.erbpartial reads fromAbbey::Theme.registerassets/tailwind.css+@source not "../../themes"in the default buildbin/rails g abbey:theme NAME [--minimal] [--from=retro]docs/THEMES.md(authoring guide) +docs/THEMES_API.md(manifest API)Built-in themes
defaultretroapp/themes/retro/)grimoiremidnightArchitecture (5 phases, 15 commits)
Abbey::Themeregistry, manifests,git mvretro/grimoire intoapp/themes/<name>/themes:tailwind:build, bundle isolation regression testrails g abbey:themewith--minimal/--fromflagsCompatibility
ABBEY_THEME=default(or unset): byte-for-byte identical default behaviormain(includes chore: batch update 5 dependencies #155 dependency batch)Test plan
bin/rails test— 46 runs, 283 assertions, 0 failuresbundle exec rubocop— cleanbin/rails tailwindcss:build— emitstailwind.css+ per-theme bundlestheme_bundle_isolation_test.rb)test/generators/abbey/theme_generator_test.rb)ABBEY_THEME={retro,grimoire,midnight} bin/devdocs/THEMES.mdTry it
Notes for reviewer
Made with Cursor