Skip to main content

Command Palette

Search for a command to run...

I Built the Same “Modify” Feature Twice. The User Contract Was Completely Different.

Same model family, same “Modify” label — but completely different assumptions about what users wanted to preserve.

Updated
5 min readView as Markdown
I Built the Same “Modify” Feature Twice. The User Contract Was Completely Different.
S
Building browser-side workflow, privacy, and metadata utilities. Interested in AI workflows, semantic SEO, and lightweight developer tools.

When I first added a “Modify” feature to my tattoo product, I assumed the interaction pattern would be reusable.

Both workflows looked similar on the surface:

  • generate an image
  • keep the result
  • describe what should change
  • send the edited image back to the model

So I expected the same editing logic to work everywhere.

It didn’t.

The reason was not the model.

It was the user contract.

The same “Modify” button can mean different things

In my AI Tattoo Generator, users often treat the result as something flexible.

They may keep the main idea but change:

  • composition
  • background
  • visual balance
  • style
  • placement direction
  • complexity
  • individual objects

A generated tattoo concept is often still negotiable.

The user may like 70% of the image and want to reshape the remaining 30%.

That makes a freeform modification request feel natural.

Something like:

Make the background simpler and make the main subject larger.

or:

Keep the composition, but make it feel more traditional.

The design itself is still being explored.

Lettering turned out to be different

I initially assumed my AI Tattoo Lettering Generator should work the same way.

But lettering has a stronger invariant:

the exact words usually matter more than almost anything else.

If someone generates a personal name, a Roman numeral date, a short phrase, or a surname, they usually are not casually experimenting with the text itself.

They are experimenting with the presentation around that text.

Things like:

  • script style
  • thickness
  • spacing
  • flourishes
  • ornaments
  • flowers
  • frames
  • vertical layout
  • line weight

The text is often the fixed center of the design.

That changed the way I thought about editing.

My first implementation tried to protect the text too aggressively

The first version had a dedicated field specifically for changing the exact wording.

The logic was roughly:

  • normal change request = modify the visual design
  • separate exact-text field = explicitly replace the words
  • if the exact-text field was empty, preserve the existing wording

This felt safe.

It also felt logically clean.

I was trying to prevent a common generative-image failure mode: changing text when the user only wanted to change the styling.

But this created a new problem.

I had introduced a permanent piece of UI for something that turned out to be relatively uncommon.

The product was now teaching every user about a special text-changing workflow, even though most people were not trying to change their text at all.

Real usage made the mismatch obvious

After watching how people actually used lettering generation, the pattern became clearer.

Users would often try several visual directions while keeping the wording stable.

They might change the style.

Then the ornament.

Then the composition.

Then the line weight.

But the core text stayed the same.

That meant the dedicated “change exact text” field was solving a valid problem, but not a frequent one.

And that matters.

A UI control has a cost even when nobody clicks it.

It adds:

  • another concept to understand
  • another branch in the interaction
  • another state to maintain
  • another explanation in the interface
  • another place where user intent can conflict

I had optimized the product for a rare edge case.

I removed the special case from the interface

The newer version is much simpler.

There is one modification input:

Describe a change

The existing lettering result is treated as the main source of truth.

By default, the system tries to preserve the wording while changing the visual design.

But if the user clearly asks to change the text inside the same request, that can still be handled.

For example:

Change “Maria” to “Maria Calderon” and make the lettering thinner.

There is no longer a need for a separate permanent input just to unlock that behavior.

The capability still exists.

The dedicated UI does not.

That distinction became important to me.

A feature does not need a control for every capability

This was the larger lesson.

When building AI interfaces, it is easy to map every possible model capability to a visible UI control.

Change style? Add a control.

Change text? Add another.

Change composition? Add another.

Preserve an element? Add another.

Eventually the interface becomes a mirror of the model API instead of a reflection of how people actually think about the task.

Users do not care about the internal capability graph.

They care about getting from their current result to the result they want.

Sometimes a low-frequency capability should remain accessible through natural language instead of becoming a permanent part of the interface.

The real difference was the invariant

The most useful distinction between the two Modify workflows was not technical.

It was this:

In the AI Tattoo Generator, the design is flexible.

In AI Lettering, the text is usually fixed.

That changes everything around the interaction.

The image-editing model can be the same.

The backend architecture can be similar.

The UI can even use the same word: “Modify.”

But the thing the user expects the system to preserve is different.

And once that invariant changes, the editing logic should change too.

Reusing the same model does not mean building the same interaction

This is probably the part I would think about first if I were building another AI editing workflow.

Before designing the controls, ask:

What does the user consider fixed?

Then ask:

What are they actually trying to explore?

Those two answers tell you much more about the right interface than the model endpoint does.

The model may expose one generic image-edit API.

The product should not.