l4d.tools/.github/copilot-instructions.md
2026-01-18 17:42:32 +01:00

13 KiB
Raw Blame History

description applyTo
L4D Tools - Early-stage Rails 8.1 app with Solid Queue, Hotwire, Kamal deployment **/*.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

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

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

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)

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:
    // 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) }
    }
    
    <!-- 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 consoleSolidQueue::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)
  • Template Engine: Use slim for views; prefer partials for reusable components

Job Enqueue (Solid Queue)

# 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

# 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

// 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)
  }
}
<!-- View: -->
<div data-controller="search">
  <input type="text" data-search-target="input" data-action="input->search#search">
</div>

Test Pattern (Minitest)

# 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