Skip to content

Swipe a message for its options, and show an org's pinned messages - #102

Merged
simonhamp merged 10 commits into
mainfrom
feat/message-pins
Oct 28, 2026
Merged

simonhamp merged 10 commits into
mainfrom
feat/message-pins

Conversation

@simonhamp

@simonhamp simonhamp commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

Fixes #103

Dragging a message sideways now slides it aside to show its options, and org threads show pinned messages the way the web does.

Dragging for options. A tap on a bubble no longer opens React and Reply. Dragging a message toward its own edge moves it with the finger, partly off the screen, and the options grow in one after another where it was, nearest the bubble first: someone else's message to the left, your own to the right by the same distance. Let go past halfway, or with a flick, and it springs open; short of that it springs back and the options shrink away. The distance is the width of the options, or less for a short bubble so some of it stays on screen. Dragging it back, opening another message, or tapping anywhere in the thread slides it shut. A closed message's options are only drawn, not tappable, so a tap beside it can't land on them. Double tap still hearts, and long press is left free for the message menu that's coming next. Swiping no longer replies on its own; Reply is one of the options.

This needs two unreleased framework changes before it can merge, both on branch feat/gesture-area-snap:

  • NativePHP/mobile-air: snap-x / snap-y on native:gesture-area, the points a pan settles on.
  • NativePHP/mobile-ui: the pan springs to the nearest snap point on release and to PHP write-backs, and on iOS a one-axis pan only starts on movement along its axis. Without it, a drag that starts on a message stops the thread scrolling on iOS.

Once both are merged, this needs a lock bump for nativephp/mobile and nativephp/mobile-ui. Simon tried it on his iPhone with both in through path repos.

Pinning. In an org, maintainers get Pin among the options (the API only lets maintainers pin). Pinning asks first with the web's "Pin for the whole org?" warning, which you can turn off. Pin and Unpin show straight away, go through writeInBackground, and take the pinned list the API answers with. If the API says no, the pin goes back and a toast says why: the API's own words for a 403 (the 25-pin limit, a timeout), otherwise "Could not pin. Try again." A pin that loses the race for the last place counts as a no, as on the web.

Pins show the way the web shows them:

  • a bar over the top of the thread with the latest pin, who wrote it, and how many there are. It lies over the thread instead of pushing it down.
  • a sheet from the bar listing every pin, latest first, with Reply, React, Unpin for maintainers, three lines of each and Show more. Reply and React work on pins from before your history too.
  • a pin beside each pinned bubble. For maintainers it turns into Unpin while the options show.

Polls bring pins from other people. A poll that lands while your own pin is still on its way is ignored, and the next poll asks again from the pins' version so nothing is missed, the same way the web handles it.

Two Android fixes this needed.

  • Android's rows didn't shrink one child to make room for the next, so a long message from someone else pushed its option slot off the screen (on main too, for React and Reply). Let a row's shrinking children wrap short of a fixed sibling on Android mobile-air#623 fixed that in the renderer, so this PR bumps the nativephp/mobile lock from 54aae817 to ca8382f8 and the bubble simply shrinks again. The bump also brings the other mobile-air main commits since then (#608, #528, #619, #617, #621, #622), so native:install android --force (and ios) is needed after pulling.
  • On Android a swipe that ended inside the bubble also landed as a tap, which put the options straight away again. A tap within 0.75 s of the drag that showed them is still treated as part of it, though the drag now consumes the touch, so this may no longer be needed.

Things to look at:

  • On iOS 26 a sideways drag from anywhere on a pushed screen goes back. The mobile-ui change makes a drag on a message win over that, so dragging your own message right doesn't pop the thread. The edge swipe still goes back.
  • The recordings below show the earlier swipe, where the message moved once the swipe was recognised, not the drag that follows the finger. The resting and open states look the same.
  • In orgs the option slot is 98 points wide instead of 64, so it fits Pin, Reply and React whether or not Pin shows. Bubbles in orgs wrap a bit earlier because of it. The web reserves the same room so a pin arriving never rewraps a bubble.
  • The nav title can turn white in light mode when a dark bubble of yours sits right behind it. That's iOS 26's Liquid Glass bar picking a colour for contrast with what's under it, not a change in this PR; it shows in the after shots because the narrower org bubbles push that teal bubble up behind the title.
  • Lucide::Pin now draws the web's Lucide pin from resources/icons instead of the SF Symbol and Material glyph, so the commit menu's "Pin to profile" and the profile's pinned label pick up the new glyph too.
  • EDGE has no custom accessibility actions, so VoiceOver and TalkBack users can't reach the options with a double tap any more. The long-press menu planned next will fix that, since double-tap-and-hold works with a screen reader.

Screenshots

thread

iOS light iOS dark Android light Android dark
Before
After

actions (before: a tap on "staging is green"; after: a swipe to the left)

iOS light iOS dark Android light Android dark
Before
After

Recordings: iOS light, before, iOS light, after, iOS dark, after, Android light, after, Android dark, after

pinned (new)

iOS light iOS dark Android light Android dark
After
  • No visible change

🤖 Generated with Claude Code

simonhamp and others added 6 commits October 27, 2026 18:41
A tap on a bubble no longer brings up React and Reply. A swipe away from
the bubble's edge does, and a swipe back or a tap anywhere in the thread
puts them away. Maintainers get Pin there too.

Pinned messages show as the web shows them: a bar over the top of the
thread with the latest one, a sheet listing them all, and a pin beside
each one in the thread. Pin and Unpin go through the background write
path, roll back with a toast when devnull says no, and polls bring pins
from the others.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A press handler on every row gave Android a ripple across the row on any
tap beside a bubble, and a round trip to PHP for nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ing itself

Android's rows don't shrink one child to make room for the next, so a long
bubble pushed its action slot off the screen, pin included. The bubble now
keeps a margin as wide as the slot, and the slot lies in it.

A swipe that ends inside the bubble also lands as a tap on Android, which
put the actions straight away again. A tap within 0.75 s of the swipe that
showed them is now taken as part of it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Simon asked for the swipe to move the message aside, partly off the
screen, with its actions where it was: someone else's message to the
left, your own to the right by the same distance. It slides by the room
its actions take, or less for a short bubble so some of it stays on the
screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t it off

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…flex-shrink

NativePHP/mobile-air#623 makes Android rows shrink a flex-shrink child to
fit a fixed-width sibling, as iOS always did. That retires the margin and
absolute slot this PR added to keep a long bubble's actions on screen.
The lock moves nativephp/mobile to ca8382f8, which brings that fix and
the other main commits since 54aae817.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
simonhamp and others added 2 commits October 28, 2026 17:07
Simon asked for the message to follow the finger rather than pop open:
each action grows in over its share of the drag, nearest the bubble
first, and on release the message springs open or back. Each message
now keeps a SharedValue its gesture area drags (with snap points at home
and fully open) and its row and buttons follow, and PHP hears where it
settled through @Dragend.

This needs the gesture area's snap points (mobile-air) and an iOS pan
that leaves vertical scrolling alone (mobile-ui), both unreleased.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A closed message's actions are shrunk and faded, but a button keeps its
whole box for taps, so a tap beside a closed message could land on an
invisible Reply or React. Simon hit this on his phone. A closed message's
actions are now only drawn, still growing in under the finger, and turn
into buttons once it settles open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main added pinning and unpinning the whole thread from its menu, as pin() and
unpin() on Conversation. The message ones here are now pinMessage() and
unpinMessage(), so the menu's actions still reach the thread's.

Main also added the pin and pin-off icons this branch had. The Lucide enum
keeps main's entries, plus the filled pin.

Both branches wrote a rendersRef() test helper. It lives in tests/Pest.php now.

The lock keeps nativephp/mobile at ca8382f8. That commit already contains
main's 16b1f23.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@simonhamp
simonhamp merged commit 69046d9 into main Oct 28, 2026
2 of 3 checks passed
@simonhamp
simonhamp deleted the feat/message-pins branch October 28, 2026 18:55
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.

the pinned bar is not displayed in iOS

1 participant