Back to all writing

The Bugs Got Smaller. That Was Progress.

IPMAT Series•Part 41•5 min read•By Mikhil

The Bugs Got Smaller. That Was Progress.

There was a stage of this project where every bug felt existential.

One missing import could reveal an entire copied architecture that had not been cleaned properly.

One sync issue could expose account-isolation problems.

One Android failure could turn into a debate about whether the app should even be using that native architecture.

Those were not really bugs.

They were symptoms of unfinished systems.

Recently, something changed.

The bugs started getting smaller.

I think that is progress.

A 503 used to scare me more

IPMATHics had a recommendation endpoint returning a server error.

A few weeks earlier, that might have triggered:

Check the entire backend.

Check auth.

Check Supabase.

Check access.

Check Strata.

Check all recommendation logic.

Instead, the failure could be isolated to one route.

One file changed.

The relevant test suites stayed green.

The surrounding architecture did not need to move.

That is what mature boundaries are supposed to make possible.

Startup sync had the same pattern

There was also a race around shared-account initialization.

Again, not good.

But the fix was small.

The system needed a controlled retry during startup.

It did not need a new sync architecture.

That distinction matters.

If every failure requires redesigning the foundation, the foundation is not stable.

If some failures can be repaired locally, the architecture is starting to carry its weight.

The test suites became guardrails instead of decorations

Earlier in the project, I learned that passing tests could give me false confidence.

That lesson still stands.

But good tests are incredibly useful once the system boundaries become clearer.

Fix the route.

Run the shared-account tests.

Run the learning tests.

Confirm the repair did not break unrelated behaviour.

The tests are not proof of perfection.

They make narrow fixes safer.

That is a much healthier role.

Fewer files changed

This sounds trivial.

It is not.

A bug fix that touches twelve unrelated files is telling you something.

Either the concern is too spread out, the abstractions are wrong, or nobody knows where the behaviour actually lives.

As the products got cleaner, more repairs started landing where I expected them to land.

That makes the codebase easier to reason about.

It also makes AI-assisted implementation much safer.

AI is much better when the boundaries are better

This project is heavily AI-assisted.

I have never hidden that.

One of the biggest practical lessons has been that AI quality depends enormously on architecture quality.

If the codebase is messy, a small request can cause a huge patch.

If responsibilities are clear, I can say:

Fix this route.

Do not touch the shared account layer.

Preserve these imports.

Run these tests.

And the change becomes much easier to inspect.

Good architecture reduces the amount of trust I have to place in generated code.

That is important.

Small bugs are easier to verify manually

A giant refactor creates too many questions.

Did practice still work?

Did review still work?

Did settings change?

Did auth change?

Did the build change?

A narrow fix creates a much smaller verification surface.

This endpoint returns the correct result now.

The relevant tests still pass.

The shared state is unchanged.

That is much more manageable.

Not every small bug means the product is mature

I want to be careful here.

The ecosystem still has large unfinished work.

Canonical evidence integration.

Content depth.

Real device testing.

Payments.

Real users.

Release operations.

A small bug does not erase those things.

The change is that local failures are increasingly local.

That is a healthier place to continue from.

Error messages also became more useful

When architecture is vague, errors are vague.

Something failed.

Try again.

Great.

As the system became more explicit about auth, evidence, sync, recommendations and access, the failures became easier to classify.

A recommendation route failure is different from an account sync failure.

A missing evidence state is different from an auth failure.

A pending event is different from a rejected one.

Clearer systems create clearer errors.

This is what "boring" engineering starts to look like

The bug exists.

You reproduce it.

You locate the boundary.

You patch the smallest responsible area.

You run the relevant checks.

You verify the user-facing path.

You move on.

No dramatic redesign.

No new framework.

No "while we're here" feature spree.

That sounds extremely ordinary.

I am beginning to appreciate ordinary.

Big bugs taught me architecture

Some of the earlier failures were incredibly useful.

They exposed duplicated logic, weak ownership, stale migrations, mixed native strategies and fake sync assumptions.

They forced major decisions.

I do not regret them.

I also do not want every week of development to look like that forever.

At some point, the foundation should stop being the most fragile thing in the project.

The codebase is becoming less magical

This is probably the best way to describe it.

Earlier:

Something breaks.

Why?

Could be anywhere.

Now:

Something breaks.

There is a reasonable place to look first.

That predictability is boring.

It is also one of the clearest signs that the project is growing up.

The bugs got smaller

Not all of them.

Not permanently.

But enough that I noticed.

A server route.

A startup retry.

A piece of copy.

A spacing issue.

A visual state.

Small failures that can be repaired without reopening the entire architecture.

For months, progress meant making the system capable of more.

Now sometimes progress is simply this:

A broken thing was exactly as broken as it looked.

Nothing underneath collapsed.

I fixed it.

Everything else stayed standing.