A Windows desktop payroll application for a real Illinois employer. The user interface is a Tauri 2 + 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.
This is not a demo. It is intended to pay real people, so correctness leads the roadmap. Statutory tax figures are verified against IRS / SSA / Illinois DOR primary sources and cited in the code; the engine throws rather than guess for any year it has not verified. See Limitations for what it deliberately does not do.
The latest tag builds and publishes unsigned Windows installers (see Releases):
*_x64-setup.exe— NSIS installer (simple double-click)*_x64_en-US.msi— Windows Installer (managed / enterprise installs)
Runs on Windows 10/11 64-bit. WebView2 installs automatically if missing. Because the build is unsigned, SmartScreen will warn on first launch — choose More info → Run anyway. All data stays local (see Data & storage).
Installers are produced automatically by the CI workflow (.github/workflows/release.yml) when a
v* tag is pushed; the release is marked prerelease. A manual (workflow_dispatch) run builds
the installers to validate the pipeline without publishing.
What "preview" means: the payroll math is correct, but this is not yet a compliance solution — no 941/940/W-2 generation, no e-file, single-state (Illinois) only.
The frontend renders in WebView2 and never touches the database directly. Every request goes through one bridge command to the Rust shell, which forwards it to the .NET sidecar over newline-delimited JSON-RPC. Payroll arithmetic stays entirely in the C# engine.
React + TypeScript (apps/desktop)
│ invoke("backend_command", { method, paramsJson })
▼
Rust shell (apps/desktop/src-tauri)
│ newline-delimited JSON-RPC over stdin/stdout
▼
payroll-backend.exe (services/PayrollManager.Backend)
│
└── PayrollManager.Domain → EF Core → SQLite
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 isbackend_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 as already-computed values 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 (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.
PayrollManager/
├─ apps/desktop/ # Tauri 2 + React 19 frontend (the current UI)
│ ├─ src/ # React app: features, router, IPC bridge
│ │ └─ features/ # employees, pay-runs, pay-stubs, reports, settings, audit
│ ├─ src-tauri/ # Rust shell — spawns and talks to the sidecar
│ │ ├─ src/main.rs # single backend_command bridge, sidecar path resolution
│ │ └─ src/sidecar.rs # JSON-RPC client + restart/retry policy
│ └─ scripts/bundle-sidecar.mjs # stages the published sidecar into the bundle
│
├─ services/PayrollManager.Backend/ # .NET sidecar: self-contained payroll-backend.exe
│ ├─ Program.cs # stdio host, DB bootstrap, command registration
│ ├─ Rpc/ # StdioHost + CommandDispatcher
│ ├─ Handlers/ # employee / pay-run / pay-stub / reporting / settings commands
│ └─ Contracts/, Validation/
│
├─ PayrollManager.Domain/ # The payroll engine (authoritative)
│ ├─ Services/Tax/ # Federal (Pub 15-T) + Illinois (IL-700-T) withholding
│ ├─ Services/ # PayrollService, employer taxes, FLSA overtime, Money, PDF
│ ├─ Services/Security/ # DPAPI SSN protector
│ ├─ Models/ # Employee, PayRun, PayStub, CompanySettings, AuditLogEntry…
│ ├─ Data/ # EF Core DbContext, migrations, DB path/bootstrap
│ └─ Migrations/
│
├─ PayrollManager.Domain.Tests/ # xUnit tests for the engine
├─ services/PayrollManager.Backend.Tests/ # xUnit tests for the sidecar commands
│
└─ (root) PayrollManager.UI + Views/ ViewModels/ … # Legacy WinUI 3 app — being replaced
The WinUI 3 project (PayrollManager.UI.csproj, plus the root Views/, ViewModels/,
Components/, etc.) is the original UI. It is being superseded by apps/desktop and is kept for
reference; new work targets the Tauri frontend and the shared PayrollManager.Domain engine.
All calculations live in PayrollManager.Domain and are covered by 173 tests across 18 test
files. Highlights:
- Federal income tax 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/other credits, other income, deductions, and extra per-period withholding.
- Illinois income tax withholding — flat 4.95% with the per-allowance exemption from Form IL-W-4 (Line 1 basic, Line 2 additional), per Booklet IL-700-T.
- FICA — Social Security 6.2% up to the annual wage base ($184,500 for 2026), Medicare 1.45%, and Additional Medicare 0.9% above $200,000 (withheld without regard to filing status, which the employee reconciles on Form 8959).
- Employer taxes — FUTA (0.6% net / 6.0% gross on the first $7,000) and Illinois SUI,
whose rate and wage base are employer-specific (from the annual IDES rate notice) and therefore
entered in Settings, never hardcoded —
0surfaces a warning rather than a silently wrong liability. - 401(k) — pre-tax deferrals capped at the IRC §402(g) elective-deferral limit, with the age-50+ catch-up.
- FLSA overtime for hourly employees.
- Money discipline — every amount is a
decimal, rounded to the cent half away from zero (matching IRS worksheet instructions, not banker's rounding). Each line is rounded as produced, and every total is the sum of already-rounded lines — so a stub's totals always match the lines printed beneath them. - Verified tax data — statutory figures for 2024, 2025, and 2026 are compiled in with
primary-source citations. Running payroll for a year with no verified figures throws
TaxRulesNotAvailableExceptioninstead of reusing another year's numbers.
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 audit log.
- Employees — list, search, and detail tabs (overview / compensation / taxes); create and edit. Captures federal W-4 and Illinois IL-W-4 fields, residential address (printed on the stub), hire/termination dates, and an encrypted SSN (masked to the last four digits).
- Pay runs — a new-pay-run wizard, the Draft→Calculated→Posted lifecycle above, void with reason, and per-run detail.
- Pay stubs — printable / PDF earnings statements (QuestPDF) with current-period and year-to-date breakdowns of earnings, taxes, and deductions.
- Dashboard & reports — company-wide summaries with CSV export.
- Settings — company info, pay periods per year, default hours per period, and employer unemployment (SUI / FUTA) configuration.
- Audit log — an append-only record of pay-run and data changes.
To run an installer: Windows 10/11 64-bit. WebView2 installs automatically if missing.
To build from source:
- Windows 10/11 64-bit
- .NET 8 SDK
- Node.js 20+
- Rust stable (1.77.2+), with the MSVC toolchain
- WebView2 runtime (preinstalled on current Windows; otherwise installed by the app)
The Rust shell launches the .NET sidecar, so the sidecar must build first. npm run tauri dev
handles that for you via the configured beforeDevCommand.
# 1. Clone
git clone https://github.com/TerminatrX/PayrollTracker.git
cd PayrollTracker/apps/desktop
# 2. Install frontend dependencies
npm install
# 3. Run the desktop app (builds the sidecar, starts Vite, launches the Tauri window)
npm run tauri devIf you prefer to build the sidecar explicitly:
dotnet build services/PayrollManager.Backend/PayrollManager.Backend.csproj| Command | Purpose |
|---|---|
npm run tauri dev |
Full desktop app with the sidecar |
npm run dev |
Vite dev server only (backend calls will fail — no desktop shell) |
npm run typecheck |
tsc --noEmit |
npm run build |
Typecheck + production frontend bundle |
npm run tauri build |
Installable Windows bundle (MSI + NSIS) |
cd apps/desktop
node src-tauri/generate-icon.mjs # icons/ is generated, not committed
npx tauri icon src-tauri/app-icon.png
npm run tauri build # publishes the sidecar, builds the UI, bundles MSI + NSISOutput lands in apps/desktop/src-tauri/target/release/bundle/{msi,nsis}.
The SQLite database lives under the per-user app-data directory — not the install directory:
%LOCALAPPDATA%\com.payrollmanager.desktop\payroll.db
On first run, migrations are applied automatically (with a backup taken beforehand), and a database left at the old install-directory location is relocated automatically — the original is left in place rather than moved. SSNs in the database are DPAPI-encrypted; only the last four digits are stored in plaintext.
Configured on the Settings page:
- Company — name, address, Tax ID
- Pay periods — periods per year (default 26, bi-weekly) and default hours per period (default 80 for hourly employees)
- Employer unemployment — Illinois SUI rate and wage base (from the IDES rate notice) and whether the full FUTA credit applies. These are employer-specific and must be entered.
Statutory federal and Illinois withholding figures are not user-editable — they are law, keyed to the year of the pay date, and maintained in code with source citations.
dotnet test PayrollManager.slnCoverage spans the withholding calculators (federal Pub 15-T and Illinois), employer taxes, FLSA overtime, pay-period math, aggregation/reporting, CSV export, pay-stub statements and persistence, database bootstrap/migrations, and the sidecar JSON-RPC command handlers.
| Layer | Technology |
|---|---|
| Desktop shell | Tauri 2 (Rust), WebView2 |
| Frontend | React 19, TypeScript, Vite, Tailwind CSS v4, TanStack React Query, React Router, react-hook-form + Zod |
| Sidecar | .NET 8 self-contained single-file executable, newline-delimited JSON-RPC over stdio |
| Engine | .NET 8, Entity Framework Core 8, SQLite, QuestPDF (pay stubs), DPAPI (SSN encryption) |
| Legacy UI | WinUI 3 (Windows App SDK) — being replaced |
This is preview software for a single Illinois employer. It computes withholding and net pay; it does not:
- generate or file employment-tax forms (941, 940, W-2) or handle deposits/e-file,
- support states other than Illinois or multi-state employees,
- provide signed installers (SmartScreen will warn — this is expected).
It is not tax or legal advice. The employer remains responsible for deposit and filing obligations. Verify results before relying on them to pay people.
No open-source license is granted. This is a private project; all rights reserved unless a
LICENSE file is added to the repository.