## [0.54.0] - 2026-09-06

### Added
- **audio:** cap output with audio.volume_max and drop the per-slider one
- **bt:** stop the radio on every switch to the BT source
- **telegram:** move polling into settings and name a hook by its door
- **telegram:** read commands on a schedule a rule owns
- **telegram:** send a message from a rule through a bot
- **rules:** name triggers by their source and add a webhook trigger
- **web:** accept an action string over plain HTTP, not only MQTT
- **www:** prioritize flows in rules page layout
- **rules:** open with ready-made rules instead of trigger types
- **rules:** add a sunrise/sunset trigger and a UDP action, and pack the payloads
- **web:** name macros and I/O slots in the action lists, and group them
- **web:** name the rule behind a macro on the I/O and remote pages
- **web:** back up and restore the rules file from the editor
- **web:** serve today's schedule at /api/rules/today
- **web:** show the day's schedule on the rules page
- **web:** group the canvas by flow when All is showing
- **ui:** show today's rules and calendar entries on one screen
- **rules:** tell the UI when today's schedule moves, instead of polling for it
- **rules:** answer what is scheduled for today, and let the bell count it again
- **web:** put every task stack in the copied diagnostics report
- **web:** add a storage check for what rules name on the card
- **ui:** add a per-screen toggle for the volume overlay
- **rules:** add a table view and per-flow enabling
- **rules:** group rules into flows
- **rules:** show how to start a macro rule over MQTT
- **rules:** show how to start a macro rule over MQTT
- **mqtt:** publish brightness, active source and the I/O slots
- **mqtt:** run actions from cmd/action and publish from a rule
- **rules:** let an event block play a spoken clip
- **rules:** add an event block that shows a notification
- **rules:** let a time trigger run on a date, monthly or yearly
- **rules:** show the full action text on the block and the full path in the editor
- **setup:** name the pins held by I/O slots
- **rules:** show the GPIO number on control blocks
- **rules:** play a station by name and an SD folder from a rule
- **rules:** pick a station by name in a play action
- **rules:** list the parameterised commands next to the field that takes them
- **rules:** draw physical controls as blocks and drop the controls table
- **rules:** draw physical controls as blocks and drop the controls table
- **rules:** list every bound control on the trigger block
- **rules:** bind a control to a rule from its trigger block
- **rules:** name remote keys from the rules page
- **rules:** manage buttons, remote keys and inputs from the rules page
- **rules:** add a block editor for rules with a live trace
- **rules:** add a rule engine with time, state and macro triggers
- **io:** add general-purpose GPIO slots with a web panel
- **vu:** add a per-screen sensitivity knob for the VU meters
- **vu:** add LED-style segments to the stereo bar VU
- **ui:** put the screensaver up from a button or IR key
- **ui:** add a shared colour override for the clock widget
- **eq:** add a colour override for the value label
- **ui:** add shared colour overrides for the weather, mode and event widgets

### Changed
- **web:** name the rule sources the way the I/O page does
- **main:** allocate every cJSON node from PSRAM, not internal RAM
- **web:** cap concurrent SD listings on the layout page
- **stacks:** move seven task stacks off internal RAM into PSRAM
- **diag:** take the CPU dump's task snapshot from PSRAM
- **weather:** keep the fetch buffers out of internal RAM
- **web:** put the HTTP socket pool back to seven after measuring six
- **web:** trim the HTTP socket pool to six after measuring the peak
- **web:** take the diag task buffers from PSRAM, not the pool they report
- **weather:** start the fetch task only while weather is enabled
- **stacks:** size the action runner, ESP-NOW and LVGL to measurements
- **rules:** load the page one request at a time
- **ws:** build the state frame on the broadcast task, not on the caller feat(diag): report every task stack, not the tightest sixteen perf(rules): make the live trace opt-in and off by default
- **ws:** build the state frame on the broadcast task, not on the caller
- **events:** reduce the store to the phone-calendar mirror
- **display:** drop the dim schedule, which is three rules now
- **events:** drop timed playback, which is a rule now
- **www:** share one SD path picker, and resolve sd_play like events do
- **web:** move the I/O slot pins from setup onto the I/O page
- **pins:** name every bindable pin slotN, with a one-time migration
- **io:** drop boot=restore and the settings write it dragged along
- **io:** merge buttons into the slot table
- **rules:** move the trace above the canvas and the block editor into a popover
- **mem:** run control actions on one shared task and size stacks from measurements
- **vu:** flip the VU sensitivity knob so higher means taller bars
- **ui:** move the shared widget colours to per-screen overrides

### Fixed
- **web:** stop every settings poll behind a hidden tab
- **telegram:** name the 409 conflict and read the whole reply
- **rules:** report the count from the reload, not from the table it replaces
- **stacks:** raise the action runner to 8192, MQTT left it 1184 B
- **settings:** never restore a saved volume of zero at boot
- **web:** make a flow read as one card, not a bar above loose lanes
- **rules:** name the state fields after what they actually test
- **stacks:** give the action runner and the I/O sweep room they actually need
- **rules:** keep a rule's name in step with its notification title
- **rules:** let a boot rule wait for the clock its condition needs play a notification clip from anywhere on the card
- **web:** quiet the 404 for a config file that has not been created yet
- **events:** stop the notification clip when the notification is dismissed
- **rules:** stop rebuilding the canvas while a field is being typed into
- **rules:** keep fire state across a reload, and seed state triggers without firing
- **sd:** reject SD paths that cannot resolve, and say when one falls back
- **rules:** don't hand a new macro rule a number a control already uses
- **setup:** count folded-away slot pins as taken
- **www:** style the I/O page save button
- **config:** raise the esp_timer task stack, which writes the settings file
- **io:** size the slot task for the settings-save path it can reach
- **buttons:** keep the list-screen re-mapping to keys, not to every action
- **rules:** start a new shelf row for each kind of control
- **rules:** box a control's press and hold together on the shelf
- **rules:** let an action switch to a custom command the catalogue also lists
- **rules:** address the trigger block by the key it is drawn with
- **rules:** drop the rule id from the lane header
- **rules:** name blocks after the buttons that add them
- **rules:** show I/O slot names in actions and hide slots that are not wired
- **web:** stop the layout editor clamping number fields mid-typing
- **eq:** centre-anchor the value label so it holds still as the text grows
- **sdcard:** skip the mount worker when the pin map has no SD lines
