MB-302SoftwareIn daily use · SaaS in development2026
TymTracker
A time-tracking and invoicing app I built when my invoicing tool's price went from about $100 to about $2,000 a year. It runs my own invoicing today, and is being rebuilt as a multi-tenant SaaS product.

It covers the whole loop: a weekly timesheet, projects and customers across several businesses, a four-step invoice wizard that only pulls uninvoiced time and billable expenses, PDF invoices by email, and automatic past-due notices. Invoiced time locks so the books can’t drift.
Decisions
- Money as integer cents, converted in exactly one place. Every money column was renamed (
hourlyRateCents,totalCents) so the compiler found every use. The API rejects non-integers and the server recomputes invoice totals. - A migration that reports instead of silently fixing. The move from SQLite to Postgres runs in one transaction as a dry run by default, and reports row counts, money totals, rounded sub-cent values and any invoice whose total doesn’t match its lines.
- Evolve in place. My own data migrates in as the first tenant. The live single-user instance keeps running until cutover.
- Plan limits are data, not code, so pricing can change without a deploy.
What the agent audit found
- Forgeable sessions. The live instance was signing sessions with a placeholder secret from the compose file. Fixed, and the app now refuses to start with a missing, short or placeholder secret.
- Public uploads with stored XSS. Uploaded files were reachable without login, and file type came from the client’s filename. Fixed with magic-byte checks and by putting uploads behind auth, then verified from outside the network.
- The Save button that did nothing, three times. The database returns
null, the form schema accepted onlyundefined, and validation failed silently. The rule is now written into the project’s agent memory so it can’t come back.
Screenshots are from a local copy running on made-up seed data, never the live instance.


