Conversation
The status reply was built by one 18-field format! in the daemon and
re-parsed by two hand-rolled readers (tray parse_status, gui
brief_status); field order was load-bearing in three places. Now
hyprlay-core::status::StatusFields owns the line in both directions:
the daemon fills the struct and sends to_wire(), the tray and GUI read
through parse_wire(), and the three liveness probes use is_status_line.
Appending a field is a one-place edit, and readers survive it by
construction (ADR-002's append-at-end convention made structural).
The wire line is byte-identical to the old format! output - proven by
the untouched tray/GUI/daemon characterization suite plus a new
full-line pin (the first the reply ever had).
Deliberate deviations from bug-for-bug, each unreachable from what the
current daemon writes (displayed().len(), channel_name().unwrap_or("-"),
ConnectionStatus::as_str):
- participants=abc parses as 0; the old tray echoed the raw token
- the tray summary shows the parsed count even when the participants
token was empty (old: no count)
- brief_status drops the trailing separator for an empty channel value
Marker lookups require a leading space (" participants=" etc.), a
slight tightening over the old find("participants=") so a channel name
embedding a marker cannot be misread. Field types follow config.rs
(u8 scale, i32 offsets) rather than the earlier sketch (u16/i16) to
avoid lossy casts.
ADR-002's Consequences now point the convention at StatusFields;
docs/dev/code-layout.md gains the status.rs row.
refactor: build and parse the status reply in one shared type
The status reply's connection word was a bare String on both sides of the wire contract: the daemon filled it from ConnectionStatus::to_string() and readers took whatever token came off the line. The write side is now sealed behind a StatusWord newtype whose only public constructor is From<ConnectionStatus>, so no bare string can reach the wire and the word set cannot drift from the enum's Display spelling. The read side keeps the reader lenient bug-for-bug: parse_wire() still fills the word from the raw token, unknown words included (a newer daemon may already speak words this build has never seen, ADR-002 forward compat), and Display renders the stored word unchanged - which is what keeps to_wire() byte-identical. The tray's up/down comparison and the GUI's brief summary read through as_str(). The wire line is byte-identical: the untouched full-line pin and the roundtrip pin pass, and two new pins lock the two halves of the contract (unknown word survives verbatim; the write side accepts only the sealed vocabulary). The type change is visible through the shared StatusFields field, so the tray/GUI readers adapt mechanically (no behavior change) - the same core-plus-fronts span as the C1 wire-contract commit, hence no scope.
hyprlay-core now owns a typed ServiceError for the DaemonControl /
ServiceManager port contract. Every variant transcribes its old
format! string byte-for-byte, so the observable wording on the
GUI/tray status lines and the CLI's stderr is unchanged; per-variant
Display pins in daemon_control lock that wording.
- crates/hyprlay-core/src/daemon_control.rs: ServiceError enum (20
variants, thiserror 2), port signatures (perform, systemctl,
spawn_daemon, quit_via_socket, install/uninstall with typed Err),
execute_toggle renders through Display, and the Display/source
characterization pins.
- src/platform/service/{systemd,launchd,windows}.rs: adapters
construct ServiceError at every failure site; launchd's
"already bootstrapped" tolerance still string-matches on the
rendered Display.
- src/platform/service/mod.rs: install_service/uninstall_service
return ServiceError for all three cfg-gated variants.
- tests/install.rs Spy and the GUI/tray FakeControl doubles adapt
mechanically (failures become ServiceError constructors);
assertions stay byte-identical.
thiserror 2.0.20 was already in Cargo.lock via iced, so the new
hyprlay-core dependency adds no new download.
refactor: type the status word and service errors
CachedUser duplicated Participant's seven persisted fields and was kept in sync by two hand-copied From impls; roster_signature enumerated the fields a third time, so a field added to one list silently broke write dedup. Participant now carries the serde derives itself (speaking is #[serde(skip)]: live-only state, filled with false on load, exactly the old conversion semantics), Roster.users holds it directly, and roster_signature is built from serde output so the signature and the roster.json format can no longer disagree. Cache bytes are unchanged; pins cover the legacy file format both ways and the per-field signature sensitivity.
refactor(daemon): derive the roster cache from the Participant type
The three service backends repeated the same two fs sequences byte for byte: create-parent + write (four copies under four names: systemd's write_file/write_bytes, launchd's write_plist, windows' write_script) and the reported remove (three identical remove_reported bodies). Both now live once in service/fs_util, taking &[u8]; string call sites pass .as_bytes(). Error variants, payloads, and report strings are unchanged, so the install.rs byte and report pins pass unmodified and windows.rs stays compile-only-verified.
refactor(platform): dedupe the service write/remove helpers into fs_util
mod.rs was a 1449-line grab-bag holding every TEA layer at once; each concern now has exactly one home. update.rs carries the one flat update match (the app's dispatch table, deliberately not per-domain), shortcut, and the async toggle/auth effects. commands.rs owns Message→Command translation plus the shared bookkeeping (mark_dirty, num_in_bounds, revert_commands). scroll.rs owns one-page navigation: the measure operation, section jumps, scrollspy, and the two shared widget ids. view.rs owns the window composition (header, sidebar, status bar). mod.rs keeps the shell: Message, Gui, run, boot, subscribe, window settings, and the blocking send wrapper. Behavior-identical: every moved body is byte-verbatim (verified by mechanical extraction diff against the old file, modulo pub(super) visibility, rustfmt line-wrapping, and forced test-path edits). All 21 tests moved with their subjects — registry/section pins to fields.rs, command/revert pins to commands.rs, scrollspy pins to scroll.rs, brief_status pins to view.rs, navigation state-transition pins to update.rs — pins semantically identical. Visibility is minimal: Message and Gui stay private in mod.rs (descendant access is free by Rust privacy; the compiler proves it); pub(super) only on the items a sibling or the parent actually calls. picker.rs's update import now names the function inside the new update module.
refactor(gui): split the app core into update/commands/scroll/view
Hunting the daemon memory regression needs a series, not a btop glance: scripts/rssprobe.sh samples VmRSS plus the VmHWM high-water mark of a PID on an interval into a CSV, with labeled scenario markers appended from a second shell (the mark subcommand). RSS far below HWM means freed-but-retained memory; RSS at HWM means real growth — the doc explains reading that pair and carries the runbook for the hyprlayd scenario series (idle, hover-storm, VC churn). While in docs/dev/debug-probes.md, fix two stale adapter paths (src/adapters/ipc.rs and src/adapters/discord.rs moved under src/daemon/adapters/).
cmd_demo exec'd target/release/tomo --config-path /tmp/tomo-demo — a binary and flag from the project-ops template this script was copied from. No tomo exists here, and scripts/demo.tape never did, so the target could not run. hyprlay has no tape-driven demo to repoint it at (the surface is a Wayland overlay, not a terminal session), so remove the command — function, description, and help/usage mentions — instead of shipping a dead target.
cargo install without --locked re-resolves and ignores Cargo.lock. winit-core 0.31.0-beta.3 added NativeKeyCode::Android, which iced_exdevtools 0.19.1 (unconditional dep of iced_layershell 0.19.1, pinned to the 0.19 line) does not match: E0004. Pin winit-core exactly so every resolution path lands on beta.2, and document --locked in the README install line. Drop the pin once iced_layershell releases on iced_exdevtools 0.20.
fix: pin winit-core for lockless installs, add the rssprobe sampler
feat: regenerate all brand assets from the circular-H icon
Release PreviewNext version: 0.3.1 (2026-09-11)Platforms
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Promotes the v0.3.1 release line to main.
Highlights
Contains the user's direct v0.3.1 commits (e.g. gitignore).