Skip to content

feat: add iMessage send with effect example - #39

Merged
blazejkustra merged 9 commits into
mainfrom
feat/imessage-effects
Sep 14, 2026
Merged

blazejkustra merged 9 commits into
mainfrom
feat/imessage-effects

Conversation

@blazejkustra

@blazejkustra blazejkustra commented Sep 14, 2026 •

Copy link
Copy Markdown
Owner

Problem

The last issue #31 idea was missing: an iMessage clone where sending a message plays a full-screen effect, convincing enough to pass for the real app in a showcase clip.

Approach

Real chat, bubbles, composer and effects sheet matched to a Messages screenshot; each effect is a transparent shader overlay that plays once. A scripted five-step demo drives it: each chip posts its own line with its effect and the reply lands once the effect ends. Invisible ink was dropped from scope.

Tests

  • iPhone 17 Pro sim: all five effects via __imsgSend and by touch, including a real long-press to the sheet; full scripted run in order, rapid triple-tap posts one line only; reply waits for the effect; bubbles ease in and stay visible after a clean restart.
  • Banners screen unchanged (BirthdayFireworks.tsx, PartyConfetti.tsx byte-identical).
  • WGSL naga-clean; yarn lint and yarn typecheck pass.
  • Not verified: GPU frame time on a device (echo samples the bubble 22× per fragment full-screen; the sheet preview loops it while open), Android. Confetti was built without the reference images, so it needs a look.

Screenshots

An iMessage clone where holding send opens the effects sheet and the
message goes out with a full-screen effect: echo, spotlight, confetti,
fireworks or lasers. Short press sends plain.

The chat, bubbles, composer and sheet are real views; every effect is a
transparent shader overlay in front of the chat that plays once and
unmounts. Echo samples a view-shot of the sent bubble and flings copies
out on a golden-angle spiral. Spotlight darkens everything except a
soft hole around the new bubble and sweeps a beam onto it. Confetti is
a one-shot shower of tumbling paper in two depth layers. Fireworks
ports the banner shader's shell and spark maths into an overlay, sized
in banner heights so bursts match the Banners screen; the banner
component itself is unchanged. Lasers fan eight beams through haze.

A screen effect starts only once the bubble has reported its rect, and
never sooner than 150 ms, matching iMessage. Invisible ink was dropped
from scope, so the sheet has no bubble tab. __imsgSend and __imsgState
are exposed in dev builds.

Claude-Session: https://claude.ai/code/session_01MYgJDumHFGETXWCh37fS7V
Echo's copies clustered within a few hundred pixels of the bubble and
shrank to a quarter size, leaving the upper half of the screen empty.
Each copy now owns a slot in a sunflower spread over the whole screen
and swings into it about the bubble, with alpha held until the last
third so the swarm fills the screen before it thins.

Confetti spawned uniformly, so there was no dense front; spawn is now
skewed so most pieces drop first and a trail follows.

Two shaders used half as an identifier, which is reserved in WGSL and
only tolerated by the simulator's compiler. Removed a dead ref in the
hook and a duplicated effect list in the screen.

Claude-Session: https://claude.ai/code/session_01MYgJDumHFGETXWCh37fS7V
The header was the giveaway. It is now the real one: dark circular
back and FaceTime buttons flanking a large avatar over a name pill with
a chevron, on solid black with no separator, and the thread opens with
the centred iMessage, Encrypted and date block. Bubble colours are
sampled from a real screenshot, with radius, padding and run spacing to
match. The composer is a plus circle beside a translucent pill whose
microphone becomes the blue send arrow only once there is text; long
press for effects and short press to send plain are unchanged. The
glyphs are drawn from plain views since the app has no icon font.

The seed conversation is now about Shopify's move back to native, kept
fond rather than bitter.

Claude-Session: https://claude.ai/code/session_01MYgJDumHFGETXWCh37fS7V
Eight one-line texts about Shopify's move back to native, without the
lecture. Thumbnail recaptured with the new bubbles.

Claude-Session: https://claude.ai/code/session_01MYgJDumHFGETXWCh37fS7V
The thread opens with one message from the other side. Each effect
button posts the next scripted reply with that effect, and the other
side answers a second later, so a showcase run walks the whole
conversation without typing. One pending reply at a time, so mashing
buttons cannot queue a monologue; once the script is spent the buttons
fall back to a default line with no reply.

The buttons are now part of the demo rather than a dev-only row: pill
chips that fill blue when pressed and keep a tick once used. The tick
always occupies its slot so the row never reflows as chips get used,
which is how a tap landed on the wrong chip on the first cut.

Claude-Session: https://claude.ai/code/session_01MYgJDumHFGETXWCh37fS7V
Each effect now owns one line and its reply, so the energy of the text
matches the effect whatever order the buttons are pressed. Spotlight
gets no reply so nothing lands on top of it. The buttons sit on one
row in the intended demo order; the used state is colour only, since
a reserved tick slot would not fit five chips on a line. Typed
messages no longer trigger scripted replies.

Claude-Session: https://claude.ai/code/session_01MYgJDumHFGETXWCh37fS7V
…yping

The drawn camera read as a play triangle; the header now tints a mask
rendered from the SF Symbol. While the other side is typing, effect
buttons are dimmed and ignored, since a quick run of taps posted three
lines before the first answer and scrambled the thread.

Claude-Session: https://claude.ai/code/session_01MYgJDumHFGETXWCh37fS7V
The other side's reply now lands shortly after the effect reports done
rather than on a fixed timer, with the buttons held until it arrives.
Incoming bubbles ease in from the tail corner with no overshoot;
outgoing ones stay static because they are measured and snapshotted
right after layout for their effect.

Message ids embed the mount time, which fixes a duplicate-key warning
Fast Refresh caused by resetting the module counter. The contact is
plain Kacper again; the surname was invented during the layout pass.
The script says agents, not robots.

Claude-Session: https://claude.ai/code/session_01MYgJDumHFGETXWCh37fS7V
Both sides now enter with the same short ease-out from their tail
corner. Extending it had made every bubble invisible: the duration
constant was declared below the entering worklet, and a worklet
captures its closure at creation, so withTiming saw an undefined
duration and never completed. The constant now precedes the worklet.

Because an outgoing bubble is snapshotted for its effect, the capture
now waits for the entrance to finish rather than firing after layout,
so echo no longer copies a half-faded bubble. The echo and lasers
lines are rewritten so the exchange answers itself.

Claude-Session: https://claude.ai/code/session_01MYgJDumHFGETXWCh37fS7V
@blazejkustra
blazejkustra merged commit 388bc53 into main Sep 14, 2026
2 checks passed
@blazejkustra
blazejkustra deleted the feat/imessage-effects branch September 14, 2026 16:53
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