Skip to content

Releases: hyperb1iss/hypercolor

Hypercolor 0.6.2

Choose a tag to compare

@github-actions github-actions released this 07 Oct 04:26

Release Notes v0.6.2

Released: 2026-10-07

This release fixes the Windows startup failure that made 0.6.1 unusable on every Windows install. FXC, the DX12 shader compiler wgpu uses on Windows, expanded the default workgroup zero-init of the 256-entry WideRgb scan array into per-element stores and spent roughly 17 seconds on each of the three scan kernels grouping them (commit 302de9f3); the resulting stall held 0.6.1 startup for 54 s on the reference desktop, well past the app supervisor's 20 second health budget. Alongside that, macOS is back with its first build since 0.3.2 (Apple silicon only), the workspace becomes publishable on crates.io, and the CI compiler cache moves to Cloudflare R2.

🌟 Highlights

⚡️ Windows daemon starts in seconds instead of a minute

gpu_area_sat.rs now compiles the area SAT and hierarchy kernels with zero_initialize_workgroup_memory: false through a new scan_compilation_options(). Every lane writes its own scan_values slot before the first barrier and only reads slots already written, so the fill was dead work on every backend. Measured on an RTX 4070 SUPER: scan_horizontal_tiles drops from 18017 ms to 71 ms, and daemon startup to healthy drops from 58-87 s to 3.7 s. Both WGSL files now document the write-before-read invariant the change depends on.

✅ Windows GPU tests actually run in CI

No CI lane had a GPU adapter and the Windows job built the daemon without wgpu, so no SparkleFlinger shader was ever compiled on Windows before a release went out. A new Rust Windows GPU job builds the daemon with only the wgpu feature, forces the WARP software adapter, pins FXC so the lane compiles shaders the way installs do, and requires GPU tests instead of letting them skip. The pipeline warmup budget test (gpu/tests/warmup.rs, 30 s budget) runs alone in its own step so other shader compiles cannot skew the measurement, and create-release now waits for the job.

🔥 macOS ships for Apple silicon only

The macos-26-intel native app build took about three hours per release and a runner died mid-build during 0.6.1. build-native-app and sign-macos now carry a single macos-arm64 entry, attach-macos requires exactly the arm64 tarball, its checksum, the arm64 DMG, and the DMG's notarization receipt, and both the formula and the cask declare depends_on arch: :arm64.

The arm64 build shipped notarized: Apple accepted both the app and the DMG, and Homebrew installs 0.6.2 through the formula and the cask on macOS 15.2 or newer. See Upgrade notes.

📦 The workspace publishes to crates.io

The CLI package in crates/hypercolor-cli is renamed from hypercolor-cli to hypercolor, so cargo install hypercolor installs the hypercolor binary. Every internal dependency gained an explicit version requirement (33 entries in [workspace.dependencies] plus 12 direct path dependencies), and scripts/set-version.ts stamps and verifies those requirements alongside the workspace version so a stale requirement cannot publish a crate asking for the previous release of its siblings.

🐛 Dashboard gauges and uptime stop freezing

Three separate stalls in the desktop dashboard are fixed: gauge exponential moving averages now step on every metrics sample via ema_step in fps_display, the uptime pill is anchored to its arrival time and advanced from the wall clock, and both track a new metrics_tick counter that bumps before the payload equality gate, so a paused render loop sending identical payloads no longer stalls the display.

⚡️ Windows and GPU

  • Skip workgroup zero-init in the area SAT and hierarchy scan kernels; the WGSL in area_sat.wgsl and area_hierarchy.wgsl documents the invariant that makes it safe
  • New SparkleFlinger GPU CI job on a DX12 WARP runner, running one process per test under nextest without fail-fast
  • gpu/tests/warmup.rs asserts pipeline warmup fits the daemon startup budget, with a failure message pointing at RUST_LOG=wgpu_core=trace,wgpu_hal=trace to find a stalling shader
  • Test display readbacks get more room on slow adapters, and pipeline warmup is gated on the shipped DX12 compiler
  • macos-gpu-interop gates the capture cache owner constructor; the macOS screen test unwraps the wgpu 30 mapped range

🐛 Desktop app and UI

  • Preview frame cadence: should_publish_preview_frame rounded a target rate up to a whole millisecond interval, so 30 fps became 34 ms and the gate refused every other frame of a ~33 ms render loop, delivering 15-19 fps to clients asking for 30. The gate now publishes once per 1/fps slot of the uptime clock using a fixed-point slot index that stays exact past u32 milliseconds of uptime
  • Gauge averages no longer freeze when an fps source locks to its target (Engine stuck at 53.3 against a steady 60)
  • StatusPill takes its value as a signal; String and &str callers convert unchanged
  • The dev macOS app bundle builds only the .app

📦 Packaging and distribution

  • Homebrew carry-forward: update-homebrew waits for attach-macos to finish rather than succeed, still fenced by create-release. Linux checksums are always required; macOS takes one of two complete forms, the release's own tarball and DMG, or no macOS assets at all, in which case the renderer carries the macOS stanza forward from the tap. A release carrying only one of the two macOS assets stops the job
  • --withdraw-macos: the renderer gains a withdrawn macOS state that replaces the on_macos download block with a fatal NotarizedMacosBuildRequirement, so a macOS install or upgrade fails with a reason instead of resolving the 0.3.2 build still in the tap. readPublishedMacos recognises the withdrawn stanza, so carry mode keeps it withdrawn and the next release with a notarized build renders macOS and the cask in full again
  • Installers refuse Intel Macs before downloading: get-hypercolor.sh and install-release.sh stop with a clear message instead of 404ing on a missing asset. All three of the installers and verify-release-artifact.sh ask sysctl hw.optional.arm64 first so a shell under Rosetta is treated as Apple silicon, with the lookup keeping PATH first and falling back to /usr/sbin
  • Bundled data moved inside the crates that read it: data/attachments/builtin/ becomes crates/hypercolor-core/attachments/ and data/openrgb/detectors.toml becomes crates/hypercolor-openrgb-host/data/detectors.toml. A crates.io tarball carries only the crate, so the old layout made openrgb-host fail to compile from its package and silently gave core an empty attachment catalog. Core's build script now fails when the folder is missing or holds no templates
  • Three crates stay off crates.io by design: hypercolor-daemon, hypercolor-app, and hypercolor-windows-helper, each of which bundles files from outside its crate directory or only makes sense as a signed build

🔧 CI and build caching

  • sccache on Cloudflare R2: the Actions cache hit its configured storage budget and turned read-only, so tag and release builds started cold (the Windows native app build for 0.6.1 hit 2% of 2,733 compile requests). R2 holds the compiler cache outside that budget with no egress charges. The action enables R2 only after a signed HEAD request proves the endpoint answers and accepts the key, otherwise it logs a warning and falls back to the local disk cache. Credentials go to curl on stdin and are masked
  • Write access follows the rule the Actions cache already enforced: READ_WRITE for pushes, dispatches, and schedules on the default branch, READ_ONLY for every pull request, tag, and opted-out lane. Fork pull requests receive no secrets
  • Source mtime refresh after cache restore: artifacts without source metadata are invalidated, malformed snapshots are rejected, refreshed times stay reusable and strictly newer on hosted runners
  • The Windows target archive is keyed by dependencies rather than commit
  • Rust tests now run when the release installers change; the macOS workspace lane checks every target
  • Docs added for the CI cache layers, the shared R2 cache, and write errors in read-only runs

✅ Tests

  • Layout cancellation tests wait on a new AfterWorkflow test hook instead of polling layouts.json under a fixed five second deadline, which was flaking on loaded Windows runners and re-opening the files the workflow was atomically replacing. Outside the persistence-test-hooks feature the hooks are a ready future, so production builds pay nothing. The four cancellation tests passed 200 consecutive runs with every core pinned
  • The Servo CSS probe matrix now compiles on macOS, still opt-in behind HYPERCOLOR_RUN_SERVO_CSS_PROBES, so the webgl2-clear probe guarding the Servo 0.6 webgl feature can run on a Mac. Verified on an M3 Pro: all eight probes pass at both display sizes

Breaking changes

Intel Macs are no longer supported

The release no longer carries a macos-amd64 tarball or an -x86_64.dmg. The curl installers stop with "Intel Macs are not supported; Hypercolor for macOS requires Apple silicon" before any download, and Homebrew refuses Intel Macs with its own architecture message from depends_on arch: :arm64. Uninstall still works on any Mac, since install-release.sh refuses only on the install path. Intel hosts still map to macos-amd64 in verify-release-artifact.sh, which keeps local source builds verifiable there.

The CLI crate publishes as hypercolor

The package in crates/hypercolor-cli is now named hypercolor. The library target is pinned to hypercolor_cli, so every use hypercolor_cli:: path stays valid, including extension crates built on run_with_extensions. A downstream workspace that names the dependency hypercolor-cli needs package = "hypercolor" on that entry and nothing else. Any -p hypercolor-cli or --exclude hypercolor-cli in your own scripts needs the new package name.

Bundled data paths moved...

Read more

Hypercolor 0.6.1

Choose a tag to compare

@github-actions github-actions released this 02 Oct 03:57

Release Notes v0.6.1

Released: 2026-10-01

A follow-up release focused on Servo render latency, GPU import safety on Linux, and two deployment gaps: a headless Docker runtime for server installs and the missing Visual C++ runtime on Windows. The Servo worker no longer polls on fixed sleeps, Linux GPU imports refuse to run across mismatched devices, and Windows installs work on machines without the VC++ redistributable.

🌟 Highlights

⚡ Servo worker driven by wakeups instead of 1ms sleeps

The Servo worker previously polled with std::thread::sleep(Duration::from_millis(1)) while waiting on script evaluation, page loads, and memory reports. Servo now receives an EventLoopWaker (worker/wake.rs), and the worker blocks on a latched condvar (ServoWakeSignal::wait_until) that Servo signals whenever it queues embedder work. The memory-report reply, which bypasses the embedder channels, uses WakingSender so it wakes the worker both when it delivers and when it is dropped undelivered.

Debug-build bench at 640x480, 60Hz, 600 frames: Bubble Garden (rAF WebGL) eval 2.13ms -> 0.13ms, worker frame 3.7ms -> 1.2ms; Digital Rain (host-driven canvas2D) eval 2.17ms -> 0.55ms, worker frame 3.6ms -> 1.4ms. The same measurement showed duplicate frames moving from 0-3 to 0-9 across repeat runs, so rAF-driven pages can still occasionally repeat a frame.

🎞️ Render-tick frame clock replaces Servo's 120Hz timer thread

New worker/frame_clock.rs adds ServoFrameClock, a RefreshDriver whose frames start on the worker's own render tick, and FrameClockRenderingContext, a wrapper that forwards every other RenderingContext method to the platform context so GPU import, readback, and WebGL surface plumbing are unchanged. Frame readiness is no longer held back waiting on a free-running timer.

Because Servo's RenderingContext trait names surfman, glow, and euclid types in its signatures, hypercolor-core gained three optional dependencies behind the servo feature: euclid, glow, and surfman. The wakeup synchronization itself uses only stdlib Arc/Mutex/Condvar.

🐛 Linux GPU imports refuse mismatched devices

crates/hypercolor-linux-gpu-interop/src/linux/device_identity.rs compares the device UUID and driver UUID reported by GL (GL_DEVICE_UUID_EXT, GL_DRIVER_UUID_EXT) against Vulkan's VkPhysicalDeviceIDProperties before allocating shared memory. Previously an import could succeed syntactically while GL ran on llvmpipe or an iGPU and Vulkan ran elsewhere, producing garbage frames with no signal. The mismatch now fails fast and falls back to CPU readback.

🐳 Headless Docker runtime for network lighting

New packaging/docker/Dockerfile, compose.yaml, and compose.gpu.yaml ship a headless daemon on ubuntu:24.04 that renders HTML effects through Servo on Mesa's surfaceless EGL and drives WLED controllers over DDP. Published to ghcr.io/hyperb1iss/hypercolor for amd64 and arm64.

🔧 Windows binaries ship the VC++ runtime they link

hypercolor-daemon.exe, hypercolor.exe, hypercolor-windows-helper.exe, hypercolor-smbus-service.exe, and the mozangle libEGL.dll/libGLESv2.dll all link the MSVC runtime dynamically, but no runtime DLLs were bundled. On a Windows machine without the Visual C++ Redistributable the daemon could not load, never bound its port, and was killed and respawned by the supervisor every 20 seconds, while the UI reported "verified Hypercolor connection is unavailable". Build runners always carry the redistributable, which is why CI never caught it, and every earlier Windows release has the same gap.

⚡ Servo Rendering

  • ServoWakeSignal latches wakeups, so a signal raised while the worker was spinning Servo's event loop returns immediately and no message slips between a spin and a wait.
  • await_animation_frame() waits up to ANIMATION_FRAME_GRACE (2ms) for an animating page that still owes a frame, woken by Servo rather than by a timer. Sessions that miss ANIMATION_FRAME_MISS_LIMIT (3) consecutive frames stop waiting and re-arm when a frame arrives on its own.
  • New frame_wait_us field on ServoRenderStageTimings, alongside evaluate_scripts_us, event_loop_us, paint_us, readback_us, and total_us. The animation-frame wait is timed separately from the event-loop stage.
  • Each session now owns an Rc<ServoFrameClock>, wired at session creation through FrameClockRenderingContext::new() and ticked via frame_clock.start_frame().

🐛 Linux GPU Interop

  • Added GpuFrameImportFallbackReason::DeviceUuidMismatch (code 27, label device_uuid_mismatch) in hypercolor-gpu-frame, and LinuxGpuInteropError::DeviceUuidMismatch { gl, vulkan } carrying both formatted UUID sets for diagnosis.
  • The mismatch is treated as non-transient: auto mode settles on CPU readback instead of retrying an import that cannot succeed.
  • The GL external-memory loader now requires glGetUnsignedBytevEXT and glGetUnsignedBytei_vEXT, exposed as GlExternalMemoryFunctions::get_unsigned_bytev_ext and get_unsigned_bytei_v_ext. Contexts and devices that report no UUIDs, or all-zero UUIDs, skip the check rather than failing.
  • DeviceUuidMismatch increments SERVO_RENDER_GPU_IMPORT_ADAPTER_MISMATCH_TOTAL, the same counter as the Windows AdapterLuidMismatch case.
  • New crates/hypercolor-gpu-frame/tests/fallback_reason_tests.rs asserts every fallback code from 1 to 27 round-trips to a unique label and pins DeviceUuidMismatch to code 27.
  • tests/raw_gl_fixture_tests.rs now serializes through a FIXTURE_LOCK: Mutex<()> acquired by acquire_fixture(); creating GL contexts from parallel test threads deadlocked in the driver.

🐳 Server Deployment

  • The container runs unprivileged as UID/GID 10001, serves the web UI and API on port 9420/tcp, streams DDP on UDP 4048, and requires HYPERCOLOR_API_KEY. State lives in the hypercolor-data volume at /var/lib/hypercolor, with XDG paths pointed into it.
  • compose.gpu.yaml is an overlay that maps /dev/dri and needs HOST_RENDER_GID and HOST_VIDEO_GID set to the host device groups. Activate with COMPOSE_FILE=compose.yaml:compose.gpu.yaml.
  • Images are published from tested artifacts rather than rebuilt: the release job loads the exact image that passed the smoke test, tags ${version}-amd64 / ${version}-arm64 plus a multiarch index, and applies latest only to stable releases.
  • New just docker-build <dist_dir> [image] and just docker-test [image] recipes (Linux only, honoring CONTAINER_ENGINE).
  • New CI jobs docker-e2e ("Docker / Headless HTML and WLED") on PRs and main, and publish-docker on tags, serialized behind the hypercolor-docker-publication concurrency group with cancel-in-progress: false so no release publication is dropped.
  • scripts/tests/docker-smoke.mjs proves unprivileged startup, API-key enforcement, UI serving, Servo HTML rendering, animated DDP output to a WLED emulator, config and scene persistence across restart, and clean exit on SIGTERM. scripts/tests/docker-workflow.test.mjs checks the release workflow contracts.
  • New guide at docs/content/guide/docker.md, with choose-your-install.md and installation.md updated to point server users at it.

🔧 Windows Packaging

  • Copy-VcRuntime() in scripts/stage-app-bundle-assets.ps1 locates the redistributable through VCToolsRedistDir or vswhere.exe and stages msvcp140.dll, vcruntime140.dll, and vcruntime140_1.dll into both the bundle root and tools/, for x86_64 and aarch64 targets.
  • New scripts/check-windows-dll-closure.ps1 extracts the built NSIS installer and reads PE import tables with llvm-objdump, deliberately never consulting System32 or PATH, which would hide the bug on build runners. It requires all five binaries to be present, fails when llvm-objdump errors or a binary reports zero imports, and resolves every MSVC runtime import against files shipped beside the binary.
  • CI clears stale bundle/nsis directories before each Tauri build so a cached installer cannot pass the check.
  • HYPERCOLOR_STOP_BROKER in installer-hooks.nsh now calls Stop-Service -Name HypercolorSmBus -Force and then waits on WaitForStatus('Stopped', [TimeSpan]::FromSeconds(20)). sc.exe stop returns when the stop is requested, so upgrades and uninstalls were replacing files the broker still held open. The macro runs from both NSIS_HOOK_PREINSTALL and NSIS_HOOK_PREUNINSTALL, and a missing service is not an error.

🔨 Maintenance

  • nix/release.json pinned to 0.6.0 with refreshed x86_64 and aarch64 hashes.
  • Cargo.lock updated for the new optional hypercolor-core dependencies.

Upgrade Notes

  • Linux GPU import users: if telemetry starts reporting device_uuid_mismatch (fallback code 27), the process is running GL and Vulkan on different devices. Check LIBGL_ALWAYS_SOFTWARE and hybrid-GPU environment variables; frames were likely already wrong before this release, they just failed silently.
  • Out-of-tree GL loaders must now resolve glGetUnsignedBytevEXT and glGetUnsignedBytei_vEXT or importer setup will fail with an explicit missing-symbol error.
  • Windows: reinstall to pick up the bundled VC++ runtime. The upgrade after 0.6.1 is the first one where the SMBus broker is actually running on machines that lacked the redistributable, and that upgrade runs the 0.6.1 uninstaller, which includes the stop-and-wait hook.
  • Docker: start from packaging/docker/compose.yaml, generate HYPERCOLOR_API_KEY (openssl rand -hex 32) into .env, and pin HYPERCOLOR_VERSION=0.6.1 if you do not want latest. Back up the hypercolor-data volume before upgrading.
  • Servo telemetry: frame_wait_us is a new render-stage field; add it to dashboards that break down frame cost. Expect lower event_loop_us as well, since the 1ms polling sleeps that inflated it are gone.

Hypercolor 0.6.0

Choose a tag to compare

@github-actions github-actions released this 30 Sep 12:47

[0.6.0] - 2026-09-30

Linux gains a managed release install with a durable update journal, rollback on probation failure, and a sandboxed release unit. The daemon keeps previous generations of every store it owns and records state-changing requests in an audit log. Lian Li L-Wireless RGB now paces on the fans' own echoes, and the macOS signing lane moved into its own job with a fast selftest.

This entry documents the range v0.5.1..HEAD.

Added

Managed Linux release installs

  • Add the hypercolor-install crate and drive Linux installs through one replayable orchestration. Releases live under ${XDG_DATA_HOME:-~/.local/share}/hypercolor/releases, update state under ${XDG_STATE_HOME:-~/.local/state}/hypercolor/update, and ~/.local/lib/hypercolor/install-journal.json points every installer at the recorded locations. The locator switch is the single commit point: before it, the old install is still in charge and nothing visible has changed.
  • Hold a started release through a probation window before committing. The installer proves the service runs that release's own daemon and that the local API answers with the release's version, then watches systemd's change notifications for a fixed 90 seconds. A crash, restart, stop, or watchdog kill in that window rolls back at once. (The window is an internal constant in hypercolor-install, not a CLI option; the code also carries a 10-minute internal ceiling.)
  • Report the release that runs again after a rollback: its unit, version, systemd invocation, process ID, and the SHA-256 of the executable it runs, so the result can be matched against systemctl --user status hypercolor.
  • Render the daemon unit with Type=notify and a sandbox (ProtectSystem=strict, ProtectHome=read-only, PrivateTmp, NoNewPrivileges, with ReadWritePaths limited to config, data, and daemon state). The unit needs systemd 239 or newer, and the user manager builds the sandbox inside an unprivileged user namespace, so kernel and security policy must allow those.
  • Write a contract line as the unit's first line naming hypercolor-public-1, so an installer never misjudges a unit written by a build it does not know. Each unit names one release directly.
  • Remove releases nothing references after each install, and uninstall from the recorded roots through hypercolor __uninstall-release: stop and disable the service, remove only the entries the installer generated, and delete the releases directory, update state, and ~/.local/lib/hypercolor, while keeping data, runtime state, and configuration.
  • Add docs/content/guide/linux-release-installs.md covering upgrade targets, ancestry rules, public-directory refusal, the sandbox's requirements, and uninstall limits.
  • Add the ordinary-user systemd guest proof harness under scripts/qualification/linux-user-guest/, pinning every kill point for probation, rollback reports, launcher swap, sandbox, and release-unit recovery.

Daemon state history and audit trail

  • Keep previous generations of every daemon-owned store through hypercolor-persistence's rolling replacement history: 10 generations by default, with a 30-second minimum interval between captures. Unchanged rewrites no longer hide stable history, and a pending shutdown capture survives them.
  • Exclude stores where old copies are useless or harmful: instance-id, asset-library, user-effects, legacy-profiles, driver-inventory, credentials, attachment-templates, and device-binding-journal. Alias scan stamps are compared by equivalence so last_seen_epoch_s churn never creates a generation.
  • Record state-changing requests in a persistent audit log and serve them from GET /api/v1/system/audit, newest first, with limit defaulting to 100 and clamped to 1000. Entries carry timestamp, transport (http, websocket, mcp), method, path, optional MCP tool, status, socket peer, forwarded-for and user-agent (each capped at 256 characters), the durable stores whose bytes changed, and latency. The peer is taken from the socket, never from a header.
  • Write status 499 for a request dropped before its response, held until the last attributed task finishes, and audit layout publications.
  • Inventory the durable stores and ship the result as a release file (share/hypercolor/durable-stores.json, built from packaging/managed/durable-stores.json). It names each store's format, owner, root, location, schema range, written version, and migration mode, for tools rather than the installer.

Nix packaging

  • Add flake.nix with a binary package and a NixOS module. Options: services.hypercolor.enable, package, autoStart, logLevel, extraArgs, smbus.enable, and input.allDevices. CI builds the flake on changes, validates the release pin, and opens a pin pull request after each release.

Web UI

  • Speak Remote bridge contract 2 for the canvas preview: previewState(), previewStream(), and setPreview(). The preview plays the bridge's video track in a muted inline <video> and is sized from the bridge's transport path.
  • Resolve daemon media through the installed transport with a shared cache: 256 entries, a 48 MiB total budget, 4 concurrent fetches, and no caching above 8 MiB per entry.
  • Count preview frames received, displayed, dropped, and unobserved, and keep the JPEG preview moving when the worker cannot.
  • Support host transport and a strict browser CSP, backed by an owned incremental browser response reader that holds at most one browser chunk and one caller-sized copy.

Hardware

  • Pace L-Wireless RGB on the fans' echoed frame tags over a sliding per-cluster window, treating the echo as a cumulative acknowledgement. On the reference V1 rig, one frame per echo ran at 3 fps; with a window of 29 to 30, the fans confirmed 25.6 to 26.5 fps of 26.6 to 28.3 offered (spec 80 §6.11, measured 2026-09-28). Windows halve when the shared TX backlogs, staleness is judged against the measured status cadence, and a lagging cluster falls out of step instead of stalling its peers.
  • Recover a fan-side stall in stages: two TX resets, then rests of 10 s, 30 s, 1 min, 2 min, and 5 min with upkeep holding fan speed while the protocol asks the backend for a fresh connect, then a power-cycle message. Protocol::session_restart lets a driver ask for its device to be connected afresh.
  • Pace Push 2 LED output on device acknowledgements within one 512-byte endpoint packet, write raw MIDI messages whole or not at all, keep every zone fresh inside the ack window, and report a stalled MIDI output from the kernel backlog, probing at a spacing that grows from 250 ms. Log firmware, power source, and uptime at connect.

Release engineering

  • Add macos-signing-selftest.yml, a signing rehearsal bounded at 30 minutes (timeout-minutes: 30), and macos-notary-status.yml, which lists recent Apple notarization submissions and prints Apple's log for one on request using the repository's App Store Connect key, with no Mac required.
  • Sign macOS artifacts in their own job from build-native-app's unsigned payload, so a slow notarization reruns without a four-hour rebuild, and publish the release without waiting on Apple. Verify macOS release signatures on the user's Mac during install.
  • Add release-workflow and version-resolution script tests, a release artifact parity test proving producer, verifier, and Rust validator agree, and scripts/check-ui-html-sinks.sh.

Other

  • Admit selected scene activation fields only with exact evidence, and guard exact runtime zone color mutations.
  • Render the GitHub social preview and README banner from the brand field.

Changed

  • Move the release installer out of hypercolor-cli/src/install/ into the hypercolor-install crate, and route Linux commands through the recorded managed authority elected under the state lock.
  • Upgrade the HTML effect renderer to Servo 0.6 (SpiderMonkey 140 to 153). Servo 0.6 makes WebGL an opt-in feature, so hypercolor-core requests webgl explicitly; every shader effect goes through WebGL2, and a webgl2-clear CSS probe fixture fails the matrix if no WebGL2 context is available. jemalloc is restored as the daemon's global allocator after servo-allocator 0.6 made it opt-in.
  • Replace the vendored midir with upstream 0.11, which drops the rsa advisory ignore (RUSTSEC-2023-0071) from deny.toml.
  • Vendor tachys 0.2.18 with a narrow patch: a module-private hc-static trusted-types policy for ToTemplate markup and borrowed-static HTML/SVG inert elements, so Chromium's require-trusted-types-for 'script' still rejects runtime raw markup. An embedding host may install a frozen provider before the app starts.
  • Refresh dependencies: rustls 0.23.45 for RUSTSEC-2026-0285, utoipa 6 with utoipa-axum 0.3 and swagger-ui 10, dirs 7, mdns-sd 0.21, tokio-tungstenite 0.30, nix 0.31, mach2 0.7, tao 0.37 for the hidden Windows Servo window, wasm-bindgen 0.2.129, Playwright 1.63 with ws 8.22, Bun 1.4.2, plus semver-compatible workspace, web UI, SDK, and Python client updates. cc is held at 1.4 so the Windows Servo build compiles.
  • Give the Windows CI lane 180 minutes for a fully cold rebuild after the Servo 0.6 refresh, overlap release validation, and reuse macOS binaries across jobs. Each notarization wait is bounded at 60 minutes (NOTARY_WAIT_TIMEOUT: 60m).
  • Create the service's writable directories before its sandbox with an ExecStartPre that runs outside it, and recreate a deleted configuration root.
  • Isolate test state: sandbox the daemon e2e harness from the invoking user, relocate every directory in daemon lifecycle unit tests, and isolate the Playwright stack's state and connection environment.
  • Retire Linux-first framing across specs and design notes, and complete the cross-platform pass in the ecosystem narrative.

Fixed

  • Restore observed device nam...
Read more

Hypercolor 0.5.1

Choose a tag to compare

@github-actions github-actions released this 11 Sep 03:20

Hypercolor 0.5.1

Studio controls now retain their scroll position, focus, and open sections
while values change. Device assignments save immediately and share undo/redo
history with layout edits. This release also restores wireless fan RGB after
fan-speed upkeep and removes unnecessary CSS transitions from Studio.

Studio editing

  • Layer-stack and Effects controls stay mounted during scene updates. Rejected
    edits restore the server value in place, and effect schemas are cached until
    reconnect.
  • Device assignments and zone moves save automatically. Undo and redo follow
    the order of assignment and layout edits, including removal and restoration
    of offline outputs.
  • The toolbar distinguishes saved state, pending assignments, and unsaved
    layout changes. Placement drafts still require Save.
  • Scene and zone changes cancel superseded editing operations without leaving
    the toolbar stuck in Saving. Replay preserves unrelated newer changes.
  • Narrower transitions on the resize handle and search field avoid unnecessary
    animation of inherited scrollbar colors. Broader UI CPU work remains separate.

Wireless fan lighting

The Lian Li wireless driver caches the latest RGB frame and restores it after
all fan-speed commands, before clock or pairing traffic. Recovery preserves
black frames from pause and stop, cluster orientation, and existing packet
pacing. The cache resets when a new device session starts.

Packet tests verify the recovery order and payloads. The local candidate also
passed live transport checks, but physical confirmation that rainbow flicker
has stopped is still pending. This change concerns fan RGB lighting, not the
LCD image transport. The existing 30 FPS wireless ceiling is unchanged.

API and Python client

The new endpoint, POST /api/v1/scene/members/edit, applies membership changes
atomically and returns the committed scene document plus canonical before/after
receipts. The generated Python client includes synchronous and asynchronous
bindings.

Clients must provide the active scene_id and a numeric If-Match revision
(quoted or bare). A missing precondition or * returns 422. Stale revisions
return 412; a changed active scene or drifted membership returns 409. After a
conflict, read the current scene and rebuild the intended edit before retrying.
Use the returned receipts for undo so hidden output metadata is preserved.
Existing assignment endpoints remain available.

Verification and upgrade

Workspace verification, UI tests, browser regressions, and Linux, Windows, and
both macOS architecture checks passed before release preparation. New coverage
includes atomic membership edits, history replay, control-widget retention,
and wireless RGB recovery.

No configuration migration is required. Devices without a stable USB serial
can still acquire a different identity when moved between ports or hubs; this
release does not guess which identical device should inherit an assignment.

Hypercolor 0.5.0

Choose a tag to compare

@github-actions github-actions released this 08 Sep 23:57
5902542

Release Notes v0.5.0

Released: 2026-09-08

This release lands two large hardware efforts: full Lian Li TL LCD and L-Wireless support (wired panels, RF controller, wireless LCD receivers) and a productized OpenRGB fallback with installation guidance, detector partitioning, and managed server startup. Alongside them, a new rig-setup agent skill turns a physical PC build into a working spatial layout, and the Studio canvas gets a hard look at frame-budget behavior on dense scenes.

🌟 Highlights

🔌 Lian Li TL LCD and L-Wireless hardware support

Spec 80 shipped in three waves. Hypercolor now drives the wired Uni Fan TL LCD panel (04fc:7393), the L-Wireless RF controller (0416:8040 TX paired with its 0416:8041 RX sibling through a new CompanionTransport), and the wireless LCD receivers for TL (1cbe:0006) and SL V3 (1cbe:0005). Wireless RGB payloads travel through a pure-Rust tinyuz codec (crates/hypercolor-hal/src/drivers/lianli/wireless/tinyuz.rs) built against the 4 KiB firmware dictionary, with ten golden .yuz fixtures pinning the bitstream. Fan streaming is armed once and paced at 30 fps.

🌈 OpenRGB fallback you no longer have to hand-run

New crate hypercolor-openrgb-host detects the installed OpenRGB binary (native, Flatpak, or AppImage), probes the SDK server, checks Linux permissions, writes a managed detector partition, and builds a headless launch spec. The desktop app supervises a loopback-only server; the CLI can own one persistently. Native drivers always win per physical device: a conflict guard disables the bridge route and records why.

🖥️ Native-first device coverage

Hypercolor now tracks what it cannot drive. GET /devices/unclaimed lists USB hardware no enabled native driver claims, GET /devices/coverage joins native, bridge, and unclaimed views per physical device, and GET /system/openrgb reports installation, endpoint probes, permission checks, and coverage in one payload. The UI surfaces this through unclaimed_hardware.rs and bridge_status.rs, including prefilled device-support issue links.

🤖 The rig-setup agent skill

A user-facing skill that onboards a physical build end to end: inventory, coverage, interview, generate, dial in. skills/rig-setup/scripts/gen_layout.py turns a case spec (mm geometry, mount positions) plus a rig spec into attachment profiles, a spatial layout, and a named scene, supporting offline generation from a saved template catalog. coverage.py decides native vs bridge vs unsupported (falling back to lsusb, system_profiler, or pnputil), and request_support.py checks for existing requests and prints a prefilled issue URL; passing --file submits the request through an authenticated gh session. Skills now ship in the release tarball under share/hypercolor/skills.

⚡ Studio canvas under a frame budget

layout_canvas.rs drops overlapping per-output backdrop filters, memoizes render metadata via Memo::new, samples pointer geometry once per animation frame (flushing final movement on release), and keeps <For> outputs keyed across edits. Measured on an 80-output scene during development, median frame intervals fell from 66.7 ms to 16.7 ms and median hover work from 5 ms to 0.7 ms. e2e/tests/studio-performance.spec.mjs pins the resulting behavior: drag and undo state, output stacking during hover, absence of per-output backdrop filters, inspector freshness, and no redundant scene fetch.

🌐 Browser-neutral UI transports

hypercolor-ui no longer assumes it is running in a browser tab. New HttpTransport and WebSocketTransport seams let a remote shell route daemon calls and event streams without inheriting browser URL construction or bearer credentials, with cancellable incremental bodies, bounded multipart, and BrowserBlobSource for File and Blob uploads. Browser-normalized remote paths (absolute, protocol-relative, backslash, control characters) are rejected before they reach a transport.

🔌 Hardware

  • Shared display encoding layer (crates/hypercolor-hal/src/display/): Corsair LCD framing and Push 2 display encoding both moved onto one engine with a single hook, an error channel, and skip-and-warn behavior pinned by display_layer_tests.rs.
  • Per-command response plans: the USB actor carries a response plan with a tolerance, and a retried command now drains leftover response reports (20 ms budget) instead of desyncing every later exchange.
  • Placeholder serial quirk: descriptors can declare that a device reports a placeholder serial, and the claim layer refuses it rather than minting a bogus identity.
  • Ambiguous HID panels: when two indistinguishable panels are present, the driver refuses to guess and counts devices by path.
  • Display mounting is a device setting: faces, media, and effects all turn with the screen's mounting rotation. DisplaySummary gains a rotation field (serde-defaulted), and Studio plus device detail expose a mounting picker.
  • Frames fit the wire cap: the daemon scales display frames to each device's transport limit and tells identical panels apart by USB port.
  • ROLI Blocks: LUMI key lighting added to the blocksd bridge, plus a blocks_rainbow example.

🌈 OpenRGB Bridge

  • CLI: hypercolor openrgb status | hints | partition | start | stop | resize <device> <zone> <size>. The managed server binds loopback only, runs headless, and logs to <data>/logs/openrgb.log.
  • Detector partition (data/openrgb/detectors.toml): per-family prefix matching for razer, lianli, corsair, dygma, nollie, and asus/ENE. Families with an active native driver are disabled in OpenRGB's config; user toggles outside the managed keys are preserved, and re-enabling requires an explicit list.
  • Permission checks (Linux): udev_rules, i2c_dev_module, i2c_nodes_writable, hidraw_nodes_writable, each carrying a concrete remedy command.
  • Driver hardening: writers paced to target_fps from a detector-class table, one shared SDK link per endpoint with a reconnecting task, zone resizing requires explicit opt-in and a resizable zone, brightness verified on output activation, frame-shape mismatches guarded, and serial plus location published in discovery metadata.
  • Live reconciliation: flipping drivers.<id>.enabled registers or unregisters the output backend without a restart, and hypercolor diagnose --check openrgb checks bridge connectivity and reports output-disabled routes.
  • Zone sizes: PATCH /config/keys/drivers.openrgb.zone_sizes merges an object patch against current settings, keyed by driver-minted controller fingerprint then zone name, and applies on connect.

🔭 Events and API

  • ConfigChanged is now published from ConfigManager itself (crates/hypercolor-core/src/config/change_stream.rs), so every writer (REST, CLI, MCP, migrations, external edits) emits exactly one event per persisted transition. Multi-leaf changes collapse to the deepest shared prefix with a null payload; per-key redaction keeps credentials out of subtree events.
  • LayoutChanged is published from the layout domain, including auto-layout repair and device-binding migration, carrying current and previous ids.
  • New WebSocket event unclaimed_devices_changed in protocol/websocket-v1.json.
  • New REST endpoints: GET /devices/unclaimed, GET /devices/coverage, POST /devices/forget, GET /system/openrgb, and PATCH /config/keys/{key}.
  • The Python client is regenerated for display rotation, display mounting, OpenRGB status, coverage, unclaimed devices, and saved-controller deletion.

🐛 Fixes

  • Saved controllers no longer leave layout ghosts. Deleting a saved-only controller now forgets its outputs across live layouts, saved layouts, every scene, and the default runtime snapshot, with physical ownership persisted so offline cleanup survives a restart. Studio requires registry evidence before offering the delete.
  • Hue: saturated entertainment colors use full brightness again, and channel topology plus gamuts refresh together on rediscovery, retaining the last valid topology when a refresh fails.
  • Primary-zone members survive restarts. Auto-layout leaves a device alone until it reports segments, and startup restore adopts outputs the persisted primary zone holds but the active layout lacks.
  • Default-face deletes reach orphaned preferences for displays that were removed or re-fingerprinted.
  • TL fan geometry moved from a ring topology to 26 explicit positions in physical LED order (two 13-LED side arcs), so spatial sampling lands where the LEDs actually are.
  • Studio inline fields no longer commit twice on Enter-then-blur, and hover no longer raises large background outputs above smaller targets.
  • SMBus discovery serializes with live output through one process-wide arbiter per bus path, so ASUS probe sequences stop interleaving with device writes.
  • Installer: absent systemd unit properties, retained-directory scaffolding checks, managed icon symlinks, lazy launcher discovery, and identity proof through /api/v1/system instead of the retired /api/v1/server.
  • Packaging: declared asset roots are always created, application and asset mounts can differ, and archive contracts are enforced in CI.
  • Discovery: unclaimed USB inventory stays current on hotplug, identical rediscovery stays revision-neutral, and a device keeps its resolved shape across shapeless rescans.

👷 Build and CI

  • The rust-build-cache action split into tested Node modules (configure.mjs, sccache-lifecycle.mjs, native-cache.mjs, source-mtimes.mjs, restore-state.mjs), each with a .test.mjs companion run by a workflow-lint job.
  • source-mtimes.mjs restores mtimes for content-identical tracked files after a cache restore, cutting false Cargo rebuilds; cached source times are invalidated when symlinks change.
  • Compiler cache lifecycle is owne...
Read more

Hypercolor 0.4.0

Choose a tag to compare

@github-actions github-actions released this 02 Sep 00:13

Release Notes v0.4.0

Released: 2026-09-01

Hypercolor 0.4.0 lands native macOS screen capture and host input, then rebuilds the daemon's public surface around two locked specs: 76 (internal API unification) and 78 (canonical API resource model). Nearly every REST route, WebSocket topic, and error body changed shape, and the in-repo clients (UI, TUI, CLI, app) plus the generated Python client moved with them in lockstep. The platform layer was also re-cut: capability seams live in neutral code, and OS calls live in audited platform crates with portable stubs.

This is a breaking release. Read the Breaking Changes and Upgrade Notes sections before updating any external client.

🌟 Highlights

🍎 Native macOS capture and host input

Five new platform crates land the macOS pipeline: hypercolor-macos-capture, hypercolor-macos-input, hypercolor-macos-owner, hypercolor-macos-media, and hypercolor-macos-session. Capture acquires frames through ScreenCaptureKit, hands opaque IOSurface surfaces to the compositor, and reduces them on Metal through hypercolor-macos-gpu-interop's native_reduction.metal. Host input runs on a CGEvent tap with an exact two-axis scroll contract, and hypercolor-macos-owner coordinates which process owns the TCC grants so privacy prompts land on a single, attested owner.

🔀 One canonical API surface

Specs 76 and 78 converge the daemon: every route error renders through DomainError into one frozen envelope, the live scene lives at /api/v1/scene as a real resource tree, power and brightness merge into a single /api/v1/output resource, and profiles fold into scenes. The 49 open-coded rollback/admit/save/publish rituals in the scene path collapse into one begin_scene_mutation / commit_scene pair with an ordered commit sequencer, so two commits can no longer publish in reverse order after out-of-order persistence.

🎨 A shared color kernel

New hypercolor-color crate owns every conversion, blend, and LED channel encoding, with a matching TypeScript kernel in sdk/packages/core/src/color/ and a GLSL prelude injected at build time (sdk/packages/core/src/tooling/glsl-prelude.ts). Rust and Bun both fence against the same table in sdk/shared/color-vectors.json (261 shared vectors), so a drift between the daemon and an effect fails CI instead of shipping as a hue shift.

🔌 Neutral platform capability boundaries

Lifecycle, publication, launcher, input, filesystem, and render policy moved behind shared seams, and the per-OS sibling modules that used to mirror each other collapsed into one engine per capability. Files carrying cfg(target_os) under crates/hypercolor-core/src and crates/hypercolor-daemon/src fall from 32 to 12, and the remaining ones are composition roots. New crates in this wave: hypercolor-gpu-frame, hypercolor-worker-retention, hypercolor-persistence, hypercolor-platform-fs (expanded), hypercolor-pipewire-interop, hypercolor-linux-input, hypercolor-linux-session, hypercolor-windows-session, hypercolor-windows-telemetry, and hypercolor-driver-support.

🛡️ Signed and gated macOS releases

Release builds now require signed artifacts. packaging/macos/signing-manifest.tsv drives a manifest-based signing actor, entitlements are split for the daemon and its sidecar, SDK and deployment targets are gated by scripts/verify-macos-deployment-target.sh, and the release lane will not publish unless the native acceptance suite passes. A signed TCC acceptance canary (crates/hypercolor-daemon/src/macos_tcc_canary/) proves the privacy grants a real install depends on.

🍎 macOS Platform

  • Capture: ScreenCaptureKit acquisition with bounded callbacks, a surface pool, exact plane import, HDR capability bound to what the stream actually delivers, and recovery fencing so an interrupted stream cannot publish a stale frame.
  • Color: a calibrated SDR capture path with an oracle test, LED tone-mapping configuration, and smoothing preserved across curve transitions.
  • Ownership: hypercolor-macos-owner persists daemon owner handovers, attests private daemon sessions, and derives launcher authority from process evidence rather than a filename. App sidecars stop through retained handles and stale reclaim is bound to the audit token.
  • Input: event-tap sessions publish per-kind platform state, and protected actions bind to the exact executor that can satisfy them instead of prompting opportunistically.
  • Diagnostics: screen_parity_diagnostics.rs reports live pipeline parity, source timing distributions ship in the status payload, and crates/hypercolor-macos-capture/examples/dump_macos_frame.rs dumps a frame for inspection.
  • Compatibility: capture survives the macOS 26 screen delivery metadata changes, and the deployment floor is macOS 15.2.

🔀 API and Protocol Convergence

  • Canonical error envelope. ApiError as a Response factory is gone; Result<T, Response> is forbidden and scan-gated by crates/hypercolor-daemon/tests/api_error_surface_tests.rs. Errors render as ApiErrorBody { error: { code, message, … }, meta }.
  • Live scene tree. /api/v1/scene exposes the running scene: /scene/zones, /scene/zones/{zone}/layout, /scene/zones/{zone}/members, /scene/zones/{zone}/layers, /scene/zones/{zone}/layers/order, and /scene/zones/{zone}/layers/{layer}/controls. Mutations resolve atomically against a base revision.
  • Zones, not groups. Render groups are zones in every path, payload field, and event. Effects target zone_id.
  • One output resource. Power and brightness merge into GET/PATCH /api/v1/output.
  • Config as a resource. GET /config, GET/PUT/DELETE /config/keys/{key}, POST /config/reset, and GET /config/schema, all driven by a key registry (crates/hypercolor-types/src/config_registry.rs). Rejections no longer echo secret values back to the caller.
  • Shared request and response types. Device, display, layout, asset, scene, effect, library, and attachment payloads now live once in hypercolor-types::api and are consumed by the daemon, UI, TUI, and CLI instead of being mirrored per client.
  • WebSocket registry. One declarative topic registry generates topic ids, typed configs, validation, and relay dispatch. Subscriptions are keyed: display_preview by device_id, interactive_preview by a client-chosen preview_id. Golden binary fixtures under crates/hypercolor-daemon/tests/fixtures/ws/ fence the wire bytes.
  • MCP. The agent surface converges on the same domain services; 17 dead stateless tool stubs and execute_tool are deleted, phantom parameters are gone, and tool schemas are closed.
  • OpenAPI. Route registration records (method, path, operation) into a catalog and a test asserts the catalog matches the documented operations, replacing the hand-maintained table.

🎨 Color, Effects, and SDK

  • hypercolor-color provides Rgb, Rgba, LinearRgba, Hsv, Hsl, Oklab, Oklch, hex parsing with no silent fallbacks, PixelBlendMode, and DevicePixelLayout encoding. Linearization is visible in every function name.
  • The compositor's sRGB tables, color_wave's LUTs, the web UI's color sites, and driver channel encoding all source from the kernel.
  • SDK effects parse color through the shared kernel; the GLSL prelude is vendored deterministically so formatter runs do not churn the diff.
  • Exact two-axis scroll reaches effects intact on macOS, Windows, Linux, and browser inputs, with legacy wheel units preserved for existing effects.
  • Servo upgrades to 0.5.0, and WebGL and Canvas2D effects now render host-side on macOS.

🧱 Platform, Drivers, and HAL

  • hypercolor-driver-api is traits and types only; credentials, mDNS, pairing plumbing, and control-surface builders move to the new hypercolor-driver-support.
  • Native transport policy moves into the drivers themselves, and Push 2's MIDI lifecycle is isolated from USB workers so one device's reconnect cannot stall another.
  • hypercolor-platform-fs gains a transactional Unix tree replacement with staging, rollback, and authority checks, plus user_dirs.
  • Imported cross-host device layouts rebind on discovery instead of stranding.
  • hypercolor-tray is deleted; tray supervision is owned by the app.

🧰 Install, Service, and CLI

  • A transactional installer lands under crates/hypercolor-cli/src/install/ with per-platform executors, a payload manifest, launcher stores, proof and validation stages, and rollback. crates/hypercolor-cli/tests/install_transaction_tests.rs and the platform suites cover it.
  • hypercolor access exposes the protected-source actions explicitly: authorize-input-monitoring, authorize-screen-recording, and choose-screen-source (which routes to /capture/source). Nothing prompts implicitly at startup.
  • hypercolor status can watch the daemon through ownership events.
  • CLI and TUI resolve config paths without a literal ~ fallback.
  • A storage-tier path-migration harness (crates/hypercolor-daemon/src/path_migration.rs) moves persisted files forward once, with backup, atomic write ordering, and restart idempotence.

🐍 Clients

  • The Python client is regenerated against the canonical envelope and resource model, and is marked 0.4.0a1. Control types the public models are typed with are now exported.
  • The UI, TUI, CLI, and desktop app all read the canonical error envelope and the keyed WebSocket wire.

👷 Build and CI

  • Bun upgrades to 1.4.0.
  • macOS CI runs capture, status, and bootstrap fixtures, and enforces Servo Clippy before merge.
  • Release publishes Intel macOS artifacts alongside Apple Silicon.
  • Generated-client checks are gated on their real inputs, and the shared color vector table triggers the Rust vector test on edit.

💥 Breaking Changes

  • Error shape changed everywhere. Every route error is now ApiErrorBody { error, meta }. Bespoke per-r...
Read more

Hypercolor 0.3.2

Choose a tag to compare

@github-actions github-actions released this 16 Aug 01:00

Release Notes v0.3.2

Released: 2026-08-15

This release makes global output power a first-class daemon concept, unifies bundled and saved presets into a single effect-scoped stack, and adds a trusted in-process API bridge for local callers. Persistence gained explicit durability reporting, and the build system picked up a cache-aware wrapper plus a pressure-triggered target GC.

🌟 Highlights

Global output power replaces ad-hoc pause

New GET /api/v1/output/power and PUT /api/v1/output/power endpoints expose a real state machine backed by OutputPowerMode (running, paused) and OutputPowerStatus (running, paused, stopped). Pausing holds outputs at their off frame while preserving live effect state, so resume picks up where you left off instead of restarting the effect. A destructive stop is now a distinct, observable state.

One preset stack per effect

GET /api/v1/effects/{id}/presets returns bundled and saved presets in a single list via EffectPresetSummary, each tagged with EffectPresetOrigin::Bundled or Saved plus an editable flag. POST /api/v1/effects/{id}/presets/{preset_id}/apply applies one directly, with an optional render_group in ApplyEffectPresetRequest. Bundled presets now carry stable identifiers, and duplicate bundled identities are rejected at load.

Trusted local execution bridge

The new crates/hypercolor-daemon/src/api/local.rs adds TrustedLocalApi, letting in-process callers execute daemon requests without a network hop or an API key. Requests are marked TrustedLocalControl and run at AccessTier::Control. Paths are validated against /api/v1/*; absolute URLs, authority headers, and traversal are rejected. TrustedLocalWebSocket provides a bounded, cancellable channel transport for /api/v1/ws.

Durable native materialization

persistence.rs now returns AtomicWriteCommitResult with four distinct outcomes: FailedBeforeReplacement, ReplacementVisibleButNotDurable, DurableWritten, and Superseded. A parent-directory fsync failure after a successful rename is no longer silently collapsed into success, so callers can tell visible from durable.

Build cache and target GC

scripts/cargo-cache-build.sh routes Trunk and Cargo through a shim that injects build.target-dir instead of exporting CARGO_TARGET_DIR, keeping sccache hits stable across isolated targets. scripts/cargo-target-gc.sh plus the hypercolor-cargo-target-gc systemd user service and timer reclaim disk under pressure, with dry-run by default and dirty worktrees always preserved.

✨ API and Contracts

  • ActiveEffectResponse gained active_preset_modified: bool, so clients can show when live controls have diverged from the selected preset. Manual control edits keep active_preset_id and set active_preset_modified to true; only POST /api/v1/effects/current/reset (via reset_group_controls) clears the id.
  • PauseEffectResponse and ResumeEffectResponse are now explicit types. Pause reports off_output_behavior and off_output_color.
  • New hypercolor-types module api/output.rs holds SetOutputPowerRequest and OutputPowerResponse, both registered in the OpenAPI document.
  • The WebSocket hello state payload gained an optional active_preset_id, letting clients render preset identity on connect.
  • Session power transitions are guarded by a transition_generation counter. In-flight fades and reconnect scans abandon themselves when superseded, making replayed or late updates idempotent.
  • Media uploads above Axum's default multipart body limit now return a proper 413 through multipart_error_response() instead of a generic multipart failure. The route limit is sized from the library's 2 GiB hard cap plus bounded framing overhead.

🤖 Clients and SDKs

  • Python: added get_output_power(), set_output_power(paused), get_effect_presets(effect_id), and apply_effect_preset(effect_id, preset_id, render_group=None) on both async and sync clients. A new OutputPowerState model exposes state and a paused convenience property. The _generated models and the output API package were regenerated to match.
  • TypeScript SDK: normalizePresetKey() in tooling/html.ts collapses control characters (0x1c–0x1f, 0x85) and runs of whitespace into single spaces without regex escape hazards, and preset-id is parsed and normalized from HTML effects. tooling/validate.ts rejects presets whose ids collide after normalization. Dependency bumps: typescript ^6.0.3 → ^7.0.2, @types/node ^26.1.0 → ^26.1.2, @clack/prompts ^1.6.0 → ^1.7.0, @biomejs/biome 2.5.2 → 2.5.6.
  • E2E: @playwright/test 1.61.1 → 1.62.0, ws 8.21.0 → 8.21.1, with a new spec asserting the global pause control.

🐛 Fixes

  • Servo capture clocks: frame_queue.rs sets emit_frame_timing and dispatches LightScriptFrameUpdate::TimingScript, keeping RAF-driven capture clocks advancing on quiet pages.
  • Screen capture startup: the CPU reducer waits for worker threads to initialize before probing fanout allocation.
  • Native sync durability: favorite and config updates acknowledge only after persistence commits, the exact installed snapshot is returned, and deleted tombstones survive favorite revisions.
  • Device vendor marks: vendors.rs tracks image load failures and falls back to a styled monogram instead of rendering a broken image.
  • WLED: the backend reports an OutputCadence derived from target FPS with with_max_frame_silence(KEEPALIVE_INTERVAL), and Windows tests retry delete-pending file locks.
  • UI presets: client-side matching heuristics in preset_matching.rs were deleted. Preset identity is now server-authoritative, with resolve_legacy_preset_id() migrating older bundled:* ids.
  • Cargo profiles: dependency debug symbols were restored, and the dev/preview profiles use line-tables-only for workspace crates. A new debugging profile opts into debug = "full".

📝 Documentation Site

  • Search loads the Elasticlunr library and index separately, recovers from transient index failures, derives section labels from the URL path, and restores focus to the trigger on close.
  • Mobile gained a working navigation surface via the nav-hamburger toggle, and the nav bar scrolls horizontally with a fade mask instead of clipping.
  • Code blocks use class-based theming with github-light and one-dark-pro, switching without a reload.
  • Heading anchors use a margin marker template (anchor-link.html) instead of an emoji, and the site now carries the app's brand assets, favicons, and motion tokens.
  • The Agents/MCP mermaid diagram renders, and docs images carry intrinsic width and height to prevent lazy-load reflow.

🔧 Build and CI

  • scripts/cargo-cache-lock.rs supervises build locks with signal forwarding and TTY handoff, keeping job control intact.
  • sccache normalizes checkout paths through SCCACHE_BASEDIRS so C/C++ artifacts are shared across worktrees.
  • just gained debug-build, build-wrapper-test, cargo-gc-test, and the gc* family; scripts/tests/ covers both the wrapper and the GC.
  • Docs-only merges no longer rebuild the compat, sdk, and ui-test matrices, and the Tauri bundle step shares one absolute target dir with the main build.
  • Unix release packaging pipelines are cached, and Rust 1.95 daemon lints are satisfied.

⚠️ Breaking Changes

  • Pause is no longer a stop. POST /api/v1/effects/pause preserves live state and holds outputs at the off frame. Clients that relied on pause tearing down the active effect must call the stop path explicitly, or read OutputPowerStatus::Stopped to distinguish the two.
  • Pause/resume response shapes changed. Responses are now PauseEffectResponse and ResumeEffectResponse. Pause adds off_output_behavior and off_output_color. The /api/v1/effects/pause and /api/v1/effects/resume paths remain as compatibility endpoints; /api/v1/output/power is the canonical surface.
  • Python pause_rendering() and resume_rendering() now return OutputPowerState instead of MutationResult. Update any code reading mutation fields off those calls.
  • Tray menu ids split. The single PAUSE_RESUME toggle became PAUSE_OUTPUT and RESUME_OUTPUT, and the command changed from TogglePause to SetPaused(bool).
  • UI preset matching removed. crates/hypercolor-ui/src/components/preset_matching.rs and its tests are gone. Consumers should read preset identity and active_preset_modified from the daemon.
  • PresetTemplate gains a required id field. Rust consumers constructing PresetTemplate literals must supply an id (use hypercolor_types::library::PresetId::stable(name)), and the generated Python preset_template model changed shape.
  • Duplicate bundled preset identities are rejected. Effect bundles shipping two presets that normalize to the same id fail to load instead of silently shadowing one another. The loader in crates/hypercolor-core/src/effect/loader.rs raises duplicate bundled preset id: ..., and install_effect() wraps it in ApiError::internal(...), so an upload to POST /api/v1/effects/install is rejected with a 500.

Upgrade Notes

  • Point pause/resume integrations at PUT /api/v1/output/power with {"state": "paused"} or {"state": "running"} and read status from GET /api/v1/output/power.
  • Regenerate any OpenAPI-derived clients: the schema adds the output tag, effect preset models, and active_preset_modified.
  • Migrate stored preset references from legacy bundled:* strings to the stable identifiers returned by GET /api/v1/effects/{id}/presets.
  • If you ship HTML effects, give each preset a preset-id and check that ids remain unique after whitespace normalization; validate flags collisions before upload, which is cheaper than debugging a failed install.
  • Callers that inspected atomic write results should handle `Replaceme...
Read more

Hypercolor 0.3.1

Choose a tag to compare

@github-actions github-actions released this 11 Aug 02:36

Release Notes v0.3.1

Released: 2026-08-11

Hypercolor becomes genuinely cross-platform in this release. Windows gets first-class host input capture and Desktop Duplication screen capture, effects can now react to your keyboard and mouse, and the daemon's state layer was rebuilt around transactional, fallible resource admission so a failed allocation retains the last good frame instead of blanking your lights. The web UI collapses to a single Studio workspace and finally works on a phone.

🌟 Highlights

⌨️ Interactive Effects: Keyboard and Mouse as Input Sources

Effects can now read live keyboard and mouse state. hypercolor-core/src/input/ gained an evdev host source on Linux (evdev.rs), a Raw Input source on Windows (windows.rs plus the new hypercolor-windows-input crate), and browser-preview injection (browser.rs) so the Studio canvas drives the same pipeline. The SDK exposes it through sdk/packages/core/src/input/ with getInputData() returning held keys, newly pressed keys, ordered KeyInputEvent/MouseInputEvent batches with capture timestamps, and an InputAvailability block. Keystrike (sdk/src/effects/keystrike/main.ts) ships as the showcase: chromatic ripples on keypress, shockwaves on click.

Capture is off by default and gated twice: consent ([input] enabled) and demand (only while an interactive effect is running). Key events were moved off the default-subscribed WebSocket events channel onto a control-tier route policy, so a passive dashboard client never sees keystrokes.

🖥️ Windows Desktop Duplication Capture

The new hypercolor-windows-capture crate captures the compositor's presented output through DXGI Desktop Duplication. It costs nothing on a static screen and needs no permission grant. Reduction runs on the GPU first via duplication/reduction.hlsl and duplication/gpu_reduction.rs with a three-slot staging ring and event-query polling; explicit failure classification falls back to the arbitrary-resolution CPU reducer in input/screen/reducer.rs. GET /api/v1/capture/monitors enumerates real monitors (index, stable source id, OS device name, dimensions, primary flag) so the settings dropdown lists your actual displays instead of guessing.

🔗 Portable Device Identity and Layout Re-binding

hypercolor-types/src/portable.rs introduces typed PortableIdentityClaim variants (USB serial, MAC address, Hue bridge id, Nanoleaf serial) that survive reboots and re-cabling. Only devices that can prove an identity get a portable key; SMBus deliberately has no constructor, because a bus location is not an identity. Pins persist in device-aliases.json, and the new binding endpoints heal orphaned layouts without editing them.

⚡ Hierarchical GPU Area Sampling

render_thread/sparkleflinger/gpu_area_sat.rs with area_sat.wgsl and area_hierarchy.wgsl replaces the fixed-ceiling readback path with a hierarchical summed-area scan. Readback now scales with LED count rather than canvas resolution, which is what removed the arbitrary canvas dimension limits across layout, preview, simulator, and config.

📱 One Studio, and It Works on a Phone

Studio is the only workspace now. The studio_ui_beta flag, the /displays, /layout, and /assets routes, and their page modules are gone. components/mobile_nav.rs adds a bottom tab bar below the 768px breakpoint with safe-area padding for notched phones, and settings was reorganized from nine flat tabs into four product-shaped ones with Advanced disclosures.

🎹 Input Pipeline

  • Linux (evdev): reads /dev/input/event* through udev uaccess, no group membership required. udev/70-hypercolor-input.rules is ordered before systemd seat-late rules and now ships in release payloads.
  • Windows (Raw Input): absolute pointer positioning, derived key repeat, and honest session-boundary detection. Raw Input cannot cross a session, so a daemon running as a service reports degradation instead of silently capturing nothing.
  • Routing: input/routing.rs and daemon/src/interaction_routing.rs add an explicit InteractionRoutePolicy (host, browser, merge) with authoritative browser route leases bound to source incarnation, so a reconnecting preview cannot steal another session's events.
  • Health: input/status.rs publishes per-source health (live, degraded, unavailable) with denied-resource counts. The daemon surfaces it on system status and the UI renders it through components/input_access_banner.rs, which offers a one-click consent toggle or the exact udev command when devices are denied.
  • Worker lifecycles are transactional across audio, evdev, media, screen, and Windows sources: partial startup rolls back every source, and timed-out workers are retained rather than leaked.

📺 Screen Capture

  • Arbitrary source and output resolutions throughout. Cadence and canvas ceilings were removed from config, layout, preview, and simulator; validation now checks real invariants instead of magic maximums.
  • Capacity is admitted transactionally through input/screen/admission.rs, with a lock-free ScreenCapacityStatusHandle publishing capture demand, analysis plans, and compute capacity. Steady-state and transition admission are separated so a settings change cannot double-charge the budget.
  • Wayland/PipeWire negotiates compositor-real formats to first light, stamps restore tokens with their session, and publishes exact CPU branches through the shared publication hub (input/screen/wayland.rs).
  • Letterbox detection no longer eats whole frames, downscales hold source aspect ratio, and canonical crop origins transform correctly.
  • Screen-mirroring effects are excluded from capture-of-self, and Screen Cast metadata is platform-neutral.

🔌 Devices, Persistence, and API

  • GET /api/v1/devices/bindings returns two halves of a re-bind decision: unresolved layout bindings no attached device resolves, and candidates attached devices no layout references.
  • POST /api/v1/devices/rebind re-pins a chosen device's portable key onto an orphaned binding's recorded identity. Returns 409 if the target device is currently live, and 422 if the device carries no portable identity claim (cross-driver inheritance is refused with 422 as well, before anything mutates).
  • Device settings schema v2 normalizes the key space to portable-key-first with local-fingerprint fallback. Migration is automatic and non-destructive: a device-settings.pre-v2.bak backup is written once, existing rows stay at their old keys until each device re-binds, and files from a newer schema load read-only rather than being overwritten.
  • New durable stores: driver-inventory.json (daemon/src/driver_inventory.rs) keeps driver-owned discovery hints such as learned network targets, persisted independently and cleared when a device is deleted. Deleting a device now also prunes its default faces and preferences.
  • Atomic state replacement: the new hypercolor-platform-fs crate replaces state files through MoveFileExW with MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH on Windows and validates destination filesystem identity to close a TOCTOU race. daemon/src/persistence.rs orders writers by generation with a retry supervisor and reports shutdown convergence, so a crash mid-write no longer truncates layouts or scenes.

🔀 WebSocket and Preview Transport

  • New binary frame tags carry u32 dimensions: 0x0B wide preview, 0x0C wide zone preview, 0x0D wide interactive preview, 0x0E wide screen zones, 0x11 extended screen zones.
  • Chunked transport arrives as 0x0F preview chunk and 0x10 preview cancel, letting large previews stream and be cancelled cleanly.
  • Clients negotiate preview_transport_v2 by fieldwise minimum against the peer's advertised budgets, falling back to preview_transport_v1. Reassembly, sender, and cursor state are each byte-bounded, and preview caches are bounded by resident bytes rather than frame count.
  • Interactive previews are connection-scoped and addressed by preview_id through api/ws/interactive_preview_relay.rs, with isolated render lanes in daemon/src/interactive_preview/.
  • Slow-consumer drops are now summarized instead of logged one line at a time.

🎨 Effects and SDK

  • Cover artwork for 40 bundled effects, embedded inline at build time by sdk/packages/core/src/tooling/build.ts and served by the daemon. The UI resolves cards by effect id instead of a guessed slug and falls back through daemon cover, cached client thumbnail, then category gradient. Artwork was raised to 960px for high-DPI cards.
  • Saved presets are promoted into the catalog, so a preset you save shows up where you look for effects.
  • The SDK build prunes retired artifacts after full builds, escapes Windows scaffold manifests correctly, and face-dev opens the devices page.
  • The bundled catalog is kept in one copy, and the effect loader probes cargo dev trees so cargo run finds it.

🛠️ Hardware and Driver Fixes

  • Push 2: MIDI pacing now applies to every output path and palette sysex is bounded to 16 writes per frame with nearest-neighbor fallback. Rawmidi goes nonblocking with a poll-based 1s deadline, so a wedged device surfaces as a timeout instead of hanging the lane.
  • SMBus: ENE indirect reads send their delays in-batch instead of fragmenting, moving the wait off the tokio tick. DRAM modules connect reliably and frame rates roughly double on dual-DIMM setups.
  • Windows PawnIO: SMBus polls stall rather than sleeping on the kernel timer, removing the ~60ms quantization that made polling unusable.
  • Hue: the connect deadline now budgets the entire bridge handshake (id, lights, entertainment, streaming, DTLS) and runs in the background, so a slow or absent bridge no longer stalls discovery.
  • Nanoleaf: external control streams to an injectable UDP port.
  • Windows sensors: CPU temperature re-probes on a 20s backoff ...
Read more

Hypercolor 0.2.1

Choose a tag to compare

@github-actions github-actions released this 15 Jul 07:49

Release Notes: Hypercolor 0.2.1

Released: 2026-07-15

Hypercolor is a Linux-first, open-source RGB lighting engine written in Rust. This is the project's initial public release: a complete, from-scratch build of every layer, from USB wire protocols to a GPU-accelerated render compositor to a reactive web UI.

🌟 Highlights

✨ SparkleFlinger Render Pipeline

A custom render-thread compositor that blends effect surfaces at 60 fps on a 640x480 RGBA canvas. Effects produce frames as independent surfaces; SparkleFlinger latches, crossfades, and composites them into one canonical frame per tick. GPU composition via wgpu is the default path on supported hardware, with a CPU fallback. Zone sampling maps the final canvas to LED positions using bilinear or Gaussian area sampling, with per-zone brightness control. Frame pacing uses adaptive admission control to hold steady cadence under load.

✨ 12 Native Hardware Driver Families

Native USB/HID and network drivers ship for 12 device families with 179 supported devices across 32 tracked vendors:

  • Razer (55 supported devices via zerocopy RazerReport)
  • Corsair (Lighting Node, iCUE LINK, LCD displays, Bragi peripherals)
  • ASUS Aura (USB HID and SMBus/I2C for DRAM DIMMs)
  • Lian Li (Uni Hub ENE, TL, and legacy protocols)
  • Nollie (Gen1, Gen2, NOS2, Legacy, Stream65 protocols)
  • PrismRGB (HID interrupt transport with dynamic topology)
  • Ableton Push 2 (MIDI control + bulk LCD display)
  • QMK (OpenRGB HID protocol for custom keyboards)
  • WLED (DDP/E1.31 network streaming)
  • Philips Hue (Entertainment API with DTLS streaming)
  • Nanoleaf (mDNS discovery, UDP panel streaming)
  • Govee (LAN multicast + cloud API with rate limiting)

Dygma (Focus protocol) and ROLI Blocks (blocksd bridge) have driver code present but no currently supported devices. An OpenRGB SDK bridge provides fallback coverage for hardware not yet natively supported.

Transport layers include hidraw, hidapi, SMBus/I2C, USB bulk, USB control, serial, MIDI, and network (UDP/TCP/DTLS). Device discovery runs in parallel with USB hotplug, mDNS, and multicast scanning.

✨ 44 Built-In Effects with TypeScript SDK

The @hypercolor/sdk npm package (sdk/packages/core/) provides a declarative effect() and canvas() API for authoring LED effects in TypeScript. 44 curated effects ship in sdk/src/effects/, spanning Canvas 2D, WebGL shaders, and audio-reactive compositions. Each effect declares typed controls (sliders, color pickers, dropdowns, sensor bindings) and curated presets. A create-hypercolor scaffolding CLI generates new effect workspaces.

Effects run inside an embedded Servo web engine with LightScript, a JS bridge that injects audio spectrum data, sensor readings, and keyboard/mouse input into effect pages each frame. GPU import (Linux via Vulkan/EGL, macOS via IOSurface, Windows via ANGLE/D3D11) pipes rendered frames directly into the wgpu compositor without CPU readback on supported platforms.

✨ Multi-Zone Scene Engine

Scenes assign independent effect stacks to named zones. Each zone gets its own render group, layer stack (effects, media, screen regions, web viewports), and spatial layout. Zones are created, renamed, and managed through the REST API and Studio UI. Layer composition supports normal, screen, additive, and difference blend modes. Scene state persists across restarts, and snapshot mode guards runtime scenes from accidental mutations.

✨ Display Faces for LCD Devices

Devices with built-in displays (Corsair LCD coolers, Ableton Push 2) can render HTML-based "faces" showing clocks, sensor gauges, now-playing info, spectrum visualizers, and system telemetry. The Face SDK (sdk/packages/core/src/faces/) provides typed accessors for display descriptors, sensor data, media playback state, and audio spectrum. Seven flagship faces ship: Neon Clock, Pulse Temp, Sensor Grid, SilkCircuit HUD, Now Playing, Spectrum, and System Pulse. Face composition supports blend modes so faces overlay the active effect on the device's screen.

✨ Cross-Platform Desktop App

A Tauri 2 desktop shell (hypercolor-app) wraps the Leptos WASM web UI and manages a supervised daemon process. The app provides a system tray with brightness presets, scene status, and diagnostics export. First-run setup handles autostart configuration and hardware support detection. Native installers ship for Linux (.deb, AUR PKGBUILD, systemd service), macOS (.dmg with hardened runtime), and Windows (NSIS per-machine installer with PawnIO SMBus broker for motherboard RGB and ANGLE EGL for Servo GPU import).

🤖 Render Pipeline and Performance

  • SparkleFlinger renders through CPU or GPU backends, selected at startup based on adapter probes. GPU path uses wgpu compute shaders for blend, sample, and preview-scale passes.
  • Deferred GPU zone sampling overlaps readback with frame output push, measured with dual-slot readback buffers and sample wait metrics.
  • Frame pacing uses a ring-buffer admission history with percentile-based tier promotion/demotion.
  • Display output workers run per-device with paced queues, write retry on failure, and fuzzy frame dedup for WLED.
  • Preview surfaces publish on demand: WebSocket relays encode RGBA or JPEG frames scaled to subscriber resolution. Loopback clients get raw RGBA; remote clients get JPEG at negotiated sizes.
  • Servo renderer sessions run on a dedicated worker thread. Host-driven frame clocking replaced JS animation loops, giving the daemon control of effect cadence. Soft stall detection and circuit breakers retire unhealthy Servo sessions.

🔧 API and Integration

  • REST API with typed envelope responses, OpenAPI spec generation (/api/openapi.json), tiered auth (loopback read-only, API key for writes, network access controls), rate limiting, and CORS binding.
  • WebSocket protocol with typed channels for canvas frames, spectrum data, device metrics, scene events, and control surface mutations. Binary frame encoding uses channel-tagged headers.
  • MCP server for AI agent control with prompts, resources, and tool endpoints covering effects, devices, scenes, displays, and system diagnostics.
  • Python client (hypercolor on PyPI) with OpenAPI-generated operations, typed models, WebSocket event streaming, and Home Assistant helpers.
  • CLI (hypercolor) with SilkCircuit-themed help, connection profiles, shell completions, and full API coverage including scene, zone, driver, and control surface commands. Embeds the TUI as hypercolor tui.

🎨 UI and Developer Experience

  • Web UI built with Leptos 0.8 CSR + Tailwind CSS v4, served by the daemon. Pages: Dashboard (performance telemetry waterfall, gauges, favorites), Effects (browse, apply, control with presets), Studio (multi-zone composition with zone tree, layer panel, and per-zone canvas preview), Devices (brand cards with vendor logos for 25 manufacturers, per-device metrics, driver control surfaces), Displays (face picker, blend composition, JPEG preview), Layout (spatial editor with undo/redo, compound selection, auto-layout), Settings, and Media (asset library with drag-and-drop upload).
  • TUI (hypercolor-tui) built with Ratatui, featuring 60fps terminal canvas preview via Kitty/Sixel/quarterblock protocols, spectrum-reactive border pulse, ambient canvas bleed, effect transition crossfades, idle breathing animation, resizable split panels, mouse interaction, and multi-zone/multi-scene support.
  • Luminary design system with semantic CSS custom property tokens, light/dark themes, ambient hue extraction from the live canvas, animated glow elevation, and SilkCircuit brand gradient accents.
  • justfile with recipes for daemon, UI, TUI, SDK, effect builds, GPU mode, and cross-platform setup scripts.

🐛 Hardware and Device Management

  • Device lifecycle state machine with connect/disconnect/reconnect, capped retry backoff, and keepalive for Razer and Corsair iCUE LINK devices.
  • Non-blocking HAL dispatch with parallel discovery scans across USB, SMBus, and network backends.
  • Per-device brightness control (global and per-zone), frame output acknowledgements with EWMA delivery rate tracking, and LED priority over display writes for USB devices.
  • 54 auto-layout attachment profiles for Corsair, Lian Li, generic strips/fans/matrices, Nollie, and Formulamod components.
  • Session and power awareness: systemd-logind integration, screensaver detection, configurable off-output behavior, and Windows sleep/resume rediscovery.
  • Extensible driver module registry (hypercolor-driver-api) with typed control surfaces, pairing flows, config enablement, and hot-reload on control value changes.
  • zerocopy typed protocol structs for Razer, Corsair Lighting Node, ASUS Aura, PrismRGB, Corsair LCD, Razer Seiren V3, and ROLI Blocks.

⚡ Input Sources

  • Audio: PulseAudio native capture on Linux, FFT spectrum analysis, beat detection, transient gating, and live input switching. Audio data feeds into effects through the LightScript bridge.
  • Screen capture: Wayland portal-based capture with sector grid, color tuning, and ambilight zone routing. A dedicated ambilight edge-projection effect ships in the SDK.
  • Sensors: CPU/GPU temperature, fan speeds, and NVML metrics. Windows reads via PawnIO MSR/SMN kernel modules. Sensor bindings map sensor values to effect controls.
  • MPRIS: Now-playing metadata with bounded album art for display faces.
  • Keyboard/mouse: evdev input capture for interactive HTML effects.

📝 Documentation and Packaging

  • Full Zola documentation site with Luminary theme, covering installation, quick start, hardware compatibility, Studio multi-zone walkthrough, effect authoring (TypeScript, GLSL, Canvas 2D, raw HTML, native Rust, display faces), API reference (REST, WebSocket, MCP, CLI, Python), architecture internals, and contributing guides.
  • Automated release pipeline: `scripts/dist...
Read more