tidy docs: condense progress, trim readme, mark plan statuses

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jamey 2026-03-01 17:15:18 +00:00
parent 580a7203c9
commit edef628214
15 changed files with 263 additions and 810 deletions

View File

@ -23,15 +23,24 @@ mix precommit # REQUIRED before committing: compile --warning-as-errors
- **Settings** - Theme configuration persistence as JSON - **Settings** - Theme configuration persistence as JSON
- **Theme** - CSS generation, ETS caching, 8 presets (Gallery, Studio, Boutique, etc.) - **Theme** - CSS generation, ETS caching, 8 presets (Gallery, Studio, Boutique, etc.)
- **Products** - Product/variant data synced from Printify - **Products** - Product/variant data synced from Printify/Printful
- **Media/Images** - Image uploads and optimization pipeline (Oban jobs) - **Media/Images** - Image uploads, optimization pipeline (Oban jobs), media library
- **Providers** - Abstraction layer for POD providers (Printify integration) - **Providers** - Abstraction layer for POD providers (Printify + Printful)
- **Pages** - Database-driven page builder (blocks, cache, defaults, 26 block types)
- **Orders** - Order lifecycle, fulfilment tracking, provider submission
- **Shipping** - Shipping rates, country detection, exchange rates
- **Analytics** - Privacy-first pageview tracking, e-commerce funnel
- **ActivityLog** - System event logging, order timeline
- **Redirects** - URL redirects, 404 monitoring, dead link scanning
- **Newsletter** - Email list collection, campaign sending
- **Search** - FTS5 full-text search index
### Web Layer (lib/berrypod_web/) ### Web Layer (lib/berrypod_web/)
- **live/** - LiveViews for shop pages and theme editor - **live/admin/** - Admin LiveViews (orders, pages, media, analytics, etc.)
- **components/page_templates/** - Shared templates between preview and live shop - **live/shop/** - Shop LiveViews (home, collection, product, cart, custom pages)
- **components/shop_components.ex** - Reusable shop UI components - **components/shop_components/** - Reusable shop UI components (split into focused modules)
- **page_renderer.ex** - Generic block-to-component dispatch for all pages
### Three-Layer CSS Architecture ### Three-Layer CSS Architecture
@ -48,7 +57,18 @@ Theme switching is instant via CSS custom property injection (no reload).
| `/` | Shop home | | `/` | Shop home |
| `/collections/:slug` | Product collection (filtering) | | `/collections/:slug` | Product collection (filtering) |
| `/products/:id` | Product detail | | `/products/:id` | Product detail |
| `/admin/theme` | Theme editor (auth required) | | `/cart` | Shopping cart |
| `/contact` | Contact + order lookup |
| `/:slug` | Custom CMS pages (catch-all, must be last scope) |
| `/setup` | First-run onboarding |
| `/admin` | Admin dashboard |
| `/admin/orders` | Order management |
| `/admin/pages` | Page editor |
| `/admin/media` | Media library |
| `/admin/analytics` | Analytics dashboard |
| `/admin/activity` | Activity log |
| `/admin/theme` | Theme editor |
| `/admin/settings` | Shop settings |
## Elixir Guidelines ## Elixir Guidelines
@ -128,7 +148,7 @@ Template pattern:
</div> </div>
``` ```
Empty state with Tailwind: Empty state with `hidden only:block`:
```heex ```heex
<div id="products" phx-update="stream"> <div id="products" phx-update="stream">
<div class="hidden only:block">No products yet</div> <div class="hidden only:block">No products yet</div>
@ -159,10 +179,11 @@ socket |> assign(form: to_form(changeset))
## JS/CSS Guidelines ## JS/CSS Guidelines
- Tailwind v4 uses `@import "tailwindcss"` syntax (no tailwind.config.js) - Project is fully Tailwind-free — hand-written CSS with `@layer`, native nesting, `oklch()`
- **Never** use `@apply` in CSS - **Never** use `@apply` in CSS
- **Never** write inline `<script>` tags - use hooks in assets/js/ - **Never** write inline `<script>` tags - use hooks in assets/js/
- All vendor deps must be imported into app.js/app.css - All vendor deps must be imported into app.js/app.css
- Prefer flat selectors or max single-level nesting (esbuild passes CSS through as-is)
## LiveView Testing ## LiveView Testing

View File

@ -2,576 +2,91 @@
> Single source of truth for project status and task tracking. > Single source of truth for project status and task tracking.
## Current Status ## Current status
**Working:** Tier 1 MVP complete. Tier 2 production readiness complete (except Litestream and e2e tests). Tier 3 compliance and quality complete. Tier 3.5 business tools mostly complete (activity log, order lookup, abandoned cart all done). Tier 4 growth and content mostly complete (page editor, legal pages, media library, custom CMS pages all done). Newsletter and product page improvements remain.
- 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
- 1679 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)
- Activity log with real-time global feed, order timeline, contextual retry buttons, nav badge
**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. **What's working:**
- Complete shop: home, collections, products, cart, checkout, about, contact, custom CMS pages, legal pages
- Theme editor with 8 presets, instant switching, full customisation
- Image optimisation pipeline (AVIF/WebP/JPEG responsive variants)
- Printify + Printful integration (sync, orders, shipping, webhooks, mockup enrichment)
- Stripe Checkout with order persistence, webhook handling, shipping costs
- Session-based cart (cross-tab sync, drawer + page, quantity controls)
- FTS5 full-text search with live modal, keyboard nav, ARIA
- Privacy-first analytics with comparison mode, filtering, CSV export
- Database-driven page builder with 26 block types, undo/redo, live editing
- Custom CMS pages with data-driven navigation, page templates, SEO
- Media library with alt text, usage tracking, orphan management
- Activity log with real-time global feed, order timeline, contextual retry
- URL redirects with auto-redirect on slug change, dead link monitoring
- Legal page generator with auto-regeneration from settings
- Abandoned cart recovery (GDPR-compliant, single email)
- Favicon generation from source image (PNG variants, SVG dark mode, webmanifest)
- Complete SEO (OG/Twitter cards, JSON-LD, sitemap, canonical URLs, meta descriptions)
- Email settings admin with 10 adapter options and test email
- No-JS support across all key flows
- Fully Tailwind-free CSS (9.8 KB gzipped shop, 17.8 KB gzipped admin)
- CI pipeline (compile warnings, format, credo, dialyzer, tests)
- Deployed on Fly.io with observability (LiveDashboard, ErrorTracker, structured logging)
- 1679+ tests passing, 99-100 PageSpeed mobile
## Task list ## Next up
Ordered by dependency level — admin shell chain first (unblocks most downstream work). ### Profit-aware pricing & sales ([plan](docs/plans/profit-aware-pricing.md))
Plans: [admin-redesign.md](docs/plans/admin-redesign.md) | [admin-font-loading.md](docs/plans/admin-font-loading.md) | [setup-wizard.md](docs/plans/setup-wizard.md) | [setup-and-launch.md](docs/plans/setup-and-launch.md) | [setup-auto-confirm.md](docs/plans/setup-auto-confirm.md) | [email-settings.md](docs/plans/email-settings.md) | [search.md](docs/plans/search.md) | [products-refactor.md](/home/jamey/.claude/plans/snug-roaming-zebra.md) | [shipping-sync.md](docs/plans/shipping-sync.md) | [printful-integration.md](docs/plans/printful-integration.md) | [provider-strategy.md](docs/plans/provider-strategy.md) | [css-migration.md](docs/plans/css-migration.md) | [analytics-v2.md](docs/plans/analytics-v2.md) | [page-builder.md](docs/plans/page-builder.md) | [media-library.md](docs/plans/media-library.md) | [custom-pages.md](docs/plans/custom-pages.md) | [admin-ux-polish.md](docs/plans/admin-ux-polish.md)
| # | Task | Depends on | Est | Status | | # | 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](docs/plans/setup-and-launch.md)) | | | |
| ~~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](docs/plans/setup-auto-confirm.md), [email settings plan](docs/plans/email-settings.md)) | | | |
| ~~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](docs/plans/admin-font-loading.md))~~ | — | 1h | done |
| | **Analytics v2** ([plan](docs/plans/analytics-v2.md)) | | | |
| ~~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](docs/plans/favicon.md)) | | | |
| ~~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 | done |
| ~~57~~ | ~~Fix any broken flows for no-JS clients~~ | 56 | TBD | done |
| | **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](docs/plans/profit-aware-pricing.md)) | | | |
| 63 | Fix Printful cost sync (cross-reference catalog API for variant costs) | — | 45m | planned | | 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 | | 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 | | 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 | | 66 | Tax toggle + Stripe Tax (shop country, registered Y/N, `automatic_tax` on checkout) | 65 | 1.5h | planned |
| 67 | Admin profit dashboard (per-product margins, per-order profit, overall P&L, VAT-aware) | 64, 65, 66 | 3h | planned | | 67 | Admin profit dashboard (per-product margins, per-order profit, overall P&L) | 64, 65, 66 | 3h | planned |
| 68 | Profit-aware price editor (show margin/profit when setting prices, warn on low/negative margin) | 67 | 2h | planned | | 68 | Profit-aware price editor (show margin/profit when setting prices, warn on low margin) | 67 | 2h | planned |
| 69 | Sales & promotions (% or fixed discount, scoped to catalogue/category/products, scheduled start/end) | 68 | 3h | planned | | 69 | Sales & promotions (% or fixed discount, scoped to catalogue/category/products, scheduled) | 68 | 3h | planned |
| 70 | Margin guard on sales (prevent discounts that breach minimum profit threshold) | 69 | 1h | 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 | | 71 | Announcement bar (dismissable shop banner for active sales) | 69 | 1.5h | planned |
| | **URL redirects** ([plan](docs/plans/url-redirects.md)) | | | |
| ~~78~~ | ~~`redirects` + `broken_urls` schemas, `Redirects` context, ETS-cached Plug in pipeline~~ | — | 2h | done | ### Platform site
| ~~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 | | # | Task | Depends on | Est | Status |
| ~~81~~ | ~~Admin redirects UI — active redirects, broken URLs (sorted by prior traffic), manual create~~ | 78 | 2h | done | |---|------|------------|-----|--------|
| ~~82~~ | ~~Dead link monitoring — scan page blocks + nav items for broken outgoing links (internal via DB lookup, external via HTTP HEAD), daily Oban cron, event-driven on page save, admin dead links tab with re-check/ignore/source links~~ | page editor | 2.5h | done |
| | **Activity log & order timeline** ([plan](docs/plans/activity-log.md)) | | | |
| ~~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 | done |
| ~~90~~ | ~~Instrument existing event points — stripe webhook, OrderNotifier, OrderSubmissionWorker, fulfilment status, ProductSyncWorker~~ | 89 | 1.5h | done |
| ~~91~~ | ~~Order timeline component on `/admin/orders/:id` — chronological feed replacing scattered field cards~~ | 89 | 1.5h | done |
| ~~92~~ | ~~Global `/admin/activity` LiveView — all activity + "needs attention" tab, resolve action, count badge on admin nav + contextual retry buttons~~ | 89 | 2h | done |
| | **Admin & page editor UX polish** ([plan](docs/plans/admin-ux-polish.md)) | | | |
| ~~103~~ | ~~Unsaved changes warning — `beforeunload` + LiveView nav guard on page editor~~ | — | 30m | done |
| ~~104~~ | ~~Block descriptions in picker — add subtitle text to each block type~~ | — | 45m | done |
| ~~105~~ | ~~Sidebar nav grouping — section headers (Shop/Content/Settings)~~ | — | 45m | done |
| ~~106~~ | ~~Nav editor input labels — visible labels above each input pair~~ | — | 30m | done |
| ~~107~~ | ~~Custom page settings inline — collapsible panel in editor~~ | — | 1h | done |
| ~~108~~ | ~~Preview with real data — load actual products/categories~~ | — | 45m | done |
| ~~109~~ | ~~Block content preview in list — one-line summary below block name~~ | — | 45m | done |
| ~~110~~ | ~~"Providers" label clarity — renamed to "Print providers"~~ | — | 5m | done |
| ~~111~~ | ~~Newsletter block backend — marked decorative with configurable settings~~ | — | 30m | done |
| ~~112~~ | ~~Block preview thumbnails in picker — SVG wireframes per block type~~ | — | 2h | done |
| | **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](docs/plans/abandoned-cart.md)) | | | |
| ~~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](docs/plans/legal-page-generator.md)) | | | |
| ~~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 | done |
| | **Media library** ([plan](docs/plans/media-library.md)) | | | |
| ~~93~~ | ~~Schema + context — alt/caption/tags on images, media type, find_usages, orphan detection~~ | — | 1.5h | done |
| ~~94~~ | ~~Admin media library UI — grid, filters, upload, detail panel, orphan management~~ | 93 | 2.5h | done |
| ~~95~~ | ~~Image picker for page builder — `:image` field type, image_id resolution in renderer~~ | 94 | 2h | done |
| ~~96~~ | ~~Polish — theme editor alt text, full modal picker, orphan cleanup on ref removal~~ | 95 | 1h | done |
| | **Custom CMS pages** ([plan](docs/plans/custom-pages.md)) | | | |
| ~~97~~ | ~~Stage 1: data model + context — schema fields, split changeset, CRUD functions, cache~~ | — | 1.5h | done |
| ~~98~~ | ~~Stage 2: routing + LiveView — `Shop.CustomPage`, catch-all route, 404 handling~~ | 97 | 1h | done |
| ~~99~~ | ~~Stage 3: admin CRUD — create/edit/delete pages, page settings, admin index~~ | 98 | 2.5h | done |
| ~~100~~ | ~~Stage 4: navigation management — data-driven nav, settings storage, admin editor~~ | 99 | 3h | done |
| ~~101~~ | ~~Stage 5: SEO + redirects — sitemap, auto-redirect on slug change, draft/published~~ | 100 | 1h | done |
| ~~102~~ | ~~Stage 6: polish — page templates, new block types, bulk ops~~ | 101 | 3-4h | done |
| | **Platform site** | | | |
| 73 | Platform/marketing site — brochure, pricing, sign-up | — | TBD | planned | | 73 | Platform/marketing site — brochure, pricing, sign-up | — | TBD | planned |
| 74 | Separation of concerns: platform site vs AGPL open source core | 73 | TBD | planned | | 74 | Separation of concerns: platform site vs AGPL open source core | 73 | TBD | planned |
See [css-migration.md](docs/plans/css-migration.md) for full plan with architecture, visual regression testing strategy, and acceptance criteria per phase. ## Future enhancements
## Usability fixes (16/18 done) Tracked in [ROADMAP.md](ROADMAP.md) — includes Litestream backup, e2e/a11y tests, AGPL licensing, security, newsletter, product reviews, hosted platform infrastructure, i18n, data export/import.
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). ## Plan files
## Roadmap All plans in [docs/plans/](docs/plans/). Completed plans are kept as architecture references.
### Tier 1 — MVP (can take real orders and fulfil them) | Plan | Status |
|------|--------|
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. | [admin-redesign.md](docs/plans/admin-redesign.md) | Complete |
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. | [admin-font-loading.md](docs/plans/admin-font-loading.md) | Complete |
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. | [admin-ux-polish.md](docs/plans/admin-ux-polish.md) | Complete |
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). | [setup-wizard.md](docs/plans/setup-wizard.md) | Complete |
| [setup-and-launch.md](docs/plans/setup-and-launch.md) | Complete |
### Tier 2 — Production readiness (can deploy and run reliably) | [setup-auto-confirm.md](docs/plans/setup-auto-confirm.md) | Complete |
| [email-settings.md](docs/plans/email-settings.md) | Complete |
5. ~~**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. | [search.md](docs/plans/search.md) | Complete |
6. **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](https://turso.tech/) (libSQL fork of SQLite) with embedded read replicas via [ecto_libsql](https://github.com/ocean/ecto_libsql) adapter — gives multi-node reads without a separate replication daemon, but adds a dependency on the libSQL fork. | [shipping-sync.md](docs/plans/shipping-sync.md) | Complete |
7. ~~**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. | [printful-integration.md](docs/plans/printful-integration.md) | Complete |
8. ~~**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. | [printify-integration-research.md](docs/plans/printify-integration-research.md) | Reference |
9. **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. | [provider-strategy.md](docs/plans/provider-strategy.md) | Reference |
| [css-migration.md](docs/plans/css-migration.md) | Complete |
### Tier 3 — Compliance & quality | [analytics-v2.md](docs/plans/analytics-v2.md) | Complete |
| [page-builder.md](docs/plans/page-builder.md) | Complete |
10. ~~**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. | [media-library.md](docs/plans/media-library.md) | Complete |
11. **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. | [custom-pages.md](docs/plans/custom-pages.md) | Complete |
12. **Security (Paraxial.io)** — Runtime application security monitoring for Elixir. Bot detection, rate limiting, vulnerability scanning. Evaluate whether it fits the self-hosted model. | [image-optimization.md](docs/plans/image-optimization.md) | Complete |
13. **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. | [products-context.md](docs/plans/products-context.md) | Complete |
14. **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. | [dry-refactor.md](docs/plans/dry-refactor.md) | Complete |
15. **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](docs/plans/favicon.md). | [abandoned-cart.md](docs/plans/abandoned-cart.md) | Complete |
16. **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](docs/plans/url-redirects.md). | [activity-log.md](docs/plans/activity-log.md) | Complete |
| [favicon.md](docs/plans/favicon.md) | Complete |
### Tier 3.5 — Business tools | [legal-page-generator.md](docs/plans/legal-page-generator.md) | Complete |
| [url-redirects.md](docs/plans/url-redirects.md) | Complete |
17. **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](docs/plans/profit-aware-pricing.md). | [profit-aware-pricing.md](docs/plans/profit-aware-pricing.md) | Planned |
18. **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.
19. **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](docs/plans/activity-log.md).
20. **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
21. **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](docs/plans/page-builder.md).
22. **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](docs/plans/legal-page-generator.md).
23. **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.
24. **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](docs/plans/abandoned-cart.md).
25. **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
26. **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.
27. **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.
28. **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.
29. **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](docs/plans/image-optimization.md) for implementation details
### Products & Provider Integration
**Status:** Complete
- [x] Products context with schemas (c5c06d9)
- [x] Provider abstraction layer
- [x] Printify client integration
- [x] Product/variant/image schemas
- [x] Admin Provider Setup UI (`/admin/providers`) - connect, test, sync
- [x] ProductSyncWorker with pagination, parallel processing, error recovery
- [x] Slug-based fallback matching for changed provider IDs
- [x] Printify webhook endpoint with HMAC verification (a9c15ea)
- Note: Printify only supports `product:deleted` and `product:publish:*` events (no `product:updated`)
- [x] 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
- [x] 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
- [x] 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
- [x] Mox provider mocking for fulfilment tests (Provider.for_type configurable via app env)
See: [docs/plans/products-context.md](docs/plans/products-context.md) for implementation details
See: [docs/plans/printify-integration-research.md](docs/plans/printify-integration-research.md) for API research & risk analysis
### Cart & Checkout
**Status:** Complete
- [x] Cart drawer component with slide-over panel (1bc08bf)
- [x] Cart page with item list and order summary (1bc08bf)
- [x] Shared CartHook for cross-page cart events (1bc08bf)
- [x] CartPersist JS hook for localStorage backup
- [x] Add-to-cart with flash status feedback
- [x] Cart item links to product pages
- [x] 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
- [x] 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
- [x] 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
- [x] Checkout success page with real-time PubSub updates
- [x] Cart clearing after successful payment
See: [ROADMAP.md](ROADMAP.md) for design notes
### Admin Settings & Stripe Setup
**Status:** Complete
- [x] 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`
- [x] 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`
- [x] 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
- [x] Orders context with schemas (ff1bc48)
- [x] Stripe Checkout integration with webhook handling
- [x] 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
- [x] 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)
- [x] 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](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](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
### Products Refactor & Search
**Status:** Complete
- [x] Denormalized product fields (cheapest_price, compare_at_price, in_stock, on_sale) recomputed from variants after sync
- [x] Product display helpers (primary_image, hover_image, option_types) and ProductImage helpers (display_url, direct_url, source_width)
- [x] Products context storefront queries (list_visible_products, get_visible_product, list_categories) with DB-level sort/filter
- [x] Renamed .name → .title across all shop components and templates
- [x] PreviewData updated to struct-compatible format (removed product_to_map indirection)
- [x] FTS5 full-text search index with BM25 ranking (title 10x, category 5x, variants 3x, description 1x)
- [x] SearchHook on_mount for all shop pages (follows CartHook pattern)
- [x] Search modal with live results (thumbnails, titles, categories, prices, click-to-navigate)
- [x] Index auto-rebuilds after each provider sync
- [x] 18 search tests, 744 total
**Follow-ups (all complete):**
- [x] Wire shop LiveViews to direct DB queries (PreviewData removed from all shop pages, cart, error page)
- [x] Search modal keyboard nav (arrow keys, Enter, Escape, Cmd+K shortcut)
- [x] Full ARIA combobox pattern (role=combobox, listbox, option, aria-selected)
- [x] SearchModal JS hook, `<.link navigate>` for client-side nav, 150ms debounce
- [x] search.ex: transaction safety on reindex, public `remove_product/1`
- [x] LIKE substring fallback when FTS5 prefix returns nothing
- [x] Admin bar replaced with header icon (gear/cog, admin-only, no public link)
- [x] Search modal race condition fix (close-on-keypress, open/close custom events)
- [x] HTTP 304 support for cached images
- [x] 10 new integration tests, 757 total
### Shipping
**Status:** Complete
- [x] ShippingRate schema + migration (per blueprint/provider/country)
- [x] Shipping context: upsert, lookup with REST_OF_THE_WORLD fallback, cart calculation
- [x] Provider behaviour: optional `fetch_shipping_rates/2` callback
- [x] Printify implementation: fetch rates per blueprint/provider, normalize country arrays
- [x] ProductSyncWorker integration: shipping rates synced alongside products
- [x] ScheduledSyncWorker (Oban cron, every 6 hours) for periodic re-sync
- [x] Live exchange rate conversion at sync time (frankfurter.app API, ECB data)
- [x] 5% configurable buffer on exchange rates to absorb fluctuations
- [x] Country detection from Accept-Language header + cookie persistence
- [x] Cart page shipping estimate with country selector (all countries with rates)
- [x] Stripe Checkout shipping_options (UK domestic + international)
- [x] Order shipping_cost field, extracted from Stripe on payment
- [x] 780 tests total
See: [plan](docs/plans/shipping-sync.md) for implementation details
### SQLite Production Tuning
**Status:** Complete
- [x] Concurrency tests proving WAL read scaling, IMMEDIATE vs DEFERRED transaction behaviour
- [x] BenchRepo for isolated testing against temp DB files (no Sandbox dependency)
- [x] `mix bench.sqlite` task with `--prod`, `--scale`, `--pool-size`, `--busy-timeout` options
- [x] PRAGMA tuning across dev/test/prod: `default_transaction_mode: :immediate`, `journal_size_limit: 64MB`, `custom_pragmas: [mmap_size: 128MB]`
- [x] 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
- [x] 1041 tests total (5 excluded: 3 benchmarks + 2 correctness)
### Analytics
**Status:** In progress (v2)
- [x] Privacy-first, cookie-free analytics (2bd2e61)
- [x] E-commerce funnel events: product_view, add_to_cart, checkout_start, purchase (f91b47f)
- [x] Browser, OS, screen size collection (f91b47f)
- [x] HTML/CSS bar chart with hourly today view and readable labels (08fcd60)
- [x] Period comparison deltas on stat cards (6eda1de)
- [x] 2-year demo seed data with growth curve (6eda1de)
- [x] Dashboard filtering (click referrer/country/device to filter all panels) (7ceee9c)
- [x] CSV export (ZIP with 12 CSVs, period + filter aware)
- [x] Entry/exit pages panel
See: [docs/plans/analytics-v2.md](docs/plans/analytics-v2.md) for v2 plan
### Media Library
**Status:** Complete
Admin media library at `/admin/media` with image grid, type/search/orphan filters, upload with alt text, detail panel (metadata editing, usage tracking, delete with protection). Image schema extended with `alt`, `caption`, `tags` fields and `"media"` type. `find_usages/1` scans product_images, theme settings, favicon variants, and page blocks. `delete_with_cleanup/1` refuses deletion of in-use images and cleans up disk variants. `:image` field type in block editor with image preview and ID input. Page renderer resolves `image_id` to responsive variant URLs, falling back to legacy `image_url`/`image_src` strings. Alt text backfill mix task. 1398 tests.
**Key files:**
- `lib/berrypod/media.ex` — extended context (list, update_metadata, find_usages, used_image_ids, delete_with_cleanup)
- `lib/berrypod/media/image.ex` — alt/caption/tags fields, media type, metadata_changeset
- `lib/berrypod_web/live/admin/media.ex` — admin media library LiveView
- `lib/berrypod_web/components/block_editor_components.ex``:image` field type
- `lib/berrypod_web/page_renderer.ex` — image_id resolution helpers
- `lib/mix/tasks/berrypod/backfill_alt_text.ex` — one-time backfill task
- `test/berrypod/media_test.exs` — 36 tests (17 new)
- `test/berrypod_web/live/admin/media_test.exs` — 11 tests
See: [docs/plans/media-library.md](docs/plans/media-library.md) for full plan
### Page Editor
**Status:** Complete — all stages done, 1516 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). Custom CMS pages with 4 utility block types (spacer, divider, button/CTA, video embed), page templates (blank/content/landing) for new page creation, duplicate page action, and mobile sidebar fix for the block picker.
**Stages:**
1. ~~Foundation — data model, cache, block registry~~ ✅ (`35f96e4`)
2. ~~Page renderer — generic renderer tested in isolation~~ ✅ (`32f54c7`)
3. ~~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~~ ✅ (`660fda9`)
7. ~~Admin editor — inline block settings editing~~ ✅ (`3f97742`)
7b. ~~SettingsField struct + repeater field type for info_card items~~ ✅ (`6fbd654`)
8. ~~Live page editor sidebar — collapsible sidebar on shop pages, backdrop dismiss, portable content block~~ ✅ (`a039c8d`)
9. ~~Undo/redo + polish — history stacks, keyboard shortcuts, settings animations~~
10. ~~Custom pages stage 6 — 4 utility block types (spacer, divider, button/CTA, video embed), page templates (blank/content/landing), duplicate page, mobile block picker fix~~
**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
- `lib/berrypod_web/live/admin/pages/` — Index (page list) + Editor (block management)
- `test/berrypod/pages_test.exs` — 62 tests
- `test/berrypod_web/page_renderer_test.exs` — 18 tests
- `lib/berrypod/pages/settings_field.ex` — typed struct for settings schema fields
- `lib/berrypod/pages/block_editor.ex` — pure functions for block manipulation
- `lib/berrypod_web/components/block_editor_components.ex` — shared editor UI components
- `lib/berrypod_web/page_editor_hook.ex` — on_mount hook for live page editing on shop pages
- `test/berrypod_web/live/admin/pages_test.exs` — 42 tests
- `test/berrypod/pages/block_editor_test.exs` — 26 tests
- `test/berrypod_web/page_editor_hook_test.exs` — 13 tests
See: [docs/plans/page-builder.md](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. |

243
README.md
View File

@ -1,201 +1,126 @@
# Berrypod # Berrypod
A beautiful, customisable e-commerce storefront built with Phoenix LiveView. Designed for print-on-demand sellers who want professional shops without design expertise. A customisable e-commerce storefront for print-on-demand sellers, built with Phoenix LiveView. Professional shops without design expertise, privacy-respecting by default, fully self-hostable.
## Features ## Features
### The Shop ### Shop
A complete storefront with all the pages you need: Complete storefront with all the pages you need:
- **Home** - Hero banner, category navigation, featured products, testimonials - **Home** — hero banner, category navigation, featured products, newsletter
- **Products** - Grid layout with hover effects and filtering - **Products** — grid layout with hover effects, sorting, filtering by collection
- **Product Detail** - Image gallery, variants, reviews, related products - **Product detail** — image gallery with per-colour filtering, variant selector, related products
- **Cart** - Full shopping cart with order summary - **Cart** — drawer + full page, quantity controls, shipping estimate, cross-tab sync
- **Checkout** - Stripe-hosted checkout with order confirmation - **Checkout** — Stripe-hosted checkout with shipping costs, order confirmation
- **About** - Rich content with your brand story - **Custom pages** — CMS pages at any URL with 26 block types
- **Contact** - Contact form with business details - **Legal pages** — auto-generated privacy, delivery, and terms from actual shop settings
- **Error pages** - Themed 404/500 pages - **Search** — FTS5 full-text search with live modal, keyboard nav, ARIA
- **Contact** — contact form with order status lookup
### Theme Editor (`/admin/theme`) ### Admin
Customise your shop's look without touching code: - **Theme editor** — 8 presets, real-time preview, comprehensive customisation
- **8 curated presets** - Gallery, Studio, Boutique, Bold, Playful, Minimal, Night, Classic - **Page builder** — drag-free block editor with undo/redo, live editing on shop pages
- **Real-time preview** - See changes instantly across all page types - **Analytics** — privacy-first, cookie-free, comparison mode, CSV export
- **Comprehensive customisation**: - **Orders** — status tracking, fulfilment timeline, provider submission
- Mood (Neutral, Warm, Cool, Dark) - **Media library** — image management with alt text, usage tracking, orphan detection
- Typography (Clean, Editorial, Modern, Classic, Friendly, Minimal) - **Activity log** — global event feed, "needs attention" tab, contextual retry
- Shape (Sharp, Soft, Round, Pill) - **URL redirects** — auto-redirect on slug change, 404 monitoring, dead link scanning
- Density (Compact, Balanced, Spacious) - **Email settings** — 10 adapter options, test email, env var precedence
- Accent colours with automatic contrast - **Print providers** — Printify + Printful with sync, orders, shipping, webhooks
- Layout options (grid columns, header style, width)
- Feature toggles (announcement bar, sticky header, etc.)
- **Logo upload** with optional SVG recolouring
- **Header image** with zoom and position controls
### Technical Highlights ### Technical highlights
- CSS custom properties for instant theme switching - Hand-written CSS with three-layer architecture (9.8 KB gzipped shop, 17.8 KB admin)
- Three-layer CSS architecture (primitives → attributes → semantic) - SQLite with BLOB storage, IMMEDIATE transactions, WAL, mmap
- ETS-based CSS caching for performance - Image optimisation pipeline (AVIF/WebP/JPEG responsive variants via Oban)
- SQLite database with BLOB storage for images - ETS caching for CSS, pages, redirects, favicons
- Shared PageTemplates between preview and live shop - 99-100 PageSpeed mobile, no-JS support across all key flows
- 1679+ tests, CI with credo + dialyzer
## Getting Started ## Getting started
### Prerequisites ### Prerequisites
- Elixir 1.17+ - Elixir 1.19+
- Erlang 27+ - Erlang/OTP 28+
- Node.js 20+ (for assets) - Node.js 20+ (for esbuild asset bundling)
### Setup ### Setup
```bash ```bash
# Clone the repository
git clone <repo-url> git clone <repo-url>
cd berrypod cd berrypod
mix setup # install deps, create DB, run migrations, build assets
# Install dependencies mix phx.server # start dev server at localhost:4000
mix setup
# Start the server
mix phx.server
``` ```
Visit: Visit `http://localhost:4000/setup` to create your admin account and connect a print provider.
- **Shop**: http://localhost:4000
- **Theme Editor**: http://localhost:4000/admin/theme
### Running Tests ### Running tests
```bash ```bash
mix test mix test # all tests
mix test path/to.exs # specific file
mix precommit # compile warnings + format + test (run before committing)
``` ```
## Project Structure ## Project structure
``` ```
lib/ lib/berrypod/ # core business logic
├── berrypod/ # Core business logic ├── accounts.ex # user accounts + auth
│ ├── settings.ex # Theme settings context ├── analytics.ex # privacy-first pageview tracking
│ ├── settings/ ├── activity_log.ex # system event logging
│ │ └── theme_settings.ex # Theme settings schema ├── cart.ex # session-based cart
│ ├── media.ex # Image upload handling ├── media.ex # image uploads, optimisation, media library
│ └── theme/ ├── newsletter.ex # email list + campaigns
│ ├── css_generator.ex # Generates CSS from settings ├── orders.ex # order lifecycle + fulfilment
│ ├── css_cache.ex # ETS cache for generated CSS ├── pages.ex # page builder (blocks, cache, defaults)
│ ├── presets.ex # 8 theme presets ├── products.ex # products, variants, categories
│ └── preview_data.ex # Mock data for previews ├── providers.ex # POD provider abstraction (Printify, Printful)
├── redirects.ex # URL redirects + dead link monitoring
├── berrypod_web/ # Web layer ├── search.ex # FTS5 full-text search
│ ├── components/ ├── settings.ex # theme + shop settings
│ │ ├── layouts/ # App and shop layouts ├── shipping.ex # shipping rates + country detection
│ │ ├── page_templates/ # Shared page templates (*.heex) ├── theme/ # CSS generation, presets, ETS cache
│ │ ├── page_templates.ex # PageTemplates module └── workers/ # Oban background jobs
│ │ └── shop_components.ex # Reusable shop UI components
│ ├── live/ lib/berrypod_web/ # web layer
│ │ ├── theme_live/ # Theme editor LiveView ├── components/
│ │ └── shop_live/ # Public shop LiveViews │ ├── layouts/ # app, admin, and shop layouts
│ └── controllers/ │ ├── shop_components/ # shop UI (product cards, cart, gallery, etc.)
│ └── error_html.ex # Themed error pages │ └── block_editor_components.ex
├── live/
│ ├── admin/ # admin LiveViews (orders, pages, media, etc.)
│ ├── shop/ # shop LiveViews (home, collection, product, etc.)
│ └── auth/ # authentication
├── page_renderer.ex # generic block-to-component dispatch
└── controllers/ # Stripe webhooks, favicons, images, cart API
assets/css/ assets/css/
├── app.css # Main stylesheet ├── shop/ # shop component styles
├── theme-layer1-primitives.css # Design tokens ├── admin/ # admin component styles
├── theme-layer2-attributes.css # Theme-specific values ├── theme-layer1-primitives.css # design tokens
└── theme-layer3-semantic.css # Component styles ├── theme-layer2-attributes.css # theme-specific values
└── theme-layer3-semantic.css # component styles
``` ```
## Routes ## Stripe setup
| Path | Description |
|------|-------------|
| `/` | Shop home page |
| `/collections/all` | All products |
| `/collections/:slug` | Category collection (filterable) |
| `/products/:id` | Product detail page |
| `/cart` | Shopping cart |
| `/checkout` | Create Stripe session (POST) |
| `/checkout/success` | Order confirmation |
| `/webhooks/stripe` | Stripe webhook endpoint |
| `/about` | About page |
| `/contact` | Contact page |
| `/admin/theme` | Theme editor (requires auth) |
| `/dev/errors/404` | Preview 404 page (dev only) |
| `/dev/errors/500` | Preview 500 page (dev only) |
## Stripe Checkout
Berrypod uses [Stripe Checkout](https://stripe.com/docs/payments/checkout) (hosted payment page) for secure payment processing.
### Setup
1. Create a [Stripe account](https://dashboard.stripe.com/register) 1. Create a [Stripe account](https://dashboard.stripe.com/register)
2. Get your API keys from the [Stripe Dashboard](https://dashboard.stripe.com/test/apikeys) 2. Navigate to Admin > Settings in your shop
3. Set environment variables: 3. Paste your secret key and click "Connect Stripe"
```bash
export STRIPE_SECRET_KEY="sk_test_..."
export STRIPE_WEBHOOK_SECRET="whsec_..."
```
### Local webhook testing
Use the [Stripe CLI](https://stripe.com/docs/stripe-cli) to forward webhooks to your local server:
For local webhook testing:
```bash ```bash
stripe listen --forward-to localhost:4000/webhooks/stripe stripe listen --forward-to localhost:4000/webhooks/stripe
``` ```
The CLI will print a webhook signing secret — use that as `STRIPE_WEBHOOK_SECRET`. Test cards: `4242 4242 4242 4242` (success), `4000 0000 0000 0002` (declined).
### Test cards
| Number | Result |
|--------|--------|
| `4242 4242 4242 4242` | Successful payment |
| `4000 0000 0000 0002` | Declined |
| `4000 0025 0000 3155` | Requires 3D Secure |
Use any future expiry date and any 3-digit CVC.
## Generating Mockups
The project includes a Printify integration for generating product mockups. This is useful for creating sample product images from Unsplash artwork.
### Prerequisites
1. A Printify account with API access
2. Set the `PRINTIFY_API_TOKEN` environment variable
### Usage
```bash
# Generate all mockups (saves to priv/static/mockups/)
export PRINTIFY_API_TOKEN="your-token"
mix generate_mockups
# Generate mockups and delete products from Printify afterwards
mix generate_mockups --cleanup
# Search for available blueprints
mix generate_mockups --search "poster"
# List all blueprints
mix generate_mockups --list-blueprints
```
Product definitions are in `lib/berrypod/printify/mockup_generator.ex`.
## Documentation ## Documentation
- [ROADMAP.md](ROADMAP.md) - Future features and improvements - [PROGRESS.md](PROGRESS.md) — current status and next tasks
- [docs/plans/](docs/plans/) - Feature plans - [ROADMAP.md](ROADMAP.md) — future vision and planned features
- [docs/research/](docs/research/) - Design research and guidelines - [CLAUDE.md](CLAUDE.md) — coding guidelines and conventions
- [docs/plans/](docs/plans/) — feature implementation plans
## Design Philosophy
1. **"One theme, infinite variations"** - Rather than multiple themes, one solid foundation with curated customisation
2. **Constrained creativity** - Limit choices to prevent poor design outcomes
3. **No professional photography required** - Works with product mockups
4. **Mobile-first** - All features work on touch devices
5. **Ethical design** - No dark patterns or fake urgency
## License ## License

View File

@ -1,78 +1,68 @@
# Berrypod Roadmap # Berrypod Roadmap
> Vision and future features. For current status, see [PROGRESS.md](PROGRESS.md). > Forward-looking vision and planned features. For current status, see [PROGRESS.md](PROGRESS.md).
## What's done
Tiers 1-3 are complete. The shop handles real orders end-to-end: browse products, add to cart, Stripe Checkout, order submission to Printify/Printful, fulfilment tracking, transactional emails. Full admin with theme editor, page builder, analytics, media library, activity log, URL redirects, and dead link monitoring. Tailwind-free CSS, 99-100 PageSpeed, 1679+ tests.
See [PROGRESS.md](PROGRESS.md) for the full list.
--- ---
## Core MVP: Cart & Checkout ✅ ## Profit-aware pricing & sales
Session-based cart, Stripe-hosted Checkout, order persistence, and webhook handling are all complete. See [PROGRESS.md](PROGRESS.md) for details. Complete cost visibility for shop owners. [Plan](docs/plans/profit-aware-pricing.md).
### Orders & Fulfillment (next up) - Fix Printful cost sync (catalog API cross-reference for variant costs)
- Submit paid orders to Printify for fulfillment - Snapshot `unit_cost` on order items at time of sale
- Track fulfillment status updates via webhook - Exact Stripe fees from Balance Transaction API (not estimated)
- Display order status to customers - Tax as a toggle: Stripe Tax at checkout, "inc. VAT" / "+ tax" display based on shop country
- Admin profit dashboard: per-product margins, per-order P&L, overall business health
- Price editor shows live margin as you set prices
- Sales & promotions: time-limited, scoped to catalogue/category/products, % or fixed
- Margin guard: prevent discounts that breach minimum profit threshold
- Announcement bar for active sales
### Cost Verification at Checkout ## Production hardening
Verify Printify costs haven't changed before completing checkout to prevent selling at a loss.
- **Litestream / SQLite replication** — continuous backup to S3-compatible storage with point-in-time recovery. For the hosted platform, evaluate Turso/libSQL with embedded read replicas
- **End-to-end & accessibility tests** — Wallaby browser tests for critical flows with A11yAudit (axe-core) assertions. WCAG 2.1 AA compliance
- **Security** — evaluate Paraxial.io for runtime security monitoring, bot detection, rate limiting
- **AGPL licensing & code hosting** — proper LICENSE file, contribution guidelines, release process. Decide on GitHub vs Codeberg vs self-hosted Forgejo
## Newsletter & email marketing
Email list collection (signup forms on shop pages). Campaign sending for product launches and sales. Simple to start: collect emails, send via Swoosh. Ties into sales & promotions for sale announcement blasts.
## Product page improvements
- Pre-checkout variant validation (verify provider availability)
- Cost change monitoring/alerts (warn if provider costs increased)
- Better image gallery (zoom, multiple angles)
- Product reviews system
## Platform vision
### Marketing site
Berrypod.com as the public face: brochure pages, pricing, comparison with Shopify/Squarespace, sign-up flow, demo store. Clear separation between the commercial platform site and the AGPL open source core.
### Hosted platform infrastructure
Multi-tenancy with per-tenant databases. OAuth connect flows for providers (Printify, Printful) and payments (Stripe Connect). The setup UI already supports this: check for OAuth credentials and show the connect button, fall back to API key form on self-hosted.
### Migration & export
Let shop owners export their data (products, orders, theme settings). Import from Shopify, WooCommerce. Portable data as a selling point for the self-hosted story.
### Internationalisation
Multi-language via Gettext, currency formatting, RTL layout support. Note: `ex_money`/`ex_cldr` currently only used for GBP formatting but add ~13 MB to the release. Consider replacing with a lightweight `format_price/2` until proper locale-aware formatting is needed.
--- ---
## Medium Features ## Design philosophy
### Page Builder 1. **"One theme, infinite variations"** — one solid foundation with curated customisation
Database-driven pages with drag-and-drop sections: 2. **Constrained creativity** — limit choices to prevent poor design outcomes
- Hero, Featured Products, Testimonials, Newsletter 3. **No professional photography required** — works with product mockups
- Per-section configuration 4. **Mobile-first** — all features work on touch devices
- See: [docs/plans/page-builder.md](docs/plans/page-builder.md) 5. **Ethical design** — no dark patterns or fake urgency
6. **Privacy-first** — cookie-free analytics, GDPR-compliant cart recovery, no tracking pixels
---
## Future Features
### Multi-Admin Support
- Multiple admin users
- Role-based permissions
- Audit logging
### Custom Domains
- Domain verification
- SSL certificate provisioning
- DNS configuration guidance
### Theme Export/Import
- JSON export of all settings
- Import with validation
- Preset sharing between shops
### Advanced Theme Features
- Custom CSS injection
- Custom JavaScript snippets
- Code-level overrides for developers
### Multi-Provider Support
Support multiple POD providers beyond Printify:
- Prodigi (better for art prints)
- Gelato (global fulfillment)
- Provider-agnostic product model
- Price comparison across providers
---
## Technical Debt
### Test Coverage
Areas needing better coverage:
- Shop LiveView integration tests
- CSS cache invalidation flow
- Theme application across all pages
- Responsive behaviour
- Accessibility validation
### Error Handling
- Better error states for missing products
- Graceful degradation when theme settings are invalid
- Network error handling in LiveView
### Rename Project
The project is named `berrypod` but it's now a full storefront. Consider renaming to `simple_shop`.

View File

@ -1,7 +1,7 @@
# Abandoned cart recovery # Abandoned cart recovery
> Status: Planned > Status: Complete
> Tasks: #7577 in PROGRESS.md > Tasks: #7577
> Tier: 4 (Growth & content) > Tier: 4 (Growth & content)
## Goal ## Goal

View File

@ -1,7 +1,7 @@
# Activity log & order timeline # Activity log & order timeline
> Status: Planned > Status: Complete
> Tasks: #8992 in PROGRESS.md > Tasks: #8992
> Tier: 3.5 (Business tools) > Tier: 3.5 (Business tools)
## Goal ## Goal

View File

@ -1,6 +1,6 @@
# Analytics dashboard v2 # Analytics dashboard v2
Status: In progress Status: Complete
## Current state (v1) ## Current state (v1)

View File

@ -1,6 +1,6 @@
# Plan: CSS migration — Tailwind + DaisyUI to modern hand-written CSS # Plan: CSS migration — Tailwind + DaisyUI to modern hand-written CSS
Status: Phases 0-7 complete. Phase 8 (optimisation) not started. Status: Complete (all phases 0-8)
## Overview ## Overview

View File

@ -1,6 +1,6 @@
# Custom CMS pages # Custom CMS pages
Status: Planned Status: Complete
See also: [page-builder.md](page-builder.md) (existing page editor plan) See also: [page-builder.md](page-builder.md) (existing page editor plan)
Six stages, ordered by priority. Each stage is independently shippable. Six stages, ordered by priority. Each stage is independently shippable.

View File

@ -1,8 +1,8 @@
# Favicon & site icon management # Favicon & site icon management
> Status: Planned > Status: Complete
> Tasks: #8688 in PROGRESS.md > Tasks: #8688
> Tier: 3 (Compliance & quality) — ships before page editor > Tier: 3 (Compliance & quality)
## Goal ## Goal

View File

@ -1,8 +1,8 @@
# Legal page generator # Legal page generator
> Status: Planned > Status: Complete
> Tasks: #8385 in PROGRESS.md > Tasks: #8385
> Tier: 4 (Growth & content), Phase 1 can ship earlier > Tier: 4 (Growth & content)
## Goal ## Goal

View File

@ -1,6 +1,6 @@
# Page builder plan # Page builder plan
Status: In progress (Stage 7 complete) Status: Complete (all stages)
## Context ## Context

View File

@ -1,6 +1,6 @@
# Printful integration plan # Printful integration plan
**Status:** Planning **Status:** Complete
**Depends on:** Shipping sync (done), Provider behaviour (done) **Depends on:** Shipping sync (done), Provider behaviour (done)
--- ---

View File

@ -1,5 +1,7 @@
# Unified setup and launch readiness # Unified setup and launch readiness
Status: Complete
## Context ## Context
The current first-run experience is disjointed: `/users/register` is a standalone page that closes after one user, the login page still links to it, and `/admin/setup` is a 3-step wizard hardcoded to Printify that bundles "go live" into the initial setup. Research into Shopify, WooCommerce and Squarespace shows that every successful platform separates initial setup (get the plumbing working) from launch readiness (guide the owner to a shop worth opening). The current first-run experience is disjointed: `/users/register` is a standalone page that closes after one user, the login page still links to it, and `/admin/setup` is a 3-step wizard hardcoded to Printify that bundles "go live" into the initial setup. Research into Shopify, WooCommerce and Squarespace shows that every successful platform separates initial setup (get the plumbing working) from launch readiness (guide the owner to a shop worth opening).

View File

@ -1,7 +1,7 @@
# Plan: Shipping rates + scheduled sync # Plan: Shipping rates + scheduled sync
**Status:** Planning **Status:** Complete
**Task:** #18 from PROGRESS.md — shipping costs at checkout **Task:** #18 — shipping costs at checkout
**Scope:** Shipping rate caching, scheduled product sync, provider behaviour extension, cart/checkout integration **Scope:** Shipping rate caching, scheduled product sync, provider behaviour extension, cart/checkout integration
--- ---