Skip to content
All work

PayrollTracker

Real Illinois payroll, on the desktop.

Payroll · Desktopv0.1.0-previewPublic repositoryUpdated 24 Jul 2026
View source

A Windows desktop payroll application for a real Illinois employer. A Tauri 2 and React shell drives a tested .NET 8 payroll engine running as a local JSON-RPC sidecar, with everything stored in SQLite on the machine. It is intended to pay real people, so correctness leads the roadmap.

Problem
Run real Illinois payroll from a Windows desktop application.
Constraint
Statutory figures change by year, and a plausible stale value can pay people incorrectly.
Decision
Keep payroll arithmetic in a tested .NET domain engine, verify each supported tax year against IRS, SSA, and Illinois DOR sources, and throw for every other year.
Evidence
173 tests across 18 files; 2024–2026 statutory figures are compiled with primary-source citations.
.NET 8RustTauri 2React 19EF CoreSQLite

What it is

This is not a demo. It is intended to pay real people, which sets the order of everything else: statutory tax figures are verified against IRS, SSA, and Illinois Department of Revenue primary sources and cited in the code, and the engine throws rather than guess for any year it has not verified.

The user interface is a Tauri 2 and React desktop app. All payroll math runs in a tested .NET 8 engine that the frontend drives as a local JSON-RPC sidecar. Data is stored locally in SQLite under the per-user app-data directory — never in the install directory.

Architecture

  1. React + TypeScript
    apps/desktop, rendered in WebView2
    invoke("backend_command")
  2. Rust shell
    apps/desktop/src-tauri
    newline-delimited JSON-RPC over stdio
  3. payroll-backend.exe
    .NET 8, self-contained single file
  4. PayrollManager.Domain
    the authoritative payroll engine
    EF Core
  5. SQLite
    %LOCALAPPDATA%, DPAPI-encrypted SSNs
The frontend renders in WebView2 and never touches the database. Every request crosses one bridge command into the Rust shell, which forwards it to the .NET sidecar. Payroll arithmetic stays entirely in the C# engine.

The payroll engine

All calculations live in PayrollManager.Domain, covered by 173 tests across 18 test files.

Federal withholding
IRS Publication 15-T percentage method for automated payroll systems (Worksheet 1A), with full Form W-4 (2020+) support: filing status, the Step 2(c) multiple-jobs checkbox, dependents and other credits, other income, deductions, and extra per-period withholding.
Illinois withholding
Flat 4.95% with the per-allowance exemption from Form IL-W-4, per Booklet IL-700-T.
FICA
Social Security at 6.2% up to the annual wage base ($184,500 for 2026), Medicare at 1.45%, and Additional Medicare at 0.9% above $200,000 — withheld without regard to filing status, which the employee reconciles on Form 8959.
Employer taxes
FUTA at 0.6% net on the first $7,000, and Illinois SUI, whose rate and wage base are employer-specific and therefore entered in Settings rather than hardcoded. A zero surfaces a warning instead of a silently wrong liability.
401(k)
Pre-tax deferrals capped at the IRC §402(g) elective-deferral limit, with the age-50-and-over catch-up.
Overtime
FLSA overtime for hourly employees.

Every amount is a decimal, rounded to the cent half away from zero — matching IRS worksheet instructions rather than banker's rounding. Each line is rounded as it is produced, and every total is the sum of already-rounded lines, so a pay stub's totals always match the lines printed beneath them.

Statutory figures for 2024, 2025, and 2026 are compiled in with primary-source citations. Running payroll for a year with no verified figures throws TaxRulesNotAvailableException instead of reusing another year's numbers.

Pay-run lifecycle

Pay runs move Draft → Calculated → Posted, with Voided as the only exit from Posted. A posted run is immutable: money has been committed, so corrections are made by voiding and issuing an adjustment run, never by editing history.

Posting recomputes the amounts and compares them against a hash of what the user reviewed, so a run can never post numbers different from the ones that were approved. Every mutation is written to an append-only audit log.

Security posture

  • No shell access from page code. The sidecar is spawned from Rust with std::process, not through Tauri's shell plugin. The only bridge is backend_command, which forwards a method name the sidecar either recognises or rejects — page code cannot name an executable, pass process arguments, or express SQL.
  • Money is display-only in the frontend. Amounts arrive already computed and are rendered as-is; the app never re-derives or sums money in JavaScript, where float drift would disagree with the backend's decimal math.
  • One request in flight at a time. The Rust client serialises calls behind a mutex, so a response line always belongs to the request just sent. A pre-send failure is retried once on a fresh process; a post-send failure, where the command may already have committed, is never silently replayed.
  • SSNs are encrypted at rest with Windows DPAPI and never sent to the frontend. Only the last four digits are kept in plaintext, for masked display.

What it deliberately does not do

The payroll math is correct, but this is not yet a compliance solution, and the preview label is meant literally.

  • No employment-tax form generation (941, 940, W-2), no deposits, and no e-file.
  • Illinois only. No other states, and no multi-state employees.
  • Installers are unsigned, so SmartScreen warns on first launch. This is expected.
  • It is not tax or legal advice. The employer remains responsible for deposit and filing obligations.