Skip to content

feat: support multi-server fallback in the userscript - #106

Merged
D3SOX merged 3 commits into
masterfrom
userscript-server-fallback
Aug 12, 2026
Merged

feat: support multi-server fallback in the userscript#106
D3SOX merged 3 commits into
masterfrom
userscript-server-fallback

Conversation

@D3SOX

@D3SOX D3SOX commented Aug 12, 2026

Copy link
Copy Markdown
Owner

Summary

Feed position checkpoints were always updating in the background, and the userscript could only point at a single Web UI address — awkward when switching between LAN and Tailscale.

  • Only update feed position checkpoints while the Feed position modal is open
  • Allow configuring multiple Web UI server addresses (one per line)
  • Before opening a download, probe addresses in order and use the first reachable one
  • Document the multi-server setup in the README

Test plan

  • Open Feed position controls…, play feed tracks, confirm checkpoints update; close the modal and confirm they stop advancing
  • Configure two servers (LAN first, Tailscale second); with LAN up, downloads open via LAN
  • Stop/block the LAN address; confirm fallback to the second server
  • With both unreachable, confirm an alert lists the tried addresses
  • Empty the server field and save; confirm reset to http://localhost:4321
  • bun test userscript/sc-gate-dl.test.ts

Made with Cursor (Composer)

Summary by CodeRabbit

  • New Features
    • Configure multiple Web UI server addresses, tried in order until a reachable server is found.
    • Added server configuration dialogs with validation, persistence, and clear connection errors.
    • Downloads and queue actions now wait for a reachable server before opening.
    • Failed queue items can be restored after connection issues.
  • Bug Fixes
    • Feed checkpoints and playback tracking are recorded only when the feed navigator is open.
    • Opening the navigator immediately captures the current playback state.
  • Documentation
    • Updated configuration examples to show multiple server addresses.

Allow configuring several Web UI addresses and probe them in order before
opening a download, and only update feed position checkpoints while that
modal is open.
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@D3SOX, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 28 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 11c5fab4-9b16-4478-b229-574acfade6a8

📥 Commits

Reviewing files that changed from the base of the PR and between 3ce15bd and 9105048.

📒 Files selected for processing (1)
  • userscript/sc-gate-dl.user.js
📝 Walkthrough

Walkthrough

The userscript now supports ordered, deduplicated Web UI server addresses with reachability fallback. Download and queue flows resolve servers asynchronously. Feed checkpoint and playback recording require an open feed navigator.

Changes

Userscript behavior

Layer / File(s) Summary
Navigator-gated checkpoint recording
userscript/sc-gate-dl.user.js
Feed checkpoint and playback recording require an open feed navigator. Opening the navigator records the current playback state.
Multi-server configuration and validation
userscript/sc-gate-dl.user.js, userscript/sc-gate-dl.test.ts, README.md
The userscript parses, deduplicates, stores, displays, and probes ordered Web UI addresses. Tests cover parsing, fallback, invalid input, API-origin mapping, and configuration wiring.
Download and queue server resolution
userscript/sc-gate-dl.user.js
Download and queue flows resolve a reachable server before opening a panel or tab. Failed queue items are restored, and API fallback origin calculation uses the resolved Web UI base.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant openDownload
  participant resolveWebuiBase
  participant WebUI
  User->>openDownload: request download
  openDownload->>resolveWebuiBase: resolve configured server
  resolveWebuiBase->>WebUI: probe servers in order
  WebUI-->>resolveWebuiBase: return reachable response or failure
  resolveWebuiBase-->>openDownload: return resolved base or error
  openDownload-->>User: open panel/tab or show error
Loading

Possibly related PRs

  • D3SOX/sc-gate-dl#89: Introduces the download and queue flows extended by asynchronous multi-server resolution.
  • D3SOX/sc-gate-dl#91: Modifies the feed tracking and playback checkpoint logic extended by navigator gating.
  • D3SOX/sc-gate-dl#96: Modifies the feed playback and checkpoint tracking logic extended by this userscript update.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: multi-server fallback support in the userscript.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch userscript-server-fallback

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 (1)
userscript/sc-gate-dl.user.js (1)

2481-2501: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Queued downloads are lost when no configured server is reachable. openDownload became asynchronous and can fail after resolveWebuiBase rejects, but it returns undefined in every path. Both queue callers remove the item from downloadQueue before they invoke it, and neither awaits the result, so a resolution failure drops the item with only an alert.

  • userscript/sc-gate-dl.user.js#L2481-L2501: return false on the resolution failure path and true on each success path, so callers can detect failure. Also move the isPanelBusy() check so the panel state is not read across the await window.
  • userscript/sc-gate-dl.user.js#L2393-L2402: await or chain the openDownload result. If it returns false, unshift the item back onto downloadQueue and call renderQueue().
  • userscript/sc-gate-dl.user.js#L2404-L2417: await or chain the openDownload result. If it returns false, re-insert the spliced item at its original index and call renderQueue().
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@userscript/sc-gate-dl.user.js` around lines 2481 - 2501, Update
userscript/sc-gate-dl.user.js:2481-2501 in openDownload to check isPanelBusy()
before awaiting resolveWebuiBase, return false when resolution fails, and return
true from every successful path. At userscript/sc-gate-dl.user.js:2393-2402,
await or chain openDownload and unshift the item back into downloadQueue
followed by renderQueue() when it returns false. At
userscript/sc-gate-dl.user.js:2404-2417, similarly handle false by reinserting
the spliced item at its original index and calling renderQueue().
🧹 Nitpick comments (2)
userscript/sc-gate-dl.user.js (2)

1231-1237: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add Escape-key dismissal to the server dialog.

The dialog sets role="dialog" and receives focus on the textarea. A keyboard user can only close it with the Cancel button, because click-outside is the only other exit. The panel at Line 2639 already handles Escape. Add the same handling here for consistency.

♻️ Proposed addition
 		const close = () => dialog.remove();
+		const onKeydown = (event) => {
+			if (event.key === 'Escape') close();
+		};
+		dialog.addEventListener('keydown', onKeydown);
 		dialog.addEventListener('click', (event) => {
 			if (event.target === dialog) close();
 		});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@userscript/sc-gate-dl.user.js` around lines 1231 - 1237, Update the server
dialog setup around the close function and existing click handlers to listen for
keydown events and call close when the pressed key is Escape, matching the
Escape-dismissal behavior used by the panel at line 2639.

1074-1090: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Align read precedence with write precedence.

persistWebuiBases writes GM_setValue first, then localStorage. readStoredWebuiRaw reads localStorage first, then GM_getValue. If the localStorage write fails while the GM_setValue write succeeds, the stale localStorage value wins on the next read, and the saved server list is silently ignored. getApiBase (Line 2233) already reads GM_getValue first.

Prefer the GM store on read for consistency.

♻️ Proposed read-order change
 	function readStoredWebuiRaw() {
-		try {
-			const stored = localStorage.getItem(WEBUI_BASE_KEY);
-			if (stored?.trim()) return stored;
-		} catch {
-			// ignore
-		}
 		try {
 			if (typeof GM_getValue === 'function') {
 				const stored = GM_getValue(WEBUI_BASE_KEY, null);
 				if (typeof stored === 'string' && stored.trim()) return stored;
 			}
 		} catch {
 			// ignore
 		}
+		try {
+			const stored = localStorage.getItem(WEBUI_BASE_KEY);
+			if (stored?.trim()) return stored;
+		} catch {
+			// ignore
+		}
 		return null;
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@userscript/sc-gate-dl.user.js` around lines 1074 - 1090, Update
readStoredWebuiRaw to check GM_getValue before localStorage, matching
persistWebuiBases and getApiBase precedence. Preserve the existing validation,
fallback behavior, and error handling while returning the first non-empty stored
value.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@userscript/sc-gate-dl.user.js`:
- Around line 1171-1209: Reduce download-path probe latency in resolveWebuiBase
by caching a recently successful activeWebuiBase for a short TTL and trying it
first before probing other bases. Add transient user-visible status around the
await in openDownload so probing does not appear stalled. Keep
isWebuiReachable’s API validation authoritative where possible, and do not treat
a no-cors response alone as proof that the responder is a sc-gate-dl Web UI.
- Around line 1240-1271: Update the save click handler to await the asynchronous
resolveWebuiBase/loadTrackIntoPanel flow before displaying the success
window.alert, and only report the configured servers after resolution succeeds.
Preserve the existing failure alert for rejected resolution and avoid showing
the success alert when resolution fails.
- Line 1178: Update the request configuration using AbortSignal.timeout to
support browsers lacking this API by adding a compatible timeout fallback, or
document and enforce the minimum required browser versions. Preserve the
existing WEBUI_REACHABILITY_TIMEOUT_MS timeout behavior.

---

Outside diff comments:
In `@userscript/sc-gate-dl.user.js`:
- Around line 2481-2501: Update userscript/sc-gate-dl.user.js:2481-2501 in
openDownload to check isPanelBusy() before awaiting resolveWebuiBase, return
false when resolution fails, and return true from every successful path. At
userscript/sc-gate-dl.user.js:2393-2402, await or chain openDownload and unshift
the item back into downloadQueue followed by renderQueue() when it returns
false. At userscript/sc-gate-dl.user.js:2404-2417, similarly handle false by
reinserting the spliced item at its original index and calling renderQueue().

---

Nitpick comments:
In `@userscript/sc-gate-dl.user.js`:
- Around line 1231-1237: Update the server dialog setup around the close
function and existing click handlers to listen for keydown events and call close
when the pressed key is Escape, matching the Escape-dismissal behavior used by
the panel at line 2639.
- Around line 1074-1090: Update readStoredWebuiRaw to check GM_getValue before
localStorage, matching persistWebuiBases and getApiBase precedence. Preserve the
existing validation, fallback behavior, and error handling while returning the
first non-empty stored value.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3134ac52-d9a7-4ae1-b875-7c6f0fc4c62b

📥 Commits

Reviewing files that changed from the base of the PR and between 4c69ca5 and 50b9b59.

📒 Files selected for processing (3)
  • README.md
  • userscript/sc-gate-dl.test.ts
  • userscript/sc-gate-dl.user.js

Comment thread userscript/sc-gate-dl.user.js
Comment thread userscript/sc-gate-dl.user.js Outdated
Comment thread userscript/sc-gate-dl.user.js
Cache the last reachable server briefly, polyfill AbortSignal.timeout,
avoid stacked configure alerts, and put failed queue items back.
@D3SOX

D3SOX commented Aug 12, 2026

Copy link
Copy Markdown
Owner Author

Composer responding on behalf of Nico

Also addressed the outside-diff queue note from CodeRabbit in 3ce15bd: openDownload now returns success/failure, and queue callers put the item back if server resolution fails.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
userscript/sc-gate-dl.user.js (1)

2518-2531: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve user activation before server resolution.

Line 2520 awaits network I/O before Line 2530 calls window.open(). Browsers can clear transient user activation during this wait. The getAlwaysOpenTab() flow can then fail to open a tab after a user click.

Open a placeholder tab synchronously before the first await, then navigate it after resolution succeeds. If the browser denies the placeholder tab, return false and show a failure message. This also lets Lines 2428-2432 and Lines 2449-2453 restore queued items when an automatic tab open is blocked.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@userscript/sc-gate-dl.user.js` around lines 2518 - 2531, Update openDownload
so the getAlwaysOpenTab flow opens a placeholder tab synchronously before the
first await, retaining its window reference. If the placeholder is blocked, show
a failure alert and return false; after resolveWebuiBase succeeds, navigate the
placeholder to buildWebuiSrc(trackUrl), and close it on resolution failure
before returning false. Preserve the existing non-tab flow and return behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@userscript/sc-gate-dl.user.js`:
- Around line 2518-2531: Update openDownload so the getAlwaysOpenTab flow opens
a placeholder tab synchronously before the first await, retaining its window
reference. If the placeholder is blocked, show a failure alert and return false;
after resolveWebuiBase succeeds, navigate the placeholder to
buildWebuiSrc(trackUrl), and close it on resolution failure before returning
false. Preserve the existing non-tab flow and return behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 471ab766-b9e5-4a53-9eab-e88fc1c1f418

📥 Commits

Reviewing files that changed from the base of the PR and between 50b9b59 and 3ce15bd.

📒 Files selected for processing (2)
  • userscript/sc-gate-dl.test.ts
  • userscript/sc-gate-dl.user.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • userscript/sc-gate-dl.test.ts

Open a placeholder tab before probing servers so browsers do not drop
user activation during the await.
@D3SOX

D3SOX commented Aug 12, 2026

Copy link
Copy Markdown
Owner Author

Composer responding on behalf of Nico

Addressed the follow-up always-open-tab note in the latest commit: on a cache miss we open about:blank synchronously before probing, then navigate (or close) that tab after resolution. Cache hits still open with noopener immediately.

@D3SOX
D3SOX merged commit 3ab022a into master Aug 12, 2026
2 checks passed
@D3SOX
D3SOX deleted the userscript-server-fallback branch August 12, 2026 05:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant