fix(02-review): WR-01 decouple Discover's featured-banner data from app-catalog dedup race
Marketplace.vue and Discover.vue both register a useCachedResource against
the shared 'app-catalog' key with different fetchers; resources.ts's
in-flight dedup means whichever view's fetcher wins a given race governs
the shared entry, silently dropping Discover's catalogFeatured side effect
when Marketplace's simpler fetcher wins.
Give the featured-banner payload its own cache key ('app-catalog:featured')
subscribed only by Discover.vue, so it always gets its own data regardless
of which view's fetcher wins the shared 'app-catalog' race. fetchAppCatalog()
already memoizes internally (1h TTL + localStorage fallback), so this is
normally a cache hit rather than an extra network request. The shared
'app-catalog' key and its dedup behavior are unchanged.
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
57989dfc58
commit
6105770420
@ -269,7 +269,6 @@ const searchQuery = ref('')
|
||||
// fetch (02-04). Both views populate the same cache entry, whichever one
|
||||
// happens to trigger a given fetch — the store doesn't care which caller's
|
||||
// fetcher ran, only the resulting cached value.
|
||||
const catalogFeatured = ref<CatalogFeatured | null>(null)
|
||||
const catalogResource = useCachedResource<MarketplaceApp[]>({
|
||||
key: 'app-catalog',
|
||||
// Preserves this view's existing dynamic-catalog-first behavior (Marketplace's
|
||||
@ -278,17 +277,28 @@ const catalogResource = useCachedResource<MarketplaceApp[]>({
|
||||
fetcher: async () => {
|
||||
const catalog = await fetchAppCatalog()
|
||||
if (catalog) {
|
||||
catalogFeatured.value = catalog.featured
|
||||
if (import.meta.env.DEV) console.log('Loaded app catalog from registry:', catalog.apps.length, 'apps')
|
||||
return catalog.apps
|
||||
}
|
||||
catalogFeatured.value = null
|
||||
if (import.meta.env.DEV) console.log('Using hardcoded app list (catalog.json unavailable)')
|
||||
return getCuratedAppList()
|
||||
},
|
||||
ttlMs: 300_000,
|
||||
persist: true,
|
||||
})
|
||||
// Featured banner payload lives on its own cache key (WR-01), deliberately
|
||||
// NOT derived from catalogResource's fetcher: 'app-catalog' is a shared key
|
||||
// with Marketplace.vue, whose simpler getCuratedAppList()-only fetcher can
|
||||
// win the in-flight dedup race and never populate the featured payload.
|
||||
// fetchAppCatalog() has its own internal memoization (1h TTL + localStorage
|
||||
// fallback), so this second subscription is normally a cache hit, not an
|
||||
// extra network round trip.
|
||||
const catalogFeatured = useCachedResource<CatalogFeatured | null>({
|
||||
key: 'app-catalog:featured',
|
||||
fetcher: async () => (await fetchAppCatalog())?.featured ?? null,
|
||||
ttlMs: 300_000,
|
||||
persist: true,
|
||||
}).data
|
||||
const communityApps = computed(() => catalogResource.data.value ?? [])
|
||||
const loadingCommunity = computed(() => catalogResource.entry.loadState === 'loading')
|
||||
// Keep-last-value error banner (D-07): a failed background refresh never
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user