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). |
| 7 | 0007_icono_etiqueta.sql |
Column tags.icon (TEXT, NULL = no icon): the Iconify id of a library tag's icon (game-icons:crown), see Deck Library and Icons. |
| 8 | 0008_partida_imagenes.sql |
Table partida_imagenes (partida_id, sha), primary key (partida_id, sha), partida_id referencing partidas(id) ON DELETE CASCADE, index partida_imagenes_sha: the own images (by SHA-256) a history replay paints, whose copies live in app_data_dir/partidas/imagenes/ (see Game Table). |
| 9 | 0009_linaje.sql | Table deck_linaje (deck_id, linaje, actualizado), deck_id the primary key and referencing decks(id) ON DELETE CASCADE, index deck_linaje_linaje; the migration also gives every existing deck its own id as lineage and its updated_at as time: which decks on different computers are «the same deck» (Deck Sharing). |
| 10 | 0010_amigos.sql | Tables amigos_mensajes (the decrypted conversation with each friend: amigo = their identity in hex, mio, tipo texto/invitacion, texto, enviado = sender's clock, orden = this device's clock, estado, id_cliente, id_servidor; unique indexes on id_cliente and on id_servidor of received ones, so a resend or a lost ack never duplicates) and amigos_claves (per friend: the encryption key last seen, verificado, cambio, leido_hasta). The server only ever held these messages encrypted (Friends and Messages). |
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| deck_linaje : "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"
partidas ||--o{ partida_imagenes : "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
text icon
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
}
deck_linaje {
text deck_id PK
text linaje
int actualizado
}
partida_imagenes {
text partida_id FK
text sha
}
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. -
deck_linaje(store/linaje.rs) says which decks are the same deck on different computers.linajeis an id shared by every copy: a new deck takes its own id (decks::saveinserts the row), a duplicate gets another one, and one imported or received keeps the one it came with.actualizadois the time (ms) of the version's last save, on the computer where it was made:decks::saveanddecks::renamerefresh it, and it travels in the.pbdeck(Biblioteca.linaje/actualizado). A deck with no row (from before the migration, or inserted by hand in a test) counts as having its own id as lineage (linaje::de,linaje::decks_de). It lives apart from the deck like tags and game lists, and writing it does not changeupdated_at. -
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. -
partida_imagenes(store/partidas.rs) lists, per game with a replay, the hashes of the own images it paints (NuevaPartida.imagenes). It is not ablob_refsowner: these images are not blobs but table images (sanitised JPEGs, other players' included), copied out of the cache toapp_data_dir/partidas/imagenes/.partidas::borrardeletes the game in one transaction and returns the hashes that no other row still uses, so the command can delete those copies; a hash shared by two games stays until the last one goes. -
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.tags.iconis set apart withetiquetas::icono(tag_icon_set), which only accepts a valid Iconify id (iconos::id_valido) orNULL;updatekeeps it. - 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 from the «Liberar espacio» dialog (src/features/library/liberar-dialog.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).
«Liberar espacio» can also convert the images of chosen decks, with the rules of Deck Package Format, and delete the originals from this device. It cannot be undone, so the order is what protects the data:
- Every JPEG is made and stored first (
optimizar). No deck has changed yet: cancelling or a failure here leaves the library as it was, and what is left over is collected bygc. - Each deck is rewritten in one transaction (
decks::save_imagenes), which keepsupdated_atand the lineage time: the content is the same, and otherwise another device would see a newer version. -
BlobStore::borrar_sin_usardeletes an original only if nothing uses it (usos: another deck, a collection, the table, the generic back), withoutgc's 24 h grace because these are known to have just stopped being needed.
estimar counts only what really frees up: an original another deck still uses stays, and its JPEG takes space too. An image where it does not pay off (transparency, flat colours) is recorded in optimizar.descartadas and not offered again, because finding out means reading it. It refuses with Busy during a job or a game. The screen does not allow it from the Design view (a card being edited holds hashes of its own), and an open deck goes through prestarDeck with conservar: false, since undo would point at deleted images. Saved games are unaffected: the table uses its own images (mesa/imagenes.rs).
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 |
The device's name when passing decks (empty: the system's, pasar::nombre_equipo) and the avatar blob hash. |
amigos.activo, amigos.cursor, amigos.hola, amigos.codigo
|
amigos/ |
Friends: whether the person accepted using the server ("1"), the sync cursor, the fingerprint of the last hello and this device's friend code. The last three are emptied on leaving or importing another identity (Friends and Messages). |
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). |
partidas/imagenes/<xx>/<sha256>.jpg |
Copies of the own table images that the replays of the game history paint (table partida_imagenes), written after a finished game is saved; a missing cache image (app_cache_dir/mesa/) is restored from here |
mesa/imagenes.rs (guardar_para_historial, servir, olvidar_de_historial), store/partidas.rs
|
Only together with the history games: without them those replays show empty cards. Each copy is deleted with the last game that uses it. |
identidad/clave |
The installation's iroh key (32 bytes, ed25519, mode 0600, written atomically): the player's identity online and on the friends server (it signs its requests and derives the message encryption key, see Friends and Messages). Importing an identity keeps the previous key in identidad/clave.anterior. On Android it is sealed instead: identidad/clave.sellada holds the key encrypted with an AES-GCM key from the Android Keystore (Cofre, android::cofre), so a copy of the file is useless off the phone. A key found unsealed is sealed at startup, and the plain file is deleted only after checking that what was sealed opens and gives the same key; if the Keystore fails it stays as it was rather than losing the identity (friends depend on it). The desktop keeps the file with user-only permissions. |
red/identidad.rs |
Only if you accept being a new player (friends would have to add you again). Export it first (Settings > Game table) to keep it. |
amigos/textos-avisos.json, amigos/avisados.json
|
Android only: the notification templates the screen last sent (amigos_textos) and the ids of pending messages already notified, for the background check with the app closed |
amigos/fondo.rs |
Yes: the templates come back when the app opens; at worst a pending message is notified once more. |
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). |
iconos/elegidos.json |
The Iconify icons the user chose for something stored (a tag icon), with their SVG data, so they show offline | iconos.rs |
Yes, but chosen icons need the network again to show. |
| 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; the images of history replays come back from partidas/imagenes/). |
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. |
pasar/<uuid>/deck.pbdeck |
The package being sent or received while an exchange runs (Deck Sharing); the folder is deleted when the exchange ends or is cancelled, and what is left is removed when «Recibir» opens or a send starts |
pasar/mod.rs (Temporal) |
Yes. |
fichero/ |
On Android, copies of the content:// files the person picks (an import is read from a copy because a ZIP needs Seek) and the temporary files written before copying or sharing; a copy is deleted when released and anything older than 24 h is swept |
fichero.rs |
Yes. |
actualizacion/ |
On Android, the APK the updater downloaded before handing it to the system installer; removed when the app is up to date | actualizar.rs |
Yes. |
iconos/ (colecciones.json, <prefix>.json) |
The Iconify catalogue for the icon picker (collection list and the names of each collection), renewed after 7 days | iconos.rs |
Yes (re-downloaded when the picker opens). |
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.
Nothing about an exchange is stored apart from the decks it creates and their lineage (deck_linaje). The code, its secret and the nearby announcement live in memory for 15 minutes; the package in transit is in app_cache_dir/pasar/. The iroh key it uses is the installation's (identidad/clave), the same as online play.
-
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 friends server keeps public keys, friendships and encrypted messages until they are acknowledged; the decrypted conversations live only in this database (
amigos_mensajes,amigos_claves, migration 0010). See Friends and Messages.
| 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. |
optimizar_lanzar while a job runs or a game is open |
Busy; nothing changes. |
| 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
- Rename-to-Tapete · Cambio de nombre a Tapete
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
- The-Card-Stamp · El sello de las cartas
- 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
- Game-Engine · Motor de la mesa
- Game-Table · Mesa de juego
- Online-Play · Juego en línea
- Friends-and-Messages · Amigos y mensajes
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