Repository: gitify-app/gitify
Pull request #978 (2024-04-06 17:27:54)

fix: manual biome linter errors

afonsojramos: <img width="858" alt="image" src="https://github.com/gitify-app/gitify/assets/19473034/2de55583-bb63-4516-b66f-f2eeaf114116">
Stacked PRs cannot be squash merged.... Oops...
afonsojramos: Should be good now, just self-reviewed the PR

Patch:
diff --git a/biome.json b/biome.json
index bc5a279b..20771d45 100644
--- a/biome.json
+++ b/biome.json
@@ -8,7 +8,12 @@
     "rules": {
       "recommended": true,
       "correctness": {
-        "useExhaustiveDependencies": "warn"
+        "useExhaustiveDependencies": {
+          "level": "warn",
+          "options": {
+            "hooks": [{ "name": "useNavigate", "stableResult": true }]
+          }
+        }
       }
     }
   },
diff --git a/first-run.js b/first-run.js
index 81ec75c0..c81ff2b7 100644
--- a/first-run.js
+++ b/first-run.js
@@ -1,7 +1,7 @@
 const { app, dialog } = require('electron');
 
-const fs = require('fs');
-const path = require('path');
+const fs = require('node:fs');
+const path = require('node:path');
 
 async function onFirstRunMaybe() {
   if (isFirstRun()) {
@@ -49,7 +49,7 @@ function isFirstRun() {
 
     fs.writeFileSync(configPath, '');
   } catch (error) {
-    console.warn(`First run: Unable to write firstRun file`, error);
+    console.warn('First run: Unable to write firstRun file', error);
   }
 
   return true;
diff --git a/main.js b/main.js
index 832a0162..ddf8ab0b 100644
--- a/main.js
+++ b/main.js
@@ -2,7 +2,7 @@ const { ipcMain, app, nativeTheme } = require('electron');
 const { menubar } = require('menubar');
 const { autoUpdater } = require('electron-updater');
 const { onFirstRunMaybe } = require('./first-run');
-const path = require('path');
+const path = require('node:path');
 
 require('@electron/remote/main').initialize();
 
diff --git a/src/__mocks__/@electron/remote.js b/src/__mocks__/@electron/remote.js
index 5f7c6164..c2250c24 100644
--- a/src/__mocks__/@electron/remote.js
+++ b/src/__mocks__/@electron/remote.js
@@ -5,6 +5,7 @@ class BrowserWindow {
     if (!instance) {
       instance = this;
     }
+    // biome-ignore lint/correctness/noConstructorReturn: This is a mock class
     return instance;
   }
   loadURL = jest.fn();
diff --git a/src/components/Logo.tsx b/src/components/Logo.tsx
index 53d45818..760b51cf 100644
--- a/src/components/Logo.tsx
+++ b/src/components/Logo.tsx
@@ -13,7 +13,7 @@ const DARK_GRADIENT_END = '#555B6E';
 export const Logo = ({ isDark, onClick, className = '', ...props }: IProps) => (
   <svg
     className={className}
-    onClick={() => onClick && onClick()}
+    onClick={() => onClick?.()}
     xmlns="http://www.w3.org/2000/svg"
     xmlnsXlink="http://www.w3.org/1999/xlink"
     viewBox="0 0 500 500"
@@ -32,11 +32,11 @@ export const Logo = ({ isDark, onClick, className = '', ...props }: IProps) => (
         <stop
           stopColor={isDark ? DARK_GRADIENT_START : LIGHT_GRADIENT_START}
           offset="0%"
-        ></stop>
+        />
         <stop
           stopColor={isDark ? DARK_GRADIENT_END : LIGHT_GRADIENT_END}
           offset="100%"
-        ></stop>
+        />
       </linearGradient>
     </defs>
     <g stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
@@ -48,7 +48,7 @@ export const Logo = ({ isDark, onClick, className = '', ...props }: IProps) => (
         })`}
         fillRule="nonzero"
       >
-        <path d="M330.391053,10.2526316 C361.413773,10.2526316 386.562656,35.4015165 386.562656,66.4242368 C386.562656,97.4469571 361.413773,122.595841 330.391053,122.595842 L330.365421,122.570211 L247.319105,122.570211 C180.042515,122.499128 125.399186,176.890222 125.159,244.166421 L125.133789,244.219632 L125.154247,245.84774 C126.219609,301.255958 164.473457,349.116728 218.453292,362.268048 C272.940509,375.542983 329.466993,350.045988 355.583747,300.428701 L272.258632,300.427737 C257.346614,300.427737 243.046846,294.496977 232.512078,283.942976 C221.97731,273.388975 216.072618,259.078414 216.099748,244.166421 C216.099748,213.150778 241.242989,188.007632 272.258632,188.007632 L371.119632,188.007632 C379.28704,188.480415 386.935401,192.166372 392.393842,198.260263 L474.869789,300.324632 L474.978789,300.325211 L474.952789,300.426632 L474.953158,300.427737 L474.951789,300.427632 L474.173714,303.448935 C446.939817,406.220475 353.906452,478.028529 247.319105,478.080211 C118.007789,478.080211 12.8157895,373.144526 12.8157895,244.166421 C12.8157895,115.188316 117.905263,10.2526316 247.319105,10.2526316 L330.391053,10.2526316 Z"></path>
+        <path d="M330.391053,10.2526316 C361.413773,10.2526316 386.562656,35.4015165 386.562656,66.4242368 C386.562656,97.4469571 361.413773,122.595841 330.391053,122.595842 L330.365421,122.570211 L247.319105,122.570211 C180.042515,122.499128 125.399186,176.890222 125.159,244.166421 L125.133789,244.219632 L125.154247,245.84774 C126.219609,301.255958 164.473457,349.116728 218.453292,362.268048 C272.940509,375.542983 329.466993,350.045988 355.583747,300.428701 L272.258632,300.427737 C257.346614,300.427737 243.046846,294.496977 232.512078,283.942976 C221.97731,273.388975 216.072618,259.078414 216.099748,244.166421 C216.099748,213.150778 241.242989,188.007632 272.258632,188.007632 L371.119632,188.007632 C379.28704,188.480415 386.935401,192.166372 392.393842,198.260263 L474.869789,300.324632 L474.978789,300.325211 L474.952789,300.426632 L474.953158,300.427737 L474.951789,300.427632 L474.173714,303.448935 C446.939817,406.220475 353.906452,478.028529 247.319105,478.080211 C118.007789,478.080211 12.8157895,373.144526 12.8157895,244.166421 C12.8157895,115.188316 117.905263,10.2526316 247.319105,10.2526316 L330.391053,10.2526316 Z" />
       </g>
     </g>
   </svg>
diff --git a/src/components/NotificationRow.tsx b/src/components/NotificationRow.tsx
index 8968245e..58e7fe31 100644
--- a/src/components/NotificationRow.tsx
+++ b/src/components/NotificationRow.tsx
@@ -5,7 +5,13 @@ import {
   ReadIcon,
 } from '@primer/octicons-react';
 import { formatDistanceToNow, parseISO } from 'date-fns';
-import { type FC, type MouseEvent, useCallback, useContext } from 'react';
+import {
+  type FC,
+  type KeyboardEvent,
+  type MouseEvent,
+  useCallback,
+  useContext,
+} from 'react';
 
 import { AppContext } from '../context/App';
 import type { Notification } from '../typesGithub';
@@ -55,7 +61,9 @@ export const NotificationRow: FC<IProps> = ({ notification, hostname }) => {
     unsubscribeNotification(notification.id, hostname);
   };
 
-  const openUserProfile = (event: MouseEvent<HTMLElement>) => {
+  const openUserProfile = (
+    event: MouseEvent<HTMLElement> | KeyboardEvent<HTMLElement>,
+  ) => {
     // Don't trigger onClick of parent element.
     event.stopPropagation();
 
@@ -89,7 +97,11 @@ export const NotificationRow: FC<IProps> = ({ notification, hostname }) => {
         <NotificationIcon size={18} aria-label={notification.subject.type} />
       </div>
 
-      <div className="flex-1 overflow-hidden" onClick={() => pressTitle()}>
+      <div
+        className="flex-1 overflow-hidden"
+        onClick={() => pressTitle()}
+        onKeyDown={() => pressTitle()}
+      >
         <div
           className="mb-1 text-sm whitespace-nowrap overflow-ellipsis overflow-hidden cursor-pointer"
           role="main"
@@ -102,11 +114,16 @@ export const NotificationRow: FC<IProps> = ({ notification, hostname }) => {
           <span className="flex items-center">
             <span title={updatedLabel} className="flex">
               {notification.subject.user ? (
-                <span title="View User Profile" onClick={openUserProfile}>
+                <span
+                  title="View User Profile"
+                  onClick={openUserProfile}
+                  onKeyDown={openUserProfile}
+                >
                   <img
                     className="rounded-full w-4 h-4 cursor-pointer"
                     src={notification.subject.user.avatar_url}
                     title={notification.subject.user.login}
+                    alt={`${notification.subject.user.login}'s avatar`}
                   />
                 </span>
               ) : (
@@ -128,6 +145,7 @@ export const NotificationRow: FC<IProps> = ({ notification, hostname }) => {
 
       <div className="flex justify-center items-center gap-2 opacity-0 group-hover:opacity-80 transition-opacity">
         <button
+          type="button"
           className="focus:outline-none h-full hover:text-green-500"
           title="Mark as Done"
           onClick={() => markNotificationDone(notification.id, hostname)}
@@ -136,6 +154,7 @@ export const NotificationRow: FC<IProps> = ({ notification, hostname }) => {
         </button>
 
         <button
+          type="button"
           className="focus:outline-none h-full hover:text-red-500"
           title="Unsubscribe"
           onClick={unsubscribe}
@@ -144,6 +163,7 @@ export const NotificationRow: FC<IProps> = ({ notification, hostname }) => {
         </button>
 
         <button
+          type="button"
           className="focus:outline-none h-full hover:text-green-500"
           title="Mark as Read"
           onClick={() => markNotificationRead(notification.id, hostname)}
diff --git a/src/components/Repository.tsx b/src/components/Repository.tsx
index 80847e70..28bef678 100644
--- a/src/components/Repository.tsx
+++ b/src/components/Repository.tsx
@@ -38,23 +38,33 @@ export const RepositoryNotifications: FC<IProps> = ({
   }, [repoNotifications, hostname]);
 
   const avatarUrl = repoNotifications[0].repository.owner.avatar_url;
+  const repoSlug = repoNotifications[0].repository.full_name;
 
   return (
     <>
       <div className="flex py-2 px-3 bg-gray-100 dark:bg-gray-darker dark:text-white group">
         <div className="flex flex-1 space-x-3 items-center mt-0 text-sm font-medium overflow-hidden overflow-ellipsis whitespace-nowrap">
           {avatarUrl ? (
-            <img className="rounded w-5 h-5" src={avatarUrl} />
+            <img
+              className="rounded w-5 h-5"
+              src={avatarUrl}
+              alt={`${repoSlug}'s avatar`}
+            />
           ) : (
             <MarkGithubIcon size={18} />
           )}
-          <span className="cursor-pointer" onClick={openBrowser}>
+          <span
+            className="cursor-pointer"
+            onClick={openBrowser}
+            onKeyDown={openBrowser}
+          >
             {repoName}
           </span>
         </div>
 
         <div className="flex justify-center items-center gap-2 opacity-0 group-hover:opacity-80 transition-opacity">
           <button
+            type="button"
             className="focus:outline-none h-full hover:text-green-500"
             title="Mark Repository as Done"
             onClick={markRepoAsDone}
@@ -65,6 +75,7 @@ export const RepositoryNotifications: FC<IProps> = ({
           <div className="w-[14px]" />
 
           <button
+            type="button"
             className="focus:outline-none h-full hover:text-green-500"
             title="Mark Repository as Read"
             onClick={markRepoAsRead}
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx
index 0b34e78b..87674b2b 100644
--- a/src/components/Sidebar.tsx
+++ b/src/components/Sidebar.tsx
@@ -28,7 +28,7 @@ export const Sidebar: FC = () => {
   }, []);
 
   const onOpenGitHubNotifications = useCallback(() => {
-    openExternalLink(`https://github.com/notifications`);
+    openExternalLink('https://github.com/notifications');
   }, []);
 
   const quitApp = useCallback(() => {
@@ -46,6 +46,7 @@ export const Sidebar: FC = () => {
     <div className="flex flex-col fixed left-14 w-14 -ml-14 h-full bg-gray-sidebar overflow-y-auto">
       <div className="flex flex-col flex-1 items-center py-4">
         <button
+          type="button"
           className="w-5 my-3 mx-auto cursor-pointer outline-none"
           title="Open Gitify on GitHub"
           onClick={onOpenBrowser}
@@ -55,6 +56,7 @@ export const Sidebar: FC = () => {
         </button>
 
         <button
+          type="button"
           className={`flex justify-around self-stretch items-center my-1 py-1 px-2 text-xs font-extrabold cursor-pointer ${
             notificationsCount > 0 ? 'text-green-500' : 'text-white'
           }`}
@@ -73,6 +75,7 @@ export const Sidebar: FC = () => {
         {isLoggedIn && (
           <>
             <button
+              type="button"
               className={sidebarButtonClasses}
               title="Refresh Notifications"
               onClick={() => {
@@ -89,6 +92,7 @@ export const Sidebar: FC = () => {
             </button>
 
             <button
+              type="button"
               className={sidebarButtonClasses}
               title="Settings"
               onClick={() => {
@@ -106,6 +110,7 @@ export const Sidebar: FC = () => {
 
         {!isLoggedIn && (
           <button
+            type="button"
             className={sidebarButtonClasses}
             title="Quit Gitify"
             aria-label="Quit Gitify"
diff --git a/src/components/fields/Checkbox.tsx b/src/components/fields/Checkbox.tsx
index ad44d442..7321e26e 100644
--- a/src/components/fields/Checkbox.tsx
+++ b/src/components/fields/Checkbox.tsx
@@ -2,7 +2,7 @@ interface IFieldCheckbox {
   name: string;
   label: string;
   checked: boolean;
-  onChange: any;
+  onChange: (evt: React.ChangeEvent<HTMLInputElement>) => void;
   placeholder?: string;
   disabled?: boolean;
 }
diff --git a/src/context/App.tsx b/src/context/App.tsx
index 6d9d5c81..96fb5e7b 100644
--- a/src/context/App.tsx
+++ b/src/context/App.tsx
@@ -65,7 +65,10 @@ interface AppContextState {
   markRepoNotificationsDone: (id: string, hostname: string) => Promise<void>;
 
   settings: SettingsState;
-  updateSetting: (name: keyof SettingsState, value: any) => void;
+  updateSetting: (
+    name: keyof SettingsState,
+    value: boolean | Theme | string | null,
+  ) => void;
 }
 
 export const AppContext = createContext<Partial<AppContextState>>({});
diff --git a/src/routes/Login.tsx b/src/routes/Login.tsx
index acd373d2..3de4d5f7 100644
--- a/src/routes/Login.tsx
+++ b/src/routes/Login.tsx
@@ -51,6 +51,7 @@ export const LoginRoute: FC = () => {
       }
 
       <button
+        type="button"
         className={loginButtonClass}
         title="Login with Personal Token"
         aria-label="Login with Personal Token"
@@ -60,6 +61,7 @@ export const LoginRoute: FC = () => {
       </button>
 
       <button
+        type="button"
         className={loginButtonClass}
         title="Login with GitHub Enterprise"
         aria-label="Login with GitHub Enterprise"
diff --git a/src/routes/LoginEnterprise.tsx b/src/routes/LoginEnterprise.tsx
index 30971256..c025ba32 100644
--- a/src/routes/LoginEnterprise.tsx
+++ b/src/routes/LoginEnterprise.tsx
@@ -108,6 +108,7 @@ export const LoginEnterpriseRoute: FC = () => {
     <div className="flex-1 bg-white dark:bg-gray-dark dark:text-white">
       <div className="flex justify-between items-center mt-4 py-2 mx-8">
         <button
+          type="button"
           className="focus:outline-none"
           title="Go Back"
           onClick={() => navigate(-1)}
diff --git a/src/routes/LoginWithToken.tsx b/src/routes/LoginWithToken.tsx
index 91f45db2..fa533303 100644
--- a/src/routes/LoginWithToken.tsx
+++ b/src/routes/LoginWithToken.tsx
@@ -62,7 +62,8 @@ export const LoginWithToken: FC = () => {
           helpText={
             <>
               To generate a token, go to GitHub,{' '}
-              <a
+              <button
+                type="button"
                 className="underline hover:text-gray-500 dark:hover:text-gray-300  cursor-pointer"
                 onClick={() =>
                   openLink(
@@ -71,7 +72,7 @@ export const LoginWithToken: FC = () => {
                 }
               >
                 personal access tokens
-              </a>{' '}
+              </button>{' '}
               and create one with the {Constants.AUTH_SCOPE.length} scopes{' '}
               <span className="underline font-extrabold text-yellow-500">
                 {Constants.AUTH_SCOPE.join(', ')}{' '}
@@ -120,6 +121,7 @@ export const LoginWithToken: FC = () => {
     <div className="flex-1 bg-white dark:bg-gray-dark dark:text-white">
       <div className="flex justify-between items-center mt-4 py-2 mx-8">
         <button
+          type="button"
           className="focus:outline-none"
           title="Go Back"
           onClick={() => navigate(-1)}
diff --git a/src/routes/Settings.tsx b/src/routes/Settings.tsx
index 68478579..310e03f6 100644
--- a/src/routes/Settings.tsx
+++ b/src/routes/Settings.tsx
@@ -98,6 +98,7 @@ export const SettingsRoute: FC = () => {
     >
       <div className="flex justify-between items-center mt-2 py-2 mx-8">
         <button
+          type="button"
           className="focus:outline-none"
           title="Go Back"
           onClick={() => navigate(-1)}
@@ -219,15 +220,17 @@ export const SettingsRoute: FC = () => {
       </div>
 
       <div className="flex justify-between items-center bg-gray-200 dark:bg-gray-darker py-1 px-8">
-        <small
+        <button
+          type="button"
           className="font-semibold cursor-pointer"
           title="View release notes"
           onClick={() => openGitHubReleaseNotes(appVersion)}
         >
           Gitify v{appVersion}
-        </small>
+        </button>
         <div>
           <button
+            type="button"
             className={footerButtonClass}
             title="Login with GitHub Enterprise"
             onClick={goToEnterprise}
@@ -239,6 +242,7 @@ export const SettingsRoute: FC = () => {
           </button>
 
           <button
+            type="button"
             className={footerButtonClass}
             title={`Logout ${accounts.user.login}`}
             role="button"
@@ -251,6 +255,7 @@ export const SettingsRoute: FC = () => {
           </button>
 
           <button
+            type="button"
             className={`${footerButtonClass} mr-0`}
             title="Quit Gitify"
             onClick={quitApp}
diff --git a/src/utils/api-requests.ts b/src/utils/api-requests.ts
index 2049f988..17132d31 100644
--- a/src/utils/api-requests.ts
+++ b/src/utils/api-requests.ts
@@ -5,7 +5,7 @@ export function apiRequest(
   method: Method,
   data = {},
 ): AxiosPromise | null {
-  axios.defaults.headers.common['Accept'] = 'application/json';
+  axios.defaults.headers.common.Accept = 'application/json';
   axios.defaults.headers.common['Content-Type'] = 'application/json';
   axios.defaults.headers.common['Cache-Control'] = 'no-cache';
   return axios({ method, url, data });
@@ -17,8 +17,8 @@ export function apiRequestAuth(
   token: string,
   data = {},
 ): AxiosPromise | null {
-  axios.defaults.headers.common['Accept'] = 'application/json';
-  axios.defaults.headers.common['Authorization'] = `token ${token}`;
+  axios.defaults.headers.common.Accept = 'application/json';
+  axios.defaults.headers.common.Authorization = `token ${token}`;
   axios.defaults.headers.common['Cache-Control'] = 'no-cache';
   axios.defaults.headers.common['Content-Type'] = 'application/json';
   return axios({ method, url, data });
diff --git a/src/utils/github-api.ts b/src/utils/github-api.ts
index d00adf63..9898080e 100644
--- a/src/utils/github-api.ts
+++ b/src/utils/github-api.ts
@@ -59,62 +59,62 @@ export function formatReason(reason: Reason): {
     case 'approval_requested':
       return {
         type: 'Approval Requested',
-        description: DESCRIPTIONS['APPROVAL_REQUESTED'],
+        description: DESCRIPTIONS.APPROVAL_REQUESTED,
       };
     case 'assign':
-      return { type: 'Assigned', description: DESCRIPTIONS['ASSIGN'] };
+      return { type: 'Assigned', description: DESCRIPTIONS.ASSIGN };
     case 'author':
-      return { type: 'Authored', description: DESCRIPTIONS['AUTHOR'] };
+      return { type: 'Authored', description: DESCRIPTIONS.AUTHOR };
     case 'ci_activity':
       return {
         type: 'Workflow Run Completed',
-        description: DESCRIPTIONS['CI_ACTIVITY'],
+        description: DESCRIPTIONS.CI_ACTIVITY,
       };
     case 'comment':
-      return { type: 'Commented', description: DESCRIPTIONS['COMMENT'] };
+      return { type: 'Commented', description: DESCRIPTIONS.COMMENT };
     case 'invitation':
       return {
         type: 'Invitation Received',
-        description: DESCRIPTIONS['INVITATION'],
+        description: DESCRIPTIONS.INVITATION,
       };
     case 'manual':
-      return { type: 'Updated', description: DESCRIPTIONS['MANUAL'] };
+      return { type: 'Updated', description: DESCRIPTIONS.MANUAL };
     case 'member_feature_requested':
       return {
         type: 'Member Feature Requested',
-        description: DESCRIPTIONS['MEMBER_FEATURE_REQUESTED'],
+        description: DESCRIPTIONS.MEMBER_FEATURE_REQUESTED,
       };
     case 'mention':
-      return { type: 'Mentioned', description: DESCRIPTIONS['MENTION'] };
+      return { type: 'Mentioned', description: DESCRIPTIONS.MENTION };
     case 'review_requested':
       return {
         type: 'Review Requested',
-        description: DESCRIPTIONS['REVIEW_REQUESTED'],
+        description: DESCRIPTIONS.REVIEW_REQUESTED,
       };
     case 'security_advisory_credit':
       return {
         type: 'Security Advisory Credit Recevied',
-        description: DESCRIPTIONS['SECURITY_ADVISORY_CREDIT'],
+        description: DESCRIPTIONS.SECURITY_ADVISORY_CREDIT,
       };
     case 'security_alert':
       return {
         type: 'Security Alert Received',
-        description: DESCRIPTIONS['SECURITY_ALERT'],
+        description: DESCRIPTIONS.SECURITY_ALERT,
       };
     case 'state_change':
       return {
         type: 'State Changed',
-        description: DESCRIPTIONS['STATE_CHANGE'],
+        description: DESCRIPTIONS.STATE_CHANGE,
       };
     case 'subscribed':
-      return { type: 'Updated', description: DESCRIPTIONS['SUBSCRIBED'] };
+      return { type: 'Updated', description: DESCRIPTIONS.SUBSCRIBED };
     case 'team_mention':
       return {
         type: 'Team Mentioned',
-        description: DESCRIPTIONS['TEAM_MENTION'],
+        description: DESCRIPTIONS.TEAM_MENTION,
       };
     default:
-      return { type: 'Unknown', description: DESCRIPTIONS['UNKNOWN'] };
+      return { type: 'Unknown', description: DESCRIPTIONS.UNKNOWN };
   }
 }
 
diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts
index 3c5190fd..67848910 100644
--- a/src/utils/helpers.ts
+++ b/src/utils/helpers.ts
@@ -142,9 +142,7 @@ async function getDiscussionUrl(
 
     const comments = discussion.comments.nodes;
 
-    let latestCommentId: string | number;
-
-    latestCommentId = getLatestDiscussionComment(comments)?.databaseId;
+    const latestCommentId = getLatestDiscussionComment(comments)?.databaseId;
 
     if (latestCommentId) {
       url += `#discussioncomment-${latestCommentId}`;
@@ -159,7 +157,7 @@ export async function fetchDiscussion(
   token: string,
 ): Promise<Discussion | null> {
   const response: GraphQLSearch<Discussion> = await apiRequestAuth(
-    `https://api.github.com/graphql`,
+    'https://api.github.com/graphql',
     'POST',
     token,
     {
@@ -242,7 +240,7 @@ export async function fetchDiscussion(
 export function getLatestDiscussionComment(
   comments: DiscussionComment[],
 ): DiscussionComment | null {
-  if (!comments || comments.length == 0) {
+  if (!comments || comments.length === 0) {
     return null;
   }
 
diff --git a/src/utils/theme.ts b/src/utils/theme.ts
index 57f90202..42bac595 100644
--- a/src/utils/theme.ts
+++ b/src/utils/theme.ts
@@ -25,10 +25,7 @@ export const setTheme = (mode?: Theme) => {
       break;
 
     default:
-      if (
-        window.matchMedia &&
-        window.matchMedia('(prefers-color-scheme: dark)').matches
-      ) {
+      if (window.matchMedia?.('(prefers-color-scheme: dark)').matches) {
         setDarkMode();
       } else {
         setLightMode();
diff --git a/webpack.common.js b/webpack.common.js
index 94dd07b1..b821c3e3 100644
--- a/webpack.common.js
+++ b/webpack.common.js
@@ -1,4 +1,4 @@
-const path = require('path');
+const path = require('node:path');
 const webpack = require('webpack');
 
 module.exports = {


Test patch:
diff --git a/src/components/NotificationRow.test.tsx b/src/components/NotificationRow.test.tsx
index b7a2d63e..98cfcdf2 100644
--- a/src/components/NotificationRow.test.tsx
+++ b/src/components/NotificationRow.test.tsx
@@ -22,7 +22,7 @@ describe('components/NotificationRow.tsx', () => {
   it('should render itself & its children', async () => {
     jest
       .spyOn(global.Date, 'now')
-      .mockImplementation(() => new Date('2014').valueOf());
+      .mockImplementation(() => new Date('2024').valueOf());
 
     const props = {
       notification: mockedSingleNotification,
@@ -34,7 +34,9 @@ describe('components/NotificationRow.tsx', () => {
   });
 
   it('should render itself & its children without avatar', async () => {
-    (global as any).Date.now = jest.fn(() => new Date('2024'));
+    jest
+      .spyOn(global.Date, 'now')
+      .mockImplementation(() => new Date('2024').valueOf());
 
     const mockNotification = mockedSingleNotification;
     mockNotification.subject.user = null;
diff --git a/src/components/__snapshots__/NotificationRow.test.tsx.snap b/src/components/__snapshots__/NotificationRow.test.tsx.snap
index 445b45ef..f12122f4 100644
--- a/src/components/__snapshots__/NotificationRow.test.tsx.snap
+++ b/src/components/__snapshots__/NotificationRow.test.tsx.snap
@@ -37,6 +37,7 @@ exports[`components/NotificationRow.tsx should render itself & its children 1`]
   <div
     className="flex-1 overflow-hidden"
     onClick={[Function]}
+    onKeyDown={[Function]}
   >
     <div
       className="mb-1 text-sm whitespace-nowrap overflow-ellipsis overflow-hidden cursor-pointer"
@@ -53,13 +54,15 @@ exports[`components/NotificationRow.tsx should render itself & its children 1`]
       >
         <span
           className="flex"
-          title="manosim updated in over 3 years"
+          title="manosim updated over 6 years ago"
         >
           <span
             onClick={[Function]}
+            onKeyDown={[Function]}
             title="View User Profile"
           >
             <img
+              alt="manosim's avatar"
               className="rounded-full w-4 h-4 cursor-pointer"
               src="https://avatars0.githubusercontent.com/u/6333409?v=3"
               title="manosim"
@@ -74,7 +77,7 @@ exports[`components/NotificationRow.tsx should render itself & its children 1`]
           <span
             className="ml-1"
           >
-            in over 3 years
+            over 6 years ago
           </span>
         </span>
       </span>
@@ -87,6 +90,7 @@ exports[`components/NotificationRow.tsx should render itself & its children 1`]
       className="focus:outline-none h-full hover:text-green-500"
       onClick={[Function]}
       title="Mark as Done"
+      type="button"
     >
       <svg
         aria-label="Mark as Done"
@@ -115,6 +119,7 @@ exports[`components/NotificationRow.tsx should render itself & its children 1`]
       className="focus:outline-none h-full hover:text-red-500"
       onClick={[Function]}
       title="Unsubscribe"
+      type="button"
     >
       <svg
         aria-label="Unsubscribe"
@@ -143,6 +148,7 @@ exports[`components/NotificationRow.tsx should render itself & its children 1`]
       className="focus:outline-none h-full hover:text-green-500"
       onClick={[Function]}
       title="Mark as Read"
+      type="button"
     >
       <svg
         aria-label="Mark as Read"
@@ -208,6 +214,7 @@ exports[`components/NotificationRow.tsx should render itself & its children with
   <div
     className="flex-1 overflow-hidden"
     onClick={[Function]}
+    onKeyDown={[Function]}
   >
     <div
       className="mb-1 text-sm whitespace-nowrap overflow-ellipsis overflow-hidden cursor-pointer"
@@ -271,6 +278,7 @@ exports[`components/NotificationRow.tsx should render itself & its children with
       className="focus:outline-none h-full hover:text-green-500"
       onClick={[Function]}
       title="Mark as Done"
+      type="button"
     >
       <svg
         aria-label="Mark as Done"
@@ -299,6 +307,7 @@ exports[`components/NotificationRow.tsx should render itself & its children with
       className="focus:outline-none h-full hover:text-red-500"
       onClick={[Function]}
       title="Unsubscribe"
+      type="button"
     >
       <svg
         aria-label="Unsubscribe"
@@ -327,6 +336,7 @@ exports[`components/NotificationRow.tsx should render itself & its children with
       className="focus:outline-none h-full hover:text-green-500"
       onClick={[Function]}
       title="Mark as Read"
+      type="button"
     >
       <svg
         aria-label="Mark as Read"
diff --git a/src/components/__snapshots__/Repository.test.tsx.snap b/src/components/__snapshots__/Repository.test.tsx.snap
index 934a3a31..557a6c18 100644
--- a/src/components/__snapshots__/Repository.test.tsx.snap
+++ b/src/components/__snapshots__/Repository.test.tsx.snap
@@ -9,12 +9,14 @@ exports[`components/Repository.tsx should render itself & its children 1`] = `
       className="flex flex-1 space-x-3 items-center mt-0 text-sm font-medium overflow-hidden overflow-ellipsis whitespace-nowrap"
     >
       <img
+        alt="manosim/notifications-test's avatar"
         className="rounded w-5 h-5"
         src="https://avatars0.githubusercontent.com/u/6333409?v=3"
       />
       <span
         className="cursor-pointer"
         onClick={[Function]}
+        onKeyDown={[Function]}
       >
         manosim/gitify
       </span>
@@ -26,6 +28,7 @@ exports[`components/Repository.tsx should render itself & its children 1`] = `
         className="focus:outline-none h-full hover:text-green-500"
         onClick={[Function]}
         title="Mark Repository as Done"
+        type="button"
       >
         <svg
           aria-label="Mark Repository as Done"
@@ -57,6 +60,7 @@ exports[`components/Repository.tsx should render itself & its children 1`] = `
         className="focus:outline-none h-full hover:text-green-500"
         onClick={[Function]}
         title="Mark Repository as Read"
+        type="button"
       >
         <svg
           aria-label="Mark Repository as Read"
@@ -126,6 +130,7 @@ exports[`components/Repository.tsx should use default repository icon when avata
       <span
         className="cursor-pointer"
         onClick={[Function]}
+        onKeyDown={[Function]}
       >
         manosim/gitify
       </span>
@@ -137,6 +142,7 @@ exports[`components/Repository.tsx should use default repository icon when avata
         className="focus:outline-none h-full hover:text-green-500"
         onClick={[Function]}
         title="Mark Repository as Done"
+        type="button"
       >
         <svg
           aria-label="Mark Repository as Done"
@@ -168,6 +174,7 @@ exports[`components/Repository.tsx should use default repository icon when avata
         className="focus:outline-none h-full hover:text-green-500"
         onClick={[Function]}
         title="Mark Repository as Read"
+        type="button"
       >
         <svg
           aria-label="Mark Repository as Read"
diff --git a/src/components/__snapshots__/Sidebar.test.tsx.snap b/src/components/__snapshots__/Sidebar.test.tsx.snap
index bebee7a5..c563023a 100644
--- a/src/components/__snapshots__/Sidebar.test.tsx.snap
+++ b/src/components/__snapshots__/Sidebar.test.tsx.snap
@@ -12,6 +12,7 @@ exports[`components/Sidebar.tsx should render itself & its children (logged in)
       data-testid="gitify-logo"
       onClick={[Function]}
       title="Open Gitify on GitHub"
+      type="button"
     >
       <svg
         aria-label="Open Gitify"
@@ -63,6 +64,7 @@ exports[`components/Sidebar.tsx should render itself & its children (logged in)
       className="flex justify-around self-stretch items-center my-1 py-1 px-2 text-xs font-extrabold cursor-pointer text-green-500"
       onClick={[Function]}
       title="4 Unread Notifications"
+      type="button"
     >
       <svg
         aria-label="4 Unread Notifications"
@@ -97,6 +99,7 @@ exports[`components/Sidebar.tsx should render itself & its children (logged in)
       className="flex justify-evenly items-center bg-transparent border-0 w-full text-sm text-white my-1 py-2 cursor-pointer hover:text-gray-500 focus:outline-none disabled:text-gray-500 disabled:cursor-default"
       onClick={[Function]}
       title="Quit Gitify"
+      type="button"
     >
       <svg
         aria-label="Quit Gitify"
@@ -137,6 +140,7 @@ exports[`components/Sidebar.tsx should render itself & its children (logged out)
       data-testid="gitify-logo"
       onClick={[Function]}
       title="Open Gitify on GitHub"
+      type="button"
     >
       <svg
         aria-label="Open Gitify"
@@ -188,6 +192,7 @@ exports[`components/Sidebar.tsx should render itself & its children (logged out)
       className="flex justify-around self-stretch items-center my-1 py-1 px-2 text-xs font-extrabold cursor-pointer text-green-500"
       onClick={[Function]}
       title="4 Unread Notifications"
+      type="button"
     >
       <svg
         aria-label="4 Unread Notifications"
@@ -222,6 +227,7 @@ exports[`components/Sidebar.tsx should render itself & its children (logged out)
       className="flex justify-evenly items-center bg-transparent border-0 w-full text-sm text-white my-1 py-2 cursor-pointer hover:text-gray-500 focus:outline-none disabled:text-gray-500 disabled:cursor-default"
       onClick={[Function]}
       title="Quit Gitify"
+      type="button"
     >
       <svg
         aria-label="Quit Gitify"
diff --git a/src/routes/__snapshots__/Login.test.tsx.snap b/src/routes/__snapshots__/Login.test.tsx.snap
index 6e5bcd3c..63533836 100644
--- a/src/routes/__snapshots__/Login.test.tsx.snap
+++ b/src/routes/__snapshots__/Login.test.tsx.snap
@@ -61,6 +61,7 @@ exports[`routes/Login.tsx should render itself & its children 1`] = `
     className="w-50 px-2 py-2 my-2 bg-gray-300 font-semibold rounded text-xs text-center dark:text-black hover:bg-gray-500 hover:text-white focus:outline-none"
     onClick={[Function]}
     title="Login with Personal Token"
+    type="button"
   >
     Login with Personal Access Token
   </button>
@@ -69,6 +70,7 @@ exports[`routes/Login.tsx should render itself & its children 1`] = `
     className="w-50 px-2 py-2 my-2 bg-gray-300 font-semibold rounded text-xs text-center dark:text-black hover:bg-gray-500 hover:text-white focus:outline-none"
     onClick={[Function]}
     title="Login with GitHub Enterprise"
+    type="button"
   >
     Login to GitHub Enterprise
   </button>
diff --git a/src/routes/__snapshots__/LoginEnterprise.test.tsx.snap b/src/routes/__snapshots__/LoginEnterprise.test.tsx.snap
index 43d74e23..c913860c 100644
--- a/src/routes/__snapshots__/LoginEnterprise.test.tsx.snap
+++ b/src/routes/__snapshots__/LoginEnterprise.test.tsx.snap
@@ -11,6 +11,7 @@ exports[`routes/LoginEnterprise.tsx renders correctly 1`] = `
       className="focus:outline-none"
       onClick={[Function]}
       title="Go Back"
+      type="button"
     >
       <svg
         aria-label="Go Back"
diff --git a/src/routes/__snapshots__/LoginWithToken.test.tsx.snap b/src/routes/__snapshots__/LoginWithToken.test.tsx.snap
index 2bc67339..79dadca9 100644
--- a/src/routes/__snapshots__/LoginWithToken.test.tsx.snap
+++ b/src/routes/__snapshots__/LoginWithToken.test.tsx.snap
@@ -11,6 +11,7 @@ exports[`routes/LoginWithToken.tsx renders correctly 1`] = `
       className="focus:outline-none"
       onClick={[Function]}
       title="Go Back"
+      type="button"
     >
       <svg
         aria-label="Go Back"
@@ -73,12 +74,13 @@ exports[`routes/LoginWithToken.tsx renders correctly 1`] = `
         >
           To generate a token, go to GitHub,
            
-          <a
+          <button
             className="underline hover:text-gray-500 dark:hover:text-gray-300  cursor-pointer"
             onClick={[Function]}
+            type="button"
           >
             personal access tokens
-          </a>
+          </button>
            
           and create one with the 
           3
diff --git a/src/routes/__snapshots__/Settings.test.tsx.snap b/src/routes/__snapshots__/Settings.test.tsx.snap
index d6bf020a..34cf42d0 100644
--- a/src/routes/__snapshots__/Settings.test.tsx.snap
+++ b/src/routes/__snapshots__/Settings.test.tsx.snap
@@ -39,6 +39,7 @@ exports[`routes/Settings.tsx should render itself & its children 1`] = `
     <button
       class="focus:outline-none"
       title="Go Back"
+      type="button"
     >
       <svg
         aria-label="Go Back"
@@ -382,17 +383,19 @@ exports[`routes/Settings.tsx should render itself & its children 1`] = `
   <div
     class="flex justify-between items-center bg-gray-200 dark:bg-gray-darker py-1 px-8"
   >
-    <small
+    <button
       class="font-semibold cursor-pointer"
       title="View release notes"
+      type="button"
     >
       Gitify v
       0.0.1
-    </small>
+    </button>
     <div>
       <button
         class="hover:text-gray-500 py-1 px-2 my-1 mx-2 focus:outline-none"
         title="Login with GitHub Enterprise"
+        type="button"
       >
         <svg
           aria-label="Login with GitHub Enterprise"
@@ -414,6 +417,7 @@ exports[`routes/Settings.tsx should render itself & its children 1`] = `
         class="hover:text-gray-500 py-1 px-2 my-1 mx-2 focus:outline-none"
         role="button"
         title="Logout octocat"
+        type="button"
       >
         <svg
           aria-label="Logout octocat"
@@ -434,6 +438,7 @@ exports[`routes/Settings.tsx should render itself & its children 1`] = `
       <button
         class="hover:text-gray-500 py-1 px-2 my-1 mx-2 focus:outline-none mr-0"
         title="Quit Gitify"
+        type="button"
       >
         <svg
           aria-label="Quit Gitify"