Writing / August 27, 2026

The Exit Path Is Part of the Feature

A file rename can obey every local rule and still trap the user. Complete workflows include the transitions that follow rejection.

software-engineeringuxproduct-development

I was reviewing downloaded logo images on my Mac when I clicked the file name at the top of the viewer and changed it in place.

That interaction removed more work than its small size suggested. Without it, I would have needed to close the image, open Downloads or Finder, determine which generated file name belonged to the image I had just inspected, rename it, and possibly reopen it. The inline editor kept the file, the image, and my reason for renaming it together.

Then I accidentally deleted the entire name. I left the editor without entering a replacement, and nothing bad happened. The file kept its previous name.

The useful part was not only that the viewer let me rename an open file. The interaction still worked when I failed to provide a valid replacement. The old name remained committed, and I could leave the edit without repairing anything.

An open door in a dark concrete corridor leads into a sunlit courtyard, with warm light falling across the unobstructed, level floor.
The route out belongs in the design from the beginning.

When Correct Rules Compose Badly

Years earlier, in an early version of Windows Explorer, I tried to rename a file that another program had open. Windows rejected the operation because the file was in use, but it left the inline rename editor active. I had already erased the original name. The only way out was to reconstruct that exact name and type it back in.

The individual rules made sense. An empty file name was invalid. The open file could not be renamed. A failed rename should not be silently reported as successful.

The experience was still broken.

Later Windows systems document one way a rename can be rejected without the file operation behaving incorrectly. On NT-derived systems, Microsoft’s CreateFile documentation explains that delete access also covers rename operations. If an existing handle does not permit FILE_SHARE_DELETE, another process can be denied the access needed to rename the file.

That sharing model does not establish the exact mechanism behind my earlier Explorer experience. It establishes the narrower point that the file layer can legitimately refuse a rename. The refusal was not the product failure.

The failure was what happened next.

The text editor knew how to reject an empty name. The file operation knew how to reject a rename that conflicted with an open handle. But the complete workflow also needed a transition from “rename rejected” back to “show the last valid name.” Without that transition, two correct local decisions composed into a trap.

Valid And Usable Are Different Properties

Engineers spend a great deal of effort preventing invalid state. We define allowed values, protect resources with locks, reject incompatible transitions, and stop operations when their preconditions are not satisfied. Each rule can be tested against the component that owns it.

A user does not experience those components independently. A user experiences the path through them.

That path includes failure. If a save is rejected, can the user still cancel? If a deployment cannot proceed, can it roll back? If a dependency needed for validation is unavailable, can the user leave the form? If a lock correctly prevents mutation, does the interface return to the previous usable state?

These questions describe behavior between components. They are easy to miss when the editor and the file operation each satisfy their local contracts. The composition can still be unusable.

The file rename can be modeled as a small transaction:

  1. Preserve the committed file name.
  2. Let the user edit a proposed replacement.
  3. Validate and attempt the rename on commit.
  4. If it fails, keep or restore the committed value.
  5. Allow cancellation regardless of why commit failed.

Preserving the committed value and keeping cancellation available are not defensive polish around the real feature. They are what make experimentation safe. The user can propose a change without becoming responsible for reconstructing the last known-good state if another layer rejects it.

Design The Rejected Transition

Happy paths often cross more system boundaries than their interfaces reveal. Renaming an image inside a viewer can involve an inline editor, application state, filesystem permissions, open handles, and whatever observes the path afterward. A smooth interaction makes that coordination feel like one action.

Failure exposes the seams. One layer says the proposed value is invalid. Another says the operation is prohibited. The interface then has to decide whether the person is returned to a usable state or stranded between those decisions.

This is why a state model that contains only successful transitions and explicit rejection is incomplete. Rejection describes what the system refused to do. It does not describe where the user goes afterward.

The filesystem can reject a rename and still leave the user somewhere usable. That transition is not an exception to the feature. It is part of the feature.