Repository navigation
Development Setup
English · Español
How to build, run and check Proxy Builder from source: prerequisites, the pnpm policy, every package.json script, the repository layout, the ts-rs bindings flow, WebKitGTK notes, git hooks and formatter/linter rules.
For what the checks do in CI see Testing and CI; for coding rules see Conventions.
| Tool | Version | Source of truth |
|---|---|---|
| Node.js | 22 or later |
package.json engines.node; CI uses Node 22 |
| pnpm | 12 or later; the exact version is pinned in packageManager (pnpm@12.14.0) |
package.json; npm is not supported |
| Rust (stable) | 1.92 or later (rust-version in src-tauri/Cargo.toml, crates/proxy-core/Cargo.toml and crates/mesa/Cargo.toml) |
CI uses the latest stable with clippy and rustfmt
|
| Node.js for the room server (optional) | 22.13 or later (it uses node:sqlite) |
server/package.json engines.node; only needed to run the server or its tests (see below) |
| Tauri system libraries (Linux) | WebKitGTK 4.1 and friends | see below |
| JDK 21, Android SDK (platform 37, build-tools 36), NDK 28.2 (optional) | Only to build for Android; see Android |
scripts/android-env.sh, .github/workflows/release.yml
|
| Tauri prerequisites (Windows, macOS) | the standard Tauri 2 ones (WebView2, MSVC build tools / Xcode command line tools) | Inferred from Tauri 2 defaults; the repo does not pin them |
Linux packages (Debian/Ubuntu), from README.md:
sudo apt install libwebkit2gtk-4.1-dev build-essential curl wget file libxdo-dev \
libssl-dev libayatana-appindicator3-dev librsvg2-devCI (.github/workflows/ci.yml) installs libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf on ubuntu-22.04 (cached, see Testing and CI). The app crate links GTK/WebKit, so cargo test/clippy for the whole workspace need those libraries even though you never open a window; the pure crates alone (cargo test -p proxy-core, cargo test -p mesa) do not.
Note: although libssl-dev is in the README list (Tauri's usual set), the dependency tree must contain no OpenSSL: reqwest is built with default-features = false and rustls, and CI fails if cargo tree -i openssl finds anything (see Testing and CI).
pnpm install
pnpm tauri devpnpm tauri dev runs beforeDevCommand (pnpm dev, i.e. Vite on port 1420, strictPort: true: it fails instead of picking another port) and compiles the Rust app. The first Rust build takes several minutes. Vite is told not to watch src-tauri/ (Cargo handles that); with TAURI_DEV_HOST set it listens on that host with HMR on port 1421 (for mobile/remote dev; see vite.config.ts). To see the phone or tablet layout on the desktop without an emulator, start the dev server with VITE_PB_FORMATO=movil or VITE_PB_FORMATO=tableta (honoured only in development, import.meta.env.DEV; src/lib/formato.ts): the layout is chosen by window width and touch, and the desktop window has a minimum width of 960 px, so without the variable the phone layout could never be seen there.
pnpm tauri build produces installers for the current platform (bundle.targets: "all" in src-tauri/tauri.conf.json); signing for the updater needs the private key in the environment, see Releases and Auto-Updates.
Configuration for pnpm 11+ lives in pnpm-workspace.yaml, not in package.json:
| Key | Value | Why |
|---|---|---|
minimumReleaseAge |
1440 (minutes) |
Only install package versions published at least a day ago. This is pnpm 11's default; it is written down so every machine (cloud sandbox, maintainer's laptop) resolves the same way. A lockfile generated without the delay once contained versions published hours earlier and was rejected by the maintainer's own pnpm install. |
allowBuilds |
esbuild: true |
The only dependency allowed to run an install script. |
Rules that follow from this:
- Regenerate
pnpm-lock.yamlwith the pnpm version inpackageManager. A lockfile produced by pnpm 10 skips the age rule and can pull brand-new versions. - CI installs with
pnpm install --frozen-lockfile(bothci.ymlandrelease.yml). - Biome ignores
pnpm-lock.yamland lint-staged excludes it from JSON formatting.
All from package.json:
| Script | Runs | Use |
|---|---|---|
pnpm dev |
vite |
Frontend dev server only (Tauri calls it for you) |
pnpm build |
tsc && vite build |
Type-check then bundle into dist/ (frontendDist) |
pnpm preview |
vite preview |
Serve the built bundle |
pnpm tauri |
tauri |
Tauri CLI (pnpm tauri dev, pnpm tauri build, pnpm tauri icon) |
pnpm bindings |
cargo test --manifest-path src-tauri/Cargo.toml --workspace export_bindings |
Regenerate src/bindings/*.ts from Rust types (see below) |
pnpm check |
check:tipos, test, check:biome, check:title, check:tactil, idiomas, check:iconos, check:knip in that order |
Everything cheap on the frontend |
pnpm check:tipos |
tsc --noEmit |
Type check |
pnpm test |
vitest run |
Frontend and script unit tests |
pnpm check:biome |
biome check . |
Lint + format check |
pnpm check:title |
node scripts/sin-title-nativo.mjs |
Fails on native title= attributes |
pnpm check:tactil |
node scripts/sin-hover-solo.mjs |
Fails on mouse-only handlers without a // táctil: comment (see Testing and CI) |
pnpm idiomas |
node scripts/idiomas.mjs |
Fails if any interface language is missing a translation or has mismatched placeholders/plurals; node scripts/idiomas.mjs --detalle also lists the warnings (see Testing and CI) |
pnpm iconos / pnpm check:iconos
|
node scripts/iconos.mjs (--check) |
Regenerate src/lib/iconos/incluidos.ts from the Iconify ids written in src/ / fail if it is stale (see Icons) |
pnpm check:knip |
knip |
Unused files and dependencies |
pnpm check:clippy |
cargo clippy --manifest-path src-tauri/Cargo.toml --workspace --all-targets -- -D warnings |
Rust lints; warnings are errors |
pnpm check:clippy-android |
bash -c '. scripts/android-env.sh && cargo clippy --manifest-path src-tauri/Cargo.toml --lib --target aarch64-linux-android -- -D warnings' |
The same lints on the Android-only branches (needs the Android environment below) |
pnpm check:rust-fmt |
cargo fmt --manifest-path src-tauri/Cargo.toml --all -- --check |
Rust format check |
pnpm test:rust |
cargo test --manifest-path src-tauri/Cargo.toml --workspace |
All Rust tests (app, proxy-core, mesa) |
pnpm format |
biome format --write . |
Reformat frontend files |
pnpm release |
node scripts/release.mjs |
Cut a release (see Releases and Auto-Updates) |
pnpm prepare |
husky |
Installs git hooks on pnpm install
|
flowchart LR
root["repo root"] --> src["src/ (React + TypeScript)"]
root --> st["src-tauri/ (Rust workspace)"]
root --> sc["scripts/ (Node tooling)"]
root --> gh[".github/workflows"]
root --> dc["docs/"]
root --> sv["server/ (room server, separate repo, git-ignored)"]
st --> app["src/ (app crate: proxy_builder_lib)"]
st --> core["crates/proxy-core (pure crate)"]
st --> mesa["crates/mesa (pure game engine)"]
st --> as["assets/ (house frame pack, fonts, JSON manifests)"]
| Path | Responsibility |
|---|---|
src/features/<area>/ |
One folder per UI area: decklist, preview, export, variants, card-editor, library, stats, set-browser, settings, shell, disenar, tutorial, mesa (game table), salas (rooms and playgroups), pasar (send and receive decks between devices) (see Frontend Architecture) |
src/stores/ |
Zustand stores: deck-store, plan-store, ui-store, trabajo-store, actualizacion-store, edicion-store, originales-store, and for the game table and online play mesa-store, vivo-store, chat-store, repeticion-store, red-store, salas-store (see State Stores) |
src/lib/ |
ipc.ts (the only door to Rust), trabajos.ts, image-url.ts, dialogos.ts, errores.ts, themes helpers, and the mobile ones: formato.ts, plataforma.ts, atras.ts, ciclo-de-vida.ts, teclado.ts, escaner.ts, pulsacion-larga.ts (Mobile) |
src/components/ |
Shared components and the ui/ primitives (shadcn-style, Radix) |
src/i18n/ |
Interface dictionaries, one folder per language code with one file per area, plus the translator and the lazy loader (see Internationalization) |
src/bindings/ |
Generated TypeScript types from Rust (do not edit; excluded from Biome and Knip) |
src/lib/iconos/incluidos.ts |
Generated by pnpm iconos: the Iconify icons the code writes literally (do not edit; excluded from Biome) |
src/themes.css, src/index.css, src/styles/, src/fonts.css
|
Theme tokens and global CSS (excluded from Biome) |
src-tauri/src/ |
App crate: lib.rs (bootstrap + command registration only), commands/<area>.rs, store/, scryfall/, fuentes/, diseno/, mesa/ (game table app layer), red/ (online play: iroh node, protocol, identity, profiles, room server client), enlace.rs (proxybuilder:// links), pasar/ (moving decks between devices), tls.rs, ciclo.rs, android.rs, fichero.rs, abrir.rs (the mobile layer), ocr.rs, actualizar.rs, temas.rs, error.rs, state.rs, jobs.rs, protocol.rs, registro.rs (see Rust App Layer) |
src-tauri/crates/proxy-core/ |
Pure core: decklist, layout, imaging, pdf, stats, diseno, model.rs, paper.rs, units.rs. No Tauri, no network, no database. Has the golden tests in tests/
|
src-tauri/crates/mesa/ |
Pure game engine: the game, its actions, randomness and what each player sees. No Tauri, no network. Tests in tests/ (see Testing and CI) |
server/ |
The room server, a separate repository cloned here and ignored by the app's git (see below) |
src-tauri/assets/ |
Bundled assets and manifests, e.g. letras-libres.json, packs-descargables.json, letras-cjk.json
|
src-tauri/capabilities/ |
Tauri permissions of the main window: default.json (including core:window:allow-destroy, needed by the close guard; no fs permission, all disk access goes through Rust commands), escritorio.json (desktop-only: window state) and movil.json (phones: the QR camera) |
src-tauri/plugins/android-extra/ |
The project's own Tauri plugin for what only Android does, with its Kotlin; a workspace member |
src-tauri/gen/android/ |
The Android project (Gradle), versioned; keystore.properties inside it is ignored by git |
scripts/ |
idiomas.mjs, sin-title-nativo.mjs, release.mjs, release-notas.mjs, tipos-impresos.mjs (regenerates crates/proxy-core/assets/tipos-impresos.json from Scryfall, the printed type lines per language; run by hand when a set adds types, needs network and takes about ten minutes per language because of Scryfall's rate limit), iconos.mjs (pnpm iconos: the bundled Iconify icons, see Icons), emoji-noto.mjs (copies the Noto Color Emoji images the UI uses into src/assets/emoji/; run by hand, needs network or --desde <clone>, and inkscape on the PATH only when an SVG is over 64 KB and must be converted to PNG), android-env.sh (the Android environment), i18n-anadir.py (adds strings to the 15 dictionaries without clashes), sin-hover-solo.mjs (pnpm check:tactil), __tests__/, icono.py and reverso.py (generate the app icon and the generic card back with Pillow; run by hand) |
docs/ |
packs-de-marcos.md (frame-pack format) and presentacion/ (README screenshots; en/ for the English ones) |
.github/workflows/ |
ci.yml, release.yml
|
src-tauri/Cargo.toml defines the workspace (members = [".", "crates/proxy-core", "crates/mesa", "plugins/android-extra"]) and the profiles. The app library is named proxy_builder_lib (the _lib suffix avoids a name clash with the binary on Windows).
Types that cross the IPC boundary are Rust structs/enums annotated #[derive(TS)] #[ts(export)]. ts-rs generates a test per exported type (named export_bindings_<Type>); running those tests writes the .ts files.
flowchart LR
R["Rust type with derive TS and ts(export)"] -->|"pnpm bindings (cargo test ... export_bindings)"| B["src/bindings/Type.ts"]
B --> I["src/lib/ipc.ts (Commands interface)"]
I --> S["stores / components via call()"]
- The destination comes from
.cargo/config.tomlat the repo root:TS_RS_EXPORT_DIR = { value = "src/bindings", relative = true }. It lives at the root, not insrc-tauri/.cargo/, because Cargo reads config relative to the directory it is launched from and the pnpm scripts launch it from the root; insrc-tauri/.cargo/the types ended up insrc-tauri/bindings/. -
pnpm bindingsmust be re-run after changing any exported Rust type; commit the result.pnpm test:rustalso runs those export tests (so they rewrite the files as a side effect), but nothing in CI diffs the result against what is committed. Inferred: a stale committed binding is only caught indirectly, whentscinpnpm checkfails against it. -
src/bindings/is never edited by hand and is ignored by Biome and Knip. -
src/lib/ipc.tsthen declares every command in theCommandsinterface (argsandresulttypes) and every event inEvents. See IPC and Error Handling.
- WebKitGTK is the webview on Linux; scroll and animation behaviour must be verified there, not only in Chrome.
- On a machine without a GPU, start the app under a virtual display:
Xvfb :99and run withWEBKIT_DISABLE_COMPOSITING_MODE=1 WEBKIT_DISABLE_DMABUF_RENDERER=1. Without these variables the entrance animations of dialogs do not advance in a GPU-less display, so dialogs stay invisible yet open and swallow clicks. Real desktops do not need them. - The window starts hidden with a dark background (
tauri.conf.json:visible: false,backgroundColor); the app shows it when the page has loaded (on_page_loadinsrc-tauri/src/lib.rs). The window-state plugin does not persist visibility (StateFlags::all() & !StateFlags::VISIBLE). - Updates only work in an installed build:
soportada()insrc-tauri/src/actualizar.rsis false underdebug_assertions(so never inpnpm tauri dev).
Building for Android is optional and only needed to work on the phone side. Everything else in this page works without it. The mobile layer is described in Mobile and Rust App Layer.
| Piece | Version | Why that one |
|---|---|---|
| JDK | 21 (Temurin) | The Android Gradle plugin's lint is known to crash on 17 (some systems set JAVA_HOME to 17, so check it) |
| Android SDK | platform 37, build-tools 36.0.0, platform-tools, command-line tools | The template of the Tauri Android project compiles against 37 |
| NDK | 28.2.13676358 (stable) | Not a beta; r28 aligns the native libraries to 16 KB pages by default, which Android 15 devices with 16 KB pages require |
| Rust targets |
aarch64-linux-android (releases, real phones) and x86_64-linux-android (the emulator) |
rustup target add ... |
| System image for the emulator | An x86_64 Google APIs image |
Runs with KVM on Linux |
Installing the packages is sdkmanager --install "platforms;android-37.0" "build-tools;36.0.0" "ndk;28.2.13676358", and .github/workflows/release.yml does the same, so the two cannot drift apart silently.
. scripts/android-env.sh (it has to be sourced, not run) sets ANDROID_HOME (edit it if your SDK lives somewhere else), JAVA_HOME from the java on the PATH, NDK_HOME/ANDROID_NDK_HOME, the NDK clang as compiler, archiver and linker of both Android targets (CC_*, CXX_*, AR_*, CARGO_TARGET_*_LINKER, API level 26 like the app's minSdk) and adds platform-tools and emulator to the PATH. pnpm check:clippy-android sources it itself.
Commands once the environment is set:
| Goal | Command |
|---|---|
| Lints of the Android branches | pnpm check:clippy-android |
| APK for the emulator |
pnpm tauri android build --apk --target x86_64 --debug (add --ci outside a terminal) |
| APK for a phone | pnpm tauri android build --apk --target aarch64 |
| Run in the emulator with live reload | pnpm tauri android dev |
A release-profile APK is not installable unless it is signed. For a local signed build create src-tauri/gen/android/keystore.properties with keyAlias, password (one for store and key) and storeFile (git ignores that file); without it Gradle prints a warning and produces an unsigned APK. Never commit a key; the real release key and how CI uses it are in Releases and Auto Updates.
The layout depends on the window size in CSS pixels, which on Android are dp, so the virtual devices are defined by their dp size and not by pixels: a phone of about 411 x 914 dp (1080 x 2400 px at 420 dpi) and a tablet of 1280 x 800 dp. Note that the tablet in landscape is 1280 dp wide, which the layout breakpoints count as desktop (on purpose); the tablet layout is tried in portrait, where the short side is 800 dp (a touch device with a short side under 600 dp counts as a phone, like Android's sw600dp).
On a machine without a display (a cloud workstation, a CI-like environment):
. scripts/android-env.sh
emulator -avd <name> -no-window -no-audio -no-snapshot -gpu swiftshader_indirect -no-boot-anim &
adb wait-for-device
adb shell getprop sys.boot_completed # repeat until it prints 1
adb install -r path/to/app.apk
adb exec-out screencap -p > screen.png # a screenshot, to look at with an image viewer
adb shell input tap X Y # taps and swipes, for scripted tests
adb emu kill # shut the emulator down-gpu swiftshader_indirect renders in software (slow, but it needs no GPU). Several people or sessions sharing one machine must not run the emulator and Gradle at once: build and emulator are a shared resource. To test a link, adb shell am start -a android.intent.action.VIEW -d "proxybuilder://unirse/<code>". The emulator's network does not pass multicast and a desktop cannot call into it, so between an emulator and a desktop it is always the emulator that connects (or a fixed port is forwarded with adb emu redir add udp:..., or the relay is used); "nearby devices" needs two real devices on the same Wi-Fi.
To try the in-app update flow without a release, build the emulator APK with the Cargo feature actualizar-local and point PB_ACTUALIZAR_URL at a latest.json served on the machine; never enable that feature elsewhere (see Releases and Auto Updates).
Debug builds read three variables; a release build ignores them (each check is behind cfg!(debug_assertions)):
| Variable | Read in | Effect |
|---|---|---|
PB_PERFIL=<name> |
src-tauri/src/red/perfil.rs, lib.rs
|
A separate instance on the same machine. The name must be letters, digits, - or _, up to 32 characters, and is lowercased; anything else is ignored. The profile gets its own data, cache and log folders (perfiles/<name> inside each app folder), so its own library and iroh identity (identidad/clave lives in the data folder); its own webview data folder (webview in its cache, so its own localStorage: language, preferences, tutorials seen); the profile in the window title; and no window-state plugin (the instances would overwrite each other's position). The single-instance plugin is also off with a profile, so several windows can be open at once; a proxybuilder:// link then is not handed to a running copy. Copy library.db* and blobs/ into the profile's data folder to have decks there. |
PB_RELAY |
src-tauri/src/red/nodo.rs (relay_de_entorno) |
none (case-insensitive): no relays, only direct addresses (LAN and tests). A relay URL: only that relay (to try your own). Any other value logs a warning and falls back to the default. Without it: the production setting, which for now is n0's public relays (RELE_PROPIO is None until there is a domain; once set, the own relay and n0's behind it). |
PB_SERVIDOR |
src-tauri/src/red/servidor/mod.rs (url) |
The room server URL, e.g. ws://127.0.0.1:8787/v1/ws; empty counts as unset. Without it the production URL is used, which is None for now, so without PB_SERVIDOR the rooms screen says there is no server. |
Other PB_* variables are for tests only and are listed in Testing and CI, except PB_PACKS_LOCAL (debug builds load downloadable frame packs from a local folder, see Frame Packs).
The room server is a separate repository cloned at server/ (ignored by the app's git; Node 22.13 or later):
cd server
pnpm install
pnpm dev # tsx watch src/index.ts; GET /healthz answers okIt reads its own variables (server/src/index.ts): PB_PUERTO (port, default 8787), PB_ESCUCHA (listen address, default 127.0.0.1, nginx goes in front in a deployment), PB_DB (SQLite file, default datos/servidor.db; :memory: for tests) and PB_RELAY_TOKEN (the token iroh-relay sends in its callout). Its other scripts: pnpm check (tsc, vitest, biome), pnpm test, pnpm protocolo:comprobar (the contract test only), pnpm build and pnpm start. With server/node_modules present, the app's pnpm test:rust also starts the server for a real test; see Testing and CI.
Two profiles are also the way to try moving decks between two desktop installations on one machine ("Receive" in one, "Send" to the nearby device or the code in the other; Deck Sharing).
A typical online test on one machine: the server running as above, the main instance with PB_SERVIDOR=ws://127.0.0.1:8787/v1/ws pnpm tauri dev (add PB_RELAY=none to stay on local addresses), and more instances started from the debug binary with the same variables plus a profile, e.g. PB_PERFIL=b src-tauri/target/debug/proxy-builder and PB_PERFIL=c …. On a headless machine run them all on the same Xvfb display with the WebKit variables above. Inferred: the extra instances load the frontend from Vite's dev server (devUrl, port 1420), so pnpm tauri dev (or pnpm dev) has to be running for them to show anything. See Online Play.
pnpm install runs husky (prepare script) which wires .husky/pre-commit:
-
pnpm exec lint-stagedusinglint-staged.config.mjs. It fixes only staged files:-
*.{ts,tsx,js,jsx,mjs,cjs}runbiome check --write --no-errors-on-unmatched; -
.json/.html(except the pnpm lockfile) runbiome format --write; -
src-tauri/**/*.rsrunrustfmt --edition 2021.
-
- If any
.ts/.tsxfile is staged:tsc --noEmit,pnpm idiomas,pnpm check:title. - If any
.rsfile is staged andcargois on thePATH:pnpm check:clippy(with-D warnings). The hook prepends~/.cargo/bintoPATHbecause GUI git clients do not inherit the interactive shell'sPATH.
Gotchas:
- Use bare
rustfmt --edition 2021on staged files, nevercargo fmtin lint-staged:cargo fmtreformats the whole crate and would drag untouched files into the commit. The--edition 2021flag is mandatory: without it rustfmt assumes edition 2015 and silently leaves the file unchanged. - A new clippy warning is fixed, or allowed locally with
#[allow(clippy::...)]plus a comment explaining why;--no-verifyis not the answer. - The hook is deliberately cheap (the slow work,
vitest,knip,cargo test, is left topnpm checkand CI).
| Scope | Rule | Where |
|---|---|---|
| TS/TSX/JS/JSON/CSS/HTML |
Tabs (indent width 2, line width 100), double quotes, semicolons, trailing commas all (JSON: none), arrow parens always |
biome.json, .editorconfig
|
| Rust | Four spaces (rustfmt default) |
.editorconfig [*.rs]
|
| Markdown/YAML | Two spaces | .editorconfig |
| Biome rules |
recommended plus a list of explicit relaxations (e.g. noNonNullAssertion, useExhaustiveDependencies, many a11y rules off) |
biome.json |
| Biome ignores |
node_modules, dist, dist-ssr, tmp, src-tauri/target, src-tauri/gen, pnpm-lock.yaml, public, src/bindings, src/lib/iconos/incluidos.ts (generated), saved test data (src-tauri/crates/proxy-core/tests/compatibilidad, src-tauri/crates/mesa/tests/catalogo, src/stores/__tests__/fixtures), src/index.css, src/themes.css, src/fonts.css, src/styles, and a couple of local tool folders; plus everything in .gitignore (vcs.useIgnoreFile), which is how server/ stays out |
biome.json files.includes, vcs
|
| Rust lints | Clippy with -D warnings; #![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used))] in src-tauri/src/lib.rs, crates/proxy-core/src/lib.rs and crates/mesa/src/lib.rs
|
Tests may use unwrap; test files add #![allow(clippy::unwrap_used, clippy::expect_used)]
|
| TypeScript |
strict, noUnusedLocals, noUnusedParameters, alias @/* to src/*
|
tsconfig.json |
Rust dev profile specifics (src-tauri/Cargo.toml): debug = "line-tables-only" with split-debuginfo = "packed" to keep target/ small (unpacked debuginfo once grew a sibling project's target past 100 GB; run cargo clean after touching it), and dependencies are compiled with opt-level = 2 even in dev because image processing at 600 PPP unoptimised takes seconds and would make the live preview unusable under tauri dev. The mesa crate gets opt-level = 2 in dev too: every action copies and compares the whole game, and its property tests play thousands.
| Symptom | Check |
|---|---|
pnpm install rejects the lockfile |
Regenerate with the packageManager pnpm version; see policy above |
| Port 1420 busy |
strictPort makes Vite fail; free the port |
Types in src/bindings stale or missing |
pnpm bindings; confirm .cargo/config.toml is at the repo root |
cargo cannot find GTK/WebKit |
Install the Linux packages above |
| Dialogs invisible on a headless display | WebKit env variables above |
| Rooms say there is no server | Debug build with PB_SERVIDOR set and the server running (see above) |
| Log of what the app did |
proxy-builder.log in the app log dir (src-tauri/src/registro.rs) |
The maintainer reviews changes in their own working tree: automated sessions leave edits uncommitted and do not push (a push triggers CI and failure emails). Before handing a patch over run the same gates as CI: pnpm bindings, pnpm check, pnpm check:rust-fmt, pnpm check:clippy (latest stable toolchain), pnpm test:rust, with biome check --write and cargo fmt applied first (lint-staged would do it at commit time). The project also expects the code-graph index to be refreshed at the end of each phase of work.
Use and build · Uso y desarrollo
- User-Guide · Guía de usuario
- Development-Setup · Preparación del entorno de desarrollo
- Testing-and-CI · Tests y CI
- Releases-and-Auto-Updates · Versiones y actualizaciones automáticas
- Conventions · Convenciones
Architecture · Arquitectura
- Architecture · Arquitectura
- IPC-and-Error-Handling · IPC y manejo de errores
- Rust-App-Layer · Capa de aplicación Rust
Data · Datos
- Deck-Model · Modelo del mazo
- Storage-and-Blobs · Almacenamiento y blobs
- Deck-Package-Format · Formato del paquete de mazo
- Deck-Library · Biblioteca de mazos
Getting cards · Conseguir cartas
- Decklist-Parsing · Parseo de la lista del mazo
- Scryfall-Integration · Integración con Scryfall
- External-Sources · Fuentes externas
- Card-Identification-and-OCR · Identificación de cartas y OCR
Layout, images, PDF · Maquetación, imagen y PDF
- Layout-Engine · Motor de maquetación
- Imaging-and-PDF · Imagen y PDF
- Export · Exportación
- Live-Preview · Vista previa en vivo
- Statistics · Estadísticas
Card designer · Diseñador de cartas
- Designer-Overview · Visión general del diseñador
- Designer-Engine · Motor del diseñador
- Designer-Composition · Composición del diseñador
- Frame-Packs · Packs de marcos
- Designer-App-Layer · Capa de aplicación del diseñador
- Designer-UI · Interfaz del diseñador
- Card-Editor-Dialog · Diálogo Editar carta
Game table · Mesa de juego
Mobile · Móvil
Frontend · Frontend
- Frontend-Architecture · Arquitectura del frontend
- State-Stores · Stores de estado
- Internationalization · Internacionalización
- Themes-and-Skins · Temas y skins
- Icons · Iconos
- Settings-and-Shell · Ajustes y shell
- Tutorials · Tutoriales
- Decklist-Panel · Panel de la lista del mazo
- Add-Card-and-Variants · Añadir carta y variantes
- Set-Browser · Explorador de sets
Reference · Referencia