Skip to content

TUI attachments live in the composer, not above the status bar - #75210

Merged
OutThisLife merged 3 commits into
mainfrom
bb/inline-attachments
Jul 31, 2026
Merged

TUI attachments live in the composer, not above the status bar#75210
OutThisLife merged 3 commits into
mainfrom
bb/inline-attachments

Conversation

@OutThisLife

Copy link
Copy Markdown
Collaborator

Summary

Attachments in the TUI behaved nothing like pastes. Attaching an image printed a 📎 Attached image: shot.png · 1920x1080 · ~1.2k tok line somewhere above the status bar and left the composer untouched, so the only evidence of the attachment lived outside the input you were editing — and sometimes text appeared in the composer anyway, from a remainder field that got spliced in on its own with no visible cause.

A collapsed paste and an attached image are the same idea: a [[ … ]] marker sitting in the input line that stands in for a payload resolved at submit. This models both as one ComposerToken with one expander, so attachments get paste's ergonomics for free.

Before / after

before after
where it shows notice line above the status bar [[ Image 1 ]] inline at the cursor
composer after attach unchanged (or a stray remainder insert) token where you were typing
how to unattach nothing — it rides along delete the token
position in the turn stapled to the front wherever the token sits
edit / select / backspace n/a ordinary characters, works for free

Every attach path now lands in the same place:

path before after
drag-drop sys() notice + maybe a stray insert token at cursor + caption
clipboard, bracketed 📎 Image #2 attached from clipboard token at cursor
clipboard, hotkey same notice token at cursor
/image <path> notice, then setInput(remainder) token appended to current input
/paste notice token appended to current input
non-image file drop detected file: X activity row path rewritten in place, silent

Notes

  • Deleting the token is how you unattach. updateInput is the one choke point every keystroke passes through, so syncTokens reconciles there and fires image.detach for anything erased. That also fixes a stale image riding along on the next unrelated turn.
  • Image tokens expand to nothing — the gateway already holds the file in attached_images. The expander eats one adjacent space so a token removed mid-sentence doesn't leave a gap.
  • Indices are never reused after a delete, or two different files would resolve to one label.
  • Tokens and the input line get refs alongside state: paste-then-immediately-Enter submits before React re-renders, and the submit path has to see the token that was just added.
  • attachedImageNotice and imageTokenMeta have no callers left and are gone.

Test plan

  • npx tsc --noEmit clean
  • npx eslint clean on every touched file
  • 15 unit tests in __tests__/attachments.test.ts — expansion ordering, image-token elision without double spaces, index allocation after deletes, drop detection
  • Full TUI suite: 1422 passed. The 24 failures (syntax, subscriptionOverlay, createGatewayEventHandler, ink-backpressure) reproduce identically on a clean origin/main worktree and touch nothing in this diff — 0 added failures.
  • Manual: drag-drop an image, confirm [[ Image 1 ]] at the cursor and no notice
  • Manual: backspace the token, confirm the image does not reach the next turn
  • Manual: /paste with a screenshot, then Enter immediately — token must resolve

A collapsed paste and an attached image are the same idea: a `[[ … ]]`
marker sitting in the input line that stands in for a payload resolved at
submit. Model both as ComposerToken and give them one expander.

Image tokens resolve to nothing — the gateway already holds the file in
attached_images — so expandTokens eats an adjacent space to avoid leaving
a gap mid-sentence. nextImageIndex never reuses an index after a delete,
or two files would collide on one label.
…attach

Every attach path now drops an `[[ Image N ]]` token where you are typing:
drag-drop, clipboard (bracketed and hotkey), /image, /paste. The composer
owns clipboard attach directly instead of calling back out to useMainApp.

Deleting the token is how you unattach — there is no second control.
updateInput is the one choke point every keystroke passes through, so
syncTokens reconciles there and detaches anything erased. That also fixes
a stale image riding along on the next unrelated turn.

Tokens and the input line get refs alongside state: paste-then-immediately
-Enter submits before React has re-rendered, and the submit path has to see
the token that was just added.
The token in the input line is the whole receipt. Drop the notices that
duplicated it somewhere the user was not looking: the drag-drop and
clipboard sys() lines, and the attachedImageNotice / "detected file: X"
activity rows above the status bar.

attachedImageNotice and imageTokenMeta have no callers left.
@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

ran on 22af266

ℹ️ Info

Desktop E2E visual evidence · View test artifacts · View job

1 visual diff.

inline evidence upload failed.

Failed to upload diff-665a0833239e-onboarding-overlay-diff.png with gh image (exit code 1): Error uploading /home/runner/work/_temp/e2e-evidence/diff-665a0833239e-onboarding-overlay-diff.png: step 0 (get upload token): uploadToken not found on repo page — do you have write access to NousResearch/hermes-agent? (or, if NousResearch enforces SAML SSO, authorize at https://github.com/orgs/NousResearch/sso)

@OutThisLife
OutThisLife merged commit dbe1442 into main Jul 31, 2026
34 checks passed
@OutThisLife
OutThisLife deleted the bb/inline-attachments branch July 31, 2026 04:56
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
…achments

TUI attachments live in the composer, not above the status bar
33hodl pushed a commit to 33hodl/hermes-agent that referenced this pull request Aug 12, 2026
…achments

TUI attachments live in the composer, not above the status bar
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant