feat(wasm): single-window web entry + trunk harness (issue #45)

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 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-06-16 13:16:35 +03:00
1 parent b2e20c95fe
commit 157d54a4d4
7 files changed
+93 -1

No files matched your search

+28
View File
@@ -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<Message>) {
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()
}
+7
View File
@@ -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";
+12 -1
View File
@@ -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()
}
}