Full Width [alt+shift+f] Shortcuts [alt+shift+k]
Sign Up [alt+shift+s] Log In [alt+shift+l]

New here?

Welcome! BoredReading is a fresh way to read high quality articles (updated every hour). Our goal is to curate (with your help) Michelin star quality articles (stuff that's really worth reading). We currently have articles in 0 categories from architecture, history, design, technology, and more. Grab a cup of freshly brewed coffee and start reading. This is the best way to increase your attention span, grow as a person, and get a better understanding of the world (or atleast that's why we built it).

13
We've had Lucy for two weeks, which qualifies us as experts, which means it is time to write about parenthood. (In all seriousness, consider the below descriptive and not prescriptive: mostly, it's a notepad filled with things that were remarkable or surprising or divergent from popular consensus.) American pop culture puts too much emphasis on the onus of diaper changing. Diapers are easy; swaddles are slightly more difficult (we've gone with what we refer to as "the three corners method"), and the true endgame foe is the onesie. Sleeves are difficult! We were nervous about Telemachus not adjusting well to having a baby sister in the house; instead, he is enamored with her, and already quite protective, and mostly just annoyed at my wife and me for getting up so often in the middle of the night. Common wisdom says that babies sleep for eighteen hours a day, the mechanics of which are hard to really internalize until you're amidst it. There is more downtime than you expect — it's just...
4 months ago

Improve your reading experience

Logged in users get linked directly to articles resulting in a better reading experience. Please login for free, it takes less than 1 minute.

More from Applied Cartography

Recursive filter schema

When we added support for complex filtering in Buttondown, I spent a long time trying to come up with a schema for filters that felt sufficiently ergonomic and future-proof. I had a few constraints, all of which were reasonable: It needed to be JSON-serializable, and trivially parsable by both the front-end and back-end. It needed to be arbitrarily extendible across a number of domains (you could filter subscribers, but also you might want to filter emails or other models.) It needed to be able to handle both and and or logic (folks tagged foo and bar as well as folded tagged foo or bar). It needed to handle nested logic (folks tagged foo and folks tagged bar or baz.) The solution I landed upon is not, I’m sure, a novel one, but googling “recursive filter schema” was unsuccessful and I am really happy with the result so here it is in case you need something like this: @dataclass class FilterGroup: filters: list[Filter] groups: list[FilterGroup] predicate: "and" | "or" @dataclass class Filter: field: str operator: "less_than" | "greater_than" | "equals" | "not_equals" | "contains" | "not_contains" value: str And there you have it. Simple, easily serializable/type-safe, can handle everything you throw at it. For example, a filter for all folks younger than 18 or older than 60 and retired: FilterGroup( predicate="or", filters=[ Field( field="age", operator="less_than", value="18" ) ], groups=[ FilterGroup( predicate="and", filters=[ Field( field="age", operator="greater_than", value="60" ), Field( field="status", operator="equals", value="retired" ) ] groups=[], ) ] )

2 days ago 3 votes
Does that dependency spark joy?

If there's been one through line in changes to Buttondown's architecture over the past six months or so, it's been the removal and consolidation of dependencies: on the front-end, back-end, and in paid services. I built our own very spartan version of Metabase, Notion, and Storybook; we vended a half-dozen or so Django packages that were not worth the overhead of pulling from PyPI (and rewrote another half-dozen or so, which we will open-source in due time); we ripped out c3, our visualization library, and built our own; we ripped out vuedraggable and a headlessui and a slew more of otherwise-underwhelming frontend packages in favor of purpose-built (faster, smaller, less-flexible) versions. [1] There are a few reasons for this: Both Buttondown as an application and I as a developer have now been around long enough to be scarred by big ecosystem changes. Python has gone through both the 2.x to 3.x transition and, more recently, the untyped to typed transition; Vue has gone from 2.x to 3.x. The academic problem of "what happens if this language completely changes?" is no longer academic, and packages that we installed back in 2018 slowly succumbed to bitrot. It's more obvious to me now than a few years ago that pulling in dependencies incurs a non-trivial learning cost for folks paratrooping into the codebase. A wrapper library around fetch might be marginally easier to invoke once you get used to it, but it's a meaningful bump in the learning curve to adapt to it for the first time. It is easier than ever to build 60% of a tool, which is problematic in many respects but useful if you know exactly which 60% you care about. (Internal tools like Storybook or Metabase are great examples of this. It was a fun and trivial exercise to get Claude to build a tool that did everything I wanted Metabase to do, and save me $120/mo in the process.) We still use a lot of very heavy, very complex stuff that we're very happy with. Our editor sits on top of tiptap (and therefore ProseMirror); we use marked and turndown liberally, because they're fast and robust. On the Python side, our number of non-infrastructural packages is smaller but still meaningful (beautifulsoup, for instance, and django-allauth / django-anymail which are both worth their weight in gold). But the bar for pulling in a small dependency is much higher than it was, say, twelve months ago. My current white whale is to finally get rid of axios. 39 call sites to go! ↩︎

a week ago 6 votes
HQ1

After many wonderful years of working out of my home office (see Workspaces), I've now "expanded" [1] into an office of my own. 406 W Franklin St #201 is now the Richmond-area headquarters of Buttondown. Send me gifts! The move is a bittersweet one; it was a great joy to be so close to Haley and Lucy (and, of course, Telly), and the flexibility of being able to hop off a call and then take the dog for a walk or hold Lucy for a while was very, very nice. At the same time, for the first time in my life that flexibility has become a little bit of a burden! It turns out it is very hard to concentrate on responding to emails when your alternative is to play with your daughter giggling in the adjoining room; similarly, as Buttondown grows and as more and more of my time is spent on calls, it turns out long-winded demos and onboarding calls are logistically trickier when it is Nap Time a scant six feet away. And, beyond that, it's felt harder and harder to turn my brain off for the day: when there is always more work to be done, it's hard not to poke away at a stubborn pull request or jot down some strategy notes instead of being more present for my family (or even for myself, in a non-work capacity.) So, I leased an office. The space is pretty cool: it's downtown in the sweet spot of a little more than a mile away from the house: trivially walkable (or bikeable, as the above photo suggests) but far enough away to give me a good bit of mental space. The building is an old manor (turned dormitory, turned office building). I've got a bay window with plenty of light but no views; I've got a nice ethernet connection and a Mac Mini with very few things installed; I've got a big Ikea desk and a printer; I've got an alarm on my phone for 4:50pm, informing me that it's time to go home, where my world becomes once again lively and lovely, full of noise and joy and laughter. Air quote because I'm fairly confident this office is actually smaller than the home office. ↩︎

a week ago 8 votes
Naughty vs. nice

I love this bit from Paul Graham on pattern-matching founders: Though the most successful founders are usually good people, they tend to have a piratical gleam in their eye. They're not Goody Two-Shoes type good. Morally, they care about getting the big questions right, but not about observing proprieties. That's why I'd use the word naughty rather than evil. They delight in breaking rules, but not rules that matter. This quality may be redundant though; it may be implied by imagination. I love this not because I agree with the sentiment, and in fact I think you can point to a lot of Icarian tendencies (and perhaps pervasive industry-wide rot) as germinating in this naughtiness, but because it is a specific and opinionated characteristic — as opposed to, like, "determined!" and "smart!" and "driven!" It's novel, it's a characteristic with a viewpoint around which reasonable people can agree/disagree. Antimetal is not a YC company, but it certainly embodies naughtiness. Its founder made a large hullabaloo about trying to commission "the highest quality publicly available version" of the Facebook Red Book, but of course couldn't resist a tiny act of digital vandalism by inserting its own branding into the scan. Does this matter, on a grand scale? Is this an evil act? Probably not, but certainly a naughty one. Buttondown in 2025 has reached a sort of escape velocity, less in terms of growth per ce (though also that!) and more in terms of the median user being very far way from my orbit: these users are less technical and more wary than the ones I am used to onboarding. Users of new tools — especially tools that must be entrusted with important data — are wary these days. They're wary of pivots to video, of shifting business models and sudden price hikes and emails announcing that the curtain is coming down this time next week. It is unfortunate that this wariness — a kind of cynicism — is not only pervasive but entirely rational. Anyone who has used anything new over the past few years has a high number of since-shuttered apps that they trusted with their time and money and data and energy, only to be rewarded with an "Our Incredible Journey" email. At a high level, I think this stems from the same vein as naughtiness: a tendency to think of systems and expectations as something to be overcome, as social contracts as a thing to be voided or ignored rather than bolstered. We get a lot of questions that boil down to "why should I trust [Buttondown]?" The blithe answer — the one that I generally try not to give, even though I think it's the most rigorous and correct one — is that, well, you shouldn't — insofaras you should only trust any company as much as you can exfiltrate your data. We've made a lot of decisions in service of decades-long continuity; we're cash-flow positive, we're stable and robust; our incentives are aligned with yours. But, more than that, the email space is novel in that you can always pack up your entire dataset — archives, addresses, et al — and ship them off to a competitor. You shouldn't need to trust us; you should find us valuable enough to be worth keeping around. Email is also unique in that it's, by software standards, a very mature industry — one with a long history already. Many of my customers come with data exports from tools that they started using fifteen years ago; prospects who I reached out to in 2019 follow up in 2025. After twelve months of active usage, we ask every paying customer a single question: "why are you still using Buttondown?" [1] There are two answers whose volume dwarf the rest: Because the customer support is really good. Because I haven't had an experience that has prompted me to look elsewhere. Customer goodwill is a real asset; it is one that will probably become more valuable over the next decade, as other software-shaped assets start to become devalued. It feels almost anodyne to say "it is in a company's best interest to do right by their customers", but our low churn and high unpaid growth in a space uniquely defined by lack of vendor lock-in is perhaps a sign that being nice is an undervalued strategy. And "being nice" in a meaningful sense is, like "being naughty", something that gets baked into an organization's culture very early and very deeply. The implicit subtext being "...given that you can, in an afternoon's work, migrate to a competitor, most of whom are substantially less expensive." ↩︎

a week ago 12 votes
YOLO-squashing our Django repository

Buttondown's core application is a Django app, and a fairly long-lived one at that — it was, until recently, sporting around seven hundred migration files (five hundred of which were in emails, the "main" module of the app). An engineer pointed out that the majority of our five minute backend test suite was spent not even running the tests but just setting up the database and running all of these migrations in parallel. I had been procrastinating squashing migrations for a while; the last time I did so was around two years ago, when I was being careful to the point of agony by using the official squash tooling offered by Django. Django's official squashing mechanism is clever, but tends to fall down when you have cross-module dependencies, and I lost an entire afternoon to trying to massage things into a workable state. This time, I went with a different tactic: just delete the damn things and start over. (This is something that is inconsiderate if you have lots of folks working on the codebase or you're letting folks self-host the codebase; neither of these apply to us.) rm rf **/migrations/* worked well for speeding up the test suite, but it was insufficient for actually handling things in production. For this, I borrowed a snippet from django-zero-migrations (a library around essentially the same concept): from django.core.management import call_command from django.db.migrations.recorder import MigrationRecorder MigrationRecorder.Migration.objects.all().delete() call_command("migrate", fake=True) And voila. No fuss, no downtime. Deployments are faster; CI is much faster; the codebase is 24K lines lighter. There was no second shoe. If you were like me 24 hours ago, trying to find some vague permission from a stranger to do this the janky way: consider the permission granted. Just take a snapshot of your database beforehand just in case, and rimraf away.

2 weeks ago 13 votes

More in technology

+ iPhone 16e review in progress: battery life

You can never do too much battery testing, but after a week with this phone I've got some impressions to share.

7 hours ago 1 votes
Reading List 03/08/2025

China’s industrial diplomacy, streetlights and crime, deorbiting Starlink satellites, a proposed canal across Thailand, a looming gas turbine shortage, and more.

7 hours ago 1 votes
Real WordPress Security

One thing you’ll see on every host that offers WordPress is claims about how secure they are, however they don’t put their money where their mouth is. When you dig deeper, if your site actually gets hacked they’ll hit you with remediation fees that can go from hundreds to thousands of dollars. They may try … Continue reading Real WordPress Security →

9 hours ago 1 votes
Odds and Ends #61: Fake Woolly Mammoths

Plus why intelligence is social, Land Registry open data, and some completely invisible VFX

yesterday 2 votes
Watch me write a task manager in 30 minutes

A core tenet of A Better Computer is showing, not telling. I don’t use a lot of press kit material or talking points from companies in my videos because I don’t particularly care about those. My incentives are fully aligned with showing software (and sometimes hardware)

yesterday 2 votes