From 3aeba74ee49ed74148cad9ca0f094d5c13a486f3 Mon Sep 17 00:00:00 2001 From: Chuck Smith Date: Wed, 27 May 2026 07:05:11 -0400 Subject: [PATCH] feat: engine-scoped 404 and 500 error pages rescue_from ActiveRecord::RecordNotFound always renders the engine's 404 view within the dashboard chrome. rescue_from StandardError renders the engine's 500 view in production; re-raises in development so the Rails debug page is preserved. Declaration order ensures the more-specific RecordNotFound handler takes precedence over StandardError. Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 1 + ROADMAP.md | 1 - .../solid_stack_web/application_controller.rb | 15 +++++++ .../errors/internal_server_error.html.erb | 8 ++++ .../solid_stack_web/errors/not_found.html.erb | 8 ++++ spec/requests/solid_stack_web/errors_spec.rb | 40 +++++++++++++++++++ 6 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 app/views/solid_stack_web/errors/internal_server_error.html.erb create mode 100644 app/views/solid_stack_web/errors/not_found.html.erb create mode 100644 spec/requests/solid_stack_web/errors_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 26fdd04..4dec936 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Engine-scoped error pages — 404 (`ActiveRecord::RecordNotFound`) and 500 (unhandled `StandardError` in production) now render within the dashboard chrome instead of falling through to the host app's error pages; in development `consider_all_requests_local` keeps the standard Rails debug page - Covering indexes added to dummy app schema — `solid_queue_jobs (finished_at, created_at)` for the slow-job scan; `(queue_name, created_at)` on `solid_queue_scheduled_executions` and `solid_queue_blocked_executions` (both previously lacked a queue-name index) - Install generator — `rails generate solid_stack_web:install` creates `config/initializers/solid_stack_web.rb` with every config option documented inline and injects the mount line into `config/routes.rb` - `SolidStackWeb.mount_path` — returns the path at which the engine is mounted in the host app, derived automatically from routes; use `link_to "Dashboard", SolidStackWeb.mount_path` to link to the dashboard without hardcoding the path diff --git a/ROADMAP.md b/ROADMAP.md index 003cfc1..83b6ab7 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -11,7 +11,6 @@ The path to v1.0.0 is staged: first achieve feature parity with `solid_queue_das > _Make it easy to adopt and easy to contribute to._ ### Remaining -- **Error pages** — engine-scoped 404/500 views so errors stay within the dashboard chrome - **Changelog-driven upgrade notes** — `UPGRADING.md` for any breaking configuration changes --- diff --git a/app/controllers/solid_stack_web/application_controller.rb b/app/controllers/solid_stack_web/application_controller.rb index 965974d..6b9810b 100644 --- a/app/controllers/solid_stack_web/application_controller.rb +++ b/app/controllers/solid_stack_web/application_controller.rb @@ -9,6 +9,13 @@ class ApplicationController < ActionController::Base before_action :authenticate! around_action :with_database_connection + rescue_from StandardError do |exception| + raise exception if Rails.application.config.consider_all_requests_local + render_internal_server_error + end + + rescue_from ActiveRecord::RecordNotFound, with: :render_not_found + helper_method :current_section private @@ -43,5 +50,13 @@ def authenticate! def request_basic_auth request_http_basic_authentication("Solid Stack Dashboard") end + + def render_not_found + render "solid_stack_web/errors/not_found", status: :not_found + end + + def render_internal_server_error + render "solid_stack_web/errors/internal_server_error", status: :internal_server_error + end end end diff --git a/app/views/solid_stack_web/errors/internal_server_error.html.erb b/app/views/solid_stack_web/errors/internal_server_error.html.erb new file mode 100644 index 0000000..2764909 --- /dev/null +++ b/app/views/solid_stack_web/errors/internal_server_error.html.erb @@ -0,0 +1,8 @@ +
+

Something Went Wrong

+
+ +
+

500 — An unexpected error occurred.

+

<%= link_to "Back to Dashboard", root_path, class: "sqw-btn sqw-btn--secondary" %>

+
\ No newline at end of file diff --git a/app/views/solid_stack_web/errors/not_found.html.erb b/app/views/solid_stack_web/errors/not_found.html.erb new file mode 100644 index 0000000..02b7333 --- /dev/null +++ b/app/views/solid_stack_web/errors/not_found.html.erb @@ -0,0 +1,8 @@ +
+

Not Found

+
+ +
+

404 — The record you’re looking for doesn’t exist or has been removed.

+

<%= link_to "Back to Dashboard", root_path, class: "sqw-btn sqw-btn--secondary" %>

+
\ No newline at end of file diff --git a/spec/requests/solid_stack_web/errors_spec.rb b/spec/requests/solid_stack_web/errors_spec.rb new file mode 100644 index 0000000..239afce --- /dev/null +++ b/spec/requests/solid_stack_web/errors_spec.rb @@ -0,0 +1,40 @@ +require "rails_helper" + +RSpec.describe "Error pages", type: :request do + let(:engine_root) { "/solid_stack" } + + describe "404 Not Found" do + it "renders the engine 404 page when a record is not found" do + get "#{engine_root}/jobs/99999999" + expect(response).to have_http_status(:not_found) + expect(response.body).to include("Not Found") + expect(response.body).to include("404") + end + + it "renders within the dashboard chrome" do + get "#{engine_root}/jobs/99999999" + expect(response.body).to include("sqw-header") + expect(response.body).to include("Back to Dashboard") + end + end + + describe "500 Internal Server Error" do + before do + allow(Rails.application.config).to receive(:consider_all_requests_local).and_return(false) + allow_any_instance_of(SolidStackWeb::JobsController).to receive(:index).and_raise(StandardError, "test error") + end + + it "renders the engine 500 page on an unhandled error" do + get "#{engine_root}/jobs" + expect(response).to have_http_status(:internal_server_error) + expect(response.body).to include("Something Went Wrong") + expect(response.body).to include("500") + end + + it "renders within the dashboard chrome" do + get "#{engine_root}/jobs" + expect(response.body).to include("sqw-header") + expect(response.body).to include("Back to Dashboard") + end + end +end