Skip to content

Turn a long text paste into a Pasted text card - #154

Merged
jelveh merged 13 commits into
mainfrom
paste-as-attachment
Nov 5, 2026
Merged

jelveh merged 13 commits into
mainfrom
paste-as-attachment

Conversation

@jelveh

@jelveh jelveh commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Pasting a big block of text (a log, a spec, a file's contents) filled the chat input with a wall of text. Now a paste of 2,000+ characters or 30+ lines becomes a card in the attachment tray, styled after how claude.ai does it.

The card

  • Shows the first few lines in small, faint type over a PASTED badge, with an edit pencil and the usual × on hover.
  • Clicking it opens a "Pasted content" dialog with the full text (size and line count at the top).
  • The pencil opens the same dialog for editing. Saving it empty removes the card.
  • The sent message shows the same card (without edit/remove), and so does the chat after a reload. Clicking it there shows the text.
  • Dark mode is styled.

What the model gets

  • The paste is not saved to assets/ like file attachments are. It's stored on the user message as a text document block marked _pasted.
  • prepareHistoryForAI flattens it to plain text, labelled Text the user pasted ("Pasted text"):. So the model reads the whole paste inline, the same as before.
  • Short pastes, file pastes and the Excel/Word rich-text case work as before.

Details

  • Pasting the same text twice shows a toast and doesn't add a second card. More pastes are numbered: "Pasted text 2", and so on.
  • Pastes count toward the 50-attachment limit.
  • Queued messages carry their pasted cards like any other attachment.

Tests

  • New scripts/test-paste-as-attachment.mjs covers the size threshold, the tray intake, the model-facing text and the send-path wiring.
  • test-composer-drafts.mjs is updated for the split between pasted text and files.
  • npm test passes.
  • Checked in the browser on the local dev server: tray card, hover, view and edit dialogs, saving edits, saving empty, dark mode, and the sent-message card. I didn't run a full signed-in send end to end.

Review fixes

A long paste used to be composer text, and several things relied on that. Each fix below is its own commit:

  • Drafts: pasted cards are saved in the composer draft, so a reload or a switch to another project brings them back. Queued messages' cards are included too.
  • Failed send: a send stopped before its message landed puts the cards back in the tray along with the text.
  • Clarifying questions: a reply now includes the pasted cards. Before, a paste-only reply did nothing.
  • Titles and labels: the project title, the version label and the naming/suggestion transcript read the paste. Before, a paste-only first message titled the project "New Chat".
  • Queued messages: they show the same card as sent ones, and clicking it shows the text.
  • Names: a new card takes the lowest unused name. Before, removing a card could give two cards the name "Pasted text 2".
  • Edit dialog: a click outside no longer throws away unsaved edits. On iOS the page no longer zooms when the dialog opens.
  • Read-only composer: while a send sets up, the composer no longer takes a long paste as a card.
  • Screen readers: a paste that becomes a card is announced.
  • Tray: file thumbnails are now the same 100px card as the pasted one. Images fill the card. Other files show a small file icon over their name. The marketing pages' composer matches.

npm test passes, with new checks in test-paste-as-attachment.mjs and test-composer-drafts.mjs. Each fix was checked in the browser on the local dev server, in light and dark mode and at phone width.

jelveh added 13 commits November 4, 2026 18:26
Pasting a log, a spec or a file's contents filled the composer with a wall
of text. A paste of 2,000+ characters or 30+ lines now goes in the
attachment tray as a card instead, styled after claude.ai's: the opening
lines in small faint type over a PASTED badge, with an edit pencil and the
usual remove button. Clicking the card shows the whole text; the pencil
opens it for editing (saving it empty removes the card).

The paste is not a file: it is never saved to assets/. It is stored as a
text document block marked as pasted, so the sent message (and a reloaded
chat) shows the same card, and the model gets the whole text inline,
labelled as text the user pasted. Shorter pastes are unchanged.
A long paste used to be composer text, so the draft saved it: a reload or a
trip to another project brought it back. As a card it lived only in memory
and was lost. The draft envelope now carries the cards (queued messages'
first, like their text), every add, edit or remove saves it, a restore puts
them back in the tray, and a send takes them out of the tray with the text.
Cards count toward the cloud mirror's size cap, and a card-only composer is
kept, not replaced, when the user signs in.
A composer send stopped before its message reached the conversation (Stop
while files upload) puts its text back in the box. A long paste used to be
part of that text; as a card it was dropped. The card now comes back with
the text. Files still don't: their blob URLs are about to be revoked.
While the AI waits on clarifying questions, the composer's text is the
reply. A long paste used to be part of that text; as a card it was left out,
and a paste-only reply did nothing at all. The cards now go in the reply
after the typed text and leave the tray.
Next to the 100px Pasted text card, a file was a 60px square with the file
icon stretched across it. Every tray thumbnail is now the same 100px
rounded card. An image fills it, with its name along the bottom; any other
file shows the file icon small and centred over its name. The marketing
pages' composer copies the app's tray, so it changes with it.
The sent message shows a long paste as a Pasted text card, but the queued
message drew it as a plain 📄 Pasted text chip that did nothing. It now
uses the same card, and clicking it shows the text.
A new card was numbered by counting the cards in the tray, so after
removing an earlier one the next paste reused a name still in use: two
"Pasted text 2" cards, and the model read both under the same label. A new
card now takes the lowest name not in use.
A long paste used to be the message's text, so the project title, the
version label (and its GitHub commit message) and the naming/suggestion
transcript read it. As a card it is a document block those skipped: a
paste-only first message titled the project "New Chat" and its version
"Update". They now read the paste after any typed text.
The paste never reaches the box, so a screen reader user heard nothing and
found the box empty. Say that it was attached, and under which name.
A click outside the Edit pasted text dialog closed it and threw the edits
away without a word. With unsaved changes it now keeps the dialog open and
puts the cursor back in the text. Cancel, the close button and Escape
still discard on purpose; an unchanged dialog still closes.
The Edit pasted text dialog focuses its 12px text area itself, and iOS
Safari zooms the page into any field under 16px when it gets focus. On
screens up to 900px the text is 16px, as for the app's other dialog fields.
While a send sets up, the composer is read-only and the browser drops a
short paste. A long one still went into the tray as a card, and the send's
draft clear then left it out of the draft.
# Conflicts:
#	package.json
#	src/js/app.js
@jelveh
jelveh merged commit c51879b into main Nov 5, 2026
1 check passed
reynaldichernando pushed a commit to reynaldichernando/builder that referenced this pull request Nov 11, 2026
* Turn a long text paste into a Pasted text card

Pasting a log, a spec or a file's contents filled the composer with a wall
of text. A paste of 2,000+ characters or 30+ lines now goes in the
attachment tray as a card instead, styled after claude.ai's: the opening
lines in small faint type over a PASTED badge, with an edit pencil and the
usual remove button. Clicking the card shows the whole text; the pencil
opens it for editing (saving it empty removes the card).

The paste is not a file: it is never saved to assets/. It is stored as a
text document block marked as pasted, so the sent message (and a reloaded
chat) shows the same card, and the model gets the whole text inline,
labelled as text the user pasted. Shorter pastes are unchanged.

* Keep a pasted text card in the composer draft

A long paste used to be composer text, so the draft saved it: a reload or a
trip to another project brought it back. As a card it lived only in memory
and was lost. The draft envelope now carries the cards (queued messages'
first, like their text), every add, edit or remove saves it, a restore puts
them back in the tray, and a send takes them out of the tray with the text.
Cards count toward the cloud mirror's size cap, and a card-only composer is
kept, not replaced, when the user signs in.

* Hand a pasted text card back when its send never lands

A composer send stopped before its message reached the conversation (Stop
while files upload) puts its text back in the box. A long paste used to be
part of that text; as a card it was dropped. The card now comes back with
the text. Files still don't: their blob URLs are about to be revoked.

* Send a pasted text card with a reply to a clarifying question

While the AI waits on clarifying questions, the composer's text is the
reply. A long paste used to be part of that text; as a card it was left out,
and a paste-only reply did nothing at all. The cards now go in the reply
after the typed text and leave the tray.

* Size the tray's file thumbnails like the pasted text card

Next to the 100px Pasted text card, a file was a 60px square with the file
icon stretched across it. Every tray thumbnail is now the same 100px
rounded card. An image fills it, with its name along the bottom; any other
file shows the file icon small and centred over its name. The marketing
pages' composer copies the app's tray, so it changes with it.

* Draw a queued message's pasted text as a card

The sent message shows a long paste as a Pasted text card, but the queued
message drew it as a plain 📄 Pasted text chip that did nothing. It now
uses the same card, and clicking it shows the text.

* Give each pasted text card its own name

A new card was numbered by counting the cards in the tray, so after
removing an earlier one the next paste reused a name still in use: two
"Pasted text 2" cards, and the model read both under the same label. A new
card now takes the lowest name not in use.

* Read a pasted text card as the user's words in titles and labels

A long paste used to be the message's text, so the project title, the
version label (and its GitHub commit message) and the naming/suggestion
transcript read it. As a card it is a document block those skipped: a
paste-only first message titled the project "New Chat" and its version
"Update". They now read the paste after any typed text.

* Announce a long paste that became a card

The paste never reaches the box, so a screen reader user heard nothing and
found the box empty. Say that it was attached, and under which name.

* Keep unsaved edits to a pasted card on a click outside

A click outside the Edit pasted text dialog closed it and threw the edits
away without a word. With unsaved changes it now keeps the dialog open and
puts the cursor back in the text. Cancel, the close button and Escape
still discard on purpose; an unchanged dialog still closes.

* Stop iOS from zooming in when the pasted text editor opens

The Edit pasted text dialog focuses its 12px text area itself, and iOS
Safari zooms the page into any field under 16px when it gets focus. On
screens up to 900px the text is 16px, as for the app's other dialog fields.

* Don't take a long paste into a read-only composer

While a send sets up, the composer is read-only and the browser drops a
short paste. A long one still went into the tray as a card, and the send's
draft clear then left it out of the draft.
@reynaldichernando
reynaldichernando deleted the paste-as-attachment branch November 11, 2026 03:08
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