Skip to content

fix(mirror): handle path-based template in test button (jsDelivr 400)#725

Merged
rainxchzed merged 4 commits into
mainfrom
fix/mirror-test-path-template
Jun 6, 2026
Merged

fix(mirror): handle path-based template in test button (jsDelivr 400)#725
rainxchzed merged 4 commits into
mainfrom
fix/mirror-test-path-template

Conversation

@rainxchzed
Copy link
Copy Markdown
Member

@rainxchzed rainxchzed commented Jun 6, 2026

Follow-up to #698 / OpenHub-Store/backend#25.

Symptom

`fastly.jsdelivr.net` shows status dot ok / 42ms, but tapping Test mirror returns "Mirror returned 400".

Root cause

`MirrorPickerViewModel.runTest()` does:

```kotlin
template.replace("{url}", probeUrl)
```

Correct for whole-URL proxies like `ghfast.top/{url}`. But jsDelivr's template is

```
https://fastly.jsdelivr.net/gh/{owner}/{repo}@{ref}/{path}
```

— no `{url}` placeholder. The replace is a no-op and the literal `{owner}`/`{repo}`/etc. braces hit jsDelivr as the request path, which returns 400.

Backend hourly probe (`MirrorStatusWorker.PROBE_ASSET`) gets this right via a hardcoded `cli/cli@v2.40.0/LICENSE` URL — which is why the status dot reports OK while the in-app test fails.

Fix

`runTest` now branches on which placeholder shape the template uses:

  • Contains `{url}` → substitute the whole GitHub URL (existing behaviour).
  • Otherwise → substitute `{owner}=cli`, `{repo}=cli`, `{ref}=v2.40.0`, `{path}=LICENSE` (same path the backend probe uses).

Summary by CodeRabbit

  • Bug Fixes

    • Improved mirror connection testing to handle both whole-URL and path-based templates for broader compatibility.
    • Standardized connection timeout handling for more reliable test behavior.
  • New Features

    • Added offline mirror fallback that selects mirrors by traffic type.
    • Bundled an additional mirror to improve availability and performance.

…fix)

User report (issue #698 follow-up): fastly.jsdelivr.net status dot shows
ok / 42ms but tapping the 'Test mirror' button returns 'Mirror returned
400'.

Root cause: the test code does template.replace("{url}", probeUrl), which
is correct for whole-URL proxies like ghfast.top/{url}. But jsDelivr's
template is https://fastly.jsdelivr.net/gh/{owner}/{repo}@{ref}/{path} —
no {url} placeholder. The replace is a no-op and the literal braces hit
jsDelivr, which returns 400 for the invalid path.

Backend probe gets this right via a hardcoded cli/cli@v2.40.0/LICENSE
URL. This patch teaches the client test to do the same substitution
(cli/cli/v2.40.0/LICENSE) when the template lacks {url} so both shapes
of mirror template are exercised correctly.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 6, 2026

Looking for one thing? Review this PR in Change Stack to search files, summaries, diffs, and code without losing your place.

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f7825865-6c2a-45db-8757-e805360108c3

📥 Commits

Reviewing files that changed from the base of the PR and between 4a9b7f2 and e8d614f.

📒 Files selected for processing (2)
  • core/data/src/commonMain/kotlin/zed/rainxch/core/data/mirror/MirrorRepositoryImpl.kt
  • feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/mirror/MirrorPickerViewModel.kt

Walkthrough

MirrorPickerViewModel.runTest() now builds test probe URLs from whole-URL ({url}) or decomposed ({owner}/{repo}/{ref}/{path}) templates with a wholeUrlProbe fallback and uses 5_000L.milliseconds for timeout. BundledMirrors records TrafficKind sets per mirror and adds a fastly_jsdelivr raw-file-only entry; MirrorRepositoryImpl drops the private val on appScope and has formatting-only adjustments.

Changes

Mirror URL Test Probe Dual Template Support

Layer / File(s) Summary
Dual template shape URL substitution in test probe
feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/mirror/MirrorPickerViewModel.kt
wholeUrlProbe constant introduced; targetUrl computation now: null template → wholeUrlProbe, template containing {url} → replace with wholeUrlProbe, template containing {owner} → replace {owner}, {repo}, {ref}, {path}, otherwise fallback to wholeUrlProbe. Timeout uses 5_000L.milliseconds. Minor related formatting/imports updated.

Bundled Mirrors TrafficKind Metadata & MirrorRepository Formatting

Layer / File(s) Summary
TrafficKind sets and mirror entry wiring
core/data/src/commonMain/kotlin/zed/rainxch/core/data/mirror/BundledMirrors.kt
Adds TrafficKind support, defines FULL_PROXY_KINDS and RAW_FILE_ONLY, extends entry(...) to accept trafficKinds (defaulting to full proxy), wires MirrorConfig.trafficKinds, and adds fastly_jsdelivr configured for raw-file-only traffic.
MirrorRepositoryImpl constructor modifier and formatting
core/data/src/commonMain/kotlin/zed/rainxch/core/data/mirror/MirrorRepositoryImpl.kt
Removes private val from appScope constructor parameter (remains used in init) and applies formatting-only reflows to migration entries, cached JSON safePut/safeGet, and preference mapping/setPreference code paths without changing behavior.

Sequence Diagram(s)

sequenceDiagram
  participant ViewModel as MirrorPickerViewModel
  participant Template as MirrorTemplate
  participant Network as NetworkClient
  ViewModel->>Template: determine template shape (null / contains {url} / contains {owner})
  ViewModel->>ViewModel: build targetUrl (use wholeUrlProbe or substitute placeholders)
  ViewModel->>Network: send probe request to targetUrl (timeout 5_000L.milliseconds)
  Network-->>ViewModel: response / HTTP error / DNS failure / other error
Loading

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs:

  • OpenHub-Store/GitHub-Store#600: Also updates mirror/proxy URL generation to replace decomposed placeholders ({owner}/{repo}@{ref}/{path}) instead of only {url}.

"I nibble code and swap the clues,
{url} or pieces — I choose.
I tag traffic kinds with care,
Fastly hops in to share.
A tiny rabbit small, I say: tests pass on their way."

🚥 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 specifically describes the main fix: handling path-based templates in the mirror test button with a concrete reference to the jsDelivr 400 error issue.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/mirror-test-path-template

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.

❤️ Share

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

BundledMirrors is the catalog served when the backend's /v1/mirrors/list
is unreachable. Backend MirrorPresets.ALL lists fastly_jsdelivr but the
client bundle was never updated to match, so users running offline (or
with a misconfigured network) saw a strictly smaller picker than users
online — and lost access to jsDelivr entirely.

Also extends the entry() helper with a trafficKinds parameter so this
raw-file-only mirror is tagged correctly (release tarballs aren't served
under jsDelivr's /gh/ path). Defaults to release_asset + raw_file for
the whole-URL-proxy mirrors so the existing six entries keep their
previous semantics.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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
`@feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/mirror/MirrorPickerViewModel.kt`:
- Around line 123-131: The else branch currently assumes any non-null, non-{url}
template should be decomposed into {owner}/{repo}/{ref}/{path}; instead, mimic
MirrorRewriter.applyTemplate(...) by first checking for the presence of the
decomposed marker (e.g. template.contains("{owner}")) before performing the
chained .replace calls — if the decomposed marker is missing return the template
as-is (or use wholeUrlProbe where appropriate); update the when branch handling
in MirrorPickerViewModel (the template/wholeUrlProbe logic) to guard the
replacement sequence with template.contains("{owner}") to avoid treating
malformed/unknown templates as decomposed.
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b2657865-72e1-49f2-8272-fee14f99fdfa

📥 Commits

Reviewing files that changed from the base of the PR and between c8329b1 and 83b397f.

📒 Files selected for processing (1)
  • feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/mirror/MirrorPickerViewModel.kt

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Jun 6, 2026

Greptile Summary

This PR fixes a 400 error when using the Test mirror button on fastly.jsdelivr.net by teaching runTest() to recognise path-based URL templates (containing {owner}) and substitute the same cli/cli@v2.40.0/LICENSE asset the backend health probe uses. It also registers fastly.jsdelivr.net as a bundled mirror with a RAW_FILE_ONLY traffic kind.

  • MirrorPickerViewModel.runTest() — adds a when branch on template shape: {url} templates substitute the full GitHub URL as before; {owner} templates substitute the jsDelivr path components; anything else falls back to the direct probe URL.
  • BundledMirrors — adds fastly.jsdelivr.net to the bundled list with trafficKinds = RAW_FILE_ONLY, and threads a new trafficKinds parameter through entry().
  • MirrorRepositoryImpl — adds sortedBy { it.status.ordinal } to readCachedCatalogOrBundled() so the initial/cached list surfaces healthy mirrors first; formatting-only changes elsewhere.

Confidence Score: 5/5

Safe to merge — the targeted fix is correct, and the only inconsistency introduced is a cosmetic ordering difference between the cached and live-refreshed catalog.

The core change — branching on template shape in runTest() — directly maps to the backend probe logic and is well-scoped. The new fastly_jsdelivr bundled entry and trafficKinds plumbing look correct. The one inconsistency (sort applied in readCachedCatalogOrBundled but not in refreshCatalog) affects display order only and does not cause wrong data or broken functionality.

MirrorRepositoryImpl.kt — the refreshCatalog() path does not apply the new sort order that readCachedCatalogOrBundled() does.

Important Files Changed

Filename Overview
feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/mirror/MirrorPickerViewModel.kt Core fix: runTest() now branches on {url} vs {owner} template shape to correctly probe jsDelivr-style path-based mirrors; other changes are formatting only.
core/data/src/commonMain/kotlin/zed/rainxch/core/data/mirror/BundledMirrors.kt Adds fastly.jsdelivr.net as a bundled mirror with RAW_FILE_ONLY traffic kind; introduces trafficKinds parameter to entry() with FULL_PROXY_KINDS as the default.
core/data/src/commonMain/kotlin/zed/rainxch/core/data/mirror/MirrorRepositoryImpl.kt Adds sortedBy { it.status.ordinal } in readCachedCatalogOrBundled() but not in refreshCatalog(), creating an inconsistent ordering between initial load and live refresh; also removes private val from appScope and reformats migration entries.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[runTest called] --> B{preference type?}
    B -->|Direct| C[template = null]
    B -->|Custom| D[template = custom string]
    B -->|Selected| E[template = catalog lookup]

    C --> F{template null?}
    D --> F
    E --> F

    F -->|Yes| G[targetUrl = wholeUrlProbe]
    F -->|No| H{contains url?}

    H -->|Yes| I[replace url with raw.githubusercontent.com probe]
    H -->|No| J{contains owner?}

    J -->|Yes| K[replace owner=cli, repo=cli, ref=v2.40.0, path=LICENSE]
    J -->|No| G

    G --> L[GET targetUrl withTimeoutOrNull 5s]
    I --> L
    K --> L

    L -->|null| M[TestResult.Timeout]
    L -->|UnresolvedAddress| N[TestResult.DnsFailure]
    L -->|other exception| O[TestResult.Other]
    L -->|200-299| P[TestResult.Success ms]
    L -->|other status| Q[TestResult.HttpError status]
Loading

Fix All in Claude Code

Reviews (3): Last reviewed commit: "Show active proxies first + some style r..." | Re-trigger Greptile

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/mirror/MirrorPickerViewModel.kt (1)

130-143: 💤 Low value

Template-based probe URL construction correctly addresses path-based templates.

The logic now properly guards decomposed substitution with template.contains("{owner}") (line 136) and provides a wholeUrlProbe fallback for unknown template shapes (line 142), fully addressing the past review concern.


Optional: Consider aligning wholeUrlProbe with backend probe resource.

The wholeUrlProbe uses octocat/Hello-World/master/README while the decomposed path uses cli/cli@v2.40.0/LICENSE (matching the backend probe per the PR description). While the PR states {url} templates keep "existing behavior," using cli/cli@v2.40.0/LICENSE for both would improve consistency with the backend probe and make cross-template comparison easier.

Suggested alignment
         val wholeUrlProbe =
-            "https://raw.githubusercontent.com/octocat/Hello-World/master/README"
+            "https://raw.githubusercontent.com/cli/cli/v2.40.0/LICENSE"
🤖 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
`@feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/mirror/MirrorPickerViewModel.kt`
around lines 130 - 143, The current probe uses wholeUrlProbe =
"https://raw.githubusercontent.com/octocat/Hello-World/master/README" which
mismatches the decomposed substitution values used in the template branch;
update wholeUrlProbe so it aligns with the backend probe and the decomposed
branch (e.g. use the cli/cli@v2.40.0/LICENSE raw URL) so targetUrl construction
(the when block that checks template == null, template.contains("{url}"), and
template.contains("{owner}")) produces consistent probe URLs across template
shapes.
🤖 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.

Nitpick comments:
In
`@feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/mirror/MirrorPickerViewModel.kt`:
- Around line 130-143: The current probe uses wholeUrlProbe =
"https://raw.githubusercontent.com/octocat/Hello-World/master/README" which
mismatches the decomposed substitution values used in the template branch;
update wholeUrlProbe so it aligns with the backend probe and the decomposed
branch (e.g. use the cli/cli@v2.40.0/LICENSE raw URL) so targetUrl construction
(the when block that checks template == null, template.contains("{url}"), and
template.contains("{owner}")) produces consistent probe URLs across template
shapes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1554ea8d-5c42-4540-a3c6-4f962202ed2d

📥 Commits

Reviewing files that changed from the base of the PR and between d040ea7 and 4a9b7f2.

📒 Files selected for processing (3)
  • core/data/src/commonMain/kotlin/zed/rainxch/core/data/mirror/BundledMirrors.kt
  • core/data/src/commonMain/kotlin/zed/rainxch/core/data/mirror/MirrorRepositoryImpl.kt
  • feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/mirror/MirrorPickerViewModel.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/data/src/commonMain/kotlin/zed/rainxch/core/data/mirror/BundledMirrors.kt

@rainxchzed rainxchzed merged commit ff26500 into main Jun 6, 2026
1 check was pending
@rainxchzed rainxchzed deleted the fix/mirror-test-path-template branch June 6, 2026 07:09
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