Skip to content

Add React dashboard and performance profiling capabilities#34

Merged
doganarif merged 3 commits intomainfrom
feat/react-dashboard-profiling
Sep 25, 2025
Merged

Add React dashboard and performance profiling capabilities#34
doganarif merged 3 commits intomainfrom
feat/react-dashboard-profiling

Conversation

@doganarif
Copy link
Owner

@doganarif doganarif commented Sep 25, 2025

Overview

This PR modernizes GoVisual's dashboard with a complete rewrite to React/Preact and adds comprehensive performance profiling capabilities.

Key Changes

Dashboard Modernization

  • Migrated from HTML templates to React: Replaced server-side rendered templates with a modern React UI built with Preact
  • TypeScript integration: Full TypeScript support for better development experience
  • Modern UI components: Using Radix UI primitives with Tailwind CSS for consistent, accessible components
  • Embedded assets: Static files are now embedded in the Go binary for zero external dependencies

Performance Profiling Features

  • CPU profiling: Track CPU-intensive operations with flame graph visualization
  • Memory profiling: Monitor memory allocations and garbage collection impact
  • SQL query tracking: Record and analyze database query performance
  • HTTP call monitoring: Track external API calls and their performance impact
  • Bottleneck detection: Automatic identification of performance issues with actionable insights

Tracing Integration

  • OpenTelemetry support: Added distributed tracing capabilities
  • Tracing example: New example application demonstrating tracing features

New Examples

  • Profiling example: Comprehensive demo of all profiling features
  • Tracing example: Demonstration of OpenTelemetry integration

Technical Details

  • Dashboard now serves as a Single Page Application with API endpoints
  • Profiling system uses pprof integration with custom metrics collection
  • Build system uses esbuild for fast compilation and bundling
  • Maintained backward compatibility for existing middleware usage

Breaking Changes

  • Removed legacy HTML template system (internal/dashboard/templates/)
  • Dashboard API endpoints remain compatible but UI structure has changed

Testing

All existing functionality preserved with enhanced visualization capabilities. New profiling features include comprehensive example applications for validation.


Summary by cubic

Rebuilt the dashboard as a fast React/Preact SPA with embedded assets, and added end-to-end performance profiling (CPU/memory, flame graphs, SQL/HTTP tracking) and tracing to help diagnose bottlenecks quickly.

  • New Features

    • Modern SPA dashboard (Preact + TypeScript, Radix UI, Tailwind); assets embedded in the Go binary.
    • Performance profiling: pprof-based CPU/memory metrics, goroutines/GC, flame graphs, bottleneck detection, SQL and external HTTP tracking.
    • Tracing middleware: detailed request flow with custom trace points.
    • New dashboard API endpoints for metrics, flame graphs, bottlenecks, system info, compare/replay requests.
    • Example apps for profiling and tracing to showcase features.
  • Migration

    • Removed legacy HTML templates; dashboard now served as a SPA. Existing API endpoints remain compatible, but UI structure changed.
    • To enable profiling: use WithProfiling(true), WithProfileType(...), WithProfileThreshold(...), and MaxProfileMetrics in options.
    • Added dependency: github.com/google/pprof for profile parsing.

- Replace HTML templates with modern React/Preact dashboard using TypeScript
- Add comprehensive performance profiling with CPU/memory tracking, flame graphs, and bottleneck detection
- Implement OpenTelemetry tracing integration
- Add profiling and tracing example applications
- Embed static assets in Go binary for zero-dependency deployment
@doganarif doganarif self-assigned this Sep 25, 2025
@doganarif doganarif added the enhancement New feature or request label Sep 25, 2025
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @doganarif, your pull request is larger than the review limit of 150000 diff characters

cursor[bot]

This comment was marked as outdated.

@doganarif doganarif merged commit 56cde20 into main Sep 25, 2025
2 checks passed
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

40 issues found across 78 files

Prompt for AI agents (all 40 issues)

Understand the root cause of the following 40 issues and fix them.


<file name="internal/dashboard/static/index.html">

<violation number="1" location="internal/dashboard/static/index.html:7">
Absolute asset path breaks when dashboard is mounted under a subpath; use a relative path so assets load under the dashboard prefix.</violation>

<violation number="2" location="internal/dashboard/static/index.html:24">
Absolute script path will bypass the dashboard mount and fail to load under non-root mounting; use a relative path.</violation>
</file>

<file name="internal/middleware/tracer.go">

<violation number="1" location="internal/middleware/tracer.go:65">
Nested StartTrace incorrectly uses the parent of the current trace, preventing proper child nesting and dropping first-level children under root.</violation>
</file>

<file name="cmd/examples/tracing/main.go">

<violation number="1" location="cmd/examples/tracing/main.go:51">
Possible nil pointer dereference: tracer.RecordCustom is called without checking tracer != nil.</violation>
</file>

<file name="internal/middleware/profiling.go">

<violation number="1" location="internal/middleware/profiling.go:60">
Starting runtime CPU profiling per request is process-wide and unsafe under concurrent traffic; it may conflict and skew metrics.</violation>

<violation number="2" location="internal/middleware/profiling.go:67">
Unbounded io.ReadAll on request body with ignored error can cause OOM and misses read failures; limit and handle errors.</violation>

<violation number="3" location="internal/middleware/profiling.go:69">
Sensitive data risk: storing full request body without redaction or size limits.</violation>

<violation number="4" location="internal/middleware/profiling.go:163">
profilingResponseWriter drops optional http interfaces (Flusher/Hijacker/Pusher), potentially breaking streaming, SSE, and WebSockets.</violation>
</file>

<file name="internal/dashboard/ui/src/components/FlameGraph.tsx">

<violation number="1" location="internal/dashboard/ui/src/components/FlameGraph.tsx:73">
Avoid setting innerHTML with untrusted data; this allows XSS. Construct the tooltip with textContent or sanitized HTML.</violation>

<violation number="2" location="internal/dashboard/ui/src/components/FlameGraph.tsx:79">
Use clientX/clientY for positioning when the tooltip uses position: fixed; pageX/pageY lead to incorrect offsets on scroll.</violation>
</file>

<file name="internal/dashboard/ui/src/App.tsx">

<violation number="1" location="internal/dashboard/ui/src/App.tsx:58">
useEffect uses filters in its callback but has an empty dependency array, causing stale filter application on live updates.</violation>
</file>

<file name="internal/profiling/flamegraph.go">

<violation number="1" location="internal/profiling/flamegraph.go:161">
Hotspot detection never recurses from root because node.Value == 0 triggers an early return, yielding empty results.</violation>
</file>

<file name="internal/dashboard/handler.go">

<violation number="1" location="internal/dashboard/handler.go:32">
Error from fs.Sub is ignored; staticFS may be nil and later Open will panic. Handle the error or provide a safe fallback.</violation>
</file>

<file name="internal/dashboard/ui/src/components/RequestComparison.tsx">

<violation number="1" location="internal/dashboard/ui/src/components/RequestComparison.tsx:97">
Objects (including JSX elements) are JSON-stringified in cells, causing incorrect rendering (e.g., Status row). Render JSX elements as-is when detected.</violation>
</file>

<file name="internal/dashboard/ui/postcss.config.js">

<violation number="1" location="internal/dashboard/ui/postcss.config.js:4">
PostCSS config references autoprefixer, but the package is not installed, likely causing the Tailwind build to fail. Install autoprefixer (devDependency) or remove the plugin from the config.</violation>
</file>

<file name="internal/dashboard/ui/src/components/ExportImport.tsx">

<violation number="1" location="internal/dashboard/ui/src/components/ExportImport.tsx:65">
CSV export does not escape internal quotes or sanitize leading formula characters, risking malformed CSV and potential CSV injection in spreadsheet apps.</violation>
</file>

<file name="internal/model/request.go">

<violation number="1" location="internal/model/request.go:25">
Adding PerformanceMetrics with a BSON tag will persist raw CPU/heap profile bytes from Metrics to MongoDB, inflating storage and impacting performance. Consider excluding these fields from BSON encoding in Metrics or avoid persisting PerformanceMetrics entirely.</violation>
</file>

<file name="internal/profiling/sqlhook.go">

<violation number="1" location="internal/profiling/sqlhook.go:79">
Nil check missing before calling profiler; calling RecordSQLQuery on a nil *Profiler will panic.</violation>

<violation number="2" location="internal/profiling/sqlhook.go:187">
Using context.Background here prevents SQL metrics from being recorded; store and reuse the prepare context or pass a request-specific ctx.</violation>
</file>

<file name="internal/dashboard/ui/tsconfig.json">

<violation number="1" location="internal/dashboard/ui/tsconfig.json:17">
Path alias for React should point to &quot;preact/compat&quot;, not &quot;@preact/compat&quot;. Use the official Preact compat subpath to ensure correct type/module resolution.</violation>

<violation number="2" location="internal/dashboard/ui/tsconfig.json:18">
Path alias for react-dom should point to &quot;preact/compat&quot;, not &quot;@preact/compat&quot;. Align with Preact’s compat subpath for reliable resolution.</violation>
</file>

<file name="internal/dashboard/ui/src/components/RequestTrace.tsx">

<violation number="1" location="internal/dashboard/ui/src/components/RequestTrace.tsx:222">
Date parsing without validation can yield NaN when start_time/end_time are missing; this breaks sorting and position calculations in the timeline.</violation>

<violation number="2" location="internal/dashboard/ui/src/components/RequestTrace.tsx:237">
Sorting directly on potentially NaN startTime can result in undefined ordering; guard with fallbacks.</violation>

<violation number="3" location="internal/dashboard/ui/src/components/RequestTrace.tsx:247">
minTime derived from the first event can be NaN; compute the minimum across valid startTimes instead.</violation>

<violation number="4" location="internal/dashboard/ui/src/components/RequestTrace.tsx:248">
maxTime calculation can become NaN due to invalid times and truthiness; filter invalid values explicitly.</violation>
</file>

<file name="internal/dashboard/ui/src/components/EnvironmentInfo.tsx">

<violation number="1" location="internal/dashboard/ui/src/components/EnvironmentInfo.tsx:61">
Guard against division by zero when computing memoryPercentage to avoid NaN and invalid style/text rendering.</violation>
</file>

<file name="internal/dashboard/ui/src/lib/api.ts">

<violation number="1" location="internal/dashboard/ui/src/lib/api.ts:110">
URL parameters are not encoded; ids with special characters can break the request or inject extra parameters.</violation>

<violation number="2" location="internal/dashboard/ui/src/lib/api.ts:129">
Missing URL-encoding for requestId; special characters can break the metrics request.</violation>

<violation number="3" location="internal/dashboard/ui/src/lib/api.ts:135">
Missing URL-encoding for requestId; special characters can break the flame graph request.</violation>
</file>

<file name="cmd/examples/profiling/main.go">

<violation number="1" location="cmd/examples/profiling/main.go:321">
rows.Scan error is ignored; failures during scan will be silently dropped, risking incorrect results.</violation>

<violation number="2" location="cmd/examples/profiling/main.go:337">
rows.Scan error is ignored in products loop; failures during scan will be silently dropped.</violation>

<violation number="3" location="cmd/examples/profiling/main.go:345">
db.QueryRow().Scan error is ignored; on failure, order_count becomes misleading.</violation>

<violation number="4" location="cmd/examples/profiling/main.go:415">
db.QueryRow().Scan error is ignored; user_count may be incorrect on failure.</violation>
</file>

<file name="internal/dashboard/ui/src/components/RequestDrawer.tsx">

<violation number="1" location="internal/dashboard/ui/src/components/RequestDrawer.tsx:202">
Response size is calculated from string length (characters), not bytes; this misreports size for non-ASCII content.</violation>
</file>

<file name=".gitignore">

<violation number="1" location=".gitignore:40">
Example binary ignore is incomplete; tracing-example remains tracked. Use a wildcard to ignore all *-example binaries.</violation>
</file>

<file name="internal/profiling/profiler.go">

<violation number="1" location="internal/profiling/profiler.go:248">
Tracer retrieval uses a different context key than the one used to store it in middleware, so SQL tracing is never recorded.</violation>
</file>

<file name="cmd/examples/profiling/README.md">

<violation number="1" location="cmd/examples/profiling/README.md:235">
Retrieving the profiler via ctx.Value(&quot;profiler&quot;) is incorrect; the profiler is not stored in context, so this snippet will never succeed.</violation>
</file>

<file name="internal/dashboard/ui/src/components/ui/dialog.tsx">

<violation number="1" location="internal/dashboard/ui/src/components/ui/dialog.tsx:41">
DialogContent uses tailwindcss-animate utilities but tailwindcss-animate plugin is not configured; animations (fade/zoom/slide) will not work.</violation>
</file>

<file name="internal/dashboard/ui/src/components/ui/tooltip.tsx">

<violation number="1" location="internal/dashboard/ui/src/components/ui/tooltip.tsx:22">
Uses tailwindcss-animate-only utilities without configuring the plugin, so animations won&#39;t apply. Replace with classes defined in your Tailwind config (e.g., animate-fade-in) or add the plugin.</violation>
</file>

<file name="internal/dashboard/ui/src/components/ui/sheet.tsx">

<violation number="1" location="internal/dashboard/ui/src/components/ui/sheet.tsx:32">
Animate-in/out variants referenced here require tailwindcss-animate but the project lacks it; these classes will have no effect.</violation>
</file>

React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.

<div id="app">
<div class="loading">Loading GoVisual Dashboard...</div>
</div>
<script src="/dashboard.js"></script>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolute script path will bypass the dashboard mount and fail to load under non-root mounting; use a relative path.

Prompt for AI agents
Address the following comment on internal/dashboard/static/index.html at line 24:

<comment>Absolute script path will bypass the dashboard mount and fail to load under non-root mounting; use a relative path.</comment>

<file context>
@@ -0,0 +1,26 @@
+    &lt;div id=&quot;app&quot;&gt;
+        &lt;div class=&quot;loading&quot;&gt;Loading GoVisual Dashboard...&lt;/div&gt;
+    &lt;/div&gt;
+    &lt;script src=&quot;/dashboard.js&quot;&gt;&lt;/script&gt;
+&lt;/body&gt;
+&lt;/html&gt;
</file context>

<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GoVisual Dashboard</title>
<link rel="stylesheet" href="/styles.css">
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolute asset path breaks when dashboard is mounted under a subpath; use a relative path so assets load under the dashboard prefix.

Prompt for AI agents
Address the following comment on internal/dashboard/static/index.html at line 7:

<comment>Absolute asset path breaks when dashboard is mounted under a subpath; use a relative path so assets load under the dashboard prefix.</comment>

<file context>
@@ -0,0 +1,26 @@
+    &lt;meta charset=&quot;UTF-8&quot;&gt;
+    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
+    &lt;title&gt;GoVisual Dashboard&lt;/title&gt;
+    &lt;link rel=&quot;stylesheet&quot; href=&quot;/styles.css&quot;&gt;
+    &lt;style&gt;
+        /* Critical CSS for initial load */
</file context>

rt.currentPath = append(rt.currentPath, len(rt.Traces)-1)
} else {
// Nested trace
parent := rt.getParentTrace()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nested StartTrace incorrectly uses the parent of the current trace, preventing proper child nesting and dropping first-level children under root.

Prompt for AI agents
Address the following comment on internal/middleware/tracer.go at line 65:

<comment>Nested StartTrace incorrectly uses the parent of the current trace, preventing proper child nesting and dropping first-level children under root.</comment>

<file context>
@@ -0,0 +1,285 @@
+		rt.currentPath = append(rt.currentPath, len(rt.Traces)-1)
+	} else {
+		// Nested trace
+		parent := rt.getParentTrace()
+		if parent != nil {
+			parent.Children = append(parent.Children, entry)
</file context>


// Check for auth header
if auth := r.Header.Get("Authorization"); auth == "" {
tracer.RecordCustom("Auth Failed", map[string]interface{}{
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possible nil pointer dereference: tracer.RecordCustom is called without checking tracer != nil.

Prompt for AI agents
Address the following comment on cmd/examples/tracing/main.go at line 51:

<comment>Possible nil pointer dereference: tracer.RecordCustom is called without checking tracer != nil.</comment>

<file context>
@@ -0,0 +1,276 @@
+
+		// Check for auth header
+		if auth := r.Header.Get(&quot;Authorization&quot;); auth == &quot;&quot; {
+			tracer.RecordCustom(&quot;Auth Failed&quot;, map[string]interface{}{
+				&quot;reason&quot;: &quot;missing_header&quot;,
+			})
</file context>

}

// profilingResponseWriter extends responseWriter with profiling capabilities
type profilingResponseWriter struct {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

profilingResponseWriter drops optional http interfaces (Flusher/Hijacker/Pusher), potentially breaking streaming, SSE, and WebSockets.

Prompt for AI agents
Address the following comment on internal/middleware/profiling.go at line 163:

<comment>profilingResponseWriter drops optional http interfaces (Flusher/Hijacker/Pusher), potentially breaking streaming, SSE, and WebSockets.</comment>

<file context>
@@ -0,0 +1,246 @@
+}
+
+// profilingResponseWriter extends responseWriter with profiling capabilities
+type profilingResponseWriter struct {
+	responseWriter *responseWriter
+	profiler       *profiling.Profiler
</file context>


// 3. Database queries
var userCount int
db.QueryRow("SELECT COUNT(*) FROM users").Scan(&userCount)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

db.QueryRow().Scan error is ignored; user_count may be incorrect on failure.

Prompt for AI agents
Address the following comment on cmd/examples/profiling/main.go at line 415:

<comment>db.QueryRow().Scan error is ignored; user_count may be incorrect on failure.</comment>

<file context>
@@ -0,0 +1,466 @@
+
+	// 3. Database queries
+	var userCount int
+	db.QueryRow(&quot;SELECT COUNT(*) FROM users&quot;).Scan(&amp;userCount)
+	results[&quot;user_count&quot;] = userCount
+
</file context>

<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-slate-200 bg-white p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg dark:border-slate-800 dark:bg-slate-950",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DialogContent uses tailwindcss-animate utilities but tailwindcss-animate plugin is not configured; animations (fade/zoom/slide) will not work.

Prompt for AI agents
Address the following comment on internal/dashboard/ui/src/components/ui/dialog.tsx at line 41:

<comment>DialogContent uses tailwindcss-animate utilities but tailwindcss-animate plugin is not configured; animations (fade/zoom/slide) will not work.</comment>

<file context>
@@ -0,0 +1,122 @@
+    &lt;DialogPrimitive.Content
+      ref={ref}
+      className={cn(
+        &quot;fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-slate-200 bg-white p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg dark:border-slate-800 dark:bg-slate-950&quot;,
+        className
+      )}
</file context>

ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 overflow-hidden rounded-md border border-slate-200 bg-white px-3 py-1.5 text-sm text-slate-950 shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-tooltip-content-transform-origin] dark:border-slate-800 dark:bg-slate-950 dark:text-slate-50",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uses tailwindcss-animate-only utilities without configuring the plugin, so animations won't apply. Replace with classes defined in your Tailwind config (e.g., animate-fade-in) or add the plugin.

Prompt for AI agents
Address the following comment on internal/dashboard/ui/src/components/ui/tooltip.tsx at line 22:

<comment>Uses tailwindcss-animate-only utilities without configuring the plugin, so animations won&#39;t apply. Replace with classes defined in your Tailwind config (e.g., animate-fade-in) or add the plugin.</comment>

<file context>
@@ -0,0 +1,30 @@
+    ref={ref}
+    sideOffset={sideOffset}
+    className={cn(
+      &quot;z-50 overflow-hidden rounded-md border border-slate-200 bg-white px-3 py-1.5 text-sm text-slate-950 shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-tooltip-content-transform-origin] dark:border-slate-800 dark:bg-slate-950 dark:text-slate-50&quot;,
+      className
+    )}
</file context>

SheetOverlay.displayName = SheetPrimitive.Overlay.displayName

const sheetVariants = cva(
"fixed z-50 gap-4 bg-white p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500 dark:bg-slate-950",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Animate-in/out variants referenced here require tailwindcss-animate but the project lacks it; these classes will have no effect.

Prompt for AI agents
Address the following comment on internal/dashboard/ui/src/components/ui/sheet.tsx at line 32:

<comment>Animate-in/out variants referenced here require tailwindcss-animate but the project lacks it; these classes will have no effect.</comment>

<file context>
@@ -0,0 +1,138 @@
+SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
+
+const sheetVariants = cva(
+  &quot;fixed z-50 gap-4 bg-white p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500 dark:bg-slate-950&quot;,
+  {
+    variants: {
</file context>

<div>Value: ${d.value}</div>
`;
tooltipRef.current.style.display = "block";
tooltipRef.current.style.left = event.pageX + 10 + "px";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use clientX/clientY for positioning when the tooltip uses position: fixed; pageX/pageY lead to incorrect offsets on scroll.

Prompt for AI agents
Address the following comment on internal/dashboard/ui/src/components/FlameGraph.tsx at line 79:

<comment>Use clientX/clientY for positioning when the tooltip uses position: fixed; pageX/pageY lead to incorrect offsets on scroll.</comment>

<file context>
@@ -0,0 +1,144 @@
+            &lt;div&gt;Value: ${d.value}&lt;/div&gt;
+          `;
+          tooltipRef.current.style.display = &quot;block&quot;;
+          tooltipRef.current.style.left = event.pageX + 10 + &quot;px&quot;;
+          tooltipRef.current.style.top = event.pageY - 28 + &quot;px&quot;;
+        }
</file context>

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

40 issues found across 78 files

Prompt for AI agents (all 40 issues)

Understand the root cause of the following 40 issues and fix them.


<file name="internal/dashboard/static/index.html">

<violation number="1" location="internal/dashboard/static/index.html:7">
Absolute asset path breaks when dashboard is mounted under a subpath; use a relative path so assets load under the dashboard prefix.</violation>

<violation number="2" location="internal/dashboard/static/index.html:24">
Absolute script path will bypass the dashboard mount and fail to load under non-root mounting; use a relative path.</violation>
</file>

<file name="internal/middleware/tracer.go">

<violation number="1" location="internal/middleware/tracer.go:65">
Nested StartTrace incorrectly uses the parent of the current trace, preventing proper child nesting and dropping first-level children under root.</violation>
</file>

<file name="cmd/examples/tracing/main.go">

<violation number="1" location="cmd/examples/tracing/main.go:51">
Possible nil pointer dereference: tracer.RecordCustom is called without checking tracer != nil.</violation>
</file>

<file name="internal/middleware/profiling.go">

<violation number="1" location="internal/middleware/profiling.go:60">
Starting runtime CPU profiling per request is process-wide and unsafe under concurrent traffic; it may conflict and skew metrics.</violation>

<violation number="2" location="internal/middleware/profiling.go:67">
Unbounded io.ReadAll on request body with ignored error can cause OOM and misses read failures; limit and handle errors.</violation>

<violation number="3" location="internal/middleware/profiling.go:69">
Sensitive data risk: storing full request body without redaction or size limits.</violation>

<violation number="4" location="internal/middleware/profiling.go:163">
profilingResponseWriter drops optional http interfaces (Flusher/Hijacker/Pusher), potentially breaking streaming, SSE, and WebSockets.</violation>
</file>

<file name="internal/dashboard/ui/src/components/FlameGraph.tsx">

<violation number="1" location="internal/dashboard/ui/src/components/FlameGraph.tsx:73">
Avoid setting innerHTML with untrusted data; this allows XSS. Construct the tooltip with textContent or sanitized HTML.</violation>

<violation number="2" location="internal/dashboard/ui/src/components/FlameGraph.tsx:79">
Use clientX/clientY for positioning when the tooltip uses position: fixed; pageX/pageY lead to incorrect offsets on scroll.</violation>
</file>

<file name="internal/dashboard/ui/src/App.tsx">

<violation number="1" location="internal/dashboard/ui/src/App.tsx:58">
useEffect uses filters in its callback but has an empty dependency array, causing stale filter application on live updates.</violation>
</file>

<file name="internal/profiling/flamegraph.go">

<violation number="1" location="internal/profiling/flamegraph.go:161">
Hotspot detection never recurses from root because node.Value == 0 triggers an early return, yielding empty results.</violation>
</file>

<file name="internal/dashboard/handler.go">

<violation number="1" location="internal/dashboard/handler.go:32">
Error from fs.Sub is ignored; staticFS may be nil and later Open will panic. Handle the error or provide a safe fallback.</violation>
</file>

<file name="internal/dashboard/ui/src/components/RequestComparison.tsx">

<violation number="1" location="internal/dashboard/ui/src/components/RequestComparison.tsx:97">
Objects (including JSX elements) are JSON-stringified in cells, causing incorrect rendering (e.g., Status row). Render JSX elements as-is when detected.</violation>
</file>

<file name="internal/dashboard/ui/postcss.config.js">

<violation number="1" location="internal/dashboard/ui/postcss.config.js:4">
PostCSS config references autoprefixer, but the package is not installed, likely causing the Tailwind build to fail. Install autoprefixer (devDependency) or remove the plugin from the config.</violation>
</file>

<file name="internal/dashboard/ui/src/components/ExportImport.tsx">

<violation number="1" location="internal/dashboard/ui/src/components/ExportImport.tsx:65">
CSV export does not escape internal quotes or sanitize leading formula characters, risking malformed CSV and potential CSV injection in spreadsheet apps.</violation>
</file>

<file name="internal/model/request.go">

<violation number="1" location="internal/model/request.go:25">
Adding PerformanceMetrics with a BSON tag will persist raw CPU/heap profile bytes from Metrics to MongoDB, inflating storage and impacting performance. Consider excluding these fields from BSON encoding in Metrics or avoid persisting PerformanceMetrics entirely.</violation>
</file>

<file name="internal/profiling/sqlhook.go">

<violation number="1" location="internal/profiling/sqlhook.go:79">
Nil check missing before calling profiler; calling RecordSQLQuery on a nil *Profiler will panic.</violation>

<violation number="2" location="internal/profiling/sqlhook.go:187">
Using context.Background here prevents SQL metrics from being recorded; store and reuse the prepare context or pass a request-specific ctx.</violation>
</file>

<file name="internal/dashboard/ui/tsconfig.json">

<violation number="1" location="internal/dashboard/ui/tsconfig.json:17">
Path alias for React should point to &quot;preact/compat&quot;, not &quot;@preact/compat&quot;. Use the official Preact compat subpath to ensure correct type/module resolution.</violation>

<violation number="2" location="internal/dashboard/ui/tsconfig.json:18">
Path alias for react-dom should point to &quot;preact/compat&quot;, not &quot;@preact/compat&quot;. Align with Preact’s compat subpath for reliable resolution.</violation>
</file>

<file name="internal/dashboard/ui/src/components/RequestTrace.tsx">

<violation number="1" location="internal/dashboard/ui/src/components/RequestTrace.tsx:222">
Date parsing without validation can yield NaN when start_time/end_time are missing; this breaks sorting and position calculations in the timeline.</violation>

<violation number="2" location="internal/dashboard/ui/src/components/RequestTrace.tsx:237">
Sorting directly on potentially NaN startTime can result in undefined ordering; guard with fallbacks.</violation>

<violation number="3" location="internal/dashboard/ui/src/components/RequestTrace.tsx:247">
minTime derived from the first event can be NaN; compute the minimum across valid startTimes instead.</violation>

<violation number="4" location="internal/dashboard/ui/src/components/RequestTrace.tsx:248">
maxTime calculation can become NaN due to invalid times and truthiness; filter invalid values explicitly.</violation>
</file>

<file name="internal/dashboard/ui/src/components/EnvironmentInfo.tsx">

<violation number="1" location="internal/dashboard/ui/src/components/EnvironmentInfo.tsx:61">
Guard against division by zero when computing memoryPercentage to avoid NaN and invalid style/text rendering.</violation>
</file>

<file name="internal/dashboard/ui/src/lib/api.ts">

<violation number="1" location="internal/dashboard/ui/src/lib/api.ts:110">
URL parameters are not encoded; ids with special characters can break the request or inject extra parameters.</violation>

<violation number="2" location="internal/dashboard/ui/src/lib/api.ts:129">
Missing URL-encoding for requestId; special characters can break the metrics request.</violation>

<violation number="3" location="internal/dashboard/ui/src/lib/api.ts:135">
Missing URL-encoding for requestId; special characters can break the flame graph request.</violation>
</file>

<file name="cmd/examples/profiling/main.go">

<violation number="1" location="cmd/examples/profiling/main.go:321">
rows.Scan error is ignored; failures during scan will be silently dropped, risking incorrect results.</violation>

<violation number="2" location="cmd/examples/profiling/main.go:337">
rows.Scan error is ignored in products loop; failures during scan will be silently dropped.</violation>

<violation number="3" location="cmd/examples/profiling/main.go:345">
db.QueryRow().Scan error is ignored; on failure, order_count becomes misleading.</violation>

<violation number="4" location="cmd/examples/profiling/main.go:415">
db.QueryRow().Scan error is ignored; user_count may be incorrect on failure.</violation>
</file>

<file name="internal/dashboard/ui/src/components/RequestDrawer.tsx">

<violation number="1" location="internal/dashboard/ui/src/components/RequestDrawer.tsx:202">
Response size is calculated from string length (characters), not bytes; this misreports size for non-ASCII content.</violation>
</file>

<file name=".gitignore">

<violation number="1" location=".gitignore:40">
Example binary ignore is incomplete; tracing-example remains tracked. Use a wildcard to ignore all *-example binaries.</violation>
</file>

<file name="internal/profiling/profiler.go">

<violation number="1" location="internal/profiling/profiler.go:248">
Tracer retrieval uses a different context key than the one used to store it in middleware, so SQL tracing is never recorded.</violation>
</file>

<file name="cmd/examples/profiling/README.md">

<violation number="1" location="cmd/examples/profiling/README.md:235">
Retrieving the profiler via ctx.Value(&quot;profiler&quot;) is incorrect; the profiler is not stored in context, so this snippet will never succeed.</violation>
</file>

<file name="internal/dashboard/ui/src/components/ui/dialog.tsx">

<violation number="1" location="internal/dashboard/ui/src/components/ui/dialog.tsx:41">
DialogContent uses tailwindcss-animate utilities but tailwindcss-animate plugin is not configured; animations (fade/zoom/slide) will not work.</violation>
</file>

<file name="internal/dashboard/ui/src/components/ui/tooltip.tsx">

<violation number="1" location="internal/dashboard/ui/src/components/ui/tooltip.tsx:22">
Uses tailwindcss-animate-only utilities without configuring the plugin, so animations won&#39;t apply. Replace with classes defined in your Tailwind config (e.g., animate-fade-in) or add the plugin.</violation>
</file>

<file name="internal/dashboard/ui/src/components/ui/sheet.tsx">

<violation number="1" location="internal/dashboard/ui/src/components/ui/sheet.tsx:32">
Animate-in/out variants referenced here require tailwindcss-animate but the project lacks it; these classes will have no effect.</violation>
</file>

React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.

<div id="app">
<div class="loading">Loading GoVisual Dashboard...</div>
</div>
<script src="/dashboard.js"></script>
Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolute script path will bypass the dashboard mount and fail to load under non-root mounting; use a relative path.

Prompt for AI agents
Address the following comment on internal/dashboard/static/index.html at line 24:

<comment>Absolute script path will bypass the dashboard mount and fail to load under non-root mounting; use a relative path.</comment>

<file context>
@@ -0,0 +1,26 @@
+    &lt;div id=&quot;app&quot;&gt;
+        &lt;div class=&quot;loading&quot;&gt;Loading GoVisual Dashboard...&lt;/div&gt;
+    &lt;/div&gt;
+    &lt;script src=&quot;/dashboard.js&quot;&gt;&lt;/script&gt;
+&lt;/body&gt;
+&lt;/html&gt;
</file context>
Fix with Cubic

<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GoVisual Dashboard</title>
<link rel="stylesheet" href="/styles.css">
Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolute asset path breaks when dashboard is mounted under a subpath; use a relative path so assets load under the dashboard prefix.

Prompt for AI agents
Address the following comment on internal/dashboard/static/index.html at line 7:

<comment>Absolute asset path breaks when dashboard is mounted under a subpath; use a relative path so assets load under the dashboard prefix.</comment>

<file context>
@@ -0,0 +1,26 @@
+    &lt;meta charset=&quot;UTF-8&quot;&gt;
+    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
+    &lt;title&gt;GoVisual Dashboard&lt;/title&gt;
+    &lt;link rel=&quot;stylesheet&quot; href=&quot;/styles.css&quot;&gt;
+    &lt;style&gt;
+        /* Critical CSS for initial load */
</file context>
Fix with Cubic

rt.currentPath = append(rt.currentPath, len(rt.Traces)-1)
} else {
// Nested trace
parent := rt.getParentTrace()
Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nested StartTrace incorrectly uses the parent of the current trace, preventing proper child nesting and dropping first-level children under root.

Prompt for AI agents
Address the following comment on internal/middleware/tracer.go at line 65:

<comment>Nested StartTrace incorrectly uses the parent of the current trace, preventing proper child nesting and dropping first-level children under root.</comment>

<file context>
@@ -0,0 +1,285 @@
+		rt.currentPath = append(rt.currentPath, len(rt.Traces)-1)
+	} else {
+		// Nested trace
+		parent := rt.getParentTrace()
+		if parent != nil {
+			parent.Children = append(parent.Children, entry)
</file context>
Fix with Cubic


// Check for auth header
if auth := r.Header.Get("Authorization"); auth == "" {
tracer.RecordCustom("Auth Failed", map[string]interface{}{
Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possible nil pointer dereference: tracer.RecordCustom is called without checking tracer != nil.

Prompt for AI agents
Address the following comment on cmd/examples/tracing/main.go at line 51:

<comment>Possible nil pointer dereference: tracer.RecordCustom is called without checking tracer != nil.</comment>

<file context>
@@ -0,0 +1,276 @@
+
+		// Check for auth header
+		if auth := r.Header.Get(&quot;Authorization&quot;); auth == &quot;&quot; {
+			tracer.RecordCustom(&quot;Auth Failed&quot;, map[string]interface{}{
+				&quot;reason&quot;: &quot;missing_header&quot;,
+			})
</file context>
Fix with Cubic

}

// profilingResponseWriter extends responseWriter with profiling capabilities
type profilingResponseWriter struct {
Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

profilingResponseWriter drops optional http interfaces (Flusher/Hijacker/Pusher), potentially breaking streaming, SSE, and WebSockets.

Prompt for AI agents
Address the following comment on internal/middleware/profiling.go at line 163:

<comment>profilingResponseWriter drops optional http interfaces (Flusher/Hijacker/Pusher), potentially breaking streaming, SSE, and WebSockets.</comment>

<file context>
@@ -0,0 +1,246 @@
+}
+
+// profilingResponseWriter extends responseWriter with profiling capabilities
+type profilingResponseWriter struct {
+	responseWriter *responseWriter
+	profiler       *profiling.Profiler
</file context>
Fix with Cubic


// 3. Database queries
var userCount int
db.QueryRow("SELECT COUNT(*) FROM users").Scan(&userCount)
Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

db.QueryRow().Scan error is ignored; user_count may be incorrect on failure.

Prompt for AI agents
Address the following comment on cmd/examples/profiling/main.go at line 415:

<comment>db.QueryRow().Scan error is ignored; user_count may be incorrect on failure.</comment>

<file context>
@@ -0,0 +1,466 @@
+
+	// 3. Database queries
+	var userCount int
+	db.QueryRow(&quot;SELECT COUNT(*) FROM users&quot;).Scan(&amp;userCount)
+	results[&quot;user_count&quot;] = userCount
+
</file context>
Fix with Cubic

<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-slate-200 bg-white p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg dark:border-slate-800 dark:bg-slate-950",
Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DialogContent uses tailwindcss-animate utilities but tailwindcss-animate plugin is not configured; animations (fade/zoom/slide) will not work.

Prompt for AI agents
Address the following comment on internal/dashboard/ui/src/components/ui/dialog.tsx at line 41:

<comment>DialogContent uses tailwindcss-animate utilities but tailwindcss-animate plugin is not configured; animations (fade/zoom/slide) will not work.</comment>

<file context>
@@ -0,0 +1,122 @@
+    &lt;DialogPrimitive.Content
+      ref={ref}
+      className={cn(
+        &quot;fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-slate-200 bg-white p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg dark:border-slate-800 dark:bg-slate-950&quot;,
+        className
+      )}
</file context>
Fix with Cubic

ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 overflow-hidden rounded-md border border-slate-200 bg-white px-3 py-1.5 text-sm text-slate-950 shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-tooltip-content-transform-origin] dark:border-slate-800 dark:bg-slate-950 dark:text-slate-50",
Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uses tailwindcss-animate-only utilities without configuring the plugin, so animations won't apply. Replace with classes defined in your Tailwind config (e.g., animate-fade-in) or add the plugin.

Prompt for AI agents
Address the following comment on internal/dashboard/ui/src/components/ui/tooltip.tsx at line 22:

<comment>Uses tailwindcss-animate-only utilities without configuring the plugin, so animations won&#39;t apply. Replace with classes defined in your Tailwind config (e.g., animate-fade-in) or add the plugin.</comment>

<file context>
@@ -0,0 +1,30 @@
+    ref={ref}
+    sideOffset={sideOffset}
+    className={cn(
+      &quot;z-50 overflow-hidden rounded-md border border-slate-200 bg-white px-3 py-1.5 text-sm text-slate-950 shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-tooltip-content-transform-origin] dark:border-slate-800 dark:bg-slate-950 dark:text-slate-50&quot;,
+      className
+    )}
</file context>
Fix with Cubic

SheetOverlay.displayName = SheetPrimitive.Overlay.displayName

const sheetVariants = cva(
"fixed z-50 gap-4 bg-white p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500 dark:bg-slate-950",
Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Animate-in/out variants referenced here require tailwindcss-animate but the project lacks it; these classes will have no effect.

Prompt for AI agents
Address the following comment on internal/dashboard/ui/src/components/ui/sheet.tsx at line 32:

<comment>Animate-in/out variants referenced here require tailwindcss-animate but the project lacks it; these classes will have no effect.</comment>

<file context>
@@ -0,0 +1,138 @@
+SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
+
+const sheetVariants = cva(
+  &quot;fixed z-50 gap-4 bg-white p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500 dark:bg-slate-950&quot;,
+  {
+    variants: {
</file context>
Fix with Cubic

<div>Value: ${d.value}</div>
`;
tooltipRef.current.style.display = "block";
tooltipRef.current.style.left = event.pageX + 10 + "px";
Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use clientX/clientY for positioning when the tooltip uses position: fixed; pageX/pageY lead to incorrect offsets on scroll.

Prompt for AI agents
Address the following comment on internal/dashboard/ui/src/components/FlameGraph.tsx at line 79:

<comment>Use clientX/clientY for positioning when the tooltip uses position: fixed; pageX/pageY lead to incorrect offsets on scroll.</comment>

<file context>
@@ -0,0 +1,144 @@
+            &lt;div&gt;Value: ${d.value}&lt;/div&gt;
+          `;
+          tooltipRef.current.style.display = &quot;block&quot;;
+          tooltipRef.current.style.left = event.pageX + 10 + &quot;px&quot;;
+          tooltipRef.current.style.top = event.pageY - 28 + &quot;px&quot;;
+        }
</file context>
Fix with Cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments