feat(cosmobiologia): crate WASM + fallback inteligente + DEPLOY.md (fase 3b)
Cierra el requerimiento del módulo web. El cliente puede correr en modo WASM (render local, scrubbing instantáneo, sin round-trip) o caer al SSR (server compone el SVG) si el bundle WASM no está desplegado. Switch automático sin configuración. cosmobiologia-web (crate nuevo, cdylib + rlib): - `lib.rs` con un único export wasm-bindgen `render_model_to_svg(json, size, rot_offset_deg) -> String` que deserializa un `RenderModel`, llama `compose_wheel` + `draw_commands_to_svg` de cosmobiologia-render, y devuelve el SVG inline listo para `wheel.innerHTML = svg`. - Cargo.toml con `wasm-bindgen` + `getrandom` con feature `wasm_js` solo bajo `target_arch = "wasm32"` (en nativo no se arrastran). - `.cargo/config.toml` con `--cfg getrandom_backend="wasm_js"` para que la transitividad `uuid → cosmobiologia-model → cosmobiologia-render` compile a wasm32-unknown-unknown. - `cargo check -p cosmobiologia-web` pasa en nativo (valida la signature). Build WASM real lo dispara el usuario con `wasm-pack build --target web --out-dir ../../../apps/ cosmobiologia-server/static/wasm` — comando documentado en DEPLOY.md y en doc del crate. cosmobiologia-server — soporte cliente WASM: - Nuevo flag `--static-wasm <dir>` (default = static/wasm relativo al cwd). Si el directorio existe, los archivos WASM se sirven en `/static/wasm/*`. Si no existe, devuelve 404 y el cliente cae al SSR. - ServeDir de `tower-http` para fileserver simple. index.html: - Nueva función `tryLoadWasm()` que hace `import dinámico` del módulo WASM al boot. Si carga OK, `wasm` global queda set; si falla (archivo no existe o error de WASM), se loguea info y sigue. - `refreshSelected()` ahora hace fetch del RenderModel JSON (`/api/sky` o `/api/charts/:id/render`); si hay WASM, llama `wasm.render_model_to_svg(json)` localmente; si no hay WASM o el render WASM falla, hace fetch del SVG SSR como fallback. - Info row muestra "WASM" o "SSR" según el modo activo — visualmente claro qué pipeline está corriendo. cosmobiologia-server/DEPLOY.md (nuevo): - Build del binario + build del WASM (con wasm-pack). - systemd service template (sandboxing básico: ProtectSystem strict, ProtectHome, PrivateTmp, NoNewPrivileges). - Caddyfile y nginx para reverse proxy con TLS. - DNS: A records para cosmobiologia.gioser.net + api.*. - CORS: warnings sobre permissive vs producción multi-usuario. - Separación demo público (DB vacía en VPS) vs desktop personal (DB compartida en `~/.local/share/cosmobiologia/`). - Backup con SQLite `.backup`. - Smoke test post-deploy con curl. - Tabla de referencia de TODOS los endpoints. Tests: 10 verdes (cosmobiologia-render::math). El cliente WASM no agrega tests propios — la lógica testeable vive en render. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
# Forzamos el backend `wasm_js` de getrandom para el target
|
||||
# wasm32-unknown-unknown — sin esta flag, el getrandom transitivo
|
||||
# vía `uuid → cosmobiologia-model → cosmobiologia-render` falla a
|
||||
# compilar a WASM con "wasm32-unknown-unknown targets are not
|
||||
# supported by default".
|
||||
[target.wasm32-unknown-unknown]
|
||||
rustflags = ['--cfg', 'getrandom_backend="wasm_js"']
|
||||
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "cosmobiologia-web"
|
||||
version = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
license = { workspace = true }
|
||||
description = "Cosmobiología — cliente WASM. Reusa cosmobiologia-render para componer la rueda en SVG localmente, sin round-trip al server por cada interacción."
|
||||
|
||||
[dependencies]
|
||||
cosmobiologia-render = { path = "../cosmobiologia-render" }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
# wasm-bindgen solo se compila para target wasm32; en nativo el
|
||||
# crate igual compila (rlib), pero esta API no se expone.
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
wasm-bindgen = { workspace = true }
|
||||
# Backend de getrandom para WASM: pide al embedder (browser) que
|
||||
# provea la randomness vía Web Crypto. Se activa con el cfg
|
||||
# `getrandom_backend = "wasm_js"` desde .cargo/config.toml.
|
||||
getrandom = { version = "0.3", features = ["wasm_js"] }
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
@@ -0,0 +1,73 @@
|
||||
//! `cosmobiologia-web` — cdylib WASM que renderiza la rueda
|
||||
//! astrológica desde el browser, sin round-trip al server por cada
|
||||
//! interacción.
|
||||
//!
|
||||
//! ## Flujo
|
||||
//!
|
||||
//! 1. El cliente JS hace `await fetch('/api/sky')` o
|
||||
//! `/api/charts/:id/render?...` y recibe un `RenderModel` JSON.
|
||||
//! 2. JS llama `render_model_to_svg(json)` (exportado desde WASM) que
|
||||
//! deserializa + corre `cosmobiologia_render::compose_wheel` +
|
||||
//! serializa SVG.
|
||||
//! 3. JS hace `wheelContainer.innerHTML = svg`.
|
||||
//!
|
||||
//! ## Build
|
||||
//!
|
||||
//! ```bash
|
||||
//! cargo install wasm-pack # una vez
|
||||
//! cd crates/modules/cosmobiologia/cosmobiologia-web
|
||||
//! wasm-pack build --target web --out-dir ../../../../apps/cosmobiologia-server/static/wasm
|
||||
//! ```
|
||||
//!
|
||||
//! Esto produce un módulo ES6 (`cosmobiologia_web.js` +
|
||||
//! `cosmobiologia_web_bg.wasm`) que el `index.html` del server
|
||||
//! importa con `import init, { render_model_to_svg } from
|
||||
//! '/static/wasm/cosmobiologia_web.js';`.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
// La API pública SOLO se expone con `wasm-bindgen` en target
|
||||
// wasm32. En nativo (rlib) el crate compila para validar la
|
||||
// signature pero no exporta nada — los tests del render ya viven
|
||||
// en `cosmobiologia-render::math`.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod wasm {
|
||||
use cosmobiologia_render::{
|
||||
compose_wheel, draw_commands_to_svg, CompositionOpts, RenderModel,
|
||||
};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// Renderea un `RenderModel` (JSON string) como SVG. El JSON sale
|
||||
/// de `/api/sky` o `/api/charts/:id/render` del server.
|
||||
///
|
||||
/// `size` es el lado del cuadrado contenedor en px (default 600).
|
||||
/// `rot_offset_deg` permite rotar la vista (jog-dial / preview).
|
||||
#[wasm_bindgen]
|
||||
pub fn render_model_to_svg(json: &str, size: f32, rot_offset_deg: f32) -> Result<String, JsValue> {
|
||||
let model: RenderModel = serde_json::from_str(json)
|
||||
.map_err(|e| JsValue::from_str(&format!("parse RenderModel: {}", e)))?;
|
||||
let opts = CompositionOpts {
|
||||
size: if size > 0.0 { size } else { 600.0 },
|
||||
rot_offset_deg,
|
||||
include_bodies: true,
|
||||
};
|
||||
let cmds = compose_wheel(&model, &opts);
|
||||
Ok(draw_commands_to_svg(&cmds, opts.size))
|
||||
}
|
||||
|
||||
/// Hook de inicialización opcional — wasm_pack lo invoca al
|
||||
/// cargar el módulo. Útil para instalar un panic hook hacia
|
||||
/// `console.error`. Por ahora no-op.
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn main_js() {}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn _native_marker() {
|
||||
// Sin target wasm32, el crate solo expone el render como
|
||||
// transitivo. Esta función vive para que `cargo check -p
|
||||
// cosmobiologia-web` valide la compilación nativa sin
|
||||
// wasm-bindgen — útil en CI y en desarrollo desktop.
|
||||
let _ = std::any::type_name::<cosmobiologia_render::RenderModel>();
|
||||
}
|
||||
Reference in New Issue
Block a user