Skip to content

Settings and Shell

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

Settings and Shell

English · Español

The shell is the frame of the app: the top bar (view switcher, deck name, save indicator, update button, help button, Settings button), the three resizable panels of the deck view, and the boot hooks that open the last deck and start autosave. The Settings dialog holds interface language, in-app updates, the optional multilingual-names index and the whole appearance system. This page documents both, what each setting does and where it is persisted.

Code: src/features/shell/ and src/features/settings/.

Shell (src/features/shell/)

File Responsibility
app-shell.tsx AppShell: header + main area; NombreDeck (inline deck-name input); mounts <Tutorial /> and calls useTutorialInicial() (Tutorials); mounts <ConfirmarCierre />; cambiarVista asks before leaving Design with an unsaved card (below); useEnlacesEntrantes (src/features/salas/enlaces-entrantes.ts) switches to Play when an invitation link proxybuilder://unirse/<code> arrives and is accepted (it is refused with a toast while a game is running; Online Play).
scroll-por-vista.ts useScrollPorVista(vista, raiz): remembers the scroll of each view and puts it back when returning (below).
confirmar-cierre.tsx ConfirmarCierre: the guard that asks before the window closes when something would be lost (below).
use-deck-inicial.ts useDeckInicial(): boot of deck, autosave, plan, undo/redo keys.
save-indicator.tsx SaveIndicator: "Saved / Saving... / Unsaved / Couldn't save".
actualizacion-automatica.tsx Auto-update loop, update toasts, BotonActualizar, PuntoActualizacion, useAvisoAjustes.

Top bar (AppShell header, 48 px high)

Left to right:

  1. Logo and title ("Proxy Builder", subtitle "Scryfall . print-ready").
  2. A single-select ToggleGroup bound to useUi().vista: Deck, Design ("Diseñar"), Library ("Biblioteca"), Play ("Jugar", Swords icon, the game table; it carries a small "Beta" pill, t("Beta") in shell.json, while the game table is still in beta). Only these four values are accepted by onValueChange (clicking the active item returns an empty value that is ignored, so one is always selected).
  3. NombreDeck (hidden in the Library and Play views): an Input that edits deck.name. Commit on blur or Enter, Escape reverts; an empty value reverts; changes go through useDeck.setSinHistorial({ name }) (saved, not undoable).
  4. Right side: SaveIndicator, BotonActualizar, the help button (ghost icon button with CircleHelp, tooltip and aria-label "Tutorial for this screen"; it calls useUi().abrirTutorial(vista) for the current screen, see Tutorials) and the Settings button.

Several of these elements carry a data-tutorial attribute (pestanas, nombre-deck, ayuda, ajustes): they are the anchors the deck tour outlines.

SaveIndicator reads useDeck().guardado / errorGuardado; it renders nothing until a deck is open, and on error wraps the label in a tooltip with mensaje(error).

Main area

vista === "deck": ResizablePanelGroup horizontal with izquierda (decklist, default 22 %, min 260 px), centro (preview, min 360 px), derecha (export, default 22 %, min 260 px) and two handles with grips. Sizes persist in localStorage["proxybuilder.paneles"] through useDefaultLayout. The other views are a plain switch on vista: disenar, mesa and, for anything else, biblioteca render the screen App passes in. mesa is MesaScreen (src/features/mesa/mesa-screen.tsx, Game Table): it asks Rust mesa_estado once and, until then, renders nothing; with no game open it shows the entrance EntradaMesa, otherwise the table. Leaving the tab keeps the game open in Rust and saves it (guardar() on unmount); coming back picks it up as it was.

The entrance (src/features/mesa/entrada.tsx) has the deck list of the library (search box, «Historial» of finished games, the most recently opened deck preselected), the «Partida a medias» card when a saved game exists («Seguir la partida» / «Descartar»), the player name, format, list to play (required in Commander when the deck has more than 100 cards), lives, mulligan, the turn helps, the sparring intensity and «Tapete de este deck…», and four ways to start: «Solitario» (asks first if it would replace the saved game), «Con amigos…» (rooms server), «Partida local…» and «En red…» («Con amigos…» and «En red…» turn into «Volver a la sala» while one of their rooms is open). The window minimum is 960 x 640 and the default 1440 x 900 (src-tauri/tauri.conf.json).

Scroll per view (scroll-por-vista.ts)

Switching tabs unmounts the previous view, so its scroll positions used to be lost. AppShell calls useScrollPorVista(vista, principal) with a ref to <main>:

  • Recording. One capture-phase scroll listener on <main> (scroll does not bubble) stores scrollTop of every ScrollArea viewport ([data-slot="scroll-area-viewport"]) in a Map<Vista, Map<index, top>> kept in a ref. The key of each scroll area is its order among the viewports inside <main>; dialogs are portalled outside <main>, so they never count.
  • Restoring. When a view becomes active, a requestAnimationFrame loop sets each saved scrollTop as soon as that viewport is tall enough (scrollHeight - clientHeight >= top), for up to 2 s (ESPERA_MS). Lists and the preview fill in after the first paint, so setting it once at mount would be clamped to 0. A wheel, pointerdown or keydown inside <main> stops the loop, so the restore never fights the user.
  • Why not keep the views mounted and hidden. display: none also resets the scroll, and the preview and the designer would keep working underneath (Live Preview, Designer UI).
  • Limits. In memory only (lost on closing the app). Because the key is the order, a scroll area that appears or disappears between visits (for example the Design form depending on whether a card is selected) can shift which one gets which position.

Leaving "Design" with an unsaved card

AppShell routes the tab switch through cambiarVista(v). If the current view is disenar and useEdicion.getState().pendiente is set (the card being designed has unsaved changes), the move is parked in salirDeDisenar and a second Confirmar ("Discard the changes?" / "This card has unsaved changes.") asks with three outcomes: Cancel, Discard (destructive, runs the parked move) and Save and continue (saves through pendiente.guardar() and moves only if it returned true). Why it exists: DisenarScreen unmounts when another view is shown, so before this guard the draft was dropped silently. Why the shell does not know how a card is saved: DisenarScreen registers a guardar callback in useEdicion while it has changes (State Stores, Designer UI), and the shell only calls it.

Closing the app (confirmar-cierre.tsx)

ConfirmarCierre registers getCurrentWindow().onCloseRequested(...) once. Flow of one close request:

flowchart TD
    A["Close requested"] --> B{"Dialog already open?"}
    B -->|"yes"| C["preventDefault, nothing else"]
    B -->|"no"| D["guardarAhora(): flush the deck"]
    D --> E["pendientes(deckSinGuardar) in lib/cierre.ts"]
    E --> F{"hayPendientes?"}
    F -->|"no"| G["return without preventDefault: Tauri's wrapper calls destroy(), window closes"]
    F -->|"yes"| H["preventDefault + Confirmar: Close Proxy Builder?"]
    H --> I["Cancel: stay, ask again at the next attempt"]
    H --> J["Close anyway: cancelarTrabajo if a job runs, save deck, destroy()"]
    H --> K["Save and close (only with an unsaved card): useEdicion.pendiente.guardar(), then close; if saving fails the dialog and the window stay"]
Loading

Decisions and invariants:

  • The deck is flushed first. Autosave waits 800 ms after the last change (State Stores), so a change made right before closing used to be lost. guardarAhora() runs on every close request; if it throws, "the deck could not be saved" becomes one more reason to ask (pendientes(true)).
  • Reasons (src/lib/cierre.ts, pendientes() / hayPendientes(); each is a field of Pendientes, and the dialog lists only those that apply): a long job (useTrabajo.actual, with its phase text: resolve, export, design language), an update downloading or installing (useActualizacion.paso is bajando or instalando; an update that is merely available or ready does not count), an open dialog with a running process or unsaved typing (lib/dialogos-abiertos.ts, below), the card being designed with unsaved changes (useEdicion.pendiente), the deck failing to save, and a game open on the table (useMesa.activa, reason «Hay una partida en la mesa: se guarda y podrás seguirla al volver.»).
  • The game is saved on every close request. Right after flushing the deck, the handler calls guardar() of src/features/mesa/conexion.ts (mesa_guardar), and «Close anyway» calls it again before destroy(). An open game still counts as a reason so the user knows it will be waiting in «Jugar» (Game Table).
  • With no reason the dialog never appears. The handler returns without preventDefault() and the window closes as usual.
  • "Close anyway" is destructive: it cancels the running job through cancelarTrabajo() (export_cancel or job_cancel), tries to save the deck once more and calls destroy(). Exports write atomically, so closing in the middle of an export never leaves a half-written PDF under its final name. If destroy() fails, a toast says the window could not be closed.
  • A second close attempt while the dialog is open is ignored (abierto ref): it is preventDefaulted and no second dialog stacks up.
  • Outside Tauri (the view opened in a plain browser during development) getCurrentWindow() throws and is caught: there is no window to guard.
  • 🛑 destroy() needs a permission. With a JavaScript onCloseRequested listener Tauri no longer closes the window by itself; the wrapper (or the handler) calls destroy(). That command is core:window:allow-destroy, which is not part of core:default / core:window:default, so src-tauri/capabilities/default.json lists it explicitly (see Rust App Layer). Without it the window could not be closed at all; the build validates the identifier.
  • The updater is not blocked by this guard. "Restart and update" saves the deck itself (guardarAhora()) and restarts from Rust (request_restart), which does not go through the window close request. Inferred: from Tauri's behaviour; not exercised end to end.
  • Not covered. Inferred: on macOS, Cmd+Q or "Quit" from the Dock may not emit a window close request, so the guard may not run there.

Dialogs that declare their state (src/lib/dialogos-abiertos.ts)

Some dialogs own a long process or unsaved state of their own. They tell the close guard through useEstadoParaCerrar({ enCurso, sinGuardar }), called with the same values the component already passes to DialogContent as ocupado / cambiosSinGuardar. The hook keeps the latest values in a ref and registers a reader in a module-level Set (registrarDialogo, which returns the function that removes it); estadoDeLosDialogos() ORs all registered readers. Because the registering component is the dialog's Contenido, which only exists while the dialog is open, the registration lives exactly as long as the dialog is open.

Why a hook per dialog and not inside DialogContent: src/components/ui/ is read-only (inherited from the design system the app is based on), so dialog.tsx is not touched.

Dialog File enCurso sinGuardar
Design the deck src/features/disenar/lote-dialog.tsx running no
Export images src/features/disenar/png-dialog.tsx running no
Link in bulk src/features/variants/vincular-lote.tsx working reviewing rows (fase === "revisar" with rows)
Collections src/features/disenar/colecciones-dialog.tsx no cambiada

Dialogs without the hook are not asked. Inferred: they only get the typed-text rule of DialogContent for closing themselves, not for closing the app.

Tests: src/lib/__tests__/cierre.test.ts (Testing and CI).

Boot (useDeckInicial, called from App)

  1. iniciarAutoguardado() and iniciarPlan() (see State Stores).
  2. A useDeck.subscribe that, whenever the open deck id changes, calls rehacerMiniaturas() (src/features/disenar/renumerar.ts) to re-render design thumbnails made stale by a changed collection, font or engine.
  3. abrirDeckInicial(t("Deck sin título")): reopen last_deck_id or create a new deck. The returned error (if any) is stored in the hook's state.
  4. Separately, a keydown listener for Ctrl/Cmd+Z (undo) and Shift variant (redo), disabled while typing in an input, textarea or contenteditable.

Window startup

Decision: the window is created hidden with a dark background colour and shown as soon as possible, to avoid a white flash.

  • tauri.conf.json: "visible": false, "backgroundColor": "#1d1b19".
  • src-tauri/src/lib.rs: the window-state plugin saves size/position/maximised state of all flags except VISIBLE (otherwise the saved state could show the window before it is painted). on_page_load shows the window on PageLoadEvent::Finished; a background thread shows it after 3 s if it is still hidden (page never loaded).
  • src/main.tsx also calls getCurrentWindow().show() right after render (it does not wait for a frame: WebKitGTK does not deliver requestAnimationFrame to hidden windows), and ponerTemaAntesDeMontar() already set the right colours, so there is no light flash. See Frontend Architecture and Themes and Skins.

Settings dialog (src/features/settings/settings-dialog.tsx)

Opened by the Settings button (AppShell keeps open in local state). DialogContent is sm:max-w-3xl with cambiosSinGuardar={false} (nothing to lose when closing). The header shows the version read once with getVersion() from @tauri-apps/api/app ("Proxy Builder v2.5.3") and, when an update is downloaded, the ReiniciarParaActualizar button.

Body, top to bottom:

Section Component Effect Persistence
Language SelectorIdioma (src/components/selector-idioma.tsx): a Select with each language's flag and its own name, over the 15 languages of IDIOMAS (src/lib/idiomas.ts) Interface language, no reload. The new dictionary is loaded first and the switch happens when it arrives, so the screen never shows half-translated text. localStorage["proxybuilder.lang"]; see Internationalization.
Updates Actualizaciones (actualizaciones.tsx) Check now, download, restart; the auto-update switch. Switch: useUi().actualizarSola in proxybuilder.preferencias. State machine in memory.
Names in other languages NombresIdiomas (nombres-idiomas.tsx) Optional download of the MTGJSON name index. On disk by Rust (see below).
Appearance Appearance (appearance.tsx) Mode, scale, smooth scroll, skin, filigree, background, material, effects, colour theme, "More themes...". localStorage keys listed in Themes and Skins.
Game table («Mesa de juego», closed at first) AjustesMesa (mesa.tsx) Six groups, below. useUi().mesa in proxybuilder.preferencias, except the dice look (Rust) and the online identity (Rust); see Game Table.

Updates section (Actualizaciones)

  • Calls useActualizacion.iniciar() on mount. If soportada === false it shows "Only in the installed app: this copy does not come from an installer (for example, in development)." and nothing else.
  • "Check now" (buscar) is disabled while busy; if a version is found and auto-update is on, it downloads straight away (like at startup).
  • When a version is available and auto is off: a "Download vX" button. When downloaded (lista/instalando): "Restart and update" button (dismisses the "actualizacion" toast, calls instalar(), and toasts the error if it fails).
  • A Progress bar appears while downloading; the last error is printed with mensaje(error); the release notes (Markdown text, English) appear in a ScrollArea when present; "You have the latest version (vX)" when up to date.
  • The status line says that on .deb/.rpm installs the system will ask for the password on restart (ActualizacionInfo.pidePermiso).

State machine details are in State Stores; the Rust updater, signature checks and release format are in Releases and Auto Updates. Installing is refused with Busy while a long job runs (the deck is saved first, the step goes back to lista). With a game open on the table the screen does not ask: the install is queued (alSalirDeLaMesa, toast «Se instalará al salir de la mesa.») and runs when the player leaves the table; Rust still refuses it with Busy motivo: partida as a safety net. Checking and downloading are not blocked.

The same SelectorIdioma picks the design language in the export panel and in the Diseñar preview (src/features/export/export-panel.tsx, src/features/disenar/vista-previa.tsx): interface, design and deck languages share the Scryfall codes of src/lib/idiomas.ts, so one component serves all three. The flags are Noto Color Emoji images (Bandera in src/components/emoji.tsx), not the system emoji font, because Windows' Segoe UI Emoji has no flags; see Frontend Architecture.

Names in other languages (NombresIdiomas)

Optional download (about the size shown on the button, "Download (about N MB)") of the MTGJSON per-language name index, stored as SQLite by Rust, so cards typed by their Spanish (or other language) printed name that Scryfall lacks can be found locally, both in the list and when linking. Without it the app asks magicthegathering.io online, which is slower. Commands: names_index_info, names_index_download (progress event names://progress with phases; during the "reading" phase the bar is full and the text says "Preparing the index..."), names_index_remove. Shows the count of downloaded names. See External Sources and Card Identification and OCR.

Appearance section

Documented control by control in Themes and Skins (mode, size 70 to 150 %, smooth scroll, Plano/Zeus/Hera, filigree, background, material, blur/grain/shadow, "Include the side panels", theme grid, "More themes...").

Smooth scroll (proxybuilder.smoothScroll, default on) mounts SmoothScroll (src/lib/SmoothScroll.ts) through SmoothScrollClient: the wheel animates the native scrollTop of the nearest scrollable ancestor. Turning it off destroys the instance.

Game table section (AjustesMesa)

Each control writes one field of useUi().mesa (PreferenciasMesa in src/stores/ui-store.ts, factory values in MESA_DE_FABRICA) through setMesa, unless said otherwise. Stored preferences are merged over MESA_DE_FABRICA when read, so a field added later gets its factory value.

Group Controls (field)
«Jugar» Offer the aura and equipment bonus when attaching (ofrecerBono, on); see the phantom's life in solitaire (fantasma, on); online, see how the others move and show them my cursor (movimientoEnVivo, on); in a local game, switch to the player whose turn it is (seguirTurno, off); sparring intensity when starting solitaire (sparring, apagado; the entrance has the same picker); holding the button opens the card menu (clicSostenido, off).
«Tablero» Size of the cards on the battlefield (tamanoCartas, 25 to 200 %, 100); size of the large view (vistaGrande, 200 to 500 px, 320); status panel next to the large view (panelEstado, on).
«Dados y monedas» (aspecto-dados.tsx, AjustesDados) The look of your dice and coin, with a CSS 3D sample thrown by «Probar» (with sound): die colour (the player's colour or your own), number colour, finish (liso, nacarado, marmol, purpurina, translucido), an own image on each face (replaces colour and finish) and the coin metal (oro, plata, cobre). Not in useUi: useAspectoDados (src/features/mesa/dados/aspecto.ts) reads and writes it through mesa_dados_get, mesa_dados_set and mesa_dados_imagen; Rust keeps it in app_settings key mesa.dados and the image in the blob store (src-tauri/src/mesa/dados.rs). Changes are sent 250 ms after the last one.
«Sonidos» One switch per group of sounds: dice and coins (sonidoDados), cards: shuffle, draw, tap, play (sonidoCartas), passing the turn and your turn (sonidoTurno), chat messages and the sparring phantom (sonidoChat), all on; one volume for all (volumen, 0 to 100 %, 60).
«Identidad en red» (identidad-red.tsx) Export the installation's online identity to a JSON file or import another one (asks first; the current one is kept as a copy in the app data folder). Done by Rust (red_identidad_exportar, red_identidad_importar); importing is refused with Busy while a game or a room is open. Not a preference.
«Rendimiento» Image quality on the battlefield (calidad: 146, 244 or 488 px wide, 244); dice and coins (dados: auto, 3d with physics, css light 3D, 2d; auto); reduce animations (reducirAnimaciones, off); shadows (sombras, on); frame counter (fps, off); «Modo rendimiento (todo al mínimo)» applies MODO_RENDIMIENTO (src/features/mesa/fps.tsx: quality 146, no shadows, reduced animations, no status panel).

PreferenciasMesa also holds fields with no control here: sparringVisto (the sparring "how it works" card was shown), trackers (player counters shown next to the life) and disposicion (board layout remembered per number of players), set from the table itself.

Update button, dot and toasts (actualizacion-automatica.tsx)

Piece Behaviour
useActualizacionAutomatica() Only when useUi().actualizarSola is true (default). After 8 s (PRIMERA_MS) and then every 6 h (CADA_MS): iniciar(); if unsupported stop; buscar(); if a version exists, bajar(). Network failures or no release are silent. Timers are cleared when the switch is turned off.
useAvisosActualizacion() One useActualizacion.subscribe, mounted once in App: a loading toast "Downloading Proxy Builder X..." with the percentage as description while bajando; a persistent success toast "Proxy Builder X is ready" with a Restart action when download completes (once per version); an error toast "Couldn't download the update" with the mapped error if the download fails. All use toast id "actualizacion" so they replace each other.
BotonActualizar Top-bar outline button "Update to X" visible only in lista/instalando; spinner while installing; tooltip says whether the system will ask for a password.
PuntoActualizacion Small dot on the Settings button: amber and pulsing while bajando; theme-primary when lista/instalando.
useAvisoAjustes() Tooltip/aria text for the Settings button while an update is in progress ("Downloading X..." / "X is ready: restart from Settings.").

The toast strings read the current language through a ref (textos.current = lang), so a language change mid-download is respected without resubscribing.

Where each preference lives (settings map)

Setting UI location Stored in
Interface language Settings localStorage proxybuilder.lang
Theme, mode, skin, background, material, blur, noise, shadow, scale, flags, smooth scroll Settings > Appearance localStorage proxybuilder.theme / .mode / .skin / .bg / .material / .blur / .noise / .shadow / .scale / .fxSidebar / .heraFiligree / .smoothScroll
Installed tweakcn themes Settings > More themes Rust files under the app data folder temas/, mirrored in proxybuilder.temasInstalados
Auto-update Settings > Updates useUi().actualizarSola (proxybuilder.preferencias)
Game table settings Settings > Mesa de juego useUi().mesa (proxybuilder.preferencias)
Dice and coin look Settings > Mesa de juego > «Dados y monedas» Rust app_settings key mesa.dados (own image in the blob store)
Game table player name Play tab entrance localStorage proxybuilder.mesa.nombre
Game table mat (per deck and general) Play tab: «Tapete de este deck…» and the table bar Rust app_settings keys mesa.tapete.<deck id>, mesa.tapete (blob protected in blob_refs)
Screens whose tutorial was already shown automatic (first visit) useUi().tutorialesVistos (proxybuilder.preferencias)
MTGJSON names index Settings Rust (downloaded file, SQLite index)
Card language, image policy, add tokens, preview quality, ask before replacing Decklist panel > Options useUi() (proxybuilder.preferencias)
Preview zoom, card info overlay Preview toolbar useUi() (proxybuilder.preferencias); "Show backs" is not persisted
Panel widths drag the handles localStorage proxybuilder.paneles, .panel-lista, .paneles.disenar
Section open/closed section headers localStorage proxybuilder.seccion.<id>
Library view, sort, grouping, size Library toolbar localStorage proxybuilder.biblioteca (filters not stored)
Last deck opened automatic Rust app_settings key last_deck_id
Last import folder / last export folder automatic Rust app_settings keys carpeta_importar, carpeta_exportar
Window size, position, maximised automatic tauri-plugin-window-state (visibility excluded)
Print settings (page size, card size, margins, bleed, cut guides, etc.) and export presets Export panel Inside the deck (Deck.settings, saved with the deck); presets in Rust (preset_* commands). See Export.

Factory values worth knowing (they are product decisions, not accidents): interface language English; theme Amber Minimal in dark mode with matte material; preview zoom 80 %; preview quality 488 px; "ask before replacing cards" on; auto-update on. Print defaults (2 mm gap, bleed 0, cut guides off, "Fill corners" off) live in the layout/export code, see Layout Engine.

Remembered folders (file dialogs)

All file dialogs go through src/lib/dialogos.ts; never call @tauri-apps/plugin-dialog directly.

Function Use
abrirParaImportar(opciones) Open dialog starting in the last import folder; returns the paths (array) or null; remembers the folder of the first path.
guardarParaExportar(opciones, nombre) Save dialog starting in the last export folder with nombre as proposed file name.
elegirCarpetaParaExportar() Directory picker (loose image export), also remembers the export folder.
recordar(tipo, ruta) Fire-and-forget remember; used when files are dragged onto the app.

Mechanics: carpeta_inicial({ tipo, nombre }) (Rust src-tauri/src/commands/carpetas.rs) returns the stored folder if it still exists (joined with nombre if given), otherwise nombre or null. carpeta_recordar({ tipo, ruta }) stores the directory of the path (the path itself if it is a directory, else its parent) under carpeta_importar or carpeta_exportar in app_settings. TipoCarpeta is "importar" | "exportar" (generated type). Import and export folders are deliberately separate, so exporting a PDF to Desktop does not redirect the next image import. Failures of either command are swallowed (the dialog just opens at its default location). Used by features/decklist/acciones.ts, features/export/acciones.ts, features/export/menu-exportar.tsx, features/library/library-screen.tsx and the designer dialogs. Tests: src/lib/__tests__/dialogos.test.ts and commands/carpetas.rs unit test la_carpeta_de_una_ruta.

Cache and image-store maintenance (not in Settings)

There is no "storage" section in the Settings dialog. Two maintenance actions live where they are relevant:

  • Clear thumbnail cache: in the decklist panel (decklist-panel.tsx), a confirmation dialog shows size and file count from cache_stats and then cache_clear empties only the derived renders cache; deck images are never touched.
  • Free unused images: in the Library screen, blob_gc removes blobs no deck references (the Rust side keeps recent ones for 24 h); the toast reports count and megabytes.

Rationale: deck images live in the blob store (they are data), the cache only holds derivatives and can always be deleted. See Storage and Blobs.

Failure modes

Situation Behaviour
localStorage blocked or corrupt Every read/write is in try/catch; defaults apply for the session.
getVersion() fails The version chip is simply not shown.
Update check fails (offline, no release, no package for this installer) Silent in the background; manual "Check now" shows the mapped error (Network, NotFound...).
Install fails (e.g. password prompt cancelled) State returns to lista, error toast; can retry without re-downloading.
Update while a PDF export runs or a game is open on the table update_install returns Busy (with a game, motivo: partida); the screen does not ask while the table is open: the install is queued and runs on leaving the table.
Closing the window with a job, a download, an unsaved card, a busy dialog, a failed deck save or a game open ConfirmarCierre asks; "Close anyway" cancels the job, saves the game and closes.
Closing the window with nothing pending The deck is flushed and the window closes without a dialog.
destroy() fails Toast "The window could not be closed."
Selected installed theme no longer exists SincronizarTemas resets to Amber Minimal.

Where to start when extending

  • New setting: if it affects the look, add to ThemeProvider-adjacent code only through new CSS/tema-inicial.ts (the provider itself is read-only); if it is a list/preview preference, add it to useUi Preferencias (State Stores); if the backend must know it across sessions, use app_setting_get/set or a dedicated command.
  • New Settings section: add a card component in src/features/settings/ and render it in SettingsDialog; text through t(); scrolling is already provided by DialogCuerpo.

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