Broaden mobile download fallbacks#33
Conversation
Manual test: - On a mobile browser (Android/iOS), export Page → Markdown and confirm the download completes. - On a mobile browser, export Page Info and confirm the .txt download completes. - On a mobile browser, export a ChatGPT conversation and confirm the download completes.
📝 WalkthroughWalkthroughThree userscripts add mobile-aware download handling: mobile detection, a mobile fallback timer, blob→data-URL conversion for small blobs, unified fallback/cleanup logic, and altered async download control flow to coordinate GM_download vs data/blob URL fallbacks while preserving desktop behaviour. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Script
participant GM_download
participant Timer
User->>Script: Request export/download
Script->>Script: Detect isMobileBrowser, create Blob, check size
alt Mobile & blob < MAX_SIZE
Script->>Script: blobToDataUrl(blob)
Script->>User: Trigger download via data URL
Script->>Script: Revoke / cleanup
else Mobile & blob >= MAX_SIZE
Script->>GM_download: Start GM_download (confirm:false)
Script->>Timer: Start MOBILE_FALLBACK_DELAY_MS
par
GM_download-->>Script: onload/onerror
Script->>Timer: clearFallbackTimer()
Script->>Script: Cleanup / resolve
and
Timer->>Script: timeout -> triggerFallback()
Script->>Script: Fallback -> data/blob URL download
Script->>Script: Cleanup / resolve
end
else Desktop
Script->>GM_download: Start GM_download (normal)
GM_download-->>Script: Complete
Script->>Script: Cleanup
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: Organization UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (3)
🧰 Additional context used📓 Path-based instructions (1)**/*.user.js📄 CodeRabbit inference engine (AGENTS.md)
Files:
🧠 Learnings (16)📓 Common learnings📚 Learning: 2026-01-05T10:24:18.701ZApplied to files:
📚 Learning: 2026-01-05T10:24:18.701ZApplied to files:
📚 Learning: 2026-01-05T10:24:18.701ZApplied to files:
📚 Learning: 2026-01-05T10:24:18.701ZApplied to files:
📚 Learning: 2026-01-05T10:24:18.701ZApplied to files:
📚 Learning: 2026-01-05T10:24:18.701ZApplied to files:
📚 Learning: 2026-01-05T10:24:18.701ZApplied to files:
📚 Learning: 2026-01-05T10:24:18.701ZApplied to files:
📚 Learning: 2026-01-05T10:24:18.701ZApplied to files:
📚 Learning: 2026-01-05T10:24:18.701ZApplied to files:
📚 Learning: 2026-01-05T10:24:18.701ZApplied to files:
📚 Learning: 2026-01-05T10:24:18.701ZApplied to files:
📚 Learning: 2026-01-05T10:24:18.701ZApplied to files:
📚 Learning: 2026-01-05T10:24:18.701ZApplied to files:
📚 Learning: 2026-01-05T10:24:18.701ZApplied to files:
🧬 Code graph analysis (2)chatgptmd.user.js (2)
pageinfoexport.user.js (2)
🔇 Additional comments (10)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
PR Compliance Guide 🔍Below is a summary of compliance checks for this PR:
Compliance status legend🟢 - Fully Compliant🟡 - Partial Compliant 🔴 - Not Compliant ⚪ - Requires Further Human Verification 🏷️ - Compliance label |
||||||||||||||||||||||||
PR Code Suggestions ✨Latest suggestions up to a011a82
Previous suggestions✅ Suggestions up to commit b6eb1e4
|
||||||||||||||||||||||||||||
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In @chatgptmd.user.js:
- Around line 1481-1501: There’s a race where the mobile fallback timer and the
promise handlers can both call fallback(), causing duplicate downloads; add a
single-run guard (e.g., a boolean like fallbackInvoked or a method on resource
such as resource.fallbackInvoked) and check/set it whenever you would call
fallback() (in the timeout, in the promise.catch, and anywhere else that
triggers fallback), ensure clearFallbackTimer and the promise resolution path
respect the guard, and only call resource.markStale(),
resource.cleanup(cleanupDelay) or fallback() when the guard indicates the action
hasn’t already run.
🧹 Nitpick comments (2)
pagemd.user.js (1)
594-622: Consider limiting data URL conversion to mobile browsers only.The data URL conversion is performed unconditionally for all files under 2MB, but the comment states it's for "Mobile compatibility". On desktop, blob URLs work reliably and are more memory-efficient. Consider gating this conversion behind
isMobileBrowserto avoid unnecessary processing on desktop:♻️ Suggested optimisation
// Mobile compatibility: prefer data URL for better download manager support // Blob URLs may fail silently on some Android browsers let downloadUrl = resource.getUrl(); - try { - const blob = resource.getBlob(); - // Only convert to data URL if size is reasonable (< 2MB for mobile compatibility) - if (blob.size < MAX_DATA_URL_FILE_SIZE_BYTES) { + if (isMobileBrowser) { + try { + const blob = resource.getBlob(); + // Only convert to data URL if size is reasonable (< 2MB for mobile compatibility) + if (blob.size < MAX_DATA_URL_FILE_SIZE_BYTES) { + const reader = new FileReader(); + const dataUrlPromise = new Promise((resolve, reject) => { + reader.onload = () => resolve(reader.result); + reader.onerror = () => reject(new Error('Failed to read blob')); + reader.readAsDataURL(blob); + }); + try { + downloadUrl = await dataUrlPromise; + } catch (err) { + if (DEBUG) { + logWarn('Failed to convert blob to data URL, falling back to blob URL', { error: err?.message || String(err) }); + } + downloadUrl = resource.getUrl(); + } + } + } catch (err) { + if (DEBUG) { + logWarn('Error while preparing download URL, using original blob URL', { error: err?.message || String(err) }); + } + } + }pageinfoexport.user.js (1)
985-1009: Same suggestion as pagemd.user.js: consider mobile-only data URL conversion.This data URL conversion logic is identical to
pagemd.user.js. The same optional refactor applies: gating behindisMobileBrowserwould avoid unnecessary processing on desktop browsers where blob URLs work reliably.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
chatgptmd.user.jspageinfoexport.user.jspagemd.user.js
🧰 Additional context used
📓 Path-based instructions (1)
**/*.user.js
📄 CodeRabbit inference engine (AGENTS.md)
**/*.user.js: Userscript metadata block must be positioned at the very beginning of the file with exact syntax:// ==UserScript==and// ==/UserScript==on their own lines, each metadata line starting with//(exactly one space after//)
Include required metadata keys:@name,@namespace(set tohttps://github.com/cbkii/userscripts),@version(datetime formatYYYY.MM.DD.HHMM),@description,@author,@icon(base64 SVG data URI with #FF1493 hot pink stroke),@matchor@include, and@run-at
Each userscript must include a base64-encoded SVG data URI icon in the@iconmetadata field using a simple line-style icon with hot pink (#FF1493) stroke color, placed after@authorand before@match
Use@matchpatterns with the narrowest practical scope (least privilege principle), preferring Chrome-style match patterns like*://example.com/*over broad patterns like*://*/*
Declare all GM APIs used in the@grantmetadata field, including legacy (GM_getValue) or async versions (GM.getValue), and avoid implicit grants
For any cross-origin network requests usingGM_xmlhttpRequest, declare required domains in@connectmetadata entries; avoid@connect *unless truly necessary
Wrap script logic in a single top-level IIFE with'use strict'directive and provide a clearmain()entrypoint
Increment@versionfor every functional change using datetime formatYYYY.MM.DD.HHMM(UTC), ensuring versions never decrease
Ensure DOM updates are idempotent (safe to run multiple times) by checking if initialization has already occurred, marking injected elements, and avoiding duplicate listeners
For any@match *://*/*or similarly broad patterns, implement dormant-by-default behavior: register menu commands and UI shell on init, but only run heavy work when Always Run is enabled or user explicitly triggers via UI
Use consistent logging with a singlecreateLogger()helper per script, store logs under `GM_setValue('userscript.logs.<short...
Files:
pagemd.user.jspageinfoexport.user.jschatgptmd.user.js
🧠 Learnings (9)
📓 Common learnings
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Consult and apply the ANDROID-XBROWSER-TESTING.md and ANDROID-XBROWSER-FIXES-TECHNICAL.md documents when designing or fixing scripts for XBrowser, especially for polling fallbacks, download functionality, and load order independence
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Use the polling fallback pattern for XBrowser compatibility: when APIs fail (e.g., `GM_download`, `fetch`), fall back to polling-based alternatives with bounded retry counts and clear backoff strategies
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Ensure compatibility with XBrowser (Android) by: sticking to documented Tampermonkey APIs, avoiding obscure/experimental features, keeping syntax ES2018-compatible, and using simple manual install paths
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Include clear manual test steps in PR/commit messages or `docs/<script>.md` documenting: happy path (expected behavior), negative path (missing elements, excluded pages), SPA navigation, and XBrowser compatibility checks
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Prioritize Android XBrowser compatibility above all other considerations; test on XBrowser and ensure scripts work with its built-in script manager
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Use canonical CDN URLs for shared dependencies: jQuery from googleapis, jQuery UI from googleapis, Readability from jsDelivr, Turndown/GFM from unpkg (UMD builds, not CJS)
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Use recommended metadata fields: `license`, `noframes`, `homepageURL` (set to `https://github.com/cbkii/userscripts`), `supportURL` (set to `https://github.com/cbkii/userscripts/issues`), `updateURL`, and `downloadURL` (both pointing to raw GitHub URLs)
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Ensure compatibility with XBrowser (Android) by: sticking to documented Tampermonkey APIs, avoiding obscure/experimental features, keeping syntax ES2018-compatible, and using simple manual install paths
Applied to files:
pagemd.user.jspageinfoexport.user.jschatgptmd.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Use the polling fallback pattern for XBrowser compatibility: when APIs fail (e.g., `GM_download`, `fetch`), fall back to polling-based alternatives with bounded retry counts and clear backoff strategies
Applied to files:
pagemd.user.jspageinfoexport.user.jschatgptmd.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Consult and apply the ANDROID-XBROWSER-TESTING.md and ANDROID-XBROWSER-FIXES-TECHNICAL.md documents when designing or fixing scripts for XBrowser, especially for polling fallbacks, download functionality, and load order independence
Applied to files:
pagemd.user.jspageinfoexport.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Use a tiny compatibility wrapper (GMX pattern) to handle both GM4 async (`GM.getValue`) and legacy (`GM_getValue`) APIs, enabling cross-manager compatibility
Applied to files:
pagemd.user.jschatgptmd.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Declare all GM APIs used in the `grant` metadata field, including legacy (`GM_getValue`) or async versions (`GM.getValue`), and avoid implicit grants
Applied to files:
pagemd.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Include required metadata keys: `name`, `namespace` (set to `https://github.com/cbkii/userscripts`), `version` (datetime format `YYYY.MM.DD.HHMM`), `description`, `author`, `icon` (base64 SVG data URI with #FF1493 hot pink stroke), `match` or `include`, and `run-at`
Applied to files:
pagemd.user.jspageinfoexport.user.jschatgptmd.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Use recommended metadata fields: `license`, `noframes`, `homepageURL` (set to `https://github.com/cbkii/userscripts`), `supportURL` (set to `https://github.com/cbkii/userscripts/issues`), `updateURL`, and `downloadURL` (both pointing to raw GitHub URLs)
Applied to files:
pagemd.user.jspageinfoexport.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : If hosting scripts with updates, set both `updateURL` (metadata URL) and `downloadURL` (full script URL) to raw GitHub URLs; increment `version` for any functional change
Applied to files:
pageinfoexport.user.js
🧬 Code graph analysis (2)
pagemd.user.js (2)
chatgptmd.user.js (5)
isMobileBrowser(73-74)DOWNLOAD_ANCHOR_DELAY_MS(75-75)MOBILE_FALLBACK_DELAY_MS(76-76)gmDownloadAsync(70-72)gmDownloadLegacy(69-69)pageinfoexport.user.js (3)
isMobileBrowser(306-307)DOWNLOAD_ANCHOR_DELAY_MS(302-302)MOBILE_FALLBACK_DELAY_MS(303-303)
chatgptmd.user.js (2)
pageinfoexport.user.js (7)
isMobileBrowser(306-307)DOWNLOAD_ANCHOR_DELAY_MS(302-302)MOBILE_FALLBACK_DELAY_MS(303-303)BLOB_STALE_MS(304-304)BLOB_REVOKE_MS(305-305)MAX_DATA_URL_FILE_SIZE_BYTES(61-61)blobToDataUrl(926-931)pagemd.user.js (6)
isMobileBrowser(452-453)DOWNLOAD_ANCHOR_DELAY_MS(454-454)MOBILE_FALLBACK_DELAY_MS(455-455)BLOB_STALE_MS(456-456)BLOB_REVOKE_MS(457-457)MAX_DATA_URL_FILE_SIZE_BYTES(64-64)
🔇 Additional comments (9)
pagemd.user.js (2)
452-455: LGTM! Mobile detection and constants are consistent across the PR.The
isMobileBrowserregex andMOBILE_FALLBACK_DELAY_MSconstant align with the other scripts in this PR (pageinfoexport.user.jsandchatgptmd.user.js), ensuring consistent mobile behaviour across the userscript suite. Based on learnings, this follows the polling fallback pattern for XBrowser compatibility.
624-630: LGTM! Clean timer cleanup helper.The
clearFallbackTimerhelper properly clears and nullifies the timer ID, preventing double-clearing issues. This pattern is consistent withchatgptmd.user.js.pageinfoexport.user.js (2)
966-975: LGTM! Promise coordination for mobile fallback path.The separate
resolveMobileFallbackpromise allows the function to wait for mobile completion while still supporting the legacy callback-based path. This is a reasonable approach for coordinating the asynchronous completion signals.
1011-1034: LGTM! Download details properly handle both mobile and non-mobile paths.The download details correctly:
- Set
confirm: falseonly on mobile for better download manager compatibility- Resolve
resolveMobileFallbackin bothonloadandonerrorto ensure the mobile promise completes- Use
void handleError(err)to properly handle the async error handlerchatgptmd.user.js (5)
73-79: LGTM! Mobile constants are consistent with other files.The mobile detection regex and
MOBILE_FALLBACK_DELAY_MSmatch the other scripts. Using2 * 1024 * 1024forMAX_DATA_URL_FILE_SIZE_BYTESis more readable than the literal2097152used in the other files—consider updating the others for consistency.
1426-1431: LGTM! Helper is consistent with pageinfoexport.user.js.The
blobToDataUrlhelper implementation is identical topageinfoexport.user.js, ensuring consistent behaviour. Per coding guidelines, each script must be self-contained, so this duplication is acceptable.
1445-1457: Good: Data URL conversion correctly gated to mobile-only here.Unlike
pagemd.user.jsandpageinfoexport.user.js, this file correctly gates the data URL conversion behindisMobileBrowser. This is the preferred approach—consider aligning the other two files with this pattern.
1465-1479: InconsistentsaveAsvalue across files.This file uses
saveAs: true(line 1468), whilepagemd.user.jsandpageinfoexport.user.jsusesaveAs: false. This affects whether users see a "Save As" dialog:
saveAs: true— prompts user for save locationsaveAs: false— saves directly to default downloads folderVerify this inconsistency is intentional. If ChatGPT exports should behave consistently with other scripts, consider aligning the value.
512-513: LGTM! Proper async integration.The
exportChatfunction now correctly awaitsmobileDownloadfor download actions, ensuring proper async flow.
Manual test: - On a mobile browser (Android/iOS), export Page → Markdown and confirm the download completes. - On a mobile browser, export Page Info and confirm the .txt download completes. - On a mobile browser, export a ChatGPT conversation and confirm the download completes.
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
pagemd.user.js (1)
594-623: Mobile data-URL conversion currently runs on desktop too (likely unintended). Lines 594-623.The comment says “Mobile compatibility”, but the conversion runs regardless of
isMobileBrowser, changing non-mobile behaviour and adding FileReader work for <2MB exports.Proposed fix (gate conversion to mobile, ensure string)
- // Mobile compatibility: prefer data URL for better download manager support - // Blob URLs may fail silently on some Android browsers - let downloadUrl = resource.getUrl(); - try { - const blob = resource.getBlob(); - // Only convert to data URL if size is reasonable (< 2MB for mobile compatibility) - if (blob.size < MAX_DATA_URL_FILE_SIZE_BYTES) { - const reader = new FileReader(); - const dataUrlPromise = new Promise((resolve, reject) => { - reader.onload = () => resolve(reader.result); - reader.onerror = () => reject(new Error('Failed to read blob')); - reader.readAsDataURL(blob); - }); - try { - downloadUrl = await dataUrlPromise; - } catch (err) { - if (DEBUG) { - logWarn('Failed to convert blob to data URL, falling back to blob URL', { error: err?.message || String(err) }); - } - // Fall back to blob URL if data URL conversion fails - downloadUrl = resource.getUrl(); - } - } - } catch (err) { - if (DEBUG) { - logWarn('Error while preparing download URL, using original blob URL', { error: err?.message || String(err) }); - } - // Use blob URL if any error occurs - } + let downloadUrl = resource.getUrl(); + if (isMobileBrowser) { + // Mobile compatibility: prefer data URL for better download manager support + // Blob URLs may fail silently on some Android browsers + try { + const blob = resource.getBlob(); + if (blob.size < MAX_DATA_URL_FILE_SIZE_BYTES) { + const reader = new FileReader(); + downloadUrl = await new Promise((resolve, reject) => { + reader.onload = () => resolve(typeof reader.result === 'string' ? reader.result : ''); + reader.onerror = () => reject(new Error('Failed to read blob')); + reader.readAsDataURL(blob); + }); + if (!downloadUrl) { + downloadUrl = resource.getUrl(); + } + } + } catch (err) { + if (DEBUG) { + logWarn('Failed to convert blob to data URL, falling back to blob URL', { error: err?.message || String(err) }); + } + downloadUrl = resource.getUrl(); + } + }pageinfoexport.user.js (1)
990-1015: Same issue as pagemd: “mobile” data-URL conversion runs even when not mobile, and duplicatesblobToDataUrl. Lines 990-1015.Proposed fix (gate + reuse helper)
- // Mobile compatibility: prefer data URL for better download manager support - // Blob URLs may fail silently on some Android browsers let downloadUrl = resource.getUrl(); - try { - const blob = resource.getBlob(); - // Only convert to data URL if size is reasonable (< 2MB for mobile compatibility) - if (blob.size < MAX_DATA_URL_FILE_SIZE_BYTES) { - const reader = new FileReader(); - const dataUrlPromise = new Promise((resolve, reject) => { - reader.onload = () => resolve(reader.result); - reader.onerror = () => reject(new Error('Failed to read blob')); - reader.readAsDataURL(blob); - }); - try { - downloadUrl = await dataUrlPromise; - } catch (err) { - log('warn', 'Failed to convert blob to data URL, falling back to blob URL', { error: err?.message || String(err) }); - // Fall back to blob URL if data URL conversion fails - downloadUrl = resource.getUrl(); - } - } - } catch (err) { - log('warn', 'Error while preparing download URL, using original blob URL', { error: err?.message || String(err) }); - // Use blob URL if any error occurs - } + if (isMobileBrowser) { + // Mobile compatibility: prefer data URL for better download manager support + // Blob URLs may fail silently on some Android browsers + try { + const blob = resource.getBlob(); + if (blob.size < MAX_DATA_URL_FILE_SIZE_BYTES) { + const dataUrl = await blobToDataUrl(blob); + if (dataUrl) { + downloadUrl = dataUrl; + } + } + } catch (err) { + log('warn', 'Failed to convert blob to data URL, falling back to blob URL', { error: err?.message || String(err) }); + downloadUrl = resource.getUrl(); + } + }
🤖 Fix all issues with AI agents
In @chatgptmd.user.js:
- Around line 1444-1512: The code passes an undocumented Tampermonkey
GM_download option confirm: false for mobile (see the detail object creation and
use in gmDownloadAsync/gmDownloadLegacy), which may be ignored or break on
non‑XBrowser managers; remove the confirm: false field or gate it behind
explicit XBrowser detection (add an isXBrowser flag and only include { confirm:
false } when true) and keep relying on the existing MOBILE_FALLBACK_DELAY_MS
fallback logic; also ensure MOBILE_FALLBACK_DELAY_MS has been validated for
Android/iOS and adjust it if real‑world testing shows 1200ms is too aggressive.
In @pageinfoexport.user.js:
- Around line 1016-1040: The onload and onerror callbacks inside the
downloadDetails object should defensively clear fallbackTimerId to avoid the
fallback timeout firing after the download finishes; at the start of
downloadDetails.onload and downloadDetails.onerror add a check like "if
(fallbackTimerId) clearTimeout(fallbackTimerId)" before calling
resolveMobileFallback/resolveLegacy, resource.cleanup, or handleError so the
timer is always cancelled even if the promise handlers (.then/.catch) haven't
run yet.
🧹 Nitpick comments (4)
pagemd.user.js (1)
4-4: Version bump looks fine; consider adding@sandbox DOMto align with repo rules. Line 4.This change is OK. Noting the metadata block still lacks
@sandbox DOM(required by your guidelines for DOM-only scripts).pageinfoexport.user.js (2)
6-6: Version bump OK; metadata still missing@license/@sandbox DOMper guidelines. Line 6.
962-988: Good: single-fire fallback viafallbackTriggered; but legacy completion machinery looks redundant now. Lines 962-988.Because
GMX.download()always yields a thenable in this file,resolveLegacy/legacyCompletioncan likely be removed to reduce state and edge cases.chatgptmd.user.js (1)
4-4: Version bump OK; metadata still missing@license/@sandbox DOMper guidelines. Line 4.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
chatgptmd.user.jspageinfoexport.user.jspagemd.user.js
🧰 Additional context used
📓 Path-based instructions (1)
**/*.user.js
📄 CodeRabbit inference engine (AGENTS.md)
**/*.user.js: Userscript metadata block must be positioned at the very beginning of the file with exact syntax:// ==UserScript==and// ==/UserScript==on their own lines, each metadata line starting with//(exactly one space after//)
Include required metadata keys:@name,@namespace(set tohttps://github.com/cbkii/userscripts),@version(datetime formatYYYY.MM.DD.HHMM),@description,@author,@icon(base64 SVG data URI with #FF1493 hot pink stroke),@matchor@include, and@run-at
Each userscript must include a base64-encoded SVG data URI icon in the@iconmetadata field using a simple line-style icon with hot pink (#FF1493) stroke color, placed after@authorand before@match
Use@matchpatterns with the narrowest practical scope (least privilege principle), preferring Chrome-style match patterns like*://example.com/*over broad patterns like*://*/*
Declare all GM APIs used in the@grantmetadata field, including legacy (GM_getValue) or async versions (GM.getValue), and avoid implicit grants
For any cross-origin network requests usingGM_xmlhttpRequest, declare required domains in@connectmetadata entries; avoid@connect *unless truly necessary
Wrap script logic in a single top-level IIFE with'use strict'directive and provide a clearmain()entrypoint
Increment@versionfor every functional change using datetime formatYYYY.MM.DD.HHMM(UTC), ensuring versions never decrease
Ensure DOM updates are idempotent (safe to run multiple times) by checking if initialization has already occurred, marking injected elements, and avoiding duplicate listeners
For any@match *://*/*or similarly broad patterns, implement dormant-by-default behavior: register menu commands and UI shell on init, but only run heavy work when Always Run is enabled or user explicitly triggers via UI
Use consistent logging with a singlecreateLogger()helper per script, store logs under `GM_setValue('userscript.logs.<short...
Files:
pagemd.user.jspageinfoexport.user.jschatgptmd.user.js
🧠 Learnings (9)
📓 Common learnings
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Consult and apply the ANDROID-XBROWSER-TESTING.md and ANDROID-XBROWSER-FIXES-TECHNICAL.md documents when designing or fixing scripts for XBrowser, especially for polling fallbacks, download functionality, and load order independence
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Use the polling fallback pattern for XBrowser compatibility: when APIs fail (e.g., `GM_download`, `fetch`), fall back to polling-based alternatives with bounded retry counts and clear backoff strategies
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Ensure compatibility with XBrowser (Android) by: sticking to documented Tampermonkey APIs, avoiding obscure/experimental features, keeping syntax ES2018-compatible, and using simple manual install paths
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Include clear manual test steps in PR/commit messages or `docs/<script>.md` documenting: happy path (expected behavior), negative path (missing elements, excluded pages), SPA navigation, and XBrowser compatibility checks
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Prioritize Android XBrowser compatibility above all other considerations; test on XBrowser and ensure scripts work with its built-in script manager
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Use canonical CDN URLs for shared dependencies: jQuery from googleapis, jQuery UI from googleapis, Readability from jsDelivr, Turndown/GFM from unpkg (UMD builds, not CJS)
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Use recommended metadata fields: `license`, `noframes`, `homepageURL` (set to `https://github.com/cbkii/userscripts`), `supportURL` (set to `https://github.com/cbkii/userscripts/issues`), `updateURL`, and `downloadURL` (both pointing to raw GitHub URLs)
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Use the polling fallback pattern for XBrowser compatibility: when APIs fail (e.g., `GM_download`, `fetch`), fall back to polling-based alternatives with bounded retry counts and clear backoff strategies
Applied to files:
pagemd.user.jspageinfoexport.user.jschatgptmd.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Ensure compatibility with XBrowser (Android) by: sticking to documented Tampermonkey APIs, avoiding obscure/experimental features, keeping syntax ES2018-compatible, and using simple manual install paths
Applied to files:
pagemd.user.jspageinfoexport.user.jschatgptmd.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Consult and apply the ANDROID-XBROWSER-TESTING.md and ANDROID-XBROWSER-FIXES-TECHNICAL.md documents when designing or fixing scripts for XBrowser, especially for polling fallbacks, download functionality, and load order independence
Applied to files:
pagemd.user.jspageinfoexport.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Use a tiny compatibility wrapper (GMX pattern) to handle both GM4 async (`GM.getValue`) and legacy (`GM_getValue`) APIs, enabling cross-manager compatibility
Applied to files:
pagemd.user.jspageinfoexport.user.jschatgptmd.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Declare all GM APIs used in the `grant` metadata field, including legacy (`GM_getValue`) or async versions (`GM.getValue`), and avoid implicit grants
Applied to files:
pagemd.user.jspageinfoexport.user.jschatgptmd.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Include required metadata keys: `name`, `namespace` (set to `https://github.com/cbkii/userscripts`), `version` (datetime format `YYYY.MM.DD.HHMM`), `description`, `author`, `icon` (base64 SVG data URI with #FF1493 hot pink stroke), `match` or `include`, and `run-at`
Applied to files:
pagemd.user.jspageinfoexport.user.jschatgptmd.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Use recommended metadata fields: `license`, `noframes`, `homepageURL` (set to `https://github.com/cbkii/userscripts`), `supportURL` (set to `https://github.com/cbkii/userscripts/issues`), `updateURL`, and `downloadURL` (both pointing to raw GitHub URLs)
Applied to files:
pagemd.user.jspageinfoexport.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : If hosting scripts with updates, set both `updateURL` (metadata URL) and `downloadURL` (full script URL) to raw GitHub URLs; increment `version` for any functional change
Applied to files:
pageinfoexport.user.js
🧬 Code graph analysis (1)
chatgptmd.user.js (6)
pageinfoexport.user.js (8)
isMobileBrowser(306-307)DOWNLOAD_ANCHOR_DELAY_MS(302-302)MOBILE_FALLBACK_DELAY_MS(303-303)BLOB_STALE_MS(304-304)MAX_DATA_URL_FILE_SIZE_BYTES(61-61)blobToDataUrl(926-931)DEBUG(53-53)log(409-414)pageunlock.user.js (2)
DEBUG(59-59)log(61-61)searchduck.user.js (2)
DEBUG(70-70)log(500-505)antiadblock.user.js (2)
DEBUG(61-61)log(315-320)userscriptlogs.user.js (2)
DEBUG(47-47)log(275-280)searchgoogle.user.js (2)
DEBUG(64-64)log(451-456)
🔇 Additional comments (8)
pagemd.user.js (3)
452-456:isMobileBrowserUA sniffing is acceptable, but keep it strictly “mobile-only” logic. Lines 452-456.Given how broad
/Mobile/can be, it’s important that mobile-only behaviour stays gated behindisMobileBrowser(see download URL conversion below).
624-640: Fallback timer helpers are clean and single-fire. Lines 624-640.
641-654:confirm: falseis XBrowser-specific and unsupported in Tampermonkey/Violentmonkey. Lines 641–654.
Theconfirmoption is only documented in XBrowser's GM_download API. Tampermonkey and Violentmonkey do not support this option and will silently ignore it. The conditional application (isMobileBrowser ? { confirm: false } : {}) works correctly but should include a comment clarifying that this targets XBrowser compatibility only, not other mobile managers.pageinfoexport.user.js (2)
302-307: Mobile detection + fallback delay are consistent with the PR intent. Lines 302-307.
1044-1091: Mobile timeout flow is sensible; please validate no “timeout then success” duplicate on real devices. Lines 1044-1091.Given the low 1200ms threshold, please test at least one “slow storage / slow manager prompt” case to ensure the timeout doesn’t routinely trigger a second fallback download. Based on learnings, align the manual test pass with the ANDROID-XBROWSER-TESTING.md / ANDROID-XBROWSER-FIXES-TECHNICAL.md guidance.
chatgptmd.user.js (3)
73-80: Constants look coherent; good that the data-URL size limit is explicit. Lines 73-80.
513-514: Good: awaiting the download path avoids “UI says done” before the export attempt finishes. Lines 513-514.
1426-1431: Nice:blobToDataUrlhelper keeps the mobile conversion readable. Lines 1426-1431.
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Tampermonkey GM_download promise resolution timing onload onerror callback
💡 Result:
Short answer: GM_download's Promise (or the async GM.download in newer managers) resolves when the download finishes — i.e., at the same point the onload callback is invoked. The onerror callback rejects/handles failure. In practice you can treat the Promise as equivalent to wiring onload → resolve and onerror → reject. [1][2][3]
Notes / practical tips:
- Some userscript managers (Tampermonkey historically exposed callbacks; newer APIs/other managers expose an async Promise-returning GM.download) explicitly document an onload callback and that the Promise resolves with the download details (and gives you an abort function). Use the onload handler if you need the raw event object synchronously. [1][2]
- If you rely on event objects from the onload handler, copy needed fields inside the callback before returning/resolving — event objects/state may no longer be reliable after the async resolution. [4]
Sources:
- Tampermonkey GM_download docs (callbacks + return details/abort; Promise behavior documented where supported). [1]
- Violentmonkey/GM API notes showing GM.download is async (resolves). [2]
- Example wrappers/gists that implement Promise by resolving onload and rejecting onerror. [3]
- Discussion about event timing / copying event data in callbacks. [4]
References:
[1] Tampermonkey GM_download documentation.
[2] Violentmonkey Privileged APIs (GM.download async note).
[3] Example: GM_download wrapped as Promise (gist).
[4] Discussion about event timing when resolving promises (JS thread).
🏁 Script executed:
# Locate and examine the full fallback implementation context in pagemd.user.js
fd -t f "pagemd.user.js" | head -1 | xargs wc -lRepository: cbkii/userscripts
Length of output: 80
🏁 Script executed:
# Read the pagemd.user.js file to understand the fallback context
sed -n '600,750p' pagemd.user.js | cat -nRepository: cbkii/userscripts
Length of output: 5944
🏁 Script executed:
# Search for MOBILE_FALLBACK_DELAY_MS and fallback-related code
rg -n "MOBILE_FALLBACK_DELAY_MS|fallbackTriggered|triggerFallback" pagemd.user.js | head -30Repository: cbkii/userscripts
Length of output: 616
🏁 Script executed:
# Find where fallbackTriggered is initialized and how triggerFallback works
rg -B5 -A10 "const triggerFallback|function triggerFallback" pagemd.user.jsRepository: cbkii/userscripts
Length of output: 435
🏁 Script executed:
# Find downloadViaAnchor and downloadViaDataUrl to understand fallback mechanisms
rg -B5 -A15 "const downloadViaAnchor|const downloadViaDataUrl" pagemd.user.js | head -80Repository: cbkii/userscripts
Length of output: 1319
🏁 Script executed:
# Check what resource.markStale() and resource.cleanup() do
rg -B3 -A8 "resource\.markStale|resource\.cleanup|createDownloadResource" pagemd.user.js | head -100Repository: cbkii/userscripts
Length of output: 2737
🏁 Script executed:
# Find MOBILE_FALLBACK_DELAY_MS definition and understand the timeout choice
rg -B5 -A5 "MOBILE_FALLBACK_DELAY_MS|DOWNLOAD_ANCHOR_DELAY_MS|BLOB_REVOKE_MS" pagemd.user.jsRepository: cbkii/userscripts
Length of output: 3376
Race condition can trigger duplicate downloads on slow-but-successful mobile downloads — the 1200ms timeout may fire before GM_download's onload callback, causing both the primary download and fallback anchor/data-URL download to execute.
The fallbackTriggered flag prevents duplicate cleanup operations but does not prevent the fallback download itself from initiating. If GM_download succeeds but the onload callback fires after 1.2 seconds on a slow mobile connection, the timeout will have already called triggerFallback(), which invokes fallback() and initiates the anchor/data-URL download. The subsequent onload callback will then return early, leaving two separate downloads in flight.
Consider implementing bounded retry logic with network-aware backoff (per the polling fallback pattern) or increasing the timeout threshold on mobile networks to allow more time for successful downloads to complete before falling back.
Manual test: - On a mobile browser (Android/iOS), export Page → Markdown and confirm the download completes. - On a mobile browser, export Page Info and confirm the .txt download completes. - On a mobile browser, export a ChatGPT conversation and confirm the download completes.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @chatgptmd.user.js:
- Around line 1479-1492: The download options object named detail in
chatgptmd.user.js sets saveAs: true which is inconsistent with pagemd.user.js
and pageinfoexport.user.js that use saveAs: false; change the saveAs property in
the detail object (the constant named detail where url, name, saveAs, onload,
onerror are defined) from true to false so all three scripts share the same
download behavior, and run a quick manual test of the download flow (including
fallbackTrigger and resource.cleanup) after the change.
🧹 Nitpick comments (3)
pagemd.user.js (1)
594-622: Consider extracting a reusableblobToDataUrlhelper.The inline Promise-based FileReader logic here duplicates similar patterns in
pageinfoexport.user.js(line 926-931) andchatgptmd.user.js(line 1426-1437). While each script must be self-contained per coding guidelines, extracting this to a named helper function within this file would improve readability and maintainability.♻️ Optional refactor to extract helper
+ const blobToDataUrl = (blob) => new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => resolve(typeof reader.result === 'string' ? reader.result : ''); + reader.onerror = () => reject(new Error('Failed to read blob as data URL')); + reader.readAsDataURL(blob); + }); + const downloadViaGM = async (resource, filename, fallback) => { // ... let downloadUrl = resource.getUrl(); try { const blob = resource.getBlob(); if (blob.size < MAX_DATA_URL_FILE_SIZE_BYTES) { - const reader = new FileReader(); - const dataUrlPromise = new Promise((resolve, reject) => { - reader.onload = () => resolve(reader.result); - reader.onerror = () => reject(new Error('Failed to read blob')); - reader.readAsDataURL(blob); - }); try { - downloadUrl = await dataUrlPromise; + downloadUrl = await blobToDataUrl(blob); } catch (err) {pageinfoexport.user.js (1)
990-1014: Use the existingblobToDataUrlhelper here.This inline FileReader/Promise logic duplicates the
blobToDataUrlhelper defined at lines 926-931. Consider using the helper for consistency and reduced duplication.♻️ Proposed refactor to use existing helper
// Mobile compatibility: prefer data URL for better download manager support // Blob URLs may fail silently on some Android browsers let downloadUrl = resource.getUrl(); try { const blob = resource.getBlob(); // Only convert to data URL if size is reasonable (< 2MB for mobile compatibility) if (blob.size < MAX_DATA_URL_FILE_SIZE_BYTES) { - const reader = new FileReader(); - const dataUrlPromise = new Promise((resolve, reject) => { - reader.onload = () => resolve(reader.result); - reader.onerror = () => reject(new Error('Failed to read blob')); - reader.readAsDataURL(blob); - }); try { - downloadUrl = await dataUrlPromise; + downloadUrl = await blobToDataUrl(blob); } catch (err) { log('warn', 'Failed to convert blob to data URL, falling back to blob URL', { error: err?.message || String(err) }); // Fall back to blob URL if data URL conversion fails downloadUrl = resource.getUrl(); } } } catch (err) {chatgptmd.user.js (1)
73-79: Mobile constants are functionally consistent.The
MAX_DATA_URL_FILE_SIZE_BYTESis expressed as2 * 1024 * 1024here versus2097152in the other files. Both evaluate to the same value (2MB), but consider using a consistent expression across all files for easier maintenance.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
chatgptmd.user.jspageinfoexport.user.jspagemd.user.js
🧰 Additional context used
📓 Path-based instructions (1)
**/*.user.js
📄 CodeRabbit inference engine (AGENTS.md)
**/*.user.js: Userscript metadata block must be positioned at the very beginning of the file with exact syntax:// ==UserScript==and// ==/UserScript==on their own lines, each metadata line starting with//(exactly one space after//)
Include required metadata keys:@name,@namespace(set tohttps://github.com/cbkii/userscripts),@version(datetime formatYYYY.MM.DD.HHMM),@description,@author,@icon(base64 SVG data URI with #FF1493 hot pink stroke),@matchor@include, and@run-at
Each userscript must include a base64-encoded SVG data URI icon in the@iconmetadata field using a simple line-style icon with hot pink (#FF1493) stroke color, placed after@authorand before@match
Use@matchpatterns with the narrowest practical scope (least privilege principle), preferring Chrome-style match patterns like*://example.com/*over broad patterns like*://*/*
Declare all GM APIs used in the@grantmetadata field, including legacy (GM_getValue) or async versions (GM.getValue), and avoid implicit grants
For any cross-origin network requests usingGM_xmlhttpRequest, declare required domains in@connectmetadata entries; avoid@connect *unless truly necessary
Wrap script logic in a single top-level IIFE with'use strict'directive and provide a clearmain()entrypoint
Increment@versionfor every functional change using datetime formatYYYY.MM.DD.HHMM(UTC), ensuring versions never decrease
Ensure DOM updates are idempotent (safe to run multiple times) by checking if initialization has already occurred, marking injected elements, and avoiding duplicate listeners
For any@match *://*/*or similarly broad patterns, implement dormant-by-default behavior: register menu commands and UI shell on init, but only run heavy work when Always Run is enabled or user explicitly triggers via UI
Use consistent logging with a singlecreateLogger()helper per script, store logs under `GM_setValue('userscript.logs.<short...
Files:
pagemd.user.jspageinfoexport.user.jschatgptmd.user.js
🧠 Learnings (12)
📓 Common learnings
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Consult and apply the ANDROID-XBROWSER-TESTING.md and ANDROID-XBROWSER-FIXES-TECHNICAL.md documents when designing or fixing scripts for XBrowser, especially for polling fallbacks, download functionality, and load order independence
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Use the polling fallback pattern for XBrowser compatibility: when APIs fail (e.g., `GM_download`, `fetch`), fall back to polling-based alternatives with bounded retry counts and clear backoff strategies
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Ensure compatibility with XBrowser (Android) by: sticking to documented Tampermonkey APIs, avoiding obscure/experimental features, keeping syntax ES2018-compatible, and using simple manual install paths
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Include clear manual test steps in PR/commit messages or `docs/<script>.md` documenting: happy path (expected behavior), negative path (missing elements, excluded pages), SPA navigation, and XBrowser compatibility checks
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Prioritize Android XBrowser compatibility above all other considerations; test on XBrowser and ensure scripts work with its built-in script manager
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Use canonical CDN URLs for shared dependencies: jQuery from googleapis, jQuery UI from googleapis, Readability from jsDelivr, Turndown/GFM from unpkg (UMD builds, not CJS)
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Use recommended metadata fields: `license`, `noframes`, `homepageURL` (set to `https://github.com/cbkii/userscripts`), `supportURL` (set to `https://github.com/cbkii/userscripts/issues`), `updateURL`, and `downloadURL` (both pointing to raw GitHub URLs)
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Ensure compatibility with XBrowser (Android) by: sticking to documented Tampermonkey APIs, avoiding obscure/experimental features, keeping syntax ES2018-compatible, and using simple manual install paths
Applied to files:
pagemd.user.jspageinfoexport.user.jschatgptmd.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Use the polling fallback pattern for XBrowser compatibility: when APIs fail (e.g., `GM_download`, `fetch`), fall back to polling-based alternatives with bounded retry counts and clear backoff strategies
Applied to files:
pagemd.user.jspageinfoexport.user.jschatgptmd.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Consult and apply the ANDROID-XBROWSER-TESTING.md and ANDROID-XBROWSER-FIXES-TECHNICAL.md documents when designing or fixing scripts for XBrowser, especially for polling fallbacks, download functionality, and load order independence
Applied to files:
pagemd.user.jspageinfoexport.user.jschatgptmd.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : If hosting scripts with updates, set both `updateURL` (metadata URL) and `downloadURL` (full script URL) to raw GitHub URLs; increment `version` for any functional change
Applied to files:
pagemd.user.jspageinfoexport.user.jschatgptmd.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Increment `version` for every functional change using datetime format `YYYY.MM.DD.HHMM` (UTC), ensuring versions never decrease
Applied to files:
pagemd.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Use recommended metadata fields: `license`, `noframes`, `homepageURL` (set to `https://github.com/cbkii/userscripts`), `supportURL` (set to `https://github.com/cbkii/userscripts/issues`), `updateURL`, and `downloadURL` (both pointing to raw GitHub URLs)
Applied to files:
pagemd.user.jspageinfoexport.user.jschatgptmd.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Use a tiny compatibility wrapper (GMX pattern) to handle both GM4 async (`GM.getValue`) and legacy (`GM_getValue`) APIs, enabling cross-manager compatibility
Applied to files:
pagemd.user.jspageinfoexport.user.jschatgptmd.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Prioritize Android XBrowser compatibility above all other considerations; test on XBrowser and ensure scripts work with its built-in script manager
Applied to files:
pagemd.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Declare all GM APIs used in the `grant` metadata field, including legacy (`GM_getValue`) or async versions (`GM.getValue`), and avoid implicit grants
Applied to files:
pagemd.user.jspageinfoexport.user.jschatgptmd.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Implement race condition mitigation when 12+ scripts run simultaneously: stagger initialization with `setTimeout(0)`, use short polling intervals with exponential backoff, batch MutationObserver callbacks, and test thoroughly with all scripts installed
Applied to files:
pagemd.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Include required metadata keys: `name`, `namespace` (set to `https://github.com/cbkii/userscripts`), `version` (datetime format `YYYY.MM.DD.HHMM`), `description`, `author`, `icon` (base64 SVG data URI with #FF1493 hot pink stroke), `match` or `include`, and `run-at`
Applied to files:
pagemd.user.jspageinfoexport.user.jschatgptmd.user.js
🧬 Code graph analysis (2)
pageinfoexport.user.js (2)
chatgptmd.user.js (5)
MOBILE_FALLBACK_DELAY_MS(76-76)BLOB_STALE_MS(77-77)BLOB_REVOKE_MS(78-78)isMobileBrowser(73-74)DOWNLOAD_ANCHOR_DELAY_MS(75-75)pagemd.user.js (5)
MOBILE_FALLBACK_DELAY_MS(455-455)BLOB_STALE_MS(456-456)BLOB_REVOKE_MS(457-457)isMobileBrowser(452-453)DOWNLOAD_ANCHOR_DELAY_MS(454-454)
chatgptmd.user.js (2)
pageinfoexport.user.js (6)
isMobileBrowser(306-307)DOWNLOAD_ANCHOR_DELAY_MS(302-302)MOBILE_FALLBACK_DELAY_MS(303-303)BLOB_STALE_MS(304-304)MAX_DATA_URL_FILE_SIZE_BYTES(61-61)blobToDataUrl(926-931)pagemd.user.js (5)
isMobileBrowser(452-453)DOWNLOAD_ANCHOR_DELAY_MS(454-454)MOBILE_FALLBACK_DELAY_MS(455-455)BLOB_STALE_MS(456-456)MAX_DATA_URL_FILE_SIZE_BYTES(64-64)
🔇 Additional comments (14)
pagemd.user.js (4)
4-4: Version bump looks good.The version format follows the required
YYYY.MM.DD.HHMMdatetime format and has been appropriately incremented for this functional change.
452-457: Mobile detection and fallback constants are well-aligned.The broader mobile detection regex (
/Android|iPhone|iPad|iPod|Mobile/i) and theMOBILE_FALLBACK_DELAY_MSconstant are consistent with the other files in this PR. This appropriately extends XBrowser-specific handling to all mobile browsers.
624-688: Mobile fallback coordination logic looks sound.The idempotent
triggerFallbackpattern withfallbackTriggeredguard prevents double-fallback execution. The conditional promise handling (awaiting on desktop, timeout-based on mobile) correctly addresses the PR objective of avoiding silent hangs whenGM_downloadcallbacks are unreliable on mobile. Based on learnings, this polling fallback pattern aligns with XBrowser compatibility guidance.
641-654: Detail object correctly configured for mobile compatibility.Setting
saveAs: falseand conditionally addingconfirm: falseon mobile helps avoid dialog prompts that may cause issues on mobile download managers. Theonloadandonerrorcallbacks are properly wired to the fallback coordination.pageinfoexport.user.js (5)
6-6: Version bump consistent with other files.Version
2026.01.10.0842matches the other scripts in this PR.
303-308: Mobile detection constants consistent across the PR.The
MOBILE_FALLBACK_DELAY_MSandisMobileBrowserdetection are identical to the other files, ensuring consistent behaviour across all three scripts.
926-931: CleanblobToDataUrlhelper implementation.This helper is well-structured with proper error handling. It's correctly extracted as a reusable function within this script.
968-988: Dual-promise coordination pattern for mobile fallback.The pattern using both
resolveLegacyandresolveMobileFallbackpromises allows the function to coordinate completion across different callback paths. ThehandleErrorfunction correctly signals both completion handlers.One observation:
resolveMobileFallbackis set tonullafter being called, and subsequent checks rely on truthiness. This works correctly but could be slightly clearer with explicit null guards.
1054-1094: Mobile fallback timeout and promise coordination looks correct.The timeout-based fallback (line 1055-1063) with subsequent promise handling correctly implements the mobile fallback pattern. The conditional awaiting (non-mobile awaits the promise, mobile relies on timeout) aligns with the PR objectives.
Minor note:
clearFallbackTimer()at line 1094 is slightly redundant as the timer should already be cleared by earlier paths, but it's harmless defensive code.chatgptmd.user.js (5)
4-4: Version bump consistent.Version
2026.01.10.0842matches the other scripts in this PR.
512-514: Export action correctly updated to usemobileDownload.The download action now awaits
mobileDownload, which handles both mobile and desktop download paths with appropriate fallbacks.
1426-1437: RobustblobToDataUrlhelper with additional validation.This implementation includes extra validation (
typeof reader.result !== 'string' || !reader.result) before resolving, which is more defensive than the other files. This is good practice for handling edge cases.
1464-1518: Mobile fallback timer coordination follows established pattern.The
fallbackTimerId,fallbackTriggered,clearFallbackTimer, andtriggerFallbackpattern is consistent with the other files. The conditional promise awaiting correctly handles mobile vs desktop paths.
1522-1531: Error handling with user feedback is good.The catch block properly marks the resource as stale, triggers the fallback, logs the error, and provides user feedback via
alert(). This gives users actionable information when downloads fail.
Manual test: - Unable to run in this environment (no mobile browser UI).
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
chatgptmd.user.js (1)
1-22: Add missing metadata fields and declare all GM APIs used.The metadata block is missing
@licenseand@sandboxfields required by repository guidelines. Additionally, the script actively checks for and usesGM.download(async GM4 API, lines 70–71) but only declares@grant GM_downloadin the metadata; you must add@grant GM.download.The script also references
unsafeWindow(lines 101, 131) to access the shared UI helper across sandbox boundaries. If you declare@sandbox DOM,unsafeWindowwill be inaccessible, creating a conflict. You must either:
- Declare
@sandbox JavaScriptor@sandbox rawinstead of@sandbox DOM, or- Remove the
unsafeWindowfallback path for the UI helperProposed metadata additions
// ==UserScript== // @name ChatGPT Exporter for Android (md/txt/json) // @namespace https://github.com/cbkii/userscripts // @version 2026.01.10.0850 // @description Export ChatGPT conversations to Markdown, JSON, or text with download, copy, and share actions. UI integrated with shared userscript panel. // @author cbcoz +// @license MIT // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjRkYxNDkzIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHBhdGggZD0iTTIxIDE1djRhMiAyIDAgMCAxLTIgMkg1YTIgMiAwIDAgMS0yLTJ2LTQiLz48cG9seWxpbmUgcG9pbnRzPSI3IDEwIDEyIDE1IDE3IDEwIi8+PGxpbmUgeDE9IjEyIiB5MT0iMTUiIHgyPSIxMiIgeTI9IjMiLz48L3N2Zz4= // @match *://chat.openai.com/* // @match *://chatgpt.com/* // @updateURL https://raw.githubusercontent.com/cbkii/userscripts/main/chatgptmd.user.js // @downloadURL https://raw.githubusercontent.com/cbkii/userscripts/main/chatgptmd.user.js // @homepageURL https://github.com/cbkii/userscripts // @supportURL https://github.com/cbkii/userscripts/issues // @run-at document-idle +// @sandbox JavaScript // @noframes // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_setClipboard // @grant GM_getValue // @grant GM_setValue // @grant GM_download +// @grant GM.download // ==/UserScript==
🤖 Fix all issues with AI agents
In @chatgptmd.user.js:
- Around line 73-80: The size cap constant MAX_DATA_URL_FILE_SIZE_BYTES should
be used inclusively to avoid an off-by-one rejection at exactly 2 MiB; find the
data-URL/file-size check that currently uses a strict < comparison against
MAX_DATA_URL_FILE_SIZE_BYTES and change it to <= MAX_DATA_URL_FILE_SIZE_BYTES
(update any related conditional branches or early returns that depend on that
comparison).
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
chatgptmd.user.js
🧰 Additional context used
📓 Path-based instructions (1)
**/*.user.js
📄 CodeRabbit inference engine (AGENTS.md)
**/*.user.js: Userscript metadata block must be positioned at the very beginning of the file with exact syntax:// ==UserScript==and// ==/UserScript==on their own lines, each metadata line starting with//(exactly one space after//)
Include required metadata keys:@name,@namespace(set tohttps://github.com/cbkii/userscripts),@version(datetime formatYYYY.MM.DD.HHMM),@description,@author,@icon(base64 SVG data URI with #FF1493 hot pink stroke),@matchor@include, and@run-at
Each userscript must include a base64-encoded SVG data URI icon in the@iconmetadata field using a simple line-style icon with hot pink (#FF1493) stroke color, placed after@authorand before@match
Use@matchpatterns with the narrowest practical scope (least privilege principle), preferring Chrome-style match patterns like*://example.com/*over broad patterns like*://*/*
Declare all GM APIs used in the@grantmetadata field, including legacy (GM_getValue) or async versions (GM.getValue), and avoid implicit grants
For any cross-origin network requests usingGM_xmlhttpRequest, declare required domains in@connectmetadata entries; avoid@connect *unless truly necessary
Wrap script logic in a single top-level IIFE with'use strict'directive and provide a clearmain()entrypoint
Increment@versionfor every functional change using datetime formatYYYY.MM.DD.HHMM(UTC), ensuring versions never decrease
Ensure DOM updates are idempotent (safe to run multiple times) by checking if initialization has already occurred, marking injected elements, and avoiding duplicate listeners
For any@match *://*/*or similarly broad patterns, implement dormant-by-default behavior: register menu commands and UI shell on init, but only run heavy work when Always Run is enabled or user explicitly triggers via UI
Use consistent logging with a singlecreateLogger()helper per script, store logs under `GM_setValue('userscript.logs.<short...
Files:
chatgptmd.user.js
🧠 Learnings (10)
📓 Common learnings
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Use the polling fallback pattern for XBrowser compatibility: when APIs fail (e.g., `GM_download`, `fetch`), fall back to polling-based alternatives with bounded retry counts and clear backoff strategies
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Consult and apply the ANDROID-XBROWSER-TESTING.md and ANDROID-XBROWSER-FIXES-TECHNICAL.md documents when designing or fixing scripts for XBrowser, especially for polling fallbacks, download functionality, and load order independence
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Ensure compatibility with XBrowser (Android) by: sticking to documented Tampermonkey APIs, avoiding obscure/experimental features, keeping syntax ES2018-compatible, and using simple manual install paths
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Prioritize Android XBrowser compatibility above all other considerations; test on XBrowser and ensure scripts work with its built-in script manager
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Use canonical CDN URLs for shared dependencies: jQuery from googleapis, jQuery UI from googleapis, Readability from jsDelivr, Turndown/GFM from unpkg (UMD builds, not CJS)
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Use recommended metadata fields: `license`, `noframes`, `homepageURL` (set to `https://github.com/cbkii/userscripts`), `supportURL` (set to `https://github.com/cbkii/userscripts/issues`), `updateURL`, and `downloadURL` (both pointing to raw GitHub URLs)
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : If hosting scripts with updates, set both `updateURL` (metadata URL) and `downloadURL` (full script URL) to raw GitHub URLs; increment `version` for any functional change
Applied to files:
chatgptmd.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Ensure compatibility with XBrowser (Android) by: sticking to documented Tampermonkey APIs, avoiding obscure/experimental features, keeping syntax ES2018-compatible, and using simple manual install paths
Applied to files:
chatgptmd.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Use the polling fallback pattern for XBrowser compatibility: when APIs fail (e.g., `GM_download`, `fetch`), fall back to polling-based alternatives with bounded retry counts and clear backoff strategies
Applied to files:
chatgptmd.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Use recommended metadata fields: `license`, `noframes`, `homepageURL` (set to `https://github.com/cbkii/userscripts`), `supportURL` (set to `https://github.com/cbkii/userscripts/issues`), `updateURL`, and `downloadURL` (both pointing to raw GitHub URLs)
Applied to files:
chatgptmd.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Declare all GM APIs used in the `grant` metadata field, including legacy (`GM_getValue`) or async versions (`GM.getValue`), and avoid implicit grants
Applied to files:
chatgptmd.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Use English-only comments and user-facing strings throughout the script
Applied to files:
chatgptmd.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Include clear manual test steps in PR/commit messages or `docs/<script>.md` documenting: happy path (expected behavior), negative path (missing elements, excluded pages), SPA navigation, and XBrowser compatibility checks
Applied to files:
chatgptmd.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Include required metadata keys: `name`, `namespace` (set to `https://github.com/cbkii/userscripts`), `version` (datetime format `YYYY.MM.DD.HHMM`), `description`, `author`, `icon` (base64 SVG data URI with #FF1493 hot pink stroke), `match` or `include`, and `run-at`
Applied to files:
chatgptmd.user.js
📚 Learning: 2026-01-05T10:24:18.701Z
Learnt from: CR
Repo: cbkii/userscripts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-05T10:24:18.701Z
Learning: Applies to **/*.user.js : Use a tiny compatibility wrapper (GMX pattern) to handle both GM4 async (`GM.getValue`) and legacy (`GM_getValue`) APIs, enabling cross-manager compatibility
Applied to files:
chatgptmd.user.js
🧬 Code graph analysis (1)
chatgptmd.user.js (1)
pagemd.user.js (6)
isMobileBrowser(452-453)DOWNLOAD_ANCHOR_DELAY_MS(454-454)MOBILE_FALLBACK_DELAY_MS(455-455)BLOB_STALE_MS(456-456)BLOB_REVOKE_MS(457-457)MAX_DATA_URL_FILE_SIZE_BYTES(64-64)
🔇 Additional comments (2)
chatgptmd.user.js (2)
513-514: AwaitingmobileDownload(...)is a good control-flow clean-up.It makes the download action sequencing more predictable (especially now that mobile paths may do async blob→data URL work).
1426-1437:blobToDataUrlhelper is fine; keep it ES2018-safe for XBrowser.This uses
FileReader, which is broadly compatible; just ensure you don’t rely on newer syntax/features around it on the mobile target browsers. Based on learnings, prioritise XBrowser compatibility.
Manual test: - Unable to run in this environment (no mobile browser UI).

User description
Motivation
blob:URLs orGM_downloadcallbacks/promises may not behave reliably.Description
isXBrowserwithisMobileBrowserand use a broader user-agent check/Android|iPhone|iPad|iPod|Mobile/iacrosspagemd.user.js,pageinfoexport.user.js, andchatgptmd.user.js, and bumped script versions.XBROWSER_FALLBACK_DELAY_MStoMOBILE_FALLBACK_DELAY_MSand added mobile-only fallback timers aroundGM_downloadthat mark blobs stale and trigger the existing fallback flow when the timer elapses.MAX_DATA_URL_FILE_SIZE_BYTESthreshold) and setconfirm: falseinGM_downloaddetails to improve compatibility with mobile download managers.awaitdownload promises but mobile flows rely on the timeout/fallback path, and added ablobToDataUrlhelper inchatgptmd.Testing
Codex Task
PR Type
Enhancement, Bug fix
Description
Replace XBrowser-specific checks with broader mobile browser detection
Add mobile-specific download timeout fallback to prevent hangs
Prefer data URL conversion for small blobs on mobile devices
Set
confirm: falsein GM_download for improved mobile compatibilityAdjust promise handling to use timeout-based fallback on mobile
Diagram Walkthrough
File Walkthrough
chatgptmd.user.js
Add mobile download timeout fallback with data URL conversionchatgptmd.user.js
isMobileBrowserdetection using user-agent regexMOBILE_FALLBACK_DELAY_MSconstant andMAX_DATA_URL_FILE_SIZE_BYTESthresholdblobToDataUrlhelper function for async blob-to-data-URLconversion
mobileDownloadto be async and implement timeout-basedfallback logic
confirm: false, and use timeout fallback instead of awaiting promisespageinfoexport.user.js
Implement mobile timeout fallback in GM_download flowpageinfoexport.user.js
isMobileBrowserdetection using user-agent regexMOBILE_FALLBACK_DELAY_MSconstant for timeout-basedfallback
saveWithGMDownloadto implement mobile-specific timeoutfallback with promise handling
mobileFallbackpromise that resolves after timeout on mobiledevices
confirm: falsein download details for mobile browsersfallback instead
pagemd.user.js
Add mobile timeout fallback to GM_download handlerpagemd.user.js
isMobileBrowserdetection using user-agent regexMOBILE_FALLBACK_DELAY_MSconstant for timeout-basedfallback
downloadViaGMto implement mobile-specific timeout fallbacklogic
clearFallbackTimerhelper and timeout-based fallback on mobiledevices
confirm: falsein download details for mobile browsersfallback instead
Summary by CodeRabbit
New Features
Bug Fixes
Chores
✏️ Tip: You can customize this high-level summary in your review settings.