← all branches

feat/discovery

safeviewing older commit
46e32b2 · incrementalpre-PRreviewed 2026-07-11 02:54 UTC0H · 0M · 3L · 3I
The branch
Purpose
Feature Discovery v3 — contextual spotlights that guide users to undiscovered platform capabilities, converting recognition over recall into onboarding momentum
Goal
Ship the Descubre discovery hub end-to-end: gap badge in header, self-initiated spotlights via ?descubre=, completion acknowledgement, and mutation-driven gap freshness
Sub-goals
  • SG-1: Contextual feature spotlights v1 — FeatureSpotlight component, spotlight catalog, PostHog flag gate
  • SG-2: Descubre hub — persistent gap badge + self-initiated spotlights via ?descubre= deep-link
  • SG-3: Retirement fix — failed spotlight actions must be retryable, not auto-retired
  • SG-4: Gap freshness on navigation — invalidate on route change so badge reflects completed actions
  • SG-5 (this commit): Completion hand-off — next-step toast + mutation-driven gap invalidation
The changes (whole branch)
What
Two orthogonal additions: (a) DescubreMenu now fires a toast when a gap item departs — pointing to the next step or showing all-done — using a prevKeysRef + itemsKey effect; (b) QueryProvider wraps QueryClient in a MutationCache.onSuccess that blanket-invalidates ['discovery'] so same-page mutations refresh the gap without requiring navigation. Plus 3 i18n strings (en/es).
Why
Without (a) users who complete a step get no acknowledgement. Without (b) same-page mutations (credential save, RPU add, monitoring toggle) didn't refresh the gap — only navigation triggered invalidation — leaving the badge stale until the user navigated elsewhere.
Areas
apps/platform/src/components/discovery+400apps/platform/src/lib/providers+2817apps/platform/src/messages+100
Blast
Incremental: 4 files, +83/−17 lines. Cumulative branch: ~9 areas, +15k/−18 lines (bulk is DB meta snapshot). Core logic: discovery domain + cross-domain shells + platform API + UI components.
pre-PR branch — no PR comment posted incremental review from 3b309497
typecheck· not run in this review passtests· no CI data — pre-PR branchcoderabbit· no .coderabbit.yaml

Findings · 6

correctness2

low

prevKeysRef not reset when `enabled` goes false — spurious toast on re-enable

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

When `enabled` is false the effect's early-return at L90 skips the `prevKeysRef.current = current` update. So the ref holds the last enabled-session's keys. If `enabled` later flips true with a changed item set (e.g. orgId changes, PostHog flag briefly toggles), `left = prev − current` can be non-empty and fires a spurious 'next step' toast — even though the user didn't complete anything. Fix: `if (!enabled) { prevKeysRef.current = null; return; }` so the guard resets the ref when going dark, making the next enable behave like a fresh mount.

info

eslint-disable suppresses toast/t/router from deps — stable in practice, undocumented

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

toast, t, and router are used inside the effect but omitted from [itemsKey, enabled]. They are stable refs in practice (useToast/useTranslations/useRouter all memoise their returns). The suppression comment gives no indication of this assumption, so a future reader or upgrade could miss a stale-closure regression silently.

conventions1

low

Multi-line comment blocks violate 'one short line max'

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

CLAUDE.md: 'Never write multi-line comment blocks — one short line max.' The 5-line block at L81-85 in DescubreMenu.tsx and the 5-line block at L22-25 in QueryProvider.tsx both violate this. Each could be a single sentence: '// toast the next step when a gap item is retired — skip initial load' and '// any mutation may change the gap — blanket-invalidate instead of per-hook tracking'.

tests1

low

Toast-trigger logic (prev-key tracking) has no unit test

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

The prevKeysRef + itemsKey effect has three non-obvious edge cases — toast on departure, skip on first load, skip on spurious enabled-flip — none of which are exercised by the existing integration tests (which validate gap computation, not the toast trigger). A React Testing Library test rendering with items=[a,b] → [b] would cover the core path and the fix for finding #1 above.

improvement2

info

prevKeysRef typed as string[] — could be SpotlightFeatureKey[] to avoid cast

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

useRef<readonly string[] | null> forces the `as readonly string[]` widening cast at L95 (because Array<SpotlightFeatureKey>.includes(string) doesn't type-check). Typing the ref as useRef<readonly SpotlightFeatureKey[] | null>(null) keeps the union type end-to-end and removes the cast. Minor type hygiene.

info

itemsKey join(',') fragile if SpotlightFeatureKey ever contains a comma

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

keys.join(',') is unambiguous today (catalog keys are simple identifiers). A future key with a comma would produce hash collisions. Safer sentinel: keys.join('|'). Low practical risk — the catalog is controlled — but worth noting for future key additions.

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:54current
  32. 3b30949needs attentionincremental0H · 2M · 3L2026-07-11 02:39
  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