Agent approvals: Stack flow
How a pending agent purchase is approved on Envoy Home: the fanned card stack, the full-stack list, the amount-first sheet, passkey / OTP, and the Shatter success mark. This page has the working prototype, React Native reference code built on @elixir/ui-kit/envoy, the Figma handoff and the success Lottie.
1 · Prototypes
On a laptop the flow chart sits on the left (click a box to jump to that screen) and RN notes open on the right. On a phone it's the bare device. Each card is tagged with the auth path it exercises; ↻ next to SS resets.
2 · React Native
Reference implementation that plugs into apps/elixir-card. Type-checked under strict against the app's own dependencies (RN 0.83.4, Reanimated 4.2, RNGH 2.30, lottie-react-native 7.3, @elixir/ui-kit/envoy), with 0 errors. Not yet run on a device.
| Figma screen | RN component | What to watch |
|---|---|---|
| H01 Home · Stack | CardStack · SectionHeader | Tilt is positional (−0.3° / +1.7° / +3.7°), one centre. "1 of 9" is the CTA → list. |
| H03 Home · Stack swipe | CardStack (Pan) | Past 90pt → fling 260ms → back of the queue; every card rotates to its new slot angle in 460ms. |
| H05 Home · approved row landed | ApprovedRow | Row drops in 380ms, then the disc scales in, then the check strokes on. |
| H06–H09 Notification rail | NotificationRail | In-screen overlay (not a Modal) · ghost cascade 0 / 70+45i ms → fill · 40pt top fade · Deny slides left, list closes up. |
| H10 Approval sheet · amount first | ApprovalSheet · PillTimer | EnvoySheet, not gorhom. The pill outline starts and ends at top centre and shrinks anticlockwise. The agent line is plain text. |
| H11 Approving | DiamondLoader | Three diamonds replace the CTA label, shown for at least 900ms. |
| H12 Face ID | authenticate() | The OS prompt from the app's passkey module; the sheet stays behind it. |
| H13–H15 OTP | ApprovalSheet → OtpStep | One hidden TextInput (oneTimeCode / sms-otp). Six digits enable Approve, with no auto-submit. Error: red underlines only. |
| H16 Payment successful | LottieView | success-shatter.json plays once and holds the tick. |
| H17 Request closed | ApprovalSheet → ExpiredStep | Opening an expired or answered request lands here. |
| H19 Lock screen push | initialRequestId | Tapping the push opens the sheet directly, not Home (Figma A01 → A05). |
3 · Success animation
Shatter: the Envoy bird takes off, shatters, the pieces pool, a green lattice fills from the bottom, a ripple turns it solid, then the tick. Generated from the prototype's own engine, so the Lottie and the prototype match.
- success-shatter.json: 120×120, 60fps, 2.85s, 1.9 MB. Plays in
lottie-react-native, lottie-web and the LottieFiles player. - success-shatter.lottie: the same animation as dotLottie, 130 KB.
- No dynamic layers. Ink #0A0A0A, green #10A37F, white tick. Dark mode needs a regenerated file.
- Play once (
loop={false}) and hold the last frame. The MP4 / WebM are for Figma, decks and the web, not the app.
4 · Figma
File 1nu4wMdlWocsKiHPNz9KmJ, page Transaction Auth. The unique screens (H01–H19) are main components, and every flow screen is an instance of one, so design changes land everywhere at once. Callouts mark Lottie and Code animation.
5 · Things that are easy to get wrong
- The push opens the sheet, not Home. Pass
initialRequestId. - Approve on a card opens the sheet (
ACTION_TYPES.OPEN_AGENT_APPROVAL); only the sheet's Approve authorises. - The bank decides OTP, not the amount:
onApproveresolves"done" | "otp". - Every busy state is the three-diamond loader, never a spinner.
- No hex outside
palette.ts. Movetokens.proposed.tsinto the theme (light + dark) before merging. 10 new colours - The list is an overlay View, so the sheet Modal can present over it; don't make it a Modal.
- Stack swipe is off while the sheet is up or a request is mid-OTP.