@@ -94,7 +94,7 @@ let pendingLoginRetry: (() => Promise<void>) | null = null;
9494let cancelledLoginProfile : string | null = null ;
9595
9696function isRefreshPending ( profile : string ) : boolean {
97- return state . refreshActiveProfile === profile || state . refreshQueue . includes ( profile ) ;
97+ return state . refreshActiveProfiles . includes ( profile ) ;
9898}
9999
100100function clearDialogError ( element : HTMLParagraphElement ) : void {
@@ -261,71 +261,30 @@ async function handleSwitchProfile(profile: string): Promise<void> {
261261 }
262262}
263263
264- async function drainRefreshQueue ( ) : Promise < void > {
265- if ( state . refreshWorkerActive ) {
266- return ;
267- }
268-
269- state . refreshWorkerActive = true ;
264+ async function performProfileRefresh ( profile : string ) : Promise < void > {
265+ state . refreshActiveProfiles . push ( profile ) ;
266+ rerenderDashboard ( ) ;
270267 try {
271- while ( state . refreshQueue . length > 0 ) {
272- const profile = state . refreshQueue . shift ( ) ;
273- if ( ! profile ) {
274- continue ;
275- }
276-
277- state . refreshActiveProfile = profile ;
278- rerenderDashboard ( ) ;
279- try {
280- await refreshProfile ( profile ) ;
281- showToast ( t ( state . locale , "refreshedProfile" , { profile } ) ) ;
282- // The backend already wrote the new quota / plan into the
283- // profiles index. Re-reading the snapshot picks those up
284- // for every card without paying for a JSONL scan.
285- //
286- // `getCurrentLiveQuota` only matters when the refreshed
287- // profile is also the active one — the live JSONL session
288- // count can be newer than the API value we just persisted
289- // (an in-flight `codex` session keeps appending
290- // `token_count` events) and `select_current_quota` picks
291- // the newer of the two. For non-active refreshes we skip
292- // it; the active card panel for that profile is rebuilt
293- // when the user switches to it, and the 15s ticker keeps
294- // the panel honest in the meantime.
295- try {
296- const snapshot = await getProfilesSnapshot ( ) ;
297- applySnapshot ( snapshot ) ;
298- if ( snapshot . current_card ?. folder_name === profile ) {
299- applyCurrentQuota ( await getCurrentLiveQuota ( ) ) ;
300- }
301- } catch ( error ) {
302- // Best-effort: a transient snapshot fetch failure leaves
303- // the cards on their pre-refresh state, which matches the
304- // pre-PR `refreshAllData(false)` behavior. Surface only
305- // to the console so a systematic failure is debuggable
306- // without spamming the user with a toast they can't act
307- // on.
308- console . warn ( "Snapshot refresh after profile refresh failed:" , error ) ;
309- }
310- } catch ( error ) {
311- showToast ( refreshProfileErrorMessage ( error ) , true ) ;
312- } finally {
313- state . refreshActiveProfile = null ;
314- rerenderDashboard ( ) ;
268+ await refreshProfile ( profile ) ;
269+ showToast ( t ( state . locale , "refreshedProfile" , { profile } ) ) ;
270+ try {
271+ const snapshot = await getProfilesSnapshot ( ) ;
272+ applySnapshot ( snapshot ) ;
273+ if ( snapshot . current_card ?. folder_name === profile ) {
274+ applyCurrentQuota ( await getCurrentLiveQuota ( ) ) ;
315275 }
276+ } catch ( error ) {
277+ console . warn ( "Snapshot refresh after profile refresh failed:" , error ) ;
316278 }
279+ } catch ( error ) {
280+ showToast ( refreshProfileErrorMessage ( error ) , true ) ;
317281 } finally {
318- state . refreshWorkerActive = false ;
282+ state . refreshActiveProfiles = state . refreshActiveProfiles . filter ( p => p !== profile ) ;
319283 rerenderDashboard ( ) ;
320284 }
321285}
322286
323287function handleRefreshProfile ( profile : string ) : void {
324- // Mirror `handleLoginProfile`'s `isRefreshPending(profile)` guard in
325- // the opposite direction: when the same profile already has a login
326- // in flight, both flows would otherwise race on writing per-profile
327- // `auth.json`. Cross-profile refresh during a login is still allowed
328- // (different sandbox + different `auth.json`).
329288 if (
330289 state . loading
331290 || state . loginActiveProfile === profile
@@ -334,9 +293,7 @@ function handleRefreshProfile(profile: string): void {
334293 return ;
335294 }
336295
337- state . refreshQueue . push ( profile ) ;
338- rerenderDashboard ( ) ;
339- void drainRefreshQueue ( ) ;
296+ void performProfileRefresh ( profile ) ;
340297}
341298
342299function loginErrorCode ( error : unknown ) : string | undefined {
@@ -1007,6 +964,12 @@ export function bootstrap(): void {
1007964 void refreshActiveQuotaSilently ( ) ;
1008965 } , 5 * 60_000 ) ;
1009966
967+ // Relative countdown timer tick: rerender the dashboard every 15 seconds
968+ // to update the remaining relative countdown times.
969+ window . setInterval ( ( ) => {
970+ rerenderDashboard ( ) ;
971+ } , 15_000 ) ;
972+
1010973 // Bulk plan refresh: forces an OAuth refresh on every OAuth profile so
1011974 // the cached id_token claims (plan tier, subscription expiry) move
1012975 // forward even for inactive profiles that the 5-min ticker never
0 commit comments