berrypod/PROGRESS.md
jamey ca9f32fa42 update progress and plan for page builder stages 1-2
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 18:08:57 +00:00

46 KiB
Raw Blame History

Berrypod Progress

Single source of truth for project status and task tracking.

Current Status

Working:

  • Theme editor with 8 presets, instant switching, full customization
  • Image optimization pipeline (AVIF/WebP/JPEG responsive variants)
  • Shop pages (home, collections, products, cart, about, contact, error, delivery, privacy, terms)
  • Mobile-first design with bottom navigation
  • 1284 tests passing, 100% PageSpeed score
  • SQLite production tuning (IMMEDIATE transactions, mmap, WAL journal limit)
  • Variant selector with color swatches and size buttons
  • Session-based cart with real variant data (add/remove/quantity, cross-tab sync)
  • Cart drawer and cart page with hydrated product info
  • Stripe Checkout with order persistence and webhook handling
  • Admin credentials page with guided Stripe setup flow
  • Encrypted settings for API keys and secrets
  • Email settings admin UI with 10 adapter options, test email, env var precedence
  • FTS5 full-text search with live results modal
  • Denormalized product fields (cheapest_price, in_stock, on_sale) for DB-level sort/filter
  • Transactional emails (order confirmation, shipping notification)
  • Demo content polished and ready for production
  • Privacy-first analytics with comparison mode (period deltas on stat cards)

Tier 1 MVP complete. CI pipeline done. Hosting & deployment done (including observability). PageSpeed CI done (99-100 mobile, 97+ desktop). Usability fixes done. Shipping costs at checkout done. Per-colour product images with gallery filtering done (both providers). Printful integration complete (sync, orders, shipping, webhooks, mockup enrichment, catalog colours). CSS migration Phases 0-7 complete — project is fully Tailwind-free (hand-written CSS, 9.8 KB gzipped shop, 17.8 KB gzipped admin). Setup and launch readiness complete — /setup onboarding page, dashboard launch checklist, provider registry, provider-agnostic setup status.

Task list

Ordered by dependency level — admin shell chain first (unblocks most downstream work).

Plans: admin-redesign.md | admin-font-loading.md | setup-wizard.md | setup-and-launch.md | setup-auto-confirm.md | email-settings.md | search.md | products-refactor.md | shipping-sync.md | printful-integration.md | provider-strategy.md | css-migration.md | analytics-v2.md | page-builder.md

# Task Depends on Est Status
Done
2 site_live setting + Settings.site_live?/0 30m done
3 Accounts.has_admin?/0 + registration lockdown 1h done
8 Coming soon page 2 1h done
9 Setup.setup_status/0 helper 2, 3 30m done
10 ThemeHook gate (redirect to coming soon) 2, 8 30m done
14 Go live / take offline toggle (on settings page) 2 30m done
1 Filesystem restructure (consolidate live/ directories) 2h done
6 Admin shell component (sidebar nav, header) 1 2-3h done
7 Admin root + child layout templates 1 1h done
11 Theme editor back-to-admin link 6 30m done
4 Admin bar on shop pages 1h done
12 Consolidate settings page 6, 7 2-3h done
13 Admin dashboard (+ setup checklist) 6, 7, 9 2h done
15 Setup wizard + admin tests 13 1.5h done
5 Search (functional search with results) 3-4h done
17 Wire shop LiveViews to DB queries (replace PreviewData indirection) 2-3h done
16 Per-colour images + gallery filtering 3h done
18 Shipping costs at checkout 17 4h done
Printful integration
24 Printful HTTP client 1.5h done
25 Printful provider (sync + orders) 24 3h done
26 Multi-provider order routing 25 1h done
27 Printful shipping rates 25 1.5h done
28 Printful mockup generation worker 25 done (existing pipeline)
29 Printful webhooks 25 1.5h done
Next up
30 Admin UI tweaks for Printful 25 30m done
31 Printful + Printify client tests with Req.Test stubs 24-30 4.5h done
Setup and launch readiness (plan)
41 Provider + payment registries 30m done
42 Make Setup provider-agnostic + add checklist fields 41 45m done
43 Setup LiveView (/setup) — account, provider, payments 41, 42 2.5h done
44 Dashboard launch checklist component + go-live 42 2h done
45 Router, auth flow, redirects 43, 44 30m done
46 CSS additions (~200 lines) 43, 44 20m done
47 Tests (setup, dashboard checklist, auth flow) 43-46 2h done
48 Remove old /admin/setup 43-47 15m done
CSS migration — Tailwind + DaisyUI to modern CSS
32 Phase 0: Foundation + screenshot tooling 30-31 1.5h done
33 Phase 1: Layout primitives + reset 32 1.5h done
34 Phase 2: Extract product inline styles 33 3h done
35 Phase 3: Extract layout + cart inline styles 33 3h done
36 Phase 4: Extract content + template inline styles 33 2.5h done
37 Phase 5: Remove Tailwind from shop pages 34-36 3h done
38 Phase 6: Replace DaisyUI (admin) 37 3h done
39 Phase 7: Remove Tailwind entirely 38 1.5h done
40 Phase 8: Unified CSS — admin inherits shop theme system 39 12.5h done
Self-hosted email + setup (auto-confirm plan, email settings plan)
50 Setup auto-confirm: secret gate, auto-login, remove check_inbox 2h done
51 Email settings admin UI with multi-adapter support 50 3h done
Bugs / polish
49 Admin font loading + cache miss path resolver (plan) 1h done

| | Analytics v2 (plan) | | | | | 52 | Comparison mode: period deltas on stat cards | — | 1h | done | | 53 | Dashboard filtering (click to filter by dimension) | 52 | 3h | done | | 54 | CSV export | 52 | 1.5h | done | | 55 | Entry/exit pages panel | — | 1h | done | | | Favicon & site icons (plan) | | | | | 86 | Favicon source upload — image_type: "icon", "use logo as icon" toggle, upload in theme editor, FaviconGeneratorWorker, favicon_variants table | — | 2.5h | done | | 87 | FaviconController serving all favicon routes + dynamic site.webmanifest; <link> tags + theme-color meta in shop_root.html.heex | 86 | 1.5h | done | | 88 | SVG dark mode injection for SVG-source favicons; icon background colour + short name customisation | 86 | 1h | done | | | No-JS support | | | | | 56 | Audit all key flows for no-JS (browse, cart, checkout, analytics) | — | 2h | planned | | 57 | Fix any broken flows for no-JS clients | 56 | TBD | planned | | | SEO | | | | | 58 | Page titles with separators across all pages | — | 1h | done | | 59 | Open Graph + Twitter Card meta tags (products, collections, home) | 58 | 2h | done | | 60 | Structured data / JSON-LD (Product, BreadcrumbList, Organization) | 59 | 2h | done | | 61 | Canonical URLs, robots.txt, sitemap.xml | 59 | 1.5h | done | | 62 | Meta descriptions (per-page, auto-generated fallbacks) | 58 | 1h | done | | | Profit-aware pricing & sales (plan) | | | | | 63 | Fix Printful cost sync (cross-reference catalog API for variant costs) | — | 45m | planned | | 64 | Cost snapshot on orders (unit_cost on order_items, total_cost/gross_profit on orders) | 63 | 1.5h | planned | | 65 | Exact Stripe fees (fetch from Balance Transaction API post-payment, not estimated) | 64 | 45m | planned | | 66 | Tax toggle + Stripe Tax (shop country, registered Y/N, enable automatic_tax on checkout, "inc. VAT" on shop) | 65 | 1.5h | planned | | 67 | Admin profit dashboard (per-product margins, per-order profit, overall P&L, VAT-aware) | 64, 65, 66 | 3h | planned | | 68 | Profit-aware price editor (show margin/profit when setting prices, warn on low/negative margin) | 67 | 2h | planned | | 69 | Sales & promotions (% or fixed discount, scoped to catalogue/category/products, scheduled start/end) | 68 | 3h | planned | | 70 | Margin guard on sales (prevent discounts that breach minimum profit threshold) | 69 | 1h | planned | | 71 | Announcement bar (dismissable shop banner for active sales, admin-configurable) | 69 | 1.5h | planned | | | URL redirects (plan) | | | | | 78 | redirects + broken_urls schemas, Redirects context, ETS-cached Plug in pipeline | — | 2h | done | | 79 | Auto-redirect on slug change — hook into upsert_product/2 to detect old/new slug diff | 78 | 45m | done | | 80 | Analytics-powered 404 monitoring — query analytics on 404, FTS5 auto-resolve, broken URLs queue | 78 | 2h | done | | 81 | Admin redirects UI — active redirects, broken URLs (sorted by prior traffic), manual create | 78 | 2h | done | | 82 | Dead link monitoring — validate stored links (internal via Phoenix.Router, external via async Oban HEAD), event-driven on product changes, admin dead links tab | page editor | 2.5h | deferred | | | Activity log & order timeline (plan) | | | | | 89 | activity_log schema + migration + ActivityLog context (log_event/3, list_for_order/1, list_recent/1, count_needing_attention/0, resolve/1) | — | 1.5h | planned | | 90 | Instrument existing event points — stripe webhook, OrderNotifier, OrderSubmissionWorker, fulfilment status, ProductSyncWorker | 89 | 1.5h | planned | | 91 | Order timeline component on /admin/orders/:id — chronological feed replacing scattered field cards | 89 | 1.5h | planned | | 92 | Global /admin/activity LiveView — all activity + "needs attention" tab, resolve action, count badge on admin nav | 89 | 2h | planned | | | Other features | | | | | 72 | Order status lookup — wire up existing stub on contact page (UI already exists, backend unbuilt) | — | 1.5h | done | | | Abandoned cart recovery (plan) | | | | | 75 | Handle checkout.session.expired webhook, store abandoned cart record | — | 1h | done | | 76 | Send single recovery email (plain text, no tracking, clear opt-out) | 75 | 1h | done | | 77 | Suppression list (unsubscribe), 30-day data pruning Oban job, Stripe footer notice | 76 | 1h | done | | | Legal page generator (plan) | | | | | 83 | LegalPages module — generate accurate privacy, delivery, and terms content from settings + provider + shipping data | — | 2.5h | done | | 84 | Wire LegalPages into Content LiveView — replace PreviewData calls, add tests | 83 | 45m | done | | 85 | Page editor integration — "Regenerate" button, auto-regenerate on settings change, customised vs auto label | 83, 19 | 1.5h | planned | | | Platform site | | | | | 73 | Platform/marketing site — brochure, pricing, sign-up | — | TBD | planned | | 74 | Separation of concerns: platform site vs AGPL open source core | 73 | TBD | planned |

See css-migration.md for full plan with architecture, visual regression testing strategy, and acceptance criteria per phase.

Usability fixes (16/18 done)

Issues from hands-on testing of the deployed prod site (Feb 2025). 16 of 18 complete. The remaining 2 are tracked as features in the task list above (#16 variant refinement, #18 shipping costs).

Roadmap

Tier 1 — MVP (can take real orders and fulfil them)

  1. Order management admin Complete (02cdc81). Admin UI at /admin/orders with status filter tabs, streamed order table, and detail view showing items, totals, and shipping address.
  2. Orders & fulfilment Complete. Submit paid orders to Printify, track fulfilment status (submitted → processing → shipped → delivered), webhook-driven status updates with polling fallback, admin UI with submit/refresh actions.
  3. Transactional emails Complete. Plain text order confirmation (on payment via Stripe webhook) and shipping notification (on dispatch via Printify webhook + polling fallback). OrderNotifier module, 10 tests.
  4. Default content pages Complete (5a43cfc). Generic ShopLive.Content LiveView handles about + 3 policy pages (delivery, privacy, terms) via live_action. Rich text with list blocks, footer links updated, theme editor preview. 10 tests (575 total).

Tier 2 — Production readiness (can deploy and run reliably)

  1. Hosting & deployment Complete. Alpine Docker image (131 MB), Fly.io config, release overlays, health check endpoint, hardcoded path fixes for releases. Observability: LiveDashboard in prod behind admin auth, ErrorTracker for exception capture, JSON structured logging, Oban/LiveView telemetry metrics, os_mon for CPU/disk/memory.
  2. Litestream / SQLite replication — Litestream for continuous SQLite backup to S3-compatible storage. Point-in-time recovery. Simple sidecar process, no code changes needed, works with vanilla SQLite. For the hosted platform (Tier 5), evaluate Turso (libSQL fork of SQLite) with embedded read replicas via ecto_libsql adapter — gives multi-node reads without a separate replication daemon, but adds a dependency on the libSQL fork.
  3. CI pipeline Complete. mix ci alias: compile --warning-as-errors, deps.unlock --unused, format --check-formatted, credo, dialyzer, test. Credo configured with sensible defaults. Dialyzer with ignore file for false positives (Stripe types, Mix tasks, ExUnit internals). 612 tests, 0 failures.
  4. PageSpeed in CI Complete. mix lighthouse task runs Google Lighthouse against the shop with configurable thresholds. Builds production assets (minified + digested + gzipped), waits for image variant cache, checks all 4 categories. Mobile: 99-100, Desktop: 97-100 across all pages. Unconditional gzip on Plug.Static.
  5. End-to-end & accessibility tests — Wallaby browser tests for critical flows (browse → add to cart → checkout → order confirmation) with A11yAudit assertions baked into each test. Covers the happy path, key error cases, and WCAG 2.1 AA compliance in one pass. Wallaby drives a headless Chrome, A11yAudit wraps axe-core for automated a11y checks within ExUnit. Focus management, ARIA labels, keyboard navigation, colour contrast — all verified as part of the e2e suite rather than a separate audit.

Tier 3 — Compliance & quality

  1. Privacy-respecting analytics In progress. Custom cookie-free analytics with pageviews, e-commerce funnel, device/browser/OS/country tracking. Period comparison deltas on stat cards, dashboard filtering. Next: CSV export, entry/exit pages.
  2. AGPL licensing & code hosting — Currently AGPL-3.0. Decide on GitHub vs Codeberg vs self-hosted Forgejo. Set up proper LICENSE file, contribution guidelines, and release process.
  3. Security (Paraxial.io) — Runtime application security monitoring for Elixir. Bot detection, rate limiting, vulnerability scanning. Evaluate whether it fits the self-hosted model.
  4. No-JS support — Audit and fix all key user flows for no-JS clients. Browse, search, cart, checkout, and analytics should all work without JavaScript. The analytics pipeline already supports no-JS (Plug records pageview, only superseded if JS connects). Cart and checkout use LiveView but should degrade gracefully.
  5. SEO — Best-in-breed SEO to match or exceed Shopify/Squarespace/WordPress plugins. Page titles with site name separator across all pages. Open Graph and Twitter Card meta tags for products (with images, prices), collections, and home. JSON-LD structured data (Product with offers/availability, BreadcrumbList, Organization). Canonical URLs, robots.txt, sitemap.xml (auto-generated from products/collections). Per-page meta descriptions with auto-generated fallbacks from product/collection data.
  6. Favicon & site icons — Upload one source image, get a complete best-practice icon setup. "Use logo as icon" toggle (on by default) with fallback to a separately uploaded icon image if the logo is wide/unsuitable at small sizes. Auto-generates PNG variants at 32×32 (browser fallback), 180×180 (iOS home screen), 192×192 and 512×512 (Android/PWA) via an Oban job using the existing image library pipeline. If the source is an SVG, the SVG itself is served as favicon.svg with a prefers-color-scheme: dark media query injected so the icon adapts to dark mode automatically. A FaviconController serves all variants from the DB at the expected root paths. Dynamic site.webmanifest served as JSON, pulling shop name, theme colour, and background colour from settings. Customisation: short name (shown under home screen icon), icon background colour (Android circle/squircle fill), theme colour (defaults to active theme primary). .ico fallback is a pre-baked static file — libvips can't output ICO, and it's only needed for very old browsers anyway. See plan.
  7. URL redirects & dead link monitoring — Preserve link equity and UX when product URLs change. Three layers: (1) automatic redirect creation when a product slug changes during sync — detected in upsert_product/2, no admin involvement needed; (2) ETS-cached Plug in the request pipeline that checks redirects before routing, 301s and halts; (3) analytics-powered 404 monitoring — when a 404 fires on a path with prior analytics history, it's a real broken URL that matters (not a bot scanner). FTS5 search attempts auto-resolution; low-confidence cases surface in an admin broken URLs queue sorted by prior traffic (highest impact first). Manual redirect creation for anything else. Redirect chains flattened on creation (A→B, B→C becomes A→C). Dead link monitoring catches broken outgoing links in your own content (social URLs, custom pages, product descriptions): internal links validated instantly via Phoenix.Router.route_info — no HTTP needed; external links checked asynchronously via Oban HEAD requests. Event-driven — product deletion/rename triggers a scan of stored links. Admin "Dead links" tab with one-click "Update link" for moved URLs. Weekly re-check cron for external links. See plan.

Tier 3.5 — Business tools

  1. Profit-aware pricing (inc. tax) — Complete cost visibility for shop owners. Fix Printful cost sync (catalog API cross-reference for variant costs). Snapshot unit_cost on order items at time of sale. Stripe does the heavy lifting: use Stripe Tax for automatic tax calculation at checkout (50+ countries, correct rates, one line of config — far better than maintaining our own rate tables); fetch exact Stripe fees from the Balance Transaction API after payment rather than estimating. Tax as a toggle: most small POD sellers aren't VAT registered (UK threshold: £90k), so by default there's no tax — prices are prices and all revenue is profit. When they flip the toggle, Stripe Tax activates at checkout and the shop shows "inc. VAT" (UK/EU/AU) or "+ tax" (US/CA) based on shop country. Profit calculations use actual post-payment data: exact Stripe fee + tax amount from Stripe session. Admin profit dashboard showing per-product margins, per-order P&L, and overall business health. Price editor shows live margin as you set prices. The goal: shop owners always know exactly what they're making, with no hidden costs. See plan.
  2. Sales & promotions — Transparent, honest alternative to discount codes (no empty "enter code" box at checkout — that's a dark pattern). Create time-limited sales scoped to the entire catalogue, specific categories, or individual products. Percentage or fixed-amount discounts. Scheduled start/end dates with automatic activation via Oban or date-bounded queries. Original price shown struck through with sale price. Margin guard: sales cannot breach a configurable minimum profit threshold — the system prevents shop owners from accidentally selling at a loss. Announcement bar (dismissable banner across the shop) to promote active sales. Later ties into newsletter for sale email blasts.
  3. Activity log & order timeline — A single activity_log table records every meaningful event: order created, confirmation email sent, submitted to provider, in production, shipped (with tracking), delivered, errors and retries. Two views on the same data: (1) order timeline on the order detail page — a chronological feed showing the complete lifecycle of that specific order, replacing the current scattered key/value cards. Errors are never overwritten — if submission failed and retried, both entries are visible. Email sends are recorded, so you can see if the confirmation actually reached the customer. (2) global activity feed at /admin/activity — reverse-chronological stream of all system events: orders, syncs, emails, abandoned carts. Two tabs: all activity and "needs attention" (unresolved errors/warnings). Count badge on the admin nav when attention is needed. 90-day pruning via Oban cron. See plan.
  4. Order status lookup — The UI stub already exists: a "Track your order" card on the contact page sidebar with an email input and Send button, but it's purely static HTML with no backend. Two possible approaches: (a) email-only lookup — customer enters their email, receives a signed magic-link showing all their orders (nicer UX, requires sending an email); (b) email + order number — inline lookup, no email sending needed, slightly more friction but simpler to build. Either way, no customer accounts required.

Tier 4 — Growth & content

  1. Page editor — Database-driven page builder where every page is a list of blocks. Generic renderer, portable + page-specific blocks, block data loaders, ETS cache, mobile-first admin editor. See plan.
  2. Legal page generator — Replace the hardcoded PreviewData placeholder content on /privacy, /delivery, and /terms with generated content that's factually accurate for each shop. Berrypod already knows which providers are connected (each with different lead times), which countries it ships to (from the shipping rates table), whether VAT is enabled, whether abandoned cart recovery is on, the shop country (drives jurisdiction language). Privacy policy cites correct statutes (UK GDPR, PECR) and includes conditional sections only for features that are actually enabled. Delivery policy quotes real shipping destinations from DB and correctly applies the Consumer Contracts Regulations Regulation 28(1)(b) exemption for POD (made-to-order goods are exempt from the 14-day right to cancel — most generic templates get this wrong). Terms cites governing law from shop country. Phase 2 (after page editor): "Regenerate from settings" button and auto-regeneration when settings change. See plan.
  3. Newsletter & email marketing — Email list collection (signup forms). Campaign sending for product launches, sales. Can be simple initially (collect emails, send via Swoosh) or integrate with a service. Ties into sales & promotions for sale announcement emails.
  4. Abandoned cart recovery — Privacy-respecting, GDPR-compliant recovery for customers who started Stripe Checkout but didn't complete payment. Triggered by checkout.session.expired webhook (Stripe fires this after 24h). Only possible for customers who entered their email on the Stripe Checkout page — anonymous cart sessions with no email are never contacted. Single plain-text email, no tracking pixels, one-click unsubscribe (suppression list honoured for all future emails). Abandoned cart records deleted after 30 days. Stripe Checkout footer text notifies customers at collection time. Lawful basis: UK PECR soft opt-in (email obtained during negotiation of a sale, single follow-up for similar products). EU: legitimate interests with documented LIA. See plan.
  5. Product page improvements — Pre-checkout variant validation (verify Printify availability). Cost change monitoring/alerts. Better image gallery (zoom, multiple angles). Product reviews system to replace the hardcoded PreviewData.reviews() on the PDP template.

Tier 5 — Platform vision

  1. Platform/marketing site — Berrypod.com as the public face: brochure pages (features, pricing, comparison), sign-up/subscribe flow, demo store showcase. Needs clear separation between the platform site (commercial, hosted by us) and the open source AGPL core (self-hostable, community-driven). The platform site should position Berrypod as fast, capable, and valuable — competing on speed, privacy, and simplicity against Shopify/Squarespace. Consider: separate Phoenix app or separate layout/router scope within the same app? The AGPL core ships without any platform branding — just the store engine.
  2. Hosted platform infrastructure — Multi-tenancy with per-tenant databases. OAuth connect flows for providers and payments: register as a Printify/Printful OAuth app so hosted users get a one-click "Connect with Printify" button on the setup page; use Stripe Connect so merchants authorise via OAuth redirect instead of pasting API keys. The setup UI already supports this — check for OAuth credentials and show the connect button when available, fall back to the API key form on self-hosted installs where no OAuth app is configured. Provider metadata (connect_mode: :oauth | :api_key) drives which form renders.
  3. Migration & export — Let shop owners export their data (products, orders, customers, theme settings). Import from other platforms (Shopify, WooCommerce). Portable data as a selling point for the self-hosted story.
  4. Internationalisation (i18n) — Multi-language support via Gettext (already in Phoenix). Currency formatting. RTL layout support. Per-shop locale configuration. Note: ex_money/ex_cldr are currently used only for Cart.format_price/1 (a single GBP formatting call) but add ~13 MB to the release (ex_cldr 9.5 MB, digital_token 3.7 MB, ex_cldr_numbers, ex_cldr_currencies). Consider replacing with a simple format_price/2 function that handles GBP/EUR/USD directly — all three use 2 decimal places and are trivial to format. Re-add ex_money later if proper locale-aware number formatting is needed (e.g., German 12.345,67 €).

Feature Areas

Theme System

Status: Complete

  • 8 theme presets (Gallery, Studio, Boutique, etc.)
  • Three-layer CSS architecture (primitives, attributes, semantic)
  • Instant theme switching via CSS custom property injection
  • Logo/header image uploads with SVG recoloring
  • Self-hosted fonts (10 typefaces, GDPR compliant)
  • ETS-cached CSS generation

Image Optimization

Status: Complete

  • Oban background job processing
  • Responsive <picture> element (AVIF/WebP/JPEG)
  • Only generates sizes <= source dimensions
  • Disk cache for variants (regenerable from DB)
  • mix optimize_images task for mockups
  • On-demand JPEG fallback generation
  • Product image download pipeline (downloads Printify CDN images, processes through Media pipeline)
    • ImageDownloadWorker downloads and links images to ProductImage
    • PreviewData uses local images for responsive <picture> elements
    • Startup recovery re-enqueues pending downloads
    • mix berrypod.download_images backfill task

See: docs/plans/image-optimization.md for implementation details

Products & Provider Integration

Status: Complete

  • Products context with schemas (c5c06d9)
  • Provider abstraction layer
  • Printify client integration
  • Product/variant/image schemas
  • Admin Provider Setup UI (/admin/providers) - connect, test, sync
  • ProductSyncWorker with pagination, parallel processing, error recovery
  • Slug-based fallback matching for changed provider IDs
  • Printify webhook endpoint with HMAC verification (a9c15ea)
    • Note: Printify only supports product:deleted and product:publish:* events (no product:updated)
  • Product image download pipeline (1b49b47)
    • Downloads Printify CDN images via ImageDownloadWorker
    • Processes through Media pipeline (WebP conversion, AVIF/WebP variants)
    • Startup recovery and mix berrypod.download_images backfill
  • Variant selector component (880e7a2)
    • Color swatches with hex colors, size buttons
    • Fixed Printify options parsing (Color/Size swap bug)
    • Filters to only published variants (not full catalog)
    • Price updates on variant change
    • Startup recovery for stale sync status
  • Per-colour product images (0fe48ba)
    • color column on product_images, tagged during sync (both providers)
    • PDP gallery filters by selected colour (hero gets all, others front+back)
    • Printify options filtered to enabled variants only (not full blueprint)
    • Hero/default colour ordered first in swatch list
    • MockupEnricher generates per-colour mockups for Printful
    • Printful catalog API fetched for hex colour codes

Future Enhancements (post-MVP)

  • Print provider insights — fetch provider name/location via get_print_providers/1 during sync, store in provider_data. Show "Ships from UK/US" on product pages. Admin dashboard showing which providers are used, their locations, and shipping cost analysis to help optimise product selection for domestic fulfilment and combined postage savings
  • Pre-checkout variant validation (verify availability before order)
  • Cost change monitoring/alerts (warn if Printify cost increased)
  • OAuth platform integration (appear in Printify's "Publish to" UI)

Technical Debt

  • Mox provider mocking for fulfilment tests (Provider.for_type configurable via app env)

See: docs/plans/products-context.md for implementation details See: docs/plans/printify-integration-research.md for API research & risk analysis

Cart & Checkout

Status: Complete

  • Cart drawer component with slide-over panel (1bc08bf)
  • Cart page with item list and order summary (1bc08bf)
  • Shared CartHook for cross-page cart events (1bc08bf)
  • CartPersist JS hook for localStorage backup
  • Add-to-cart with flash status feedback
  • Cart item links to product pages
  • Session-based cart with real variants (1bc08bf)
    • Cart stores {variant_id, qty} tuples in session
    • Hydrates with real product data via Products context
    • Cross-tab sync via PubSub, session persistence via CartController API
  • Stripe Checkout integration (ff1bc48, stripity_stripe ~> 3.2)
    • Stripe-hosted Checkout with redirect flow
    • Webhook handler for checkout.session.completed/expired
    • Signature verification via CacheRawBody + construct_event
    • Shipping address collection during checkout
  • Order/OrderItem schemas and context (ff1bc48)
    • Order number format: SS-YYMMDD-XXXX
    • Payment status tracking (pending → paid/failed)
    • Price snapshots in OrderItem (protects against changes)
    • Idempotent webhook processing
  • Checkout success page with real-time PubSub updates
  • Cart clearing after successful payment

See: ROADMAP.md for design notes

Admin Settings & Stripe Setup

Status: Complete

  • Encrypted settings infrastructure (eede9bb)
    • encrypted_value column on settings table
    • put_secret/2, get_secret/2, has_secret?/1, secret_hint/1
    • AES-GCM encryption via SECRET_KEY_BASE
    • Secrets loaded into Application env on startup via Secrets.load_all/0
  • Guided Stripe setup flow (eede9bb)
    • Stripe.Setup module: connect, disconnect, verify, auto-create webhook
    • Three-state admin UI: not configured, connected (production), connected (dev/localhost)
    • Auto-creates Stripe webhook endpoint on production hosts
    • Dev mode shows Stripe CLI instructions for localhost
    • Manual signing secret input for dev/advanced use
    • API key verification via Stripe.Balance.retrieve/2
  • Admin credentials page at /admin/settings (eede9bb)
    • Single Secret key input with "Connect Stripe" button
    • Masked key hints (e.g. sk_test_•••789)
    • Disconnect button clears keys from DB and Application env
    • CSSCache test startup crash fixed (handle_continue pattern)

Orders & Fulfilment

Status: Complete

  • Orders context with schemas (ff1bc48)
  • Stripe Checkout integration with webhook handling
  • Order management admin UI (02cdc81, Roadmap #1)
    • Order list with status filter tabs (all/paid/pending/failed/refunded) and counts
    • Streamed table with row click navigation to detail
    • Order detail with info card, shipping address, line items table with totals
    • Nav link in admin bar, 15 tests
  • Printify order submission and fulfilment tracking (Roadmap #2)
    • 9 fulfilment fields on orders (status, provider_order_id, tracking, timestamps)
    • submit_to_provider/1 with idempotent guard, error handling, address mapping
    • refresh_fulfilment_status/1 polls provider for status updates
    • OrderSubmissionWorker (Oban, :checkout queue, max_attempts: 3)
    • FulfilmentStatusWorker (Oban Cron, every 30 mins, :sync queue)
    • Printify order webhook handlers (sent-to-production, shipment:created, shipment:delivered)
    • Stripe webhook auto-enqueues submission after payment confirmed
    • Admin UI: fulfilment badge column, fulfilment card with tracking, submit/refresh buttons
    • Mox provider mocking for test isolation, 33 new tests (555 total)
  • Transactional emails (Roadmap #3)
    • OrderNotifier module with plain text emails via Swoosh
    • Order confirmation sent from Stripe webhook after payment + address/email updates
    • Shipping notification sent from Printify shipment webhook + polling fallback
    • Guards for missing customer_email, graceful tracking info handling
    • 10 tests (565 total)

See: docs/plans/products-context.md for schema design

DRY Refactor

Status: Complete

All 8 items from the plan done. Key wins: ThemeHook eliminated mount duplication, shop_layout saved ~195 lines, shop_components split into 5 focused modules (largest file dropped from 4,487 to ~1,600 lines), Settings repo lookups consolidated via fetch_setting/1, secrets loading made scalable via registry pattern.

See: docs/plans/dry-refactor.md for full analysis and plan

Shop Page Integration Tests

Status: Complete

All shop pages now have LiveView integration tests (612 total):

  • Product detail page (15 tests) — rendering, breadcrumbs, variant selection, price updates, add-to-cart, related products, fallback for unknown IDs
  • Cart page (10 tests) — empty state, item display with DB fixtures, order summary, increment/decrement, remove, checkout button
  • Home page (12 tests) — hero section, category nav, featured products, image+text section, navigation links
  • Collection page (16 tests, pre-existing) — category filtering, sorting, URL params
  • Content pages (10 tests, pre-existing) — about, delivery, privacy, terms

CI Pipeline

Status: Complete

  • mix ci alias: compile --warning-as-errors → deps.unlock --unused → format --check-formatted → credo → dialyzer → test
  • mix precommit alias: compile --warning-as-errors → deps.unlock --unused → format → test
  • Credo with tuned config (disabled AliasUsage, ModuleDoc, PredicateFunctionNames; relaxed line length, nesting, complexity)
  • Dialyzer with ignore file for known false positives (Stripe library types, Mix.Task dev-only modules, ExUnit internals)
  • All credo issues resolved (map_join, filter consolidation, nesting extraction)
  • 612 tests, 0 failures

Hosting & Deployment

Status: Complete

  • Alpine Docker image (131 MB), Fly.io config, release overlays
  • Health check endpoint at /health
  • Hardcoded path fixes for releases (Application.app_dir/2)
  • LiveDashboard at /admin/dashboard behind admin auth (all envs)
  • ErrorTracker at /admin/errors — auto-captures Phoenix/LiveView/Oban exceptions, stored in SQLite, pruner for resolved errors
  • JSON structured logging in prod via logger_json (machine-parseable)
  • Oban job duration/count and LiveView mount/event telemetry metrics
  • os_mon for CPU, disk, and OS memory in LiveDashboard

Status: Complete

  • Denormalized product fields (cheapest_price, compare_at_price, in_stock, on_sale) recomputed from variants after sync
  • Product display helpers (primary_image, hover_image, option_types) and ProductImage helpers (display_url, direct_url, source_width)
  • Products context storefront queries (list_visible_products, get_visible_product, list_categories) with DB-level sort/filter
  • Renamed .name → .title across all shop components and templates
  • PreviewData updated to struct-compatible format (removed product_to_map indirection)
  • FTS5 full-text search index with BM25 ranking (title 10x, category 5x, variants 3x, description 1x)
  • SearchHook on_mount for all shop pages (follows CartHook pattern)
  • Search modal with live results (thumbnails, titles, categories, prices, click-to-navigate)
  • Index auto-rebuilds after each provider sync
  • 18 search tests, 744 total

Follow-ups (all complete):

  • Wire shop LiveViews to direct DB queries (PreviewData removed from all shop pages, cart, error page)
  • Search modal keyboard nav (arrow keys, Enter, Escape, Cmd+K shortcut)
  • Full ARIA combobox pattern (role=combobox, listbox, option, aria-selected)
  • SearchModal JS hook, <.link navigate> for client-side nav, 150ms debounce
  • search.ex: transaction safety on reindex, public remove_product/1
  • LIKE substring fallback when FTS5 prefix returns nothing
  • Admin bar replaced with header icon (gear/cog, admin-only, no public link)
  • Search modal race condition fix (close-on-keypress, open/close custom events)
  • HTTP 304 support for cached images
  • 10 new integration tests, 757 total

Shipping

Status: Complete

  • ShippingRate schema + migration (per blueprint/provider/country)
  • Shipping context: upsert, lookup with REST_OF_THE_WORLD fallback, cart calculation
  • Provider behaviour: optional fetch_shipping_rates/2 callback
  • Printify implementation: fetch rates per blueprint/provider, normalize country arrays
  • ProductSyncWorker integration: shipping rates synced alongside products
  • ScheduledSyncWorker (Oban cron, every 6 hours) for periodic re-sync
  • Live exchange rate conversion at sync time (frankfurter.app API, ECB data)
  • 5% configurable buffer on exchange rates to absorb fluctuations
  • Country detection from Accept-Language header + cookie persistence
  • Cart page shipping estimate with country selector (all countries with rates)
  • Stripe Checkout shipping_options (UK domestic + international)
  • Order shipping_cost field, extracted from Stripe on payment
  • 780 tests total

See: plan for implementation details

SQLite Production Tuning

Status: Complete

  • Concurrency tests proving WAL read scaling, IMMEDIATE vs DEFERRED transaction behaviour
  • BenchRepo for isolated testing against temp DB files (no Sandbox dependency)
  • mix bench.sqlite task with --prod, --scale, --pool-size, --busy-timeout options
  • PRAGMA tuning across dev/test/prod: default_transaction_mode: :immediate, journal_size_limit: 64MB, custom_pragmas: [mmap_size: 128MB]
  • Benchmarks confirmed: IMMEDIATE mode eliminates transaction upgrade BUSY errors (0% vs 73-80% failure rate under contention), prod mode 5-12x faster than dev, 300 concurrent mixed requests with zero errors
  • 1041 tests total (5 excluded: 3 benchmarks + 2 correctness)

Analytics

Status: In progress (v2)

  • Privacy-first, cookie-free analytics (2bd2e61)
  • E-commerce funnel events: product_view, add_to_cart, checkout_start, purchase (f91b47f)
  • Browser, OS, screen size collection (f91b47f)
  • HTML/CSS bar chart with hourly today view and readable labels (08fcd60)
  • Period comparison deltas on stat cards (6eda1de)
  • 2-year demo seed data with growth curve (6eda1de)
  • Dashboard filtering (click referrer/country/device to filter all panels) (7ceee9c)
  • CSV export (ZIP with 12 CSVs, period + filter aware)
  • Entry/exit pages panel

See: docs/plans/analytics-v2.md for v2 plan

Page Editor

Status: In progress — Stage 2 of 9 complete, 1284 tests

Database-driven page builder. Every page is a flat list of blocks stored as JSON — add, remove, reorder, and edit blocks on any page. One generic renderer for all pages (no page-specific render functions). Portable blocks (hero, featured_products, image_text, etc.) work on any page. Page-specific blocks (product_hero, cart_items, etc.) are restricted to their native page. Block data loaders dynamically load data based on which blocks are on the page. ETS-cached page definitions. Mobile-first admin editor with live preview, undo/redo, accessible reordering (no drag-and-drop), inline settings forms, and "reset to defaults". CSS-driven page layout (not renderer-driven).

Stages:

  1. Foundation — data model, cache, block registry (35f96e4)
  2. Page renderer — generic renderer tested in isolation (32f54c7)
  3. Next → Wire simple pages — Home, Content (x4), Contact, Error
  4. Wire shop pages — Collection, PDP, Cart, Search
  5. Wire order pages + theme preview — CheckoutSuccess, Orders, OrderDetail, theme editor
  6. Admin editor — page list + block management (reorder, add, remove, duplicate, save)
  7. Admin editor — inline block settings editing
  8. Live preview — split layout with real-time preview
  9. Undo/redo + polish — history stacks, keyboard shortcuts, animations

Key files created:

  • lib/berrypod/pages.ex — context (CRUD + cache + load_block_data)
  • lib/berrypod/pages/ — Page schema, BlockTypes (26 types), Defaults (14 pages), PageCache (ETS)
  • lib/berrypod_web/page_renderer.ex — generic renderer dispatching blocks to existing shop components
  • test/berrypod/pages_test.exs — 34 tests
  • test/berrypod_web/page_renderer_test.exs — 18 tests

See: docs/plans/page-builder.md for full plan


Completed Work Reference

Feature Commit Notes
Analytics comparison mode 6eda1de Period deltas on stat cards, 2-year seed data, zero-baseline handling, 1041 tests
Analytics v1 + chart improvements 2bd2e61..08fcd60 Cookie-free analytics, e-commerce funnel, HTML/CSS bar chart, hourly today view
Printify + Printful client tests b0aed4c, a45e85e Req.Test stubs for both HTTP clients, provider integration tests, mockup enricher tests, 972 tests
SQLite production tuning 162bf4c, 19d8c7d Concurrency tests, mix bench.sqlite task, IMMEDIATE transactions, mmap 128MB, journal_size_limit 64MB, 898 tests
Per-colour images + gallery filtering 0fe48ba colour column on product_images, per-colour mockup enrichment, PDP gallery filtering, Printify option filtering, hero colour ordering, 821 tests
Printful catalog colours 4e19d4c Fetch hex codes from catalog product API during sync, cached per catalog_product_id
Printful integration 3c788bf..24d61f7 HTTP client, provider (sync + orders), shipping rates, webhooks, mockup enrichment, admin UI
Shipping costs at checkout Rates, exchange rates, country detection, Stripe shipping options, 780 tests
Search + admin polish 44933ac Search race condition fix, image 304s, LIKE fallback, admin header icon, 757 tests
DB wiring + search UX 57c3ba0 Shop LiveViews use DB queries, search keyboard nav, ARIA, 755 tests
FTS5 search + products refactor 037cd16 FTS5 index, BM25 ranking, search modal, denormalized fields, Product struct usage, 744 tests
PageSpeed CI 516d0d0 mix lighthouse task, prod asset build, gzip, 99-100 mobile scores
Observability eaa4bbb LiveDashboard in prod, ErrorTracker, JSON logging, Oban/LV metrics, os_mon
Hosting & deployment Alpine Docker, Fly.io, health check, release path fixes
PDP image gallery 8445e9e Scroll-snap carousel, dots, thumbnails, arrows, lightbox, 9 tests
CI pipeline mix ci/precommit aliases, credo, dialyzer, 621 tests
Default content pages 5a43cfc Generic Content LiveView, delivery/privacy/terms pages, 10 tests
Transactional emails Plain text order confirmation + shipping notification, 10 tests
Printify order submission & fulfilment Submit, track, webhooks, polling, admin UI, 33 tests
Order management admin 02cdc81 List/detail views, status filters, 15 tests
Encrypted settings & Stripe setup eede9bb Guided setup flow, encrypted secrets, admin credentials page
Stripe checkout & orders ff1bc48 Stripe Checkout, webhooks, order persistence
Demo content & link fixes cff2170 Broken links, placeholder text, responsive about image
Cart UI infrastructure 1bc08bf Cart drawer, cart page, CartHook, CartPersist
Variant selector 880e7a2 Color swatches, size buttons, price updates
Product image download 1b49b47 PageSpeed 100% with local images
Wire shop to real data c818d03 PreviewData uses Products context
Printify webhooks a9c15ea Deletion + publish events
Products context Phase 1 c5c06d9 Schemas, provider abstraction
Admin provider setup UI 5b736b9 Connect, test, sync with pagination
Oban Lifeline plugin c1e1988 Rescue orphaned jobs
Image optimization Multiple Full pipeline complete
Self-hosted fonts - 10 typefaces, 728KB
Mobile bottom nav - Fixed tab bar
PageSpeed 100% - All optimizations
Theme presets (8) - Gallery, Studio, etc.