This document outlines the high-level architecture and key components that make up the Fast Search Card.
These components form the foundation of the application, managing data, state, and the connection to Home Assistant.
-
index.jsx- Responsibility: The main entry point of the application.
- Function: It exposes the
mount,updateHass, andunmountfunctions required by Home Assistant to render the Lovelace card. It wraps the entire application in theDataProvider.
-
DataProvider.jsx- Responsibility: The "brain" of the card. This is the central hub for all data and state management.
- Function:
- Initializes and manages the IndexedDB database for persistent caching.
- Manages a Memory Cache for instant access to frequently used data.
- Fetches all entities, areas, and device registries from Home Assistant.
- Initializes the System Entity Registry (
registry.js). - Manages global state for settings, favorites, and all entities.
- Provides React Context Hooks (
useData,useEntities,useFavorites,useSettings) for all child components. - Subscribes to Home Assistant's real-time state change events and updates the
entitiesstate immutably.
These components define the primary user interface and layout.
- Location:
src/components/SearchField.jsx - Responsibility: Main component orchestrating the entire card UI
- Function:
- Manages core UI state (expanded, search value, active category)
- Renders search bar and category buttons
- Displays filtered entity results
- Handles entity selection and DetailView display
- Integrates fuzzy search functionality
State Management:
{
isExpanded: boolean, // Card expansion state
searchValue: string, // Search input value
activeCategory: string, // Selected category (devices, sensors, actions)
selectedEntity: object, // Currently selected entity
viewMode: string // "grid" or "list"
}Hooks Used:
useSearchFieldState- UI state managementuseFuzzySearch- Fuzzy search logic (Fuse.js)useEntities- Entity data from DataProvideruseFavorites- Favorites managementuseSettings- Card settings
Sub-Components:
SearchInputSection.jsx- Search bar and logoCategoryButtonsPanel.jsx- Category filter buttonsGroupedDeviceList.jsx- Grouped entity resultsFilterControlPanel.jsx- Additional filtersDetailViewWrapper.jsx- DetailView containerAIModeSection.jsx- AI chat interface
Search Filters:
Uses searchFilters.js to:
- Group entities by area (room)
- Filter by domain (light, switch, etc.)
- Apply custom patterns
- Sort by favorites and recents
- Location:
src/components/DetailView.jsx - Responsibility: Expanded panel showing detailed entity information and controls
- Function:
- Manages tab navigation (Controls, Context, History, Schedule, Settings)
- Renders entity header with icon, name, area
- Displays favorite button
- Dynamically loads tab content
- Handles system entity views via lazy loading
State Management:
{
activeTab: number, // Current tab index (0-4)
isMobile: boolean, // Mobile layout flag
isFavorite: boolean, // Favorite status
sliderPosition: object // Tab slider position { x, width, height }
}Tab Structure:
const tabs = [
{ icon: '⚙️', label: 'Controls', component: UniversalControlsTab },
{ icon: '🔗', label: 'Context', component: ContextTab },
{ icon: '📊', label: 'History', component: HistoryTab },
{ icon: '⏰', label: 'Schedule', component: ScheduleTab },
{ icon: '⚙️', label: 'Settings', component: SettingsTab }
];Sub-Components:
DetailHeader.jsx- Entity header with back buttonEntityIconDisplay.jsx- Large entity icon displayTabNavigation.jsx- Tab selector with animated slidertabIcons.jsx- Tab icon definitions
Props:
{
item: object, // Entity object
isVisible: boolean, // Panel visibility
onBack: function, // Back button handler
onToggleFavorite: function, // Favorite toggle handler
onServiceCall: function, // Service call handler
onActionNavigate: function, // Action navigation handler
hass: object, // Home Assistant connection
lang: string, // Language code
isFavorite: boolean // Favorite status
}Layout Modes:
- Desktop - Tabs at top, fixed height panel
- Mobile - Tabs at bottom, full-screen panel
System Entity Integration:
Uses SystemEntityLazyView for:
- Settings entity
- Marketplace entity
- Schedule Viewer entity
- Custom plugins
- Location:
src/components/DeviceCard.jsx - Responsibility: Renders individual entity as card (grid) or row (list)
- Function:
- Displays entity icon, name, state
- Animated icon based on state (via
AnimatedDeviceIcons.jsx) - Handles click events to open DetailView
- Responsive icon sizing
- System entity integration
- Glassmorphism styling
Props:
{
device: object, // Entity object
viewMode: string, // "grid" or "list"
onClick: function, // Click handler
index: number, // List index for animation delay
lang: string, // Language code
animationKey: string, // Animation trigger key
roomIndex: number, // Room index (for grouping)
itemsPerRoom: array, // Items per room (for animation)
isPanelAnimationComplete: boolean // Animation state flag
}View Modes:
Grid View (DeviceCardGridView.jsx):
- Card-based layout
- Shows icon, name, state
- Responsive grid (1-4 columns)
- Aspect ratio maintained
- Hover scale effect
List View (DeviceCardListView.jsx):
- Compact row layout
- Icon on left, text on right
- More entities visible
- Smaller spacing
- Quick scanning
Features:
-
Responsive Icon Sizes:
- Desktop (>768px): 56px
- Tablet (480-768px): 48px
- Phone (360-480px): 36px
- Small phones (<360px): 32px
-
Active State Detection:
- Lights: "on" state
- Sensors: Numeric values
- Binary sensors: "on" state
- Background color changes based on state
-
State Display:
- Numeric sensors: Formatted value with unit
- Binary sensors: Translated state (Open/Closed, On/Off)
- Standard entities: Translated state
- Sensor advice for context (e.g., "High", "Normal", "Low")
-
Animations:
- Staggered entrance (0.08s delay per item)
- Hover scale (1.02x)
- Active state glow
- Smooth transitions
System Entity Integration:
Uses DeviceCardIntegration.jsx for:
- Custom system entity icons
- Special animation variants
- Unique styling per system entity type
Animation Variants:
{
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: { delay: animationDelay }
},
hover: {
scale: 1.02,
backgroundColor: isActive ? '#FFFFFF' : 'rgba(255,255,255,0.18)'
}
}- Location:
src/components/SubcategoryBar.jsx - Responsibility: Horizontal filter bar for result refinement
- Function:
- Filter by area (room)
- Filter by device type (domain)
- Show suggested filters
- Smooth scrolling horizontal layout
Filter Types:
- By Area: Living Room, Bedroom, Kitchen, etc.
- By Type: Lights, Switches, Sensors, etc.
- Suggestions: Based on current results
Features:
- Horizontal scroll on mobile
- Active filter highlighting
- Clear all filters button
- Smooth animations
- Responsive design
These components are rendered inside the DetailView panel.
- Location:
src/components/tabs/UniversalControlsTab.jsx - Responsibility: Default "Controls" tab providing domain-specific controls
- Refactored: October 2025 (Previously 427 lines)
Key Features:
- Domain-specific control rendering
- Circular slider integration for analog controls
- Quick action buttons for common operations
- State display and feedback
- Responsive layout
Supported Domains:
- Light - Brightness slider, color picker, color temperature
- Climate - Temperature slider, HVAC mode, fan mode, preset
- Cover - Position slider, open/close/stop buttons
- Fan - Speed slider, oscillation, direction, preset
- Media Player - Volume slider, source selection
- Switch - On/Off toggle
- Lock - Lock/Unlock buttons
- Vacuum - Start, pause, return to dock
Components Used:
CircularSlider- Main analog controlClimateSettingsBar- Climate mode selectionPresetButtonsGroup- Quick preset buttonsControlButton- Individual control buttonsSolarCarousel- Solar/energy entity display
Props:
{
item: object, // Entity object
onServiceCall: function,// Service call handler
hass: object, // Home Assistant connection
lang: string // Language code
}- Location:
src/components/tabs/ContextTab.jsx - Responsibility: Displays related automations, scripts, and scenes
- Refactored: September 2025 (Previously 612 lines)
Key Features:
- Lists automations that reference the entity
- Shows associated scripts
- Displays related scenes
- Filter tabs (All, Automations, Scripts, Scenes)
- One-tap execution for scripts and scenes
- Visual state indicators
Sub-Components:
FilterTabs.jsx- Tab selector for action typesActionItem.jsx- Individual action display card
Props:
{
item: object, // Entity object
onActionNavigate: function, // Navigation handler
onServiceCall: function,// Service call handler
hass: object, // Home Assistant connection
lang: string // Language code
}Data Flow:
- Fetches all automations/scripts/scenes via
actionUtils.js - Filters by entity_id references
- Groups by type (automation, script, scene)
- Renders filtered list with action buttons
- Location:
src/components/tabs/HistoryTab.jsx - Responsibility: Visual history display with charts and analytics
- Refactored: October 2025 (Previously 795 lines, -48%)
Key Features:
- Interactive timeline charts (Chart.js)
- Configurable time ranges (1h - 30 days)
- State change events with timestamps
- Time-of-day analysis (Morning, Day, Evening, Night)
- Event statistics and summaries
- Accordion sections with animations
Refactoring Highlights:
- CSS Extracted: 255 lines to
HistoryTab.css - Animations Extracted:
accordionAnimations.js - Utilities Extracted:
timeFormatters.js,historyDataProcessors.js
Time Ranges:
['1h', '3h', '6h', '12h', '24h', '7d', '30d']Components Used:
UsageTimelineChart- Chart.js wrapper- Accordion sections with Framer Motion
Props:
{
item: object, // Entity object
hass: object, // Home Assistant connection
lang: string // Language code
}Utilities Used:
formatTime(timestamp, timeRange)- Time formattingformatDuration(minutes)- Duration formattinggenerateCategoryData(history)- Time-of-day grouping
External CSS:
src/components/tabs/HistoryTab.css (255 lines)
- Location:
src/components/tabs/ScheduleTab.jsx - Responsibility: Full scheduler UI for creating and managing timers
- Refactored: October 2025 (Previously 889 lines, -33%)
Key Features:
- Visual time picker (iOS-style)
- Weekday selection for recurring schedules
- Domain-specific action configuration
- Edit existing schedules
- Delete schedules
- Integration with
nielsfaber/scheduler-component
Refactoring Highlights:
- Service Actions: Extracted to
serviceActionBuilders.js - Edit State Loaders: Extracted to
editStateLoaders.js - Timer Names: Extracted to
timerNameGenerators.js - Components: Extracted 3 components
Sub-Components:
SchedulePickerTable.jsx(188 lines) - Main picker interfaceAddScheduleButton.jsx(27 lines) - Add schedule buttonScheduleActionButtons.jsx(57 lines) - Cancel/Confirm buttonsScheduleList.jsx- List of existing schedulesScheduleListItem.jsx- Individual schedule cardScheduleFilter.jsx- Filter by entity
Domain-Specific Settings:
- Climate:
ClimateScheduleSettings.jsx- HVAC mode & temperature - Cover: Position slider (0-100%)
- Standard: On/Off action selection
Props:
{
item: object, // Entity object
onServiceCall: function,// Service call handler
hass: object, // Home Assistant connection
lang: string // Language code
}Utilities Used:
createServiceAction(item, action, settings, position, t)- Build service callgenerateTimerName(item, action, settings, position, lang, t)- Create nameloadScheduleState(timer)- Load existing schedule for editing
Integration:
Requires nielsfaber/scheduler-component:
# configuration.yaml
scheduler:
# Component configuration- Location:
src/components/tabs/SettingsTab.jsx - Responsibility: In-card configuration interface
Key Features:
- Multi-tab settings interface
- General, Appearance, Privacy, About tabs
- Live preview of changes
- Import/Export configurations
Sub-Components:
GeneralSettingsTab.jsx- Language, basic settingsAppearanceSettingsTab.jsx- View mode, theme settingsPrivacySettingsTab.jsx- Excluded entity patternsAboutSettingsTab.jsx- Version, build info
Settings Managed:
{
language: string, // UI language (de, en, es, ...)
viewMode: string, // "grid" or "list"
excludedPatterns: string[], // Entity filter patterns
cardHeight: number // Fixed height or null
}Privacy Features:
- Live pattern preview
- Template library (hide sensors, unavailable, etc.)
- Import/Export as JSON
- Regex pattern support
These are common components used across various tabs.
The circular slider underwent major refactoring to improve maintainability and reusability. It's now split into multiple components and hooks.
- Location:
src/components/controls/CircularSlider.jsx - Responsibility: Main orchestration component for circular controls
- Refactored from: 957 lines → 525 lines (-45%)
Key Features:
- Physics-based spring animations (Framer Motion)
- Temperature-based color gradients for climate domain
- Power toggle with value restoration
- Responsive sizing (200px mobile → 280px desktop)
- Progress mode for read-only displays
- Smooth drag interactions (mouse & touch)
Props:
{
// Value Props
value: number, // Current value (0-100)
min: number, // Minimum value (default: 0)
max: number, // Maximum value (default: 100)
step: number, // Value increment (default: 1)
// Display Props
label: string, // Display label (e.g., "Brightness")
displayValue: string, // Optional display text override
subValue: string, // Optional sub-value text
unit: string, // Unit symbol (e.g., "%", "°C")
showUnit: boolean, // Show unit (default: false)
// Style Props
color: string, // Primary color (default: "#FFD700")
size: number, // Fixed size or null for auto
// Behavior Props
disabled: boolean, // Disable interaction
readOnly: boolean, // Read-only mode
progressMode: boolean, // Progress bar mode
hideSlider: boolean, // Hide drag handle
// Power Control
showPower: boolean, // Show power toggle
powerState: boolean, // Power on/off state
// Callbacks
onValueChange: function, // Value change callback
onPowerToggle: function, // Power toggle callback
// Domain
domain: string // Optional domain (e.g., "climate")
}Custom Hooks Used:
useSliderAnimation- Spring animations and value countinguseCircularDrag- Mouse and touch drag interactionsusePowerState- Power toggle with value restoration
Utility Functions:
valueToAngle- Convert value to SVG angleangleToValue- Convert SVG angle to valuegetTemperatureColor- Temperature-based color mappingcalculateResponsiveSize- Responsive size calculation
- Location:
src/components/controls/CircularSliderDisplay.jsx - Responsibility: Animated value display component
- Extracted from:
CircularSlider.jsx(October 2025)
Features:
- Animated value display with Framer Motion
- Optional sub-value display
- Unit display with positioning
- Label display with uppercase styling
- Horizontal scrolling for long text
- Responsive font sizing
Props:
{
value: number, // Current value
animatedValue: MotionValue, // Framer Motion animated value
displayValue: string, // Optional display text
subValue: string, // Optional sub-value
unit: string, // Unit symbol
showUnit: boolean, // Show unit
label: string, // Label text
size: number, // Slider size for responsive fonts
isDragging: boolean, // Drag state
valueRef: object, // Ref for value element
scrollOffset: number // Horizontal scroll offset
}Responsive Font Sizes:
- Main value:
clamp(26px, 12.2vw, 39px)(mobile) →clamp(38px, 18vw, 48px)(desktop) - Unit:
clamp(9px, 3.7vw, 13px)(mobile) →clamp(13px, 5.5vw, 19px)(desktop) - Sub-value:
clamp(10px, 4.4vw, 15px)(mobile) →clamp(13px, 5.5vw, 19px)(desktop) - Label:
clamp(8px, 3.2vw, 10px)(mobile) →clamp(10px, 4vw, 13px)(desktop)
- Location:
src/components/controls/PowerToggle.jsx - Responsibility: Reusable power switch component
- Extracted from:
CircularSlider.jsx(October 2025)
Features:
- Animated toggle switch with smooth transitions
- Responsive sizing based on slider size
- Disabled state support
- Pointer events management
- Glassmorphism design
Props:
{
isOn: boolean, // Power state
onChange: function, // Toggle callback
disabled: boolean, // Disabled state
size: number, // Slider size for positioning
show: boolean // Show/hide toggle
}Positioning:
- Desktop: Bottom left of slider
- Mobile: Bottom center of slider
Styling:
- Background:
rgba(255, 255, 255, 0.15)(off), accent color (on) - Size:
40px(mobile) →48px(desktop) - Border:
1px solid rgba(255, 255, 255, 0.2)
- Responsibility: Touch-friendly "roller" picker components
- Function:
- Time selection (hour & minute rollers)
- Multi-select picker (weekdays, modes)
- Smooth scrolling animations
- iOS-style design
Used in: ScheduleTab.jsx, ClimateScheduleSettings.jsx
- Responsibility: Climate control settings panel
- Function:
- HVAC mode selection (Heat, Cool, Auto, Off)
- Fan mode selection (Auto, Low, Medium, High)
- Preset mode selection (Home, Away, Sleep)
- Temperature display
- Responsibility: Reusable control button component
- Function:
- Glassmorphism styling
- Icon support
- Active/inactive states
- Hover animations
- Responsibility: Group of preset buttons for climate/fan controls
- Function:
- Horizontal button group
- Active state highlighting
- Quick preset selection
- Responsibility: Chat-style interface for experimental AI mode
- Function:
- Natural language input
- Chat message display
- Typing indicators
- Simulated AI responses (placeholder)
This is a modular architecture for managing core card features and enabling plugins.
-
SystemEntity.js- The base class that all system entities (like Settings, Marketplace) and all third-party plugins must extend. Defines the common interface (
onMount,onUnmount,toEntity).
- The base class that all system entities (like Settings, Marketplace) and all third-party plugins must extend. Defines the common interface (
-
registry.js- A singleton registry that is initialized by
DataProvider.jsx. - It automatically discovers all built-in system entities (from
/entities/) and provides methods (register,getEntity,getViewComponent) to manage them.
- A singleton registry that is initialized by
-
SystemEntityLoader.js- A utility for dynamically loading plugins from external sources like a URL, GitHub, or a user-uploaded ZIP file. It validates the plugin's manifest before registering it with the
registry.js.
- A utility for dynamically loading plugins from external sources like a URL, GitHub, or a user-uploaded ZIP file. It validates the plugin's manifest before registering it with the
-
appearanceConfig.js- A centralized design configuration file. It maps system entity domains (e.g.,
settings,pluginstore) to their specific colors, icons, and animations, ensuring a consistent look and feel.
- A centralized design configuration file. It maps system entity domains (e.g.,
-
DetailViewIntegration.jsx- A helper component used by
DetailView.jsxto lazy-load the UI (theviewComponent) of a system entity or plugin only when the user clicks on it.
- A helper component used by
-
System Entity Examples:
entities/settings/index.js: Defines the "Settings" entity.entities/pluginstore/index.js: Defines the "Plugin Store" entity.entities/all-schedules/index.js: Defines the global "All Schedules" viewer entity.
The card uses a comprehensive set of custom hooks for state management and reusable logic.
- Location:
src/hooks/useSliderAnimation.js - Purpose: Manages spring animations and value counting for circular slider
- Extracted from:
CircularSlider.jsx(October 2025)
Features:
- Physics-based spring animations (Framer Motion)
- Initial counting animation (0 → value on mount)
- Smooth value transitions
- Handle position transforms
Returns:
{
currentValue: number, // Current slider value (state)
setCurrentValue: function, // State setter
springValue: MotionValue, // Framer Motion spring value
countingValue: MotionValue, // Animated counting spring
handleAngle: MotionValue, // Transformed angle for handle position
hasAnimated: boolean // Flag for initial animation
}Spring Configuration:
springValue: {
stiffness: 280, // Fast, responsive motion
damping: 28, // Controlled bounce
mass: 0.8, // Light, quick response
restDelta: 0.001, // High precision
restSpeed: 0.01 // Minimum speed for "rest"
}
countingValue: {
stiffness: 100, // Slower counting animation
damping: 30,
restDelta: 0.01
}- Location:
src/hooks/useCircularDrag.js - Purpose: Manages mouse and touch drag interactions for circular slider
- Extracted from:
CircularSlider.jsx(October 2025)
Features:
- SVG coordinate transformation
- ViewBox scaling calculations
- Angle-to-value conversion with Math.atan2
- Value clamping and stepping
- Auto-power-on when dragging
- Global event listeners during drag
- Touch and mouse event support
- Prevent scroll during touch drag
Returns:
{
isDragging: boolean, // Current drag state
handleMouseDown: function, // Mouse event handler
handleTouchStart: function // Touch event handler
}Interaction Flow:
1. User clicks/touches slider
2. Calculate SVG coordinates relative to center
- Get bounding rect
- Scale by ViewBox ratio (200 / rect.width)
3. Convert coordinates to angle
- deltaX = scaledX - CENTER_X
- deltaY = scaledY - CENTER_Y
- angle = Math.atan2(deltaY, deltaX) * (180/π)
4. Convert angle to value
- Normalize angle (-90° to 270°)
- Map to value range (min to max)
5. Apply stepping and clamping
- Round to nearest step
- Clamp between min and max
6. Update spring value for smooth animation
7. Trigger callbacks (onValueChange)
- Location:
src/hooks/usePowerState.js - Purpose: Manages power toggle with value restoration
- Extracted from:
CircularSlider.jsx(October 2025)
Features:
- Local power state management
- Last value memory (default: 50%)
- Restore previous value on power-on
- Reset to 0 on power-off
- Integration with slider animation values
Returns:
{
localPowerState: boolean, // Current power state
setLocalPowerState: function, // State setter
lastSliderValue: number, // Last non-zero value
setLastSliderValue: function, // Setter for last value
handlePowerToggle: function // Toggle handler with restoration
}Power Toggle Behavior:
// Power Off
setCurrentValue(0);
springValue.set(0);
countingValue.set(0, false);
// Power On
setCurrentValue(lastSliderValue); // Restore saved value
springValue.set(lastSliderValue);
countingValue.set(lastSliderValue, false);
onValueChange(lastSliderValue);- Purpose: Fuzzy search logic with Fuse.js
- Features:
- Typo-tolerant searching
- Multi-field searching (name, area, domain)
- Configurable threshold
- Real-time results
- Purpose: UI state management for SearchField
- Features:
- Expansion state
- Search value
- Category selection
- Entity selection
- Purpose: Favorites management
- Features:
- Add/remove favorites
- Persistent storage (IndexedDB)
- Favorite status checking
- Purpose: Fetch and process entity history
- Features:
- History API integration
- Time range filtering
- Data caching
- Loading states
The card uses a comprehensive set of utility functions organized by purpose.
- Location:
src/utils/circularSliderGeometry.js - Purpose: Centralized geometry constants and calculations
Constants:
VIEW_BOX_SIZE = 200 // SVG viewBox dimensions
RADIUS = 70 // Circle radius
CENTER_X = 100 // Center X coordinate
CENTER_Y = 100 // Center Y coordinate
CIRCUMFERENCE = 439.8 // 2 * PI * RADIUSFunctions:
calculateResponsiveSize()
// Returns optimal slider size based on screen width
// Mobile (<600px): 200px
// Tablet (600-960px): 240px
// Desktop (>960px): 280px- Location:
src/utils/circularSliderTransforms.js - Purpose: Pure math functions for value/angle conversion
Functions:
valueToAngle(value, min, max)
// Converts value (0-100) to angle (-90° to 270°)
// -90° = top of circle (start position)
// Example: valueToAngle(50, 0, 100) → 90° (right)
angleToValue(angle, min, max)
// Converts angle to value with range normalization
// Example: angleToValue(90, 0, 100) → 50
getProgressOffset(currentValue, min, max, circumference)
// Calculates stroke-dashoffset for progress mode
// Used for animated arc drawing- Location:
src/utils/temperatureColors.js - Purpose: Temperature-based color mapping for climate domain
Color Gradient:
< 16°C → #0288D1 (Blue - Cold)
16-18°C → #03A9F4 (Light Blue)
18-20°C → #00BCD4 (Cyan)
20-22°C → #4CAF50 (Green - Comfortable)
22-24°C → #8BC34A (Light Green)
24-26°C → #CDDC39 (Yellow Green)
26-28°C → #FF9800 (Orange)
> 28°C → #F44336 (Red - Hot)Function:
getTemperatureColor(temperature, unit)
// Supports both Celsius and Fahrenheit
// Automatic °F → °C conversion
// Smooth color transitions- Location:
src/utils/serviceActionBuilders.js - Purpose: Build Home Assistant service call objects
Function:
createServiceAction(item, actionValue, climateSettings, coverPosition, t)
// Climate Example:
{
service: "climate.set_temperature",
entity_id: "climate.living_room",
service_data: {
temperature: 22,
hvac_mode: "heat"
}
}
// Cover Example:
{
service: "cover.set_cover_position",
entity_id: "cover.bedroom_blinds",
service_data: { position: 50 }
}
// Standard Example:
{
service: "light.turn_on",
entity_id: "light.bedroom"
}- Location:
src/utils/editStateLoaders.js - Purpose: Parse and load edit state from existing timers/schedules
Functions:
loadCoverEditState(timer)- Parse cover timer configurationloadClimateEditState(timer)- Parse climate timer with HVAC modeloadStandardEditState(timer)- Parse on/off timerloadTimerState(timer)- Load timer details (time, weekdays, enabled)loadScheduleState(timer)- Complete schedule state loader
- Location:
src/utils/timerNameGenerators.js - Purpose: Generate user-friendly timer names
Function:
generateTimerName(item, actionValue, climateSettings, coverPosition, lang, t)
// Examples:
"Bedroom Light / Turn On"
"Living Room Thermostat / Heat / 22°C"
"Bedroom Blinds / Position 50%"Helper:
getHvacModeLabel(mode, lang)
// Returns localized HVAC mode labels
// "heat" → "Heizen" (de) / "Heat" (en) / "Chauffage" (fr)- Location:
src/utils/timeFormatters.js - Purpose: Time and duration formatting
Functions:
formatTime(timestamp, timeRange)
// Returns formatted time string based on range
// ≤24h: "14:30" (time only)
// >24h: "28.10 14:30" (date + time)
formatDuration(minutes)
// Returns human-readable duration
// Examples: "5 min", "2h 30min", "3d 5h"
getHoursFromTimeframe(timeRange)
// Converts time range string to hours
// "24h" → 24, "7d" → 168- Location:
src/utils/historyDataProcessors.js - Purpose: Process history events into time-of-day categories
Function:
generateCategoryData(history)
// Processes history array into categories:
// {
// morning: [], // 06:00 - 12:00
// day: [], // 12:00 - 18:00
// evening: [], // 18:00 - 22:00
// night: [] // 22:00 - 06:00
// }- Location:
src/utils/animationVariants.js - Purpose: Centralized Framer Motion animation variants
Exports:
circularSliderContainerVariants- Container animationscircularSliderSvgVariants- SVG element animationscircularSliderHandleVariants- Drag handle animationscircularSliderValueVariants- Value display animationscircularSliderLabelVariants- Label animationsdeviceCardVariants- Device card animationsdeviceListItemVariants- List item animationsdeviceGridItemVariants- Grid item animationsdetailPanelVariants- Detail panel animationseasings- Custom easing functionsdurations- Standard animation durations
- Location:
src/utils/animations/accordionAnimations.js - Purpose: Accordion-specific animations for HistoryTab
- Extracted from:
HistoryTab.jsx(October 2025)
Variants:
accordionVariants = {
collapsed: {
height: 0,
opacity: 0,
transition: {
height: { duration: 0.4, ease: [0.16, 1, 0.3, 1] },
opacity: { duration: 0.3, ease: 'easeOut' }
}
},
expanded: {
height: "auto",
opacity: 1,
transition: {
height: { duration: 0.4, ease: [0.16, 1, 0.3, 1] },
opacity: { duration: 0.3, delay: 0.1, ease: 'easeIn' }
}
}
}- Purpose: Domain-specific logic and helpers
- Functions:
getIconForDomain(domain)- Default icon per domaingetBackgroundStyle(domain)- Background gradient per domaingetDomainDisplayName(domain, lang)- Localized domain names
- Purpose: Entity state formatting and calculations
- Functions:
getStateText(entity, lang)- Formatted state textgetStateDuration(entity)- Time since last changegetQuickStats(entity)- Quick stat summary
- Purpose: Entity manipulation and queries
- Functions:
filterEntitiesByDomain(entities, domain)- Filter by domainsortEntitiesByName(entities)- Alphabetical sortgroupEntitiesByArea(entities)- Group by room
- Purpose: Icon selection logic
- Functions:
getEntityIcon(entity)- Determine icon based on state/attributesgetDeviceClassIcon(deviceClass)- Icon for device class
-
Icon System:
iconRegistry.js: A large mapping file that defines which icon component to use based on an entity'sdomain,state, andattributes(e.g.,device_class).AnimatedDeviceIcons.jsx: A React component that takes an entity, queries theiconRegistry.js, and renders the correct, state-aware animated icon (e.g., a spinningWashingMachineOnicon or a staticWashingMachineOfficon).
-
Chart System:
ChartComponents.jsx: A wrapper for theChart.jslibrary, providing specific components likeUsageTimelineChartfor theHistoryTab.jsx.chartConfig.js: The central configuration forChart.js. It uses tree-shaking (selective imports) to drastically reduce the bundle size by only importing the controllers, scales, and elements that are actually used.
-
Translation System:
src/utils/translations/index.js: The main entry point for i18n. It loads all language files (de, en, es, fr, etc.) and exports theuseTranslationhook.src/utils/translations/helpers.js: Provides utility functions for formatting sensor values, device states, and relative time based on the selected language.
-
Notification System:
toastNotification.js: A global system for showing non-intrusive "toast" notifications for success or error messages.toast.css: The styling for the toast notifications.
The Fast Search Card underwent major refactoring to improve maintainability, testability, and reusability.
Before: 889 lines After: 596 lines Reduction: -33% (-293 lines)
Extractions:
- Utilities: 3 files (161 lines)
serviceActionBuilders.js(40 lines) - Service action creationeditStateLoaders.js(86 lines) - Edit state parsingtimerNameGenerators.js(35 lines) - Timer name generation
- Components: 3 files (272 lines)
AddScheduleButton.jsx(27 lines)ScheduleActionButtons.jsx(57 lines)SchedulePickerTable.jsx(188 lines)
Before: 795 lines After: 416 lines Reduction: -48% (-379 lines)
Extractions:
- CSS: 1 file (255 lines)
HistoryTab.css- All inline styles
- Utilities: 3 files (148 lines)
accordionAnimations.js(65 lines) - Framer Motion variantstimeFormatters.js(44 lines) - Time/duration formattinghistoryDataProcessors.js(39 lines) - Data processing
Before: 957 lines After: 525 lines Reduction: -45% (-432 lines)
Extractions:
- Utilities: 3 files (116 lines)
temperatureColors.js(29 lines) - Color mappingcircularSliderGeometry.js(41 lines) - Geometry constantscircularSliderTransforms.js(46 lines) - Transform functions
- Components: 2 files (355 lines)
PowerToggle.jsx(153 lines) - Reusable power switchCircularSliderDisplay.jsx(202 lines) - Value display
- Hooks: 3 files (260 lines)
useSliderAnimation.js(73 lines) - Spring animationsuseCircularDrag.js(139 lines) - Drag interactionsusePowerState.js(48 lines) - Power state management
Files Refactored: 3 major components New Files Created: 18 modular files Total Lines Reduced: -1,104 lines (-42% average)
New File Structure:
src/
├── components/
│ ├── controls/
│ │ ├── CircularSlider.jsx (525 lines) ✓
│ │ ├── CircularSliderDisplay.jsx (202 lines) ✓ NEW
│ │ └── PowerToggle.jsx (153 lines) ✓ NEW
│ └── tabs/
│ ├── ScheduleTab.jsx (596 lines) ✓
│ │ └── components/
│ │ ├── AddScheduleButton.jsx ✓ NEW
│ │ ├── ScheduleActionButtons.jsx ✓ NEW
│ │ └── SchedulePickerTable.jsx ✓ NEW
│ └── HistoryTab.jsx (416 lines) ✓
│ └── HistoryTab.css ✓ NEW
├── hooks/
│ ├── useSliderAnimation.js ✓ NEW
│ ├── useCircularDrag.js ✓ NEW
│ └── usePowerState.js ✓ NEW
└── utils/
├── temperatureColors.js ✓ NEW
├── circularSliderGeometry.js ✓ NEW
├── circularSliderTransforms.js ✓ NEW
├── timeFormatters.js ✓ NEW
├── historyDataProcessors.js ✓ NEW
├── serviceActionBuilders.js ✓ NEW
├── editStateLoaders.js ✓ NEW
├── timerNameGenerators.js ✓ NEW
└── animations/
└── accordionAnimations.js ✓ NEW
- Smaller Files: Components under 600 lines are easier to understand and modify
- Clear Separation: Utilities, components, and hooks have distinct responsibilities
- Single Responsibility: Each file has one clear purpose
- Better Organization: Logical file structure and naming
- Pure Functions: Utilities can be unit tested in isolation
- Isolated Hooks: Custom hooks can be tested independently
- Component Isolation: Smaller components are easier to test
- Mock-friendly: Clear dependencies make mocking straightforward
- Shared Components: PowerToggle, CircularSliderDisplay can be used anywhere
- Portable Hooks: useCircularDrag can be adapted for other circular controls
- Generic Utilities: timeFormatters, temperatureColors work in any context
- Consistent Patterns: Same patterns can be applied to other large files
- CSS Extraction: Separate CSS files enable better browser caching
- Tree Shaking: Smaller, focused files improve tree-shaking efficiency
- Bundle Optimization: Clearer imports help bundler optimization
- Reduced Overhead: Less inline styling reduces HTML size
Break large components into smaller, focused components:
// Before: CircularSlider (957 lines)
// After: CircularSlider + PowerToggle + CircularSliderDisplayMove pure functions to separate utility files:
// Before: Inline functions in component
// After: circularSliderTransforms.js with pure functionsEncapsulate complex state logic in custom hooks:
// Before: useState, useEffect in component
// After: useSliderAnimation, useCircularDrag, usePowerStateMove inline styles to separate CSS files:
// Before: <style>{`...`}</style> in component (255 lines)
// After: HistoryTab.cssCentralize magic numbers and constants:
// Before: radius = 70 (scattered in code)
// After: RADIUS exported from circularSliderGeometry.jsLarge Files Remaining:
SearchField.jsx- Could extract sub-componentsUniversalControlsTab.jsx- Domain-specific controls could be separatedSettingsTab.jsx- Settings sub-panels could be extractedDataProvider.jsx- Cache logic could be extracted
Recommended Approach:
- Identify large files (>500 lines)
- Analyze for extractable utilities, components, hooks
- Create new files with clear naming
- Update imports in main component
- Test thoroughly
- Document changes
The card follows standard React/Preact patterns:
// Parent passes props down
<CircularSlider
value={brightness}
onValueChange={handleBrightnessChange}
/>
// Child emits events up via callbacks
const handleDrag = (newValue) => {
onValueChange(newValue); // Bubble up
};Global state uses React Context:
// DataProvider exposes contexts
const [entities] = useEntities();
const [favorites, toggleFavorite] = useFavorites();
const [settings, updateSettings] = useSettings();Home Assistant calls go through service layer:
// Component → Handler → Service
onServiceCall(domain, service, data);
↓
handleServiceCall(domain, service, data);
↓
haService.callService(domain, service, data);Complex logic encapsulated in custom hooks:
// Reusable across components
const { isDragging, handleMouseDown } = useCircularDrag({
svgRef,
angleToValue,
onValueChange
});Utilities (High Priority):
// Test pure functions
describe('valueToAngle', () => {
it('converts 0 to -90°', () => {
expect(valueToAngle(0, 0, 100)).toBe(-90);
});
it('converts 50 to 90°', () => {
expect(valueToAngle(50, 0, 100)).toBe(90);
});
});Custom Hooks:
// Test hook behavior
import { renderHook, act } from '@testing-library/preact-hooks';
describe('usePowerState', () => {
it('restores last value on power on', () => {
const { result } = renderHook(() => usePowerState(false, 75));
act(() => {
result.current.handlePowerToggle({ target: { checked: true }});
});
expect(result.current.lastSliderValue).toBe(75);
});
});Component Interactions:
// Test component integration
describe('CircularSlider', () => {
it('updates value on drag', () => {
const onValueChange = jest.fn();
render(<CircularSlider value={50} onValueChange={onValueChange} />);
// Simulate drag
fireEvent.mouseDown(screen.getByRole('slider'));
fireEvent.mouseMove(screen.getByRole('slider'), { clientX: 200, clientY: 100 });
expect(onValueChange).toHaveBeenCalled();
});
});User Flows:
- Search for entity
- Open DetailView
- Adjust circular slider
- Create schedule
- View history
The Fast Search Card uses a modular, maintainable component architecture with clear separation of concerns. The October 2025 refactoring significantly improved code quality while maintaining all functionality. The card is well-positioned for future enhancements and community contributions.