Last active
September 17, 2026 09:33
-
-
Save warrenseine/e293fc70db61eb7b00b951c2482e955f to your computer and use it in GitHub Desktop.
Collapse test files in Linear reviews
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Collapse test files in Linear reviews | |
| // @namespace http://tampermonkey.net/ | |
| // @version 2026-09-17 | |
| // @description Collapse test files in Linear reviews | |
| // @author Warren Seine | |
| // @match https://linear.app/* | |
| // @icon https://www.google.com/s2/favicons?sz=64&domain=linear.app | |
| // @run-at document-idle | |
| // @grant none | |
| // ==/UserScript== | |
| (function() { | |
| 'use strict'; | |
| // Basenames to collapse: test_*.py and *.test.ts / *.test.tsx. | |
| const TEST_FILE = /(?:^test_.*\.py|\.test\.tsx?)$/; | |
| // Anything under a tests directory counts too, whatever it is called — | |
| // conftest.py, fixtures, helpers. | |
| const TEST_DIRECTORY = /(?:^|\/)tests\//; | |
| // The Diff tab of a review. | |
| const DIFF_TAB = /^\/[^/]+\/review\/[^/]+\/changes\b/; | |
| // Only expanded files carry this label; a collapsed one reads "Expand file | |
| // contents", so the selector doubles as the state check. | |
| const COLLAPSE = 'button[aria-label="Collapse file contents"]'; | |
| const EXPAND = 'Expand file contents'; | |
| // The path sits on a copy-to-clipboard control in the file header. | |
| const PATH = '[aria-label^="Copy path"]'; | |
| // One file per pass, and the poll doubles as the pace. Collapsing several in | |
| // the same tick loses all but the first few: the state update is batched and | |
| // the re-render puts back every file it has not committed yet. | |
| const PACE = 120; | |
| // Files the reader expanded by hand are theirs — never collapse them again. | |
| const manual = new Set(); | |
| // Linear re-renders rows as the list scrolls, so collapsing is retried rather | |
| // than done once. The cap per visit stops the retry from becoming a loop. | |
| const MAX_COLLAPSES = 10; | |
| let attempts = new Map(); | |
| let tab = null; | |
| function filePath(toggle) { | |
| // The header is a few generic wrappers up from the button. | |
| for (let el = toggle; el; el = el.parentElement) { | |
| const copy = el.querySelector(PATH); | |
| if (copy) return copy.getAttribute('aria-label').replace(/^Copy path:\s*/, '').trim(); | |
| } | |
| return null; | |
| } | |
| function isTestFile(path) { | |
| return TEST_DIRECTORY.test(path) || TEST_FILE.test(path.split('/').pop()); | |
| } | |
| // Collapses the first test file still expanded. A file is rendered more than | |
| // once — a sticky header alongside the row — but the copies toggle together, | |
| // so clicking whichever comes first shuts both. | |
| function collapseNextTestFile() { | |
| if (!DIFF_TAB.test(document.location.pathname)) { | |
| tab = null; | |
| return; | |
| } | |
| if (tab !== document.location.pathname) { | |
| tab = document.location.pathname; | |
| attempts = new Map(); | |
| } | |
| for (const toggle of document.querySelectorAll(COLLAPSE)) { | |
| const path = filePath(toggle); | |
| if (!path || manual.has(path) || !isTestFile(path)) continue; | |
| const done = attempts.get(path) || 0; | |
| if (done >= MAX_COLLAPSES) continue; | |
| attempts.set(path, done + 1); | |
| toggle.click(); | |
| return; | |
| } | |
| } | |
| // A real click on a collapsed file's toggle hands that file over to the reader. | |
| document.addEventListener('click', event => { | |
| if (!event.isTrusted || !event.target.closest) return; | |
| const toggle = event.target.closest('button'); | |
| if (!toggle || toggle.getAttribute('aria-label') !== EXPAND) return; | |
| const path = filePath(toggle); | |
| if (path && isTestFile(path)) manual.add(path); | |
| }, true); | |
| // Rows render progressively as the list scrolls, and the Diff tab is reached | |
| // by client-side navigation, so poll instead of running once. | |
| setInterval(collapseNextTestFile, PACE); | |
| collapseNextTestFile(); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment