This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Prima is a Phoenix LiveView component library providing unstyled, accessible UI components. The repository is structured as:
- Root directory - Library code (published to Hex)
- demo/ directory - Demo application for development and testing
mix deps.get # Get library dependencies
mix compile # Compile library
mix assets.build # Build library JavaScript (prima.js)
mix hex.build # Build hex package for publishingcd demo
mix setup # Full setup (deps, assets setup, assets build)
mix deps.get # Get demo dependencies
mix assets.setup # Install Tailwind and esbuild
mix assets.build # Build demo assets
# Check if server is already running at localhost:4000 before starting
mix phx.server # Start development server (demo app at localhost:4000)Library tests (from root):
mix test # Run library unit testsDemo/integration tests (from demo/):
cd demo
mix test # Run all tests including Wallaby browser tests
mix test path/to/file # Run tests for a single file
mix test path/to/file:123 # Run a single test starting on given lineFor Wallaby browser tests specifically:
- Tests are in
demo/test/wallaby/demo_web/ - ChromeDriver-based integration tests for UI interactions
- Run with standard
mix testcommand from demo/ directory
prima/ # Root - library code only
lib/prima/ # Library components (published to Hex)
modal.ex
dropdown.ex
combobox.ex
assets/js/ # Library JavaScript
prima.js # Main export
hooks/ # Component hooks
priv/static/assets/ # Built library bundle
prima.js
mix.exs # Library-only dependencies
config/config.exs # Library build config (esbuild only)
demo/ # Demo app (not published)
lib/demo/
application.ex # Demo OTP application
lib/demo_web/ # Demo Phoenix application
endpoint.ex
router.ex
live/ # Demo pages and fixtures
config/ # Demo config files
assets/ # Demo assets
test/ # Demo tests including Wallaby
mix.exs # Demo dependencies (includes {:prima, path: ".."})
The library follows a three-layer pattern for each component:
- Phoenix Component (
lib/prima/*.ex) - Server-side rendering and LiveView integration - JavaScript Hook (
assets/js/hooks/*.js) - Client-side behavior and DOM manipulation - CSS Integration - Tailwind-based styling with standard data attribute selectors
# Standard component pattern
defmodule Prima.ComponentName do
use Phoenix.Component
# Main component function with slots and attributes
attr :id, :string, required: true
slot :inner_block, required: true
def component_name(assigns) do
~H"""
<div phx-hook="ComponentName" prima-ref={@id}>
<%= render_slot(@inner_block) %>
</div>
"""
end
enddata-prima-ref- Component instance identifierdata-focus- Focus state for dropdown items (true/false)- Standard data attributes for component state management
- Modal - Dialog/popup with overlay (
lib/prima/modal.ex) - Dropdown - Menu/select functionality (
lib/prima/dropdown.ex) - Combobox - Searchable input with suggestions (
lib/prima/combobox.ex)
Playwright MCP is the preferred tool for exploring and debugging the demo application.
- The development server runs on
http://localhost:4000 - Start the server from demo/ directory:
cd demo && mix phx.server - Always assume the server is running - navigate to
localhost:4000to view components - If nothing loads or the server isn't running, ask the user how to continue
- Use Playwright MCP tools to:
- Navigate to demo pages and view components visually
- Take screenshots to understand current UI state
- Interact with components (click, hover, type) to test behavior
- Inspect page snapshots to understand DOM structure
- Prefer Playwright MCP to using curl
Debugging JavaScript Hooks:
- Add
console.log()statements in JavaScript hook files (assets/js/hooks/*.js) - Use Playwright's browser console tools to view console output
- Console logs are very useful for understanding hook behavior and state changes
- Consider adding temporary debug logs when troubleshooting component issues
- ExUnit tests for simple component logic and rendering (library tests in root
test/) - Wallaby tests for complex UI interactions and components with JavaScript behavior (in
demo/test/wallaby/), especially:- Modal transitions and overlay behavior
- Dropdown keyboard navigation and interactions
- Combobox search and selection behavior
- Form integration and race conditions
Important: Due to the interactive nature of Prima components (heavy JavaScript integration, DOM manipulation, keyboard navigation), prefer Wallaby tests over unit tests for component testing. Components rely on Phoenix LiveView hooks and client-side behavior that can only be properly tested in a browser environment.
- esbuild with two configurations:
- Root:
library- Library export for distribution (buildspriv/static/assets/prima.js) - Demo:
default- Demo app development (buildsdemo/priv/static/assets/app.js)
- Root:
- Tailwind CSS (demo only) with standard data attribute selectors for component states
- Library assets built from root:
mix assets.build - Demo assets built from demo/:
cd demo && mix assets.build
When running the demo server (cd demo && mix phx.server):
- Phoenix automatically recompiles the library dependency when files in
lib/prima/change - Browser automatically refreshes when library or demo files change
- Watch patterns configured in
demo/config/dev.exsinclude../lib/prima/
- Located in
demo/lib/demo_web/ - Structured with sidebar navigation and separate component pages:
/- Introduction page/dropdown- Dropdown component demos/modal- Modal component demos/combobox- Combobox component demos/history-modal- History modal demos
- Serves as development environment and living documentation
- Unstyled by default - Minimal CSS, maximum customization
- Accessibility first - ARIA attributes, focus management, keyboard navigation
- Phoenix LiveView native - Deep integration with LiveView patterns
- Minimal JavaScript footprint via Phoenix LiveView hooks
- JavaScript hooks handle only essential client-side behavior
- All hooks export to main
assets/js/prima.jsfor easy integration
- Library code:
lib/prima/(included in package, published to Hex) - Demo/development code:
demo/(excluded from package) - Keep library components free of demo-specific dependencies
- Standard Tailwind data attribute selectors (e.g.,
data-[focus=true]:) for component state styling - Components use standard HTML data attributes for state management
- Heroicons integrated in demo for consistent icon usage
-
For hidden elements in Wallaby, use Query.visible(false)
-
Performance optimization: Avoid
refute_has()calls as they wait for Wallaby's default 3-second timeout. Use theassert_missing/2helper instead:# Usage - fast and semantic: |> assert_missing(Query.css("#element[data-focus]")) # Instead of slow: |> refute_has(Query.css("#element[data-focus]")) # waits 3000ms
-
Hook Readiness: Always wait for Prima hooks to be fully ready before interacting with components to avoid race conditions. Use
visit_fixturehelper which visits the URL and waits for the component with given ID to initialize before continuing with the test:session |> visit_fixture("/some-page", "#my-combobox") |> click(Query.css("#my-combobox input")) |> assert_has(Query.css("#my-combobox [role=option]"))
Why this is needed: JavaScript hooks need time to set up event listeners after page load. The
visit_fixture/2helper waits for thedata-prima-ready="true"attribute that hooks set when fully initialized. -
Arrow key navigation: Use correct Wallaby syntax for arrow keys in tests:
# Correct syntax: |> send_keys([:down_arrow]) |> send_keys([:up_arrow]) |> send_keys([:left_arrow]) |> send_keys([:right_arrow]) # Incorrect (doesn't work): |> send_keys([:arrow_down]) # Wrong |> send_keys([:arrow_up]) # Wrong
-
JavaScript debugging: Enable JS console logging in a test by adding
Application.put_env(:wallaby, :js_logger, :stdio)at the beginning of the test feature. This allows you to seeconsole.log()output from JavaScript hooks during test execution. More convenient than modifying config files:feature "my test with logging", %{session: session} do Application.put_env(:wallaby, :js_logger, :stdio) session |> visit("/some-page") # ... rest of test end
- NEVER use sleep or setTimout in tests. It's okay for temporary debugging but all tests that are kept and checking in eventually need to work without artificial waiting
Before writing any code, batch read these files in ONE message:
demo/lib/demo_web/router.exdemo/lib/demo_web/live/fixtures_live.exdemo/lib/demo_web/live/fixtures_live.html.heex- A similar existing fixture file
Then create ALL required files in this order:
- Fixture HTML file (
.html.heex) - Helper functions in
fixtures_live.ex(if needed) - Route in
router.ex - Template entry in
fixtures_live.html.heex - Test file last
This prevents test failures from missing infrastructure.
- Remember to not add unnecessary comments
- Only add comments to document tricky operations or add additional context
- Pedantic comments are useless
- Use conventional commit format:
type: description - Types:
feat,fix,refactor,test,docs,chore - Keep first line under 72 characters
- Use present tense ("Add feature" not "Added feature")
- Reference issue numbers when applicable
feat: add keyboard navigation to dropdown component
fix: resolve focus trap issue in modal overlay
test: add Wallaby tests for combobox selection
refactor: simplify hook initialization logic
docs: update component usage examples
- Focus on the "why" rather than the "what"
- Separate library changes from demo changes
- Don't include the Claude Code attribution footer
- Always run
mix formatbefore committing