From a9a20039ebce0707cc4308492f58ba261b324ca0 Mon Sep 17 00:00:00 2001 From: archipelago Date: Thu, 30 Jul 2026 07:43:34 -0400 Subject: [PATCH] feat(02-02): tracer tab refreshes silently and every side effect is placed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Puts Marketplace.vue's data on the cache and adds the subtle background- refresh indicator D-05 calls for: - RefreshIndicator.vue: presentational, state-driven (ResourceLoadState). Renders nothing for ready/idle/loading — a first load is the view's own skeleton's job, not this component's — and a role="status" aria-live="polite" spinner only while refreshing. A fixed-size outer slot keeps its appearance/disappearance from ever shifting layout. - Marketplace.vue: loadCommunityMarketplace()/loadBitcoinPruneStatus() move onto useCachedResource behind shared keys app-catalog (300s TTL, persist — near-static catalog, D-06 discretion) and bitcoin.prune-status (default 30s TTL, persist — both non-sensitive/small per T-02-01). app-catalog is a shared key so Discover.vue's identical loader picks up the same entry without its own conversion in 02-04. Error handling is keep-last-value (D-07): a rejected refresh sets the existing communityError banner ref, never a toast, and prior app cards stay on screen. - Side-effect audit (the precedent 02-04 repeats across remaining tabs): marketplaceAnimationDone is genuinely once-per-session intro state, stays in onMounted; the two data loads needed no onMounted/onActivated hook of their own at all — useCachedResource's internal onActivated (wired in Task 1) already revalidates them on every kept-alive tab re-entry, staleness-gated so a quick revisit issues no fetch. No interval, subscription or window listener exists in this view, so no onDeactivated teardown was needed either. - Tests: RefreshIndicator's full render-nothing/render-something matrix added to keepAliveTabs.test.ts (no router dependency, safe to colocate). The D-07 rejected-refresh-keeps-content-and-no-toast test mounts Marketplace.vue itself but lives in a new file, views/__tests__/MarketplaceRefresh.test.ts, following the in-repo CloudPeersRefresh.test.ts mount-the-view pattern — vi.mock('vue-router') is hoisted file-wide, so colocating it in keepAliveTabs.test.ts would clobber that file's real createRouter/createMemoryHistory used by the DashboardRouterView tests (Rule 3 auto-fix; deviation from the plan's literal single-test-file file list, documented here for the SUMMARY). Marketplace.vue gains a defineExpose({ loadCommunityMarketplace, loadBitcoinPruneStatus }) for tests, mirroring Cloud.vue's existing defineExpose({ loadPeers }). Full suite (87 files / 709 tests), type-check and build all green; the built Marketplace chunk carries refresh-indicator. Co-Authored-By: Claude Fable 5 --- neode-ui/src/components/RefreshIndicator.vue | 60 ++++++++++++ neode-ui/src/views/Marketplace.vue | 80 +++++++++++----- .../__tests__/MarketplaceRefresh.test.ts | 92 +++++++++++++++++++ .../dashboard/__tests__/keepAliveTabs.test.ts | 25 +++++ 4 files changed, 233 insertions(+), 24 deletions(-) create mode 100644 neode-ui/src/components/RefreshIndicator.vue create mode 100644 neode-ui/src/views/__tests__/MarketplaceRefresh.test.ts diff --git a/neode-ui/src/components/RefreshIndicator.vue b/neode-ui/src/components/RefreshIndicator.vue new file mode 100644 index 00000000..dd9c8d1e --- /dev/null +++ b/neode-ui/src/components/RefreshIndicator.vue @@ -0,0 +1,60 @@ + + + + + diff --git a/neode-ui/src/views/Marketplace.vue b/neode-ui/src/views/Marketplace.vue index faffc006..b88158af 100644 --- a/neode-ui/src/views/Marketplace.vue +++ b/neode-ui/src/views/Marketplace.vue @@ -57,6 +57,7 @@ :aria-label="t('marketplace.searchApps')" class="app-header-search px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:outline-none focus:border-white/40 transition-colors" /> + @@ -69,6 +70,7 @@
discover

App Store

+