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

Class Method Details

.is_debug_modeObject

Return true if TINA4_DEBUG is enabled.



158
159
160
# File 'lib/tina4/error_overlay.rb', line 158

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.

Parameters:

  • exception (Exception)

    the caught exception

  • request (Hash, nil) (defaults to: nil)

    optional request details (Rack env or custom hash)

Returns:

  • (String)

    complete HTML page



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
# File 'lib/tina4/error_overlay.rb', line 41

def render_error_overlay(exception, request: nil)
  exc_type = exception.class.name
  exc_msg  = exception.message

  # ── 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)
  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 &mdash; 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.



116
117
118
119
120
121
122
123
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
# File 'lib/tina4/error_overlay.rb', line 116

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(message)}</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(message)}</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