Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/panel/Panel.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useState, useEffect, useCallback, useRef } from 'react';
import type { TabState, TimelineEvent } from '@/types';
import { DEBUGGER_TOGGLE_FEEDBACK_MS, REDUX_SEARCH_TIMEOUT_MS } from './constants';
import { UIStateTab } from './tabs/UIStateTab';
import { PerformanceTab } from './tabs/PerformanceTab';
import { SideEffectsTab } from './tabs/SideEffectsTab';
Expand Down Expand Up @@ -136,7 +137,7 @@ export function Panel() {
type: newEnabled ? 'ENABLE_DEBUGGER' : 'DISABLE_DEBUGGER',
tabId,
});
setTimeout(() => setIsTogglingDebugger(false), 3000);
setTimeout(() => setIsTogglingDebugger(false), DEBUGGER_TOGGLE_FEEDBACK_MS);
}, [tabId, isDebuggerEnabled]);

const handleTabChange = useCallback((newTab: TabId) => {
Expand All @@ -145,7 +146,7 @@ export function Panel() {
if (newTab === 'redux' && !state.reduxDetected) {
setIsSearchingRedux(true);
safeSendMessage({ type: 'SEARCH_REDUX', tabId });
setTimeout(() => setIsSearchingRedux(false), 5000);
setTimeout(() => setIsSearchingRedux(false), REDUX_SEARCH_TIMEOUT_MS);
}
}, [tabId, state.reduxDetected]);

Expand Down
22 changes: 22 additions & 0 deletions src/panel/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Timeout constants for UI feedback (spinners, toast-like states).
* Values tuned for perceived responsiveness without flicker.
*/

/** Feedback duration for short UI actions (scan toggle, dispatch button) */
export const UI_FEEDBACK_SHORT_MS = 800;

/** Feedback duration for medium UI actions (refresh, monitor toggle) */
export const UI_FEEDBACK_MEDIUM_MS = 1000;

/** Feedback for debugger enable/disable — longer because background work continues */
export const DEBUGGER_TOGGLE_FEEDBACK_MS = 3000;

/** Time to wait for Redux store auto-detection */
export const REDUX_SEARCH_TIMEOUT_MS = 5000;

/** Correlation analysis feedback */
export const CORRELATION_FEEDBACK_MS = 3000;

/** Snapshot creation feedback (short — operation is fast) */
export const SNAPSHOT_CREATE_FEEDBACK_MS = 500;
3 changes: 2 additions & 1 deletion src/panel/tabs/MemoryTab.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useState, useEffect, useMemo } from 'react';
import type { MemoryReport, CrashEntry } from '@/types';
import { UI_FEEDBACK_MEDIUM_MS } from '../constants';

interface MemoryTabProps {
report: MemoryReport | null;
Expand Down Expand Up @@ -38,7 +39,7 @@ export function MemoryTab({ report, tabId }: MemoryTabProps) {
type: newState ? 'START_MEMORY_MONITORING' : 'STOP_MEMORY_MONITORING',
tabId,
});
setTimeout(() => setIsTogglingMonitor(false), 1000);
setTimeout(() => setIsTogglingMonitor(false), UI_FEEDBACK_MEDIUM_MS);
};

useEffect(() => {
Expand Down
3 changes: 2 additions & 1 deletion src/panel/tabs/PerformanceTab.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useMemo, useState } from 'react';
import type { Issue, ComponentInfo, RenderInfo, PageLoadMetrics } from '@/types';
import { IssueCard } from '../components/IssueCard';
import { UI_FEEDBACK_SHORT_MS } from '../constants';

interface PerformanceTabProps {
issues: Issue[];
Expand Down Expand Up @@ -50,7 +51,7 @@ export function PerformanceTab({ issues, components, renders, tabId, pageLoadMet
type: 'TOGGLE_SCAN',
payload: { enabled: newState },
});
setTimeout(() => setIsTogglingScan(false), 800);
setTimeout(() => setIsTogglingScan(false), UI_FEEDBACK_SHORT_MS);
};

const renderStats = useMemo(() => {
Expand Down
5 changes: 3 additions & 2 deletions src/panel/tabs/ReduxTab.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { ReduxAction } from '@/types';
import { useCallback, useState } from 'react';
import { UI_FEEDBACK_SHORT_MS, UI_FEEDBACK_MEDIUM_MS } from '../constants';

interface ReduxTabProps {
detected: boolean;
Expand Down Expand Up @@ -37,7 +38,7 @@ export function ReduxTab({ detected, state, actions, tabId, isSearching }: Redux
type: 'REFRESH_REDUX_STATE',
tabId,
});
setTimeout(() => setIsRefreshing(false), 1000);
setTimeout(() => setIsRefreshing(false), UI_FEEDBACK_MEDIUM_MS);
}, [tabId]);

const clearOverrides = useCallback(() => {
Expand Down Expand Up @@ -139,7 +140,7 @@ if (process.env.NODE_ENV === 'development') {
} catch {
setDispatchError('Invalid JSON payload');
}
setTimeout(() => setIsDispatching(false), 800);
setTimeout(() => setIsDispatching(false), UI_FEEDBACK_SHORT_MS);
};

const togglePath = (path: string) => {
Expand Down
5 changes: 3 additions & 2 deletions src/panel/tabs/TimelineTab.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useState, useMemo, useCallback, useRef, useEffect } from 'react';
import type { TimelineEvent, TimelineEventType, RenderEventPayload, StateChangeEventPayload, EffectEventPayload, ErrorEventPayload, MemoryEventPayload, CorrelationResult, ContextChangeEventPayload } from '@/types';
import { CORRELATION_FEEDBACK_MS, SNAPSHOT_CREATE_FEEDBACK_MS } from '../constants';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

To prevent race conditions when rapidly clicking different events to correlate, we should track and clear the pending timeout. We can declare a module-level variable to hold the timeout ID.

import { CORRELATION_FEEDBACK_MS, SNAPSHOT_CREATE_FEEDBACK_MS } from '../constants';

let correlationTimeoutId: ReturnType<typeof setTimeout> | null = null;


interface TimelineTabProps {
events: TimelineEvent[];
Expand Down Expand Up @@ -89,7 +90,7 @@ export function TimelineTab({ events, tabId, onClear }: TimelineTabProps) {
setIsCorrelating(false);
}
);
setTimeout(() => setIsCorrelating(false), 3000);
setTimeout(() => setIsCorrelating(false), CORRELATION_FEEDBACK_MS);
Comment on lines 90 to +93

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Clear any existing correlation timeout before starting a new one, and also clear it when the response successfully arrives. This prevents older timeouts from prematurely setting isCorrelating to false when multiple events are clicked in rapid succession.

Suggested change
setIsCorrelating(false);
}
);
setTimeout(() => setIsCorrelating(false), 3000);
setTimeout(() => setIsCorrelating(false), CORRELATION_FEEDBACK_MS);
setIsCorrelating(false);
if (correlationTimeoutId) {
clearTimeout(correlationTimeoutId);
correlationTimeoutId = null;
}
}
);
if (correlationTimeoutId) {
clearTimeout(correlationTimeoutId);
}
correlationTimeoutId = setTimeout(() => {
setIsCorrelating(false);
correlationTimeoutId = null;
}, CORRELATION_FEEDBACK_MS);

}, [tabId]);

const clearCorrelation = useCallback(() => {
Expand Down Expand Up @@ -139,7 +140,7 @@ export function TimelineTab({ events, tabId, onClear }: TimelineTabProps) {
};

setSnapshots(prev => [...prev, newSnapshot]);
setTimeout(() => setIsCreatingSnapshot(false), 500);
setTimeout(() => setIsCreatingSnapshot(false), SNAPSHOT_CREATE_FEEDBACK_MS);
}, [events]);

const exportSnapshot = useCallback((snapshot: TimelineSnapshot) => {
Expand Down
Loading