From 2761223185334687182a90043dc7eaebdf26896f Mon Sep 17 00:00:00 2001 From: Tsahi Matsliah Date: Wed, 16 Sep 2026 16:32:12 +0300 Subject: [PATCH 01/21] feat(auth): give the public pages' signup strip the sticky banner's auth stack The anonymous cover strip on Explore, tags, sources, squads, users, best-of, watercooler, world and profile pages offered a Sign up / Log in pair that only opened the modal. The sticky auth banner already converts with a single-primary stack (Google primary, GitHub fill, email link, log in link), so the strip now renders that same AuthOptions stack beside the copy, laid out like the banner: copy on the left, auth column on the right from laptop, stacked and centred on tablet. The cover art chrome moves into HijackingCoverCard so the extension's strip keeps its centred layout while this one lays out its own content. Co-Authored-By: Claude Fable 5.1 --- .../auth/ExploreSignupStrip.spec.tsx | 94 ++++++++++-- .../components/auth/ExploreSignupStrip.tsx | 89 ++++++++---- .../components/auth/HijackingCoverStrip.tsx | 135 ++++++++---------- 3 files changed, 204 insertions(+), 114 deletions(-) diff --git a/packages/shared/src/components/auth/ExploreSignupStrip.spec.tsx b/packages/shared/src/components/auth/ExploreSignupStrip.spec.tsx index afbc7f3ba8..e3a06844d6 100644 --- a/packages/shared/src/components/auth/ExploreSignupStrip.spec.tsx +++ b/packages/shared/src/components/auth/ExploreSignupStrip.spec.tsx @@ -3,8 +3,11 @@ import { QueryClient } from '@tanstack/react-query'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { TestBootProvider } from '../../../__tests__/helpers/boot'; -import { ExploreSignupStrip } from './ExploreSignupStrip'; -import { hijackingCoverStripMinHeight } from './HijackingCoverStrip'; +import { + ExploreSignupStrip, + exploreSignupStripMinHeight, +} from './ExploreSignupStrip'; +import { AuthDisplay } from './common'; import { useViewSize } from '../../hooks/useViewSize'; import { AuthTriggers } from '../../lib/auth'; import { LogEvent, TargetId, TargetType } from '../../lib/log'; @@ -14,6 +17,53 @@ jest.mock('../../hooks/useViewSize', () => ({ useViewSize: jest.fn(), })); +/* The real form is the whole auth stack. What this file is about is the strip + around it and the handoff it makes to the modal, so the mock stands in for + the two ways out of the form. */ +jest.mock('./AuthOptions', () => ({ + __esModule: true, + default: ({ + trigger, + signupStyle, + onAuthStateUpdate, + }: { + trigger: string; + signupStyle?: string; + onAuthStateUpdate?: (props: Record) => void; + }) => { + const { AuthDisplay: Display } = jest.requireActual('./common'); + + return ( +
+ {trigger} + + +
+ ); + }, +})); + const mockUseViewSize = useViewSize as jest.Mock; const logEvent = jest.fn(); const showLogin = jest.fn(); @@ -59,6 +109,18 @@ describe('ExploreSignupStrip', () => { expect(logEvent).toHaveBeenCalledWith(impression); }); + it('should render the sticky banner signup stack', () => { + renderComponent(); + + expect(screen.getByTestId('auth-options')).toHaveAttribute( + 'data-signup-style', + 'singlePrimary', + ); + expect(screen.getByTestId('trigger')).toHaveTextContent( + AuthTriggers.Onboarding, + ); + }); + it('should render nothing for logged-in users', () => { const { container } = renderComponent({ isLoggedIn: true, @@ -74,7 +136,7 @@ describe('ExploreSignupStrip', () => { expect(screen.queryByRole('heading')).not.toBeInTheDocument(); expect(container.firstElementChild?.firstElementChild).toHaveClass( - hijackingCoverStripMinHeight, + exploreSignupStripMinHeight, ); expect(logEvent).not.toHaveBeenCalled(); }); @@ -108,20 +170,24 @@ describe('ExploreSignupStrip', () => { expect(logEvent).toHaveBeenCalledWith(impression); }); - it('should open signup inline and log the click', async () => { + it('should hand the email signup off to the modal', async () => { renderComponent(); - await userEvent.click(screen.getByRole('button', { name: /Sign up/ })); + await userEvent.click( + screen.getByRole('button', { name: 'Continue with email' }), + ); - expect(logEvent).toHaveBeenCalledWith({ - event_name: LogEvent.Click, - target_type: TargetType.SignupButton, - target_id: TargetId.ExploreStrip, - }); expect(showLogin).toHaveBeenCalledWith({ trigger: AuthTriggers.Onboarding, - options: { isLogin: false }, + options: { + isLogin: false, + defaultDisplay: AuthDisplay.Registration, + formValues: undefined, + }, }); + expect(logEvent).not.toHaveBeenCalledWith( + expect.objectContaining({ event_name: LogEvent.Click }), + ); }); it('should open login inline and log the click', async () => { @@ -136,7 +202,11 @@ describe('ExploreSignupStrip', () => { }); expect(showLogin).toHaveBeenCalledWith({ trigger: AuthTriggers.Onboarding, - options: { isLogin: true }, + options: { + isLogin: true, + defaultDisplay: undefined, + formValues: undefined, + }, }); }); }); diff --git a/packages/shared/src/components/auth/ExploreSignupStrip.tsx b/packages/shared/src/components/auth/ExploreSignupStrip.tsx index 6e1c442036..9ec46f4514 100644 --- a/packages/shared/src/components/auth/ExploreSignupStrip.tsx +++ b/packages/shared/src/components/auth/ExploreSignupStrip.tsx @@ -1,11 +1,14 @@ import type { ReactElement } from 'react'; import React from 'react'; import classNames from 'classnames'; +import AuthOptions from './AuthOptions'; +import type { AuthOptionsProps } from './common'; +import { AuthDisplay } from './common'; import type { HijackingCoverCopy } from './HijackingCoverStrip'; import { - HijackingCoverAuthActions, - HijackingCoverStrip, - HijackingCoverStripPlaceholder, + HijackingCoverCard, + hijackingCoverBodyClassName, + hijackingCoverHeadingClassName, } from './HijackingCoverStrip'; import { useAuthContext } from '../../contexts/AuthContext'; import { useLogContext } from '../../contexts/LogContext'; @@ -20,7 +23,12 @@ const copy: HijackingCoverCopy = { body: 'Log in to pick up where you left off.', }; -// The new tab's cover strip for anonymous visitors, tablet and up. +// The card's height with the auth stack: stacked under the copy until laptop, +// beside it from there. +export const exploreSignupStripMinHeight = 'min-h-[22rem] laptop:min-h-[17rem]'; + +// The new tab's cover strip for anonymous visitors, tablet and up, with the +// sticky auth banner's signup stack in place of a sign up / log in pair. export function ExploreSignupStrip({ className, }: { @@ -47,9 +55,12 @@ export function ExploreSignupStrip({ // Holds the strip's slot in the server HTML until boot answers. if (!isAuthReady) { return ( - +
+
+
); } @@ -57,28 +68,56 @@ export function ExploreSignupStrip({ return null; } - const onAuthClick = (isLogin: boolean) => (): void => { - logEvent({ - event_name: LogEvent.Click, - target_type: isLogin ? TargetType.LoginButton : TargetType.SignupButton, - target_id: TargetId.ExploreStrip, - }); + const onAuthStateUpdate: AuthOptionsProps['onAuthStateUpdate'] = (props) => { + if (props.isLoginFlow) { + logEvent({ + event_name: LogEvent.Click, + target_type: TargetType.LoginButton, + target_id: TargetId.ExploreStrip, + }); + } - showLogin({ trigger: AuthTriggers.Onboarding, options: { isLogin } }); + showLogin({ + trigger: AuthTriggers.Onboarding, + options: { + isLogin: !!props.isLoginFlow, + defaultDisplay: props.defaultDisplay, + formValues: props.email ? { email: props.email } : undefined, + }, + }); }; return ( - +
+
+

{copy.heading}

+

{copy.body}

+
+ - } - /> +
+ ); } diff --git a/packages/shared/src/components/auth/HijackingCoverStrip.tsx b/packages/shared/src/components/auth/HijackingCoverStrip.tsx index 395ca2a4a8..420c8ef9ed 100644 --- a/packages/shared/src/components/auth/HijackingCoverStrip.tsx +++ b/packages/shared/src/components/auth/HijackingCoverStrip.tsx @@ -1,7 +1,6 @@ import type { ReactElement, ReactNode } from 'react'; import React from 'react'; import classNames from 'classnames'; -import { Button, ButtonSize, ButtonVariant } from '../buttons/Button'; import { cloudinaryHijackingCoverArt } from '../../lib/image'; export const hijackingPrimaryCta = @@ -15,54 +14,47 @@ export interface HijackingCoverCopy { body: string; } -interface HijackingCoverAuthActionsProps { - signup: string; - login: string; - onSignupClick: () => void; - onLoginClick: () => void; +const coverArtPosition = { objectPosition: '50% 62%' }; + +// The card's height without a sizer. +export const hijackingCoverStripMinHeight = 'min-h-[14rem]'; + +interface HijackingCoverCardProps { + children: ReactNode; + className?: string; } -// The sign up / log in pair for surfaces that offer both. -export function HijackingCoverAuthActions({ - signup, - login, - onSignupClick, - onLoginClick, -}: HijackingCoverAuthActionsProps): ReactElement { +// The cover art card; children lay out the content over it. +export function HijackingCoverCard({ + children, + className, +}: HijackingCoverCardProps): ReactElement { return ( - <> - - - +
+
+ +
+
+ {children} +
+
); } -const coverArtPosition = { objectPosition: '50% 62%' }; +export const hijackingCoverHeadingClassName = + 'font-bold text-white typo-title2 [text-shadow:0_2px_18px_rgba(0,0,0,0.6)]'; -// The card's height without a sizer. -export const hijackingCoverStripMinHeight = 'min-h-[14rem]'; +export const hijackingCoverBodyClassName = + 'text-white/80 text-balance text-sm [text-shadow:0_1px_12px_rgba(0,0,0,0.6)]'; interface HijackingCoverStripProps { copy: HijackingCoverCopy; @@ -82,48 +74,37 @@ export function HijackingCoverStrip({ className, }: HijackingCoverStripProps): ReactElement { return ( -
-
- + {!!sizer && ( +
-
-
- {!!sizer && ( -
- {sizer} -
+ className="invisible hidden tablet:flex tablet:flex-row tablet:items-stretch" + > + {sizer} +
+ )} +
+

{copy.heading}

+

-

- {copy.heading} -

-

- {copy.body} -

-
- {actions} -
+ {copy.body} +

+
+ {actions}
-
+ ); } From 96cbca2dc099baa8d90ab28adbc65d5e9e0c403b Mon Sep 17 00:00:00 2001 From: Tsahi Matsliah Date: Wed, 16 Sep 2026 16:38:25 +0300 Subject: [PATCH 02/21] fix(auth): scrim the strip's auth column and match the banner's column width Co-Authored-By: Claude Fable 5.1 --- .../components/auth/ExploreSignupStrip.tsx | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/shared/src/components/auth/ExploreSignupStrip.tsx b/packages/shared/src/components/auth/ExploreSignupStrip.tsx index 9ec46f4514..02174b9789 100644 --- a/packages/shared/src/components/auth/ExploreSignupStrip.tsx +++ b/packages/shared/src/components/auth/ExploreSignupStrip.tsx @@ -89,6 +89,7 @@ export function ExploreSignupStrip({ return ( +
{copy.heading}

{copy.body}

- +
+ +
); From a80b308779512ca41243c4841288976c30b6c32a Mon Sep 17 00:00:00 2001 From: Tsahi Matsliah Date: Wed, 16 Sep 2026 16:48:32 +0300 Subject: [PATCH 03/21] fix(auth): use arbitrary opacities so the strip's scrim actually renders The palette's opacity scale is empty, so raw-pepper-90/90 style modifiers never emit a rule and the gradient is invalid at computed-value time. Co-Authored-By: Claude Fable 5.1 --- packages/shared/src/components/auth/ExploreSignupStrip.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared/src/components/auth/ExploreSignupStrip.tsx b/packages/shared/src/components/auth/ExploreSignupStrip.tsx index 02174b9789..e5305f6d4d 100644 --- a/packages/shared/src/components/auth/ExploreSignupStrip.tsx +++ b/packages/shared/src/components/auth/ExploreSignupStrip.tsx @@ -89,7 +89,7 @@ export function ExploreSignupStrip({ return ( -
+
Date: Wed, 16 Sep 2026 16:53:34 +0300 Subject: [PATCH 04/21] chore(auth): order the strip scrim's classnames Co-Authored-By: Claude Fable 5.1 --- packages/shared/src/components/auth/ExploreSignupStrip.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared/src/components/auth/ExploreSignupStrip.tsx b/packages/shared/src/components/auth/ExploreSignupStrip.tsx index e5305f6d4d..8ef84a58e1 100644 --- a/packages/shared/src/components/auth/ExploreSignupStrip.tsx +++ b/packages/shared/src/components/auth/ExploreSignupStrip.tsx @@ -89,7 +89,7 @@ export function ExploreSignupStrip({ return ( -
+
Date: Wed, 16 Sep 2026 17:00:25 +0300 Subject: [PATCH 05/21] feat(auth): put the strip's copy and auth stack on the left over a progressive blur Google and GitHub sit side by side at the medium size, with the email link and log in link under them, so the art stays clear on the right. Co-Authored-By: Claude Fable 5.1 --- .../src/components/auth/AuthOptionsInner.tsx | 2 + .../components/auth/ExploreSignupStrip.tsx | 21 +++++----- .../auth/OnboardingRegistrationForm.tsx | 15 ++++++- .../shared/src/components/auth/common.tsx | 2 + packages/shared/src/styles/base.css | 40 +++++++++++++++++++ 5 files changed, 69 insertions(+), 11 deletions(-) diff --git a/packages/shared/src/components/auth/AuthOptionsInner.tsx b/packages/shared/src/components/auth/AuthOptionsInner.tsx index 579e033600..255ddd2050 100644 --- a/packages/shared/src/components/auth/AuthOptionsInner.tsx +++ b/packages/shared/src/components/auth/AuthOptionsInner.tsx @@ -146,6 +146,7 @@ function AuthOptionsInner({ isOnboardingFunnel, compact, signupStyle, + inlineProviders, preferGithub, autoTriggerProvider, socialProviderScopes, @@ -871,6 +872,7 @@ function AuthOptionsInner({ hideSignupDisclaimer={hideSignupDisclaimer} compact={compact} signupStyle={signupStyle} + inlineProviders={inlineProviders} preferGithub={preferGithub} onAuthOpenLogged={() => setHasLoggedAuthOpen(true)} /> diff --git a/packages/shared/src/components/auth/ExploreSignupStrip.tsx b/packages/shared/src/components/auth/ExploreSignupStrip.tsx index 8ef84a58e1..792e2f5ce2 100644 --- a/packages/shared/src/components/auth/ExploreSignupStrip.tsx +++ b/packages/shared/src/components/auth/ExploreSignupStrip.tsx @@ -2,6 +2,7 @@ import type { ReactElement } from 'react'; import React from 'react'; import classNames from 'classnames'; import AuthOptions from './AuthOptions'; +import { ButtonSize } from '../buttons/Button'; import type { AuthOptionsProps } from './common'; import { AuthDisplay } from './common'; import type { HijackingCoverCopy } from './HijackingCoverStrip'; @@ -23,9 +24,8 @@ const copy: HijackingCoverCopy = { body: 'Log in to pick up where you left off.', }; -// The card's height with the auth stack: stacked under the copy until laptop, -// beside it from there. -export const exploreSignupStripMinHeight = 'min-h-[22rem] laptop:min-h-[17rem]'; +// The card's height with the copy and auth stack in one left column. +export const exploreSignupStripMinHeight = 'min-h-[18rem]'; // The new tab's cover strip for anonymous visitors, tablet and up, with the // sticky auth banner's signup stack in place of a sign up / log in pair. @@ -89,20 +89,21 @@ export function ExploreSignupStrip({ return ( -
+
+
+
+
-
+

{copy.heading}

{copy.body}

-
+
diff --git a/packages/shared/src/components/auth/OnboardingRegistrationForm.tsx b/packages/shared/src/components/auth/OnboardingRegistrationForm.tsx index 52922b20a4..8d8398c706 100644 --- a/packages/shared/src/components/auth/OnboardingRegistrationForm.tsx +++ b/packages/shared/src/components/auth/OnboardingRegistrationForm.tsx @@ -40,6 +40,7 @@ interface OnboardingRegistrationFormProps extends AuthFormProps { hideSignupDisclaimer?: boolean; compact?: boolean; signupStyle?: SignupStyle; + inlineProviders?: boolean; preferGithub?: boolean; onAuthOpenLogged?: () => void; } @@ -119,6 +120,7 @@ export const OnboardingRegistrationForm = ({ hideSignupDisclaimer, compact, signupStyle, + inlineProviders, preferGithub, onAuthOpenLogged, }: OnboardingRegistrationFormProps): ReactElement => { @@ -283,9 +285,18 @@ export const OnboardingRegistrationForm = ({ return (
-
    +
      {signupProviders.map((provider, index) => ( -
    • +