Skip to content

tui3: a picture's step row names the image model, and opening it shows the prompt - #1026

Merged
santoshkumarradha merged 3 commits into
devfrom
feat/image-step-names-model
Sep 14, 2026
Merged

santoshkumarradha merged 3 commits into
devfrom
feat/image-step-names-model

Conversation

@santoshkumarradha

@santoshkumarradha santoshkumarradha commented Sep 14, 2026 •

Copy link
Copy Markdown
Member

What the row shows now

generate_image was the one hand whose step row said nothing about itself: no
target and no model, because the tool is in no gloss table and its file is
usually a name the tool chose rather than one the caller wrote. The row was
generate_image and a clock.

It now reads:

├─▶ generate_image circle.png · seedream-5              1.2 MB · 2.4s
  • the file is the substance and takes the primary ink;
  • the image model is a qualifier behind the middle dot and recedes to dim —
    the parameter hierarchy toolview.go already states for a read's line range
    and a bash call's cd prefix;
  • the word is the short spelling (modelui.ModelWord), the one this surface
    already uses for a model everywhere else. No fourth spelling was invented.
  • unknown model → nothing. No placeholder, no dangling separator.

Opening the step (click it, or ↑/↓ + enter) shows what went IN, as prose:

│ "a small red circle, flat vector, centred on white"
│ 1024x1024
│ from art/sketch.png
│ drawn with seedream-5
│ <the picture>
│ /…/circle.png   1024×1024 · 1.2 MB

The prompt leads, quoted, wrapped, capped at twelve lines with the existing
… N more lines foot. Under it one dim line per other input the call actually
gave — an input nobody gave has no line, because "default" is a word this
surface is not in a position to fill in. A running generation shows the same
block above its clock: a prompt is readable long before the picture is.

Where the name comes from

One road, no new field on the wire:

fact road
the file the call's path argument, else the path out of the result — through app.picturePath, the rule the picture preview already used
the model that drew the tail of that same result line, … , generated on <model> — written by internal/session's describeGeneratedImage from the same model variable the request carried
what was asked for the call's own model argument, from Event.Args

The model is chosen inside the tool, after the call's word is resolved
against the live catalog, so the result is the only place the answer exists. The
surface does not re-derive it and does not show the session's default as a
stand-in — a hosted task room's row is drawn by this same composer from a node
whose default this window was never told.

internal/session/tools_image_test.go pins the contract between the two
packages: the model that rode the request is exactly what stands after
, generated on at the end of the result's first line, for the slot default
and for a picked word alike.

One thing deliberately NOT built

The brief asked for asked for X · drew with Y because <reason>. That case
cannot happen.
A model word generate_image cannot use is REFUSED before a
penny is spent (no image model matches "xyz", fish-audio/s1 makes speech, not image) — the tool never silently substitutes. Writing that branch would be
describing machinery that does not exist. What does happen and is shown is
asked for best · drawn with seedream-5: the request and the answer, both real,
both off roads already there.

Evidence

Laptop: go build ./..., go vet ./..., gofmt -l internal/ cmd/ clean.

Spark gate — ~/af-imgmodel, detached on feat/image-step-names-model:

LAWS_EXIT=0
ok  	internal/iconlaw	0.241s
...
ok  	cmd/aforge	68.992s
ok  	internal/config	2.731s
ok  	internal/manual	1.565s
ok  	internal/provider	71.358s
ok  	internal/session	185.161s
ok  	internal/tui3	312.669s
PRREADY_EXIT=0

and after the manual follow-up commit: ok internal/manual 1.660s,
ok internal/e2e 0.343s (the untagged needle gate).

Real-model drive — bin/aforge chat --debug in tmux at 200×50 on Spark,
moonshotai/kimi-k3, a real OpenRouter key, ~$0.25. Screens under
spark:~/af-imgmodel-evidence/.

The row, after a real generation (success3/, verbatim):

    ▾ drawing red apple into apple.png
    ╰─▶ generate_image apple.png · seedream-5-0-pro
    │ ▸ apple.png · preview  [open original]

The same row while it ran — the file from the arguments, no model yet:

    ╰─▶ generate_image apple.png

The opened step, on a call whose generation was refused by the image model
(the first drive, verbatim — wrapped at the frame, not truncated):

    ╰─▶ generate_image circle.png
    │ ▾ circle.png · collapse  [open original]
    │ "A single solid flat red circle, perfectly centered on a plain white background. Minimal flat vector graphic st
    │ (#E02323), no outline, no shadow, no texture, no other elements. The circle occupies about two thirds of the fr
    │ 256x256
    │ /tmp/srimg/v3/projects/-tmp-srimg-w/9c2324791eb01c13/work/circle.png
    │ Image generation failed (bytedance-seed/seedream-5-0-pro): API error (400): …

— the prompt quoted and wrapped, the one input the call gave, then the picture
block. No model line, because that call produced no picture to attribute; the
model it tried is in the failure the same block shows, and the manual now says
so in as many words.

Not verified by hand: the drawn with <model> / asked for … · drawn with …
line in a real terminal (covered by internal/tui3/imagemodel_test.go; the drive
that produced a successful picture would not stay on the row long enough to open
it without re-sending the prompt, so the capture was abandoned rather than paid
for). A real --host / task-room row — the composer is shared, so it should
follow, but it was not driven. The phone tier.

Owed, not in this change: a FAILED generation could name its model as a
qualifier too — the fact is in the result, in a different sentence shape. Doing it
properly means one marker spelled the same way in every outcome of
describeGeneratedImage and its failure strings, which is a respell of
person-facing text and belongs in its own change.

🤖 Generated with Claude Code

https://claude.ai/code/session_0182eyxXP8Xe7ADNKpYDe4wv

santoshkumarradha and others added 3 commits September 13, 2026 22:57
…s the prompt

`generate_image` is the one hand whose model a person chooses per call, and
until now the step row said neither which model drew nor what it was asked for.
The row was the bare tool name — `generate_image` and a clock — because the tool
is in no gloss table and its target was never read out of anything.

The row now reads `generate_image circle.png · seedream-5`. The file is the
substance and takes the ink; the image model is a qualifier behind the middle
dot and recedes to dim, which is the parameter hierarchy toolview.go already
states for a read's line range and a bash call's `cd` prefix. It rides the two
roads the row already draws from and no third: the file comes from the call's
own `path` argument, or — because the tool names the file itself when nobody
did — out of the result, through the same [app.picturePath] rule the picture
preview has always used; the model comes out of the tail of that same result
line, which is where the tool writes the model the request actually carried.
Nothing new goes on the wire, and the surface never guesses at a default it is
not in a position to know: an unknown model draws nothing at all.

Opening the step shows what went IN, as prose rather than as the JSON it
arrived in. The prompt leads, quoted and wrapped and capped at twelve lines like
every other expansion; under it one dim line per other input the call gave, and
then the model line — `drawn with seedream-5`, or `asked for best · drawn with
seedream-5` when the call asked in its own words and the catalog answered with a
name. A running generation shows the same block above its clock, because a
prompt is readable long before the picture is. The picture and its whole path
still close the block, untouched.

The word is [modelui.ModelWord], which is the spelling this surface already uses
for a model everywhere else, so no fourth one was invented. The picture hands
join toolFit's middle-cut family and phoneTarget's basename family, so a narrow
row cuts the path and keeps the model and a phone row gives the qualifier up
first — both by the rules those branches already applied, not by a new one.

A session test pins the contract the two packages share: the model that rode the
request is what stands after `, generated on ` at the end of the result's first
line, for the default and for a picked word alike. Reword that sentence and it
fails there, where the fix is cheap, rather than in a terminal where the row
would simply go quiet.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_0182eyxXP8Xe7ADNKpYDe4wv
…de the file

Met on the real-model drive: seedream refused a 256x256 request, so the call had
no file and no attribution to put on the row — and the model it tried was in the
failure line the expansion shows. The page says so rather than leaving a person
to wonder why the qualifier is missing.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_0182eyxXP8Xe7ADNKpYDe4wv
@santoshkumarradha
santoshkumarradha marked this pull request as ready for review September 14, 2026 03:19
@santoshkumarradha
santoshkumarradha merged commit c1f0649 into dev Sep 14, 2026
2 checks passed
@santoshkumarradha
santoshkumarradha deleted the feat/image-step-names-model branch September 14, 2026 03:20
@santoshkumarradha santoshkumarradha added the area:chat The v3 surface a person sits in front of (internal/tui3) label Oct 3, 2026
@santoshkumarradha santoshkumarradha added this to the Chat & connections milestone Oct 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:chat The v3 surface a person sits in front of (internal/tui3)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant