.vbw-planning/milestones/07-rails-audit-and-refactoring/05-view-layer-extraction/01-PLAN.md
Extract ~50 lines of inline view logic from _details.html.erb (362 lines) into a SourceDetailsPresenter (V14). The template currently builds hashes with conditional formatting, number_with_precision calls, date formatting, and ternary operators -- all of which belong in a presenter.
app/views/source_monitor/sources/_details.html.erb (362 lines) -- largest partial, contains inline formatting logic (lines 177-230)sources_controller.rb -- instantiate the presenter inside _details.html.erb partial to avoid file conflicts with Plan 02 which modifies the controller's index actionCheck if app/presenters/source_monitor/base_presenter.rb exists. If not, create it (SimpleDelegator with ActionView helper includes).
Create test/presenters/source_monitor/source_details_presenter_test.rb:
fetch_interval_display returns formatted string like "30 minutes (~0.50 hours)"circuit_state_label returns "Closed" when circuit not open, "Open until {date}" when openadaptive_interval_label returns "Auto" or "Fixed"details_hash returns a Hash with expected keys (Website, Fetch interval, Adaptive interval, Scraper, etc.)formatted_next_fetch_at handles nil and present timestampsformatted_last_fetched_at handles nil and present timestampscreate_source! factory from existing test helpersCreate app/presenters/source_monitor/source_details_presenter.rb:
SourceMonitor::SourceDetailsPresenter < SourceMonitor::BasePresenter_details.html.erb lines 177-230: fetch_interval_display, circuit_state_label, adaptive_interval_labeldetails_hash method that builds the hash currently inlined in the templatenumber_with_precision, conditional ternaries, and strftimeModify app/views/source_monitor/sources/_details.html.erb:
<% presenter = SourceMonitor::SourceDetailsPresenter.new(source) %>presenter.details_hash callsnumber_with_precision(source.fetch_interval_minutes / 60.0, ...) with presenter.fetch_interval_displaypresenter.circuit_state_labelsources_controller.rb (Plan 02 modifies it for index action)bin/rails test test/presenters/source_monitor/source_details_presenter_test.rb -- all passbin/rails test test/controllers/source_monitor/sources_controller_test.rb -- all passbin/rails test -- full suite passesbin/rubocop -- zero offenses