Skip to content

A pasted list of addresses becomes one chip per address - #89

Merged
karatektus merged 6 commits into
mainfrom
fix/compose-paste-recipients
Oct 28, 2026
Merged

karatektus merged 6 commits into
mainfrom
fix/compose-paste-recipients

Conversation

@karatektus

Copy link
Copy Markdown
Owner

Closes #82.

#83 by @jo2904, with the review fixes on top, so it can merge after #75.

What changes

  • A pasted list of addresses becomes one chip per address #83 as it was: a pasted list of addresses becomes one chip per address, read with the web composer's pattern (pl_mail#380). See A pasted list of addresses becomes one chip per address #83 for the details.
  • Punctuation around an address is refused (asRecipient). Jane Doe (jane@doe.fr) and jo@x.fr. at the end of a sentence used to become chips for (jane@doe.fr) and jo@x.fr., which failed only at submission. Brackets, quotes and the other characters an unquoted address cannot hold are now refused, as is a leading, trailing or doubled dot in either half. The refused pieces stay in the field and are reported. Two new cases in ComposeInputTest.
  • French copy of compose_error_not_an_address, now that French translation #75 is in. Without it, lint's MissingTranslation fails.
  • A German comment about endonyms said "both languages"; there are three now.

How it was checked

  • No Android SDK here, so CI's build is the check. Key and placeholder coverage of every values-fr/ file against values/ was checked with a script.

Generated by Claude Code

jo2904 and others added 5 commits October 27, 2026 08:55
The address parser had one model: cut on `,`, `;` and new lines, then
read each entry as `Name <address>` or a bare address. A probe test
against it showed what that does to text people actually paste:

  "a@x.fr b@y.fr"                  -> nothing (one entry, two @)
  "a@x.fr<TAB>b@y.fr"              -> nothing
  "Jean Dupont <jd@x.fr> jo@jo.fr" -> nothing (fails ^(.*?)<([^>]+)>$)
  "jo@jo.fr Jean Dupont <jd@x.fr>" -> one recipient, jd@x.fr, named
                                      "jo@jo.fr Jean Dupont"
  "mailto:jo@jo.fr"                -> the address "mailto:jo@jo.fr"
  "anna lists@example.org"         -> one address with a space in it

Cutting on separators cannot be fixed by adding a space to the list:
a space must split `a@x.fr b@y.fr` and must not split `Jean Dupont`.
So `recipientPieces()` picks each recipient out instead, with the
web composer's own pattern (pl_mail#380):

  (?:"[^"]*"|[^<>"@;,\r\n])*<[^<>]*>|[^\s<>;,"]+

A recipient is a name followed by `<address>`, or a run with no space
in it. A name may hold spaces, and commas inside quotes, but no `@`
outside quotes. That last rule is what keeps the address in front of a
spaced name from becoming part of the name. Each piece keeps its text
when it is not an address, so the field can hand it back rather than
drop it (next commit).

One validator for every entry, `asRecipient()`, used by both paths:
`mailto:` in front is dropped, and an address with whitespace in it is
refused. Deliberately no dot required in the domain, unlike the web's
check: plMail is self-hosted and `anna@nas` is a real address on a home
network. The server is the one that rejects what is wrong.

`parseAddresses()`, which reads share intents and mailto: links, keeps
cutting on `,`, `;` and new lines only. Those lists are written by
programs, one entry per comma, and a space inside an entry means the
entry is broken: splitting `anna lists@example.org` would write to
lists@example.org, a stranger. Now it refuses it, which is what the
comment in Mailto.kt already claimed happened. Mailto's own splitting
and decoding are untouched, and its 18 tests pass unchanged.

Three comments said the share path used "the same parser" as the field
for pasted text. It now shares the validator, not the splitting, and
they say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CFCshXUp57k1yHeHy68JXy
Counterpart to pl_mail#379 / #380 on the web.

What the field did with a paste: it committed only when the new text
*ended* in `,`, `;` or a space. A list copied out of another client
rarely ends in a separator, so it sat in the line as text until Enter
or until focus left, and then went through the old parser (previous
commit) that dropped most of it without a word.

`recipientEdit()` now decides what a change to the line means, as a
pure function so the JVM suite can hold it:

- A typed character commits only when it ends a recipient, a separator
  or whitespace. Anything else would chip `jo@x` while the user is
  still typing `jo@x.fr`, because `jo@x` is already an address.
- A paste commits at once. A text field reports values, not paste
  events, so a paste is "more than one character arrived at once".
  Deleting never commits.
- An unclosed `<` or quote is somebody typing `Jean <jd@x.fr` by hand,
  and the space in it must not cut it into pieces.
- Text with no address in it is never a commit: `Jean Dupont` pasted
  is a contact to look up. A single plain address pasted alone also
  stays text, as before, so suggestions still open on it.

That last rule also fixes typing a two-word name. The old onValueChange
handed "Jean " to commit(), which found no address and returned without
updating the field: the space was swallowed and "Jean Dupont" could not
be typed, let alone searched.

What is not an address stays in the line, joined with "; " as on the
web, and is reported. The rest of the list is still added, de-duplicated
without regard to case against the chips already there and within the
paste. Enter on text with no address reports it too. Leaving the line
with a half-typed name does not: that is an abandoned search, not a
mistake.

The report is a snackbar through ComposeError, not a line under the
field. Every composer error already goes that way, the text itself
stays visible in the field for correcting, and the header is kept to
one line per address field on purpose; an error row under To, Cc or
Bcc would be the only one of its kind. The message names the first
piece, as the web does: "“bob” is not an email address. Correct it in
the field, or remove it." New string in values/ and values-de/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CFCshXUp57k1yHeHy68JXy
`Jane Doe (jane@doe.fr)` and `jo@x.fr.` at the end of a sentence each
reached the validator as one run with no space in it, and became chips
for "(jane@doe.fr)" and "jo@x.fr." that failed only at submission.
Brackets, quotes and the other characters an unquoted address cannot
hold are now refused, as is a dot at either end of either half or two
in a row. Refused pieces stay in the field and are reported, as any
other.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FXqu9W8fGcoAz9KCdCHwEG
…unts three languages

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FXqu9W8fGcoAz9KCdCHwEG
@github-actions github-actions Bot added area: compose Concerns compose area: settings Concerns settings labels Oct 28, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FXqu9W8fGcoAz9KCdCHwEG
@karatektus
karatektus merged commit 9df3cb2 into main Oct 28, 2026
4 checks passed
@karatektus
karatektus deleted the fix/compose-paste-recipients branch October 28, 2026 10:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: compose Concerns compose area: settings Concerns settings

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pasting a list of addresses into To, Cc or Bcc loses most of it

3 participants