AlpineTime: what the spec went through before touching code
The spec document is at version 7. Each version existed because the previous one had a gap — something that looked like a detail until it wasn’t. Version 7 is not the final version; it’s just where the code caught up to the thinking.
The constraints that drove the most decisions weren’t business logic — they were regulatory and cultural. MWST (Swiss VAT) applies per client, at a configurable rate, with some clients exempt. That one requirement means you can’t compute VAT inline on each line item; you need to carry the rate through to the report total and apply it once. It also means the rate has to live on the client record, not on the invoice.
nDSG — Swiss data protection law — pushed for pseudonymization support from the start. Not retrofitted. Employee records need to be anonymizable without breaking the audit trail, which means soft deletes and a separate audit log, not cascading hard deletes.
Time storage: everything in UTC, timestamptz columns throughout, date derived in Europe/Zurich. This sounds obvious until you’re generating a billing report for March and an entry at 23:30 UTC on March 31 is actually April 1 in Zurich. Getting this wrong at spec time means fixing it in production.
I18n from day one. de-CH primary, English secondary. No retrofitting — the Swiss market means German is not optional, and the codebase should reflect that from the first page, not as an afterthought.
Some things the spec got wrong:
Optional client/project/task on time entries. The original design required all three before saving. In practice, people start the timer, then fill in the project. Making them mandatory at creation blocks the fastest workflow. Phase 24 changed this — all-null is valid, billable defaults to false when there’s no task. The cascade logic got more complex; the UX got better.
Overlap resolution UI. The spec said “detect overlaps, reject entry.” That’s wrong. What actually happens: someone forgets to stop the timer, manually enters the real time later, and now there’s a conflict. Auto-rejecting means they lose the entry. Auto-merging means they lose control. The right answer is to surface the conflict with both entries visible and let the user choose — merge or replace. That’s not in version 1 of the spec.
Audit trail. Not in scope for the MVP. Added in phase 25. Probably should have been there earlier.
Thirty phases, numbered upfront. Gave structure to what would otherwise be a long undifferentiated list of things to build. The first 26 were the alpha build; phases 27-30 were post-alpha hardening — security, test coverage, code quality, operations. The numbering meant I always knew where I was and what was left, even when individual phases took longer than expected.