BBDF (Bring Back Developer Frameworks)

Adds a button on the Replit side bar that lets you open the Developer Frameworks page, instead of being restricted to the Agent input

// ==UserScript==
// @name         BBDF (Bring Back Developer Frameworks)
// @namespace    https://spin.rip/
// @match        https://replit.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=replit.com
// @grant        none
// @version      1.3.1
// @author       Spinfal
// @description  Adds a button on the Replit side bar that lets you open the Developer Frameworks page, instead of being restricted to the Agent input
// @license      GPL-3.0-or-later
// @run-at       document-idle
// ==/UserScript==

(function () {
  const BTN_ID = 'bbdf-developer-frameworks-btn';

  // minimal: add the button, keep it present across spa route changes
  function addButtonIfMissing() {
    const newReplBtn = document.querySelector('[data-cy="sidebar-new-repl-btn"]');
    if (!newReplBtn) return;
    const container = newReplBtn.parentElement || newReplBtn.closest('div');
    if (!container) return;
    if (container.querySelector('#' + BTN_ID)) return;

    const a = document.createElement('a');
    a.id = BTN_ID;
    a.setAttribute('data-cy', 'bbdf-developer-frameworks-btn');
    a.className = 'Button_button__oRQ_x css-7x8c3s';
    a.href = '/developer-frameworks';
    a.role = 'link';
    a.ariaLabel = 'Developer Frameworks';
    a.style.cssText = '--Button--height:32px;--Button--shrink:0;--Button--align-self:auto;--Button--alignment:center;display:flex;';

    a.innerHTML = `
      <div class="useView_view__C2mnv useView_isHorizontal__Q1YOp" style="--useView--gap:8px;--useView--justify:center;--useView--align:center;--useView--grow:1;--useView--shrink:1;">
        <svg width="16" height="16" viewBox="0 0 24 24" aria-hidden="true" focusable="false" style="width:16px;height:16px;">
          <rect x="2" y="2" width="20" height="20" rx="4" ry="4" fill="currentColor" opacity="0.12"></rect>
          <text x="12" y="16" text-anchor="middle" font-size="11" font-family="Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif" font-weight="700" fill="currentColor">&lt;3</text>
        </svg>
        <span class="Text_singleLineWrapper__REG8N" style="--Text--font-size:var(--font-size-default);--Text--font-family:inherit;">
          <span class="useView_view__C2mnv Text_text__T_hn_ Text_singleLine__lXpWA" style="--Text--font-family:inherit;--Text--font-size:var(--font-size-default);--Text--line-height:var(--line-height-default);">Developer Frameworks</span>
        </span>
      </div>
    `;

    container.appendChild(a);
  }

  function hookHistory() {
    const origPush = history.pushState;
    const origReplace = history.replaceState;
    if (!history._bbdfHooked) {
      history.pushState = function () {
        const ret = origPush.apply(this, arguments);
        window.dispatchEvent(new Event('bbdf:navigation'));
        return ret;
      };
      history.replaceState = function () {
        const ret = origReplace.apply(this, arguments);
        window.dispatchEvent(new Event('bbdf:navigation'));
        return ret;
      };
      history._bbdfHooked = true;
    }
    window.addEventListener('popstate', () => window.dispatchEvent(new Event('bbdf:navigation')));
  }

  function startObserver() {
    const obs = new MutationObserver(addButtonIfMissing);
    obs.observe(document.documentElement, { childList: true, subtree: true });
    window.__BBDF_OBSERVER__ = obs;
  }

  function init() {
    hookHistory();
    startObserver();
    addButtonIfMissing();
    window.addEventListener('bbdf:navigation', addButtonIfMissing);
    let tries = 0;
    const iv = setInterval(() => {
      addButtonIfMissing();
      if (++tries > 20) clearInterval(iv);
    }, 250);
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init, { once: true });
  } else {
    init();
  }
})();