feat/discovery
safeviewing older commit6be8018 · incrementalPR #305reviewed 2026-07-14 00:15 UTC0H · 0M · 3L · 1I- Purpose
- Contextual feature discovery system for existing users — ambient spotlights, a Descubre hub, and a first-run product tour, all flag-gated off by default.
- Goal
- Ship Feature Discovery v1: PostHog-gated spotlights + hub + linear tour, with FCIS domain layer, RLS-scoped discovery_state table, and full i18n.
- Sub-goals
- SG-1: Domain + DB — discovery_state table, FCIS namespace, RLS migration 0058
- SG-2: API — discovery contract + handler + mapper (JSend, ts-rest)
- SG-3: UI — FeatureSpotlight, DescubreMenu, GuidedTour components
- SG-4: Hardening — atomic upsert, render latch, preview-bypass fix (VERCEL_ENV not Host header)
- SG-5: Permanent tour re-entry in avatar menu, decoupled from gap/DescubreMenu lifecycle
- What
- Added ProductTourMenuItem — a permanent 'retake tour' entry in the avatar dropdown, decoupled from DescubreMenu which unmounts at gap=0. Added import and JSX insertion in DashboardHeader.
- Why
- DescubreMenu unmounts when gap=0 (clean adoption state), orphaning the tour re-entry. The tour is a help affordance that should always be reachable regardless of discovery progress.
- Areas
- apps/platform+932−21domains/core/src/discovery+391−0domains/cross-domain/src+930−0packages/api+73−0packages/database+15−0packages/analytics+37−0e2e/platform+63−0packages/secrets+15−0
- Blast
- ~50 files, +2456/−21 across discovery domain, API, UI, and DB. Feature-flag gated — no users see this until flag is flipped.
Findings · 5
correctness1
Tour re-entry silently no-ops on mobile
apps/platform/src/components/discovery/ProductTourMenuItem.tsx:44
ProductTourMenuItem gates visibility on flagEnabled || isPreviewDeployment() but not on isDesktop. GuidedTour requires isDesktop to open. On mobile the item renders and click navigates to /bills/contratos?tour=1, but the tour silently doesn't open. Pre-existing behavior shared with DescubreMenu — avatar-menu placement makes it more reachable on mobile, slightly widening the silent no-op surface.
conventions1
Component placement consistent with shared DashboardHeader
apps/platform/src/components/discovery/ProductTourMenuItem.tsx
DashboardHeader is used in the top-level (dashboard) layout.tsx, making it a shared component. ProductTourMenuItem in src/components/discovery/ is consistent placement.
tests2
Flag-gate null branch not directly tested
apps/platform/src/components/discovery/ProductTourMenuItem.tsx:44
No unit test covers the null-render branch (flag=false AND not preview). Existing tests (spotlight-latch.test.ts, discovery-anchors.spec.ts) don't exercise this path. A single RTL test mocking PostHog as disabled and isPreviewDeployment as false would pin it. Low risk: the worst outcome is the item showing when it shouldn't — no data or security impact.
Navigation onClick has no test coverage (acceptable)
apps/platform/src/components/discovery/ProductTourMenuItem.tsx:47
The click handler calls router.push with a compile-time constant URL. Testing this would be testing string interpolation of typed constants — negligible regression risk.
improvement1
Duplicate useSyncExternalStore flag-reading block — extract useDiscoveryFlag()
apps/platform/src/components/discovery/ProductTourMenuItem.tsx:22
Four discovery components (ProductTourMenuItem, DescubreMenu, GuidedTour, FeatureSpotlight) now each contain the same 10-line useSyncExternalStore + useCallback + posthog?.onFeatureFlags + isFeatureFlagEnabled block verbatim. A useDiscoveryFlag(): boolean hook in use-discovery-flag.ts would centralize the subscription, snapshot, and SSR default. Any future flag-key or API change is then a one-file edit.
History · 36 commits
- c1fe7b7needs attentionincremental3H · 4M · 3L2026-07-27 06:02
- 414db56safeincremental0H · 0M · 2L2026-07-23 00:52
- ea68968needs attentionincremental0H · 4M · 7L2026-07-22 23:01
- 4bdb2c9needs attentionincremental1H · 3M · 5L2026-07-21 23:22
- 6e3f255safeincremental0H · 0M · 0L2026-07-20 16:46
- cc41079blockedincremental5H · 5M · 3L2026-07-20 16:25
- 2451ee0needs attentionincremental0H · 5M · 6L2026-07-20 16:09
- ab7c103needs attentionincremental0H · 1M · 6L2026-07-20 15:52
- 7eff611needs attentionincremental0H · 1M · 2L2026-07-18 00:41
- 246c67csafeincremental0H · 0M · 0L2026-07-17 23:58
- 7521b17safeincremental0H · 0M · 0L2026-07-17 23:35
- 1847ec8needs attentionincremental0H · 2M · 4L2026-07-17 23:28
- 5d3175cneeds attentionincremental3H · 6M · 4L2026-07-17 19:31
- d3f875dneeds attentionincremental1H · 2M · 4L2026-07-17 18:01
- 47d25faneeds attentionincremental1H · 1M · 2L2026-07-17 00:46
- 440d838needs attentionincremental4H · 9M · 9L2026-07-17 00:27
- 7466f97needs attentionincremental0H · 1M · 5L2026-07-16 23:20
- b686d58needs attentionincremental0H · 2M · 2L2026-07-16 14:24
- 9ea1446blockedincremental2H · 7M · 10L2026-07-16 13:44
- 6f39bb9needs attentionincremental0H · 2M · 7L2026-07-14 21:53
- 2eadf2aneeds attentionincremental0H · 1M · 2L2026-07-14 20:12
- 97bd08fneeds attentionincremental0H · 2M · 5L2026-07-14 19:40
- a48e3ffneeds attentionincremental5H · 8M · 6L2026-07-14 18:22
- 69473b7needs attentionincremental2H · 5M · 3L2026-07-14 01:34
- 92e4235needs attentionincremental2H · 1M · 3L2026-07-14 01:04
- 6be8018safeincremental0H · 0M · 3L2026-07-14 00:15current
- 090b4daneeds attentionincremental1H · 3M · 5L2026-07-13 23:50
- fa7683bneeds attentionincremental1H · 4M · 3L2026-07-13 18:55
- d64cd72needs attentionincremental2H · 3M · 2L2026-07-13 16:27
- c1f337bneeds attentionincremental3H · 7M · 14L2026-07-13 13:30
- 46e32b2safeincremental0H · 0M · 3L2026-07-11 02:54
- 3b30949needs attentionincremental0H · 2M · 3L2026-07-11 02:39
- 9f8dbdfneeds attentionincremental3H · 5M · 5L2026-07-11 02:32
- 23191eeneeds attentionincremental5H · 12M · 7L2026-07-11 02:18
- fa9b88fneeds attentionincremental0H · 1M · 2L2026-07-11 01:30
- 6a8bf42blockedfull8H · 11M · 8L2026-07-11 01:08