Skip to content

Commit db38872

Browse files
authored
fix(bootstrap): suppress CACHED banner for mixed hydration state (koala73#2029)
'mixed' means some tiers fetched live data (partially filled from cache). Showing 'Live data unavailable' was factually incorrect and caused a confusing flash on every load since markBootstrapAsLive() clears it seconds later anyway. Now only source === 'cached' (total fallback, zero live data) triggers the CACHED banner and header indicator. Offline mode retains the banner for both cached and mixed since any snapshot beats blank when offline.
1 parent 3bb47ac commit db38872

File tree

1 file changed

+14
-4
lines changed

1 file changed

+14
-4
lines changed

src/App.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export class App {
118118

119119
private getCachedBootstrapUpdatedAt(): number | null {
120120
const cachedTierTimestamps = Object.values(this.bootstrapHydrationState.tiers)
121-
.filter((tier) => tier.source === 'cached' || tier.source === 'mixed')
121+
.filter((tier) => tier.source === 'cached')
122122
.map((tier) => tier.updatedAt)
123123
.filter((value): value is number => typeof value === 'number' && Number.isFinite(value));
124124

@@ -130,16 +130,26 @@ export class App {
130130
const statusIndicator = this.state.container.querySelector('.status-indicator');
131131
const statusLabel = statusIndicator?.querySelector('span:last-child');
132132
const online = typeof navigator === 'undefined' ? true : navigator.onLine !== false;
133-
const usingCachedBootstrap = this.bootstrapHydrationState.source === 'cached' || this.bootstrapHydrationState.source === 'mixed';
133+
// Only treat a complete cache fallback (no live data at all) as "cached" for UI purposes.
134+
// 'mixed' means live data was partially fetched — showing "Live data unavailable" would be misleading.
135+
const usingCachedBootstrap = this.bootstrapHydrationState.source === 'cached';
134136
const cachedUpdatedAt = this.getCachedBootstrapUpdatedAt();
135137

136138
let statusMode: 'live' | 'cached' | 'unavailable' = 'live';
137139
let bannerMessage: string | null = null;
138140

139141
if (!online) {
140-
if (usingCachedBootstrap) {
142+
// Offline: show banner regardless of mixed/cached (any cached data is better than nothing)
143+
const hasAnyCached = this.bootstrapHydrationState.source === 'cached' || this.bootstrapHydrationState.source === 'mixed';
144+
if (hasAnyCached) {
141145
statusMode = 'cached';
142-
const freshness = cachedUpdatedAt ? describeFreshness(cachedUpdatedAt) : t('common.cached').toLowerCase();
146+
const offlineCachedAt = this.bootstrapHydrationState.tiers
147+
? Math.min(...Object.values(this.bootstrapHydrationState.tiers)
148+
.filter((tier) => tier.source === 'cached' || tier.source === 'mixed')
149+
.map((tier) => tier.updatedAt)
150+
.filter((v): v is number => typeof v === 'number' && Number.isFinite(v)))
151+
: NaN;
152+
const freshness = Number.isFinite(offlineCachedAt) ? describeFreshness(offlineCachedAt) : t('common.cached').toLowerCase();
143153
bannerMessage = t('connectivity.offlineCached', { freshness });
144154
} else {
145155
statusMode = 'unavailable';

0 commit comments

Comments
 (0)