← all branches

feat/discovery

needs attentionviewing older commit
3b30949 · incrementalpre-PRreviewed 2026-07-11 02:39 UTC0H · 2M · 3L · 1I
The branch
Purpose
Feature Discovery (Descubre) — surface unused Batu platform capabilities to operators via contextual spotlights and a persistent gap badge so they adopt more of the product without needing a guided tour
Goal
Descubre hub: a persistent sparkles button in the header with a count badge showing undiscovered features; clicking deep-links to the feature page and triggers a self-initiated spotlight. Disappears at gap = 0.
Sub-goals
  • SG-1: discovery domain FCIS entity — discovery-state schema, decisions, queries, shells, mapper
  • SG-2: cross-domain feature-discovery coordinator — gap evaluation spanning core + utility domains
  • SG-3: DescubreMenu UI — persistent header badge with dropdown deep-links
  • SG-4: FeatureSpotlight UI — coachmark cards triggered by ?descubre= param or budget-based scheduling
  • SG-5: spotlight-catalog — typed registry of feature keys → page paths → anchor selectors
  • SG-6: analytics events — discovery_gap_viewed, spotlight_shown, spotlight_completed
  • SG-7: E2E anchors spec — validates spotlight anchor presence on relevant pages
  • SG-8: cache freshness fix — invalidate gap query on route change (this commit)
The changes (whole branch)
What
Single-commit incremental: adds useEffect in DescubreMenu to invalidate the ['discovery'] query cache on every pathname change, and sets staleTime to 0, fixing a live bug where the gap badge remained stale after the user completed an action (e.g. saving CFE credentials) because the component never unmounts during a session.
Why
The DescubreMenu lives in the dashboard header and is never unmounted. With staleTime: 60s the badge could read as 'tour stuck' for up to a minute after completing a setup step — found live post-deploy with the CFE credential step.
Areas
apps/platform/src/api+2780apps/platform/src/components/discovery+4520apps/platform/src/app+332domains/core/src/discovery+5760domains/cross-domain/src+11580e2e/platform+480packages/api/src/schemas+730packages/database/src/schema+950
Blast
~2700 lines net-new across 8 areas: full FCIS discovery domain + cross-domain coordinator + platform API surface + header UI + E2E anchors + analytics events + DB schema. The incremental being reviewed is 18 lines in one file.
staleTime: 0 combined with invalidateQueries — likely over-fetching, review recommended
CI· No PR; CI status not availableCodeRabbit· No .coderabbit.yaml

Findings · 6

correctness3

medium

staleTime: 0 and invalidateQueries on pathname overlap — double fetch on every navigation

apps/platform/src/components/discovery/DescubreMenu.tsx:64

staleTime: 0 makes TanStack Query treat every read as stale and refetch on focus/mount automatically. The useEffect invalidateQueries on pathname then triggers a second concurrent refetch on navigation. The two mechanisms serve the same purpose but compound: on initial mount and on every route change the component fires two requests instead of one. Recommended: restore staleTime to 60_000 and keep only the invalidateQueries effect, which is the surgical fix for the mid-session stale-badge bug.

medium

['discovery'] prefix invalidation is broader than the specific gap query

apps/platform/src/components/discovery/DescubreMenu.tsx:65

TanStack Query prefix-matches on queryKey, so invalidateQueries({ queryKey: ['discovery'] }) will bust any future query registered under ['discovery', ...]. The actual query key is ['discovery', 'gap', orgId]. Today there is only one discovery query, so the behaviour is correct. As new discovery sub-queries are added the invalidation will over-bust them on every route change. Consider targeting ['discovery', 'gap', orgId] to match intent exactly.

low

queryClient in useEffect dependency array is a stable singleton

apps/platform/src/components/discovery/DescubreMenu.tsx:66

queryClient from useQueryClient() is the same object for the lifetime of the provider and will never change between renders. Listing it as a dep is correct for lint exhaustive-deps but it is the kind of dependency that misleads future readers into thinking it could vary. No functional impact.

conventions1

info

Block comment recounts bug history — belongs in commit message, not source

apps/platform/src/components/discovery/DescubreMenu.tsx:57

The 4-line comment includes 'found live: after connecting CFE the badge kept showing the CFE step' — a good commit-message line, not a durable source invariant. The invariant worth keeping is one sentence: why route-change invalidation is the right scope (this component has no sight of where actions happen). Trim to that.

tests1

low

No component test for the pathname → invalidateQueries pathway

apps/platform/src/components/discovery/DescubreMenu.tsx:64

The live bug this commit fixes (stale badge after completing an action) is not guarded by any test. Domain unit tests cover pure decision logic; the E2E spec navigates once per test and never crosses routes to verify badge refresh. A React Testing Library test that mocks useQueryClient, simulates a pathname change, and asserts invalidateQueries was called is ~20 lines and directly pins the regression.

improvement1

low

void on invalidateQueries silently swallows errors

apps/platform/src/components/discovery/DescubreMenu.tsx:65

void queryClient.invalidateQueries(...) discards the returned Promise. If invalidation throws during an unmount race nothing surfaces. Either await inside the effect, or add an explicit .catch(() => {}) no-op to signal the fire-and-forget intent to reviewers.

History · 36 commits

  1. c1fe7b7needs attentionincremental3H · 4M · 3L2026-07-27 06:02
  2. 414db56safeincremental0H · 0M · 2L2026-07-23 00:52
  3. ea68968needs attentionincremental0H · 4M · 7L2026-07-22 23:01
  4. 4bdb2c9needs attentionincremental1H · 3M · 5L2026-07-21 23:22
  5. 6e3f255safeincremental0H · 0M · 0L2026-07-20 16:46
  6. cc41079blockedincremental5H · 5M · 3L2026-07-20 16:25
  7. 2451ee0needs attentionincremental0H · 5M · 6L2026-07-20 16:09
  8. ab7c103needs attentionincremental0H · 1M · 6L2026-07-20 15:52
  9. 7eff611needs attentionincremental0H · 1M · 2L2026-07-18 00:41
  10. 246c67csafeincremental0H · 0M · 0L2026-07-17 23:58
  11. 7521b17safeincremental0H · 0M · 0L2026-07-17 23:35
  12. 1847ec8needs attentionincremental0H · 2M · 4L2026-07-17 23:28
  13. 5d3175cneeds attentionincremental3H · 6M · 4L2026-07-17 19:31
  14. d3f875dneeds attentionincremental1H · 2M · 4L2026-07-17 18:01
  15. 47d25faneeds attentionincremental1H · 1M · 2L2026-07-17 00:46
  16. 440d838needs attentionincremental4H · 9M · 9L2026-07-17 00:27
  17. 7466f97needs attentionincremental0H · 1M · 5L2026-07-16 23:20
  18. b686d58needs attentionincremental0H · 2M · 2L2026-07-16 14:24
  19. 9ea1446blockedincremental2H · 7M · 10L2026-07-16 13:44
  20. 6f39bb9needs attentionincremental0H · 2M · 7L2026-07-14 21:53
  21. 2eadf2aneeds attentionincremental0H · 1M · 2L2026-07-14 20:12
  22. 97bd08fneeds attentionincremental0H · 2M · 5L2026-07-14 19:40
  23. a48e3ffneeds attentionincremental5H · 8M · 6L2026-07-14 18:22
  24. 69473b7needs attentionincremental2H · 5M · 3L2026-07-14 01:34
  25. 92e4235needs attentionincremental2H · 1M · 3L2026-07-14 01:04
  26. 6be8018safeincremental0H · 0M · 3L2026-07-14 00:15
  27. 090b4daneeds attentionincremental1H · 3M · 5L2026-07-13 23:50
  28. fa7683bneeds attentionincremental1H · 4M · 3L2026-07-13 18:55
  29. d64cd72needs attentionincremental2H · 3M · 2L2026-07-13 16:27
  30. c1f337bneeds attentionincremental3H · 7M · 14L2026-07-13 13:30
  31. 46e32b2safeincremental0H · 0M · 3L2026-07-11 02:54
  32. 3b30949needs attentionincremental0H · 2M · 3L2026-07-11 02:39current
  33. 9f8dbdfneeds attentionincremental3H · 5M · 5L2026-07-11 02:32
  34. 23191eeneeds attentionincremental5H · 12M · 7L2026-07-11 02:18
  35. fa9b88fneeds attentionincremental0H · 1M · 2L2026-07-11 01:30
  36. 6a8bf42blockedfull8H · 11M · 8L2026-07-11 01:08