GitHub - Latest

Always keep an eye on the latest activity of your favorite projects

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name          GitHub - Latest
// @version       1.9.3
// @description   Always keep an eye on the latest activity of your favorite projects
// @author        Journey Over
// @license       MIT
// @match         *://github.com/*
// @require       https://cdn.jsdelivr.net/gh/StylusThemes/Userscripts@0171b6b6f24caea737beafbc2a8dacd220b729d8/libs/utils/utils.min.js
// @grant         none
// @run-at        document-end
// @icon          https://www.google.com/s2/favicons?sz=64&domain=github.com
// @homepageURL   https://github.com/StylusThemes/Userscripts
// @namespace https://greasyfork.org/users/32214
// ==/UserScript==

(async function() {
  'use strict';

  const logger = Logger('GH - Latest', { debug: false });
  const BUTTON_ID = 'latest-issues-button';
  const QUERY_STRING = 'q=sort%3Aupdated-desc';
  const NAVIGATION_SELECTOR = 'nav[aria-label="Repository"] > ul';

  // Find Issues tab first
  const findTemplateTab = (navigationBody) => {
    const issuesAnchor = navigationBody.querySelector('a[href*="/issues"]');
    if (issuesAnchor) {
      const listItem = issuesAnchor.closest('li');
      if (listItem) return listItem;
      return issuesAnchor.closest(':scope > *') || issuesAnchor;
    }
    return null;
  };

  // Clone template tab and transform it into "Latest issues" with custom icon and query
  const createLatestIssuesTab = (templateTab) => {
    const clonedTab = templateTab.cloneNode(true);
    const anchorElement = clonedTab.querySelector('a') || clonedTab;

    if (!anchorElement) return clonedTab;

    anchorElement.id = BUTTON_ID;

    // Preserve base path while replacing query string for latest issues
    try {
      const urlObject = new URL(anchorElement.href, location.origin);
      anchorElement.href = `${urlObject.pathname}?${QUERY_STRING}`;
    } catch {
      anchorElement.href = `${(anchorElement.href || '#').split('?')[0]}?${QUERY_STRING}`;
    }

    // Remove aria-current to prevent underline styling for latest issues tab
    anchorElement.removeAttribute('aria-current');

    anchorElement.style.float = 'none';
    if (clonedTab.style) clonedTab.style.marginLeft = 'auto';

    // Replace existing icon with flame SVG for "latest" indicator
    const svgElement = clonedTab.querySelector('svg');
    if (svgElement) {
      svgElement.setAttribute('viewBox', '0 0 16 16');
      svgElement.innerHTML = `<path fill-rule="evenodd" d="M5.05 0.31c0.81 2.17 0.41 3.38-0.52 4.31-0.98 1.05-2.55 1.83-3.63 3.36
      -1.45 2.05-1.7 6.53 3.53 7.7-2.2-1.16-2.67-4.52-0.3-6.61-0.61 2.03 0.53 3.33 1.94 2.86
      1.39-0.47 2.3 0.53 2.27 1.67-0.02 0.78-0.31 1.44-1.13 1.81 3.42-0.59 4.78-3.42
      4.78-5.56 0-2.84-2.53-3.22-1.25-5.61-1.52 0.13-2.03 1.13-1.89 2.75 0.09 1.08-1.02
      1.8-1.86 1.33-0.67-0.41-0.66-1.19-0.06-1.78 1.25-1.23 1.75-4.09-1.88-6.22l-0.02-0.02z"/>`;
    }

    const textSpan = clonedTab.querySelector('[data-component="text"]');
    if (textSpan) {
      textSpan.textContent = 'Latest issues';
      textSpan.setAttribute('data-content', 'Latest issues');
    } else {
      const simpleSpan = clonedTab.querySelector('span');
      if (simpleSpan) simpleSpan.textContent = 'Latest issues';
    }

    const counterElement = clonedTab.querySelector('[data-component="counter"], .Counter, .counter');
    if (counterElement) counterElement.remove();

    return clonedTab;
  };

  const addLatestIssuesButton = () => {
    // If the button already exists in the DOM, do nothing
    if (document.getElementById(BUTTON_ID)) return;

    const navigationBody = document.querySelector(NAVIGATION_SELECTOR);
    if (!navigationBody) {
      logger.debug('Navigation selector not found');
      return;
    }

    const templateTab = findTemplateTab(navigationBody);
    if (!templateTab) {
      logger.debug('Template tab not found');
      return;
    }

    logger.debug('Adding latest issues button');
    navigationBody.appendChild(createLatestIssuesTab(templateTab));
  };

  const initialize = () => {
    logger('Initializing GitHub Latest Issues script');

    // 1. Initial attempt
    addLatestIssuesButton();

    // 2. Persistent Observer: This watches for GitHub wiping the nav bar
    // We do NOT disconnect this observer. It stays alive to fight React re-renders.
    const observer = new MutationObserver(() => {
      addLatestIssuesButton();
    });

    observer.observe(document.body, {
      childList: true,
      subtree: true
    });

    // 3. Handle specific Turbo events (SPA navigation)
    document.addEventListener('turbo:render', () => {
      logger.debug('Turbo render event, re-adding latest issues button');
      addLatestIssuesButton();
    });
  };

  initialize();
})();