Skip to content

Storage and Blobs

Pablo Martínez López edited this page Oct 27, 2026 · 13 revisions

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).

Directory ownership rule

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).

SQLite: library.db

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

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.

Tables

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
    }
Loading

Notes on the tables:

  • deck_cards primary key is (deck_id, id); front_blob / back_blob are denormalised copies of the card's image hashes so the garbage collector and the library can find used images without parsing card_json. The full DeckCard is card_json. A duplicate card id inside one deck becomes InvalidInput with motivo = duplicate_card_id.
  • decks.default_back_json is the serialized FaceImage; default_back_blob is its hash (for the same reason).
  • presets.kind is one of frontOffsets, backOffsets, print (store/presets.rs, saved by name: same kind and name replaces) or collection (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 by list).
  • scryfall_cards is a metadata cache (TTL one week, scryfall/cache.rs; prices refresh after 24 h). Note that despite being a cache it lives inside library.db in app_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: comandantes and cartas are JSON arrays of copy ids of the deck (DeckCard.id). They live apart from the deck, like tags, because decks::save rewrites the deck on every autosave and would overwrite them; ids no longer in the deck are only warned about when the list is checked. guardar upserts by (deck_id, id) (a new id when empty) with the name trimmed and capped at 60 characters.
  • partidas and partida_decks (store/partidas.rs) are the history of the game table. registro is the PartidaGuardada of the game (the same gzip JSON as the saved game, mesa/guardada.rs) or NULL when whoever saved it never had the whole game (a guest online). partida_decks has no foreign key to decks on 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 its partida_decks); playing never touches decks.updated_at. Each player in the JSON also carries the play list used (listaId, lista, cartasLista), written from deck_listas when the game is saved; rows saved before that read without them.
  • tags.color is one of COLORES (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, and create returns the existing tag if the name already exists.
  • Dates are milliseconds since 1970 (store::ahora_ms).

What touches updated_at

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.

Key operations (store/decks.rs)

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.

The blob store

BlobStore (store/blobs.rs) is a content-addressed image store.

  • Layout: app_data_dir/blobs/<first two hex chars>/<sha256>.<ext> (BlobStore::path_of). ext is jpg, png or webp (proxy_core::imaging::probe::Kind).
  • Identity is the SHA-256 of the file bytes, lowercase hex, 64 chars (es_huella validates 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.

API

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.

Garbage collection

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:

  1. deck_cards.front_blob or back_blob equals its hash.
  2. decks.default_back_blob equals it.
  3. A row in blob_refs points to it (designer art, designer emblem, "before" images, collection emblems, game table mats, the dice image, the online avatar).
  4. It is the generic back (app_settings.generic_back_blob).
  5. 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).

Render cache (app_cache_dir/renders/)

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.

app_settings (key/value in SQLite)

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.

Per-deck autosave and undo (src/stores/deck-store.ts)

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) onto pasado (capped at MAX_HISTORIAL = 100), clears futuro, sets guardado = "dirty" and bumps version. setCartas and setAjustes are wrappers. No screen calls deck_save itself.
  • 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 in antes, so a concurrent user edit wins.
  • Undo/redo. deshacer / rehacer swap snapshots between pasado and futuro and mark dirty (so undo is also saved). cargar(deck) resets history.
  • Autosave. iniciarAutoguardado(retardoMs = 800) (started in src/features/shell/use-deck-inicial.ts) subscribes to version changes while dirty and debounces an 800 ms timer. 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 if version did not move meanwhile, otherwise it stays "dirty". On failure the state is "error" (errorGuardado set); 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) reads last_deck_id, tries deck_open, and falls back to deck_create; then writes last_deck_id.
  • Rust side. deck_save -> decks::save rewrites 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.

Data directory layout

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.

app_data_dir

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).

app_cache_dir

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.

Other locations

  • Log file proxy-builder.log in Tauri's app_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 in lib.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.

Online play

  • app_settings: red.apodo (nickname) and red.avatar (a blob hash, protected by a blob_refs row of kind red/avatar). The look of the dice is mesa.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.

Failure modes

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.

Where to start when extending

  • New persisted column: add 000N_*.sql, append it to MIGRACIONES, never edit old files. Remember the ALTER TABLE ... ADD COLUMN default 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_dir if features depend on it staying, app_cache_dir if it is purely re-creatable; write with escribir_atomico.

See also

Documentation · Documentación

Language: English · Español

Use and build · Uso y desarrollo

Architecture · Arquitectura

Data · Datos

Getting cards · Conseguir cartas

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

Card designer · Diseñador de cartas

Game table · Mesa de juego

Frontend · Frontend

Reference · Referencia

Clone this wiki locally