Skip to content

feat(chat): suggest @mentions from the server's pool - #942

Merged
bmc08gt merged 10 commits into
mainfrom
feat/mention-picker
Oct 2, 2026
Merged

bmc08gt merged 10 commits into
mainfrom
feat/mention-picker

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Adds the @-mention picker to the group-chat composer, backed by the server's ranked pool from Chat.GetMentionSuggestions (flipcash2-client-protocol 0.15.0, code-payments/flipcash2-client-protocol#22).

  • ChatService.getMentionSuggestions wraps the RPC. ErrorGetMentionSuggestions maps each result case by name, not by rawValue:. MentionSuggestion is its own type, not a Member.
  • ServerMentionSearch fetches the pool once per picker visit, filters it locally in the server's order, excludes blocked users, and moves a new message's sender to the front.
  • A failed fetch, DENIED or NOT_FOUND shows no suggestions, with no fallback. The next picker visit retries.
  • RosterSearchSource stays as the seam, with ServerMentionSearch as its only implementation.
  • Package.resolved pins 0.15.0 at a86e91bd.

This no longer depends on roster sync. Chat.GetRoster is kept private for now, so the roster-search stack (#915) is parked, and no store schema changes ship here.

Bar motion

The mention list and the reply strip share a GlassEffectContainer. With the list open, a reply peels out of the list's bottom edge and merges back in when it closes. Typing @ with a reply open splits the list out of the strip. A card opening or closing on its own still slides out from behind the composer.

Fixed along the way:

  • The composer slid down from the top of the bar when the list opened. The bar's host is now screen-tall and its height is reported after SwiftUI's update, so only the clip moves.
  • Filtering lifted the list's bottom edge about a row off the composer. The list now resizes in a single animation, and the clip holds the taller height until the spring ends.
  • With both cards open, the transcript lost its bottom pin: the list's room counted the list itself, and the bar flipped between two heights.

Name colours

ComplementaryPalette.nameColor lightens a person's .middle stop toward white until it reaches 4.5:1 against #262626, so names like #553ED9 (2.2:1) can be read. Mention rows, quote authors and the reply strip use it.

@bmc08gt bmc08gt self-assigned this Oct 1, 2026
@bmc08gt
bmc08gt force-pushed the feat/mention-picker branch from 48a3bdb to 28a4044 Compare October 1, 2026 18:34
@bmc08gt
bmc08gt changed the base branch from feat/roster-search to main October 1, 2026 18:34
@bmc08gt
bmc08gt marked this pull request as ready for review October 1, 2026 18:35
bmc08gt added 10 commits October 2, 2026 09:06
Typing @ at the start of the text or after whitespace, with a collapsed
cursor, opens a list of members above the reply strip. The fullwidth @
also triggers, matching Android. "a@b" never opens it. Whitespace, moving
the cursor off the word, deleting the @, send, or a pick closes it.
Groups only.

The word after the @ goes to RosterSearchSource; a new word cancels the
search in flight. Members without a username are left out, since a pick
inserts "@username " as plain text. The first open per visit prepares the
source and re-runs the query when it lands.

The list shows 4 rows, 3 with a reply open, and 2 when the full count
would leave under 120pt of transcript above the keyboard. Row metrics
live in MentionListMetrics for the design pass.

setBarHeight used to infer the reply strip's height from the delta
after `replying` flipped and subtract that stored value on close. The
list changes height while open, which would leave that value stale and
put the clip below the bar. The bar now reports which cards are open
and the height of any card still exiting; the clip subtracts the
exiting height and travels only when the set of open cards changes.
A row count change while open snaps.
RosterSearchSource.prepare now returns whether the source could ready
itself. The picker searches again only on success, and a picker closed
before prepare lands stays closed. Matches Android 345dde414.
Rows are 50pt with a 1pt white-10% divider inset to the name and none
after the last, so 4/3/2 rows measure 203/152/101pt. The row cap now
counts the dividers, through the same listHeight the frame uses.

The handle takes #EBEBF5 at 60%, and a pressed row gets the dark
tertiary fill. The card keeps the reply strip's glass, radius and
inset rather than the spec's solid fill.
A pick mid-text inserted "@username " before the existing space,
leaving "hi @maria  there". When whitespace already follows the word,
insert "@username" alone and put the cursor after that whitespace. At
the end of the text or before a non-space, the trailing space stays.
Add the flipcash2 0.15.0 RPC to ChatService and FlipClient. A
MentionSuggestion carries the user's profile and their last_sent_at; one
without a readable user id or username is dropped, since a pick inserts
the username. ErrorGetMentionSuggestions maps the result explicitly and
classifies like ErrorGetRoster.

Builds only against the local 0.15.0 checkout (FLIPCASH_PROTO_LOCAL) until
that version is tagged and the pin moves.
ServerMentionSearch is the session's RosterSearchSource. It fetches
Chat.GetMentionSuggestions once per composing session, when the picker
first opens in a visit, and filters the held pool locally: every query
word must prefix a display-name or username token, and matches keep the
server's order. A new message moves its sender to the front of the pool;
a sender the pool doesn't hold is not added, since the server decides who
is suggested. Fetched profiles are written to the profile cache.

A chat whose pool can't be fetched, or that the server answers DENIED or
NOT_FOUND, matches nobody and shows no error; the user can still type a
handle by hand. Text normalization lives in MentionSearchText, which
follows Android's rules.
Picks up Chat.GetMentionSuggestions. Package.resolved moves from 0.14.1
to 0.15.0 (a86e91bd).
The mention rows drew the monogram placeholder for everyone. Load each
member's thumbnail through the shared ProfileAvatarStore and pass it to
the avatar, as the reactors sheet does.
Some palette hues are too dark to read as a name on the dark surface
(#553ED9 is 2.2:1 against #262626). `ComplementaryPalette.nameColor`
blends the `.middle` stop 15% toward white until it reaches 4.5:1,
for at most ten steps, against a fixed #262626 so Android can apply
the same rule and land on the same colour.

Mention rows, the transcript's quote author and the composer's reply
strip use it. The reply strip's rule takes the name colour too, so it
keeps the same contrast as the name beside it.
…face

The mention list and the reply strip now share a GlassEffectContainer,
each with its own glass ID. With the list open, a reply peels out of
the list's bottom edge, and closing it merges the strip back in. Typing
`@` with a reply open splits the list out of the strip. The container's
spacing equals the 12pt gap the cards rest at, so a neck joins the two
shapes until the split lands. A card opening or closing on its own still
slides out from behind the composer.

This also fixes several jumps while the list resizes:

- The composer slid down 219pt from the top of the bar when the list
  opened. The bar's host was resized inside SwiftUI's animated update,
  so the whole bar animated toward its new size. The host is now
  screen-tall and pinned to the bottom, and its height is reported
  after the update.
- Filtering moved the list's bottom edge about a row off the composer
  and back. When results change, the list now resizes in a single
  animation, and the UIKit clip holds the taller of the old and new
  heights until the spring ends.
- With both cards open, the transcript lost its bottom pin. The list's
  room included the list itself, so the bar flipped between 313.7 and
  149.7pt within 17ms. The room now leaves the list out.
- The transcript and the fade above the bar follow the glass edge
  during a held shrink, so they no longer wait out the hold and then
  snap.

Rows are built eagerly. A lazy row created while the card grew slid in
from the wrong place, along with its divider.
@bmc08gt
bmc08gt force-pushed the feat/mention-picker branch from 28a4044 to 0e77a1e Compare October 2, 2026 21:20
@bmc08gt
bmc08gt merged commit 16f7f3b into main Oct 2, 2026
2 of 3 checks passed
@bmc08gt
bmc08gt deleted the feat/mention-picker branch October 5, 2026 15:16
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