Skip to content

test explor - #119

Open
ujjwal-devzy wants to merge 1 commit into
mainfrom
exp-test
Open

test explor#119
ujjwal-devzy wants to merge 1 commit into
mainfrom
exp-test

Conversation

@ujjwal-devzy

@ujjwal-devzy ujjwal-devzy commented Jan 6, 2026

Copy link
Copy Markdown
Owner

Summary by DevzyAi

Release Notes

New Features

  • Added comprehensive error boundary system with graceful error recovery options (reset state or reload page)
  • Introduced advanced task filtering capabilities including search, status, priority, assignee, and tag-based filters
  • Added keyboard shortcuts for improved task management navigation
  • Implemented performance optimization hooks for throttling, idle callbacks, and intersection observation

Bug Fixes

  • Enhanced network error handling with more specific error messages and timeout cleanup to prevent memory leaks

Improvements

  • Tasks now automatically sort by priority and update time for better organization
  • Added optimistic UI updates with automatic rollback on operation failures

@neatcod-simulator-dev

neatcod-simulator-dev Bot commented Jan 6, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

This PR introduces comprehensive error handling and performance optimization across the application. A global ErrorBoundary component now wraps the entire app, providing graceful error recovery with reset and reload options. The HTTP client gains enhanced network error detection and timeout management. New performance hooks (useThrottle, useIdleCallback, useIntersectionObserver) enable efficient rendering strategies. Task management receives significant upgrades with advanced filtering capabilities, optimistic UI updates with rollback mechanisms, and priority-based sorting for improved user experience.

Sequence

sequenceDiagram
    participant User
    participant App
    participant ErrorBoundary
    participant TaskBoard
    participant TaskContext
    participant HTTPClient
    participant API

    User->>App: Loads Application
    App->>ErrorBoundary: Wraps entire app
    ErrorBoundary->>TaskBoard: Renders components
    
    User->>TaskBoard: Applies filters/search
    TaskBoard->>TaskContext: Request filtered tasks
    TaskContext->>TaskContext: Sort by priority & time
    TaskContext-->>TaskBoard: Return filtered tasks
    
    User->>TaskBoard: Creates/Updates task
    TaskBoard->>TaskContext: Optimistic update
    TaskContext->>HTTPClient: API request
    HTTPClient->>API: Fetch with timeout
    
    alt Success
        API-->>HTTPClient: Response
        HTTPClient-->>TaskContext: Success
        TaskContext-->>TaskBoard: Confirm update
    else Network Error
        API-->>HTTPClient: Network failure
        HTTPClient->>HTTPClient: Detect error type
        HTTPClient-->>TaskContext: Error with code
        TaskContext->>TaskContext: Rollback state
        TaskContext-->>TaskBoard: Show error
        TaskBoard-->>User: Display error message
    else Runtime Error
        TaskBoard->>ErrorBoundary: Error thrown
        ErrorBoundary-->>User: Show error UI
        User->>ErrorBoundary: Reset/Reload
        ErrorBoundary->>App: Recover application
    end
Loading

Changes

Files Summary
src/App.tsx
src/shared/components/ErrorBoundary.tsx
src/shared/components/index.ts
🔒 Introduced comprehensive error boundary system with customizable fallback UI, error logging, and recovery mechanisms (state reset/page reload). Wraps entire application for global error catching and graceful degradation.
src/core/api/http-client.ts ⚠️ Enhanced HTTP client with granular network error detection, specific error codes for fetch failures, and proper timeout cleanup to prevent memory leaks.
src/core/hooks/index.ts
src/core/hooks/usePerformance.ts
⚡ Added three performance optimization hooks: useThrottle for rate-limiting, useIdleCallback for idle-time execution, and useIntersectionObserver for visibility tracking.
src/features/tasks/components/TaskBoard.tsx
src/features/tasks/hooks/useTaskFilters.ts
💡 Implemented advanced task filtering with search, status, priority, assignee, and tag filters. Added keyboard shortcuts and dynamic filter count tracking for enhanced UX.
src/features/tasks/context/TaskContext.tsx
src/features/tasks/hooks/useTasks.ts
🎯 Enhanced task management with optimistic UI updates, rollback mechanisms on errors, priority-based sorting, and clearError method for better error state control.

🛡️ With boundaries strong and errors caught,
Our app now handles what we thought
Might crash and burn with runtime woe,
But graceful recovery steals the show!

Performance hooks make rendering swift,
While filters give our tasks a lift,
Optimistic updates, rollback too,
A resilient codebase, tried and true! 🚀

📋 Detailed File Changes

📊 Changes by Category (5 groups)

🛡️ Error Handling & Resilience

Comprehensive error boundary system for React app and enhanced HTTP client error handling with network-specific error management

Files Summary
src/App.tsx, src/shared/components/ErrorBoundary.tsx, src/shared/components/index.ts Introduced a comprehensive error boundary system for the React application. Added a new ErrorBoundary component with customizable error UI, fallback rendering, error logging, and recovery mechanisms (state reset and page reload). The component is now exported from the shared components library and implemented as the top-level wrapper in the application, encapsulating all providers and the main AppLayout. This provides global error catching and handling for the entire application, improving error resilience and user experience. Supports optional error callbacks and displays error stack traces in development mode. ## Detailed Review 🔒 Security: The error boundary prevents application crashes by catching runtime errors and providing graceful degradation. ⚠️ Error Handling: Comprehensive error handling with customizable fallback UI and recovery options improves application stability. 💡 Suggestion: The error boundary implementation follows React best practices for error handling at the application level. 🎯 Logic: The component provides multiple recovery strategies (reset state, reload page) giving users options to recover from errors.
src/core/api/http-client.ts Enhanced error handling in the HTTP client by adding a try-catch block for fetch requests and introducing more specific network error handling. The changes improve error detection and provide more informative error messages for network-related failures, including specific checks for 'Failed to fetch' and 'NetworkError' messages. Added proper timeout clearing in both successful and error scenarios to prevent potential memory leaks. ## Detailed Review 🔒 Security: The changes improve error handling by explicitly catching and re-throwing fetch errors, which prevents silent failures. ⚠️ Error Handling: Added granular error detection for network-specific errors, providing more precise error codes and messages. ⚡ Performance: The changes include clearing the timeout in both successful and error scenarios, preventing potential memory leaks or lingering timers. 🎯 Logic: The modification introduces a more comprehensive approach to handling network-related errors, with clearer error categorization and messaging.

📋 Task Management Features

Advanced task filtering capabilities (search, status, priority, assignee, tags) and robust error handling with optimistic UI updates

Files Summary
src/features/tasks/components/TaskBoard.tsx, src/features/tasks/hooks/useTaskFilters.ts Enhanced the TaskBoard component with advanced filtering capabilities including search, status, priority, assignee, and tag-based filters. Introduced a new useTaskFilters hook that provides comprehensive filtering logic with filtered tasks, available assignees and tags, and active filter count tracking. Added keyboard shortcuts, error handling, and a dynamic filtering mechanism that significantly improves task management UI interactivity and user experience.
src/features/tasks/context/TaskContext.tsx, src/features/tasks/hooks/useTasks.ts Enhanced task management with robust error handling and optimistic UI updates. Added clearError method to the context and implemented error dispatching for create, update, delete, and move task operations with fallback mechanisms to revert state changes. The useTasks hook now sorts tasks by priority first (using a priorityOrder mapping), then by update time, providing a more organized task display. Removed an ESLint disable comment and expanded the hook's return value to include the clearError method. ## Detailed Review ⚠️ Error Handling: Comprehensive error handling with optimistic updates and rollback mechanisms ensures data consistency even when operations fail. 💡 Suggestion: The priority-based sorting with fallback to update time provides a logical and user-friendly task ordering. 🎯 Logic: The addition of clearError method gives users control over error state management, improving UX.

⚡ Performance Optimization

New performance hooks including useThrottle for rate-limiting, useIdleCallback for deferred execution, and other optimization utilities

Files Summary
src/core/hooks/index.ts, src/core/hooks/usePerformance.ts Introduced three new performance optimization React hooks: useThrottle for rate-limiting function calls, useIdleCallback for executing callbacks during browser idle time, and useIntersectionObserver for tracking element visibility. These hooks are exported from the core hooks module and provide advanced performance optimization techniques for React components, enabling efficient rendering strategies and resource management.

✨ Enhance your code reviews with DevzyAi - AI-powered code analysis and suggestions to help your team write better code.

Learn more about DevzyAi


Enhance your code reviews with DevzyAi - AI-powered code analysis and suggestions to help your team write better code.

Learn more about DevzyAi

🏛️ Architect Review

Overall Assessment: ✅ Excellent

Detailed Analysis

Approach Assessment

The PR introduces three distinct architectural improvements: global error handling, performance optimization utilities, and enhanced task management with optimistic updates. Let me evaluate each:

Error Handling Architecture:
The ErrorBoundary implementation is solid and follows React best practices. Wrapping the entire app at the top level is the correct approach for catching unhandled errors. The component provides good configurability (custom fallback, error callbacks, detail visibility) and includes appropriate safeguards (stack traces only in dev mode). The integration with the HTTP client to handle network errors and timeouts is well-thought-out.

Performance Hooks:
Creating a dedicated usePerformance.ts module with three specialized hooks (throttle, idle callback, intersection observer) is a good architectural decision. These are genuinely reusable utilities that don't belong in feature-specific code. The implementations are clean and follow React hooks conventions properly.

Task Management with Optimistic Updates:
The optimistic update pattern with rollback mechanisms is architecturally sound for improving perceived performance. However, there's a concern: the implementation spreads optimistic update logic across multiple operations (create, update, delete, move) with similar patterns. This could benefit from a higher-level abstraction to reduce duplication and ensure consistency.

Filtering System:
The useTaskFilters hook centralizes filtering logic, which is good. However, the implementation appears to compute filters on every render, which could become a performance bottleneck with large task lists. Consider memoization strategies or moving filter computation to a more efficient layer.

Scalability Concerns:

  • The task context is growing in responsibility (state management, error handling, optimistic updates, filtering). As the application scales, this could become a maintenance burden.
  • No mention of pagination or virtualization for task lists, which will be necessary at scale.

Verdict: ACCEPTABLE

The approaches are generally sound, but there are opportunities for better abstraction (optimistic updates) and potential performance concerns (filter computation) that should be addressed before the codebase grows significantly.


Reuse Recommendations

New Code Recommendation Reason
ErrorBoundary Create new (✓ justified) This is a foundational component that doesn't exist in the codebase. Properly placed in shared/components.
useThrottle, useIdleCallback, useIntersectionObserver Create new (✓ justified) These are generic performance utilities correctly placed in core/hooks. No existing alternatives found.
useTaskFilters Create new (✓ justified) Feature-specific hook that encapsulates complex filtering logic. Appropriate for the tasks feature.
Optimistic update pattern Consider abstraction The pattern is repeated across create/update/delete/move operations. Consider creating a generic useOptimisticMutation hook that accepts operation type, API call, and state update logic. This would reduce duplication and ensure consistent error handling.
HTTP client error handling Enhance existing (✓ good) Correctly enhances the existing http-client rather than creating a new one.

Specific Recommendation:
Create a generic optimistic update hook in core/hooks:

// core/hooks/useOptimisticUpdate.ts
function useOptimisticUpdate<T>(
  optimisticUpdate: () => void,
  apiCall: () => Promise<T>,
  onSuccess?: (result: T) => void,
  onError?: (error: Error) => void
)

This would consolidate the repeated try-catch-rollback pattern seen in task operations.


Pattern Consistency

Pattern Status Notes
Error Boundary Placement ✅ Consistent Top-level wrapping in App.tsx follows React best practices and is the standard pattern for global error handling.
Hook Organization ✅ Consistent Performance hooks in core/hooks and feature-specific hooks in features/tasks/hooks follows proper separation of concerns.
Context Usage ⚠️ Deviation TaskContext is accumulating multiple responsibilities (state, errors, filtering, optimistic updates). Most contexts in React apps should focus on a single concern. Consider splitting into TaskStateContext and TaskOperationsContext.
Error Handling Pattern ✅ Consistent The try-catch-finally pattern with error state management is consistent across task operations.
API Client Enhancement ✅ Consistent Enhancing the existing http-client with timeout cleanup and network error detection follows the established pattern of centralizing HTTP concerns.
Optimistic Updates ⚠️ Deviation While the pattern itself is good, the implementation is duplicated across multiple operations rather than abstracted. This creates maintenance risk as the pattern evolves.

Key Deviation Justification:
The TaskContext responsibility growth is concerning. While it works for the current scale, it violates the Single Responsibility Principle and will become harder to test and maintain as features grow.


Overall Recommendation

The architectural changes demonstrate good understanding of React patterns and introduce valuable improvements (error boundaries, performance hooks, optimistic updates). However, there are two areas needing attention: (1) abstract the repeated optimistic update pattern into a reusable hook to reduce duplication, and (2) consider splitting the TaskContext to prevent it from becoming a monolithic state manager. These refinements would significantly improve long-term maintainability.

Score: Good

task.title.toLowerCase().includes(query) ||
task.description.toLowerCase().includes(query) ||
task.tags.some(tag => tag.toLowerCase().includes(query)) ||
task.assignee?.toLowerCase().includes(query)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Pattern Detected: Missing Null/Undefined Checks Causing Runtime Errors (4 occurrences)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Severity: HIGH

This issue appears in 4 locations across 3 files. Similar fix can be applied to all locations.

📍 Affected Locations:
src/features/tasks/hooks/useTaskFilters.ts:45
src/features/tasks/hooks/useTaskFilters.ts:65
src/features/tasks/hooks/useTasks.ts:38
src/shared/components/ErrorBoundary.tsx:32

💡 General Guidance:

🐛 Bug: Potential runtime error when task.assignee is null or undefined
The code calls .toLowerCase() on task.assignee without null checking. While there's an optional chain on line 45, it only protects the includes() call, not the toLowerCase() call.


src/shared/components/ErrorBoundary.tsx:32

🟡 Medium

Fix: Try again" will immediately trigger the same error again, creating a poor user experience.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
💡 Tip: Fixing this pattern across all locations will improve code quality consistently.

🤖 AI Fix Prompt - Copy this to your AI assistant

Fix the null/undefined check issue in src/features/tasks/hooks/useTaskFilters.ts at line 45

The code is calling toLowerCase() on task.assignee without properly checking if it exists first. The current optional chaining only protects the includes() method but not the toLowerCase() call, which means if task.assignee is null or undefined, the code will throw a runtime error when trying to call toLowerCase() on a non-existent value.

The fix should add proper null/undefined checking before calling toLowerCase(). Change the condition to check if task.assignee exists first, then convert to lowercase, and finally check if it includes the search term. The safest approach is to use optional chaining for the entire chain or add an explicit null check.

This same pattern appears in 3 other locations in the codebase that should be fixed similarly:
Line 65 in the same file (src/features/tasks/hooks/useTaskFilters.ts)
Line 38 in src/features/tasks/hooks/useTasks.ts where updatedAt might be undefined before calling getTime()
Line 32 in src/shared/components/ErrorBoundary.tsx where error handling might access undefined properties

The corrected code should follow this pattern: first verify the property exists, then perform string operations. For example, use task.assignee?.toLowerCase()?.includes(filters.assignee.toLowerCase()) or add an explicit check like task.assignee && task.assignee.toLowerCase().includes(filters.assignee.toLowerCase()).

Files that will be affected by this change:
src/features/tasks/hooks/useTaskFilters.ts (primary fix location)
src/features/tasks/components/TaskBoard.tsx (direct dependent that uses this hook)

Requirements for the fix:
Ensure no runtime errors occur when task.assignee is null or undefined
Maintain the existing filter logic behavior when values are present
Apply consistent null checking pattern across all similar occurrences
Test with edge cases including null, undefined, and empty string values
Verify the fix works correctly in the TaskBoard component that depends on this hook

Comment on lines 136 to +152
const deleteTask = useCallback((id: string) => {
const success = taskRepository.delete(id);
if (success) {
dispatch({ type: 'DELETE_TASK', payload: id });
const taskToDelete = state.tasks.find(t => t.id === id);
if (!taskToDelete) return;

dispatch({ type: 'DELETE_TASK', payload: id });

try {
const success = taskRepository.delete(id);
if (!success) {
dispatch({ type: 'ADD_TASK', payload: taskToDelete });
dispatch({ type: 'SET_ERROR', payload: 'Failed to delete task' });
}
} catch (error) {
dispatch({ type: 'ADD_TASK', payload: taskToDelete });
dispatch({ type: 'SET_ERROR', payload: 'Failed to delete task' });
}
}, []);
}, [state.tasks]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Pattern Detected: Bug: Race Conditions in Optimistic Updates with Stale Data (3 occurrences)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Severity: HIGH

This issue appears in 3 locations across 2 files. Similar fix can be applied to all locations.

📍 Affected Locations:
src/features/tasks/context/TaskContext.tsx:152
src/features/tasks/context/TaskContext.tsx:178
src/features/tasks/hooks/useTaskFilters.ts:43

💡 General Guidance:

try {
const success = taskRepository.delete(id);
if (!success) {

  •   dispatch({ type: 'ADD_TASK', payload: taskToDelete });
    
  •   // Only rollback if task still doesn't exist
    
  •   const stillDeleted = !state.tasks.find(t => t.id === id);
    
  •   if (stillDeleted) dispatch({ type
    
📋 Specific Suggestions for Each File (Click to expand)

src/features/tasks/context/TaskContext.tsx:152

🟠 High

Fix: try {
const success = taskRepository.delete(id);
if (!success) {

  •   dispatch({ type: 'ADD_TASK', payload: taskToDelete });
    
  •   // Only rollback if task still doesn't exist
    
  •   const stillDeleted = !state.tasks.find(t => t.id === id);
    
  •   if (stillDeleted) dispatch({ type
    

src/features/tasks/context/TaskContext.tsx:178

🟠 High

Fix: try {
const updated = taskRepository.moveTask(taskId, newStatus);
if (updated) {
dispatch({ type: 'UPDATE_TASK', payload: updated });
} else {

  •   dispatch({ type: 'UPDATE_TASK', payload: currentTask });
    
  •   const latestTask = getCurrentTask();
    
  •   if (latestTas
    

src/features/tasks/hooks/useTaskFilters.ts:43

🟠 High

Fix: 🐛 Bug: Potential runtime error when task.description is null or undefined
The code calls .toLowerCase() on task.description without checking if it exists. If a task has no description, this will throw a TypeError.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
💡 Tip: Fixing this pattern across all locations will improve code quality consistently.

🤖 AI Fix Prompt - Copy this to your AI assistant

Fix the race condition bugs in optimistic updates across src/features/tasks/context/TaskContext.tsx at lines 152 and 178, and src/features/tasks/hooks/useTaskFilters.ts at line 43

There are critical race conditions in the task management system where optimistic updates can rollback to stale data. When operations fail and trigger rollbacks, the code doesn't verify whether the state has changed since the optimistic update was applied. This means if another operation modified the same task in the meantime, the rollback will overwrite that newer data with old cached values.

In TaskContext.tsx at line 152, when a task deletion fails, the code blindly re-adds the cached taskToDelete without checking if the task was already re-added by another operation or if the current state has diverged.

In TaskContext.tsx at line 178, when moving a task to a new status fails, the rollback uses the cached currentTask without verifying if that task has been updated by other operations during the async repository call.

In useTaskFilters.ts at line 43, there's an additional bug where task.description.toLowerCase() is called without null checking, which will throw a TypeError if description is null or undefined.

The fix should implement proper state verification before rollback operations:

For line 152 in TaskContext.tsx, before dispatching ADD_TASK on rollback, check if the task still doesn't exist in the current state. Only rollback if the task is genuinely missing, not if it was re-added by another operation.

For line 178 in TaskContext.tsx, instead of using the cached currentTask for rollback, fetch the latest version of the task from the current state at rollback time. Only apply the rollback if the task hasn't been modified by other operations.

For line 43 in useTaskFilters.ts, add optional chaining or explicit null check before calling toLowerCase() on task.description.

The pattern to follow is: capture the optimistic state, perform the async operation, then verify current state before applying any rollback. Use state.tasks.find() to get the latest version of entities rather than relying on closure-captured values.

Files that will be affected by this fix:
src/features/tasks/context/TaskContext.tsx
src/features/tasks/hooks/useTaskFilters.ts
src/features/tasks/hooks/useTasks.ts (indirect impact as it consumes the context)

Requirements:
Implement state verification before all rollback operations in optimistic updates
Replace cached entity references with fresh lookups from current state during rollback
Add null safety checks for optional properties like task.description
Ensure the fix maintains the existing dispatch action types and payload structures
Test that concurrent operations don't overwrite each other's changes
Verify that legitimate rollbacks still work when no concurrent modifications occurred

Comment on lines +43 to +63
export function useIdleCallback(callback: () => void, deps: unknown[] = []) {
const callbackRef = useRef(callback);

useEffect(() => {
callbackRef.current = callback;
}, [callback]);

useEffect(() => {
if (typeof requestIdleCallback !== 'undefined') {
const id = requestIdleCallback(() => {
callbackRef.current();
});
return () => cancelIdleCallback(id);
} else {
const timeoutId = setTimeout(() => {
callbackRef.current();
}, 1);
return () => clearTimeout(timeoutId);
}
}, deps);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Pattern Detected: Bug: Unstable Dependencies Causing Hook Re-execution (3 occurrences)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Severity: HIGH

This issue appears in 3 locations across 2 files. Each location requires specific attention based on its context.

📍 Affected Locations:
src/core/hooks/usePerformance.ts:63
src/core/hooks/usePerformance.ts:84
src/features/tasks/components/TaskBoard.tsx:100

💡 General Guidance:

use the effect to re-run on every render if deps is a new array instance (which it will be if passed inline). The effect should use a stable reference or spread the deps.

📋 Specific Suggestions for Each File (Click to expand)

src/core/hooks/usePerformance.ts:63

🟡 Medium

Fix: use the effect to re-run on every render if deps is a new array instance (which it will be if passed inline). The effect should use a stable reference or spread the deps.


src/core/hooks/usePerformance.ts:84

🟡 Medium

Fix: try]) => {

  •  callback(entry.isIntersecting);
    
  • }, options);
  •  callbackRef.current(entry.isIntersecting);
    
  • }, optionsRef.current);

    observer.observe(element);

    return () => {
    observer.disconnect();
    };

  • }, [elementRef, callback, options]);
  • }, [elementRef]);
    }


---

#### `src/features/tasks/components/TaskBoard.tsx:100`

🟡 **Medium**

**Fix:** try {
+                clearError();
+              } catch (err) {
+                console.error('Failed to clear error:', err);
+              }
+            }}
             aria-label="Dismiss error"
           >


</details>

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
💡 **Tip:** Fixing this pattern across all locations will improve code quality consistently.


<details>
<summary>🤖 <b>AI Fix Prompt</b> - Copy this to your AI assistant</summary>

<blockquote>

Fix the unstable dependency arrays causing unnecessary hook re-executions in src/core/hooks/usePerformance.ts at lines 43-63, 63-84 and src/features/tasks/components/TaskBoard.tsx at line 100

The problem is that dependency arrays are being passed as inline array literals to useEffect and useCallback hooks. Every time the component renders, a new array instance is created even if the values inside are the same. This causes React to think the dependencies have changed, triggering unnecessary effect re-runs and callback recreations. This defeats the purpose of memoization and can lead to performance issues and infinite render loops.

In usePerformance.ts there are two specific issues:

First issue around line 63 in the useThrottle or useIdleCallback hook - the deps array is likely being passed inline like [deps] or the callback itself is not stable. This needs to be fixed by either using a ref for the callback or ensuring the deps array has a stable reference.

Second issue around line 84 in the useIntersectionObserver hook - both the callback and options object are being passed directly in the dependency array. Since functions and objects are recreated on each render, this causes the effect to run every time. The fix is to use refs to store the callback and options, then access them via callbackRef.current and optionsRef.current inside the effect. The dependency array should only include elementRef.

Third issue in TaskBoard.tsx around line 100 - similar pattern where an inline function or array is causing instability.

The correct pattern for these hooks is:

For callbacks that need to be stable - wrap them in useCallback with proper dependencies or store them in a ref using useRef and update the ref in a separate useEffect

For objects like options - either memoize them with useMemo outside the hook or store them in a ref

For dependency arrays - never pass inline arrays, always define them as a const variable with proper memoization or use the spread operator on a stable reference

Look at how other custom hooks in the codebase handle this pattern. Check if there are examples of useCallback, useMemo, or useRef being used to stabilize dependencies in other hook files.

Files that need changes:
src/core/hooks/usePerformance.ts - fix both the useThrottle/useIdleCallback and useIntersectionObserver implementations
src/features/tasks/components/TaskBoard.tsx - fix the dependency issue in the error handling code

Requirements:
Ensure all callbacks used in effects are either wrapped in useCallback or stored in refs
Ensure all object dependencies like options are memoized or stored in refs
Remove function and object references from dependency arrays and replace with refs
Keep the dependency arrays minimal - only include primitive values or stable refs
Test that the hooks no longer re-run on every render by adding console logs
Verify that the actual functionality still works correctly after the changes
Make sure the fixes follow React hooks best practices and ESLint rules for exhaustive-deps

</blockquote>

</details>

Comment on lines +326 to +331
if (error.message.includes('Failed to fetch') || error.message.includes('NetworkError')) {
return {
code: ERROR_CODES.NETWORK_ERROR,
message: 'Network connection failed. Please check your internet connection.',
};
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium

🐛 Bug: Fragile error detection relies on string matching which is locale-dependent and browser-specific

The error message matching approach is unreliable because:

  1. Error messages vary across browsers (Chrome: "Failed to fetch", Firefox: "NetworkError when attempting to fetch resource")
  2. Messages may be localized in non-English browsers
  3. The check happens AFTER the generic network error check, making it unreachable code
-      if (error.message.includes('Failed to fetch') || error.message.includes('NetworkError')) {
+      // Check for network errors by error type/name instead of message
+      if (error.name === 'TypeError' || error instanceof TypeError) {
         return {
           code: ERROR_CODES.NETWORK_ERROR,
           message: 'Network connection failed. Please check your internet connection.',
         };
       }

       return {
         code: ERROR_CODES.NETWORK_ERROR,
         message: error.message || 'Network error occurred',

Note: This code block appears to be unreachable since it comes after a generic return statement for network errors. Consider restructuring the error handling logic to check for specific network error types first, then fall back to the generic handler.

Comment thread src/App.tsx
Comment on lines +489 to +497
<ErrorBoundary>
<ToastProvider>
<AuthProvider>
<ProjectProvider>
<AppLayout />
</ProjectProvider>
</AuthProvider>
</ToastProvider>
</ErrorBoundary>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium

🐛 Bug: ErrorBoundary placement outside ToastProvider prevents error notifications

The ErrorBoundary wraps the ToastProvider, which means if an error occurs, the toast system won't be available to display error messages. This breaks the user feedback mechanism during errors.

-    <ErrorBoundary>
-      <ToastProvider>
+    <ToastProvider>
+      <ErrorBoundary>
        <AuthProvider>
          <ProjectProvider>
            <AppLayout />
          </ProjectProvider>
        </AuthProvider>
-      </ToastProvider>
-    </ErrorBoundary>
+      </ErrorBoundary>
+    </ToastProvider>

Alternative consideration: If ErrorBoundary needs to catch ToastProvider errors, consider having two ErrorBoundaries - one outside for ToastProvider errors (with basic fallback), and one inside for application errors (with toast notifications).

Comment on lines +3 to +41
export function useThrottle<T extends (...args: unknown[]) => unknown>(
callback: T,
delay: number
): T {
const lastRun = useRef<number>(0);
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);

const throttledCallback = useCallback(
((...args: Parameters<T>) => {
const now = Date.now();
const timeSinceLastRun = now - lastRun.current;

if (timeSinceLastRun >= delay) {
lastRun.current = now;
return callback(...args);
}

if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}

timeoutRef.current = setTimeout(() => {
lastRun.current = Date.now();
callback(...args);
}, delay - timeSinceLastRun);
}) as T,
[callback, delay]
);

useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);

return throttledCallback;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium

severity: medium
🐛 Bug: useThrottle implementation has stale closure issue and incorrect throttle behavior

The throttledCallback doesn't include callback in its dependencies properly, and the throttle logic is flawed. When a throttled call is scheduled, it uses the delay from the first call rather than ensuring the callback runs at the end of the throttle window. Additionally, the callback reference can become stale.

-export function useThrottle<T extends (...args: unknown[]) => unknown>(
-  callback: T,
-  delay: number
-): T {
-  const lastRun = useRef<number>(0);
-  const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
-
-  const throttledCallback = useCallback(
-    ((...args: Parameters<T>) => {
-      const now = Date.now();
-      const timeSinceLastRun = now - lastRun.current;
-
-      if (timeSinceLastRun >= delay) {
-        lastRun.current = now;
-        return callback(...args);
-      }
-
-      if (timeoutRef.current) {
-        clearTimeout(timeoutRef.current);
-      }
-
-      timeoutRef.current = setTimeout(() => {
-        lastRun.current = Date.now();
-        callback(...args);
-      }, delay - timeSinceLastRun);
-    }) as T,
-    [callback, delay]
-  );
+export function useThrottle<T extends (...args: unknown[]) => unknown>(
+  callback: T,
+  delay: number
+): T {
+  const lastRun = useRef<number>(0);
+  const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
+  const callbackRef = useRef(callback);
+
+  useEffect(() => {
+    callbackRef.current = callback;
+  }, [callback]);
+
+  const throttledCallback = useCallback(
+    ((...args: Parameters<T>) => {
+      const now = Date.now();
+      const timeSinceLastRun = now - lastRun.current;
+
+      if (timeSinceLastRun >= delay) {
+        lastRun.current = now;
+        return callbackRef.current(...args);
+      }
+
+      if (timeoutRef.current) {
+        clearTimeout(timeoutRef.current);
+      }
+
+      timeoutRef.current = setTimeout(() => {
+        lastRun.current = Date.now();
+        callbackRef.current(...args);
+      }, delay - timeSinceLastRun);
+    }) as T,
+    [delay]
+  );

Comment on lines +127 to +202
<div className="board-filters">
<div className="board-search">
<SearchInput
value={filters.searchQuery}
onChange={(e) => updateFilter('searchQuery', e.target.value)}
placeholder="Search tasks... (⌘K)"
className="task-board-search"
/>
</div>
<div className="board-filter-controls">
<Button
variant="ghost"
size="sm"
onClick={() => setShowFilters(!showFilters)}
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" />
</svg>
Filters
{activeFilterCount > 0 && (
<Badge variant="primary" className="filter-badge">
{activeFilterCount}
</Badge>
)}
</Button>
{activeFilterCount > 0 && (
<Button variant="ghost" size="sm" onClick={resetFilters}>
Clear
</Button>
)}
</div>
</div>

{showFilters && (
<div className="board-filters-panel">
<div className="filter-group">
<label className="filter-label">Status</label>
<Select
value={filters.status}
onChange={(e) => updateFilter('status', e.target.value as TaskStatus | 'all')}
options={[
{ value: 'all', label: 'All Statuses' },
{ value: 'backlog', label: 'Backlog' },
{ value: 'in_progress', label: 'In Progress' },
{ value: 'review', label: 'Review' },
{ value: 'done', label: 'Done' },
]}
/>
</div>
<div className="filter-group">
<label className="filter-label">Priority</label>
<Select
value={filters.priority}
onChange={(e) => updateFilter('priority', e.target.value as TaskPriority | 'all')}
options={[
{ value: 'all', label: 'All Priorities' },
{ value: 'critical', label: 'Critical' },
{ value: 'high', label: 'High' },
{ value: 'medium', label: 'Medium' },
{ value: 'low', label: 'Low' },
]}
/>
</div>
<div className="filter-group">
<label className="filter-label">Assignee</label>
<Select
value={filters.assignee}
onChange={(e) => updateFilter('assignee', e.target.value)}
options={[
{ value: 'all', label: 'All Assignees' },
...availableAssignees.map(assignee => ({ value: assignee, label: assignee })),
]}
/>
</div>
</div>
)}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 High

🐛 Bug: Type casting without validation can cause runtime errors

Lines 166 and 180 cast e.target.value to specific types without validation. If the Select component returns an unexpected value, this could cause type errors downstream when the filter functions expect valid enum values.

           <div className="filter-group">
             <label className="filter-label">Status</label>
             <Select
               value={filters.status}
-              onChange={(e) => updateFilter('status', e.target.value as TaskStatus | 'all')}
+              onChange={(e) => {
+                const value = e.target.value;
+                if (value === 'all' || ['backlog', 'in_progress', 'review', 'done'].includes(value)) {
+                  updateFilter('status', value as TaskStatus | 'all');
+                }
+              }}
               options={[
                 { value: 'all', label: 'All Statuses' },
                 { value: 'backlog', label: 'Backlog' },
                 { value: 'in_progress', label: 'In Progress' },
                 { value: 'review', label: 'Review' },
                 { value: 'done', label: 'Done' },
               ]}
             />
           </div>
           <div className="filter-group">
             <label className="filter-label">Priority</label>
             <Select
               value={filters.priority}
-              onChange={(e) => updateFilter('priority', e.target.value as TaskPriority | 'all')}
+              onChange={(e) => {
+                const value = e.target.value;
+                if (value === 'all' || ['critical', 'high', 'medium', 'low'].includes(value)) {
+                  updateFilter('priority', value as TaskPriority | 'all');
+                }
+              }}
               options={[
                 { value: 'all', label: 'All Priorities' },
                 { value: 'critical', label: 'Critical' },
                 { value: 'high', label: 'High' },
                 { value: 'medium', label: 'Medium' },
                 { value: 'low', label: 'Low' },
               ]}
             />
           </div>
🤖 AI Fix Prompt - Copy this to your AI assistant

Fix the type casting bug in src/features/tasks/components/TaskBoard.tsx at lines 166 and 180

The code currently casts e.target.value directly to TaskStatus or TaskPriority union types without validating that the value is actually one of the expected enum values. This creates a runtime type safety issue because if the Select component somehow returns an unexpected value, the cast will succeed at compile time but cause type errors when the filter functions try to process invalid enum values.

The fix should add validation before the type cast to ensure the value is either 'all' or one of the valid enum values. For the status filter at line 166, validate against 'all' and the TaskStatus values: 'backlog', 'in_progress', 'review', 'done'. For the priority filter at line 180, validate against 'all' and the TaskPriority values: 'critical', 'high', 'medium', 'low'.

Replace the inline onChange handlers with proper validation logic that checks if the value matches the expected options before calling updateFilter. Only perform the type cast after confirming the value is valid. If the value is invalid, the handler should either ignore it or log a warning for debugging.

Check if there are type definitions for TaskStatus and TaskPriority in src/features/tasks/types that could be used to make the validation more maintainable. Consider creating a helper function or constant array of valid values that can be reused if similar Select components exist elsewhere in the codebase.

Files that might need changes:
src/features/tasks/components/TaskBoard.tsx (primary fix location)
src/features/tasks/types (check enum definitions)

Requirements:
Validate the value before type casting
Handle both status and priority filters
Maintain the existing updateFilter function signature
Ensure the fix doesn't break the existing filter functionality
Consider extracting validation logic if it improves code reusability
Add appropriate error handling or logging for invalid values

Comment on lines +54 to +56
'mod+k': () => {
const searchInput = document.querySelector<HTMLInputElement>('.task-board-search input');
searchInput?.focus();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium

🐛 Bug: DOM query selector could fail silently

The keyboard shortcut uses a brittle CSS class selector that could break if the className changes. If the element isn't found, the shortcut silently fails with no feedback to the user.

   useKeyboardShortcuts({
     'mod+k': () => {
-      const searchInput = document.querySelector<HTMLInputElement>('.task-board-search input');
-      searchInput?.focus();
+      const searchInput = document.querySelector<HTMLInputElement>('.task-board-search input');
+      if (searchInput) {
+        searchInput.focus();
+      } else {
+        console.warn('Search input not found for keyboard shortcut');
+      }
     },

onChange={(e) => updateFilter('assignee', e.target.value)}
options={[
{ value: 'all', label: 'All Assignees' },
...availableAssignees.map(assignee => ({ value: assignee, label: assignee })),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Low

💡 Suggestion: Potential empty assignee list edge case

If availableAssignees is empty, the dropdown will only show "All Assignees". Consider adding a disabled placeholder option or hiding the filter when no assignees exist.

           <div className="filter-group">
             <label className="filter-label">Assignee</label>
             <Select
               value={filters.assignee}
               onChange={(e) => updateFilter('assignee', e.target.value)}
+              disabled={availableAssignees.length === 0}
               options={[
                 { value: 'all', label: 'All Assignees' },
+                ...(availableAssignees.length === 0 
+                  ? [{ value: '', label: 'No assignees available', disabled: true }]
+                  : []),
                 ...availableAssignees.map(assignee => ({ value: assignee, label: assignee })),
               ]}
             />
           </div>

Comment on lines +100 to 108
try {
const newTask = taskRepository.create(dto);
dispatch({ type: 'ADD_TASK', payload: newTask });
return newTask;
} catch (error) {
dispatch({ type: 'SET_ERROR', payload: 'Failed to create task' });
throw error;
}
}, []);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium

🐛 Bug: Error handling doesn't prevent state corruption when repository throws

The try-catch wraps the dispatch, but if taskRepository.create() throws before returning, the error is caught and re-thrown, but no error state is set because the dispatch never happened. This creates inconsistent error handling compared to other methods.

  const createTask = useCallback((dto: CreateTaskDTO): Task => {
    try {
      const newTask = taskRepository.create(dto);
      dispatch({ type: 'ADD_TASK', payload: newTask });
      return newTask;
    } catch (error) {
      dispatch({ type: 'SET_ERROR', payload: 'Failed to create task' });
      throw error;
    }
   }, []);

Comment on lines 110 to +134
const updateTask = useCallback((id: string, dto: UpdateTaskDTO) => {
const updated = taskRepository.update(id, dto);
if (updated) {
dispatch({ type: 'UPDATE_TASK', payload: updated });
const currentTask = state.tasks.find(t => t.id === id);
if (!currentTask) return;

const optimisticUpdate: Task = {
...currentTask,
...dto,
updatedAt: new Date(),
};

dispatch({ type: 'UPDATE_TASK', payload: optimisticUpdate });

try {
const updated = taskRepository.update(id, dto);
if (updated) {
dispatch({ type: 'UPDATE_TASK', payload: updated });
} else {
dispatch({ type: 'UPDATE_TASK', payload: currentTask });
dispatch({ type: 'SET_ERROR', payload: 'Failed to update task' });
}
} catch (error) {
dispatch({ type: 'UPDATE_TASK', payload: currentTask });
dispatch({ type: 'SET_ERROR', payload: 'Failed to update task' });
}
}, []);
}, [state.tasks]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 High

🐛 Bug: Race condition in optimistic update - stale task reference

The currentTask is captured at the start, but by the time the rollback happens (lines 127, 131), another update might have modified the task. Rolling back to the stale currentTask will overwrite those changes.

  const updateTask = useCallback((id: string, dto: UpdateTaskDTO) => {
-   const currentTask = state.tasks.find(t => t.id === id);
+   const getCurrentTask = () => state.tasks.find(t => t.id === id);
+   const currentTask = getCurrentTask();
    if (!currentTask) return;

    const optimisticUpdate: Task = {
      ...currentTask,
      ...dto,
      updatedAt: new Date(),
    };
    
    dispatch({ type: 'UPDATE_TASK', payload: optimisticUpdate });
    
    try {
      const updated = taskRepository.update(id, dto);
      if (updated) {
        dispatch({ type: 'UPDATE_TASK', payload: updated });
      } else {
-       dispatch({ type: 'UPDATE_TASK', payload: currentTask });
+       const latestTask = getCurrentTask();
+       if (latestTask) dispatch({ type: 'UPDATE_TASK', payload: latestTask });
        dispatch({ type: 'SET_ERROR', payload: 'Failed to update task' });
      }
    } catch (error) {
-     dispatch({ type: 'UPDATE_TASK', payload: currentTask });
+     const latestTask = getCurrentTask();
+     if (latestTask) dispatch({ type: 'UPDATE_TASK', payload: latestTask });
      dispatch({ type: 'SET_ERROR', payload: 'Failed to update task' });
     }
  }, [state.tasks]);
🤖 AI Fix Prompt - Copy this to your AI assistant

Fix the race condition in src/features/tasks/context/TaskContext.tsx at lines 110-134

There is a critical race condition in the updateTask function's optimistic update pattern. The problem occurs because currentTask is captured at the beginning of the function (line 110), but when the rollback happens in the catch block (lines 127, 131), the state may have been modified by other concurrent updates. Rolling back to the stale currentTask reference will overwrite any changes that happened between the initial capture and the rollback, causing data loss.

The root cause is that the function closes over the task state at the moment it starts executing, but React state updates are asynchronous and batched. If another update modifies the same task while this update is in flight, the rollback will revert to an outdated version of the task, discarding the intermediate changes.

The fix should create a function getCurrentTask that reads the latest task state from state.tasks at the moment it's called, not when the updateTask function starts. Use this function to get the current task at the beginning for the optimistic update, and call it again during rollback to get the most recent version of the task before reverting. This ensures the rollback always uses the latest state, not a stale closure.

Replace the single currentTask capture with a getCurrentTask function that returns state.tasks.find(t => t.id === id). Call getCurrentTask() at the start to get currentTask for the optimistic update. In both error paths (lines 127 and 131), replace the currentTask rollback with a fresh call to getCurrentTask() and only dispatch if a task is found.

This pattern is similar to how state updates should be handled in React when you need the latest state value rather than a captured closure value. Check if there are similar optimistic update patterns in useTasks.ts or other context files that might have the same issue.

Files that will be affected by this change:
src/features/tasks/context/TaskContext.tsx (direct fix)
src/features/tasks/hooks/useTasks.ts (may need similar fix if it uses this pattern)

Requirements:
The getCurrentTask function must read from state.tasks each time it's called, not capture a closure
All rollback operations must use a fresh getCurrentTask() call to avoid stale data
Add null checks after getCurrentTask() in rollback paths since the task might have been deleted
Preserve the existing optimistic update behavior for the success path
Ensure the fix doesn't break the existing dispatch flow or error handling
Test with rapid concurrent updates to the same task to verify no data loss occurs

Comment on lines +36 to +68
const filteredTasks = useMemo(() => {
let result = [...tasks];

if (debouncedSearchQuery) {
const query = debouncedSearchQuery.toLowerCase();
result = result.filter(task =>
task.title.toLowerCase().includes(query) ||
task.description.toLowerCase().includes(query) ||
task.tags.some(tag => tag.toLowerCase().includes(query)) ||
task.assignee?.toLowerCase().includes(query)
);
}

if (filters.status !== 'all') {
result = result.filter(task => task.status === filters.status);
}

if (filters.priority !== 'all') {
result = result.filter(task => task.priority === filters.priority);
}

if (filters.assignee !== 'all') {
result = result.filter(task => task.assignee === filters.assignee);
}

if (filters.tags.length > 0) {
result = result.filter(task =>
filters.tags.some(tag => task.tags.includes(tag))
);
}

return result;
}, [tasks, debouncedSearchQuery, filters]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Low

⚡ Performance: Multiple array iterations could be optimized

The filtering logic performs multiple sequential filter operations, creating intermediate arrays. For large task lists, this could be optimized by combining filters into a single pass.

-    let result = [...tasks];
-
-    if (debouncedSearchQuery) {
-      const query = debouncedSearchQuery.toLowerCase();
-      result = result.filter(task =>
-        task.title.toLowerCase().includes(query) ||
-        task.description?.toLowerCase().includes(query) ||
-        task.tags.some(tag => tag.toLowerCase().includes(query)) ||
-        task.assignee?.toLowerCase()?.includes(query)
-      );
-    }
-
-    if (filters.status !== 'all') {
-      result = result.filter(task => task.status === filters.status);
-    }
-
-    if (filters.priority !== 'all') {
-      result = result.filter(task => task.priority === filters.priority);
-    }
-
-    if (filters.assignee !== 'all') {
-      result = result.filter(task => task.assignee === filters.assignee);
-    }
-
-    if (filters.tags.length > 0) {
-      result = result.filter(task =>
-        filters.tags.some(tag => task.tags.includes(tag))
-      );
-    }
-
-    return result;
+    const query = debouncedSearchQuery?.toLowerCase();
+    
+    return tasks.filter(task => {
+      // Search query filter
+      if (query && !(
+        task.title.toLowerCase().includes(query) ||
+        task.description?.toLowerCase().includes(query) ||
+        task.tags.some(tag => tag.toLowerCase().includes(query)) ||
+        task.assignee?.toLowerCase()?.includes(query)
+      )) {
+        return false;
+      }
+      
+      // Status filter
+      if (filters.status !== 'all' && task.status !== filters.status) {
+        return false;
+      }
+      
+      // Priority filter
+      if (filters.priority !== 'all' && task.priority !== filters.priority) {
+        return false;
+      }
+      
+      // Assignee filter
+      if (filters.assignee !== 'all' && task.assignee !== filters.assignee) {
+        return false;
+      }
+      
+      // Tags filter
+      if (filters.tags.length > 0 && !filters.tags.some(tag => task.tags.includes(tag))) {
+        return false;
+      }
+      
+      return true;
+    });

Comment on lines +34 to +38
.sort((a, b) => {
const priorityDiff = priorityOrder[b.priority] - priorityOrder[a.priority];
if (priorityDiff !== 0) return priorityDiff;
return b.updatedAt.getTime() - a.updatedAt.getTime();
}),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium

🐛 Bug: Potential runtime error when task priority is undefined or invalid

The code assumes all tasks have a valid priority from the priorityOrder map. If a task has an undefined priority or a priority value not in the map, priorityOrder[b.priority] will return undefined, causing NaN in the subtraction and unpredictable sort behavior.

        .sort((a, b) => {
-         const priorityDiff = priorityOrder[b.priority] - priorityOrder[a.priority];
+         const priorityA = priorityOrder[a.priority] ?? 0;
+         const priorityB = priorityOrder[b.priority] ?? 0;
+         const priorityDiff = priorityB - priorityA;
          if (priorityDiff !== 0) return priorityDiff;
          return b.updatedAt.getTime() - a.updatedAt.getTime();
        }),

Comment on lines +62 to +64
<Button variant="ghost" onClick={() => window.location.reload()}>
Reload page
</Button>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium

severity: low
💡 Suggestion: Page reload loses unsaved user data

The "Reload page" button performs a hard refresh which will discard any unsaved form data, drafts, or in-memory state. Consider warning users or attempting to preserve critical state.

- <Button variant="ghost" onClick={() => window.location.reload()}>
+ <Button 
+   variant="ghost" 
+   onClick={() => {
+     if (confirm('Reloading will discard unsaved changes. Continue?')) {
+       window.location.reload();
+     }
+   }}
+ >
    Reload page
  </Button>

Comment on lines +66 to +73
{process.env.NODE_ENV === 'development' && this.state.error && (
<details className="error-boundary-details">
<summary>Error details</summary>
<pre className="error-boundary-stack">
{this.state.error.stack}
</pre>
</details>
)}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium

severity: low
💡 Suggestion: Stack trace could expose sensitive information in development builds deployed to staging

While checking NODE_ENV === 'development' is good, development builds are sometimes deployed to staging environments accessible to external users. Stack traces can reveal file paths, internal variable names, and system architecture.

- {process.env.NODE_ENV === 'development' && this.state.error && (
+ {(process.env.NODE_ENV === 'development' || process.env.REACT_APP_SHOW_ERROR_DETAILS === 'true') && this.state.error && (
    <details className="error-boundary-details">
      <summary>Error details</summary>
      <pre className="error-boundary-stack">
        {this.state.error.stack}
      </pre>
    </details>
  )}

Comment on lines +1 to +81
import { Component, type ReactNode, type ErrorInfo } from 'react';
import { Button } from './Button';

interface ErrorBoundaryProps {
children: ReactNode;
fallback?: ReactNode;
onError?: (error: Error, errorInfo: ErrorInfo) => void;
}

interface ErrorBoundaryState {
hasError: boolean;
error: Error | null;
}

export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false, error: null };
}

static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return { hasError: true, error };
}

componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error('ErrorBoundary caught an error:', error, errorInfo);
this.props.onError?.(error, errorInfo);
}

handleReset = () => {
this.setState({ hasError: false, error: null });
};

render() {
if (this.state.hasError) {
if (this.props.fallback) {
return this.props.fallback;
}

return (
<div className="error-boundary">
<div className="error-boundary-content">
<svg
width="64"
height="64"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
className="error-boundary-icon"
>
<circle cx="12" cy="12" r="10" />
<line x1="12" y1="8" x2="12" y2="12" />
<line x1="12" y1="16" x2="12.01" y2="16" />
</svg>
<h2 className="error-boundary-title">Something went wrong</h2>
<p className="error-boundary-message">
{this.state.error?.message || 'An unexpected error occurred'}
</p>
<div className="error-boundary-actions">
<Button onClick={this.handleReset}>Try again</Button>
<Button variant="ghost" onClick={() => window.location.reload()}>
Reload page
</Button>
</div>
{process.env.NODE_ENV === 'development' && this.state.error && (
<details className="error-boundary-details">
<summary>Error details</summary>
<pre className="error-boundary-stack">
{this.state.error.stack}
</pre>
</details>
)}
</div>
</div>
);
}

return this.props.children;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium

severity: low
💡 Suggestion: Missing error reporting integration

The ErrorBoundary logs errors to console but doesn't integrate with error tracking services (Sentry, LogRocket, etc.). The onError callback is optional, so errors might go unreported in production.

Consider adding a default error reporter:

componentDidCatch(error: Error, errorInfo: ErrorInfo) {
  console.error('ErrorBoundary caught an error:', error, errorInfo);
  
  // Report to error tracking service
  if (process.env.NODE_ENV === 'production') {
    // Example: Sentry.captureException(error, { contexts: { react: errorInfo } });
  }
  
  this.props.onError?.(error, errorInfo);
}

import { useEffect, useMemo } from 'react';
import { useTaskContext } from '../context/TaskContext';
import { COLUMNS_CONFIG, type TaskStatus, type TaskColumn } from '../types';
import { COLUMNS_CONFIG, type TaskStatus, type TaskColumn, type TaskPriority } from '../types';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium

⚠️ Potential Duplicate Detected

Symbol: TaskStatus

This symbol appears to already exist in the codebase:
src/features/tasks/types/index.ts:3

Recommendation:

  • Consider reusing the existing implementation instead of creating a duplicate
  • If this is intentional (e.g., different functionality), consider renaming to avoid confusion
  • If consolidating, ensure all existing usages are updated
View existing implementation

src/features/tasks/types/index.ts:3


export type TaskStatus = 'backlog' | 'in_progress' | 'review' | 'done';

import { useEffect, useMemo } from 'react';
import { useTaskContext } from '../context/TaskContext';
import { COLUMNS_CONFIG, type TaskStatus, type TaskColumn } from '../types';
import { COLUMNS_CONFIG, type TaskStatus, type TaskColumn, type TaskPriority } from '../types';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium

⚠️ Potential Duplicate Detected

Symbol: TaskColumn

This symbol appears to already exist in the codebase:
src/features/tasks/components/TaskColumn.tsx:16, src/features/tasks/components/TaskColumn.tsx:16, src/features/tasks/types/index.ts:18

Recommendation:

  • Consider reusing the existing implementation instead of creating a duplicate
  • If this is intentional (e.g., different functionality), consider renaming to avoid confusion
  • If consolidating, ensure all existing usages are updated
View existing implementation

src/features/tasks/components/TaskColumn.tsx:16


export function TaskColumn({ column, onMoveTask, onDeleteTask, onSelectTask }: TaskColumnProps) {

src/features/tasks/components/TaskColumn.tsx:16


export function TaskColumn({ column, onMoveTask, onDeleteTask, onSelectTask }: TaskColumnProps) {

@neatcod-simulator-dev

Copy link
Copy Markdown
Contributor

📋 Additional Findings (Outside Changed Lines)

The following issues were detected in areas related to your changes but are outside the diff range. These cannot be added as inline comments but may be relevant to your PR.


📍 src/App.tsx:181

🟡 Medium

⚠️ Potential Duplicate Detected

Symbol: Header

This symbol appears to already exist in the codebase:
src/shared/components/Card.tsx:75

Recommendation:

  • Consider reusing the existing implementation instead of creating a duplicate
  • If this is intentional (e.g., different functionality), consider renaming to avoid confusion
  • If consolidating, ensure all existing usages are updated
View existing implementation

src/shared/components/Card.tsx:75


export function CardHeader({ children, action, className = '' }: CardHeaderProps) {


📍 src/features/tasks/context/TaskContext.tsx:51

🟡 Medium

⚠️ Potential Duplicate Detected

Symbol: for

This symbol appears to already exist in the codebase:
src/core/utils/array.ts:38, src/core/utils/array.ts:94, src/core/utils/array.ts:184 (+17 more)

Recommendation:

  • Consider reusing the existing implementation instead of creating a duplicate
  • If this is intentional (e.g., different functionality), consider renaming to avoid confusion
  • If consolidating, ensure all existing usages are updated
View existing implementation

src/core/utils/array.ts:38


    for (const sortFn of sortFns) {

src/core/utils/array.ts:94


  for (let i = 0; i < array.length; i += size) {


📍 src/features/tasks/context/TaskContext.tsx:6

🟡 Medium

⚠️ Potential Duplicate Detected

Symbol: ReactNode

This symbol appears to already exist in the codebase:
src/features/auth/context/AuthContext.tsx:13, src/features/projects/context/ProjectContext.tsx:12, src/shared/components/Dropdown.tsx:6 (+6 more)

Recommendation:

  • Consider reusing the existing implementation instead of creating a duplicate
  • If this is intentional (e.g., different functionality), consider renaming to avoid confusion
  • If consolidating, ensure all existing usages are updated
View existing implementation

src/features/auth/context/AuthContext.tsx:13


  type ReactNode,

src/features/projects/context/ProjectContext.tsx:12


  type ReactNode,


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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant