l4d.tools/.github/copilot-instructions.md
CroneKorkN 987f3c08cc
Some checks failed
CI / scan_ruby (push) Has been cancelled
CI / scan_js (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
CI / system-test (push) Has been cancelled
.github/copilot-instructions.md: update
2026-01-18 16:21:47 +01:00

290 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
description: 'L4D Tools - Early-stage Rails 8.1 app with Solid Queue, Hotwire, Kamal deployment'
applyTo: '**/*.rb'
---
# L4D Tools - AI Coding Guide
## Project Overview
**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
### Setup & Running
```bash
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
```
### Testing
```bash
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
```
### 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:migrate # Apply pending migrations
bin/rails db:rollback # Revert last migration step
bin/rails db:reset # Wipe dev DB and re-run schema + seeds
```
### 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 - 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 + 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
<!-- In view: -->
<div data-controller="search">
<input data-search-target="input" data-action="input->search#search">
</div>
```
### 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/`
- **Integration Tests**: `test/integration/`
- **System Tests**: Full browser tests via Capybara/Selenium
- **Parallelization**: Enabled by default; workers scale to CPU count
- **Fixtures**: Auto-loaded and available as lowercase underscore-separated variables (e.g., `posts(:one)`)
## Project-Specific Guidelines
### 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
- 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
### 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 (`<turbo-frame>`) 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
# app/jobs/my_job.rb
class MyJob < ApplicationJob
queue_as :default
def perform(arg1, arg2)
# async work here
end
end
# Trigger: MyJob.perform_later(arg1, arg2)
# Test: MyJob.perform_now(arg1, arg2)
```
### Model Scopes & Queries
```ruby
# app/models/post.rb
class Post < ApplicationRecord
scope :published, -> { where(published: true) }
scope :recent, -> { order(created_at: :desc) }
def self.active; where(active: true); end
end
# Usage: Post.published.recent
```
### Stimulus Controller
```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
<!-- View: -->
<div data-controller="search">
<input type="text" data-search-target="input" data-action="input->search#search">
</div>
```
### Test Pattern (Minitest)
```ruby
# test/models/post_test.rb
class PostTest < ActiveSupport::TestCase
fixtures :posts # Auto-loads test/fixtures/posts.yml
test "post is valid" do
assert posts(:one).valid?
end
end
```
## Security & Production Readiness
- Brakeman scans detect security issues; fix all warnings before merge
- Bundler-audit tracks vulnerable gems; update versions regularly
- Strong parameters required for all user input in controllers
- RAILS_MASTER_KEY (from `config/master.key`) must be set in Docker via secrets
- 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