- save_state_(): split into two clear branches instead of conditional
FanTraits construction trick
- CopyFan: copy source preset modes once in setup() instead of on
every get_traits() call
1. save_state_() bound a reference to supported_preset_modes() from
a temporary FanTraits returned by get_traits(). When the compat
path fired, the temporary was destroyed and the reference dangled.
Fix: keep the FanTraits alive for the loop duration.
2. Restore deleted std::vector<std::string> / initializer_list<std::string>
overloads to preserve clear compile-time diagnostics.
- Deprecated FanTraits setters store in an owned compat_preset_modes_
vector (same copy cost as pre-PR). No heap leak, no dangling pointer.
- Subclass get_traits() calls wire_preset_modes_() to attach the
Fan-owned pointer to the returned traits.
- Getter/find/supports all check compat vector as fallback.
- Move supported_preset_modes_ pointer and ensure helper to private
- Move static EMPTY_VECTOR from inline header getter to a single
file-scope constant in fan.cpp (avoids duplication per TU)
- Add 2026.11.0 removal comments on all compat code paths
Two bugs fixed from Copilot review:
1. Dangling pointer on copy: deprecated setters stored data in an
OwnedPresetModes struct on FanTraits. If the traits object was
copied, the copy's pointer dangled. Fix: deprecated setters now
heap-allocate (intentional leak). Pointer survives any copy.
Remove the OwnedPresetModes wrapper entirely.
2. find_preset_mode_ / save_state_ only searched the Fan-owned
vector, breaking external components using the deprecated traits
setters. Fix: fall back to get_traits() when the entity vector
is null.
Fan entities live for the entire program lifetime, so the preset
modes vector never needs to be freed. Use a raw pointer (null by
default, allocated on first set_supported_preset_modes() call)
instead of an inline std::vector member.
This saves 24 bytes of RAM per Fan instance for components that
don't use preset modes (binary, bedjet, tuya, etc.).
Wrap the deprecated owned vector in a struct with a no-op copy
constructor. FanTraits copies (which happen on every get_traits()
call) don't pay the 24-byte cost of copying an empty vector.
FanTraits contained a std::vector<const char *> for preset modes.
Every get_traits() call reconstructed this vector, causing heap
allocations on every publish_state() and control()/perform() call.
Move the vector storage to the Fan base class and have FanTraits hold
a const pointer instead. Internal callers (save_state_, find_preset_mode_)
search the Fan-owned vector directly, avoiding traits reconstruction.
The old FanTraits setters are preserved as deprecated compatibility
overloads (removed in 2026.11.0) that self-own the data, so external
components continue to compile — they just get a deprecation warning
and still heap-allocate until they migrate.