From ab1f29269e60e742d88a5bf53f7e5558b2758365 Mon Sep 17 00:00:00 2001 From: bill Date: Mon, 8 Jun 2026 20:14:19 -0600 Subject: [PATCH] Update handoff docs for completed engine layer Mark engine as done in overview and build order. Update next-steps to focus on test gap analysis. Add engine file map, data flow, and dev setup quirks (Zeitwerk conflict, engine routes conflict) to architecture. Co-Authored-By: Claude Opus 4.8 --- handoff/architecture.md | 64 +++++++++++++++++++++++++++++++++- handoff/next-steps.md | 76 +++++++++++++---------------------------- handoff/overview.md | 9 +++-- 3 files changed, 91 insertions(+), 58 deletions(-) diff --git a/handoff/architecture.md b/handoff/architecture.md index 57626fe..77dceb5 100644 --- a/handoff/architecture.md +++ b/handoff/architecture.md @@ -4,13 +4,28 @@ ``` lib/ - whetrails.rb Public entry point. Exposes Whetrails.inspect_model(ModelClass) + whetrails.rb Public entry point. Requires engine if Rails is loaded. whetrails/ version.rb VERSION constant (0.1.0) chain.rb Data structures (ModelChain, CallbackEntry, ValidatorEntry) inspector.rb Core engine — reads Rails internals, builds ModelChain formatter.rb Terminal renderer — formats ModelChain as plain text cli.rb CLI logic — boots Rails, resolves model name, calls inspector + engine.rb Rails::Engine definition (isolate_namespace Whetrails) + engine_routes.rb Engine routes — resources :models + root + +app/ + controllers/whetrails/ + application_controller.rb Base: before_action loads all user-defined AR models + models_controller.rb index (redirect to first model), show (render chain) + helpers/whetrails/ + models_helper.rb vscode_uri, short_path, condition_label, validator_type_label + views/ + layouts/whetrails/ + application.html.erb Layout: sidebar model list + main content area + whetrails/models/ + index.html.erb Empty — index action always redirects + show.html.erb Lifecycle timeline + validators table exe/ whetrails Executable entry point — calls Whetrails::CLI.run(ARGV) @@ -22,6 +37,8 @@ app/models/order.rb Development fixture model with sample callbacks/ ## Data flow +### CLI + ``` exe/whetrails → CLI#run_inspect @@ -40,6 +57,23 @@ exe/whetrails → Formatter#render prints to stdout ``` +### Engine (browser UI) + +``` +GET /whetrails/ + → ModelsController#index + → eager_load! + discover user models via Object.const_source_location + → redirect_to first model + +GET /whetrails/models/:id (:id is the model name, e.g. "Order") + → ModelsController#show + → params[:id].constantize → model class + → Inspector#inspect_model → ModelChain + → renders show.html.erb: lifecycle timeline grouped by event, + each callback shows filter name, vscode:// source link, condition badges, + module badge if defined in a concern +``` + ## Key Rails API details These are the actual Rails internals the inspector uses — documented here because they are @@ -88,6 +122,34 @@ validator.options[:if] # => conditions (NOT instance variables — different validator.options[:unless] ``` +## Dev setup quirks + +This repo is both the gem source and the development Rails app. That creates two issues +that were solved and should not be undone: + +**1. Zeitwerk autoloading conflict** + +`config/application.rb` has `config.autoload_lib(ignore: %w[assets tasks whetrails.rb whetrails])`. +The `whetrails.rb` and `whetrails` entries are critical: without them, Zeitwerk tries to +autoload `lib/whetrails/chain.rb` as `Whetrails::Chain` (wrong constant name), crashing on +every request. The gem files use manual `require_relative` — they must not be Zeitwerk-autoloaded. + +**2. Engine routes conflict** + +`lib/whetrails/engine.rb` overrides the engine's routing path: +```ruby +config.paths.add "config/routes.rb", with: "lib/whetrails/engine_routes.rb" +``` +Without this, Rails uses `config/routes.rb` as the engine's routes file (same directory), +which causes `mount Whetrails::Engine` to execute twice and fail with a duplicate route name error. +Engine routes live in `lib/whetrails/engine_routes.rb` instead. + +**3. Gem not in Gemfile by default** + +The gem is listed as `gem "whetrails", path: "."` in `Gemfile`. This is intentional — +`Bundler.require` must explicitly load the gem so `Whetrails::Engine` is defined before +`config/routes.rb` tries to mount it. + ## Known limitations - **Proc source location**: Proc-based callbacks (e.g. `before_save { do_something }`) report diff --git a/handoff/next-steps.md b/handoff/next-steps.md index 6a3a93c..1a65a15 100644 --- a/handoff/next-steps.md +++ b/handoff/next-steps.md @@ -1,64 +1,34 @@ # Next Steps -## Immediate: Rails Engine (browser UI) +## Immediate: Test Gap Analysis -Mount a UI at `/whetrails` inside the host Rails app. A developer adds the gem to their -Gemfile, mounts the engine in `config/routes.rb`, starts the server, and visits `/whetrails`. +The engine is done. The next layer uses the extracted chain as input to find uncovered +callbacks and validators. -### What the UI needs to show +### How it works -- A list of all models in the app (left sidebar or index page) -- Click a model → full chain view (same data as CLI, but visual) -- Lifecycle timeline: each stage as a row, callbacks as nodes within it -- Source file/line as a clickable link (opens in editor via `vscode://` URI scheme) -- Conditions rendered inline (`if: :paid?` shown as a badge) -- Callbacks from concerns/gems visually distinguished from model-defined ones - -### Technical approach for the engine - -```ruby -# lib/whetrails/engine.rb -module Whetrails - class Engine < ::Rails::Engine - isolate_namespace Whetrails - end -end -``` - -Rails engine conventions: -- Routes go in `config/routes.rb` inside the gem -- Controllers in `app/controllers/whetrails/` -- Views in `app/views/whetrails/` -- The host app mounts it: `mount Whetrails::Engine, at: "/whetrails"` - -The engine controllers call `Whetrails::Inspector` directly — no duplication of logic. - -### Model discovery - -The engine index page needs to list all AR models in the host app: - -```ruby -Rails.application.eager_load! -models = ApplicationRecord.descendants # or ActiveRecord::Base.descendants -``` - -## After engine: Test Gap Analysis - -Once the chain is extracted, gap analysis is: - -1. Run the test suite with SimpleCov (or equivalent) to get line coverage data -2. For each callback method in the chain, check whether its source lines are covered -3. For each validator, check whether the validation path is exercised +1. Run the host app's test suite with SimpleCov to get line coverage data +2. For each `CallbackEntry` in the `ModelChain`, check whether `source_file:source_line` + is covered in the SimpleCov report +3. For each `ValidatorEntry`, check whether the validation path is exercised 4. Report: "these 3 callbacks have no test coverage" -The chain data structure (`ModelChain`) is already the right shape for this — each -`CallbackEntry` has `source_file` and `source_line`, which can be cross-referenced -against SimpleCov's coverage report. +The `ModelChain` data structure is already the right shape — each `CallbackEntry` has +`source_file` and `source_line`, ready to cross-reference against SimpleCov output. -## Product / distribution +### Integration point -- Publish to RubyGems.org once the engine is working -- The gem should be dev/test-only (add to Gemfile under `group :development`) +Surface this in the engine UI: add a "Coverage" column to each callback row, showing +green/red/unknown based on whether SimpleCov data is available and whether the line +is covered. + +SimpleCov writes JSON to `coverage/.resultset.json` after a test run. Parse that file +directly — no need to instrument the test suite beyond what SimpleCov already does. + +## After gap analysis: distribution + +- Publish to RubyGems.org +- The gem should be dev/test-only (add under `group :development`) - Pricing model TBD — could be open source with a paid cloud version, or MIT with a commercial license for teams @@ -70,3 +40,5 @@ against SimpleCov's coverage report. - Dev database: SQLite at `storage/development.sqlite3` - Gitea running in Docker on localhost:3000 (container name: `gitea`) - Gitea API token for `bill`: `c2948775b80bcaa0ba65c6359658d0d48c340c4e` +- Dev server runs on port 3100 (`bin/rails server -p 3100`) +- Visit `http://localhost:3100/whetrails` to see the engine UI diff --git a/handoff/overview.md b/handoff/overview.md index e7fcce0..683a595 100644 --- a/handoff/overview.md +++ b/handoff/overview.md @@ -10,20 +10,19 @@ This is the first half of a two-part tool. The second half (not yet built) uses ## Current state (as of 2026-06-08) -Two layers are complete: +Three layers are complete: 1. **Core inspector** — extracts the full chain from any ActiveRecord model class at runtime 2. **CLI** — `whetrails inspect ` boots the host Rails app and renders the chain to the terminal - -The next layer (Rails engine / browser UI) has not been started. +3. **Rails engine** — browser UI mounted at `/whetrails`, sidebar model list, lifecycle timeline with vscode source links ## Build order ``` [DONE] 1. Gem core lib/whetrails/inspector.rb [DONE] 2. CLI exe/whetrails + lib/whetrails/cli.rb -[NEXT] 3. Rails engine browser UI mounted at /whetrails - 4. Test gap layer uses chain data to find uncovered callbacks/validators +[DONE] 3. Rails engine browser UI mounted at /whetrails +[NEXT] 4. Test gap layer uses chain data to find uncovered callbacks/validators ``` ## Repository