Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README-QUESTIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# README questions — needs Cole's input

1. **Problem provenance.** One line on how this started — presumably your
own Rekordbox library and a specific chore (which one?). The draft has a
`<!-- TODO: verify -->` where it belongs.

2. **Waveform state.** ADR-0008 (May 2026) made the track-inspector
waveform a labeled synthetic preview, but the feature docs now claim a
native ANLZ color waveform in track detail. If any surface still shows
the synthetic preview, say which, so the README doesn't overclaim; the
draft currently just says ANLZ waveforms are parsed.
297 changes: 134 additions & 163 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,165 +1,136 @@
# decks

> Local-first AI DJ assistant for Rekordbox 7

**Status:** MVP implementation in progress. Core desktop workflows and local MCP tooling are implemented; real-library validation and `v0.1.0` release verification remain.

`decks` is a native desktop app that sits next to Rekordbox 7 and helps you maintain a clean, well-tagged, well-cued library. It reads your Rekordbox SQLCipher database directly, surfaces problems and opportunities across the library, lets a local Claude agent propose changes, and exports approved edits back to Rekordbox via XML (or — opt-in — applies them in-place under a write guard).

Everything runs on your machine. Your library never leaves your laptop except via enrichment APIs you explicitly enable, and those calls go through a local cache first.

---

## Why decks

Rekordbox's UI is great for performance but tedious for library hygiene at scale: normalizing artist/title formatting across thousands of tracks, finding tracks missing cues or playlist membership, identifying audio duplicates, relocating broken file paths, or staging bulk metadata edits. `decks` treats the library as a first-class object you can query, audit, and edit safely — with a Claude agent on the side that can see only what you've shown it and can only write through reviewable, reversible staged changes.

## Features

### Library access & browsing

- **SQLCipher reader.** Direct read-only access to Rekordbox 7's `master.db` (`crates/rekordbox-db`). Writes are gated behind an explicit, opt-in Sync flow under a `WriteGuard` that refuses to run while Rekordbox is open (WAL-lock probe) and takes a timestamped backup before the first write of a session (see [ADR-0010](docs/DECISIONS.md)).
- **Virtualized track table.** Filterable, sortable, resizable columns; multi-select; right-click context actions. Energy column hydrated from the local feature cache; Key values tinted with the Mixed In Key Camelot palette; inline Tag chips when bindings exist.
- **Track detail panel.** Metadata, cues, and a high-fidelity native Pioneer ANLZ color waveform (PWAV/PWV3/PWV4/PWV5) — not a re-decoded approximation.
- **Native audio preview.** Play/pause, seek, and interactive waveform scrubbing via `rodio`, with an end-of-track event so transport state stays consistent.
- **Analytics dashboard.** Genre / key / BPM distributions rendered with recharts.
- **Persistent filters.** Per-library filter state in `localStorage`, keyed by library path so multiple libraries don't collide.

### Workflow views

- **Inbox** — tracks missing metadata, cues, or playlist membership, with one-click audit.
- **Incoming** — ingest new files from the filesystem, fuzzy-match against the library, and stage adds. Backend CSV parsing with column-mapping UI for batch imports.
- **Track Matcher** — manual review surface for ambiguous matches (`crates/track-matcher`).
- **Smart Fixes** — title/artist normalization: case, encoded chars, garbage suffixes, URLs, promo tags, extracted remixer/artist (`crates/smart-fixes`).
- **Cleanup** — broken-path Relocate workflow with fuzzy filename + size matching (`crates/relocate`), plus audio-fingerprint duplicate detection (chromagram hash + Hamming grouping).
- **Duplicates** — library-wide duplicate scan combining exact title/artist, fuzzy title, and audio-fingerprint strategies in a single view; per-group "keep one" picker archives the rest.
- **Custom Tags** — manage user tags with picker modal, usage badges, and bulk apply (T-key shortcut).
- **Archive** — review and restore previously archived tracks.
- **Sync** — apply staged changes back to `master.db` with options for `cue_destination` (memory/hot/both), `keep_grids`, and `convert_keys` (Camelot / Open Key / original).

### Agent & change pipeline

- **In-app Claude chat panel.** Read-only tools by default; staged changes accumulate as a reviewable batch with inline diffs. Conversation history persists in a local SQLite (WAL) cache.
- **Staged-change lifecycle.** Every mutation flows through `Proposed → Accepted/Rejected → Exported/Applied`. Supported change types: track metadata, cues, playlists, track delete.
- **Bulk "Add intro cues" tool.** Reads the real ANLZ beat grid to stage perfect 1.1 downbeat memory cues + 4-bar loops.
- **Rekordbox XML export.** Default safe egress for accepted changes; round-trip tested (`crates/rekordbox-xml`).

### Local MCP server (provider-neutral)

`decks` ships a CLI that exposes its library tools over MCP so any MCP host — Claude Code, Gemini CLI, OpenAI Responses API, Cursor — can call them with your subscription instead of the desktop chat panel.

```sh
# stdio transport (Claude Code, Gemini CLI)
decks mcp

# local HTTP transport (OpenAI Responses API remote MCP)
decks mcp-http --bind 127.0.0.1:8765

# direct diagnostic invocation
decks tools call library_search --library /path/to/master.db --json '{"query":"UKG"}'
```

Available tools (read-only): `library_search`, `library_get_track`, `library_list_playlists`, `library_get_playlist`, `library_list_cues`, `health_orphan_scan`, `health_duplicate_scan`, `health_fuzzy_duplicate_scan`, `health_broken_link_scan`, `staging_list_changes`. XML export is intentionally omitted from MCP discovery until the shared tool service owns the export path.

See [docs/MCP.md](docs/MCP.md) for full setup including Claude Code, Gemini CLI, and OpenAI Responses API.

### Privacy & safety guarantees

- **No telemetry.** No analytics. No remote logging.
- **`master.db` is read-only by default.** Writes only happen through the explicit Sync flow, behind a `WriteGuard`.
- **Refuses to write while Rekordbox is running.** WAL lock is probed before any mutation.
- **Per-session backup.** First write of a session creates a timestamped copy of `master.db` next to the original.
- **Anthropic API key stored in the OS keychain.** Never written to disk in plaintext or committed config.

## Architecture

```
┌─────────────────────────────────────────────────────────┐
│ React / TypeScript frontend (apps/desktop/src) │
│ Vite · Tailwind · Radix UI · Zustand · TanStack Query │
├─────────────────────────────────────────────────────────┤
│ Tauri IPC layer (apps/desktop/src-tauri) │
│ Typed commands + events bridging frontend ↔ Rust core │
├─────────────────────────────────────────────────────────┤
│ Rust core (crates/*) │
│ rekordbox-db · rekordbox-xml · audio-analysis │
│ audio-tags · stratum-dsp · relocate · smart-fixes │
│ track-matcher · agent-tools · changes · cache │
│ classify · scoring · enrichment · embeddings · ranker │
│ plugins · decks-core (facade) │
└─────────────────────────────────────────────────────────┘
```

- **Tauri 2** desktop shell — system WebView, no Chromium ([ADR-0001](docs/DECISIONS.md)).
- **Rust workspace** with one crate per bounded concern.
- **TypeScript/React** frontend, fully typed IPC.
- **Local SQLite (WAL) cache** for audio features, waveform peaks, conversation history, and fingerprints (`crates/cache`).
- **Shared tool service** (`crates/agent-tools`) — the same Rust functions back the in-app chat, the MCP server, and the `decks tools call` CLI.

See [docs/architecture.md](docs/architecture.md) for the full design, [docs/data-model.md](docs/data-model.md) for schema details, and [docs/DECISIONS.md](docs/DECISIONS.md) for the ADR log.

## Repository layout

```
apps/
desktop/ Tauri 2 + React frontend & src-tauri backend
cli/ `decks` CLI (mcp, mcp-http, tools call)
crates/
rekordbox-db SQLCipher reader + ANLZ beat-grid parser
rekordbox-xml Rekordbox XML parse/emit (round-trip tested)
audio-analysis Decoding, peak extraction, fingerprinting
audio-tags ID3 / tag reading
cache SQLite WAL cache (features, waveforms, conversation)
changes Staged-change lifecycle + applier + key formats
relocate Broken-path matching (fuzzy filename + size)
smart-fixes Title/artist normalization
track-matcher Fuzzy library matching for incoming tracks
agent-tools Shared tool service (chat + MCP + CLI)
decks-core Facade re-export crate
stratum-dsp · classify · scoring · enrichment ·
embeddings · ranker · prodjlink · plugins
docs/ Architecture, ADRs, MCP, manual test plan, journal
scripts/ dev.sh, release.sh, real-library-smoke.sh, …
fixtures/ Synthetic test libraries (real fixtures gitignored)
```

## Development

```sh
# Prerequisites: Rust stable, Node 20+, pnpm 9+
./scripts/dev.sh
```

Common workflows:

```sh
cargo test --workspace
cargo clippy --workspace --all-targets -- -D warnings
pnpm test # vitest
pnpm typecheck && pnpm lint
pnpm e2e # Playwright
pnpm --filter desktop tauri build # produce a packaged .app / DMG
./scripts/real-library-smoke.sh # data-layer smoke against a real master.db
```

See [docs/MANUAL_TEST_PLAN.md](docs/MANUAL_TEST_PLAN.md) for the human-in-the-loop verification checklist, and [docs/STATUS.md](docs/STATUS.md) for current implementation state.

## Attribution

Core Rust logic vendored and adapted from [reklawdbox](https://github.com/ryan-voitiskis/reklawdbox) (MIT, Ryan Voitiskis). See [NOTICE](NOTICE).

## Roadmap

`decks` is working toward feature parity with [Lexicon DJ](https://www.lexicondj.com) within
Rekordbox. See [docs/ROADMAP.md](docs/ROADMAP.md) for the epic queue,
[docs/lexicon/](docs/lexicon/) for the reference specification, and
[docs/lexicon/PARITY.md](docs/lexicon/PARITY.md) for the current feature matrix.

## License

GPL-3.0-or-later — see [LICENSE](LICENSE).

Relicensed from MIT in 2026 so the project can use copyleft analysis libraries
(libKeyFinder, aubio, audiowaveform) that have no permissive equivalent. The
vendored reklawdbox code was MIT, which is GPL-compatible; attribution is
preserved in [NOTICE](NOTICE). See ADR-0011 in [docs/DECISIONS.md](docs/DECISIONS.md).
Local-first AI library assistant for Rekordbox DJs — audit, clean, and
re-cue a music library with a Claude agent that can only act through
reviewable staged changes.

**Status:** MVP in progress. Core desktop workflows and the local MCP
tooling work; real-library validation and a first tagged release (v0.1.0)
remain.

<!-- SCREENSHOT: the differentiator shot — the chat panel proposing changes
with the staged-changes diff view open next to the track table (Proposed →
Accepted lifecycle visible). Second choice: track detail with the native
ANLZ color waveform. Keep under 5MB. -->

## The problem

Rekordbox is built for performing, not for library hygiene. Normalizing
artist and title formatting across thousands of tracks, finding tracks with
no cues or no playlist, spotting audio duplicates, and relocating broken
file paths are all one-track-at-a-time chores in its UI. The paid benchmark
for this job is Lexicon DJ; decks is working toward Lexicon parity within
Rekordbox (the parity matrix in `docs/lexicon/PARITY.md` drives the
roadmap) as a local-first desktop app with an agent attached — one that can
see only what you've shown it and can only write through staged,
reversible changes.

<!-- TODO: verify — one line on provenance (built against your own library?
what chore triggered it?). See README-QUESTIONS.md. -->

## How it works

- A Tauri 2 desktop app: React/TypeScript frontend over a Rust workspace
(~20 crates, one per bounded concern), with typed IPC between them.
- `crates/rekordbox-db` opens Rekordbox 7's SQLCipher `master.db` directly,
read-only, and parses the native ANLZ files (beat grids, color
waveforms). Audio features, waveform peaks, fingerprints, and chat
history live in a local SQLite (WAL) cache.
- Every mutation — from the agent, Smart Fixes, Cleanup, or the Duplicates
view — is a staged change: `Proposed → Accepted/Rejected →
Exported/Applied`, previewable as a diff. The default egress is
round-trip-tested Rekordbox XML export; an opt-in Sync flow writes
directly to `master.db` under a `WriteGuard`.
- The same Rust tool service backs three surfaces at once: the in-app
Claude chat panel, an MCP server (`decks mcp` / `decks mcp-http`) usable
from any MCP host on your existing subscription, and a `decks tools call`
CLI.
- Everything runs on your machine. No telemetry; enrichment APIs
(MusicBrainz, opt-in Discogs) only fire if enabled, through a local
cache. The Anthropic key lives in the OS keychain.
Comment on lines +44 to +50

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Repo files (top-level):\n'
git ls-files | sed -n '1,120p'

printf '\nREADME around lines 1-90:\n'
if [ -f README.md ]; then
  cat -n README.md | sed -n '1,90p'
else
  fd -a '^README\.md$' . | while read -r f; do echo "===== $f ====="; cat -n "$f" | sed -n '1,90p'; done
fi

printf '\nSearch for Claude/MCP/Anthropic/network/telemetry terms:\n'
rg -n -i 'Claude|anthropic|telemetry|network|subscription|MCP|mcp|MusicBrainz|Discogs|keychain|api' README.md . --glob '!node_modules' --glob '!dist' --glob '!build' | sed -n '1,240p'

Repository: cole-hackman/decks

Length of output: 42575


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Relevant source excerpts:\n'
for f in \
  apps/desktop/src-tauri/src/claude_agent.rs \
  apps/desktop/src-tauri/src/lib.rs \
  crates/agent-tools/src/mcp.rs \
  apps/desktop/src/agent/useAgent.ts \
  apps/desktop/src-tauri/capabilities/default.json
do
  if [ -f "$f" ]; then
    echo "===== $f ====="
    wc -l "$f"
    if [ "$f" = "README.md" ]; then
      cat -n "$f" | sed -n '40,54p;116,125p'
    elif [ "$f" = "apps/desktop/src-tauri/capabilities/default.json" ]; then
      cat -n "$f" | sed -n '1,220p'
    else
      rg -n -C 4 'claude|Claude|anthropic|ClaudeCode|get_claude_code_status|mcp|http|MCP|library|staged|tool' "$f" | sed -n '1,260p'
    fi
  fi
done

Repository: cole-hackman/decks

Length of output: 37798


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Antropics SDK usage:\n'
rg -n -C 3 'Anthropic|`@anthropic`|apiKey|client|messages\.stream|stream_claude_code_chat|getApiKey' apps/desktop/src apps/desktop/src-tauri/src crates/agent-tools/src/mcp.rs README.md docs/MCP.md 2>/dev/null | sed -n '1,260p'

printf '\nRelevant docs around Claude keys/subscription/network:\n'
for f in README.md docs/MCP.md docs/STATUS.md docs/UI_AUDIT.md CLAUDE.md
do
  if [ -f "$f" ]; then
    echo "===== $f ====="
    rg -n -C 4 'Claude|Anthropic|api key|subscription|network|API traffic|MCP|telemetry|local|keychain' "$f" | sed -n '1,220p'
  fi
done

printf '\nWeb search for Claude Code MCP network behavior and Anthropic API billing:\n'
web_search "Claude Code MCP server sends tool output to Anthropic API billing Anthropic key Claude code external host"

Repository: cole-hackman/decks

Length of output: 40396


🌐 Web query:

Claude Code MCP tool results sent to Anthropic API billing Anthropic API key Claude Code MCP server tool_output

💡 Result:

When using Claude Code with Model Context Protocol (MCP) servers, tool usage is integrated into your standard Anthropic API billing [1][2]. There is no separate "MCP billing" category; instead, the interaction consumes tokens as part of the normal request-response lifecycle [1][3]. How billing works for Claude Code MCP usage: 1. Token Consumption: You are billed for the tokens consumed by your API requests, which include the tool definitions, tool calls (tool_use blocks), and tool results (tool_result blocks) sent to and from the model [1][4]. 2. Context Window Impact: Because MCP tool definitions (names, descriptions, and schemas) are included in the prompt's tools parameter, they consume context window tokens [1][4]. Loading a large number of MCP tools can increase your token usage significantly [5][6]. 3. Automated Reasoning: Invoking an MCP tool as part of an automated reasoning chain initiated by your prompt counts toward your total token usage for that request, but it does not count as a separate, new prompt or turn against rate limits [3]. 4. Cost Tracking Caveats: - Built-in reporting tools (like /context in Claude Code) may report inflated token usage estimates [7][6]. This often occurs because the tool calculates costs by summing individual tool definitions separately, which incorrectly accounts for shared system instructions multiple times [7][6]. - Client-side estimates (e.g., total_cost_usd or /cost) are approximations for budgeting [2][8]. For authoritative billing, always refer to the Usage page in the Claude Console or your cloud provider's billing dashboard [2][8]. 5. Efficiency Tips: - To reduce costs, use --strict-mcp-config or pin your MCP server configurations to keep the tool surface constant across turns [4]. Changing the tool definitions mid-session can break the cache and trigger expensive "cold" rebuilds [4]. - Large toolsets can be managed by only loading the tools necessary for the current task [5]. In summary, MCP tool results are sent back to the Anthropic API as part of the ongoing conversation history, and you are billed based on the total token usage—input and output—of these interactions [1][9].

Citations:


🌐 Web query:

site:docs.anthropic.com Claude Code MCP tool results sent to Anthropic API billing

💡 Result:

Claude Code and other MCP-enabled applications do not have a separate billing category specifically for "MCP tool results." Instead, usage related to MCP tools is billed through standard Anthropic API token consumption and, where applicable, specific server-side tool costs [1]. How MCP tool usage impacts your billing: 1. Token Consumption: Using MCP tools increases the number of tokens in your API requests and responses [1]. This includes: - Tool Definitions: The tool names, descriptions, and schemas in your tools parameter consume tokens [1]. - Tool Interaction: The tool_use and tool_result content blocks sent to and from the API are counted as input and output tokens [1]. 2. Standard API Pricing: These tokens are billed at the standard rate for the Claude model you are using, just like any other API request [1]. 3. Server-Side Tool Costs: While MCP tools (which are generally client-side integrations) are billed via token volume [1], certain built-in server-side tools (like specific web search or code execution tools when used outside of free tiers) may incur additional usage-based charges that appear on your bill [2][3][1]. 4. Claude Code Specifics: If you are using Claude Code, the session usage reported in the tool is an estimate based on list prices and does not necessarily match your official bill in the Claude Console [4]. For authoritative billing information, you should always consult the Usage page in the Claude Console [4]. In summary, MCP tool results themselves do not have a unique, extra charge; they are simply part of the overall token usage generated by the conversation context [1].

Citations:


Document the MCP network and billing boundary.

decks mcp runs locally, but external MCP hosts call tool results through Claude Code under the authenticated Claude subscription, while the in-app chat sends conversation history and tool results via the Anthropic API to the configured API key. Clarify that MCP library tool output leaves the machine, distinguish this traffic from telemetry, and avoid saying Claude access comes from “your existing subscription” without naming Claude Code.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 44 - 50, Update the README description of the MCP
server and local execution around the Rust tool service to name Claude Code as
the authenticated subscription boundary, explain that external MCP hosts route
tool results through Claude Code, and state that in-app chat sends conversation
history and tool results to Anthropic via the configured API key. Clarify that
MCP library tool output leaves the machine while distinguishing this traffic
from telemetry, and remove the ambiguous “your existing subscription” wording.


## Running it

Requires Rust stable, Node 20+, pnpm 9+, and a Rekordbox 7 library
(`master.db`) to point at.

git clone https://github.com/cole-hackman/decks
cd decks
./scripts/dev.sh

Tests run against synthetic fixture libraries in `fixtures/` — no real
library needed:

cargo test --workspace
pnpm test && pnpm typecheck && pnpm lint
pnpm e2e # Playwright
./scripts/real-library-smoke.sh # data-layer smoke vs a real master.db copy
Comment on lines +61 to +67

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Separate fixture tests from the real-library smoke test.

The heading says no real library is needed, but line 67 invokes a real master.db. scripts/real-library-smoke.sh also defaults to ~/Library/Pioneer/rekordbox/master.db and does not create a copy. Move the smoke command to an optional real-library section and document the explicit input path or copy.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 61 - 67, Update the README testing documentation to
keep cargo, pnpm, and Playwright fixture tests under the “no real library
needed” section, then move ./scripts/real-library-smoke.sh to a separate
optional real-library smoke section. Document that the script requires an
explicit master.db input path or a user-created copy, rather than implying the
default path is automatically safe or fixture-based.


## Scope and non-goals

**In scope:** Rekordbox 7, deep — browsing, auditing, smart fixes,
duplicates, relocation, cues, smartlists, staged edits back to the
library.

**Not in scope (deliberately, for now):**

- Other DJ software (Serato, Traktor, Engine DJ, VirtualDJ) and USB/CDJ
export — deferred until Rekordbox parity is done.
- Cloud anything: no sync, no backup service, no mobile app.
- Silent fixes. Playlist duplicate entries are surfaced, not auto-removed;
every change is staged for review.

## Tradeoffs

**Staged changes everywhere, direct writes almost nowhere.** The founding
invariant was "never mutate `master.db`" — all edits stage into a
reviewable batch and leave via XML export the user imports manually. That
bought trust (an agent can propose bulk edits to a library you care about
with zero blast radius) and cost friction: for "rename one genre across
200 tracks," quit-Rekordbox-and-import is a heavy loop. ADR-0010 formally
relaxed the invariant for one path: an opt-in Sync that writes in-place,
gated by a `WriteGuard` that probes Rekordbox's WAL lock, takes a
timestamped backup on first write, and commits atomically — reducing the
worst case from "library destroyed" to "copy the `.bak` back."

**Tauri 2 over Electron.** The core logic is Rust (SQLCipher access, audio
analysis, DSP), so Tauri lets the frontend call it directly instead of
through an FFI or subprocess boundary, and ships a ~5–10 MB binary using
30–80 MB of memory — which matters when Rekordbox and a DAW are open next
to it. The cost: the system WebView differs per OS (Safari, WebView2,
WebKitGTK), so rendering must be verified on each platform instead of once
in a bundled Chromium.

## Known limitations and failure modes

- It has not been validated against a real library at scale yet — that's
the line between here and v0.1.0. Synthetic fixtures cover the test
suites; real Rekordbox databases have column variants and ANLZ quirks
the fixtures don't.
- The whole foundation is a reverse-engineered format: SQLCipher schema
and ANLZ binary parsing (vendored and adapted from MIT-licensed
reklawdbox). A Rekordbox update can break reads — and the Sync write
path — overnight.
- An agent session once shipped a non-compiling workspace while its notes
claimed "verified clean." ADR-0009 is the scar: agent-built work is
untrusted until the full verification suite passes, and "done" claims
without captured command output don't count.
- CI runs the Rust suite (fmt, clippy, tests) on macOS and Windows plus
frontend typecheck/lint/vitest — but the Playwright e2e suite and the
real-library smoke are local-only gates, and Linux isn't in the matrix.
- No telemetry cuts both ways: nothing phones home, and nothing tells me
what broke on someone else's machine.

## What I'd do next

1. Real-library validation and the v0.1.0 release — the smoke harness
exists; it needs to be run in anger against large libraries.
2. Work down the Lexicon parity matrix (`docs/lexicon/PARITY.md`) — one
epic per branch, already queued in `docs/ROADMAP.md`.
3. Put Linux in the CI matrix and get e2e running in CI, so the
three-WebView cost of the Tauri decision is actually paid down.

## Stack

Tauri 2 · Rust · React · TypeScript · Vite · Tailwind · SQLite/SQLCipher ·
GPL-3.0-or-later
Loading