Infinite Mac: Resource Fork Roundtripping #

One of the frequent emails that I get about Infinite Mac is “I’ve been copying over these files to every mac I’ve owned for the past 20 (or 30 or 40) years, how can I load them into your site?” While there’s been a “The Outside World” drive from the very beginning, all it can do is a best-effort import/export using available browser APIs. Classic Mac OS used resource forks for structured data and stored additional metadata in the file system (where it belongs). Modern macOS still supports all of this, but unfortunately none of that data is visible to web apps – all they can read is the “data fork” of the file (i.e. its raw contents).

This is a long-standing issue – once Macintosh files started to be distributed over the Internet in the mid-90s, they were usually in a container format like MacBinary or StuffIt to preserve this extra data. While StuffIt is still around, having to download a separate program goes against the ethos of Infinite Mac being as easy to use as possible. After some investigation, I remembered that .zip archives created by modern macOS preserve resource forks ("modern" in this case being relative – this feature dates back to 10.3). Specifically they create a parallel __MACOSX directory structure that has metadata and resource forks in an AppleDouble file.

A while back I added detection of such .zip files, which helped with getting data in. More recently, I also switched the .zip files that Infinite Mac generates when exporting to also use this convention, allowing the Finder to expand and reconstruct the full contents. I also wanted to make this feature somewhat discoverable, and I chanced upon an exemption to the “browsers can’t see resource forks” limitation that I mentioned above – when dragging a whole folder into the window it’s possible to read a file’s resource fork via a magic file/..namedfork/rsrc path (sadly there’s no equivalent for the metadata). I used this to show a notification to remind users of the .zip capabilities.

This video shows these features working together – an old app of mine starts out on the modern macOS host – it can be imported, its vers resource edited, and then exported back out (miraculously today’s Finder still displays information based on it).

Odds and Ends

Using the same notification mechanism, I also made the need to click into the window for emulators that use relative mouse movements (DingsuPPC, PearPC, Previous) more discoverable. Thanks to this tip I figured out how to (ab)use GitHub releases to store large files, letting me finally share the Mac OS X disk images I’ve been using. Finally I fixed some longstanding issues with audio playback in some browsers and stuck modifier keys.

Pair Domains Pricing #

I've been using Pair Domains (née pairNIC) as my default domain registrar for 20+ years. It dates back to my using Pair Networks for website hosting (since 1998, and mscape.com still lives there) - this is a spin-off service. The admin UI is not the fanciest, but it has the control that I need, and the (human) support has always been great.

I recently got a renewal notice for a .com domain, and was a bit surprised at the price – $116.35 for 5 years. My mental model is that it should be closer to $10/year. Pair has changed hands a couple of times in the past few years, first sold to Libsyn in 2018 and then to the more mysterious Your.Online in 2024. The latter is effectively a private equity roll-up, and I was curious if the high prices were due to them. Between the Wayback Machine and my email receipts I was able to put together this historical pricing table:

Year Registration Renewal (5 yr) Renewal (1 yr)
2025 $12 $24.49 $26.99
2024 $11 $20 $23
2023 $11 $19.57 $22.88
2022 $8 $16.57 $19
2021 $8 $16.57 $19
2020 $9.77 $13 $19
2018 $13 $13 $19
2017 $13 $13 $19
2015 $19 $13 $19
2014 $19 $13 $19
2011 $19 $13 $19
2008 $19 $13 $19

It looks like Libsyn started this trend, but the new owners leaned into it even more. The increased divergence between registration and renewal feels like an attempt to rely on laziness/intertia. The renewal price I was quoted was slightly less than the above pricing table ($23.27 vs. $24.49) so perhaps there was some attempt at frog boiling for long-timer customers. But in this case I had already been using Cloudflare's registrar for new projects, so I bit the bullet and did the hour or so of ClickOps to get everything switched over. Not the most satisfying way to spend a weekend afternoon, but hopefully this post can be a PSA for anyone else that has been similarly stuck.

Infinite Mac Construction Set #

tl;dr: You can now embed any OS from Infinite Mac into your website, from 1984’s System 1.0 through 2005’s Mac OS X 10.4. There’s documentation for customizing and controlling embedded instances programmatically. As a demo of what’s possible, Infinite Monkey hooks up an emulated Mac 128K to OpenAI’s and Anthropic’s computer-using models, letting the technologies of 1984 and 2025 to finally meet. The instigator behind all this was Marcin Wichary, whose recent Frame of preference article is another showcase of the embedding capabilities.

A Kindred Spirit

Marcin’s articles – with their attention to detail and passion that they convey – have always struck a chord with me, whether they’re about underrated fonts or underrated movies. I’ve also appreciated that he strives for interactivity, from his Google Doodles to his Config talks. A few months ago, Marcin approached me about doing a modern take on his GUIdebook site, something with a story to tell and a way for readers to experience the material first-hand. Specifically, he wanted a way to embed emulated Mac instances in an article, ideally in as seamless of a way as possible.

Infinite Mac was already used by sites like Classic Macintosh Game Demos, DiscMaster, and Macintosh Repository to host runnable custom instances. But those were full-screen experiences and, in some cases, required a custom fork of the site. I wanted the experience to be closer to a YouTube or Google Maps embed – a snippet of HTML that’s easy to drop into any site, but still controllable via query parameters. To enable this, I added a new /embed endpoint that’s better suited to iframing. In addition to hiding the screen bezel and other chrome, it lets the embedding site control the screen resolution, get notified of the screen contents changing, and send mouse and keyboard events to the emulated Mac.

To keep embedded instances lightweight, I added an option for auto-pausing when they’re hidden. This uses both in-page (IntersectionObserver) and cross-page (visibilitychange) signals to pause and resume the emulator. Fortunately, I didn’t need to implement pausing in each emulator – instead I hooked into their input-reading loop and used Atomics.wait to cheaply suspend execution until the “paused” bit is cleared.

There were a couple of surprises along the way. First, while Chrome lets SharedArrayBuffer work in iframes (via the allow="cross-origin-isolated" attribute), this is not yet supported in Safari/WebKit-based browsers. That made my 2021-era Safari workaround newly relevant. It mostly worked, but it assumed a single global emulator instance which in Marcin’s tests had the hilarious effect of making every Mac on the page receive the same input. I added per-instance tracking so that each one gets its own event stream.

Infinite Mac network request waterfalling

Second, Marcin noticed his custom disk images loaded much more slowly than the built-in ones. Some of that was due to them bypassing Cloudflare’s cache, but even when that was enabled, they were slower. It turned out that prefetching – added in the earliest days of the site, when all it ran was System 7 – had become even more important for more modern Mac OS versions. They read a lot more data at startup, and being blocked for 50-100ms for each chunk adds up when they need to get through a couple of hundred. Extending the prefetching to remote disks too (and caching these prefetched chunks via the service worker) shaved a lot of time off booting the Mac OS 8.5 and NeXTStep images from Marcin’s article.

Infinite Mac embed builder dialog

I wanted to make embedding as approachable as possible (well, approachable for those interested in computing platforms from 20 to 40 years ago). There is an embed HTML builder that uses a variant of the custom instance dialog to generate the <iframe> markup for a specific instance (a construction set if you will). I also wrote documentation for the query parameters and message events to control the instance and receive state-change notifications.

Making a Demo Site

Marcin’s article is a kind of demo, and I had a crude testbed, but I wanted to more thoroughly dogfood the embedding support. It occurred to me that its capabilities (get screen content updates, send synthetic input events) made it a perfect fit for the computer use models recently launched by OpenAI and Anthropic. Their demos control a Docker container or other remote environment; having something in the browser is both satisfying (nothing to set up) and safe (the emulation sandbox already limits what the model can do).

The Mac actually has a long history of being controlled by another program, thus the Infinite Monkey site was born (as an aside, the original Monkey desk accessory is installed in the System 1.0 image if you’d like to try it out). Hooking it up was relatively straightforward, the useComputer and useChat hooks have the core glue logic. I even had Claude Code write most of the Anthropic glue code. I went with a “bring your own API key” approach – the models are somewhat expensive, and I did not want the donors to foot the bill. This does make it somewhat less accessible (the OpenAI version of the model is only available for Tier 3 accounts, and possibly not even then). The demo video should help those who don’t have access.

The actual experience of letting an LLM drive System 1.0 is a bit like a dog walking on its hind legs – impressive that it works at all, but objectively a bit underwhelming. The models are slow, it’s definitely faster to use the computer yourself (Anthropic’s own documentation calls this out). They also struggle with the UI conventions of older platforms, especially the press-and-hold mechanics of pull-down menus. OpenAI’s model can’t use them at all, since it can only request click and drag actions. Anthropic’s fares better with its separate left_mouse_down and left_mouse_up, but it often tries to click first, even when instructed otherwise.


OpenAI vs. Anthropic's handling of pull-down menus

This is just one possibility, I’m curious what other things could be built using embedded instances. And if there’s a capability that you wish they had, feel free to file an issue suggesting it.

Odds and Ends

I modernized the site’s Cloudflare setup, adopting both static assets and the official Vite plugin. Both worked as advertised: I have less code to maintain, and the local dev experience is closer to production. It was a welcome change from the usual experience on the frontend dependency treadmill.

The auto-pausing work above also suggested how I might implement a speed setting for more machine types. This fulfills a long-standing feature request to allow older software to run more accurately. It’s somewhat amusing that even in this many-layered environment (an emulator compiled to WebAssembly, which is in turn interpreted or compiled to the native platform) some things can be too fast.

Epilogue: Doctor. Manhattan has nothing on me

  • It is 2006. I am working on making one website (Google Reader) embeddable in another (Gmail).
  • It is 2007. I am working on making one website (Google Reader) embeddable in another (Blogger).
  • It is 2008. I am working on making one website (Google Reader) embeddable in another (iGoogle).
  • It is 2012. I am working on making any web app (Chrome Apps) embeddable in a host environment (Chrome).
  • It is 2017. I am working on making any web app (Live Apps) embeddable in another (Quip documents).
  • It is 2020. I am working on making one website (Quip chat) embeddable in another (Salesforce Lightning).
  • It is 2021. I am working on making one website (Quip documents) embeddable in another (Slack).
  • It is 2022. I am working on making one service (Tailscale SSH) embeddable in another (Tailscale Admin Panel).
  • It is 2023. I am working on making one service (Sierra agents) embeddable in any website or iOS app.
  • It is 2024. I am working on making one service (Sierra agents) embeddable in any Android app.
  • It is 2025. I am working on making one website (Infinite Mac) embeddable in any other.

Infinite Mac OS X #

tl;dr: Infinite Mac can now run early Mac OS X, with 10.1 and 10.3 being the best supported versions. It’s not particularly snappy, but as someone who lived through that period, I can tell you that it wasn’t much better on real hardware. Infinite HD has also been rebuilt to have some notable indie software from that era.

Mac OS X 10.1 running NetNewsWire Lite and Terminal

Porting PearPC

I’ve been tracking DingusPPC progress since my initial port and making the occasional contribution myself, with the hope of using it to run Mac OS X in Infinite Mac. While it has continued to improve, I reached a plateau last summer; my attempts would result in either kernel panics or graphical corruption. I tried to reduce the problem a bit via a deterministic execution mode, but it wasn’t really clear where to go next. I decided to take a break from this emulator and explore alternate paths of getting Mac OS X to run.

PearPC was the obvious choice – it was created with the express purpose of emulating Mac OS X on x86 Windows and Linux machines in the early 2000s. By all accounts, it did this successfully for a few years, until interest waned after the Intel switch (sadly one of the authors passed away around then). I had earlier dismissed it as a “dead” codebase, but I decided that the satisfaction of getting something working compensated for dealing with legacy C++ (complete with its own string class, sprintf implementation, and GIF decoder). An encouraging discovery was that kanjitalk755 (the de-facto Basilisk II and SheepShaver maintainer) had somewhat recently set up an experimental branch of PearPC that built and ran on modern macOS. I was able to replicate their work without too much trouble, and with that existence proof I started on my sixth port of an emulator to WebAssembly/Emscripten and the Infinite Mac runtime.

In some ways PearPC not being actively developed made things easier – I didn’t have to worry about merging in changes from upstream, or agonize over how to structure my modifications to make them easier to contribute back. It was also helpful that PearPC was already a multi-platform codebase and thus had the right layers of abstraction to make adding another target pretty easy. As a bonus, it didn’t make pervasive use of threads or other harder-to-port concepts. Over the course of a few days, I was able to get it to build, output video, load disk images, and get mouse and keyboard input hooked up. It was pretty satisfying to have Mac OS X 10.2 running in a browser more reliably than it previously had.

Performance

While PearPC ran 10.2 more reliably, it felt slower than DingusPCC. I had spent some time last year making some optimizations to the latter, partly inspired by the TinyPPC emulator in this SheepShaver fork (aren’t all these names fun?). I ported DingusPPC’s benchmark harness and then set about replicating the performance work in PearPC (both emulators are pure interpreters driven by a lookup table, so the process was relatively straightforward). I was able to shave off about 15 seconds from the 10.2 boot time – it helps from a saving lives perspective, but is still not enough given that it takes almost 2 minutes to be fully operational. In the end, I copped out and added a UI disclaimer that Mac OS X can be slow to boot. I also got flashbacks to the “is it snappy yet?” discussions from the early days of Mac OS X – it was indeed slow, but not this slow.

Performance is still not as good as DingusPPC’s – the biggest bottleneck is the lack of any kind of caching in the MMU, so all loads and stores are expensive since they involve complex address computations. DingusPPC has a much more mature tiered cache that appears to be quite effective. More generally, while PearPC may be more stable than DingusPPC at running 10.2-10.4, it’s a much less principled codebase (I came across many mystery commits) and it “cheats” in many ways (it has a custom firmware and video driver, and only the subset of PowerPC instructions that are needed for Mac OS X are implemented). I’m still holding out hope for DingusPPC to be the fast, stable, and correct choice for the long term.

A Side Quest

I implemented the “unified decoding table” approach in PearPC’s interpreter one opcode family at a time. When I got to the floating point operations, I assumed it was going to be another mechanical change. I was instead surprised to see that behavior regressed – I got some rendering glitches in the Dock, and the Finder windows would not open at all. After some debugging, I noticed that the dispatching for opcode groups 59 and 63 didn’t just do a basic lookup on the relevant instruction bits. It first checked the FP bit of the Machine State Register (MSR), and if it was not set it would throw a “floating point unavailable” exception.

I initially thought this was the emulator being pedantic – all PowerPC chips used in Macs had an FPU, so this should never happen. However, setting a breakpoint showed that the exception was being hit pretty frequently during Mac OS X startup. The xnu kernel sources of that time period are available, and though I’m not familiar with the details, there are places where the FP bit is cleared and a handler for the resulting exception is registered. I assume this is an optimization to avoid having to save/restore FPU registers during context switches (if they’re not being used). The upshot was that once I implemented the equivalent FP check in my optimized dispatch code, the rendering problems went away.

This reminded me of the rendering glitches that I had encountered when trying to run Mac OS X under DingusPPC. Even when booting from the 10.2 install CD (which does not kernel panic) I would end up with missing text and other issues:

Mac OS X 10.2 installer showing text rendering glitches

Checking the DingusPPC sources showed that it never checked the FP bit, and always allowed floating point instructions to go through. I did a quick hack to check it and raise an exception if needed, and the glitches went away!

Mac OS X 10.2 installer correctly rendering text

The proper implementation was a bit more complicated, and I ended up revising it a bit to avoid a performance hit (and another contributor did another pass). But at the end of it all, DingusPPC became a lot more stable, which was a nice side effect. Better yet, it can run 10.1 reliably, which PearPC cannot. I ended up using a combination of both emulators to run a broader subset of early Mac OS X (unfortunately 10.0 is still unstable, and the Public Beta kernel panics immediately, but I’m holding out hope for the future).

Rebuilding Infinite HD

Part of the appeal of Infinite Mac is that the emulated machines also have an “Infinite HD” mounted with a lot of era-appropriate software to try. With Mac OS X running, it was time to build an alternate version that went beyond the 80s and 90s classic Mac apps I had collected. I had my favorites, but I also put out a call for suggestions and got plenty of ideas.

For actually building the disk image, I extended the automated approach that I first launched the site with. Disk images were even more popular in the early days of Mac OS X than they are today, so I added a way to import .dmgs as additional folders in the generated image. However, I quickly discovered that despite having the same extension, there are many variants, and the hdiutil that ships with modern macOS cannot always mount images generated more than 20 years ago. In the end I ended up with a Rube Goldberg approach that first extracts the raw partition via dmg2img and then recreates a “modern” disk image that can be mounted and copied from.

As for getting the actual software, the usual sites like Macintosh Garden do have some from that era, but it’s not a priority for them. Early to mid 2000s Mac OS X software appears to be a bit of a blind spot – it’s too new to be truly “retro”, but too old to still be available from the original vendor (though there are exceptions). I ended up using the Wayback Machine a lot. As a bonus, I also installed the companion “Developer” CDs for each Mac OS X version, so tools like Project Builder and Interface Builder are also accessible.

Mac OS X 10.4 running Delicious Library, CandyBar, PCalc and Pixelmator

The only limitation that I ran into is that my disk build process is centered around HFS, but HFS+ was the default of that time period, and it introduced more advanced capabilities like longer file names containing arbitrary Unicode characters. Files from disk images that rely HFS+ features do not translate losslessly, but luckily this was not an issue for most software. To actually mount multiple drives (up to 3, between the boot disk, Infinite HD, and Saved HD), I ended up borrowing a clever solution from a DingusPPC fork: a multi-partition disk image is created on the fly from an arbitrary number of partition images that are specified at startup.

Aqua

To make the addition of Mac OS X to Infinite Mac complete, I also wanted to have an Aqua mode for the site’s controls, joining the classic, Platinum, and NeXT appearances. That prompted the question: which Aqua?

Screenshots of the logout dialog in Mac OS X 10.1 to 10.4
Aqua: the early years

Though the more subdued versions from 10.3 and 10.4 are my favorites, I decided to go with the 10.0/10.1 one since it has the biggest nostalgia factor. I wanted to use the exact same image assets as the OS, and since they make heavy use of semi-transparency, regular screenshots were not going to be good enough. I used resource_dasm and pxm2tga to extract the original assets from Extras.rsrc and create my own version of Aqua:

Infinite Mac custom instance configuration dialog, rendered with an Aqua appearance

If the recent rumors of a big UI revamp do come true, it’ll be nice to have this reference point of its ancestor.

Odds and Ends

The ability to mount multiple images means that you can also have a Mac OS 9 partition and start the Classic compatibility environment (this only works under 10.1 – PearPC never supported Classic). You can thus emulate classic Mac apps inside an emulated Mac OS X inside a WebAssembly virtual machine:

Mac OS X 10.1 running Stickies, Scrapbook and Calculator under Classic

There was a recent storm in a teacup about a Calculator behavior change. Using these Mac OS X images, it’s possible to verify that versions through 10.3 didn’t have the “repeatedly press equals” behavior, but 10.4 did.

Since Mac OS X boot is rather slow, I wanted to have a way to show more progress. PearPC has a built-in way to trigger verbose mode, but DingusPPC did not, so I added a way to specify Open Firmware variables at startup. This is now exposed in the custom instance dialog via the “Debug Mode” switch.

Though I’ve moved away from custom domain names, I thought macosx.app would make a nice addition to my collection. Unfortunately it’s taken, though in a rather weird way. I even contacted the YouTuber whose video it redirects to, and he said he was not the one that registered it. It expires in a couple of months, so maybe I’ll be able to grab it.

The End Of The Line?

“When Alexander saw the breadth of his domain, he wept for there were no more worlds to conquer.”
Hans Gruber Plutarch Some Frenchman

Mac OS X support catches Infinite Mac up to the modern day, unless I happen to get access to some time travel mechanics. There are of course two more CPU transitions to go through and numerous small changes, but Tiger is fundamentally recognizable to any current-day macOS user.

Except that in the retrocomputing world, it’s always possible to go deeper or more obscure. A/UX is not something that I’m very familiar with, but it was a contemporary of classic Mac OS and would be interesting to compare to NeXTStep. Shoebill runs it, and the codebase looks approachable enough to port. Then there’s Lisa, the Pippin (DingusPPC has some nascent support), and further afield the Newton (via Einstein?). We’ll see what moves me next.

A Post-Credits Sequence

When I first began exploring ways of running Mac OS X, I mentioned that QEMU seemed too daunting to port to WebAssembly given my limited time. Furthermore, the performance of the qemu.js experiment from a few years ago made it seem like even if it did run, it would be much too slow to be usable. However, I recently became aware of qemu-wasm via this FOSDEM presentation. The performance of its Linux guest demos is encouraging: I ran an impromptu bennmark of computing an MD5 checksum of 100 MB of data and it completed it in 8 seconds (vs. 13 for DingusPPC and 18 for PearPC). There’s still a big gap between that and a graphical guest like Mac OS X, but it’s nice to have this existence proof.

Update: See also the discussion on Hacker News.

Gardening Week: Evergreen Engineering #

New company, new corporate blog for me to post on. Gardening Week is a post about an engineering team ritual that we've developed at Sierra. Once again you can tell that it was not ghost-written because I got to cram in a lot of links to obscure articles.

More seriously, it's been great to be at Sierra from the start and get to influence the engineering culture in such a fundamental way. I know tech company blog posts can paint overly rosy pictures, but Gardening Week is genuinely one of my favorite parts about working here.

Infinite Mac: Macintosh Garden Library #

The Macintosh Garden is a great resource in the retro Mac community. It has an archive of nearly every piece of software released in the 80s and 90s, complete with screenshots, manuals, and metadata like year of release and operating system requirements. From its debut Infinite Mac would let you use files from the Garden: download it to your computer, and then drag it in to have it appear in the “Downloads” folder.  But while doable, it’s not the same as being discoverable or pleasant to use if you wanted to do this more than a few times. 

Inspired by the CD-ROM library feature, I decided to investigate what it would take to add a “Macintosh Garden” drawer to the site. The goal was to allow any item in the Garden’s catalog to be loaded into the emulated Mac with one click (at least for the versions that support “The Outside World”, which is most from System 7 to Mac OS 9). I reached out to the Garden’s maintainer, who was on board with the project and even provided a JSON dump of the site’s catalog of 20,000 applications and games. Building the UI was a fun exercise in making the CD-ROM drawer into a reusable component - another contribution to my collection of Classic- and Platinum-themed UI controls.

In keeping with the secondary goal of Infinite Mac bringing the best of web technologies to retro-computing, I wanted the Macintosh Garden drawer to be as fast as possible. The entire catalog was 86 megabytes of JSON, which would take a while to load, even with gzip compression (27 MB). I decided to create a custom data format, with only a small index file being necessary to render the drawer list view and support search-as-you-type. It contains plain JavaScript arrays with known indices for the title, author, and other data. This approach (inspired by the JsPbLite format) minimizes redundancy while keeping decoding simple. The file is 1.5 MB, and only 439 KB when compressed with gzip, which - combined with preloading - makes the drawer pretty snappy.

A separate “details” file with descriptions, download URLs, and other information is loaded by the Cloudflare worker, which serves pieces of it on-demand. The worker also handles proxying of downloads from the Garden, both to avoid running into CORS issues and because Cloudflare’s caching should help with frequently-used items. For downloads that are large CD-ROM images, the HTTP range-based streaming approach from the CD-ROM library is also used to help with performance.

The search UI supports operators, which were implemented by a simple predicate function run as part of a linear search (the data set is small enough that fancier indexing is not required). As a small UI touch, I finally got to use scroll snap for something (the carousel view for screenshots). I also added support for triggering downloads of an item via a URL parameter, so it’s possible to share links to a favorite item.

In terms of other Infinite Mac-related work, it’s been relatively quiet. I have been tracking DingusPPC development, and it’s now possible to (sometimes, very slowly) boot Mac OS X 10.2. I also made some small quality-of-life improvements: more control over scaling, it’s harder to accidentally close the site via Command-W, and era-appropriate fonts are now used.

Weather Text: My Own Bespoke Weather App #

I was a happy user of the Dark Sky weather app for many years. Even more than the localized and timely notifications (I live in a place with predictable weather) I appreciated its Apple Watch complications. I specifically used the three-line textual summary shown in the middle of the Modular face.

Apple Watch Modular face with Dark Sky complication
My preferred Apple Watch face, circa 2022

Apple acquired Dark Sky in 2020, incorporated many of its features into the built-in Weather app (with iOS 16/watchOS 10), and shut down the original app at the end of 2022. Unfortunately the complication layout was not carried over – the only large complications are multi-hour/day graphs that have too much information and are hard to see at a glance. On my iPhone, I first switched to Weather Line (RIP) and later to Weather Strip, but neither had a watch app. I considered CARROT Weather, which has customizable complications, as a possible replacement. However, it couldn’t replicate the same layout, and paying for a separate subscription just for a watch app seemed wasteful.

On the other hand my time is free, and I figured that with WeatherKit (the one good thing to come out of the Dark Sky acquisition), I could build my own complication that showed exactly what I wanted. I procrastinated doing this for more than a year (squinting at the Weather complication every morning and thinking “I should do something about this”) but I reached a lull in Infinite Mac development and decided to once again work on an Apple platform developed in this century. It turns out that my procrastination paid off: watchOS 9 introduced WidgetKit for complications and Xcode 15 made wireless debugging (the only option for Apple Watch development) more reliable.

After fighting a bit with my old nemesis, provisioning profiles, and figuring out how to get location access in a widget (which may change in the future), I had something up and running. WeatherKit makes it particularly easy to replicate the Dark Sky complication – there’s even a property to get an SF Symbol for the current conditions and pre-computed sunrise/sunset times

Weather Text is the resulting app - it has a minimal watch UI to request location access and show a preview, but the main focus is the complication/widget (which also shows up in Smart Stack).

Weather Text app screen Apple Watch Modular face with Weather Text complication

I decided not to put the app on the App Store. WeatherKit has a limited free quota, and while it was unlikely that the app would suddenly boom in popularity, I didn’t want to worry about it. I could have made the app paid up-front to deter casual installs, but my App Store account is not set up for payments, and I didn’t want to go through the hassle of that.

The app is instead available via TestFlight, which is hopefully enough of a deterrent that I’ll remain under the free limit (and builds expiring every 90 days also gives me additional control). I would have thought that only going through the lightweight TestFlight approval would exempt me from review shenanigans. However, I still had to change my original icon, despite the presence of plenty of other dark background icons, including the original Dark Sky one.

Weather Text is a home cooked meal/hammer kind of app, mostly meant for me (though I’m not the only one who misses Dark Sky’s complications). I don’t have grand ambitions for it, it’s not a forever project (modulo minimal upkeep to make sure it works with new OS releases). Though modern platforms don’t have quite as much end-user programmability as they used to (though widget.json/Widget Construction Set and Scriptable look interesting), as a middle-aged gentleman programmer it’s nice to still be able to make them fit exactly to my needs.