Skip to content

Spec: GitHub File Hider first version #1

Closed
12 of 12 issues completed
Closed

Description

@ribeirogab

Problem Statement

On GitHub, a reviewer sees every changed file of a pull request in one long diff on the Files changed page. Tests, lockfiles, generated code, and similar files sit between the code the reviewer wants to read first. GitHub's own filters (file extension, viewed files, ownership) cannot hide files by path pattern, cannot keep that choice for one pull request, and cannot save reusable rule sets. Reviewers scroll past noise, lose focus, and have no quick way to review one group of files first and the rest later.

Solution

GitHub File Hider is a Chrome extension that adds a control to the toolbar of the Files changed page. The reviewer turns on presets, custom rules, and Always show rules once, in a Settings page that applies to every repository and stores data only in the browser. In a pull request, the reviewer activates filtering (manual mode, remembered for that pull request) or lets it activate when the page opens (automatic mode).

While filtering is active, matching files disappear from the diff and, by default, from the sidebar tree. The control shows the hidden count, and its menu gives access to the presets, the custom rules, the tree setting, and the Settings page. Show all files pauses filtering for the current view. A direct link to a hidden file reveals that file temporarily and explains why. The extension changes only the display: it never changes files, comments, reviews, viewed state, or GitHub's totals.

Terms follow the project glossary (CONTEXT.md).

User Stories

Activation and the control

  1. As a reviewer, I want a control in the toolbar of the Files changed page, so that I can hide files without leaving the pull request.
  2. As a reviewer in manual mode, I want each new pull request to start with filtering inactive, so that I see the full pull request until I choose otherwise.
  3. As a reviewer, I want to select Hide files to activate filtering in the current pull request, so that matching files disappear at once.
  4. As a reviewer, I want the extension to remember that I activated filtering in a pull request, so that a reload keeps the same files hidden.
  5. As a reviewer in manual mode, I want a different pull request to start inactive, so that the choice for one pull request does not affect another.
  6. As a reviewer, I want to turn filtering off for the pull request from the menu, so that the extension forgets my activation there.
  7. As a reviewer, I want to choose automatic mode, so that my rules apply in every pull request as soon as the Files changed page opens.
  8. As a reviewer in automatic mode, I want Show all files one click away, so that I can see everything in one pull request when I need it.
  9. As a reviewer, I want Show all files to pause filtering without changing my rules, my activation, or my activation mode, so that I can restore filtering in one click.
  10. As a reviewer, I want Hide files again to restore filtering after Show all files, so that I return to my focused view.
  11. As a reviewer, I want Show all files to end when I reload or leave the pull request, so that a temporary pause never becomes a hidden setting.
  12. As a reviewer, I want the hidden count in the control, for example 8 files hidden, so that I always know how many files I am not seeing.
  13. As a reviewer, I want the hidden count to stay correct when GitHub lists more files, so that I can trust it on large pull requests.
  14. As a reviewer, I want the hidden count to be the same whether the tree is filtered or not and whichever diff blocks are loaded, so that the number always means the same thing.
  15. As a reviewer, I want the hidden count to exclude temporarily visible files, so that it matches what I cannot see.
  16. As a reviewer, I want the control to show how many files match my rules before I activate filtering, so that I know what Hide files will do.
  17. As a new user, I want the menu to show an empty state when no rule is on, so that I learn how to start.
  18. As a reviewer, I want the control and Show all files to stay available when every file is hidden, so that I never get stuck on an empty page.
  19. As a reviewer, I want an empty state in the diff when every file is hidden, with a way to show them, so that I understand why the diff is empty.
  20. As a reviewer, I want activation to apply to every view of the pull request, including a single commit or a commit range, so that switching commits keeps my view.
  21. As a reviewer, I want a pull request to be identified without regard to the letter case of the owner and repository names, so that one pull request is never treated as two.

Rules and presets

  1. As a reviewer, I want a Tests preset with **/*.spec.*, **/*.test.*, and **/__tests__/**, so that I can hide test files without writing rules.
  2. As a reviewer, I want a Lockfiles preset with **/package-lock.json, **/yarn.lock, and **/pnpm-lock.yaml, so that I can hide lockfiles without writing rules.
  3. As a new user, I want both presets disabled on first installation, so that nothing is hidden until I choose.
  4. As a reviewer, I want to turn presets on and off from the menu in the pull request, so that I can change what I hide without opening Settings.
  5. As a reviewer, I want the menu to say that preset, custom rule, and tree changes apply to all repositories, so that I do not think they apply only to this pull request.
  6. As a reviewer, I want to see how many files each preset matches in the current pull request, so that I can decide which preset to use.
  7. As a reviewer, I want to add, edit, and remove rules in a preset, so that it fits the naming conventions of my project.
  8. As a reviewer, I want to restore a preset to its default rules, so that I can undo my edits.
  9. As a reviewer, I want a Modified label on presets I changed, so that I know which presets differ from the defaults.
  10. As a reviewer, I want unmodified presets to receive improved default rules in later versions, so that I benefit from updates without any action.
  11. As a reviewer, I want to create custom rules, so that I can hide files that presets do not cover, such as docs/** or **/generated/**.
  12. As a reviewer, I want to turn each custom rule on or off without deleting it, so that I can keep rules that I use sometimes.
  13. As a reviewer, I want Always show rules that win over every hide rule, so that one important file stays visible while similar files are hidden.
  14. As a reviewer, I want every pattern to start at the repository root, so that docs/** matches only the root docs directory and the meaning of a rule is predictable.
  15. As a reviewer, I want **/ to match zero or more directories, so that one rule covers root and nested files.
  16. As a reviewer, I want * and ? to match only within one path segment, so that a pattern does not cross folders by accident.
  17. As a reviewer, I want a trailing / to mean the whole folder, so that docs/ works like docs/**.
  18. As a reviewer, I want patterns with spaces to work, so that I can match real paths that contain spaces.
  19. As a reviewer, I want matching to be case-sensitive, so that it agrees with Git paths.
  20. As a reviewer, I want a clear message for an invalid pattern (empty, leading /, backslash, ***, duplicate in the same list), so that I can fix the rule at once.
  21. As a reviewer, I want a renamed or moved file to be matched by its new path, so that a file moved out of a hidden folder becomes visible.
  22. As a reviewer, I want a deleted file to be matched by its old path, so that deleted test files are hidden with the other tests.
  23. As a reviewer, I want pattern syntax help with examples in Settings, so that I can write rules without external documentation.
  24. As a reviewer, I want a file such as src/test-utils.ts to stay visible with the Tests preset, so that only real test files are hidden.

Sidebar tree

  1. As a reviewer, I want matching files hidden from the sidebar tree by default, so that the tree shows only what I review now.
  2. As a reviewer, I want a tree folder to disappear when all its files are hidden, so that the tree has no empty folders.
  3. As a reviewer, I want to turn tree filtering off, so that the tree stays complete while the diff is filtered.
  4. As a reviewer with tree filtering off, I want matching entries muted with an icon and a tooltip that names the rule, so that I know which files are hidden in the diff and why.
  5. As a reviewer with tree filtering off, I want to select a muted tree entry to reveal that file, so that I can open one hidden file quickly.

Direct links and reveals

  1. As a reviewer, I want a direct link to a hidden file to reveal that file temporarily, so that links from teammates always work.
  2. As a reviewer, I want a direct link to a line or a review comment in a hidden file to reveal the file and bring the target into view, so that I can answer the comment.
  3. As a reviewer, I want a notice that explains why a file is temporarily visible and which rule matches it, so that a file I hid does not confuse me.
  4. As a reviewer, I want Hide again on the notice, so that I can hide the revealed file when I am done.
  5. As a reviewer, I want other matching files to stay hidden when one file is revealed, so that a link does not undo my filtering.
  6. As a reviewer, I want a reveal to last until I select Hide again, turn filtering off, or leave the pull request, so that the file does not disappear while I read it.
  7. As a reviewer, I want GitHub's own navigation that changes the URL to a hidden file to reveal it like a direct link, so that GitHub navigation never lands on nothing.
  8. As a reviewer on a large pull request in single file mode, I want the open file to stay visible, with a Temporarily visible label if it matches, so that the page is never empty.
  9. As a reviewer, I want kept files labelled Always shown while filtering is active, so that I know why they are visible.

Dynamic page

  1. As a reviewer, I want files that GitHub loads later to be filtered without a reload, so that large pull requests work.
  2. As a reviewer, I want filtering to keep working when I move between GitHub pages without a full page load, so that navigation is seamless.
  3. As a reviewer, I want Settings changes to apply at once in open pull request tabs, so that I do not need to reload.

Settings page

  1. As a user, I want the extension icon in the Chrome toolbar to open the Settings page, so that I can find the settings without a popup.
  2. As a user, I want a Settings page with General, Presets, Custom rules, and Always show sections, so that every option has one place.
  3. As a user, I want the Settings page to say that settings apply to every repository and stay in this browser, so that I understand their scope.
  4. As a user, I want the Settings page to say that GitHub File Hider is not affiliated with GitHub, so that I am not misled.
  5. As a new user, I want installation to open no page, so that the extension stays quiet until I use it.

Display only and privacy

  1. As a reviewer, I want hidden files to keep their viewed state, comments, and review state, so that the extension never changes my review.
  2. As a reviewer, I want GitHub's file totals and diff statistics unchanged, so that I still see the true size of the pull request.
  3. As a privacy-minded user, I want no server, account, or synchronization, so that my file paths and settings stay in my browser.
  4. As a privacy-minded user, I want the extension to request only storage and github.com access, so that it cannot read other sites.

Accessibility and appearance

  1. As a keyboard user, I want the control and menu to follow GitHub's ActionMenu keyboard contract, so that I can use everything without a pointer.
  2. As a screen reader user, I want changes to the hidden count and reveals announced once, so that I know what changed.
  3. As a reviewer, I want the extension UI to follow GitHub's light, dark, and other themes, so that it looks like part of the page.

Distribution

  1. As a user who does not use the store, I want a ZIP in GitHub Releases with manifest.json at its root, so that I can load the extension unpacked.
  2. As a maintainer, I want the manifest version and version_name to follow the UTC date format of the release standards, so that every store update has a greater version.

Implementation Decisions

Platform

  • Chrome Manifest V3. Permissions: storage only. Host access: https://github.com/* only.
  • One content script on github.com. GitHub is a single-page application, so the script watches route changes and activates only on the current Files changed page: /<owner>/<repo>/pull/<number>/changes, including /changes/<sha> and /changes/<from>..<to>. On the classic page (/pull/<number>/files) it does nothing.
  • A service worker opens the Settings page when the user selects the extension icon. There is no popup, and installation opens no page.
  • The Settings page is a full-tab extension page.
  • TypeScript without a UI framework, bundled into plain extension files. Use the smallest bundler that works (esbuild is the default choice). The existing toolchain stays: pnpm, Biome, and TypeScript.

Modules

Rules (pure, no browser APIs). It compiles and validates patterns, owns the preset catalog with its default rules, and evaluates one path against the settings.

type Reason =
  | { kind: "preset"; presetId: PresetId; pattern: string }
  | { kind: "custom"; ruleId: string; pattern: string }
  | { kind: "always"; ruleId: string; pattern: string };

type Evaluation = { matching: boolean; kept: boolean; reason: Reason | null };

evaluate(path: string, settings: Settings): Evaluation;
validatePattern(pattern: string, list: string[]): string | null;

The shapes are adapted from the prototype's engine. Pattern semantics (ADR 0001):

  • Every pattern is anchored at the repository root.
  • **/ matches zero or more directories.
  • A trailing /** or / matches everything inside a directory.
  • * and ? match within one path segment.
  • Matching is case-sensitive, and spaces are allowed.
  • No brace expansion, negation, or character classes.

Always show rules win over every hide rule. The reason for a matching file is the first matching preset rule, in catalog order, or else the first matching custom rule that is on.

Settings store. It owns the persisted settings in chrome.storage.local, the first-install defaults, normalization, and change notifications across tabs.

type Settings = {
  schemaVersion: 1;
  mode: "manual" | "automatic";
  treeFiltering: boolean;
  presets: Record<PresetId, { enabled: boolean; rules?: string[] }>;
  customRules: { id: string; pattern: string; enabled: boolean }[];
  alwaysShow: { id: string; pattern: string }[];
  activations: Record<PullRequestKey, true>;
};

The shape is adapted from the prototype.

  • rules exists only for a modified preset. The effective rules are the stored list, or else the default rules of the installed version. Storing a list that equals the defaults removes it, and restoring a preset removes it too (ADR 0003).
  • PullRequestKey is owner/repo#number in lowercase.
  • Activations are stored only in manual mode and have no expiry. Turning filtering off removes the entry.
  • First install: manual mode, tree filtering on, both presets disabled, no custom rules, no Always show rules, no activations.

Filtering session (pure). It turns the settings, the pull request key, the listed files, and the session events into the visibility of each file and the model for the control and the menu.

  • Session state lives in memory only and resets on reload or when the user leaves the pull request:
    • showingAll.
    • The set of revealed paths.
  • Filtering is active when the mode is automatic or the pull request has an activation. Filtering is on when it is active and the user is not showing all files.
  • File states: hidden, temporarily visible, kept, and visible.
  • Hidden count: the number of listed files in the hidden state.
  • Control views:
    • empty: no rule is on.
    • inactive.
    • filtering.
    • showing: active, but showing all files.
  • Events: activate, turn off, show all, hide files again, reveal a path, hide a revealed path, and change settings.
  • In single file mode, the open file counts as revealed.

GitHub page adapter. It is the only module that knows GitHub's DOM.

  • It detects the route and the pull request key.
  • It lists the files that GitHub lists in the current view, after GitHub's own filters. A renamed file uses its new path, and a deleted file uses its old path.
  • It finds the diff block and the tree entry for a path. It hides them with a class and never removes or reorders GitHub's nodes. It hides a tree folder when all its files are hidden.
  • It maps direct-link hashes to paths:
    • #diff-<sha256 of path>, with an optional L<n> or R<n> line suffix.
    • #r<id> review comments, resolved to the file that contains them.
    • It computes SHA-256 with Web Crypto.
  • It detects single file mode and the open file.
  • It observes DOM additions, virtualized blocks, and route changes, and applies the visibility again idempotently. Its own injected nodes must not trigger extra changes.
  • It provides the injection anchors that the design handoff names: the toolbar after GitHub's file filter, the overlay root, the space before a file block, the file header actions, the tree row, the top of the diff, and the bottom of the tree.

In-page UI. It renders the toolbar control, the menu, the notice, the labels, the tree hints, the empty states, and a polite live region. It follows the design prototype:

  • Toolbar control. A split button (Hide files and a caret) when filtering is not on; a single menu button with the hidden count while filtering.
  • Menu. It contains, in this order:
    • The pull request switch, in manual mode only.
    • Show all files or Hide files again.
    • A Presets group with an All repositories note. It holds the presets with their counts, the custom rules summary, and Hide in sidebar tree.
    • Settings.
  • Notice. A notice with Hide again above a revealed file.
  • Labels. Temporarily visible and Always shown in the file header.
  • Theme. Colors read GitHub's CSS custom properties, with the prototype's values as fallbacks.
  • Copy. All text comes from the prototype's copy deck.

Settings page. It has these sections:

  • General: the activation mode, tree filtering, and the scope and storage facts.
  • Presets: each preset has its own on/off switch, a rule list, Modified, and Restore defaults.
  • Custom rules: each rule has its own on/off switch.
  • Always show: the rules have no on/off switch.

The page also has the pattern syntax help and the "not affiliated with GitHub" note. It updates open tabs through storage change events.

Packaging. The build produces a folder with manifest.json at its root and a ZIP named after the release standards. The publication workflow is a deferred decision.

Testing Decisions

  • A good test drives external behavior only: what a user sees and does. Tests must not assert internal state, private functions, or intermediate data.

  • Seam 1 (main): the built extension end to end.

    • Playwright launches Chromium with the unpacked build loaded.
    • Requests to https://github.com/... are routed to local fixtures, so the real content script runs on its real URL.
    • The fixtures are copies of the real Files changed page, captured while signed in and sanitized, plus the responses for incremental loads.
    • Tests act like a user and assert visible diff blocks, tree entries, the control text, the menu, the notice, and the Settings page.
    • Scenarios:
      • First-install state.
      • Activation and reload.
      • A new pull request starts inactive.
      • Automatic mode.
      • How long Show all files lasts.
      • The hidden count with tree filtering on and off, and while files load.
      • Always show.
      • Direct links to a file, a line, and a comment.
      • Revealing a file from the tree.
      • Single file mode.
      • A commit range view.
      • The classic page stays unchanged.
      • Settings changes reach an open tab.
      • The extension icon opens Settings.
  • Seam 2: the rules module.

    • Table-driven Vitest tests for:
      • Pattern semantics and validation messages.
      • Preset defaults and modified presets.
      • Always show precedence.
    • Every example in the product documents is a table row, for example:
      • src/test-utils.ts does not match.
      • *.lock does not match apps/web/yarn.lock.
      • docs/ equals docs/**.
  • Prior art. The repository has no tests yet. The behavioral reference is the design prototype's engine with its demo:

    • 22 files, of which 10 match: Tests 7, Lockfiles 2, custom rule 1.
    • src/payments/checkout.spec.ts is kept by an Always show rule.

    Reuse that demo configuration as one end-to-end scenario.

  • Fixture upkeep. Refresh the fixtures when GitHub changes the page. The compatibility checklist covers the diff, the tree, dynamically loaded files, virtualized large pull requests, single file mode, and direct links.

  • pnpm check and the TypeScript type check must pass.

Out of Scope

  • Other browsers and GitHub Enterprise installations.
  • The classic Files changed page (/pull/<number>/files).
  • Other GitHub pages, such as commit pages and compare pages outside a pull request.
  • Settings for individual repositories, and a per-pull-request opt-out in automatic mode.
  • Synchronization between devices, import, and export.
  • Interface translations.
  • Beta releases and the publication workflow.
  • Editing code or changing GitHub review state, viewed state, totals, or statistics.
  • Documentation and generated-file presets (custom rules cover them).
  • Special handling for files with review comments.

Further Notes

  • Product overview: docs/product.md. Glossary: CONTEXT.md. Release standards: docs/releasing.md.
  • Decisions:
    • ADR 0001: patterns are anchored at the repository root.
    • ADR 0002: the name stays GitHub File Hider.
    • ADR 0003: storage keeps only preset edits.
  • Design reference: open design/prototype/index.html in Chrome. It holds the pull request replica, the Settings page, and the handoff (tokens, components, injection points, keyboard, copy deck). Its engine (design/prototype/lib/engine.js) is the reference implementation of the rules and the filtering session.
  • Facts about GitHub's page, from changelogs and live checks on 2026-10-02:
    • The new Files changed page is the default since 2026-01-22, at /pull/<number>/changes. The classic page is still an opt-out.
    • Signed-out visitors get the classic page, so fixtures need a signed-in capture.
    • Large pull requests can switch to single file mode, and the largest ones (up to 3,000 files) virtualize the diff.
    • The anchors are #diff-<sha256>, L/R line suffixes, and #r<id>. The tree and anchors use the new path for renamed files.
    • Not confirmed: whether the tree lists every file before all diff blocks load. Verify this with the fixtures.
  • Known limitations:
    • In single file mode, GitHub's previous and next buttons do not skip matching files.
    • GitHub navigation that does not change the URL can move to a hidden file; that file stays hidden.
    • Renaming or transferring a repository resets the activation of its pull requests.

Activity

  1. self-assigned this
    on Oct 2, 2026
  2. ribeirogab commented on Oct 2, 2026

    @ribeirogab
    OwnerAuthor

    The extension implementation and unfinished visual changes were reverted at the maintainer's request. Commit: a619cac. The main branch files exactly match the pre-implementation tree at 49e302d. Issues #3, #4, #5, #6, #7, #8 and #12 were reopened; all implementation acceptance criteria, including the partial criteria in #9, are unchecked. #13 was cancelled. #1, #2, #10 and #11 retain their prior states.

  3. ribeirogab commented on Oct 3, 2026

    @ribeirogab
    OwnerAuthor

    First version implemented

    All tickets of this spec are closed: #3, #4, #5, #6, #7, #8, #9, #10, #11, #12 (#2 was closed before; #13 was not planned). Commits on main: e68f909 through c28da71.

    What was built

    • Extension: Manifest V3, storage permission and https://github.com/* only. One content script detects the Files changed page (including /changes/<sha>, /changes/<from>..<to>, and ?mode=single), follows navigation without a full page load, and does nothing on the classic /files page or other pages. The extension icon opens the full-tab Settings page; there is no popup and nothing opens on install.
    • Modules: Rules (pure, root-anchored patterns per ADR 0001), Settings model and store (chrome.storage.local, preset edits only per ADR 0003), Filtering session (pure), GitHub page adapter, in-page UI, Settings page, release packaging.
    • In-page UI: the toolbar control (empty, ready split button, N files hidden, Automatic label, showing all), the ActionMenu (presets with counts, custom rules summary, tree switch, Settings), the direct-link notice with Hide again, the Temporarily visible and Always shown labels, tree hints for tree filtering off, the all-hidden empty state, and the tree note, with the ActionMenu keyboard contract and polite announcements.
    • Behavior: manual mode remembered per lowercase pull request key; automatic mode; session-only Show all files; direct links to files, lines, and review comments (#r<id> via the embedded JSON); muted tree entries that reveal like links; large pull requests (progressive and virtualized lists, Loading placeholders, single file mode); settings changes reach open tabs without a reload.
    • Visual fidelity: every surface is ported from design/prototype/index.html value for value. Colors read GitHub's Primer custom properties with the prototype's light and dark values as fallbacks; Mona Sans (SIL OFL 1.1) ships with the extension.
    • Packaging: pnpm release writes github-file-hider-YYYY.MM.DD.N.zip with manifest.json at the root, using the UTC date and the next unused daily counter from v* tags.

    How to test by hand

    Follow "Manual test on the demo repository" in the README. In short: pnpm install && pnpm build, load dist unpacked in chrome://extensions, and open https://github.com/ribeirogab/github-file-hider-demo/pull/1/changes while signed in. Turn on Tests and Lockfiles from the menu, add **/generated/** and the Always show rule src/payments/checkout.spec.ts on the Settings page, and select Hide files: 11 of the 25 files are hidden and checkout.spec.ts shows Always shown. Then try Show all files, a direct link to Button.spec.tsx, tree filtering off, automatic mode on pull request 2, and single file mode on pull request 3.

    Test results (fresh clone of main)

    • pnpm check and pnpm typecheck: pass.
    • pnpm test:unit: 97 tests pass (pattern semantics and product examples, validation, ADR 0003, Always show precedence, the prototype demo of 10 of 22 files, the filtering session, release versions).
    • pnpm test:e2e: 59 tests pass in Chromium with the unpacked build, https://github.com/** routed to the sanitized fixtures (served with a GitHub-like content security policy) and every other request blocked. They include the real extension action, the release ZIP loaded as an unpacked extension, and the 800-file, optimized, and single file mode fixtures.
    • pnpm test:visual: 156 comparisons (78 surfaces and states, light and dark) against baselines captured from the prototype with pnpm visual:baseline, all at zero different pixels. FH_VISUAL_REPORT=1 pnpm test:visual writes a side-by-side image of each to tests/visual/results/report/.

    Known gaps and decisions

    • The new Files changed page has no File filter button in its toolbar (the file filter is in the tree pane), so the control sits at the end of the toolbar's start group, after the commit selector.
    • The comment fixture has no expanded thread, so #r<id> highlights the commented line; on github.com GitHub opens the thread once the file is visible.
    • Pixel comparisons cover GitHub light and dark, the prototype's two themes. Other GitHub themes use the Primer tokens and are not compared. The Settings page follows the system light or dark theme.
    • The prototype renders the revealed tree hint in the muted color (its tree replica styles every tree icon), so the extension matches that.
    • In the visual captures, the prototype's bar, its fake browser bar, and the sticky or scrolling containers of its GitHub replica are removed from the layout around the surface being compared, so only the extension's surfaces are compared. Captures use software rasterization for deterministic pixels.
    • GitHub's real stylesheets are not loaded in the tests, so leakage of GitHub's CSS into the extension's UI on github.com is checked by hand (the components set their own fonts, sizes, and box sizing).
  4. ribeirogab commented on Oct 5, 2026

    @ribeirogab
    OwnerAuthor

    The first version is complete and published.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

ready-for-agentFully specified; an agent can pick it up

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions