feat/discovery
needs attentionviewing older commit92e4235 · incrementalPR #305reviewed 2026-07-14 01:04 UTC2H · 1M · 3L · 2I- Purpose
- Contextual feature discovery for existing users — ambient spotlights, Descubre hub, and first-run guided product tour. Entirely flag-gated (feature-discovery-spotlights, OFF by default); zero user impact until flag is flipped.
- Goal
- Feature Discovery v1: surface unused capabilities to existing users via coachmarks, a gap-aware hub, and a 5-step product tour — FCIS domain layer + PostHog flag gating throughout.
- Sub-goals
- SG-1: Ambient spotlights — contextual coachmarks for unused capabilities, paced ≤1/rolling-day, retire-on-dismiss
- SG-2: Descubre hub — header badge + gap listing + Volver a tomar el tour re-entry
- SG-3: First-run linear product tour — fixed 5 steps, scrim + spotlight cutout, Hazlo ahora shortcuts
- SG-4: FCIS domain layer — discovery_state table (migration 0058), pure decisions, RLS, JSend API
- SG-5: Adversarial review fixes — VERCEL_ENV gating, spotlight self-destruct latch, atomic upsert, gate-key exemption
- SG-6 (this commit): Bug fix — last tour step auto-closed on missing anchor; degrade to centered modal instead
- What
- Single commit: anchor-miss behavior in TourOverlay changed from auto-advance/complete to graceful centered-modal degradation. Dep array tightened from [step.anchor, step.id, stepIdx, isLast, onStep, onComplete] to [step.anchor, step.id].
- Why
- The last tour step pointed at the Descubre header badge, which disappears once the gap is empty. The old auto-complete path closed the tour before the user finished reading the final step — unread content silently skipped.
- Areas
- apps/platform+1502−20domains/cross-domain+1312−0domains/core+498−0packages/secrets+152−12packages/database+131−0packages/api+89−0packages/analytics+72−0e2e/platform+48−0
- Blast
- 51 files, +3835/-32 across 8 areas. Feature is flag-gated (fails closed if flag absent) — zero user impact until flag is explicitly flipped.
Findings · 8
correctness2
Dep-array narrowing assumes unique step IDs — fragile for future steps
apps/platform/src/components/discovery/GuidedTour.tsx:193
Removing stepIdx from deps is safe because step.id uniquely tracks stepIdx changes. If a future step were added with a duplicate id, the effect would silently fail to re-run on that transition, leaving rect stale. Adding stepIdx back would cost nothing and remove the implicit assumption.
Centered fallback has no scroll path if the card's nav buttons are off-viewport
apps/platform/src/components/discovery/GuidedTour.tsx
Not a regression — card UI is unchanged. If a future step body is very long and pushes Siguiente off-screen while the card is centered (no scroll), a user could appear stuck. Worth checking card max-height for steps that may lack anchors.
conventions3
Comment block uses all-caps (NEVER, CENTERED) inconsistent with surrounding style
apps/platform/src/components/discovery/GuidedTour.tsx:183
The 5-line comment explains a genuinely non-obvious invariant (appropriate per codebase rule), but NEVER and CENTERED in all-caps are inconsistent with the surrounding code style. Downcasing to prose would match conventions while preserving the WHY explanation.
Confirm exhaustive-deps lint is not suppressed for the narrowed dep array
apps/platform/src/components/discovery/GuidedTour.tsx:193
The dep array intentionally omits stepIdx, isLast, onStep, onComplete. Verify eslint-plugin-react-hooks exhaustive-deps is active for this file — future edits that reintroduce references to those identifiers should get a lint error, not a silent stale-closure.
analytics: discovery.spotlight_anchor_missing no longer implies auto-completion
apps/platform/src/components/discovery/GuidedTour.tsx
The existing event still fires on miss — observability is intact. But if any PostHog query used this event as a tour-completion proxy, that interpretation is now incorrect. Worth a one-time audit of any dashboards querying this event.
tests3
No unit test: onComplete must NOT be called on anchor miss
apps/platform/src/components/discovery/GuidedTour.tsx:180
The fix's core invariant — that a missing anchor leaves the tour open rather than auto-completing it — has no automated verification. Render TourOverlay with a step whose anchor matches nothing, advance fake timers past the 8x250ms retry window, and assert both onComplete and onStep are never called.
No unit test: centered-modal fallback when anchor is absent
apps/platform/src/components/discovery/GuidedTour.tsx:180
The positive assertion — that rect stays null and the card renders in centered layout (no spotlight positioning) — is also untested. A component test should assert the fallback card is visible after the retry timeout, confirming the UX degradation is correct.
Playwright anchor smoke doesn't cover the missing-anchor degradation path
e2e/platform/discovery-anchors.spec.ts
The spec verifies anchors ARE present (a CI tripwire), but doesn't exercise what happens when one is absent — the exact scenario this fix addresses. A dedicated component test or E2E test covering the degradation path would lock in the fix.
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:04current
- 6be8018safeincremental0H · 0M · 3L2026-07-14 00:15
- 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