From 157d54a4d4312b6354945c8a148366eb3d189bd9 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Tue, 16 Jun 2026 13:16:35 +0300 Subject: [PATCH] feat(wasm): single-window web entry + trunk harness (issue #45) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The binary now compiles for wasm32 as a runnable web target: - Split the main-window render into `view_main`, bypassing the multi-window id dispatch the browser cannot use. - Add `run_web` / `boot_web`: a single-window `iced::application` entry for wasm (the browser canvas is the only window). Native keeps the multi-window `iced::daemon` via cfg-split in `main`. - Add a `console_error_panic_hook` (wasm-only) for readable panics. - Add `index.html` + `Trunk.toml` so `trunk serve` builds the wasm bundle (no-default-features → no solid3d) with COOP/COEP headers. Native build and all 75 tests unchanged. Browser runtime (rayon threads, canvas attach, real file I/O) is the next iteration. Co-Authored-By: Claude Opus 4.8 --- Cargo.lock | 11 +++++++++++ Cargo.toml | 3 +++ Trunk.toml | 16 ++++++++++++++++ index.html | 16 ++++++++++++++++ src/app/mod.rs | 28 ++++++++++++++++++++++++++++ src/app/view.rs | 7 +++++++ src/main.rs | 13 ++++++++++++- 7 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 Trunk.toml create mode 100644 index.html diff --git a/Cargo.lock b/Cargo.lock index 3c34e0f6..efa9b978 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,7 @@ version = "0.5.7" dependencies = [ "acadrust", "bytemuck", + "console_error_panic_hook", "cosmic-text", "flate2", "fontdb", @@ -977,6 +978,16 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + [[package]] name = "convert_case" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index fae4e8e2..15a5c814 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,3 +79,6 @@ acadrust = { git = "https://github.com/HakanSeven12/acadrust", branch = "main" } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] open = "5" ureq = { version = "3", default-features = false, features = ["rustls"] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +console_error_panic_hook = "0.1" diff --git a/Trunk.toml b/Trunk.toml new file mode 100644 index 00000000..e58869cd --- /dev/null +++ b/Trunk.toml @@ -0,0 +1,16 @@ +# Trunk config for the web (wasm) build of Open CAD Studio (issue #45). +# +# Build: trunk build --release +# Dev: trunk serve +# +# Requires: rustup target add wasm32-unknown-unknown +# cargo install trunk wasm-bindgen-cli + +[build] +target = "index.html" + +[serve] +# Cross-origin isolation headers enable SharedArrayBuffer, which wasm threads +# (rayon) need. GitHub Pages cannot set these; a self-hosted server (e.g. the +# Open-AEC host) can. Without them the app still runs single-threaded. +headers = { "Cross-Origin-Opener-Policy" = "same-origin", "Cross-Origin-Embedder-Policy" = "require-corp" } diff --git a/index.html b/index.html new file mode 100644 index 00000000..a58c5347 --- /dev/null +++ b/index.html @@ -0,0 +1,16 @@ + + + + + + Open CAD Studio + + + + + + diff --git a/src/app/mod.rs b/src/app/mod.rs index ae19fe7e..67b2e295 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1670,10 +1670,21 @@ impl OpenCADStudio { Task::batch([open_main, check_update, focus_cmd, cli_open, assoc_prompt]), ) } + + /// Single-window boot for the web build: no OS-window creation (the browser + /// canvas is the only window), no file-association registration or CLI file + /// open. Secondary manager windows are unavailable on the web for now. + #[cfg(target_arch = "wasm32")] + fn boot_web() -> (Self, Task) { + let mut s = Self::new(); + let focus = s.focus_cmd_input(); + (s, focus) + } } use std::path::PathBuf; +#[cfg(not(target_arch = "wasm32"))] pub fn run() -> iced::Result { iced::daemon( OpenCADStudio::boot, @@ -1744,3 +1755,20 @@ pub fn run() -> iced::Result { .theme(|state: &OpenCADStudio, _| state.active_theme.clone()) .run() } + +/// Single-window entry for the web (wasm) build. Uses `iced::application` +/// instead of `iced::daemon`: the browser canvas is the only window, so the +/// main-window view is rendered directly and the manager/dialog windows are +/// unavailable for now (see issue #45). Native keeps the multi-window `run`. +#[cfg(target_arch = "wasm32")] +pub fn run_web() -> iced::Result { + iced::application( + OpenCADStudio::boot_web, + OpenCADStudio::update, + OpenCADStudio::view_main, + ) + .subscription(OpenCADStudio::subscription) + .title(|_state: &OpenCADStudio| "Open CAD Studio".to_string()) + .theme(|state: &OpenCADStudio| state.active_theme.clone()) + .run() +} diff --git a/src/app/view.rs b/src/app/view.rs index 75339340..44703435 100644 --- a/src/app/view.rs +++ b/src/app/view.rs @@ -511,6 +511,13 @@ impl OpenCADStudio { ); } + self.view_main() + } + + /// The primary window: viewport, ribbon, tab bar, status bar. Split out of + /// `view` so the single-window web build can render it directly, bypassing + /// the multi-window id dispatch above (the web build has no extra windows). + pub fn view_main(&self) -> Element<'_, Message> { let i = self.active_tab; let tab = &self.tabs[i]; let is_paper = tab.scene.current_layout != "Model"; diff --git a/src/main.rs b/src/main.rs index 0a3427e9..c7cff8cf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,5 +28,16 @@ fn main() -> iced::Result { if std::env::var_os("WGPU_BACKEND").is_none() { std::env::set_var("WGPU_BACKEND", "dx12,vulkan"); } - app::run() + + // Web (wasm) uses the single-window entry; native uses the multi-window + // daemon. Trunk calls `main` from its generated JS bootstrap. + #[cfg(target_arch = "wasm32")] + { + console_error_panic_hook::set_once(); + app::run_web() + } + #[cfg(not(target_arch = "wasm32"))] + { + app::run() + } }