Refactor tooltip system to use centralized enum-based definitions

- Create centralized tooltip definitions with TooltipType enum
- Add comprehensive tooltip content for 31 different field types
- Include Lighthouse relationships, calculations, and documentation links
- Implement click-to-open modal system with rich content display
- Refactor Tooltip component to use enum-based content lookup
- Update RequestsTable and RequestDebugger to use simplified enum syntax
- Add Modal component with keyboard shortcuts and backdrop dismiss
- Maintain type safety with proper TypeScript interfaces
- Clean up component props by centralizing all tooltip content

Benefits:
- Single source of truth for all tooltip definitions
- Improved maintainability and consistency
- Type-safe enum usage prevents errors
- Reduced code duplication across components
- Enhanced UX with detailed modal explanations

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Michael Mainguy 2025-08-11 12:04:09 -05:00
parent aa6e29fb0c
commit 2e533925a2
5 changed files with 824 additions and 29 deletions

View File

@ -1,4 +1,6 @@
import { useState, useMemo } from 'react' import { useState, useMemo } from 'react'
import { Tooltip } from './shared/Tooltip'
import { TooltipType } from './shared/tooltipDefinitions'
import type { TraceEvent } from '../../types/trace' import type { TraceEvent } from '../../types/trace'
interface RequestDebuggerProps { interface RequestDebuggerProps {
@ -327,7 +329,9 @@ export default function RequestDebugger({ traceEvents }: RequestDebuggerProps) {
return ( return (
<div style={{ fontSize: '12px', fontFamily: 'monospace', marginTop: '10px' }}> <div style={{ fontSize: '12px', fontFamily: 'monospace', marginTop: '10px' }}>
<strong>Network Timing (from ResourceReceiveResponse):</strong> <Tooltip type={TooltipType.NETWORK_TIMING}>
<strong>Network Timing (from ResourceReceiveResponse):</strong>
</Tooltip>
<div style={{ marginLeft: '10px', marginTop: '5px' }}> <div style={{ marginLeft: '10px', marginTop: '5px' }}>
<div><strong>requestTime:</strong> {timing.requestTime} seconds</div> <div><strong>requestTime:</strong> {timing.requestTime} seconds</div>
<div><strong>dnsStart:</strong> {timing.dnsStart} ms</div> <div><strong>dnsStart:</strong> {timing.dnsStart} ms</div>
@ -472,17 +476,30 @@ export default function RequestDebugger({ traceEvents }: RequestDebuggerProps) {
borderRadius: '8px', borderRadius: '8px',
padding: '20px' padding: '20px'
}}> }}>
<h3>Request Details: {selectedRequest.id}</h3> <h3>
<Tooltip type={TooltipType.REQUEST_DETAILS}>
Request Details: {selectedRequest.id}
</Tooltip>
</h3>
<div style={{ marginBottom: '15px', wordBreak: 'break-all', fontSize: '14px' }}> <div style={{ marginBottom: '15px', wordBreak: 'break-all', fontSize: '14px' }}>
<strong>URL:</strong> {selectedRequest.url} <strong>URL:</strong> {selectedRequest.url}
</div> </div>
{/* Duration Calculations */} {/* Duration Calculations */}
{calculateDurations(selectedRequest.events)} <div style={{ marginBottom: '20px' }}>
<Tooltip type={TooltipType.DURATION_CALCULATIONS}>
<div></div>
</Tooltip>
{calculateDurations(selectedRequest.events)}
</div>
{/* Event Details */} {/* Event Details */}
<div style={{ marginTop: '30px' }}> <div style={{ marginTop: '30px' }}>
<h4>All Events for this Request:</h4> <h4>
<Tooltip type={TooltipType.ALL_EVENTS}>
All Events for this Request:
</Tooltip>
</h4>
{(() => { {(() => {
const baseTimestamp = selectedRequest.events.sendRequest?.ts const baseTimestamp = selectedRequest.events.sendRequest?.ts
@ -491,7 +508,10 @@ export default function RequestDebugger({ traceEvents }: RequestDebuggerProps) {
{selectedRequest.events.sendRequest && ( {selectedRequest.events.sendRequest && (
<div style={{ marginBottom: '20px', padding: '10px', backgroundColor: '#e3f2fd', borderRadius: '4px' }}> <div style={{ marginBottom: '20px', padding: '10px', backgroundColor: '#e3f2fd', borderRadius: '4px' }}>
<strong>ResourceSendRequest</strong> - {formatTimestamp(selectedRequest.events.sendRequest.ts, baseTimestamp)} <Tooltip type={TooltipType.RESOURCE_SEND_REQUEST}>
<strong>ResourceSendRequest</strong>
</Tooltip>
{' '}- {formatTimestamp(selectedRequest.events.sendRequest.ts, baseTimestamp)}
<pre style={{ fontSize: '12px', marginTop: '10px', overflow: 'auto' }}> <pre style={{ fontSize: '12px', marginTop: '10px', overflow: 'auto' }}>
{JSON.stringify(selectedRequest.events.sendRequest.args, null, 2)} {JSON.stringify(selectedRequest.events.sendRequest.args, null, 2)}
</pre> </pre>
@ -500,7 +520,10 @@ export default function RequestDebugger({ traceEvents }: RequestDebuggerProps) {
{selectedRequest.events.receiveResponse && ( {selectedRequest.events.receiveResponse && (
<div style={{ marginBottom: '20px', padding: '10px', backgroundColor: '#e8f5e8', borderRadius: '4px' }}> <div style={{ marginBottom: '20px', padding: '10px', backgroundColor: '#e8f5e8', borderRadius: '4px' }}>
<strong>ResourceReceiveResponse</strong> - {formatTimestamp(selectedRequest.events.receiveResponse.ts, baseTimestamp)} <Tooltip type={TooltipType.RESOURCE_RECEIVE_RESPONSE}>
<strong>ResourceReceiveResponse</strong>
</Tooltip>
{' '}- {formatTimestamp(selectedRequest.events.receiveResponse.ts, baseTimestamp)}
{formatTiming((selectedRequest.events.receiveResponse.args as any)?.data?.timing)} {formatTiming((selectedRequest.events.receiveResponse.args as any)?.data?.timing)}
<pre style={{ fontSize: '12px', marginTop: '10px', overflow: 'auto' }}> <pre style={{ fontSize: '12px', marginTop: '10px', overflow: 'auto' }}>
{JSON.stringify(selectedRequest.events.receiveResponse.args, null, 2)} {JSON.stringify(selectedRequest.events.receiveResponse.args, null, 2)}
@ -510,7 +533,10 @@ export default function RequestDebugger({ traceEvents }: RequestDebuggerProps) {
{selectedRequest.events.receivedData.map((event, index) => ( {selectedRequest.events.receivedData.map((event, index) => (
<div key={index} style={{ marginBottom: '20px', padding: '10px', backgroundColor: '#fff3e0', borderRadius: '4px' }}> <div key={index} style={{ marginBottom: '20px', padding: '10px', backgroundColor: '#fff3e0', borderRadius: '4px' }}>
<strong>ResourceReceivedData #{index + 1}</strong> - {formatTimestamp(event.ts, baseTimestamp)} <Tooltip type={TooltipType.RESOURCE_RECEIVED_DATA}>
<strong>ResourceReceivedData #{index + 1}</strong>
</Tooltip>
{' '}- {formatTimestamp(event.ts, baseTimestamp)}
{index === selectedRequest.events.receivedData.length - 1 && ( {index === selectedRequest.events.receivedData.length - 1 && (
<span style={{ color: '#ff9800', fontWeight: 'bold', marginLeft: '10px' }}> LAST DATA CHUNK</span> <span style={{ color: '#ff9800', fontWeight: 'bold', marginLeft: '10px' }}> LAST DATA CHUNK</span>
)} )}
@ -532,7 +558,11 @@ export default function RequestDebugger({ traceEvents }: RequestDebuggerProps) {
{/* Additional Event Types */} {/* Additional Event Types */}
{selectedRequest.events.parseOnBackground && selectedRequest.events.parseOnBackground.length > 0 && ( {selectedRequest.events.parseOnBackground && selectedRequest.events.parseOnBackground.length > 0 && (
<div style={{ marginTop: '20px' }}> <div style={{ marginTop: '20px' }}>
<h5>V8 Parse Events:</h5> <h5>
<Tooltip type={TooltipType.V8_PARSE_EVENTS}>
V8 Parse Events:
</Tooltip>
</h5>
{selectedRequest.events.parseOnBackground.map((event, index) => ( {selectedRequest.events.parseOnBackground.map((event, index) => (
<div key={index} style={{ marginBottom: '15px', padding: '10px', backgroundColor: '#e1f5fe', borderRadius: '4px' }}> <div key={index} style={{ marginBottom: '15px', padding: '10px', backgroundColor: '#e1f5fe', borderRadius: '4px' }}>
<strong>{event.name}</strong> - {formatTimestamp(event.ts, baseTimestamp)} <strong>{event.name}</strong> - {formatTimestamp(event.ts, baseTimestamp)}
@ -547,7 +577,11 @@ export default function RequestDebugger({ traceEvents }: RequestDebuggerProps) {
{selectedRequest.events.compile && selectedRequest.events.compile.length > 0 && ( {selectedRequest.events.compile && selectedRequest.events.compile.length > 0 && (
<div style={{ marginTop: '20px' }}> <div style={{ marginTop: '20px' }}>
<h5>V8 Compile Events:</h5> <h5>
<Tooltip type={TooltipType.V8_COMPILE_EVENTS}>
V8 Compile Events:
</Tooltip>
</h5>
{selectedRequest.events.compile.map((event, index) => ( {selectedRequest.events.compile.map((event, index) => (
<div key={index} style={{ marginBottom: '15px', padding: '10px', backgroundColor: '#f3e5f5', borderRadius: '4px' }}> <div key={index} style={{ marginBottom: '15px', padding: '10px', backgroundColor: '#f3e5f5', borderRadius: '4px' }}>
<strong>{event.name}</strong> - {formatTimestamp(event.ts, baseTimestamp)} <strong>{event.name}</strong> - {formatTimestamp(event.ts, baseTimestamp)}
@ -562,7 +596,11 @@ export default function RequestDebugger({ traceEvents }: RequestDebuggerProps) {
{selectedRequest.events.evaluateScript && selectedRequest.events.evaluateScript.length > 0 && ( {selectedRequest.events.evaluateScript && selectedRequest.events.evaluateScript.length > 0 && (
<div style={{ marginTop: '20px' }}> <div style={{ marginTop: '20px' }}>
<h5>Script Evaluation Events:</h5> <h5>
<Tooltip type={TooltipType.SCRIPT_EVALUATION}>
Script Evaluation Events:
</Tooltip>
</h5>
{selectedRequest.events.evaluateScript.map((event, index) => ( {selectedRequest.events.evaluateScript.map((event, index) => (
<div key={index} style={{ marginBottom: '15px', padding: '10px', backgroundColor: '#e8f5e8', borderRadius: '4px' }}> <div key={index} style={{ marginBottom: '15px', padding: '10px', backgroundColor: '#e8f5e8', borderRadius: '4px' }}>
<strong>{event.name}</strong> - {formatTimestamp(event.ts, baseTimestamp)} <strong>{event.name}</strong> - {formatTimestamp(event.ts, baseTimestamp)}
@ -695,7 +733,11 @@ export default function RequestDebugger({ traceEvents }: RequestDebuggerProps) {
{/* URL Loader Events */} {/* URL Loader Events */}
{selectedRequest.events.throttlingURLLoader && selectedRequest.events.throttlingURLLoader.length > 0 && ( {selectedRequest.events.throttlingURLLoader && selectedRequest.events.throttlingURLLoader.length > 0 && (
<div style={{ marginTop: '20px' }}> <div style={{ marginTop: '20px' }}>
<h5>Throttling URL Loader Events ({selectedRequest.events.throttlingURLLoader.length}):</h5> <h5>
<Tooltip type={TooltipType.THROTTLING_URL_LOADER}>
Throttling URL Loader Events ({selectedRequest.events.throttlingURLLoader.length}):
</Tooltip>
</h5>
{selectedRequest.events.throttlingURLLoader.map((event, index) => ( {selectedRequest.events.throttlingURLLoader.map((event, index) => (
<div key={index} style={{ marginBottom: '15px', padding: '10px', backgroundColor: '#fff3e0', borderRadius: '4px' }}> <div key={index} style={{ marginBottom: '15px', padding: '10px', backgroundColor: '#fff3e0', borderRadius: '4px' }}>
<strong>{event.name}</strong> - {formatTimestamp(event.ts, baseTimestamp)} <strong>{event.name}</strong> - {formatTimestamp(event.ts, baseTimestamp)}

View File

@ -1,6 +1,8 @@
import React from 'react' import React from 'react'
import RequestRowSummary from './RequestRowSummary' import RequestRowSummary from './RequestRowSummary'
import ScreenshotRow from './ScreenshotRow' import ScreenshotRow from './ScreenshotRow'
import { Tooltip } from '../shared/Tooltip'
import { TooltipType } from '../shared/tooltipDefinitions'
import styles from './HTTPRequestViewer.module.css' import styles from './HTTPRequestViewer.module.css'
import type { HTTPRequest, ScreenshotEvent } from './types/httpRequest' import type { HTTPRequest, ScreenshotEvent } from './types/httpRequest'
@ -39,24 +41,96 @@ const RequestsTable: React.FC<RequestsTableProps> = ({
<table className={styles.table}> <table className={styles.table}>
<thead className={styles.tableHeader}> <thead className={styles.tableHeader}>
<tr> <tr>
<th className={`${styles.tableHeaderCell} ${styles.center} ${styles.expandColumn}`}>Expand</th> <th className={`${styles.tableHeaderCell} ${styles.center} ${styles.expandColumn}`}>
<th className={`${styles.tableHeaderCell} ${styles.left}`}>Method</th> <Tooltip type={TooltipType.EXPAND_ROW}>
<th className={`${styles.tableHeaderCell} ${styles.center}`}>Status</th> Expand
<th className={`${styles.tableHeaderCell} ${styles.left}`}>Type</th> </Tooltip>
<th className={`${styles.tableHeaderCell} ${styles.center}`}>Priority</th> </th>
<th className={`${styles.tableHeaderCell} ${styles.right}`}>Start Time</th> <th className={`${styles.tableHeaderCell} ${styles.left}`}>
<th className={`${styles.tableHeaderCell} ${styles.right}`}>Queue Time</th> <Tooltip type={TooltipType.HTTP_METHOD}>
<th className={`${styles.tableHeaderCell} ${styles.right}`}>DNS</th> Method
<th className={`${styles.tableHeaderCell} ${styles.right}`}>Connection</th> </Tooltip>
<th className={`${styles.tableHeaderCell} ${styles.right}`}>Server Latency</th> </th>
<th className={`${styles.tableHeaderCell} ${styles.left}`}>URL</th> <th className={`${styles.tableHeaderCell} ${styles.center}`}>
<th className={`${styles.tableHeaderCell} ${styles.right}`}>Duration</th> <Tooltip type={TooltipType.HTTP_STATUS}>
<th className={`${styles.tableHeaderCell} ${styles.right}`}>Total Response Time</th> Status
<th className={`${styles.tableHeaderCell} ${styles.right}`}>Size</th> </Tooltip>
<th className={`${styles.tableHeaderCell} ${styles.right}`}>Content-Length</th> </th>
<th className={`${styles.tableHeaderCell} ${styles.center}`}>Protocol</th> <th className={`${styles.tableHeaderCell} ${styles.left}`}>
<th className={`${styles.tableHeaderCell} ${styles.center}`}>CDN</th> <Tooltip type={TooltipType.RESOURCE_TYPE}>
<th className={`${styles.tableHeaderCell} ${styles.center}`}>Cache</th> Type
</Tooltip>
</th>
<th className={`${styles.tableHeaderCell} ${styles.center}`}>
<Tooltip type={TooltipType.REQUEST_PRIORITY}>
Priority
</Tooltip>
</th>
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
<Tooltip type={TooltipType.START_TIME}>
Start Time
</Tooltip>
</th>
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
<Tooltip type={TooltipType.QUEUE_TIME}>
Queue Time
</Tooltip>
</th>
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
<Tooltip type={TooltipType.DNS_TIME}>
DNS
</Tooltip>
</th>
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
<Tooltip type={TooltipType.CONNECTION_TIME}>
Connection
</Tooltip>
</th>
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
<Tooltip type={TooltipType.SERVER_LATENCY}>
Server Latency
</Tooltip>
</th>
<th className={`${styles.tableHeaderCell} ${styles.left}`}>
<Tooltip type={TooltipType.REQUEST_URL}>
URL
</Tooltip>
</th>
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
<Tooltip type={TooltipType.REQUEST_DURATION}>
Duration
</Tooltip>
</th>
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
<Tooltip type={TooltipType.TOTAL_RESPONSE_TIME}>
Total Response Time
</Tooltip>
</th>
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
<Tooltip type={TooltipType.TRANSFER_SIZE}>
Size
</Tooltip>
</th>
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
<Tooltip type={TooltipType.CONTENT_LENGTH}>
Content-Length
</Tooltip>
</th>
<th className={`${styles.tableHeaderCell} ${styles.center}`}>
<Tooltip type={TooltipType.HTTP_PROTOCOL}>
Protocol
</Tooltip>
</th>
<th className={`${styles.tableHeaderCell} ${styles.center}`}>
<Tooltip type={TooltipType.CDN_DETECTION}>
CDN
</Tooltip>
</th>
<th className={`${styles.tableHeaderCell} ${styles.center}`}>
<Tooltip type={TooltipType.CACHE_STATUS}>
Cache
</Tooltip>
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View File

@ -0,0 +1,119 @@
import { useEffect } from 'react'
interface ModalProps {
isOpen: boolean
onClose: () => void
title: string
children: React.ReactNode
}
export function Modal({ isOpen, onClose, title, children }: ModalProps) {
// Close modal on Escape key
useEffect(() => {
const handleEscape = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
onClose()
}
}
if (isOpen) {
document.addEventListener('keydown', handleEscape)
// Prevent body scrolling when modal is open
document.body.style.overflow = 'hidden'
}
return () => {
document.removeEventListener('keydown', handleEscape)
document.body.style.overflow = 'unset'
}
}, [isOpen, onClose])
if (!isOpen) return null
return (
<div
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 10000,
padding: '20px'
}}
onClick={onClose}
>
<div
style={{
backgroundColor: 'white',
borderRadius: '8px',
boxShadow: '0 20px 40px rgba(0, 0, 0, 0.3)',
maxWidth: '600px',
maxHeight: '80vh',
width: '100%',
overflow: 'hidden',
display: 'flex',
flexDirection: 'column'
}}
onClick={(e) => e.stopPropagation()}
>
{/* Modal Header */}
<div
style={{
padding: '20px',
borderBottom: '1px solid #e9ecef',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
backgroundColor: '#f8f9fa'
}}
>
<h2 style={{ margin: 0, color: '#495057', fontSize: '18px', fontWeight: 'bold' }}>
{title}
</h2>
<button
onClick={onClose}
style={{
background: 'none',
border: 'none',
fontSize: '24px',
cursor: 'pointer',
color: '#6c757d',
padding: '0',
width: '30px',
height: '30px',
borderRadius: '50%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
transition: 'background-color 0.2s'
}}
onMouseEnter={(e) => {
e.currentTarget.style.backgroundColor = '#e9ecef'
}}
onMouseLeave={(e) => {
e.currentTarget.style.backgroundColor = 'transparent'
}}
>
×
</button>
</div>
{/* Modal Content */}
<div
style={{
padding: '20px',
overflow: 'auto',
flex: 1
}}
>
{children}
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,215 @@
import { useState } from 'react'
import { Modal } from './Modal'
import { TOOLTIP_DEFINITIONS } from './tooltipDefinitions'
import type { TooltipTypeValues } from './tooltipDefinitions'
// Tooltip component for field explanations
interface TooltipProps {
children: React.ReactNode
type: TooltipTypeValues
}
export function Tooltip({ children, type }: TooltipProps) {
const { title, description, lighthouseRelation, calculation, links } = TOOLTIP_DEFINITIONS[type]
const [isHovered, setIsHovered] = useState(false)
const [isModalOpen, setIsModalOpen] = useState(false)
const handleIconClick = (e: React.MouseEvent) => {
e.preventDefault()
e.stopPropagation()
setIsHovered(false) // Hide hover tooltip when opening modal
setIsModalOpen(true)
}
return (
<>
<div style={{ position: 'relative', display: 'inline-flex', alignItems: 'center' }}>
{children}
<span
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
onClick={handleIconClick}
style={{
marginLeft: '6px',
cursor: 'pointer',
color: '#007bff',
fontSize: '14px',
fontWeight: 'bold',
width: '16px',
height: '16px',
borderRadius: '50%',
backgroundColor: '#e3f2fd',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
userSelect: 'none',
transition: 'all 0.2s ease',
border: '1px solid transparent'
}}
onMouseDown={(e) => {
e.currentTarget.style.backgroundColor = '#bbdefb'
e.currentTarget.style.borderColor = '#2196f3'
}}
onMouseUp={(e) => {
e.currentTarget.style.backgroundColor = '#e3f2fd'
e.currentTarget.style.borderColor = 'transparent'
}}
>
?
</span>
{/* Hover tooltip - only show when not modal open */}
{isHovered && !isModalOpen && (
<div style={{
position: 'absolute',
top: '25px',
left: '0',
backgroundColor: '#333',
color: 'white',
padding: '8px 12px',
borderRadius: '6px',
fontSize: '12px',
lineHeight: '1.4',
maxWidth: '300px',
zIndex: 1000,
boxShadow: '0 4px 8px rgba(0,0,0,0.3)',
whiteSpace: 'normal',
pointerEvents: 'none'
}}>
<div style={{ fontWeight: 'bold', marginBottom: '4px', color: '#4fc3f7' }}>
{title}
</div>
<div style={{ marginBottom: '6px' }}>
{description}
</div>
<div style={{ fontSize: '11px', color: '#90caf9', fontStyle: 'italic' }}>
Click for detailed information
</div>
</div>
)}
</div>
{/* Modal with detailed content */}
<Modal
isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
title={title}
>
<div style={{ lineHeight: '1.6' }}>
<div style={{
marginBottom: '20px',
fontSize: '15px',
color: '#495057'
}}>
{description}
</div>
{lighthouseRelation && (
<div style={{
marginBottom: '20px',
padding: '15px',
backgroundColor: '#fff3e0',
borderRadius: '6px',
borderLeft: '4px solid #ffb74d'
}}>
<div style={{
fontWeight: 'bold',
marginBottom: '8px',
color: '#e65100',
fontSize: '14px'
}}>
🎯 Lighthouse Relationship
</div>
<div style={{ color: '#5d4037', fontSize: '14px' }}>
{lighthouseRelation}
</div>
</div>
)}
{calculation && (
<div style={{
marginBottom: '20px',
padding: '15px',
backgroundColor: '#e8f5e8',
borderRadius: '6px',
borderLeft: '4px solid #81c784'
}}>
<div style={{
fontWeight: 'bold',
marginBottom: '8px',
color: '#2e7d32',
fontSize: '14px'
}}>
🧮 Calculation
</div>
<div style={{
color: '#1b5e20',
fontSize: '14px',
fontFamily: 'monospace',
backgroundColor: '#f1f8e9',
padding: '8px',
borderRadius: '4px',
border: '1px solid #c8e6c9'
}}>
{calculation}
</div>
</div>
)}
{links && links.length > 0 && (
<div style={{
padding: '15px',
backgroundColor: '#f3e5f5',
borderRadius: '6px',
borderLeft: '4px solid #ba68c8'
}}>
<div style={{
fontWeight: 'bold',
marginBottom: '12px',
color: '#6a1b9a',
fontSize: '14px'
}}>
📚 Learn More
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
{links.map((link, index) => (
<a
key={index}
href={link.url}
target="_blank"
rel="noopener noreferrer"
style={{
color: '#1976d2',
textDecoration: 'none',
fontSize: '14px',
padding: '8px 12px',
backgroundColor: 'white',
borderRadius: '4px',
border: '1px solid #e3f2fd',
transition: 'all 0.2s ease',
display: 'inline-block'
}}
onMouseEnter={(e) => {
e.currentTarget.style.backgroundColor = '#e3f2fd'
e.currentTarget.style.borderColor = '#2196f3'
e.currentTarget.style.transform = 'translateY(-1px)'
e.currentTarget.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)'
}}
onMouseLeave={(e) => {
e.currentTarget.style.backgroundColor = 'white'
e.currentTarget.style.borderColor = '#e3f2fd'
e.currentTarget.style.transform = 'translateY(0)'
e.currentTarget.style.boxShadow = 'none'
}}
>
🔗 {link.text}
</a>
))}
</div>
</div>
)}
</div>
</Modal>
</>
)
}

View File

@ -0,0 +1,345 @@
// Centralized tooltip definitions
export const TooltipType = {
// HTTP Request Table Headers
EXPAND_ROW: 'EXPAND_ROW',
HTTP_METHOD: 'HTTP_METHOD',
HTTP_STATUS: 'HTTP_STATUS',
RESOURCE_TYPE: 'RESOURCE_TYPE',
REQUEST_PRIORITY: 'REQUEST_PRIORITY',
START_TIME: 'START_TIME',
QUEUE_TIME: 'QUEUE_TIME',
DNS_TIME: 'DNS_TIME',
CONNECTION_TIME: 'CONNECTION_TIME',
SERVER_LATENCY: 'SERVER_LATENCY',
REQUEST_URL: 'REQUEST_URL',
REQUEST_DURATION: 'REQUEST_DURATION',
TOTAL_RESPONSE_TIME: 'TOTAL_RESPONSE_TIME',
TRANSFER_SIZE: 'TRANSFER_SIZE',
CONTENT_LENGTH: 'CONTENT_LENGTH',
HTTP_PROTOCOL: 'HTTP_PROTOCOL',
CDN_DETECTION: 'CDN_DETECTION',
CACHE_STATUS: 'CACHE_STATUS',
// Request Debugger Headers
REQUEST_DETAILS: 'REQUEST_DETAILS',
DURATION_CALCULATIONS: 'DURATION_CALCULATIONS',
ALL_EVENTS: 'ALL_EVENTS',
RESOURCE_SEND_REQUEST: 'RESOURCE_SEND_REQUEST',
RESOURCE_RECEIVE_RESPONSE: 'RESOURCE_RECEIVE_RESPONSE',
RESOURCE_RECEIVED_DATA: 'RESOURCE_RECEIVED_DATA',
V8_PARSE_EVENTS: 'V8_PARSE_EVENTS',
V8_COMPILE_EVENTS: 'V8_COMPILE_EVENTS',
SCRIPT_EVALUATION: 'SCRIPT_EVALUATION',
THROTTLING_URL_LOADER: 'THROTTLING_URL_LOADER',
NETWORK_TIMING: 'NETWORK_TIMING'
} as const
export type TooltipTypeValues = typeof TooltipType[keyof typeof TooltipType]
export interface TooltipDefinition {
title: string
description: string
lighthouseRelation?: string
calculation?: string
links?: Array<{ text: string; url: string }>
}
export const TOOLTIP_DEFINITIONS: Record<TooltipTypeValues, TooltipDefinition> = {
// HTTP Request Table Headers
[TooltipType.EXPAND_ROW]: {
title: "Expand Row",
description: "Click to expand and see detailed request breakdown including timing waterfall, headers, and response details.",
lighthouseRelation: "Expanded view helps understand request bottlenecks affecting LCP, FCP, and overall performance scores."
},
[TooltipType.HTTP_METHOD]: {
title: "HTTP Method",
description: "The HTTP method used for this request (GET, POST, PUT, DELETE, etc.). Different methods have different caching and performance implications.",
lighthouseRelation: "GET requests are cacheable and can improve repeat visit performance. POST requests cannot be cached, affecting performance metrics.",
links: [
{ text: "HTTP Methods - MDN", url: "https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods" }
]
},
[TooltipType.HTTP_STATUS]: {
title: "HTTP Status Code",
description: "HTTP response status code indicating success (2xx), redirection (3xx), client error (4xx), or server error (5xx).",
lighthouseRelation: "Status codes affect Lighthouse scoring: 4xx/5xx errors hurt performance scores, redirects (3xx) add latency affecting LCP and FCP.",
links: [
{ text: "HTTP Status Codes", url: "https://developer.mozilla.org/en-US/docs/Web/HTTP/Status" },
{ text: "Avoid Redirects", url: "https://web.dev/articles/redirects" }
]
},
[TooltipType.RESOURCE_TYPE]: {
title: "Resource Type",
description: "Type of resource being requested: Document, Stylesheet, Script, Image, Font, XHR, Fetch, etc.",
lighthouseRelation: "Different resource types have different priority and impact on Core Web Vitals. Scripts/CSS block rendering, images affect LCP.",
calculation: "Derived from Content-Type header and request context.",
links: [
{ text: "Resource Prioritization", url: "https://web.dev/articles/resource-prioritization" },
{ text: "Critical Rendering Path", url: "https://web.dev/articles/critical-rendering-path" }
]
},
[TooltipType.REQUEST_PRIORITY]: {
title: "Request Priority",
description: "Browser's internal priority for this request: VeryHigh, High, Medium, Low, VeryLow. Determines resource loading order.",
lighthouseRelation: "High priority resources are critical for LCP and FCP. Low priority resources should not block critical content.",
links: [
{ text: "Resource Prioritization", url: "https://web.dev/articles/resource-prioritization" },
{ text: "Chrome Resource Priorities", url: "https://docs.google.com/document/d/1bCDuq9H1ih9iNjgzyAL0gpwNFiEP4TZS-YLRp_RuMlc" }
]
},
[TooltipType.START_TIME]: {
title: "Start Time",
description: "When the browser initiated this request, relative to navigation start or first request.",
lighthouseRelation: "Earlier start times generally improve performance scores. Delayed critical resource requests hurt LCP and FCP.",
calculation: "Timestamp relative to navigation start or performance.timeOrigin.",
links: [
{ text: "Navigation Timing API", url: "https://developer.mozilla.org/en-US/docs/Web/API/Navigation_timing_API" }
]
},
[TooltipType.QUEUE_TIME]: {
title: "Queue Time",
description: "Time spent waiting in browser's request queue before being sent. Indicates network congestion or connection limits.",
lighthouseRelation: "High queue times delay resource loading, negatively impacting LCP, FCP, and Speed Index scores.",
calculation: "Time from request initiation to actual network send start.",
links: [
{ text: "HTTP/1.1 vs HTTP/2 Connection Limits", url: "https://web.dev/articles/http2" }
]
},
[TooltipType.DNS_TIME]: {
title: "DNS Lookup Time",
description: "Time spent resolving the domain name to an IP address. First request to a domain includes DNS lookup.",
lighthouseRelation: "DNS lookup time contributes to TTFB (Time to First Byte) and overall request latency, affecting all performance metrics.",
calculation: "dnsEnd - dnsStart from Navigation Timing API.",
links: [
{ text: "Optimize DNS Lookups", url: "https://web.dev/articles/preconnect-and-dns-prefetch" },
{ text: "DNS Performance", url: "https://developer.mozilla.org/en-US/docs/Web/Performance/dns-prefetch" }
]
},
[TooltipType.CONNECTION_TIME]: {
title: "Connection Time",
description: "Time to establish TCP connection (and SSL handshake if HTTPS). Includes connection reuse optimization.",
lighthouseRelation: "Connection time contributes to TTFB and affects all network-dependent metrics. HTTP/2 connection reuse improves performance.",
calculation: "connectEnd - connectStart, includes SSL time if HTTPS.",
links: [
{ text: "Preconnect to Required Origins", url: "https://web.dev/articles/preconnect-and-dns-prefetch" },
{ text: "HTTP/2 Connection Reuse", url: "https://web.dev/articles/http2" }
]
},
[TooltipType.SERVER_LATENCY]: {
title: "Server Latency",
description: "Time from when request was sent to when first response byte was received. Measures server processing time.",
lighthouseRelation: "Server latency is a key component of TTFB (Time to First Byte), directly affecting LCP and FCP scores.",
calculation: "responseStart - requestStart from Navigation Timing API.",
links: [
{ text: "Reduce Server Response Times (TTFB)", url: "https://web.dev/articles/ttfb" },
{ text: "Server Performance Optimization", url: "https://web.dev/articles/fast" }
]
},
[TooltipType.REQUEST_URL]: {
title: "Request URL",
description: "The full URL of the requested resource. Long URLs with many parameters can impact performance.",
lighthouseRelation: "URL structure affects caching efficiency. Query parameters can prevent caching, hurting repeat visit performance.",
links: [
{ text: "HTTP Caching", url: "https://web.dev/articles/http-cache" }
]
},
[TooltipType.REQUEST_DURATION]: {
title: "Request Duration",
description: "Total time from request start to completion, including all network phases and data transfer.",
lighthouseRelation: "Long durations for critical resources directly impact LCP, FCP, and Speed Index. Background resource duration affects overall performance score.",
calculation: "responseEnd - requestStart from Navigation Timing API.",
links: [
{ text: "Optimize Resource Loading", url: "https://web.dev/articles/critical-rendering-path" }
]
},
[TooltipType.TOTAL_RESPONSE_TIME]: {
title: "Total Response Time",
description: "Complete time from navigation start to request completion. Shows request timing in context of page load.",
lighthouseRelation: "Response time relative to navigation affects when resources become available for rendering, impacting LCP and FCP timing.",
calculation: "responseEnd - navigationStart."
},
[TooltipType.TRANSFER_SIZE]: {
title: "Transfer Size",
description: "Actual bytes transferred over network after compression, encoding, and headers. Includes HTTP overhead.",
lighthouseRelation: "Transfer size affects download time and bandwidth usage, impacting LCP for large resources and overall Speed Index.",
calculation: "Actual bytes received including headers and compression.",
links: [
{ text: "Optimize Resource Sizes", url: "https://web.dev/articles/fast#optimize_your_content_efficiency" },
{ text: "Enable Compression", url: "https://web.dev/articles/reduce-network-payloads-using-text-compression" }
]
},
[TooltipType.CONTENT_LENGTH]: {
title: "Content-Length",
description: "Uncompressed size of response body as declared in Content-Length header. May differ from actual transfer size.",
lighthouseRelation: "Large content sizes increase download time, especially impacting LCP for critical resources and mobile performance scores.",
calculation: "Value from Content-Length HTTP response header.",
links: [
{ text: "Optimize Images", url: "https://web.dev/articles/fast#optimize_your_images" },
{ text: "Minify Resources", url: "https://web.dev/articles/reduce-network-payloads-using-text-compression" }
]
},
[TooltipType.HTTP_PROTOCOL]: {
title: "HTTP Protocol",
description: "HTTP protocol version used: HTTP/1.1, HTTP/2, or HTTP/3. Newer protocols offer performance benefits.",
lighthouseRelation: "HTTP/2 and HTTP/3 improve multiplexing and reduce latency, positively impacting all performance metrics especially on mobile.",
links: [
{ text: "HTTP/2 Benefits", url: "https://web.dev/articles/http2" },
{ text: "HTTP/3 Performance", url: "https://web.dev/articles/http3" }
]
},
[TooltipType.CDN_DETECTION]: {
title: "CDN Detection",
description: "Indicates if request was served by a Content Delivery Network. CDNs improve performance by serving content from geographically closer servers.",
lighthouseRelation: "CDN usage typically improves TTFB, LCP, and FCP by reducing latency. Critical for good mobile performance scores.",
links: [
{ text: "CDN Performance Benefits", url: "https://web.dev/articles/content-delivery-networks" }
]
},
[TooltipType.CACHE_STATUS]: {
title: "Cache Status",
description: "Whether the resource was served from cache (browser, proxy, CDN) or fetched fresh from origin server.",
lighthouseRelation: "Cached resources load faster, improving repeat visit performance and Lighthouse scores. Critical for mobile performance optimization.",
links: [
{ text: "HTTP Caching", url: "https://web.dev/articles/http-cache" },
{ text: "Browser Caching", url: "https://web.dev/articles/uses-long-cache-ttl" }
]
},
// Request Debugger Headers
[TooltipType.REQUEST_DETAILS]: {
title: "Request Details",
description: "Comprehensive trace analysis for a specific HTTP request, showing all related browser events and timing data.",
lighthouseRelation: "This detailed breakdown helps understand what contributes to Lighthouse metrics like LCP, FCP, and Speed Index. Each request impacts overall page load performance.",
links: [
{ text: "Chrome DevTools Network Panel", url: "https://developer.chrome.com/docs/devtools/network/" },
{ text: "Understanding Resource Loading", url: "https://web.dev/articles/critical-rendering-path" }
]
},
[TooltipType.DURATION_CALCULATIONS]: {
title: "Duration Calculations",
description: "Key timing metrics extracted from trace events that show how long different phases of the request took.",
lighthouseRelation: "Directly impacts LCP (Largest Contentful Paint), FCP (First Contentful Paint), and overall Performance Score. Network request timings are critical components of Core Web Vitals.",
calculation: "Total Duration = Receive Response timestamp - Send Request timestamp. Individual phases calculated from trace event timing data.",
links: [
{ text: "Lighthouse Performance Scoring", url: "https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/" },
{ text: "Core Web Vitals", url: "https://web.dev/articles/vitals" },
{ text: "Network Request Timing", url: "https://developer.mozilla.org/en-US/docs/Web/API/Performance_API/Resource_timing" }
]
},
[TooltipType.ALL_EVENTS]: {
title: "All Events for this Request",
description: "Complete chronological list of browser trace events related to this HTTP request, from initiation to completion.",
lighthouseRelation: "These events show the complete request lifecycle that affects Lighthouse timing metrics. Each event type contributes differently to performance scores.",
links: [
{ text: "Chrome Trace Event Format", url: "https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview" },
{ text: "DevTools Performance Panel", url: "https://developer.chrome.com/docs/devtools/performance/" }
]
},
[TooltipType.RESOURCE_SEND_REQUEST]: {
title: "ResourceSendRequest Event",
description: "Marks when the browser initiates sending the HTTP request. This is the starting point for network timing measurements.",
lighthouseRelation: "Start time for calculating request latency. Affects TTI (Time to Interactive) and Speed Index when blocking critical resources.",
calculation: "Request Duration = ResourceReceiveResponse.ts - ResourceSendRequest.ts",
links: [
{ text: "Network Request Lifecycle", url: "https://developer.chrome.com/blog/resource-loading-insights/" },
{ text: "Critical Request Chains", url: "https://web.dev/articles/critical-request-chains" }
]
},
[TooltipType.RESOURCE_RECEIVE_RESPONSE]: {
title: "ResourceReceiveResponse Event",
description: "Indicates when the browser receives the HTTP response headers. Contains critical timing data like DNS lookup, connection establishment, and server response time.",
lighthouseRelation: "Key component of LCP (Largest Contentful Paint) and FCP (First Contentful Paint) measurements. Server response time directly impacts Lighthouse Performance Score.",
calculation: "Server Response Time = receiveHeadersEnd - sendStart (from timing object). Total request time measured from SendRequest to this event.",
links: [
{ text: "Server Response Time Optimization", url: "https://web.dev/articles/ttfb" },
{ text: "Navigation Timing API", url: "https://developer.mozilla.org/en-US/docs/Web/API/Navigation_timing_API" }
]
},
[TooltipType.RESOURCE_RECEIVED_DATA]: {
title: "ResourceReceivedData Events",
description: "Shows when chunks of response data are received from the server. Multiple events indicate streaming/chunked transfer encoding.",
lighthouseRelation: "Affects Progressive Loading metrics and Speed Index. More data chunks generally indicate better streaming performance for large resources.",
calculation: "Data transfer progress can be tracked by summing encodedDataLength across all chunks. Last chunk indicates download completion.",
links: [
{ text: "Progressive Loading", url: "https://web.dev/articles/progressive-loading" },
{ text: "Chunked Transfer Encoding", url: "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding" }
]
},
[TooltipType.V8_PARSE_EVENTS]: {
title: "V8 Parse Events",
description: "JavaScript parsing events that occur when V8 engine processes downloaded JS files. Background parsing improves main thread performance.",
lighthouseRelation: "Directly impacts TTI (Time to Interactive) and TBT (Total Blocking Time). Background parsing reduces main thread blocking, improving Lighthouse performance scores.",
calculation: "Parse time = event duration. Background parsing happens off main thread, reducing blocking time.",
links: [
{ text: "JavaScript Parsing Performance", url: "https://v8.dev/blog/background-compilation" },
{ text: "Reduce JavaScript Execution Time", url: "https://web.dev/articles/bootup-time" }
]
},
[TooltipType.V8_COMPILE_EVENTS]: {
title: "V8 Compile Events",
description: "JavaScript compilation events in V8 engine. Shows when parsed JavaScript is compiled to bytecode or optimized machine code.",
lighthouseRelation: "Compilation time affects TTI (Time to Interactive) and can contribute to TBT (Total Blocking Time) if done on main thread. Efficient compilation improves runtime performance.",
calculation: "Compile time = event duration. Background compilation reduces main thread impact.",
links: [
{ text: "V8 Compilation Pipeline", url: "https://v8.dev/docs/ignition" },
{ text: "JavaScript Performance Best Practices", url: "https://web.dev/articles/fast" }
]
},
[TooltipType.SCRIPT_EVALUATION]: {
title: "Script Evaluation Events",
description: "JavaScript execution/evaluation events. Shows when compiled scripts are actually executed by V8 engine.",
lighthouseRelation: "Direct impact on TTI (Time to Interactive) and TBT (Total Blocking Time). Script evaluation on main thread blocks user interaction, heavily affecting Lighthouse performance scores.",
calculation: "Execution time = event duration. All execution happens on main thread and contributes to blocking time.",
links: [
{ text: "Reduce JavaScript Execution Time", url: "https://web.dev/articles/bootup-time" },
{ text: "Main Thread Blocking", url: "https://web.dev/articles/long-tasks-devtools" }
]
},
[TooltipType.THROTTLING_URL_LOADER]: {
title: "Throttling URL Loader Events",
description: "Network throttling and URL loading events from Chrome's network stack. Shows how requests are managed and potentially throttled.",
lighthouseRelation: "Network throttling affects all timing metrics (LCP, FCP, FID). Understanding these events helps identify network bottlenecks that impact Lighthouse scores.",
calculation: "Throttling delays can be calculated from event timestamps. Multiple events show request queuing and prioritization.",
links: [
{ text: "Network Throttling in DevTools", url: "https://developer.chrome.com/docs/devtools/network/#throttle" },
{ text: "Resource Prioritization", url: "https://web.dev/articles/resource-prioritization" }
]
},
[TooltipType.NETWORK_TIMING]: {
title: "Network Timing Breakdown",
description: "Detailed network timing phases from the ResourceReceiveResponse event, showing DNS lookup, connection, SSL handshake, and data transfer times.",
lighthouseRelation: "These timings directly feed into TTFB (Time to First Byte) and overall request duration. DNS, connection, and SSL times affect all network-dependent Lighthouse metrics.",
calculation: "Total time = receiveHeadersEnd - requestTime. Each phase represents different network bottlenecks that can be optimized.",
links: [
{ text: "Navigation Timing API", url: "https://developer.mozilla.org/en-US/docs/Web/API/Navigation_timing_API" },
{ text: "Optimize TTFB", url: "https://web.dev/articles/ttfb" }
]
}
}