PavtiPro

05 / 08
TauriSQLiteNext.jsTypeScriptGitHub ActionsSupabase

Date

Oct 2025

Contribution

Team of 2

Type

Desktop app for a small business

PavtiPro is a billing app for jewelry shops. A salesperson builds an invoice in three steps, adds items like gold, silver or diamond pieces, takes payment, and prints a customer copy and a merchant copy. My teammate built it first as a Next.js web app on Supabase. It worked, but it needed a hosted database and an internet connection to bill a customer.

My part was turning it into a desktop app that runs fully offline. The data now lives in a SQLite file on the shop's own machine, and installers for Windows, macOS and Linux are built and attached to a GitHub release automatically.

What it does

Invoices

Multiple items per invoice, with design number, gold colour, carat, grams and stone weight. 5% VAT is worked out per item and per invoice.

Payments

Cash, credit card, debit card and old gold, split across methods if needed. Advance payments are tracked against the remaining amount due.

Edits and cancellations

An edited invoice links back to the original and records who changed it. Cancellations keep a reason and a timestamp.

Tax-free sales

Tourist details are stored per invoice for tax-free refunds. The request format for the refund provider is built and validated, but not yet sent anywhere.

Who built what

Invoice builder, payments, advance payments, print layout, edit and cancel logic, tax-free data
Teammate
Supabase web version, first Tauri shell and dark mode
Teammate
SQLite schema and data layer, local login with bcrypt
Me
Static export for the desktop build, cross-platform installers and automated releases
Me

How it works

Next.js app

Frontend


  • Next.js (static export) | Tauri window | Plain files
SQL results

Repository layer

Data access


  • tauri-plugin-sql | SQLite (local machine) | Same interface as before
Query results

Database

pavtipro.db


  • Nine tables | Created on first launch | Schema migrations

Static export

Tauri loads plain files, so Next.js server actions and middleware had to go. I switched the app to a static export and removed the server-only code.

Same repo interface, new storage

The pages already talked to a set of repository modules. I rewrote those modules to run SQL through tauri-plugin-sql instead of calling Supabase, then moved the remaining pages that still called Supabase directly.

Schema on first launch

A database singleton creates the nine tables if they don't exist, then runs a short list of ALTER TABLE migrations. A migration that fails because the column already exists is skipped.

Local auth

Supabase Auth was replaced with bcrypt password hashes in SQLite and a small session record in local storage.

Shipping it

release-please

Reads conventional commit messages, opens a release PR, and tags a version when it is merged. The app went to 2.0.0 for the offline version, then 2.1.0.

Build workflow

Runs on each published release. A matrix builds the Windows (.exe, .msi), macOS (.dmg) and Linux (.AppImage, .deb) installers and uploads them to the release.

Cleanup

I removed 27 debug, test and setup pages left over from the web version. They depended on Supabase and broke the static build.

Problems worth telling

  • Cold-start hang

    Problem
    On first launch the app sometimes froze on a blank screen. The Tauri bridge wasn't always ready when the app tried to open the database.
    Fix
    I added a timeout and up to three retries, and made the login check treat a failed load as 'signed out' instead of waiting forever.
  • Dynamic routes in a static export

    Problem
    Pages like a single invoice view use an ID in the URL, which a static export can't pre-render.
    Fix
    I fixed these along with the type mismatches that surfaced once Supabase's generated types were gone.
  • Pinned Rust version

    Problem
    CI failed because a dependency needed Rust's 2024 edition.
    Fix
    Unpinning the toolchain fixed it.

What's next

  • Send tax-free requests to the provider. The builders, validators and a test against sample requests are already in place.
  • Work through the refactor list I wrote after the migration. Top of it: splitting the new-invoice page, which is about 2,600 lines, and reading the business name, tax number and VAT rate from settings instead of code.