ontimev2_thumbnail

OnTime V2

Von am 17.09.2026

OnTime started because I didn’t want to pay for a time tracker. Version one only had that traccker and one Tauri 2 codebase that shipped to the web, Windows and Android.

V2 lives in a new repository, but it is of course built on Version one. The tracker is still at its core and around it, OnTime now handles teams, invoices and payments.

What changed

In V1, one person tracked their own time but in V2, everything happens inside workspaces. A workspace can consist of just you or a whole team and every member has one of four roles: owner, admin, manager or member.

But the basics are of course still there: a timer, manual entries and clients, projects and tasks with their own detail pages. The calendar is now shared and even updates live across devices. Projects can have budgets that send a notification when they cross a threshold. The whole app is in English and German and on mobile it works offline thanks to a persisted query cache.

Invoices and payments

This is where V2 changes the most from its previous version. Billable time becomes an invoice and when you send it, an Edge Function renders it as a PDF and freezes it. If you edit the underlying time entries later, the document the client already received doesn’t change.

Additionally, OnTime connects to your bank through Enable Banking. The connection is read-only by design and you bring your own credentials, which are stored encrypted in Supabase Vault. A scheduled pg_cron job pulls in transactions and matches them against open invoices. When a match is confident, the invoice is marked paid automatically. Everything else waits in a queue for one-click review.

Database changes

V1’s database only had to serve one person tracking their own hours. V2 needed a real data model, so I didn’t patch the old schema and started from the start.

As said before, Everything belongs to a workspace: Clients, projects, tasks, time entries and invoices all hang off a workspace and every row knows which workspace it belongs to. That one change is what makes teams possible. It’s also what the security model is built on.

Also, every table has row-level security policies that check the caller’s workspace and role before returning a single row. Views run with security_invoker, so they apply the permissions of the person querying them instead of quietly bypassing them. Owners, admins, managers and members all hit the same tables and get different answers. Since the frontend isn’t trusted to enforce any of this, pgTAP suites test the rules against a real database.

Another security measure was to add a timestamp to entries when they get deleted. Now they are moved to a trash view, where they can be restored. A pg_cron job purges them for good once they’ve been in the trash long enough, so nobody has to clean up by hand.

An invoice can’t point at time entries and hourly rates, because those can change or be edited. When an invoice is sent, it stores a JSONB snapshot of everything it was built from and the PDF is frozen alongside it. Rates also keep a history, so billing a project in March uses March’s rate even if it went up in June. Invoice numbers are generated per workspace and restart each year.

pg_cron also triggers the bank sync so that transactions come in on a schedule, get matched against open invoices and confident matches mark the invoice as paid. The bank credentials never sit in a plain column and are encrypted in Supabase Vault.

The shared calendar and the notifications subscribe to database changes through Supabase Realtime. When someone stops a timer on their phone, the entry shows up on everyone else’s desktop without a refresh.

Testing

Tests run in three layers:

  • Vitest for units and components
  • Playwright end-to-end tests against a preview build
  • pgTAP for the security rules

Unit and end-to-end coverage are combined into one number and uploaded to Codecov on every CI run. Right now I have about 40% coverage but that will still be improved in later versions.

Beitrag kommentieren

(*) Pflichtfeld