iOS Bugs Appearing in Apple Ads  

If there was any doubt that the golden era of Apple was over, this is the nail in the coffin. The company will continue to achieve great things for some time to come much in the same way that Holywood continued to flourish subsequent to its golden era. But Apple is not the same perfectionist underdog for which its original enthusiasts were first attracted to it.

Death by Self-Driving Cars  

More people die in the U.S. from opioids (prescription painkillers) and heroin than from firearms. In 2015, nearly 4 times as many people died from drug overdoses than from guns.1 If we derive our morals from desired outcomes2 and buy into the notion that more gun control will reduce gun violence, then to be consistent, for every protest we make that there should be more gun control, we should protest 2-3 times as often in favor of outlawing painkillers.

I don’t know anybody who does that. I’m guessing you don’t either.

My point is this: it may be that humans are more dangerous behind the wheel than are computers. But I bet 20 years from now, computers will slip up and still kill pedestrians, such as in this tragic story from The Verge. Even if computers are 4x safer than humans behind the wheel, the public won’t allow it. There’s something about the computer-driven deaths that’s sickening and perceptively avoidable in ways that the human-driven ones aren’t.

  1. I’d be willing to bet that about half of Americans would guess that this ratio is roughly flipped. In doing so, they’d only be reflecting what they’re directionally led to believe by mainstream media. ↩︎
  2. As opposed to absolute morals that govern, as a matter of secondary consequence, social outcomes; which in broad strokes is the more correct approach. ↩︎

Yearly 1.2  

Yearly 1.2 is now available for download in the iTunes Store. The biggest change I made was enabling a custom start date other than January 1 for the reading schedule (a request I’d received from friends and strangers alike). For the settings panel I implemented SlideMenuControllerSwift, which was really a joy to work with.1

While I was at it, I added a few more things to the settings panel, including a handy link to go to the app’s Settings for notification management, as well as a link to iTunes for leaving a review.

One new change in 1.2 that I didn’t mention in the (admittedly scanty) release notes is this: when you download the app for the first time, instead of just blindly showing a standard alert dialog requesting that you grant permission for notifications, I first show a custom dialog box that contains this message:

Yearly works best when you receive a morning notification. It silently appears on your lock screen at 5:00 AM and shows what your reading is for the day. To enable notifications, please tap “Allow” in the next window.

Underneath this custom dialog box is a single button whose title is, “Ok, got it,” upon which tapping inside brings up the aforementioned default dialog.

All of Yearly’s UI is built in Storyboard and I’m starting to seriously wish I’d taken the time to learn how to do it programmatically instead. At this point, whenever I want to rearrange anything, the number of constraints I have to redo is tedious. Sometimes I even have to dip into the XML source code of the Storyboard to remove, say, an outlet reference to a view that’s been added, hooked up, and subsequently deleted.2

  1. I’ve learned that you can’t judge a book by its cover when it comes to 3rd party code. The documentation screenshots for SlideMenuControllerSwift were a turn-off at first, but once I realized how smooth, reliable, and flexible its underlying UX was and that its UI was completely determined by what you brought to the table, I decided to give it a go. If I can be vain for a moment, I think the design of Yearly’s settings panel is better than the screenshots for SlideMenuControllerSwift and does a better job “selling” the library. ↩︎
  2. Perhaps the bigger lesson here is that unless you’re an iOS ninja, you want to do your design prototyping elsewhere, and only go into Xcode once you have a pretty solid idea of what you want to design. It doesn’t have to be drafted elsewhere to the pixel per se, but if you’ve got the basic building blocks nailed down before you start implementing them, you’ll save a lot of time. The pitfall I often run into is that I have exactly what I want to build in my head and think it’s all a solved problem, but then when I go to implement it, I’m forced to reckon with scenarios (relating to both UI and UX) that just don’t fly in real life. ↩︎

3 Days a Juror

Early February I received a summons in the mail to serve as a juror for the District Court of Tulsa County. My start date was March 5. This is a recounting of the three days in which I served.1

I arrived at the Civic Center Parkade parking garage in downtown Tulsa on Monday, March 5 at 8:40 AM, blaming the 10 minute tardiness to the backed up traffic at a dysfunctional light at 96th and Riverside. At the parkade, I asked the lady if parking would be free for me since I summoned for jury. She maintained that it would still cost me $5, which meant I’d be making $15 that day, not the $20 we were promised as reimbursement for our service.2

The car safely parked on the second floor of the garage, I stumbled about on foot downtown, unfamiliar with the area. I finally arrived at the courthouse at 8:50 AM, now a solid 20 minutes late. I was exhausted and weak with hunger, having run a 10K that morning with no breakfast. There was a long line outside the Tulsa County Courthouse thanks to the rigamarole of going through security. It was a crisp and rather windy 50°F, and people were wishing aloud that they’d brought coats as they shivered in line. The security setup was similar to an airport’s. When it was finally my turn to put my backpack through the scanner, an agent flagged it for containing a fork utensil, an item the courthouse had recently begun banning. My fork was intended for some grilled eggs which would serve as my breakfast once I had a moment to myself. The agent made it clear that he couldn’t throw away the fork himself; it was my responsibility to dispose of it outside the courthouse, which meant exiting and starting over. I walked the four blocks back to my car and wolfed down my breakfast, using my precious fork. Then I left the fork in the car, walked back to the courthouse, and started all over again at the back of the line.

By 9:40 AM I was finally in the courthouse basement, which serves as a waiting room for summoned jury candidates. There were hundreds of people down there, as well as a free wifi hotspot that couldn’t handle the load. I tried tethering from my smartphone but its connection was E (extended) down there in the concrete bunker. In the back of my mind I planned to arrive at 8:00 AM the next morning to beat the crowds, but I learned a lady next to me had arrived at that exact hour and been surprised to see the line already fully formed. It made zero difference apparently.

Around 10:30 AM we were given a 10-minute break and reminded that if we left the building, we would have to go through security again. I went to a vending machine, still faint with hunger, eager to get some chips or most anything. The vending machines only accepted cash, and I only had credit cards. There was an ATM next to it but ATM’s don’t accept credit cards. Luckily there was a little shop open next to the vending machines that accepted credit cards. I got a mediocre hot dog with cheese for $2.50. The shop was so slow and overwhelmed with jurors that I arrived back to the basement 5 minutes late, but lots of other people were still coming from break too, and nobody cared.

At 11:45 AM we were given a 90-minute lunch break and reminded that if we took our cars anywhere to eat, we’d have to re-enter the parking garage and pay an additional $5. As the room cleared out, I noticed that the wifi started working as its load went to manageable levels. I had to go eat lunch myself though, so after a few minutes of computer work on a side project, I walked the 4 blocks back to my car again. I’d begun noticing that the heels of my feet were hurting from a new pair of dress shoes. When I got back to the car, I realized that my also-brand new socks were wearing down right where the top back of the shoe met the heel, and they were soaked in blood. Worried, I took off my shoes and socks and discovered that my heels were rubbed raw. All of that walking had caused a real ruckus. After finishing my sandwich I limped back to courthouse basement, wincing with every step, trying to make it look like no big deal.

By 2:00 PM, stuck in a chair with white noise reminiscent of an airport terminal, with wifi intermittently usable, I had grown suicidal. I started to wonder if maybe the best course was to just hang all the defendants regardless of their status and release all of us prisoners from the basement. It was dreadful down there. A lot of people were on their phones, some were trying to nap, others were putting together jigsaw puzzles. It was crowded and difficult to get work done as we watched our lives tick away.

But then at 2:30 PM, a bailiff arrived and summoned a group of us into a courtroom for a criminal case. It involved a man accused of pointing a gun at someone else back in 2016. The judge excited us by promising that the trial would be over by end of tomorrow, or Wednesday morning by the very latest. She asked us some preliminary questions, a process called voir dire. The goal was to weed out from the pool of potential jurors anyone who might not be able to offer a fair assessment due to conflicts of interest, biases in favor of one party or the other, etc. One of the jurors recognized the defense attorney as someone who had defended his brother in a previous case, and this juror was consequently summoned to return to the basement as an illegible candidate for this hearing. The judge’s preliminary questions were over by 3:30 PM. We were told to return to the basement the following day no later than 9:30 AM, at which point our bailiff would take us back to the courtroom.

The next day, Tuesday, I put on some band aids on my bloody heels, and used a different pair of shoes that unfortunately also rubbed in the exact same spot. The line through security was much shorter this time, almost nonexistent. It dawned on me that since jury summons occur on Mondays and each day thereafter people are dismissed or told to come back at varying times throughout the day, the Monday line wait is probably always the worst.

Back in the courtroom at the appointed hour, the prosecuting attorney and the defense attorney questioned us about various things, continuing the voir dire process from the day before. We were asked whether we owned guns, where we kept them, if we were members of the NRA, and if we thought there was a responsible way to handle guns. A good half of the room were gun owners. Next, the prosecuting attorney asked if anyone would have a hard time convicting someone over purely circumstantial evidence. One fellow did, and the attorney said that they would like to dismiss him (each side gets to choose a total of 3 people to dismiss). Because this dismissal process is supposed to happen while the court is adjourned, the judge indicated that the attorney’s dismissal request was premature. I’d later learn that this was the very first trial in the prosecuting attorney’s career. After the questioning was complete, we took a 15 minute break. When we returned, the jury selection was announced. It was comprised of a total of 7 people — 6 jurors and one backup, called an alternate juror. I wasn’t chosen.

I’d learn the following day that the defense attorney had been at his profession for twenty years and that the jury acquitted his client of the charge. I have no idea if the man was guilty or innocent, but sometimes the winner is determined by who’s the better attorney, not by who’s telling the truth. When talking about this with another fellow juror, a bartender who had also been dismissed, and we both agreed that, though we’d heard none of the evidence of the case, we didn’t like the vibes from the defendant and suspected him to be guilty of the charge. As an aside, imagine if we’d been selected for that jury. And then imagine that this sort of thing really does happen all the time in criminal courts. Jurors affirm that they won’t make up their minds until they’ve heard all the facts, but from the very outset, they have intuitions about how they feel about a case. Of course they do. They’re already leaning towards a verdict. Nobody is truly objective, no matter how much they want to think that they are.

Those of us who weren’t selected for the case were told to return to the basement. We anticipated we’d be dismissed, done for the week, and done for the next 5 years.3 Alas, when we descended from the 5th floor around 11:15 AM and reported at the basement desk, we were told we could take a 10 minute break and then we had to return. There were still cases for that week that didn’t yet have juries, and everyone had to stay in the pool until all of the cases had their juries selected.

One thing I started noticing was that there were three types of people down in that basement. First, there were the down-and-outers, the bums who lived on Netflix all day long, who got out of bed assuming the day was a wash anyway, and who consequently didn’t seemingly mind being there in the least. Second, there were the upper middle class elitists, who made plenty of money and were kind of bummed that they had to be there, but were taking it in stride as they fielded important phone calls, who knew they wouldn’t really feel the loss of money that they’d otherwise be making at their cushy jobs. Third, there were the average hard working middle to lower middle class country boys, who were somewhere between upset and furious that they had to be there, and eager to get out as soon as they could, who didn’t have any hesitation loudly dropping lines like, “THEY’RE GUILTY” as soon as their names were called, in hopes that they’d be dismissed for cause before their plaintiff even arrived.

At 11:45 AM we were once again given a 90-minute lunch break. “It’s not very often you get to take a ninety minute lunch break,” the lady at the desk said over the intercom, trying to give us something to be grateful for amidst the misery of the basement. I met up with a friend working a few buildings down the street. We went to a company cafeteria, got some nachos, and exchanged notes of how our weeks were going. After returning back around 1:00 PM, I opened my MacBook Pro and got back to work, doing some freelancing on a WordPress project. We were told that there was one more jury that needed to be assembled and that once that was complete, the rest of us were free to leave the courthouse for good. About 2:00 PM they started calling the names for this final panel. I listened as the names rattled off, one by one. And then they called mine.

I cringed when I heard it, knowing that this meant I’d have one more day to go at a bare minimum. A cowboy who’d been in the pool for the previous criminal trial, who had been particularly eager to leave and had gone on and on about his gun collection in hopes of getting dismissed from the case, and who’d had his wish granted, heard his name called too. As I picked up my backpack and prepared to meet the bailiff for this new case, I asked him with a rueful grin, “Why is this happening to us?” His loud, grim answer was, “I guess we just got f****** lucky.” Once we were all assembled, the bailiff told us that he had some “good news,” which was that the trial wouldn’t start until the following day at 2:00 PM. You could hear the jurors grumbling, wondering what was good about this news. As far as we were concerned, it simply meant an extension to our jail time.

The bailiff revealed that this would be a civil case, and I could tell from the number of juror candidates for this trial that the jury would be a full 12, not 6 like the previous criminal case.

Two or three people, including the cowboy, approached the bailiff and told him they had a problem. They didn’t get paid except for days they actually worked, they said, and if they lost any more days of work at their regular jobs, they would stand to lose some serious coin for the month. The bailiff replied, “I’m sorry, but there’s nothing I can do about that. You’ll have to talk to the judge about that tomorrow at two o’clock.” “At two o’clock,” repeated the cowboy with a long face. He began plotting a scheme.

Wednesday rolled around, and this time I was finally forced to discard my new dress shoes and put on an old disheveled pair instead. They were the only shoes that didn’t murder my heels with every step. I arrived at the 5th floor of the courthouse by 2:00 PM and we walked into the courtroom. The case was a lawsuit in which someone had rear ended another driver. The voir dire process would go on to expose cause for removal for three jurors before the plaintiff and defense attorneys exercised their three respective strikes.

To start with, there was a man who worked for a lawn care business. He tried to make a case that if he was not excused to go mow his clients’ lawns, his company would lose those contracts, and since he was the newest employee at the company, he’d be the first victim of the resulting layoffs that he seemed so sure of. At that point he would be unemployed, and that wasn’t groovy because he needed to put food on the table for his wife and daughters. In other words, serving as a juror would be undue hardship. He made it clear that he had expected to be done with his jury duty by mid-week. The judge tried to be sympathetic but emphatically told him that jury duty typically lasted for one week and that the balance of regular work and civic duty as juror was something to which everyone was subject. Mr. Lawn Mower wasn’t going to get off that easy.

After hearing this rant, the cowboy from the previous day offered a better excuse for himself. He said that yesterday he had gotten word that his uncle had passed away and that the funeral would be held tomorrow, Thursday. I was floored when I heard this. He hadn’t said anything about this to the plaintiff yesterday, I thought. What’s going on here? The judge told him that she was sorry for his loss, and excused him. Later in the hallway, I asked a fellow juror if he believed the cowboy’s story. He laughed and said it seemed suspicious. It was the perfectly concocted story. The cowboy had chosen the death of an uncle — a family relation that’s not too far removed to be deemed trivial, but not too close to raise eyebrows. It was the perfect sweet spot; too sweet. What’s the judge to do, heartlessly demand a death certificate as proof?

Maybe the story was true; I’ll never know for sure. But the previous day, this very cowboy had been complaining about how he needed to get back to earning real money, and then, just like that, sometime after Tuesday at 3:30 PM, his uncle had conveniently given up the ghost, a story he offered only after it was clear from Mr. Lawn Mower’s unsuccessful rant that a work-related concern wasn’t a sufficiently undue hardship for which to be excused from jury.

And then there was the hispanic gentleman who stated he had been on the faulty end of a car collision and that as a result of this experience, it would be impossible for him to be fair in the trial, and that he was therefore unqualified to serve as a juror in the case. I’m still not sure if he was using this purely as an excuse to be done with jury for the week or if he was genuinely being that impossible about it. I’m leaning towards the former explanation. Like all of us at that point, he just wanted to be done. We all knew that this was the final case for the week, so being dismissed from this particular courtroom meant being dismissed from the courthouse. In response, the plaintiff attorney (the one suing for damages to the defendant for his client’s sustained ongoing injuries, allegedly from this long-ago car collision) requested that the judge dismiss the juror for cause. The judge glanced at the defense attorney, who motioned that he was in agreement. And that was the end of that juror. He picked up his things and walked out.

After the judge asked some questions and we took a 15-minute break, the attorneys began their questioning. The plaintiff attorney had the burden of proof, so he started first. One of the first things he did was address Mr. Lawn Mower with his southern drawl. “I’m really getting the feeling that you don’t want to be here,” he said as kindly as that sentence can be said. Mr. Lawn Mower sighed, “I think everybody here has that feeling.”4 “Are you able to give this case your full attention over the next couple of days, or is your mind constantly going to be drifting to your work?” “I think about my work all the time. Especially so when I’m at a place like… this.” The plaintiff attorney knew not spend one of his three strikes on someone who could be dismissed for cause. At the consent of the judge and the defense attorney, he relieved Mr. Lawn Mower of his juror duty, and the latter shuffled out, clearly grateful that someone had finally freed him from the courthouse cage, albeit grieved that it had taken this long. As he walked out, a sense of relief swept through the whole room. You could feel it.

After the attorneys finished their voir dire, we took a 15 minute break whilst they selected the final jury. In the hallway, I chatted with a fellow juror who was a bartender — the same aforementioned bartender — about what our chances were of getting selected. Both of us were hoping we wouldn’t get selected so we could go home and be done with it, but we wanted to go about it more classily than those three who had been dismissed. After all, it would be downright embarrassing to have to explain to your family and friends that you were dismissed for cause.

The bailiff summoned us back in the courtroom a few minutes shy of 5:00 PM, and the judge announced the jury. As she called out the names, neither Mr. Bartender nor I were selected. But there was one little problem when she was finished: there were only twelve people in the jury seats. The alternate juror had not been announced. An attorney pointed this out to the judge and they huddled around the desk, comparing notes to see who the 13th person was. Finally they figured it out, and alas, it was Mr. Bartender. He walked to his juror seat, sat down, and looked back at me, grinning sheepishly. I grinned back. He would have a very long Thursday and Friday. This was going to be a boring lawsuit for him; since it was a civil case and not a criminal one, only 9 out of the 12 jurors would have to come to an agreement, but because he was the alternate juror, he likely wouldn’t even get to contribute to that verdict.

All I knew was that it was 5:00 PM, I’d served 3 days as juror, been in two courtrooms for two cases, been selected for neither one, and it was time to go home. Those of us who had not been selected walked out of the courtroom and celebrated in the hallway that we had not been chosen. We were free.

I went in search of an elevator.

  1. Drinking Caffeine is a technology-focused column but I’m realizing that vivid, unique tales are also interesting and hard to pass up. Also, for full clarity, the trials in which I was made privy have been completed. Everything in this story is non-sensitive information. Still, you’ll notice I’m vague with the names of pretty much everybody in here. That’s attributable to the simple fact that I’m terrible with names. ↩︎
  2. I would later learn that at some earlier point, the court had been tasked to decide between either providing free parking or reimbursing for milage, and it’d opted for the latter. I also learned that mileage reimbursement is calculated automatically based on your documented address of residence. ↩︎
  3. If you’re summoned to jury any time during the next five years after a previous summons, you are excused. ↩︎
  4. His answer here was vague. I’m not sure if he meant, “Everyone here has the feeling that I don’t want to be here,” or, “Everyone here has the feeling that they themselves don’t want to be here.” At first I thought he meant the former, but upon reflection I think he meant the latter. ↩︎

What Good Is Your Mac Doing You?

When a user visits your web application, is it clear to them that you must’ve designed it on a Mac?

We say we use Macs because we deeply care about the user experience, but then all to often, we go and use those Macs to design web applications with UI / UX that could’ve been achieved using a Windows machine.

There are people out there right now building better websites using Windows than many with macOS. Developing on a Mac is not a guarantee that you’ll build something great. Your app’s polish is dependent first and foremost on your hand, not your paint brush. If you’re determined, achieving greatness apart from the Mac is within grasp.

Don’t hide behind the excuse that you can’t get your work done on anything other than macOS. Windows 10 has bash support, VSCode, Chrome, Slack, and iTunes. Most web developers don’t use any apps or software that are actually unique to the Mac.

If your Mac isn’t making a material difference in the quality of your output, you need to stop and ask yourself who you’re kidding. If your app is just as mediocre as everyone else’s, then you simply paid an awful lot of money for a pretty piece of aluminum. There’s one and only one person who’s benefiting from that aluminum.  

Nathan Kontny, CTO at Highrise, writing at Medium:

So I made Trick A Journalist as satire on how bad marketing has gotten. But I also made it for a much more important reason.

It’s a Bad Marketing Honeypot. The people signing up for this, and unfortunately there’s quite a few, are banned from using Highrise.

In Shoe Dog nomenclature, I imagine that Phil Knight’s dad would call building “jackassing around” on the web, but still, this is awesome.

Calculate Your Average Interval Pace and Total Interval Distance for a Strava Run

Lately I’ve been implementing Jack Daniels’ Running Formula step-count interval workout. The way it works is this: you run at interval pace for 20 steps, then you jog for 20 steps. Then you run at interval pace 40 steps, then you jog 40 steps. You keep doing this until you do 200 steps of each pace, then you run another 200 of each, and wind your way back down: 180 interval, 180 jog, 160 interval, 160 jog, so on and so forth until you arrive once again at 20 of each.1 This run comes out to be between 3 and 4 miles in total distance and about 2 miles of it is at interval pace. If you’re running 20-30 miles per week, this is a good amount of interval running for that week.2

Once your activity is uploaded to Strava, you’ll see a zipper pattern of a fast lap, a slow lap, a fast lap, a slow lap. Lurking amongst this data are two things that Strava doesn’t reveal out of the box: the total distance of your interval-pace laps, and this total distance’s average pace.3 Everything you need to compute these two data points exists; you just need a way to harvest it. This is where Strava’s API comes in.

First you’ll need an access token. To do this, create a Strava app4 and then head to this URL:[clientID]&response_type=code&redirect_uri=http://localhost&scope=view_private

Change out the [clientID] with your app’s client ID.

Once you’ve authorized your own app, you’ll get redirected to localhost, which unless you have a local server running will result in a “This site can’t be reached” Chrome page.5 That’s ok though, because your payload is the code parameter in the URL. Grab that and do a POST request to with these params:

  • client_id
  • client_secret

I like to handle POST requests like these using the Advanced REST client. If you’re using that for this, you’ll want to select multipart/form-data as your body content type in order to get this to work.

This POST will return a payload with your athlete information, but the piece you’re after is the access_token. Store this somewhere safe. You worked hard for it, it won’t expire, and you’ll reuse it later.

Now, get the ID of the activity you’re interested in. You’ll find the ID in the URL of the activity. E.g. has an ID of 1428717700. Now go here to get the payload of your activity:6[activityID]?access_token=[yourAccessToken]

Copy all the JSON from the response of this request, pop open your Chrome console, and write this:

const activity = [paste from clipboard]

Then run this in the console:

const totals = { distance: 0, seconds: 0 };
activity.laps.forEach(lap => {
  if (lap.pace_zone === 6) {
    totals.distance += lap.distance;
    totals.seconds += lap.elapsed_time;

totals.distance =  totals.distance * 0.000621371;


You now have, logged at the bottom of the console, the total number of miles and seconds of your interval pace. Head over to (or similar — there are a number of online calculators that achieve what that one does, so use your favorite) and plug in these numbers, and you’ll get your output. This is the process by which I can know that my run today contained a total of 2.15 miles at a 5:33/mi pace, even though the overall activity was 3.45 miles at a 7:46/mi pace.

Groovy, right?7

  1. I don’t keep track of my footfalls in my head, although you could do that if you had to. Instead, I manually lap each set of steps using my Garmin vívoactive 3, which conveniently lets you see your number of steps per lap and lets you manually create new laps. As an aside, it’s exactly this sort of functionality that Garmin offers and that Apple Watch doesn’t that explains why serious athletes still prefer Garmin. ↩︎
  2. Jack Daniels recommends that your weekly amount of interval distance be the lesser of 10K or 8% of your weekly mileage. This is right in that sweet spot. ↩︎
  3. These two data points are immensely useful, because they let you know how you’re performing compared to previous workouts. Your total distance and total average pace aren’t as helpful for this sort of workout because those are influenced heavily by how quickly or slowly you’re jogging in between interval laps. The question we’re trying to answer is this: how much quality interval time did we get? How fast were the intervals and how much distance did they cover? In my mind, the answers to those questions are some of the most valuable things we can know when analyzing a completed interval workout. ↩︎
  4. This app is for your eyes only, so name it whatever you want, and upload a picture of your dog as the app’s icon. As an aside, it’s bizarre to me that an icon is required when creating a Strava app. Why? ↩︎
  5. I assume you’re using Chrome for all this. 😛 ↩︎
  6. Strava access tokens are per-athlete which means you’ll only be able to do this on your own activities. ↩︎
  7. If you’re thinking, “This is quite a hassle and this entire process should be automated in software,” you’re on the right track. I eventually want to build this out similarly to how I built out, but it all takes time, and right now I’ve got too many other irons in the fire. So for now, computing this number takes some elbow grease. Our only consolation is that the coaches and runners who lived pre-Strava and pre-Garmin must’ve done an awful lot of number crunching by hand on yellow notepads. I don’t envy those days. ↩︎

The Stack Overflow Question That Led to Ross Ulbricht’s Arrest  

Moments after posting this question, Ross Ulbricht changed his email address on Stack Overflow from one in which his name was associated to the fictitious “[email protected]” Stack Overflow keeps track of all previous email addresses used, and when an IRS agent approached Stack Overflow with a subpoena, the company provided the original email address, thereby revealing the identity of the user.

Ross Ulbricht is in jail for life for masterminding the Silk Road website. This Stack Overflow post was a critical clue.1

  1. I highly recommend reading American Kingpin for the full story. ↩︎

Michael Lopp’s iPhone Home Screen  


Strava. Because smaller more connected [villages] are bringing us a healthier internet. I have a lot to say about Strava in a future article. I pay a subscription fee for Strava.

It warms my heart that the Vice President of engineering at Slack has Strava on his home screen.

Five Minutes and Fifteen Seconds

Yesterday I wrote a somewhat cryptic comment on a Strava activity headlined 40.87 miles ahead of pace, as of this millisecond:

If you want a fun math problem, you can find out when I changed the title of this post, to an accuracy range of 5 minutes and 15 seconds. The only data points you need to derive this number are (1) my precise distance up to this point (2) my yearly goal. The latter is publicly available on my profile, but I suppose to get the former you’d have to hack my Strava account, since OAuth tokens are per-athlete. So never mind. 🙃

What in the world am I talking about? Well, I have a one thousand mile running goal this year. When we do the math in a 365-day year, that comes out to about 2.73973 miles that we must average every day.

1000 / 365 = 2.7397260274

In my headline, I gave away the fact that I was 40.87 miles ahead of pace at the time that I modified the headline. That’s rounded to the hundredth place. How many times per day would this number change? We can easily find out. We need to run 2.73973 miles per day, and since we’re keeping track of this in hundredths, we multiply that by 100:

2.7397260274 * 100 = 273.9726

This resulting 273.9726 is how many times per day that 40.87 number will regress by a hundredth of a mile. To answer how many times per day this would change in seconds, we find out how many seconds are in a day:

60 * 60 * 24 = 86400

And then we divide that by the number of times our pace will regress:

86400 / 273.9726 = 315.3600

In other words, that number will regress every 315.36 seconds, or every 5 minutes and fifteen seconds. This is why I said in the Strava activity that we could get within “an accuracy range of 5 minutes and 15 seconds.”

With that explanation in the backdrop, we can get to the fun math problem of which I spoke. We need my precise distance year-to-date in order to be able to solve that problem, and I have that precise distance. It’s 262824.7 meters. Let’s figure out how many miles that is:

262824.7 * 0.000621371 = 163.3116466637

Next we need to find out what my distance for the year up to this point should be in order for me to be on pace. To do this, we take 163 miles and subtract from it the 40 miles:

163.3116466637 - 40.87 = 122.44

Now all we have to do is find out how many 315.3600-second intervals occur from January 1 to the time that we’d need to have run 122.44 miles, get the total number of seconds that occurred in those intervals, and we’ll have our answer.

First, we need to establish the pattern. For the first (315.3600 / 2) seconds of the year, we need to have run .004978437341451041 miles. Since we’re rounding to the hundredths, that comes out to be zero miles. For the 315.3600 seconds following, we need to have run .01 miles. For the next 315.3600 seconds, we need to have run .02 miles.

Here’s some JavaScript to demonstrate. First some core functions, which are the backbone of my distance web app:

const amountOfYearExpired = (time = new Date().getTime()) => {
  const startOfYear = new Date(`Jan 1, ${new Date(time).getFullYear()}`).getTime();
  const startOfNextYear = new Date(`Jan 1, ${new Date(time).getFullYear() + 1}`).getTime();
  const percentagePassed = (time - startOfYear) / (startOfNextYear - startOfYear);
  return percentagePassed;

const metersToMiles = (meters) => {
  return (meters * 0.000621371);

paceDelta = (currentDistance, goal, timestamp = new Date().getTime()) => {
  if (!goal) {
    return 0;
  const percentagePassed = amountOfYearExpired(timestamp);
  const currentTarget = goal * percentagePassed;
  // if this number is negative then we're behind pace
  const output = metersToMiles(currentDistance) - currentTarget;
  return output;

Next we use them like so to prove the established pattern above.

// Subtract 1 to get the value at the very last millisecond before
// it switches to the second half of the first interval. Multiply
// by 1000 to get the value in milliseconds.
const firstHalfOfFirstInterval = (315.3600 / 2) * 1000 - 1;
const date = new Date(new Date('January 1, 2018').getTime() + firstHalfOfFirstInterval);
const delta = Math.abs(paceDelta(0, 1000, date));

The above delta variable has a value of 0.004999968290208016. Rounded to hundredths, that’s 0. If we don’t subtract 1 from firstHalfOfFirstInterval, we get exactly .005. Rounded to hundredths, that’s .01. It stays at .01 for the next 315.3600 seconds:

Math.abs(paceDelta(0, 1000, new Date(new Date('January 1, 2018').getTime() + ((315.3600 / 2) * 1000 - 1) + 315.3600 * 1000)))
// outputs 0.014999968290208016

And then once we re-supply that one final millisecond, we arrive at .02 when rounding to the hundredths:

Math.abs(paceDelta(0, 1000, new Date(new Date('January 1, 2018').getTime() + ((315.3600 / 2) * 1000) + 315.3600 * 1000)))
// outputs 0.015000000000000001

With this established pattern, all we have to do is multiply 12,243 (122.44 miles times 100, since our interval duration is per one hundredth of a mile increments, and then subtract 1, since the full 12,244 will round up and yield our upper limit) by our interval duration, and then add a half an interval to that (because of the aforementioned rounding pattern), and we’ll have the start time of our range.

12,243.5 * 315.3600 = 3,861,110.16

To get the end time of our range, we add an interval of 315.3600 from the previous formula:

12,244.5 * 315.3600 = 3,861,425.52

Let’s multiply those by 1000 to change them to milliseconds, and make dates out of them. Notice we subtract 1 millisecond from the finish since otherwise it’s technically in the next range:

const jan1InUnix = new Date('January 1, 2018').getTime();
// Wed Feb 14 2018 16:31:50 GMT-0600 (CST)
const start = new Date(jan1InUnix + 3861110160);
// Wed Feb 14 2018 16:37:05 GMT-0600 (CST)
const finish = new Date(jan1InUnix + 3861425519);

We can check these numbers to make sure they’re in the right range and representative of that range’s extreme lower and upper limits, which indeed they are:

// outputs 122.435 which rounds to 122.44, the lower limit
Math.abs(paceDelta(0, 1000, jan1InUnix + 3861110160));
// outputs 122.4449999682902 which also rounds to 122.44, the upper limit
Math.abs(paceDelta(0, 1000, jan1InUnix + 3861425519));

When we look at the activity, the start time was around 3:25 PM CST with a moving time of 53:27 minutes. That puts the finish time in the 4:18 PM CST range. We can know with confidence that somewhere between 4:31:50 PM CST and 4:37:05 PM CST, about 14 to 19 minutes after I completed the run, I modified the headline. That’s an accuracy range of five minutes and fifteen seconds.

How “Complex” and “Confusing” Is the Modern Web Really?  

Frank Chimero:

That breaks my heart, because so much of my start on the web came from being able to see and easily make sense of any site I’d visit. I had view source, but each year that goes by, it becomes less and less helpful as a way to investigate other people’s work. Markup balloons in size and becomes illegible because computers are generating it without an eye for context. Styles become overly verbose and redundant to the point of confusion. Functionality gets obfuscated behind compressed Javascript.

I want to make a few points about this piece overall.

  1. First, HTML tables and spacer gifs are not simpler than Flexbox and CSS Grid. More importantly, tables are not mobile friendly. They should never ever be used in modern web development.
  2. If someone’s building with “overly verbose” stylesheets that override each other as they cascade, they’re doing it wrong. If it’s difficult to understand what’s going on in a DOM because there are too many nested DOM nodes, they’re doing it wrong. If you want an example of a modern web app that avoids these pitfalls, look no further than my distance tracking web app for athletes.
  3. This complaint that, “Functionality gets obfuscated behind compressed Javascript” is a real kicker. In a 2002 web era, what would be different here? Is Frank wishing that the JavaScript simply didn’t exist, and by extension, the functionality? Or is he wishing that it existed on the backend, where you couldn’t view it whatsoever? Or is he wishing that it remained as JavaScript, albeit not obfuscated (thereby murdering load performance)? If the litmus test for whether an app is great or not is by how easy it is to decipher the source code, then all iOS apps are by extension the devil incarnate.
  4. Just because somebody publishes a 90-page ebook doesn’t mean that font faces are as complicated as all that. It just means that they’re trying to sell an $8 ebook and realize it’d be a harder sell if it were only 15 pages. Granted, anything to do with web technology can get complex if you choose for it to. If you’re working for Facebook, maybe you need a 90-page ebook to decide exactly how you want to serve up your fonts. But for the little web design projects that it sounds like Frank is doing, he doesn’t need it.
  5. A web page in 2018 can be as simple as it was in the early 2000s if you choose for it to be. The only thing that’s changed is that in the intervening years we’ve built tools that let you make things more complex if you need for them to be. The early 2000s quite frankly didn’t have the sophisticated web apps that we have today, and arguably they could not because the tools did not exist. It does not take a mandatorily greater amount of work to achieve the same things on the web today than it did 15 years ago. Rather, there is a greater depth of complexity awaiting you if you need it. In other words, we didn’t go from A to B. We went from A to A and optionally B if you need it. That’s progress, not regress. Frank writes of his past days back when the web was ostensibly great, “Perhaps I was fascinated by the potential of bashing together something in my room, hitting a button, then having it be ‘out there.‘” I still do that all the time, in 2018, and it’s still great. Again.

(Via Nick Heer, who is still struggling to remember the syntax of Flexbox, which tells me he’s not writing much Flexbox.)

Track Your Yearly Running Goal With This Simple One-Screen Calculator  

In an effort to cut needless fat out of my software budget, I’m letting my Strava Premium expire this spring. $60 a year for Strava Premium doesn’t sound so bad; $600 over the next decade sounds like a lot.1 Moreover, Garmin Connect shows 100% of the in-depth data that Strava Premium does, and Garmin Connect is free.2 The only thing I knew I’d really miss about Strava Premium is the ability to track how far I’m ahead or behind pace in my yearly goal of running 1,000 miles. So this morning I threw together this mobile-friendly HTML page that computes this for you. It’s localStorage cached, and therefore persistent yet anonymous. I’m 124 miles into my yearly goal, which puts me 26.54 miles ahead of pace as of this sentence.3

Usually the subscribers to a service like Strava Premium do it because they love the service and want to support it. What I’ve learned is that those people will always be around, and that there’s no shame in the other 97% of us using the software for free.

I love Strava greatly; I love reducing rows on my credit card statements even more. 💸

Update: The HTML page is now here. I’m now frying bigger fish at the previous URL. More on that later. Maybe.

  1. And with inflation and whatnot, who thinks Strava Premium or any other premium service for that matter will not be higher in ten years than it is today? ↩︎
  2. You have to own a Garmin device to use Garmin Connect, so it’s free in the same sense that Apple Messages is free. ↩︎
  3. I think Strava recalculates your ahead-or-behind amount every 24 hours or when you upload a new activity, whichever comes sooner. My “app” computes it based on the exact timestamp of page load, accurate to the millisecond. A 1,000-miles-per-year goal means you need to average roughly 2.7 miles every day. If your calculator refreshes its output every 24 hours, when does that refresh occur? At midnight? If so, what timezone? Regardless of when it is, your ahead-or-behind amount stays constant all day long and then jumps backwards 2.7 miles all at once every 24 hours. Nobody wants that. That number should be completely smooth in its regression, and lurch forward when you complete a new run. ↩︎

Let’s Bury the Hustle  

This is one of my all-time favorite pieces by DHH. It’s brutally savage and rings true.1 His 5-step process is an essential guide for how to nurture professional creativity.

  1. My only complaint about DHH’s writing style is his disrespectful and borderline obnoxious crassness. Using four-letter words to get a point across is a lazy copout to serious prose, and it achieves no service to the reader. ↩︎

Fewer Podcasts, More Audio Books

Listening to the same podcast feeds to glean new information is like squeezing the same grapefruits over and over again. Unless they’re usually high quality, well-researched shows, once you’ve heard a half-dozen episodes of a podcast, you’ve heard most of what their hosts have to say. After that, they’re just re-voicing their opinions, offering variations on well-worn themes.1 You’ll continue to get a drip here and a drop there, but what’s the point?

Stop listening to the same people say the same things, and renew your mind with fresh books.2

  1. Podcasts are the Hipsterville version of radio talk shows. They’re more sophisticated but they contain the same inescapable problem of repeating themselves over and over and over and… ↩︎
  2. The best place to do that is Audible. Contrary to misconception, Audible can be a pay-per-month service, but it isn’t a typical one like Zwift or Netflix in which you’re exchanging dollars for zero value during a month that you don’t use the service. Rather, if you don’t use Audible for a given month, your credits that you’ve accrued will rollover to the next month. Moreover, you’re not renting audiobooks from Audible; you’re buying them. You keep them for life; you have perpetual access to them. In short, the value that you get for $14.99 per month is equivalent to adding new books to your bookshelf on a monthly basis, except you’re adding them in an accessible form that guarantees you’ll actually get good out of them. That’s more than I can say for the scores of books, for which I paid good money, that are sitting on my shelves in various stages of neglect. ↩︎

Why We Forget the Books We Read  

Julie Beck, writing for The Atlantic:1

The lesson from his binge-watching study is that if you want to remember the things you watch and read, space them out. I used to get irritated in school when an English-class syllabus would have us read only three chapters a week, but there was a good reason for that. Memories get reinforced the more you recall them, Horvath says. If you read a book all in one stretch—on an airplane, say—you’re just holding the story in your working memory that whole time. “You’re never actually reaccessing it,” he says.

If you really want to recall a book, read it over an extended period of time. A few pages per day is better than fifty pages per weekend. Counterintuitively, the longer it takes you to read a book, the better retention you’ll have of it.

(Via Bob Ryskamp.)

  1. The URL of this Atlantic piece is delightful. ↩︎

How to Change the Default Background Color of a Stickies Note on macOS  

The intuitive way would be for Stickies to have a Preferences window where you specify your defaults. Stickies prides itself on being a nimble app however, so it doesn’t have a Preferences window. Instead, you select a sticky that you’ve customized to your heart’s content, go to Window, and select “Use as Default.”

I’ve been using a Mac for more than a decade and I never knew this until today. I’m delighted to have finally changed my default Stickies background from yellow to blue.

Persistent Local Overrides Delayed Until Chrome 65  

From Kayce Basques’ release notes for Chrome 64:1

Whoops! We originally scheduled this feature to launch in Chrome 64, but pulled it close to the deadline in order to smooth out some rough edges.

The ability for local overrides to persist between pages loads is going to be nothing short of amazing, especially for projects that have Hot Module Reloading. It is often the case that I will locally override the CSS for a page, get it looking exactly like I want it, then head over to my editor to make the changes for real. I’ll make the first change and then without thinking save the file. HMR internally refreshes the page, and I lose my other changes. I then have to continue making the rest of the changes in the editor from memory. Persistent local overrides solves that problem.

  1. There’s something the Chrome team doesn’t mention in its Chrome 64 update that’s worth mentioning: the San Francisco font looks different, in a good way. It’s now much closer to how the font appears in Safari. If you prefer doing your web reading in Safari because of its superior typography, that reason is going away. ↩︎

Why Disney Owns  

There’s a scene in Pixar’s Inside Out where Riley is determined to leave her parents and return to Minnesota. She gets out her laptop, goes to the web address for the fictitious company Transway Bus Lines, and books a ticket. The URL of the old-Safari-esque browser on the Chromebook-esque laptop is this:

It’s a lousy, and thereby realistic, URL. It lacks SSL and everything past the TLD contains unhelpful information. I was curious to see if this URL actually worked or not, so I went to The page doesn’t have any DNS records that it can resolve to, but a WHOIS lookup shows that Disney, the distributor for the movie, purchased the domain in 2014, one year before the film’s release.1

By owning this domain, Pixar and Disney ensured that they were in full control of the user experience of this fictitious brand. Very few people are going to pause the movie and capture this address bar — the laptop screen appears for a fleeting second — but the companies wanted all the bases covered.

This kind of attention to detail is so great.

  1. It’ll be interesting to see if the company renews this domain in 2019 when its 5-year purchase expires. ↩︎

What Browsers Do With the Extra Pixel When Centering Items Within a Container  

Have you ever wondered what happens with a container with a width of 10 pixels and a div inside of it that has a width of 3 pixels and a margin of 0 auto? It sounds like a contrived example, but these sorts of dilemmas are common in a world of mobile-fluid flexbox layouts. This morning I got down to business and figured out what happens with “extra” pixels. The tl;dr is this: in a horziontal situation, the left side of the aligned item gets the extra pixel. In a vertical situation, the top side gets the extra pixel. This feels consistent with CSS’s preference for top-left in things like border-radius.

As I mention in the gist, this discussion is irrelavent on retina screens; since a CSS 1px equals two actual pixels on a retina screen, a retina screen can render any centered layout in perfect symmetry.

How the Top Rated London Restaurant on TripAdvisor Was Fabricated  

Eli Rosenberg, writing at the Washington Post:

It was a unique restaurant in London and certainly the hardest to get into. And it beat out thousands of upscale restaurants in the city to earn the top ranking on the popular review site TripAdvisor for a time, drawing a flood of interest.

There was just one small problem: It didn’t exist.

This entire piece is fascinating and I recommend reading it in its entirety.

To put this story into a broader context though, I’ll tell a story. I’m listening to an audio recording of Shoe Dog whilst running and my mind’s been blown by how easy it was to fabricate businesses and locations in the 1960s. Oregonian Phil Knight, who would later become the founder of Nike, flew to Japan in his mid twenties and told shoe company Onitsuka Tiger that he wanted to be a distributor of its Tiger shoe in the Pacific Northwest. When asked what his company name was, he replied, “Blue Ribbon.” Blue Ribbon was a nonexistent entity that Phil made up on the spot. This was pre-internet; the world was a big place, and there wasn’t a way for Onitsuka to verify whether such a legal entity existed or not. Onitsuka agreed to sell Phil’s postulated company a thousand Tiger shoes at wholesale, whilst Phil flew back to Oregon and scrambled to create the company Blue Ribbon. Later, when Onitsuka was concerned that Blue Ribbon wasn’t a big enough U.S. distributor compared to Blue Ribbon’s competitors, Phil countered this by saying that Blue Ribbon was represented on both coasts, which wasn’t true. Skeptical, Onitsuka said it would ship Phil’s newest shoe order to its east coast address, to which Phil replied that he would wire the exact address shortly. He promptly had one of his employees go and create a Blue Ribbon store in Boston, then he wired Onitsuka the address.

In the internet era, Phil Knight’s fabrication would be harder to pull off; but Oobah Butler’s fabrication was only possible because of the internet.

You can’t trust everything you encounter on the internet, but that law applies just as much offline.

How Facebook Kills Personal Development  

DHH, writing at Signal v. Noise:

What allowed me to change and prosper was the freedom to grow apart and lose touch with people. It’s hard to change yourself if you’re stuck in the same social orbit. There’s a gravitational force that pulls you into repeating the same circular pattern over and over again. Breaking out of that takes tremendous force.

Whilst was reading this, something struck me. If you view Facebook’s primary problem as one of fake news and insular group think, then you’ll be in favor of people having as many kinds of friends as possible, retaining every connection they meet in real life. You’ll view this statement as a good thing:1

Knowing that everything you share will be seen by all these people from your past quietly. moderates what you actually share.

To DHH, this kind of moderation is a bad thing. It stifles growth.

This dilemma of how many connections to retain on Facebook is simple: don’t use Facebook.

  1. There’s a lot of typos in this piece. DHH’s a busy man. ↩︎

Burger King Explains Net Neutrality  

ISPs deliberately slowing down lanes of traffic might occur in a future of no government regulation. But saying that this sort of thing used to go on and that we’re returning to this because of the repeal of so-called net neutrality is blatantly misleading. Everyone in that video is getting furious about a hypothetical that has never occurred. If you’re the kind of person who thinks the government should put laws in place to protect hypothetical scenarios that are unlikely to occur based on historic data, you’ll be for net neutrality. Otherwise you won’t be.

Update: what would Burger King have to change about their ad in order to make it an historically accurate analogy to ISP favoritism? They’d have to make it so that all the customers get their burgers in the normal timeframe, except for a few people who get their burgers faster than usual. Think of it as the dozen passengers who have priority boarding and who fly first class on an airplane. They’re special, everyone knows it, and everyone’s cool with it.1 Being in favor of net neutrality is roughly equivalent to being against priority boarding on an airplane.

  1. Well, almost everyone. I’m sure you could find some stage 4 liberals who pontificate the “inequality” of riding first class and how world poverty could be ended once and for all if they simply took the extra cost and gave it to those in greatest need, but you get my drift. ↩︎

You Needn’t Learn English to Learn to Code  

Nick Heer:

It’s not enough to know what words to put where; good programmers understand the specific use of these words, and that requires an understanding of the grammatical syntax of English.

It’s laughable to think that you’d have to have a deep understanding of English to know how to program. There are millions of software developers who have limited English skills. You don’t have to be a grammar whiz to understand what if, when, case, while, and let mean. You only have to understand what they do in the context of software. I’ve never heard a developer who was a non-native English speaker complain about this, and I’ve worked with and corresponded with a lot of them. If you’re incapable of learning the meaning of 10 or so English words, you’re not cut out to be a software developer.

Moreover, getting a language to support an indeterminate number of keywords in the name of i18n would be a nightmare to maintain. Microsoft Office tried this for a while by shipping with a partially translated VBA. It was a disaster. It’s never going to happen for Swift.

Stack Overflow Analyzes Remote Work from Its 2017 Developer Survey Results  

Julia Silge:

There is a big difference in the overall experience levels of remote and non-remote developers. In most countries, developers who work remotely have more years of professional coding experience than those who work in their companies’ offices. This is a large effect, a difference of 5 years of median experience in the United States and Canada.

The data crunching that Stack Overflow is doing with its survey results is hugely insightful.