- Add /api/v1/system/shutdown endpoint that triggers clean uvicorn exit
- Persist all 15 stores to disk during shutdown via _save_all_stores()
- Add force parameter to BaseJsonStore._save() to bypass restore freeze
- Restart script now requests graceful shutdown via API (15s timeout),
falls back to force-kill only if server doesn't exit in time
- Broadcast server_restarting event over WebSocket before shutdown
- Frontend shows "Server restarting..." overlay instantly on WS event,
replacing the old dynamically-created overlay from settings.ts
- Add server_ref module to share uvicorn Server + TrayManager refs
- Add i18n keys for restart overlay (en/ru/zh)
Set WLED_RESTART=1 env var on tray restart so __main__.py skips
opening a new browser tab — the user already has one open.
Add important TODO item to eliminate WLED naming throughout the app
(package rename, i18n, build scripts, etc.).
Populate <select> <option> elements from gradient entities before
creating IconSelect — the trigger display needs matching options to
sync correctly. Add _syncSelectOptions helper used by all three
palette pickers (gradient, effect, audio).
Both actions show a confirmation dialog before proceeding.
Restart uses os.execv to re-launch the process in-place.
Shutdown stops the server and exits the tray.
Replace the gradient stop editor (canvas, markers, stop list) in the
CSS editor gradient section with a simple gradient entity selector.
Gradients are now created/edited exclusively in the Gradients tab.
Fix effect and audio palette pickers to populate from gradient entities
dynamically instead of hardcoded HTML options.
Unify all gradient/palette pickers via _buildGradientEntityItems().
Also: rename "None (use own speed)" → "None (no sync)" for sync clocks.
Add i18n keys for gradient selector and missing error messages.
Fix ~68 pre-existing strict null errors across 13 feature modules.
Add non-null assertions for DOM element lookups, null coalescing for
optional values, and type guards for nullable properties. Zero tsc
errors now with --noEmit.
Add full gradient editor modal with name, description, visual stop
editor, tags, and dirty checking. Gradient editor now supports ID
prefix to avoid DOM conflicts between CSS editor and standalone modal.
Fix color picker popover clipped by template-card overflow:hidden.
Fix gradient canvas not sizing correctly in standalone modal.
Add gradient_id field to color strip sources for referencing reusable
gradient entities. Improve audio stream processing and effect stream
with new parameters.
Add pystray-based system tray icon with Open UI / Restart / Quit
actions. Add __main__.py for `python -m wled_controller` support.
Update start-hidden.vbs with embedded Python fallback for both
installed and dev environments.
New notification effects:
- Chase: light bounces across strip with Gaussian glow tail
- Gradient flash: bright center fades to edges with exponential decay
Queue priority: notifications with color_override get high priority and
interrupt the current effect.
Also fixes transient preview for notification sources — adds WebSocket
"fire" command so inline preview works without a saved source, plus
auto-fires on preview open so the effect is visible immediately.
Effects: add 7 new procedural effects (rain, comet, bouncing ball, fireworks,
sparkle rain, lava lamp, wave interference) and custom palette support via
user-defined [[pos,R,G,B],...] stops.
Gradient: add easing functions (linear, ease_in_out, step, cubic) for stop
interpolation, plus noise_perturb and hue_rotate animation types.
Daylight: add longitude field and NOAA solar equations for accurate
sunrise/sunset based on latitude, longitude, and day of year.
Candlelight: add wind simulation (correlated gusts), candle type presets
(taper/votive/bonfire), and wax drip effect with localized brightness dips.
Also fixes editor preview to include all new fields for inline LED test.
Three new processing template filters for both picture and color strip sources:
- HSL Shift: hue rotation (0-359°) + lightness multiplier via vectorized RGB↔HSL
- Contrast: LUT-based contrast adjustment around mid-gray (0.0-3.0)
- Temporal Blur: exponential moving average across frames for motion smoothing
Add 5 WebGL shader background effects (Aurora, Plasma, Digital Rain,
Starfield, Warp Tunnel) via a new bg-shaders.ts engine that shares
a dedicated canvas. Add 5 style presets (Sakura, Ocean, Copper, Vapor,
Monolith) with distinctive font pairings. Remove CSS particles effect
in favor of shader-based alternatives. Fix dot grid visibility and
tune all shader intensities for subtle ambient appearance.
Add style presets (font + color combinations) and background effect
presets as a new Appearance tab in the settings modal. Style presets
include Default, Midnight, Ember, Arctic, Terminal, and Neon — each
with curated dark/light theme colors and Google Font pairings.
Background effects (Dot Grid, Gradient Mesh, Scanlines, Particles)
use a dedicated overlay div alongside the existing WebGL Noise Field.
All choices persist to localStorage and restore on page load.
Scene name and fallback scene in automation cards are now clickable,
navigating to the corresponding scene preset card. Also renders the
deactivation mode label which was previously set but never displayed.
Auth is now optional: when `auth.api_keys` is empty, all endpoints are
open (no login screen, no Bearer tokens). Health endpoint reports
`auth_required` so the frontend knows which mode to use.
Backup/restore fixes:
- Auto-backup uses atomic writes (was `write_text`, risked corruption)
- Startup backup skipped if recent backup exists (<5 min cooldown),
preventing rapid restarts from rotating out good backups
- Restore rejects all-empty backups to prevent accidental data wipes
- Store saves frozen after restore to prevent stale in-memory data
from overwriting freshly-restored files before restart completes
- Missing stores during restore logged as warnings
- STORE_MAP completeness verified at startup against StorageConfig
Camera engine and video stream support now gracefully degrade when
opencv-python-headless is not installed. The app starts fine without
it — camera engine simply doesn't register and video streams raise
a clear ImportError with install instructions.
Saves ~45MB for users who don't need camera/video capture.
- Notification history: replace text buttons with icon buttons, use modal-footer for proper positioning
- CSS test: reject 0-LED picture sources with clear error message, show WS close reason in UI
- Calibration: distribute LEDs by aspect ratio (16:9 default) instead of evenly across edges
- Value source schedule: replace native time input with custom HH:MM picker matching automation style
- Remove "No ... yet" empty state labels from all CardSection instances
- Replace winsdk (~35MB) with winrt packages (~2.5MB) for OS notification
listener. API is identical, 93% size reduction.
- Replace wmi (~3-5MB) with ctypes for monitor names (EnumDisplayDevicesW)
and camera names (SetupAPI). Zero external dependency.
- Migrate cv2.resize/imencode/LUT to Pillow/numpy in 5 files (filters,
preview helpers, kc_target_processor). OpenCV only needed for camera
and video stream now.
- Fix DefWindowProcW ctypes overflow on 64-bit Python (pre-existing bug
in platform_detector display power listener).
- Fix openLightbox import in streams-capture-templates.ts (was using
broken window cast instead of direct import).
- Add mandatory data migration policy to CLAUDE.md after silent data
loss incident from storage file rename without migration.
- Move startup banner into main.py so it shows the actual configured
port instead of a hardcoded 8080 in the launcher scripts
- Wrap tkinter import in try/except so embedded Python (which lacks
tkinter) logs a warning instead of crashing the overlay thread
Tests that call get_available_displays() or capture_display() require
a real display ($DISPLAY on Linux, always available on Windows/macOS).
Mark them with @requires_display to skip on headless CI instead of
failing with "$DISPLAY not set".
Affects: test_screen_capture.py (4 tests), test_api.py (1 test).
- Lazy-import tkinter in screen_overlay.py (TYPE_CHECKING + runtime
import) so the module loads on headless Linux CI without libtk8.6
- Fix test_wled_client.py: mock all HTTP endpoints with respx (info,
cfg, state) instead of hitting real network
- Fix test_calibration.py: assert numpy array shape instead of tuple
- Fix test_processor_manager.py: update to current API (async
remove_device, dict settings, no update_calibration)
- Fix test_screen_capture.py: get_edge_segments allows more segments
than pixels
341 tests passing, 0 failures.
screen_overlay.py imported tkinter at module level, which cascaded
through processor_manager → every test touching the app on CI where
libtk8.6.so is unavailable. Move to TYPE_CHECKING + runtime lazy
import so the overlay module loads cleanly on headless systems.
Also fix test_processor_manager.py to use ProcessorDependencies().
- Refactor process-picker.ts into generic NamePalette with two concrete
instances: ProcessPalette (running processes) and NotificationAppPalette
(OS notification history apps)
- Notification color strip app colors and filter list now use
NotificationAppPalette (shows display names like "Telegram" instead of
process names like "telegram.exe")
- Fix case-insensitive matching for app_colors and app_filter_list in
notification_stream.py
- Compact browse/remove buttons in notification app color rows with
proper search icon
- Remove old inline process-picker HTML/CSS (replaced by palette overlay)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Server: send initial frame immediately after metadata for api_input
sources so the client gets the current buffer (fallback color if
inactive) instead of never receiving a frame when push_generation
stays at 0.
Frontend: clear all test modal canvases on open to prevent stale
pixels from previous sessions. Reset FPS timestamps after metadata
so the initial bootstrap frame isn't counted in the FPS chart.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the body position:fixed hack with overflow:hidden on html element,
which works cleanly with scrollbar-gutter:stable to prevent layout shift.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Rename all 54 .js files to .ts, update esbuild entry point
- Add tsconfig.json, TypeScript devDependency, typecheck script
- Create types.ts with 25+ interfaces matching backend Pydantic schemas
(Device, OutputTarget, ColorStripSource, PatternTemplate, ValueSource,
AudioSource, PictureSource, ScenePreset, SyncClock, Automation, etc.)
- Make DataCache generic (DataCache<T>) with typed state instances
- Type all state variables in state.ts with proper entity types
- Type all create*Card functions with proper entity interfaces
- Type all function parameters and return types across all 54 files
- Type core component constructors (CardSection, IconSelect, EntitySelect,
FilterList, TagInput, TreeNav, Modal) with exported option interfaces
- Add comprehensive global.d.ts for window function declarations
- Type fetchWithAuth with FetchAuthOpts interface
- Remove all (window as any) casts in favor of global.d.ts declarations
- Zero tsc errors, esbuild bundle unchanged
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Bulk selection mode: Ctrl+Click or toggle button to enter, Escape to exit
- Shift+Click for range select, bottom toolbar with SVG icon action buttons
- All CardSections wired with bulk actions: Delete everywhere,
Start/Stop for targets, Enable/Disable for automations
- Remove expand/collapse all buttons (no collapsible sections remain)
- Fix graph node color picker overlay persisting after outside click
- Add Icons section to frontend.md conventions
- Add trash2, listChecks, circleOff icons to icon system
- Backend: processing loop performance improvements (monotonic timestamps,
deque-based FPS tracking)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously modals showed generic "Failed to add/save" messages. Now they
extract and display the actual error detail from the API response (e.g.,
"URL is required", "Name already exists"). Handles Pydantic validation
arrays by joining msg fields.
Fixed in 8 files: device-discovery, devices, calibration,
advanced-calibration, scene-presets, automations, command-palette.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- KC test WS now acquires from LiveStreamManager instead of creating its
own DXGI duplicator, eliminating capture contention with running LED targets
- Tree-nav refactored to compact dropdown on click with outside-click dismiss
(closes on click outside the trigger+panel, not just outside the container)
- KC target card badge (e.g. "Daylight Cycle") no longer wastes empty space
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use { behavior: 'instant' } in unlockBody scrollTo to override
CSS scroll-behavior: smooth on html element
- Use { preventScroll: true } on focus() restore in Modal.forceClose
- Add overflow-y: scroll to body.modal-open to prevent scrollbar shift
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix CSS specificity: .dimmed now overrides .graph-edge-active opacity/filter
- Add data-from/data-to to flow dot groups so they can be dimmed per-edge
- Dim flow dots on non-chain edges in highlightChain(), restore on clear
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Settings modal split into 3 tabs: General, Backup, MQTT
- Log viewer moved to full-screen overlay with compact toolbar
- External URL setting: API endpoints + UI for configuring server domain
used in webhook/WS URLs instead of auto-detected local IP
- Sources tab tree restructured: Picture Source (Screen Capture/Static/
Processed sub-groups), Color Strip, Audio, Utility
- TreeNav extended to support nested groups (3-level tree)
- Audio tab split into Sources and Templates sub-tabs
- Fix audio template test: device picker now filters by engine type
(was showing WASAPI indices for sounddevice templates)
- Audio template test device picker disabled during active test
- Rename "Input Source" to "Source" in CSS test preview (en/ru/zh)
- Fix i18n: log filter/level items deferred to avoid stale t() calls
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Graph editor:
- Floating FPS tooltip on hover over running output_target nodes (300ms delay)
- Shows errors, uptime, and FPS sparkline seeded from server metrics history
- Tooltip positioned below node with fade-in/out animation
- Uses pointerover/pointerout with relatedTarget check to prevent flicker
- Fixed-width tooltip (200px) with monospace values to prevent layout shift
- Node titles show full names (removed truncate), no native SVG <title> tooltips
Documentation:
- Added duration/numeric formatting conventions to contexts/frontend.md
- Added node hover tooltip docs to contexts/graph-editor.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New features:
- Override composite blend mode: per-pixel alpha from brightness
(black=transparent, bright=opaque). Ideal for API input over effects.
- API input test preview FPS chart uses shared createFpsSparkline
(same look as target card charts)
Fixes:
- Fix api_input source not surviving server restart: from_dict was
still passing removed led_count field to constructor
- Fix composite layer brightness/processing selectors not aligned:
labels get fixed width, selects fill remaining space
- Fix CSPT input selector showing in non-CSPT CSS test mode
- Fix test modal LED/FPS controls showing for api_input sources
- Server only sends test WS frames when api_input push_generation changes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
API Input CSS rework:
- Remove led_count field from ApiInputColorStripSource (always auto-sizes)
- Add segment-based payload: solid, per_pixel, gradient modes
- Segments applied in order (last wins on overlap), auto-grow buffer
- Backward compatible: legacy {"colors": [...]} still works
- Pydantic validation: mode-specific field requirements
Test preview:
- Enable test preview button on api_input cards
- Hide LED/FPS controls for api_input (sender controls those)
- Show input source selector for all CSS tests (preselected)
- FPS sparkline chart using shared createFpsSparkline (same as target cards)
- Server only sends frames when push_generation changes (no idle frames)
HAOS integration:
- New light.py: ApiInputLight entity per api_input source (RGB + brightness)
- turn_on pushes solid segment, turn_off pushes fallback color
- Register wled_screen_controller.set_leds service for arbitrary segments
- New services.yaml with field definitions
- Coordinator: push_colors() and push_segments() methods
- Platform.LIGHT added to platforms list
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend: send border_width in WS metadata and frame_dims (width, height)
as a separate JSON message on first JPEG frame.
Frontend: render semi-transparent green overlay rectangles on each active
edge showing the sampling region depth, plus a small px label. Overlays
are proportionally sized based on border_width relative to frame dimensions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Key Colors test:
- New WS endpoint for live KC target test streaming (replaces REST polling)
- Auto-connect on lightbox open, auto-disconnect on close
- Uses same FPS/preview_width as CSS source test (no separate controls)
- Removed FPS selector, start/stop toggle, and updateAutoRefreshButton
Device cards:
- Fix full re-render on every poll caused by relative "Last seen" time in HTML
- Last seen label now patched in-place via data attribute (like FPS metrics)
- Remove overlay visualization button from LED target cards
Sync clocks:
- Fix card not updating start/stop icon: invalidate cache before reload
Other:
- Tab indicator respects bg-anim toggle (hidden when dynamic background off)
- Camera backend icon grid uses SVG icons instead of emoji
- Frontend context rule: no emoji in IconSelect items
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
UI fixes:
- Automation card badge moved to flex layout — title truncates, badge stays visible
- Automation condition pills max-width increased to 280px
- Dashboard crosslinks fixed: pass correct sub-tab key (led-targets not led)
- navigateToCard only skips data load when tab already has cards in DOM
- Badge gets white-space:nowrap + flex-shrink:0 to prevent wrapping
New features:
- formatCompact() for large frame/error counters (1.2M, 45.2K) with hover title
- Log filter and log level selects replaced with IconSelect grids
- OpenRGB devices now support software brightness control
OpenRGB improvements:
- Added brightness_control capability (uses software brightness fallback)
- Change-threshold dedup compares raw pixels before brightness scaling
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>