simpleshop_theme/PLAN.md

35 KiB

SimpleShop Theme - Implementation Plan

Overview

Integrate the complete theme feature from theme-demo-v28.html into the Phoenix LiveView application. The system will provide 9 curated presets and extensive customization options with real-time preview. All data (including uploaded images) will be stored in a single SQLite database file.

Key Concept: This codebase IS the shop (like WordPress is one site). Multiple users can manage it, but there's only one shop/site per deployment.

Core Architectural Decisions

1. Database Storage Strategy

  • Images as BLOBs in SQLite - Complete self-containment in single database file
  • Application-level settings - NO Shop model, just Settings tables for the site
  • Multiple admin users - Any authenticated user can manage theme (no ownership model)
  • SVG stored as both BLOB and TEXT - Enable recoloring feature

2. CSS Architecture

  • Three-layer CSS system using Custom Properties:
    • Layer 1: Primitives (static) - spacing, fonts, radii
    • Layer 2: Theme tokens (dynamic) - generated from settings
    • Layer 3: Semantic aliases (static) - friendly variable names
  • Inline <style> generation in LiveView for instant preview
  • ETS cache for storefront CSS (invalidate on save)

3. LiveView Structure

  • Single LiveView (ThemeLive.Index) with nested function components
  • No iframe - Direct preview in same LiveView using data attributes
  • Smart preview data - Mock data initially, switch to real products when available
  • Optimistic UI - Update preview immediately, persist on "Save" button
  • Idiomatic Phoenix/LiveView - Use Tailwind utilities where possible, CSS variables for dynamic theming

4. Development Workflow

  • Semantic git commits - Commit each logical step for easy rollback
  • Test-driven - Write Phoenix tests alongside implementation (ExUnit, ConnCase, LiveViewTest)
  • Manual verification - Test in browser after each phase
  • Progressive enhancement - Each commit should leave app in working state

Database Schema

Philosophy: No Shop model - this app IS the shop. Settings are site-wide, managed by any admin user.

Migration 1: Create Settings Table

File: priv/repo/migrations/YYYYMMDDHHMMSS_create_settings.exs

defmodule SimpleshopTheme.Repo.Migrations.CreateSettings do
  use Ecto.Migration

  def change do
    create table(:settings, primary_key: false) do
      add :id, :binary_id, primary_key: true
      add :key, :string, null: false  # e.g., "site_name", "theme_settings"
      add :value, :text, null: false  # JSON or plain text
      add :value_type, :string, null: false, default: "string"  # string, json, integer, boolean

      timestamps(type: :utc_datetime)
    end

    create unique_index(:settings, [:key])
  end
end

Migration 2: Create Images Table

File: priv/repo/migrations/YYYYMMDDHHMMSS_create_images.exs

defmodule SimpleshopTheme.Repo.Migrations.CreateImages do
  use Ecto.Migration

  def change do
    create table(:images, primary_key: false) do
      add :id, :binary_id, primary_key: true

      add :image_type, :string, null: false  # "logo" | "header" | "product"
      add :filename, :string, null: false
      add :content_type, :string, null: false
      add :file_size, :integer, null: false

      # BLOB storage
      add :data, :binary, null: false

      # SVG support
      add :is_svg, :boolean, default: false
      add :svg_content, :text  # For recoloring

      # Optional: thumbnail for admin UI performance
      add :thumbnail_data, :binary

      timestamps(type: :utc_datetime)
    end

    create index(:images, [:image_type])
  end
end

File Structure

New Contexts and Schemas

lib/simpleshop_theme/
├── settings/
│   ├── setting.ex               # Setting schema (key-value store)
│   └── theme_settings.ex        # Embedded schema for theme settings
├── settings.ex                  # Settings context (site-wide config API)
├── media/
│   ├── image.ex                 # Image schema (replaces ShopImage)
│   └── svg_recolorer.ex         # Recolor SVG logos
├── media.ex                     # Media context (image uploads/serving)
└── theme/
    ├── presets.ex               # Preset definitions (9 presets)
    ├── css_generator.ex         # Generate CSS from settings
    ├── css_cache.ex             # ETS cache GenServer
    └── preview_data.ex          # Smart preview (mock → real products)

LiveView Components

lib/simpleshop_theme_web/live/theme_live/
├── index.ex                     # Main LiveView
├── index.html.heex              # Template
├── preset_selector.ex           # 9 preset buttons (function component)
├── branding_section.ex          # Logo/header uploads
├── customization_panel.ex       # Accordion with all options
├── preview_frame.ex             # Preview area with page switcher
└── preview_pages/
    ├── home.html.heex          # Preview homepage
    ├── collection.html.heex    # Preview product grid
    ├── pdp.html.heex           # Preview product detail page
    ├── cart.html.heex          # Preview cart
    ├── about.html.heex         # Preview about page
    ├── contact.html.heex       # Preview contact page
    └── error.html.heex         # Preview 404 page

Controllers

lib/simpleshop_theme_web/controllers/
└── image_controller.ex          # Serve images from BLOBs

Static Assets & Tests

priv/static/css/
├── theme-primitives.css         # Layer 1: Fixed CSS variables
└── theme-semantic.css           # Layer 3: Semantic aliases

assets/css/
└── app.css                      # Import theme CSS here

test/simpleshop_theme/
├── settings_test.exs            # Settings context tests
├── media_test.exs               # Media context tests
└── theme/
    ├── css_generator_test.exs   # CSS generation tests
    ├── presets_test.exs         # Preset validation tests
    └── preview_data_test.exs    # Preview data tests

test/simpleshop_theme_web/
├── controllers/
│   └── image_controller_test.exs
└── live/
    └── theme_live_test.exs      # LiveView integration tests

Implementation Status

Completed Phases (1-8):

  • Phase 1: Database Foundation
  • Phase 2: CSS Architecture
  • Phase 3: Smart Preview Data
  • Phase 4: Theme LiveView (Basic)
  • Phase 5: Preview Pages
  • Phase 6: Customization Options
  • Phase 6.5: Fix Theme Visual Application
  • Phase 7: File Uploads (Logo & Header)
  • Phase 8: Persistence & Polish

Current Phase: Phase 9 - Storefront Integration 🚧

See detailed Phase 9 plan at: .claude/plans/snuggly-forging-cat.md

Phase 9 Goal: Apply the theme system to the actual public-facing storefront

Key deliverables:

  • Public storefront routes (/, /products/:slug, /collections, /cart, etc.)
  • Storefront LiveViews using real/mock data
  • LoadTheme plug to inject theme settings and CSS
  • CSS cache integration for performance
  • Navigation between admin theme editor and public shop
  • All 7 pages working on storefront (home, collection, product, cart, about, contact, error)

Implementation Phases (Details)

Phase 1: Database Foundation

Goal: Set up Settings and Media models for site-wide configuration

Steps:

  1. Create migrations for settings and images tables
  2. Run migrations
  3. Create lib/simpleshop_theme/settings/setting.ex schema
  4. Create lib/simpleshop_theme/settings/theme_settings.ex embedded schema
  5. Create lib/simpleshop_theme/settings.ex context with:
    • get_setting/2 - Get setting by key with default
    • put_setting/3 - Set a setting value
    • get_theme_settings/0 - Get theme settings as struct
    • update_theme_settings/1 - Update theme settings
  6. Create lib/simpleshop_theme/media/image.ex schema
  7. Create lib/simpleshop_theme/media.ex context with:
    • upload_image/2 - Upload image with type
    • get_image/1 - Get image by ID
    • delete_image/1 - Delete image
  8. Create lib/simpleshop_theme/theme/presets.ex with all 9 preset definitions
  9. Create seed data in priv/repo/seeds.exs for default theme settings
  10. Write tests:
    • test/simpleshop_theme/settings_test.exs
    • test/simpleshop_theme/media_test.exs

Files to create:

  • priv/repo/migrations/*_create_settings.exs
  • priv/repo/migrations/*_create_images.exs
  • lib/simpleshop_theme/settings/setting.ex
  • lib/simpleshop_theme/settings/theme_settings.ex
  • lib/simpleshop_theme/settings.ex
  • lib/simpleshop_theme/media/image.ex
  • lib/simpleshop_theme/media.ex
  • lib/simpleshop_theme/theme/presets.ex
  • test/simpleshop_theme/settings_test.exs
  • test/simpleshop_theme/media_test.exs

Files to modify:

  • priv/repo/seeds.exs - Add default theme settings

Git commit: feat: add Settings and Media contexts with theme settings schema

Validation:

  • Run tests: mix test
  • Run iex -S mix and test Settings API
  • Run seeds: mix run priv/repo/seeds.exs

Phase 2: CSS Architecture ✓

Goal: Set up CSS generation system using Phoenix/Tailwind best practices

Steps:

  1. Create priv/static/css/theme-primitives.css with primitive CSS variables from demo
  2. Create priv/static/css/theme-semantic.css with semantic aliases
  3. Update assets/css/app.css to import theme CSS files
  4. Create lib/simpleshop_theme/theme/css_generator.ex module:
    • generate/1 - Takes ThemeSettings, returns CSS custom properties string
    • Helper functions for each theme token (mood, typography, shape, density)
    • hex_to_hsl/1 for accent color conversion
    • Use Tailwind-friendly approach (CSS variables that Tailwind can reference)
  5. Create lib/simpleshop_theme/theme/css_cache.ex GenServer with ETS table
  6. Add CSSCache to supervision tree in lib/simpleshop_theme/application.ex
  7. Write tests:
    • test/simpleshop_theme/theme/css_generator_test.exs
    • test/simpleshop_theme/theme/presets_test.exs

Files to create:

  • priv/static/css/theme-primitives.css
  • priv/static/css/theme-semantic.css
  • lib/simpleshop_theme/theme/css_generator.ex
  • lib/simpleshop_theme/theme/css_cache.ex
  • test/simpleshop_theme/theme/css_generator_test.exs
  • test/simpleshop_theme/theme/presets_test.exs

Files to modify:

  • assets/css/app.css
  • lib/simpleshop_theme/application.ex

Git commit: feat: add CSS generation system with custom properties and ETS cache

Validation:

  • Run tests: mix test test/simpleshop_theme/theme/
  • Generate CSS for each preset in IEx, verify output
  • Check CSS variables work with Tailwind

Phase 3: Smart Preview Data ✓

Goal: Create preview data system that uses real data when available, falls back to mock

Steps:

  1. Create lib/simpleshop_theme/theme/preview_data.ex with:
    • products/0 - Real products if exist, else mock products
    • cart_items/0 - Mock cart contents
    • testimonials/0 - Mock testimonials for homepage
    • categories/0 - Mock product categories
    • has_real_products?/0 - Check if shop has products
    • Mock data uses placeholder images from https://placehold.co/
  2. Write tests:
    • test/simpleshop_theme/theme/preview_data_test.exs

Files to create:

  • lib/simpleshop_theme/theme/preview_data.ex
  • test/simpleshop_theme/theme/preview_data_test.exs

Git commit: feat: add smart preview data system with mock fallback

Validation:

  • Run tests: mix test test/simpleshop_theme/theme/preview_data_test.exs
  • Call functions in IEx, verify data structure
  • Verify falls back to mock when no products exist

Phase 4: Theme LiveView (Basic) ✓

Goal: Get basic theme editor working with preset switching

Steps:

  1. Create lib/simpleshop_theme_web/live/theme_live/index.ex:
    • Mount: Load theme settings from Settings context
    • Assign: :theme_settings, :preview_page, :generated_css
    • Event handlers: apply_preset, change_preview_page, save_theme
  2. Create lib/simpleshop_theme_web/live/theme_live/index.html.heex:
    • Two-column layout (controls sidebar + preview area)
    • Render preset buttons using Tailwind/daisyUI components
    • Render preview frame with generated CSS in <style> tag
    • Use Phoenix.Component for reusable pieces
  3. Add route to lib/simpleshop_theme_web/router.ex:
    • live "/admin/theme", ThemeLive.Index (authenticated)
  4. Add "Theme" link to user menu/navigation
  5. Write tests:
    • test/simpleshop_theme_web/live/theme_live_test.exs

Files to create:

  • lib/simpleshop_theme_web/live/theme_live/index.ex
  • lib/simpleshop_theme_web/live/theme_live/index.html.heex
  • test/simpleshop_theme_web/live/theme_live_test.exs

Files to modify:

  • lib/simpleshop_theme_web/router.ex
  • lib/simpleshop_theme_web/components/layouts.ex (add nav link)

Git commit: feat: add Theme LiveView with preset switching

Validation:

  • Run tests: mix test test/simpleshop_theme_web/live/theme_live_test.exs
  • Visit /admin/theme, click presets, verify CSS changes
  • Test authentication requirement

Phase 5: Preview Pages ✓

Goal: Create all 7 preview page templates

Steps:

  1. Create preview page templates by porting HTML from theme-demo-v28.html:
    • lib/simpleshop_theme_web/live/theme_studio_live/preview_pages/home.html.heex
    • lib/simpleshop_theme_web/live/theme_studio_live/preview_pages/collection.html.heex
    • lib/simpleshop_theme_web/live/theme_studio_live/preview_pages/pdp.html.heex
    • lib/simpleshop_theme_web/live/theme_studio_live/preview_pages/cart.html.heex
    • lib/simpleshop_theme_web/live/theme_studio_live/preview_pages/about.html.heex
    • lib/simpleshop_theme_web/live/theme_studio_live/preview_pages/contact.html.heex
    • lib/simpleshop_theme_web/live/theme_studio_live/preview_pages/error.html.heex
  2. Port CSS from demo to theme CSS files (primitives, semantic)
  3. Wire up mock data in templates
  4. Implement page switcher buttons in preview frame
  5. Add data attributes system (data-mood, data-typography, etc.) to preview container

Files to create:

  • 7 preview page .html.heex files in preview_pages/ directory

Files to modify:

  • lib/simpleshop_theme_web/live/theme_studio_live/index.html.heex - Add page switcher
  • CSS files - Port all styles from demo

Git commit: feat: add preview page templates with theme styling

Validation:

  • Run tests: mix test test/simpleshop_theme_web/live/theme_live_test.exs
  • Switch between all 7 pages, verify they render correctly
  • Verify preview data shows (mock or real)

Phase 6: Customization Options ✓

Goal: Add all customization controls beyond presets

Steps:

  1. Create option group components in index.html.heex:
    • Mood selector (4 options: neutral, warm, cool, dark)
    • Typography selector (7 options: clean, editorial, modern, classic, friendly, minimal, impulse)
    • Shape selector (4 options: sharp, soft, round, pill)
    • Density selector (3 options: spacious, balanced, compact)
    • Grid columns selector (3 options: 2, 3, 4)
    • Header layout selector (3 options: standard, centered, minimal)
  2. Add color pickers:
    • Accent color (with live preview)
    • Secondary accent color
    • Sale color
  3. Add advanced options accordion:
    • Font size (small, medium, large)
    • Heading weight (light, regular, bold)
    • Layout width (contained, wide, full)
    • Button style (filled, outline, soft)
    • Card shadow (none, subtle, pronounced)
    • Product text align (left, center)
    • Image aspect ratio (square, portrait, landscape)
  4. Add feature toggles:
    • Announcement bar
    • Sticky header
    • Hover image
    • Quick add button
    • Show prices
    • PDP trust badges
    • PDP reviews
    • PDP related products
  5. Implement update_setting event handler in LiveView
  6. Wire all controls to update theme settings and regenerate CSS

Files to modify:

  • lib/simpleshop_theme_web/live/theme_live/index.ex - Add update_setting handler
  • lib/simpleshop_theme_web/live/theme_live/index.html.heex - Add all controls
  • test/simpleshop_theme_web/live/theme_live_test.exs - Add tests for customization

Git commit: feat: add full customization controls with real-time preview

Validation:

  • Run tests: mix test test/simpleshop_theme_web/live/theme_live_test.exs
  • Change each option, verify preview updates instantly
  • Test all toggles work correctly

Phase 6.5: Fix Theme Visual Application 🔧

Goal: Fix the theme controls so they actually visually affect the preview pages

Problem: Theme controls update the database but don't visually change the preview. The Phoenix implementation is missing the data-attribute-based CSS system that the demo uses.

Root Cause Analysis: The demo HTML uses data attributes (data-mood="warm", data-typography="editorial", etc.) on the .preview-frame element, and the CSS uses attribute selectors like .preview-frame[data-mood="warm"] { ... } to apply styles. The Phoenix LiveView implementation is missing these data attributes entirely.

Discovered Issues:

Issue Status Root Cause
Fonts don't change Broken 1. Google Fonts not loaded
2. No data attributes on preview-frame
Mood/colors don't work (except dark) Broken No data attributes on preview-frame
Shape doesn't work Broken No data attributes on preview-frame
Density doesn't work Broken No data attributes on preview-frame
Grid columns 4 shows as 2 Broken Tailwind can't process dynamic class interpolation
Accent color doesn't update ⚠️ Partial CSS generated but may be overridden
Header layout has no component Missing No header component in preview pages

Steps:

  1. Add Google Fonts to root layout (lib/simpleshop_theme_web/components/layouts/root.html.heex):

    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,500;9..144,600;9..144,700&family=Inter:wght@400;500;600;700&family=Libre+Baskerville:wght@400;700&family=Nunito:wght@400;600;700&family=Nunito+Sans:opsz,wght@6..12,300;6..12,400;6..12,500;6..12,600&family=Outfit:wght@300;400;500;600&family=Source+Sans+3:wght@400;500;600&family=Space+Grotesk:wght@400;500;600&display=swap" rel="stylesheet">
    
  2. Add data attributes to preview frame (lib/simpleshop_theme_web/live/theme_live/index.html.heex:205):

    <div class="preview-frame bg-white overflow-auto"
         data-mood={@theme_settings.mood}
         data-typography={@theme_settings.typography}
         data-shape={@theme_settings.shape}
         data-density={@theme_settings.density}
         data-grid={@theme_settings.grid_columns}
         data-header={@theme_settings.header_layout}
         style="min-height: 600px; max-height: calc(100vh - 200px);">
    
  3. Copy CSS from demo file (theme-demo-v28.html lines 81-217) to create priv/static/css/theme-layer2-attributes.css:

    • All the .preview-frame[data-mood="..."] rules
    • All the .preview-frame[data-typography="..."] rules
    • All the .preview-frame[data-shape="..."] rules
    • All the .preview-frame[data-density="..."] rules
    • All the .preview-frame[data-grid="..."] rules
    • All the .preview-frame[data-header="..."] rules
  4. Import the new CSS file in assets/css/app.css:

    @import "../priv/static/css/theme-primitives.css";
    @import "../priv/static/css/theme-layer2-attributes.css";
    @import "../priv/static/css/theme-semantic.css";
    
  5. Fix grid columns dynamic classes - Replace string interpolation with conditional logic in all preview pages:

    <!-- BEFORE (broken): -->
    <div class={"grid gap-6 grid-cols-1 sm:grid-cols-2 lg:grid-cols-#{@theme_settings.grid_columns}"}>
    
    <!-- AFTER (working): -->
    <div class={["grid gap-6 grid-cols-1 sm:grid-cols-2",
                 case @theme_settings.grid_columns do
                   "2" -> "lg:grid-cols-2"
                   "3" -> "lg:grid-cols-3"
                   "4" -> "lg:grid-cols-4"
                   _ -> "lg:grid-cols-3"
                 end]}>
    
  6. Add header component to preview pages - Create a header section that responds to data-header attribute:

    <header class="theme-header" style="background-color: var(--t-surface-raised); border-bottom: 1px solid var(--t-border-subtle);">
      <!-- Header content that adapts based on data-header="standard|centered|minimal" -->
    </header>
    
  7. Update CSSGenerator to work alongside attribute-based CSS:

    • Keep generating accent color HSL variables (these are used by all themes)
    • Remove mood/typography/shape/density generation (now handled by attribute CSS)
    • Focus on dynamic values like accent_color, secondary_accent_color, sale_color
  8. Test all controls in browser to verify visual changes work

Files to create:

  • priv/static/css/theme-layer2-attributes.css - Attribute-based theme CSS from demo

Files to modify:

  • lib/simpleshop_theme_web/components/layouts/root.html.heex - Add Google Fonts
  • lib/simpleshop_theme_web/live/theme_live/index.html.heex - Add data attributes to preview-frame
  • assets/css/app.css - Import new CSS file
  • lib/simpleshop_theme/theme/css_generator.ex - Simplify to focus on color variables
  • All preview page files - Fix grid columns dynamic classes
  • All preview page files - Add header component

Git commit: fix: add data attributes and attribute-based CSS to make theme controls work visually

Validation:

  • Change mood - verify background colors change
  • Change typography - verify fonts change
  • Change shape - verify button/card border radius changes
  • Change density - verify spacing changes
  • Change grid columns - verify all 3 options (2, 3, 4) work correctly
  • Change accent color - verify primary buttons change color
  • Change header layout - verify header structure changes

Phase 7: File Uploads (Logo & Header) ✓

Goal: Enable logo and header image uploads with database storage

Steps:

  1. Add dependency to mix.exs: {:image, "~> 0.54"} for thumbnail generation
  2. Implement SVG detection and text storage in image.ex changeset
  3. Add Media context functions for logo/header:
    • Update upload_image/2 to handle thumbnails
    • Add get_logo/0, get_header/0 convenience functions
  4. Create lib/simpleshop_theme_web/controllers/image_controller.ex:
    • show/2 - Serve image BLOB with proper content-type and caching
    • recolored_svg/2 - Serve recolored SVG
  5. Create lib/simpleshop_theme/media/svg_recolorer.ex for SVG color manipulation
  6. Add routes for image serving in router
  7. Configure LiveView uploads in index.ex:
    • :logo_upload - Accept PNG, JPG, WebP, SVG (max 2MB)
    • :header_upload - Accept PNG, JPG, WebP (max 5MB)
  8. Add upload controls to branding section in template using daisyUI components:
    • Logo mode selector (text-only, logo+text, logo-only, header-image, logo+header)
    • Logo upload with preview
    • Logo size slider (24-120px)
    • SVG recolor toggle + color picker
    • Header image upload with preview
    • Header zoom slider (100-200%)
    • Header position sliders (X and Y: 0-100%)
  9. Implement upload event handlers
  10. Display uploaded images in preview
  11. Write tests:
    • test/simpleshop_theme_web/controllers/image_controller_test.exs
    • test/simpleshop_theme/media/svg_recolorer_test.exs

Files to create:

  • lib/simpleshop_theme_web/controllers/image_controller.ex
  • lib/simpleshop_theme/media/svg_recolorer.ex
  • test/simpleshop_theme_web/controllers/image_controller_test.exs
  • test/simpleshop_theme/media/svg_recolorer_test.exs

Files to modify:

  • lib/simpleshop_theme/media.ex - Add logo/header helpers
  • lib/simpleshop_theme/media/image.ex - SVG handling
  • lib/simpleshop_theme_web/live/theme_live/index.ex - Upload config and handlers
  • lib/simpleshop_theme_web/live/theme_live/index.html.heex - Branding section UI
  • lib/simpleshop_theme_web/router.ex - Image serving routes
  • mix.exs - Add :image dependency
  • test/simpleshop_theme_web/live/theme_live_test.exs - Add upload tests

Git commit: feat: add image uploads with BLOB storage and SVG recoloring

Validation:

  • Run tests: mix test
  • Upload logo and header images
  • Verify BLOB storage in database
  • Test SVG recoloring works
  • Verify images display in preview
  • Test image serving with proper cache headers

Phase 8: Persistence & Polish ✓

Goal: Complete save functionality and UX polish

Steps:

  1. Implement "Save Theme" button and handler:
    • Show loading state during save
    • Flash success/error message
    • Invalidate CSS cache
  2. Add "Reset to Preset" functionality
  3. Implement unsaved changes warning
  4. Add keyboard shortcuts (Cmd+S to save)
  5. Debounce color picker inputs (300ms) to avoid excessive renders
  6. Add loading skeletons for preview
  7. Handle edge cases:
    • File too large error
    • Invalid file type error
    • Network errors
  8. Add client-side validation
  9. Improve mobile responsiveness of editor
  10. Add tooltips/help text for complex options

Files to modify:

  • lib/simpleshop_theme_web/live/theme_live/index.ex - Save logic, debouncing
  • lib/simpleshop_theme_web/live/theme_live/index.html.heex - UI polish
  • JavaScript hooks for keyboard shortcuts (if needed)
  • test/simpleshop_theme_web/live/theme_live_test.exs - Test persistence

Git commit: feat: add theme persistence with UX polish and validation

Validation:

  • Run tests: mix test
  • Save theme, reload page, verify settings persist
  • Test unsaved changes warning
  • Test keyboard shortcuts
  • Verify error handling for edge cases
  • Test mobile responsiveness

Phase 9: Storefront Integration 🚧

Goal: Apply theme to actual public-facing shop

Status: Ready to implement - See detailed plan in .claude/plans/snuggly-forging-cat.md

High-level overview:

  1. Extract shared components from preview pages (header, footer, product card, etc.)
  2. Create LoadTheme plug to inject theme settings and CSS into all public requests
  3. Create dedicated shop layout with .shop-root class and data attributes
  4. Build storefront LiveViews for all pages:
    • Home (/)
    • Collection (/collections/:slug)
    • Product Detail (/products/:slug)
    • Cart (/cart)
    • About (/about)
    • Contact (/contact)
    • Error (404 page)
  5. Wire up CSS cache invalidation when theme is saved
  6. Add cache warming on application startup
  7. Add navigation links between admin and storefront
  8. Comprehensive testing and polish

Key files to create (23 files):

  • 5 shared component modules in components/shop/
  • 1 LoadTheme plug
  • 1 shop layout template
  • 6 storefront LiveView modules + templates
  • 1 error page template
  • 6 test files

Key files to modify (9 files):

  • Router (add public routes and plug)
  • Settings context (add cache invalidation)
  • Application (add cache warming)
  • Theme editor (add "View Shop" link)
  • Preview pages (use shared components)

Detailed implementation plan: See .claude/plans/snuggly-forging-cat.md for 12-step implementation guide with code examples, validation steps, and architectural decisions.

Git commit strategy: Commit after each major step (components extraction, plug creation, each LiveView, etc.)

Validation approach:

  • Test theme editor still works after component extraction
  • Test each storefront page individually as built
  • Verify CSS cache invalidation flow
  • Test navigation between admin and storefront
  • Comprehensive testing in Step 12

Technical Implementation Details

CSS Generation Example

# lib/simpleshop_theme/theme/css_generator.ex
defmodule SimpleshopTheme.Theme.CSSGenerator do
  alias SimpleshopTheme.Settings.ThemeSettings

  def generate(%ThemeSettings{} = settings) do
    """
    .preview-frame, .shop-root {
      #{generate_accent(settings.accent_color)}
      #{generate_mood(settings.mood)}
      #{generate_typography(settings.typography)}
      #{generate_shape(settings.shape)}
      #{generate_density(settings.density)}
    }
    """
  end

  defp generate_accent(hex) do
    {h, s, l} = hex_to_hsl(hex)
    """
    --t-accent-h: #{h};
    --t-accent-s: #{s}%;
    --t-accent-l: #{l}%;
    --t-accent: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));
    """
  end

  defp hex_to_hsl("#" <> hex), do: hex_to_hsl(hex)
  defp hex_to_hsl(hex) when byte_size(hex) == 6 do
    {r, ""} = Integer.parse(String.slice(hex, 0..1), 16)
    {g, ""} = Integer.parse(String.slice(hex, 2..3), 16)
    {b, ""} = Integer.parse(String.slice(hex, 4..5), 16)

    r = r / 255
    g = g / 255
    b = b / 255

    max = Enum.max([r, g, b])
    min = Enum.min([r, g, b])

    l = (max + min) / 2

    if max == min do
      {0, 0, round(l * 100)}
    else
      d = max - min
      s = if l > 0.5, do: d / (2 - max - min), else: d / (max + min)

      h = cond do
        max == r -> (g - b) / d + (if g < b, do: 6, else: 0)
        max == g -> (b - r) / d + 2
        max == b -> (r - g) / d + 4
      end

      {round(h * 60), round(s * 100), round(l * 100)}
    end
  end

  defp generate_mood("neutral"), do: """
    --t-surface-base: #ffffff;
    --t-surface-raised: #ffffff;
    --t-text-primary: #171717;
    --t-border-default: #e5e5e5;
  """

  defp generate_mood("warm"), do: """
    --t-surface-base: #fdf8f3;
    --t-surface-raised: #fffcf8;
    --t-text-primary: #1c1917;
    --t-border-default: #e7e0d8;
  """

  defp generate_mood("cool"), do: """
    --t-surface-base: #f4f7fb;
    --t-surface-raised: #f8fafc;
    --t-text-primary: #0f172a;
    --t-border-default: #d4dce8;
  """

  defp generate_mood("dark"), do: """
    --t-surface-base: #0a0a0a;
    --t-surface-raised: #171717;
    --t-text-primary: #fafafa;
    --t-border-default: #262626;
    --p-shadow-strength: 0.25;
  """

  # Similar for typography, shape, density...
end

LiveView Upload Configuration

# In mount/3
socket =
  socket
  |> allow_upload(:logo_upload,
    accept: ~w(.png .jpg .jpeg .webp .svg),
    max_entries: 1,
    max_file_size: 2_000_000,
    auto_upload: true
  )
  |> allow_upload(:header_upload,
    accept: ~w(.png .jpg .jpeg .webp),
    max_entries: 1,
    max_file_size: 5_000_000,
    auto_upload: true
  )

# Handle upload completion
def handle_event("logo_uploaded", _params, socket) do
  uploaded_files =
    consume_uploaded_entries(socket, :logo_upload, fn %{path: path}, entry ->
      file_binary = File.read!(path)

      {:ok, image} = Media.upload_image(%{
        image_type: "logo",
        filename: entry.client_name,
        content_type: entry.client_type,
        file_size: entry.client_size,
        data: file_binary
      })

      {:ok, image}
    end)

  case uploaded_files do
    [image | _] ->
      # Update theme settings to reference this image
      settings = socket.assigns.theme_settings
      {:ok, _} = Settings.update_theme_settings(%{settings | logo_image_id: image.id})
      {:noreply, assign(socket, :logo_image, image)}
    [] ->
      {:noreply, socket}
  end
end

Image Controller Serving

defmodule SimpleshopThemeWeb.ImageController do
  use SimpleshopThemeWeb, :controller
  alias SimpleshopTheme.Media

  def show(conn, %{"id" => id}) do
    case Media.get_image(id) do
      nil ->
        send_resp(conn, 404, "Image not found")

      image ->
        conn
        |> put_resp_content_type(image.content_type)
        |> put_resp_header("cache-control", "public, max-age=31536000, immutable")
        |> put_resp_header("etag", "\"#{image.id}\"")
        |> send_resp(200, image.data)
    end
  end

  def recolored_svg(conn, %{"id" => id, "color" => color}) do
    with %{is_svg: true, svg_content: svg} <- Media.get_image(id),
         clean_color <- String.trim_leading(color, "#"),
         recolored <- SimpleshopTheme.Media.SVGRecolorer.recolor(svg, "##{clean_color}") do
      conn
      |> put_resp_content_type("image/svg+xml")
      |> put_resp_header("cache-control", "public, max-age=3600")
      |> send_resp(200, recolored)
    else
      _ -> send_resp(conn, 400, "Invalid request")
    end
  end
end

Key Dependencies to Add

# mix.exs
{:image, "~> 0.54"}           # Thumbnail generation (uses libvips)
{:fast_sanitize, "~> 0.2"}     # SVG sanitization (security)

Router Updates

# lib/simpleshop_theme_web/router.ex

# Theme editor (authenticated)
scope "/admin", SimpleshopThemeWeb do
  pipe_through [:browser, :require_authenticated_user]

  live_session :theme_editor,
    on_mount: [{SimpleshopThemeWeb.UserAuth, :require_authenticated}] do
    live "/theme", ThemeLive.Index, :index
  end
end

# Image serving (public)
scope "/", SimpleshopThemeWeb do
  pipe_through :browser

  get "/images/:id", ImageController, :show
  get "/images/:id/recolored/:color", ImageController, :recolored_svg
end

# Public storefront (future - Phase 9)
# scope "/", SimpleshopThemeWeb do
#   pipe_through :browser
#   live "/", ShopLive.Home, :index
#   live "/products/:slug", ShopLive.Product, :show
# end

Critical Files Reference

To Create

  • lib/simpleshop_theme/settings.ex - Settings context
  • lib/simpleshop_theme/settings/setting.ex - Setting schema (key-value)
  • lib/simpleshop_theme/settings/theme_settings.ex - Embedded theme settings
  • lib/simpleshop_theme/media.ex - Media context
  • lib/simpleshop_theme/media/image.ex - Image schema
  • lib/simpleshop_theme/media/svg_recolorer.ex - SVG manipulation
  • lib/simpleshop_theme/theme/presets.ex - 9 preset definitions
  • lib/simpleshop_theme/theme/css_generator.ex - CSS generation
  • lib/simpleshop_theme/theme/css_cache.ex - ETS caching
  • lib/simpleshop_theme/theme/preview_data.ex - Smart preview data
  • lib/simpleshop_theme_web/live/theme_live/index.ex - Main LiveView
  • lib/simpleshop_theme_web/controllers/image_controller.ex - Image serving
  • priv/static/css/theme-primitives.css - CSS Layer 1
  • priv/static/css/theme-semantic.css - CSS Layer 3
  • Comprehensive test files for all contexts and LiveViews

To Modify

  • lib/simpleshop_theme/application.ex - Add CSS cache to supervision tree
  • lib/simpleshop_theme_web/router.ex - Add theme and image routes
  • lib/simpleshop_theme_web/components/layouts.ex - Add theme nav link
  • assets/css/app.css - Import theme CSS
  • priv/repo/seeds.exs - Add default theme settings
  • mix.exs - Add dependencies

Reference Files

  • SIMPLESHOP_THEME_STUDIO_SPEC.md - Complete specification
  • theme-demo-v28.html - Working prototype (CSS, HTML structure, JavaScript logic)

Success Criteria

User can access theme studio at /admin/theme All 9 presets work with real-time preview All customization options update preview instantly Logo and header images upload and store as BLOBs SVG recoloring works correctly Preview shows all 7 mock pages Settings persist to database "Save" button works with success feedback Images served efficiently from database CSS cached in ETS for performance Everything self-contained in SQLite file

Next Steps After Implementation

  1. Add Products Context - So users can add real products
  2. Build Storefront - Public-facing shop pages using saved theme
  3. Add Orders/Cart - E-commerce functionality
  4. Multi-admin Support - Invite additional users to manage shop
  5. Custom Domains - Allow users to point their own domain
  6. Theme Export/Import - Share themes between shops
  7. Advanced Features - Custom CSS, code injection, etc.