feat: streak-triggered ask-for-review strip#6084
Open
tsahimatsliah wants to merge 20 commits into
Open
Conversation
Adds an inline strip at the top of the post modal that asks long-streak users for a store review (Yes routes to the right Chrome/Edge/Firefox/App Store/Play Store URL by platform, falls back to X share for Firefox/Safari desktop). No routes them into the existing Feedback modal with a UX-issue category. Step-1 dismissal triggers a 14-day cooldown loop; any engagement past step 1 is permanent. Threshold and rollout are GrowthBook-controlled via featureAskForReview. Covered by unit specs for the destination helper, visibility hook (including the cooldown loop), and the strip UI. Storybook DemoPanel exposes every state for design review. Co-authored-by: Cursor <cursoragent@cursor.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Co-authored-by: Cursor <cursoragent@cursor.com>
Adds a floating QA panel mounted in MainLayout, gated on ?ask-for-review-qa=1, that bypasses all visibility gates and lets you trigger the real strip + real handlers on any post page. Includes state inspector, destination override, session/cooldown reset, and manual Feedback modal/action-complete triggers. Co-authored-by: Cursor <cursoragent@cursor.com>
…d modal - Portal the strip to document.body with fixed top + z-max so it sits above the post modal, navigation, and X button - Simplify to a single step (Yes / No / dismiss); fill stars in yellow - Yes now opens a centered AskForReviewConfirmModal (star cover image, production-style review CTA) instead of a second inline step - No still opens the prefilled Feedback modal - Update tests + Storybook story labels to match the new flow Co-authored-by: Cursor <cursoragent@cursor.com>
Use the shared cover-image success modal for the review ask and tighten the floating strip so it reads as part of the post modal instead of a full-width page banner. Co-authored-by: Cursor <cursoragent@cursor.com>
…rompt Co-authored-by: Cursor <cursoragent@cursor.com> # Conflicts: # packages/shared/src/lib/featureManagement.ts
Replace the generic cover-image review modal with the compact dark review card that mirrors the production ask, including generated rating artwork and destination-specific platform icon. Co-authored-by: Cursor <cursoragent@cursor.com>
Make the review question participate in the post layout so it pushes modal and page content down instead of covering navigation, and clarify the Yes/No actions with stronger visual affordance. Co-authored-by: Cursor <cursoragent@cursor.com>
Move the ask-for-review strip out of BasePostContent (where it was rendering inside the article above the title and looking like part of the content). Portal it into the post modal overlay above the modal box with a visible gap, and render it above the page container on the post page — so it reads as a separate strip sitting on top of the article, scrolling with it.
Tighten visuals and contrast: streak avatar circle with white icon, solid bun-default pill with white text for the streak label, edge-to-edge on mobile / rounded card on tablet+. Replace the "Quick question" / "Are you enjoying daily.dev?" pair with more explicit copy ("Quick check-in" + "How is daily.dev working out for you?" + a sentence that spells out what each answer does) and clearer button labels ("I'm loving it" / "Could be better").
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a ?force-ask-for-review=1 query param that bypasses all gates (auth, streak, feature flag, cooldown, session, destination) so reviewers can see and visually QA the strip on any post page or in the post modal without setting up a real streak or flipping the GrowthBook flag. Falls back to Chrome Web Store as the destination when the platform has no real one. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ly renders Reverting the portal-into-overlay approach — it was the right intent (sit physically above the modal box) but in practice the strip wasn't rendering for reviewers. Put it back in BasePostContent where it mounts reliably, and let the redesigned card visuals (rounded border, surface-float background, shadow, and the new copy/contrast) carry the "this is its own thing, not part of the article" feeling instead. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Trim the strip to feel less content-heavy: - One short question + a small streak/CTA line, no separate "Quick check-in" label or oversized streak pill. - Smaller padding (p-3), rounded-12, shadow-1, smaller 9px avatar circle, ButtonSize.Small actions. - Swap the orange bun palette for cabbage-subtler / cabbage-default — the streak flame icon is replaced with FeedbackIcon since the strip is about feedback, not the streak itself. Also persist the force-show flag in localStorage so ?force-ask-for-review=1 survives navigation from the feed into the article modal (and `?force-ask-for-review=0` clears it). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…icon contrast
- Drop the em-dash and rewrite the subtitle as something a human would say: "You've shown up {N} days in a row. Worth a sec?"
- Pin both Yes and Not really buttons to the same tablet:w-28 so they line up at equal width on tablet+ (still flex-1 on mobile).
- Flip the avatar circle from cabbage-subtler with cabbage-default icon (low contrast, washed out) to a solid cabbage-default circle with a white icon — same brand color, much more visible.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…itives
Drop the hand-rolled gradient + logo + stars + platform-icon illustration in AskForReviewConfirmModal and rebuild it on the same primitives MarketingCtaModal uses — Modal (FlexibleCenter, Small), Title, Description, CTAButton from marketing/cta/common — so it matches every other campaign popup in the product instead of looking like a one-off.
Not coupling to boot's MarketingCta type because that's server-driven (campaignId + clearMarketingCta mutation) and would conflict with any real campaign in the boot cache. Reusing the visual building blocks keeps the look consistent without touching campaign state.
Also rewrites the copy: "Thanks for being a regular 💜" + "Mind leaving us a quick review on {platform}? It helps other devs find daily.dev." with a "Leave a review" CTA.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Make the confirm modal store-aware so the wording matches the store the user will land on: - ReviewDestination gains headline / body / ctaText / image so each store (Chrome Web Store, Edge Add-ons, Firefox Add-ons, App Store, Play Store, X share fallback) owns its own copy. - AskForReviewConfirmModal now reads those fields and renders a CardCover hero image above the CTA — matching MarketingCtaModal layout. - Added askForReviewPlaceholderImage in lib/image.ts as a temporary single asset for all destinations. Engineering will swap per-store images later. Tightened the device routing tests: - iPad Safari → App Store - Chrome on iOS (CriOS) → App Store - Sanity-check that the active destination carries store-specific headline / body / ctaText / image. Full matrix (already covered + new) is now: Extension Chrome → Chrome Web Store Extension Edge → Edge Add-ons Extension Firefox→ Firefox Add-ons iOS Safari → App Store iOS Chrome → App Store iPad Safari → App Store Android Chrome → Play Store Desktop Chrome/Brave → Chrome Web Store Desktop Edge → Edge Add-ons Desktop Safari/Firefox → X share fallback Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Remove the ?force-ask-for-review URL flag and forceShow paths from useAskForReviewVisibility. It bypassed every gate (auth, streak, GB, cooldown, completed action) and persisted in localStorage forever — not safe to ship. Internal testing should use the existing ?ask-for-review-qa=1 panel. - Lazy-load AskForReviewQAPanel via next/dynamic + webpackChunkName so the ~300 LOC QA module is no longer parsed on every MainLayout mount. - Add AskForReviewConfirmModal.spec.tsx (6 tests): destination copy renders, CTA href + target=_blank, hero image alt, click-through marks action complete + closes, dismiss writes cooldown + closes, App Store path. - Move isAndroidUserAgent from askForReview.ts into lib/func.ts as `isAndroid`, sitting next to `isIOS` for consistency. - Compute isCooldownActive once in the visibility hook and reuse for both the gate and the return value. - Add why-comments at the strip mount point (placement choice) and at the CTA handler (action marked complete on click-through, not actual review submission — click-through is the engagement signal we re-ask against). - Rename confirm modal close aria-label to "Dismiss review prompt" so it doesn't collide with the drawer's built-in Close button in tests. 37 tests passing in the ask-for-review surface (was 31). Strict typecheck clean for changed files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rompt # Conflicts: # packages/shared/src/lib/featureManagement.ts
The story duplicated what the in-app QA panel (?ask-for-review-qa=1) already provides — switching destinations and streak values to preview every state — except the QA panel runs against real auth, streak, and GrowthBook context instead of mocks. Storybook in this repo is for design-system primitives (Tooltip, Dropdown, FormWrapper, etc.), not for one-off feature components behind a flag. Removing the story drops 226 LOC of mock scaffolding, eliminates a parallel fixture to maintain alongside the QA panel, and unblocks the failing Vercel storybook check. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The QA panel was a dev-only test scaffold but it lived in the shared library and got mounted by MainLayout for every visitor. Removing it entirely: - Delete AskForReviewQAPanel.tsx (~349 LOC) and its MainLayout mount. - Drop getQAOverride / setQAOverride / AskForReviewQAOverride / the ASK_FOR_REVIEW_QA_KEY storage key from lib/askForReview.ts. - Drop the dev-only clearDismissedAt / clearShownThisSession helpers and the unused getDestinationById lookup table. - Strip qa-* gate bypasses from useAskForReviewVisibility — the hook now evaluates the production gates only. - Drop the spec line for clearDismissedAt. If we need to manually preview the strip during development, the path is to flip ask_for_review in GrowthBook dev mode or seed the streak/action state via the existing test infra — not to ship a QA UI to every user. Net: -461 / +6 LOC. 36 tests still passing, strict typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Inline strip rendered at the top of the post modal that asks long-streak users for a store review. Two inline steps; happy users go to the right platform's review URL, unhappy users land in the existing Feedback modal.
[Yes][No][ x ][Leave a review]CTA opening the platform URL in a new tab.LazyModal.FeedbackwithFeedbackCategory.UxIssueprefilled.Behavior
streak.current >= streakThreshold, GrowthBook variant enabled, destination available for the user's platform, no streak-milestone modal pending, not yet shown this session.localStorage[askForReview:dismissedAt]and the strip stays hidden forcooldownDays. After the cooldown elapses, the next 3-day streak shows it again. Engagement past step 1 (Leave a review, No, step-2 dismiss) is permanent viaActionType.AskedForReviewComplete.sessionStorage[askForReview:shownThisSession]prevents re-mounting on subsequent posts.getReviewDestination):GrowthBook
Recommend rolling out at low percentage first: the day the flag flips on, every existing user with
streak.current >= 3becomes eligible.Out of scope
BasePostContentonly renders in the post page / article modal)Test plan
pnpm --filter shared lintcleanpnpm --filter shared testclean (incl. newAskForReviewStrip,useAskForReviewVisibility,askForReviewspecs)node ./scripts/typecheck-strict-changed.jscleanComponents/AskForReviewStrip/Demo paneland exercise each destination + dismissNotes for reviewers
completeUserActionmutation accepts an arbitrary actiontype: String!(packages/shared/src/graphql/actions.ts). The two-action cooldown model in the original spec was replaced with localStorage becauseuseActions().completeActionshort-circuits on duplicate completion (line 96-98), so we cannot refreshcompletedAtfor a cooldown loop without backend changes.FeedbackModalgains adefaultCategoryprop; the No path passesFeedbackCategory.UxIssue.Made with Cursor
Preview domain
https://feat-ask-for-review-prompt.preview.app.daily.dev