Whole-project review pass over the entire crate. No new features.
All function definitions preserved; layout and visibility reorganised.
Bug fixes:
- bubble: ExtractIconExW HICON pair was leaked per bubble toggle.
Extract once into a process-wide OnceLock, reuse forever (bounded).
- usage/anthropic: parse_iso8601 was stripping the UTC offset without
applying it — negative-offset users saw countdowns up to 14h wrong.
Now parses signed minutes and computes utc_secs = local - off*60.
Also rejects y<1970, mo∉[1,12], d∉[1,max_day(mo,y)] up front so
malformed API responses can't index DAYS_IN_MONTH out of bounds.
- usage: clamp utilization to [0,100] at all four Window construction
sites so a misbehaving server can't render "121%".
- bubble: GetDC and CreateCompatibleDC results weren't checked. Guard
both; release the screen DC on the CreateCompatibleDC failure path.
Refactor:
- Drop type TrayIconKind = ProviderId aliases (5 sites); use ProviderId
directly everywhere. Inline the identity-function kind_to_provider.
- Delete panel::bar_color_for shim (was just argument-reorder glue).
- Replace local scale_to_dpi fns in bubble.rs and panel.rs with
use crate::os::dpi::scale as scale_to_dpi (brings os::dpi into the
live import graph; was unused before).
- Delete dead PCWSTR import + #[allow(dead_code)] sentinel in
tray/badge.rs; fold the trailing `use BOOL` into the top imports.
- Inline app::primary_dpi() to crate::os::dpi::for_system().
app.rs:
- Add update_settings(|s: &mut AppState|) helper that locks state,
runs the closure, snapshots Settings, drops the lock, then saves
to disk. Convert four pure-mutate-then-save callsites.
Layering: bubble.rs no longer reaches upward into crate::app::.
Introduce bubble::Callbacks (fn-pointers), OnceLock<Callbacks>, and
bubble::install_callbacks(). The wnd_proc dispatches the six prior
upward calls via a private dispatch() helper. app::run installs
callbacks once at startup; the six on_bubble_* / recheck_theme fns
are demoted from pub fn to fn.
Resource-warning logs added: dispatch() warns on uninstalled
callbacks; app_icons() warns when ExtractIconExW returns nulls.
Build: cargo build --release clean; cargo clippy reports zero new
warnings (11 pre-existing, all in untouched code).
Bubble shape changes from circle to rounded rectangle showing two stacked
horizontal bars — top: session (5h), bottom: weekly (7d) — each followed
by a right-aligned "X% · Yh Zm" string (percent + countdown).
Bubble surface:
- BubbleConfig/BubbleState carry session+weekly percents and texts (mirrors
PanelData); update_percentage renamed to update_data
- Aspect ratio fixed at 3:1; size_logical is interpreted as width with
height derived. Clamp is 120..360 (was 32..128 square)
- Hit-testing uses a rounded-rect predicate (point_in_rounded_rect) shared
with the alpha mask so paint and click area can't drift
- New rgb_to_dib helper for direct DIB writes — BI_RGB 32bpp stores B,G,R,X
in memory which is the opposite of COLORREF. The previous code wrote
COLORREF-packed u32 straight into DIB pixels; invisible while every color
was gray, but the new orange/red bar fills would have rendered blue
- bar_h capped at h/4 (range 6..18) so the text font derived from it stays
small enough that "100% · 23h" fits in right_text_w (= 6×bar_h, min 56);
the first iteration had a 19-px font in a 60-px column and ellipsized
away the countdown
- Initial session_text/weekly_text seeded with "…" so the bubble has
visible feedback during the first poll instead of two empty grey tracks
Compile + cleanup needed to make the port build at all:
- Color::from_hex added back as an infallible wrapper around parse_hex
(15 call sites in bubble.rs/panel.rs assumed the old infallible API)
- Color::to_colorref → into_colorref at 5 call sites
- GetModuleFileNameW added to the LibraryLoader import in bubble.rs
- usage::Error gains Creds(#[from] creds::Error) so `?` works in the
Anthropic and ChatGPT providers
- FlattenBoxed::flatten renamed to flatten_box — the std Option::flatten
was shadowing it and yielding Option<Box<T>> instead of Option<T>
- PanelState marked unsafe Send (HWND has *mut c_void; state is only
touched from the UI thread, Mutex is for OnceLock satisfaction)
- Crate-level #![allow(dead_code)] for in-progress port API surface
(creds, usage, update, os::dpi); unused pub-use re-exports removed
App wiring:
- propagate_to_ui now feeds both windows + their formatted texts into
update_data (was a single percent)