Repository navigation
Storage and Blobs
English · Español
Everything the app persists locally is split in two places: app_data_dir (the SQLite library library.db, the content-addressed image store blobs/, downloaded resources, the game in progress and the online identity) and app_cache_dir (derived files that can be deleted at any time). Decks depend on app_data_dir; nothing depends on app_cache_dir.
Code: src-tauri/src/store/ (db.rs, decks.rs, blobs.rs, etiquetas.rs, colecciones.rs, presets.rs, settings.rs, listas.rs, partidas.rs, migrations/*.sql), src-tauri/src/mesa/guardada.rs (the game in progress), src-tauri/src/state.rs (Paths, AppState::open), src-tauri/src/render.rs (render cache) and src/stores/deck-store.ts (the screen side).
Decision: deck images live in the blob store under app_data_dir/blobs/, not in the cache. Why: decks reference images by SHA-256, so the images are user data; the operating system or the user may clear a cache directory whenever they like, and that must never break a deck. The cache (app_cache_dir/renders/) only holds derived renders (thumbnails, design previews) that are recomputed from blobs on demand.
AppState::open(paths) (state.rs) creates both directories, opens library.db, runs store::decks::rellenar_refs once, builds BlobStore::new(data/blobs) and Recursos::abrir(data), and loads (or creates) the online identity key with red::identidad::cargar_o_crear(data). Paths { data, cache } is filled in src-tauri/src/lib.rs from Tauri's app_data_dir() / app_cache_dir(), through red::perfil::carpeta (with a debug profile, perfiles/<name>/ inside each).
Db (store/db.rs) wraps a single rusqlite::Connection in a Mutex (rusqlite is not Sync; long jobs release the lock between steps). On open it sets journal_mode=WAL, foreign_keys=ON, synchronous=NORMAL, then runs migrations. Db::in_memory() is used by tests.
Migrations are include_str! SQL files listed in order in MIGRACIONES (store/db.rs) and tracked in schema_migrations (version, applied_at) (created with CREATE TABLE IF NOT EXISTS before the loop). Each runs in its own transaction, together with its schema_migrations row. Rule: migrations only ever append; a published one is never edited.
| # | File | Adds |
|---|---|---|
| 1 | 0001_init.sql |
decks, deck_cards (+ indexes deck_cards_orden, deck_cards_front, deck_cards_back), blobs, presets (+ presets_kind), app_settings, scryfall_cards (+ scryfall_nombre, scryfall_impresion). |
| 2 | 0002_blob_refs.sql |
blob_refs (owner_kind, owner_id, blob) + blob_refs_blob: image references that are neither a card front nor back (designer art, emblems, "before" images). |
| 3 | 0003_biblioteca.sql |
Library organisation: columns decks.pinned, decks.notes, decks.opened_at, decks.cover_card_id; tables tags (+ unique tags_name on name COLLATE NOCASE) and deck_tags (+ deck_tags_tag). |
| 4 | 0004_idioma_deck.sql |
Column decks.lang_override (deck language chosen by hand; NULL means "computed from the cards"). |
| 5 | 0005_listas_juego.sql |
Table deck_listas (deck_id, id, nombre, comandantes, cartas, updated_at), primary key (deck_id, id), deck_id referencing decks(id) ON DELETE CASCADE: the game table's game lists (Game Table). |
| 6 | 0006_partidas.sql |
Tables partidas (one row per finished game: start and end times, format and mode as JSON values, players and winners as JSON, how it ended, a note, rounds, duration, catalogue version and registro, the compressed record or NULL) and partida_decks (partida_id, jugador, deck_id, puesto), primary key (partida_id, jugador), partida_id referencing partidas(id) ON DELETE CASCADE; indexes partidas_terminada and partida_decks_deck: the game history (Game Table). |
Test: abrir_dos_veces_aplica_la_migracion_una_sola_vez (db.rs) also runs PRAGMA integrity_check.
erDiagram
decks ||--o{ deck_cards : "ON DELETE CASCADE"
decks ||--o{ deck_tags : "ON DELETE CASCADE"
tags ||--o{ deck_tags : "ON DELETE CASCADE"
decks ||--o{ deck_listas : "ON DELETE CASCADE"
decks }o--o{ blobs : "default_back_blob"
deck_cards }o--o{ blobs : "front_blob / back_blob"
blob_refs }o--|| blobs : "blob"
decks ||--o{ blob_refs : "owner_kind=deck"
presets ||--o{ blob_refs : "owner_kind=preset"
decks ||--o{ blob_refs : "owner_kind=mesa (game table mat)"
partidas ||--o{ partida_decks : "ON DELETE CASCADE"
decks {
text id PK
text name
text decklist_text
text settings_json
text default_back_json
text default_back_blob
int schema_version
int created_at
int updated_at
int pinned
text notes
int opened_at
text cover_card_id
text lang_override
}
deck_cards {
text id
text deck_id FK
int position
text front_blob
text back_blob
text card_json
}
blobs {
text hash PK
text ext
text mime
int width
int height
int bytes
text origin
int created_at
}
blob_refs {
text owner_kind
text owner_id
text blob
}
presets {
text id PK
text kind
text name
text json
int updated_at
}
tags {
text id PK
text name
text color
int created_at
}
deck_tags {
text deck_id
text tag_id
}
deck_listas {
text deck_id
text id
text nombre
text comandantes
text cartas
int updated_at
}
app_settings {
text key PK
text value
}
scryfall_cards {
text id PK
text oracle_id
text name
text lang
text set_code
text collector_number
text json
int fetched_at
}
partidas {
text id PK
int empezada
int terminada
text formato
text modo
text jugadores
text ganadores
text como
text nota
int rondas
int duracion_ms
int version_catalogo
blob registro
}
partida_decks {
text partida_id FK
int jugador
text deck_id
int puesto
}
Notes on the tables:
-
deck_cardsprimary key is(deck_id, id);front_blob/back_blobare denormalised copies of the card's image hashes so the garbage collector and the library can find used images without parsingcard_json. The fullDeckCardiscard_json. A duplicate card id inside one deck becomesInvalidInputwithmotivo = duplicate_card_id. -
decks.default_back_jsonis the serializedFaceImage;default_back_blobis its hash (for the same reason). -
presets.kindis one offrontOffsets,backOffsets,print(store/presets.rs, saved by name: same kind and name replaces) orcollection(store/colecciones.rs, saved by id so a rename does not create a second collection; a collection that no longer deserializes, for example from a future version, is skipped bylist). -
scryfall_cardsis a metadata cache (TTL one week,scryfall/cache.rs; prices refresh after 24 h). Note that despite being a cache it lives insidelibrary.dbinapp_data_dir, because statistics and the library work offline from it (commands/stats.rs,biblioteca.rs). -
deck_listas(store/listas.rs) keeps the game lists of the game table:comandantesandcartasare JSON arrays of copy ids of the deck (DeckCard.id). They live apart from the deck, like tags, becausedecks::saverewrites the deck on every autosave and would overwrite them; ids no longer in the deck are only warned about when the list is checked.guardarupserts by(deck_id, id)(a new id when empty) with the name trimmed and capped at 60 characters. -
partidasandpartida_decks(store/partidas.rs) are the history of the game table.registrois thePartidaGuardadaof the game (the same gzip JSON as the saved game,mesa/guardada.rs) orNULLwhen whoever saved it never had the whole game (a guest online).partida_deckshas no foreign key todeckson purpose: deleting a deck keeps its games, with the deck name written inside the players' JSON. Saving a game with an id that exists replaces it (and itspartida_decks); playing never touchesdecks.updated_at. Each player in the JSON also carries the play list used (listaId,lista,cartasLista), written fromdeck_listaswhen the game is saved; rows saved before that read without them. -
tags.coloris one ofCOLORES(gray red orange amber green teal sky blue violet pink); an unknown colour falls back to gray; tag names are trimmed, capped at 40 characters, unique case-insensitively, andcreatereturns the existing tag if the name already exists. - Dates are milliseconds since 1970 (
store::ahora_ms).
decks::save and decks::rename set updated_at. Pin, notes (max 4000 chars), cover card, language override, tags, game lists and "opened at" deliberately do not (etiquetas.rs, decks::marcar_abierto), so organising the library does not reorder it by modification date. Test: favorito_notas_y_portada_no_cambian_la_fecha.
| Function | Behaviour |
|---|---|
create(db, name, default_back) |
New uuid, default PrintSettings, DECK_SCHEMA_VERSION, then save. The command layer (commands/library.rs crear_deck) passes the generic back. |
open(db, id) |
Reads the deck row and all card_json ordered by position; NotFound if missing, Db if JSON no longer deserializes. deck_open also calls marcar_abierto; deck_peek does not. |
save(db, deck) |
One transaction. First validates that every referenced image exists in blobs (fronts, backs, default back, and designer images from refs_del_deck); a miss is InvalidInput, motivo = blob_missing. Then upserts the decks row (keeps created_at and the library columns), deletes and reinserts all deck_cards in order, and rewrites the deck's blob_refs. Returns the save time. |
list(db) |
DeckSummary per deck for the library: card count, cover (chosen card if still present with an image, otherwise the first card with an image), tags. Ordered by updated_at DESC. |
duplicate(db, id, name) |
Opens, assigns new uuids to the deck and every card, saves, then copies notes, cover (remapped to the new card id), language override and tags (not the favourite). Images are shared, not copied. |
delete(db, id) |
Deletes the deck (cascade removes cards, tags and game lists), its blob_refs (owner_kind = 'deck'), its game table mat reference (owner_kind = 'mesa') and its mat setting (mesa.tapete.<id>). Image files are not removed; garbage collection does that later. |
rellenar_refs(db) |
One-time backfill of blob_refs from existing decks, guarded by the app_settings key blob_refs_v1 (an empty table is not a valid marker because a deck without designs leaves it empty). |
Tests: guardar_y_reabrir_135_cartas_da_lo_mismo, no_se_guarda_una_carta_con_una_imagen_que_no_existe, dos_cartas_con_el_mismo_id_es_un_error_claro, duplicar_renombrar_y_borrar, la_limpieza_respeta_el_arte_de_los_disenos, el_relleno_de_referencias_se_hace_una_vez.
BlobStore (store/blobs.rs) is a content-addressed image store.
- Layout:
app_data_dir/blobs/<first two hex chars>/<sha256>.<ext>(BlobStore::path_of).extisjpg,pngorwebp(proxy_core::imaging::probe::Kind). - Identity is the SHA-256 of the file bytes, lowercase hex, 64 chars (
es_huellavalidates this and is reused to validate paths coming from packages and the image protocol). Two decks with the same card share one file. - Metadata row in
blobs:ext,mime,width,height,bytes,origin,created_at.
| Function | Behaviour |
|---|---|
put_bytes(db, bytes, origin) -> FaceImage |
Probes the format (ErrorCode::Unsupported if it is not a supported image; corrupt gives ErrorCode::Image with motivo = corrupt), rejects any side above LADO_MAXIMO = 12000 px (Image, motivo = too_large), hashes, writes atomically if absent, INSERT OR IGNOREs the row. If the blob already existed, the returned origin is the one it first entered with. |
import_files(db, paths) |
Reads user files and put_bytes them as Upload. It copies, never links, so deleting the original cannot break a deck. |
generic_back(db) |
Inserts the bundled generic back (assets/reverso-generico.png, 745x1040, origin Generated) and records its hash in app_settings under generic_back_blob. |
locate(db, hash) |
Path and Kind for a known blob (looks up ext in blobs); None for malformed hashes. |
gc(db, ocupado) |
See below. |
escribir_atomico(dest, bytes) |
Writes to a .part temp in the same folder (its name is unique per write: process id and a counter, because the table preparation writes in parallel and two copies of the same card, or two decks sharing it, write the same destination at once; with a fixed name one renamed the other's temp and failed with «no such file», shown as «cannot write to disk»; test escribir_a_la_vez_el_mismo_destino_no_falla), sync_all, then renames; a crash never leaves a half file under the final name. Reused for most downloads in the app. |
BlobStore::gc deletes blobs that nothing uses and that are older than 24 hours (GRACIA_MS). A blob is kept if any of these holds:
-
deck_cards.front_bloborback_blobequals its hash. -
decks.default_back_blobequals it. - A row in
blob_refspoints to it (designer art, designer emblem, "before" images, collection emblems, game table mats, the dice image, the online avatar). - It is the generic back (
app_settings.generic_back_blob). - It was created less than 24 hours ago.
Why the grace period: an image may belong to a download whose deck has not been saved yet. Why ocupado: gc returns ErrorCode::Busy while a resolve/export job is running (AppState::ocupado()), because the job may hold hashes that no deck row references yet. ocupado() is also true while a game is open on the table (Game Table). The only caller is blob_gc (commands/library.rs), triggered by the "free unused images" action in the library screen (src/features/library/library-screen.tsx); there is no automatic GC. The file is removed first (a missing file is fine), then the blobs row.
blob_refs owners: owner_kind = 'deck' (rewritten on every decks::save from CardDesign::blobs()), 'preset' (a collection's emblem, written in colecciones::save, removed in colecciones::delete) and 'mesa' (the game table mat: owner_id is the deck id or general; written and removed by mesa/tapete.rs::guardar, and removed with its deck in decks::delete; with owner_id = 'dados', the image of the player's dice, written by mesa/dados.rs::guardar) and 'red' (owner_id = 'avatar', the online avatar, written and removed by red/identidad.rs::guardar). The mat cannot use 'deck' because decks::save rewrites a deck's 'deck' references on every save and would drop it. Test: la_limpieza_solo_borra_lo_huerfano_y_viejo.
Gotcha: any new place that stores a blob hash must be reflected in one of the four protections above, or GC will eventually delete the image and the deck will fail to save or print (save refuses to store a reference to a missing blob, so you will usually notice at the next save).
AppState::carpeta_renders() (render.rs) is the only cache directory managed by the app UI. Contents, all derived and regenerated on demand:
Path under renders/
|
Content | Written by |
|---|---|---|
<key>.jpg |
Processed preview thumbnails (key from RenderSpec::key()) |
AppState::miniatura (render.rs) |
t/<hash>.jpg |
Search-result thumbnails fetched from Scryfall or other sources | protocol.rs |
d/<hash>-<width>.png |
Designer card previews |
diseno/vista.rs imagen
|
c/<hash>-<width>.png, c/<hash>.json
|
Designer card "layer without art" and the art-slot info |
diseno/vista.rs capa
|
b/<blob>-<side>.jpg or .png
|
Downscaled art for live dragging |
diseno/vista.rs arte_reducido
|
m/<sha256 or s-<id>-<face>>-<width>.jpg |
Game table images reduced to the width the screen asks for |
mesa/imagenes.rs servir
|
PDF rendering has no disk cache ("it weighs too much", render_para_pdf). The commands cache_stats / cache_clear (commands/library.rs) measure and remove_dir_all exactly this folder; they are exposed in the decklist panel (src/features/decklist/decklist-panel.tsx) with a confirmation text. Test: vaciar_la_cache_no_toca_las_imagenes_de_los_decks.
Accessed through store/settings.rs (get, set, upsert) and, from the screen, the generic commands app_setting_get / app_setting_set (src/lib/ipc.ts). Keys seen in the code:
| Key | Written by | Meaning |
|---|---|---|
last_deck_id |
src/stores/deck-store.ts |
Deck opened at start-up. |
generic_back_blob |
BlobStore::generic_back |
Hash of the generic back; protects it from GC. |
blob_refs_v1 |
decks::rellenar_refs |
Marks that blob_refs was backfilled. |
carpeta_importar, carpeta_exportar
|
commands/carpetas.rs |
Last folder used by import / export file dialogs (kept separately). |
mesa.tapete.<deck id>, mesa.tapete
|
mesa/tapete.rs |
The game table mat of a deck, or the general one: JSON { blob, velo, desenfoque }. The deck key is removed in decks::delete. |
mesa.dados |
mesa/dados.rs |
The look of the player's dice and coin (AspectoDados, JSON). |
red.apodo, red.avatar
|
red/identidad.rs |
Nickname and avatar blob hash used online. |
Other UI preferences (theme, skin, language, panel sizes, library view) are not here; they are localStorage keys with the prefix proxybuilder. (for example proxybuilder.lang, proxybuilder.theme, proxybuilder.preferencias, proxybuilder.biblioteca), see Settings-and-Shell and Themes-and-Skins.
The open deck lives in the zustand store useDeck (deck, guardado, errorGuardado, version, pasado, futuro). See also State-Stores.
-
Single entry point. Every user change goes through
aplicar(cambio): it pushes a snapshot (cards,settings,defaultBack; not the name or list text) ontopasado(capped atMAX_HISTORIAL = 100), clearsfuturo, setsguardado = "dirty"and bumpsversion.setCartasandsetAjustesare wrappers. No screen callsdeck_saveitself. -
Changes that are not undoable.
setSinHistorial(name,decklistText) marks dirty without history.refrescarCartas(antes, nuevas)replaces cards (for example regenerated design thumbnails) without history and only for cards that are still the same object as inantes, so a concurrent user edit wins. -
Undo/redo.
deshacer/rehacerswap snapshots betweenpasadoandfuturoand mark dirty (so undo is also saved).cargar(deck)resets history. -
Autosave.
iniciarAutoguardado(retardoMs = 800)(started insrc/features/shell/use-deck-inicial.ts) subscribes toversionchanges while dirty and debounces an800 mstimer. One save in flight at a time: if another change arrives mid-save, it chains another save afterwards with the latest deck. After a save, the state is"saved"only ifversiondid not move meanwhile, otherwise it stays"dirty". On failure the state is"error"(errorGuardadoset); the next change re-arms the save. -
Flushing.
guardarAhora()saves immediately if not already saved; it is called before opening another deck, duplicating/exporting from the library and before an in-app update installs. -
Start-up.
abrirDeckInicial(nombre)readslast_deck_id, triesdeck_open, and falls back todeck_create; then writeslast_deck_id. -
Rust side.
deck_save->decks::saverewrites the whole deck in one transaction (see above), so a save is atomic: either the whole new deck or the previous one.
Tests: src/stores/__tests__/deck-store.test.ts (five quick changes save once, undo x3 restores and redo replays, error then retry, no-history changes), deck-store-abrir.test.ts.
Everything below is relative to the platform's app_data_dir or app_cache_dir (resolved by Tauri; the paths are not hard-coded). "Safe to delete" means no deck data is lost; the entry is recreated or re-downloaded when needed.
| Path | Content | Created by | Safe to delete? |
|---|---|---|---|
library.db (+ -wal, -shm) |
SQLite library: decks, cards, tags, presets (including collections), settings, scryfall_cards
|
store/db.rs |
No. Deleting it deletes every deck. Quit the app first; copy the three files together. |
blobs/<xx>/<sha256>.<ext> |
Deck images (content-addressed) | store/blobs.rs |
No. Decks would reference missing images. Use the in-app "free unused images" instead. |
modelos/ |
Two OCR models (text-detection.rten, text-recognition.rten, ~12 MB), downloaded on first OCR use |
ocr.rs |
Yes (re-downloaded when OCR is used again). |
temas/<id>.json, temas/fuentes/<hash>.woff2
|
Installed tweakcn themes (converted to CSS) and their Google Fonts | temas.rs |
Yes, but those themes disappear until reinstalled. |
simbolos/ (indice.json + SVGs) |
Scryfall mana/set symbols, renewed when the index is older than 30 days | diseno/recursos.rs |
Yes (re-downloaded). |
fuentes/<sha256>.<ttf|otf>, fuentes/licencias/
|
Fonts imported by the user or from the free-fonts catalogue, with their licences |
diseno/recursos.rs, diseno/letras_libres.rs
|
Only if you accept designs that use them falling back to another font. User-imported fonts are not recoverable. |
marcos/<id>/ |
Frame packs: original (Card Conjurer frames, downloaded), downloadable packs such as Balatro, and packs imported by the user |
diseno/original.rs, diseno/descargables.rs, diseno/recursos.rs
|
Downloaded ones: yes (re-downloaded and SHA-256 checked). User-imported packs: no backup elsewhere. |
packs-extras/<id>/ |
Extra content of downloadable packs (backs, tokens, ready-made cards), kept apart from the pack | diseno/descargables.rs |
Yes (re-downloaded). |
partidas/actual.json.gz |
The game in progress on the table (PartidaGuardada): catalogue version, config, prepared decks, initial game, events, save time and the best-of score, as gzip JSON written atomically. Written when the game opens, every 50 events (CADA), after 20 s with unsaved events (CADA_MS) and when forced (mesa_guardar, which the screen calls when leaving the tab or closing the window; mesa_salir with guardar; mesa_siguiente_partida). Not written by a guest online (the host keeps the game) or during a replay. Deleted by mesa_salir without guardar (not when leaving a replay). Finished games go to the partidas table instead |
mesa/guardada.rs, commands/mesa.rs
|
Yes, but the game in progress is lost. A file from a newer catalogue is refused with Unsupported (motivo: version). |
identidad/clave |
The installation's iroh key (32 bytes, ed25519, mode 0600, written atomically): the player's identity online and on the room server. Importing an identity keeps the previous key in identidad/clave.anterior
|
red/identidad.rs |
Only if you accept being a new player (playgroups would need a new invitation). Export it first (Settings > Game table) to keep it. |
perfiles/<name>/ |
A whole separate data directory for a debug instance (PB_PERFIL; never in release) |
red/perfil.rs |
Yes. |
letras-cjk/<code>.ttf, letras-cjk/Noto-CJK-OFL.txt
|
Noto Sans JP/KR/SC/TC for designed cards in Japanese, Korean and Chinese (ja, ko, zhs, zht; about 10–18 MB each) and their OFL licence, downloaded the first time a deck uses one of those design languages |
diseno/letras_cjk.rs, diseno/recursos.rs (guardar_cjk, leer_cjk) |
Yes (re-downloaded and SHA-256 checked when the language is chosen again; meanwhile those cards show the missing-fonts warning). |
| Path | Content | Created by | Safe to delete? |
|---|---|---|---|
renders/ (+ t/, d/, c/, b/, m/) |
Derived thumbnails, designer previews and reduced game table images |
render.rs, protocol.rs, diseno/vista.rs, mesa/imagenes.rs
|
Yes (button in the decklist panel). |
mesa/ (<xx>/<sha256>.jpg, s/<id>-<face>.jpg, i/<key>.json) |
Game table card faces prepared at 745 px (ANCHO_MESA, JPEG 85), Scryfall prints, mats reduced to 1920 px wide (ANCHO_TAPETE, index key t-<blob>; the mat itself stays in blobs/), and the index from preparation key to image |
mesa/imagenes.rs, mesa/preparar.rs, mesa/tapete.rs
|
Yes (prepared again when a game starts or is resumed). |
mesa/s/<scryfall id>-<face>.jpg |
Scryfall faces shown by «Originals in my language» (and the ones an online game downloads from Scryfall), as downloaded |
mesa/imagenes.rs, mesa/originales.rs
|
Yes (downloaded again when shown). |
mesa/r/<xx>/<sha256>.jpg |
In an online game, the bytes of other players' own images as received (the ones whose SHA-256 matches, to pass on); the sanitised copy shown on screen goes to mesa/<xx>/
|
red/imagenes.rs |
Yes (asked for again when shown). |
scryfall-card-names.json, scryfall-set-codes.json
|
Catalogue of Scryfall card names and set codes for local OCR matching, renewed weekly | scryfall/nombres.rs |
Yes (re-downloaded when needed; needs network). |
cockatrice-tokens.xml |
Cockatrice token database (only the tokens Scryfall lacks are used), renewed weekly | fuentes/cockatrice.rs |
Yes. |
mtgjson/nombres.sqlite |
Optional offline card names in other languages, built from MTGJSON | fuentes/mtgjson.rs |
Yes (a Settings action also removes it). |
perfiles/<name>/ (+ webview/) |
The cache of a debug instance (PB_PERFIL), with its own WebView data directory so instances do not share localStorage
|
red/perfil.rs, lib.rs (crear_ventana) |
Yes. |
- Log file
proxy-builder.login Tauri'sapp_log_dir(registro.rs): every network request is logged there so you can check that opening a saved deck downloads nothing. - Window geometry is kept by
tauri-plugin-window-state(registered inlib.rs, with the visibility flag excluded). Inferred: it writes into the app config directory; not read in this repo. - The updater's downloaded package and signature handling are described in Releases-and-Auto-Updates.
-
app_settings:red.apodo(nickname) andred.avatar(a blob hash, protected by ablob_refsrow of kindred/avatar). The look of the dice ismesa.dados(its image,mesa/dados). - The room server keeps its own SQLite (players, playgroups, invitations) on the server; rooms live in its memory. The app stores nothing of them locally: after a restart it re-registers its room. See Online Play.
| Situation | Result |
|---|---|
Saving a deck whose card references an image not in blobs
|
InvalidInput (motivo = blob_missing, blob = <hash>); the autosave state becomes "error". |
| Two cards with the same id in one deck |
InvalidInput (motivo = duplicate_card_id). |
| Unsupported or corrupt image bytes |
Unsupported / Image (motivo = corrupt). |
| Image larger than 12000 px on a side |
Image (motivo = too_large). |
blob_gc while a job runs or a game is open |
Busy. |
| Stored JSON that no longer deserializes |
Db (with detalle). |
| Poisoned DB mutex |
Internal. |
Error codes are defined in src-tauri/src/error.rs; see IPC-and-Error-Handling.
- New persisted column: add
000N_*.sql, append it toMIGRACIONES, never edit old files. Remember theALTER TABLE ... ADD COLUMNdefault must satisfy existing rows. - New data that references an image: store its hash in a protected place (see GC list) and add it to
refs_del_deck/CardDesign::blobs(). - New downloaded resource: choose
app_data_dirif features depend on it staying,app_cache_dirif it is purely re-creatable; write withescribir_atomico.
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
Frontend · Frontend
- Frontend-Architecture · Arquitectura del frontend
- State-Stores · Stores de estado
- Internationalization · Internacionalización
- Themes-and-Skins · Temas y skins
- 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
- Set-Browser · Explorador de sets
Reference · Referencia