Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## [Ceiling] Unreleased

### Fixed
- **Signing out of StepFun no longer leaves a live Oasis token in the keyring.** A successful refresh wrote the new token to the OS keyring (`codexbar-stepfun` / `api_key`), and Revoke stored credentials only cleared Preferences, cookies, and token-accounts. The next fetch then read the leftover and stayed signed in. Revoke now asks the provider to delete that copy. A keyring error fails the revoke instead of reporting success while the token remains.
- **Reset countdowns agree at a remaining day and in the last minute.** The CLI statusline used `hours > 24`, so 24h 10m stayed "24h 10m" while the tray and the TypeScript hooks already said "1d 0h". The native tooltip and taskbar strip floored a still-future 30s remainder to "0m", the stuck-timer leftover SBS-621 fixed in the hooks. Every reset surface now floors one total of minutes, clamps a sub-minute remainder to 1, and cuts a day at 1440 minutes. A user looking at the tray, the CLI, and the tooltip at 24h 1s sees "1d 0h" (or "Resets in 1d" in the locale sentence); at 30s they see "1m", never "0m".
- **A failed update check no longer tells you that you are current.** Checking for updates treated a GitHub outage, a rate-limit, or an unreadable release payload the same as "no newer release", so About said you were up to date. Only a successful "latest is not newer" is Idle now. Failures, including the existing 15s timeout, are Error, and About shows that the check could not run. A second check after a download is ready no longer clears Install & Restart.
- **Charts opens in a couple of seconds instead of half a minute.** On a machine holding gigabytes of Codex and Claude transcripts, opening Charts started three separate walks of the same logs at once, and each one read every file from the top: Estimated API value scanned ninety days when the furthest period it shows reaches back sixty, the activity heatmap scanned thirty, and the provider charts scanned again on top. Nothing was kept, so switching tabs paid for all of it again, and clicking Yesterday or 30 days re-ran a full scan for numbers the card already had in hand. Each transcript is now parsed once and its records are kept in a small index beside your settings; a file that grew since is resumed from where the last read stopped rather than re-read from the start, and a file that was replaced rather than appended to is read again in full. Providers are scanned at the same time instead of one after another, both cards keep their last result on disk so a restart is not a cold start, and the work runs in the background shortly after launch for anyone who opens these cards, so the wait lands where nobody is watching it. The numbers are unchanged: an indexed scan is checked against a full re-parse, and the index is discarded outright if model prices move, since it stores the dollars they produced.
Expand Down
18 changes: 18 additions & 0 deletions rust/src/providers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,18 @@ pub(crate) fn resolve_api_key(
)))
}

/// Delete provider-owned copies of live tokens that survive the shared
/// credential files. `revoke_managed_credentials` calls this so settings does
/// not hardcode a vendor. Today only StepFun writes a refreshed token to the
/// OS keyring (SBS-920). Other providers only *read* keyring entries the user
/// or another app placed there; those are left alone.
pub(crate) fn clear_persisted_credentials(provider: crate::core::ProviderId) -> anyhow::Result<()> {
match provider {
crate::core::ProviderId::StepFun => stepfun::clear_persisted_credentials(),
_ => Ok(()),
}
}

/// Whether a parsed URL points at the local machine.
///
/// Host-based, never prefix-based: `http://localhost@evil.example` and
Expand Down Expand Up @@ -229,4 +241,10 @@ mod browser_cookie_policy_tests {
Err(crate::core::ProviderError::NoCookies)
));
}

#[test]
fn clear_persisted_credentials_is_noop_for_providers_without_a_shadow_store() {
assert!(clear_persisted_credentials(crate::core::ProviderId::Claude).is_ok());
assert!(clear_persisted_credentials(crate::core::ProviderId::Codex).is_ok());
}
}
Loading
Loading