ammocpr/CLAUDE.md
Graham McIntire bbe5bde145
Initial implementation of ammo price tracker
Phoenix 1.8 app that scrapes ammunition retailers (Lucky Gunner,
SGAmmo) for price data and displays historical price trends.

- Data model: retailers, calibers, products, price snapshots
- Scraper infrastructure with Req, Floki, realistic browser headers
- Oban-scheduled scrape jobs (every 4h with randomized delays)
- LiveView pages: homepage with category cards, caliber detail with
  price table, Chart.js price history, and price stats banner
- 18 seeded calibers across handgun/rifle/rimfire/shotgun categories
- 77 tests
2026-03-11 15:58:12 -05:00

4 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

Ammoprices is a Phoenix 1.8 web application (Elixir ~> 1.15) with LiveView, Ecto/PostgreSQL, and Tailwind CSS v4 with daisyUI. Currently a fresh scaffold with no domain logic implemented yet.

Common Commands

mix setup                  # Install deps, create DB, run migrations, build assets
mix phx.server             # Start dev server (localhost:4000)
iex -S mix phx.server      # Start dev server with IEx shell
mix test                   # Run all tests (auto-creates/migrates test DB)
mix test test/path_test.exs          # Run a single test file
mix test test/path_test.exs:42       # Run a specific test by line number
mix test --failed                    # Re-run previously failed tests
mix precommit              # Compile (warnings-as-errors) + unlock unused deps + format + test
mix format                 # Format code
mix ecto.gen.migration name          # Generate a new migration
mix ecto.migrate           # Run pending migrations
mix ecto.reset             # Drop and recreate database

mix precommit is the required pre-commit check — always run it before finishing changes.

Architecture

Application Structure

  • Ammoprices.Application — Supervision tree: Telemetry, Repo, DNSCluster, PubSub, Endpoint
  • Ammoprices.Repo — Ecto repo using PostgreSQL adapter
  • Ammoprices.Mailer — Swoosh mailer (local adapter in dev, viewable at /dev/mailbox)

Web Layer (lib/ammoprices_web/)

  • AmmopricesWeb (ammoprices_web.ex) — Defines use macros for :router, :controller, :live_view, :live_component, :html. The html_helpers/0 block imports CoreComponents, aliases Layouts and Phoenix.LiveView.JS, and sets up verified routes
  • AmmopricesWeb.Router — Browser pipeline only. Currently just GET / → PageController.home
  • AmmopricesWeb.CoreComponents — UI component library using daisyUI + Tailwind. Includes flash/1, button/1, icon/1, standard form/table components
  • AmmopricesWeb.Layoutsapp/1 layout with navbar and theme toggle (light/dark/system). Templates wrap content with <Layouts.app flash={@flash}>

Config

  • config/config.exs — Generator defaults: UTC timestamps, binary IDs
  • config/dev.exs — DB: ammoprices_dev, live reload watchers for esbuild/tailwind
  • config/test.exs — DB: ammoprices_test, SQL sandbox mode

Assets (assets/)

  • Tailwind CSS v4 with @import "tailwindcss" syntax (no tailwind.config.js)
  • daisyUI plugin with custom light/dark themes defined in app.css
  • esbuild bundles app.js — only app.js and app.css bundles are supported
  • Vendor libs: topbar.js, heroicons.js, daisyui.js, daisyui-theme.js
  • JS hooks for LiveView use colocated hook syntax (names prefixed with .)

Test Support (test/support/)

  • ConnCase — For controller/LiveView tests, sets up conn and DB sandbox
  • DataCase — For context/schema tests, provides errors_on/1 helper
  • LazyHTML — Available in tests for HTML assertion selectors

Key Conventions

  • HTTP client: Use Req (already included). Never use HTTPoison, Tesla, or :httpc
  • LiveView templates: Always begin with <Layouts.app flash={@flash}> wrapper
  • Icons: Use <.icon name="hero-x-mark" /> component, never Heroicons modules
  • Forms: Always use to_form/2 assigned in LiveView, access via @form[:field] in templates
  • Collections: Always use LiveView streams, never assign raw lists
  • JS in templates: Use colocated hook <script :type={Phoenix.LiveView.ColocatedHook}> with . prefixed names, never raw <script> tags
  • No inline scripts: Import vendor deps into app.js/app.css, no external src/href in layouts
  • Tailwind classes: Use list syntax class={["base", condition && "extra"]} for conditional classes
  • Never use @apply in CSS