Adds SQLite FTS5 search index with BM25 ranking across product title,
category, variant attributes, and description. Search modal now has
live results with thumbnails, prices, and click-to-navigate. Index
rebuilds automatically after each provider sync.
Also fixes Access syntax on Product/ProductImage structs (Map.get
instead of bracket notation) which was causing crashes when real
products were loaded from the database.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds cheapest_price, compare_at_price, in_stock, on_sale columns to
products table (recomputed from variants after each sync). Shop
components now work with Product structs directly instead of plain
maps from PreviewData. Renames .name to .title, adds Product display
helpers (primary_image, hover_image, option_types) and ProductImage
helpers (display_url, direct_url, source_width). Adds Products context
query functions for storefront use (list_visible_products,
get_visible_product, list_categories with DB-level sort/filter).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Dashboard at /admin shows setup progress (when not live), stat cards
(orders, revenue, products), and recent paid orders table. Replaces
the old AdminController redirect. Add Dashboard to sidebar nav as
first item, update admin bar and theme editor links to /admin.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Merge shop status, payments, products (Printify), account (email/password),
and advanced (dashboard/error tracker links) into /admin/settings. Simplify
Auth.Settings to a redirector for /users/settings and confirm-email tokens.
Remove Providers from sidebar nav. Update all redirects and tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New admin root + child layouts with daisyUI drawer sidebar
- AdminLayoutHook tracks current path for active nav highlighting
- Split router into :admin, :admin_theme, :user_settings live_sessions
- Theme editor stays full-screen with back link to admin
- Admin bar on shop pages for logged-in users (mount_current_scope)
- Strip Layouts.app wrapper from admin LiveViews
- Remove nav from root.html.heex (now only serves auth pages)
- 9 new layout tests covering sidebar, active state, theme editor, admin bar
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All internal links used plain <a href>, causing full page reloads
between pages in the same live_session. Changed to <.link navigate>
for client-side navigation across layout, product, cart, base, and
content components.
Added explicit width/height attributes to all SVG icons so they
render at sensible sizes before CSS loads on initial page visit.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- collection filter bar: horizontal scroll on mobile instead of wrapping
across 3 rows, smaller pills at mobile sizes
- add sale collection filter at /collections/sale (filters on_sale products)
- hero :page variant: add consistent top padding (var(--space-2xl))
- contact page: remove redundant top padding (hero handles it now)
- error page: fix CSS path from /assets/app.css to /assets/css/app.css
(broken in production due to asset fingerprinting)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The stretched-link ::after pseudo-element (z-index: 0) was covering
the image area, intercepting touch events. Give the image container
z-index: 1 so swipe gestures reach the scroll container.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
shop_footer now calls PreviewData.categories() itself, removing the
need to thread categories through CartHook, shop_layout, and all 8
page templates.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- add missing cta_href to hero section and error page CTAs
- replace hardcoded footer shop links with real product categories
- restructure product cards with stretched-link pattern so category
badges link to their collection page
- unify social icons: footer and contact page share the same default
links from a single source in content.ex
- add search implementation plan (docs/plans/search.md, deferred)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The drawer now has full quantity controls, remove, subtotal, and
checkout — the link to the cart page added friction without value.
Cart page remains accessible via the basket icon in nav.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move increment/decrement handlers from Cart LiveView into CartHook so
they work from any page's drawer. Enable show_quantity_controls on the
drawer's cart_item_row. Scope cart tests to #main-content to avoid
duplicate button matches.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wire up +/− buttons with phx-click events and handle_event handlers,
clamp to 1–99, reset to 1 after add-to-cart. Trust badges now use a
single hero-check-circle icon and sentence case text.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rewrite breadcrumb as semantic ol/li with aria-current="page", CSS
chevron separators, 0.875em font size, and ellipsis truncation on
mobile for long product names.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Mobile: swipeable carousel with dot indicators, no lightbox trigger.
Desktop: carousel with thumbnail grid, prev/next arrows, click to
open existing lightbox. Keeps all lightbox appearance and behaviour.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Product cards now use CSS scroll-snap on touch devices (mobile) for
swiping between images, with dot indicators and a JS hook for active
state. Desktop keeps the existing hover crossfade via @media (hover:
hover). Dots use size differentiation (WCAG 2.2 AA compliant) with
outline rings for contrast on any background.
Also fixes: no-image placeholder (SVG icon instead of broken img),
unnecessary wrapper div for single-image cards, and dev static asset
caching (was immutable for all envs, now only prod).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add priority attr to product_card, thread through to responsive_image
- First 2 featured products get fetchpriority="high" and eager loading
- Remove longPollFallbackMs to avoid unnecessary fallback attempt
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace one-off ShopLive.About with generic ShopLive.Content that
handles all static content pages via live_action. Add delivery &
returns, privacy policy, and terms of service pages with sample
content. Update footer help links and theme editor preview.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Admin UI at /admin/orders to view, filter, and inspect orders.
Adds list_orders/1 and count_orders_by_status/0 to the Orders
context, status filter tabs, clickable order table with streams,
and a detail page showing items, totals, and shipping address.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Store API keys and secrets encrypted in the SQLite database via the
existing Vault module (AES-256-GCM). The only external dependency is
SECRET_KEY_BASE — everything else lives in the portable DB file.
- Add encrypted_value column to settings table with new "encrypted" type
- Add put_secret/get_secret/delete_setting/secret_hint to Settings context
- Add Secrets module to load encrypted config into Application env at startup
- Add Stripe.Setup module with connect/disconnect/verify_api_key flow
- Auto-creates webhook endpoints via Stripe API in production
- Detects localhost and shows Stripe CLI instructions for dev
- Add admin credentials page at /admin/settings with guided setup:
- Not configured: single Secret key input with dashboard link
- Connected (production): status display, webhook info, disconnect
- Connected (dev): Stripe CLI instructions, manual signing secret input
- Remove Stripe env vars from dev.exs and runtime.exs
- Fix CSSCache test startup crash (handle_continue instead of init)
- Add nav link for Credentials page
507 tests, 0 failures.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Stripe-hosted Checkout integration with full order lifecycle:
- stripity_stripe ~> 3.2 with sandbox/prod config via env vars
- Order and OrderItem schemas with price snapshots at purchase time
- CheckoutController creates pending order then redirects to Stripe
- StripeWebhookController verifies signatures and confirms payment
- Success page with real-time PubSub updates from webhook
- Shop flash messages for checkout error feedback
- Cart cleared after successful payment
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace all placeholder text with demo-aware copy that signals "replace me"
- Update USPs for POD accuracy (made to order, quality materials)
- Fix broken footer links (/delivery, /returns → /contact)
- Add real platform URLs to social icons with target="_blank"
- Make cart item images and names link to product pages
- Switch about page image to responsive_image component
- Add missing cart_status to collection page cart drawer
- Unify search hint text across all page templates
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Cart context with pure functions for add/remove/update/hydrate
- Price formatting via ex_money (replaces all float division)
- CartHook on_mount with attach_hook for shared event handlers
(open/close drawer, remove item, PubSub sync)
- Accessible cart drawer with focus trap, scroll lock, aria-live
- Cart page with increment/decrement quantity controls
- Preview mode cart drawer support in theme editor
- Cart persistence to session via JS hook + API endpoint
- 19 tests covering all Cart pure functions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix Printify options parsing (Color/Size were swapped)
- Add extract_option_types/1 for frontend display with hex colors
- Filter option types to only published variants (not full catalog)
- Track selected variant in LiveView with price updates
- Color swatches for color-type options, text buttons for size
- Disable unavailable combinations
- Add startup recovery for stale sync status
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Downloads Printify CDN images via ImageDownloadWorker, processes
through Media pipeline (WebP conversion, AVIF/WebP variant generation),
and links to ProductImage via new image_id FK.
- Add image_id to product_images table
- ImageDownloadWorker downloads and processes external images
- sync_product_images preserves image_id when URL unchanged
- PreviewData uses local images for responsive <picture> elements
- VariantCache enqueues pending downloads on startup
- mix simpleshop.download_images backfill task
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add /admin/providers LiveView for connecting and managing POD providers
- Implement pagination for Printify API (handles all products, not just first page)
- Add parallel processing (5 concurrent) for faster product sync
- Add slug-based fallback matching when provider_product_id changes
- Add error recovery with try/rescue to prevent stuck sync status
- Add checksum-based change detection to skip unchanged products
- Add upsert tests covering race conditions and slug matching
- Add Printify provider tests
- Document Printify integration research (product identity, order risks,
open source vs managed hosting implications)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Create separate CSS bundles to reduce shop page load times:
- app-shop.css (45KB/7.8KB gzip): Shop pages only, no daisyUI
- app.css (139KB): Admin pages with daisyUI and theme editor
Key changes:
- Add app-shop.css with targeted @source paths for shop files only
- Move .preview-frame rules from theme-layer2-attributes.css to app.css
- Delete fonts.css (fonts now generated inline by CSSGenerator)
- Add inline all-fonts generation in theme editor for typography switching
- Configure separate Tailwind profiles and watchers for both bundles
Shop pages now load 54% less CSS by excluding:
- daisyUI components (admin only)
- .preview-frame theme switching rules (editor only)
- Admin-specific Tailwind utilities
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add path_resolver parameter to font generation functions so both
font preloads and CSS @font-face declarations use the same digested
paths in production. This prevents duplicate font downloads when
preloads were using digested paths but CSS used non-digested paths.
- Add path_resolver parameter to Fonts.generate_font_faces/2,
Fonts.preload_links/2, and Fonts.generate_all_font_faces/1
- Update CSSGenerator.generate/2 to accept path_resolver
- Update CSSCache.warm/0 to use Endpoint.static_path/1
- Move CSSCache to start after Endpoint in supervision tree
- Add 1-year cache headers for static assets
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Update theme preview to use optimized responsive images with modern
format support (AVIF/WebP with JPEG fallback).
- Change mockup URLs from .jpg to base paths for srcset generation
- Add source_width to preview products for proper variant selection
- Add responsive_image component with <picture> element
- Update image_text_section to use optimized 800px WebP variant
This ensures the theme preview loads optimal image formats and sizes,
matching the production responsive image behavior.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Create a DRY Fonts module that centralizes all font definitions and
generates @font-face declarations dynamically based on typography preset.
- Add Fonts module with typography font mappings
- Generate @font-face CSS in CSSGenerator based on active preset
- Add font preload links in shop_root layout for performance
- Remove hardcoded font-face declarations from CSS
This improves font loading performance by only loading fonts for the
active typography preset and preloading critical fonts.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The shop.html.heex and shop_root.html.heex were both full HTML documents.
When nested (root layout + live layout), this created invalid HTML with
duplicate <!DOCTYPE>, <html>, <head>, and <body> tags.
This caused:
- "Cannot bind multiple views to the same DOM element" console errors
- Failed Lighthouse audits
- Potential rendering issues
Fix:
- shop_root.html.heex: Now contains the full HTML document with theme
settings, CSS variables, and data attributes
- shop.html.heex: Now just passes through @inner_content
Results:
- Console errors: Gone
- Best Practices: 96 → 100
- Total Blocking Time: 140ms → 30ms
- HTML validation: Passes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Self-hosted fonts:
- Download all 10 typefaces (35 font files, 728KB) from Google Fonts
- Create @font-face declarations in assets/css/fonts.css
- Remove Google Fonts external dependency from layouts
- Privacy improvement (no Google tracking)
- Performance improvement (no DNS lookup to fonts.googleapis.com)
- GDPR compliant (no third-party requests)
Admin access:
- Add /admin route that redirects to /admin/theme (requires auth)
- Remove Admin link from footer (too visible for visitors)
- Shop owners can bookmark or type /admin directly
Layout improvements:
- Create shop_root.html.heex as minimal root for shop pages
- Shop pages no longer show admin nav bar
Other:
- Update .gitignore to exclude digested static files
- Add PageSpeed 100% task to ROADMAP.md
- Fix test to check /users/settings instead of shop homepage
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace cramped horizontal nav on mobile with a fixed bottom tab bar
for thumb-friendly navigation. The header nav is now hidden on mobile
(<768px) and the bottom nav provides Home, Shop, About, and Contact
links with icons.
- Add mobile_bottom_nav component with icon + label nav items
- Active page has accent-colored background highlight and larger icon
- Add shadow to lift nav visually off the page
- Update all page templates with bottom padding and bottom nav
- Remove CSS rule that was overriding Tailwind's hidden class
- Responsive header padding (tighter on mobile)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove marketing fluff "Be the first" - everyone on the list gets
emails at the same time. Just describe what they'll receive.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
More personal and authentic for a solo POD seller:
- "Get in touch" instead of "Contact Us"
- "Drop me a message" instead of "Drop us"
- "I'll get back to you" instead of "we'll"
- "Send a message" instead of "Send us a message"
- "How can I help?" instead of "How can we help?"
- "I'll send you a link" instead of "we'll send"
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
More accurate than "Stay in touch" - newsletters are one-way communication.
Also updated description slightly: "news" instead of "updates" to avoid
repetition with the title.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
More neutral and personal than "Follow us":
- Works for all platform types (GitHub, Ko-fi, etc., not just social follows)
- "me" fits a solo POD seller better than corporate "us"
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Select platforms typical for a quirky, nerdy POD seller:
- Instagram: primary visual portfolio
- Bluesky: nerdy Twitter alternative
- Mastodon: federated/open web presence
- Ko-fi: indie tips and commissions
- GitHub: open source projects
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>