29 KiB
Technical Specification: Map Badge Card Enhanced Features
Created: 2025-11-19
Planner: @planner
Status: Draft → Ready for Implementation
Overview
This technical specification details the implementation of four enhanced features for the Home Assistant Map Badge Card:
- Adjustable Marker Size - Configurable marker presets (small/medium/large) via UI
- Speed Calculation - Real-time speed tracking using position history with Haversine formula
- Popup Follow Fix - Ensure info popups remain anchored to moving markers
- Predicted Activity - Speed-based activity inference as alternative to sensor data
Objectives
- Enable users to customize marker appearance with three size presets
- Calculate and display accurate travel speed in entity popups
- Fix popup positioning bugs for moving entities across both map providers
- Provide fallback activity detection based on movement patterns
- Maintain 100% backward compatibility with existing configurations
- Ensure consistent behavior across Leaflet and Google Maps providers
Success Criteria
- Marker sizes render correctly at 36px, 48px, and 64px dimensions
- Speed calculations show < 10% error compared to GPS accuracy
- Popups track marker movement without reposition lag
- Activity prediction matches documented thresholds (1, 7, 25 km/h)
- All new configuration options appear in UI editor
- Existing cards continue functioning without configuration changes
- Unit test coverage ≥ 80% for new calculation logic
- Manual testing confirms functionality on both map providers
Architecture
Component Structure
Map Badge Card System
├── Main Component (map-badge-card.js)
│ └── ConfigManager (config-manager.js)
│ ├── EntityDataFetcher (entity-data-fetcher.js) ← ADD: Speed calc & history
│ ├── IframeMessenger (iframe-messenger.js) ← UPDATE: Pass marker_size
│ └── Editor UI Layer (editor-ui.js, editor-handlers.js) ← UPDATE: New controls
└── Map Renderer (map-badge-v2.html) ← UPDATE: Dynamic sizes, popup tracking
├── Leaflet Implementation
└── Google Maps Implementation
Technology Stack
- Language: JavaScript ES6 Modules
- Framework: Home Assistant Custom Card (Legacy API)
- Map Libraries: Leaflet 1.x, Google Maps JavaScript API
- Key Algorithms: Haversine formula for distance calculation
- Browser APIs: PostMessage for iframe communication
Design Patterns
-
Configuration Cascade: Default → User Config → Runtime Override
- Prevents cascading re-renders by batching config updates
- Maintains backward compatibility with optional fields
-
Position History Ring Buffer: Fixed-size array (5 entries) per entity
- Prevents memory leaks from infinite history growth
- Provides sufficient data points for accurate speed averaging
-
Data Transformation Pipeline: Raw HA State → Cached Entity Data → Map Parameters
- Centralizes calculation logic in EntityDataFetcher
- Ensures consistent data format across consumers
-
Provider Abstraction: Unified interface for Leaflet/Google Maps
- Minimizes provider-specific code duplication
- Enables feature parity across implementations
Implementation Phases
Phase 1: Foundation (Data Structures & Configuration)
Objective: Establish constants, config validation, and parameter passing infrastructure
Assigned to: @coder-mid
Tasks:
-
Update Constants (
src/constants.js)- Add
MARKER_SIZESobject with small/medium/large presets (marker width, badge size, popup offset) - Add
ACTIVITY_THRESHOLDSobject defining speed boundaries (still, walking, cycling, vehicle) - Extend
DEFAULT_CONFIGwithmarker_size,use_predicted_activity,activity_source - Complexity: Simple (data definition only)
- Dependencies: None
- Deliverables: Updated constants file with 3 new exports
- Add
-
Update ConfigManager (
src/config-manager.js)- Extend
setConfig()to validate and store new marker/activity configuration - Add marker_size to
buildIframeParams()return value - Add boolean flag tracking for config change detection
- Complexity: Simple (config pass-through, no logic changes)
- Dependencies: Phase 1.1
- Deliverables: Modified config manager with new parameter handling
- Extend
Phase 2: Core Features (Calculation & Rendering)
Objective: Implement speed calculation engine and dynamic marker sizing
Assigned to: @coder-senior
Tasks:
-
Enhance EntityDataFetcher (
src/entity-data-fetcher.js)- Add
_positionHistoryMap (entityId → PositionEntry[]) - Implement
_calculateHaversineDistance()(returns meters) - Implement
calculateSpeed()(returns {speed_kmh, speed_mph, time_diff}) - Implement
_updatePositionHistory()with ring buffer logic (max 5 entries) - Implement
predictActivity()(returns string: 'still', 'walking', 'on_bicycle', 'in_vehicle') - Modify
fetchEntities()to calculate/store speed, update history - Modify
prepareEntityData()to include speed and predicted_activity fields - Complexity: Medium (requires mathematical accuracy + caching)
- Dependencies: Phase 1.1
- Deliverables: Enhanced data fetcher with calculation methods
- Add
-
Update Map HTML Rendering (
src/map-badge-v2.html)- Parse
marker_sizeURL parameter on load - Create CSS variables:
--marker-size,--badge-size,--popup-offset - Update
.custom-marker-wrapperCSS to use variables - Modify Leaflet icon creation to use dynamic
iconSize,iconAnchor,popupAnchor - Modify Google Maps
CustomMarker.draw()to use dynamic dimensions - Ensure marker CSS class names remain consistent
- Complexity: Medium (requires CSS/JS synchronization)
- Dependencies: Phase 2.1, Phase 1.2
- Deliverables: Map renderer supporting 3 size presets
- Parse
Phase 3: UI Integration (Editor Controls)
Objective: Add configuration controls to visual editor
Assigned to: @coder-junior
Tasks:
-
Add Marker Size Control (
src/editor-ui.js)- Extend
_generateAppearanceSection()with<ha-select id="marker_size"> - Include three
<mwc-list-item>entries: small (36px), medium (48px), large (64px) - Add descriptive helper text below dropdown
- Complexity: Simple (HTML template modification)
- Dependencies: Phase 2.2
- Deliverables: Editor UI with marker size dropdown
- Extend
-
Add Activity Source Control (
src/editor-ui.js)- Extend appearance section with
<ha-select id="activity_source"> - Include two options: 'Activity Sensor' and 'Speed-Based Prediction'
- Add multi-line helper text explaining speed thresholds
- Complexity: Simple (HTML template modification)
- Dependencies: Phase 3.1
- Deliverables: Editor UI with activity source selector
- Extend appearance section with
-
Attach Editor Event Handlers (
src/editor-handlers.js)- Add
_attachMarkerSizeListener()method - Add
_attachActivitySourceListener()method - Register both in
attachListeners()call - Ensure handlers use
onChange()callback properly - Complexity: Simple (event listener pattern)
- Dependencies: Phase 3.2
- Deliverables: Functional UI controls with state management
- Add
Phase 4: Popup & Display Logic
Objective: Fix popup anchoring and enhance content with speed data
Assigned to: @coder-mid
Tasks:
-
Fix Popup Following Behavior (
src/map-badge-v2.html)- Leaflet: Ensure
marker.update()called when popup open during position change - Google Maps: Store InfoWindow reference per marker, call
setPosition()on update - Add position change detection to trigger popup reposition
- Prevent popup flicker during rapid updates
- Complexity: Medium (requires timing/animation testing)
- Dependencies: Phase 2.2
- Deliverables: Anchored popups that track marker movement
- Leaflet: Ensure
-
Enhance Popup HTML (
src/map-badge-v2.html)- Modify
createPopupHTML()to acceptspeedDataparameter - Add speed display row with icon when
speedData.speed_kmh !== null - Format speed as "X.X km/h" (one decimal place)
- Position speed below state, above activity in visual hierarchy
- Complexity: Medium (requires styling + conditional rendering)
- Dependencies: Phase 4.1
- Deliverables: Richer popup content with speed information
- Modify
-
Implement Activity Selection Logic (
src/entity-data-fetcher.js)- Add
_getActivityToUse(data, config)helper method - Respect
config.activity_sourcewhen determining displayed activity - Return predicted activity when
speed_predictedselected and speed exists - Fallback to sensor activity otherwise
- Modify
prepareEntityData()to use activity selection logic - Complexity: Simple (conditional logic)
- Dependencies: Phase 4.2
- Deliverables: Configurable activity source behavior
- Add
Phase 5: Integration & Dynamic Updates
Objective: Enable runtime config changes and ensure system-wide consistency
Assigned to: @coder-mid
Tasks:
-
Extend IframeMessenger (
src/iframe-messenger.js)- Add
markerSizeparameter tosendConfigUpdate() - Include marker_size in PostMessage payload
- Maintain backward compatibility (default to 'medium')
- Complexity: Simple (parameter pass-through)
- Dependencies: Phase 4.3
- Deliverables: Messenger supporting marker size propagation
- Add
-
Update Config Change Detection (
src/map-badge-card.js)- Add
'marker_size'tovisualPropsChangedcheck array - Pass
config.marker_sizeto_messenger.sendConfigUpdate() - Ensure config reload triggers re-render with new marker size
- Complexity: Simple (property tracking)
- Dependencies: Phase 5.1
- Deliverables: Main card component integrated with new features
- Add
-
Handle Runtime Marker Size Changes (
src/map-badge-v2.html)- Listen for
marker_sizein config-update PostMessage - Update CSS variables when new size received
- Trigger
updateAllMarkers()to re-render with new dimensions - Preserve popup open/close state during re-render toggle
- Complexity: Simple (event handler + batch update)
- Dependencies: Phase 5.2
- Deliverables: Live marker size adjustment without reload
- Listen for
API Contracts
Configuration Schema
Location: User config passed to setConfig()
New Properties:
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
marker_size |
string | No | 'medium' |
Marker preset: 'small', 'medium', 'large' |
activity_source |
string | No | 'sensor' |
Activity mode: 'sensor' or 'speed_predicted' |
use_predicted_activity |
boolean | No | false |
(Deprecated) Backward compatibility flag |
Example Valid Configurations:
# Minimal config (uses defaults)
type: custom:map-badge-card
entities:
- person: person.john
# Marker size only
type: custom:map-badge-card
marker_size: large
google_api_key: YOUR_KEY
# Full feature set
type: custom:map-badge-card
marker_size: medium
activity_source: speed_predicted
entities:
- person: person.john
activity: sensor.john_phone_activity
Iframe Parameters
Location: URL query string passed to map-badge-v2.html
New Parameters:
| Parameter | Type | Example | Description |
|---|---|---|---|
marker_size |
string | marker_size=large |
Selected size preset |
Complete URL Example:
map-badge-v2.html?marker_size=medium&dark_mode=false&theme_color=%23ff9800&...
PostMessage API (Config Updates)
Location: Communication from main card to iframe
Enhanced Message Structure:
interface ConfigUpdateMessage {
type: 'config-update';
zones: ZoneConfig[];
activities: ActivityConfig[];
marker_border_radius: number;
badge_border_radius: number;
marker_size: string; // NEW: Size preset
timestamp: number;
}
Sending Example:
// From map-badge-card.js
_messenger.sendConfigUpdate(
config.zones,
config.activities,
config.marker_border_radius,
config.badge_border_radius,
config.marker_size // New parameter
);
Receiving Example:
// In map-badge-v2.html
if (event.data.type === 'config-update') {
if (event.data.marker_size) {
applyMarkerSize(event.data.marker_size);
}
}
Data Models
Marker Size Presets
Location: src/constants.js
interface MarkerSize {
marker: number; // Base marker diameter (pixels)
badge: number; // Badge diameter (pixels)
popupOffset: number; // Popup anchor offset (pixels, negative)
}
export const MARKER_SIZES: Record<string, MarkerSize> = {
small: {
marker: 36,
badge: 15,
popupOffset: -52
},
medium: {
marker: 48,
badge: 20,
popupOffset: -68
},
large: {
marker: 64,
badge: 24,
popupOffset: -88
}
};
Usage:
- Leaflet:
iconSize = [marker, marker + 14] - Google Maps:
div.style.width = marker + 'px' - CSS:
--marker-size: ${marker}px
Activity Thresholds
Location: src/constants.js
interface ActivityThresholds {
still: number; // km/h threshold for "still" (upper bound)
walking: number; // km/h threshold for "on_bicycle" (upper bound)
cycling: number; // km/h threshold for "in_vehicle" (upper bound)
vehicle: number; // km/h threshold for "vehicle" (unused, for parity)
}
export const ACTIVITY_THRESHOLDS: ActivityThresholds = {
still: 1, // < 1 km/h → 'still'
walking: 7, // 1-7 km/h → 'walking'
cycling: 25, // 7-25 km/h → 'on_bicycle'
vehicle: 25 // > 25 km/h → 'in_vehicle'
};
Activity Mapping:
function predictActivity(speedKmh: number): string {
if (speedKmh < ACTIVITY_THRESHOLDS.still) return 'still';
if (speedKmh < ACTIVITY_THRESHOLDS.walking) return 'walking';
if (speedKmh < ACTIVITY_THRESHOLDS.cycling) return 'on_bicycle';
return 'in_vehicle';
}
Position History Entry
Location: Internal to EntityDataFetcher
interface PositionEntry {
latitude: number;
longitude: number;
timestamp: number; // Unix timestamp in milliseconds
}
// Storage structure
private _positionHistory: Map<string, PositionEntry[]>;
History Management:
- Maximum 5 entries per entity (ring buffer)
- Oldest entries removed automatically
- New entries appended with current timestamp
- Speed calculation requires ≥ 2 entries
Speed Calculation Result
Location: Calculated in EntityDataFetcher, passed to map
interface SpeedData {
speed_kmh: number; // Speed in kilometers per hour
speed_mph: number; // Speed in miles per hour
time_diff: number; // Time difference in seconds
distance_m?: number; // Optional: distance in meters
accuracy?: number; // Optional: GPS accuracy if available
}
Calculation Formula:
const distance = haversine(prevLat, prevLon, currLat, currLon); // meters
const timeDiff = (currTime - prevTime) / 1000; // seconds
const speedKmh = (distance / 1000) / (timeDiff / 3600); // km/h
Entity Data Output
Location: EntityDataFetcher.prepareEntityData() return value
interface EntityDataMap {
[entityId: string]: {
state: string;
attributes: {
friendly_name: string;
latitude: number;
longitude: number;
entity_picture?: string;
// ... other HA attributes
};
activity: string; // Selected activity ('sensor' or 'speed_predicted')
speed: SpeedData | null; // Calculated speed (null if insufficient history)
predicted_activity: string | null; // Calculated activity (null if no speed)
zone: string | null;
zone_color: string | null;
};
}
Activity Selection Logic:
const activity = config.activity_source === 'speed_predicted' && speedData
? predictActivity(speedData.speed_kmh)
: sensorActivity;
Testing Requirements
Unit Tests (Target: 80% coverage)
Location: New test files or existing test structure
-
Haversine Distance Calculation
// Test: Known locations with expected distances test('haversine: New York to London', () => { const distance = haversine(40.7128, -74.0060, 51.5074, -0.1278); expect(distance).toBeCloseTo(5570000, -4); // ~5570km }); // Edge: Same location → distance = 0 // Edge: Antipodal points → half circumference -
Speed Calculation
// Test: Simulated position history test('speed: 100m in 10 seconds', () => { const history = [ { lat: 0, lon: 0, timestamp: 0 }, { lat: 0.0009, lon: 0, timestamp: 10000 } // ~100m north ]; const speed = calculateSpeed('person.test', history[1]); expect(speed.speed_kmh).toBeCloseTo(36, 1); // 100m/10s = 36 km/h }); // Edge: Single history entry → returns null // Edge: Zero time difference → returns null // Edge: Negative coordinates → still calculates correctly -
Activity Prediction
// Test: All threshold boundaries test('predictActivity: walking threshold', () => { expect(predictActivity(0.5)).toBe('still'); expect(predictActivity(1)).toBe('still'); // Edge case expect(predictActivity(1.1)).toBe('walking'); expect(predictActivity(6.9)).toBe('walking'); expect(predictActivity(7)).toBe('walking'); // Edge case expect(predictActivity(7.1)).toBe('on_bicycle'); expect(predictActivity(24.9)).toBe('on_bicycle'); expect(predictActivity(25)).toBe('on_bicycle'); // Edge case expect(predictActivity(25.1)).toBe('in_vehicle'); }); // Edge: Negative speed → should not occur, but handle gracefully // Edge: Null/undefined speed → returns null -
Marker Size CSS Application
// Test: CSS variable setting test('applyMarkerSize: small preset', () => { applyMarkerSize('small'); expect(document.documentElement.style.getPropertyValue('--marker-size')).toBe('36px'); }); // Edge: Invalid preset → defaults to medium // Edge: Empty string → defaults to medium
Integration Tests
Location: Test card loading with configuration
-
Config Propagation Flow
// Test: marker_size flows through entire system test('config propagation: marker_size', async () => { const config = { marker_size: 'large', /* ... */ }; card.setConfig(config); await card.updateComplete; // Verify iframe URL contains marker_size=large expect(iframe.src).toContain('marker_size=large'); // Verify CSS variable set in iframe expect(iframe.contentDocument.documentElement.style.getPropertyValue('--marker-size')).toBe('64px'); }); -
Position History Persistence
// Test: History maintained across multiple updates test('position history: 5 updates', async () => { const fetcher = new EntityDataFetcher(); // Simulate 5 position updates for (let i = 0; i < 5; i++) { await fetcher.fetchEntities(); await sleep(1000); } // Verify history has 5 entries const history = fetcher._positionHistory['person.test']; expect(history.length).toBe(5); // 6th update should still have 5 (ring buffer) await fetcher.fetchEntities(); expect(history.length).toBe(5); }); -
Popup Following Behavior
// Test: Markers + markers move → popup stays anchored test('popup following: Leaflet', async () => { const marker = createMarkerOSM(entityData); marker.bindPopup('Test').openPopup(); const initialPopupPos = marker.getPopup().getLatLng(); // Move marker marker.setLatLng([newLat, newLon]); marker.update(); const newPopupPos = marker.getPopup().getLatLng(); expect(newPopupPos.lat).toBeCloseTo(newLat, 6); expect(newPopupPos.lng).toBeCloseTo(newLon, 6); }); // Repeat for Google Maps with InfoWindow.setPosition() -
Activity Override Logic
// Test: speed_predicted overrides sensor test('activity source: speed_predicted', () => { const config = { activity_source: 'speed_predicted' }; const data = { activity: { state: 'walking' }, speed: { speed_kmh: 50 } }; const result = entityDataFetcher.prepareEntityData(config); expect(result['person.test'].activity).toBe('in_vehicle'); // From speed, not sensor });
Manual Testing Scenarios
Environment: Real Home Assistant instance + mobile device
-
Marker Size Visualization
- Configure card with
marker_size: small→ verify 36px markers - Configure card with
marker_size: medium→ verify 48px markers (default) - Configure card with
marker_size: large→ verify 64px markers - Test both Leaflet and Google Maps providers
- Verify badges scale proportionally within markers
- Check popup positioning aligns correctly at all sizes
- Configure card with
-
Speed Calculation Accuracy
- Walk 100m at constant speed, verify calculated speed matches expected
- Drive at 50 km/h, verify speed display within ±2 km/h
- First card load shows no speed (waiting for history)
- After 2+ position updates, speed appears in popup
- Check speed updates smoothly (not jumping erratically)
-
Popup Tracking Behavior
- Open popup, move ~10 meters → popup should move with marker
- Rapid movement (driving) → popup stays anchored without lag
- Switch between map providers → behavior consistent
- Popup close/reopen during movement → re-anchors correctly
- No visual flicker or reposition artifacts
-
Activity Prediction Validation
- Stand still (< 1 km/h) → shows 'still'
- Walk slowly (3 km/h) → shows 'walking'
- Cycle (15 km/h) → shows 'on_bicycle'
- Drive (60 km/h) → shows 'in_vehicle'
- Switch between sensor and speed_predicted → activity updates
- Speed-based mode works without activity sensor configured
-
Configuration Editor UX
- Marker size dropdown shows preview text with pixel dimensions
- Activity source selector includes helpful threshold explanation
- Changes reflect immediately in preview (if available)
- Configuration YAML updates correctly when UI changed
- Form validation prevents invalid values
-
Backward Compatibility
- Existing cards without new config options → work unchanged
- Default values match previous hardcoded behavior
- Migration from old to new config → seamless transition
- No console errors or warnings with legacy configurations
Performance Requirements
- Speed Calculation: < 5ms per entity (single haversine calculation)
- Position History: Memory < 1KB per entity (5 entries × ~24 bytes)
- Popup Updates: < 16ms frame time (60 FPS target)
- Config Updates: < 50ms full re-render with new marker size
- Concurrent Entities: Tested with 10+ entities without degradation
Dependencies & Risks
External Dependencies
- Home Assistant Frontend: Must support existing card API
- Browser Support: ES6 modules, CSS variables (Chrome 49+, Firefox 31+, Safari 9.1+)
- Map APIs: Leaflet (bundled), Google Maps (external API key required)
- No new runtime dependencies (Haversine formula self-implemented)
Internal Dependencies
-
ConfigManager must be updated before EntityDataFetcher
- Reason: Default values required for speed calculation logic
- Mitigation: Phased implementation with clear ordering
-
EntityDataFetcher must be complete before Map HTML
- Reason: Map renderer consumes speed/speed/predicted_activity fields
- Mitigation: Phase 2 fully completes before Phase 4 begins
-
Editor UI must be complete before integration testing
- Reason: Manual testing requires UI configuration controls
- Mitigation: Phase 3 prioritized early in development cycle
Risk Assessment
Risk 1: GPS Jitter Causes Erratic Speed Readings
Likelihood: Medium
Impact: Medium (poor user experience)
Mitigation:
- Implement minimum time threshold (5 seconds) between speed calculations
- Average across multiple history points (not just last 2)
- Add GPS accuracy filtering if available in HA attributes
- Display "-- km/h" when accuracy below threshold
Risk 2: Google Maps InfoWindow Performance Issues
Likelihood: Low
Impact: High (UI freezes during movement)
Mitigation:
- Throttle setPosition() calls to max 4x per second
- Test with 10+ moving markers simultaneously
- Provide fallback to disable auto-follow for power users
- Document performance considerations in README
Risk 3: Position History Lost on Iframe Reload
Likelihood: High
Impact: Low-Medium (speed unavailable temporarily)
Mitigation:
- Document that history is main-card-scoped, not persisted across reloads
- Reduce iframe reload frequency (already batched in existing code)
- Accept temporary speed unavailability as acceptable UX
- Consider future enhancement: persist history in localStorage
Risk 4: Mobile Browser CSS Variable Support
Likelihood: Low
Impact: Medium (marker sizing fails on old browsers)
Mitigation:
- Provide JavaScript fallback: direct style manipulation if CSS variables unsupported
- Test on iOS Safari 10+, Android Chrome 50+
- Document minimum browser requirements in README
- Graceful degradation: stick to medium size if feature detection fails
Risk 5: Activity Sensor vs Speed Mismatch Confuses Users
Likelihood: Medium
Impact: Low (user education issue)
Mitigation:
- Add explicit UI label: "Activity (from speed)" when in predictive mode
- Include configuration helper text explaining thresholds
- Document in README when to use each mode
- Consider debug logging to help users understand selection logic
Open Questions
-
Q: Should speed calculation continue when device is stationary?
A: Yes, but will return 0 km/h. Helps indicate "still" vs "no data" -
Q: How to handle time sync issues if HA server clock drifts?
A: Usestate.last_changedtimestamp (UTC from HA), not client time -
Q: Should popup follow be configurable (enable/disable)?
A: Defer to future enhancement if requested. Current plan: always follow. -
Q: Add imperial (mph) display option?
A: Calculate both but display km/h only for now. Add config option if users request. -
Q: Persist position history across browser sessions?
A: Not in scope. History is ephemeral by design (security/privacy consideration).
Implementation Notes
Code Quality Standards
- ESLint: Maintain existing rules
- Formatting: Match existing 2-space indentation, semicolons
- Comments: Add JSDoc for new public methods, inline comments for complex calculations
- Naming:
camelCasefor variables/functions,PascalCasefor classes,UPPER_CASEfor constants
Performance Optimizations
- Memoization: Cache marker size CSS variable values to avoid repeated calculations
- Batch Updates: Use requestAnimationFrame for popup position updates if needed
- Early Returns: Return early in speed calc if insufficient history (avoid math operations)
- Object Pooling: Reuse PositionEntry objects to reduce GC pressure (if many entities)
Backward Compatibility
- All new config properties optional with sensible defaults
- Default
marker_size: 'medium'matches current 48px hardcoded size - Default
activity_source: 'sensor'preserves existing behavior - Graceful fallbacks in map renderer if parameters missing
- Existing card configs continue working without changes
Debugging Support
Add debug logging (respect existing debugMode flag):
if (this._debugMode) {
console.log(`[MapBadge] Speed: ${speedKmh.toFixed(1)} km/h for ${entityId}`);
console.log(`[MapBadge] Activity: ${activity} (source: ${config.activity_source})`);
}
Future Extensibility
- Marker Size: Easy to add 'xlarge' preset (just add to MARKER_SIZES)
- Activity Thresholds: Could be user-configurable (expose in UI)
- Speed Calculation: Could add averaging window configuration
- Popup Content: Speed display could be toggled via config
Status Updates
- 2025-11-19: Technical specification created by @documentator
- 2025-11-19: Ready for implementation phase (pending coder assignment)
- Next: Phase 1 development to begin upon Vexa delegation
Specification Status: ✅ READY FOR IMPLEMENTATION