feat(onboarding): new tab activation primer before signup wall#6107
Open
tsahimatsliah wants to merge 37 commits into
Open
feat(onboarding): new tab activation primer before signup wall#6107tsahimatsliah wants to merge 37 commits into
tsahimatsliah wants to merge 37 commits into
Conversation
Insert a permission-priming step at the very top of the post-install onboarding flow so users see an explicit "this is what Chrome is about to ask and which button to tap" screen before they encounter Chrome's "Change back to Google?" override-confirmation bubble. The screen recreates the dialog visually, highlights the "Keep it" button with a brand-color callout, and addresses developer skepticism with concrete trust claims (no browsing history, no clickbait, reversible in chrome://extensions). The webapp asks the extension to programmatically open chrome://newtab via the existing ping content-script bridge so the bubble appears while the user is still primed. Success is detected via a localStorage signal written by the ping script after the new-tab page broadcasts activation; failure falls through to a recovery screen with a "I activated it" retry and a "Continue without new tab" skip path. Gated behind featureOnboardingPermissionPrimer (default off) for A/B rollout. Re-entry via the existing ?r=extension param from HijackingLoginStrip now correctly skips the primer. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
TEMPORARY — remove before merging. Forces the new tab activation primer to render for every fresh install regardless of the GrowthBook featureOnboardingPermissionPrimer value. Marked with TODO(REMOVE-BEFORE-MERGE) so the override is easy to find and revert. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously the install URL was hardcoded to the production Rebrandly redirect (https://r.daily.dev/install), which always bounced the post-install tab to app.daily.dev — even for dev/staging builds. That made the local primer flow untestable end-to-end: installing a locally-built extension would open the production webapp, where the new code does not exist. Now in non-production builds the install URL points directly at the configured webapp's /onboarding path. Production behavior is unchanged. Also adds a TODO-marked one-shot console.info on the onboarding page that surfaces the primer gating conditions for local QA — to be removed along with the FORCE_PRIMER_FOR_TESTING override before merge. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Broadens the FORCE_PRIMER_FOR_TESTING bypass so the primer is visible even when the user is already logged in, has completed it before, lacks the extension marker, or arrived via ?r=extension. Also skips the localStorage write on complete so reloading re-triggers the primer. Promotes the gating-condition console log from .info to .warn so it is hard to miss in DevTools. All marked TODO(REMOVE-BEFORE-MERGE). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… builds" The dev webapp URL in .env points at local.fylla.dev, which is not actually wired up on every developer's machine, so the post-install tab opened a blank page instead of the local webapp. Restore the original production Rebrandly redirect. To exercise the primer locally, navigate to <your-local-webapp>/onboarding directly — the FORCE_PRIMER_FOR_TESTING override in onboarding.tsx renders the primer on every visit regardless of how you arrived. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
In dev builds (NODE_ENV=development) route the post-install tab to https://app.staging.daily.dev:5002/onboarding so the primer flow can be tested locally end-to-end. Production builds keep the Rebrandly redirect unchanged. Marked TODO(REMOVE-BEFORE-MERGE). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the NODE_ENV check with a literal staging URL so the post-install tab reliably opens the local primer flow regardless of how the extension is built. Marked TODO(REMOVE-BEFORE-MERGE). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Testing override so the full visual flow can be simulated locally: after the primer triggers the new tab (and Chrome's confirmation bubble appears in that new tab), redirect the originating tab to https://app.daily.dev/onboarding so the user lands on the real production signup wall. In production this redirect is unnecessary because the primer is already served from app.daily.dev. Marked TODO(REMOVE-BEFORE-MERGE). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reworks the primer recovery state to focus on a single, clear action:
re-enable daily.dev. Removes the "Continue without new tab" skip path.
The new primary CTA bounces a request through the existing ping
content-script bridge to the extension's service worker, which calls
chrome.tabs.create({ url: 'chrome://extensions' }) — web origins cannot
navigate to chrome:// URLs directly. Also adds a stylized mockup of the
daily.dev card on the extensions page with a callout on the on/off
toggle so users know exactly what to flip.
If the bridge fails (extension already disabled, content script gone),
nothing visibly happens, but the mockup gives the user enough context to
navigate manually.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Testing override so the new tab does not fall into the boot/API "Connection lost" UI when running an unsigned local extension build. On the very first new tab after install we (a) broadcast activation back to the primer and (b) immediately redirect the tab to app.daily.dev/onboarding so the user lands on the real signup wall. Implemented at the top of the App component (before providers render) so no API call ever fires. Subsequent new tabs and the action-button new tab (?source=button) render normally. Marked TODO(REMOVE-BEFORE-MERGE). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ensions page Two fixes: 1. Remove the post-trigger window.location.href redirect from the primer tab. The redirect lives on the new tab itself (extension App.tsx), so the primer tab should stay in its waiting state and detect activation via the localStorage signal. This is the intended split: new tab handles its own handoff, primer tab observes the success signal. 2. The "Open extensions page" button now falls through to copying chrome://extensions to the clipboard with an inline confirmation message if the extension bridge fails (the most common failure path: the user picked "Change it back", Chrome disabled the extension, and the service worker is no longer there to receive the bridge message). Also handles the rare case where clipboard write is blocked by showing a plain instruction. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per request, the "Open extensions page" button no longer copies to clipboard when the bridge fails. It tries to navigate via the extension service worker, and if that fails (almost always because Chrome disabled the extension when the user picked "Change it back") shows an inline message telling the user to open chrome://extensions manually. Web pages cannot navigate to chrome:// URLs without an extension proxying the call, so when the extension is disabled the only option is to instruct the user. No JS workaround exists for this Chrome browser restriction. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… page The primer no longer lives inside /onboarding. A new /activate page hosts only the new-tab activation flow — no auth wall, no funnel, no gating. The user stays on that page until the new-tab override is detected (via the localStorage bridge), at which point we router.replace to /onboarding so the existing signup flow takes over. This makes the post-install funnel two clearly separated steps instead of a conditional state inside /onboarding: install → /activate (primer) → success → /onboarding (signup) Also removes all the testing scaffolding I had layered on /onboarding (FORCE_PRIMER_FOR_TESTING, debug console.warn, shouldShowPrimerProd, ?r=extension skip, related imports). /onboarding goes back to its pre-PR shape. Install URL constant now points at /activate so the post-install tab opens the primer directly. Production deployment also needs the Rebrandly redirect updated to /activate (noted in the comment). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a 2s heartbeat from the primer page to the extension service
worker via the existing content-script bridge. Chrome has no event for
"user picked Change it back", but it disables our extension as a side
effect — at which point browser.runtime.sendMessage from the content
script starts throwing "Extension context invalidated". The heartbeat
catches that and flips the primer to the recovery screen after two
consecutive missed pings (~4-7s), well before the 10s post-trigger
blind timeout would fire.
Mechanics:
- New ExtensionMessageType.PingExtensionAlive
- Background returns { alive: true }
- New bridge helper pingExtensionFromPage with 3s timeout
- Ping content script forwards and catches throws/rejects
- Primer runs interval heartbeat from mount until completion/recovery
- One forgiveness slot per cycle to absorb service-worker cold starts
Also threads the existing recovery transition through a single
goToRecovery helper so the three paths (storage-key rejection signal,
post-trigger timeout, heartbeat failure) stay in sync on log events
and idempotency.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Applies the design review's biggest moves so the screen does its one
real job: transfer the muscle memory for tapping "Keep it" on the
Chrome dialog that's about to appear.
• Headline changes from "Please activate your new tab" (a beg) to
"Tap 'Keep it' when Chrome asks." (a coaching cue). Smaller too —
Title1 instead of Mega2, so it sits on an instruction card, not a
marketing hero.
• CTA button text changes from "Activate" to "Keep it" — same word,
same hand position, same outcome as the Chrome bubble. The button
is now training the click that's 1 second away.
• Replaces the video placeholder with a layered visual: the static
Chrome dialog mockup (already with the brand-color glow on the
"Keep it" button) sits on top of a stylized blurred peek of the
new-tab feed. The feed peek counters Chrome's "Change back to
Google?" framing — gives the user something concrete to keep,
instead of an "unknown extension."
• Three trust bullets restored under the CTA — Private / Curated /
Reversible — each a single line, no defensive "Chrome shows this
prompt…" footnote (which was inviting the very objection it tried
to dispel). Bullets are tertiary text, footnote size — present
without dominating.
• Adds a "Step 1 of 1 · Takes 2 seconds" chip above the headline as
an urgency tell — communicates this is the only friction, no
surprise downstream steps.
• Tightens vertical density (gap-5 inner, py-8 outer) so headline +
visual + CTA + bullets all sit above the fold on a 13" laptop.
Recovery screen unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…meMs
Closes the remaining gaps from the design review:
• Adds the missing one-line subhead under the headline:
"Chrome's confirmation pops up the moment you click below."
Headline + subhead now form a tight instruction card (per
"reduce headline size ~30%, pair it immediately with a one-line
subhead"). Headline sized to LargeTitle so it carries weight
without dominating.
• Adds the missing caption under the layered visual:
"This is what opens every time you hit ⌘T."
This is the payoff phrase the review called out — turns
"Keep it" from "keep some unknown extension" into "keep this
feed I'm looking at."
• Adds the KeepItClickTimeMs telemetry. Captures Date.now() in a
ref when the primer CTA is clicked, then computes the delta and
attaches it as `extra: { keep_it_click_time_ms: N }` on the
ExtensionNewTabActivated event. Small values = priming worked;
large values = the user hesitated on Chrome's bubble.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…eo placeholder Per feedback the feed-cards-grid backdrop wasn't reading right. Restored the simpler video/GIF placeholder slot (dashed box, play-icon, label) so the layout, sizing, and position are exercised in advance — when the real recording is ready, replacing <VideoPlaceholder /> with a <video> element is a one-line swap. Kept the payoff caption underneath the slot: "This is what opens every time you hit ⌘T." Deleted ChromeDialogMockup entirely (it was the basis of the layered visual that's now gone). Going forward the real dialog and click target live in the video itself — much higher fidelity than any hand-rolled mockup could be. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… Cloudinary before merge)
I didn't have Cloudinary credentials in this environment, so the demo
video file (~395 KB) is committed to webapp/public/activate-demo.mp4
and the primer's <video> element references it as /activate-demo.mp4.
Next.js serves public files at the root path, so the URL resolves on
both local dev (app.staging.daily.dev:5002) and any future deploy.
Replaces the dashed-box VideoPlaceholder with a real <video> element
using the canonical autoplay-loop attributes pattern from
OnboardingPlusVariationV1.tsx: muted + autoPlay + loop + playsInline +
disablePictureInPicture + controls={false}.
TODO(BEFORE-MERGE): upload the video to Cloudinary and swap the
ACTIVATION_DEMO_URL constant for the hosted URL (plus a poster image
URL). Then drop the binary from the repo. Marked clearly in the
component comment.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…Keep-it annotation
Addressing the priority issues from the second design review:
1. Feed peek was wasted in the video composite (modal covered the
blur, caption promised what the image couldn't deliver). Adopted
Option B from the review: drop the video composite entirely. Now
two clean artifacts stacked vertically — Chrome dialog mockup on
top (clean dark background, no busy backdrop), feed screenshot
placeholder below with the "every new tab" caption.
2. Chrome's real palette puts "Change it back" as the prominent
primary blue and "Keep it" as the lighter secondary — exactly
the conversion problem. Mockup now mirrors that reality
faithfully so the user recognizes the actual dialog, but we
compensate aggressively: brand-green ring + animate-pulse on
"Keep it", plus the existing animated arrow and "Tap this one"
label below the dialog. The annotation has to be louder than
Chrome's primary styling.
3. Subhead was filler. Rewrote to do real work:
"In 1 second, Chrome will pop up this exact box. Tap the left
button." Now the subhead reinforces what (this exact box), when
(1 second), and which button (left).
4. ⌘T was Mac-only — read as gibberish on Windows/Linux/ChromeOS.
Replaced with universal: "Every time you open a new tab, you'll
see this."
Minor polish:
- Dropped headline trailing period (punchier as a directive).
- Step 1 of 1 pill gets a hairline border and slightly larger
typo-footnote sizing so it doesn't read as a tooltip.
- Bullets wrapped in a centered max-w-[24rem] block so they're
visually centered as a unit while keeping left-aligned scannable
rows underneath.
Also deletes the temporary /activate-demo.mp4 binary that was a
stopgap for testing — now the page renders without it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adopts the corrected design brief. The job of this page isn't "show
the feed payoff" — there's no feed to show yet — it's "remove every
surprise between install and the Chrome bubble so the reflex is Keep
it, not get-this-away-from-me."
Changes:
1. Three-step journey rail under the dialog mock:
Keep it (1, highlighted) → Sign in (5s) (2) → Tab live (3)
The user has now seen the signup wall coming, so when they hit
it 5 seconds from now, no surprise-fueled buyer's remorse.
2. Step pill honesty: "Step 1 of 1 · Takes 2 seconds" was lying —
there are actually three steps. Changed to "Step 1 of 3 · ~10
seconds total". Honesty pre-empts the same reflex it was trying
to suppress.
3. Trust bullets reordered — Reversibility moves to FIRST. It's the
direct antidote to Chrome's "what if I don't like this" reflex.
Private and Curated stay as supporting evidence.
4. Social proof under CTA: "Trusted by millions of developers ·
4.8★ on the Chrome Web Store" — reframes Keep it from "trust
this stranger" to "join the obvious default."
5. CTA microcopy: "Then a 5-second sign-in, and you're in." —
the journey preview embedded in the moment of commitment.
6. "Why is Chrome asking this?" disclosure restored as a visible,
clickable single line under the bullets. The skeptical user
(most likely to click Change it back) is also the most likely
to open the disclosure and have their paranoia drop. Fires
LogEvent.ExtensionPrimerWhyChromeExpanded on first expansion
so we can segment conversion by openers vs non-openers — the
review's bet is openers convert 1.5-2x.
7. Subhead tightened: drop "In 1 second" — adds wording, no signal.
Page structure now: pill → headline → subhead → dialog mock →
journey rail → CTA → CTA microcopy → social proof → 3 bullets →
disclosure. Every element either predicts a moment, defuses an
objection, or transfers muscle memory.
The one metric to watch (per review):
ExtensionNewTabActivated / ExtensionPrimerShown — full survival
rate through the Chrome bubble. Segment by ExtensionPrimerWhy-
ChromeExpanded to test the disclosure hypothesis.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Strips the activate page to the three elements that actually do
muscle-memory transfer for the upcoming Chrome bubble:
1. Headline: "One click and you're in" — forward intent, zero
defensiveness, zero warning tone. The user already chose to
install; the page does not re-sell them.
2. Demo video — large, centered, autoplay/loop/muted. Subtitles
intended to be burned into the video itself (one artifact
teaching the action visually AND verbally, which is how humans
learn motor sequences). Subtitle script for the production
re-record is in the TODO above ACTIVATION_DEMO_URL.
3. One button: "Activate new tab" — triggers the sequence. The
video does the Keep-it mirror; the button just starts the flow.
Stripped (every element below was re-selling the user / planting
doubts that didn't exist / re-explaining what the video already
shows):
- Step pill ("Step 1 of 3 · ~10 seconds total")
- Subhead
- Static Chrome dialog mockup (ChromeDialogMockup) — replaced by the
video, which does the same job and adds the cursor motion
- 3-step journey rail (JourneyRail)
- CTA microcopy ("Then a 5-second sign-in, and you're in.")
- Social proof line ("Trusted by millions of developers · 4.8★ …")
- Three trust bullets (TrustBullets) — Reversible / Private /
Curated. Each one raised an objection the user hadn't formed yet;
cumulatively they read as defensive theatre.
- "Why is Chrome asking this?" disclosure (WhyChromeAsks)
State machine, bridges, heartbeat, polling, telemetry, recovery
screen — all untouched. Only the non-recovery presentation layer
was rewritten.
Restored /activate-demo.mp4 in webapp/public (was deleted in a
previous iteration when the video composite was abandoned). Same
TODO(BEFORE-MERGE) as before: upload to Cloudinary, drop the binary
from the repo.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… one reassurance
The previous "minimal" pass confused minimal with clear. Five elements,
each one explicitly teaching the upcoming Chrome interaction:
1. Headline (explicit, not poetic):
"Almost there — Chrome will ask one quick question."
Tells the user what's about to happen. No surprise to recoil from.
2. Sub-line (which button, what it does):
"Tap "Keep it" to finish setting up your new tab."
Names the click and the outcome.
3. Video — sized large. Page container widens to max-w-[48rem]
(was 36rem) and the video drops its own width cap to fill that
container. ~50% of viewport height on a 13" laptop. Burned-in
subtitles still TODO for the re-record.
4. CTA: "Keep it" — mirrors Chrome's button now that the page has
taught the word.
5. One reassurance line under the CTA (replaces the three trust
bullets):
"Takes 2 seconds. Reversible anytime."
Reversibility is the one bullet that actively defuses
"Change it back". Privacy and curation are battles already won
at the Chrome Web Store listing.
Recovery headline changed from "Almost there" to "Let's turn it back
on" to avoid collision with the new primary headline. Recovery body
shortened.
Same five-element philosophy applied — every element on this page
reinforces "in 2 seconds, tap the left button." Everything else is
removed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The video element was using `aspect-video` (16:9), but the actual source file is 1748x1080 — wider than 16:10. The mismatch was letterboxing the playback inside the element, producing left/right empty gaps. Replaced aspect-video with aspect-[1748/1080] so the element hugs the frame. Comment notes the value needs to be updated when the production-quality video (with burned-in subtitles) is recorded at a different dimension. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…covery
Three updates in one pass:
1. Subhead bumped from Body to Title3 so it carries proper weight
against the LargeTitle headline. Applied to both states (primary
"Almost there…" and recovery "Let's turn it back on") so they
match visually.
2. Added text-wrap: balance to both the headline and subhead. This
prevents the orphaned-trailing-word "floating line" effect on
copy that wraps at narrow widths — the wrapper distributes lines
evenly instead.
3. Recovery screen now uses the same ActivationDemoVideo as the
primary state (replacing ExtensionsPageCardMockup). A different
re-enable-focused video can be dropped in later; for now both
states render the same demo so the recovery layout matches the
primary almost exactly. ExtensionsPageCardMockup deleted —
unused.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous copy made the user parse two abstract sentences before they knew what to do: - "Almost there — Chrome will ask one quick question." - "Tap 'Keep it' to finish setting up your new tab." The headline communicated "something is about to happen" without saying what. The subhead buried the action behind framing. New copy leads with welcome + the action verb, drops the abstract framing entirely: - "Welcome! Let's activate your new tab." - "Tap 'Keep it' on the Chrome popup." Headline is now proactive (parallels the recovery state's "Let's turn it back on"); subhead names the click and the surface in seven words. Lower cognitive load, no second-guessing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ActivationDemoVideo now takes a `variant` prop and serves a different video + matching aspect ratio per state: - variant="primary" → /activate-demo.mp4 at aspect-[1748/1080] - variant="recovery" → /recovery-demo.mp4 at aspect-[1152/720] Recovery screen previously reused the primary clip as a stand-in; now it gets its dedicated walkthrough showing how to re-enable daily.dev from chrome://extensions. Same TODO(BEFORE-MERGE) as the primary video: upload both to Cloudinary and drop the binaries from public/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
"Keep it" was chosen to mirror Chrome's confirmation button for muscle-memory transfer, but on the primer page itself the word is ambiguous — keep what? Users who skim past the video lose the context that the button is preparing them for. "Open new tab" is explicit about what clicking will do: it literally triggers chrome.tabs.create which opens a new tab where Chrome will then ask the override question. Less cognitive load, no mistranslation if the user doesn't watch the demo. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…oter
The primer's outer container was using min-h-dvh, which made it claim
a full viewport height of its own — but the activate page also has
OnboardingHeader above it and FooterLinks below. Total stacked height
exceeded the viewport, so the centered content was anchored to its
own 100vh block, not the visible area.
Switched min-h-dvh → flex-1 on the primer's outer container. Now it
grows to fill the remaining space inside the page's flex column
(viewport - header - footer), and its existing items-center +
justify-center actually center the content within the visible area.
Applies to both the primary ("Welcome! Let's activate your new tab.")
and recovery ("Let's turn it back on") states, since both use the
same outer container.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ambiguous "it" Headline: "Let's turn it back on" → "Let's get your new tab back" - Names what the user actually wants (their new tab) - Drops the ambiguous "it" — no parsing needed Subhead: "Open the extensions page, find daily.dev, and flip the toggle back on." → "Open chrome://extensions and toggle daily.dev on." — same instruction, half the words. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…bridge failure
When the "Open extensions page" bridge fails (extension disabled →
service worker gone → web origin can't navigate to chrome://), we now:
1. Automatically write "chrome://extensions" to the clipboard on the
same click that produced the failure.
2. Render a copy-styled pill displaying the URL in monospace, with a
copy icon. Clicking the pill re-copies and flashes a check-icon
"Copied" state for 2 seconds before resetting.
Updated hint copy:
Old: "Couldn't open it automatically — open a new tab and go to
chrome://extensions to turn daily.dev back on."
New: "Couldn't open it automatically. We copied the link for
you — paste it into a new tab."
The pill is the affordance — short message + visible URL + one tap to
re-copy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The activate page has exactly one job: get the user to tap "Open new tab" so Chrome's bubble can be activated. The standard footer (Daily Dev Ltd · Guidelines · Explore · Tags · Sources · Squads · Leaderboard) is a forest of escape hatches from that single goal — every link is a chance for the user to wander off mid-activation. Removing FooterLinks. The page is now header + primer, nothing else. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three small adjustments to the primary "Welcome! Let's activate your new
tab." screen — recovery state unchanged:
- Video capped at max-w-[43rem] (~90% of the 48rem container) so it
no longer dominates the page.
- CTA button bumped Large → XLarge — more visual weight on the only
action on the page.
- Gap between the button and the "Takes 2 seconds. Reversible
anytime." line went from gap-2 (0.5rem) to gap-4 (1rem), and the
line bumped from typo-caption1 to typo-callout so it's readable
next to a larger button without competing with it.
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
chrome://newtab(via the existing ping content-script bridge) so the dialog appears while the user is still primed; success is detected via a localStorage signal, failure falls through to a recovery screen.chrome://extensions), and an expandable "Why does Chrome ask this?" disclosure.featureOnboardingPermissionPrimer(default off) for A/B rollout. Re-entry via the existing?r=extensionparam fromHijackingLoginStripnow correctly skips the primer (the param was previously unread).Why
Today only a minority of installs end up with the daily.dev new tab actually active, because the user first hits the signup wall and only later encounters Chrome's confirmation bubble — at which point the "Change back to Google?" framing nudges them toward reverting. New-tab activation correlates strongly with retention, so converting more of those bubble interactions is a bigger lever than signup itself.
Implementation notes
chrome://newtabis used inchrome.tabs.create—chrome.runtime.getURL('index.html')would load the override page directly viachrome-extension://and would not register as an NTP visit, deferring the bubble.chrome.management.onDisabledwas considered as a rejection signal but does not fire for the listening extension's own self-disable; the primer relies on a 10s timeout + recovery screen instead.#1a73e8/#d3e3fd) so the preview matches what the user sees seconds later.Telemetry
New
LogEvents for the full funnel:ExtensionPrimerShown,ExtensionPrimerCtaClick,ExtensionNewTabTriggeredExtensionNewTabActivated(also fires once per install from the new tab page itself)ExtensionDialogRejected,ExtensionPrimerRecoveryShown,ExtensionPrimerSkippedTest plan
featureOnboardingPermissionPrimeron: confirm primer renders before the signup wall.ExtensionNewTabActivatedfires and the primer auto-advances to signup.HijackingPagestill works and routing through?r=extensionskips the primer on re-entry.🤖 Generated with Claude Code
Preview domain
https://claude-quirky-murdock-99152e.preview.app.daily.dev