Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'

This commit is contained in:
ruv
2026-02-28 14:39:40 -05:00
7854 changed files with 3522914 additions and 0 deletions

View File

@@ -0,0 +1,140 @@
//! RuVector Desktop — wry webview wrapping the Causal Atlas dashboard.
//!
//! Embeds the entire Vite-built dashboard (HTML, JS, CSS, WASM) at compile
//! time via rust-embed, serves it from a tiny background HTTP server, and
//! opens a native webview window. Single binary, no external dependencies.
use rust_embed::Embed;
use std::net::TcpListener;
use std::sync::Arc;
use std::thread;
use tao::dpi::LogicalSize;
use tao::event::{Event, WindowEvent};
use tao::event_loop::{ControlFlow, EventLoop};
use tao::window::WindowBuilder;
use wry::WebViewBuilder;
/// All files from `../../rvf/dashboard/dist/` are embedded at compile time.
/// This includes index.html, JS chunks, CSS, and the WASM solver binary.
#[derive(Embed)]
#[folder = "../rvf/dashboard/dist/"]
struct DashboardAssets;
/// Find an available port to serve on.
fn find_port() -> u16 {
let listener = TcpListener::bind("127.0.0.1:0").expect("bind ephemeral port");
listener.local_addr().unwrap().port()
}
/// Start a tiny HTTP server that serves embedded dashboard files.
fn start_asset_server(port: u16) {
let addr = format!("127.0.0.1:{port}");
let server = Arc::new(tiny_http::Server::http(&addr).expect("start HTTP server"));
// Spawn worker threads to handle requests
for _ in 0..2 {
let srv = Arc::clone(&server);
thread::spawn(move || {
loop {
let request = match srv.recv() {
Ok(r) => r,
Err(_) => break,
};
let url_path = request.url().trim_start_matches('/');
let path = if url_path.is_empty() { "index.html" } else { url_path };
// Try to find the embedded file
let response = match DashboardAssets::get(path) {
Some(file) => {
let mime = mime_guess::from_path(path).first_or_octet_stream();
let data = file.data.to_vec();
tiny_http::Response::from_data(data)
.with_header(
tiny_http::Header::from_bytes(
b"Content-Type",
mime.as_ref().as_bytes(),
)
.unwrap(),
)
.with_header(
tiny_http::Header::from_bytes(
b"Access-Control-Allow-Origin",
b"*",
)
.unwrap(),
)
}
None => {
// SPA fallback: serve index.html for hash-routed paths
match DashboardAssets::get("index.html") {
Some(file) => {
let data = file.data.to_vec();
tiny_http::Response::from_data(data).with_header(
tiny_http::Header::from_bytes(
b"Content-Type",
b"text/html; charset=utf-8",
)
.unwrap(),
)
}
None => tiny_http::Response::from_string("Not Found")
.with_status_code(404),
}
}
};
let _ = request.respond(response);
}
});
}
}
fn main() {
let port = find_port();
let url = format!("http://127.0.0.1:{port}");
// Start embedded asset server in background
start_asset_server(port);
// Give server a moment to bind
thread::sleep(std::time::Duration::from_millis(50));
// Create native window + webview
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("RuVector — Causal Atlas")
.with_inner_size(LogicalSize::new(1400.0, 900.0))
.with_min_inner_size(LogicalSize::new(800.0, 500.0))
.build(&event_loop)
.expect("create window");
let _webview = WebViewBuilder::new()
.with_url(&url)
.with_devtools(cfg!(debug_assertions))
.with_initialization_script(
r#"
// Inject desktop app context so the dashboard knows it's running natively
window.__RUVECTOR_DESKTOP__ = true;
window.__RUVECTOR_VERSION__ = '2.0.0';
"#,
)
.build(&window)
.expect("create webview");
println!("RuVector Desktop v2.0.0");
println!(" Dashboard: {url}");
println!(" Window: 1400x900");
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
if let Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} = event
{
*control_flow = ControlFlow::Exit;
}
});
}