The AI pair became a teammate — and it changed how we run reviews
Autocomplete was a productivity tweak. Agents that open pull requests are an org chart question.
Field notes from our engineers, designers and support team — on modern web development, the tools we've adopted (and dropped), AI in the delivery pipeline, and the culture that decides whether any of it ships.
Autocomplete was a productivity tweak. Agents that open pull requests are an org chart question. Here's how our delivery process actually changed once the model stopped suggesting lines and started proposing changes.
Read the article →Practical write-ups on the web platform, our toolchain, and how teams work in 2026 — written by the people doing the work, not a content agency.
Autocomplete was a productivity tweak. Agents that open pull requests are an org chart question.
Two years ago the honest description of AI in our workflow was "a very fast autocomplete." It saved keystrokes. It did not change how the team worked. That is no longer a fair description, and pretending otherwise has become the expensive option.
The shift happened when the tooling stopped operating inside a single file and started operating across a repository — reading the tests, running them, opening a branch, and putting up a change with a description attached. The moment a machine can produce a pull request, your review process is the thing that decides whether that is an asset or a liability.
Our throughput went up, but that was the least interesting part. Three structural things changed:
The model is very good at answering the question you asked. It has no opinion about whether it was the right question.
We wrote these down after a few months of getting it wrong, and they've held up:
Junior engineers get less out of this than the tooling vendors imply, and more than the pessimists claim. The thing that atrophies is not typing — it's the debugging instinct you build by being stuck for two hours. We deliberately protect that: some work is assigned as "no agent", not because it's more efficient, but because the person needs to have been there.
The teams we see struggling are the ones that treated this as a headcount question rather than a process question. The teams doing well treated it as what it is: a very fast contributor with no memory of your last incident, no relationship with your client, and no stake in whether the thing still works in eighteen months. Review accordingly.
A pile of things we used to install libraries for are now one CSS property. Our bundles shrank because we deleted code, not because we optimised it.
The most effective performance work we did last year was not optimisation. It was deletion. We went through our component library and removed dependencies that the browser had quietly made redundant, and the bundle got smaller on its own.
If you last audited your stack a few years ago, this list is worth an afternoon:
:has() removed a genuinely embarrassing amount of JavaScript whose only job was adding a class to a parent element.text-wrap: balance fixed a category of headline-typography complaints that used to come back from designers every single project.Every dependency is a small debt with an interest rate. The platform is the only thing on the page nobody has to maintain for you.
Our rule is simple and slightly annoying to apply: before adding a package, someone has to check whether the platform does it, and check the current browser support baseline rather than the support table they remember from an old project. Support data moves faster than habits do — most of the "we can't use that yet" beliefs on a given team are two or three years stale.
The counterweight is honest: frameworks still earn their place. Complex state, forms with real validation logic, anything with heavy interactivity — we're not hand-rolling that out of principle. What changed is the default. We start from the platform and add a framework when the complexity justifies it, rather than starting from a framework and never revisiting.
A lot of our clients' customers are on mid-range Android phones, on mobile data, sometimes on a congested network. A 400KB JavaScript bundle isn't an abstract metric there — it's a bounce. When we cut payload, the conversion numbers move in a way that's visible in the client's own dashboard, not just in a synthetic score. That's the argument that actually gets budget approved for performance work: not the score, the sales.
When the feedback loop drops from thirty seconds to under one, developers stop context-switching. That's the whole benefit, and it's bigger than it sounds.
There's a well-worn observation that the difference between a one-second and a ten-second feedback loop is not ten times — it's the difference between staying in a task and leaving it. Ten seconds is long enough to check a message. Once you've checked the message, the cost isn't ten seconds anymore.
That's the honest case for the wave of JavaScript tooling rewritten in Rust and Go. The benchmarks are impressive in a way that borders on marketing, but the thing we actually noticed was behavioural: people stopped tabbing away while waiting for a rebuild.
Fast tools don't make developers faster. They make developers present.
Newer tools have thinner ecosystems. We hit two plugins that didn't exist yet and one subtle difference in how a transform handled an edge case in a legacy client project. Our approach: migrate new projects first, leave stable legacy projects alone, and never migrate a toolchain in the same sprint as a feature deadline. A build system change that breaks a release is a self-inflicted incident, and "it's faster now" is a poor thing to say during one.
One more note, because it's the part teams skip: measure before you migrate. We recorded our actual cold start, hot reload and CI times before touching anything. Half the value of the migration was being able to show the numbers to people who had to approve the disruption.
The gap between a beautiful Figma file and a shipped interface is where projects quietly lose their quality. Someone has to own that gap.
Every agency has seen this failure mode. The design file is excellent. The build is competent. The shipped product is somehow worse than both — spacing drifts, the empty states were never designed, the loading state is a spinner someone added at the last minute, the hover feels wrong on touch devices.
Nobody did anything wrong. The gap between design and engineering was simply nobody's job.
A design engineer is not a designer who can code a bit, and not an engineer with good taste. It's a person who owns the translation layer:
A design system isn't a component library. It's an agreement, and someone has to be responsible for keeping it.
On a small team, this is a hat, not a headcount. What matters is that the responsibility is named and assigned. The failure isn't the absence of a job title — it's the assumption that the translation happens automatically because both sides are competent.
The practical test: ask your team who decides what an error state looks like. If the answer is "whoever gets there first," you've found the gap. Assign it to someone, give them the authority to say no, and the quality gap closes within a project or two.
It moved from an ethical argument to a commercial and legal one. The work didn't change — the deadline did.
For most of the last decade, accessibility on commercial projects was won by persuasion. Someone on the team cared, made the case, and either got budget or didn't. That argument is getting easier to win, for reasons that have nothing to do with anyone becoming more virtuous.
Regulatory pressure has increased in several major markets, enterprise procurement now routinely asks for an accessibility statement before signing, and the reputational cost of a public complaint is real. If your client sells to government, education, healthcare or large corporates anywhere, this shows up as a question in a form long before it shows up as a lawsuit.
When we audit inherited sites, the same handful of issues account for the overwhelming majority of failures — and none of them are hard:
alt="image1.jpg" is a real thing we find often).divs that can't be reached or dismissed by keyboard.Nearly everything we find in an audit would have cost nothing to prevent and takes real money to retrofit.
We stopped treating this as an audit phase. Contrast is checked when the palette is chosen, not after the site is built. Every interactive component gets a keyboard pass during development. Automated checks run in CI to catch regressions — while being clear internally that automation catches maybe a third of real issues, and the rest needs a person with a keyboard and a screen reader.
And the framing we use with clients isn't compliance. It's reach. Accessible sites work better on cheap phones, in bright sunlight, on flaky connections, for older users, and for anyone using your product one-handed while doing something else — which is most people, most of the time. The disability case is the right one morally; the "your interface is used in bad conditions by tired people" case is the one that gets it into scope.
Treating the network as an enhancement rather than a requirement is not an exotic architecture here. It's the realistic one.
Most web applications are built on an assumption that is false for a large share of their users: that the network is there, and fast. Every interaction becomes a request, every request becomes a spinner, and every spinner on a weak connection becomes a user deciding your product is broken.
Local-first flips the default. The application reads and writes to local storage on the device, and synchronisation with the server happens in the background. The interface responds instantly because it isn't waiting for anyone.
Optimistic UI is a trick. Local-first is an architecture. The difference shows up the first time someone spends twenty minutes offline.
This is not free, and we've talked clients out of it. You inherit real distributed-systems problems: conflict resolution when two devices edit the same record, schema migrations on data sitting in a browser you don't control, and a much harder debugging story because state now lives in a place you can't query from the server.
Our rule of thumb: if the app is mostly read-only, or every user session is short and connected, a good cache is enough and you should stop there. If people work in your app — writing, editing, returning to it repeatedly — and any of them are on a poor connection, local-first pays for itself. Anything in between deserves a conversation, not a default.
The teams that struggled with remote work didn't lack software. They lacked the habit of writing decisions down.
The remote-versus-office argument has calmed down into something more practical: most teams are distributed at least some of the time, and the question is no longer whether it works but what makes it work. In our experience the answer is boring and it is not a tool.
Teams that struggle share one trait: important decisions live in conversations. A call happened, three people agreed, and the reasoning exists only in their heads. Everyone else finds out later, usually by building the wrong thing.
Writing is slower for the author and dramatically faster for everyone else. That trade is the entire discipline.
We over-corrected into pure async and lost something real. New team members onboard far slower without incidental conversation, and difficult feedback delivered in writing reliably lands harder than intended. We now deliberately schedule the things that need presence — onboarding, feedback, and the messy early phase of a project where nobody knows the shape yet — and keep everything else in writing.
Copying a Silicon Valley product playbook here fails in specific, predictable ways. Most of them are technical.
We get asked to "build something like" a well-known international product fairly often. It's usually a reasonable request with an unreasonable assumption underneath: that the same design decisions will hold here. They frequently don't, and the reasons are concrete enough to design around.
The constraints here produce better engineering. A product that works on a weak connection on a cheap phone works everywhere.
The Nigerian technology sector didn't grow because it imitated well. It grew because a generation of teams solved local problems — payments, logistics, identity, access to credit — that global products had no incentive to solve. That's still where the interesting work is, and the engineering talent to do it is here.
Our position with clients is straightforward: build for the user you actually have. The version of your product that survives a 3G connection, a ₦-conscious data plan and a five-year-old Android phone is a stronger product than the one that only looks good in a demo on office WiFi.
Novelty has a maintenance bill, and the client pays it eighteen months after the agency has moved on.
A recurring job for us is inheriting a site built two or three years ago on a stack chosen because it was interesting. The original team is gone. The framework had a breaking major release. The obscure hosting platform changed its pricing. Nothing is broken yet, and everything is expensive to touch.
This is not an argument for building badly. It's an argument about where to spend your limited novelty budget.
Every project gets a small number of "interesting" choices — the places where a new approach genuinely creates advantage. Everything else gets the most boring option that works. On a typical client build, the interesting part is the product logic that makes the business money. The database, the hosting, the CMS and the deployment pipeline should be so dull that a competent developer who has never met us can pick them up.
The client doesn't own your architecture decisions. They own the consequences.
We treat the handover package as part of the deliverable, not a courtesy at the end: a README that explains how to run it, documented environment variables, a written deployment process, and a recorded walkthrough. It takes a day. It's the difference between a client who can grow the thing and a client who is locked in — and being the vendor who's easy to leave turns out to be an excellent reason to be kept.
New articles, things we've broken and fixed, and the tooling changes worth your attention — sent when there's something to say.