Repository navigation
IPC and Error Handling
English · Español
Everything the screen asks of Rust goes through one typed function, call() in src/lib/ipc.ts; everything Rust pushes back goes through typed events listened to with on(). Failures always arrive as an AppError { code, params } that the screen turns into a sentence in the current language. This page lists all 177 commands, the error model, the generated TypeScript bindings, the long-job protocol, and the file dialogs.
src/lib/ipc.ts exports:
| Export | Role |
|---|---|
interface Commands |
One entry per Rust command: name: { args: {...}; result: T }. The compiler checks every call() against it. |
call<C extends keyof Commands>(cmd, args?) |
Thin wrapper over invoke from @tauri-apps/api/core. The only place invoke is imported (verified by grep: no other file in src/ calls invoke). |
interface Events |
Event name to payload type for the events the screen listens to with on() (19 entries). The online-play notices (red://aviso, salas://…) and mesa://originales are not in it: they are listened to with listen directly (see Events). |
on(evento, f) |
Dynamic-imports listen from @tauri-apps/api/event; returns the unlisten function (as a promise). |
Conventions that bite:
-
Argument names are camelCase on the JS side. Rust parameters are snake_case; Tauri maps them:
job_idisjobId,scryfall_idisscryfallId,type_filteristypeFilter,highres_onlyishighresOnly. Struct fields inside payloads follow each struct's#[serde(rename_all = "camelCase")]. -
Unit results become
null(result: nullinCommands), andOption<T>becomesT | null. -
Commandsis hand-maintained. The argument and result types it references are generated (see ts-rs bindings), but the list itself is not. Adding a Rust command without aCommandsentry compiles fine and simply cannot be called from typed code. Today the 177 names ingenerate_handler!and the 177 keys ofCommandsmatch exactly (checked by diffing both lists). -
Direct Tauri imports outside
ipc.tsexist only for non-command APIs:@tauri-apps/plugin-dialog(src/lib/dialogos.ts),getCurrentWindow().show()(src/main.tsx),getCurrentWebview()for file drops (src/features/preview/arrastrar-ficheros.ts),getVersion()(src/features/settings/settings-dialog.tsx),convertFileSrc(src/lib/image-url.ts),getCurrentWindow()for the close guard (src/features/shell/confirmar-cierre.tsx) and full screen (src/features/mesa/barra-mesa.tsx), theChannelclass of@tauri-apps/api/core(src/features/mesa/conexion.ts, see Channels), andlistenfrom@tauri-apps/api/eventfor the events that are not inEvents(src/features/mesa/red/conexion-red.ts,src/features/salas/conexion-salas.ts,src/features/mesa/originales.ts).
// src-tauri/src/lib.rs
.invoke_handler(tauri::generate_handler![
commands::sistema::ping,
commands::carpetas::carpeta_recordar,
// ... 216 entries
])Each area is a module in src-tauri/src/commands/ (mod.rs lists them): actualizar, cards, carpetas, decklist, diseno, export, fichero, fuentes, iconos, identify, imagen, layout, library, mesa, pasar, red, salas, sistema, stats, temas. The two link commands (enlace_pendiente, enlace_invitacion) live outside that folder, in src-tauri/src/enlace.rs, and so does pbdeck_pendiente (src-tauri/src/abrir.rs). Commands take State<'_, AppState> (see Rust App Layer), sometimes an AppHandle (to emit events or use plugins), and return AppResult<T> (Result<T, AppError>). Some return T directly because they cannot fail: cheap lookups (ping, parse_decklist, deck_list_text, arrange_cards, cache_stats, themes_installed, ocr_ready, names_index_info, design_structure, design_remove, update_status, red_estado, salas_estado, enlace_pendiente, enlace_invitacion), the cancel commands (job_cancel, export_cancel), the live-movement datagrams (mesa_fantasma, mesa_senal, unit result), and the jobs that only hand back an id and report failures in their done event (resolve_decklist, export_pdf, identify_batch, mesa_empezar, mesa_retomar). The jobs red_unirse, red_empezar and salas_deck return AppResult<u32> because they can refuse before starting (Busy, a code that does not parse, no room); design_complete_lang also returns AppResult<u32> but never fails before the id.
Files are under src-tauri/src/commands/. "Job" means the command returns a job id at once and reports progress and completion through events (see Long jobs).
System (sistema.rs)
| Command | Purpose |
|---|---|
ping |
Returns "pong"; proves the bridge works. |
ping_error |
Always fails with InvalidInput; proves errors cross the bridge and translate. |
app_plataforma |
Plataforma (escritorio, android or ios, from cfg!(target_os)): the screen asks it once (src/lib/plataforma.ts) to decide touch behaviour and what to hide on a phone ("Show in folder", for instance). It is the same enum the room server's Hola carries so that a phone is not chosen as host. |
app_salir |
app.exit(0): the Android Back button on the library has no "close", so the screen asks twice ("Press again to exit") and then calls this, after saving its own state. |
app_pantalla_completa(si) |
Hides or shows Android's system bars. The screen no longer calls it: the activity keeps the app full screen all the time (Mobile). On desktop it does nothing (Unsupported from android.rs is taken as "nothing to do" by sin_barras_es_ok; any other error is returned). |
app_compartir_texto(texto) |
Shares a text (a game code or invitation link) with another phone app. The plugin only shares files, so the text goes into invitacion.txt in the cache and is shared as text/plain. Unsupported outside Android: the screen only offers it there. |
Folders for dialogs (carpetas.rs)
| Command | Purpose |
|---|---|
carpeta_recordar |
Remembers the folder of a chosen path under app_settings key carpeta_importar or carpeta_exportar (TipoCarpeta). |
carpeta_inicial |
Returns the path to open a dialog at: last folder of that kind (if it still exists), joined with a suggested file name. |
Files on a phone (fichero.rs, and pbdeck_pendiente in src-tauri/src/abrir.rs; see Paths and content URIs)
| Command | Purpose |
|---|---|
fichero_temporal(nombre) |
A fresh path inside the cache (app_cache_dir/fichero/<uuid>/<name>, name sanitised) where an export is written before sharing it. The copy is kept: Copia::conservar. |
compartir_fichero(ruta) |
Shares a file with another app (ACTION_SEND). Only a content:// or a file inside app_cache_dir (canonicalised, so .. or a link cannot leave it); any other path is InvalidInput, motivo: compartir_fuera, so the webview cannot hand the database or the node key to another app (fichero::para_compartir). Runs in spawn_blocking. Unsupported outside Android. |
pbdeck_pendiente |
The content:// of a .pbdeck that another app opened with this one (or that started the app), once (then null). Later ones arrive as the pbdeck://abierto event. Nothing is imported by Rust at that moment: the screen offers it, with the importer's options, and the person confirms. |
Updater (actualizar.rs; logic in src-tauri/src/actualizar.rs, see Releases and Auto Updates)
| Command | Purpose |
|---|---|
update_status |
Current version, whether this install can update (soportada) and instalaElSistema (Android: the system installer asks to confirm and relaunches the app; there is no "restart"). |
update_check |
Looks up latest.json; returns ActualizacionInfo or null. |
update_download |
Downloads the found version; emits update://progress. |
update_install |
Installs the downloaded version and restarts. Refuses with Busy while AppState::ocupado(). |
Decklist text (decklist.rs; see Decklist Parsing, Export)
| Command | Purpose |
|---|---|
parse_decklist |
Parses a pasted list into ParsedDecklist (proxy_core::decklist::parse). Sync and cheap; called while typing. |
deck_list_text |
Writes the deck as a list in a FormatoLista (Moxfield, Arena, MTGO, plain) for copying. |
deck_list_save |
Saves that list to an absolute .txt path (rejects anything else with InvalidInput). |
Layout (layout.rs; see Layout Engine)
| Command | Purpose |
|---|---|
compute_page_plan |
layout::plan(&deck) to PagePlan. Errors: InvalidInput with motivo card_does_not_fit or invalid_settings. |
arrange_cards |
"Auto arrange": returns the new card id order. |
Library: decks, tags, blobs, settings, presets, packages (library.rs; see Deck Library, Storage and Blobs, Deck Package Format)
| Command | Purpose |
|---|---|
deck_list |
Summaries of all decks. |
deck_create |
New empty deck with the app's generic back. |
deck_open |
Loads a deck (and records it as opened). |
deck_peek |
Loads a deck without recording it as opened (cover picker). |
deck_details |
Colors, class, price, languages, formats of decks (all if ids is empty), from the Scryfall cache, no network. |
deck_save |
Persists the deck; returns SaveResult { updatedAt }. Called by autosave. |
deck_rename / deck_duplicate / deck_delete
|
Rename, copy with a new name, delete a deck. |
tag_list / tag_create / tag_update / tag_delete
|
User tags (create returns the existing tag if the name exists). |
tag_icon_set |
Sets or clears (null) a tag's icon, an Iconify id (InvalidInput, motivo: icono, if it is not one). |
deck_tags_set |
Adds and removes tags on several decks at once. |
deck_pin |
Favorite flag for several decks. |
deck_notes_set |
Notes text of a deck. |
deck_cover_set |
Chosen cover card (or null). |
deck_lang_set |
Manual deck language override (or null to compute it). |
blob_import_files |
Copies user-chosen image files into the blob store; returns FaceImage[]. The names may be content:// URIs. |
blob_generic_back |
The app's generic card back as a FaceImage. |
blob_gc |
Deletes unreferenced blobs older than 24 h; Busy while a job runs. |
optimizar_decks, optimizar_estimar(ids), optimizar_lanzar(ids)
|
Decks with images to optimize, what it would free, and the job (optimizar://progress, optimizar://done; cancelled with job_cancel); Busy while a job runs or a game is open. |
app_setting_get / app_setting_set
|
Key/value settings in SQLite (app_settings). |
cache_stats / cache_clear
|
Size of / deletes app_cache_dir/renders. Never touches blobs. |
deck_export_package |
Writes a deck with all images to a .pbdeck; returns bytes. |
deck_import_package |
Imports a .pbdeck (new ids, images verified); returns the Deck. |
preset_list / preset_save / preset_delete
|
Named presets by PresetKind (print settings etc.). |
Cards: Scryfall and lists (cards.rs; see Scryfall Integration, Add Card and Variants, Set Browser)
| Command | Purpose |
|---|---|
resolve_decklist |
Job. Resolves a ParsedDecklist to cards with stored images. Events resolve://progress, resolve://done. |
job_cancel |
Cancels any job started from AppState::jobs (same implementation as export_cancel). Returns whether the job still existed. |
card_autocomplete |
Scryfall autocomplete names. |
card_search |
Scryfall syntax search, at most 50 results (CardSummary[]); optional language and highres-only. |
card_prints |
All printings of a card name (variant picker). |
cartas_buscar |
One page of the Search screen (FiltrosCartas, page from 1) → PaginaCartas (cartas, total, hayMas). No query or no results is an empty page, not an error. See Card-Search. |
carta_ficha / carta_aclaraciones
|
The details of a printing (FichaCarta); its rulings (AclaracionCarta[]). |
card_fetch |
Downloads one printing and returns it as a DeckCard. |
card_fetch_externa |
Same for a card from a non-Scryfall source (FuenteCarta, today Cockatrice tokens). |
deck_tokens_detect |
Proposes missing tokens for a deck (FichaPropuesta[]); adds nothing. |
sets_list / set_cards
|
Set catalogue; cards of a set, optionally by type. |
import_archidekt |
Imports a public Archidekt deck by URL. |
Export (export.rs; see Export, Imaging and PDF)
| Command | Purpose |
|---|---|
export_pdf |
Job. Exports the given deck snapshot to a PDF path or content:// (several files if the deck splits; on Android, one ZIP). Events export://progress, export://done. |
export_estimate |
Approximate PDF size (bytes, images, pages), how many PDFs (files) and the memory warning (memory, lowMemory); see Export. |
export_calibration |
Writes the duplex calibration sheet; texts come from the screen. |
export_cancel |
Cancels the export job. |
export_open / export_reveal
|
Open the file with the system viewer / show it in its folder. Only existing .pdf or .pbdeck files are accepted (exportado()), else InvalidInput. On Android export_open shares the file instead and export_reveal is Unsupported. |
Single card image (imagen.rs)
| Command | Purpose |
|---|---|
card_export_image |
Saves one face of a card (front or back; deckBack is the deck's back) as an image, the same one the PDF would print (edits, darkening, designed card), at the PDF quality's DPI and without bleed. The format comes from the file extension (PNG if there is none; an unknown one is InvalidInput with motivo: formato). Returns the written path. Runs in spawn_blocking. |
Statistics (stats.rs; see Statistics)
| Command | Purpose |
|---|---|
deck_stats |
DeckStats for cards and an optional DeckKind; asks Scryfall /cards/collection for missing or stale (over 24 h) data. |
Identify and link own cards (identify.rs; see Card Identification and OCR)
| Command | Purpose |
|---|---|
ocr_ready |
Are the OCR models downloaded? |
card_identify |
Proposes Scryfall cards for one stored image (hint text and optional OCR). |
identify_batch |
Job. Identifies many images; per-item result in identify://progress, end in identify://done. Cancel with job_cancel. |
card_link |
Card info for a chosen Scryfall id (cache first, then network, then stored for offline stats). |
card_link_externa |
Same for an external-source card. |
Name index and themes (fuentes.rs, temas.rs; see External Sources, Themes and Skins)
| Command | Purpose |
|---|---|
names_index_info / names_index_download / names_index_remove
|
Status, download (emits names://progress) and removal of the MTGJSON multi-language name index. |
themes_catalog |
The tweakcn registry. |
theme_fetch |
One theme by id or link, to preview. |
theme_install / themes_installed / theme_remove
|
Install (CSS + fonts), list, remove tweakcn themes. |
themes_open_community |
Opens the tweakcn community gallery in the browser. |
Icons (iconos.rs; see Icons)
| Command | Purpose |
|---|---|
iconos_colecciones |
Every visible Iconify collection (ColeccionIconos { prefijo, nombre, total, categoria }), cached a week. |
iconos_nombres(prefijo) |
The names of one collection, cached a week. |
iconos_buscar(texto) |
Search by name across every collection (up to 999 ids). |
iconos_datos(ids) |
{ id: DatosIcono { body, vb } } for the icons asked for (memory, chosen icons on disk, or the API); unsafe SVG bodies are dropped. |
iconos_elegir(id) |
Stores a chosen icon in app_data_dir/iconos/elegidos.json so it shows offline; NotFound if it does not exist. |
Card designer (diseno.rs; see Designer App Layer, Designer Engine, Frame Packs)
| Command | Purpose |
|---|---|
design_draft |
Draft design for a deck card (mechanical snapshot EN/ES, original names, next free footer number). |
collection_list / collection_save / collection_delete
|
Design collections. |
fonts_list / font_import / font_remove
|
Own fonts. |
packs_list / pack_import / pack_remove
|
Frame packs. |
fonts_free_list / font_free_download
|
Free (OFL) font catalogue and download. |
original_info / original_download
|
State and download of the "Original" frames (emits original://progress). |
packs_downloadable_list / pack_downloadable_download
|
Downloadable packs and download (emits packs://progress). |
pack_extra_use |
Copies a pack extra (back, token, finished card) into the blob store. |
symbols_status |
Mana-symbol status; downloads if missing or stale (refresh). |
design_register |
Registers the design being edited for pbimg://…/d/<hash>/<width>; returns the hash. |
design_overlay |
The card without artwork plus the artwork window (pbimg://…/c/…), for live artwork moves; null for composed or large cards. |
design_fit_report |
Warnings: text that does not fit, missing symbols or fonts. |
design_structure |
How a face is laid out (layout and split text) for the editor. |
design_suggest_translation |
Experimental translation suggestion for a face. |
design_drafts_for_deck |
Job. Drafts for all undesigned cards. Events design://progress, design://done. |
design_complete_lang |
Job. Completes a design language on the deck's designed cards: downloads the CJK fonts for Japanese, Korean or Chinese and the printing each card is missing. Each finished card arrives in designlang://progress, the end in designlang://done; cancel with job_cancel. |
design_export_png |
Job. Exports designed cards as PNG into a folder. Events designpng://progress, designpng://done. |
design_save / design_remove
|
Apply a design to a card (thumbnail into blob store) / revert the card. |
design_rerender / design_renumber
|
Refresh stale thumbnails / renumber designs by deck order; return the changed cards. |
art_from_clipboard / art_from_blob / art_from_scryfall / art_from_card
|
Obtain artwork: system clipboard, stored file, Scryfall art_crop, crop of an existing card (fractions 0–1). |
Game table (mesa.rs; logic in src-tauri/src/mesa/, see Game Table)
| Command | Purpose |
|---|---|
mesa_empezar |
Job. Prepares the decks of a ConfigMesa and opens the game. Events mesa://progress, mesa://done. |
mesa_retomar |
Job. Resumes the saved game, preparing again any image missing from the cache. Same events. |
mesa_estado |
EstadoMesa { abierta, deckIds, guardada }: whether a game is open and, if not, the summary of the saved one. |
mesa_suscribir |
Takes a Channel<MensajeMesa>; sends the whole view and, from then on, one patch per event. |
mesa_vista |
The whole VistaMesa (when a patch arrives out of order), without the log. |
mesa_accion |
Sends an Accion (optionally como another local player); the change arrives through the channel, a refusal is Rejected (espectador for a spectator). |
mesa_mirar_como |
In a local game of several, shows (and plays as) another player; Rejected red online. |
mesa_encuentro, mesa_banquillo, mesa_banquillo_cambiar, mesa_siguiente_partida
|
The best-of scoreboard, a player's deck and sideboard, swapping cards between games and opening the next game. mesa_banquillo and mesa_banquillo_cambiar are async: in an online game each player changes only their own deck, and a guest asks the host and waits up to 10 s (Network sin_respuesta otherwise). Rejections: ajeno, invitado, sin_terminar, sin_mazos, cambio. |
mesa_resumen |
The ResumenPartida of the open game (places, rounds, duration), counted by mesa::resumir from the log. |
partidas_de_deck |
HistorialDeck of a deck: its finished games (FilaPartida), wins and the games' ids. |
partidas_todas |
HistorialGeneral: a page of every finished game of every deck (limite, desde), with the total and the wins. |
partida_borrar |
Deletes a game from the history together with its replay. |
repeticion_abrir |
Opens a game of the history as a replay in its own session (VistaRepeticion); Rejected partida_abierta with a game open, sin_registro without a record, Unsupported with version if it comes from a newer catalogue. |
repeticion_actual, repeticion_ir, repeticion_salir
|
Replay the game that has just ended, in place (Rejected sin_terminar if it has not ended); go to a point (pos, como); leave the replay (back to the finished game or, if it was opened from the history, close the table). While replaying, mesa_accion is Rejected repeticion except for chat and quick notices; the actions of other players online keep being applied to the live game. |
mesa_guardar |
Saves the game now (leaving the tab, closing the window). |
mesa_salir |
Leaves the table: with guardar the game stays saved, without it the file is deleted. |
mesa_info_carta |
Rules text of a card the viewer may see («Inspeccionar»). |
mesa_origen_scryfall |
An OrigenCarta for a Scryfall card or token not in the deck (downloads its images). |
mesa_sparring_carta |
The OrigenCarta of one of the sparring phantom's cards, by name (only names in its pool; InvalidInput otherwise). |
mesa_originales |
Starts «Originals in my language»: for the given Scryfall-linked cards (≤ 1,000) looks up the printing in the interface language and answers each through mesa://originales. Screen only. |
mesa_listas |
ListasDeDeck { listas, cuentan } of a deck: its game lists and how many cards count without one. |
mesa_lista_cartas |
The deck's eligible copies with their data (CartaDeLista, from Scryfall or the design) for the editor. |
mesa_lista_comprobar |
AvisoLista[] of a list for a format (only warnings or blocking ones, see Game Table). |
mesa_lista_guardar / mesa_lista_borrar
|
Save (create or replace; returns it with its id) or delete a game list. mesa_empezar refuses a deck that needs a list and has none (InvalidInput, motivo: falta_lista) or a list that is not valid (motivo: lista). |
mesa_tapete_get / mesa_tapete_set / mesa_tapete_ajustes
|
The mat of a deck (deck: null = the general one): read, set or remove, change only veil and blur. |
mesa_dados_get / mesa_dados_set / mesa_dados_imagen
|
The look of the player's dice and coin (VistaAspectoDados): read, save (colours checked), set or remove the image of their own. |
Online play with a direct code (red.rs; logic in src-tauri/src/red/, see Online Play)
| Command | Purpose |
|---|---|
red_identidad / red_identidad_set
|
The Identidad used online (public key, nickname, avatar); change the nickname and the avatar (a blob hash or none). |
red_identidad_exportar / red_identidad_importar
|
Save the installation's identity to a file / replace it with one exported before. Importing is Busy (motivo: partida) with a game, a direct-code room or a server room open; a file that is not an identity is InvalidInput identidad. |
red_estado |
EstadoRed { papel, codigo, sala, empezada, puedeCeder }: whether this app hosts, plays or watches, the code to invite (host only), who is in the room (host only; a guest gets it through red://aviso; each row has an id for choosing seats and teams) and, for the host, whether leaving now hands the game over to another player (puedeCeder) instead of closing it. |
red_crear |
Opens an online game as host: starts the node, serves whoever comes with the code and returns it (PB1-…). Busy partida if a game or room is already open. |
red_unirse |
Job. Joins with a PeticionUnion (code, nickname, role, deck, list, format): prepares the deck (if playing) and connects. Events mesa://progress, mesa://done; a refusal from the host is Rejected with its motivo. |
red_apodo |
Changes this app's name in the waiting room: the host tells everyone at once, a guest asks the host (AlHost::Apodo). NotFound sin_sala without a room. |
red_anfitrion_deck |
The host changes the deck name it is seen with in the waiting room (None: none). NotFound sin_sala if there is no room or it has started. |
red_deck |
Job. A guest changes the deck (and list) it plays with in the waiting room: prepares it and sends it to the host. Events mesa://progress, mesa://done. |
red_vuelta |
The online game this guest dropped out of and can go back to (Option<VueltaRed>: nickname and time), from red/vuelta.rs. |
red_vuelta_olvidar |
Forgets that game. |
red_volver |
Job. Joins that game again with its code, as a player without a deck (the host recognises the node and gives the seat back); also with the table open after losing the connection. NotFound sin_vuelta without one; a Rejected from the host forgets it. |
red_empezar |
Job. The host starts the game with those in the room who brought a deck; config carries the host first (and their deck) and, after it, what the host chose for each guest (colour and team) in the order of invitados, the room ids (EnSala.id) by seat; if they are not exactly the players with a deck in the room, Rejected sala_cambio. There is no sparring online. Same events. NotFound sin_sala without an unstarted room. |
red_salir |
Leaves the online game (in the room or started): the host hands it to another player if it can (Anfitrion::irse) and otherwise closes it for everyone; a guest says goodbye. The table itself is closed by mesa_salir. |
mesa_fantasma, mesa_senal
|
Live movement: send a ghost (card or cursor, coordinates 0–10,000) or a 2 s ring as datagrams; the host sets the sender and limits the rate. Without an online game, or as a spectator, they do nothing. |
Room server and playgroups (salas.rs; client in src-tauri/src/red/servidor/, see Online Play)
Every command that needs the server fails with Network sin_servidor when there is no client; the server's own refusals come back as Server (see Error model).
| Command | Purpose |
|---|---|
salas_conectar |
Starts the server client if it was not running (with the nickname) and returns EstadoSalas. |
salas_estado |
EstadoSalas: whether a server is configured, whether it is connected, this installation's key (yo), the room and the role in it, and the deck prepared for the room. |
salas_playgroups / salas_pg_crear / salas_pg_salir / salas_pg_expulsar / salas_pg_rol
|
List, create, leave a playgroup, expel a member (by key), change a member's RolPlaygroup; each returns the new Playgroup[]. |
salas_invitar / salas_revocar
|
An Invitacion { codigo, caduca } to a playgroup (or, with null, to the room one is in) / revoke one. |
salas_usar |
Uses an invitation code; returns Usada (playgroups or the room that was entered). |
salas_sala_crear / salas_sala_unirse
|
Create a room (SalaNueva, 2–10 players) / join one as player or spectator; returns EstadoSala. Both, and salas_usar, are Busy partida with a game or a direct-code room open. |
salas_sala_salir |
Leaves the room (and its game, if any). |
salas_sala_expulsar, salas_sala_expulsar_jugador
|
Expel a member from the room (by key) or a player from the table (by seat): the creator through the room server, or the host with a direct code (Rejected no_anfitrion otherwise). |
salas_deck |
Job. Prepares the deck to play in the room and tells the server. Events mesa://progress, mesa://done; NotFound sin_sala outside a room. |
salas_empezar / salas_terminar
|
Tell the server the room's game starts / has ended. |
Moving decks between devices (pasar.rs; the logic in src-tauri/src/pasar/, see Deck Sharing)
| Command | Purpose |
|---|---|
pasar_recibir_abrir / pasar_recibir_estado / pasar_recibir_cerrar / pasar_recibir_cancelar
|
"Receive": open it (starts the shared iroh node if needed; returns the EstadoReceptor with the PBD1- code, its QR and the six-digit code), read its state, close it, or cancel what is being received. Changes arrive as the pasar://receptor event. |
pasar_peticion(aceptar) / pasar_responder(decisiones)
|
Accept or refuse a device on the local network that wants to send; answer, deck by deck, what to do with the offer (Decision: how much to take and, if the same deck already exists, Resolucion). |
pasar_cerca_buscar / pasar_cerca_parar
|
Look for devices nearby that have "Receive" open (mDNS; takes the Wi-Fi multicast lock on Android); the list arrives as pasar://cerca. |
pasar_compartir_codigo(texto) |
Offers the «Recibir» code to another app through the system's share sheet. The text must be a valid PBD1- code. Android only; elsewhere Unsupported. |
pasar_codigo_comprobar(texto) |
Checks a pasted or scanned PBD1- code before sending. A game code (PB1-) pasted here is InvalidInput with motivo: codigo_de_partida; and a deck code pasted where a game code goes (red_unirse) is codigo_de_deck, which has its own sentence. See Error model. |
pasar_nombre / pasar_nombre_set(nombre)
|
This device's name when passing decks; empty returns to the system's. Async: on Android it asks the Kotlin plugin. |
pasar_enviar(destino, decks, modo) / pasar_enviar_cancelar / pasar_emisor_estado / pasar_emisor_olvidar
|
Send decks to a Destino (nearby device or code); returns at once, progress arrives as pasar://emisor. |
pasar_completar(deckId) |
Job (an id, with pasar://imagenes-progress and pasar://imagenes-done): download the images a deck received as "originals only" is missing. |
deck_paquete_info, deck_paquete_guardar, deck_paquete_compartir
|
Size of a deck's .pbdeck in each ModoPaquete (original, optimizado, sinProxies); save it (a path or content://) or share it with another app in one of them. |
paquete_leer, paquete_importar
|
Read a .pbdeck without importing it (what it holds and whether the same deck is already here) / import it with what the person chose; InvalidInput eleccion if nothing was chosen and falta_resolucion if the deck already exists and no resolution was given. |
Invitation links (src-tauri/src/enlace.rs)
| Command | Purpose |
|---|---|
enlace_pendiente |
The code of the proxybuilder://unirse/<code> (or tapete://unirse/<code>) link that started the app, once (then null). Links that arrive later come as the salas://enlace event. |
enlace_invitacion |
The invitation link of a code (the one copied in the panel), or null. |
Plain fn commands are synchronous and must be cheap (SQLite lookups, layout::plan). async fn commands await network or offload work. Heavy CPU work is explicitly offloaded: export_pdf uses tauri::async_runtime::spawn_blocking; the designer wraps renders in a helper that runs on a separate thread (commands/diseno.rs, "Corre f en un hilo aparte"). Inferred: because Tauri runs plain sync commands on the main thread, keeping them cheap matters for UI responsiveness; check the Tauri docs when adding a heavy sync command.
Defined in src-tauri/src/error.rs:
pub struct AppError { pub code: ErrorCode, pub params: BTreeMap<String, String> }
impl AppError { pub fn new(code) -> Self; pub fn with(self, clave, valor: impl ToString) -> Self }
pub type AppResult<T> = Result<T, AppError>;It derives Serialize and TS, so a failed invoke rejects with the JSON object { code: "InvalidInput", params: { motivo: "…" } }. Rust never writes user-facing prose: params carries data for the sentence, not a message.
ErrorCode has 14 variants:
| Code | Meaning | Typical sources |
|---|---|---|
Network |
No connection or server not answering; with motivo from error::red / error::estado_http (HTTP) or error::red_sin_http (iroh, mDNS), a sentence per cause |
error.rs (red, estado_http, red_sin_http; the last one used by pasar/), used by scryfall/client.rs, fuentes/mod.rs, commands/cards.rs (Archidekt), ocr.rs, diseno/{letras_libres,letras_cjk,original}.rs; updater (Reqwest, Network) |
RateLimited |
HTTP 429 after retries | scryfall/client.rs |
NotFound |
Card, deck, blob, theme, update version missing |
render.rs leer_blob (blob param), card_link, actualizar.rs
|
Io |
Disk read or write |
From<std::io::Error>, export_open failures, a content:// that cannot be opened (fichero.rs io_uri) |
Db |
SQLite error |
From<rusqlite::Error>, lock poisoning is Internal
|
InvalidInput |
Nonsense from the screen |
From<LayoutError>, deck_list_save, exportado(), card_export_image (motivo: formato), game lists (falta_lista, lista, lista_sin_nombre), online (codigo, deck, identidad), a code of the wrong kind (codigo_de_deck, codigo_de_partida, red/codigo.rs), moving decks (eleccion, falta_resolucion, oferta_grande, sin_espacio, sin_oferta, huella, importar), fichero.rs leer_bytes over its limit (fichero_grande) and compartir_fichero with a path outside the cache (compartir_fuera) |
Cancelled |
The user cancelled |
jobs flag checks in resolve, export, identify, PNG, design batch; PdfError::Cancelled; updater AuthenticationFailed
|
Pdf |
PDF could not be built |
From<PdfError>, krilla image errors in exportar
|
Image |
Image unreadable or too large |
From<ImagingError>, From<ProbeError>
|
Unsupported |
File format not accepted, or something that exists only on another platform |
ImagingError::Unsupported, ProbeError::Unsupported; mesa/guardada.rs with motivo: version for a saved game or replay from a newer catalogue; android.rs outside Android (detalle: android), export_reveal on a content:// (detalle: content), elegirCarpetaParaExportar on a phone (detalle: carpeta, thrown on the screen) and, on Android, motivo: instalar_permiso when the system does not yet allow this app to install packages (see Releases and Auto Updates) |
Busy |
A job, or a game open on the table, prevents this now (motivo: partida when it is the game or an online room) |
blob_gc, optimizar_lanzar, update_install, red_crear, red_unirse, red_volver, red_identidad_importar, salas_usar, salas_sala_crear, salas_sala_unirse, and JS-side guards |
Internal |
Unexpected (programming error, failed thread pool, poisoned lock) | various map_err
|
Rejected |
The game table refuses that action. params.motivo is either the engine's Rechazo (NoExiste, SinPermiso, ZonaVacia, FueraDeTurno, Limite, NoSePuede, written with {:?}) or a reason of the app layer in snake_case: the session (repeticion, como, red, no_existe, no_anfitrion), the best-of match (invitado, sin_encuentro, sin_terminar, encuentro_terminado, cambio), the replay (partida_abierta, sin_registro, sin_terminar, no_es_repeticion), a spectator acting (espectador), a direct-code host without the majority (sin_mayoria), and the host's refusal to a guest (codigo, version, llena, empezada, sin_deck, ritmo, sin_partida, expulsado, no_activo…) |
mesa/sesion.rs (impl From<Rechazo> for AppError), mesa/encuentro.rs, mesa/repeticion.rs, commands/mesa.rs (mesa_accion, repeticion_abrir, repeticion_actual, mesa_info_carta), red/invitado.rs, salas_sala_expulsar_jugador
|
Server |
The room server said no; params.code is the protocol code (version, firma, limite, no_existe, caducada, llena, sin_permiso, ya_empezada, expulsado, mensaje), with the server's params
|
red/servidor/cliente.rs (pedir, and the handshake, which carries only code) |
Parameter keys used across the app (counted by grep): detalle (free-form technical detail, most common), motivo (machine reason such as card_does_not_fit, invalid_settings, too_large, corrupt), estado (HTTP status), blob, url, fichero, deck, carta, ficha, plus pack-loading keys (estilo, hueco, falta, color, version). Gotcha: most sentences in src/lib/errores.ts contain no {placeholders}, so these params are usually not shown to the user; they are for logs, tests and future texts. The exception is estado in the Network rechazo and estado sentences.
proxy-core does not know AppError. Each module has a thiserror enum and the app converts with From:
| Core error | Defined in | Converted in | Becomes |
|---|---|---|---|
LayoutError (CardDoesNotFit, InvalidSettings) |
proxy-core/src/layout/mod.rs |
src-tauri/src/error.rs |
InvalidInput + motivo
|
ImagingError (Unsupported, Corrupt, TooLarge, Encode) |
proxy-core/src/imaging/mod.rs |
src-tauri/src/render.rs |
Unsupported; Image + motivo=too_large; Image + detalle
|
ProbeError (Unsupported, Corrupt) |
proxy-core/src/imaging/probe.rs |
src-tauri/src/store/blobs.rs |
Unsupported; Image + motivo=corrupt
|
PdfError (Geometry, Image, Write, Cancelled) |
proxy-core/src/pdf/mod.rs |
src-tauri/src/commands/export.rs |
Cancelled; otherwise Pdf + detalle
|
PackError |
proxy-core/src/diseno/pack.rs |
src-tauri/src/diseno/recursos.rs → de_pack
|
pack keys (falta, estilo, hueco, color, version) |
DisenoError |
proxy-core/src/diseno/mod.rs |
designer app layer |
Inferred: mapped at call sites with map_err (not read in full) |
LineError |
proxy-core/src/decklist/mod.rs |
not an error type at the bridge | Serialized inside ParsedLine; the screen shows a text per variant |
Also in the app crate: From<std::io::Error> (Io) and From<rusqlite::Error> (Db) in error.rs; the updater maps tauri_plugin_updater::Error in actualizar.rs → error().
Both crates deny unwrap()/expect() outside tests (#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used))]), so failures must flow into these types.
src/lib/errores.ts:
-
TEXTOS: Record<ErrorCode, string>has one Spanish source sentence per code, wrapped inparaTraducir(...)so the i18n tooling (pnpm idiomas) sees it; English comes fromsrc/i18n/en/. TheRecord<ErrorCode, …>type makes the compiler fail if Rust adds a code the screen does not cover (afterpnpm bindings). -
esAppError(e): object whosecodeis a key ofTEXTOS. Rejections frominvokeareunknown, and a plain JS object such as{ code: "Busy", params: {} }qualifies, which is how the front end raisesBusyitself. -
MOTIVOS: Partial<Record<ErrorCode, Record<string, string>>>: a sentence of its own for known reasons of a code, looked up byparams.motivoor, failing that,params.code. TodayRejectedhas one perRechazovariant plus the host's refusals (see below) andpartida_abierta,sin_registro,sin_terminarandrepeticion(game history and replay);InvalidInputhas the game list reasons (falta_lista,lista,lista_sin_nombre) andcodigo,deck,identidad;Unsupportedhasversionandinstalar_permiso(Android: «Android needs your permission to install the update: its setting has been opened…»);InvalidInputalso hascodigo_de_deck(the code of "Receive a deck",PBD1-, pasted where a game code goes);Busyhaspartida(«Hay una partida abierta: termínala o sal de la mesa para actualizar.», written for the update but also shown when an online command is refused for the same reason); andServerhas one per protocol code. Reasons with no entry (como,red,no_existe,no_es_repeticion,invitado,sin_encuentro,encuentro_terminado,cambio) show the code's general sentence. An unknown reason falls back to the code's general sentence, so a newer engine never shows a raw reason. -
mensajeDeError(e, t):AppError→t(MOTIVOS[code][motivo] ?? TEXTOS[code], params);Error→ its message; anything else →String(e). -
useLang().mensaje(e)(src/components/language-provider.tsx) ismensajeDeErrorbound to the activet. Screens callmensaje(e)incatchblocks and show it in a toast or inline.
Guarded by src/lib/__tests__/errores.test.ts and the Rust test el_error_viaja_como_codigo_y_datos in error.rs.
A job command never rejects for work failures; its done event carries error: AppError | null next to result. The front end checks d.error and treats Cancelled as a quiet outcome (src/features/decklist/acciones.ts → resolver returns null; src/features/export/acciones.ts → exportarA announces "Exportación cancelada").
The texts of src/lib/errores.ts::MOTIVOS also cover online play: Network with motivo sin_servidor, sin_respuesta or desconectado; NotFound sin_sala; InvalidInput codigo (a direct code that does not parse), deck and identidad (an imported file that is not an identity); Rejected from the host (codigo, version, llena, empezada, sin_deck, ritmo, espectador, sin_partida, expulsado, no_anfitrion, besides the engine's reasons; a refusal of an action sent later reaches a guest as MensajeMesa::Rechazo, not as the error of mesa_accion); and every Server code, expulsado included. red and como (looking or acting as another player online) have no sentence of their own. Network notices go as events (red://aviso, salas://…), not errors. See Online Play.
Plain HTTP failures (Scryfall, Archidekt, external sources, downloads) build their Network error with crate::error::red(reqwest::Error) or crate::error::estado_http(StatusCode), never by hand, so the screen can tell them apart: conexion (could not connect: check the connection, firewall or antivirus), tiempo (timed out), respuesta (an answer the app does not understand), rechazo (401/403, shown with {estado}: usually a VPN, a proxy or a blocked network) and estado (any other error status, with {estado}). Both functions also write a warn line to proxy-builder.log with the host and the full chain of causes, which is what to ask for when someone reports a network problem. Network failures that are not HTTP (the iroh connections and mDNS of Deck Sharing) use crate::error::red_sin_http(motivo, Option<&dyn Error>) instead: the same Network with motivo, and with an underlying error the same chain of causes in the log and in detalle; without one (a timeout, a rejection from the other side) only the motivo is logged. The code stays Network, so retries and matches!(e.code, ErrorCode::Network | …) keep working. See Scryfall Integration.
Shared types derive TS and carry #[ts(export)]; some also #[serde(rename_all = "camelCase")]. There are 295 generated files in src/bindings/ (do not edit; the header says so).
-
pnpm bindingsrunscargo test --manifest-path src-tauri/Cargo.toml --workspace export_bindings. ts-rs generates oneexport_bindings_<Type>test per exported type, and running the tests writes the files. - The output directory is set by
.cargo/config.tomlat the repo root:TS_RS_EXPORT_DIR = { value = "src/bindings", relative = true }. It must live at the root, not insrc-tauri/.cargo/, because Cargo reads config from the directory it is launched in, and the pnpm scripts launch from the root. -
u64/i64fields use#[ts(type = "number")](e.g.ExportResult.bytes,SaveResult.updatedAt) to avoidbigint. - Doc comments on Rust types are copied into the
.tsfiles (note the Spanish text inAppError.ts). -
ErrorCodeis a string union;AppError.paramsis{ [key in string]: string }. - CI runs
pnpm check(which includestsc --noEmit), so a stale binding that no longer matchesCommandsfails the build. See Testing and CI.
src-tauri/src/jobs.rs → Jobs (field AppState.jobs):
| Method | Behaviour |
|---|---|
alta() |
Allocates the next u32 id (atomic counter) and an Arc<AtomicBool> cancel flag; returns both. |
cancelar(id) |
Sets the flag; false if the job is already gone. |
baja(id) |
Removes the job; the job calls it when done. |
Separately, AppState::trabajo() returns a TrabajoEnMarcha guard (RAII, counter trabajos); AppState::ocupado() is true while any guard lives. It is used so blob GC and updater installs never run under a long job. Guards are held by resolve_decklist, export_pdf, identify_batch, design_drafts_for_deck, design_complete_lang, design_export_png, names_index_download, card_fetch, card_fetch_externa, deck_tokens_detect, mesa_empezar, mesa_retomar, red_unirse, red_volver, red_empezar, salas_deck (grep for .trabajo()). ocupado() is also true while a game is open on the table (AppState::hay_partida()), because an update would restart the app in the middle of it.
Canonical job command shape (from resolve_decklist and export_pdf):
let (job_id, cancelado) = state.jobs.alta();- Spawn (
tauri::async_runtime::spawnorspawn_blocking), holdlet _marca = estado.trabajo(); - Run, calling
app.emit("<name>://progress", XProgressEvent { job_id, … }), checkingcancelado.load(...)between steps and returningCancelled. -
estado.jobs.baja(job_id), thenapp.emit("<name>://done", XDoneEvent { job_id, result, error }). - Return
job_idimmediately.
Cancellation is cooperative: nothing is killed; the worker checks the flag. Both job_cancel and export_cancel call jobs.cancelar.
| Event | Payload (src/bindings/) |
Emitted by | Listened by |
|---|---|---|---|
resolve://progress / resolve://done
|
ResolveProgressEvent { jobId, progress } / ResolveDoneEvent { jobId, result, error }
|
resolve_decklist |
features/decklist/acciones.ts via lanzarTrabajo
|
export://progress / export://done
|
ExportProgressEvent { jobId, phase (prepare/compose/write), done, total } / ExportDoneEvent
|
export_pdf |
features/export/acciones.ts |
identify://progress / identify://done
|
IdentifyProgressEvent { jobId, done, total, blob, … } / IdentifyDoneEvent { jobId, error }
|
identify_batch |
features/variants/vincular-lote.tsx |
design://progress / design://done
|
DesignProgressEvent { jobId, done, total, … } / DesignDoneEvent { jobId, error }
|
design_drafts_for_deck (diseno/lote.rs) |
features/disenar/lote-dialog.tsx |
designlang://progress / designlang://done
|
The same DesignProgressEvent (with the finished card) / DesignDoneEvent
|
design_complete_lang (diseno/idioma.rs) |
features/disenar/renumerar.ts (useTrabajo type idioma) |
designpng://progress / designpng://done
|
DesignPngProgressEvent, DesignPngDoneEvent
|
design_export_png (diseno/png.rs) |
features/disenar/png-dialog.tsx |
mesa://progress / mesa://done
|
MesaProgressEvent { jobId, mazo, mazos, hechas, total } / MesaDoneEvent { jobId, error }
|
mesa_empezar, mesa_retomar, red_unirse, red_volver, red_empezar, salas_deck
|
features/mesa/conexion.ts (conProgreso, also called from features/mesa/red/conexion-red.ts and features/salas/conexion-salas.ts) |
original://progress |
{ done, total } |
original_download |
features/disenar/marcos-originales.ts |
packs://progress |
{ id, done, total } |
pack_downloadable_download |
features/disenar/packs-descargables.ts |
names://progress |
{ fase, hecho, total } |
names_index_download |
features/settings/nombres-idiomas.tsx |
update://progress |
{ hecho, total } |
update_download |
stores/actualizacion-store.ts |
ciclo://pausa / ciclo://vuelta
|
none |
ciclo.rs (Android only): the app went to the background / came back |
lib/ciclo-de-vida.ts (saves the deck and the card being designed) / features/mesa/red/conexion-red.ts (the online game reconnects) |
pbdeck://abierto |
string (a content://) |
abrir.rs, when another app opens a .pbdeck with this one |
features/library/abrir-pbdeck.ts |
pasar://receptor, pasar://emisor, pasar://cerca
|
EstadoReceptor, EstadoEmisor, EquipoCerca[]
|
pasar/ |
features/pasar/estado.ts |
pasar://imagenes-progress / pasar://imagenes-done
|
ImagenesProgreso / ImagenesFin
|
pasar_completar |
features/pasar/imagenes.ts |
salas://enlace |
string (the code) |
enlace.rs, when a proxybuilder://unirse/… or tapete://unirse/… link reaches the running app |
features/salas/enlaces-entrantes.ts |
original://, packs://, names:// and update://progress have no job id: they belong to a single awaited command (the invoke itself resolves when the download ends), are not cancelable, and are listened to directly with on().
Some events are not in Events and are listened to with listen from @tauri-apps/api/event, typed at the call:
| Event | Payload | Emitted by | Listened by |
|---|---|---|---|
red://aviso |
AvisoRed |
red/mesa.rs (online game notices: room, start, connection, links) |
features/mesa/red/conexion-red.ts |
salas://conexion, salas://playgroups, salas://sala, salas://enlaces, salas://host, salas://fuera
|
boolean, Playgroup[], EstadoSala, EnlaceSala[], HostSala, MotivoFuera
|
red/servidor/sala.rs |
features/salas/conexion-salas.ts |
mesa://originales |
OriginalMesa (one per card) |
mesa_originales (commands/mesa.rs) |
features/mesa/originales.ts |
The game table does not use events for its state: mesa_suscribir receives a tauri::ipc::Channel<MensajeMesa> created on the screen (new Channel() in src/features/mesa/conexion.ts, typed Channel<MensajeMesa> in Commands). A channel belongs to one subscriber and delivers in order, which a stream of patches needs (a lost or reordered patch would desynchronise the view). The session keeps it and sends Vista { vista, registro } (with the last 500 events that still count, Registro::vigentes(), REGISTRO_EN_VISTA in mesa/sesion.rs), Parche { parche, evento }, Cerrada, or, online, Rechazo { motivo } when the host did not accept an intention of this screen (it arrives later, not as the answer of mesa_accion); a patch whose version does not follow makes the screen ask mesa_vista. Subscribing again (after switching tabs) replaces the channel and starts with a full view. A spectator, who has no session of their own, subscribes to the view it watches (AppState.mirada, red/mirada.rs). See Game Table.
src/lib/trabajos.ts:
lanzarTrabajo(progreso, final, lanzar: () => Promise<number>, alProgreso)
: Promise<{ jobId: number; fin: Promise<DoneEvent> }>Invariants:
-
Listen before launching. A short job may finish before
invokereturns the id; its final event would be lost. Soon(progreso)andon(final)are registered first. - Events that arrive before the id is known are buffered (
pendientesP,pendientesD) and replayed after the id arrives; later events are filtered byjobId. -
finresolves with the done payload (even when it carries an error); both listeners are removed when it does. Iflanzar()throws, listeners are removed and the error is rethrown.
Tested in src/lib/__tests__/trabajos.test.ts.
src/stores/trabajo-store.ts → useTrabajo holds the running resolve, export, design-language job or game table preparation (TipoTrabajo = "resolver" | "exportar" | "idioma" | "mesa"): { tipo, jobId, fase, hechas, total } with empezar, setId, avanzar, terminar. The footer of the left panel and the export button read it. resolver() and exportarA() throw { code: "Busy", params: {} } if actual is set: the one-job-at-a-time rule is enforced in the front end (Rust only counts running jobs for ocupado(); resolve_decklist itself does not refuse a second call). cancelarTrabajo() in acciones.ts calls export_cancel for exports and job_cancel otherwise. Designer batch, PNG export and bulk link use lanzarTrabajo from their own dialogs without useTrabajo.
Dialogs come from @tauri-apps/plugin-dialog (capability dialog:default), wrapped only by src/lib/dialogos.ts:
| Function | Behaviour |
|---|---|
abrirParaImportar(opciones) |
open() starting in the last import folder; remembers the folder of the first chosen path. Returns string[] | null. |
guardarParaExportar(opciones, nombre) |
save() starting in the last export folder with the suggested name; remembers the choice. |
elegirCarpetaParaExportar() |
Folder picker for exporting loose images. Not on a phone (Unsupported, thrown on the screen): Android has no folder picker. |
elegirDestinoDeImagenes({ titulo, nombre }) |
Where to put images that go into per-language folders: a folder on desktop, a ZIP on a phone. |
recordar(tipo, ruta) |
Fire-and-forget carpeta_recordar; also used when files are dragged onto the app. |
On Android the system pickers return a content:// URI instead of a path, so none of this applies there: nothing is remembered, defaultPath is only the proposed name, the filters become MIME types (filtrosParaAbrirEnMovil, filtrosParaGuardarEnMovil: Android does not know .pbdeck or .ttf, so an open filter containing an unknown extension is dropped and the person may pick anything, and a save filter with no known type falls back to application/octet-stream, with which Android keeps the name as typed) and the asking is esMovil() from src/lib/plataforma.ts. The mobile paths are in Paths and content URIs.
Import and export folders are remembered separately by Rust (commands/carpetas.rs, app_settings keys carpeta_importar and carpeta_exportar) so, for example, exporting a PDF does not move where "import images" opens. carpeta_inicial returns the suggested name alone if nothing is stored or the stored folder disappeared; a failing lookup falls back to the plain name in inicial(). CLAUDE.md requires all file dialogs to go through this module; src/lib/__tests__/dialogos.test.ts covers it.
Paths come back to the screen as plain strings (on Android, URIs) and go to Rust commands (export_pdf, deck_export_package, font_import…). Rust re-validates what it opens (exportado() in export.rs, .txt check in deck_list_save), because the WebView is not trusted to name arbitrary files. The capability file grants no fs permission at all.
On desktop the screen hands Rust a path and Rust uses it. On Android a dialog returns a content:// URI instead: a permission the system gives to that one file, with no folder, often no extension in the URI and sometimes a provider that can only be read as a pipe. The rule is that every command that reads or writes a file the person chose goes through src-tauri/src/fichero.rs, never std::fs on a dialog path, and that on desktop the result is exactly what it was (a path is a path). Format and name come as parameters or from the name Android reports (DISPLAY_NAME), never from the URI.
| Function | What it does |
|---|---|
es_uri(ruta) |
true for a string that starts with content://. |
leer(app, ruta, nombre?) |
Puts the file within std::fs's reach: a path as it is; a URI copied into the cache (app_cache_dir/fichero/<uuid>/), because the .pbdeck is a ZIP and needs Seek, which a cloud provider's pipe does not give. The returned Copia deletes the folder when dropped. |
leer_bytes(app, ruta, tope) |
The whole content; a URI over tope bytes is InvalidInput with motivo: fichero_grande. |
escribir(app, ruta, nombre?, f) |
Gives f a local path to write: the real one, or a temporary file in the cache that is then copied to the URI. A URI cannot be renamed, so escribir_atomico (the .part then rename) is not available there. |
escribir_bytes, copiar_a
|
Write bytes / copy a local file to a path (in pieces, to a provisional name, then renamed) or to a URI. |
temporal(app, nombre) |
A new empty path in the cache, removed on drop (Copia::conservar keeps it). Used for exports and for sharing. |
para_compartir(app, ruta) |
Leaves the file where the Android project's FileProvider can see it (the cache) and returns the path and the MIME type (mime_de); the copy stays until the next sweep. Accepts only a URI or a file already inside the cache (compartir_fuera otherwise). |
zip_de_carpeta, zip_de_lista
|
Pack files into a ZIP (images, PDFs and ZIPs are stored, the rest deflated). |
nombre_seguro, nombre_destino
|
A name that can be created in any folder (no separators or control characters, not empty, 120 characters) / the name a write will have, the Android one for a URI. |
Why a copy to the cache and not a streamed read: the importers (.pbdeck, ZIP packs, fonts) want a seekable file, and a copy turns the three kinds of source (path, local URI, cloud URI) into one. Why Acceso: the logic runs against the trait Acceso { abrir, nombre }, implemented over tauri-plugin-fs in the app (DeLaApp, which opens a URI with app.fs().open(..) and gets a real std::fs::File from the descriptor the system hands over) and over local files in the tests. tauri-plugin-fs is used only from Rust: no fs: permission is granted to the WebView. The cache folder is swept on use (anything older than 24 hours in fichero/ goes).
Opening a file from outside (another app, ACTION_VIEW) is src-tauri/src/abrir.rs: the Android plugin hands over a URI, which is kept (PENDIENTE) and announced with pbdeck://abierto; the screen asks pbdeck_pendiente once if it was not listening yet. Only content:// strings of at most 2048 characters count (valida). Rust does not import anything at that point.
- Write
#[tauri::command] pub fn|async fn name(state: State<'_, AppState>, …) -> AppResult<T>insrc-tauri/src/commands/<area>.rs; put real logic in a non-command function so tests can call it with anAppStatebuilt ontempfiledirs. - Register it in
generate_handler!insrc-tauri/src/lib.rs. - Add its entry to
Commandsinsrc/lib/ipc.ts(camelCase argument names). - Derive
Serialize, TSwith#[ts(export)]on any new payload type; runpnpm bindings. - Return errors as
AppErrorwith an existingErrorCode; add a code only if the screen needs a distinct sentence (then add its text toTEXTOSandsrc/i18n/en/). - For a long job: follow the five-step shape above, add both events to
Events, calllanzarTrabajofrom the screen. - Run
pnpm check,pnpm check:clippy,pnpm test:rust.
Use and build · Uso y desarrollo
- User-Guide · Guía de usuario
- Development-Setup · Preparación del entorno de desarrollo
- Testing-and-CI · Tests y CI
- Releases-and-Auto-Updates · Versiones y actualizaciones automáticas
- Conventions · Convenciones
Architecture · Arquitectura
- Architecture · Arquitectura
- IPC-and-Error-Handling · IPC y manejo de errores
- Rust-App-Layer · Capa de aplicación Rust
Data · Datos
- Deck-Model · Modelo del mazo
- Storage-and-Blobs · Almacenamiento y blobs
- Deck-Package-Format · Formato del paquete de mazo
- Deck-Library · Biblioteca de mazos
Getting cards · Conseguir cartas
- Decklist-Parsing · Parseo de la lista del mazo
- Scryfall-Integration · Integración con Scryfall
- External-Sources · Fuentes externas
- Card-Identification-and-OCR · Identificación de cartas y OCR
Layout, images, PDF · Maquetación, imagen y PDF
- Layout-Engine · Motor de maquetación
- Imaging-and-PDF · Imagen y PDF
- Export · Exportación
- Live-Preview · Vista previa en vivo
- Statistics · Estadísticas
Card designer · Diseñador de cartas
- Designer-Overview · Visión general del diseñador
- Designer-Engine · Motor del diseñador
- Designer-Composition · Composición del diseñador
- Frame-Packs · Packs de marcos
- Designer-App-Layer · Capa de aplicación del diseñador
- Designer-UI · Interfaz del diseñador
- Card-Editor-Dialog · Diálogo Editar carta
Game table · Mesa de juego
Mobile · Móvil
Frontend · Frontend
- Frontend-Architecture · Arquitectura del frontend
- State-Stores · Stores de estado
- Internationalization · Internacionalización
- Themes-and-Skins · Temas y skins
- Icons · Iconos
- Settings-and-Shell · Ajustes y shell
- Tutorials · Tutoriales
- Decklist-Panel · Panel de la lista del mazo
- Add-Card-and-Variants · Añadir carta y variantes
- Card-Search · Búsqueda de cartas
- Set-Browser · Explorador de sets
Reference · Referencia