From bb9e75796cd5f9ed4000dfd09b6fc19ec6833339 Mon Sep 17 00:00:00 2001 From: Stefanie Jane Date: Sat, 25 Jul 2026 22:04:42 -0700 Subject: [PATCH 1/2] fix(servo): stop importing EffectCategory the servo lane cannot use The only consumer of EffectCategory in the servo renderer sits behind cfg(feature = "servo-gpu-import"), so building with servo alone leaves the import dead and -D warnings fails the lane. Qualify it at the use site instead. This was invisible until now for two stacked reasons: the evdev lint that kept main red since 2026-07-21 failed the same jobs first, and clippy only lints the package it is pointed at, so daemon runs compile core without lints and no local run ever linted core with the servo feature enabled. Co-Authored-By: Nova (Claude Opus 5) --- crates/hypercolor-core/src/effect/servo/renderer.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/hypercolor-core/src/effect/servo/renderer.rs b/crates/hypercolor-core/src/effect/servo/renderer.rs index 2e5001d7..672c6ce8 100644 --- a/crates/hypercolor-core/src/effect/servo/renderer.rs +++ b/crates/hypercolor-core/src/effect/servo/renderer.rs @@ -11,9 +11,7 @@ use anyhow::{Result, bail}; use hypercolor_types::canvas::{Canvas, DEFAULT_CANVAS_HEIGHT, DEFAULT_CANVAS_WIDTH, Rgba}; use hypercolor_types::display::DisplayDescriptor; -use hypercolor_types::effect::{ - ControlKind, ControlValue, EffectCategory, EffectMetadata, EffectSource, -}; +use hypercolor_types::effect::{ControlKind, ControlValue, EffectMetadata, EffectSource}; use std::collections::HashMap; use std::path::PathBuf; use std::time::Duration; @@ -374,7 +372,9 @@ fn host_driven_animation(metadata: &EffectMetadata) -> bool { #[cfg(feature = "servo-gpu-import")] fn should_reuse_cached_gpu_frame_on_no_ready(metadata: &EffectMetadata) -> bool { - metadata.category == EffectCategory::Display + // Qualified so the import doesn't go dead when servo builds without + // servo-gpu-import — this function is the only consumer. + metadata.category == hypercolor_types::effect::EffectCategory::Display } mod frame_poll; From d131326cd6822f15bf74a322e771d02325e03bb3 Mon Sep 17 00:00:00 2001 From: Stefanie Jane Date: Sat, 25 Jul 2026 22:04:42 -0700 Subject: [PATCH 2/2] chore(python): regenerate the OpenAPI client The drift gate failed on main after the merge; the stale models are the spec-71 input-status surface, which landed while CI was already red and never got a green regeneration. just python-generate-check passes after this. Co-Authored-By: Nova (Claude Opus 5) --- .../hypercolor/_generated/models/__init__.py | 2 + .../models/api_response_system_status_data.py | 15 +++ .../_generated/models/input_status.py | 107 ++++++++++++++++++ .../_generated/models/system_status.py | 15 +++ 4 files changed, 139 insertions(+) create mode 100644 python/src/hypercolor/_generated/models/input_status.py diff --git a/python/src/hypercolor/_generated/models/__init__.py b/python/src/hypercolor/_generated/models/__init__.py index 973d764e..08850d72 100644 --- a/python/src/hypercolor/_generated/models/__init__.py +++ b/python/src/hypercolor/_generated/models/__init__.py @@ -215,6 +215,7 @@ from .health_checks import HealthChecks from .health_response import HealthResponse from .identify_request import IdentifyRequest +from .input_status import InputStatus from .invoke_control_action_request import InvokeControlActionRequest from .latest_frame_status import LatestFrameStatus from .layer_order_request import LayerOrderRequest @@ -493,6 +494,7 @@ "HealthChecks", "HealthResponse", "IdentifyRequest", + "InputStatus", "InvokeControlActionRequest", "LatestFrameStatus", "LayerOrderRequest", diff --git a/python/src/hypercolor/_generated/models/api_response_system_status_data.py b/python/src/hypercolor/_generated/models/api_response_system_status_data.py index dac3d73d..ade0846b 100644 --- a/python/src/hypercolor/_generated/models/api_response_system_status_data.py +++ b/python/src/hypercolor/_generated/models/api_response_system_status_data.py @@ -10,6 +10,7 @@ if TYPE_CHECKING: from ..models.effect_health_status import EffectHealthStatus + from ..models.input_status import InputStatus from ..models.latest_frame_status import LatestFrameStatus from ..models.preview_runtime_status import PreviewRuntimeStatus from ..models.render_acceleration_status import RenderAccelerationStatus @@ -37,6 +38,12 @@ class ApiResponseSystemStatusData: effect_health (EffectHealthStatus): event_bus_subscribers (int): global_brightness (int): + input_ (InputStatus): Host keyboard/mouse capture health, for consent and remediation UX. + + `enabled` is the consent config gate. `host_capturing` is true when a + host backend is actively reading device nodes. `devices_denied` counts + input nodes present but unreadable (udev rules missing) — the signal + that distinguishes "input is off" from "input is on but blocked". preview_runtime (PreviewRuntimeStatus): render_loop (RenderLoopStatus): running (bool): @@ -62,6 +69,7 @@ class ApiResponseSystemStatusData: effect_health: EffectHealthStatus event_bus_subscribers: int global_brightness: int + input_: InputStatus preview_runtime: PreviewRuntimeStatus render_loop: RenderLoopStatus running: bool @@ -103,6 +111,8 @@ def to_dict(self) -> dict[str, Any]: global_brightness = self.global_brightness + input_ = self.input_.to_dict() + preview_runtime = self.preview_runtime.to_dict() render_loop = self.render_loop.to_dict() @@ -154,6 +164,7 @@ def to_dict(self) -> dict[str, Any]: "effect_health": effect_health, "event_bus_subscribers": event_bus_subscribers, "global_brightness": global_brightness, + "input": input_, "preview_runtime": preview_runtime, "render_loop": render_loop, "running": running, @@ -175,6 +186,7 @@ def to_dict(self) -> dict[str, Any]: @classmethod def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: from ..models.effect_health_status import EffectHealthStatus + from ..models.input_status import InputStatus from ..models.latest_frame_status import LatestFrameStatus from ..models.preview_runtime_status import PreviewRuntimeStatus from ..models.render_acceleration_status import RenderAccelerationStatus @@ -210,6 +222,8 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: global_brightness = d.pop("global_brightness") + input_ = InputStatus.from_dict(d.pop("input")) + preview_runtime = PreviewRuntimeStatus.from_dict(d.pop("preview_runtime")) render_loop = RenderLoopStatus.from_dict(d.pop("render_loop")) @@ -273,6 +287,7 @@ def _parse_latest_frame(data: object) -> LatestFrameStatus | None | Unset: effect_health=effect_health, event_bus_subscribers=event_bus_subscribers, global_brightness=global_brightness, + input_=input_, preview_runtime=preview_runtime, render_loop=render_loop, running=running, diff --git a/python/src/hypercolor/_generated/models/input_status.py b/python/src/hypercolor/_generated/models/input_status.py new file mode 100644 index 00000000..6a82f7a4 --- /dev/null +++ b/python/src/hypercolor/_generated/models/input_status.py @@ -0,0 +1,107 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="InputStatus") + + +@_attrs_define +class InputStatus: + """Host keyboard/mouse capture health, for consent and remediation UX. + + `enabled` is the consent config gate. `host_capturing` is true when a + host backend is actively reading device nodes. `devices_denied` counts + input nodes present but unreadable (udev rules missing) — the signal + that distinguishes "input is off" from "input is on but blocked". + + Attributes: + backends (list[str]): + devices_denied (int): + devices_opened (int): + enabled (bool): + host_capture_registered (bool): + host_capturing (bool): + """ + + backends: list[str] + devices_denied: int + devices_opened: int + enabled: bool + host_capture_registered: bool + host_capturing: bool + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + backends = self.backends + + devices_denied = self.devices_denied + + devices_opened = self.devices_opened + + enabled = self.enabled + + host_capture_registered = self.host_capture_registered + + host_capturing = self.host_capturing + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "backends": backends, + "devices_denied": devices_denied, + "devices_opened": devices_opened, + "enabled": enabled, + "host_capture_registered": host_capture_registered, + "host_capturing": host_capturing, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + backends = cast(list[str], d.pop("backends")) + + devices_denied = d.pop("devices_denied") + + devices_opened = d.pop("devices_opened") + + enabled = d.pop("enabled") + + host_capture_registered = d.pop("host_capture_registered") + + host_capturing = d.pop("host_capturing") + + input_status = cls( + backends=backends, + devices_denied=devices_denied, + devices_opened=devices_opened, + enabled=enabled, + host_capture_registered=host_capture_registered, + host_capturing=host_capturing, + ) + + input_status.additional_properties = d + return input_status + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/python/src/hypercolor/_generated/models/system_status.py b/python/src/hypercolor/_generated/models/system_status.py index eb3d7460..015e5ad6 100644 --- a/python/src/hypercolor/_generated/models/system_status.py +++ b/python/src/hypercolor/_generated/models/system_status.py @@ -10,6 +10,7 @@ if TYPE_CHECKING: from ..models.effect_health_status import EffectHealthStatus + from ..models.input_status import InputStatus from ..models.latest_frame_status import LatestFrameStatus from ..models.preview_runtime_status import PreviewRuntimeStatus from ..models.render_acceleration_status import RenderAccelerationStatus @@ -37,6 +38,12 @@ class SystemStatus: effect_health (EffectHealthStatus): event_bus_subscribers (int): global_brightness (int): + input_ (InputStatus): Host keyboard/mouse capture health, for consent and remediation UX. + + `enabled` is the consent config gate. `host_capturing` is true when a + host backend is actively reading device nodes. `devices_denied` counts + input nodes present but unreadable (udev rules missing) — the signal + that distinguishes "input is off" from "input is on but blocked". preview_runtime (PreviewRuntimeStatus): render_loop (RenderLoopStatus): running (bool): @@ -62,6 +69,7 @@ class SystemStatus: effect_health: EffectHealthStatus event_bus_subscribers: int global_brightness: int + input_: InputStatus preview_runtime: PreviewRuntimeStatus render_loop: RenderLoopStatus running: bool @@ -103,6 +111,8 @@ def to_dict(self) -> dict[str, Any]: global_brightness = self.global_brightness + input_ = self.input_.to_dict() + preview_runtime = self.preview_runtime.to_dict() render_loop = self.render_loop.to_dict() @@ -154,6 +164,7 @@ def to_dict(self) -> dict[str, Any]: "effect_health": effect_health, "event_bus_subscribers": event_bus_subscribers, "global_brightness": global_brightness, + "input": input_, "preview_runtime": preview_runtime, "render_loop": render_loop, "running": running, @@ -175,6 +186,7 @@ def to_dict(self) -> dict[str, Any]: @classmethod def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: from ..models.effect_health_status import EffectHealthStatus + from ..models.input_status import InputStatus from ..models.latest_frame_status import LatestFrameStatus from ..models.preview_runtime_status import PreviewRuntimeStatus from ..models.render_acceleration_status import RenderAccelerationStatus @@ -210,6 +222,8 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: global_brightness = d.pop("global_brightness") + input_ = InputStatus.from_dict(d.pop("input")) + preview_runtime = PreviewRuntimeStatus.from_dict(d.pop("preview_runtime")) render_loop = RenderLoopStatus.from_dict(d.pop("render_loop")) @@ -273,6 +287,7 @@ def _parse_latest_frame(data: object) -> LatestFrameStatus | None | Unset: effect_health=effect_health, event_bus_subscribers=event_bus_subscribers, global_brightness=global_brightness, + input_=input_, preview_runtime=preview_runtime, render_loop=render_loop, running=running,