Skip to content

feat: implement real-time performance overlay (#168) - #182

Merged
krishkhinchi merged 2 commits into
7-Blocks:mainfrom
Pranitrane:feature/168-realtime-performance-overlay
Aug 13, 2026
Merged

feat: implement real-time performance overlay (#168)#182
krishkhinchi merged 2 commits into
7-Blocks:mainfrom
Pranitrane:feature/168-realtime-performance-overlay

Conversation

@Pranitrane

@Pranitrane Pranitrane commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

User description

Summary

This PR implements a real-time developer performance overlay for the KEPLER platform. The overlay monitors application rendering frame rates, 3D Cesium scene update timings, browser memory usage, and API network request latencies in real time. It assists developers in quickly identifying performance bottlenecks and optimizing satellite visualizations during development and testing.

Key Changes

  • Performance Store (performanceStore.ts): Built a Zustand store to manage real-time FPS, frame update times, Cesium scene update metrics, JS Heap memory telemetry, API call logs, and performance bottleneck alerts.
  • Network Interceptor (networkInterceptor.ts): Non-intrusively wraps window.fetch to track active network request counters, average API latency, status codes, and flags slow endpoints (> 500ms).
  • Cesium Performance Hook (useCesiumPerformance.ts): Hooks into Cesium.Viewer.scene.preRender and postRender events to track scene update durations, total active entities, and rendered satellite counts on EarthTwin.tsx.
  • Developer Overlay UI (PerformanceOverlay.tsx):
    • Interactive Positioning & Dragging: Supports free-form mouse dragging and corner docking presets (top-right, top-left, bottom-right, bottom-left).
    • Compact Minimized Mode: High-density pill bar showing live FPS, satellite count, and memory footprint.
    • 5 Expandable Diagnostic Tabs: Overview (with live FPS sparkline chart), Cesium 3D Scene, Network & API Latency, Memory & Resources, and Bottlenecks Log.
    • Keyboard Shortcut: Pressing Shift + P toggles overlay visibility.
  • Header Toggle Button (PerformanceToggleButton.tsx): Compact status button mounted in the header of MainLayout.tsx showing live FPS and active bottleneck warnings.

Related Issue

Fixes #168

Type of Change

  • 🐛 Bug fix
  • ✨ New feature
  • 📚 Documentation update
  • 🚀 Performance improvement
  • 🎨 UI/UX enhancement
  • 🔧 Refactoring
  • 🧹 Chore / Maintenance
  • 🔒 Security improvement

Screenshots / Screen Recordings

1. Expanded Diagnostics Panel

Expanded Performance Overlay

2. Minimized Compact Pill Mode

Minimized Performance Bar

Testing Performed

  • Tested locally
  • Tested relevant functionality
  • Checked for linting errors
  • Checked for TypeScript/build errors
  • Tested responsive behavior (if applicable)

Test Verification Summary:

  • Verified clean TypeScript compilation and Vite bundle creation via npm run build.
  • Verified dragging, corner docking, tab switching, and minimizing/expanding behavior.
  • Verified real-time telemetry updates during satellite navigation, camera rotation, and API fetching.
  • Verified Shift + P keyboard shortcut toggle.

Breaking Changes

None. This feature is strictly additive and non-breaking.

Checklist

  • I have read and followed the contributing guidelines.
  • My code follows the project's coding standards and guidelines.
  • I have completed testing of my changes.
  • I have updated documentation where applicable.
  • I have checked that my changes do not introduce unintended regressions.

ECSoC26 Submission

ECSoC26 contributors only — select your difficulty level by checking exactly one box below.
Leaving all boxes unchecked, or checking more than one, will cause the automation to fail.

  • ECSoC26-L1 – Beginner
  • ECSoC26-L2 – Intermediate
  • ECSoC26-L3 – Advanced

CodeAnt-AI Description

Add a real-time performance overlay for monitoring the 3D globe and application activity

What Changed

  • Added a developer overlay that shows live frame rate, frame timing, satellite and scene entity counts, memory usage, and API latency
  • Added network request history with status, duration, active request counts, and warnings for calls taking over 500 ms
  • Added alerts for low frame rates, slow scene updates, high memory usage, and slow API responses
  • Added docked or draggable positioning, minimized mode, diagnostic tabs, alert clearing, and a Shift + P visibility shortcut
  • Added a header toggle showing current FPS and active performance warnings

Impact

✅ Faster identification of slow globe rendering
✅ Clearer API and memory diagnostics
✅ Immediate visibility into performance bottlenecks

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

Summary by CodeRabbit

  • New Features
    • Added a draggable performance diagnostics overlay with minimized and tabbed views.
    • Added monitoring for frame rate, scene updates, Cesium metrics, memory usage, network activity, and bottlenecks.
    • Added a command-bar toggle, live FPS display, warning indicators, and the Shift+P keyboard shortcut.
    • Added automatic alerts for low performance, high memory usage, slow scene updates, and slow API calls.
    • Added development proxy support for API and WebSocket connections.

@codeant-ai

codeant-ai Bot commented Aug 9, 2026

Copy link
Copy Markdown

🤖 CodeAnt AI — Review Status

Status Commit Started (UTC) Finished (UTC)
✅ Reviewed your PR 1edb84c Aug 09, 2026 · 12:36 12:39

@codeant-ai

codeant-ai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@github-actions github-actions Bot added ECSoC26 Official label for ECSoC26 event contributions. ECSoC26-L2 Level 2 contribution for the ECSoC26 event. backend Backend development bug Something isn't working database Database and schema related changes documentation Improvements or additions to documentation enhancement New feature or request frontend Frontend development size/XL Very large or complex contribution. type:backend Changes backend services or server-side logic. type:bug Fixes an existing bug or unexpected behavior. type:documentation Improves project documentation. type:feature Introduces a new feature or enhancement. type:frontend Changes frontend or client-side code. labels Aug 9, 2026
@codeant-ai codeant-ai Bot added the size:XL This PR changes 500-999 lines, ignoring generated files label Aug 9, 2026
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 89ce078c-a975-48ee-b6ae-5fe0e725f66c

📥 Commits

Reviewing files that changed from the base of the PR and between 1edb84c and 8a401d9.

📒 Files selected for processing (3)
  • frontend/src/components/EarthTwin.tsx
  • frontend/src/components/layouts/MainLayout.tsx
  • frontend/tsconfig.app.json
💤 Files with no reviewable changes (1)
  • frontend/tsconfig.app.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/components/layouts/MainLayout.tsx
  • frontend/src/components/EarthTwin.tsx

📝 Walkthrough

Walkthrough

The frontend adds a Zustand performance store, Cesium and network instrumentation, and a configurable diagnostics overlay. The overlay is integrated into the main layout with FPS, memory, network, Cesium, and bottleneck views.

Changes

Performance diagnostics

Layer / File(s) Summary
Metrics store and Cesium collection
frontend/src/store/performanceStore.ts, frontend/src/hooks/useCesiumPerformance.ts, frontend/src/components/EarthTwin.tsx
The performance store tracks frame, Cesium, memory, network, and bottleneck metrics. The Cesium hook records render timing and entity counts for the active viewer.
Network instrumentation and runtime setup
frontend/src/utils/networkInterceptor.ts, frontend/vite.config.ts, frontend/tsconfig.app.json
The fetch interceptor records request activity, timing, status, and failures. Vite proxies API and WebSocket traffic. TypeScript configuration values remain unchanged while the arrays use expanded formatting.
Diagnostics overlay and layout integration
frontend/src/components/PerformanceOverlay/*, frontend/src/components/layouts/MainLayout.tsx
The layout adds the toggle button and overlay. The overlay supports dragging, minimizing, diagnostic tabs, keyboard toggling, metric displays, and bottleneck alerts.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CesiumViewer
  participant useCesiumPerformance
  participant usePerformanceStore
  participant PerformanceOverlay
  CesiumViewer->>useCesiumPerformance: emit render events
  useCesiumPerformance->>usePerformanceStore: publish Cesium metrics
  PerformanceOverlay->>usePerformanceStore: read metrics and alerts
  usePerformanceStore-->>PerformanceOverlay: return diagnostic data
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the main change: a real-time performance overlay.
Description check ✅ Passed The description covers the required sections, issue link, feature scope, screenshots, testing, breaking changes, checklist, and one ECSoC26 level.
Linked Issues check ✅ Passed The implementation addresses issue #168 requirements for telemetry, diagnostics, alerts, network tracking, and overlay controls.
Out of Scope Changes check ✅ Passed The changes remain related to the performance overlay; configuration formatting and development proxies support the frontend implementation.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the AI Artificial Intelligence and Machine Learning label Aug 9, 2026

</div>

<PerformanceOverlay />

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: PerformanceOverlay is mounted permanently, and its effects start the animation-frame loop, memory polling, keyboard listener, and network interception before checking isEnabled; closing or never opening the overlay only returns null from its render and does not stop those activities. This imposes continuous monitoring overhead on every dashboard session even when the developer overlay is disabled. Gate the monitoring effects on enabled state or mount the monitoring lifecycle only while enabled. [performance]

Severity Level: Major ⚠️
- ⚠️ Every dashboard session performs hidden frame monitoring.
- ⚠️ All fetches incur interceptor and store-update overhead.
- ⚠️ Disabled overlay still polls browser memory telemetry.

Fix in Cursor Fix in VSCode Claude

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** frontend/src/components/layouts/MainLayout.tsx
**Line:** 414:414
**Comment:**
	*Performance: `PerformanceOverlay` is mounted permanently, and its effects start the animation-frame loop, memory polling, keyboard listener, and network interception before checking `isEnabled`; closing or never opening the overlay only returns `null` from its render and does not stop those activities. This imposes continuous monitoring overhead on every dashboard session even when the developer overlay is disabled. Gate the monitoring effects on enabled state or mount the monitoring lifecycle only while enabled.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +25 to +32
const entityCount = viewer.entities.values.length;
let satelliteCount = 0;

// Count entities marked as catalog satellites/objects
for (const entity of viewer.entities.values) {
if (entity.properties?.catalogData) {
satelliteCount++;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: Scanning every entity on every Cesium postRender event adds an O(n) operation to each rendered frame. Since useCesiumPerformance is installed for EarthTwin regardless of whether the overlay is enabled, large catalogs and collision sets can cause the diagnostic instrumentation itself to reduce rendering performance and distort the measured scene timings. Maintain these counts incrementally when entities are added or removed, or sample the scan less frequently. [performance]

Severity Level: Major ⚠️
- ⚠️ Cesium performs an entity scan on every rendered frame.
- ⚠️ Large catalog views receive unnecessary instrumentation work.
- ⚠️ Diagnostic overhead can distort reported scene timings.

Fix in Cursor Fix in VSCode Claude

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** frontend/src/hooks/useCesiumPerformance.ts
**Line:** 25:32
**Comment:**
	*Performance: Scanning every entity on every Cesium `postRender` event adds an O(n) operation to each rendered frame. Since `useCesiumPerformance` is installed for `EarthTwin` regardless of whether the overlay is enabled, large catalogs and collision sets can cause the diagnostic instrumentation itself to reduce rendering performance and distort the measured scene timings. Maintain these counts incrementally when entities are added or removed, or sample the scan less frequently.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +157 to +166
if (sceneTimeMs > 35) {
const alert: BottleneckAlert = {
id: `cesium-${Date.now()}`,
type: 'SLOW_SCENE_UPDATE',
severity: sceneTimeMs > 60 ? 'CRITICAL' : 'WARNING',
message: `Cesium scene update took ${sceneTimeMs.toFixed(1)}ms (${entityCount} active entities)`,
timestamp: Date.now(),
};
newBottlenecks = [alert, ...state.bottlenecks.slice(0, MAX_BOTTLENECK_LOGS - 1)];
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: Every frame whose measured scene time exceeds 35 ms appends another SLOW_SCENE_UPDATE alert, with no duplicate suppression or cooldown. A sustained slowdown therefore fills the 15-entry log with identical samples within a fraction of a second, hides other alert types, and keeps the warning indicator active even when the slowdown has ended. Suppress repeated alerts for the same condition or only add one after a recovery transition/cooldown. [logic error]

Severity Level: Major ⚠️
- ⚠️ Bottleneck history fills with identical scene alerts.
- ⚠️ Earlier memory and network alerts are evicted.
- ⚠️ Developers lose a useful diagnosis timeline.

Fix in Cursor Fix in VSCode Claude

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** frontend/src/store/performanceStore.ts
**Line:** 157:166
**Comment:**
	*Logic Error: Every frame whose measured scene time exceeds 35 ms appends another `SLOW_SCENE_UPDATE` alert, with no duplicate suppression or cooldown. A sustained slowdown therefore fills the 15-entry log with identical samples within a fraction of a second, hides other alert types, and keeps the warning indicator active even when the slowdown has ended. Suppress repeated alerts for the same condition or only add one after a recovery transition/cooldown.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +61 to +63
const currentFps = Math.min(60, Math.round((frameCount * 1000) / (now - lastFpsUpdate)));
const avgFrameTime = delta;
updateFrameMetrics(currentFps, avgFrameTime, isDropped);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: avgFrameTime is populated with only the final frame's delta, despite being displayed as the frame time for the entire 500 ms FPS sample and named as an average. A single scheduling stall or unusually short final frame can make the displayed frame time contradict the reported FPS and bottleneck message. Accumulate frame deltas during the sample and report their average, or rename the metric to indicate that it is the last-frame duration. [incorrect variable usage]

Severity Level: Major ⚠️
- ⚠️ Overview frame-time metric contradicts sampled FPS.
- ⚠️ Low-FPS alerts report misleading frame durations.
- ⚠️ Developers may misdiagnose rendering stalls.

Fix in Cursor Fix in VSCode Claude

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** frontend/src/components/PerformanceOverlay/PerformanceOverlay.tsx
**Line:** 61:63
**Comment:**
	*Incorrect Variable Usage: `avgFrameTime` is populated with only the final frame's `delta`, despite being displayed as the frame time for the entire 500 ms FPS sample and named as an average. A single scheduling stall or unusually short final frame can make the displayed frame time contradict the reported FPS and bottleneck message. Accumulate frame deltas during the sample and report their average, or rename the metric to indicate that it is the last-frame duration.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 15

🧹 Nitpick comments (3)
frontend/src/utils/networkInterceptor.ts (1)

3-13: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The fetch patch is permanent and cannot be removed.

initNetworkInterceptor replaces window.fetch and provides no way to restore originalFetch. Two consequences follow.

First, the wrapper stays active for the whole page lifetime even after the overlay unmounts. Second, if Vite hot-replaces this module during development, isInterceptorInitialized resets to false and the already-wrapped fetch is wrapped again. Requests are then counted twice.

Return a teardown function and call it from the overlay effect cleanup.

♻️ Proposed fix: return a teardown function
-export function initNetworkInterceptor(): void {
-  if (isInterceptorInitialized || typeof window === 'undefined') return;
+export function initNetworkInterceptor(): () => void {
+  if (isInterceptorInitialized || typeof window === 'undefined') return () => {};
   isInterceptorInitialized = true;
 
   const originalFetch = window.fetch;
   };
+
+  return () => {
+    window.fetch = originalFetch;
+    isInterceptorInitialized = false;
+  };
 }

Also applies to: 66-69

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/utils/networkInterceptor.ts` around lines 3 - 13, Update
initNetworkInterceptor to return a teardown function that restores the original
window.fetch and resets initialization state; ensure repeated initialization
does not layer wrappers, including after hot replacement. Update the overlay’s
effect cleanup to invoke this teardown when the overlay unmounts.
frontend/src/store/performanceStore.ts (2)

205-209: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

avgResponseTimeMs becomes insensitive to recent latency.

The average is cumulative over totalRequests for the whole session. After a few hundred requests, a new slow call barely moves the value. A live diagnostics tool benefits more from a recent-window average. Consider averaging over recentCalls instead, or keeping both values.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/store/performanceStore.ts` around lines 205 - 209, Update the
performance metrics calculation in the state update containing newAvgResponse so
avgResponseTimeMs reflects a recent-call window rather than the cumulative
session total. Reuse recentCalls to calculate the windowed average, ensuring the
current call is included and the window remains bounded; preserve cumulative
totals separately if they are still needed for overall statistics.

84-84: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

MAX_HISTORY_LENGTH is dead code; the history length is pinned to 20.

updateFrameMetrics uses state.fpsHistory.slice(1), so the history keeps the initial array length of 20. MAX_HISTORY_LENGTH at Line 84 is never read. Either seed the history from the constant, or bound the array with the constant.

♻️ Proposed fix to use the constant
-  fpsHistory: Array(20).fill(60),
+  fpsHistory: Array(MAX_HISTORY_LENGTH).fill(60),
-      const newFpsHistory = [...state.fpsHistory.slice(1), fps];
+      const newFpsHistory = [...state.fpsHistory, fps].slice(-MAX_HISTORY_LENGTH);

Also applies to: 98-98, 128-130

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/store/performanceStore.ts` at line 84, Make MAX_HISTORY_LENGTH
authoritative in the performance history flow: update the initial fpsHistory
seed and the updateFrameMetrics trimming logic to use this constant, ensuring
the array remains capped at 30 entries and eliminating the dead constant.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/src/components/layouts/MainLayout.tsx`:
- Around line 413-414: The developer performance tooling must be
development-only and collect telemetry only while enabled. In
frontend/src/components/layouts/MainLayout.tsx lines 413-414 and the toggle at
line 244, use import.meta.env.DEV gates and React.lazy for both
PerformanceOverlay and PerformanceToggleButton imports; in
frontend/src/components/PerformanceOverlay/PerformanceOverlay.tsx lines 42-44,
initialize the network interceptor only when isEnabled and restore fetch during
cleanup; in lines 47-104, depend on isEnabled in the frame and memory effects
and return early when disabled; in frontend/src/hooks/useCesiumPerformance.ts
lines 12-13, subscribe to isEnabled and attach or remove Cesium
preRender/postRender listeners according to its value.

In `@frontend/src/components/PerformanceOverlay/PerformanceOverlay.tsx`:
- Around line 7-35: Update
frontend/src/components/PerformanceOverlay/PerformanceOverlay.tsx lines 7-35 by
replacing the whole-store usePerformanceStore() destructure with individual
field selectors, and move metric subscriptions into a child component mounted
only when isEnabled is true; the overlay visibility controls may remain
subscribed in the parent. Update
frontend/src/components/PerformanceOverlay/PerformanceToggleButton.tsx line 6 to
select isEnabled, toggleOverlay, fps, and the alert count individually, avoiding
a whole-store subscription.
- Around line 96-98: Update the unsupported-memory branch in the performance
metrics interval to detect `perfObj.memory` being unavailable once, call
`updateMemoryMetrics({ isSupported: false })` only once, and stop or clear the
recurring interval afterward. Preserve the existing supported-memory update
flow.
- Around line 436-438: Update the status-color condition in the
PerformanceOverlay render around call.status so all HTTP 2xx statuses use the
success color, while non-2xx statuses—including 3xx responses—retain the error
color. Keep the existing displayed status text and ERR fallback unchanged.
- Around line 108-115: Update handleKeyDown so the overlay toggles only for an
unmodified Shift+P press: reject events with ctrlKey, metaKey, altKey, or other
unintended modifiers before preventDefault. Extend the editable-target guard
beyond INPUT, TEXTAREA, and SELECT to also ignore contentEditable elements,
while preserving the existing toggleOverlay behavior for valid shortcuts.
- Around line 249-266: Update the dock-position menu around the
PerformanceOverlay component’s position trigger and setPosition handler to use
open state: toggle the menu from the trigger’s onClick, keep it visible while
open as well as on hover, and ensure the trigger exposes its expanded state.
Preserve the existing position options and selection behavior.
- Around line 136-139: Update the drag coordinate clamping in the pointer-move
handler around setCustomCoords to derive the panel’s current width and height
from containerRef instead of fixed 300 and 200 pixel values. Use the measured
dimensions for the right and bottom bounds so both expanded and minimized states
remain fully within the viewport while preserving the existing minimum margins.
- Around line 53-67: Update measureFrame to report uncapped measured FPS,
compute avgFrameTime as (now - lastFpsUpdate) / frameCount, and accumulate
dropped frames throughout each sampling window rather than forwarding only the
boundary frame’s boolean. Change updateFrameMetrics and its performanceStore
implementation to accept and add the dropped-frame count, resetting the
accumulator after each update.

In `@frontend/src/components/PerformanceOverlay/PerformanceToggleButton.tsx`:
- Line 7: Update the warningCount calculation in PerformanceToggleButton to use
bottlenecks.length directly if all typed alert severities should be counted;
otherwise change the predicate to count only CRITICAL alerts, matching the
intended UI behavior and avoiding unnecessary array allocation.

In `@frontend/src/hooks/useCesiumPerformance.ts`:
- Around line 21-40: Update handlePostRender to throttle metric sampling to a
fixed interval rather than processing and writing metrics on every frame. Cache
satelliteCount and recompute it only when viewer.entities.values.length changes,
preserving the existing catalogData detection logic; only call
updateCesiumMetrics when the sampling interval elapses.
- Around line 45-51: Update the cleanup returned by useCesiumPerformance to
always invoke the removePre and removePost callbacks, removing the
scene.isDestroyed() guard around them. Preserve the existing listener
registration and ensure cleanup releases the handlers and viewer reference even
when the scene has already been destroyed.

In `@frontend/src/store/performanceStore.ts`:
- Line 136: Update all BottleneckAlert id assignments in updateCesiumMetrics and
addNetworkCall to guarantee uniqueness beyond Date.now(), reusing the existing
NetworkCallMetric random-suffix pattern or a module-level monotonic counter.
Apply the change to each alert id literal, including the assignments near the
referenced locations, while preserving their existing id prefixes.
- Around line 154-174: In performanceStore.ts, update both updateCesiumMetrics
(lines 154-174) and updateMemoryMetrics (lines 176-196) to prevent sustained
conditions from appending alerts on every call: apply the same time-based
cooldown used by updateFrameMetrics before adding SLOW_SCENE_UPDATE or
HIGH_MEMORY alerts, or skip insertion when the newest bottlenecks entry has the
same type. Preserve metric updates and the existing bounded alert buffer
behavior.

In `@frontend/src/utils/networkInterceptor.ts`:
- Around line 20-30: Update the URL capture logic in initNetworkInterceptor to
remove query strings before assigning request URLs to url, covering string, URL,
and Request inputs. Preserve the URL origin and path while ensuring recentCalls
never receives query parameters.
- Around line 32-34: Update the method-detection logic in the network
interceptor to fall back to the method on args[0] when args[1] is absent and
args[0] is a Request object. Preserve the existing args[1].method handling for
fetch(url, options), and ensure Request-based calls such as POST are recorded
with their actual method instead of defaulting to GET.

---

Nitpick comments:
In `@frontend/src/store/performanceStore.ts`:
- Around line 205-209: Update the performance metrics calculation in the state
update containing newAvgResponse so avgResponseTimeMs reflects a recent-call
window rather than the cumulative session total. Reuse recentCalls to calculate
the windowed average, ensuring the current call is included and the window
remains bounded; preserve cumulative totals separately if they are still needed
for overall statistics.
- Line 84: Make MAX_HISTORY_LENGTH authoritative in the performance history
flow: update the initial fpsHistory seed and the updateFrameMetrics trimming
logic to use this constant, ensuring the array remains capped at 30 entries and
eliminating the dead constant.

In `@frontend/src/utils/networkInterceptor.ts`:
- Around line 3-13: Update initNetworkInterceptor to return a teardown function
that restores the original window.fetch and resets initialization state; ensure
repeated initialization does not layer wrappers, including after hot
replacement. Update the overlay’s effect cleanup to invoke this teardown when
the overlay unmounts.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ee88bf55-a56c-44f8-8918-90392f44521b

📥 Commits

Reviewing files that changed from the base of the PR and between 9ac97e9 and 1edb84c.

⛔ Files ignored due to path filters (45)
  • backend/__pycache__/debug_log.cpython-314.pyc is excluded by !**/*.pyc
  • backend/ai/__pycache__/__init__.cpython-314.pyc is excluded by !**/*.pyc
  • backend/ai/__pycache__/openai_service.cpython-314.pyc is excluded by !**/*.pyc
  • backend/ai/__pycache__/workflow.cpython-314.pyc is excluded by !**/*.pyc
  • backend/api/__pycache__/__init__.cpython-314.pyc is excluded by !**/*.pyc
  • backend/api/__pycache__/router.cpython-314.pyc is excluded by !**/*.pyc
  • backend/api/v1/__pycache__/__init__.cpython-314.pyc is excluded by !**/*.pyc
  • backend/api/v1/endpoints/__pycache__/__init__.cpython-314.pyc is excluded by !**/*.pyc
  • backend/api/v1/endpoints/__pycache__/agents.cpython-314.pyc is excluded by !**/*.pyc
  • backend/api/v1/endpoints/__pycache__/auth.cpython-314.pyc is excluded by !**/*.pyc
  • backend/api/v1/endpoints/__pycache__/catalog.cpython-314.pyc is excluded by !**/*.pyc
  • backend/api/v1/endpoints/__pycache__/collisions.cpython-314.pyc is excluded by !**/*.pyc
  • backend/api/v1/endpoints/__pycache__/dashboard.cpython-314.pyc is excluded by !**/*.pyc
  • backend/api/v1/endpoints/__pycache__/satellites.cpython-314.pyc is excluded by !**/*.pyc
  • backend/api/v1/endpoints/__pycache__/weather.cpython-314.pyc is excluded by !**/*.pyc
  • backend/app/__pycache__/__init__.cpython-314.pyc is excluded by !**/*.pyc
  • backend/app/__pycache__/main.cpython-314.pyc is excluded by !**/*.pyc
  • backend/app/core/__pycache__/__init__.cpython-314.pyc is excluded by !**/*.pyc
  • backend/app/core/__pycache__/config.cpython-314.pyc is excluded by !**/*.pyc
  • backend/app/core/__pycache__/error_handlers.cpython-314.pyc is excluded by !**/*.pyc
  • backend/app/core/__pycache__/exceptions.cpython-314.pyc is excluded by !**/*.pyc
  • backend/app/core/__pycache__/scheduler.cpython-314.pyc is excluded by !**/*.pyc
  • backend/app/core/__pycache__/security.cpython-314.pyc is excluded by !**/*.pyc
  • backend/database/__pycache__/__init__.cpython-314.pyc is excluded by !**/*.pyc
  • backend/database/__pycache__/session.cpython-314.pyc is excluded by !**/*.pyc
  • backend/models/__pycache__/__init__.cpython-314.pyc is excluded by !**/*.pyc
  • backend/models/__pycache__/db_models.cpython-314.pyc is excluded by !**/*.pyc
  • backend/orbital/__pycache__/__init__.cpython-314.pyc is excluded by !**/*.pyc
  • backend/orbital/__pycache__/collision_engine.cpython-314.pyc is excluded by !**/*.pyc
  • backend/orbital/__pycache__/orbit_engine.cpython-314.pyc is excluded by !**/*.pyc
  • backend/orbital/__pycache__/spacetrack.cpython-314.pyc is excluded by !**/*.pyc
  • backend/orbital/providers/__pycache__/__init__.cpython-314.pyc is excluded by !**/*.pyc
  • backend/orbital/providers/__pycache__/base.cpython-314.pyc is excluded by !**/*.pyc
  • backend/orbital/providers/__pycache__/cache.cpython-314.pyc is excluded by !**/*.pyc
  • backend/orbital/providers/__pycache__/celestrak.cpython-314.pyc is excluded by !**/*.pyc
  • backend/orbital/providers/__pycache__/chain.cpython-314.pyc is excluded by !**/*.pyc
  • backend/orbital/providers/__pycache__/spacetrack.cpython-314.pyc is excluded by !**/*.pyc
  • backend/schemas/__pycache__/__init__.cpython-314.pyc is excluded by !**/*.pyc
  • backend/schemas/__pycache__/api_schemas.cpython-314.pyc is excluded by !**/*.pyc
  • backend/services/__pycache__/__init__.cpython-314.pyc is excluded by !**/*.pyc
  • backend/services/__pycache__/risk_service.cpython-314.pyc is excluded by !**/*.pyc
  • backend/services/__pycache__/simulation_service.cpython-314.pyc is excluded by !**/*.pyc
  • backend/services/__pycache__/weather_service.cpython-314.pyc is excluded by !**/*.pyc
  • backend/websocket/__pycache__/__init__.cpython-314.pyc is excluded by !**/*.pyc
  • backend/websocket/__pycache__/manager.cpython-314.pyc is excluded by !**/*.pyc
📒 Files selected for processing (9)
  • frontend/src/components/EarthTwin.tsx
  • frontend/src/components/PerformanceOverlay/PerformanceOverlay.tsx
  • frontend/src/components/PerformanceOverlay/PerformanceToggleButton.tsx
  • frontend/src/components/layouts/MainLayout.tsx
  • frontend/src/hooks/useCesiumPerformance.ts
  • frontend/src/store/performanceStore.ts
  • frontend/src/utils/networkInterceptor.ts
  • frontend/tsconfig.app.json
  • frontend/vite.config.ts

Comment thread frontend/src/components/layouts/MainLayout.tsx
Comment on lines +7 to +35
const {
isEnabled,
isMinimized,
position,
customCoords,
activeTab,
fps,
frameTimeMs,
droppedFrames,
fpsHistory,
renderedSatellites,
activeEntities,
cesiumSceneUpdateTimeMs,
memory,
activeRequests,
avgResponseTimeMs,
totalRequests,
slowRequestsCount,
recentCalls,
bottlenecks,
toggleOverlay,
toggleMinimized,
setPosition,
setCustomCoords,
setActiveTab,
updateFrameMetrics,
updateMemoryMetrics,
clearBottlenecks,
} = usePerformanceStore();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Both components subscribe to the entire performance store and re-render on every metric write.

Each component calls usePerformanceStore() with no selector. In Zustand v5 that subscribes to the whole state object, and every set() produces a new object identity. useCesiumPerformance writes once per rendered Cesium frame, so both components re-render at the frame rate.

  • frontend/src/components/PerformanceOverlay/PerformanceOverlay.tsx#L7-L35: replace the single destructure with one selector per field. Move the metric selectors into a child component that mounts only when isEnabled is true, so no metric subscription exists while the overlay is closed.
  • frontend/src/components/PerformanceOverlay/PerformanceToggleButton.tsx#L6-L6: select isEnabled, toggleOverlay, fps, and the alert count individually. This component stays mounted in the MainLayout header at all times, so the whole-store subscription re-renders the header on every frame.
📍 Affects 2 files
  • frontend/src/components/PerformanceOverlay/PerformanceOverlay.tsx#L7-L35 (this comment)
  • frontend/src/components/PerformanceOverlay/PerformanceToggleButton.tsx#L6-L6
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/PerformanceOverlay/PerformanceOverlay.tsx` around
lines 7 - 35, Update
frontend/src/components/PerformanceOverlay/PerformanceOverlay.tsx lines 7-35 by
replacing the whole-store usePerformanceStore() destructure with individual
field selectors, and move metric subscriptions into a child component mounted
only when isEnabled is true; the overlay visibility controls may remain
subscribed in the parent. Update
frontend/src/components/PerformanceOverlay/PerformanceToggleButton.tsx line 6 to
select isEnabled, toggleOverlay, fps, and the alert count individually, avoiding
a whole-store subscription.

Comment on lines +53 to +67
const measureFrame = (now: number) => {
const delta = now - lastTime;
lastTime = now;
frameCount++;

const isDropped = delta > 33.3; // Dropped frame threshold (> 30fps baseline)

if (now - lastFpsUpdate >= 500) { // Update store twice per second
const currentFps = Math.min(60, Math.round((frameCount * 1000) / (now - lastFpsUpdate)));
const avgFrameTime = delta;
updateFrameMetrics(currentFps, avgFrameTime, isDropped);

frameCount = 0;
lastFpsUpdate = now;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Three measurement defects in the frame sampler: the 60 FPS clamp, the mislabeled average, and the dropped-frame undercount.

Math.min(60, ...) at Line 61 caps the reported rate. On a 120 Hz or 144 Hz display the overlay always reports 60 and hides the real frame rate. A performance tool must report the measured value.

avgFrameTime at Line 62 is assigned delta, which is the duration of the single frame that crossed the 500 ms boundary. It is not an average. The true average is available as (now - lastFpsUpdate) / frameCount.

isDropped at Line 58 is computed on every frame but only forwarded at the sampling boundary. droppedFrames therefore increments at most twice per second and ignores every dropped frame between samples. Accumulate the count across the window instead.

🐛 Proposed fix
     let frameCount = 0;
     let lastFpsUpdate = performance.now();
+    let droppedInWindow = 0;
 
     const measureFrame = (now: number) => {
       const delta = now - lastTime;
       lastTime = now;
       frameCount++;
 
-      const isDropped = delta > 33.3; // Dropped frame threshold (> 30fps baseline)
+      if (delta > 33.3) droppedInWindow++; // Dropped frame threshold (> 30fps baseline)
 
       if (now - lastFpsUpdate >= 500) { // Update store twice per second
-        const currentFps = Math.min(60, Math.round((frameCount * 1000) / (now - lastFpsUpdate)));
-        const avgFrameTime = delta;
-        updateFrameMetrics(currentFps, avgFrameTime, isDropped);
-
+        const elapsed = now - lastFpsUpdate;
+        const currentFps = Math.round((frameCount * 1000) / elapsed);
+        const avgFrameTime = elapsed / frameCount;
+        updateFrameMetrics(currentFps, avgFrameTime, droppedInWindow);
+
+        droppedInWindow = 0;
         frameCount = 0;
         lastFpsUpdate = now;
       }

This changes the third argument of updateFrameMetrics from a boolean to a count. Update the signature in performanceStore.ts at Line 74 and the accumulation at Line 131 accordingly:

-  updateFrameMetrics: (fps: number, frameTimeMs: number, isDroppedFrame: boolean) => void;
+  updateFrameMetrics: (fps: number, frameTimeMs: number, droppedFrameCount: number) => void;
-      const newDropped = isDroppedFrame ? state.droppedFrames + 1 : state.droppedFrames;
+      const newDropped = state.droppedFrames + droppedFrameCount;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/PerformanceOverlay/PerformanceOverlay.tsx` around
lines 53 - 67, Update measureFrame to report uncapped measured FPS, compute
avgFrameTime as (now - lastFpsUpdate) / frameCount, and accumulate dropped
frames throughout each sampling window rather than forwarding only the boundary
frame’s boolean. Change updateFrameMetrics and its performanceStore
implementation to accept and add the dropped-frame count, resetting the
accumulator after each update.

Comment on lines +96 to +98
} else {
updateMemoryMetrics({ isSupported: false });
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

Avoid the repeated store write when performance.memory is unavailable.

On Firefox and Safari, perfObj.memory is always undefined. This branch then calls updateMemoryMetrics({ isSupported: false }) every 2 seconds for the lifetime of the page. Each call produces a new state object and re-renders every store subscriber, including PerformanceToggleButton, which stays mounted at all times.

Detect the lack of support once and stop the interval.

♻️ Proposed fix
   useEffect(() => {
+    const perfObj = window.performance as unknown as {
+      memory?: { usedJSHeapSize: number; totalJSHeapSize: number; jsHeapSizeLimit: number };
+    };
+
+    if (!perfObj.memory) {
+      updateMemoryMetrics({ isSupported: false });
+      return;
+    }
+
     const updateMem = () => {
-      const perfObj = window.performance as unknown as {
-        memory?: { usedJSHeapSize: number; totalJSHeapSize: number; jsHeapSizeLimit: number };
-      };
-
-      if (perfObj.memory) {
-        ...
-      } else {
-        updateMemoryMetrics({ isSupported: false });
-      }
+      const m = perfObj.memory!;
+      // ...existing calculation
     };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/PerformanceOverlay/PerformanceOverlay.tsx` around
lines 96 - 98, Update the unsupported-memory branch in the performance metrics
interval to detect `perfObj.memory` being unavailable once, call
`updateMemoryMetrics({ isSupported: false })` only once, and stop or clear the
recurring interval afterward. Preserve the existing supported-memory update
flow.

Comment on lines +108 to +115
const handleKeyDown = (e: KeyboardEvent) => {
if (e.shiftKey && (e.key === 'P' || e.key === 'p')) {
if (['INPUT', 'TEXTAREA', 'SELECT'].includes((e.target as HTMLElement)?.tagName)) {
return;
}
e.preventDefault();
toggleOverlay();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Shift + P also fires with additional modifiers and inside editable elements.

The condition checks only e.shiftKey. Ctrl + Shift + P and Cmd + Shift + P therefore also toggle the overlay, and e.preventDefault() runs. Those combinations open the command menu in Chrome DevTools and in VS Code, so the collision affects the intended developer audience.

The guard at Line 110 also misses contentEditable regions.

🐛 Proposed fix
     const handleKeyDown = (e: KeyboardEvent) => {
-      if (e.shiftKey && (e.key === 'P' || e.key === 'p')) {
-        if (['INPUT', 'TEXTAREA', 'SELECT'].includes((e.target as HTMLElement)?.tagName)) {
+      if (e.shiftKey && !e.ctrlKey && !e.metaKey && !e.altKey && (e.key === 'P' || e.key === 'p')) {
+        const target = e.target as HTMLElement | null;
+        if (
+          ['INPUT', 'TEXTAREA', 'SELECT'].includes(target?.tagName ?? '') ||
+          target?.isContentEditable
+        ) {
           return;
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleKeyDown = (e: KeyboardEvent) => {
if (e.shiftKey && (e.key === 'P' || e.key === 'p')) {
if (['INPUT', 'TEXTAREA', 'SELECT'].includes((e.target as HTMLElement)?.tagName)) {
return;
}
e.preventDefault();
toggleOverlay();
}
const handleKeyDown = (e: KeyboardEvent) => {
if (e.shiftKey && !e.ctrlKey && !e.metaKey && !e.altKey && (e.key === 'P' || e.key === 'p')) {
const target = e.target as HTMLElement | null;
if (
['INPUT', 'TEXTAREA', 'SELECT'].includes(target?.tagName ?? '') ||
target?.isContentEditable
) {
return;
}
e.preventDefault();
toggleOverlay();
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/PerformanceOverlay/PerformanceOverlay.tsx` around
lines 108 - 115, Update handleKeyDown so the overlay toggles only for an
unmodified Shift+P press: reject events with ctrlKey, metaKey, altKey, or other
unintended modifiers before preventDefault. Extend the editable-target guard
beyond INPUT, TEXTAREA, and SELECT to also ignore contentEditable elements,
while preserving the existing toggleOverlay behavior for valid shortcuts.

Comment on lines +45 to +51
return () => {
if (!scene.isDestroyed()) {
removePre();
removePost();
}
};
}, [viewer]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect Cesium Event/Scene destroy semantics in the installed package.
set -euo pipefail

fd -t f 'Event.js' node_modules/cesium/Source/Core 2>/dev/null | head -5
fd -t f 'package.json' node_modules/cesium --max-depth 2 2>/dev/null | head -1 | xargs -r jq '.version'

# Show how other hooks in this repo detach Cesium scene event listeners.
rg -n -C 5 'preRender|postRender|removeEventListener' --glob 'frontend/src/**/*.ts*'

Repository: 7-Blocks/Kepler

Length of output: 153


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Tracked ts files matching hook/EarthTwin/overlay names:"
git ls-files | rg '(^|/)useCesiumPerformance\.ts$|EarthTwin\.tsx$|PerformanceOverlay\.tsx$' || true

echo
echo "useCesiumPerformance outline/content:"
if [ -f frontend/src/hooks/useCesiumPerformance.ts ]; then
  wc -l frontend/src/hooks/useCesiumPerformance.ts
  cat -n frontend/src/hooks/useCesiumPerformance.ts
fi

echo
echo "EarthTwin relevant references:"
if [ -f frontend/src/components/EarthTwin.tsx ]; then
  rg -n -C 6 'useEffect|createViewer|viewer|setViewerInstance|destroy|scene' frontend/src/components/EarthTwin.tsx
else
  rg -n -C 6 'EarthTwin|viewer|setViewerInstance|destroy|scene' -g '*.tsx' -g '*.ts'
fi

echo
echo "PerformanceOverlay relevant references:"
if [ -f frontend/src/components/PerformanceOverlay.tsx ]; then
  wc -l frontend/src/components/PerformanceOverlay.tsx
  rg -n -C 6 'usePerformanceStore|isEnabled|requestAnimationFrame|setInterval|preRender|postRender' frontend/src/components/PerformanceOverlay.tsx
fi

echo
echo "Cesium package presence/version:"
if [ -f frontend/package.json ]; then
  jq -r '.dependencies.["cesium"], .devDependencies.["cesium"]' frontend/package.json
fi
if [ -f package.json ]; then
  jq -r '.dependencies.["cesium"]' package.json
fi

Repository: 7-Blocks/Kepler

Length of output: 18906


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Package files:"
git ls-files '*package.json' '*package-lock.json' '*yarn.lock' '*pnpm-lock.yaml' | sed -n '1,80p'

echo
echo "Frontend package version refs:"
if [ -f frontend/package.json ]; then
  jq -r '
    .dependencies.cesium // _,
    .devDependencies.cesium // _,
    .peerDependencies.cesium // _
  ' frontend/package.json
fi

echo
echo "Locate installed Cesium Event/Scene files if any:"
fd -t f 'Event|Scene' node_modules/cesium 2>/dev/null | sed -n '1,80p' || true

echo
echo "Search repository for scene destroy/event cleanup patterns:"
rg -n -C 3 'isDestroyed|addEventListener\(|removeEventListener\(|preRender|postRender|scene.*destroy|renderLoop' frontend/src --glob '*.ts' --glob '*.tsx'

Repository: 7-Blocks/Kepler

Length of output: 685


🌐 Web query:

Cesium Viewer destroy Scene preRender postRender event listeners removed

💡 Result:

When you call viewer.destroy in CesiumJS, it performs a comprehensive cleanup of the viewer, including its associated widgets and the scene [1][2]. While viewer.destroy handles internal components, it does not automatically remove custom event listeners that you have manually attached to the scene's lifecycle events (such as preRender or postRender) or other external objects [3]. To ensure there are no memory leaks or unexpected behavior after destroying the viewer, you should manually remove any event listeners you added. Key points for managing these events: 1. Lifecycle Events (preRender/postRender): If you have attached functions to scene events, you must remove them explicitly before or during the cleanup process. The addEventListener method on these events returns a remove function that you should store and call [3]. Example: const removePreRender = viewer.scene.preRender.addEventListener(myCallback); // Later, when cleaning up: removePreRender; viewer.destroy; 2. ScreenSpaceEventHandler: If you created a custom ScreenSpaceEventHandler, you must call its.destroy method manually. The viewer's own internal screenSpaceEventHandler is managed by viewer.destroy, though updates in recent versions have improved robustness in checking its state [4][5][6]. 3. Viewer Cleanup: Calling viewer.destroy is the correct and necessary step to release WebGL resources and clear the scene, but it assumes responsibility only for the components the viewer itself instantiated [1][2]. Any custom references or listeners created outside of the viewer's direct scope remain your responsibility to clean up [7]. If you are experiencing memory issues, ensure that you are not holding onto references to the viewer or its objects in closures, as these will prevent the garbage collector from reclaiming that memory even after destroy is called [7].

Citations:


Always remove the Cesium performance listeners on cleanup.

EarthTwin destroys the viewer once and then calls setViewerInstance(null), which can make useCesiumPerformance()’s cleanup receive a destroyed scene without removing preRender/postRender handlers. Store the remove callbacks outside the scene.isDestroyed() check, so destroyed scenes do not leave the closure handlers and viewer reference registered.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/hooks/useCesiumPerformance.ts` around lines 45 - 51, Update the
cleanup returned by useCesiumPerformance to always invoke the removePre and
removePost callbacks, removing the scene.isDestroyed() guard around them.
Preserve the existing listener registration and ensure cleanup releases the
handlers and viewer reference even when the scene has already been destroyed.

let newBottlenecks = state.bottlenecks;
if (fps < 30 && (!state.bottlenecks.length || state.bottlenecks[0]?.type !== 'LOW_FPS')) {
const alert: BottleneckAlert = {
id: `fps-${Date.now()}`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Alert id values can collide within the same millisecond.

Every BottleneckAlert id is derived only from Date.now(). updateCesiumMetrics runs per frame and addNetworkCall can complete several requests in the same millisecond. Two alerts then share an id. PerformanceOverlay.tsx at Line 515 uses alert.id as the React key, so React reports duplicate keys and can reuse the wrong element.

Use the same random-suffix pattern already applied to NetworkCallMetric at Line 202, or a module-level counter.

🐛 Proposed fix: monotonic id helper
+let alertSeq = 0;
+const nextAlertId = (prefix: string) => `${prefix}-${Date.now()}-${++alertSeq}`;

Then replace each literal, for example:

-          id: `cesium-${Date.now()}`,
+          id: nextAlertId('cesium'),

Also applies to: 159-159, 183-183, 215-215, 241-241

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/store/performanceStore.ts` at line 136, Update all
BottleneckAlert id assignments in updateCesiumMetrics and addNetworkCall to
guarantee uniqueness beyond Date.now(), reusing the existing NetworkCallMetric
random-suffix pattern or a module-level monotonic counter. Apply the change to
each alert id literal, including the assignments near the referenced locations,
while preserving their existing id prefixes.

Comment on lines +154 to +174
updateCesiumMetrics: (sceneTimeMs, entityCount, satelliteCount) =>
set((state) => {
let newBottlenecks = state.bottlenecks;
if (sceneTimeMs > 35) {
const alert: BottleneckAlert = {
id: `cesium-${Date.now()}`,
type: 'SLOW_SCENE_UPDATE',
severity: sceneTimeMs > 60 ? 'CRITICAL' : 'WARNING',
message: `Cesium scene update took ${sceneTimeMs.toFixed(1)}ms (${entityCount} active entities)`,
timestamp: Date.now(),
};
newBottlenecks = [alert, ...state.bottlenecks.slice(0, MAX_BOTTLENECK_LOGS - 1)];
}

return {
cesiumSceneUpdateTimeMs: sceneTimeMs,
activeEntities: entityCount,
renderedSatellites: satelliteCount,
bottlenecks: newBottlenecks,
};
}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Two alert paths have no de-duplication, so a sustained condition floods the alert buffer.

updateFrameMetrics guards against repeating a LOW_FPS alert by inspecting state.bottlenecks[0]. The scene and memory paths have no equivalent guard. A condition that persists therefore appends a new alert on every call, evicts all other alert types from the 15-slot buffer, and notifies every store subscriber.

  • frontend/src/store/performanceStore.ts#L154-L174: updateCesiumMetrics runs once per rendered frame from useCesiumPerformance. Add a time-based cooldown before appending a SLOW_SCENE_UPDATE alert.
  • frontend/src/store/performanceStore.ts#L176-L196: updateMemoryMetrics runs every 2 seconds from the overlay. Add the same cooldown before appending a HIGH_MEMORY alert, or skip the alert when the newest entry is already HIGH_MEMORY.
📍 Affects 1 file
  • frontend/src/store/performanceStore.ts#L154-L174 (this comment)
  • frontend/src/store/performanceStore.ts#L176-L196
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/store/performanceStore.ts` around lines 154 - 174, In
performanceStore.ts, update both updateCesiumMetrics (lines 154-174) and
updateMemoryMetrics (lines 176-196) to prevent sustained conditions from
appending alerts on every call: apply the same time-based cooldown used by
updateFrameMetrics before adding SLOW_SCENE_UPDATE or HIGH_MEMORY alerts, or
skip insertion when the newest bottlenecks entry has the same type. Preserve
metric updates and the existing bounded alert buffer behavior.

Comment on lines +20 to +30
let url = 'unknown';
let method = 'GET';

try {
if (typeof args[0] === 'string') {
url = args[0];
} else if (args[0] instanceof URL) {
url = args[0].toString();
} else if (args[0] && typeof args[0] === 'object' && 'url' in args[0]) {
url = (args[0] as Request).url;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Full request URLs including query strings are retained and rendered.

url keeps the complete request URL with its query string. It is stored in recentCalls in the performance store and rendered in PerformanceOverlay.tsx at Line 432, which strips only the origin. Query strings in this application can carry access tokens, catalog identifiers, or search terms.

performanceStore.ts at Line 218 already strips the query for alert messages, so the intent to avoid leaking query data exists. Apply the same treatment at the capture point.

MainLayout.tsx mounts PerformanceOverlay, and PerformanceOverlay calls initNetworkInterceptor unconditionally, so this applies to every user, not only developers.

🛡️ Proposed fix: strip the query string at capture
     let url = 'unknown';
     let method = 'GET';
 
     try {
+      let rawUrl = '';
       if (typeof args[0] === 'string') {
-        url = args[0];
+        rawUrl = args[0];
       } else if (args[0] instanceof URL) {
-        url = args[0].toString();
+        rawUrl = args[0].toString();
       } else if (args[0] && typeof args[0] === 'object' && 'url' in args[0]) {
-        url = (args[0] as Request).url;
+        rawUrl = (args[0] as Request).url;
       }
+      // Drop query strings and fragments; they can carry tokens or PII.
+      url = rawUrl.split(/[?#]/)[0] || 'unknown';

Also applies to: 43-50

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/utils/networkInterceptor.ts` around lines 20 - 30, Update the
URL capture logic in initNetworkInterceptor to remove query strings before
assigning request URLs to url, covering string, URL, and Request inputs.
Preserve the URL origin and path while ensuring recentCalls never receives query
parameters.

Comment thread frontend/src/utils/networkInterceptor.ts
@krishkhinchi

Copy link
Copy Markdown
Member

Hello @Pranitrane, Please attach screenshots of the updates/changes and also add related issue no. along with your PR. Thanks!

@Pranitrane

Pranitrane commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

issue no: 168: feature: real time performance overlay
image
Expanded Performance overlay

image Minimized performance Bar

@krishkhinchi krishkhinchi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM!!

@krishkhinchi
krishkhinchi merged commit d0ccc27 into 7-Blocks:main Aug 13, 2026
5 checks passed
@ecsoc-sentinel ecsoc-sentinel Bot added ECSoC26-L2 Level 2 contribution for the ECSoC26 event. and removed ECSoC26-L2 Level 2 contribution for the ECSoC26 event. labels Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI Artificial Intelligence and Machine Learning backend Backend development bug Something isn't working database Database and schema related changes documentation Improvements or additions to documentation ECSoC26-L2 Level 2 contribution for the ECSoC26 event. ECSoC26 Official label for ECSoC26 event contributions. enhancement New feature or request frontend Frontend development size/XL Very large or complex contribution. size:XL This PR changes 500-999 lines, ignoring generated files type:backend Changes backend services or server-side logic. type:bug Fixes an existing bug or unexpected behavior. type:documentation Improves project documentation. type:feature Introduces a new feature or enhancement. type:frontend Changes frontend or client-side code.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

📈 Real-Time Performance Overlay

2 participants