Skip to content

fix: navbar listener leaks, download overlap, and web fonts - #1036

Merged
boomzero merged 14 commits into
devfrom
codex/fix-navbar-listener-leak
Oct 3, 2026
Merged

boomzero merged 14 commits into
devfrom
codex/fix-navbar-listener-leak

Conversation

@boomzero

@boomzero boomzero commented Oct 3, 2026 •

Copy link
Copy Markdown
Member

What does this PR aim to accomplish?:

Fix the navbar resize-listener leak deferred during emergency release #1033 the downloads.php card overlap, and inconsistent navbar fonts between /web and classic pages. With NewTopBar enabled, every refresh previously registered another permanent resize listener: once per second on the contest app and every 100 ms on classic pages. Long-lived tabs accumulated callbacks and could retain navbars replaced by Vue.

Addresses the review finding on #1033.

How does this PR accomplish the above?:

Both UI refresh paths now use a shared navbar styler. The spacer uses a block-level span so legacy body > div selectors continue to target the page content container; the downloads handler previously appended its cards inside the spacer, underneath the fixed navbar. A stable navbar reuses its instance and listener while refreshing the existing styles, spacer and blur overlay. Navbar replacement, removal, or disabling NewTopBar removes the old listener, restores the original navbar styles and classes, removes styler-owned overlays, styles and spacers, and clears the retained navbar reference. Existing page-owned overlays and spacers are preserved with their original inline geometry restored. Resize events skip detached navbars. A shared, deduplicated monochrome font loader runs during early initialization and both fallback paths, so /web requests the same Source Serif 4, Playfair Display and JetBrains Mono stylesheet as classic pages instead of staying on fallback fonts.

Validation: all 18 npm tests passed using installed Chromium, including a new browser regression with 4,000 refreshes, navbar replacement, resize geometry, setting changes, navbar removal, detached-node resize guards, full teardown, style restoration and preservation of page-owned elements. A downloads regression runs the real page handler, reproduces the failure with the old spacer, and verifies that all 15 cards remain below the navbar after the fix and after its height changes. Font-loading coverage checks /web and classic initialization with and without cached Bootstrap CSS, before and after head creation, duplicate initialization, and disabled skins. Userscript syntax and git diff whitespace checks passed. No additional dependency was introduced.

The contributor guide and complete template have been read. This PR targets dev; version metadata is managed by the existing bot. The final checklist remains unchecked because item 11 requires a human to verify both the new and old/classic XMOJ UI; automated browser fixture checks do not assert that manual verification. Please verify both UIs before confirming the checklist.


By submitting this pull request, I confirm the following:

  1. I have read and understood the contributor's guide, as well as this entire template. I understand which branch to base my commits and Pull Requests against.
  2. I have commented on my proposed changes within the code.
  3. I have tested my changes.
  4. I am willing to help maintain this change if there are issues with it later.
  5. It is compatible with the GNU General Public License v3.0
  6. I have squashed any insignificant commits. (git rebase)
  7. I have checked that another pull request for this purpose does not exist.
  8. I have considered and confirmed that this submission will be valuable to others.
  9. I accept that this submission may not be used, and the pull request can be closed at the will of the maintainer.
  10. I give this submission freely and claim no ownership to its content.
  11. I have verified that my changes work correctly in both the new UI and the old/classic UI.

  • I have read the above and my PR is ready for review. Check this box to confirm

@sourcery-ai

sourcery-ai Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Reviewer's Guide

The PR replaces repeated NavbarStyler construction with a shared, explicitly managed instance that owns one removable resize listener, cleans up when navbar state changes, and ignores detached elements; a Playwright regression test validates listener counts, visual geometry updates, and cleanup across sustained refreshes and navbar replacement/removal.

Sequence diagram for shared navbar styler refresh

sequenceDiagram
    participant Refresh as UI refresh timer
    participant Update as UpdateNavbarStyler
    participant Styler as NavbarStyler
    participant Window as window
    participant Navbar as Navbar element

    Refresh->>Update: UpdateNavbarStyler()
    Update->>Update: querySelector(navbar)
    alt NewTopBar enabled and same navbar
        Update->>Styler: init()
        Styler->>Navbar: applyStyles()
        Styler->>Navbar: createOverlay()
        Styler->>Navbar: createSpacer()
    else NewTopBar enabled and navbar replaced
        Update->>Styler: destroy()
        Styler->>Window: removeEventListener(resize)
        Update->>Styler: new NavbarStyler(navbar)
        Styler->>Window: addEventListener(resize)
    else NewTopBar disabled or navbar removed
        Update->>Styler: destroy()
        Styler->>Window: removeEventListener(resize)
    end
Loading

State diagram for navbar styler lifecycle

stateDiagram-v2
    [*] --> Unmanaged
    Unmanaged --> Managed: UpdateNavbarStyler() with navbar and NewTopBar
    Managed --> Managed: UpdateNavbarStyler() with same navbar / init()
    Managed --> Managed: resize / updateBlurOverlay()
    Managed --> Unmanaged: navbar replaced or NewTopBar disabled / destroy()
    Managed --> Unmanaged: navbar removed / destroy()
    Unmanaged --> [*]
Loading

Flow diagram for safe navbar resize handling

flowchart TD
    Resize[resize event] --> Connected{navbar.isConnected}
    Connected -->|yes| Blur["updateBlurOverlay()"]
    Connected -->|no| Skip[Skip detached navbar]
Loading

File-Level Changes

Change Details Files
Centralize navbar styling refreshes behind a lifecycle-aware shared styler.
  • Route initial styling and periodic refreshes through UpdateNavbarStyler.
  • Reuse the styler for the same navbar while refreshing styles, overlays, and spacers.
  • Destroy and recreate the styler when the navbar changes, is removed, or NewTopBar is disabled.
  • Guard resize handling against detached navbar elements.
XMOJ.user.js
Add browser regression coverage for resize-listener cleanup and navbar lifecycle behavior.
  • Exercise thousands of refreshes without listener growth.
  • Verify replacement, resize geometry, setting changes, removal, and retained-reference cleanup.
  • Include the new test in the npm test command.
tests/navbar-styler.test.cjs
package.json

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Deploying xmoj-script-dev-channel with  Cloudflare Pages  Cloudflare Pages

Latest commit: 8758133
Status: ✅  Deploy successful!
Preview URL: https://bd27eca5.xmoj-script-dev-channel.pages.dev
Branch Preview URL: https://codex-fix-navbar-listener-le.xmoj-script-dev-channel.pages.dev

View logs

@hendragon-bot hendragon-bot Bot added the user-script This issue or pull request is related to the main user script label Oct 3, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="XMOJ.user.js" line_range="2824-2827" />
<code_context>
         }
     }

+    destroy() {
+        window.removeEventListener('resize', this.resizeHandler);
+        this.navbar = null;
+    }
+
     init() {
</code_context>
<issue_to_address>
**issue (bug_risk):** When the navbar is removed or NewTopBar is disabled, `destroy()` removes only the resize listener and clears the navbar reference; the `#navbar-spacer` element and `#blur-overlay` remain in the document. The spacer continues to reserve the old navbar height, leaving a blank gap after the navbar has disappeared.

**Triggers:** When a page removes its navbar or the user disables NewTopBar without reloading.

**Suggested fix:** Remove or reset the styler-owned `#navbar-spacer` and `#blur-overlay` during destruction, while preserving any elements that were not created by this styler.
</issue_to_address>

Sourcery assessment

Approval pending. 1 finding to address first.

Blocking findings: XMOJ.user.js:2827


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread XMOJ.user.js
sourcery-ai[bot]
sourcery-ai Bot previously approved these changes Oct 3, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sourcery assessment

Approved.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c1b6d4b53d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread XMOJ.user.js

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 4 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread tests/navbar-styler.test.cjs

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread XMOJ.user.js
@boomzero boomzero changed the title fix: reuse navbar styler and clean up resize listeners fix: prevent navbar listener leaks and download overlap Oct 3, 2026
@sourcery-ai
sourcery-ai Bot dismissed their stale review October 3, 2026 13:16

Sourcery withdrew this approval because it has stopped reviewing this pull request.

@sourcery-ai

sourcery-ai Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

Sourcery has withdrawn its approval of this pull request. It auto-reviews a pull request 5 times, and this push is past that limit, so the approval no longer reflects code Sourcery has read.

Comment @sourcery-ai review to get a fresh review, which can approve again.

Re-reviews, rate limits and approvals

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 3 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="tests/navbar-styler.test.cjs">

<violation number="1" location="tests/navbar-styler.test.cjs:184">
P2: This assertion passes even when the spacer does not grow: the initial 88px spacer plus 16px margin leaves the card around 104px, still below a 96px navbar. Assert that the card top advances by the navbar-height delta so the test catches stale spacing.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

firstCardTop: document.querySelector('.software_item').getBoundingClientRect().top
};
});
assert.ok(after.firstCardTop >= after.navbarBottom, 'the spacing must follow navbar height changes');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This assertion passes even when the spacer does not grow: the initial 88px spacer plus 16px margin leaves the card around 104px, still below a 96px navbar. Assert that the card top advances by the navbar-height delta so the test catches stale spacing.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At tests/navbar-styler.test.cjs, line 184:

<comment>This assertion passes even when the spacer does not grow: the initial 88px spacer plus 16px margin leaves the card around 104px, still below a 96px navbar. Assert that the card top advances by the navbar-height delta so the test catches stale spacing.</comment>

<file context>
@@ -133,3 +133,56 @@ test('navbar refreshes keep one resize listener and release replaced navbars', {
+                firstCardTop: document.querySelector('.software_item').getBoundingClientRect().top
+            };
+        });
+        assert.ok(after.firstCardTop >= after.navbarBottom, 'the spacing must follow navbar height changes');
+    } finally {
+        await browser.close();
</file context>
Suggested change
assert.ok(after.firstCardTop >= after.navbarBottom, 'the spacing must follow navbar height changes');
assert.ok(after.firstCardTop >= before.firstCardTop + (after.navbarBottom - before.navbarBottom), 'the first row must move with navbar height changes');

@boomzero boomzero changed the title fix: prevent navbar listener leaks and download overlap fix: navbar listener leaks, download overlap, and web fonts Oct 3, 2026
@boomzero
boomzero merged commit b46e5c2 into dev Oct 3, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/L user-script This issue or pull request is related to the main user script

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants