A software developer who is particularly good at turning strong design and business requirements into finished products.

Timglas

Native macOS application · Swift, SwiftUI, AppKit · Independent · 2026 to present · timglas.app · source

My role
My own product rather than client work. Sole designer and developer.
What I delivered
A native macOS application, 87 Swift files and around 47,000 lines with no third-party dependencies: menu-bar and window modes, automatic tracking rules, idle resolution, local JSON storage with rotating backups and recovery, Shortcuts and Siri intents, a URL scheme, Spotlight indexing, CSV, JSON, iCal and PDF export, and 93 tests.
Why it mattered
Time data is only worth having if you would put it on an invoice. The app assumes people track imperfectly and makes correcting a day cheap, and it keeps everything local: no account, no backend, no telemetry.
Current status
In active development and public on GitHub. It is the app I use to track my own work. Swift was a language I did not know when I started it.
Timglas running in the macOS menu bar

Scope

Timglas is a menu-bar-first time tracker for macOS, built for freelancers and independent professionals. It is my own product rather than client work, and it is the project I use to learn things properly rather than just well enough to ship.

87 Swift files, around 47,000 lines. Swift, SwiftUI and AppKit, macOS 14.6 and up, and no third-party dependencies at all. No account system, no backend, no analytics, no telemetry. Your work history is a readable JSON file on your own disk.

Why it exists

Every time tracker I tried failed in one of two ways. Either it was so unobtrusive that it recorded nothing useful, or it demanded so much bookkeeping that tracking time became its own task competing with the work being tracked.

The design premise is that both failures come from the same mistake: assuming people track time perfectly. They do not. They forget to start the timer, leave it running through lunch, work at 11pm and finish after midnight, take a call they never logged. A tracker that only handles the tidy case produces data nobody trusts, and data nobody trusts does not get billed.

So the whole application is organised around imperfect days. You can backfill time you did not record, resolve idle periods after the fact, label gaps in the timeline, split an entry that turned out to be two things, and review unassigned work later. The tracker assumes you will get it wrong and makes getting it right afterwards cheap.

One object owns the truth

There is a single Tracker object, isolated to the main actor, and every mutation goes through it. Starting, stopping, pausing, switching activity, editing an entry, applying an automatic rule, handling a URL, responding to a Shortcuts intent. Six different ways to start a timer, one place where starting a timer actually happens.

That sounds obvious and is easy to lose. The tempting alternative is for the menu bar controller to own the menu bar's state and the window to own the window's, at which point the two disagree, and a timer that shows as running in one place and stopped in another is worse than no timer at all.

The discipline inside that object is about what gets published. SwiftUI re-renders every view observing a changed property, so a value that ticks every second and is published carelessly will redraw the entire interface once a second. Several properties are deliberately not published for exactly that reason, with the reasoning written next to them so a future version of me does not helpfully "fix" it.

WAYS IN Menu bar Main window Global shortcut Shortcuts and Siri timglas:// URLs Automatic rules Tracker One MainActor object Owns every mutation Publishes what changed, and nothing that did not Views SwiftUI, observing a single object Store Local JSON, rotating backups on disk Exports CSV, JSON, iCal, PDF

Time is harder than it looks

The single largest test file in the project covers interval arithmetic, and that is the honest measure of where the difficulty is. Not the interface. The subtraction.

Consider an entry a user types in as starting at 23:00 and ending at 01:00, leaving both dates set to the same day, because that is what a form defaults to and nobody thinks about it. Read literally, the end precedes the start: the duration is negative, and depending on how carelessly the code is written the entry either vanishes or corrupts a day's total. What should happen is that the end rolls forward one day, because a person who worked from eleven at night until one in the morning worked two hours and expects to be paid for two hours.

AS TYPED Start Tue 23:00 End Tue 01:00 End precedes start, by 22 hours AS STORED Start Tue 23:00 End Wed 01:00 The end rolls forward one day Two hours Tue 23:00 Wed 01:00 TUESDAY WEDNESDAY

That is one case. The others are just as ordinary and just as easy to get wrong. A paused timer has to freeze its duration and bank the pause, so resuming does not silently bill the lunch break. Stopping while paused should end the entry at the moment of the pause, not the moment of the stop. Reopening a closed entry has to preserve what it already accumulated. A running timer shown inside a report for last week has to be clipped to that week rather than counted whole. An entry typed in backwards should be corrected rather than rejected, but only when it is genuinely backwards and not merely overnight.

None of this is visible in a screenshot. All of it is the difference between a tracker whose totals you would put on an invoice and one you would not.

Automatic tracking, without surveillance

Timglas can track time on its own, using rules matched against the application in front, the window title, the URL, the time of day and the weekday. That is genuinely useful and it is also the feature most capable of being creepy, so the constraints matter more than the capability.

The first is minimal privilege in practice rather than in principle. Reading a window title or a browser URL requires the Accessibility permission, so a rule that only needs to know which application is frontmost never asks for a title. There is a test asserting exactly that: app-only rules perform no accessibility reads. The system is only queried for what a rule actually needs.

The second is that automation never overrides intent. If you started a timer by hand, a rule does not quietly stop it and start something else. Suggestions surface, and a grace period exists so that alt-tabbing to check something does not shred a session into fragments.

The third is a concurrency problem that only shows up in real use. Probing another application for its window title is asynchronous and slow enough that a person switching apps quickly can outrun it. So the probe result is stale by the time it returns, describing an application no longer in front. The tracker coalesces probes and rejects results that no longer apply: if the front application changed again, if automatic tracking was switched off, or if the timer was stopped while the probe was in flight, the answer is discarded. Several tests exist for nothing but these races, because the failure they prevent is time silently attributed to the wrong project.

Idle detection follows the same philosophy. The system reports how long since the last input event, and rather than guessing what an absence meant, Timglas asks: keep it, discard it, or assign it somewhere else.

The automatic tracking rules editor

Data you can lose confidence in, but not lose

Local-first sounds reassuring until you consider that it also means there is no server holding a copy when something goes wrong. Being the only copy is a responsibility, not a feature.

Storage is plain JSON, written with sorted keys so the file diffs cleanly and a person can read it. Daily backups rotate, with older ones pruned. If the data file fails to decode, it is not overwritten and it is not silently discarded: the damaged file is quarantined, the app surfaces a recovery notice rather than starting empty and pretending nothing happened, and saving is suppressed rather than writing over a file that might still be recoverable.

The rename is a small example of the same habit. The app was called Takt first, and its data lived in a folder with that name. When it became Timglas, a one-time migration moves the old directory and its backups across rather than starting empty.

Nothing was at stake yet, since the app is not released and the only history in that folder was my own. That is rather the point. Data migration is a discipline that has to already exist by the time it matters, because the version where it matters is the version where somebody else has a year of billable hours in the file.

Living inside the system

An application that only works when its window is open is a website in a bad disguise. Timglas is built to be reachable from wherever you already are.

It runs as a pure menu-bar application, or with a Dock icon and a full window if you prefer. Timers start from a global keyboard shortcut without touching the app. App Intents expose starting, stopping and querying the current task to Shortcuts and Siri, so tracking can be automated into other workflows. A timglas:// URL scheme makes actions linkable from anywhere that opens a link. Entries are indexed into Spotlight, so past work is searchable from the system rather than only from inside the app. Notifications, launch at login, and a Pomodoro rhythm layered over the running timer complete it.

Reports export to CSV, JSON, iCalendar and PDF, and CSV imports back in. The export formats are deliberate: your data leaves in shapes other tools accept, because a local-first application that traps your history is just a cloud service with worse uptime.

Reports and revenue calculations

Tests, and where they are

93 tests, and their distribution says more than the number does. The heaviest coverage is on interval arithmetic, then storage migrations, then report grouping, then the automatic tracking races, then imports and exports, Spotlight indexing, idle resolution and the URL and Intent flows.

What is deliberately not tested is the interface. Views are the part I can see is wrong. Time arithmetic, migrations across schema versions and asynchronous races are the parts that break silently, months later, in somebody else's month of billable work.

Status

In active development, public on GitHub, and the app I use to track my own work. Swift was a language I did not know when I started it.