Skip to content

Capa de aplicación Rust

Claude edited this page Oct 26, 2026 · 8 revisions

Capa de aplicación Rust

English · Español

El crate de la app (proxy-builder, librería proxy_builder_lib, en src-tauri/) conecta Tauri con el crate puro proxy-core: construye la ventana y los plugins, es dueño de AppState (base de datos, almacén de blobs, clientes HTTP, trabajos), sirve el protocolo de imagen pbimg:// y registra todos los comandos. Esta página cubre la secuencia de arranque, el estado compartido, el protocolo y la caché de renders, la configuración de Tauri (ventana, CSP, capabilities, plugins) y un mapa de módulos del crate.

Puntos de entrada

Fichero Función
src-tauri/src/main.rs Seis líneas: fn main() { proxy_builder_lib::run() }. En las compilaciones release de Windows, #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] suprime la consola.
src-tauri/src/lib.rs pub fn run() (marcada con #[cfg_attr(mobile, tauri::mobile_entry_point)]): builder, plugins, protocolo, setup, generate_handler!. También declara todos los módulos y #![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used))].
src-tauri/build.rs tauri_build::build().
src-tauri/Cargo.toml Tipos de crate de la librería staticlib, cdylib, rlib; el nombre de la librería lleva el sufijo _lib para evitar un choque de nombres en Windows con el binario. Miembros del workspace: la app y crates/proxy-core. Ajustes del perfil de desarrollo (véase más abajo).

lib.rs es pequeño a propósito: solo arranca y registra. Si crece, mueve código a un módulo bajo commands/ o a otro sitio.

Secuencia de arranque

sequenceDiagram
    participant OS
    participant RUN as lib.rs run()
    participant TAU as Runtime de Tauri
    participant ST as closure de setup
    participant WV as WebView (ventana main, oculta)
    participant FE as main.tsx

    OS->>RUN: arranca proxy_builder_lib::run
    RUN->>TAU: Builder: plugins opener, dialog, updater, window-state
    RUN->>TAU: manage(Actualizador)
    RUN->>TAU: on_page_load(muestra la ventana al terminar, Finished)
    RUN->>TAU: register_asynchronous_uri_scheme_protocol("pbimg", protocol::manejador)
    TAU->>ST: setup(app)
    ST->>ST: Paths { data: app_data_dir, cache: app_cache_dir }
    ST->>ST: registro::iniciar(app_log_dir)
    ST->>ST: AppState::open(paths): dirs, library.db + migraciones, rellenar_refs, BlobStore, Recursos, Scryfall, Externo
    ST->>TAU: manage(AppState)
    ST->>ST: lanza una tarea: original::completar (marcos Original que falten)
    ST->>ST: lanza un hilo: tras 3 s muestra la ventana si sigue oculta
    TAU->>WV: crea la ventana (visible false, fondo 1d1b19)
    WV->>FE: carga index.html y JS
    FE->>FE: prepararTemasInstalados, ponerTemaAntesDeMontar
    FE->>WV: pinta el árbol de React
    FE->>TAU: getCurrentWindow().show()
    TAU-->>WV: ventana mostrada (fondo oscuro, tema ya aplicado)
    Note over RUN,TAU: Rust también la muestra en PageLoadEvent::Finished
Loading

Detalles y motivos:

  1. La ventana arranca oculta y oscura. tauri.conf.json fija "visible": false y "backgroundColor": "#1d1b19". Así se evita un destello blanco mientras carga el WebView.
  2. El plugin window-state no debe hacerla visible. tauri_plugin_window_state::Builder se configura con StateFlags::all() & !StateFlags::VISIBLE, de modo que un flag «visible» guardado no pueda mostrar la ventana antes de tiempo. La misma máscara la usa actualizar.rs → guardar_ventana antes de reiniciar para actualizar. El plugin solo se añade con #[cfg(desktop)] y es una dependencia solo de escritorio en Cargo.toml.
  3. Tres formas independientes de mostrar la ventana, para que nunca pueda quedarse oculta: on_page_load (Rust la muestra en PageLoadEvent::Finished), ensenarVentana() al final de src/main.tsx (llama a getCurrentWindow().show(), permitido por la capability core:window:allow-show) y un hilo de reserva en setup que la muestra a los 3 s si is_visible() es falso. Por qué no esperar al primer fotograma pintado: WebKitGTK no da fotogramas de requestAnimationFrame a una ventana oculta, lo que antes retrasaba la ventana hasta el respaldo de 3 s. El tema se aplica a <html> antes de que React monte (ponerTemaAntesDeMontar), así que mostrarla pronto no produce destellos.
  4. Los fallos de setup abortan la app. Los errores de AppState::open se convierten a String y se devuelven desde setup; run() entonces imprime Proxy Builder no ha podido arrancar: … por stderr y sale con estado 1 (no hay ventana donde mostrar un error).
  5. Orden de los plugins: opener, dialog y updater se registran primero; .manage(actualizar::Actualizador::default()) registra el estado propio del actualizador.
  6. Cuando el shell está montado, src/features/shell/use-deck-inicial.ts arranca el autoguardado (iniciarAutoguardado, 800 ms) y el store del plan (iniciarPlan, 100 ms), y después abrirDeckInicial abre el último mazo del ajuste last_deck_id o crea uno.

El registro se inicializa en setup con registro::iniciar(&app_log_dir): añade a proxy-builder.log, con tracing_subscriber::fmt hacia ese fichero (sin ANSI, sin target, nivel INFO, try_init para que un segundo inicio sea inofensivo). Su propósito declarado es demostrar que un mazo guardado no descarga nada: cada petición a Scryfall se registra como una línea con método, host, ruta y estado (scryfall/client.rs). Los fallos al crear el directorio o el fichero se ignoran en silencio. Otros módulos registran con tracing::info!/warn! (actualizador, temas, símbolos, modelos de OCR, fuentes externas).

AppState

src-tauri/src/state.rs. Se registra con app.manage(...), se inyecta en los comandos como State<'_, AppState> y se obtiene desde el AppHandle en las tareas lanzadas (app.state::<AppState>()).

Campo Tipo Significado
db store::db::Db Conexión SQLite tras un Mutex; WAL, foreign_keys=ON, synchronous=NORMAL; migraciones aplicadas al abrir (MIGRACIONES 1–5; solo se añaden, nunca se edita una ya publicada). Los trabajos largos sueltan el lock entre pasos.
blobs store::blobs::BlobStore Almacén de imágenes direccionado por contenido bajo paths.data/blobs.
paths Paths { data, cache } app_data_dir (biblioteca, imágenes, recursos) y app_cache_dir (derivados borrables).
scryfall Arc<Scryfall> Cliente con límite de frecuencia y reintentos para api.scryfall.com y las descargas de imágenes.
externo Arc<fuentes::Externo> Cliente con lista permitida para todo sitio que no sea Scryfall.
jobs jobs::Jobs Flags de cancelación de los trabajos en marcha.
miniaturas Mutex<HashMap<String,String>> Miniaturas de resultados de búsqueda: SHA-256 de la URL a la URL. Lo rellenan las búsquedas (commands/cards.rs → registrar_miniatura, resumir; se vacía al pasar de 5000 entradas) y lo lee la ruta /t/.
diseno diseno::recursos::Recursos Letras, packs de marcos y símbolos del diseñador, cargados al arrancar desde paths.data.
vistas Mutex<diseno::vista::Vistas> Diseños registrados para la vista previa en vivo (/d/), con tope de los 64 más recientes.
imagenes CacheImagenes Ilustraciones decodificadas para pintar diseños.
detalles biblioteca::CacheDetalles Detalles de la biblioteca por mazo, en memoria con clave updated_at.
renders tokio::sync::Semaphore Limita los renders de miniaturas simultáneos a available_parallelism() (4 si falla).
mesa Mutex<Option<mesa::sesion::Sesion>> La sesión de la mesa de juego, si hay una partida abierta (Mesa de juego).
red red::Red La red: la clave de la instalación, el nodo de iroh (se levanta al hacer falta), el papel P2P (Sala), el cliente del servidor de salas y su sala (Salon) (Juego en línea).
mirada Mutex<Option<red::mirada::Mirada>> Lo que ve un espectador (sin sesión, solo la vista y sus parches).
trabajos (privado) AtomicUsize Número de trabajos largos en marcha; véanse trabajo() / ocupado(). ocupado() es verdadero mientras corre un trabajo o hay una partida abierta (hay_partida()), así no corren ni la limpieza de blobs ni una actualización (que reinicia la app).

AppState::open(paths) crea ambos directorios, abre library.db, ejecuta una vez store::decks::rellenar_refs (rellena blob_refs para que la GC proteja las imágenes diseñadas), construye el almacén de blobs y los recursos del diseñador, y los dos clientes HTTP. Los tests construyen un AppState sobre directorios de tempfile (véanse los ayudantes estado() en render.rs, commands/cards.rs, diseno/*.rs).

Notas de concurrencia: db.conn() devuelve un MutexGuard o Internal si está envenenado; mantenlo poco tiempo. trabajo() devuelve una guarda cuyo Drop decrementa el contador, de modo que los retornos tempranos y el desenrollado por pánico la liberan.

El protocolo de imagen pbimg://

Se registra con register_asynchronous_uri_scheme_protocol(protocol::ESQUEMA, protocol::manejador). El manejador copia el AppHandle, lanza una tarea asíncrona por petición y responde mediante UriSchemeResponder. Solo se usa la ruta de la URL (peticion.uri().path()).

En el lado del WebView la base es convertFileSrc("", "pbimg"): pbimg://localhost/ en Linux/macOS y http://pbimg.localhost/ en Windows (src/lib/image-url.ts → raiz()), por lo que la CSP lista ambas.

Formas de URL

protocol.rs → leer_ruta convierte la ruta en Ruta (y no se acepta nada más):

Ruta Ruta Devuelve Producido por (src/lib/image-url.ts)
/r/<sha256>/<params>/<width> Render { blob, params, ancho } Miniatura JPEG de un blob guardado, procesada con las ediciones del mazo urlMiniatura(blob, PreviewParams, ancho)
/t/<sha256 of URL> Miniatura { huella } Imagen de un resultado de búsqueda (PNG o JPEG, detectado por los bytes mágicos) urlBusqueda(huella)
/d/<huella>/<width> Diseno { huella, ancho } Vista previa PNG de un diseño registrado con design_register urlDiseno
/c/<huella>/<width> Capa { huella, ancho } PNG de la carta sin su ilustración (design_overlay) urlCapa
/b/<sha256>/<side> Arte { blob, lado } Ilustración del almacén de blobs, reducida para que su lado largo sea de 1000 o 2000 px (JPEG, o PNG si tiene transparencia) urlArte(blob, 1000 | 2000)
/x/<pack>/<n> Extra { pack, indice } Imagen PNG extra de un pack de marcos descargable urlExtra(pack, n)
/f/<sha256>.woff2 Letra { huella } Fichero de letra de un tema de tweakcn instalado raizProtocolo() usado por src/lib/temas-instalados.ts
/m/<sha256>/<ancho> Mesa { imagen: ImagenMesa::Propia, ancho } JPEG de una cara de la mesa de juego preparada por la app (o de un tapete) urlMesa({ tipo: "propia" }, ancho)
/m/s/<id de Scryfall>/<cara>/<ancho> Mesa { imagen: ImagenMesa::Scryfall, ancho } JPEG de una impresión de Scryfall sin retocar usada en la mesa urlMesa({ tipo: "scryfall" }, ancho)

Validación, aplicada y probada con tests unitarios (los tests de protocol.rs las_rutas_raras_se_rechazan y otros):

  • <sha256> / <huella> deben pasar store::blobs::es_huella (exactamente 64 caracteres hexadecimales en minúscula), lo que además bloquea el path traversal.
  • <width> debe ser uno de ANCHOS = [146, 244, 488, 672, 745, 1000] (reflejado por ANCHOS en image-url.ts); <side> uno de LADOS_ARTE = [1000, 2000] (diseno/vista.rs).
  • <params> es el base64url (sin relleno) del JSON de PreviewParams (render.rs): edits, edge (oscurecido de bordes), back, bleedMm, bleedMode, fillCorners, card (CardSize). Máximo 2048 caracteres (MAX_PARAMS); debe decodificarse y deserializarse.
  • <pack> son 1–40 caracteres de [A-Za-z0-9_-]; <n> un entero no negativo.
  • Los anchos de /m/ tienen que ser de ANCHOS_MESA (los seis de ANCHOS más 1920, para el tapete); <id de Scryfall> tiene que pasar mesa::imagenes::es_id_scryfall (36 caracteres, dígitos hexadecimales y guiones) y <cara> es 0 o 1 (test las_rutas_de_la_mesa). Los ficheros salen de app_cache_dir/mesa/; con 745 o más se sirven tal cual y los anchos menores se reducen una vez en renders/m/ (mesa/imagenes.rs::servir). Véase Mesa de juego.
  • Códigos de estado: una forma reconocida pero no parseable es 400, una forma desconocida 404, un blob/diseño que falta 404, otro fallo de renderizado 500, una descarga remota fallida para /t/ 502.
  • Toda respuesta pone X-Content-Type-Options: nosniff y Access-Control-Allow-Origin: *; las correctas añaden Cache-Control: max-age=31536000, immutable, porque la URL contiene todo lo que cambia la imagen.

Por qué URLs en lugar de bytes por IPC: ningún byte de imagen cruza el IPC y WebKit cachea por URL. La URL lleva todos los parámetros de renderizado, así que cambiar una edición o un ajuste simplemente produce una URL nueva.

Concurrencia

Las rutas /r/, /d/, /c/, /b/ primero hacen await estado.renders.acquire() (semáforo dimensionado al número de núcleos) y luego ejecutan el trabajo de CPU en tauri::async_runtime::spawn_blocking. /x/ y /f/ son lecturas pequeñas de fichero y se saltan el semáforo. /m/ corre en spawn_blocking sin el semáforo: las imágenes de la mesa se preparan antes, así una petición es leer un fichero o, una vez por ancho, reducirlo. Como el frontend solo monta <img> para las páginas visibles (useVisible, véase Vista previa en vivo), la cola del semáforo se mantiene corta.

La ruta /t/

  1. Busca app_cache_dir/renders/t/<hash>.jpg; lo sirve si existe.
  2. Si no, busca el hash en AppState.miniaturas; un hash desconocido es 404.
  3. Descarga: las URLs que empiezan por https://cards.scryfall.io/ usan scryfall.download; cualquier otra pasa por externo.bajar(url, MAX_IMAGEN) y fuentes::limpiar_imagen (decodificar y recodificar), de modo que solo se obtienen fuentes de la lista permitida.
  4. Escribe el fichero de forma atómica en la caché y lo devuelve.

Así, el WebView nunca contacta con ningún host de imágenes; la CSP de img-src solo permite 'self' pbimg: http://pbimg.localhost data: blob:.

render.rs: miniaturas y caché

src-tauri/src/render.rs convierte un blob más unos ajustes en píxeles, tanto para la pantalla como para el PDF.

Elemento Comportamiento
PreviewParams (#[derive(TS)]) Lo que la pantalla pone en la URL. Rust decide si el oscurecido de bordes se aplica a esta imagen a partir de su origen y de si es un reverso (proxy_core::imaging::pipeline::edge_for), la misma regla que al exportar.
spec_de_miniatura(blob, img, params, ancho) RenderSpec con target: SpecTarget::Width(ancho), calidad JPEG 85.
spec_de_casilla(slot, img, edits, settings) RenderSpec para una casilla del PagePlan a resolución de impresión: el tamaño de carta es el recorte de la casilla (así funcionan las cartas grandes), bleed_mm es el bleed_full_mm de la casilla, SpecTarget::Dpi(quality.dpi()), codificación según los ajustes de impresión; las ediciones solo se aplican si slot.apply_edits.
AppState::carpeta_renders() paths.cache/renders.
AppState::leer_blob(hash) Lee el fichero localizado por el almacén de blobs, sondea el tamaño, lee origin de la tabla blobs; NotFound (parámetro blob) si falta.
AppState::miniatura(blob, params, ancho) Fichero de caché renders/<RenderSpec::key()>.jpg; si no está, renderiza y luego lo escribir_atomico; los errores al escribir la caché se ignoran.
render_para_pdf(state, spec) Renderiza para imprimir sin caché en disco («pesa demasiado»).
From<ImagingError> for AppError Véase IPC y manejo de errores.

RenderSpec::key() es el SHA-256 de (PIPELINE_VERSION, spec) serializado como JSON (proxy-core/src/imaging/pipeline.rs, PIPELINE_VERSION = 5). Cualquier cambio en el spec o un aumento de la versión de la canalización da un nombre de fichero distinto, lo que invalida los ficheros de caché antiguos sin borrarlos (los elimina cache_clear). Las claves de la caché PNG del diseñador incluyen su propia versión del motor (proxy_core::diseno::VERSION_MOTOR).

Organización de la caché bajo app_cache_dir/renders/:

Ruta Contenido
<spec key>.jpg Miniaturas de blobs
t/<url hash>.jpg Imágenes de resultados de búsqueda (pueden ser bytes PNG pese a la extensión; el protocolo detecta el tipo)
d/<huella>-<width>.png Vistas previas en vivo de diseños
c/<huella>-<width>.png, c/<huella>.json Capa del diseño y la información de su ventana de ilustración
b/<blob>-<side>.jpg o .png Ilustración reducida

cache_stats y cache_clear (commands/library.rs) miden y hacen remove_dir_all exactamente de esta carpeta. Regla: aquí solo viven derivados; las imágenes de los mazos nunca están en este árbol. Un test unitario de commands/library.rs comprueba que la carpeta de renders está bajo paths.cache y no bajo paths.data.

El escritor atómico genérico es store/blobs.rs → escribir_atomico(destino, bytes): crea los directorios padre, escribe <name>.<ext>.part, hace fsync y renombra. Un fallo nunca deja un fichero a medio escribir con su nombre definitivo. Los PDF, los ficheros de paquete, las cachés y los blobs lo usan todos.

Configuración de Tauri

src-tauri/tauri.conf.json

Clave Valor / significado
productName, identifier, version Proxy Builder, un identificador de paquete en DNS inverso (léelo en el fichero), 2.5.3 (se mantiene a la par con Cargo.toml y package.json; véase Versiones y actualizaciones automáticas)
build beforeDevCommand: pnpm dev, devUrl: http://localhost:1420, beforeBuildCommand: pnpm build, frontendDist: ../dist
app.windows[0] label main, 1440x900, mínimo 960x640, visible: false, backgroundColor: #1d1b19
app.security.csp default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; img-src 'self' pbimg: http://pbimg.localhost data: blob:; style-src 'self' 'unsafe-inline'; font-src 'self' data: pbimg: http://pbimg.localhost; connect-src ipc: http://ipc.localhost; object-src 'none'; base-uri 'self'; form-action 'none'. devCsp es null (el HMR de Vite funciona en desarrollo).
bundle targets: all, createUpdaterArtifacts: true, categoría Utility; el paquete Debian tiene depends: [] (aquí no se fuerza WebKitGTK como dependencia)
plugins.updater Clave pública de Minisign, endpoint https://github.com/PoolFlamingo/proxy-builder/releases/latest/download/latest.json, requireSignedVersion: true, installMode: passive en Windows

Consecuencias de la CSP: el WebView no puede hacer fetch de nada fuera del IPC, no puede cargar scripts ni imágenes remotos y no puede enviar formularios. 'wasm-unsafe-eval' deja al WebView compilar WebAssembly (la física de los dados 3D, véase Mesa de juego); no permite eval de JavaScript. style-src 'unsafe-inline' hace falta por los estilos en línea de React y el <style> inyectado para los temas instalados. Las letras de los temas instalados vienen de pbimg://…/f/…. El test la_configuracion_del_actualizador_es_valida de src-tauri/src/actualizar.rs lee tauri.conf.json y falla si cambian la firma del actualizador, la forma del endpoint o la cabecera de la clave pública.

src-tauri/capabilities/default.json

Se aplica a la ventana main y concede únicamente: core:default, core:window:allow-show, core:window:allow-destroy, core:window:allow-set-fullscreen, opener:default, dialog:default, window-state:default. La descripción enuncia la regla: ningún permiso fs; todo el acceso a disco pasa por comandos de Rust. core:window:allow-show deja que main.tsx muestre la ventana; core:window:allow-destroy deja que la guarda de cierre (ConfirmarCierre, Ajustes y shell) la cierre: con un escuchante de JavaScript en onCloseRequested, Tauri deja de cerrar la ventana por su cuenta y lo hace destroy(), y ese permiso no está incluido en core:default ni en core:window:default, así que sin la entrada explícita la ventana no se podría cerrar (la compilación valida el identificador). core:window:allow-set-fullscreen es para el botón de pantalla completa de la mesa de juego (src/features/mesa/barra-mesa.tsx). El plugin del actualizador no tiene entrada de capability porque solo se usa desde Rust (actualizar.rs, commands/actualizar.rs). Los comandos propios registrados con generate_handler! no están limitados por una lista de permisos por comando en esta configuración (la app no define un conjunto de permisos propio; Deducido de que el fichero de capabilities solo contiene permisos de plugins y del core).

Plugins

Plugin (crate) Usado desde Para
tauri-plugin-dialog JS (src/lib/dialogos.ts) Diálogos de abrir/guardar/carpeta
tauri-plugin-opener Rust (OpenerExt en commands/export.rs, commands/temas.rs) Abrir ficheros exportados, mostrarlos en su carpeta, abrir la página de la comunidad de tweakcn
tauri-plugin-updater Solo Rust (actualizar.rs) Comprobar, descargar, verificar la firma, instalar
tauri-plugin-deep-link (escritorio) Solo Rust (enlace.rs, montado en lib.rs) Enlaces de invitación proxybuilder://; sin entrada en las capacidades porque la pantalla nunca lo llama
tauri-plugin-single-instance (escritorio, feature deep-link) Solo Rust (lib.rs), no con PB_PERFIL Un segundo arranque pasa sus argumentos (el enlace) a la app abierta
tauri-plugin-window-state (escritorio) Plugin + save_window_state de Rust Recordar tamaño, posición, maximizada; nunca la visibilidad

Otras dependencias de plataforma: gtk en Linux y arboard en el resto, ambas para leer la imagen del portapapeles desde Rust (diseno/portapapeles.rs; WebKitGTK rechaza navigator.clipboard.read() en el webview).

Notas sobre los perfiles de Cargo (src-tauri/Cargo.toml)

  • [profile.dev] debug = "line-tables-only", split-debuginfo = "packed", dependencias con debug = false: mantiene target/ pequeño.
  • [profile.dev.package."*"] opt-level = 2: las dependencias (image, resize, PDF) se compilan optimizadas incluso en desarrollo, porque el procesado de imagen sin optimizar vuelve inutilizable la vista previa bajo pnpm tauri dev.
  • reqwest usa rustls con default-features = false; el CI falla si aparece OpenSSL en cargo tree.
  • Dependencias de la mesa de juego: getrandom (la semilla de cada partida, de la entropía del sistema), regex (el «+N/+N» fijo de auras y equipos) y flate2 con el motor zlib-rs (la partida guardada; el mismo zlib-rs que ya trae zip).

Mapa de módulos del crate de la app

Módulo Responsabilidad Página con más detalle
commands/* Comandos de Tauri, un fichero por área IPC y manejo de errores
error.rs AppError, ErrorCode, implementaciones de From IPC y manejo de errores
state.rs, jobs.rs, registro.rs Estado compartido, flags de trabajos, fichero de registro esta página
protocol.rs, render.rs pbimg:// y renders esta página, Vista previa en vivo
store/ (db, blobs, decks, presets, settings, colecciones, etiquetas, migrations/) Biblioteca SQLite y almacén de blobs Almacenamiento y blobs
package.rs Paquetes ZIP .pbdeck Formato del paquete de mazo
biblioteca.rs Detalles por mazo para la biblioteca (colores, clase, precio, idiomas), en caché por updated_at Biblioteca de mazos
scryfall/ (client, rate, cache, models, resolve, busquedas, fichas, fichas_auto, nombres) Acceso a Scryfall, catálogo de nombres, resolución de listas Integración con Scryfall
archidekt.rs Importación desde una URL de Archidekt (ids exactos de Scryfall) Integración con Scryfall
fuentes/ (cockatrice, mtgio, mtgjson, mod.rs con Externo) Fuentes que no son Scryfall tras una lista permitida Fuentes externas
ocr.rs, originales.rs OCR opcional y detección del nombre original Identificación de cartas y OCR
diseno/ Parte de la app del diseñador de cartas (borradores, renderizado con recursos, vista previa en vivo, ilustración, exportación a PNG, lote, descarga de packs y letras, portapapeles) Capa de aplicación del diseñador
temas.rs Temas y letras de tweakcn Temas y skins
actualizar.rs Envoltorio del actualizador Versiones y actualizaciones automáticas
enlace.rs Enlaces de invitación proxybuilder://unirse/<código>: validación, enlace_pendiente, enlace_invitacion Juego en línea
mesa/ (sesion, preparar, imagenes, guardada, tapete, ejemplo) La sesión de la mesa de juego alrededor del crate mesa, la preparación de decks e imágenes, la partida guardada y el tapete Mesa de juego
red/ (perfil, identidad, nodo, protocolo, codigo, anfitrion, invitado, mirada, imagenes, latencia, mesa, colores, servidor/) y commands/{red,salas}.rs El juego en red: perfiles de debug, identidad, nodo de iroh, protocolo de la partida, código directo, host e invitado, espectadores, imágenes por la red, el cliente del servidor de salas y la sala en la app Juego en línea

Invariantes y trampas conocidas

  • Nunca bloquees el runtime asíncrono con trabajo de CPU. Usa spawn_blocking (como hacen export_pdf, los manejadores del protocolo y en_hilo en commands/diseno.rs).
  • Mantén state.trabajo() en cada trabajo largo o blob_gc y update_install podrían ejecutarse bajo él. Una partida abierta cuenta como ocupado por sí sola (hay_partida()).
  • cache_clear solo debe tocar renders/. Una nueva ubicación de caché para datos críticos del mazo rompería esta garantía; ponlos en app_data_dir y en el almacén de blobs.
  • Añade las rutas nuevas del protocolo con un test en protocol.rs tanto de aceptación como de rechazo (el path traversal, las longitudes de hash incorrectas, el hexadecimal en mayúsculas y los anchos incorrectos ya están cubiertos).
  • No edites migraciones ya publicadas; añade 0006_*.sql y añádelo a MIGRACIONES en store/db.rs.
  • Almacén de blobs frente a caché: el almacén de blobs está en app_data_dir porque los mazos dependen de él; la GC conserva las imágenes de menos de 24 h (GRACIA_MS) y todo lo referenciado por deck_cards, decks.default_back_blob, blob_refs o el ajuste del reverso genérico.
  • Las compilaciones release ocultan la consola en Windows; usa el fichero de registro en app_log_dir para depurar el comportamiento de red.

Tests que vigilan esta capa

Dónde Qué
src-tauri/src/protocol.rs (mod tests) Las rutas válidas se parsean; se rechazan las malformadas, el traversal, los anchos/lados incorrectos, los nombres de pack incorrectos y .ttf en lugar de .woff2; las rutas de la mesa de juego (las_rutas_de_la_mesa)
src-tauri/src/mesa/ (sesion.rs, guardada.rs, preparar.rs) Los parches dan la vista entera, los rechazos llevan su motivo, reiniciar, una partida abierta deja la app ocupada, guardar y seguir conserva la huella, la segunda preparación no pinta nada
src-tauri/src/render.rs (mod tests) Construcción de RenderSpec a partir de casillas, conversión de errores
src-tauri/src/jobs.rs alta_cancelar_y_baja
src-tauri/src/error.rs Serialización de errores
src-tauri/src/actualizar.rs La configuración del actualizador en tauri.conf.json es válida
src-tauri/src/commands/library.rs La carpeta de la caché está bajo la caché, no bajo los datos
src-tauri/src/commands/carpetas.rs la_carpeta_de_una_ruta

Por dónde empezar para ampliarlo

  • Comando o trabajo nuevo: IPC y manejo de errores.
  • Recurso compartido nuevo: añade un campo a AppState e inicialízalo en AppState::open; haz que los tests lo construyan mediante Paths sobre directorios temporales.
  • Plugin nuevo: añade el crate, .plugin(...) en lib.rs, un permiso en capabilities/default.json solo si lo usa el WebView, y actualiza la CSP solo si necesita orígenes nuevos (evítalo; es preferible un comando de Rust).

Véase también

Documentation · Documentación

Language: English · Español

Use and build · Uso y desarrollo

Architecture · Arquitectura

Data · Datos

Getting cards · Conseguir cartas

Layout, images, PDF · Maquetación, imagen y PDF

Card designer · Diseñador de cartas

Game table · Mesa de juego

Frontend · Frontend

Reference · Referencia

Clone this wiki locally