From 37a1f31b3f6dd94ffbf0efd758d1e8759f6e730f Mon Sep 17 00:00:00 2001 From: Tsahi Matsliah Date: Sun, 14 Jun 2026 10:21:15 +0300 Subject: [PATCH 01/14] feat(shared): add ExtensionShowcase onboarding component Sider-style feature walkthrough for the daily.dev extension: a vertical feature dock on desktop that collapses to a horizontal scrollable tab strip on mobile, with a prop-driven media (video/image) + title + description detail panel. Ships with the default daily.dev feature set and a Storybook story. Reusable across onboarding, modals, and pages. Co-Authored-By: Claude Opus 4.8 --- .../ExtensionShowcase.spec.tsx | 48 +++++ .../ExtensionShowcase/ExtensionShowcase.tsx | 201 ++++++++++++++++++ .../ExtensionShowcaseMedia.tsx | 56 +++++ .../ExtensionShowcase/defaultFeatures.tsx | 98 +++++++++ .../onboarding/ExtensionShowcase/types.ts | 30 +++ .../components/ExtensionShowcase.stories.tsx | 37 ++++ 6 files changed, 470 insertions(+) create mode 100644 packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.spec.tsx create mode 100644 packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.tsx create mode 100644 packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcaseMedia.tsx create mode 100644 packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx create mode 100644 packages/shared/src/components/onboarding/ExtensionShowcase/types.ts create mode 100644 packages/storybook/stories/components/ExtensionShowcase.stories.tsx diff --git a/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.spec.tsx b/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.spec.tsx new file mode 100644 index 00000000000..7523a375e9e --- /dev/null +++ b/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.spec.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { ExtensionShowcase } from './ExtensionShowcase'; +import { defaultExtensionShowcaseFeatures } from './defaultFeatures'; + +describe('ExtensionShowcase', () => { + it('shows the first feature detail by default', () => { + render(); + + const [first] = defaultExtensionShowcaseFeatures; + expect( + screen.getByRole('heading', { name: first.title }), + ).toBeInTheDocument(); + expect(screen.getByText(first.description)).toBeInTheDocument(); + }); + + it('swaps the detail panel when a feature is selected', () => { + const onFeatureChange = jest.fn(); + render(); + + const target = defaultExtensionShowcaseFeatures[2]; + // both desktop dock and mobile strip render a button per feature + const [button] = screen.getAllByRole('button', { name: target.label }); + fireEvent.click(button); + + expect(onFeatureChange).toHaveBeenCalledWith(target.id); + expect( + screen.getByRole('heading', { name: target.title }), + ).toBeInTheDocument(); + }); + + it('respects defaultFeatureId', () => { + const target = defaultExtensionShowcaseFeatures[3]; + render(); + + expect( + screen.getByRole('heading', { name: target.title }), + ).toBeInTheDocument(); + }); + + it('hides the CTA when ctaLabel is empty', () => { + render(); + + expect( + screen.queryByRole('link', { name: /add to browser/i }), + ).not.toBeInTheDocument(); + }); +}); diff --git a/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.tsx b/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.tsx new file mode 100644 index 00000000000..6cedbb3a8d7 --- /dev/null +++ b/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.tsx @@ -0,0 +1,201 @@ +import type { ReactElement } from 'react'; +import React, { useState } from 'react'; +import classNames from 'classnames'; +import { + Typography, + TypographyColor, + TypographyTag, + TypographyType, +} from '../../typography/Typography'; +import { Button } from '../../buttons/Button'; +import { ButtonVariant, ButtonSize } from '../../buttons/common'; +import { ChromeIcon } from '../../icons'; +import { downloadBrowserExtension } from '../../../lib/constants'; +import { anchorDefaultRel } from '../../../lib/strings'; +import { ExtensionShowcaseMedia } from './ExtensionShowcaseMedia'; +import { defaultExtensionShowcaseFeatures } from './defaultFeatures'; +import type { ExtensionShowcaseFeature } from './types'; + +export interface ExtensionShowcaseProps { + /** Features to render. Defaults to the daily.dev extension feature set. */ + features?: ExtensionShowcaseFeature[]; + /** Feature selected on first render. Defaults to the first feature. */ + defaultFeatureId?: string; + /** Optional heading above the showcase. */ + title?: string; + /** Optional sub-heading above the showcase. */ + description?: string; + /** CTA button label. Pass an empty string to hide the CTA. */ + ctaLabel?: string; + /** CTA destination. Defaults to the extension download link. */ + ctaHref?: string; + /** Fires when the CTA is clicked (for tracking / step transitions). */ + onCtaClick?: () => void; + /** Fires when the selected feature changes. */ + onFeatureChange?: (featureId: string) => void; + className?: string; +} + +interface ShowcaseNavItemProps { + feature: ExtensionShowcaseFeature; + isActive: boolean; + vertical: boolean; + onClick: () => void; +} + +function ShowcaseNavItem({ + feature, + isActive, + vertical, + onClick, +}: ShowcaseNavItemProps): ReactElement { + return ( + + ); +} + +export function ExtensionShowcase({ + features = defaultExtensionShowcaseFeatures, + defaultFeatureId, + title, + description, + ctaLabel = 'Add to browser', + ctaHref = downloadBrowserExtension, + onCtaClick, + onFeatureChange, + className, +}: ExtensionShowcaseProps): ReactElement { + if (!features.length) { + throw new Error('ExtensionShowcase requires at least one feature'); + } + + const [activeId, setActiveId] = useState(defaultFeatureId ?? features[0].id); + const activeFeature = + features.find((feature) => feature.id === activeId) ?? features[0]; + + const selectFeature = (featureId: string): void => { + setActiveId(featureId); + onFeatureChange?.(featureId); + }; + + return ( +
+ {(title || description) && ( +
+ {title && ( + + {title} + + )} + {description && ( + + {description} + + )} +
+ )} + +
+ + + + +
+ +
+ + {activeFeature.title} + + + {activeFeature.description} + +
+
+
+ + {ctaLabel && ( + + )} +
+ ); +} diff --git a/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcaseMedia.tsx b/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcaseMedia.tsx new file mode 100644 index 00000000000..02486af61b2 --- /dev/null +++ b/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcaseMedia.tsx @@ -0,0 +1,56 @@ +import type { ReactElement } from 'react'; +import React from 'react'; +import classNames from 'classnames'; +import type { ExtensionShowcaseMedia as Media } from './types'; + +interface ExtensionShowcaseMediaProps { + media?: Media; + className?: string; +} + +export function ExtensionShowcaseMedia({ + media, + className, +}: ExtensionShowcaseMediaProps): ReactElement { + const wrapperClass = classNames( + 'relative aspect-video w-full overflow-hidden rounded-16 border border-border-subtlest-tertiary bg-surface-float', + className, + ); + + if (!media) { + return
; + } + + if (media.type === 'video') { + return ( +
+
+ ); + } + + return ( +
+ {media.alt +
+ ); +} diff --git a/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx b/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx new file mode 100644 index 00000000000..912ef74d410 --- /dev/null +++ b/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx @@ -0,0 +1,98 @@ +import React from 'react'; +import { + HomeIcon, + SearchIcon, + BookmarkIcon, + TLDRIcon, + ReadingStreakIcon, + SquadIcon, + ShortcutsIcon, +} from '../../icons'; +import { + cloudinaryOnboardingActivationDemo, + cloudinaryOnboardingExtension, +} from '../../../lib/image'; +import { BrowserName } from '../../../lib/func'; +import type { ExtensionShowcaseFeature } from './types'; + +const screenshot = cloudinaryOnboardingExtension[BrowserName.Chrome]; + +// Real per-feature demo assets do not exist yet — these reuse the existing +// onboarding screenshot/video as placeholders. Swap `media` per feature once +// design delivers dedicated clips. +const placeholderImage: ExtensionShowcaseFeature['media'] = { + type: 'image', + src: screenshot.default, + retinaSrc: screenshot.retina, + alt: 'daily.dev extension preview', +}; + +export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ + { + id: 'feed', + label: 'New tab feed', + icon: , + title: 'Your personalized feed in every new tab', + description: + 'Turn every new tab into a developer feed tuned to your stack. The moment you open your browser, the best content from across the dev world is already waiting for you.', + media: { + type: 'video', + src: cloudinaryOnboardingActivationDemo, + alt: 'daily.dev new tab feed in action', + }, + }, + { + id: 'search', + label: 'AI search', + icon: , + title: 'Search the dev world, powered by AI', + description: + 'Ask anything and get answers pulled from real developer content instead of generic results. Your AI co-pilot lives one tab away.', + media: placeholderImage, + }, + { + id: 'bookmarks', + label: 'Bookmarks', + icon: , + title: 'Save now, read when it clicks', + description: + 'Bookmark any post in a click and pick it back up later on any device. Never lose that article you meant to get to.', + media: placeholderImage, + }, + { + id: 'companion', + label: 'Companion', + icon: , + title: 'Read smarter with the companion', + description: + 'The daily.dev companion rides along on any article, giving you an instant TLDR, the upvotes, and the discussion right on the page you are reading.', + media: placeholderImage, + }, + { + id: 'streak', + label: 'Reading streaks', + icon: , + title: 'Build a reading habit that sticks', + description: + 'Keep your streak alive and stay sharp. A little every day adds up, and daily.dev keeps you coming back.', + media: placeholderImage, + }, + { + id: 'squads', + label: 'Squads', + icon: , + title: 'Find your people in Squads', + description: + 'Join developer communities built around what you love. Share, learn, and grow alongside millions of developers.', + media: placeholderImage, + }, + { + id: 'shortcuts', + label: 'Shortcuts', + icon: , + title: 'Your shortcuts, one tab away', + description: + 'Pin the tools and sites you open every day so they are always a click away from your new tab.', + media: placeholderImage, + }, +]; diff --git a/packages/shared/src/components/onboarding/ExtensionShowcase/types.ts b/packages/shared/src/components/onboarding/ExtensionShowcase/types.ts new file mode 100644 index 00000000000..fe3f05c323b --- /dev/null +++ b/packages/shared/src/components/onboarding/ExtensionShowcase/types.ts @@ -0,0 +1,30 @@ +import type { ReactElement } from 'react'; + +export type ExtensionShowcaseMedia = + | { + type: 'video'; + src: string; + poster?: string; + alt?: string; + } + | { + type: 'image'; + src: string; + retinaSrc?: string; + alt?: string; + }; + +export interface ExtensionShowcaseFeature { + /** Stable identifier, also used for tracking. */ + id: string; + /** Short label shown in the left dock / mobile tab strip. */ + label: string; + /** Icon rendered in the dock. Should accept `secondary` and `className`. */ + icon: ReactElement; + /** Detail panel heading. */ + title: string; + /** Detail panel body copy. */ + description: string; + /** Right-side media. Video is autoplayed muted/looped; image supports retina. */ + media?: ExtensionShowcaseMedia; +} diff --git a/packages/storybook/stories/components/ExtensionShowcase.stories.tsx b/packages/storybook/stories/components/ExtensionShowcase.stories.tsx new file mode 100644 index 00000000000..d920a89eca7 --- /dev/null +++ b/packages/storybook/stories/components/ExtensionShowcase.stories.tsx @@ -0,0 +1,37 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { ExtensionShowcase } from '@dailydotdev/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase'; + +const meta: Meta = { + title: 'Components/Onboarding/ExtensionShowcase', + component: ExtensionShowcase, + parameters: { + layout: 'fullscreen', + }, + render: (args) => ( +
+ +
+ ), +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + title: 'Everything daily.dev does in your browser', + description: + 'A quick tour of what the extension unlocks the moment you install it.', + }, +}; + +export const WithoutHeader: Story = { + args: {}, +}; + +export const CustomCta: Story = { + args: { + title: 'Make every tab count', + ctaLabel: 'Get it for Chrome', + }, +}; From 1a18f34767f2e6fb6613fb2761ab75dea761e212 Mon Sep 17 00:00:00 2001 From: Tsahi Matsliah Date: Sun, 14 Jun 2026 10:47:06 +0300 Subject: [PATCH 02/14] feat(shared): refine ExtensionShowcase feature set and copy Reorder the default features for conversion and rewrite to benefit-led copy grounded in real extension capabilities: - add "Read it here" (in-app reader) to highlight reduced tab clutter - reframe companion around context/comments/related articles - sharpen AI search around community-vetted dev content - tie shortcuts and reading streak to the new-tab takeover Co-Authored-By: Claude Opus 4.8 --- .../ExtensionShowcase/defaultFeatures.tsx | 62 +++++++++++-------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx b/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx index 912ef74d410..69cb9aad15e 100644 --- a/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx +++ b/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx @@ -1,12 +1,13 @@ import React from 'react'; import { HomeIcon, + EmbedIcon, + TLDRIcon, SearchIcon, BookmarkIcon, - TLDRIcon, + ShortcutsIcon, ReadingStreakIcon, SquadIcon, - ShortcutsIcon, } from '../../icons'; import { cloudinaryOnboardingActivationDemo, @@ -27,27 +28,47 @@ const placeholderImage: ExtensionShowcaseFeature['media'] = { alt: 'daily.dev extension preview', }; +// Ordered for conversion: lead with what the extension *is* (the new tab), +// then the "aha" tab-saving and context features, then habit and community. export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ { id: 'feed', label: 'New tab feed', icon: , - title: 'Your personalized feed in every new tab', + title: 'Every new tab becomes your dev briefing', description: - 'Turn every new tab into a developer feed tuned to your stack. The moment you open your browser, the best content from across the dev world is already waiting for you.', + 'Stop staring at a blank page. Every new tab opens to a feed tuned to your stack, so you stay in the loop on the best dev content throughout your work routine — without ever going looking for it.', media: { type: 'video', src: cloudinaryOnboardingActivationDemo, alt: 'daily.dev new tab feed in action', }, }, + { + id: 'read-here', + label: 'Read it here', + icon: , + title: 'Read articles without the tab chaos', + description: + 'Open any post right inside daily.dev instead of spawning yet another tab. Read distraction-free with the discussion right beside it, and keep your browser clean.', + media: placeholderImage, + }, + { + id: 'companion', + label: 'Companion', + icon: , + title: 'Context on every article you read', + description: + 'The daily.dev companion rides along on any site you visit. Get an instant TLDR, see what the community thinks in the comments, and surface more relevant articles — right on the page.', + media: placeholderImage, + }, { id: 'search', label: 'AI search', icon: , - title: 'Search the dev world, powered by AI', + title: 'Ask anything, get real dev answers', description: - 'Ask anything and get answers pulled from real developer content instead of generic results. Your AI co-pilot lives one tab away.', + 'Skip the SEO spam. daily.dev search only pulls from developer content the community has actually read and upvoted, so your answers come from sources you can trust.', media: placeholderImage, }, { @@ -56,25 +77,25 @@ export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ icon: , title: 'Save now, read when it clicks', description: - 'Bookmark any post in a click and pick it back up later on any device. Never lose that article you meant to get to.', + 'Bookmark anything in a click and pick it back up later on any device. Never lose that article you meant to get to.', media: placeholderImage, }, { - id: 'companion', - label: 'Companion', - icon: , - title: 'Read smarter with the companion', + id: 'shortcuts', + label: 'Shortcuts', + icon: , + title: 'Keep your shortcuts on every new tab', description: - 'The daily.dev companion rides along on any article, giving you an instant TLDR, the upvotes, and the discussion right on the page you are reading.', + 'Pin the tools and sites you open all day — GitHub, your docs, your dashboards — so they stay one click away and you never lose them when daily.dev takes over the new tab.', media: placeholderImage, }, { id: 'streak', - label: 'Reading streaks', + label: 'Reading streak', icon: , - title: 'Build a reading habit that sticks', + title: 'Keep your streak, keep your momentum', description: - 'Keep your streak alive and stay sharp. A little every day adds up, and daily.dev keeps you coming back.', + 'With daily.dev in every new tab, staying sharp becomes automatic. Show up, keep your streak alive, and always find something worth reading.', media: placeholderImage, }, { @@ -83,16 +104,7 @@ export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ icon: , title: 'Find your people in Squads', description: - 'Join developer communities built around what you love. Share, learn, and grow alongside millions of developers.', - media: placeholderImage, - }, - { - id: 'shortcuts', - label: 'Shortcuts', - icon: , - title: 'Your shortcuts, one tab away', - description: - 'Pin the tools and sites you open every day so they are always a click away from your new tab.', + 'Join developer communities built around the tools and topics you love. Share what you are learning and grow alongside millions of developers.', media: placeholderImage, }, ]; From 654b547cb5dc2ae5809bbf6efe5ff8579f4721d0 Mon Sep 17 00:00:00 2001 From: Tsahi Matsliah Date: Sun, 14 Jun 2026 11:08:28 +0300 Subject: [PATCH 03/14] feat(shared): make ExtensionShowcase extension-exclusive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop webapp-also features (AI search, Squads, bookmarks) that don't motivate installing the extension. Keep only browser-level powers the website can't replicate, with clever benefit-led framing: - new tab takeover, companion on any site, read-it-here embedded reading - top-sites shortcuts, ambient streak habit - add "Focus mode" (pause the new tab) — control + install objection-handling Co-Authored-By: Claude Opus 4.8 --- .../ExtensionShowcase/defaultFeatures.tsx | 63 +++++++------------ 1 file changed, 22 insertions(+), 41 deletions(-) diff --git a/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx b/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx index 69cb9aad15e..ebe9220239e 100644 --- a/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx +++ b/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx @@ -1,13 +1,11 @@ import React from 'react'; import { HomeIcon, - EmbedIcon, TLDRIcon, - SearchIcon, - BookmarkIcon, + EmbedIcon, ShortcutsIcon, ReadingStreakIcon, - SquadIcon, + PauseIcon, } from '../../icons'; import { cloudinaryOnboardingActivationDemo, @@ -28,8 +26,9 @@ const placeholderImage: ExtensionShowcaseFeature['media'] = { alt: 'daily.dev extension preview', }; -// Ordered for conversion: lead with what the extension *is* (the new tab), -// then the "aha" tab-saving and context features, then habit and community. +// Every item here answers "why the extension, not just the website?" — these +// are browser-level powers the webapp can't replicate (owning the new tab, +// injecting into other sites, reading browser top sites, pausing the new tab). export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ { id: 'feed', @@ -37,74 +36,56 @@ export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ icon: , title: 'Every new tab becomes your dev briefing', description: - 'Stop staring at a blank page. Every new tab opens to a feed tuned to your stack, so you stay in the loop on the best dev content throughout your work routine — without ever going looking for it.', + 'You open dozens of blank tabs a day. The extension turns each one into a feed tuned to your stack — so staying current happens in the gaps of your workday, with zero effort.', media: { type: 'video', src: cloudinaryOnboardingActivationDemo, alt: 'daily.dev new tab feed in action', }, }, - { - id: 'read-here', - label: 'Read it here', - icon: , - title: 'Read articles without the tab chaos', - description: - 'Open any post right inside daily.dev instead of spawning yet another tab. Read distraction-free with the discussion right beside it, and keep your browser clean.', - media: placeholderImage, - }, { id: 'companion', label: 'Companion', icon: , - title: 'Context on every article you read', - description: - 'The daily.dev companion rides along on any site you visit. Get an instant TLDR, see what the community thinks in the comments, and surface more relevant articles — right on the page.', - media: placeholderImage, - }, - { - id: 'search', - label: 'AI search', - icon: , - title: 'Ask anything, get real dev answers', + title: 'Bring daily.dev onto any site you visit', description: - 'Skip the SEO spam. daily.dev search only pulls from developer content the community has actually read and upvoted, so your answers come from sources you can trust.', + 'Reading a doc, a blog, or a Hacker News thread? The companion sidebar rides along on the page itself — instant TLDR, what the community thinks in the comments, and related reads. This is the one thing only the extension can do.', media: placeholderImage, }, { - id: 'bookmarks', - label: 'Bookmarks', - icon: , - title: 'Save now, read when it clicks', + id: 'read-here', + label: 'Read it here', + icon: , + title: 'Read articles without drowning in tabs', description: - 'Bookmark anything in a click and pick it back up later on any device. Never lose that article you meant to get to.', + 'Open any post right inside daily.dev instead of spawning yet another tab. Distraction-free reading with the discussion beside it — your browser stays clean.', media: placeholderImage, }, { id: 'shortcuts', label: 'Shortcuts', icon: , - title: 'Keep your shortcuts on every new tab', + title: 'Your most-visited sites, pinned to every tab', description: - 'Pin the tools and sites you open all day — GitHub, your docs, your dashboards — so they stay one click away and you never lose them when daily.dev takes over the new tab.', + 'The extension pulls your top sites straight from the browser, so GitHub, your docs, and your dashboards stay one click away. Your muscle memory keeps working, even with daily.dev on the new tab.', media: placeholderImage, }, { id: 'streak', label: 'Reading streak', icon: , - title: 'Keep your streak, keep your momentum', + title: 'A daily habit that builds itself', description: - 'With daily.dev in every new tab, staying sharp becomes automatic. Show up, keep your streak alive, and always find something worth reading.', + 'Because daily.dev greets you on every new tab, keeping your streak alive takes no willpower. Show up, read one thing, stay sharp — day after day.', media: placeholderImage, }, { - id: 'squads', - label: 'Squads', - icon: , - title: 'Find your people in Squads', + id: 'focus', + label: 'Focus mode', + icon: , + title: 'Heads-down? Pause it in one click', description: - 'Join developer communities built around the tools and topics you love. Share what you are learning and grow alongside millions of developers.', + 'Need to focus, or just want your blank tab back for a while? Pause the new tab for as long as you like and point it anywhere — full control, only because the extension owns the page.', media: placeholderImage, }, ]; From bf6fa8b4d61a60fcc66a07287de18351b402326d Mon Sep 17 00:00:00 2001 From: Tsahi Matsliah Date: Sun, 14 Jun 2026 11:10:58 +0300 Subject: [PATCH 04/14] feat(shared): reprioritize ExtensionShowcase, split shortcuts Lead with "Read it here" as the top value, keep the new-tab feed high, and split shortcuts into two distinct items: - Most visited: auto-pulled from the browser's top sites - Shortcuts: custom pinned apps/tools Co-Authored-By: Claude Opus 4.8 --- .../ExtensionShowcase/defaultFeatures.tsx | 51 +++++++++++-------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx b/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx index ebe9220239e..a87dd3e53b2 100644 --- a/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx +++ b/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx @@ -1,9 +1,10 @@ import React from 'react'; import { - HomeIcon, - TLDRIcon, EmbedIcon, + HomeIcon, + SitesIcon, ShortcutsIcon, + TLDRIcon, ReadingStreakIcon, PauseIcon, } from '../../icons'; @@ -26,10 +27,20 @@ const placeholderImage: ExtensionShowcaseFeature['media'] = { alt: 'daily.dev extension preview', }; -// Every item here answers "why the extension, not just the website?" — these -// are browser-level powers the webapp can't replicate (owning the new tab, -// injecting into other sites, reading browser top sites, pausing the new tab). +// Every item answers "why the extension, not just the website?" — these are +// browser-level powers the webapp can't replicate. Ordered by install pull: +// lead with reading inside daily.dev, then the new-tab experience and the two +// shortcut flavors (auto most-visited vs. custom pins), then the rest. export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ + { + id: 'read-here', + label: 'Read it here', + icon: , + title: 'Read any article right inside daily.dev', + description: + 'No more graveyard of half-read tabs. Open links inside daily.dev in a clean reader with the discussion right beside them, and close the loop without ever leaving your feed.', + media: placeholderImage, + }, { id: 'feed', label: 'New tab feed', @@ -44,30 +55,30 @@ export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ }, }, { - id: 'companion', - label: 'Companion', - icon: , - title: 'Bring daily.dev onto any site you visit', + id: 'most-visited', + label: 'Most visited', + icon: , + title: 'Your most-visited sites, right where you left them', description: - 'Reading a doc, a blog, or a Hacker News thread? The companion sidebar rides along on the page itself — instant TLDR, what the community thinks in the comments, and related reads. This is the one thing only the extension can do.', + 'daily.dev pulls the sites you open most straight from your browser, so the new tab still knows where you were headed. No setup, nothing to relearn.', media: placeholderImage, }, { - id: 'read-here', - label: 'Read it here', - icon: , - title: 'Read articles without drowning in tabs', + id: 'shortcuts', + label: 'Shortcuts', + icon: , + title: 'Pin the apps and tools you live in', description: - 'Open any post right inside daily.dev instead of spawning yet another tab. Distraction-free reading with the discussion beside it — your browser stays clean.', + 'Add your own shortcuts to GitHub, Jira, your docs — whatever you open all day — so your essentials sit one click from every new tab.', media: placeholderImage, }, { - id: 'shortcuts', - label: 'Shortcuts', - icon: , - title: 'Your most-visited sites, pinned to every tab', + id: 'companion', + label: 'Companion', + icon: , + title: 'Bring daily.dev onto any site you visit', description: - 'The extension pulls your top sites straight from the browser, so GitHub, your docs, and your dashboards stay one click away. Your muscle memory keeps working, even with daily.dev on the new tab.', + 'Reading a doc, a blog, or a Hacker News thread? The companion sidebar rides along on the page itself — instant TLDR, what the community thinks in the comments, and related reads. This is the one thing only the extension can do.', media: placeholderImage, }, { From 77532a326e71c1cb6b4aedd38a742795162cbf5e Mon Sep 17 00:00:00 2001 From: Tsahi Matsliah Date: Sun, 14 Jun 2026 12:23:02 +0300 Subject: [PATCH 05/14] feat(shared): add Daily brief to ExtensionShowcase, enrich shortcuts Lead with the personalized daily brief (the AI "presidential briefing" delivered as a morning ritual on your first tab). Mention one-click bookmarks-bar import in the shortcuts copy. Co-Authored-By: Claude Opus 4.8 --- .../ExtensionShowcase/defaultFeatures.tsx | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx b/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx index a87dd3e53b2..558630463bb 100644 --- a/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx +++ b/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { + BriefIcon, EmbedIcon, HomeIcon, SitesIcon, @@ -27,11 +28,20 @@ const placeholderImage: ExtensionShowcaseFeature['media'] = { alt: 'daily.dev extension preview', }; -// Every item answers "why the extension, not just the website?" — these are -// browser-level powers the webapp can't replicate. Ordered by install pull: -// lead with reading inside daily.dev, then the new-tab experience and the two -// shortcut flavors (auto most-visited vs. custom pins), then the rest. +// Ordered by install pull. Lead with the daily brief (the morning ritual the +// extension delivers on your first tab), then reading inside daily.dev, the +// new-tab experience, the two shortcut flavors, and the rest. Every item leans +// on something the extension does that the website alone can't. export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ + { + id: 'brief', + label: 'Daily brief', + icon: , + title: 'Your morning brief, on your first tab of the day', + description: + 'Open your browser to a brief built just for you. An AI agent reads the dev world overnight — releases, discussions, hot takes — and compresses what actually matters into a two-minute read. You stop trying to keep up, because it already did.', + media: placeholderImage, + }, { id: 'read-here', label: 'Read it here', @@ -69,7 +79,7 @@ export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ icon: , title: 'Pin the apps and tools you live in', description: - 'Add your own shortcuts to GitHub, Jira, your docs — whatever you open all day — so your essentials sit one click from every new tab.', + 'Add your own shortcuts to GitHub, Jira, your docs — or import your whole bookmarks bar in a click. Your essentials stay one click from every new tab, and nothing leaves your browser.', media: placeholderImage, }, { From eb60b71da559624595e8b589578860f6b06e8fae Mon Sep 17 00:00:00 2001 From: Tsahi Matsliah Date: Sun, 14 Jun 2026 13:38:26 +0300 Subject: [PATCH 06/14] feat(shared): frame Daily brief as auto-opening on the new tab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lean into the "brief on wake" concept — the personalized brief opens itself on the first new tab of the day, no inbox or app to open. Co-Authored-By: Claude Opus 4.8 --- .../onboarding/ExtensionShowcase/defaultFeatures.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx b/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx index 558630463bb..ffaea89853f 100644 --- a/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx +++ b/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx @@ -37,9 +37,9 @@ export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ id: 'brief', label: 'Daily brief', icon: , - title: 'Your morning brief, on your first tab of the day', + title: 'Your brief opens itself, the moment you start your day', description: - 'Open your browser to a brief built just for you. An AI agent reads the dev world overnight — releases, discussions, hot takes — and compresses what actually matters into a two-minute read. You stop trying to keep up, because it already did.', + 'No inbox to dig through, no app to open. The first new tab of your day greets you with a brief built just for you — an AI agent reads the dev world overnight (releases, discussions, hot takes) and compresses what actually matters into a two-minute read. You stop trying to keep up, because it already did.', media: placeholderImage, }, { From 0645ae029e05970ae384f4a4cae67bf99c11928f Mon Sep 17 00:00:00 2001 From: Tsahi Matsliah Date: Sun, 14 Jun 2026 14:07:03 +0300 Subject: [PATCH 07/14] feat(shared): reorder ExtensionShowcase dock New order: new tab feed, read it here, daily brief, most visited, shortcuts, companion, reading streak, focus mode. Co-Authored-By: Claude Opus 4.8 --- .../ExtensionShowcase/defaultFeatures.tsx | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx b/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx index ffaea89853f..9a341fe7a7c 100644 --- a/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx +++ b/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx @@ -28,19 +28,23 @@ const placeholderImage: ExtensionShowcaseFeature['media'] = { alt: 'daily.dev extension preview', }; -// Ordered by install pull. Lead with the daily brief (the morning ritual the -// extension delivers on your first tab), then reading inside daily.dev, the -// new-tab experience, the two shortcut flavors, and the rest. Every item leans -// on something the extension does that the website alone can't. +// Ordered by install pull. Lead with the new-tab feed (what the extension is), +// then reading inside daily.dev, the daily brief, the two shortcut flavors, the +// companion, and the rest. Every item leans on something the extension does +// that the website alone can't. export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ { - id: 'brief', - label: 'Daily brief', - icon: , - title: 'Your brief opens itself, the moment you start your day', + id: 'feed', + label: 'New tab feed', + icon: , + title: 'Every new tab becomes your dev briefing', description: - 'No inbox to dig through, no app to open. The first new tab of your day greets you with a brief built just for you — an AI agent reads the dev world overnight (releases, discussions, hot takes) and compresses what actually matters into a two-minute read. You stop trying to keep up, because it already did.', - media: placeholderImage, + 'You open dozens of blank tabs a day. The extension turns each one into a feed tuned to your stack — so staying current happens in the gaps of your workday, with zero effort.', + media: { + type: 'video', + src: cloudinaryOnboardingActivationDemo, + alt: 'daily.dev new tab feed in action', + }, }, { id: 'read-here', @@ -52,17 +56,13 @@ export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ media: placeholderImage, }, { - id: 'feed', - label: 'New tab feed', - icon: , - title: 'Every new tab becomes your dev briefing', + id: 'brief', + label: 'Daily brief', + icon: , + title: 'Your brief opens itself, the moment you start your day', description: - 'You open dozens of blank tabs a day. The extension turns each one into a feed tuned to your stack — so staying current happens in the gaps of your workday, with zero effort.', - media: { - type: 'video', - src: cloudinaryOnboardingActivationDemo, - alt: 'daily.dev new tab feed in action', - }, + 'No inbox to dig through, no app to open. The first new tab of your day greets you with a brief built just for you — an AI agent reads the dev world overnight (releases, discussions, hot takes) and compresses what actually matters into a two-minute read. You stop trying to keep up, because it already did.', + media: placeholderImage, }, { id: 'most-visited', From 7b1a3db5bd935bca6d7f0bdd0c68e0fe51d6b0b7 Mon Sep 17 00:00:00 2001 From: Tsahi Matsliah Date: Sun, 14 Jun 2026 14:22:52 +0300 Subject: [PATCH 08/14] feat(shared): redesign ExtensionShowcase for focus and conversion Match the Sider-grade structure: a soft pastel field that funnels the eye inward, a contained white card holding the dock + preview, a sharp accented title (sparkle + brand eyebrow) that changes per feature, and an oversized, product-specific primary CTA. Drop the static title/description props in favor of the dynamic per-feature headline. Co-Authored-By: Claude Opus 4.8 --- .../ExtensionShowcase/ExtensionShowcase.tsx | 138 +++++++++--------- .../components/ExtensionShowcase.stories.tsx | 17 +-- 2 files changed, 72 insertions(+), 83 deletions(-) diff --git a/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.tsx b/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.tsx index 6cedbb3a8d7..9647e87288b 100644 --- a/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.tsx +++ b/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.tsx @@ -9,7 +9,8 @@ import { } from '../../typography/Typography'; import { Button } from '../../buttons/Button'; import { ButtonVariant, ButtonSize } from '../../buttons/common'; -import { ChromeIcon } from '../../icons'; +import { ChromeIcon, SparkleIcon } from '../../icons'; +import { IconSize } from '../../Icon'; import { downloadBrowserExtension } from '../../../lib/constants'; import { anchorDefaultRel } from '../../../lib/strings'; import { ExtensionShowcaseMedia } from './ExtensionShowcaseMedia'; @@ -21,11 +22,7 @@ export interface ExtensionShowcaseProps { features?: ExtensionShowcaseFeature[]; /** Feature selected on first render. Defaults to the first feature. */ defaultFeatureId?: string; - /** Optional heading above the showcase. */ - title?: string; - /** Optional sub-heading above the showcase. */ - description?: string; - /** CTA button label. Pass an empty string to hide the CTA. */ + /** Big primary CTA label. Pass an empty string to hide the CTA. */ ctaLabel?: string; /** CTA destination. Defaults to the extension download link. */ ctaHref?: string; @@ -82,9 +79,7 @@ function ShowcaseNavItem({ export function ExtensionShowcase({ features = defaultExtensionShowcaseFeatures, defaultFeatureId, - title, - description, - ctaLabel = 'Add to browser', + ctaLabel = 'Get the daily.dev extension', ctaHref = downloadBrowserExtension, onCtaClick, onFeatureChange, @@ -104,72 +99,69 @@ export function ExtensionShowcase({ }; return ( -
- {(title || description) && ( -
- {title && ( - - {title} - - )} - {description && ( - - {description} - - )} -
+
- + {activeFeature.title} + + - +
+
+ -
- -
- - {activeFeature.title} - + + +
+ } onClick={onCtaClick} > diff --git a/packages/storybook/stories/components/ExtensionShowcase.stories.tsx b/packages/storybook/stories/components/ExtensionShowcase.stories.tsx index d920a89eca7..6d211ce6b00 100644 --- a/packages/storybook/stories/components/ExtensionShowcase.stories.tsx +++ b/packages/storybook/stories/components/ExtensionShowcase.stories.tsx @@ -18,20 +18,17 @@ export default meta; type Story = StoryObj; export const Default: Story = { - args: { - title: 'Everything daily.dev does in your browser', - description: - 'A quick tour of what the extension unlocks the moment you install it.', - }, -}; - -export const WithoutHeader: Story = { args: {}, }; export const CustomCta: Story = { args: { - title: 'Make every tab count', - ctaLabel: 'Get it for Chrome', + ctaLabel: 'Add daily.dev to Chrome — free', + }, +}; + +export const StartOnBrief: Story = { + args: { + defaultFeatureId: 'brief', }, }; From 7569e08d54608e0aba360e9ee3de85dfe603f6ae Mon Sep 17 00:00:00 2001 From: Tsahi Matsliah Date: Sun, 14 Jun 2026 15:00:10 +0300 Subject: [PATCH 09/14] feat(shared): static header, per-feature CTA, softer ExtensionShowcase bg - Revert the per-tab dynamic title; the header is static again and the per-feature headline lives back in the detail panel. - CTA copy now adapts to the active feature (e.g. "Get your daily brief"), falling back to ctaLabel when a feature defines none. - Soften the background to a faint cabbage tint fading to the page color so it stops competing with the reading area. Co-Authored-By: Claude Opus 4.8 --- .../ExtensionShowcase.spec.tsx | 59 ++++++++++--- .../ExtensionShowcase/ExtensionShowcase.tsx | 87 +++++++++++-------- .../ExtensionShowcase/defaultFeatures.tsx | 8 ++ .../onboarding/ExtensionShowcase/types.ts | 3 + 4 files changed, 110 insertions(+), 47 deletions(-) diff --git a/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.spec.tsx b/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.spec.tsx index 7523a375e9e..aa163039586 100644 --- a/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.spec.tsx +++ b/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.spec.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import { ExtensionShowcase } from './ExtensionShowcase'; import { defaultExtensionShowcaseFeatures } from './defaultFeatures'; +import type { ExtensionShowcaseFeature } from './types'; describe('ExtensionShowcase', () => { it('shows the first feature detail by default', () => { @@ -14,35 +15,69 @@ describe('ExtensionShowcase', () => { expect(screen.getByText(first.description)).toBeInTheDocument(); }); - it('swaps the detail panel when a feature is selected', () => { + it('keeps the heading static and swaps the detail panel on selection', () => { const onFeatureChange = jest.fn(); - render(); + render( + , + ); const target = defaultExtensionShowcaseFeatures[2]; - // both desktop dock and mobile strip render a button per feature const [button] = screen.getAllByRole('button', { name: target.label }); fireEvent.click(button); expect(onFeatureChange).toHaveBeenCalledWith(target.id); + expect( + screen.getByRole('heading', { name: 'Static title' }), + ).toBeInTheDocument(); expect( screen.getByRole('heading', { name: target.title }), ).toBeInTheDocument(); }); - it('respects defaultFeatureId', () => { - const target = defaultExtensionShowcaseFeatures[3]; - render(); + it('shows the active feature CTA and updates it when switching', () => { + render(); + + const [first] = defaultExtensionShowcaseFeatures; + expect(screen.getByRole('link', { name: first.cta })).toBeInTheDocument(); + + const target = defaultExtensionShowcaseFeatures[2]; + const [button] = screen.getAllByRole('button', { name: target.label }); + fireEvent.click(button); + expect(screen.getByRole('link', { name: target.cta })).toBeInTheDocument(); expect( - screen.getByRole('heading', { name: target.title }), - ).toBeInTheDocument(); + screen.queryByRole('link', { name: first.cta }), + ).not.toBeInTheDocument(); }); - it('hides the CTA when ctaLabel is empty', () => { - render(); + it('falls back to ctaLabel when the feature has no cta', () => { + const feature: ExtensionShowcaseFeature = { + id: 'solo', + label: 'Solo', + icon: , + title: 'Solo title', + description: 'Solo description', + }; + render(); expect( - screen.queryByRole('link', { name: /add to browser/i }), - ).not.toBeInTheDocument(); + screen.getByRole('link', { name: 'Fallback CTA' }), + ).toBeInTheDocument(); + }); + + it('hides the CTA when neither feature cta nor ctaLabel is set', () => { + const feature: ExtensionShowcaseFeature = { + id: 'solo', + label: 'Solo', + icon: , + title: 'Solo title', + description: 'Solo description', + }; + render(); + + expect(screen.queryByRole('link')).not.toBeInTheDocument(); }); }); diff --git a/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.tsx b/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.tsx index 9647e87288b..a16a1bb0f20 100644 --- a/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.tsx +++ b/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.tsx @@ -9,8 +9,7 @@ import { } from '../../typography/Typography'; import { Button } from '../../buttons/Button'; import { ButtonVariant, ButtonSize } from '../../buttons/common'; -import { ChromeIcon, SparkleIcon } from '../../icons'; -import { IconSize } from '../../Icon'; +import { ChromeIcon } from '../../icons'; import { downloadBrowserExtension } from '../../../lib/constants'; import { anchorDefaultRel } from '../../../lib/strings'; import { ExtensionShowcaseMedia } from './ExtensionShowcaseMedia'; @@ -22,7 +21,11 @@ export interface ExtensionShowcaseProps { features?: ExtensionShowcaseFeature[]; /** Feature selected on first render. Defaults to the first feature. */ defaultFeatureId?: string; - /** Big primary CTA label. Pass an empty string to hide the CTA. */ + /** Static heading above the card. */ + title?: string; + /** Static sub-heading above the card. */ + subtitle?: string; + /** Fallback CTA label used when the active feature defines no `cta`. */ ctaLabel?: string; /** CTA destination. Defaults to the extension download link. */ ctaHref?: string; @@ -79,6 +82,8 @@ function ShowcaseNavItem({ export function ExtensionShowcase({ features = defaultExtensionShowcaseFeatures, defaultFeatureId, + title = 'Everything daily.dev adds to your browser', + subtitle = 'A quick tour of what you unlock the moment you install it.', ctaLabel = 'Get the daily.dev extension', ctaHref = downloadBrowserExtension, onCtaClick, @@ -98,35 +103,38 @@ export function ExtensionShowcase({ onFeatureChange?.(featureId); }; + const ctaText = activeFeature.cta ?? ctaLabel; + return (
-
- - - - {activeFeature.label} - - - - {activeFeature.title} - -
+ {(title || subtitle) && ( +
+ {title && ( + + {title} + + )} + {subtitle && ( + + {subtitle} + + )} +
+ )}
@@ -162,18 +170,27 @@ export function ExtensionShowcase({
- - {activeFeature.description} - +
+ + {activeFeature.title} + + + {activeFeature.description} + +
- {ctaLabel && ( + {ctaText && ( )}
diff --git a/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx b/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx index 9a341fe7a7c..9eb0c777a97 100644 --- a/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx +++ b/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx @@ -40,6 +40,7 @@ export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ title: 'Every new tab becomes your dev briefing', description: 'You open dozens of blank tabs a day. The extension turns each one into a feed tuned to your stack — so staying current happens in the gaps of your workday, with zero effort.', + cta: 'Get daily.dev on every new tab', media: { type: 'video', src: cloudinaryOnboardingActivationDemo, @@ -53,6 +54,7 @@ export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ title: 'Read any article right inside daily.dev', description: 'No more graveyard of half-read tabs. Open links inside daily.dev in a clean reader with the discussion right beside them, and close the loop without ever leaving your feed.', + cta: 'Get the in-app reader', media: placeholderImage, }, { @@ -62,6 +64,7 @@ export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ title: 'Your brief opens itself, the moment you start your day', description: 'No inbox to dig through, no app to open. The first new tab of your day greets you with a brief built just for you — an AI agent reads the dev world overnight (releases, discussions, hot takes) and compresses what actually matters into a two-minute read. You stop trying to keep up, because it already did.', + cta: 'Get your daily brief', media: placeholderImage, }, { @@ -71,6 +74,7 @@ export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ title: 'Your most-visited sites, right where you left them', description: 'daily.dev pulls the sites you open most straight from your browser, so the new tab still knows where you were headed. No setup, nothing to relearn.', + cta: 'Bring my top sites back', media: placeholderImage, }, { @@ -80,6 +84,7 @@ export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ title: 'Pin the apps and tools you live in', description: 'Add your own shortcuts to GitHub, Jira, your docs — or import your whole bookmarks bar in a click. Your essentials stay one click from every new tab, and nothing leaves your browser.', + cta: 'Get my shortcuts everywhere', media: placeholderImage, }, { @@ -89,6 +94,7 @@ export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ title: 'Bring daily.dev onto any site you visit', description: 'Reading a doc, a blog, or a Hacker News thread? The companion sidebar rides along on the page itself — instant TLDR, what the community thinks in the comments, and related reads. This is the one thing only the extension can do.', + cta: 'Get the companion', media: placeholderImage, }, { @@ -98,6 +104,7 @@ export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ title: 'A daily habit that builds itself', description: 'Because daily.dev greets you on every new tab, keeping your streak alive takes no willpower. Show up, read one thing, stay sharp — day after day.', + cta: 'Start my reading streak', media: placeholderImage, }, { @@ -107,6 +114,7 @@ export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ title: 'Heads-down? Pause it in one click', description: 'Need to focus, or just want your blank tab back for a while? Pause the new tab for as long as you like and point it anywhere — full control, only because the extension owns the page.', + cta: 'Add daily.dev to my browser', media: placeholderImage, }, ]; diff --git a/packages/shared/src/components/onboarding/ExtensionShowcase/types.ts b/packages/shared/src/components/onboarding/ExtensionShowcase/types.ts index fe3f05c323b..5b22bc3a61d 100644 --- a/packages/shared/src/components/onboarding/ExtensionShowcase/types.ts +++ b/packages/shared/src/components/onboarding/ExtensionShowcase/types.ts @@ -25,6 +25,9 @@ export interface ExtensionShowcaseFeature { title: string; /** Detail panel body copy. */ description: string; + /** Optional CTA label shown while this feature is active. Falls back to the + * component's `ctaLabel` when omitted. */ + cta?: string; /** Right-side media. Video is autoplayed muted/looped; image supports retina. */ media?: ExtensionShowcaseMedia; } From 5a4fa1965598f72ff225ceef405b1588089fa5e5 Mon Sep 17 00:00:00 2001 From: Tsahi Matsliah Date: Sun, 14 Jun 2026 15:11:32 +0300 Subject: [PATCH 10/14] feat(shared): soften ExtensionShowcase background, trim header copy The previous "subtlest" accent maps to the raw vivid hue, so it read bold. Switch to the alpha-based overlay-float-cabbage tint (~8%) for a soft, clean pastel wash. Shorten the default title and drop the default subtitle to cut text noise at the top. Co-Authored-By: Claude Opus 4.8 --- .../onboarding/ExtensionShowcase/ExtensionShowcase.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.tsx b/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.tsx index a16a1bb0f20..0cd9852c386 100644 --- a/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.tsx +++ b/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.tsx @@ -82,8 +82,8 @@ function ShowcaseNavItem({ export function ExtensionShowcase({ features = defaultExtensionShowcaseFeatures, defaultFeatureId, - title = 'Everything daily.dev adds to your browser', - subtitle = 'A quick tour of what you unlock the moment you install it.', + title = 'Everything the extension unlocks', + subtitle, ctaLabel = 'Get the daily.dev extension', ctaHref = downloadBrowserExtension, onCtaClick, @@ -108,7 +108,7 @@ export function ExtensionShowcase({ return (
From 4d35dc7a1fd78fe9de90c696f86c0b2e653da44f Mon Sep 17 00:00:00 2001 From: Tsahi Matsliah Date: Sun, 14 Jun 2026 23:32:30 +0300 Subject: [PATCH 11/14] feat(shared): move CTA inside the card, single message per feature - The primary CTA now lives inside the white card, full-width below the dock + preview row (matching the reference). - Collapse the detail panel to one message: drop the per-feature title and show a single value sentence per feature. Co-Authored-By: Claude Opus 4.8 --- .../ExtensionShowcase.spec.tsx | 38 +++++-------- .../ExtensionShowcase/ExtensionShowcase.tsx | 53 +++++++------------ .../ExtensionShowcase/defaultFeatures.tsx | 24 +++------ .../onboarding/ExtensionShowcase/types.ts | 4 +- 4 files changed, 43 insertions(+), 76 deletions(-) diff --git a/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.spec.tsx b/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.spec.tsx index aa163039586..9ea4ba079bb 100644 --- a/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.spec.tsx +++ b/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.spec.tsx @@ -4,18 +4,22 @@ import { ExtensionShowcase } from './ExtensionShowcase'; import { defaultExtensionShowcaseFeatures } from './defaultFeatures'; import type { ExtensionShowcaseFeature } from './types'; +const soloFeature: ExtensionShowcaseFeature = { + id: 'solo', + label: 'Solo', + icon: , + description: 'Solo description', +}; + describe('ExtensionShowcase', () => { - it('shows the first feature detail by default', () => { + it('shows the first feature message by default', () => { render(); const [first] = defaultExtensionShowcaseFeatures; - expect( - screen.getByRole('heading', { name: first.title }), - ).toBeInTheDocument(); expect(screen.getByText(first.description)).toBeInTheDocument(); }); - it('keeps the heading static and swaps the detail panel on selection', () => { + it('keeps the heading static and swaps the message on selection', () => { const onFeatureChange = jest.fn(); render( { expect( screen.getByRole('heading', { name: 'Static title' }), ).toBeInTheDocument(); - expect( - screen.getByRole('heading', { name: target.title }), - ).toBeInTheDocument(); + expect(screen.getByText(target.description)).toBeInTheDocument(); }); it('shows the active feature CTA and updates it when switching', () => { @@ -54,14 +56,9 @@ describe('ExtensionShowcase', () => { }); it('falls back to ctaLabel when the feature has no cta', () => { - const feature: ExtensionShowcaseFeature = { - id: 'solo', - label: 'Solo', - icon: , - title: 'Solo title', - description: 'Solo description', - }; - render(); + render( + , + ); expect( screen.getByRole('link', { name: 'Fallback CTA' }), @@ -69,14 +66,7 @@ describe('ExtensionShowcase', () => { }); it('hides the CTA when neither feature cta nor ctaLabel is set', () => { - const feature: ExtensionShowcaseFeature = { - id: 'solo', - label: 'Solo', - icon: , - title: 'Solo title', - description: 'Solo description', - }; - render(); + render(); expect(screen.queryByRole('link')).not.toBeInTheDocument(); }); diff --git a/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.tsx b/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.tsx index 0cd9852c386..5217afa9d5c 100644 --- a/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.tsx +++ b/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.tsx @@ -136,7 +136,7 @@ export function ExtensionShowcase({ )} -
+
-
- {ctaText && ( - - )} + {ctaText && ( + + )} +
); } diff --git a/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx b/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx index 9eb0c777a97..5473bd52524 100644 --- a/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx +++ b/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx @@ -37,9 +37,8 @@ export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ id: 'feed', label: 'New tab feed', icon: , - title: 'Every new tab becomes your dev briefing', description: - 'You open dozens of blank tabs a day. The extension turns each one into a feed tuned to your stack — so staying current happens in the gaps of your workday, with zero effort.', + 'Every new tab becomes a feed tuned to your stack, so staying current happens in the gaps of your day — with zero effort.', cta: 'Get daily.dev on every new tab', media: { type: 'video', @@ -51,9 +50,8 @@ export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ id: 'read-here', label: 'Read it here', icon: , - title: 'Read any article right inside daily.dev', description: - 'No more graveyard of half-read tabs. Open links inside daily.dev in a clean reader with the discussion right beside them, and close the loop without ever leaving your feed.', + 'Open any article inside daily.dev in a clean reader with the discussion beside it — no more graveyard of half-read tabs.', cta: 'Get the in-app reader', media: placeholderImage, }, @@ -61,9 +59,8 @@ export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ id: 'brief', label: 'Daily brief', icon: , - title: 'Your brief opens itself, the moment you start your day', description: - 'No inbox to dig through, no app to open. The first new tab of your day greets you with a brief built just for you — an AI agent reads the dev world overnight (releases, discussions, hot takes) and compresses what actually matters into a two-minute read. You stop trying to keep up, because it already did.', + 'Your first tab of the day opens to an AI-built brief that compresses everything that matters into a two-minute read.', cta: 'Get your daily brief', media: placeholderImage, }, @@ -71,9 +68,8 @@ export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ id: 'most-visited', label: 'Most visited', icon: , - title: 'Your most-visited sites, right where you left them', description: - 'daily.dev pulls the sites you open most straight from your browser, so the new tab still knows where you were headed. No setup, nothing to relearn.', + 'Your most-visited sites come straight from your browser, so the new tab still knows where you were headed — no setup.', cta: 'Bring my top sites back', media: placeholderImage, }, @@ -81,9 +77,8 @@ export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ id: 'shortcuts', label: 'Shortcuts', icon: , - title: 'Pin the apps and tools you live in', description: - 'Add your own shortcuts to GitHub, Jira, your docs — or import your whole bookmarks bar in a click. Your essentials stay one click from every new tab, and nothing leaves your browser.', + 'Pin the apps you live in — or import your bookmarks bar in a click — so your essentials stay one click from every tab.', cta: 'Get my shortcuts everywhere', media: placeholderImage, }, @@ -91,9 +86,8 @@ export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ id: 'companion', label: 'Companion', icon: , - title: 'Bring daily.dev onto any site you visit', description: - 'Reading a doc, a blog, or a Hacker News thread? The companion sidebar rides along on the page itself — instant TLDR, what the community thinks in the comments, and related reads. This is the one thing only the extension can do.', + 'The companion rides along on any site you visit, adding an instant TLDR, what the community thinks, and related reads.', cta: 'Get the companion', media: placeholderImage, }, @@ -101,9 +95,8 @@ export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ id: 'streak', label: 'Reading streak', icon: , - title: 'A daily habit that builds itself', description: - 'Because daily.dev greets you on every new tab, keeping your streak alive takes no willpower. Show up, read one thing, stay sharp — day after day.', + 'daily.dev greets you on every new tab, so keeping your reading streak alive takes no willpower.', cta: 'Start my reading streak', media: placeholderImage, }, @@ -111,9 +104,8 @@ export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ id: 'focus', label: 'Focus mode', icon: , - title: 'Heads-down? Pause it in one click', description: - 'Need to focus, or just want your blank tab back for a while? Pause the new tab for as long as you like and point it anywhere — full control, only because the extension owns the page.', + 'Need to focus? Pause the new tab for as long as you like and point it anywhere — full control, in one click.', cta: 'Add daily.dev to my browser', media: placeholderImage, }, diff --git a/packages/shared/src/components/onboarding/ExtensionShowcase/types.ts b/packages/shared/src/components/onboarding/ExtensionShowcase/types.ts index 5b22bc3a61d..54704779a92 100644 --- a/packages/shared/src/components/onboarding/ExtensionShowcase/types.ts +++ b/packages/shared/src/components/onboarding/ExtensionShowcase/types.ts @@ -21,9 +21,7 @@ export interface ExtensionShowcaseFeature { label: string; /** Icon rendered in the dock. Should accept `secondary` and `className`. */ icon: ReactElement; - /** Detail panel heading. */ - title: string; - /** Detail panel body copy. */ + /** Single-sentence value message shown in the detail panel. */ description: string; /** Optional CTA label shown while this feature is active. Falls back to the * component's `ctaLabel` when omitted. */ From aaa12be5c2f28f65d48d4ae6d4307fa6c1caa91c Mon Sep 17 00:00:00 2001 From: Tsahi Matsliah Date: Sun, 14 Jun 2026 23:35:20 +0300 Subject: [PATCH 12/14] feat(shared): align ExtensionShowcase CTA to the right column Move the primary CTA inside the detail column, under the message, so it sits only on the right side rather than spanning the full card width. Co-Authored-By: Claude Opus 4.8 --- .../ExtensionShowcase/ExtensionShowcase.tsx | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.tsx b/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.tsx index 5217afa9d5c..4d4886160ac 100644 --- a/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.tsx +++ b/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.tsx @@ -136,7 +136,7 @@ export function ExtensionShowcase({ )} -
+
- - {ctaText && ( - - )}
); From b92780ead3ddfc58fa31a5315fb5deb41bd164ab Mon Sep 17 00:00:00 2001 From: Tsahi Matsliah Date: Sun, 14 Jun 2026 23:37:51 +0300 Subject: [PATCH 13/14] feat(shared): refresh ExtensionShowcase dock icons New tab feed -> NewTab, Read it here -> Sites (globe), Daily brief -> TLDR, Most visited -> Star, Companion -> SidebarArrowLeft (expand sidebar), Reading streak -> Hot (flame), Focus mode -> Moon. Shortcuts unchanged. Co-Authored-By: Claude Opus 4.8 --- .../ExtensionShowcase/defaultFeatures.tsx | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx b/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx index 5473bd52524..f21fe8d9c50 100644 --- a/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx +++ b/packages/shared/src/components/onboarding/ExtensionShowcase/defaultFeatures.tsx @@ -1,13 +1,13 @@ import React from 'react'; import { - BriefIcon, - EmbedIcon, - HomeIcon, + NewTabIcon, SitesIcon, - ShortcutsIcon, TLDRIcon, - ReadingStreakIcon, - PauseIcon, + StarIcon, + ShortcutsIcon, + SidebarArrowLeft, + HotIcon, + MoonIcon, } from '../../icons'; import { cloudinaryOnboardingActivationDemo, @@ -36,7 +36,7 @@ export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ { id: 'feed', label: 'New tab feed', - icon: , + icon: , description: 'Every new tab becomes a feed tuned to your stack, so staying current happens in the gaps of your day — with zero effort.', cta: 'Get daily.dev on every new tab', @@ -49,7 +49,7 @@ export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ { id: 'read-here', label: 'Read it here', - icon: , + icon: , description: 'Open any article inside daily.dev in a clean reader with the discussion beside it — no more graveyard of half-read tabs.', cta: 'Get the in-app reader', @@ -58,7 +58,7 @@ export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ { id: 'brief', label: 'Daily brief', - icon: , + icon: , description: 'Your first tab of the day opens to an AI-built brief that compresses everything that matters into a two-minute read.', cta: 'Get your daily brief', @@ -67,7 +67,7 @@ export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ { id: 'most-visited', label: 'Most visited', - icon: , + icon: , description: 'Your most-visited sites come straight from your browser, so the new tab still knows where you were headed — no setup.', cta: 'Bring my top sites back', @@ -85,7 +85,7 @@ export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ { id: 'companion', label: 'Companion', - icon: , + icon: , description: 'The companion rides along on any site you visit, adding an instant TLDR, what the community thinks, and related reads.', cta: 'Get the companion', @@ -94,7 +94,7 @@ export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ { id: 'streak', label: 'Reading streak', - icon: , + icon: , description: 'daily.dev greets you on every new tab, so keeping your reading streak alive takes no willpower.', cta: 'Start my reading streak', @@ -103,7 +103,7 @@ export const defaultExtensionShowcaseFeatures: ExtensionShowcaseFeature[] = [ { id: 'focus', label: 'Focus mode', - icon: , + icon: , description: 'Need to focus? Pause the new tab for as long as you like and point it anywhere — full control, in one click.', cta: 'Add daily.dev to my browser', From fea2ceed0a7150ef497281979916a73686a2ed77 Mon Sep 17 00:00:00 2001 From: Tsahi Matsliah Date: Mon, 15 Jun 2026 17:47:42 +0300 Subject: [PATCH 14/14] feat(shared): reduce ExtensionShowcase CTA Chrome icon size Co-Authored-By: Claude Opus 4.8 --- .../onboarding/ExtensionShowcase/ExtensionShowcase.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.tsx b/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.tsx index 4d4886160ac..5d292b481c8 100644 --- a/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.tsx +++ b/packages/shared/src/components/onboarding/ExtensionShowcase/ExtensionShowcase.tsx @@ -10,6 +10,7 @@ import { import { Button } from '../../buttons/Button'; import { ButtonVariant, ButtonSize } from '../../buttons/common'; import { ChromeIcon } from '../../icons'; +import { IconSize } from '../../Icon'; import { downloadBrowserExtension } from '../../../lib/constants'; import { anchorDefaultRel } from '../../../lib/strings'; import { ExtensionShowcaseMedia } from './ExtensionShowcaseMedia'; @@ -182,7 +183,7 @@ export function ExtensionShowcase({ rel={anchorDefaultRel} variant={ButtonVariant.Primary} size={ButtonSize.XLarge} - icon={} + icon={} onClick={onCtaClick} > {ctaText}