Archive for August, 2026

On not becoming a cyborg

I was a voracious reader as a kid, and I noticed a funny phenomenon: whatever writer I had most recently read, my next paper for English class would sound like them. If I read a bunch of Stephen King, I’d sound like Stephen King. If I read the Narnia series, I’d sound like C.S. Lewis (complete with British spellings and antiquated turns of phrase).

I know I’ve carried this tic into adulthood. As much as I’ve tried to find my own consistent voice in my blog writing, I know that whatever author I last read is sitting there in the back of my head, weighing in on my word choices and sentence rhythm.

So imagine my alarm when, in my last blog post, the word “load-bearing” popped into my head. I used scare quotes and even called it out as a “Claude-ism,” but I couldn’t fight the feeling that it was le mot juste. I couldn’t find another phrase that felt more appropriate. And as Stephen King said, if you have to hunt for a word in the thesaurus, it’s the wrong word, so I went with it.

The most recent book I’ve been reading is Anna Karenina, and it would be much more flattering to my ego if Tolstoy’s short, punchy phrases (at least in the translation I’m reading) made their way into my writing. And maybe they did. But then there’s the obvious Claude loanword in the middle of it, sticking out like an blemish.

More and more I’ve found my brain alighting on these phrases: “load-bearing,” “belt-and-suspenders,” “earns its keep.” As much as I try to bat them away, they keep coming back. It shouldn’t be surprising: my job these days is effectively to shepherd agents, skimming their meandering robo-prose and trying to steer them towards better code. But I can’t help but be disturbed at how much they’re rubbing off on me.

Immersion

I have a degree in linguistics, so I understand the power of immersion. I mean, you don’t need to have read Chomsky to notice things like people picking up on the accents of those around them. I have a cousin who lived in England for a decade, and her accent morphed into a Translatlantic, Katharine Hepburn-esque hybrid of Kansas drawl and Bristol pep, until eventually she moved back and it wore away.

For a more dramatic example, when I visit family in France, I find that after a weeklong adjustment my brain starts thinking in French, dreaming in French. (Much to the detriment of my mental capabilities, I’m sure, since my French is much worse than my English!)

I like to imagine that reading is slightly different, though. When you’re reading a good novel and you get lost in it, the author is in some sense hijacking your brain – you visualize their world, their words become your thoughts. And I find that this affects my writing much more than my speech, which makes sense: reading/writing and listening/speaking are governed by different parts of the brain.

I think it’s also possible to resist the environment you’re immersed in. I spent years in big-tech corporate culture, where every phrase is hedged and every expression flattened into a kind of mush designed to be as inoffensive as possible. I hated this voice, but of course I noticed it sneaking into my writing over the years (“on the other hand,” “to be fair,” etc.). If you read my pre-big-tech posts, they’re a lot more wild and freewheeling. I’d like to think I’m de-programming myself now and getting closer to that original voice.

(This is actually one of the reasons I stopped asking for reviews of my drafts. The reviewers were extremely helpful – thank you all! – but I found that it made me over-think things, try to anticipate every possible negative reaction from my audience, and thus land on a kind of “both-sides” journalism that made it unclear what I was trying to say in the first place.)

At some level though, I have to accept that “you are what you eat” when it comes to your linguistic diet. The evidence of accents, language, style, and rhetoric all point toward the current Borg of LLMs being an overwhelmingly poisoned water the more you dunk yourself in it. You can resist, you can cleanse yourself in other waters, but to some degree I worry that “resistance is futile” because they will infect your speech, your writing, and even your thoughts in ways that may even be invisible to you.

Stay human

So how do we fight this horde of agents colonizing our brains? In short: stay human, friends. Resist the whispering earring. Be jealous of your time and attention, and try to find other sources of artistic and intellectual sustenance than the chatbots. As doomed as it may feel, try to keep a part of yourself that is the tiny little village of Gauls holding off the Roman invaders. Whatever your inner sanctum is, protect it from AI infiltration.

One strategy is to be deliberate about where you expose yourself to AI influence. I recently vibe-coded a silly guitar app to try to help me get better at guitar solos – I play a phrase, the AI responds to my phrase, etc. I have no concerns about my guitar-playing becoming robotic, because I’m not trying to become Eddie Van Halen; I’m starting from such a low bar that even playing a halfway-decent riff in the pentatonic scale is challenging enough for me. Elsewhere, I’m more guarded.

For example, this is why I don’t use LLMs for any of my writing – not even to spellcheck. I’d rather my prose have all the warts of my sometimes-stilted sentences, my often too-esoteric word choice, my generous sprinkling of odd English idioms, than to give it even a whiff of Claudese.

Plus, I know that as soon as I ask the chatbot for help on even a single sentence (“can you help this land better?”), it’s all over for me. I’ve already given up on keeping my own voice in my code: I used to care a lot about variable names, function arrangement, which kinds of for-loops to use… and now I just accept Claude’s little stylistic quirks because they’re not worth fighting. If I let the same thing happen to my English writing, I’d feel like it had lost some indelible stamp of me-ness.

I think there’s also something to be said for showing a courtesy to others: be clear what’s LLM-authored and what’s not. In my workplace writing, I have a habit of bookending all my Claude quotes with clear markers – <blockquote>, <details>/<summary>, etc. – to make it clear which words are Nolan-words and which are robo-words. I certainly find myself tuning out a piece of writing if it appears to be AI-generated, and the worst thing is not knowing whether someone’s sent you their robot ghostwriting or not: it forces you to do this mental classification for everything you read. So I try to spare others this cognitive strain. (Unless my bots disobey me!)

Maybe this is a losing battle, though. Maybe the kids are already growing up with their brains shaped by AI the same way they were shaped by social media. Maybe the whispering earring will win, and it will hollow out some part of our brains the same way we all forgot how to navigate once Google Maps came out. (This certainly describes me.) But personally, I’m too much of a purist to go gentle into that good night. I plan to resist in my own small ways, even as I accept that generative AI has become an inescapable fact of everyday life and in particular the life of a programmer.

Even if it’s Quixotic, to me this quest is crucial, critical, all-important, foundational, paramount for the perseverance of the human spirit in the age of AI. The only thing it’s not is “load-bearing.”

The asteroid currently hitting frontend web development

A lot of the educators I admire in the frontend web space seem to be either bowing out or dialing back their efforts: Axel Rauschmayer, Salma Alam-Naylor, Josh W. Comeau, to name a few. Other well-known luminaries like Kent C. Dodds, Addy Osmani, Rachel Nabors, and Lydia Hallie have pivoted from talking about frontend development to talking about… well, take a wild guess.

I never made a living from talking about web dev, but I do have this blog, and I’ve done conference talks and podcasts about stuff I’m excited about. Recently for example, my passion was explaining all the ins and outs of CSS performance: how shadow DOM works, how the browser’s style engine works, the traps of CSS-in-JS, etc.

Just for kicks, I decided to ask Claude Sonnet how it would handle a Chrome trace with high “Style Calculation” costs. This is one of my favorite brain-teasers, because it’s the kind of thing that I saw web devs – even experienced ones! – trip up on over and over again. Claude gave a perfectly admirable answer:

Click to see

 

Prompt: Answer without searching the internet: you’re looking at a Chrome trace, and you see repeated spans of high Style cost but relatively low Layout cost. What kind of things do you look for in the webapp to optimize? What do you measure next?

What “Style” cost actually represents

The Style (Recalculate Style) phase is where the browser matches CSS selectors against the DOM and computes the final computed style for each affected element. It’s expensive independent of whether anything actually moves or resizes (that’s Layout’s job). So if Style is high but Layout is low, you’re likely paying for selector matching / invalidation over many elements, not for expensive geometry.

What to look for in the webapp

1. Selector complexity and count

  • Overly complex or deeply nested selectors (.a .b .c >; .d + .e), especially descendant combinators, force the engine to walk more of the DOM/ancestor chain per match attempt.
  • Universal selectors or attribute selectors ([data-foo="bar"]) applied broadly.
  • CSS-in-JS libraries that generate huge numbers of unique class names/rules (style recalculation has to consider the whole rule set).

2. Invalidation scope — this is usually the big one

  • A class/attribute/style toggle high up in the DOM tree (e.g., toggling a class on <body> or a top-level container) can force style recalculation on large subtrees even if only a few elements’ final styles change.
  • Check whether a single state change (e.g., a “theme” class, a “loading” class, a hover state) is invalidating far more elements than necessary. Chrome’s “Selector Stats” (enable in DevTools Performance settings) will show you which selectors are slow and how many elements they were tested against — this is the single most useful next measurement.

3. Frequency/amplification

  • Something re-triggering style recalc many times per frame — e.g., reading a style/layout property in a loop right after a class change (forces synchronous recalc), or a JS animation using .classList.toggle() or inline style updates on many nodes per rAF tick instead of batching.
  • Repeated small DOM mutations each forcing a style pass rather than being batched into one.

4. Inherited/computed property propagation

  • Changing an inherited property (like font-size or color on a common ancestor) forces recompute of all descendants’ computed styles, even without layout changes (e.g., color, visibility, cursor are Style-only, not Layout-triggering).
  • Custom properties (CSS variables) changed at a high-level scope — updating a --custom-prop on :root or a shared ancestor recomputes style for every element referencing it.

5. Shadow DOM / component boundaries

  • If using a framework with lots of shadow roots or scoped styles, check whether style recalculation is being needlessly repeated per component instance due to non-shared stylesheets.

What to measure next

  1. Enable “Selector Stats” in the Performance panel (gear icon → Enable selector stats). Re-record the trace. This gives you, per Recalculate Style event, the top slow selectors and match counts — that tells you directly which CSS rules and how many elements are responsible.
  2. Check the “Recalculate Style” event’s initiator/call stack in the trace to see what JS triggered it — a class toggle, a style.setProperty, an animation frame callback, etc.
  3. Count how many elements are affected per recalc — DevTools shows “Elements Affected” per Recalculate Style event in the summary pane. Compare that number to how many elements actually changed appearance — a big gap indicates over-broad invalidation.
  4. Look at where in the DOM the triggering mutation happens — is it near the root (broad invalidation) vs. scoped to a small subtree (narrow, cheap)?
  5. Check for forced synchronous style — look for “Forced reflow” purple warnings; even though you said Layout is low, sometimes a forced style read (getComputedStyle) interleaved with a DOM write shows up as many small back-to-back Style events rather than one batched one.

Typical fixes once you find the culprit

  • Scope class/state toggles to the smallest subtree possible instead of a common ancestor.
  • Simplify selectors, prefer single class selectors over descendant chains.
  • Move CSS custom property updates to the smallest scope needed.
  • Batch DOM/class changes so recalculation happens once, not per-mutation.
  • Use content-visibility: auto or contain: style layout on independent subtrees to limit invalidation scope.

These days, given a slow-loading website, even as someone who’s written about browser performance for years and who literally worked on a browser performance team, I would probably just chuck a Chrome trace at Claude Code and have it suggest improvements. In fact I’ve done this very thing in my day job and gotten some good results.

The future of frontend

So where does this leave frontend dev education? Not in a great place obviously; I wish I had some more uplifting answers for people who (like me) used to get a lot of fulfillment out of trying to raise the bar for frontend developers everywhere. I do have some guesses though, and I think the problem is still worth puzzling through.

The core question is where frontend development itself lands in this new era. Sadly it feels to me like there are several trends pointing against increased investment in frontend knowledge:

The frontend is less risky to just hand to an agent. If you’re using an agent to write a database migration, you probably want to put it through several rounds of AI code review, scrutinize it yourself, run it on staging first, etc. If you write a React component with an agent, though, then the risk of just yolo’ing it into production is (typically) much lower.

Note I’m not saying there are zero risks: the agent could mess up accessibility, it could cause an infinite loop that blocks users, etc. But in general, frontend code is a lot more ephemeral and replaceable than other types of code. So I expect many AI coders will feel comfortable just letting their agent handle it unsupervised (for better or worse).

DevExp is becoming less critical overall. A lot of the pre-LLM discussion in the frontend space was about ergonomics versus outcomes: “The ‘developer experience’ bait-and-switch” by Alex Russell is a great example. For another example, Svelte and Solid have long argued that their ergonomics lead to better outcomes than React: less code, better performance, etc.

Meanwhile, Cursor and Viget have blogged about migrating their codebases from Solid and Lit, respectively, to React. Since rewrites are less expensive with agents, this may be a bit surprising: why not move to the more performant/less verbose framework? The answer (explicitly in Cursor’s case, and I suspect for Viget as well) is of course: “the agents know React.” For better or worse, React is heavily overrepresented in the training weights, and “agent experience” is starting to matter more than developer experience.

Standards will catch up. I’ve been out of the web standards space for a couple years now, so this is pure speculation on my part. But I imagine that a lot of the efforts to improve the ergonomics of building websites – better CSS shorthands, terser JavaScript syntax, etc. – will become perceived as less important relative to things that actually move the needle on performance, capabilities, etc. At the end of the day, it’s just not very different for an agent to write 3 lines of CSS instead of 1, and anyway using the newer syntax might actually be harder because you have to coach the agent about things that aren’t in its training weights.

In some ways this shift might have already been underway. I remember several years ago at TPAC, well before the AI coding boom, I told someone on the Chrome team that I was working on web component standards. They responded that they weren’t interested in that, because those APIs only affected the developer experience and didn’t actually make the browser more capable (e.g. Project Fugu). That stuck with me because it’s a good point: APIs like shadow DOM and custom elements don’t give web developers any new superpowers; they just change where and how the code gets authored. I expect such things will move out of the spotlight as AI coding takes over.

This doesn’t mean that standards will disappear from the topics a frontend dev needs to keep up on, but I imagine it will become less about “use this newer syntax” (an evergreen source of material for conference talks and articles) and more “here are these emerging capabilities.” And I predict the latter group will be much smaller than the former, since they tend to be more contentious for standards bodies and there’s just a smaller pool of features to draw from.

Whither frontend education?

So how can the field of frontend education adapt to this hostile future? To avoid being utterly bleak, here are some positive directions I think it could go in.

First off, the agents still need to be educated about the big picture. Agents and harnesses seem to love writing React and specifically SPAs, but SPAs are not the answer to everything. You can burn a lot of tokens having an agent write a big complex SPA for your marketing site, and then fix all the bugs with the back button, focus state, performance, etc., or you can just choose an MPA framework like Astro or Eleventy and call it a day. Maybe these frameworks will be a bit harder for the agents to work with (specifically Astro since it kinda-sorta looks like React but isn’t), but my guess is that since you’re writing ~50% less code overall it won’t matter.

Second, making websites that work well for agents is probably going to be a fruitful endeavor for the near future. Vercel’s is-agentic is a good example of this. Ironically, this points back to good fundamentals that public-facing websites should have been doing anyway: server-rendered content, proper accessibility, page speed, etc. But if slapping the word “AI” on it is what gets people to care about it then hey, I’m all for it.

Note that I’m a little bit less sanguine about this second point, because I’m not sure the web even survives in its current form as agents become more of a thing. If I want to figure out how much it costs to fly from Seattle to Paris, I’d much rather ask an agent than click through an infuriating series of buttons on a slow-loading website. The only reason I can’t is because these websites explicitly block bots, or they don’t offer an MCP, but I’m sure there are several startups champing at the bit to solve that problem. So I’m not sure how sustainable the current situation is.

Third, we can offer consulting services for vibe-coded monstrosities. A massive amount of AI-generated frontend code is being pumped out right now, and some of it (to use a Claude-ism) will certainly become “load-bearing.” If those websites are slow, non-compliant, and riddled with security holes, then it may not be enough to ask the agent “fix my website pls.” There could be an opportunity here for real expertise, especially if there’s money on the line and the vibe coder’s knowledge of web development doesn’t extend past “websites are apps hosted on the internet.”

(I acknowledge that this is the shakiest of my three points, since I can totally imagine the next generation of “self-healing” web apps to eclipse the average expert in 2027 or 2028. But for the time being: yes, expertise still matters.)

Conclusion

The point of this post wasn’t to make myself feel better, or to dance on the graves of all the careers that have been upended by the recent AI boom. I’m a naturally gloomy person, and this post was me allowing myself to wallow in my own gloominess. I don’t take any pleasure from noting that the huge body of knowledge I’ve built up over the years has been rendered nearly obsolete, nor am I happy to see the same thing happen to my much-more-qualified peers. But pretending that it’s not happening isn’t a valid strategy either.

There’s a mood in some of the blogs I read these days along the lines of “I’m so tired of talking about AI” or “Please don’t mention AI to me ever again.” I’m sure some of this is a kind of world-weary, above-it-all air that feels good to wear as a badge of distinction. But I think a lot of it also comes from real fear. It’s scary to admit that you don’t know what’s going to happen in a year. It’s destabilizing to imagine your career going along a certain trajectory, serenely landing at retirement, and then to see everything upset just a few years from your goal.

The metaphor I’ve been using is that an asteroid just hit the earth, and we’re still surveying the wreckage. It’s hard to predict what will happen after the dust settles (let alone which tiny rodents will usher in the Age of Mammals!), but ignoring the crater altogether seems like the worst kind of denial. Another metaphor is covid: when covid hit, I don’t recall thinking, “Ugh, I’m so tired of talking about covid” – instead, I wanted to learn everything I could about viruses, epidemiology, masking, etc. This turned out to be a good idea, since covid was going to dominate my life for the next few years (at which point yes, I did finally get tired of talking about it!).

I hardly have a crystal ball, but this post was my attempt to think through where my most cherished field might be going in the future. I admit that I have a lot less skin in the game these days: I’m out of web standards, I don’t even work on the frontend at my current gig, and my blog has mostly been a lot of wailing and gnashing of teeth about AI rather than my usual menu of browsers, performance, accessibility, etc. That said, I still have a lot of love and respect for the frontend field, and I care about what happens to it in the future. It may be unrecognizable in just a few years, but if nothing else, I hope my peers find a way to navigate all these changes and to thrive in this weird new world.

You can just choose how many bugs you want now

There’s a bizarre aspect of AI coding that I’ve been trying to put my finger on, and I think it’s this: you can basically just decide how many bugs you want your software to have now.

We discovered this first with security, because of course security bugs are the most non-negotiable ones. But I think once the vulnpocalypse is over, we’ll start to turn our attention to other types of bugs: correctness, performance, accessibility, reliability, etc.

Some of us are already doing this. For example, I find myself spending a lot of time these days in code review, using tools like my triple-agent code review skill as well as Geoffrey Litt’s explain-diff skill.

My experience is that, in a complex system, you can basically find as many bugs as you ask the agents for. If you get tired of tackling bugs in the PR itself, have no fear: the agent will also find plenty of preexisting bugs for you to spend time on. The question is just when you want to stop and call it “done.”

Of course the bugs are not free to fix. There are still many tradeoffs to consider: lines-of-code versus likelihood that the bug will actually occur, the risk of introducing new bugs in a complex solution, the cost of making the code harder to understand for future reviewers or agents, etc. But the finding of the bugs has become nearly free, and AI agents are also capable of finding very subtle, intricate bugs that otherwise could have flown under the radar for years. What we do with this situation is the interesting question.

As many have noted, it doesn’t seem like the overall polish of software has increased since AI coding became a thing. If anything, there is just more junk and shovelware out there, of dubious quality. I think this demonstrates that, although our ability to find new bugs has skyrocketed, our overall tolerance for bugs has not changed. There are still plenty of winds blowing in the opposite direction:

  • The preventable problem paradox: if an incident occurs and you swoop in to fix it, you’re a hero. If you prevent the problem from ever occurring in the first place, then nobody knows you did anything.
  • Related: the pressure inside many software orgs is to keep shipping visible results, not to fine-tune something that already “works.” With AI coding this is magnified: management often assumes that 10x productivity means 10x more visible features and apps.
  • Laziness: one of the classic virtues of a programmer, this time working against us. I find myself mentally exhausted after slogging through the umpteenth AI-generated bug report, which requires me to carefully think through intricate aspects of the system and weigh the pros and cons of fixing it. I imagine many of my peers in the industry have just tuned out AI code reviews or only focus on the most critical findings.

Avoiding epicycles

There are a few ways we can approach this problem, though, that don’t require unending toil. One way is to set up the agent on a loop, e.g. “do a code review, fix all critical/high/medium issues, then repeat.” I find this can work, but it has a tendency to create lots of epicycles.

If you’re not familiar with the concept: in the pre-Copernican1 model of the solar system, ancient astronomers “fixed” miscalculations in the planets’ orbits by simply adding more circles to their movement. This improved the accuracy of the predictions, but at the cost of making the overall model more complicated. Obviously just saying “the earth moves around the sun” greatly simplifies the whole thing, but first you need the insight to make this simplification possible.

I’ve found that AI agents are pretty bad at such dramatic simplifications (in other words, “LLMs can’t jump”). They will happily build one epicycle per bug until the code is a spaghetti mess. So a valuable part of AI code review is still to ask questions like “How can we make this simpler?” and “Is there a fundamental flaw with the codebase that we should fix before we tackle this class of bugs?”

Another technique that works well is to have good tests. (Easier said than done!) For example, when I was playing around with vibe coding the W3C IndexedDB API, it became pretty clear to me that an agent could just grind through the test suite, and if it got close to 100% then I could be reasonably certain to have a bug-free implementation. But the only reason this works is because the Web Platform Tests are a phenomenally good test suite, honed by years of independent browser implementers discovering odd bugs and adding test cases for every unlikely scenario you can think of. Most companies, in their first-party codebases, could only dream of such a test suite.

I can imagine, though, that if you’re building a system from scratch, and especially if your goal is to reproduce the output of an existing system, then you can get pretty far by just putting all your effort into the test suite and then letting the agent go nuts on the rest. PGRust seems to be having some success with this.

A third technique is to just simplify your system design so that whole classes of bugs become impossible. For example, I’ve long been an advocate for multi-page apps (MPAs) over single-page apps (SPAs), just because, with MPAs, entire bug categories simply don’t exist: breaking the back button, losing scroll state, leaking client-side memory, improper accessibility during page navigations, etc.

Of course you lose some power with a simpler system versus a complex one, and maybe a reasonable answer is to deliberately choose a more complex system while also just fixing all the bugs. I feel though that this would still have a tendency towards epicycles, and I would much rather read (or debug!) a codebase built on simpler principles rather than one built on complex ones, even if they both have the same overall bug posture.

Conclusion

It’s become cliché to note that we’re in unprecedented times, and that everybody is figuring out what exactly software engineering is supposed to look like when robots can do a good chunk of what used to be “the job.” And yet, it still remains worth saying. Whatever I wrote in this blog post may become outdated in a matter of months, and the next 5 AI-related articles you read on Hacker News will probably argue 5 different opinions. It’s a cacophonous mess, and I have low confidence that I’ve figured out all the answers.

What I’ve defaulted to is focusing on the short term: i.e. what are agents good at today, and where can humans still provide some value. Some people are running with the assumption that all concerns of code quality, complexity, and maintainability will be swept away someday by agents that can easily manage whatever baroque legacy system they’re handed. That may end up true, but I’m not going to bet on it because I haven’t seen it yet. For now, I’m still concerned about things like the DRY and KISS principles, keeping a working theory of the code in my head (ala Peter Naur), and trying to steer the agent toward better code quality.

I do think it’s interesting though, that we have a much greater ability to tackle more and more subtle bugs than we ever had before. Maybe this will lead to a reliability renaissance, or maybe it will lead to the same overall bugginess, just with more apps and more features in each app. I know that my personal preference is for greater software craftsmanship, but it remains to be seen how the software industry as a whole will step up to this challenge.

Footnotes

1. Technically, Copernicus’s system still had epicycles, and only Kepler managed to get rid of them. I think in some ways this makes the analogy stronger: in software, a re-architecture sometimes isn’t obviously better right away, and only shows its value over time.