diff --git a/CLAUDE.md b/CLAUDE.md index f2f7e83..ffb9ff6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -62,6 +62,7 @@ Data flow at runtime: timers poll `/api/now-playing`, `/api/state`, `/api/themes - **A `/` in a scene window title crashes GTK at app_start on Linux.** Keep the branded "SUB/WAVE" slash out of `app.zon` / `shell_windows` titles; it's fine in `runWithOptions.window_title`. - **The SDK cannot resize a live window.** Per-mode window shapes apply at next launch only. +- **A `hidden_inset_tall` masthead must pad `insets.left` AND `insets.right`.** The window-control cluster sits on a different edge per platform — macOS traffic lights lead, Windows min/max/close trail — so `onChrome` maps both into `chrome_leading` / `chrome_trailing` and `player-top.native` spacers both ends. Dropping the trailing one put the gear button under the DWM caption buttons on Windows, and fed the host's per-present caption-colour sampler (which reads the pixel 8px leading of the cluster and pushes it through `DWMWA_CAPTION_COLOR` + `USE_IMMERSIVE_DARK_MODE`) an antialiased glyph edge instead of flat header — the caption buttons flickered. - **The FFT spectrum feed only emits while a window is visibly on screen** (occlusion gate in the SDK). A flat visualizer from an app launched in the background is not a bug — activate the app first. - SDK 0.8.4 still has **no OS media-controls surface** (no MPNowPlayingInfoCenter / MPRemoteCommandCenter / MPRIS / hardware media keys — re-checked at the 0.8.4 upgrade). The substitutes are the tray extra, the in-window keyboard transport, and the background track toast: 0.8.4 added `fx.showNotification`, so a track change while the app is backgrounded posts a desktop notification. It is opt-in (back panel → NOTIFICATIONS) and the whole decision lives in `Model.shouldNotifyTrack` — pure, because the effect is inert and unrecorded under fake execution, so no test can observe the call itself. - SDK 0.8.4 also has **no audio output-device API** — the platform seam is load/play/pause/stop/seek/volume, with no enumeration and no device property, so there is no in-app "play through these speakers" picker to build. Route it at the OS (`pavucontrol`/PipeWire, Windows volume mixer; macOS has nothing native). The upstream request lives in `docs/sdk-audio-device-request.md`. diff --git a/src/main.zig b/src/main.zig index 548053d..32c1308 100644 --- a/src/main.zig +++ b/src/main.zig @@ -163,12 +163,18 @@ fn onLifecycle(event: native_sdk.LifecycleEvent) ?Msg { }; } -// Hidden-titlebar chrome geometry → model (masthead pads around the traffic -// lights). All-zero on standard chrome / fullscreen / non-macOS. +// Hidden-titlebar chrome geometry → model (the masthead pads around the window +// controls). BOTH horizontal edges are mapped because the cluster sits on a +// different one per platform: macOS puts the traffic lights left, Windows puts +// min/max/close right. Dropping `right` left the gear button underneath the DWM +// caption buttons on Windows, which also fed the host's caption-colour sampler +// a glyph edge instead of flat header — see CLAUDE.md's gotchas. All-zero on +// standard chrome and in fullscreen. fn onChrome(chrome: native_sdk.platform.WindowChrome) ?Msg { return Msg{ .chrome_changed = .{ .top = chrome.insets.top, .leading = chrome.insets.left, + .trailing = chrome.insets.right, } }; } diff --git a/src/model.zig b/src/model.zig index cf31567..c9bdf53 100644 --- a/src/model.zig +++ b/src/model.zig @@ -165,10 +165,14 @@ pub const DayTab = struct { label: []const u8, }; -/// Payload for chrome_changed (hidden-titlebar insets, macOS). +/// Payload for chrome_changed (hidden-titlebar insets). The window-control +/// cluster lands on the edge its platform puts it on — macOS reports the +/// traffic lights as `leading`, Windows reports min/max/close as `trailing` — +/// so the masthead pads BOTH and the unused edge is honestly zero. pub const ChromeInsets = struct { top: f32 = 0, leading: f32 = 0, + trailing: f32 = 0, }; // ------------------------------------------------------------------ model @@ -317,9 +321,11 @@ pub const Model = struct { sheet: Sheet = .none, mini_open: bool = false, - // hidden-titlebar chrome insets (macOS traffic lights) + // hidden-titlebar chrome insets (macOS traffic lights lead, Windows + // min/max/close trails) chrome_top: f32 = 0, chrome_leading: f32 = 0, + chrome_trailing: f32 = 0, // sleep timer (wall-clock deadline; 0 = off) sleep_deadline_ms: i64 = 0, @@ -2731,6 +2737,7 @@ pub fn update(model: *Model, msg: Msg, fx: *Effects) void { .chrome_changed => |c| { model.chrome_top = c.top; model.chrome_leading = c.leading; + model.chrome_trailing = c.trailing; }, // ------------------------------------------------------ request slip @@ -4357,6 +4364,24 @@ test "update check: newer tag arms the notice, older clears it, failures keep st try testing.expect(m.update_available()); } +test "chrome insets land on both edges, so the masthead clears controls on any platform" { + var fx = Effects.init(testing.allocator); + defer fx.deinit(); + fx.executor = .fake; + var m: Model = .{}; + // macOS shape: traffic lights lead, nothing trails. + update(&m, .{ .chrome_changed = .{ .top = 52, .leading = 78, .trailing = 0 } }, &fx); + try testing.expectEqual(@as(f32, 52), m.chrome_top); + try testing.expectEqual(@as(f32, 78), m.chrome_leading); + try testing.expectEqual(@as(f32, 0), m.chrome_trailing); + // Windows shape: the min/max/close cluster trails instead. Dropping it is + // what put the gear button under the DWM caption buttons, whose colour the + // host samples from the pixel just leading of the cluster every present. + update(&m, .{ .chrome_changed = .{ .top = 32, .leading = 0, .trailing = 138 } }, &fx); + try testing.expectEqual(@as(f32, 0), m.chrome_leading); + try testing.expectEqual(@as(f32, 138), m.chrome_trailing); +} + test "log_dir_value reports the diagnostic log folder" { const m: Model = .{}; // Unopened in the suite, so it reads empty rather than crashing. diff --git a/src/views/player-top.native b/src/views/player-top.native index 0cb849e..73fb430 100644 --- a/src/views/player-top.native +++ b/src/views/player-top.native @@ -25,6 +25,12 @@