Module: Tina4::ErrorOverlay
- Defined in:
- lib/tina4/error_overlay.rb
Constant Summary collapse
- BG =
── Colour palette (Catppuccin Mocha) ──────────────────────────────
"#1e1e2e"- SURFACE =
"#313244"- OVERLAY_COLOR =
"#45475a"- TEXT_COLOR =
"#cdd6f4"- SUBTEXT =
"#a6adc8"- RED =
"#f38ba8"- YELLOW =
"#f9e2af"- BLUE =
"#89b4fa"- GREEN =
"#a6e3a1"- LAVENDER =
"#b4befe"- PEACH =
"#fab387"- ERROR_LINE_BG =
"rgba(243,139,168,0.15)"- CONTEXT_LINES =
7
Class Method Summary collapse
-
.is_debug_mode ⇒ Object
Return true if TINA4_DEBUG is enabled.
-
.render_error_overlay(exception, request: nil) ⇒ String
Render a rich HTML error overlay.
-
.render_production_error(status_code: 500, message: "Internal Server Error", path: "") ⇒ Object
Render a safe, generic error page for production.
Class Method Details
.is_debug_mode ⇒ Object
Return true if TINA4_DEBUG is enabled.
166 167 168 |
# File 'lib/tina4/error_overlay.rb', line 166 def is_debug_mode Tina4::Env.is_truthy(ENV.fetch("TINA4_DEBUG", "")) end |
.render_error_overlay(exception, request: nil) ⇒ String
Render a rich HTML error overlay.
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/tina4/error_overlay.rb', line 41 def (exception, request: nil) exc_type = exception.class.name exc_msg = exception. # Stamp captured_at once when the overlay is generated. Each frame # compares its source file's mtime to this single timestamp and # flags itself stale if the file changed afterwards — protects # against the "browser cached an old overlay, then the AI rewrote # the file" confusion where the displayed source no longer matches # what actually raised the error. captured_at = Time.now.to_f # ── Stack trace ── frames_html = +"" backtrace = exception.backtrace || [] backtrace.each do |line| file, lineno, method = parse_backtrace_line(line) frames_html << format_frame(file, lineno, method, captured_at: captured_at) end # ── Request info ── request_pairs = [] if request.is_a?(Hash) request.each do |k, v| key = k.to_s if v.is_a?(Hash) v.each { |hk, hv| request_pairs << ["#{key}.#{hk}", hv.to_s] } elsif key.start_with?("HTTP_") || %w[REQUEST_METHOD REQUEST_URI SERVER_PROTOCOL REMOTE_ADDR SERVER_PORT QUERY_STRING CONTENT_TYPE CONTENT_LENGTH method url path].include?(key) request_pairs << [key, v.to_s] end end end request_section = request_pairs.empty? ? "" : collapsible("Request Details", table(request_pairs)) # ── Environment ── env_pairs = [ ["Framework", "Tina4 Ruby"], ["Version", defined?(Tina4::VERSION) ? Tina4::VERSION : "unknown"], ["Ruby", RUBY_VERSION], ["Platform", RUBY_PLATFORM], ["Debug", ENV.fetch("TINA4_DEBUG", "false")], ["Log Level", ENV.fetch("TINA4_LOG_LEVEL", "ERROR")] ] env_section = collapsible("Environment", table(env_pairs)) stack_section = collapsible("Stack Trace", frames_html, open_by_default: true) <<~HTML <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>Tina4 Error — #{esc(exc_type)}</title> <style> *{margin:0;padding:0;box-sizing:border-box;} body{background:#{BG};color:#{TEXT_COLOR};font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;padding:24px;line-height:1.5;} </style> </head> <body> <div style="max-width:960px;margin:0 auto;"> <div style="margin-bottom:24px;"> <div style="display:flex;align-items:center;gap:12px;margin-bottom:12px;"> <span style="background:#{RED};color:#{BG};padding:4px 12px;border-radius:4px;font-weight:700;font-size:13px;text-transform:uppercase;">Error</span> <span style="color:#{SUBTEXT};font-size:14px;">Tina4 Debug Overlay</span> </div> <h1 style="color:#{RED};font-size:28px;font-weight:700;margin-bottom:8px;">#{esc(exc_type)}</h1> <p style="color:#{TEXT_COLOR};font-size:18px;font-family:'SF Mono','Fira Code','Consolas',monospace;background:#{SURFACE};padding:12px 16px;border-radius:6px;border-left:4px solid #{RED};">#{esc(exc_msg)}</p> </div> #{stack_section} #{request_section} #{env_section} <div style="margin-top:32px;padding-top:16px;border-top:1px solid #{OVERLAY_COLOR};color:#{SUBTEXT};font-size:12px;"> Tina4 Debug Overlay — This page is only shown in debug mode. Set TINA4_DEBUG=false in production. </div> </div> </body> </html> HTML end |
.render_production_error(status_code: 500, message: "Internal Server Error", path: "") ⇒ Object
Render a safe, generic error page for production.
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/tina4/error_overlay.rb', line 124 def render_production_error(status_code: 500, message: "Internal Server Error", path: "") # Determine color based on status code code_color = case status_code when 403 then "#f59e0b" when 404 then "#3b82f6" else "#ef4444" end <<~HTML <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>#{status_code} — #{esc()}</title> <style> * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: system-ui, -apple-system, sans-serif; background: #0f172a; color: #e2e8f0; min-height: 100vh; display: flex; align-items: center; justify-content: center; } .error-card { background: #1e293b; border: 1px solid #334155; border-radius: 1rem; padding: 3rem; text-align: center; max-width: 520px; width: 90%; } .error-code { font-size: 8rem; font-weight: 900; color: #{code_color}; opacity: 0.6; line-height: 1; margin-bottom: 0.5rem; } .error-title { font-size: 1.5rem; font-weight: 700; margin-bottom: 0.75rem; } .error-msg { color: #94a3b8; font-size: 1rem; margin-bottom: 1.5rem; line-height: 1.5; } .error-path { font-family: 'SF Mono', monospace; background: #0f172a; color: #{code_color}; padding: 0.5rem 1rem; border-radius: 0.5rem; font-size: 0.85rem; word-break: break-all; margin-bottom: 1.5rem; display: inline-block; } .error-home { display: inline-block; padding: 0.6rem 2rem; background: #3b82f6; color: #fff; text-decoration: none; border-radius: 0.5rem; font-size: 0.9rem; font-weight: 600; } .error-home:hover { opacity: 0.9; } .logo { font-size: 1.5rem; margin-bottom: 1rem; opacity: 0.5; } </style> </head> <body> <div class="error-card"> <div class="error-code">#{status_code}</div> <div class="error-title">#{esc()}</div> <div class="error-msg">Something went wrong while processing your request.</div> #{path.to_s.empty? ? '' : "<div class=\"error-path\">#{esc(path)}</div><br>"} <a href="/" class="error-home">Go Home</a> </div> </body> </html> HTML end |