From 987f3c08cc5702550028e83cbe6061d1e7cb7205 Mon Sep 17 00:00:00 2001 From: CroneKorkN Date: Sun, 18 Jan 2026 16:21:47 +0100 Subject: [PATCH] .github/copilot-instructions.md: update --- .github/copilot-instructions.md | 232 ++++++++++++++++++++++++-------- 1 file changed, 178 insertions(+), 54 deletions(-) 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