(This is the first experimental piece that is not an all-in-one monthly summary, but instead a standalone technical note that is supposed to be short and to the point. Let's go.)
Stopwatch is such a commonplace feature, but I have never thought about its design (data structure) and implementation. UX-wise I thought at first that it would be handy (based on my own experience) to have multiple stopwatches that can be run concurrently (but not in parallel, in the sense of single-core, multi-threaded processing via context-switching). But eventually I realized that I didn't have to actually use a list of stopwatches if parallel usage is forbidden in the first place. I could instead implement a "Save & Load" mechanism along with a single stopwatch.
But eventually I did not implement this extra feature at all. For one, it would probably disrupt the currently minimalistic UI; and more importantly, after a more careful look at the need, it was one of those features that I found handy in just very few occasions over more than two years. The frequency of demand does not warrant its implementation. Exposing this feature to every user, imposing this extra cognitive load to all, is most likely not beneficial.
Data Structure
At first, a stopwatch that you could pause and resume indefinitely seemed to me like a list of (start, stop) pairs of timestamps. But since there were two alternating states, I realized that when a stopwatch was running, the last pair would be incomplete. Therefore, you couldn't type the overall structure as such. So indeed, I needed to start with a union type where each of the two states had different data associated.
A second realization that struck me was that only the last, or most recent, start time needed to be stored, as all the previous start/stop pairs could be reduced to a single duration. (It's like garbage collection.)
type Stopwatch
= Running Int Time.Posix
| Sleeping Int
Now that's all I needed to represent the complete information of a stopwatch. But to display it, I needed one more piece. We know that when a stopwatch is paused, its display is static, as all we care about is the cumulative duration. But when it's running, it looks like a ticking clock, e.g. the reading is updated every second. For illustration purposes, in the running state, one can view the last start/stop pair as (start, now), where "now" is a variable whose value is periodically updated (via a subscription).
One interesting observation is that, although "now" is required to display the stopwatch, it is not part of its data structure. One way to make sense of this is as follows: even when a stopwatch is running, it is possible to persist its state so that it can survive a reload, and to achieve that, we don't have to persist "now" repeatedly, because in a sense, "now" is universally persisted, and we can simply query its value whenever we need to.
Issues
I noticed that after the introduction of the stopwatch, the app's "Energy Impact" (mainly CPU I suppose) according to Firefox's Task Manager, had increased, from previously idle, to "Low (0.9)".
I will try something next. For now I can think of some directions to explore:
- Lazy
- Subscribe to
Time.everyonly when the stopwatch is running
Design Highlights
As I said previously, since I started using Arrow back in 2019, I had never thought it was necessary/beneficial to have a built-in stopwatch; instead I'd been using my old Casio for the purpose. But now that I'm in the process of preparing Arrow for its debut, I reason that it is probably an expected functionality.
It turned out that I much enjoyed the design and implementation process. Now that I've built it, I'm even more delighted, and a bit surprised, to see that it really improves the workflow, and now Arrow is significantly more ergonomic with this virtual gadget replacing my Casio.
Here are the features that I think contribute to the UX:
- State persistence over reload/restart or system sleeping/shutdown
- Form filling: a button-click triggers auto-filling of the duration field by pulling data from the stopwatch, after which the stopwatch is auto reset (can also append to an existing duration)
- Color-coding the display as a pronounced visual indicator of the running/sleeping state (a glimpse afar can remind a user to pause/resume the stopwatch)
- Keyboard shortcut to control the stopwatch
- Record first, file next: use the stopwatch without having to create a session up front
- Ubiquitous, unobtrusive placement: available everywhere but does not disrupt the rest workflow