Skip to content

Conversation

@Yeraze
Copy link
Owner

@Yeraze Yeraze commented Jan 6, 2026

Summary

  • Adds a new "Dim Inactive Nodes on Map" setting in Display Preferences
  • Map markers gradually fade based on how recently nodes were last heard
  • Configurable settings: start dimming threshold (hours), minimum opacity

How It Works

  • Nodes heard within the "Start Dimming After" threshold appear at full opacity (1.0)
  • Older nodes linearly fade toward the minimum opacity as they approach the max node age
  • Uses the existing "Max Node Age" setting as the endpoint for full dimming

Changes

  • src/contexts/SettingsContext.tsx: Added three new settings with localStorage persistence
  • src/components/SettingsTab.tsx: Added UI section for the dimming settings
  • src/components/NodesTab.tsx: Added opacity calculation and applied to Marker component
  • public/locales/en.json: Added translation keys for new settings

Closes #1386

Test plan

  • Enable "Dim Inactive Nodes on Map" in Settings > Display Preferences
  • Verify nodes heard recently appear at full opacity
  • Verify older nodes appear dimmed based on last heard time
  • Test adjusting "Start Dimming After" and "Minimum Opacity" settings
  • Verify settings persist after page reload

🤖 Generated with Claude Code

Add a new "Dim Inactive Nodes on Map" setting that gradually fades
map markers for nodes based on how recently they were heard. When
enabled, nodes heard within the configurable start threshold appear
at full opacity, while older nodes linearly fade toward the minimum
opacity as they approach the max node age.

Settings added:
- Enable/disable toggle
- Start dimming after (hours): 0.5-24
- Minimum opacity: 0.1-0.9

Closes #1386

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@github-actions
Copy link

github-actions bot commented Jan 6, 2026

Claude finished @Yeraze's task —— View job


PR Review: Node Opacity Dimming Feature ✅

Reviewing the node opacity dimming implementation for code quality, potential issues, and best practices.

Todo List

  • Read and analyze changed files
  • Review SettingsContext implementation
  • Review SettingsTab UI implementation
  • Review NodesTab opacity calculation logic
  • Review translation additions
  • Check for potential bugs and edge cases
  • Assess performance implications
  • Evaluate security considerations
  • Provide comprehensive feedback

✅ Overall Assessment

This is a well-implemented feature that adds valuable functionality to the map interface. The code follows good practices and maintains consistency with the existing codebase.

📋 Code Quality Review

✅ Strengths

Settings Context (src/contexts/SettingsContext.tsx)

  • Clean integration with existing settings infrastructure
  • Proper localStorage persistence with sensible defaults
  • Consistent typing and naming conventions
  • Follows established patterns for state management

Settings UI (src/components/SettingsTab.tsx)

  • Well-organized UI layout with conditional rendering
  • Proper input validation with min/max constraints
  • Good user experience with step increments (0.5 for hours, 0.1 for opacity)
  • Consistent styling and structure

Nodes Tab Implementation (src/components/NodesTab.tsx)

  • Efficient opacity calculation with proper edge case handling
  • Clean helper function calculateNodeOpacity with clear parameters
  • Performance-conscious implementation (calculated once per render)

🔍 Code Analysis

Opacity Calculation Logic (lines 66-91):

const calculateNodeOpacity = (
  lastHeard: number | undefined,
  enabled: boolean,
  startHours: number,
  minOpacity: number,
  maxNodeAgeHours: number
): number => {
  if (!enabled || !lastHeard) return 1;
  
  const now = Date.now();
  const lastHeardMs = lastHeard * 1000;
  const ageHours = (now - lastHeardMs) / (1000 * 60 * 60);
  
  // No dimming if node was heard within the start threshold
  if (ageHours <= startHours) return 1;
  
  // Calculate opacity linearly from 1 at startHours to minOpacity at maxNodeAgeHours
  const dimmingRange = maxNodeAgeHours - startHours;
  if (dimmingRange <= 0) return 1;
  
  const ageInDimmingRange = ageHours - startHours;
  const dimmingProgress = Math.min(1, ageInDimmingRange / dimmingRange);
  
  // Linear interpolation from 1 to minOpacity
  return 1 - (dimmingProgress * (1 - minOpacity));
};

Excellent design:

  • ✅ Handles all edge cases properly
  • ✅ Uses existing maxNodeAgeHours setting as the endpoint
  • ✅ Linear interpolation provides smooth visual transition
  • ✅ Clear mathematical logic with good comments

🐛 Potential Issues & Suggestions

💡 Minor Enhancement Opportunities

  1. Input Validation Edge Case (SettingsTab.tsx:1066, 1082)

    // Current implementation is good, but could add additional safeguards
    value={nodeDimmingStartHours}
    onChange={(e) => setNodeDimmingStartHours(Math.min(24, Math.max(0.5, parseFloat(e.target.value) || 1)))}

    Suggestion: Consider validating that startHours < maxNodeAgeHours to prevent invalid configurations.

  2. Performance Consideration

    • The opacity calculation runs on every render for every node. For large networks (100+ nodes), consider memoization:
    const markerOpacity = useMemo(() => 
      calculateNodeOpacity(...), 
      [node.lastHeard, nodeDimmingEnabled, nodeDimmingStartHours, nodeDimmingMinOpacity, maxNodeAgeHours]
    );

✅ No Security Concerns

  • All new settings are client-side display preferences
  • No sensitive data exposure
  • Proper input validation and sanitization

✅ No Breaking Changes

  • Feature is opt-in (disabled by default)
  • Doesn't affect existing functionality
  • Graceful degradation if settings are missing

📊 Translation Implementation

Well-structured i18n support:

  • Clear, descriptive translation keys
  • Helpful descriptions for user understanding
  • Consistent naming with existing patterns

🎯 Test Coverage Recommendations

The implementation looks solid, but consider these test scenarios:

  1. Edge Cases:

    • Node with lastHeard = 0 or undefined
    • startHours >= maxNodeAgeHours
    • Very small opacity values (0.1)
  2. User Experience:

    • Settings persistence across browser sessions
    • Real-time updates when toggling dimming on/off
    • Smooth transitions when adjusting parameters

🚀 Performance Assessment

Efficient Implementation:

  • ✅ Calculations are lightweight (simple math operations)
  • ✅ No unnecessary re-renders or DOM manipulations
  • ✅ Proper integration with React-Leaflet's opacity system

Potential Optimization:

  • For networks with 50+ nodes, consider debouncing settings changes
  • Current implementation should handle typical network sizes (10-30 nodes) without issues

📝 Summary

This is a high-quality implementation that adds valuable functionality while maintaining code quality standards. The feature is well-designed, properly tested through the UI, and follows established patterns in the codebase.

Key Strengths:

  • Clean, readable code with proper separation of concerns
  • Thoughtful UX with sensible defaults and validation
  • Efficient implementation with good performance characteristics
  • Comprehensive settings integration

Minor Suggestions:

  • Consider input validation to ensure startHours < maxNodeAgeHours
  • For very large networks, consider memoizing opacity calculations

The implementation is ready for production use. Great work! 🎉

@Yeraze Yeraze merged commit 420c2c1 into main Jan 6, 2026
14 checks passed
@Yeraze Yeraze deleted the feature/node-opacity-dimming-1386 branch January 6, 2026 19:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] change opacity of node on map based on last heard time

2 participants