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

73 lines
4 KiB
Markdown

# 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
```bash
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.Layouts`** — `app/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