heading · body

Transcript

The Happy Path Doesnt Exist Notes On Software Fluidity

read summary →

TITLE: The Happy Path Doesn’t Exist: Notes on Software Fluidity CHANNEL: Interface Studies DATE: 2026-04-23 URL: https://www.youtube.com/watch?v=VzjvBYaGHgA

---TRANSCRIPT---

Welcome to Interface Studies, my name is Saleh.

There’s a term that does a lot of quiet damage in software design.

It’s the happy path.

It refers to the ideal user journey.

The one where everything works.

Input is valid.

No one gets interrupted.

Nothing fails.

It is a useful concept for communicating intent.

But the problem is not the happy path itself.

The problem is what happens to everything else once the term exists.

Everything that isn’t the happy path acquires a name too.

Edge cases.

The word is doing work here.

An edge is marginal.

Peripheral.

Something unlikely.

An edge case is something you handle after the real design is done.

And this is encoded so deeply in design practice that it shapes the sequence of work.

Define the ideal flow first, then circle back to the exceptions if time and budget permit.

But software doesn’t experience this distinction.

A system running in the real world,

On a device that might lose connectivity,

In a session that might be interrupted by a user who might make unexpected choices,

Encounters its states in no particular order of importance.

The paused state is not less real than the running state.

The error state is not less present than the success state.

The return from background state is not a footnote.

These are not deviations from the system.

They are the system.

They are what it is doing most of the time.

The happy path is one trajectory through a much larger graph.

Designing only that trajectory doesn’t simplify the graph.

It leaves the rest of it undesigned.

Which means that it still exists, still gets reached, and produces an experience that nobody deliberately shaped.

Now this is not a new problem.

It predates AI generation tools by decades.

And it isn’t caused by any particular software or design methodology.

It’s a problem of disposition.

The tendency to organize design thinking around the ideal sequence.

And to treat everything that diverges from it as a secondary task.

A famous analysis by Nielsen Norman Group for where products consistently fail, the pattern was clear.

Edge cases fall through the cracks not because teams lack skill, but because their scenario is designers would rather not consider.

Today, what AI generation tools do is intensify this tendency.

You can produce a component library,

A coherent visual language, and a set of plausible screens in minutes.

The surface arrives fully formed before the structural questions have been asked.

The happy path is sketched.

The aesthetic is resolved.

And the gap between what is visible and what is actually designed is wider than ever.

Harder to see because the output looks finished.

This video is a thinking practice.

We’re going to take one of the simplest time-bound behaviors you can put into software.

An egg timer.

And we will follow every design question wherever it leads.

We’re not going to use Figma. We’re not going to use code or generation.

And actually the point isn’t the egg timer itself.

The point is what it reveals about the nature of software.

Software is something that is always in motion.

That its states are not problems to manage,

But the substance of what it is.

The real design is the practice of giving that motion form.

A digital egg timer, stripped of everything non-essential, has three states.

Idle,

Running,

Elapsed.

There are also two transitions.

You start it,

And eventually it finishes.

That’s the atom.

And even here, design decisions cannot be avoided.

When the timer reaches zero,

Does it hold at elapsed until you dismiss it, or does it reset automatically?

This sounds trivial, but it isn’t.

Hold until dismissed encodes an assumption that you need to acknowledge completion before moving on.

Auto-reset encodes an assumption that you’re already thinking about the next thing.

These are two different theories of how people process the end of a time-bounded task.

Does the countdown update every second?

Or is it every half a second?

A timer that updates once per second feels stable, deliberate.

Sub-second updates feel alive, slightly anxious.

Neither is neutral.

What triggers the return from elapsed to idle?

Is it a tap?

A swipe?

Or is it an automatic timeout?

Each makes a different claim about how much attention the system is entitled to demand.

None of these decisions appear in the UI until they’ve already been made at the level of the underlying model.

The interface is the rendering of choices about state and transition.

Design is not choosing what things look like.

Design is choosing what the system does and then making that visible.

Now, before the timer can run, someone has to set it.

The physical egg timer solved this with a dial.

One affordance.

An affordance is the perceived possibility for action an object offers.

One gesture,

And we’re done.

In software, input modality is an open question, and every answer implies a model of who is using the system and in what state of mind.

A number pad assumes precision.

You know exactly what you want and can express it numerically.

A scroll wheel assumes approximation.

Fine-tuning here is acceptable.

Presets, like soft, medium, or hard, assume your need maps onto a fixed vocabulary.

Voice assumes that you’d rather speak than tap.

Each modality has a failure mode that is invisible in the interface itself.

Presets fail silently.

If your situation doesn’t match the presets’ assumptions, the timer is wrong with no indication that it is wrong.

A number pad fails through ambiguous input.

A number like this could mean three minutes or five hours, and the interface will often accept both of them without comment.

Voice fails through misrecognition, which produces no error at all, just a timer running to the wrong duration while you’re not watching.

Then there are the defaults.

First launch.

No prior session.

The timer shows what exactly?

An empty field?

A suggested value?

The last used duration?

Each choice makes a claim about what the user knows upon arrival.

There is no neutral option.

Every blank screen is already a decision.

Now the timer is running.

Something happens.

This is where the flow ceases to be linear and where many designs reveal the decisions that weren’t made.

Pause introduces a question that goes deeper than the button.

On mobile, the standard approach is to schedule the completion alert at the moment the timer starts.

The operating system fires it independently of the app.

So pausing here means cancelling that scheduled alert and creating a new one at the adjusted end time.

This is not a UI decision in isolation.

It surfaces as a UI problem, like the alert firing at the wrong time.

But the cause was a decision about how the system manages its relationship with the operating system.

The design of the pause button and the structure of the notification system are not separable.

Extend, which is adding 30 seconds for example, the microwave model, is intuitive until you’re at 7 seconds remaining and tap it accidentally.

Now you have time you didn’t want.

Does that require confirmation then?

At what threshold?

This is a question about the cost of an error versus the cost of its correction.

And it has no universal answer.

Cancel and reset are not the same operation.

Cancel is abandonment.

This timer is no longer needed.

Reset is preparation.

Clear it for a new duration.

Treating them as identical produces confusion at precisely the moment when the user’s intent is clearest and the system’s interpretation matters most.

There’s also re-entry.

What does the user see when they return to the app mid-timer?

They left.

Time passed.

The interface has to reconstruct that understanding instantly.

Not just show the current time remaining, but make the elapsed time legible at a glance.

The re-entry state is as consequential as any active state.

Something like this doesn’t appear on the happy path flow.

Something else that gets compressed in design practice is failure.

One of the most important things to say here is that errors are not failure of the system.

They are states of the system.

And states require design.

An error message is a transition.

It moves the user from an unexpected condition into something they can act on.

A poorly designed error,

Like something went wrong, is a state with no exit.

A well-designed one identifies the condition and opens at least one path out.

The egg timer’s error surface is small but instructive.

A duration of zero, for example.

And one may ask why zero was allowed to be entered anyway.

A duration entered as 3 hours rather than 3 minutes.

A partial input. The user typed a number, switched apps, came back.

Is the timer set? To what value?

Or something like the system clock changes while the timer is running.

I know we’re getting absurd with these states, but it’s important to understand that on an open system, especially within the world of software or apps, these things may happen.

They may not be relevant to deal with instantly, but it’s important to think about them.

What about when the device restarts?

Notification permission is revoked mid-session. The timer keeps running, completes, and the user receives no alert.

The system has no recovery path.

But it should at least know that this has happened.

And then surface it before the egg overcooks.

The principle here is the same one running through the whole video.

Every condition the system can encounter, expected or not, is a state.

Undesigned states are not absent.

They are present as undefined behavior.

In user experience, undefined behavior looks like confusion.

Moving on. The timer completes while you’re looking at something else.

At this point, the design problem stops being about the interface and becomes about presence.

Which is the system’s ability to make itself known when its own screen is not visible.

On iOS, the standard approach is to schedule an alert at the computed end time when the timer starts.

The operating system delivers it.

Not the app.

This means if the user cancels the timer, the app must explicitly tell the OS to remove that pending alert.

If the app is force quit before that removal happens, the alert fires anyway.

And that will happen for a timer that no longer exists.

This is a ghost notification.

On Android, scheduled alerts are cleared when the device restarts unless the app explicitly registers to receive the reboot event and reschedules them.

Without that, all running timers are silently lost after a restart.

The notification is not a UI element here.

It is the system’s presence in the world when its interface is not visible.

Designing the timer without designing the notification in full is designing half a system.

Now let’s make things a bit more interesting.

Two timers.

Eggs and pasta.

The state machine is now running in parallel, and immediately problems emerge that have no single timer analog.

One timer fills a screen naturally.

Two require differentiation.

Three require a list.

The layout is not a styling decision anymore.

It is a structural decision about how the system represents concurrent processes to someone who needs to track all of them simultaneously.

What about labels?

Labels like timer 1 and timer 2 tell you nothing when your hands are full.

User-defined labels require an additional input step at creation.

This is friction added to every timer for the benefit of the minority running multiple.

There is no clear resolution.

Two timers complete within the same second.

Two alerts fire.

The user is looking at neither.

What does the interface show when both are in elapsed, waiting for acknowledgment at the same time?

The list order is also not neutral.

Creation order, time remaining order, and completion order each produce different behavior and different expectations.

If the list reorders as timers approach completion, the UI is in constant motion while the user is trying to track all of it.

Continuing with this manufactured complexity, at some point someone will ask, can the timer know about eggs?

Not just a countdown to a number the user entered anymore.

It’s calculating the right duration based on actual conditions.

Like the size of the egg, where it was stored, and how the water is set up.

The timer here stops being a countdown and becomes a calculation.

Duration is now a function of five independent variables.

Doneness, size, starting temperature, water state, and altitude.

A preset row, soft, medium, hard, is fast.

It is also silently wrong for any user who deviates from assumed defaults.

Refrigerated eggs. Cold start water. Altitude above 1500 meters.

These are not unnatural conditions. They produce materially different results.

The preset trades accuracy for speed for anyone outside the assumed norm with no indication that the tradeoff is being made.

Adding inputs for all these variables recovers accuracy.

It also adds friction to every use, the majority of which won’t need it.

Speed and accuracy here are genuinely in tension.

And that tension does not resolve itself.

The design must take a position.

Every state so far has a shared assumption.

The user sets the duration.

They type a number, turn a dial, tap a preset, or speak a command.

The system receives a value.

The timer starts.

The line between intent and action is direct.

Even when the input is ambiguous or the modality is imperfect.

The AI layer breaks that line.

When the user says, I’ve got large eggs straight from the fridge. I want them jammy.

The system doesn’t receive a duration. It receives language and it produces an interpretation.

The timer that starts is not the one the user set.

It is the one the system inferred they wanted.

Those are not the same thing.

And the difference between them is a new kind of state.

One where the system and the user may have different understandings of what was just agreed.

This state has no analog anywhere else in this video.

A paused timer is unambiguously paused.

A canceled timer is gone.

An elapsed timer fired at the correct time or it didn’t.

In every previous state, the system’s state and the user’s understanding of it are, at least in principle, the same.

An inferred state introduces a structural gap between what the user believes the system understood and what the system actually computed.

The interface produces no signal that an interpretation took place. Let alone that it might be wrong.

This is what makes the confirmation step inadequate as a solution.

Showing something like seven minutes surfaces the output of the inference.

Not the reasoning behind it.

The user sees a number they didn’t enter with no visibility into the confidence that produced it.

Or the parameters that were assumed.

Or even the cases where the same phrasing would have resolved differently.

They confirm or they don’t. Either way, the inference process is opaque.

Act on the inference immediately, errors are silent until the egg is wrong.

Require confirmation and the friction that voice was supposed to remove is back.

But now with an additional cognitive step, which is evaluating a number you didn’t choose against an expectation you haven’t yet quantified.

The confirmation step asks the user to audit a decision they didn’t make.

We see that in planning mode for agents, upon code generation or website generation, for example.

What makes this architecturally distinct is the shift in agency it produces.

In every previous state, the interface is a surface the user acts upon.

Here it becomes a surface the user supervises.

The system has already made a decision. The user’s role is to assess it.

And to do that, they need to hold in mind what they intended, compare it to what was inferred, and decide whether the gap is acceptable.

That is a fundamentally different cognitive relationship with that system.

And it requires a fundamentally different interface.

One that makes the inference legible, not just its output.

The timer is the same object.

The countdown runs the same way.

But the state space now includes states the user never explicitly created.

And the design has to account for what it means to be responsible for a system whose decisions you can only partially see.

Who are we designing for?

If we’re designing something for everyone, the visual display is one rendering of the system state.

It is not the only one.

A user navigating by screen reader encounters the same timer, the same states, and the same transitions.

But it’s happening through a completely different output channel.

When the timer reaches elapsed, the visual language is immediate and peripheral.

The number hits zero, an animation triggers, and the eye catches it.

For a user relying on audio output, that transition arrives as an interruption.

An announcement that pulls attention from whatever else they were doing and redirects it to the timer.

Think of screen readers, for example.

The experience of the same state change is fundamentally different depending on the surface through which it’s rendered.

Multiple simultaneous timers present a particular problem here.

A screen can hold several countdowns in parallel.

The eye scans, compares, and tracks.

Audio output is linear.

The system must decide what to announce, when, and how often.

All of that without becoming noise.

These are not accessibility accommodations added on top of the design.

They are the same design questions asked again through a different medium.

Same state machine, different rendering.

The same obligation to account for every state expressed through a channel the happy path flow never considered.

This is the full diagram.

Every state, every transition, every error branch, every cross boundary alert,

And every parameter dependency, every inference layer, and every rendering surface.

It is ridiculously large and complicated.

The point here is not exactly the diagram.

The point I’m trying to make here is that it isn’t that is excessive.

The point here is that it was always there.

Latent in the problem from the beginning.

And it’s present whether designed for or not.

The physical egg timer didn’t encounter most of it because a mechanical object can refuse to encounter conditions it wasn’t built for.

Software cannot refuse in the same way.

The moment you place time-bounded behavior in a computational medium, the full state space exists whether you have drawn it or not.

The states designers mark as edge cases are not at the edges.

They are the system operating under conditions other than the ideal one.

Which is to say, most of the time.

Design practice has long organized itself around the ideal flow.

It’s not necessarily laziness, but it is a sequencing problem that the vocabulary of the discipline reinforces.

Call something an edge case and you have already decided that it comes later.

Later in most projects means never.

But software fluidity is the thing underneath the happy path.

The system is always in motion.

It transitions between states. It runs in conditions it wasn’t designed for.

It crosses process boundaries.

It fails.

And every one of those conditions designed for or not produces an experience.

The egg timer just made that visible.

Now, acknowledging this is not enough.

The disposition has to change before the deliverable does.

Which means we have to change the questions asked at the start of design work.

Not at the end of it.

So what can be done here?

Let’s talk about some solutions.

Starting from states, not screens, is important.

Before any visual decision is made, we have to map what the system can be.

Every state, every condition that transitions between them, every state the system enters when input is unexpected or context changes.

This is not a flow diagram exactly.

A flow diagram shows the happy path with branches.

A state map shows the full graph, including states that have no natural place in a linear sequence.

The design work is giving every node in that graph a considered deliberate form.

Another useful thing is to rename edge cases.

The word here is doing damage.

What the industry calls an edge case is almost always a real case.

Which is a condition real users encounter under real circumstances.

A name change is not cosmetic here.

Calling them alternate states or simply states changes where they appear in the sequence of design work.

They move from the residue phase into the structural phase where the decisions that shape them are actually available.

We can also design the re-entry, not just the flow.

Most design deliverables specify what the system looks like when the user is actively progressing through an intended sequence.

Almost none specify what the user sees when they return mid-session.

Or resume after interruption. Or even arrive at a screen through an unexpected route.

These states are often the first thing a real user encounters.

They should be first-class design objects, not inferences left to implementation.

Also, how about testing by leaving the path deliberately?

Once a design is at any level of fidelity, the most productive thing a designer can do is attempt to break it.

Not to find bugs, but to find undesigned states.

Pausing halfway through. Entering nothing. Or entering the wrong thing.

Switching away and coming back.

Force the completion state while looking elsewhere.

Every blank or undefined response to one of these actions is a state that exists in the system without a design.

Finally, and probably the most important one, is treating error states as primary UI.

An error message is not a warning label appended to a working interface.

It is the interface at that moment.

It requires the same specificity as any other state.

What condition does it name?

What does it tell the user about what happened?

And what transition does it open?

Again, something went wrong is a state with no exit.

Which is by any measure the worst possible design.

The nice thing with all this that none of it requires new tools.

The tools available for designers are adequate for this work.

And now with generation, they might be even easier if someone thought about them before.

What is required is that state thinking precedes surface thinking.

That the question what can this system be comes before the question of what should this look like.

The sequence is not a minor adjustment.

It is the difference between designing a system and decorating one.