Latest Posts (20 found)
Justin Duke Yesterday

Unshipping Keystatic

Two years after initially adopting it , we've formally unshipped Keystatic . Our CMS, such as it is, is now a bunch of Markdoc files and a TypeScript schema organizing the front matter — which is to say, it's not really a CMS at all. There were a handful of reasons for this move, in no specific order: That last point is basically what I wrote about Invoke — it's a terrible heuristic, judging a project by its commit frequency, and I know that. Things can and should be finished! And yet. When you're already on the fence, a quiet GitHub graph is the thing that tips you over. To Keystatic's credit, it was tremendously easy to extricate. The whole migration was maybe two hours of work, most of which was just deleting code. That's the sign of a well-designed library — one that doesn't metastasize into every corner of your codebase. I wish more tools were this easy to leave. Our team's use of Keystatic as an actual front-end CMS had dropped to zero. All of the non-coders have grown sufficiently adept with Markdown that the GUI was gathering dust; Keystatic had become a pure schema validation and rendering tool, and offered fairly little beyond what we were already getting from our build step. Some of the theoretically nice things — image hosting, better previewing — either didn't work as smoothly as we'd like or were supplanted entirely by Vercel's built-in features. The project appears to have atrophied a little bit, commits dwindling into the one-per-quarter frequency despite a healthy number of open issues. This is not to besmirch the lovely maintainers, who have many other things going on. But it's harder to stick around on a library you're not getting much value from when you're also worried there's not a lot of momentum down the road.

0 views
Justin Duke 5 days ago

What Happened Was

Two of my absolute favorite films of all time, albeit for very different reasons, are My Dinner with Andre and Before Sunrise . Both of these films, which I highly encourage you to watch more than anything else I talk about if you haven't already done so, are about the enchantment and sucker of one single really interesting conversation. The two films diverge pretty heavily from there. My Dinner with Andre is a film about work, fulfillment, and status. And Before Sunrise is a film about youth in love. But the beauty in both comes from not just their simplicity and formless structure, but in the recursive nature of the dialogue, just like in real life, where a pregnant pause or a sidelong glance suddenly carries with it enormous weight after understanding not just the comment but the 75 minutes preceding it. What Happened Was is interested in that last thing too. And in the unraveling of yourself that happens when you spend time being intimate in a literal sense with anyone. But is more interested in a funhouse mirror look at the human psyche. And has perhaps more cynical and caustic things to say about the way people express themselves through others. Our dual protagonists are a paralegal and an executive assistant. Both seem a little off, but not wholly so. And then, over the course of the worst first date in the world, we watch the characters reduce themselves to mania. This is an uncomfortable film to watch. Rather than transposing yourself into Andre and his counterpart, or Jesse and his counterparty, you find yourself just kind of internally screaming on behalf of both characters who have a Lynchian sense of bizarre behavior. In terms of inspiration, this draws more from Waiting for Godot than Who's Afraid of Virginia Woolf. The dread you feel is less from a place of sadness and understanding and more from a sense of shock and increasing bewilderment. And to that extent, it flatly did not work for me quite as much as I hoped. But as in all two-part plays, the film ends with two monologues, one from each character, where they lay bare the things that at that point are almost nakedly obvious to us, the viewer. And while I can't say either monologue or scene was particularly well written, I will say that both of them will stick with me for a long, long time. (I'm not sure the preceding seventy minutes earned those monologues, but that's a point beside.)

0 views
Justin Duke 6 days ago

Golinks

If you've never encountered golinks before: they're short, memorable URLs that redirect to longer ones. Instead of telling a coworker "the dashboard is at ," you just say . Instead of bookmarking seventeen different Notion pages, you type or or and trust that you'll end up in the right place. I discovered them at Stripe, though I believe they were invented at Google, and I have not stopped using them since. One thing leads to another. You decide that you no longer need Tailscale because the main reason you spun up Tailscale was for a project that ended up shipping — and therefore spending per-seat pricing on a service that you literally only use for golinks seems a bit silly and prohibitive. 1 Side note: I still really love Tailscale and think it's a great product and would be shocked if we aren't using it again by the end of the year. But! And then you need to find a replacement for golinks, and you cannot get dragged back to golinks.io or Trotto, both of which are slow, cumbersome, and expensive besides. So what was I to do? First, I looked at the open source options, none of which struck me as particularly compelling. I have a set of requirements that I don't think are esoteric, but others might: And nothing quite fit the bill. I had a revelation: I discovered that you could use a default search engine as the routing proxy instead of or DNS interception like Tailscale's MagicDNS. For a week or two, I had this sitting within Django in our monorepo out of ease — simply intercept any incoming search query, redirect it if something's already in the database, and then if it's not but it looks like it could be, send to the empty state prompting the user to create a golink. But frankly, this was just slower than I wanted. Not for any interesting reason, but just the usual Python request-response lifecycle stuff. I could, of course, invest in making it better and faster and was planning on doing so, but figured I would take one last trip around the internet to see if there was some other solution that I somehow missed. And that's when I discovered GoToTools . There is nothing really interesting to say about this product besides the fact that it is very good for what it does. Its author appears to have built it out of the same frustration that I had. And the highest compliment I can give it is that in a year where I've already cut down substantially on the number of services I pay for — in favor of those that I vend — I have absolutely no compunction about starting to use this. The pricing is extraordinary. The performance is really good. It works and is fast and lets me not spend time thinking about golinks and instead lets me spend time using them. A reasonable price Persistence The ability to use golinks without a Chrome extension Performance

0 views
Justin Duke 1 weeks ago

Maybe use Plain

When I wrote about Help Scout , much of my praise was appositional. They were the one tool I saw that did not aggressively shoehorn you into using them as a CRM to the detriment of the core product itself. This is still true. They launched a redesign that I personally don't love, but purely on subjective grounds. And there's still a fairly reasonable option for — and I mean this in a non-derogatory way — baby's first support system. I will call out also: if you want something even simpler, Jelly , which is an app that leans fully into the shared inbox side of things. It is less featureful than Help Scout, but with a better design and lower price point. If I was starting a new app today, this is what I would reach for first. But nowadays I use Plain . Plain will not solve all of your problems overnight. It's only a marginally more expensive product — $35 per user per month compared to Help Scout's $25 per user per month. The built-in Linear integration is worth its weight in gold if you're already using Linear, and its customer cards (the equivalent of Help Scout's sidebar widgets) are marginally more ergonomic to work with. The biggest downside that we've had thus far is reliability — less in a cosmic or existential sense and more that Plain has had a disquieting number of small-potatoes incidents over the past three to six months. My personal flowchart for what service to use in this genre is something like: But the biggest thing to do is take the tooling and gravity of support seriously as early as you can. Start with Jelly. If I need something more than that, see if anyone else on the team has specific experience that they care a lot about, because half the game here is in muscle memory rather than functionality. If not, use Plain.

0 views
Justin Duke 1 weeks ago

Outgrowing Django admin

For a bit of dessert work this week, I'm working on a full-fledged attempt at replacing the majority of our stock Django admin usage with something purposeful. I say majority and not totality because even though I am an unreasonable person, I am not that unreasonable. We have over a hundred Django models, and the idea of trying to rip and replace each and every one of them — or worse yet, to design some sort of DSL by which we do that — is too quixotic even for me. The vast majority of our admin usage coalesces around three main models, and they're the ones you might guess: the user/newsletter model, the email model, and the subscriber model. My hope is that building out a markedly superior interface for interacting with these three things and sacrificing the long tail still nets out for a much happier time for myself and the support staff. Django admin is a source of both much convenience as much frustration: the abstractions make it powerful and cheap when you're first scaling, but the bill for those abstractions come due in difficult and intractable ways. When I talk with other Django developers, they divide cleanly into one of two camps: either "what are you talking about, Django admin is perfect as-is" or "oh my God, I can't believe we didn't migrate off of it sooner." Ever the annoying centrist, I find myself agreeing with both camps: Let's set aside the visual design of the admin for a second, because arguing about visual design is not compelling prose. To me, the core issue with Django's admin interface, once you get more mature, is the fact that it's a very simple request-response lifecycle. Django pulls all the data, state, and information you might need and throws it up to a massive behemoth view for you to digest and interact with. It is by definition atomic: you are looking at a specific model, and the only way to bring in other models to the detail view is by futzing around with inlines and formsets. The classic thing that almost any Django developer at scale has run into is the N+1 problem — but not even necessarily the one you're thinking about. Take a fairly standard admin class: If you've got an email admin object and one of the fields on the is a — because you want to be able to change and see which user wrote a given email — Django by default will serialize every single possible user into a nice tag for you. Even if this doesn't incur a literal N+1, you're asking the backend to generate a select with thousands (or more) options; the serialization overhead alone will timeout your request. And so the answer is, nowadays, to use or , which pulls in a jQuery 1.9 package 1 Yes, in 2026. No, I don't want to talk about it. to call an Ajax endpoint instead: This is the kind of patch that feels like a microcosm of the whole problem: technically correct, ergonomically awkward, and aesthetically offensive. But the deeper issue is composability rather than performance. A well-defined data model has relationships that spread in every direction. A subscriber has Stripe subscriptions and Stripe charges. It has foreign keys onto email events and external events. When you're debugging an issue reported by a subscriber, you want to see all of these things in one place, interleaved and sorted chronologically. Django admin's answer to this is inlines: This works — until it doesn't. You start to run into pagination issues; you can't interleave those components with one another because they're rendered as separate, agnostic blocks; you can't easily filter or search within a single inline. You could create a helper method on the subscriber class to sort all related events and present them as a single list, but you once again run into the non-trivial problem of this being part of a fixed request-response lifecycle. And that kind of serialized lookup can get really expensive: You can do more bits of cleverness — parallelizing lookups, caching aggressively, using and everywhere — but now you're fighting the framework rather than using it. The whole point of Django admin was to not build this stuff from scratch, and yet here you are, building bespoke rendering logic inside callbacks. I still love Django admin. On the next Django project I start, I will not create a bespoke thing from day one but instead rely on my trusty, outdated friend until it's no longer bearable. But what grinds my gears is the fact that, as far as I can tell, every serious Django company has this problem and has had to solve it from scratch. There's no blessed graduation path, whether in the framework itself or the broader ecosystem. I think that's one of the big drawbacks of Django relative to its peer frameworks. As strong and amazing as its community is, it's missing a part of the flywheel from more mature deployments upstreaming their findings and discoveries back into the zeitgeist. Django admin is an amazing asset; I am excited to be, if not rid of it, to be seeing much less of it in the future.

0 views
Justin Duke 1 weeks ago

Notes on "Harness Engineering"

I find it useful and revealing to perform very close readings of engineering blog posts from frontier labs. They seem like meaningful artifacts that, despite their novelty, are barely discussed at any level except the surface one. I try to keep an open but keen mind when reading these posts, both trying to find things that don't make much sense when you think about them for more than a couple seconds and what things are clearly in the internal zeitgeist for these companies but haven't quite filtered themselves out into mainland. And so I read Harness Engineering with this spirit in mind. Some notes: This was a fairly negative list of notes, and I want to end with something positive: I do generally agree with the thrust of the thesis. Ryan writes: This is the kind of architecture you usually postpone until you have hundreds of engineers. With coding agents, it's an early prerequisite: the constraints are what allows speed without decay or architectural drift. I think this is absolutely the right mindset. Build for developer productivity as if you have one more order of magnitude of engineers than you actually do. It's disingenuous to make this kind of judgment without knowing more about the use case and purpose of the application itself, but the quantitative metrics divulged are astounding. The product discussed in this post has been around for five months. It contains over one million lines of code and is not yet ready for public consumption but has a hundred or so users. If you had told me those statistics in any other context, I would be terrified of what was happening within that poor Git repository — which is to say nothing of a very complicated stack relative to an application of that size. Why do you need all this observability for literally one hundred internal users? Again, there might be a very reasonable answer that we are not privy to. Most of the techniques discussed in this essay — like using as an index file rather than a monolith — have been fully integrated into the meta at this point. But there's one interesting bit about using the repository as the main source of truth, and in particular building a lot of tooling around things like downloading Slack discussions or other bits of exogenous data so they can be stored at a repository level. My initial reaction was one of revulsion. Again, that poor, poor Git repository. But in a world where you're optimizing for throughput, getting to eliminate network calls and MCP makes a lot of sense — though I can't help but feel like storing things as flat files as opposed to throwing it in a SQLite database or something a little bit more ergonomic would make more sense. 1 See insourcing-your-data-warehouse . The essay hints at, but does not outright discuss, failure modes. They talk about the rough harness for continuous improvement and paying down of technical debt, as well as how to reproduce and fix bugs, but comparatively little about the circumstances by which those bugs and poor patterns are introduced in the first place. Again, I get it. It's an intellectual exercise, and I'm certainly not one to suggest that human-written code is immune from bugs and poor abstractions. But this does feel a little bit like Synecdoche, New York — an intellectual meta-exercise that demands just as much attention and care and fostering as the real thing. At which point one must ask themselves: why bother?

1 views
Justin Duke 2 weeks ago

The death of software, the A24 of software

Steven Sinofsky recently published Death of Software. Nah. , arguing via historical case studies that AI will not kill software any more than previous technological shifts killed their respective incumbents. I agree with the headline thesis. But I think his media analogy deserves a sharper look, because it actually complicates his optimism in ways worth taking seriously. He writes that there is "vastly more media today than there was 25 years ago," pointing to streaming as evidence that disruption creates abundance rather than destruction. This is telling, because I agree with both sides of the glass: The shift to streaming has not killed media. But it has, to put it mildly, made the aggregate quality of the product worse, and in doing so shifted the value generated away from creative labor and towards platforms and capital. Warner Bros. is, to hear some people say it, the last great conventional studio producing consistently risky and high-quality work that advances the medium forward; Netflix, Apple, et al do put out some extremely great stuff, but the vast majority of their budget goes to things like Red Notice — films designed with their audiences' revealed preferences (i.e., browsing their phone while the film is on) in mind. And yet! The greatest studio of the past decade was also a studio founded in, essentially, the past decade — A24, in 2012. I think it's uncontroversial to say that no other studio has had a higher batting average, and they've done it the right way: very pro-auteur, very fiscally disciplined, focusing more on an overall portfolio brand and strong relationships than the need for Yet Another Tentpole Franchise. A24 didn't succeed despite the streaming era — they succeeded because of it. The explosion of mediocre content created a vacuum for taste, for curation, for a brand that stood for something. When everything is abundant and most of it is forgettable, the scarce thing is discernment . The interesting question isn't "will there be more software?" — it's who captures the value, and what excellence looks like in a world of abundance. (Kicker: A24 just took a round of additional funding from Thrive Capital last year. The market, it seems, agrees.) There will be more software, not less, in the future. The quality of that software — as defined by the heuristics of yesteryear — will be lower.

1 views
Justin Duke 2 weeks ago

One status field per model

Any sufficiently old application starts to succumb to a pernicious form of technical debt known in street parlance as shitty data modeling . Sometimes this manifests as the god object: a single model that represents the user, and the settings, and the DNS configuration, and twelve other things. Sometimes this comes in the form of a table (or multiple tables) where the initial set of data modeling concerns in the early goings of the project don't quite match the reality discovered along the way, and a series of subtle mismatches collide with each other in the same way that subtle mismatches between tectonic plates do. Data models, unlike other areas of tech debt, are correctly scary to refactor. Even in Django — an application framework with really robust, mature migration tooling — reshaping data in production is non-trivial. The weight associated with even relatively simple schema changes can be so overwhelming as to forever dissuade a would-be re-architect from making things right. Therefore, it is that much more important to spend the extra mental energy early on to make sure, whenever possible, your data model is a roughly correct one, and to course correct early when it isn't. There are many ways to do this, and the goal of describing a virtuous data model in its entirety is too large and broad a problem for this measly little essay. Instead, I want to share a heuristic that I have found particularly useful — one which is summed up, as many of my blog posts are, in the title. Every data model must have at most one status field. If you're thinking about making a change such that a model has more than one status field, you have the wrong data model. Let me illustrate via self-flagellation and talk about Buttondown's own problematic model: the object. The object has three status fields within its lush, expansive confines: This is incorrect. We should have created standalone models for the sending domain and hosting domain, each with a simple field of its own, and drawn foreign keys from the onto those. We did not do this, because at the time it felt like overkill. And so. You pay the price — not in any one specific bug, but in weirdness , in the difficulty of reasoning about the code. Is there a meaningful difference between an status and a of for an active newsletter, versus an status and a of ? What queries should return which combinations? The confusion compounds. Again, I know this sounds trivial. But every good data model has syntactic sugar around the state machine, and every good state machine has a unary representation of its state. 1 See also: enums . A field (the normal one)

0 views
Justin Duke 2 weeks ago

RIP XSLT

If you visit in your browser, you will see that I have done the Cool Kid thing and added styling to it via XSLT. This was originally something I've wanted to do for years — both for my own blog and for Buttondown — and I finally got around to it, only to immediately learn that XSLT support is being deprecated . I can't even be particularly upset about the decision. It makes sense and is entirely rational — the security story around libxslt is grim, and if only 0.02% of page loads use XSLT, the cost of maintaining it outweighs the benefit. And yet. It is nonetheless a bummer. So enjoy those beautiful RSS feeds until November 17, 2026, when they go away forever. Or perhaps I will go with the polyfill option suggested by Chrome to hold on to them for a little bit longer — until that too is inevitably deprecated.

0 views
Justin Duke 3 weeks ago

January, 2026

This is not, if I'm being honest, the simple, structured start to 2026 that I had in mind. Rigor and early workouts have been replaced by pulled floors and sheets of ice. After spending a lovely week in Park City with the Third South folks, we came back home and had 12 hours of respite until, board by board, our floors were pulled up for replacement. The good news — it's always important to focus on the good news — is that the damage was less extensive than we expected. The bad news, because there is always bad news to go along with good news, is that this week we learned that we would be hit by an ice storm. And so, we decamped at my parents' house, the same one that I spent my formative years reading Redwall and playing Final Fantasy even though my parents thought I was asleep. Haley and I are, to a fault, creatures of habit and routine, and it would be a lie to say that the past two weeks haven't been draining in the same way a day spent in transit is draining. We miss our house. We miss our things. For Lucy, though, this has been a permanent vacation — a whirlwind of delight that started in Utah and has extended without ceasing. In the span of two weeks, she went from walking, if she remembered about it, to quite literally sprinting through the house, chasing anything and everything she wanted. It is fascinating to watch a toddler learn about the world. There is a transparency to them, and her effortless and endless delight in discovering the cause and effect of things that I have cynically grown to consider mundane — such as a light switch — more than makes up for a little bit of inclement weather. I haven't been working much. I haven't been writing much. I haven't been reading much. I have been watching my daughter discover the world and run headlong into it, hands outstretched. | Post | Genre | | ------------------------------------------------------------------------------------------------ | ---------- | | Tabula Rasa (Vol. 1) | Book | | Levels of the Game | Book | | A Shadow Intelligence | Book | | Cameraperson | Film | | Eternity | Film | | Go | Film | | Tinker Tailor Soldier Spy (2011) | Film | | The Pigeon Tunnel | Film | | Ocean's Twelve | Film | | Uses (January 2026) | Personal | | Terragon, Conductor, PyCharm | Technology | | Migrating to PlanetScale | Technology | | Refactoring a product is tricky | Technology | | Every model should have a notes field | Technology | | Pure strategy | Technology | | The Diplomat (Season 2) | Television |

0 views
Justin Duke 3 weeks ago

The Hudsucker Proxy

When does an indulgence become sour? I ask this because indulgence is the word that most immediately comes to mind at the finish of this film. And largely in a negative way. Closer to flippancy than resplendence. And yet the same word can be leveled at the last film I watched. Ocean's 12. A movie which I thought, on the merits, was not exactly good, and yet I had a good time with it. It's a weird pair of films to compare. The filmmaker is perhaps less so; it's not unreasonable to consider Soderbergh and the Coens in the same relative stratosphere, both in terms of longevity and breadth of work. HudSucker Proxy was made very early on by the Coens. Ocean's 12 was made perhaps at the peak of Soderbergh's cache. Where and for this being such an early film in their canon, the technical achievement is remarkable. This film looks and feels beautiful and striking in a way that I've described as obvious, but not unwelcome. One of the reasons why, deep down, I love the Oceans film so much is because you get the very strong sense that Soderbergh is cooking up for you the most delicious and expensive meal in the world. It is a project where he is alchemizing his pleasures and giving them to you, letting you get swept a lot in the exact same way he would be, and that is very much not the goal of The Hudsucker Proxy. Frankly, it's difficult to tell what the goal of the film is beyond technical wonderment and perhaps a skewering of lesser film. One gets the sense that the Coens are making fun rather than having it. The pastiche here runs the gamut from boardroom drama to His Girl Friday, and speedruns the list of clichés, many of which are funny in isolation. The script is, if incoherent, extremely clever, peppered with one-liners and callbacks. But... you leave the movie with a kind of unfulfillment. The Coens can both be humanistic, but I simply do not care about any single member of this group. What's worse, I'm not sure I'm supposed to.

0 views
Justin Duke 3 weeks ago

Brief notes on migrating to Postgres-backed jobs

It seems premature to talk about a migration that is only halfway done, even if it's the hard half that's done — but I think there's something useful in documenting the why and how of a transition while you're still in the thick of it, before the revisionist history of completion sets in. Early last year, we built out a system for running background jobs directly against Postgres within Django. This very quickly got abstracted out into a generic task runner — shout out to Brandur and many other people who have been beating this drum for a while. And as far as I can tell, this concept of shifting away from Redis and other less-durable caches for job infrastructure is regaining steam on the Rails side of the ecosystem, too. The reason we did it was mostly for ergonomics around graceful batch processing. It is significantly easier to write a poller in Django for stuff backed by the ORM than it is to try and extend RQ or any of the other task runner options that are Redis-friendly. Django gives you migrations, querysets, admin visibility, transactional guarantees — all for free, all without another moving part. And as we started using it and it proved stable, we slowly moved more and more things over to it. At the time of this writing, around half of our jobs by quantity — which represent around two-thirds by overall volume — have been migrated over from RQ onto this system. This is slightly ironic given that we also last year released django-rq-cron , a library that, if I have my druthers, we will no longer need. Fewer moving parts is the watchword. We're removing spindles from the system and getting closer and closer to a simple, portable, and legible stack of infrastructure.

1 views
Justin Duke 3 weeks ago

LLM as advance team

My first foray into using git worktree-style development — spinning up multiple workspaces and having LLM agents attack different problems in parallel — was a failure. I found myself simultaneously exhausted and unproductive, the equivalent of doing a circuit course in Bean Boots. The entire thing felt good in the pernicious way increasingly familiar to developers using these sorts of tools, where you can delude yourself into believing that noise and diffs indicate forward progress, even though at the end of the day you've run a marathon and out of the twenty PRs you shipped, five of them are buggy and three of them are relevant to the actual important thing you should be doing. I am back using worktrees and having a very good time doing so — with a slightly different mindset, which is to treat them not as parallel agents but as an advance team . If I've got five or six things I know I want to do in a given focus block, the first thing I'll do is open up five or six different worktrees within Conductor and spend two or three minutes dictating via Aqua high-level goals and linking in any relevant Linear, Sentry, or Slack data. Then Opus goes off and whirs for thirty minutes, and I don't pay any attention until I decide it's time to pick up that task and that task only. What I find is never complete, but always useful. Bug fixes might have correctly diagnosed the root cause but implemented the fix or regression test in a way that I find clumsy. Feature branches might have done an awful job wiring together the architecture but given me a couple Storybooks that I can click around and better refine my thoughts with. Refactors give me a sense of the scope and a few regression tests that were missing. More often than not, the branch spun up by Conductor never even makes it to GitHub, let alone the main branch. It's purely a first draft from an overstimulated and undercompensated robotic junior colleague — but that is value additive. I couldn't tell you what the dollar amount of that value is, but right now it's certainly greater than zero. And the tax on my workflow is minimal at best.

0 views
Justin Duke 1 months ago

Ocean's Twelve

This might not be the perfect time or whatever to talk about it but I've been doing my homework and I'd really like to play a more central role this time around. I consider this film's prequel as close as you can get to a perfect film. Ocean's Eleven is a movie that knew exactly what its goal was—to be as relentlessly and easily entertaining and pleasurable as possible—and succeeds in doing so more than any other movie with similar ambitions. The sequel to such an endeavor has an inherently impossible task ahead of it. I held off on watching this for a long time. Partially because it seemed unnecessary; why would I watch a sequel when I could just watch the original again? And partially because it is poorly reviewed, in the same way many of Soderbergh's works are. The phrase self-satisfied and bizarrely sloppy comes up a lot in reviews of his early-aughts output. I think it's fair to be upset. The heist in this movie is, to a certain extent, on us , the viewer, for sitting down and thinking that we were getting treated to a heist movie when instead what Soderbergh wants to give us is two hours' time in the companionship of people who are effortlessly beautiful and charming. This film is filled with metatext: the Julia Roberts bit, Clooney's age, Matt Damon trying to become a leader. You can accuse some of the smaller bits as rehash, and I agree with the central complaint that a plot twist which invalidates everything we've seen for the preceding hour is unsatisfying. At the end of the day, I didn't care that much because I enjoyed watching my buddies having fun. It is a lesser film; it still succeeds in its goals, with grace and panache. One more thing: this movie hints at creating a slightly larger mythos, in the same way John Wick eventually created an extended universe unto itself. While part of me would have loved to see six more of these films, I think the key to their enduring charm is that they are a snapshot in wide frame. The warmest and happiest scenes involve as many people as possible, whereas the formula of John Wick really only requires a single protagonist and an endless barrage of faceless, unnamed fodder.

0 views
Justin Duke 1 months ago

Uses (January 2026)

A few updates to my Uses page: Software : I switched from Cursor to Zed . Zed is fast, native, and the editing experience is snappier. I've also been using Conductor to run coding agents in parallel, which has been a game-changer for working across multiple tasks at once. Miscellaneous : Three new additions: Bronson tees . Bronson is a Chinese workwear company that I suspect will, in due time, fold. In the meantime, there is no better value for your dollar if you want extremely high-quality tees. - Magic Sleeve . A silly workmat that seems gratuitous until you travel with it. - Encased MagSafe mount . I throw these everywhere as an easy way to mount your phone: one next to the squat rack, one in the living room for FaceTiming with Apple TV, one in the kitchen for keeping a recipe on Mela handy.

0 views
Justin Duke 1 months ago

Migrating to PlanetScale

First off, huge amount of credit to Mati for migrating our database to PlanetScale. I highly recommend reading the blog post . He does a good job talking about the boring stuff, which is to say, talking about the interesting stuff when reflecting on how this project went. Three other things come to mind, from my side of the fence: This is the largest infrastructural project that Buttondown has done which I have not been a part of — a fact that is both surreal and, frankly, very cool. 2. Mati hinted at this in the blog post, but outside of the obvious quantitative improvements brought by PlanetScale, the insights dashboard is worth its weight in gold. Being able to very easily see problematic queries and fix them has drastically changed our database posture, perhaps even more than the change in hardware itself. I find myself staring at the insights dashboard and thinking about what other parts of the infrastructure — CI tests, outbound SMTP, etc. — we need this for as well. 3. PlanetScale folks recommended we start using sqlcommenter to annotate our queries with the API routes that generated them. This was a good suggestion, but that package has the same malnourishment problem that so many Google-born OSS projects do: you end up pulling in a lot of dependencies, including some very old ones, to execute what is essentially 50 lines of code. Rather than do that, I asked Mr. Claude to vend the relevant snippets into a middleware that we can plop into our codebase. It is below:

0 views
Justin Duke 1 months ago

Levels of the Game

It seems fortuitous that my McPhee reading spree coincided with having watched Challengers . Like Challengers did sixty years after the fact, Levels of the Game uses tennis as an object of fascination in its own right—see also Infinite Jest and how much of that book, indeed all of DFW's worldview, was shaped by the relative weirdness of the tennis circuit compared to its team-based sport brethren. But even more than that, I'm interested in it as a canvas to explore systemic issues. Challengers touches on class nominally, but Guadagnino is at the end of the day much more interested in the love triangle that dominates the film, and in the idea of competition as a pure entity. McPhee has no problem dispensing with subtext and speaking plainly about the differences between his twin protagonists: one is white and comes from a solidly middle-class background; the other is black and comes from a solidly lower-class one. Side note: Arthur Ashe was born in Richmond, Virginia, and I live one block away from a boulevard named in his honor. You can credibly accuse Richmond of using Ashe as a bulwark against criticism, given how many of its other heroes are old white Confederates. But Ashe did in fact grow up here, and this book is not sparing in its description of how white Richmond rejected him. McPhee is not really interested in competition the way Guadagnino is; he describes Ashe and Graebner less like fierce competitors and more like two rival members of the same French New Wave. Part of this is truth—they were literal teammates playing in the Davis Cup together. That aspect of tennis, somewhat alien to me, is interesting in its own right. And while we know from the future that Ashe emerged as the superior and more exemplary player, McPhee is more interested in talking about form and style than raw prowess. This is a brief book—really just a snapshot of a single day—and as such it never outstays its welcome. By the last few passages, McPhee has perhaps run out of novel ways to describe a backhand. But it's a good read and a lot of fun. It speaks about style and grace and athletics, and it elevates the form of sport in such a way that sixty years after its original publication, it still feels not just prescient but modern.

0 views
Justin Duke 1 months ago

Eternity

Love isn't just one happy moment, right? It's a million. And it's bickering in the car, and supporting someone when they need it, and it's growing together, and looking after each other. It can't be denied that this movie isn't really, really funny. Some of the runners, such as the Korean War bit, or the pretzel bit, were just great laugh lines from a writing team — and I think the film's willingness and steadfastness not to engage in the minutia of the framing device and its acting mechanics was a very smart choice, because that's really not what the movie is interested in whatsoever. In general, I think this movie wa sa success and I attribute that success to the script's unwillingness to take the easy way out. I appreciated that all three vertices in our little love triangle are fairly flawed in different ways: The movie fades in quality in the few instances where it stoops to melodrama - mostly in the middle act, which any viewer is going to know beat by beat, and therefore goes on entirely too long and with way too few laugh lines. Given the audaciousness of the framing device, the movie did not quite take full advantage of its visual possibilities. The little sequence of Elizabeth Olsen gaping between eternities was legitimately cool, as long as you didn't think about it particularly hard — but the most beautiful and interesting parts of the film were in the junctions themselves, rather than the paradises. (Perhaps that is a deliberate metaphor.) The movie that comes most readily to mind, having watched Eternity, is Palm Springs : also a high-concept rom-com that never takes itself too seriously and has legitimately hilarious moments 1 And a bit of sloppiness. which, in a different world, probably could have been a massive box office success — if its goal was, at all, to land in the box office. This is a vehicle largely for Miles Teller and Elizabeth Olsen to be charming: and while they share almost zero chemistry, their individual charisma makes up for it, as does a great collection of complimentary performances from their surrounding cast. (The movie also owes a lot to The Good Place, of course, but Eternal Sunshine of the Spotless Mind which is perhaps winkingly echoed in the title.) This is not high art — nor is it pablum. I want more of these films! Elizabeth Olsen isn't given much to work with, but the text of her character has a little bit of scuminess, and she sells the pathos strongly enough. - Miles Teller's character is, for sure, guilty of everything that his rival accuses him of - in the same way that we all have a little self-interest burrowed deep in our heart. - And Callum Turner's character is clearly has some anger problems and a bit of subtextual one-dimensionality — the traits that you do ignore as a 25-year-old newlywed, but would grate on you after 65 years of marriage.

0 views
Justin Duke 1 months ago

Cameraperson

It seems fitting that, to close out the year I finally watched Koyaanisqatsi, I also got to watch Cameraperson — which is in many ways, and none of them dismissive or demeaning to either film, a funhouse mirror of its antecedent. Koyaanisqatsi is a film that's very interested in collage and rapidity, and at times felt like a sensory HIIT where you feel the push and the rest and the push and the rest, and the cavalcade of stock washes over you. Cameraperson is an antithesis — is collage, yes, assembled from around a dozen or so vignettes, all of them quiet, both literally and figuratively, but meticulously placed so that you, the viewer, are given the space and time to form the connections yourself. The dialogue in this film is spartan; the visuals are arresting and deliberate. Nothing feels wasteful. Kristen Johnson is very interested in relationships between storytelling and memory and between identity and witness. She is interested in the vastness and fragility of human existence. She does not have many answers; she wants you to help her find them. The best movies take you places: sometimes that is into someone's head, sometimes that is into a Nigerian NICU. She takes you there quietly, never flinching, never letting go of your hand. I don't know what else to say. I think it's somewhat disingenuous to call it an entertaining movie, but it's certainly an enchanting one, and I am different and better for having watched it.

0 views
Justin Duke 2 months ago

Avoid single-use variables

After all these years programming, it's fun to catch myself changing my style preferences in real-time. Here's a change I find myself getting very aggressive about: eliminating single-use variables (or functions, or what have you). A trivial example, of course. A slightly less trivial one: (Usual caveats apply: there are cases where you shouldn't do this, ie for performance or testing ergonomics, 1 I am, for the record, completely okay with shaping how you organize your code in service of testing as long as it's not gross — and everyone, deep down, knows when it's gross. , and also I don't think lines should be this short it's just hard to show a diff in a blog post.) But this slight tweak helps me in a few ways: Reveals complexity . A lot of seemingly-hairy methods can be unwound into a very straightforward sequence of nested transformations. 2. Improves readability . When reviewing code, I often find myself first understanding the surface-level logic of how things plug together and then diving into the details of how things work. This significantly ameliorates the effort involved in the first step.

0 views