From 0c64a8f1bc446e12e51bc6acaf04d3cd761b4542 Mon Sep 17 00:00:00 2001 From: Afonso Jorge Ramos Date: Fri, 8 May 2026 14:47:46 +0200 Subject: [PATCH 1/6] feat(forges): add tagline metadata to forge adapters --- src/renderer/utils/forges/gitea/adapter.ts | 1 + src/renderer/utils/forges/github/adapter.ts | 1 + src/renderer/utils/forges/types.ts | 2 ++ 3 files changed, 4 insertions(+) diff --git a/src/renderer/utils/forges/gitea/adapter.ts b/src/renderer/utils/forges/gitea/adapter.ts index 4e0537354..407e50116 100644 --- a/src/renderer/utils/forges/gitea/adapter.ts +++ b/src/renderer/utils/forges/gitea/adapter.ts @@ -74,6 +74,7 @@ function getDisplayHelpers( export const giteaAdapter: ForgeAdapter = { id: 'gitea', displayName: 'Gitea', + tagline: 'Self-hosted Git service', icon: ServerIcon, capabilities, diff --git a/src/renderer/utils/forges/github/adapter.ts b/src/renderer/utils/forges/github/adapter.ts index da8a052de..3064a64fa 100644 --- a/src/renderer/utils/forges/github/adapter.ts +++ b/src/renderer/utils/forges/github/adapter.ts @@ -88,6 +88,7 @@ function getDisplayHelpers( export const githubAdapter: ForgeAdapter = { id: 'github', displayName: 'GitHub', + tagline: 'github.com & GitHub Enterprise', icon: MarkGithubIcon, capabilities: githubCapabilities, diff --git a/src/renderer/utils/forges/types.ts b/src/renderer/utils/forges/types.ts index a372531af..f6942e95e 100644 --- a/src/renderer/utils/forges/types.ts +++ b/src/renderer/utils/forges/types.ts @@ -99,6 +99,8 @@ export interface ForgeAdapter { readonly id: Forge; /** User-facing forge name (e.g. "GitHub", "Gitea"). */ readonly displayName: string; + /** Short caption shown beside the forge name on the login screen. */ + readonly tagline?: string; /** Icon used for the platform in the UI. */ readonly icon: FC; /** Static or computed capability matrix for this forge. */ From 924cf214c117865d8de4603a1d08dee927475ccc Mon Sep 17 00:00:00 2001 From: Afonso Jorge Ramos Date: Fri, 8 May 2026 14:47:57 +0200 Subject: [PATCH 2/6] feat(login): redesign with segmented forge selector and primary-CTA hierarchy --- src/renderer/App.css | 56 +++ src/renderer/routes/Login.test.tsx | 13 + src/renderer/routes/Login.tsx | 182 ++++++-- .../routes/__snapshots__/Login.test.tsx.snap | 408 ++++++++++-------- 4 files changed, 440 insertions(+), 219 deletions(-) diff --git a/src/renderer/App.css b/src/renderer/App.css index e40ad0e89..f892c4725 100644 --- a/src/renderer/App.css +++ b/src/renderer/App.css @@ -50,3 +50,59 @@ img.emoji { margin: 0 0.05em 0 0.1em; vertical-align: -0.1em; } + +/* --- Login route ------------------------------------------------------ */ + +@keyframes gitify-login-fade-up { + from { + opacity: 0; + transform: translateY(6px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes gitify-login-panel-fade { + from { + opacity: 0; + transform: translateY(4px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.gitify-login-fade { + animation: gitify-login-fade-up 360ms cubic-bezier(0.2, 0.7, 0.2, 1) both; +} + +/* Re-fires whenever the active forge changes (key={activeAdapter.id}). */ +.gitify-login-panel { + animation: gitify-login-panel-fade 220ms cubic-bezier(0.2, 0.7, 0.2, 1) both; +} + +/* Soft brand halo behind the logo — anchored to the page, ignores layout. */ +.gitify-login-halo { + position: fixed; + inset: -10% 50% auto -10%; + pointer-events: none; + width: 120%; + height: 60%; + background: radial-gradient( + ellipse at 50% 25%, + color-mix(in srgb, var(--fgColor-accent) 10%, transparent) 0%, + transparent 60% + ); + z-index: 0; + filter: blur(12px); +} + +@media (prefers-reduced-motion: reduce) { + .gitify-login-fade, + .gitify-login-panel { + animation: none; + } +} diff --git a/src/renderer/routes/Login.test.tsx b/src/renderer/routes/Login.test.tsx index dfc561a96..447d3669f 100644 --- a/src/renderer/routes/Login.test.tsx +++ b/src/renderer/routes/Login.test.tsx @@ -59,6 +59,7 @@ describe('renderer/routes/Login.tsx', () => { it('should navigate to login with Gitea personal access token', async () => { renderWithProviders(, { isLoggedIn: false }); + await userEvent.click(screen.getByTestId('forge-tab-gitea')); await userEvent.click(screen.getByTestId('login-gitea-pat')); expect(navigateMock).toHaveBeenCalledTimes(1); @@ -66,4 +67,16 @@ describe('renderer/routes/Login.tsx', () => { state: { forge: 'gitea' }, }); }); + + it('should switch the visible login methods when changing forges', async () => { + renderWithProviders(, { isLoggedIn: false }); + + expect(screen.getByTestId('login-github')).toBeInTheDocument(); + expect(screen.queryByTestId('login-gitea-pat')).not.toBeInTheDocument(); + + await userEvent.click(screen.getByTestId('forge-tab-gitea')); + + expect(screen.queryByTestId('login-github')).not.toBeInTheDocument(); + expect(screen.getByTestId('login-gitea-pat')).toBeInTheDocument(); + }); }); diff --git a/src/renderer/routes/Login.tsx b/src/renderer/routes/Login.tsx index 96b3a42eb..63683b5d6 100644 --- a/src/renderer/routes/Login.tsx +++ b/src/renderer/routes/Login.tsx @@ -1,4 +1,4 @@ -import { type FC, useEffect } from 'react'; +import { type FC, useEffect, useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { Button, Heading, Stack, Text } from '@primer/react'; @@ -8,10 +8,28 @@ import { useAppContext } from '../hooks/useAppContext'; import { LogoIcon } from '../components/icons/LogoIcon'; import { Centered } from '../components/layout/Centered'; -import { Size } from '../types'; +import { type Forge, Size } from '../types'; +import type { + ForgeAdapter, + LoginMethodDescriptor, +} from '../utils/forges/types'; import { listAdapters } from '../utils/forges/registry'; import { showWindow } from '../utils/system/comms'; +import { cn } from '../utils/ui/cn'; + +/** + * Pick the method that should drive the dominant CTA for a forge. + * Falls back to the first registered method when no `primary` variant exists. + */ +function pickPrimaryMethod( + adapter: ForgeAdapter, +): LoginMethodDescriptor | undefined { + return ( + adapter.loginMethods.find((m) => m.variant === 'primary') ?? + adapter.loginMethods[0] + ); +} export const LoginRoute: FC = () => { const navigate = useNavigate(); @@ -19,6 +37,12 @@ export const LoginRoute: FC = () => { const { isLoggedIn } = useAppContext(); + const [activeForge, setActiveForge] = useState(adapters[0].id); + const activeAdapter = useMemo( + () => adapters.find((a) => a.id === activeForge) ?? adapters[0], + [activeForge, adapters], + ); + useEffect(() => { if (isLoggedIn) { showWindow(); @@ -26,43 +50,139 @@ export const LoginRoute: FC = () => { } }, [isLoggedIn]); + const goTo = (method: LoginMethodDescriptor) => { + if (method.state) { + navigate(method.route, { state: method.state }); + } else { + navigate(method.route); + } + }; + + const primary = pickPrimaryMethod(activeAdapter); + const alternates = activeAdapter.loginMethods.filter((m) => m !== primary); + return ( - - + + )} + +
+ + {activeAdapter.tagline && ( + + {activeAdapter.tagline} + + )} + + {primary && ( + + )} + + {alternates.length > 0 && ( + + + or sign in with + + {alternates.map((method) => ( + + ))} + + )} + +
); diff --git a/src/renderer/routes/__snapshots__/Login.test.tsx.snap b/src/renderer/routes/__snapshots__/Login.test.tsx.snap index 60f9231a0..96db011cf 100644 --- a/src/renderer/routes/__snapshots__/Login.test.tsx.snap +++ b/src/renderer/routes/__snapshots__/Login.test.tsx.snap @@ -10,66 +10,77 @@ exports[`renderer/routes/Login.tsx > should render itself & its children 1`] = ` data-padding="spacious" data-wrap="nowrap" > + From c158b579cf0adbeeefd8f1cad487cb622132fa80 Mon Sep 17 00:00:00 2001 From: Afonso Jorge Ramos Date: Fri, 8 May 2026 14:49:49 +0200 Subject: [PATCH 3/6] fix(login): tighten layout to fit menubar window --- src/renderer/routes/Login.tsx | 59 ++---- .../routes/__snapshots__/Login.test.tsx.snap | 190 ++++++++---------- 2 files changed, 108 insertions(+), 141 deletions(-) diff --git a/src/renderer/routes/Login.tsx b/src/renderer/routes/Login.tsx index 63683b5d6..d37129b5d 100644 --- a/src/renderer/routes/Login.tsx +++ b/src/renderer/routes/Login.tsx @@ -65,7 +65,7 @@ export const LoginRoute: FC = () => { diff --git a/src/renderer/routes/__snapshots__/Login.test.tsx.snap b/src/renderer/routes/__snapshots__/Login.test.tsx.snap index 96db011cf..6b68a78b3 100644 --- a/src/renderer/routes/__snapshots__/Login.test.tsx.snap +++ b/src/renderer/routes/__snapshots__/Login.test.tsx.snap @@ -18,7 +18,7 @@ exports[`renderer/routes/Login.tsx > should render itself & its children 1`] = ` class="prc-Stack-Stack-UQ9k6" data-align="center" data-direction="vertical" - data-gap="normal" + data-gap="condensed" data-justify="start" data-padding="none" data-wrap="nowrap" @@ -170,18 +170,12 @@ exports[`renderer/routes/Login.tsx > should render itself & its children 1`] = ` data-padding="none" data-wrap="nowrap" > -

- github.com & GitHub Enterprise -

-
-
- or sign in with -
- - + -
+ + +

+ github.com & GitHub Enterprise +

From a8697f78dfd9a5930906f690fb8498c1059480f2 Mon Sep 17 00:00:00 2001 From: Afonso Jorge Ramos Date: Fri, 8 May 2026 14:52:21 +0200 Subject: [PATCH 4/6] fix(login): force pill rounding on active forge tab over primer button reset --- src/renderer/routes/Login.tsx | 3 ++- src/renderer/routes/__snapshots__/Login.test.tsx.snap | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/renderer/routes/Login.tsx b/src/renderer/routes/Login.tsx index d37129b5d..96c37528a 100644 --- a/src/renderer/routes/Login.tsx +++ b/src/renderer/routes/Login.tsx @@ -101,7 +101,7 @@ export const LoginRoute: FC = () => { aria-controls={`forge-panel-${adapter.id}`} aria-selected={isActive} className={cn( - 'inline-flex cursor-pointer items-center gap-2 rounded-full', + 'inline-flex cursor-pointer items-center gap-2', 'px-3 py-1.5 text-sm font-medium transition-colors', 'outline-none focus-visible:ring-2 focus-visible:ring-[var(--focus-outlineColor)]', isActive @@ -112,6 +112,7 @@ export const LoginRoute: FC = () => { key={adapter.id} onClick={() => setActiveForge(adapter.id)} role="tab" + style={{ borderRadius: '9999px' }} type="button" > diff --git a/src/renderer/routes/__snapshots__/Login.test.tsx.snap b/src/renderer/routes/__snapshots__/Login.test.tsx.snap index 6b68a78b3..f8d8ed250 100644 --- a/src/renderer/routes/__snapshots__/Login.test.tsx.snap +++ b/src/renderer/routes/__snapshots__/Login.test.tsx.snap @@ -103,9 +103,10 @@ exports[`renderer/routes/Login.tsx > should render itself & its children 1`] = `