ENH - polish weather refresh experience

Migrate the Weather app to the Sky UI primitives with richer condition effects, updated WebP icons, and pull-to-refresh feedback.\n\nAdd a shared reload cooldown for weather, banking, and housing actions, including localized status feedback and test-server coverage.
This commit is contained in:
Type
2026-08-14 17:09:03 +02:00
parent 8744640068
commit 562dd8f321
18 changed files with 686 additions and 174 deletions
@@ -0,0 +1,28 @@
import { describe, expect, it } from 'vitest'
import {
allowManualReload,
isReloadCooldownActive,
} from '@/utils/reload-cooldown'
describe('reload cooldown', () => {
it('blocks after too many reloads in a short window and recovers', () => {
const state = { cooldownUntil: 0, reloadAttempts: [] as number[] }
expect(allowManualReload(state, 1_000)).toBe(true)
expect(allowManualReload(state, 2_000)).toBe(true)
expect(allowManualReload(state, 3_000)).toBe(true)
expect(allowManualReload(state, 4_000)).toBe(false)
expect(isReloadCooldownActive(state, 13_999)).toBe(true)
expect(isReloadCooldownActive(state, 14_000)).toBe(false)
expect(allowManualReload(state, 14_000)).toBe(true)
})
it('forgets reloads outside the rolling window', () => {
const state = { cooldownUntil: 0, reloadAttempts: [] as number[] }
expect(allowManualReload(state, 1_000)).toBe(true)
expect(allowManualReload(state, 12_000)).toBe(true)
expect(state.reloadAttempts).toEqual([12_000])
})
})
+40
View File
@@ -0,0 +1,40 @@
export const RELOAD_COOLDOWN_ERROR = 'reload_cooldown'
const RELOAD_LIMIT = 3
const RELOAD_WINDOW_MS = 10_000
const RELOAD_COOLDOWN_MS = 10_000
export interface ReloadCooldownState {
cooldownUntil: number
reloadAttempts: number[]
}
export function isReloadCooldownActive(
state: ReloadCooldownState,
now = Date.now(),
): boolean {
if (state.cooldownUntil <= now) {
state.cooldownUntil = 0
return false
}
return true
}
export function allowManualReload(
state: ReloadCooldownState,
now = Date.now(),
): boolean {
if (isReloadCooldownActive(state, now)) return false
state.reloadAttempts = state.reloadAttempts.filter(
(attemptedAt) => now - attemptedAt < RELOAD_WINDOW_MS,
)
if (state.reloadAttempts.length >= RELOAD_LIMIT) {
state.cooldownUntil = now + RELOAD_COOLDOWN_MS
state.reloadAttempts = []
return false
}
state.reloadAttempts.push(now)
return true
}