
I already had a working image generation flow.
A user could describe a tattoo idea, choose a style, generate an image, and try another direction if the first result was not right.
For a while, I assumed the next useful feature would be better generation history.
Save more results.
Restore previous settings.
Maybe eventually add branches or versions.
Then I started noticing a simpler behavior.
Users were not always asking for another image.
Sometimes they already liked most of the image in front of them.
They just wanted one part changed.
The difference between “another result” and “change this”
One recent example made this very clear.
A user generated a Roman numeral tattoo with flowers around the lettering.
The main lettering direction was already working.
The next instruction was simply:
Remove the flowers.
That request is very different from:
Generate another Roman numeral tattoo.
The user was not looking for another idea.
They were trying to preserve the part that worked and remove the part that did not.
That sounds obvious in hindsight, but it changed how I thought about the workflow.
A normal generation loop assumes:
input → result → try again
What this user needed was closer to:
result → describe one change → revised result
So I added a focused Modify flow to AIMakeTattoo.
I did not build an image editor
Once I started thinking about image editing, the feature could have become much larger very quickly.
I could have added:
masks
brushes
layers
selection tools
a canvas
undo history
version trees
branching
Those are all reasonable image-editing features.
But none of them were required to solve the behavior I had actually observed.
The user already had a source image.
They already knew what needed to change.
So the interaction stayed deliberately small:
keep the current result → describe one change → generate the revised version
The user does not need to select an area manually or reconstruct the original prompt.
The existing image carries most of the visual context.
The text instruction describes the difference.
The source result stays authoritative
One implementation detail mattered more than I first expected.
I did not want the browser to send any arbitrary image URL and ask the backend to edit it.
Instead, the Modify request references an existing generation job.
Conceptually:
type ModifyRequest = {
sourceJobId: string
instruction: string
}
The server resolves the source generation from that ID and verifies that the current user or anonymous visitor is allowed to access it.
Only then does it resolve the generated image and submit the edit.
That gives me a much cleaner boundary.
The browser tells the server:
Modify this generation.
It does not tell the server:
Trust this random image URL I am sending you.
This also meant the existing ownership model could continue to work for both signed-in and anonymous users.
Editing reused the same generation rules
I also decided that Modify should not become a separate credit or quota system.
An edit is still an image-generation operation.
It still consumes model capacity.
It still has cost.
It can still create concurrency problems if several requests are submitted at once.
So the surrounding infrastructure remains shared:
free quota
paid credits
concurrency lock
job state
provider polling
result handling
The model operation changes, but the product rules do not need to.
The flow is roughly:
open Modify
↓
validate source generation
↓
reserve quota or paid credit
↓
acquire generation lock
↓
submit source image + edit instruction
↓
wait for edited result
↓
store successful generation
↓
show the new result
Keeping the edit flow inside the existing generation infrastructure was much simpler than creating a parallel editing product.
I kept the instruction deliberately narrow
The Modify input is not meant to become another giant prompt box.
The useful behavior I am trying to support is usually something like:
Remove the flowers.
Make the design narrower.
Keep the composition but change the colors.
Remove the supporting symbol.
The source image already contains the visual direction.
The instruction only needs to describe the delta.
That is an important distinction.
The goal is not to regenerate the idea from text.
The goal is to preserve as much of the current result as possible while changing one thing.
I also avoided a version tree
Another feature I considered was explicit parent-child version history.
For example:
Original
├─ Version A
│ └─ Version A2
└─ Version B
I did not build that either.
A successful modification currently becomes another normal generation result.
For signed-in users it can appear in generation history, and I can still retain enough internal context to know where it came from.
For example:
{
generationMode: "modify",
sourceJobId,
editInstruction
}
That gives me provenance without forcing the user to manage a branching system.
If users eventually create long continuation chains, then a version tree may become useful.
But I do not want to build that before I see the behavior.
History and continuation are not the same thing
This was the part I had been getting wrong.
Generation history answers:
Where is the result I made earlier?
Modify answers:
How do I continue from the result I already like?
Those are related, but they are not the same problem.
A gallery of old generations is useful for retrieval.
It does not automatically create continuity.
And a full project system may preserve every possible state while also making the user manage complexity they never asked for.
For now, a small action attached directly to the result seems closer to the real need.
The metric I care about now is not just usage
The next question is whether Modify becomes a real continuation loop or mostly a one-time rescue action.
There are a few behaviors I want to watch:
how often a generated result is modified
whether the modified result is accepted or modified again
whether users return to the original result later
Those patterns would tell me something very different.
If most people modify once and stop, the feature may mainly be rescuing nearly-good generations.
If people repeatedly branch and revisit previous versions, then the product may eventually need a stronger continuation model.
I would rather let that behavior pull the product toward complexity than add the complexity first.
The useful feature may be preservation, not editing
I originally thought I was adding image editing.
But the more useful product idea may be something narrower:
preserve what already works.
Generative products often optimize for producing another output.
That makes sense when the current output is wrong.
But when a result is already 80% right, another completely new generation can be worse than the current one.
At that point, the user does not necessarily want more creativity.
They may want more control.
The interaction becomes:
Keep this.
Change that.
That is a much smaller feature than a full image editor.
For this product, it may also be the more useful one.
Comments (0)
Login to post a comment.