Ten years ago today, I wrote this post.
As many of my posts do, it used many words to say one simple thing:
In the end, all I can say is that I’m so deeply, deeply lucky to have found you, Erin.
Twenty years ago today, Erin and I decided to become boyfriend and girlfriend. I would tell you that I asked her; she would tell you she asked me. Given my horrible memory, she’s surely right, but I kinda like that a definitive answer is lost to time. I’ve retconned it to be that we just made the decision together.
Together. Which we’ve been — as of today — for twenty years.
Erin said to me recently that people are often a part of a season of your life. A great example of this is coworkers; you may be close while you work together, but once one of you leaves, and you’re no longer speaking every weekday, friendship often tends to leave as well.
It’s a different season.
To make it twenty years with anyone — even family — is an immense accomplishment. It requires choosing each other often. In a romantic relationship, it requires choosing each other every time. Even if you don’t particularly want to, at that particular moment. When it’s easy, but also when it’s hard. Especially when it is hard.
What’s been so great about spending twenty years with Erin is that it is very rarely hard. Well, for me anyway. 🫣
Our twenty year wedding anniversary isn’t until a couple summers from now. To many couples, that is the anniversary that matters most. To me, while our wedding was immensely important, it was a formal codification of something we already knew. Something we had known for two and a half years: we are each other’s person.
We’ve known that since 2005. Since the sixteenth of January, to be exact.
Here’s to twenty years, Erin. To merging two lives into one. To bringing two all-new lives into the world. To trying, every day. To the [overwhelming majority of] times where it doesn’t feel like trying at all. To choosing each other… every day.
I love you. Happy anniversary. 🥰
For years, my iPhone’s home screens looked about like this:
The first screen was the one I used most times. It featured a Carrot Weather widget at the top, four rows of icons, and the dock. Maybe ⅔ of the time I used my phone, I was able to do so using this screen. I worked very hard to curate this screen.
The second screen was 80% or so of the remaining ⅓ of the time I used my phone. It was mostly curated — I didn’t worry about it quite as much as I did the first screen, but I did definitely rearrange things from time to time.
The third screen was a junk drawer, and it looked like it.
Over time, I got really sick of this. Muscle memory let me get to most apps on the second and third screens quickly, but it just felt… gross. There must be a better way.
I’m pretty sure it was either in chatting with Myke, or listening to one of his shows, that I heard him discuss a different approach. Though he’s not the only one to espouse working with your iPhone this way. I had long found this technique to be… well… lunacy. But around fall of last year, the idea of it started to really pull at me.
That approach — that new home screen thought technology — was Spotlight, and the App Library.
Myke discussed how he often uses Spotlight to get to his apps. Said differently, he searches for them instead of swiping around and tapping on the icon on his home screen. I knew of others who did the same thing, and it always seemed so inefficient and bonkers to me.
But the idea kept eating at me.
The more I thought about it, the more I felt like most of the time I used my iPhone really was concentrated on a handful of apps. The rest of the time could be any of the literally of hundreds of other apps I had on my phone. With my then-current approach of every-app-must-live-on-a-home-screen, I was optimizing a ton for apps I rarely needed.
So I ran an experiment.
I decided to rejigger my home screens and take a wildly new [to me] approach.
Here’s what I came up with:
The first home screen looks pretty much the same. There are some minor changes — the images of the old home screens are actually from late summer 2022. However, in broad strokes, it’s the same.
The second home screen is wildly different. There are three slots for widgets:
- The top slot is a stack for photos:
- Widgetsmith’s
On This Day
- Photos’
Featured
photos widget
- Widgetsmith’s
- The next slot are for status/monitoring:
- Fantastical’s
Event List + Calendar
widget - Parcel’s
Your Deliveries
widget
- Fantastical’s
- The third, half-width slot has Foodnoms’
Goals Summary
widget.
Also on the second screen are four frequently used apps that couldn’t quite make it to my first screen:
- Pushover, which is an incredible tool for sending yourself push notifications
- Sports Alerts, which is a no-fuss sports scores app with great Live Activities
- DS Cam (App Store link), which allows me to see my home security cameras
- Volvo, which allows me to precondition the climate in Erin’s XC90
There is no third home screen.
The key to this approach is to embrace Spotlight and — to a lesser extent — the App Library.
I didn’t think I’d be able to last this way. I never cared for Spotlight, and almost never opened the App Library. I generally found them both burdensome.
After a couple of days, I was entirely adjusted. I’m here to tell you that if I can make it work for me, you can make it work for you, too.
I started with this as an experiment last fall, I’ve come to absolutely love this approach. I’ve optimized my home screens for 80% usage; about 80% of the time, I’m opening one of the apps on one of these two home screens. For the rest of the time, I have Spotlight and the App Library.
Not only has this sped up opening the app I want, it’s also cut down on the number of badges I see. While I know I could control that in Settings, it’s a far easier solution to make them just… disappear.
This new home screen thought technology took a long time to really sink in, but once I embraced it, I almost immediately fell in love.
For more on my rationale and journey, you can hear Myke and I also discuss this on Analog(ue) episode #230.
I had been describing myself as disgusted by this result, but I eventually realized that was incorrect. What I actually am is disillusioned. America was a dream, and now that dream is gone from me. Holding on to beliefs like “good triumphs over evil” and “justice will be served” has always required taking a long view. But now, when a convicted felon has yet again managed to evade consequences and scam his way into the presidency, I find my faith in my country shattered. It lies in pieces at my feet and I am unsure what I’m going to do about that.
Callsheet — my app that’s like IMDB but for people with taste — uses iCloud for several things, notably, storing your “pinned items”. Internally, I call pinned items “favorites”, so in this post I’ll refer to them interchangably. Regardless, today, there’s only one list of pinned items.
My most-requested feature is to add support for multiple lists. Generally, so people can have things like To Watch, Watching, and Did Watch. Naturally, your particular needs may differ, but this request is common… and constant.
Due mostly to life obligations (all is well), but also my fear of how much work this will be, I’ve been kicking this can down the road for a while now. (See also: compliance to Swift 6’s strict concurrency). This week, I’ve started to really dig in.
Today I hit a wall, and I’m too tired and exasperated to do a long and involved write-up with pictures and stuff. The short-short version is:
Make sure you add indexes before adding data that you need to query that relies on those indexes; if you don’t, they may not work as expected.
When tackling multiple lists of pins, the first thing I did was to add a
new record type (“table”, sorta-kinda) called FavoriteList
, which is peer
to the pre-existing Favorite
. Then I added a new field (“column”,
sorta-kinda) to Favorite
that contains a CKRecord.Reference
to the favorite/pin’s parent list.
Once I got the schema updated in development, I started writing code. Thanks
to some genuinely (and uncharacteristically for Apple) helpful sample code,
I was able to put something together quickly. However, it didn’t work. Thanks
to some genuinely (and uncharacteristically for Apple) helpful error messaging,
I quickly realized I needed to add an index to Favorite
; specifically for
this new reference field I added.
I added the compulsory indexes, and then went back to writing code.
Quickly, I was flummoxed; things still weren’t working. Now, for a different
reason: in trying to get favorites that are part of a given list, I was coming
up short. No matter what I tried, no records were being returned. When I looked
at the records in the iCloud Developer Dashboard, it appeared everything was
good. I could click from the field in Favorite
and it would load the list
in FavoriteList
. But whenever I tried to query using my code, it wouldn’t
work. I would come up with no records.
In chatting with some incredibly kind folks on Slack, one of them asked me to try to replicate the query I was doing in code on the Developer Dashboard. I got the same empty results. This was a critical tip, because it led me to believe that my own code was not — for once — the issue.
After a couple hours of this, and in desperation, I deleted all my existing
Favorite
and added new ones. I tried my queries again, and they worked no
sweat. Both on the dashboard and in my code.
Thinking about this some more, and in chatting with the good samaritans on Slack, we all came to the same conclusion: the index probably didn’t properly… uh… index… whatever data was already there before the index was created.
So, if you’re ever in a situation where you’re getting wonky results (or no results at all) from a new field in CloudKit, consider whether the data you’ve added was done so before or after you added the corresponding index.
Apple folks, FB15563372.
Since we last spoke about Tailscale, my post was linked by Tailscale themselves, and I’m pleased to report Tailscale has sponsored one of my podcasts a couple times.
With that in mind, I want to make it clear that they do not know I’m writing this post, and have not requested anything of me with regard to my website.
I just cleanly solved a weird problem that’s been nagging me for a very long time, and thanks to Tailscale, I was able to do so quite easily. I thought it was a great case study in what makes Tailscale so amazing.
The Issue
My parents have a network attached storage that they use for, among other things, recording security cameras locally. As such, they will occasionally access it remotely, and thus having a SSL certificate is pretty much required these days.
Thankfully, Synology makes it easy to request a Let’s Encrypt certificate. However, to do so requires you to have the router forward ports 80 and 443 to the Synology. Generally speaking, I do not want those ports forwarded anywhere, so I only turn on those forwards when renewal time comes around.
Messing about with their in-home router is not easily accomplished from my house, 45 minutes away from theirs. Furthermore, Let’s Encrypt certificates have to be refreshed every 90 days, so this dance happens quarterly.
The Complication
My predicament is complicated by my parents’ oddball ISP-issued router refusing to respond to network requests coming from outside the network. Even over a traditional VPN hosted in their house, the router would not respond to requests from my house. This very well could have been user error, but that was what I experienced.
I needed a way to have packets from my computer appear to be originating on my parents’ network.
Solution #1
My first solution to this was to host a docker container that basically exposed Firefox via the web. Yo dogg, I heard you like web browsers in your web browsers. So, the process was:
- Get on the VPN hosted at my folks’
- Get on the Firefox docker image via
http://192.168.1.x
- Make the port forwarding changes to their router
- Request a certificate renewal on their Synology
- Undo the port forwarding changes
This worked just fine, but it was… involved.
Enter Tailscale
Tailscale is many many different features, all rolled into one product. A core tenet of Tailscale is that you should be able to migrate to Tailscale incrementally; you shouldn’t have to go all-in from the get-go. Tailscale has some features that help facilitate incremental adoption.
One of these features is Subnet routers; they are summarized quite well in the Tailscale documentation:
In cases [where you can’t install Tailscale on every device], you can set up a subnet router to access these devices from your Tailscale network (known as a tailnet). Subnet routers act as a gateway, relaying traffic from your tailnet to a physical subnet.
In short, if you have a subnet router, you can route
local network → tailnet → subnet router → remote network
So, in principle, I could enter my parents’ router’s IP address into my browser, and it will load.
A New Complication
The way subnet routers typically work is that they effectively bridge two networks
together. My parents’ network is 192.168.1.x
; mine is 192.168.17.x
. On the
surface, this seems fine, but so many networks are 192.168.1.x
. I suspect
there will come a time one of my portable devices is on a 192.168.1.x
network,
and I may want to reach local devices on that network. By default, a basic
subnet router will intercept all requests to 192.168.1.x
and try to serve them
via my parents’ network. That could easily make things wonky, and lead to
my devices not being able to reach local peers.
I’d like my parents’ network to stay… at my parents’ house. I’d just like to be able to peek into it for the purposes of tweaking their router’s settings every now and again.
A New Solution
Tailscale has another trick up its sleeve that I realized would be a perfect fit for this scenario. Tailscale offers “4via6” subnet routers. It occurred to me recently that this is the fix I’ve been looking for.
Tailscale’s problem statement is this:
In a large network, you may have existing subnets with overlapping IPv4 addresses. If there are two entirely separate virtual private clouds (VPCs) using the identical set of IPs […]
That’s the scenario I’m worried about: I’m on a 192.168.1.x
network, but my
parents have already “claimed” the 192.168.1.x
address space.
4via6 routers solve this by:
The 4via6 (“4 via 6”) subnet router feature provides an unambiguous, unique IPv6 address for each overlapping subnet, so a Tailscale node’s traffic is routed to the correct device.
In short, I told the subnet router at my parents’ “I’d like you to expose this [otherwise internal] network on the tailnet only as IPv6”. That means all the IPv4 addresses in my parents’ house are exposed using a special IPv6 address:
fd7a:115c:a1e0:b1a:0:XXXX:YYYY:YYYY
Where:
fd7a:115c:a1e0:b1a:0
= a special Tailscale prefix to indicate 4via6XXXX
= the identifier for the target subnetYYYY
= the IPv4 address represented in hex
So, if I’ve designated my parents’ house as 123
(which is 7b
in hex), then
192.168.1.1
would be:
fd7a:115c:a1e0:b1a:0:7b:c0a8:101
Progress! However, an IPv6 address is not particularly memorable, which kinda stinks.
MagicDNS
Tailscale also offers “MagicDNS”, which does many things, but it will also convert/resolve specially formed hostnames into the appropriate IPv6 addresses.
So, if my tailnet name is smiley-tiger.ts.net
, then I can open my parents’
router’s configuration page by entering this URL into a browser on my computer:
http://192-168-1-1-via-123.smiley-tiger.ts.net/
That will automatically get resolved by MagicDNS to
http://fd7a:115c:a1e0:b1a:0:7b:c0a8:101
…which in turn lets me log into my parents’ router remotely, anytime, without any fiddling nor Docker containers required.
I can do this no matter what network my computer is on, as long as my subnet router at my parents’ is also on. Tailscale connects everything together.
It’s ✨ Magic ✨
This is what makes Tailscale so great — once you can connect your devices together, it opens a world of possibilities. Those possibilities easily extend to the devices on your tailnet, but they can extend to devices beyond your tailnet, with just a little bit of work.
Seriously, Tailscale is so great; you really should try it.
At some point, I should probably look into automating some (all?) of this certificate renewal process, but for right now, I’m happy to bask in the work being far quicker and easier than it’s ever been before.
Just under a week ago, on this past Friday, I played a part in raising over $130,000 for St. Jude Children’s Research Hospital.
On the nearly 12-hour livestream, we participated in a series of ridiculous
hijinks. We smashed a ridiculous “gaming” PC. We played Jenga. We ran two Relay
relay races. I sat in on an episode of Robot or Not. A champion
was crowned belted for one of our co-founders. Fun was had.
But more than anything else, money was raised.
I’ve discussed St. Jude before here on my blog, and everything I said then remains true today. St. Jude is a collection of amazing humans, whose singular goal is beyond reproach:
No child should die in the dawn of life.
Compared to the awe-inspiring work that the St. Jude staff does, our intense but short-lived twelve hour marathon was… well… nothing. But it makes my heart incredibly happy to try to raise money for such a worthwhile organization.
During my time at St. Jude last week, I got to sit in on a presentation by three St. Jude doctors, each discussing some of the work that they’ve done at St. Jude. Some of the quotes from these doctors are really incredible:
I wasn’t happy because [a six-year treatment] took too long
We’re not happy with a 75% success rate
We’re curing kids at a price; [how can we make treatment gentler]?
We’ve proved [a revolutionary new treatment] can work; now we need to make it work
We’re [working to] end the diagnostic odyssey
It’s not just the clinicians and researchers at St. Jude making a difference, though. There was an entire army of ALSAC employees working tirelessly behind the scenes to make the Podcastathon run without a hitch. To everyone we worked with, but especially Jill, Jolie, and Ricky, your efforts are noticed, and they’re appreciated. 💙
I’m so honored I was asked to participate this year, and I’m doubly pleased that we figured out a way to rope Erin into the festivities. 🥰 It is a rare privilege to be able to do something that is so noble, and also have fun doing it. It’s a double bonus to be able to do all this with some of your closest friends.
As I write this, it’s still September, which means Relay’s campaign is still live. If you have even a couple dollars extra, I strongly encourage you to throw them St. Jude’s way.
Let’s cure childhood cancer… together.
Over the summer this year, developer Nolen Royalty made a website that was One Million Checkboxes. The idea was that there were a million checkboxes on the page, and if someeone changed the state of a box, it was changed for everyone.
The site got picked up pretty quickly on several mainstream media outlets, which led to an interesting exercise in urgent optimization. However, the thing that really made me smile — a lot — was a story about a bunch of intrepid teens.
You can read the story, or spend a little under ten minutes to watch Nolen tell it. Either way, I don’t want to spoil it, because it’s such a great journey.
In the last month or so, I had the pleasure of guesting on two different podcasts.
First, on 7 August, I filled in for my good friend Lex Friedman on his daily podcast, Your Daily Lex, episode #780. It was an absolute pleasure making fun of his crappy football team on an episode his own podcast. Sorry, not sorry. Go Giants!
Today, I joined Dan Moren, Mikah Sargent, and Karrisa Bell on Clockwise. On this episode, we discussed how we purchase/read ebooks, what pet-related technology we use, our favorite iPhone accessibility features, and what tech features we’d implement/fix if we had the magic to do so.
Callsheet launched just shy of a year ago, which means that annual subscribers are going to be getting renewal emails soon, if not already. Callsheet has exceeded my wildest dreams — it is a multi-award winning app, having collected both an Upgradie and an App Store Editor’s Choice award. The feedback from users has been extraordinary, and I’m so thankful for the praise it’s received. 🥹
Over the last year, I’ve added a ton of new features, and made a bunch of improvements. Some favorites include Plex and Channels integration, native visionOS support, search within lists, as well as quietly launching “cast union search” — the ability to see what movies two actors both appeared in.
So, when the Apple renewal email rolls in, I’d really love it if you’d promptly ignore it, and let your subscription continue. 😏 Callsheet has been a highlight of my career, and I have vast plans for year two — starting with vast improvements to the pinning system. 🫣
For the record, here’s what your $9, or local equivalent, has gotten you this year:
2024.2
- Support season-level cast for ensemble shows like Fargo, True Detective, etc. You can see this in an episode’s cast list.
- Attempt to prevent spoilers from showing while data/images are loaded
- Account for spoiler settings in “You may know them from” section
- Add shortcut in search to load by TMDB ID: “tmdb[m|s|p]:#”. For example, “tmdbm:1669” will jump you directly to The Hunt for Red October. Use ‘s’ for shows and ‘p’ for people
- Fix an issue (that most of you probably didn’t see) wherein character names were always hidden on episode cast lists
- Fix a rare issue where a TV show had a Specials season, but it wasn’t being displayed
- Fix issue where using the search URL scheme wouldn’t work when the app is not already in memory (It would previously ignore your search if the app wasn’t backgrounded).
- Fix icon selector not showing the correct state in some circumstances
- Fix hang in “More Purchase Options” screen when reached via Search History
- Fix issue where releases without associated ratings were being ignored, leading to incorrect release dates
- Add debug logging around language/region overrides and iCloud status
- Update to latest version of TelemetryDeck package
- Use native SwiftUI review requests rather than dropping down to UIKit
2024.3
- Fix broken segmented control on TV episode view when using larger font sizes
- Fix episode counts not being summed for actors that have multiple roles on the same show
- Fix ages being shown in movie/episode details even if “Show Ages” was disabled in Persnickety Preferences
2024.4
- You can now pull-down-to-search on movies, TV shows, and TV episodes! This is far and away the most frequent request I get. Now it’s there!
- When you share a person/movie/show/season/episode, you’ll now be presented with a menu, offering to share a web link, or a Callsheet link. The latter is super useful for sending to other Callsheet users.
2024.5
- The same pull-down-to-search on movies from TV shows, and TV episodes in the last version is now added to people as well!
- Fixes an issue where up/down chevrons for TV episodes weren’t refreshing the cast/crew lists
- When you do a pull-down-to-search, the app will scroll to the cast list automatically; this is particularly useful for regular-sized phones
- Fixes an issue where the Cast/Crew label was tappable if a person has only cast or only crew credits
- Pins will now be hidden if you don’t have any AND you’ve searched 10+ times, or, now, if it’s been at least a week since you’ve purchased
2024.6
- Fixes an issue with the layout/behavior or spoiler settings
2024.7
- Add heights to actor screen, where possible. Not every actor will have their height listed, but many will.
- The units are specified by your phone’s settings. Within the Settings app:
General
→Language & Region
→Measurement System
- However, to see the alternative unit, simply tap the actor’s height. If the height shown is in feet/inches, the popover will show centimeters, and vice/versa.
2024.8
- Now available on visionOS! If you’re subscribed to this version, you’ll automatically be subscribed on visionOS once you download the app there. (Note that in visionOS the icon is different!)
- Now requires iOS 17.0
- Attempt to show buttons for all quick access links rather than one and the More… button. This is most visible on People and on everything on iPadOS
2024.9
- Slightly change the default icon. The original is available in the in-app
Settings
→Persnickety Preferences
→App Icon
. - Hide search box when an image is zoomed in
- Some refinements to the TV season carousel: when a show has only one season, the larger button is used — the same as multiple-season shows; when a show has more than 10 seasons, the dates of those seasons are now shown.
- When you scroll laterally in the main/Discover screen, and then drill into a movie/show, and then return to Discover, it should remember[-ish] your scroll position.
- When you are in an episode view and switch between Guests/Cast/Crew, and then drill into a person, and then return to the episode, your prior G/C/C selection is no longer ignored
- Tapping an item in Search History will move it to the top of the list.
- Fix edge case where, if an actor was in a show and movie with the same ID number, only one would show on their filmography. In this case, Bruce Willis, Die Hard, and The Ellen Degeneres Show
- Fix some small layout issues, mostly in iPad
2024.10
- Movie/show titles will show in the language you specify in Language Override, or your device’s native language, whenever possible.
- To see titles in a movie/show’s original language, tap the title when looking at the details of that item.
- If you tap on a movie or TV show’s rating, a popover will show the genres for that movie/show
- Small accessibility improvement for TV ratings
- Performance improvements for movies & shows
- Add ugly in-app log viewer; you can see it by opening callsheet://logs in Safari
- Fix presentation for instances where a popover element on visionOS is used but there isn’t actually a popover. Say, a movie or show that has ratings but no genres.
2024.11
- You can now search for films that two people worked on together. Tap the “Find shared film credits” on a person’s details. Note this only works for films and not shows.
- Add Pride & Trans icon sets (see Persnickety Preferences)
- Loading people was slow due to the request that tries to load a person’s height. That’s now been separated out, so the view should load far faster.
- Rejigger purchase detection in order to add lots more logging and also be more forgiving.
- Add context menus for zoomed-in posters & profile photos
- Fix issue where some bad data coming back from a search result would cause all results to be ignored
- Fix issue with TV episode titles not updating when using the up/down chevrons
2024.12
- Localize for Dutch, French, German, Hebrew, Italian, Norwegian, Polish, Portuguese, Russian, Spanish, Swedish, Ukrainian
- New dark-themed OG icons
- Add ↑ ↓ buttons to page through movies that are part of a collection/series. (Say, the nine Star Wars movies)
- Add spoiler prevention option for summaries, as some of them have no chill. (Looking at you, Handmaid’s Tale season 5)
- Slightly rearrange Settings & add a link to set the app’s language (shown only if you have more than one language enabled on your device)
- Fix Spoiler Settings insta-closing the first time you open it
- Fix weird kerning issue in Spoiler Settings
- Some improved logging around subscription verification so I can see if me blaming StoreKit2 is justified or not 😇
Not a bad deal for $9.
In the last week or so, my pals Jelly and Ste have posted about their small but very important roles in the creation of Callsheet.
First, Jelly writes:
I was listening to Accidental Tech Podcast one day, and my good mate Casey Liss was chatting away about his upcoming app, Flookup. […] So I found myself opening up Sketch and getting to work. Could I make an icon before the episode was done?
Jelly’s post also outs my piss-poor rendition of the icon that he ended up making so very pretty.
Next, Ste writes:
Fast forward to the WWDC keynote in early June, where they briefly discussed the Vision Pro. At one point, they flashed a large set of app icons on the screen. […] An icon that I created appeared (if ever so briefly) in the WWDC keynote. Achievement unlocked!
Callsheet would look — and feel — very different without the efforts of these two incredible friends. 🥰