Fix automation badge overflow, dashboard crosslinks, compact numbers, icon grids, OpenRGB brightness

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>
This commit is contained in:
2026-03-17 01:29:17 +03:00
parent 304fa24389
commit 29b43b028d
16 changed files with 143 additions and 35 deletions
@@ -288,16 +288,13 @@ class OpenRGBLEDClient(LEDClient):
Builds raw OpenRGB UpdateZoneLeds packets directly with struct.pack,
bypassing RGBColor object creation to avoid GC pressure.
"""
# Apply brightness scaling
if brightness < 255:
pixel_array = (pixel_array.astype(np.uint16) * brightness >> 8).astype(np.uint8)
# Truncate to match target LED count
n_target = self._device_led_count
if len(pixel_array) > n_target:
pixel_array = pixel_array[:n_target]
# Change-threshold dedup — skip if average per-LED color change < 2
# Change-threshold dedup — compare RAW pixels before brightness scaling
# so low brightness doesn't crush differences below the threshold.
# GPU I2C/SMBus writes cause system-wide stalls; minimizing writes is critical.
if self._last_sent_pixels is not None and self._last_sent_pixels.shape == pixel_array.shape:
diff = np.mean(np.abs(pixel_array.astype(np.int16) - self._last_sent_pixels.astype(np.int16)))
@@ -305,6 +302,10 @@ class OpenRGBLEDClient(LEDClient):
return
self._last_sent_pixels = pixel_array.copy()
# Apply brightness scaling after dedup
if brightness < 255:
pixel_array = (pixel_array.astype(np.uint16) * brightness >> 8).astype(np.uint8)
# Separate mode: resample full pixel array independently per zone
if self._zone_mode == "separate" and len(self._target_zones) > 1:
n_src = len(pixel_array)
@@ -27,7 +27,7 @@ class OpenRGBDeviceProvider(LEDDeviceProvider):
@property
def capabilities(self) -> set:
return {"health_check", "auto_restore", "static_color"}
return {"health_check", "auto_restore", "static_color", "brightness_control"}
def create_client(self, url: str, **kwargs) -> LEDClient:
return OpenRGBLEDClient(