berrypod/PROGRESS.md
jamey 2bd2e613c7
All checks were successful
deploy / deploy (push) Successful in 3m20s
add privacy-first analytics with progressive event collection
Three-layer pipeline: Plug for all HTTP requests (no JS needed), LiveView
hook for SPA navigations, JS hook for screen width. ETS-backed buffer
batches writes to SQLite every 10s. Daily-rotating salt for visitor hashing.
Includes admin dashboard with date ranges, visitor trends, top pages,
sources, devices, and e-commerce conversion funnel. Oban cron for 12-month
data retention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:50:55 +00:00

426 lines
30 KiB
Markdown

# 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
- 972 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
**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](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)
| # | 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 |
**All tasks complete.** No remaining work in the task list.
See [css-migration.md](docs/plans/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)
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.
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.
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.
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.
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.
### Tier 3 — Compliance & quality
10. **Privacy-respecting analytics** — Self-hosted, cookie-free analytics. Plausible, Umami, or a lightweight custom solution. No Google Analytics, no third-party tracking. GDPR-friendly by design.
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.
12. **Security (Paraxial.io)** — Runtime application security monitoring for Elixir. Bot detection, rate limiting, vulnerability scanning. Evaluate whether it fits the self-hosted model.
### Tier 4 — Growth & content
13. **Page editor** — Database-driven pages with drag-and-drop sections. Extend the theme system to custom pages beyond the defaults. Replaces the static content pages from Tier 1 with editable versions. **Removes PreviewData usage from `content.ex`** (about, delivery, privacy, terms content blocks are currently hardcoded in PreviewData).
14. **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.
15. **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
16. **Hosted platform** — Marketing/brochure site for Berrypod as a service. Subscribe/sign-up flow. 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.
17. **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.
18. **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] 972 tests total (5 excluded: 3 benchmarks + 2 correctness)
### Page Editor
**Status:** Future (Tier 4)
Database-driven pages with drag-and-drop sections. Initially, default content pages (terms, privacy, delivery) will be static templates (Tier 1), later made editable via the page editor.
See: [docs/plans/page-builder.md](docs/plans/page-builder.md) for design
---
## Completed Work Reference
| Feature | Commit | Notes |
|---------|--------|-------|
| 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. |