apps/mobile · page comments
+One sheet, not a floating card
++ Replacing the anchored comment popover with a single Liquid Glass sheet + that carries the quick replies and the composer — built the way + Instagram, Snapchat and Messages build the same thing. +
+What's on screen today
+
+ Tap a block in comment mode and a card is placed beside it by
+ popoverPlacement(), which flips it above or below the
+ block, clamps it under the header, and subtracts the keyboard from the
+ container so it has somewhere to land. Quick feedback is a second
+ surface, pushed from inside that card.
+
Why the placement keeps fighting us
+-
+
-
+ 1
+ Two axes of guessing. The card starts at an assumed
+
ESTIMATED_HEIGHT = 180, measures itself in +onLayout, then moves — the first frame is in the + wrong place. +
+ -
+ 2
+ The keyboard is modelled by hand:
+
keyboardWillShowlisteners shrink the container and a + ReanimatedwithTiming(250ms)approximates a curve + UIKit owns. +
+ - + 3 + On a tall block there is nowhere good to go. The flip puts + the card over the selection; the clamp pins it under the glass + header. Both branches hide what you are commenting on. + +
-
+ 4
+ Quick feedback is a different surface — ⚡ pushes
+
pages/[slug]/quick, a real formSheet, on top of a + card that is imitating one. +
+ -
+ 5
+ Dismissal is hand-built: an ✕ in the card plus a
+
pointer-downmessage from inside the WebView to catch + outside taps. +
+ - + 6 + It is a rectangle we drew. Nothing about it is a system + surface, so it gets no glass, no grabber, no drag, and no + keyboard choreography. + +
How everyone else does quick replies
++ Instagram, Snapchat, WhatsApp and Messages all solved this, and they + converged on nearly the same object. Worth copying the parts that are + settled rather than re-deciding them. +
+ +The five rules they agree on
+-
+
- + 1 + The row lives immediately above the input, in the same + container. It never appears as its own screen, and it rides + the keyboard up rather than being replaced by it. + +
- + 2 + One tap sends. No confirm, no second step, no "are + you sure" — haptic, send, dismiss. The whole point is that it is + faster than typing. + +
- + 3 + Six or fewer, fixed. Instagram ships six emoji, + Messages six tapbacks, Snapchat seven Bitmoji reactions. Past + that it stops being scannable and becomes a menu. + +
- + 4 + "More" goes at the end. Instagram's ⊕ and WhatsApp's + + are the last item in the row and open the full picker. + The escape hatch never sits in the middle of the fast path. + +
- + 5 + The target is quoted. Instagram shows the story + behind the bar, Snapchat attaches the reaction under the message, + WhatsApp quotes the bubble. You always see what you are replying + to while you reply. + +
+ Two places our case differs, and what to do about it. +
++ Our quick replies are actions, not emotions. ❤️ needs no label + in any language; "ask for this to be removed" does. Glyph-only is why + those rows read as reaction bars rather than forms — but the current + trash / ⚡ / thumbs-up row is already glyph-only and already ambiguous + at a glance. Adopt the shape, not the bareness: SF Symbol with a + one-word caption under it. +
++ ⚡ is in the wrong position. It is the "more" affordance and it + currently sits between the two fast actions. By rule 4 it belongs + last: Remove · Looks good · More. +
+The proposal
++ Selecting a block opens one sheet, at a detent that fits its contents. + On iOS 26 a partial-detent sheet is Liquid Glass by default and detaches + from the screen edges, so the page stays visible behind and around it — + which is exactly the property the floating card was trying to buy and + kept losing. +
+ +fitToContents, keyboard down,
+ glass over the page. Two quick replies are one tap; "More" is last,
+ per rule 4.
+ What the system gives us for free
+-
+
- ✓Glass. A partial-detent sheet on iOS 26 gets the material, the specular edge and the shadow with no code — the same language as the toolbar capsules already floating over this page. +
- ✓Placement. Always bottom, always full width, always a height that fits its contents. +
- ✓Keyboard. UIKit raises and lowers it on the real curve. Delete the listeners and the timing constant. +
- ✓Dismissal. Drag the grabber, tap outside, swipe — all native, all one
router.back()that clears the selection.
+ - ✓Room to grow. A thread list, an intent picker, an attachment row all have somewhere to live. The card had nowhere. +
Viewfinder button in the glass toolbar, unchanged.
+Haptic, blue highlight, anchor stored via setPick — unchanged.
Keyboard down. Page scrolls so the highlight clears the sheet.
+One tap posts and dismisses. Tapping the field raises the keyboard and the sheet with it.
+Thread created, sheet closes, selection clears, pin lands on the block.
+How to build the container
+
+ Three shapes are available, and the repo already has a precedent for
+ one: workspace/[id]/line-comment is this exact thing — a
+ comment composer in a formSheet route, fed its anchor through a store.
+
| Shape | What it costs | |
|---|---|---|
| Take | +
+ expo-router formSheet route +
|
+
+ Matches the mobile convention and the Costs a route transition and moves submit to another screen — but |
+
| Skip | +In-screen sheet An absolutely positioned view with |
+ Keeps state local and skips the transition, but re-implements detents, grabber, drag-to-dismiss and the dim layer — and would need |
+
| Skip | +Composer overlay, no sheet The native module pinned to the bottom with |
+ Fewest moving parts on paper, but its expanded mode claims the whole screen. It is built for "write a prompt", not "annotate the thing you can see". |
+
The one thing the sheet doesn't solve: the block can end up underneath it.
+
+ No protocol change needed. The host already knows the selection's rect
+ (rects[PENDING_ANCHOR_ID], kept live by the
+ track message) and the current offset
+ (scrollYRef), so it can send the existing
+ restore-scroll with
+ y = scrollY + rect.top − targetTop when the sheet opens,
+ and again when the keyboard changes the visible strip.
+
Known trap: the Liquid Glass flare on formSheet.
+
+ react-native-screens never sets
+ UISheetPresentationController.backgroundEffect (iOS 26.1),
+ so a partial-detent sheet keeps the material's interactive
+ glass: a bright specular highlight chases your finger across the
+ sheet, and on a dark background it reads as a glowing blob. Filed
+ September 2026 against 4.26.2 / 4.27.0 — we are on 4.26.0 — and still
+ open. The reporter's patch sets a UIColorEffect matching
+ the sheet background. Budget for either a patch or a plainer sheet
+ background until it lands upstream.
+
"Can we use native text input?"
+
+ Worth splitting: TextInput is already a real
+ UITextView. The live choice is whether comments adopt
+ modules/composer, the SwiftUI composer that Home, the
+ terminal and files-changed already use.
+
| Option | What you get | What it costs |
|---|---|---|
Keep RN |
+ Already themed with uniwind, already used by |
+ No dictation, no paste handling, no native height reporting. The quick row stays RN buttons we draw ourselves. |
+
Adopt the
|
+ The quick replies become the native |
+ Built for agent sessions: models, session tabs, slash commands and attachments all passed empty. Its expand/backdrop machinery overlaps the sheet, so |
+
+ Recommendation: ship the sheet with the RN composer, then swap.
+ The sheet is the change that removes the bugs; the input is a swap
+ behind two props (onSubmit, pending). Doing
+ both at once puts a pod rebuild between us and knowing whether the
+ layout is right. If the quick-keys strip turns out to be the
+ look you want, it lands as a second commit with nothing else moving.
+
What changes in the tree
+| Path | Note | |
|---|---|---|
| + | app/(authenticated)/pages/[slug]/comment.tsx | Route re-export, plus the |
| + | screens/…/pages/[slug]/comment/CommentSheet.tsx | Quoted anchor, quick row, composer. Modelled on |
| − | screens/…/components/CommentPopover/ | Gone: the card, the keyboard listeners, the ✕, the |
| ~ | screens/…/components/ComposerActions/ | Reorders to Remove · Looks good · More and gains captions. Still shared with whatever renders it. |
| − | packages/shared/src/page-comments/popoverLayout.ts | Mobile stops calling it. |
| ~ | app/(authenticated)/pages/[slug]/quick.tsx | Becomes the "More" expansion inside the sheet rather than a sheet of its own. |
| ~ | PageDetailScreen.tsx | Keeps selection, highlight, pins and |
+ Note on the working tree. The in-flight insetTop
+ work — popoverLayout.ts, its tests, and the
+ headerHeight plumbing — exists to keep the floating card
+ clear of the glass header. The sheet makes that unnecessary on
+ mobile. The PageFrame contentInset fix is
+ unrelated and should stay either way.
+
Open questions
+Three calls worth making before anyone writes code.
+ +1. Does the keyboard come up on open?
++ The popover autofocuses, and Instagram raises the keyboard with its + reaction row — but Instagram's sheet is the whole screen, so + it costs nothing. Ours has to leave the page visible, and autofocus + spends half the screen before you have decided whether to type. +
+Recommend: open with the keyboard down. Quick replies are one tap; the field is one more.
+2. Captions under the quick replies, or glyphs alone?
+
+ Glyph-only is what makes these rows feel like reaction bars, and it is
+ what the native quickKeys strip gives us. But 🗑 on a
+ document means "delete this comment" at least as readily as "ask for
+ this to be removed".
+
Recommend: SF Symbol with a one-word caption. Revisit once the labels have earned their recognition.
+3. Does the sheet show the thread when one already exists?
++ Tapping an existing pin opens the full all-comments sheet with focus. + With a per-anchor sheet in place, a pin could open the same sheet + showing that thread with a reply field — which is how every app in the + prior art handles it. +
+Recommend: out of scope here. Get new comments onto one surface first, unify pins after.
+Sources
+-
+
- Instagram's quick-response emoji for Stories — Social Media Today +
- How to react on an Instagram story — the six-emoji bar and the ⊕ full-keyboard escape +
- Snapchat emoji reactions — long-press, tap, attaches under the message +
- Snapchat Bitmoji reactions — the fixed set of seven +
- Presenting Liquid Glass sheets in SwiftUI on iOS 26 — partial detents float and use glass; large detent turns opaque +
- react-native-screens #4605 — the formSheet glass flare, still open +