diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index 2461085..87b32b6 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -1,5 +1,5 @@
---
-description: 'L4D Tools - Rails 8.1 application with Solid Queue, Hotwire stack'
+description: 'L4D Tools - Early-stage Rails 8.1 app with Solid Queue, Hotwire, Kamal deployment'
applyTo: '**/*.rb'
---
@@ -7,70 +7,99 @@ applyTo: '**/*.rb'
## Project Overview
-**l4d_tools** is a Rails 8.1 application using modern Rails conventions. Key architectural decisions:
-- **Database**: SQLite (development), supports production deployment via Kamal/Docker
-- **Job Processing**: Solid Queue (replaces Sidekiq) - background jobs embedded with app via `:solid_queue` plugin in Puma
-- **Frontend Stack**: Hotwire (Turbo + Stimulus) with importmap-rails and Propshaft
-- **Testing**: Minitest with Capybara for system tests (parallelized workers)
-- **Deployment**: Kamal-based Docker containerization with private registry support
-- **Security Scanning**: Brakeman (security), Rubocop-rails-omakase (style), Bundler-audit (gems)
+**l4d_tools** is a fresh Rails 8.1 application in early development stage. No domain models exist yet—focus is on establishing clean patterns for future features.
+
+**Core Stack:**
+- **Framework**: Rails 8.1 (modern defaults: modern browser support, Propshaft asset pipeline)
+- **Database**: SQLite with WAL mode (development); production supports Kamal/Docker deployment
+- **Jobs**: Solid Queue (embedded in Puma via `plugin :solid_queue` when `SOLID_QUEUE_IN_PUMA=true`)
+- **Frontend**: Hotwire (Turbo + Stimulus) + importmap-rails (no build step)
+- **Testing**: Minitest + Capybara with parallel workers
+- **Deployment**: Kamal (Docker/container-based) with private registry at `localhost:5555`
+- **Security**: Brakeman, Rubocop-rails-omakase, Bundler-audit
## Critical Developer Workflows
-### Initial Setup
+### Setup & Running
```bash
-bin/setup # Installs gems, prepares DB, starts server
-bin/setup --reset # Full reset including DB wipe
-bin/setup --skip-server # Setup without starting server
+bin/setup # Install gems, prepare SQLite DB, start server on :3000
+bin/dev # Start Rails dev server (invokes bin/rails server)
+bin/rails console # Interactive REPL with full app context
```
-### Development Commands
+### Testing
```bash
-bin/dev # Start Rails server (port 3000)
-bin/rails console # REPL with app context
-bin/rails test # Full test suite (parallel workers)
-bin/rails test test/path # Specific test file/directory
-bundle exec brakeman # Security vulnerability scan
-bundle exec rubocop -a # Auto-fix style violations
+bin/rails test # Full test suite (auto-parallelized by CPU cores)
+bin/rails test test/models/post_test.rb # Single test file
+bin/rails test test/models/post_test.rb:42 # Specific test by line number
```
-### Database Management
+### Code Quality
+```bash
+bundle exec brakeman # Security scanning; fix ALL warnings before merge
+bundle exec rubocop -a # Auto-fix style issues (rails-omakase rules)
+bundle exec bundler-audit # Check gem vulnerabilities; update outdated gems
+```
+
+### Database (SQLite WAL mode)
```bash
-bin/rails db:prepare # Create/migrate DB
-bin/rails db:reset # Wipe and re-seed
bin/rails db:migrate # Apply pending migrations
-bin/rails db:rollback # Revert last migration
+bin/rails db:rollback # Revert last migration step
+bin/rails db:reset # Wipe dev DB and re-run schema + seeds
```
-### Deployment (Kamal/Docker)
-- **Config**: `config/deploy.yml` - registry, servers, environment variables
-- **Secrets**: `.kamal/secrets` file (contains RAILS_MASTER_KEY)
-- **Registry**: Private registry at localhost:5555 for deployment target
-- **Job Processing**: `SOLID_QUEUE_IN_PUMA=true` runs supervisor in web process (single-server only)
-- **Build**: `docker build -t l4d_tools .` builds production image
+### Deployment (Kamal)
+- **Config**: `config/deploy.yml` (servers, registry endpoint, environment variables)
+- **Build**: Auto-built by Kamal; Dockerfile uses multi-stage build
+- **Registry**: Private Docker registry at `localhost:5555` (configured in deploy.yml)
+- **Secrets**: Set `RAILS_MASTER_KEY` env var via Kamal secrets or system env
+- **Job Queue**: `SOLID_QUEUE_IN_PUMA=true` embeds queue supervisor in Puma (single-server deployments only)
## Architecture & Key Components
-### Job Processing (Solid Queue)
-- **Location**: `app/jobs/`
-- **Pattern**: Inherit from `ApplicationJob < ActiveJob::Base`
-- **Configuration**: `config/queue.yml` defines adapters and queues
-- **Execution**: Jobs run synchronously in test mode; async in development/production
-- **Puma Integration**: `plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"]` in `config/puma.rb`
-- **Usage**: `MyJob.perform_later(args)` for async; `MyJob.perform_now(args)` for testing
+### Job Processing (Solid Queue - no separate Redis/Sidekiq)
+- **Store**: Database-backed; data lives in Solid Queue tables (queue_schema.rb)
+- **Location**: `app/jobs/` - inherit from `ApplicationJob < ActiveJob::Base`
+- **Configuration**: `config/queue.yml` defines:
+ - `threads: 3` - worker threads per process
+ - `processes: JOB_CONCURRENCY` env var - defaults to 1 (scale via env)
+ - `polling_interval: 0.1` - how frequently workers check for jobs
+- **Enqueue**: `MyJob.perform_later(args)` for async; `MyJob.perform_now(args)` for testing/immediate
+- **Puma Integration**: `plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"]` in `config/puma.rb` runs supervisor in same process (single-server deployments only)
+- **In Tests**: Jobs run synchronously (development mode behavior)
+- **Important**: No Sidekiq/Redis—Solid Queue is database-backed and built into Puma
-### Frontend (Hotwire)
-- **Turbo**: Rapid page transitions via AJAX without full reloads
-- **Stimulus**: Minimal DOM binding framework; controllers in `app/javascript/controllers/`
-- **Asset Pipeline**: Propshaft + importmap-rails; no build step needed
-- **Entry Point**: `app/javascript/application.js` loads all controllers
-- **Naming**: `data-controller="hello"` maps to `HelloController` in `app/javascript/controllers/hello_controller.js`
+### Frontend (Hotwire + Importmap, no build step)
+- **Turbo**: Handles navigation via AJAX (replaces full page loads); auto-loads from `data-turbo=true`
+- **Stimulus**: Zero-config JavaScript binding via `data-controller="name"` → `NameController` in `app/javascript/controllers/name_controller.js`
+- **Asset Pipeline**: Propshaft (modern successor to Sprockets) + importmap-rails
+ - CSS: All files in `app/assets/stylesheets/` auto-loaded via `stylesheet_link_tag :app`
+ - JS: Controllers auto-discovered; no webpack/bundler required
+ - Entry point: `app/javascript/application.js` imports controllers and libraries
+- **Example Stimulus binding**:
+ ```javascript
+ // app/javascript/controllers/search_controller.js
+ import { Controller } from "@hotwired/stimulus"
+ export default class extends Controller {
+ static targets = ["input"]
+ search() { console.log(this.inputTarget.value) }
+ }
+ ```
+ ```erb
+
+
+
+
+ ```
-### Database & Models
-- **Schema**: `db/schema.rb` auto-generated from migrations (commit to VCS)
-- **Migrations**: Generate via `bin/rails generate migration CreateTableName`
-- **Patterns**: Use model scopes for complex queries; keep migrations simple and reversible
-- **Fixtures**: YAML files in `test/fixtures/*.yml` auto-loaded by Minitest
+### Database & Migrations (SQLite WAL)
+- **Schema**: `db/schema.rb` auto-generated; commit to git (tracks schema evolution)
+- **Migrations**: Generate via `bin/rails generate migration DescriptiveNameHere`
+ - Use `change` method for reversible migrations (preferred)
+ - Always add indexes for foreign keys
+ - Test rollback: `bin/rails db:rollback` verifies reversibility
+- **SQLite Limits**: AUTOINCREMENT primary keys, pragma constraints, no ALTER COLUMN
+- **Models**: No domain models exist yet; structure will be pattern-driven once first models are created
### Testing Structure
- **Unit Tests**: `test/models/`, `test/helpers/`, `test/controllers/`
@@ -81,18 +110,70 @@ bin/rails db:rollback # Revert last migration
## Project-Specific Guidelines
-- Follow RuboCop-rails-omakase style guide strictly; run `rubocop -a` to auto-fix
+### Early Stage Patterns (No Domain Models Yet)
+- **Start with migrations**: Define database schema first; models follow naturally
+- **Keep ApplicationRecord/ApplicationJob clean**: These are templates for future models/jobs
+- **Use concerns sparingly**: Extract concerns only after 2+ models need the same behavior
+- **Establish directory structure early**: Create `app/services/`, `app/presenters/` if needed for future patterns
+
+### Code Quality & Style
+- Follow RuboCop-rails-omakase style guide strictly; run `bundle exec rubocop -a` to auto-fix
- Use snake_case for variables/methods, CamelCase for classes
- Extract reusable business logic into `app/services/` to keep models lean
-- Use Solid Queue `perform_later` for background work; never block HTTP requests
-- Prefer Turbo Frames for dynamic UI updates over full page reloads
+- Keep `config/routes.rb` simple; add routes only when adding controller actions
- Write tests using fixtures; auto-loaded into test instance variables
- Use `bin/rails test` to run full suite with parallel workers
-- Keep `config/routes.rb` simple; add routes only when adding controller actions
-- Store secrets in `Rails.application.credentials` (encrypted via `master.key`)
-- Debug with `byebug` or Rails logger; avoid `puts` for production debugging
-## Common Patterns
+### Performance & Background Jobs
+- Use Solid Queue `perform_later` for background work; never block HTTP requests
+- Set `JOB_CONCURRENCY` env var to scale job processing beyond default (1 process × 3 threads)
+- Test jobs with `MyJob.perform_now` in test env; they run synchronously
+- Monitor job failures in `bin/rails console`: `SolidQueue::Job.where(status: :failed)`
+
+### Frontend & UI
+- Prefer Turbo Frames (``) for dynamic UI updates over full page reloads
+- Always set `data-turbo="false"` on forms that should NOT use Turbo (e.g., file uploads, legacy forms)
+- Stimulus controllers are zero-config; name them kebab-case, file them snake_case
+- Test JavaScript with system tests; Capybara + Selenium support Turbo navigation
+
+### Secrets & Credentials
+- Store all secrets in `Rails.application.credentials` (encrypted via `master.key`)
+- Never commit `config/master.key`; set `RAILS_MASTER_KEY` env var in production
+- Use `bin/rails credentials:edit` to add new secrets
+- Development uses `.key` file automatically; test/production use env variable
+
+### Debugging
+- Debug with `byebug` or Rails logger; avoid `puts` for production debugging
+- Use `Rails.logger.info("...")` for production-safe logging
+- Inspect Solid Queue tables directly: `bin/rails console` → `SolidQueue::Job.last(10)`
+
+## Integration Points & Data Flows
+
+### HTTP Request Lifecycle
+1. **Routing**: `config/routes.rb` → Controller action
+2. **Parameter Validation**: Use `strong_parameters` in ApplicationController (already configured)
+3. **Business Logic**: Execute in service objects or job queues, not controllers
+4. **Response**: Render ERB template (with Turbo) or JSON (if API)
+5. **Browser**: Turbo replaces page or frame; Stimulus controllers initialize
+
+### Database Access Patterns
+- **Queries**: Use model scopes and class methods (not raw SQL)
+- **Write Operations**: Controller delegates to service → model (transaction boundaries)
+- **Background Jobs**: Solid Queue tables store jobs; accessed via ActiveJob interface
+- **Migrations**: Always reversible; schema changes are version-controlled in `db/schema.rb`
+
+### Job Queue Integration
+1. **Enqueue**: `MyJob.perform_later(id)` in controller/service
+2. **Polling**: Solid Queue supervisor (in Puma) polls for jobs every 0.1s
+3. **Execution**: Worker thread picks job, calls `perform(id)`
+4. **Persistence**: Results/errors logged in job records; failures retryable
+5. **Monitoring**: Check status via `SolidQueue::Job` model in console
+
+### Asset & Static File Pipeline
+- **CSS**: All files in `app/assets/stylesheets/` auto-included via `stylesheet_link_tag :app`
+- **JS**: Controllers auto-discovered from `app/javascript/controllers/`; no bundling required
+- **Images**: Reference via `image_tag "name"` (fingerprinted in production)
+- **PWA**: Service worker template available in `app/views/pwa/` (commented out in routes)
### Job Enqueue (Solid Queue)
```ruby
@@ -164,3 +245,46 @@ end
- SQLite adequate for single-server; use PostgreSQL for multi-server scaling
- Content Security Policy configured in `config/initializers/content_security_policy.rb`
- Never commit `master.key` or `.kamal/secrets`; set RAILS_MASTER_KEY via environment
+
+## Key Directory Structure
+
+- `app/controllers/` - HTTP request handlers; keep thin (validate + delegate to services)
+- `app/models/` - ActiveRecord models; use scopes for queries, keep business logic minimal
+- `app/views/` - ERB templates with Turbo/Stimulus integration
+- `app/javascript/controllers/` - Stimulus controllers for DOM interactivity
+- `app/jobs/` - Solid Queue background jobs; use for time-consuming operations
+- `config/` - Application configuration; routes, database, queue, deployment settings
+- `test/` - Test suite mirroring app structure; fixtures auto-loaded
+- `db/migrations/` - Version-controlled database schema changes
+
+## Common Pitfalls to Avoid
+
+### Early-Stage Mistakes
+- **Don't skip migrations**: Always create reversible migrations with `change` blocks (or up/down)
+- **Don't hard-code job names**: Reference jobs via constants/classes, not strings
+- **Don't mix concerns in models**: Business logic belongs in services; models are data containers
+- **Don't create routes without controllers**: Routes must map to actual controller actions
+
+### Solid Queue Specifics
+- **No Sidekiq/Redis**: Job data is stored in SQLite; don't assume Redis-style operations
+- **Single-server default**: `JOB_CONCURRENCY=1` by default; set env var to scale
+- **Test mode synchronous**: Jobs run immediately in tests; use `perform_now` explicitly if needed
+- **Monitor job failures**: Failed jobs remain in DB; check `SolidQueue::Job.where(status: :failed)`
+
+### Hotwire/Frontend Gotchas
+- **Set data-turbo="false" for file uploads**: Turbo doesn't handle multipart forms well
+- **Use `data-turbo-method="delete"` for non-GET actions**: Turbo expects method hints
+- **Stimulus controllers auto-initialize**: No manual instantiation needed; just use HTML attributes
+- **CSS conflicts with Turbo**: Use scoped styles; avoid global `.active` or `.selected` classes
+
+### SQLite Development
+- **No concurrent migrations**: SQLite locks during schema changes; don't run multiple devs simultaneously
+- **WAL mode enabled**: Provides better concurrency; don't disable without reason
+- **No ALTER COLUMN**: Use temporary tables pattern for complex migrations
+- **Indexes matter**: SQLite can't optimize without them; always index foreign keys
+
+### Deployment & Secrets
+- **RAILS_MASTER_KEY required**: Set env var before Docker startup; app won't boot without it
+- **Registry authentication**: Docker registry at localhost:5555 must be running for Kamal builds
+- **Single-server assumption**: `SOLID_QUEUE_IN_PUMA=true` only works with one web server
+- **Database file location**: SQLite database is in `storage/` directory; ensure volume mounts in Docker