10 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Prerequisites
Before setting up the project, ensure you have the following installed:
- Elixir 1.17+
- Erlang/OTP
- PostgreSQL with PostGIS extension
Project Overview
This is an Elixir Phoenix LiveView application that serves as a real-time APRS (Automatic Packet Reporting System) tracker and visualizer. It connects to the APRS-IS network to receive live amateur radio packets and displays them on an interactive map interface.
Development Commands
Setup
mix setup- Complete project setup (deps.get + ecto.setup)mix deps.get- Install dependenciesmix ecto.setup- Create database, run migrations, and seed datamix ecto.reset- Drop and recreate databasemix phx.server- Start Phoenix server (http://localhost:4000)iex -S mix phx.server- Start server in interactive Elixir shell
Testing
mix test- Run full test suitemix test --stale- Run only tests affected by code changesmix test.watch- Continuous testing with file watchingmix test --cover- Generate test coverage reports
Code Quality
mix format- Format code according to .formatter.exsmix credo- Static code analysis and style checkingmix dialyzer- Static type analysis (must run and fix errors/warnings)mix sobelow- Security vulnerability scanning- CRITICAL: ALWAYS run
mix formatBEFORE committing - never commit unformatted code - MANDATORY: Run
mix compile --warnings-as-errorsand ensure it passes before considering any task complete
Assets (No Node.js Required)
mix assets.deploy- Build and minify frontend assets (Tailwind CSS + ESBuild)- Phoenix uses standalone ESBuild and Tailwind binaries - no npm/yarn needed
- JavaScript bundling handled by ESBuild
- CSS compilation handled by Tailwind CLI
Architecture
Core Components
- Aprsme.AprsIsConnection - TCP connection to APRS-IS network with reconnection logic
- Aprsme.PacketConsumer - Processes incoming APRS packets using GenStage pipeline
- Aprsme.Packet - Database schema for APRS packets with PostGIS geographic data
- AprsmeWeb.MapLive.Index - Main real-time map interface using Phoenix LiveView
- Aprsme.Workers.PacketCleanupWorker - Oban background job for data cleanup
Data Flow
- APRS-IS connection receives packets via TCP
- PacketConsumer processes packets through GenStage pipeline
- Packets stored in PostgreSQL with PostGIS geographic indexing
- LiveView broadcasts real-time updates to connected clients via PubSub
- Background workers handle cleanup and maintenance tasks
Key Dependencies
- Phoenix LiveView for real-time UI without JavaScript
- PostGIS for geographic data storage and spatial queries
- Oban for background job processing
- GenStage for packet processing pipelines
- Tailwind CSS + ESBuild for frontend assets (no Node.js)
Test-Driven Development
MANDATORY: Follow strict test-driven development (TDD) practices:
CRITICAL: When a new feature or bug is introduced, ALWAYS write a test for it FIRST, then write the code to satisfy the test. This is non-negotiable.
- Red Phase: Write failing tests first before implementing any functionality
- Green Phase: Write minimal code to make tests pass
- Refactor Phase: Improve code while keeping tests green
TDD Workflow
- ALWAYS write tests before implementing new features or fixing bugs
- When addressing a bug, first write a test that reproduces the bug (it should fail)
- When adding a feature, first write tests that define the expected behavior
- Start with the simplest failing test case
- Write only enough code to make the test pass
- Refactor with confidence knowing tests will catch regressions
- Run
mix testfrequently during development - Use
mix test.watchfor continuous feedback
Testing Patterns
Tests use comprehensive mocking to prevent external connections:
- APRS-IS connections are mocked in test environment
- Database uses sandbox mode for isolation
- External API calls mocked with Mox library
- Write unit tests for business logic, integration tests for workflows
- Test edge cases and error conditions thoroughly
- Maintain high test coverage with
mix test --cover
Code Style Guidelines
- CRITICAL: Never write production code without tests first
- Use LiveView for UI interactions, minimize JavaScript
- Prefer pattern matching over if/case statements
- Follow idiomatic Elixir conventions
- CRITICAL: ALWAYS run
mix formatBEFORE committing - this is non-negotiable - Address any compiler warnings
- Run
mix dialyzerand fix all errors/warnings - MANDATORY: Run
mix compile --warnings-as-errorsand ensure it passes before considering any task complete - Use function composition over nested conditionals
- Write descriptive test names that explain behavior
Pre-Commit Checklist
CRITICAL: NEVER commit or push code with syntax errors or compilation failures. Always validate before committing.
MANDATORY PRE-COMMIT STEPS - Must be executed in this exact order before EVERY git commit:
-
mix format- ALWAYS run this FIRST before any git operations- This formats all Elixir code according to project standards
- NEVER skip this step - unformatted code should never be committed
-
mix compile --warnings-as-errors- ensure no warnings or compilation errors- Must pass with zero warnings and zero errors
-
mix test- ensure all tests pass (at minimum, ensure no syntax/compilation errors)- At minimum verify no compilation failures even if some tests fail
-
MANDATORY: If any step fails, fix the issues before proceeding
-
Only after ALL checks pass should you commit and push your changes
NEVER PUSH BROKEN CODE: Syntax errors, compilation failures, or basic test failures should be fixed immediately before any git operations. Pushing broken code breaks CI/CD pipelines and wastes deployment resources.
REMEMBER: Always run mix format before every commit - this is non-negotiable and must become automatic habit.
Important Documentation Updates
- MANDATORY: Whenever you implement improvements or changes to the system:
- Update
/CHANGELOG.mdwith:- Add new entries under
[Unreleased]section - Use categories: Added, Changed, Fixed, Removed
- Be specific and user-focused in descriptions
- Add new entries under
- Update
/docs/improvement-todos.mdwith:- Mark completed items as done with the implementation date
- Add any new improvements discovered during implementation
- Update priority levels based on new insights
- Document any technical decisions or trade-offs made
- Update
- This ensures continuity across sessions and helps track progress on system improvements
Web Testing
- MANDATORY: When viewing any website or web application, always use Puppeteer to take screenshots and interact with the page
- Use
mcp__puppeteer__puppeteer_navigate,mcp__puppeteer__puppeteer_screenshot, and other Puppeteer tools - This ensures accurate visual feedback and proper testing of the user interface
Deployment
The application supports Kubernetes deployment with manifests in k8s/ directory and GitHub Actions CI/CD pipeline. Database migrations run automatically via init containers.
Infrastructure
The application runs on a highly available infrastructure:
- Kubernetes: k3s cluster deployed across 3 VMs
- Virtualization: Proxmox VE hosting the VMs
- Hardware: 3 Intel N100 nodes, each with:
- 32GB RAM
- 1TB SSD storage
- Low power consumption (~15W per node)
- Distribution: VMs spread across physical nodes for hardware redundancy
Kubernetes Commands
The app is deployed in a k3s cluster with the following structure:
- App name:
aprs - Namespace:
aprs - Deployment: StatefulSet with 2-3 replicas
- Manifests: Located in
~/dev/infra/clusters/aprs/
Common kubectl commands for debugging:
# Check pod status
kubectl get pods -n aprs
# Get logs from the app
kubectl logs -f deployment/aprs -n aprs
# Get logs from a specific pod
kubectl logs <pod-name> -n aprs
# Describe pod for events and details
kubectl describe pod <pod-name> -n aprs
# Restart the statefulset
kubectl rollout restart statefulset/aprs -n aprs
# Check statefulset status
kubectl rollout status statefulset/aprs -n aprs
# Execute commands in the pod (StatefulSet)
kubectl exec -it aprs-0 -n aprs -- /app/bin/aprsme remote
# Check cluster membership
kubectl exec -it <pod-name> -n aprs -- /app/bin/aprsme eval "Node.list()"
# Check leader status
kubectl exec -it <pod-name> -n aprs -- /app/bin/aprsme eval "Aprsme.Cluster.LeaderElection.is_leader?()"
Clustering Architecture
The application uses distributed Erlang clustering to ensure only one APRS-IS connection across multiple replicas:
-
StatefulSet Deployment:
- Uses Kubernetes StatefulSet for stable pod names (aprs-0, aprs-1, etc.)
- Headless service provides DNS entries for each pod
- Stable network identities enable Erlang distribution
-
Leader Election: Uses
:globalregistry for distributed leader election- Only the elected leader maintains the APRS-IS connection
- Automatic failover when leader goes down
- Leader election managed by
Aprsme.Cluster.LeaderElection
-
Connection Management:
Aprsme.Cluster.ConnectionManagerstarts/stops APRS-IS based on leadership- Uses
DynamicSupervisorto manage connection lifecycle - Prevents duplicate connections and packet processing
-
Cluster Configuration:
- Uses
libclusterwith Kubernetes.DNS strategy - Automatic node discovery via headless service
- Erlang cookie configured via RELEASE_COOKIE environment variable
- Environment variables:
CLUSTER_ENABLED=true- Enables clusteringRELEASE_NODE- Erlang node nameRELEASE_COOKIE- Erlang distribution cookie
- Uses
-
Deployment:
- Default replicas: 3 (configurable in
aprs-deployment.yaml) - Only leader processes APRS packets
- All nodes serve web traffic
- Default replicas: 3 (configurable in
1Password Integration
When working with passwords and secrets:
- ALWAYS use 1Password account ID:
YOOATCZZSVGH7AD6VABUVPORLI - Store all passwords, API keys, and secrets in 1Password
- Use the
opCLI tool for programmatic access - Never hardcode passwords in configuration files