Skip to content

feat(plugin): implement YouTube WASM plugin via yt-dlp#23

Merged
mpiton merged 4 commits intomainfrom
feat/24-youtube-plugin
Apr 11, 2026
Merged

feat(plugin): implement YouTube WASM plugin via yt-dlp#23
mpiton merged 4 commits intomainfrom
feat/24-youtube-plugin

Conversation

@mpiton
Copy link
Copy Markdown
Owner

@mpiton mpiton commented Apr 11, 2026

Summary

  • Adds plugins/vortex-mod-youtube/, the first production crawler plugin for Vortex, targeting wasm32-wasip1 via extism-pdk 1.4.
  • Resolves YouTube videos, Shorts, playlists, and channels by delegating media extraction to yt-dlp through the host run_subprocess function — no direct network I/O from the plugin sandbox.
  • Closes task 24 from specs/01-mvp/README.md.

Architecture

Native-testable core / WASM-thin-wrapper split. The plugin keeps business logic in plain Rust modules that compile for the host toolchain, and gates the #[plugin_fn] exports / #[host_fn] imports behind cfg(target_family = "wasm"). Result: 77 unit tests run under cargo test --target x86_64-unknown-linux-gnu without spinning up a WASM runtime, while the release build still produces a 1.2 MB stripped .wasm binary.

Module Purpose
url_matcher Regex-based classification of youtube.com / youtu.be / /shorts/ / /playlist / /@handle / /channel/ / /user/ / /c/ URLs; video + playlist ID extraction.
metadata Serde parsing of yt-dlp --dump-json (single video) and --flat-playlist (accepts both JSONL and envelope shapes). Classifies formats as muxed / video_only / audio_only / unknown via vcodec/acodec inspection.
quality_manager Format selection by target resolution (360p → 4320p + Best) with height-bucket fallback and user container preference. Audio-only path picks highest abr.
extractor Pure helpers that build subprocess request JSON and parse the response envelope. UTF-8-safe stderr truncation.
plugin_api (WASM-only) #[plugin_fn] exports: can_handle, supports_playlist, extract_links, get_media_variants, extract_playlist. #[host_fn] extern \"ExtismHost\" import of run_subprocess.
error thiserror enum with SerdeJson(#[from]) preserving the error source chain.

Security hardening (applied from code review)

  • -- sentinel inserted before every URL argument passed to yt-dlp, so a URL that accidentally starts with - can never be interpreted as an option. Defense in depth beyond host-side URL validation.
  • Stderr truncation operates on character boundaries rather than byte offsets — multi-byte yt-dlp output (non-ASCII filenames, localised errors) can no longer cause a WASM trap.
  • Least-privilege manifest: only subprocess = [\"yt-dlp\"] is declared; the http capability is omitted because all network access is delegated to yt-dlp.
  • The sole unsafe block (the run_subprocess host-function call) documents four named invariants: host symbol resolution, ABI shape, capability check, and ownership.
  • Zero .unwrap() / .expect() in production paths (CLAUDE.md rule). build_subprocess_request returns Result even though the failure branch is practically unreachable.
  • Flat-playlist parser accepts both JSONL (typical for playlists) and envelope (sometimes emitted for channels) shapes — prior fail-fast behaviour would have broken every channel URL.

Test plan

  • cargo test --target x86_64-unknown-linux-gnu — 77/77 pass (url_matcher: 22, metadata: 16, quality_manager: 13, extractor: 10, ipc_tests: 10, error: 0 new)
  • cargo clippy --target x86_64-unknown-linux-gnu --all-targets -- -D warnings — clean
  • cargo clippy --all-targets -- -D warnings (WASM target) — clean
  • cargo fmt -- --check — clean
  • cargo build --release — produces target/wasm32-wasip1/release/vortex_mod_youtube.wasm (1.2 MB)
  • lefthook pre-push (src-tauri workspace cargo test + vitest + tsc) — clean
  • Manual smoke test: copy plugin.toml and the release .wasm into ~/.config/vortex/plugins/vortex-mod-youtube/, ensure yt-dlp is on PATH, then verify plugin_loader.resolve_url("https://youtu.be/...") routes to the plugin in a running Vortex build.
  • Manual smoke test: trigger extract_links and get_media_variants end-to-end once a driving adapter wires them in.

Follow-ups (not in this PR)

  • Wire extract_links / get_media_variants / extract_playlist calls from the Vortex application layer — currently only can_handle is consumed by resolve_url.
  • Graceful partial-parse for malformed lines in very long playlists (currently fails fast on the first bad entry).
  • Collapse watch_path_regex and watch_id_regex into a single compiled pattern.

Summary by cubic

Adds a hardened YouTube WASM plugin via yt-dlp that handles videos, Shorts, playlists, and channels with strict URL validation and robust parsing. Completes MVP task 24.

  • New Features

    • New plugins/vortex-mod-youtube crate for wasm32-wasip1 using extism-pdk 1.4; exports can_handle, supports_playlist, extract_links, get_media_variants, extract_playlist.
    • Delegates extraction to yt-dlp (--dump-json); supports quality/container choice and audio-only; variants listed from metadata.
    • Hardened URL handling: explicit YouTube host allowlist (incl. youtube-nocookie.com, youtu.be); path-based regex matching; trims userinfo/ports; rejects query/fragment smuggling; preserves case of IDs; improved ID extraction for ports/userinfo.
    • Robust parsing: playlist parser accepts JSONL and envelope; canonical watch-URL fallback when missing; guards invalid durations; get_media_variants rejects playlists; extract_playlist rejects single videos; can_handle rejects unsupported paths like /embed/.
    • Security/quality: -- sentinel before URLs; least-privilege manifest (subprocess = ["yt-dlp"], no HTTP); UTF-8-safe stderr truncation; 105 native tests; release .wasm ~1.2 MB.
  • Migration

    • Ensure yt-dlp is on PATH and host allows subprocess for yt-dlp.
    • Build for wasm32-wasip1, then install to ~/.config/vortex/plugins/vortex-mod-youtube/ with plugin.toml and exactly one .wasm file (rename to vortex-mod-youtube.wasm if needed).
    • Optional plugin.toml config: default_quality (incl. best), preferred_format, audio_only, subtitles, cookie_file.

Written for commit 5750b8e. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Added a Vortex YouTube WASM plugin: supports videos, playlists, channels, and Shorts; media variant listing with selectable quality (360p–4320p/Best), container preference, and audio-only mode. Plugin runs yt-dlp via host subprocess.
  • Documentation

    • Added plugin README and manifest with install, build, runtime, config and host prerequisites.
  • Tests

    • Added extensive native unit tests for URL classification, parsing, IPC helpers, and quality/format selection.
  • Chores

    • Optimized WASM release build/profile for size.

Introduce vortex-mod-youtube, the first production crawler plugin,
as an independent Cargo crate targeting wasm32-wasip1 with extism-pdk 1.4.
The plugin resolves YouTube videos, shorts, playlists, and channels by
delegating media extraction to yt-dlp through the Vortex run_subprocess
host function — keeping the WASM sandbox boundary intact.

Architecture split for testability: all business logic (URL matching,
yt-dlp JSON parsing, quality selection, subprocess request/response
helpers) lives in native-compilable modules, and a thin `plugin_api`
module gated behind `cfg(target_family = "wasm")` holds the
`#[plugin_fn]` exports and `#[host_fn]` imports. This lets 77 unit
tests run under the host toolchain without a WASM runtime while still
producing a 1.2 MB stripped release binary.

Security hardening applied from code review:
- `--` sentinel before every yt-dlp URL arg (option-injection defense
  in depth beyond host-side URL validation)
- UTF-8-safe stderr truncation (char boundaries, never byte offsets)
  to avoid WASM aborts on non-ASCII yt-dlp output
- `PluginError::SerdeJson` with `#[from]` preserves the underlying
  serde_json error source chain
- Least-privilege manifest: only `subprocess = ["yt-dlp"]` declared,
  no `http` capability (all network I/O goes through yt-dlp itself)
- Documented safety invariants on the sole `unsafe` block around the
  `run_subprocess` host-function call
- Zero `.unwrap()`/`.expect()` in production paths per CLAUDE.md
- Flat-playlist parser now accepts both JSONL and envelope formats
  (yt-dlp switches between them depending on channel URL shape)

Covers specs/01-mvp/24-youtube-plugin.md success criteria.
@github-actions github-actions bot added the documentation Improvements or additions to documentation label Apr 11, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 11, 2026

📝 Walkthrough

Walkthrough

Adds a new Vortex YouTube WASM plugin crate (plugins/vortex-mod-youtube) implementing YouTube URL classification and ID extraction, yt-dlp subprocess IPC (host-imported), JSON parsing for video/playlist metadata, format/quality selection, Extism plugin exports, native unit tests, manifests, and documentation. Deleted a domain-model spec.

Changes

Cohort / File(s) Summary
Manifests & docs
CHANGELOG.md, plugins/vortex-mod-youtube/README.md, plugins/vortex-mod-youtube/plugin.toml, plugins/vortex-mod-youtube/.cargo/config.toml, plugins/vortex-mod-youtube/Cargo.toml
Added changelog entry, README, plugin manifest (declares yt-dlp subprocess capability and config schema), WASM build target config, and crate manifest with deps and size-optimized release profile.
Public API & DTOs
plugins/vortex-mod-youtube/src/lib.rs
Public DTOs, handler functions, response builders, and URL-gating helpers for link extraction and media-variant responses (native-testable and used by WASM exports).
WASM plugin bindings
plugins/vortex-mod-youtube/src/plugin_api.rs
Added Extism host import run_subprocess and plugin exports (can_handle, supports_playlist, extract_links, get_media_variants, extract_playlist) with error-to-Extism mapping.
URL matching
plugins/vortex-mod-youtube/src/url_matcher.rs
Normalization, strict host validation, classification (Video, Shorts, Playlist, Channel, Unknown), ID extraction, and comprehensive tests.
Metadata parsing
plugins/vortex-mod-youtube/src/metadata.rs
Parses yt-dlp single-video JSON and flat-playlist shapes (envelope & JSONL), normalizes fields (duration, formats, subtitles), classifies formats, and returns contextual parse errors.
Quality selection
plugins/vortex-mod-youtube/src/quality_manager.rs
Quality enum, SelectionPrefs, and select_best_format with height/container/audio-only selection rules and tests.
IPC / subprocess helpers
plugins/vortex-mod-youtube/src/extractor.rs
Defines SubprocessRequest/SubprocessResponse, yt-dlp arg builders (single/playlist), request serialization, response parsing with stderr truncation, and unit tests.
Error types
plugins/vortex-mod-youtube/src/error.rs
Added PluginError enum covering parse/serde/subprocess/host/unsupported/no-match cases.
Tests
plugins/vortex-mod-youtube/src/ipc_tests.rs
Native unit tests for URL classification, parsing, and response-building logic (no WASM runtime required).
Removed spec
specs/01-mvp/03-domain-models.md
Deleted domain-models specification document (removes documented domain-layer scope and referenced paths).

Sequence Diagram

sequenceDiagram
    autonumber
    actor Client as Client/Host
    participant Plugin as Vortex YouTube<br/>WASM Plugin
    participant HostSubproc as Host<br/>run_subprocess
    participant YtDlp as yt-dlp

    Client->>Plugin: extract_links(youtube_url)
    activate Plugin
    Plugin->>Plugin: ensure_youtube_url() / classify_url()
    Plugin->>Plugin: build yt-dlp args (single/playlist)
    Plugin->>HostSubproc: run_subprocess(JSON request)
    activate HostSubproc
    HostSubproc->>YtDlp: spawn yt-dlp with args
    activate YtDlp
    YtDlp-->>HostSubproc: stdout, stderr, exit_code
    deactivate YtDlp
    HostSubproc-->>Plugin: JSON response (exit_code, stdout, stderr)
    deactivate HostSubproc
    Plugin->>Plugin: parse stdout -> VideoInfo / Playlist
    Plugin->>Plugin: build response DTO (ExtractLinksResponse / MediaVariants)
    Plugin-->>Client: JSON result or mapped error
    deactivate Plugin
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

rust, dependencies

🐰 I hopped through regex, JSON bright,

I asked the host to run the flight,
yt-dlp sang stdout clear and true,
I sorted formats, picked the view,
Hooray — the links are ready for you! 🎵🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(plugin): implement YouTube WASM plugin via yt-dlp' accurately and concisely describes the main change: implementing a new YouTube plugin for the Vortex project using WASM and yt-dlp, which matches the scope of this comprehensive plugin addition.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ 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 feat/24-youtube-plugin

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

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 11, 2026

Greptile Summary

This PR introduces plugins/vortex-mod-youtube/, a WASM plugin targeting wasm32-wasip1 that resolves YouTube videos, Shorts, playlists, and channels by delegating extraction to yt-dlp via the host's run_subprocess capability. The architecture cleanly separates native-testable business logic from the WASM-gated #[plugin_fn] exports, enabling 77 unit tests to run without a WASM runtime.

All findings are P2 — see inline comments for edge-case details.

Confidence Score: 5/5

Safe to merge — all findings are P2 edge cases with low real-world probability.

No P0/P1 issues found. The four P2 findings (envelope heuristic false-positive, negative-duration silent truncation, empty-URL fallback, playlist URL accepted by get_media_variants) are all low-probability edge cases that don't affect the primary video/playlist extraction path. Security hardening (-- sentinel, char-boundary truncation, least-privilege manifest) is solid, and 77 unit tests cover the core logic thoroughly.

plugins/vortex-mod-youtube/src/metadata.rs (envelope detection heuristic and parse_duration edge cases)

Important Files Changed

Filename Overview
plugins/vortex-mod-youtube/src/metadata.rs yt-dlp JSON parsing with dual JSONL/envelope support; envelope heuristic can false-positive on string values containing "entries", silent empty-URL and negative-duration edge cases flagged.
plugins/vortex-mod-youtube/src/plugin_api.rs WASM plugin exports and host-fn wiring; get_media_variants silently accepts playlist URLs and extracts the first video instead of rejecting the call.
plugins/vortex-mod-youtube/src/url_matcher.rs Regex-based URL classification with OnceLock caching; well-tested, host allowlist correctly prevents subdomain spoofing, no issues found.
plugins/vortex-mod-youtube/src/extractor.rs Subprocess request/response helpers with -- URL sentinel and char-boundary stderr truncation; clean implementation, no issues found.
plugins/vortex-mod-youtube/src/quality_manager.rs Format selection by resolution with container preference; audio-only path ignores container preference (by design for now), logic is correct and well-tested.
plugins/vortex-mod-youtube/src/lib.rs Public API DTOs and native-testable business logic helpers; clean separation of concerns, no issues found.
plugins/vortex-mod-youtube/src/error.rs Minimal thiserror enum with #[from] for serde_json; correct and complete for current error surface.
plugins/vortex-mod-youtube/src/ipc_tests.rs Native integration tests covering all public entry points; comprehensive coverage with no gaps.
plugins/vortex-mod-youtube/plugin.toml Plugin manifest with least-privilege subprocess-only capability; several config options (cookie_file, auto_subtitles, audio_format) defined but not yet wired to arg builders.
plugins/vortex-mod-youtube/Cargo.toml Correct cdylib + rlib crate type for WASM/native dual compilation; release profile tuned for size with LTO and stripping.

Sequence Diagram

sequenceDiagram
    participant Host as Vortex Host
    participant PA as plugin_api (WASM)
    participant UM as url_matcher
    participant EX as extractor
    participant RS as run_subprocess (host fn)
    participant MD as metadata

    Host->>PA: extract_links(url)
    PA->>UM: ensure_youtube_url(url)
    PA->>UM: classify_url(url)
    alt Video / Shorts
        PA->>EX: yt_dlp_args_for_single_video(url)
    else Playlist / Channel
        PA->>EX: yt_dlp_args_for_playlist(url)
    end
    PA->>EX: build_subprocess_request(args)
    PA->>RS: unsafe run_subprocess(req_json)
    RS-->>PA: response_json
    PA->>EX: parse_subprocess_response(response_json)
    alt single video
        PA->>MD: parse_single_video(stdout)
        MD-->>PA: VideoInfo
        PA->>PA: build_single_video_response(video)
    else playlist
        PA->>MD: parse_flat_playlist(stdout)
        MD-->>PA: Playlist
        PA->>PA: build_playlist_response(playlist)
    end
    PA-->>Host: JSON string (ExtractLinksResponse)
Loading

Fix All in Claude Code

Reviews (1): Last reviewed commit: "feat(plugin): implement YouTube WASM plu..." | Re-trigger Greptile

Comment on lines +226 to +234
if let Some(first_line) = trimmed.lines().next() {
let contains_entries_key = first_line.contains("\"entries\"");
let looks_single_object = trimmed.lines().filter(|l| !l.trim().is_empty()).count() == 1
&& trimmed.starts_with('{');
if looks_single_object && contains_entries_key {
let env: RawPlaylistEnvelope = serde_json::from_str(trimmed)?;
return Ok(from_envelope(env));
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Envelope detection can false-positive on "entries" in string values

first_line.contains("\"entries\"") scans raw JSON bytes and matches any field whose value is exactly the word entries — not just the object key. A single-entry JSONL output where, say, the playlist name is entries would produce a raw line containing "playlist":"entries", which embeds the substring "entries" verbatim. With looks_single_object also true, the code attempts deserialization as RawPlaylistEnvelope, fails (wrong type for the entries field), and the ? operator propagates the error — the JSONL fallback path never executes.

Anchoring the search to the key position (adding the colon separator) eliminates the false-positive: look for "entries": rather than just "entries".

Fix in Claude Code

Comment on lines +282 to +286
url: raw.webpage_url.or(raw.url).unwrap_or_default(),
duration: parse_duration(raw.duration),
thumbnail: raw.thumbnail,
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Silent empty-URL fallback

When yt-dlp emits a flat-playlist entry with neither webpage_url nor url (e.g., a private/deleted video placeholder), unwrap_or_default() silently produces an empty string. Callers receive a PlaylistEntry with url: "" rather than an error, which will fail opaquely when the URL is later used. Consider returning an Option<PlaylistEntry> and filtering at the call site so empty-URL entries are simply dropped.

Fix in Claude Code

Comment on lines +346 to +352
fn parse_duration(value: Option<serde_json::Value>) -> Option<u64> {
value.and_then(|v| match v {
serde_json::Value::Number(n) => n.as_f64().map(|f| f.round() as u64),
serde_json::Value::String(s) => s.parse::<f64>().ok().map(|f| f.round() as u64),
_ => None,
})
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Saturating cast silently converts negative durations to Some(0)

Since Rust 1.45 the as u64 cast is saturating, so (-1.0f64).round() as u64 == 0. Any yt-dlp output that emits a negative duration (malformed or live-stream sentinel) produces Some(0) rather than None, making it indistinguishable from a 0-second video. A guard before the cast makes the intent explicit:

Suggested change
fn parse_duration(value: Option<serde_json::Value>) -> Option<u64> {
value.and_then(|v| match v {
serde_json::Value::Number(n) => n.as_f64().map(|f| f.round() as u64),
serde_json::Value::String(s) => s.parse::<f64>().ok().map(|f| f.round() as u64),
_ => None,
})
}
serde_json::Value::Number(n) => n.as_f64().and_then(|f| {
if f < 0.0 { None } else { Some(f.round() as u64) }
}),

Fix in Claude Code

Comment on lines +74 to +81
pub fn get_media_variants(url: String) -> FnResult<String> {
ensure_youtube_url(&url).map_err(error_to_fn_error)?;

let stdout = call_yt_dlp(yt_dlp_args_for_single_video(&url))?;
let video = parse_single_video(&stdout).map_err(error_to_fn_error)?;
let variants = build_media_variants_response(video);
Ok(serde_json::to_string(&variants)?)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 get_media_variants silently accepts playlist URLs

ensure_youtube_url accepts any recognised YouTube URL, including playlists and channels. When called with a playlist URL, yt_dlp_args_for_single_video (which includes --no-playlist) causes yt-dlp to extract the first video in the playlist, but the caller receives no indication that the returned variants belong to a different (first) video rather than the playlist itself. Guarding against non-single-video URLs would prevent this silent mismatch:

let kind = classify_url(&url);
if !matches!(kind, UrlKind::Video | UrlKind::Shorts) {
    return Err(error_to_fn_error(PluginError::UnsupportedUrl(url)));
}

Fix in Claude Code

Copy link
Copy Markdown

@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: 9

🧹 Nitpick comments (2)
plugins/vortex-mod-youtube/src/metadata.rs (1)

223-236: Try envelope deserialization directly instead of line heuristics.

This only recognizes the envelope form when it is a single non-empty line and "entries" appears on that first line. Any valid multi-line envelope falls through to JSONL parsing and errors. A direct serde_json::from_str::<RawPlaylistEnvelope> attempt would make this parser much more robust.

Suggested change
-    if let Some(first_line) = trimmed.lines().next() {
-        let contains_entries_key = first_line.contains("\"entries\"");
-        let looks_single_object = trimmed.lines().filter(|l| !l.trim().is_empty()).count() == 1
-            && trimmed.starts_with('{');
-        if looks_single_object && contains_entries_key {
-            let env: RawPlaylistEnvelope = serde_json::from_str(trimmed)?;
-            return Ok(from_envelope(env));
-        }
+    if trimmed.starts_with('{') {
+        if let Ok(env) = serde_json::from_str::<RawPlaylistEnvelope>(trimmed) {
+            return Ok(from_envelope(env));
+        }
     }
 
     parse_jsonl_playlist(output)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/vortex-mod-youtube/src/metadata.rs` around lines 223 - 236, The
current heuristic only treats the output as a RawPlaylistEnvelope when it is a
single non-empty line containing "entries", which misses valid multi-line JSON
envelopes; replace the heuristic with a direct attempt to deserialize into
RawPlaylistEnvelope by calling
serde_json::from_str::<RawPlaylistEnvelope>(trimmed) and, on Ok(env), return
Ok(from_envelope(env)); only if that deserialization returns Err should you fall
back to parse_jsonl_playlist(output). Update the block around trimmed,
RawPlaylistEnvelope, from_envelope, and parse_jsonl_playlist to try serde
deserialization first and then fallback to JSONL parsing.
plugins/vortex-mod-youtube/src/quality_manager.rs (1)

163-181: Make same-height fallback deterministic.

When preferred_container misses, the final .next() inherits yt-dlp’s input order. That can arbitrarily flip between muxed and video-only entries at the same height. Add an explicit secondary ranking in this bucket instead of relying on source order.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/vortex-mod-youtube/src/quality_manager.rs` around lines 163 - 181,
The fallback picks the first entry from at_height which follows yt-dlp input
order and can flip arbitrarily; make this deterministic by applying a stable
secondary ranking on at_height before taking next(): sort the at_height bucket
to prefer muxed entries (ones with audio) over video-only, then tie-break by a
numeric quality metric such as bitrate or filesize (descending), and finally use
a stable comparison on ext or format id to ensure total ordering; update the
selection logic around at_height (the block using prefs.preferred_container,
FormatEntry entries, chosen_height and the final
ok_or(PluginError::NoMatchingFormat)) to use that sorted/stable iterator.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@plugins/vortex-mod-youtube/plugin.toml`:
- Line 16: Update the plugin configuration schema so the "best" preset is
selectable: modify the default_quality options in plugin.toml by adding "best"
to the options array for the default_quality entry (the existing line that
defines default_quality = { type = "string", default = "1080p", options = [...]
} ) so the list includes "best" alongside the other quality strings.

In `@plugins/vortex-mod-youtube/README.md`:
- Line 3: Replace the placeholder repository link "https://github.com/..." in
the README.md title line with the actual Vortex repository URL so readers can
navigate to the host project; update the text "YouTube WASM plugin for
[Vortex](https://github.com/...)." to reference the real repo (e.g.,
https://github.com/Nexus-Mods/Vortex) so the markdown link points to the correct
Vortex project.
- Around line 26-35: The README uses two different WASM filenames
(vortex_mod_youtube.wasm vs vortex-mod-youtube.wasm); pick one and make it
consistent across the build output line and the install tree. Update the
sentence that states the resulting WASM binary and the install example so both
reference the same filename (either vortex_mod_youtube.wasm or
vortex-mod-youtube.wasm) and ensure the plugin.toml/install path stays correct.

In `@plugins/vortex-mod-youtube/src/lib.rs`:
- Around line 76-79: handle_can_handle currently uses
url_matcher::is_youtube_url which is too broad; change it to call
classify_url(url) and return true only when classify_url returns one of the
specific URL kinds your handlers actually support (i.e., the same variants the
extractor/routing code accepts), so the routing contract matches extraction;
apply the same tightening to the other helper(s) mentioned (the
ensure_youtube_url/related helper in the 153-159 region) so both use
classify_url and check for supported kinds rather than the broad regex.

In `@plugins/vortex-mod-youtube/src/metadata.rs`:
- Around line 191-200: The code is converting missing media URLs into empty
strings by calling unwrap_or_default() on raw.webpage_url and raw.thumbnail when
constructing VideoInfo; instead, preserve the absence by assigning the Option
values directly (use raw.webpage_url and raw.thumbnail without
unwrap_or_default) so downstream MediaLink.url sees None instead of "". Update
any downstream construction that assumes non-empty String to handle
Option<String> (or explicitly derive a fallback like
format!("https://youtu.be/{}", raw.id) where appropriate). Apply the same fix to
the other URL fields built later (the block around lines 278-285) so no missing
URL is silently turned into "".

In `@plugins/vortex-mod-youtube/src/plugin_api.rs`:
- Around line 52-66: The match over UrlKind in the response builder uses arms
that don't exist; update the match in the block that calls classify_url(...) to
handle the enum variants defined in url_matcher.rs (Video, Playlist, Channel,
Other) instead of Shorts/Unknown: group Playlist and Channel to call
yt_dlp_args_for_playlist + parse_flat_playlist + build_playlist_response, handle
Video (which should also cover shorts if classify_url classifies them) with
yt_dlp_args_for_single_video + parse_single_video + build_single_video_response,
and treat Other by returning
error_to_fn_error(PluginError::UnsupportedUrl(url)); keep calls to call_yt_dlp,
parse_* and build_* as-is.
- Around line 72-91: After ensure_youtube_url(&url), proactively validate the
YouTube resource kind and return an UnsupportedUrl FnError if it doesn't match
the function's expectation: in get_media_variants ensure the URL is a single
video (reject playlist/channel), and in extract_playlist ensure the URL is a
playlist (reject single-video); do this by probing yt-dlp metadata (reuse
call_yt_dlp/yt_dlp_args_for_single_video or yt_dlp_args_for_playlist to get the
info JSON) and inspecting the parsed result before calling parse_single_video or
parse_flat_playlist so you return UnsupportedUrl instead of dispatching to the
wrong parser (refer to get_media_variants, extract_playlist, ensure_youtube_url,
call_yt_dlp, parse_single_video, parse_flat_playlist, and UnsupportedUrl).

In `@plugins/vortex-mod-youtube/src/url_matcher.rs`:
- Around line 68-90: classify_url() lowercases and trims input before matching
but extract_video_id() only trims and extract_playlist_id() doesn't trim or
lowercase, causing mismatches; update extract_video_id() to both trim and
lowercase its input (apply the same normalization used by classify_url()) before
running watch_id_regex(), short_host_id_regex(), and shorts_id_regex(), and
update extract_playlist_id() to normalize (trim and lowercase) the url before
calling playlist_id_regex() so extraction behavior matches classification;
reference the functions extract_video_id, extract_playlist_id, classify_url,
watch_id_regex, short_host_id_regex, shorts_id_regex, and playlist_id_regex when
making the change.
- Around line 95-103: has_youtube_host currently does a substring search causing
false positives; change it to parse the input as a URL (use the existing URL
parsing approach or a Url/parse equivalent) and then compare the parsed host
(Url::host_str()) against the canonical hostnames
("youtube.com","www.youtube.com","m.youtube.com","music.youtube.com","youtu.be");
for youtu.be you may also accept the host exactly "youtu.be"; ensure any parse
errors return false so non-URLs are rejected and avoid inspecting the entire
string for embedded YouTube links.

---

Nitpick comments:
In `@plugins/vortex-mod-youtube/src/metadata.rs`:
- Around line 223-236: The current heuristic only treats the output as a
RawPlaylistEnvelope when it is a single non-empty line containing "entries",
which misses valid multi-line JSON envelopes; replace the heuristic with a
direct attempt to deserialize into RawPlaylistEnvelope by calling
serde_json::from_str::<RawPlaylistEnvelope>(trimmed) and, on Ok(env), return
Ok(from_envelope(env)); only if that deserialization returns Err should you fall
back to parse_jsonl_playlist(output). Update the block around trimmed,
RawPlaylistEnvelope, from_envelope, and parse_jsonl_playlist to try serde
deserialization first and then fallback to JSONL parsing.

In `@plugins/vortex-mod-youtube/src/quality_manager.rs`:
- Around line 163-181: The fallback picks the first entry from at_height which
follows yt-dlp input order and can flip arbitrarily; make this deterministic by
applying a stable secondary ranking on at_height before taking next(): sort the
at_height bucket to prefer muxed entries (ones with audio) over video-only, then
tie-break by a numeric quality metric such as bitrate or filesize (descending),
and finally use a stable comparison on ext or format id to ensure total
ordering; update the selection logic around at_height (the block using
prefs.preferred_container, FormatEntry entries, chosen_height and the final
ok_or(PluginError::NoMatchingFormat)) to use that sorted/stable iterator.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dc1cd2e4-c68e-49a0-b29f-84778abfcf82

📥 Commits

Reviewing files that changed from the base of the PR and between 8eeaa25 and fee29b9.

⛔ Files ignored due to path filters (1)
  • plugins/vortex-mod-youtube/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • CHANGELOG.md
  • plugins/vortex-mod-youtube/.cargo/config.toml
  • plugins/vortex-mod-youtube/Cargo.toml
  • plugins/vortex-mod-youtube/README.md
  • plugins/vortex-mod-youtube/plugin.toml
  • plugins/vortex-mod-youtube/src/error.rs
  • plugins/vortex-mod-youtube/src/extractor.rs
  • plugins/vortex-mod-youtube/src/ipc_tests.rs
  • plugins/vortex-mod-youtube/src/lib.rs
  • plugins/vortex-mod-youtube/src/metadata.rs
  • plugins/vortex-mod-youtube/src/plugin_api.rs
  • plugins/vortex-mod-youtube/src/quality_manager.rs
  • plugins/vortex-mod-youtube/src/url_matcher.rs

Comment on lines +52 to +66
let kind = classify_url(&url);
let response = match kind {
UrlKind::Playlist | UrlKind::Channel => {
let stdout = call_yt_dlp(yt_dlp_args_for_playlist(&url))?;
let playlist = parse_flat_playlist(&stdout).map_err(error_to_fn_error)?;
build_playlist_response(playlist)
}
UrlKind::Video | UrlKind::Shorts => {
let stdout = call_yt_dlp(yt_dlp_args_for_single_video(&url))?;
let video = parse_single_video(&stdout).map_err(error_to_fn_error)?;
build_single_video_response(video)
}
UrlKind::Unknown => {
return Err(error_to_fn_error(PluginError::UnsupportedUrl(url)));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix the UrlKind match arms.

This match does not line up with plugins/vortex-mod-youtube/src/url_matcher.rs:11-40, which defines UrlKind as Video | Playlist | Channel | Other. Shorts and Unknown will not compile against that enum.

Suggested change
     let response = match kind {
         UrlKind::Playlist | UrlKind::Channel => {
             let stdout = call_yt_dlp(yt_dlp_args_for_playlist(&url))?;
             let playlist = parse_flat_playlist(&stdout).map_err(error_to_fn_error)?;
             build_playlist_response(playlist)
         }
-        UrlKind::Video | UrlKind::Shorts => {
+        UrlKind::Video => {
             let stdout = call_yt_dlp(yt_dlp_args_for_single_video(&url))?;
             let video = parse_single_video(&stdout).map_err(error_to_fn_error)?;
             build_single_video_response(video)
         }
-        UrlKind::Unknown => {
+        UrlKind::Other => {
             return Err(error_to_fn_error(PluginError::UnsupportedUrl(url)));
         }
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/vortex-mod-youtube/src/plugin_api.rs` around lines 52 - 66, The match
over UrlKind in the response builder uses arms that don't exist; update the
match in the block that calls classify_url(...) to handle the enum variants
defined in url_matcher.rs (Video, Playlist, Channel, Other) instead of
Shorts/Unknown: group Playlist and Channel to call yt_dlp_args_for_playlist +
parse_flat_playlist + build_playlist_response, handle Video (which should also
cover shorts if classify_url classifies them) with yt_dlp_args_for_single_video
+ parse_single_video + build_single_video_response, and treat Other by returning
error_to_fn_error(PluginError::UnsupportedUrl(url)); keep calls to call_yt_dlp,
parse_* and build_* as-is.

Copy link
Copy Markdown

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

Choose a reason for hiding this comment

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

5 issues found across 14 files

Prompt for AI agents (unresolved issues)

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


<file name="plugins/vortex-mod-youtube/README.md">

<violation number="1" location="plugins/vortex-mod-youtube/README.md:35">
P2: The install filename is inconsistent with the documented build artifact name (`vortex_mod_youtube.wasm` vs `vortex-mod-youtube.wasm`), which can break manual installation.</violation>
</file>

<file name="plugins/vortex-mod-youtube/src/plugin_api.rs">

<violation number="1" location="plugins/vortex-mod-youtube/src/plugin_api.rs:74">
P2: `get_media_variants` does not enforce a video/short URL kind before running single-video extraction. Reject playlist/channel URLs explicitly to avoid incorrect or inconsistent results.</violation>
</file>

<file name="plugins/vortex-mod-youtube/src/url_matcher.rs">

<violation number="1" location="plugins/vortex-mod-youtube/src/url_matcher.rs:96">
P2: Host detection uses substring matching, so non-YouTube URLs that contain a YouTube URL in query/fragment can be misclassified as YouTube.</violation>
</file>

<file name="plugins/vortex-mod-youtube/src/metadata.rs">

<violation number="1" location="plugins/vortex-mod-youtube/src/metadata.rs:231">
P2: Envelope parsing should not return immediately on heuristic matches; deserialize defensively and fall back to JSONL when it is not actually an envelope.</violation>
</file>

<file name="plugins/vortex-mod-youtube/plugin.toml">

<violation number="1" location="plugins/vortex-mod-youtube/plugin.toml:16">
P2: The new `[config]` entries are currently a no-op: manifest parsing ignores this section, so these defaults/options are not applied at runtime.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

subprocess = ["yt-dlp"]

[config]
default_quality = { type = "string", default = "1080p", options = ["360p", "480p", "720p", "1080p", "1440p", "2160p", "4320p"] }
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 11, 2026

Choose a reason for hiding this comment

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

P2: The new [config] entries are currently a no-op: manifest parsing ignores this section, so these defaults/options are not applied at runtime.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At plugins/vortex-mod-youtube/plugin.toml, line 16:

<comment>The new `[config]` entries are currently a no-op: manifest parsing ignores this section, so these defaults/options are not applied at runtime.</comment>

<file context>
@@ -0,0 +1,22 @@
+subprocess = ["yt-dlp"]
+
+[config]
+default_quality = { type = "string", default = "1080p", options = ["360p", "480p", "720p", "1080p", "1440p", "2160p", "4320p"] }
+preferred_format = { type = "string", default = "mp4", options = ["mp4", "webm", "mkv"] }
+audio_only = { type = "boolean", default = false }
</file context>
Fix with Cubic

Resolve 12 unique findings from PR #23 review (coderabbitai, greptile,
cubic-dev-ai). Focus on correctness + security hardening of the
YouTube plugin. No behaviour change to user-facing APIs beyond
explicit rejection of previously-accepted-but-misrouted URLs.

url_matcher.rs
- Replace substring host matching with proper URL parsing so that
  `https://example.com/?next=https://youtube.com/watch?v=x` no longer
  smuggles through as a YouTube URL.
- Add `normalize_for_matching` as a single chokepoint that validates
  the host against an explicit allowlist, strips userinfo + port, and
  lowercases path/query before regex matching. This also transparently
  fixes the previous failure on URLs like `www.youtube.com:443/...`.
- Normalize `extract_video_id` and `extract_playlist_id` inputs with
  trim + host validation so that a valid classified URL no longer
  fails the subsequent id-extraction step.
- Add `youtube-nocookie.com` (+ `www.`) to the recognised host set.
- New tests: port authority, userinfo authority, query/fragment
  smuggling, evil.com prefix, trailing-whitespace extraction.

metadata.rs
- Replace the heuristic `first_line.contains("\"entries\"")` detector
  with a defensive try-parse: only attempt envelope deserialisation
  when the output is a single top-level JSON object, and silently fall
  back to JSONL parsing when the envelope shape does not fit. Removes
  the false-positive path where a JSONL line containing a playlist
  title of "entries" would commit to envelope parsing and fail the
  whole extraction.
- Stop silently emitting empty strings when yt-dlp omits `webpage_url`
  / `url` for a single video or a playlist entry. Instead, derive a
  canonical `https://www.youtube.com/watch?v={id}` fallback so
  downstream MediaLink.url is always navigable.
- Add `seconds_to_u64` guard that rejects NaN, infinity, and negative
  durations. Previously `(-1.0_f64).round() as u64` was saturating to
  `Some(0)`, indistinguishable from a legitimate 0-second video and
  hiding malformed or live-stream sentinel values.
- New tests: single-line-entries-as-value, playlist-missing-url
  fallback, single-video-missing-webpage_url fallback,
  negative-duration-as-None, nan-duration-as-None.

lib.rs + plugin_api.rs
- `handle_can_handle` now matches classify_url against the exact
  supported kinds (Video | Shorts | Playlist | Channel) instead of
  delegating to `is_youtube_url`. Adding a new UrlKind later will
  force an explicit routing decision here.
- `ensure_youtube_url` now returns the classified kind for callers to
  reuse, and `ensure_single_video` / `ensure_playlist_or_channel`
  guards enforce kind-specific preconditions.
- `get_media_variants` rejects playlist/channel URLs so it no longer
  silently returns the first video's variants when handed a playlist.
- `extract_playlist` rejects single-video URLs for the same reason.
- New tests: ensure_single_video rejects playlist, accepts video +
  shorts; ensure_playlist_or_channel rejects video, accepts playlist
  + channel; can_handle rejects /embed/ (unsupported kind).

README.md
- Replace placeholder `https://github.com/...` with the actual repo
  URL.
- Fix install instructions: the cargo build artifact is named
  `vortex_mod_youtube.wasm` (underscores), but the Vortex loader
  expects the wasm file to match the plugin directory name
  (`vortex-mod-youtube.wasm`, hyphens). Install steps now cp+rename
  explicitly and explain why.

plugin.toml
- Add "best" to `default_quality.options` to match the documented
  Quality::Best enum variant.
- Document that the `[config]` section is forward-compatible only —
  the current manifest parser at src-tauri/src/adapters/driven/plugin/
  manifest.rs ignores it, so defaults/options are not yet wired at
  runtime.

Tests: 96 passing (77 → +19), clippy clean on both native and
wasm32-wasip1 targets, release WASM still 1.2 MB.
Copy link
Copy Markdown

@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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@plugins/vortex-mod-youtube/README.md`:
- Around line 44-48: The fenced code block in README.md should include a
language identifier to satisfy MD040; update the block around the plugin path
listing by changing the opening backticks from ``` to ```text so the snippet
(the tree showing ~/.config/vortex/plugins/vortex-mod-youtube/ with plugin.toml
and vortex-mod-youtube.wasm) is fenced as text.
- Around line 30-33: The README incorrectly states that the Vortex plugin loader
requires the wasm binary filename to match the plugin name; instead update the
wording to reflect actual behavior in
src-tauri/src/adapters/driven/plugin/manifest.rs: it enforces that the plugin
directory name matches the manifest plugin name and that there is exactly one
.wasm file in that directory, not a strict filename match. Edit the paragraph
referencing filename matching to say that you must ensure the directory name
matches the plugin name in plugin.toml and that the build outputs a single .wasm
file in that directory (rename/copy the artifact into the directory if
necessary), and optionally add a brief note pointing readers to manifest.rs for
exact validation rules.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8e8a0578-9cd9-4ec5-af31-0ed65925b1ed

📥 Commits

Reviewing files that changed from the base of the PR and between fee29b9 and 6d5571f.

📒 Files selected for processing (7)
  • plugins/vortex-mod-youtube/README.md
  • plugins/vortex-mod-youtube/plugin.toml
  • plugins/vortex-mod-youtube/src/ipc_tests.rs
  • plugins/vortex-mod-youtube/src/lib.rs
  • plugins/vortex-mod-youtube/src/metadata.rs
  • plugins/vortex-mod-youtube/src/plugin_api.rs
  • plugins/vortex-mod-youtube/src/url_matcher.rs
✅ Files skipped from review due to trivial changes (1)
  • plugins/vortex-mod-youtube/plugin.toml
🚧 Files skipped from review as they are similar to previous changes (1)
  • plugins/vortex-mod-youtube/src/url_matcher.rs

The `specs/` directory is listed in `.gitignore` and the project rule
in CLAUDE.md is explicit: "Ne jamais commit le dossier specs". One
file (`03-domain-models.md`) slipped through before the gitignore rule
was added and has been tracked ever since.

Remove it from the git index with `git rm --cached` — the local file
remains on disk (it is now covered by the existing `specs/` ignore
entry) so no planning content is lost.
Copy link
Copy Markdown

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

Choose a reason for hiding this comment

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

2 issues found across 8 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

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


<file name="plugins/vortex-mod-youtube/src/url_matcher.rs">

<violation number="1" location="plugins/vortex-mod-youtube/src/url_matcher.rs:117">
P2: ID extraction is inconsistent with URL normalization: URLs with userinfo/port can be accepted as YouTube URLs but still fail extraction.</violation>

<violation number="2" location="plugins/vortex-mod-youtube/src/url_matcher.rs:203">
P2: `youtube-nocookie.com` is allowlisted but not actually supported by the regex matchers, so those URLs are still rejected.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Resolve 4 findings from the second review pass (coderabbitai,
cubic-dev-ai) on PR #23. Two bugs in url_matcher share a root cause
— regex patterns hard-coded `youtube\.com` into full-URL matches —
so the fix is a path-based regex refactor that eliminates both.

url_matcher.rs — path-based regex refactor
- `youtube-nocookie.com` was on the host allowlist but still failed
  regex matching: the patterns looked for literal `youtube\.com` in
  the URL, which is not a substring of `youtube-nocookie.com`.
  Refactor the regex set to operate on the *path+query* portion of
  the URL instead of the full URL — the path is host-agnostic so
  all allowlisted authorities (youtube.com, www., m., music.,
  youtube-nocookie.com, www.youtube-nocookie.com, youtu.be) share a
  single regex set.
- `extract_video_id` / `extract_playlist_id` now operate on the same
  path+query slice as `classify_url`. Previously the extract helpers
  validated the host but ran the id regex against the original
  (un-normalised) URL, so URLs with explicit ports (`:443`) or
  userinfo (`user:pass@`) passed host validation and then failed at
  id extraction. With path-based matching the authority is stripped
  upstream and the regex sees a clean `/watch?v=…` fragment.
- Introduce `validate_and_split(url) -> Option<(host_lower, path)>`
  as the single host-validation chokepoint. `classify_url`,
  `extract_video_id`, and `extract_playlist_id` all route through it.
- Extract `extract_youtu_be_id` helper for the short-link host —
  youtu.be paths are the id itself, which does not fit a regex that
  assumes `/watch?v=…`.
- Path-based regexes use `(?i)` only on the keyword portion
  (`watch`, `shorts`, `playlist`, `channel`, `user`, `c`) so that
  the captured id preserves its original case. YouTube ids are
  case-sensitive; lowercasing them produces a different video.

README.md
- Fix install docs: the Vortex plugin loader enforces *directory
  name* == manifest `name` and *exactly one .wasm file* in the
  directory — not a strict filename match. The previous wording
  implied filename was pinned. Document both rules explicitly and
  note that the cargo build artifact (`vortex_mod_youtube.wasm`,
  underscores) and the renamed install target
  (`vortex-mod-youtube.wasm`, hyphens) are both acceptable.
- Add `text` language id to the install layout fenced code block
  (MD040 lint).

New tests (9):
- detects_valid_youtube_urls: add youtube-nocookie.com variants
- classifies_youtube_nocookie_watch_as_video
- extracts_video_id_from_youtube_nocookie
- extracts_video_id_from_url_with_port
- extracts_video_id_from_url_with_userinfo
- extracts_playlist_id_from_url_with_port
- accepts_mixed_case_watch_keyword
- preserves_case_of_video_id

Tests: 105 passing (96 → +9), clippy clean on both native and
wasm32-wasip1 targets, release WASM still 1.2 MB.
Copy link
Copy Markdown

@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)
plugins/vortex-mod-youtube/README.md (1)

60-62: Consider adding a cross-platform test command note.

cargo test --target x86_64-unknown-linux-gnu is explicit but Linux-target-specific. A short note (or fallback cargo test) would reduce confusion for macOS/Windows contributors.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/vortex-mod-youtube/README.md` around lines 60 - 62, The README
currently shows a Linux-specific test command `cargo test --target
x86_64-unknown-linux-gnu`; update the test instructions to mention a
cross-platform fallback and alternatives — e.g., keep the explicit Linux target
line but add a sentence recommending contributors on macOS/Windows to run plain
`cargo test` or specify their own target triple, and note how to run
cross-platform CI tests if applicable; reference the README section where the
current `cargo test --target x86_64-unknown-linux-gnu` example appears to place
the new note adjacent to that command.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@plugins/vortex-mod-youtube/README.md`:
- Around line 60-62: The README currently shows a Linux-specific test command
`cargo test --target x86_64-unknown-linux-gnu`; update the test instructions to
mention a cross-platform fallback and alternatives — e.g., keep the explicit
Linux target line but add a sentence recommending contributors on macOS/Windows
to run plain `cargo test` or specify their own target triple, and note how to
run cross-platform CI tests if applicable; reference the README section where
the current `cargo test --target x86_64-unknown-linux-gnu` example appears to
place the new note adjacent to that command.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9c559edf-4772-48ae-a18a-1a9fb6ba6f16

📥 Commits

Reviewing files that changed from the base of the PR and between 42a54f3 and 5750b8e.

📒 Files selected for processing (2)
  • plugins/vortex-mod-youtube/README.md
  • plugins/vortex-mod-youtube/src/url_matcher.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • plugins/vortex-mod-youtube/src/url_matcher.rs

@mpiton mpiton merged commit 670acdc into main Apr 11, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant