X WebUI Cleaner

Hides some UI elements on X and keeps the Following feed in chronological order.

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         X WebUI Cleaner
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Hides some UI elements on X and keeps the Following feed in chronological order.
// @match        https://x.com/*
// @match        https://twitter.com/*
// @grant        GM_addStyle
// @run-at       document-idle
// @license MIT
// ==/UserScript==

(function () {
  'use strict';

  const INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
  const MENU_OPEN_DELAY = 500; // ms to wait for sort dropdown to render

  // ---------------------------------------------------------------------------
  // 1. STATIC ELEMENT HIDING
  // Injected once as a stylesheet. Persists for the lifetime of the page.
  // Selectors use stable attributes (href, aria-label, data-testid) over class names.
  // ---------------------------------------------------------------------------
  GM_addStyle(`
    /* Grok icon */
    a[href="/i/grok"][aria-label="Grok"],
    /* Creator Studio */
    a[href="/i/jf/creators/studio"][aria-label],
    /* Premium signup tab */
    a[href="/i/premium_sign_up"][data-testid="premium-signup-tab"],
    /* Subscribe to Premium aside */
    aside[aria-label*="Premium"] {
      display: none !important;
      pointer-events: none !important;
    }
  `);

  // ---------------------------------------------------------------------------
  // 2. DYNAMIC ELEMENT HIDING — run ONCE after the page has settled.
  // Targets the Recommended tab and Trending section, which X renders via
  // JavaScript after initial load. No MutationObserver needed.
  // ---------------------------------------------------------------------------
    function hideDynamicElements() {
        // --- For You ("추천") tab — always the first child of the tab list ---
        const forYouTab = document.querySelector('[role="tablist"]')?.firstElementChild;
        if (forYouTab) forYouTab.style.setProperty('display', 'none', 'important');

        // --- Entire right sidebar ---
        const sidebar = document.querySelector('[data-testid="sidebarColumn"]');
        if (sidebar) sidebar.style.setProperty('display', 'none', 'important');
    }

    // Called once, outside the function, after X's JS has rendered the elements.
    setTimeout(hideDynamicElements, 1500);
  // ---------------------------------------------------------------------------
  // 3. HELPERS
  // ---------------------------------------------------------------------------

  /** Finds the first element matching `selector` whose text contains `text`. */
  function findByText(selector, text) {
    return [...document.querySelectorAll(selector)].find(el =>
      el.textContent.includes(text)
    );
  }

  /** Clicks an element if it exists. Returns true on success. */
  function tryClick(el) {
    if (!el) return false;
    el.click();
    return true;
  }

  // ---------------------------------------------------------------------------
  // 4. FOLLOWING TAB
  // The Following tab is uniquely identified by [role="tab"][aria-haspopup="menu"].
  // No text-search loop is needed.
  // ---------------------------------------------------------------------------
  function ensureFollowingTab() {
    const followingTab = document.querySelector('[role="tab"][aria-haspopup="menu"]');
    if (!followingTab) return false;

    if (followingTab.getAttribute('aria-selected') !== 'true') {
      followingTab.click();
    }
    return true;
  }

  // ---------------------------------------------------------------------------
  // 5. CHRONOLOGICAL ORDER
  // Opens the sort dropdown, then clicks "최근" only if it isn't already active.
  // Note: the sort menu is never pre-open during automated checks, so we must
  // open it first, then inspect its state before deciding whether to click.
  // ---------------------------------------------------------------------------
  function ensureLatestOrder() {
    const sortButton = document.querySelector(
      '[data-testid="timelineSort"], [aria-label*="타임라인"], [aria-label*="Timeline"]'
    );
    if (!tryClick(sortButton)) return;

    setTimeout(() => {
      const latestItem = findByText('[role="menuitem"]', '최근');
      if (!latestItem) return;

      // A checkmark SVG inside the menuitem means this option is already selected.
      if (latestItem.querySelector('svg')) {
        // Already in latest mode — dismiss the menu without making a change.
        sortButton.click();
      } else {
        tryClick(latestItem);
      }
    }, MENU_OPEN_DELAY);
  }

  // ---------------------------------------------------------------------------
  // 6. MAIN CHECK
  // ---------------------------------------------------------------------------
  function runCheck() {
    if (!ensureFollowingTab()) return; // Tab not in DOM yet; skip.

    // Brief delay so the tab's feed can render before we touch the sort order.
    setTimeout(ensureLatestOrder, MENU_OPEN_DELAY);
  }

  // ---------------------------------------------------------------------------
  // 7. SCHEDULING
  // Runs once on load, then every 10 minutes only while this tab is active.
  // Also re-runs immediately when the user switches back to this tab.
  // ---------------------------------------------------------------------------
  window.addEventListener('load', runCheck);

  setInterval(() => {
    if (document.visibilityState === 'visible') runCheck();
  }, INTERVAL_MS);

  document.addEventListener('visibilitychange', () => {
    if (document.visibilityState === 'visible') runCheck();
  });

})();