Skip to content

feat: compress ApplyData payloads with gzip and Base93 - #1015

Open
boomzero wants to merge 29 commits into
devfrom
feat/apply-data-gzip-base91
Open

feat: compress ApplyData payloads with gzip and Base93#1015
boomzero wants to merge 29 commits into
devfrom
feat/apply-data-gzip-base91

Conversation

@boomzero

@boomzero boomzero commented Aug 21, 2026

Copy link
Copy Markdown
Member

What does this PR aim to accomplish?

Upgrade the existing ApplyData (“获取数据”) component from plain Base64 transport to compressed, bracket-safe transports that support larger inputs while retaining a safe exact mode.

How does this PR accomplish the above?

  • uses gzip + Base93 as the default exact transport;
  • excludes [ and ] from Base93 so payload boundaries remain unambiguous;
  • adds an optional NSC3 high-speed numeric transport and retains legacy NSC1 decoding;
  • reconstructs the full signed long long range with JavaScript BigInt;
  • adds a separate line-preservation choice for numeric mode;
  • preserves mixed or noncanonical input verbatim;
  • creates #apply_data when the page does not provide it;
  • preserves each problem's configured IOFile;
  • writes and flushes the bracketed payload through cerr, then calls abort() to trigger SIGABRT without the logic_error wrapper;
  • accepts both raw [payload] stderr lines and legacy what(): [payload] output;
  • continues to extract multiple valid payloads and treats malformed ones as a normal fetch failure.

Mode details

Mode What it does Use it when Caveats
Default gzip mode Compresses the entire input byte-for-byte with gzip, then encodes it using bracket-safe Base93. The input contains arbitrary text, Unicode, mixed tokens, significant whitespace, or is read with getline/character-level APIs. This is the safest choice and always restores the original bytes, but it cannot exploit numeric structure.
High-speed numeric mode, “保留换行:是” Parses canonical signed decimal integers within the long long range. NSC3 separates values from per-line number counts and compresses both streams using packed raw, delta, frame-of-reference, run, and back-reference blocks. The input is mainly numeric and original row boundaries matter. Canonical spaces, empty lines, line breaks, and a final newline are restored exactly. Tabs, repeated spaces, CRLF, leading zeros, non-number tokens, or other noncanonical content cause an exact verbatim fallback. Random line lengths can still exceed the judge output limit because their line structure contains real entropy.
High-speed numeric mode, “保留换行:否” Uses the same NSC3 number compression, discards canonical numeric line boundaries, and reconstructs the values as one space-separated line without a trailing newline. The target program reads only with whitespace-insensitive extraction such as cin >> value or scanf("%lld", ...). Do not use it with getline, line-based parsing, meaningful row/column boundaries, or code that requires a final newline. Mixed/noncanonical input still falls back verbatim and is not flattened.

The “保留换行” control is available only when “高速数值模式” is enabled and defaults to “是”. Enabling numeric mode alone therefore does not alter line layout.

Known limitation

With exact line preservation, 200,000 uniformly random line lengths from 1–8 carry roughly 75 KB of unavoidable structure. The measured NSC3 result is 79,714 bytes / about 97,510 Base93 characters, which can exceed the judge output limit. When line boundaries are semantically irrelevant, disabling preservation reduces that tested payload to 26 Base93 characters.

Verification

  • XMOJ.user.js passes node --check;
  • both embedded encoders compile as C++11 with strict warnings;
  • NSC3 round trips pass for empty input, signed 64-bit boundaries, canonical data, empty lines, noncanonical/mixed fallback, and 10,000 sequential values;
  • the 1,000,004-byte / 200,001-line structured reproducer round-trips in 345 Base93 characters;
  • the 1,799,578-byte random-line reproducer round-trips in 97,510 characters with exact lines and 26 characters with line preservation disabled;
  • gzip + Base93 round trips pass for empty, Unicode, and repetitive text;
  • extraction tests pass for both raw stderr and legacy exception-wrapped payloads;
  • the production numeric program is verified to terminate via SIGABRT while retaining a recoverable stderr payload;

Summary by Sourcery

Upgrade ApplyData transport and controls to support smaller, exact payloads and optimized numeric input generation.

New Features:

  • Add compressed gzip/Base93 payload transport with exact byte preservation by default.
  • Add optional NSC3 high-speed numeric transport with signed 64-bit support, legacy NSC1 decoding, and configurable line preservation.
  • Create the ApplyData controls automatically when the page does not provide them.

Bug Fixes:

  • Improve payload extraction by accepting both raw stderr output and legacy exception-wrapped output while ignoring malformed payloads.
  • Preserve configured problem-specific input/output file settings during data generation.
  • Ensure generated helper programs flush payloads to stderr and terminate reliably via SIGABRT.

Enhancements:

  • Support verbatim fallback for mixed or noncanonical numeric input and provide UI guidance for numeric and line-preservation modes.

Build:

  • Bump the project and userscript version to 3.6.3.

Summary by cubic

Compresses ApplyData payloads with gzip + bracket‑safe Base93 and adds an optional high‑speed numeric stream (NSC3). Previously we Base64‑encoded and threw an exception; now we write a bracketed Base93 payload to stderr and abort, reducing size and improving extraction while preserving exact bytes.

  • Transport/encoder: C++ helper reads stdin honoring per‑problem IOFile; default is gzip+Base93 with [ and ] excluded; optional NSC3 for signed 64‑bit input, with NSC_IGNORE_LINES to flatten lines; writes [payload] to stderr, flushes, then aborts; removes the Base64/exception path.
  • Decoder: Scans stderr for bracketed payloads or legacy what(): [payload]; decodes Base93 to gzip or NSC1/NSC3; reconstructs 64‑bit values via BigInt; supports multiple payloads; ignores malformed ones; treats no payload as a normal failure.
  • UI: Renders ApplyData controls even if #apply_data is missing; adds toggles for “高速数值模式” and “保留换行”(default on) with inline explanations.
  • Behavior: Exact mode round‑trips arbitrary input; numeric mode falls back to verbatim on mixed/noncanonical input; disabling line preservation outputs one space‑separated line for whitespace‑insensitive reads.
  • Rollout: Bump to prerelease 3.6.3 in package.json and Update.json; no migrations.

Written for commit 0d5fef7. Summary will update on new commits.

Review in cubic

Summary by Sourcery

Upgrade ApplyData transport and decoding to support smaller exact payloads and an optional high-speed numeric mode while preserving compatibility and input fidelity.

New Features:

  • Add compressed gzip/Base93 exact transport and an optional high-speed NSC3 numeric transport for ApplyData payloads.
  • Add controls for numeric mode and optional line preservation, with guidance for appropriate input-reading patterns.
  • Automatically create the ApplyData UI container when it is absent and retain configured problem I/O filenames.

Bug Fixes:

  • Preserve arbitrary, mixed, and noncanonical input verbatim while supporting full signed long long values.
  • Improve payload extraction by accepting raw and legacy exception-wrapped stderr formats and handling malformed payloads as fetch failures.
  • Make generated helper programs reliably expose payloads through stderr before terminating.

Enhancements:

  • Support legacy NSC1 decoding alongside NSC3 and restore numeric data with JavaScript BigInt.
  • Replace the previous Base64/exception-based data transport with bracket-safe payload encoding and multiple-payload extraction.

Build:

  • Bump the userscript and package version to 3.6.3 and update release metadata.

@hendragon-bot hendragon-bot Bot added the user-script This issue or pull request is related to the main user script label Aug 21, 2026
@sourcery-ai

sourcery-ai Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Implements a new ApplyData transport pipeline that replaces Base64/exception-based stderr encoding with bracket-safe Base93 plus gzip or NSC3 numeric compression, adds client-side decoders and UI controls for numeric mode and line preservation, auto-creates the ApplyData container, and bumps the script/package version to 3.6.3.

Sequence diagram for updated ApplyData data fetch pipeline

sequenceDiagram
    actor User
    participant Browser as XMOJ.user.js
    participant Judge as xmoj.tech
    participant Helper as Cpp_helper

    User->>Browser: click GetDataButton
    Browser->>Browser: Build Code (NumberStreamEnabled / GzipCode)
    Browser->>Judge: fetch submit.php (Code)
    Judge->>Helper: run helper program
    Helper->>Helper: rd()
    alt NumberStreamEnabled
        Helper->>Helper: ns()
    else Default gzip mode
        Helper->>Helper: gz()
    end
    Helper->>Helper: b93()
    Helper->>Judge: write [payload] to stderr
    Helper->>Helper: abort()
    Judge-->>Browser: reinfo.php HTML (stderr in #errtxt)
    Browser->>Browser: ExtractData(ErrorData)
    loop for each [payload]
        Browser->>Browser: DecodePayload(payload)
        alt NSC1/NSC3 header
            Browser->>Browser: NumberStreamDecode(rawData)
        else gzip data
            Browser->>Browser: GzipDecode(rawData)
        end
        Browser->>Browser: render decoded data in ApplyDiv
    end
Loading

File-Level Changes

Change Details Files
Replace Base64+logic_error ApplyData transport with gzip+Base93 or NSC3 numeric encoding and add matching client-side decoding and controls.
  • Auto-create the #apply_data container under the results area when missing and reuse its parent as the ApplyData panel root.
  • Add in-page Base93 decoder and numeric stream (NSC1/NSC3) decoders in JavaScript, including BigInt-based varint handling and a payload extractor that parses bracketed payloads from raw or legacy what(): stderr lines while ignoring malformed entries.
  • Introduce a gzip+Base93 C++ helper program that reads stdin (respecting IOFile), compresses with a custom gzip implementation, encodes with bracket-safe Base93, writes the payload as a single [payload] line to stderr, flushes, then terminates via abort() instead of throwing logic_error.
  • Introduce an NSC3 numeric C++ helper program that parses canonical signed long long input, optionally ignores line boundaries via NSC_IGNORE_LINES, compresses numbers and line-structure with NSC3, wraps them in a Base93-encoded [payload], and aborts after writing to stderr.
  • Wire the ApplyData button to submit either the gzip or NSC3 helper code depending on a new numeric-mode toggle, preserving per-problem IOFile configuration in the generated program.
  • Add two new UI buttons to toggle "高速数值模式" and "保留换行" (line preservation), including dynamic styling, enable/disable behavior, and descriptive text for each mode.
  • Update the ApplyData click handler to use the new ExtractData pipeline, display each decoded dataset in a separate block, and keep the existing show/hide behavior for data blocks.
  • Bump userscript and package versions from 3.6.2 to 3.6.3 and update Update.json metadata accordingly.
XMOJ.user.js
Update.json
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 Aug 21, 2026

Copy link
Copy Markdown

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

Latest commit: 0d5fef7
Status: ✅  Deploy successful!
Preview URL: https://0d2f4a42.xmoj-script-dev-channel.pages.dev
Branch Preview URL: https://feat-apply-data-gzip-base91.xmoj-script-dev-channel.pages.dev

View logs

@boomzero boomzero changed the title feat: compress ApplyData payloads with gzip and Base91 feat: compress ApplyData payloads with gzip and Base93 Aug 21, 2026
@boomzero
boomzero marked this pull request as ready for review August 21, 2026 14:08
@boomzero

Copy link
Copy Markdown
Member Author

Tested and confirmed to work

@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 2 issues, and left some high level feedback:

  • Both C++ helpers inline a full Base93 encoder (b93) and rd implementation separately; consider factoring out the shared pieces so future changes to the transport format need to be updated in only one place.
  • GzipDecode relies on DecompressionStream('gzip'), which is not available in all browsers; adding a capability check and a clearer failure path (or a fallback decoder) would make the ApplyData feature degrade more gracefully.
  • The numeric/line-preservation toggle buttons update aria-pressed states in a somewhat inverted way (e.g. line-preservation button uses !PreserveLineBreaks); it may be worth double-checking the ARIA semantics so assistive technologies reflect the actual on/off state of each mode.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Both C++ helpers inline a full Base93 encoder (`b93`) and `rd` implementation separately; consider factoring out the shared pieces so future changes to the transport format need to be updated in only one place.
- `GzipDecode` relies on `DecompressionStream('gzip')`, which is not available in all browsers; adding a capability check and a clearer failure path (or a fallback decoder) would make the ApplyData feature degrade more gracefully.
- The numeric/line-preservation toggle buttons update `aria-pressed` states in a somewhat inverted way (e.g. line-preservation button uses `!PreserveLineBreaks`); it may be worth double-checking the ARIA semantics so assistive technologies reflect the actual on/off state of each mode.

## Individual Comments

### Comment 1
<location path="XMOJ.user.js" line_range="5571-5577" />
<code_context>
+                                    LineBreakButton.disabled = !NumberStreamEnabled;
+                                    LineBreakButton.classList.toggle("btn-outline-secondary", PreserveLineBreaks || !NumberStreamEnabled);
+                                    LineBreakButton.classList.toggle("btn-outline-warning", !PreserveLineBreaks && NumberStreamEnabled);
+                                    LineBreakButton.setAttribute("aria-pressed", String(!PreserveLineBreaks));
+                                }
+                                LineBreakButton.addEventListener("click", () => {
</code_context>
<issue_to_address>
**suggestion (bug_risk):** The aria-pressed state is inverted relative to the label and might confuse accessibility tooling.

The button text is "保留换行:是/否" and the visually active (ON) state is PreserveLineBreaks=true, but aria-pressed is bound to !PreserveLineBreaks. This causes assistive tech to report the opposite state from what’s shown. Please bind aria-pressed to PreserveLineBreaks (the ON state), or omit aria-pressed if this control should behave as a simple toggle without pressed semantics.

```suggestion
                                function UpdateLineBreakButton() {
                                    LineBreakButton.innerText = "保留换行:" + (PreserveLineBreaks ? "是" : "否");
                                    LineBreakButton.disabled = !NumberStreamEnabled;
                                    LineBreakButton.classList.toggle("btn-outline-secondary", PreserveLineBreaks || !NumberStreamEnabled);
                                    LineBreakButton.classList.toggle("btn-outline-warning", !PreserveLineBreaks && NumberStreamEnabled);
                                    LineBreakButton.setAttribute("aria-pressed", String(PreserveLineBreaks));
                                }
```
</issue_to_address>

### Comment 2
<location path="XMOJ.user.js" line_range="5740" />
<code_context>
                                 });
                             }
-                            document.getElementById("apply_data").addEventListener("click", () => {
+                            ApplyDataElement.addEventListener("click", () => {
                                 let ApplyElements = document.getElementsByClassName("data");
                                 for (let i = 0; i < ApplyElements.length; i++) {
</code_context>
<issue_to_address>
**question (bug_risk):** Binding the click handler to the apply_data container instead of a dedicated control may change the interaction area and event behavior.

Previously the listener was bound to the specific `#apply_data` element, likely a dedicated control. Now it’s bound to `ApplyDataElement`, which is used as a generic container while buttons are appended to its parent. This makes any click within the container (including empty areas) toggle all `.data` elements, while clicks on the new buttons won’t trigger the handler. To preserve the original behavior, consider attaching the listener to a specific toggle button rather than the container.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread XMOJ.user.js
Comment on lines +5571 to +5577
function UpdateLineBreakButton() {
LineBreakButton.innerText = "保留换行:" + (PreserveLineBreaks ? "是" : "否");
LineBreakButton.disabled = !NumberStreamEnabled;
LineBreakButton.classList.toggle("btn-outline-secondary", PreserveLineBreaks || !NumberStreamEnabled);
LineBreakButton.classList.toggle("btn-outline-warning", !PreserveLineBreaks && NumberStreamEnabled);
LineBreakButton.setAttribute("aria-pressed", String(!PreserveLineBreaks));
}

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.

suggestion (bug_risk): The aria-pressed state is inverted relative to the label and might confuse accessibility tooling.

The button text is "保留换行:是/否" and the visually active (ON) state is PreserveLineBreaks=true, but aria-pressed is bound to !PreserveLineBreaks. This causes assistive tech to report the opposite state from what’s shown. Please bind aria-pressed to PreserveLineBreaks (the ON state), or omit aria-pressed if this control should behave as a simple toggle without pressed semantics.

Suggested change
function UpdateLineBreakButton() {
LineBreakButton.innerText = "保留换行:" + (PreserveLineBreaks ? "是" : "否");
LineBreakButton.disabled = !NumberStreamEnabled;
LineBreakButton.classList.toggle("btn-outline-secondary", PreserveLineBreaks || !NumberStreamEnabled);
LineBreakButton.classList.toggle("btn-outline-warning", !PreserveLineBreaks && NumberStreamEnabled);
LineBreakButton.setAttribute("aria-pressed", String(!PreserveLineBreaks));
}
function UpdateLineBreakButton() {
LineBreakButton.innerText = "保留换行:" + (PreserveLineBreaks ? "是" : "否");
LineBreakButton.disabled = !NumberStreamEnabled;
LineBreakButton.classList.toggle("btn-outline-secondary", PreserveLineBreaks || !NumberStreamEnabled);
LineBreakButton.classList.toggle("btn-outline-warning", !PreserveLineBreaks && NumberStreamEnabled);
LineBreakButton.setAttribute("aria-pressed", String(PreserveLineBreaks));
}

Comment thread XMOJ.user.js
});
}
document.getElementById("apply_data").addEventListener("click", () => {
ApplyDataElement.addEventListener("click", () => {

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.

question (bug_risk): Binding the click handler to the apply_data container instead of a dedicated control may change the interaction area and event behavior.

Previously the listener was bound to the specific #apply_data element, likely a dedicated control. Now it’s bound to ApplyDataElement, which is used as a generic container while buttons are appended to its parent. This makes any click within the container (including empty areas) toggle all .data elements, while clicks on the new buttons won’t trigger the handler. To preserve the original behavior, consider attaching the listener to a specific toggle button rather than the container.

@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.

2 issues found and verified against the latest diff

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="XMOJ.user.js">

<violation number="1" location="XMOJ.user.js:5535">
P2: Exact mode loses bytes when the payload starts with a UTF-8 BOM or contains invalid UTF-8. `TextDecoder` strips the BOM and substitutes invalid bytes with U+FFFD, so use a byte-preserving representation/decoder for the returned data (and define how non-UTF-8 bytes are displayed).</violation>

<violation number="2" location="XMOJ.user.js:5576">
P2: When `PreserveLineBreaks` is true, the button displays `保留换行:是` but exposes `aria-pressed="false"`, so assistive technology reports the opposite state. Bind `aria-pressed` to `PreserveLineBreaks`.</violation>
</file>

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

Re-trigger cubic

Comment thread XMOJ.user.js
if (rawData.length >= 4 && rawData[0] === 78 && rawData[1] === 83 && rawData[2] === 67 && (rawData[3] === 49 || rawData[3] === 51)) {
return NumberStreamDecode(rawData);
}
return new TextDecoder().decode(await GzipDecode(rawData));

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: Exact mode loses bytes when the payload starts with a UTF-8 BOM or contains invalid UTF-8. TextDecoder strips the BOM and substitutes invalid bytes with U+FFFD, so use a byte-preserving representation/decoder for the returned data (and define how non-UTF-8 bytes are displayed).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At XMOJ.user.js, line 5535:

<comment>Exact mode loses bytes when the payload starts with a UTF-8 BOM or contains invalid UTF-8. `TextDecoder` strips the BOM and substitutes invalid bytes with U+FFFD, so use a byte-preserving representation/decoder for the returned data (and define how non-UTF-8 bytes are displayed).</comment>

<file context>
@@ -5174,10 +5174,428 @@ async function main() {
+                                    if (rawData.length >= 4 && rawData[0] === 78 && rawData[1] === 83 && rawData[2] === 67 && (rawData[3] === 49 || rawData[3] === 51)) {
+                                        return NumberStreamDecode(rawData);
+                                    }
+                                    return new TextDecoder().decode(await GzipDecode(rawData));
+                                }
+
</file context>

Comment thread XMOJ.user.js
LineBreakButton.disabled = !NumberStreamEnabled;
LineBreakButton.classList.toggle("btn-outline-secondary", PreserveLineBreaks || !NumberStreamEnabled);
LineBreakButton.classList.toggle("btn-outline-warning", !PreserveLineBreaks && NumberStreamEnabled);
LineBreakButton.setAttribute("aria-pressed", String(!PreserveLineBreaks));

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: When PreserveLineBreaks is true, the button displays 保留换行:是 but exposes aria-pressed="false", so assistive technology reports the opposite state. Bind aria-pressed to PreserveLineBreaks.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At XMOJ.user.js, line 5576:

<comment>When `PreserveLineBreaks` is true, the button displays `保留换行:是` but exposes `aria-pressed="false"`, so assistive technology reports the opposite state. Bind `aria-pressed` to `PreserveLineBreaks`.</comment>

<file context>
@@ -5174,10 +5174,428 @@ async function main() {
+                                    LineBreakButton.disabled = !NumberStreamEnabled;
+                                    LineBreakButton.classList.toggle("btn-outline-secondary", PreserveLineBreaks || !NumberStreamEnabled);
+                                    LineBreakButton.classList.toggle("btn-outline-warning", !PreserveLineBreaks && NumberStreamEnabled);
+                                    LineBreakButton.setAttribute("aria-pressed", String(!PreserveLineBreaks));
+                                }
+                                LineBreakButton.addEventListener("click", () => {
</file context>
Suggested change
LineBreakButton.setAttribute("aria-pressed", String(!PreserveLineBreaks));
LineBreakButton.setAttribute("aria-pressed", String(PreserveLineBreaks));

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

Labels

size/XL 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