- Create PROGRESS.md as single source of truth for status - Slim ROADMAP.md to vision only (~100 lines, down from ~500) - Expand CLAUDE.md with streams, auth routing, forms, workflow - Convert AGENTS.md to stub pointing to CLAUDE.md - Update plan files with status headers, remove progress trackers Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
6.2 KiB
6.2 KiB
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
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
- Primitives (layer1) - Design tokens as CSS custom properties
- Attributes (layer2) - Theme-specific design rules
- 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
:reqlibrary for HTTP requests (not httpoison, tesla, httpc) - Lists don't support index access (
list[i]), useEnum.at/2 - Access changeset fields with
Ecto.Changeset.get_field/2, notchangeset[:field] - Preload associations in queries when needed in templates
- Rebinding: Must bind result of
if/caseblocks:# 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.userin templates, never@current_user - Use
to_form/2for 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:
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:
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 iforelsif- usecondorcase - Use
phx-no-curly-interpolationfor 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:
# 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:
<div id="products" phx-update="stream">
<div :for={{dom_id, product} <- @streams.products} id={dom_id}>
{product.title}
</div>
</div>
Empty state with Tailwind:
<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:
def handle_event("validate", %{"product" => params}, socket) do
{:noreply, assign(socket, form: to_form(params, as: :product))}
end
From changeset:
changeset = Product.changeset(%Product{}, params)
socket |> assign(form: to_form(changeset))
Gotchas
phx-hookwith DOM manipulation requiresphx-update="ignore"- Avoid LiveComponents unless you have a specific need (isolated state, targeted updates)
- Never use deprecated
phx-update="append"orphx-update="prepend"
JS/CSS Guidelines
- Tailwind v4 uses
@import "tailwindcss"syntax (no tailwind.config.js) - Never use
@applyin 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")
Documentation Workflow
Single source of truth: 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:
- Check PROGRESS.md for current status and next task
- Read relevant plan file for implementation details
- Focus on one task at a time