- add detailed task spec for /admin/providers UI with webhook integration - add product sync strategy with manual, webhook, and scheduled sync - update PROGRESS.md to prioritise admin provider UI as next task - add writing style guidelines (british english, sentence case, concise) - add commit guidelines (atomic, imperative, suggest at checkpoints) - add pragmatic testing guidelines (test boundaries, skip trivial) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
240 lines
7.8 KiB
Markdown
240 lines
7.8 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## Project Overview
|
|
|
|
SimpleShop is a customisable e-commerce storefront for print-on-demand sellers, built with Phoenix 1.8 + LiveView 1.1 on Elixir/Erlang. Uses SQLite with BLOB storage for images. Licensed under AGPL-3.0.
|
|
|
|
## Common Commands
|
|
|
|
```bash
|
|
mix setup # Install deps, create DB, run migrations, build assets
|
|
mix phx.server # Start dev server at localhost:4000
|
|
mix test # Run all tests
|
|
mix test path/to.exs # Run specific test file
|
|
mix test --failed # Re-run failed tests
|
|
mix precommit # REQUIRED before committing: compile --warning-as-errors, format, test
|
|
```
|
|
|
|
## Architecture
|
|
|
|
### Core Contexts (lib/simpleshop_theme/)
|
|
|
|
- **Settings** - Theme configuration persistence as JSON
|
|
- **Theme** - CSS generation, ETS caching, 8 presets (Gallery, Studio, Boutique, etc.)
|
|
- **Products** - Product/variant data synced from Printify
|
|
- **Media/Images** - Image uploads and optimization pipeline (Oban jobs)
|
|
- **Providers** - Abstraction layer for POD providers (Printify integration)
|
|
|
|
### Web Layer (lib/simpleshop_theme_web/)
|
|
|
|
- **live/** - LiveViews for shop pages and theme editor
|
|
- **components/page_templates/** - Shared templates between preview and live shop
|
|
- **components/shop_components.ex** - Reusable shop UI components
|
|
|
|
### Three-Layer CSS Architecture
|
|
|
|
1. **Primitives** (layer1) - Design tokens as CSS custom properties
|
|
2. **Attributes** (layer2) - Theme-specific design rules
|
|
3. **Semantic** (layer3) - Component styles
|
|
|
|
Theme switching is instant via CSS custom property injection (no reload).
|
|
|
|
### Key Routes
|
|
|
|
| Path | Purpose |
|
|
|------|---------|
|
|
| `/` | Shop home |
|
|
| `/collections/:slug` | Product collection (filtering) |
|
|
| `/products/:id` | Product detail |
|
|
| `/admin/theme` | Theme editor (auth required) |
|
|
|
|
## Elixir Guidelines
|
|
|
|
- Use `:req` library for HTTP requests (not httpoison, tesla, httpc)
|
|
- Lists don't support index access (`list[i]`), use `Enum.at/2`
|
|
- Access changeset fields with `Ecto.Changeset.get_field/2`, not `changeset[:field]`
|
|
- Preload associations in queries when needed in templates
|
|
- **Rebinding:** Must bind result of `if`/`case` blocks:
|
|
```elixir
|
|
# WRONG - rebinding inside block is lost
|
|
if connected?(socket), do: socket = assign(socket, :val, val)
|
|
|
|
# RIGHT - bind result to variable
|
|
socket = if connected?(socket), do: assign(socket, :val, val), else: socket
|
|
```
|
|
|
|
## Phoenix 1.8 Guidelines
|
|
|
|
- **Always** wrap LiveView templates with `<Layouts.app flash={@flash} ...>`
|
|
- Use `@current_scope.user` in templates, never `@current_user`
|
|
- Use `to_form/2` for all form handling, access via `@form[:field]`
|
|
- Use `<.input>` component from core_components.ex
|
|
- Use `<.icon name="hero-x-mark">` for icons, not Heroicons modules
|
|
|
|
### Auth Routing
|
|
|
|
Routes requiring auth go in `:require_authenticated_user` live_session:
|
|
```elixir
|
|
live_session :require_authenticated_user,
|
|
on_mount: [{SimpleshopThemeWeb.UserAuth, :require_authenticated}] do
|
|
live "/admin/theme", ThemeLive.Index
|
|
end
|
|
```
|
|
|
|
Public routes with optional user go in `:current_user` live_session:
|
|
```elixir
|
|
live_session :current_user,
|
|
on_mount: [{SimpleshopThemeWeb.UserAuth, :mount_current_scope}] do
|
|
live "/", ShopLive.Home
|
|
end
|
|
```
|
|
|
|
## HEEx Template Guidelines
|
|
|
|
- Use `{...}` for interpolation in attributes, `<%= %>` for block constructs in bodies
|
|
- Class lists require bracket syntax: `class={["base", @cond && "extra"]}`
|
|
- Use `<%!-- comment --%>` for template comments
|
|
- Never use `else if` or `elsif` - use `cond` or `case`
|
|
- Use `phx-no-curly-interpolation` for literal braces in code blocks
|
|
- **Never** use `<% Enum.each %>` - always use `<%= for item <- @items do %>`
|
|
|
|
## LiveView Guidelines
|
|
|
|
### Streams (Required for Collections)
|
|
|
|
Always use streams for lists to prevent memory issues:
|
|
```elixir
|
|
# Mount
|
|
socket |> stream(:products, Products.list_products())
|
|
|
|
# Add item
|
|
socket |> stream_insert(:products, new_product)
|
|
|
|
# Reset (e.g., filtering)
|
|
socket |> stream(:products, filtered_list, reset: true)
|
|
|
|
# Delete
|
|
socket |> stream_delete(:products, product)
|
|
```
|
|
|
|
Template pattern:
|
|
```heex
|
|
<div id="products" phx-update="stream">
|
|
<div :for={{dom_id, product} <- @streams.products} id={dom_id}>
|
|
{product.title}
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
Empty state with Tailwind:
|
|
```heex
|
|
<div id="products" phx-update="stream">
|
|
<div class="hidden only:block">No products yet</div>
|
|
<div :for={{dom_id, product} <- @streams.products} id={dom_id}>...</div>
|
|
</div>
|
|
```
|
|
|
|
### Form Handling
|
|
|
|
From params:
|
|
```elixir
|
|
def handle_event("validate", %{"product" => params}, socket) do
|
|
{:noreply, assign(socket, form: to_form(params, as: :product))}
|
|
end
|
|
```
|
|
|
|
From changeset:
|
|
```elixir
|
|
changeset = Product.changeset(%Product{}, params)
|
|
socket |> assign(form: to_form(changeset))
|
|
```
|
|
|
|
### Gotchas
|
|
|
|
- `phx-hook` with DOM manipulation requires `phx-update="ignore"`
|
|
- Avoid LiveComponents unless you have a specific need (isolated state, targeted updates)
|
|
- Never use deprecated `phx-update="append"` or `phx-update="prepend"`
|
|
|
|
## JS/CSS Guidelines
|
|
|
|
- Tailwind v4 uses `@import "tailwindcss"` syntax (no tailwind.config.js)
|
|
- **Never** use `@apply` in CSS
|
|
- **Never** write inline `<script>` tags - use hooks in assets/js/
|
|
- All vendor deps must be imported into app.js/app.css
|
|
|
|
## LiveView Testing
|
|
|
|
- Use `element/2`, `has_element/2` - never test raw HTML
|
|
- Reference DOM IDs from templates in tests
|
|
- Debug with LazyHTML: `LazyHTML.filter(document, "selector")`
|
|
|
|
## Writing Style
|
|
|
|
- Casual, British English tone throughout (code, comments, commits, docs)
|
|
- Sentence case always, never title case ("Admin providers" not "Admin Providers")
|
|
- Brief and tight in prose. Cut fluff. No waffle.
|
|
- Avoid em dashes, semicolons in prose, and overly formal language
|
|
- Don't sound like an LLM wrote it (no "straightforward", "robust", "leverage", "comprehensive")
|
|
- Comments explain why, not what. Skip obvious ones.
|
|
|
|
**Code style:**
|
|
- Explicit and obvious over clever and terse
|
|
- Readable code that anyone can follow beats "smart" one-liners
|
|
- Stick to idiomatic Phoenix/Elixir/LiveView/Oban patterns
|
|
- Follow conventions from the existing codebase
|
|
- When in doubt, check how Phoenix generators do it
|
|
|
|
## Commits
|
|
|
|
- Suggest a commit at logical checkpoints (feature complete, tests passing)
|
|
- Run `mix precommit` before committing
|
|
- Atomic commits with one logical change each
|
|
- Commit messages: imperative mood, lowercase, no full stop
|
|
- Good: `add provider connection form validation`
|
|
- Bad: `Added provider connection form validation.`
|
|
|
|
## Testing
|
|
|
|
Write tests for new features, but be pragmatic about coverage.
|
|
|
|
**Do test:**
|
|
- Public context functions (the API boundary)
|
|
- Critical user flows (auth, checkout, sync)
|
|
- Edge cases and error handling
|
|
- Complex business logic
|
|
|
|
**Skip tests for:**
|
|
- Trivial getters/setters
|
|
- Framework-generated code
|
|
- Pure UI tweaks with no logic
|
|
- Implementation details that may change
|
|
|
|
**Approach:**
|
|
- Prefer integration tests over isolated unit tests for LiveViews
|
|
- One test file per module, colocated in `test/` mirror of `lib/`
|
|
- Use fixtures from `test/support/fixtures/` for test data
|
|
|
|
## Documentation Workflow
|
|
|
|
**Single source of truth:** [PROGRESS.md](PROGRESS.md)
|
|
- Update after completing any feature or task
|
|
- Contains current status, next steps, and task breakdown
|
|
- Link to plan files for implementation details
|
|
|
|
**Plan files** (docs/plans/*.md):
|
|
- Implementation references, not status trackers
|
|
- Mark status at top (e.g., "Status: Complete")
|
|
- Keep detailed architecture/design decisions
|
|
|
|
**Task sizing:**
|
|
- Break features into ~1-2 hour tasks
|
|
- Each task should fit in one Claude session without context overflow
|
|
- Include: files to modify, acceptance criteria, estimate
|
|
|
|
**Before starting work:**
|
|
1. Check PROGRESS.md for current status and next task
|
|
2. Read relevant plan file for implementation details
|
|
3. Focus on one task at a time
|