Harden PRD live site smoke coverage
This commit is contained in:
@@ -5,5 +5,8 @@ pub mod plugin_packages;
|
|||||||
pub mod plugin_signatures;
|
pub mod plugin_signatures;
|
||||||
pub mod servo_sidecar;
|
pub mod servo_sidecar;
|
||||||
|
|
||||||
|
#[cfg(all(test, feature = "live-site-smoke"))]
|
||||||
|
pub(crate) mod prd_live_sites;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod plugin_package_test_support;
|
mod plugin_package_test_support;
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
use std::{collections::BTreeSet, error::Error, fs, path::PathBuf};
|
||||||
|
|
||||||
|
pub(crate) const PRD_TOP_SITE_CASES: &[LiveSiteCase] = &[
|
||||||
|
LiveSiteCase { url: "https://github.com", title_fragment: "GitHub" },
|
||||||
|
LiveSiteCase { url: "https://example.com", title_fragment: "Example Domain" },
|
||||||
|
LiveSiteCase { url: "https://servo.org/", title_fragment: "Servo" },
|
||||||
|
];
|
||||||
|
|
||||||
|
pub(crate) const PRD_REFERENCE_SITE_CASES: &[LiveSiteCase] = &[
|
||||||
|
LiveSiteCase {
|
||||||
|
url: "https://blog.google/products-and-platforms/products/chrome/new-chrome-productivity-features/",
|
||||||
|
title_fragment: "Chrome",
|
||||||
|
},
|
||||||
|
LiveSiteCase {
|
||||||
|
url: "https://www.microsoft.com/en-us/edge/features/vertical-tabs",
|
||||||
|
title_fragment: "Microsoft Edge",
|
||||||
|
},
|
||||||
|
LiveSiteCase {
|
||||||
|
url: "https://resources.arc.net/hc/en-us/articles/19230755904151-Favorites-Top-Tabs-Across-Every-Space",
|
||||||
|
title_fragment: "Favorites",
|
||||||
|
},
|
||||||
|
LiveSiteCase {
|
||||||
|
url: "https://resources.arc.net/hc/en-us/articles/19228855311127-Auto-Archive-Clean-as-you-go",
|
||||||
|
title_fragment: "Auto Archive",
|
||||||
|
},
|
||||||
|
LiveSiteCase { url: "https://vivaldi.com/features/workspaces/", title_fragment: "Workspaces" },
|
||||||
|
LiveSiteCase {
|
||||||
|
url: "https://help.vivaldi.com/desktop/tabs/tab-tiling/",
|
||||||
|
title_fragment: "Tab Tiling",
|
||||||
|
},
|
||||||
|
LiveSiteCase { url: "https://www.gpui.rs/", title_fragment: "gpui" },
|
||||||
|
LiveSiteCase { url: "https://docs.rs/gpui", title_fragment: "gpui" },
|
||||||
|
LiveSiteCase { url: "https://zed.dev/blog/videogame", title_fragment: "Leveraging Rust" },
|
||||||
|
LiveSiteCase {
|
||||||
|
url: "https://github.com/longbridge/gpui-component/",
|
||||||
|
title_fragment: "gpui-component",
|
||||||
|
},
|
||||||
|
LiveSiteCase {
|
||||||
|
url: "https://github.com/zed-industries/awesome-gpui/",
|
||||||
|
title_fragment: "awesome-gpui",
|
||||||
|
},
|
||||||
|
LiveSiteCase { url: "https://servo.org/", title_fragment: "Servo" },
|
||||||
|
LiveSiteCase {
|
||||||
|
url: "https://servo.org/blog/2026/04/13/servo-0.1.0-release/",
|
||||||
|
title_fragment: "Servo",
|
||||||
|
},
|
||||||
|
LiveSiteCase { url: "https://developers.cloudflare.com/d1/", title_fragment: "Cloudflare" },
|
||||||
|
LiveSiteCase {
|
||||||
|
url: "https://developers.cloudflare.com/workers/platform/storage-options/",
|
||||||
|
title_fragment: "Cloudflare",
|
||||||
|
},
|
||||||
|
LiveSiteCase {
|
||||||
|
url: "https://developers.cloudflare.com/kv/concepts/how-kv-works/",
|
||||||
|
title_fragment: "Cloudflare",
|
||||||
|
},
|
||||||
|
LiveSiteCase { url: "https://better-auth.com/blog/1-5", title_fragment: "Better Auth" },
|
||||||
|
LiveSiteCase {
|
||||||
|
url: "https://developers.cloudflare.com/d1/platform/limits/",
|
||||||
|
title_fragment: "Cloudflare",
|
||||||
|
},
|
||||||
|
LiveSiteCase {
|
||||||
|
url: "https://component-model.bytecodealliance.org/",
|
||||||
|
title_fragment: "WebAssembly Component Model",
|
||||||
|
},
|
||||||
|
LiveSiteCase {
|
||||||
|
url: "https://docs.wasmtime.dev/api/wasmtime/component/index.html",
|
||||||
|
title_fragment: "wasmtime",
|
||||||
|
},
|
||||||
|
LiveSiteCase { url: "https://docs.wasmtime.dev/security.html", title_fragment: "Wasmtime" },
|
||||||
|
];
|
||||||
|
|
||||||
|
pub(crate) struct LiveSiteCase {
|
||||||
|
pub(crate) url: &'static str,
|
||||||
|
pub(crate) title_fragment: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn assert_prd_reference_urls_are_covered() -> Result<(), Box<dyn Error>> {
|
||||||
|
let prd = fs::read_to_string(prd_path())?;
|
||||||
|
let prd_urls = prd_reference_urls(&prd);
|
||||||
|
let covered_urls = PRD_REFERENCE_SITE_CASES
|
||||||
|
.iter()
|
||||||
|
.map(|case| normalized_url(case.url))
|
||||||
|
.collect::<BTreeSet<_>>();
|
||||||
|
let missing_urls = prd_urls
|
||||||
|
.iter()
|
||||||
|
.filter(|url| !covered_urls.contains(url.as_str()))
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
assert!(missing_urls.is_empty(), "missing PRD live-site smoke cases: {missing_urls:?}");
|
||||||
|
assert_eq!(prd_urls.len(), PRD_REFERENCE_SITE_CASES.len());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prd_path() -> PathBuf {
|
||||||
|
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..").join("..").join("PRD.md")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prd_reference_urls(prd: &str) -> Vec<String> {
|
||||||
|
prd.lines()
|
||||||
|
.filter(|line| line.starts_with("[R"))
|
||||||
|
.filter_map(|line| {
|
||||||
|
let start = line.find("https://")?;
|
||||||
|
let url = line[start..].split_whitespace().next()?;
|
||||||
|
Some(normalized_url(url))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalized_url(url: &str) -> String {
|
||||||
|
url.trim().trim_end_matches('/').to_string()
|
||||||
|
}
|
||||||
@@ -2,87 +2,18 @@ use std::error::Error;
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "live-site-smoke")]
|
||||||
|
use crate::services::prd_live_sites::{
|
||||||
|
LiveSiteCase, PRD_REFERENCE_SITE_CASES, PRD_TOP_SITE_CASES,
|
||||||
|
assert_prd_reference_urls_are_covered,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(feature = "live-site-smoke")]
|
#[cfg(feature = "live-site-smoke")]
|
||||||
const LIVE_SITE_WIDTH: u32 = 934;
|
const LIVE_SITE_WIDTH: u32 = 934;
|
||||||
#[cfg(feature = "live-site-smoke")]
|
#[cfg(feature = "live-site-smoke")]
|
||||||
const LIVE_SITE_HEIGHT: u32 = 657;
|
const LIVE_SITE_HEIGHT: u32 = 657;
|
||||||
#[cfg(feature = "live-site-smoke")]
|
#[cfg(feature = "live-site-smoke")]
|
||||||
const MINIMUM_CONTENT_PIXELS: u64 = 1_000;
|
const MINIMUM_CONTENT_PIXELS: u64 = 1_000;
|
||||||
#[cfg(feature = "live-site-smoke")]
|
|
||||||
const PRD_TOP_SITE_CASES: &[LiveSiteCase] = &[
|
|
||||||
LiveSiteCase { url: "https://github.com", title_fragment: "GitHub" },
|
|
||||||
LiveSiteCase { url: "https://example.com", title_fragment: "Example Domain" },
|
|
||||||
LiveSiteCase { url: "https://servo.org/", title_fragment: "Servo" },
|
|
||||||
];
|
|
||||||
#[cfg(feature = "live-site-smoke")]
|
|
||||||
const PRD_REFERENCE_SITE_CASES: &[LiveSiteCase] = &[
|
|
||||||
LiveSiteCase {
|
|
||||||
url: "https://blog.google/products-and-platforms/products/chrome/new-chrome-productivity-features/",
|
|
||||||
title_fragment: "Chrome",
|
|
||||||
},
|
|
||||||
LiveSiteCase {
|
|
||||||
url: "https://www.microsoft.com/en-us/edge/features/vertical-tabs",
|
|
||||||
title_fragment: "Microsoft Edge",
|
|
||||||
},
|
|
||||||
LiveSiteCase {
|
|
||||||
url: "https://resources.arc.net/hc/en-us/articles/19230755904151-Favorites-Top-Tabs-Across-Every-Space",
|
|
||||||
title_fragment: "Favorites",
|
|
||||||
},
|
|
||||||
LiveSiteCase {
|
|
||||||
url: "https://resources.arc.net/hc/en-us/articles/19228855311127-Auto-Archive-Clean-as-you-go",
|
|
||||||
title_fragment: "Auto Archive",
|
|
||||||
},
|
|
||||||
LiveSiteCase { url: "https://vivaldi.com/features/workspaces/", title_fragment: "Workspaces" },
|
|
||||||
LiveSiteCase {
|
|
||||||
url: "https://help.vivaldi.com/desktop/tabs/tab-tiling/",
|
|
||||||
title_fragment: "Tab Tiling",
|
|
||||||
},
|
|
||||||
LiveSiteCase { url: "https://www.gpui.rs/", title_fragment: "gpui" },
|
|
||||||
LiveSiteCase { url: "https://docs.rs/gpui/latest/gpui/", title_fragment: "gpui" },
|
|
||||||
LiveSiteCase { url: "https://zed.dev/blog/videogame", title_fragment: "Leveraging Rust" },
|
|
||||||
LiveSiteCase {
|
|
||||||
url: "https://github.com/longbridge/gpui-component/",
|
|
||||||
title_fragment: "gpui-component",
|
|
||||||
},
|
|
||||||
LiveSiteCase {
|
|
||||||
url: "https://github.com/zed-industries/awesome-gpui/",
|
|
||||||
title_fragment: "awesome-gpui",
|
|
||||||
},
|
|
||||||
LiveSiteCase { url: "https://servo.org/", title_fragment: "Servo" },
|
|
||||||
LiveSiteCase {
|
|
||||||
url: "https://servo.org/blog/2026/04/13/servo-0.1.0-release/",
|
|
||||||
title_fragment: "Servo",
|
|
||||||
},
|
|
||||||
LiveSiteCase { url: "https://developers.cloudflare.com/d1/", title_fragment: "Cloudflare" },
|
|
||||||
LiveSiteCase {
|
|
||||||
url: "https://developers.cloudflare.com/workers/platform/storage-options/",
|
|
||||||
title_fragment: "Cloudflare",
|
|
||||||
},
|
|
||||||
LiveSiteCase {
|
|
||||||
url: "https://developers.cloudflare.com/kv/concepts/how-kv-works/",
|
|
||||||
title_fragment: "Cloudflare",
|
|
||||||
},
|
|
||||||
LiveSiteCase { url: "https://better-auth.com/blog/1-5", title_fragment: "Better Auth" },
|
|
||||||
LiveSiteCase {
|
|
||||||
url: "https://developers.cloudflare.com/d1/platform/limits/",
|
|
||||||
title_fragment: "Cloudflare",
|
|
||||||
},
|
|
||||||
LiveSiteCase {
|
|
||||||
url: "https://component-model.bytecodealliance.org/",
|
|
||||||
title_fragment: "WebAssembly Component Model",
|
|
||||||
},
|
|
||||||
LiveSiteCase {
|
|
||||||
url: "https://docs.wasmtime.dev/api/wasmtime/component/index.html",
|
|
||||||
title_fragment: "wasmtime",
|
|
||||||
},
|
|
||||||
LiveSiteCase { url: "https://docs.wasmtime.dev/security.html", title_fragment: "Wasmtime" },
|
|
||||||
];
|
|
||||||
|
|
||||||
#[cfg(feature = "live-site-smoke")]
|
|
||||||
struct LiveSiteCase {
|
|
||||||
url: &'static str,
|
|
||||||
title_fragment: &'static str,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn accepts_loading_report_with_visible_content() -> Result<(), ServoSidecarError> {
|
fn accepts_loading_report_with_visible_content() -> Result<(), ServoSidecarError> {
|
||||||
@@ -137,6 +68,12 @@ fn desktop_sidecar_opens_prd_reference_sites() -> Result<(), Box<dyn Error>> {
|
|||||||
assert_live_sites_render(PRD_REFERENCE_SITE_CASES)
|
assert_live_sites_render(PRD_REFERENCE_SITE_CASES)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "live-site-smoke")]
|
||||||
|
#[test]
|
||||||
|
fn prd_reference_live_site_cases_cover_prd_urls() -> Result<(), Box<dyn Error>> {
|
||||||
|
assert_prd_reference_urls_are_covered()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "live-site-smoke")]
|
#[cfg(feature = "live-site-smoke")]
|
||||||
fn assert_live_sites_render(cases: &[LiveSiteCase]) -> Result<(), Box<dyn Error>> {
|
fn assert_live_sites_render(cases: &[LiveSiteCase]) -> Result<(), Box<dyn Error>> {
|
||||||
let client = ServoSidecarClient::new()?;
|
let client = ServoSidecarClient::new()?;
|
||||||
|
|||||||
@@ -440,3 +440,7 @@ mod tests {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(test, feature = "live-site-smoke"))]
|
||||||
|
#[path = "web_surface_live_site_tests.rs"]
|
||||||
|
mod web_surface_live_site_tests;
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
use ely_domain::{BrowserTab, ProfileId, SpaceId, TabId, UrlText};
|
||||||
|
use gpui::{Bounds, point, px, size};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
services::prd_live_sites::{
|
||||||
|
LiveSiteCase, PRD_REFERENCE_SITE_CASES, PRD_TOP_SITE_CASES,
|
||||||
|
assert_prd_reference_urls_are_covered,
|
||||||
|
},
|
||||||
|
shell::{
|
||||||
|
web_surface_frame::WebSurfaceFrame,
|
||||||
|
web_surface_geometry::{WebSurfaceScrollOffset, WebSurfaceSize},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::WebSurfaceStore;
|
||||||
|
|
||||||
|
const LIVE_SURFACE_WIDTH: u32 = 934;
|
||||||
|
const LIVE_SURFACE_HEIGHT: u32 = 657;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn web_surface_cases_cover_prd_reference_urls() -> Result<(), Box<dyn Error>> {
|
||||||
|
assert_prd_reference_urls_are_covered()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn web_surface_opens_and_renders_prd_top_sites() -> Result<(), Box<dyn Error>> {
|
||||||
|
assert_web_surfaces_render(PRD_TOP_SITE_CASES)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn web_surface_opens_and_renders_prd_reference_sites() -> Result<(), Box<dyn Error>> {
|
||||||
|
assert_web_surfaces_render(PRD_REFERENCE_SITE_CASES)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_web_surfaces_render(cases: &[LiveSiteCase]) -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut store = WebSurfaceStore::new();
|
||||||
|
for case in cases {
|
||||||
|
let tab = web_tab(case.url)?;
|
||||||
|
let bounds = live_surface_bounds();
|
||||||
|
|
||||||
|
assert!(store.record_viewport_size(tab.id(), bounds), "{}", case.url);
|
||||||
|
let request = store
|
||||||
|
.prepare_request(&tab)
|
||||||
|
.ok_or_else(|| format!("missing web surface request for {}", case.url))?;
|
||||||
|
let snapshot = request.client.snapshot(request.snapshot_request)?;
|
||||||
|
let frame = WebSurfaceFrame::from_snapshot(
|
||||||
|
request.requested_url,
|
||||||
|
request.scroll_offset,
|
||||||
|
request.click_point,
|
||||||
|
request.typed_text,
|
||||||
|
snapshot,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
frame.size(),
|
||||||
|
WebSurfaceSize { width: LIVE_SURFACE_WIDTH, height: LIVE_SURFACE_HEIGHT },
|
||||||
|
"{}",
|
||||||
|
case.url
|
||||||
|
);
|
||||||
|
assert_eq!(frame.scroll_offset(), WebSurfaceScrollOffset::default(), "{}", case.url);
|
||||||
|
assert!(frame.url_label().contains(normalized_url(case.url)), "{}", frame.url_label());
|
||||||
|
assert!(frame.title_label().contains(case.title_fragment), "{}", frame.title_label());
|
||||||
|
assert_eq!(frame.detail_label(), "934x657", "{}", case.url);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn live_surface_bounds() -> Bounds<gpui::Pixels> {
|
||||||
|
Bounds::new(
|
||||||
|
point(px(0.0), px(0.0)),
|
||||||
|
size(px(LIVE_SURFACE_WIDTH as f32), px(LIVE_SURFACE_HEIGHT as f32)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn web_tab(url: &str) -> Result<BrowserTab, Box<dyn Error>> {
|
||||||
|
Ok(BrowserTab::new(TabId::new(), SpaceId::new(), ProfileId::new(), "Web", UrlText::parse(url)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalized_url(url: &str) -> &str {
|
||||||
|
url.trim_end_matches('/')
|
||||||
|
}
|
||||||
@@ -1,12 +1,31 @@
|
|||||||
#![cfg(feature = "servo-engine")]
|
#![cfg(feature = "servo-engine")]
|
||||||
|
|
||||||
use std::error::Error;
|
use std::{collections::BTreeSet, error::Error, fs, path::PathBuf};
|
||||||
|
|
||||||
#[path = "sidecar/support.rs"]
|
#[path = "sidecar/support.rs"]
|
||||||
mod support;
|
mod support;
|
||||||
|
|
||||||
use support::*;
|
use support::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sidecar_prd_reference_cases_cover_prd_urls() -> Result<(), Box<dyn Error>> {
|
||||||
|
let prd = fs::read_to_string(prd_path())?;
|
||||||
|
let prd_urls = prd_reference_urls(&prd);
|
||||||
|
let covered_urls = PRD_REFERENCE_SITE_COMPATIBILITY_CASES
|
||||||
|
.iter()
|
||||||
|
.map(|case| normalized_url(case.url))
|
||||||
|
.collect::<BTreeSet<_>>();
|
||||||
|
let missing_urls = prd_urls
|
||||||
|
.iter()
|
||||||
|
.filter(|url| !covered_urls.contains(url.as_str()))
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
assert!(missing_urls.is_empty(), "missing PRD sidecar smoke cases: {missing_urls:?}");
|
||||||
|
assert_eq!(prd_urls.len(), PRD_REFERENCE_SITE_COMPATIBILITY_CASES.len());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sidecar_opens_and_renders_prd_sites_to_rgba_files() -> Result<(), Box<dyn Error>> {
|
fn sidecar_opens_and_renders_prd_sites_to_rgba_files() -> Result<(), Box<dyn Error>> {
|
||||||
for case in PRD_SITE_COMPATIBILITY_CASES {
|
for case in PRD_SITE_COMPATIBILITY_CASES {
|
||||||
@@ -18,6 +37,25 @@ fn sidecar_opens_and_renders_prd_sites_to_rgba_files() -> Result<(), Box<dyn Err
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn prd_path() -> PathBuf {
|
||||||
|
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..").join("..").join("PRD.md")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prd_reference_urls(prd: &str) -> Vec<String> {
|
||||||
|
prd.lines()
|
||||||
|
.filter(|line| line.starts_with("[R"))
|
||||||
|
.filter_map(|line| {
|
||||||
|
let start = line.find("https://")?;
|
||||||
|
let url = line[start..].split_whitespace().next()?;
|
||||||
|
Some(normalized_url(url))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalized_url(url: &str) -> String {
|
||||||
|
url.trim().trim_end_matches('/').to_string()
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sidecar_opens_and_renders_prd_reference_sites_to_rgba_files() -> Result<(), Box<dyn Error>> {
|
fn sidecar_opens_and_renders_prd_reference_sites_to_rgba_files() -> Result<(), Box<dyn Error>> {
|
||||||
for case in PRD_REFERENCE_SITE_COMPATIBILITY_CASES {
|
for case in PRD_REFERENCE_SITE_COMPATIBILITY_CASES {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ pub(super) const PRD_REFERENCE_SITE_COMPATIBILITY_CASES: &[PrdSiteCompatibilityC
|
|||||||
title_fragment: "Tab Tiling",
|
title_fragment: "Tab Tiling",
|
||||||
},
|
},
|
||||||
PrdSiteCompatibilityCase { url: "https://www.gpui.rs/", title_fragment: "gpui" },
|
PrdSiteCompatibilityCase { url: "https://www.gpui.rs/", title_fragment: "gpui" },
|
||||||
PrdSiteCompatibilityCase { url: "https://docs.rs/gpui/latest/gpui/", title_fragment: "gpui" },
|
PrdSiteCompatibilityCase { url: "https://docs.rs/gpui", title_fragment: "gpui" },
|
||||||
PrdSiteCompatibilityCase {
|
PrdSiteCompatibilityCase {
|
||||||
url: "https://zed.dev/blog/videogame",
|
url: "https://zed.dev/blog/videogame",
|
||||||
title_fragment: "Leveraging Rust",
|
title_fragment: "Leveraging Rust",
|
||||||
|
|||||||
Reference in New Issue
Block a user