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
MAX_FRAMES =

OVERLAY-DEC-03: cap the rendered frames so a deep/recursive stack yields a bounded page, not one source-file read per frame.

50
SENSITIVE_KEY_RE =

OVERLAY-DEC-02: request fields whose KEY matches this are masked in the dev overlay (Authorization/Cookie/Set-Cookie headers via authorization|cookie — Rack spells them HTTP_AUTHORIZATION / HTTP_COOKIE; password/token/secret/api_key body/param keys via the rest). Over-matching a benign field is the SAFE direction in a dev tool — over-masking leaks nothing; under-masking leaks a secret.

/password|passwd|secret|token|authorization|cookie|key/i
REDACTED =
"[redacted]"

Class Method Summary collapse

Class Method Details

.is_debug_modeObject

Return true if TINA4_DEBUG is enabled.



153
154
155
# File 'lib/tina4/error_overlay.rb', line 153

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



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

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

  # 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 (OVERLAY-DEC-03: capped) ──
  # A recursive stack of thousands of frames would otherwise do one source-file
  # read per frame and emit an unbounded page; render only the innermost
  # MAX_FRAMES and note the rest.
  frames_html = +""
  backtrace = exception.backtrace || []
  backtrace.first(MAX_FRAMES).each do |line|
    file, lineno, method = parse_backtrace_line(line)
    frames_html << format_frame(file, lineno, method, captured_at: captured_at)
  end
  hidden = backtrace.length - [backtrace.length, MAX_FRAMES].min
  if hidden.positive?
    frames_html << "<div style=\"color:#{SUBTEXT};padding:8px 0;font-size:13px;\">" \
      "&#8230; #{hidden} more stack frames hidden (truncated at #{MAX_FRAMES})</div>"
  end

  # ── Request info (OVERLAY-DEC-02: sensitive fields redacted) ──
  request_pairs = []
  if request.is_a?(Hash)
    request.each do |k, v|
      key = k.to_s
      if v.is_a?(Hash)
        v.each do |hk, hv|
          pair_key = "#{key}.#{hk}"
          request_pairs << [pair_key, redact(pair_key, hv.to_s)]
        end
      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, redact(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