Home Services Work Pricing Blog Contact Get a Quote
The Plutterfot Journal

How the web is actually being built right now.

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.

#ai-assisted-delivery #web-platform #build-tooling #design-engineering #accessibility #naija-tech

Studio Pulse

LIVE
0 updates
Short notes from the Plutterfot team as we build. Longer thinking goes into the articles below.
the archive

Everything we've been thinking about.

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.

{ ai · review }
Tooling & AI

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.

Engineering team Read

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.

What actually changed for us

Our throughput went up, but that was the least interesting part. Three structural things changed:

  • Review became the bottleneck, and that's correct. Writing code stopped being the scarce resource. Judgement about whether code should exist became the scarce resource. We staffed accordingly — more time budgeted for review, less for first drafts.
  • Small, boring changes stopped being deferred. Dependency bumps, missing tests, inconsistent error handling, the accessibility fix everyone agreed with and nobody scheduled. These now get done, because the cost of starting them collapsed.
  • Specification quality became visible. A vague ticket used to produce a slow developer asking clarifying questions. Now it produces a fast, confident, wrong implementation. Ambiguity has a shorter path to production than it used to.
The model is very good at answering the question you asked. It has no opinion about whether it was the right question.

The rules we settled on

We wrote these down after a few months of getting it wrong, and they've held up:

  • A human author owns every merged change. "The agent wrote it" is not a defence in a post-incident review, and we don't accept it as one.
  • No agent-generated change ships without a test a human read. Generated tests that assert the generated behaviour are a closed loop that proves nothing.
  • Anything touching auth, payments, or customer data gets a slower path — two reviewers, no exceptions, regardless of how the diff was produced.
  • Diff size caps. If a proposed change is too large to review carefully, it gets broken up, not skimmed. Skimming a 900-line diff is theatre.
  • We keep a written architecture context file in the repo. Most bad output we saw traced back to missing context, not a weak model.

The part nobody puts in the marketing

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.

<platform />
Web platform

Ship less JavaScript: the browser finally caught up

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.

Web team Read

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:

  • Container queries replaced most of our "component needs to know the viewport" hacks. Components now respond to the space they're actually in, which is what we always meant.
  • :has() removed a genuinely embarrassing amount of JavaScript whose only job was adding a class to a parent element.
  • The View Transitions API covers the page and state transitions we used to pull in an animation library for. Progressive enhancement is built in — browsers without it just cut to the new state, which is fine.
  • Native dialog and popover handle focus trapping and the top layer without the accessibility bugs our hand-rolled modals shipped with for years.
  • text-wrap: balance fixed a category of headline-typography complaints that used to come back from designers every single project.
  • Native CSS nesting and custom properties mean a large share of what we used a preprocessor for is now just CSS.
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.

How we decide now

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.

Why this matters more here than in a lot of markets

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.

$ build --fast
Tooling & AI

Our build tools got rewritten in systems languages, and we felt it

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.

Platform team Read

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.

What we changed

  • Bundling and dev server. Moving to a modern native-core bundler took our cold start from "make coffee" to "blink." Hot updates are effectively instant on projects where they used to be a visible pause.
  • Linting and formatting. Consolidating onto a single fast toolchain removed a config sprawl problem as much as a speed problem. One tool, one config, one thing to explain to a new hire.
  • Type checking in CI. This remains the slowest step for us, and it's the one worth watching as the ecosystem's native ports mature.
  • Package installs. Faster installers changed CI cost more than local experience, but CI cost is real money when you're running builds on every push.
Fast tools don't make developers faster. They make developers present.

The trade we accepted

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.

design ⇄ code
Team culture

Design engineering is the role most teams are missing

The gap between a beautiful Figma file and a shipped interface is where projects quietly lose their quality. Someone has to own that gap.

Design team Read

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.

What the role actually does

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:

  • Turns design decisions into tokens and components that are the only way to build in the codebase, so drift becomes structurally difficult rather than merely discouraged.
  • Designs the states nobody sketches — loading, empty, error, offline, too-long-content, zero-permission. In real products these are most of what users see on a bad day.
  • Prototypes interaction in the browser, where motion, latency and input methods are real, instead of in a tool where everything is instant and everyone has a mouse.
  • Holds the line on accessibility and performance as design constraints, not as a QA checklist item at the end.
A design system isn't a component library. It's an agreement, and someone has to be responsible for keeping it.

You may not need to hire one

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.

a11y · default
Web platform

Accessibility stopped being a nice-to-have

It moved from an ethical argument to a commercial and legal one. The work didn't change — the deadline did.

Web team Read

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.

The uncomfortable finding

When we audit inherited sites, the same handful of issues account for the overwhelming majority of failures — and none of them are hard:

  • Text and interactive elements with insufficient colour contrast, usually introduced by a brand palette nobody tested.
  • Images with missing or useless alt text (alt="image1.jpg" is a real thing we find often).
  • Forms where labels aren't associated with inputs, so a screen reader announces "edit text, blank."
  • Custom dropdowns and modals built from divs that can't be reached or dismissed by keyboard.
  • No visible focus indicator, usually because someone removed the default outline for aesthetic reasons and never replaced it.
Nearly everything we find in an audit would have cost nothing to prevent and takes real money to retrofit.

How we build it in instead

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.

sync ⟳ local
Web platform

Local-first: building for the network we actually have

Treating the network as an enhancement rather than a requirement is not an exotic architecture here. It's the realistic one.

Engineering team Read

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.

Where this earned its keep for us

  • A field data collection tool where staff worked in areas with unreliable coverage. Previously they wrote on paper and typed it in later; now the app just works and reconciles when signal returns.
  • An internal operations dashboard where the perceived speed improvement was so large that people assumed we'd upgraded the server. We hadn't.
  • A retail point-of-sale flow where a dropped connection used to mean a lost sale.
Optimistic UI is a trick. Local-first is an architecture. The difference shows up the first time someone spends twenty minutes offline.

Be honest about the cost

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.

async > sync
Team culture

Distributed teams don't fail on tools. They fail on writing.

The teams that struggled with remote work didn't lack software. They lacked the habit of writing decisions down.

Operations Read

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.

What we actually enforce

  • Decisions get written where the work lives. Not in a chat thread that scrolls away — in the ticket, the repo, or the project doc. If it isn't written down, it didn't happen.
  • Meetings need a document, not an agenda. A short written proposal read at the start beats forty minutes of someone talking through slides, and it produces a record for free.
  • Default to async, reserve sync for disagreement. Status updates are writing. Arguments are a call. Getting these backwards is the single most common time sink we see.
  • Response-time expectations are explicit. "Within the working day" as a norm removes an enormous amount of low-grade anxiety about whether you're allowed to close the laptop.
Writing is slower for the author and dramatically faster for everyone else. That trade is the entire discipline.

The part we got wrong first

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.

🇳🇬 build local
Business & growth

Building for Nigerian users is a different engineering brief

Copying a Silicon Valley product playbook here fails in specific, predictable ways. Most of them are technical.

Plutterfot Read

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.

What changes

  • Mobile isn't a majority — it's effectively the whole thing. Designing desktop-first and adapting down produces an interface that feels like a compromise for almost every real user. We start on a mid-range Android device and test there.
  • Data costs money and connections vary. Heavy pages aren't a performance metric here, they're a spending decision imposed on your user. Payload discipline is a business feature.
  • Payments are their own discipline. Multiple methods, transfer flows, real failure and retry paths. A payment flow that only handles the happy case is not a payment flow.
  • Trust has to be engineered. Visible business details, real people, clear pricing, a support channel that answers. WhatsApp isn't a fallback contact method here — for many businesses it's the primary one, and treating it as an afterthought costs conversions.
  • Power and device reliability are part of the brief. Sessions get interrupted. Anything that loses a user's work when the device dies mid-form will be abandoned.
The constraints here produce better engineering. A product that works on a weak connection on a cheap phone works everywhere.

The opportunity in it

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.

choose boring
Business & growth

Choose boring technology — then spend the excitement where it counts

Novelty has a maintenance bill, and the client pays it eighteen months after the agency has moved on.

Plutterfot Read

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.

The rule we use

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.

  • Can a stranger maintain it? If the answer requires a specific person, that's a risk the client is carrying without being told.
  • What happens at the next major version? Every dependency is a future migration with a date you don't control.
  • Is the hosting replaceable? Platform-specific features are fine right up until pricing or policy changes.
  • Who edits the content? If the answer is "they email us," we built the wrong thing.
The client doesn't own your architecture decisions. They own the consequences.

Handover is a feature

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.

One useful email a month. No filler.

New articles, things we've broken and fixed, and the tooling changes worth your attention — sent when there's something to say.