From 878d8f63ac30702a5335b18ca5fdc094457bb680 Mon Sep 17 00:00:00 2001 From: Jamey Greenwood Date: Tue, 30 Dec 2025 21:41:25 +0000 Subject: [PATCH] feat: add CSS generation system with custom properties and ETS cache - Create theme-primitives.css with spacing, fonts, radius scales - Create theme-semantic.css with semantic CSS variable aliases - Update app.css to import theme CSS files - Add CSSGenerator module for dynamic CSS token generation - Generates mood variables (neutral, warm, cool, dark) - Generates typography variables (7 font combinations) - Generates shape variables (sharp, soft, round, pill) - Generates density variables (spacious, balanced, compact) - Converts hex colors to HSL for flexible manipulation - Add CSSCache GenServer with ETS table for performance - Caches generated CSS to avoid regeneration - Warms cache on application startup - Provides invalidation for theme updates - Add CSSCache to application supervision tree - Add comprehensive tests for CSS generation (29 tests) - Add comprehensive tests for preset validation (14 tests) - All tests passing (58 total tests, 0 failures) - Verified CSS generation and caching work correctly in IEx --- assets/css/app.css | 6 + lib/simpleshop_theme/application.ex | 4 +- lib/simpleshop_theme/theme/css_cache.ex | 107 +++++++ lib/simpleshop_theme/theme/css_generator.ex | 274 ++++++++++++++++++ priv/static/css/theme-primitives.css | 58 ++++ priv/static/css/theme-semantic.css | 80 +++++ .../theme/css_generator_test.exs | 153 ++++++++++ test/simpleshop_theme/theme/presets_test.exs | 133 +++++++++ 8 files changed, 813 insertions(+), 2 deletions(-) create mode 100644 lib/simpleshop_theme/theme/css_cache.ex create mode 100644 lib/simpleshop_theme/theme/css_generator.ex create mode 100644 priv/static/css/theme-primitives.css create mode 100644 priv/static/css/theme-semantic.css create mode 100644 test/simpleshop_theme/theme/css_generator_test.exs create mode 100644 test/simpleshop_theme/theme/presets_test.exs diff --git a/assets/css/app.css b/assets/css/app.css index 0133acc..80cefc4 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -102,4 +102,10 @@ /* Make LiveView wrapper divs transparent for layout */ [data-phx-session], [data-phx-teleported-src] { display: contents } +/* Theme CSS - Layer 1: Primitives (fixed CSS variables) */ +@import url("/css/theme-primitives.css"); + +/* Theme CSS - Layer 3: Semantic aliases */ +@import url("/css/theme-semantic.css"); + /* This file is for your main application CSS */ diff --git a/lib/simpleshop_theme/application.ex b/lib/simpleshop_theme/application.ex index 0dae44d..d4c8c25 100644 --- a/lib/simpleshop_theme/application.ex +++ b/lib/simpleshop_theme/application.ex @@ -14,8 +14,8 @@ defmodule SimpleshopTheme.Application do repos: Application.fetch_env!(:simpleshop_theme, :ecto_repos), skip: skip_migrations?()}, {DNSCluster, query: Application.get_env(:simpleshop_theme, :dns_cluster_query) || :ignore}, {Phoenix.PubSub, name: SimpleshopTheme.PubSub}, - # Start a worker by calling: SimpleshopTheme.Worker.start_link(arg) - # {SimpleshopTheme.Worker, arg}, + # Theme CSS cache + SimpleshopTheme.Theme.CSSCache, # Start to serve requests, typically the last entry SimpleshopThemeWeb.Endpoint ] diff --git a/lib/simpleshop_theme/theme/css_cache.ex b/lib/simpleshop_theme/theme/css_cache.ex new file mode 100644 index 0000000..e274f84 --- /dev/null +++ b/lib/simpleshop_theme/theme/css_cache.ex @@ -0,0 +1,107 @@ +defmodule SimpleshopTheme.Theme.CSSCache do + @moduledoc """ + GenServer that maintains an ETS table for caching generated theme CSS. + + This provides fast lookups for theme CSS without regenerating it on every request. + The cache is invalidated when theme settings are updated. + """ + + use GenServer + + @table_name :theme_css_cache + + ## Client API + + @doc """ + Starts the CSS cache GenServer. + """ + def start_link(opts) do + GenServer.start_link(__MODULE__, opts, name: __MODULE__) + end + + @doc """ + Gets cached CSS for the site theme. + + Returns `{:ok, css}` if found in cache, or `:miss` if not cached. + + ## Examples + + iex> CSSCache.get() + {:ok, "/* Theme CSS ... */"} + + iex> CSSCache.get() + :miss + + """ + def get do + case :ets.lookup(@table_name, :site_theme) do + [{:site_theme, css}] -> {:ok, css} + [] -> :miss + end + end + + @doc """ + Caches CSS for the site theme. + + ## Examples + + iex> CSSCache.put(css_string) + :ok + + """ + def put(css) when is_binary(css) do + :ets.insert(@table_name, {:site_theme, css}) + :ok + end + + @doc """ + Invalidates the cached CSS, forcing regeneration on next request. + + ## Examples + + iex> CSSCache.invalidate() + :ok + + """ + def invalidate do + :ets.delete(@table_name, :site_theme) + :ok + end + + @doc """ + Warms the cache by generating and storing CSS from current theme settings. + + ## Examples + + iex> CSSCache.warm() + :ok + + """ + def warm do + alias SimpleshopTheme.Settings + alias SimpleshopTheme.Theme.CSSGenerator + + settings = Settings.get_theme_settings() + css = CSSGenerator.generate(settings) + put(css) + :ok + end + + ## Server Callbacks + + @impl true + def init(_opts) do + :ets.new(@table_name, [ + :set, + :public, + :named_table, + read_concurrency: true, + write_concurrency: false + ]) + + # Warm the cache on startup + warm() + + {:ok, %{}} + end +end diff --git a/lib/simpleshop_theme/theme/css_generator.ex b/lib/simpleshop_theme/theme/css_generator.ex new file mode 100644 index 0000000..17f47b2 --- /dev/null +++ b/lib/simpleshop_theme/theme/css_generator.ex @@ -0,0 +1,274 @@ +defmodule SimpleshopTheme.Theme.CSSGenerator do + @moduledoc """ + Generates CSS custom properties (Layer 2: Theme Tokens) from theme settings. + + This module converts ThemeSettings into CSS variables that bridge the gap + between fixed primitives (Layer 1) and semantic aliases (Layer 3). + """ + + alias SimpleshopTheme.Settings.ThemeSettings + + @doc """ + Generates CSS for theme settings. + + Returns a string of CSS custom properties that can be injected into a