Crear ventanas en tiempo de ejecución
Construir una ventana desde Rust y desde el frontend, la regla de las etiquetas únicas, y los tres patrones que cubren casi todos los casos: la ventana única, la ventana por documento y la efímera.
Construir una ventana
Desde Rust, con el constructor:
use tauri::{WebviewUrl, WebviewWindowBuilder};let win = WebviewWindowBuilder::new( app, "settings", // la etiqueta, única WebviewUrl::App("ajustes.html".into()), // una ruta de tu frontend).title("Ajustes").inner_size(520.0, 620.0).resizable(false).build()?;Y desde el frontend, con el equivalente en JavaScript. Las mismas opciones, los mismos nombres en su convención.
La WebviewUrl acepta tres formas y conviene conocerlas: una ruta dentro de tu frontend, una URL externa —con todo lo que el nivel 32 va a decir sobre eso— o una página en blanco a la que después se le inyecta contenido.
La regla de la etiqueta
Dos ventanas no pueden tener la misma etiqueta al mismo tiempo. Intentar crear una con una etiqueta que ya existe falla, y esa es exactamente la situación que se produce cuando el usuario pulsa dos veces «Ajustes».
De ahí sale el patrón que hay que escribir siempre:
// El patrón de la ventana única: si existe, enfócala; si no, créala.fn abrir_ajustes(app: &tauri::AppHandle) -> tauri::Result<()> { if let Some(w) = app.get_webview_window("settings") { w.set_focus()?; // ya está abierta: tráela al frente w.unminimize()?; // y desminimízala, que es lo que se olvida return Ok(()); } WebviewWindowBuilder::new(app, "settings", WebviewUrl::App("ajustes.html".into())) .title("Ajustes") .build()?; Ok(())}Las dos líneas del primer bloque son el noventa por ciento de la calidad percibida de esta funcionalidad: enfocar y desminimizar. Una aplicación que no desminimiza deja al usuario pulsando un botón que aparentemente no hace nada, porque la ventana está ahí pero encogida en la barra.
Los tres patrones
Para el segundo patrón, una advertencia sobre la etiqueta: hay que sanearla. Si la derivas de algo que viene del usuario —un nombre de fichero, un título— puede llevar caracteres que la etiqueta no admite, o ser tan larga que resulte inmanejable. Derivarla de un identificador interno, o de un hash corto, evita el problema entero.
Los permisos de una ventana creada
Es lo que se olvida y produce una ventana que se abre y no funciona.
Las capabilities apuntan a ventanas por su etiqueta, y una ventana nueva no hereda los permisos de la que la creó. Si tu fichero de capabilities dice "windows": ["main"], la ventana de ajustes no tiene nada concedido: su JavaScript se ejecuta, se pinta, y cualquier invoke se rechaza.
Hay dos formas de resolverlo, y la elección importa:
// A · una capability que aplica a varias ventanas nombradas{ "identifier": "ui", "windows": ["main", "settings"], "permissions": ["core:default"] }// B · una capability propia para la ventana secundaria, más estrecha{ "identifier": "settings-only", "windows": ["settings"], "permissions": ["core:default", "store:allow-set"] }La segunda es la que este track recomienda, y por la razón de siempre: una ventana de ajustes no necesita lo mismo que la principal, y darle solo lo suyo acota el daño si algo se ejecuta ahí que no debería. La fase V desarrolla el criterio.
Para las ventanas de etiqueta generada, las capabilities admiten patrones con comodín, de forma que doc-* cubre todas las del segundo patrón sin tener que conocer sus nombres por adelantado.