Case study · 2026
Amethyst: counting time only while Chrome is focused
I built a local Chrome extension that records active time per site. The engineering problem is recording a minute only while Chrome is still focused, even when the focus event never arrives.
On macOS, Chrome can miss the focus-lost event when you switch apps, so each one-minute save reads which window is focused before it records time.

My role and the product boundary
I built the extension's tracking, local storage, dashboard, and CSV export. Amethyst records how long you actively spend on each site.
The totals stay in extension storage. A server is not required to decide whether a minute counts.
The decision: read window focus before each save
A site counts only while its session is running. The session pauses when the window blurs, the tab hides, the browser goes idle, or the URL is not a normal web page.
Chrome reports that blur through windows.onFocusChanged. On macOS that event can fail to arrive when you switch apps with Cmd+Tab or the Dock. The session stays open, and the next save would add time for a site you had already left.
A one-minute alarm writes elapsed time. Before that write, the worker calls windows.getLastFocused(). If that window is not focused, the worker pauses the session with reason window-blur. commitElapsed adds nothing while the session is paused.
When Chrome has no active tab to continue, the pause is stamped at the last real event, not at the alarm. The gap after that event is not added.
One-minute alarm
→ read the last focused window
→ pause if Chrome is not focused
→ commit elapsed time only if the session is still running
windows.onFocusChanged still pauses as soon as the event arrives. The alarm is the check for the event that did not.
The limit of a timed check
The missed focus event is corrected at the next one-minute save, not at the moment you leave Chrome. Time after the last real event is left out. The chart does not update in the same instant as Cmd+Tab.
Hidden tabs, idle, and chrome:// pages pause through their own events. Those paths do not wait for the focus alarm.
Verification in the repository
The Vitest suite runs the tracking reducer without Chrome. Pausing on window blur records only the time until the pause. Duplicate activity pings before a commit do not add extra time. Reconciling the focused tab starts the new site even when a visibility message arrives early and is dropped.
Those tests pass a window id into the reducer. They do not drop windows.onFocusChanged the way Chrome can. Switching apps with Cmd+Tab on macOS, waiting for the one-minute save, and checking that the site you left did not keep growing is still a browser check.
What this project demonstrates
The weekly chart is what you see. The work is pausing the session before a save when Chrome is not the focused window.
I would carry that into other counters that depend on a browser event: keep the session, and read window focus again before committing time a missed event could inflate.