Reading List
The most recent articles from a list of feeds I subscribe to.
Some notes on using nix
Recently I started using a Mac for the first time. The biggest downside I’ve noticed so far is that the package management is much worse than on Linux. At some point I got frustrated with homebrew because I felt like it was spending too much time upgrading when I installed new packages, and so I thought – maybe I’ll try the nix package manager!
nix has a reputation for being confusing (it has its whole own programming language!), so I’ve been trying to figure out how to use nix in a way that’s as simple as possible and does not involve managing any configuration files or learning a new programming language. Here’s what I’ve figured out so far! We’ll talk about how to:
- install packages with nix
- build a custom nix package for a C++ program called paperjam
- install a 5-year-old version of hugo with nix
As usual I’ve probably gotten some stuff wrong in this post since I’m still pretty new to nix. I’m also still not sure how much I like nix – it’s very confusing! But it’s helped me compile some software that I was struggling to compile otherwise, and in general it seems to install things faster than homebrew.
what’s interesting about nix?
People often describe nix as “declarative package management”. I don’t care that much about declarative package management, so here are two things that I appreciate about nix:
- It provides binary packages (hosted at https://cache.nixos.org/) that you can quickly download and install
- For packages which don’t have binary packages, it makes it easier to compile them
I think that the reason nix is good at compiling software is that:
- you can have multiple versions of the same library or program installed at a time (you could have 2 different versions of libc for instance). For example I have two versions of node on my computer right now, one at
/nix/store/4ykq0lpvmskdlhrvz1j3kwslgc6c7pnv-nodejs-16.17.1and one at/nix/store/5y4bd2r99zhdbir95w5pf51bwfg37bwa-nodejs-18.9.1. - when nix builds a package, it builds it in isolation, using only the
specific versions of its dependencies that you explicitly declared. So
there’s no risk that the package secretly depends on another package on your
system that you don’t know about. No more fighting with
LD_LIBRARY_PATH! - a lot of people have put a lot of work into writing down all of the dependencies of packages
I’ll give a couple of examples later in this post of two times nix made it easier for me to compile software.
how I got started with nix
here’s how I got started with nix:
- Install nix. I forget exactly how I did this, but it looks like there’s an official installer and an unofficial installer from zero-to-nix.com. The instructions for uninstalling nix on MacOS with the standard multi-user install are a bit complicated, so it might be worth choosing an installation method with simpler uninstall instructions.
- Put
~/.nix-profile/binon my PATH - Install packages with
nix-env -iA nixpkgs.NAME - That’s it.
Basically the idea is to treat nix-env -iA like brew install or apt-get install.
For example, if I want to install fish, I can do that like this:
nix-env -iA nixpkgs.fish
This seems to just download some binaries from https://cache.nixos.org – pretty simple.
Some people use nix to install their Node and Python and Ruby packages, but I haven’t
been doing that – I just use npm install and pip install the same way I
always have.
some nix features I’m not using
There are a bunch of nix features/tools that I’m not using, but that I’ll mention. I originally thought that you had to use these features to use nix, because most of the nix tutorials I’ve read talk about them. But you don’t have to use them.
- NixOS (a Linux distribution)
- nix-shell
- nix flakes
- home-manager
- devenv.sh
I won’t go into these because I haven’t really used them and there are lots of explanations out there.
where are nix packages defined?
I think packages in the main nix package repository are defined in https://github.com/NixOS/nixpkgs/
It looks like you can search for packages at https://search.nixos.org/packages. The two official ways to search packages seem to be:
nix-env -qaP NAME, which is very extremely slow and which I haven’t been able to get to actually worknix --extra-experimental-features 'nix-command flakes' search nixpkgs NAME, which does seem to work but is kind of a mouthful. Also all of the packages it prints out start withlegacyPackagesfor some reason
I found a way to search nix packages from the command line that I liked better:
- Run
nix-env -qa '*' > nix-packages.txtto get a list of every package in the Nix repository - Write a short
nix-searchscript that just grepspackages.txt(cat ~/bin/nix-packages.txt | awk '{print $1}' | rg "$1")
everything is installed with symlinks
One of nix’s major design choices is that there isn’t one single bin with all
your packages, instead you use symlinks. There are a lot of layers of symlinks. A few examples of symlinks:
~/.nix-profileon my machine is (indirectly) a symlink to/nix/var/nix/profiles/per-user/bork/profile-111-link/~/.nix-profile/bin/fishis a symlink to/nix/store/afkwn6k8p8g97jiqgx9nd26503s35mgi-fish-3.5.1/bin/fish
When I install something, it creates a new profile-112-link directory with new symlinks and updates my ~/.nix-profile to point to that directory.
I think this means that if I install a new version of fish and I don’t like it, I can
easily go back just by running nix-env --rollback – it’ll move me to my previous profile directory.
uninstalling packages doesn’t delete them
If I uninstall a nix package like this, it doesn’t actually free any hard drive space, it just removes the symlinks.
$ nix-env --uninstall oil
I’m still not sure how to actually delete the package – I ran a garbage collection like this, which seemed to delete some things:
$ nix-collect-garbage
...
85 store paths deleted, 74.90 MiB freed
But I still have oil on my system at /nix/store/8pjnk6jr54z77jiq5g2dbx8887dnxbda-oil-0.14.0.
There’s a more aggressive version of nix-collect-garbage that also deletes old versions of your profiles (so that you can’t rollback)
$ nix-collect-garbage -d --delete-old
That doesn’t delete /nix/store/8pjnk6jr54z77jiq5g2dbx8887dnxbda-oil-0.14.0 either though and I’m not sure why.
upgrading
It looks like you can upgrade nix packages like this:
nix-channel --update
nix-env --upgrade
(similar to apt-get update && apt-get upgrade)
I haven’t really upgraded anything yet. I think that if something goes wrong with an upgrade, you can roll back (because everything is immutable in nix!) with
nix-env --rollback
Someone linked me to this post from Ian Henry that
talks about some confusing problems with nix-env --upgrade – maybe it
doesn’t work the way you’d expect? I guess I’ll be wary around upgrades.
next goal: make a custom package of paperjam
After a few months of installing existing packages, I wanted to make a custom package with nix for a program called paperjam that wasn’t already packaged.
I was actually struggling to compile paperjam at all even without nix because the version I had
of libiconv I has on my system was wrong. I thought it might be easier to
compile it with nix even though I didn’t know how to make nix packages yet. And
it actually was!
But figuring out how to get there was VERY confusing, so here are some notes about how I did it.
how to build an example package
Before I started working on my paperjam package, I wanted to build an example existing package just to
make sure I understood the process for building a package. I was really
struggling to figure out how to do this, but I asked in Discord and someone
explained to me how I could get a working package from https://github.com/NixOS/nixpkgs/ and build it. So here
are those instructions:
step 1: Download some arbitrary package from nixpkgs on github, for example the dash package:
wget https://raw.githubusercontent.com/NixOS/nixpkgs/47993510dcb7713a29591517cb6ce682cc40f0ca/pkgs/shells/dash/default.nix -O dash.nix
step 2: Replace the first statement ({ lib , stdenv , buildPackages , autoreconfHook , pkg-config , fetchurl , fetchpatch , libedit , runCommand , dash }: with with import <nixpkgs> {}; I don’t know why you have to do this,
but it works.
step 3: Run nix-build dash.nix
This compiles the package
step 4: Run nix-env -i -f dash.nix
This installs the package into my ~/.nix-profile
That’s all! Once I’d done that, I felt like I could modify the dash package and make my own package.
how I made my own package
paperjam has one dependency (libpaper) that also isn’t packaged yet, so I needed to build libpaper first.
Here’s libpaper.nix. I basically just wrote this by copying and pasting from
other packages in the nixpkgs repository.
My guess is what’s happening here is that nix has some default rules for
compiling C packages (like “run make install”), so the make install happens
default and I don’t need to configure it explicitly.
with import <nixpkgs> {};
stdenv.mkDerivation rec {
pname = "libpaper";
version = "0.1";
src = fetchFromGitHub {
owner = "naota";
repo = "libpaper";
rev = "51ca11ec543f2828672d15e4e77b92619b497ccd";
hash = "sha256-S1pzVQ/ceNsx0vGmzdDWw2TjPVLiRgzR4edFblWsekY=";
};
buildInputs = [ ];
meta = with lib; {
homepage = "https://github.com/naota/libpaper";
description = "libpaper";
platforms = platforms.unix;
license = with licenses; [ bsd3 gpl2 ];
};
}
Basically this just tells nix how to download the source from GitHub.
I built this by running nix-build libpaper.nix
Next, I needed to compile paperjam. Here’s a link to the nix package I wrote. The main things I needed to do other than telling it where to download the source were:
- add some extra build dependencies (like
asciidoc) - set some environment variables for the install (
installFlags = [ "PREFIX=$(out)" ];) so that it installed in the correct directory instead of/usr/local/bin.
I set the hashes by first leaving the hash empty, then running nix-build to get an error message complaining about a mismatched hash. Then I copied the correct hash out of the error message.
I figured out how to set installFlags just by running rg PREFIX
in the nixpkgs repository – I figured that needing to set a PREFIX was
pretty common and someone had probably done it before, and I was right. So I
just copied and pasted that line from another package.
Then I ran:
nix-build paperjam.nix
nix-env -i -f paperjam.nix
and then everything worked and I had paperjam installed! Hooray!
next goal: install a 5-year-old version of hugo
Right now I build this blog using Hugo 0.40, from 2018. I don’t need any new features so I haven’t felt a need to upgrade. On Linux this is easy: Hugo’s releases are a static binary, so I can just download the 5-year-old binary from the releases page and run it. Easy!
But on this Mac I ran into some complications. Mac hardware has changed in the
last 5 years, so the Mac Hugo binary I downloaded crashed. And when I tried to
build it from source with go build, that didn’t work either because Go build
norms have changed in the last 5 years as well.
I was working around this by running Hugo in a Linux docker container, but I didn’t love that: it was kind of slow and it felt silly. It shouldn’t be that hard to compile one Go program!
Nix to the rescue! Here’s what I did to install the old version of Hugo with nix.
installing Hugo 0.40 with nix
I wanted to install Hugo 0.40 and put it in my PATH as hugo-0.40. Here’s how
I did it. I did this in a kind of weird way, but it worked (Searching and installing old versions of Nix packages
describes a probably more normal method).
step 1: Search through the nixpkgs repo to find Hugo 0.40
I found the .nix file here https://github.com/NixOS/nixpkgs/blob/17b2ef2/pkgs/applications/misc/hugo/default.nix
step 2: Download that file and build it
I downloaded that file (and another file called deps.nix in the same directory), replaced the first line with with import <nixpkgs> {};, and built it with nix-build hugo.nix.
That almost worked without any changes, but I had to make two changes:
- replace
with stdenv.libtowith libfor some reason. - rename the package to
hugo040so that it wouldn’t conflict with the other version ofhugothat I had installed
step 3: Rename hugo to hugo-0.40
I write a little post install script to rename the Hugo binary.
postInstall = ''
mv $out/bin/hugo $out/bin/hugo-0.40
'';
I figured out how to run this by running rg 'mv ' in the nixpkgs repository and just copying and modifying something that seemed related.
step 4: Install it
I installed into my ~/.nix-profile/bin by running nix-env -i -f hugo.nix.
And it all works! I put the final .nix file into my own personal nixpkgs repo so that I can use it again later if I
want.
reproducible builds aren’t magic, they’re really hard
I think it’s worth noting here that this hugo.nix file isn’t magic – the
reason I can easily compile Hugo 0.40 today is that many people worked for a long time to make it possible to
package that version of Hugo in a reproducible way.
that’s all!
Installing paperjam and this 5-year-old version of Hugo were both
surprisingly painless and actually much easier than compiling it without nix,
because nix made it much easier for me to compile the paperjam package with
the right version of libiconv, and because someone 5 years ago had already
gone to the trouble of listing out the exact dependencies for Hugo.
I don’t have any plans to get much more complicated with nix (and it’s still very possible I’ll get frustrated with it and go back to homebrew!), but we’ll see what happens! I’ve found it much easier to start in a simple way and then start using more features if I feel the need instead of adopting a whole bunch of complicated stuff all at once.
I probably won’t use nix on Linux – I’ve always been happy enough with apt
(on Debian-based distros) and pacman (on Arch-based distros), and they’re
much less confusing. But on a Mac it seems like it might be worth it. We’ll
see! It’s very possible in 3 months I’ll get frustrated with nix and just go back to homebrew.
5-month update: rebuilding my nix profile
Update from 5 months in: nix is still going well, and I’ve only run into 1
problem, which is that every nix-env -iA package installation started failing
with the error “bad meta.outputsToInstall”.
This script from Ross Light fixes that problem though. It lists every derivation installed in my current profile and creates a new profile with the exact same derivations. This feels like a nix bug (surely creating a new profile with the exact same derivations should be a no-op?) but I haven’t looked into it more yet.
Talon is amazing
↗ Rauno's web interface guidelines
Rauno Freiberg (designer at Vercel) shares his web guidelines for web interfaces. A few that stood out for me:
- Inputs should be wrapped with a
<form>to submit by pressing Enter- Interactive elements should disable
user-selectfor inner content- Actions that are frequent and low in novelty should avoid extraneous animations
Read them all on Rauno’s (beautiful) website, and check out the Craft section while you’re there.
Safari 16.4 Is An Admission
If you're a web developer not living under a rock, you probably saw last week's big Safari 16.4 reveal. There's much to cheer, but we need to talk about why this mega-release is happening now, and what it means for the future.
But first, the list!
WebKit's Roaring Twenties
Apple's summary combines dozens of minor fixes with several big-ticket items. Here's an overview of the most notable features, prefixed with the year they shipped in Chromium:
- : Web Push for iOS (but only for installed PWAs)
- : PWA Badging API (for unread counts) and
idsupport (making updates smoother) - : PWA installation for third-party browsers (but not to parity with "Smart Banners")
- A bevy of Web Components features, many of which Apple had held up in standards bodies for years1, including:
- : Constructable Stylesheets (important for performance)
- : Form participation and default ARIA role
- : Declarative Shadow DOM for "SSR"
- Myriad small CSS improvements and animation fixes, but also:
- :
<iframe>lazy loading - :
Clear-Site-Datafor Service Worker use at scale - : Web Codecs for video (but not audio)
- : WASM SIMD for better ML and games
- : Compression Streams
- : Reporting API (for learning about crashes and metrics reporting)
- : Screen Orientation & Screen Wake Lock APIs (critical for games)
- : Offscreen Canvas (but only 2D, which isn't what folks really need)
- Critical usability and quality fixes for WebRTC
A number of improvements look promising, but remain exclusive to macOS and iPadOS:
- Fullscreen API fixes
- AVIF and AV1 support
The lack of iOS support for Fullscreen API on <canvas> elements continues to harm game makers; likewise, the lack of AVIF and AV1 holds back media and streaming businesses.
Regardless, Safari 16.4 is astonishingly dense with delayed features, inadvertantly emphasising just how far behind WebKit has remained for many years and how effective the Blink Launch Process has been in allowing Chromium to ship responsibly while consensus was witheld in standards by Apple.
The requirements of that process accelerated Apple's catch-up implementations by mandating proof of developer enthusiasm for features, extensive test suites, and accurate specifications. This collateral put the catch-up process on rails for Apple.
The intentional, responsible leadership of Blink was no accident, but to see it rewarded so definitively is gratifying.
The size of the release was expected in some corners, owing to the torrent of WebKit blog posts over the last few weeks:
- : Web Share changes
- : Form participation for Web Components
- : CSS Nesting (not enabled for Beta)
- : Declarative Shadow DOM
- : User Activation API changes
- : Web Push API for iOS
This is a lot, particularly considering that Apple has upped the pace of new releases to once every eight weeks (or thereabouts) over the past year and a half.
Good Things Come In Sixes
Leading browsers moved to 6-week update cadence by 2011 at the latest, routinely delivering fixes at a quick clip. It took another decade for Apple to finally adopt modern browser engineering and deployment practices.
Starting in September 2021, Safari moved to an eight-week cadence. This is a sea change all its own.
Before Safari 15, Apple only delivered two substantial releases per year, a pattern that had been stable since 2016:
- New features were teased at WWDC in the early summer
- They landed in the Fall alongside a new iOS version
- A second set of small features trickled out the next Spring
For a decade, two releases per year meant that progress on WebKit bugs was a roulette that developers lost by default.
In even leaner years (2012-2015), a single Fall release was all we could expect. This excruciating cadence affected Safari along with every other iOS browser forced to put its badge on Apple's sub-par product.
Contrast Apple's manufactured scarcity around bug fix information with the open bug tracking and reliable candecne of delivery from leading browsers. Cupertino manages the actual work of Safari engineers through an Apple-internal system ("Radar"), making public bug reports a sort of parallel track. Once an issue is imported to a private Radar bug it's more likely to get developer attention, but this also obscures progress from view.
This lack of transparency is by design.
It provides Apple deniability while simultaneously setting low expectations, which are easier to meet. Developers facing showstopping bugs end up in a bind. Without competitive recourse, they can't even recommend a different browser because every iOS browser is forced to use WebKit, meaning every iOS browser is at least as broken as Safari.
Given the dire state of WebKit, and the challenges contributors face helping to plug the gaps, these heartbreaks have induced a learned helplessness in much of the web community. So little improved, for so long, that some assumed it never would.
But here we are, with six releases a year and WebKit accelerating the pace at which it's closing the (large) gap.
What Changed?
Many big-ticket items are missing from this release — iOS fullscreen API for <canvas>, Paint Worklets, true PWA installation APIs for competing browsers, Offscreen Canvas for WebGL, Device APIs (if only for installed web apps), etc. — but the pace is now blistering.
This is the power of just the threat of competition.
Apple's laywers have offered claims in court and in regulatory filings defending App Store rapaciousness because, in their telling, iOS browsers provide an alternative. If developers don't like the generous offer to take only 30% of revenue, there's always Cupertino's highly capable browser to fall back on.
The only problem is that regulators ask follow-up questions like "is it?" and "what do developers think?"
Which they did.
TL;DR: it wasn't, and developers had lots to say.
This is, as they say, a bad look.
And so Apple hedged, slowly at first, but ever faster as 2021 bled into 2022 and the momentum of additional staffing began to pay dividends.
Headcount Is Destiny
Apple had the resources needed to build a world-beating browser for more than a decade. The choice to ship a slower, less secure, less capable engine was precisely that: a choice.
Starting in 2021, Apple made a different choice, opening up dozens of Safari team positions. From 2023 perspective of pervasive tech layoffs, this might look like the same exuberant hiring Apple's competitors recently engaged in, but recall Cupertino had maintained extreme discipline about Safari staffing for nearly two decades. Feast or famine, Safari wouldn't grow, and Apple wouldn't put significant new resourcing into WebKit, no matter how far it fell behind.
The decision to hire aggressively, including some "big gets" in standards-land, indicates more is afoot, and the reason isn't that Tim lost his cool. No, this is a strategy shift. New problems needed new (old) solutions.
Apple undoubtedly hopes that a less egregiously incompetent Safari will blunt the intensity of arguments for iOS engine choice. Combined with (previously winning) security scaremongering, reduced developer pressure might allow Cupertino to wriggle out of needing to compete worldwide, allowing it to ring-fence progress to markets too small to justify browser development resources (e.g., just the EU).
Increased investment also does double duty in the uncertain near future. In scenarios where Safari is exposed to real competition, a more capable engine provides fewer reasons for web developers to recommend other browsers. It takes time to board up the windows before a storm, and if competition is truly coming, this burst of energy looks like a belated attempt to batten the hatches.
It's critical to Apple that narrative discipline with both developers and regulators is maintained. Dilatory attempts at catch-up only work if developers tell each other that these changes are an inevitable outcome of Apple's long-standing commitment to the web (remember the first iPhone!?!). An easily distracted tech press will help spread the idea that this was always part of the plan; nobody is making Cupertino do anything it doesn't want to do, nevermind the frantic regulatory filings and legal briefings.
But what if developers see behind the veil? What if they begin to reflect and internalise Apple's abandonment of web apps after iOS 1.0 as an exercise of market power that held the web back for more than a decade?
That might lead developers to demand competition. Apple might not be able to ring-fence browser choice to a few geographies. The web might threaten Cupertino's ability to extract rents in precisely the way Apple represented in court that it already does.
Early Innings
Rumours of engine ports are afoot. The plain language of the EU's DMA is set to allow true browser choice on iOS. But the regulatory landscape is not at all settled. Apple might still prevent progress from spreading. It might yet sue its way to curtailing the potential size and scope of the market that will allow for the web to actually compete, and if it succeeds in that, no amount of fast catch-up in the next few quarters will pose a true threat to native.
Consider the omissions:
- PWA installation prompting
- Fullscreen for
<canvas> - Real Offscreen Canvas
- Improved codecs
- Web Transport
- WebGPU
- Device APIs
Depending on the class of app, any of these can be a deal-breaker, and if Apple isn't facing ongoing, effective competition it can just reassign headcount to other, "more critical" projects when the threat blows over. It wouldn't be the first time.
So, this isn't done. Not by a long shot.
Safari 16.4 is an admission that competition is effective and that Apple is spooked, but it isn't an answer. Only genuine browser choice will ensure the taps stay open.
FOOTNOTES
Apple's standards engineers have a long and inglorious history of stalling tactics in standards bodies to delay progress on important APIs, like Declarative Shadow DOM (DSD).
The idea behind DSD was not new, and the intensity of developer demand had only increased since Dimitri's 2015 sketch. A 2017 attempt to revive it was shot down in 2018 by Apple engineers without evidence or data.
Throughout this period, Apple would engage sparsely in conversations, sometimes only weighing in at biannual face-to-face meetings. It was gobsmacking to watch them argue that features were unnecessary directly to the developers in the room who were personally telling them otherwise. This was disheartening because a key goal of any proposal was to gain support from iOS. In a world where nobody else could ship-and-let-live, and where Mozilla could not muster an opinion (it did not ship Web Components until late 2018), any whiff of disinterest from Apple was sufficient to kill progress.
The phrase "stop-energy" is often misused, but the dampening effect of Apple on the progress of Web Components after 2015-2016's burst of V1 design energy was palpable. After that, the only Web Components features that launched in leading-edge browsers were those that an engineer and PM were willing to accept could only reach part of the developer base.
I cannot stress enough how effectively this slowed progress on Web Components. The pantomime of regular face-to-face meetings continued, but Apple just stopped shipping. What had been a grudging willingness to engage on new features became a stalemate.
But needs must.
In early 2020, after months of background conversations and research, Mason Freed posted a new set of design alternatives, which included extensive performance research. The conclusion was overwhelming: not only was Declarative Shadow DOM now in heavy demand by the community, but it would also make websites much faster.
The proposal looked shockingly like those sketched in years past. In a world where
<template>existed and Shadow DOM V1 had shipped, the design space for Declarative Shadow DOM alternatives was not large; we just needed to pick one.An updated proposal was presented to the Web Components Community Group in March 2020; Apple objected on spurious grounds, offering no constructive counter.2
Residual questions revolved around security implications of changing parser behaviour, but these were also straightforward. The first draft of Mason's Explainer even calls out why the proposal is less invasive than a whole new element.
Recall that Web Components and the
<template>element themselves were large parser behaviour changes; the semantics for<template>even required changes to the long-settled grammar of XML (long story, don't ask). A drumbeat of (and proposals for) new elements and attributes post-HTML5 also represent identical security risks, and yet we barrel forward with them. These have notably included<picture>,<portal>(proposed),<fencedframe>(proposed),<dialog>,<selectmenu>(proposed), and<img srcset>.The addition of
<template shadowroot="open">would, indeed, change parser behaviour, but not in ways that were unknowably large or unprecedented. Chromium's usage data, along with the HTTP Archive crawl HAR file corpus, provided ample evidence about the prevalence of patterns that might cause issues. None were detected.And yet, at TPAC 2020, Apple's representatives continued to press the line that large security issues remained. This was all considered at length. Google's security teams audited the colossal volume of user-generated content Google hosts for problems and did not find significant concerns. And yet, Apple continued to apply stop-energy.
The feature eventually shipped with heavy developer backing as part of Chromium 90 in April 2021 but without consensus. Apple persistently repeated objections that had already been answered with patient explication and evidence.
Cupertino is now implementing this same design, and Safari will support DSD soon.
This has not been the worst case of Apple's deflection and delay — looking at you, Push Notifications — but serves as an exemplar. Problem solvers have been forced into a series of high-stakes gambits to solve developer problems by Apple (and, to a lesser extent, Mozilla) over Cupertino's dozen years of engine disinvestment.
Even in Chromium, DSD was delayed by several quarters. Because of the Apple Browser Ban, cross-OS availability was postponed by two years. The fact that Apple will ship DSD without changes and without counterproposals across the long arc of obstruction implies claims of caution were, at best, overstated.
The only folks to bring data to the party were Googlers and web developers. No new thing was learned through groundless objection. No new understanding was derived from the delay. Apple did no research about the supposed risks. It has yet to argue why it's safe now, but wasn't then.
So let's call it what it was: concern trolling.
Uncritical acceptance of the high-quality design it had long delayed is an admission, of sorts. It demonstrates Apple's ennui about developer and user needs (until pressed), paired with great skill at deflection.
The playbook is simple:
- Use opaque standards processes to make it look like occasional attendance at a F2F meeting is the same thing as good-faith co-engineering.
- "Just ask questions" when overstretched or uninterested in the problem.
- Spread FUD about the security or privacy of a meticulously-vetted design.
- When all else fails, say you will formally object and then claim that others are "shipping whatever they want" and "not following standards" when they carefully launch a specced and tested design you were long consulted about, but withheld good faith engagement to improve.
The last step works because only insiders can distinguish between legitimate critiques and standards process jockeying. Hanging the first-mover risk around the neck of those working to solve problems is nearly cost-free when you can also prevent designs from moving forward in standards, paired with a market veto (thaks to anti-competitive shenanigans).
Play this dynamic out over dozens of features across a decade, and you'll better understand why Chromium participants get exercised about the responsibility theatre various Apple engineers put on to avoid engaging substantively, while simultaneously blocking all forward movement. Understood in context, it decodes as delay and deflection; a way to avoid using standards bodies to help actually solve problems.
Cupertino has paid no price for deploying these smoke screens, thanks to the Apple Browser Ban and a lack of curiosity in the press. Without those shields, Apple engineers would have had to offer convincing arguments from data for why their positions were correct. Instead, they have whatabouted for over three years, only to suddenly implement proposals they recently opposed when the piercing gaze of regulators finally fell on WebKit.3 ⇐
The presence or absence of a counterproposal when objecting to a design is a primary indicator of seriousness within a standards discussion. All parties will have been able to examine proposals before any meeting, and in groups that operate by consensus, blocking objections are understood to be used sparingly by serious parties.
It's normal for disagreements to surface over proposed designs, but engaged and collaborative counter-parties will offer soft concerns – "we won't block on this, but we think it could be improved..." – or through the offer to bring a counterproposal. The benefits of a concrete counter are large. It demonstrates good faith in working to solve the problem and signals a willingness to ship the offered design. Threats to veto, or never implement a specific proposal, are just not done in the genteel world of web standards.
Over the past decade, making veto threats while offering neither data nor a counterproposal have become a hallmark of Apple's web standards footprint. It's a bad look, but it continues because nobody in those rooms wants to risk pissing off Cupertino. Your narrator considered a direct accounting of just the consequences of these tactics a potentially career-ending move; that's how serious the stakes are.
The true power of a monopoly in standards is silence — the ability to get away with things others blanch at because they fear you'll hold an even larger group of hostages next time. ⇐
Apple has rolled out the same playbook in dozens of areas over the last decade, and we can learn a few things from this experience.
First, Apple corporate does not care about the web, no matter how much the individuals that work on WebKit (deeply) care. Cupertino's artificial bandwidth constraints on WebKit engineering ensured that it implements only when pressured.
That means that external pressure must be maintained. Cupertino must fear losing their market share for doing a lousy job. That's a feeling that hasn't been felt near the intersection of I-280 and CA Route 85 in a few years. For the web to deliver for users, gatekeepers must sleep poorly.
Lastly, Apple had the capacity and resources to deliver a richer web for a decade but simply declined. This was a choice — a question of will, not of design correctness or security or privacy.
Safari 16.4 is evidence, an admission that better was possible, and the delaying tactics were a sort of gaslighting. Apple disrespects the legitimate needs of web developers when allowed, so it must not be.
Lack of competition was the primary reason Apple feared no consequence for failing to deliver. Apple's protectionism towards Safari's participation-prize under-achievement hasn't withstood even the faintest whiff of future challengers, which should be an enduring lesson: no vendor must ever be allowed to deny true and effective browser competition. ⇐