refactor: move settings, strings, colours and geometry into a portable core

Settings, the English strings, the colour ramp and the bubble geometry no
longer touch Win32 types, so they compile and test on every platform.
Snapping, clamping, default placement, panel placement and the monitor
probe take plain rects; settings loading is split into parsing and an
offscreen check fed with the monitor list. ProviderId now serialises as
its slug.
This commit is contained in:
tiennm99 committed 2026-10-21 12:23:15 +07:00
1 parent d8f339208d
commit bc1a098875
12 files changed
+1573 -418

No files matched your search

+3 -34
View File
@@ -1,38 +1,7 @@
// Usage subsystem: trait + per-provider implementations + registry.
//
// Phase 2 stubs out only `types`. Phase 4 fills in `anthropic`, `chatgpt`,
// `refresh`, `registry`, and `headers`. The trait below is the contract
// every provider must satisfy.
// Usage subsystem. Only the shared data shapes are compiled for now; the
// providers, registry, headers and refresh orchestrator return with the
// HTTP client port.
pub mod anthropic;
pub mod chatgpt;
pub mod headers;
pub mod refresh;
pub mod registry;
pub mod types;
pub use registry::Registry;
pub use types::{ProviderId, UsageWindows, Window};
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("authentication required")]
AuthRequired,
#[error("no credentials configured")]
NoCredentials,
#[error("token expired after refresh attempt")]
TokenExpired,
#[error("network: {0}")]
Network(#[from] crate::net::Error),
#[error("credentials: {0}")]
Creds(#[from] crate::creds::Error),
#[error("unexpected response shape: {0}")]
BadResponse(String),
}
/// Every provider exposes a stable identity and a sync `poll` that performs
/// HTTP calls against the supplied client.
pub trait UsageProvider: Send {
fn id(&self) -> ProviderId;
fn poll(&mut self, http: &crate::net::Client) -> Result<UsageWindows, Error>;
}
+29 -1
View File
@@ -2,10 +2,17 @@
use std::time::SystemTime;
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
use serde::Serialize;
/// Serialises as its [`slug`](ProviderId::slug), which also names the
/// provider's bubble window and tray icon.
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize)]
pub enum ProviderId {
#[serde(rename = "claude")]
Claude,
#[serde(rename = "chatgpt")]
ChatGpt,
#[serde(rename = "opencode-go")]
OpenCodeGo,
}
@@ -21,6 +28,11 @@ impl ProviderId {
}
}
/// Inverse of [`slug`](ProviderId::slug).
pub fn from_slug(slug: &str) -> Option<Self> {
Self::ALL.into_iter().find(|p| p.slug() == slug)
}
pub fn metadata(self) -> ProviderMetadata {
match self {
Self::Claude => ProviderMetadata {
@@ -87,3 +99,19 @@ pub struct ProviderSnapshot {
pub id: ProviderId,
pub windows: UsageWindows,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serialises_as_slug_and_parses_back() {
for provider in ProviderId::ALL {
let json = serde_json::to_string(&provider).expect("serialise");
assert_eq!(json, format!("\"{}\"", provider.slug()));
assert_eq!(ProviderId::from_slug(provider.slug()), Some(provider));
assert_eq!(provider.metadata().slug, provider.slug());
}
assert_eq!(ProviderId::from_slug("codex"), None);
}
}