fix(feedback): stop the Sentry report button covering "Next" on mobile - #577
Merged
guillermoscript merged 2 commits intoJul 28, 2026
Merged
Conversation
Sentry's feedback integration ran with `autoInject: true`, which plants a fixed 50px actor button in the bottom-right corner. On a 390x844 viewport that landed on top of the lesson footer's primary action: the actor at (324,778,50x50) covered roughly 72% of the "Next" button at (338,796,40x40), so tapping to advance a lesson opened a bug report instead. Reporting a bug is a rare action, so it no longer holds prime thumb real estate. `autoInject` is off and the trigger is ours: - `components/shared/feedback-button.tsx` renders a 44px icon-only puck that rests dimmed (opacity .45) on the right edge at 62% height, clear of both the dashboard header and any bottom action bar. Measured clearance from "Next" is 259px. - It can be dragged anywhere, snaps to the nearest edge, clamps inside the viewport (respecting `env(safe-area-inset-*)` via a probe element), and remembers where it was parked in localStorage. - Opening lives on `click`, not `pointerup`, so Enter and Space work; a drag sets a flag that suppresses the trailing click. Arrow keys reposition it, since dragging is pointer-only. - Position moves via `transform` only, never a layout property, and the transition list drops `transform` mid-drag so the puck tracks the finger exactly and eases only when snapping home. Reduced motion is honoured. - `components/user-nav.tsx` gains a "Report a problem" item: the discoverable entry point, at zero screen cost. Availability and reduced-motion are read through `useSyncExternalStore` with a `false` server snapshot, so the puck can't appear in the first client render where the server rendered nothing. The dialog itself was English-only for Spanish tenants; it's built outside React so it can't reach next-intl, but `<html lang>` is server-rendered and readable at init, so the form strings are now localized too. The dialog also follows the app's resolved theme rather than the OS, which was wrong whenever a user had overridden the theme in-app. Verified in Chrome at 390x844 against the seeded student on a real lesson page: bug reproduced with `autoInject` flipped back on (overlap true), then gone after (overlap false); drag snaps and persists across navigation; keyboard nudges move it 62px per press; dialog opens and reads correctly in both en and es. Build, typecheck and lint clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FJ4dqwXuyKUPtg11ni5BSb
Captured at 390x844 on /dashboard/student/courses/1001/lessons/1001 as the seeded student. The before frame reproduces master exactly: `autoInject` flipped back on and our own puck hidden, so only Sentry's actor is present. Intersection math on that frame puts 72% of the "Next" button under it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FJ4dqwXuyKUPtg11ni5BSb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Sentry's feedback widget no longer plants a fixed button in the bottom-right corner, where it sat on top of the lesson footer's Next button on mobile. The trigger is ours now: a 44px puck that rests out of the way, can be dragged anywhere, and remembers where you put it. A "Report a problem" item was also added to the user menu.
Closes #
Why
Sentry.feedbackIntegration()defaults toautoInject: true, which injects a 50px actor button at bottom-right. At 390×844 that lands on the lesson footer's primary action:(324, 778, 50×50)(338, 796, 40×40)So tapping to advance a lesson opened a bug report instead. Reporting a bug is a rare action and shouldn't hold prime thumb real estate at all.
Making it draggable alone wouldn't have been enough — that just delegates the collision to the user, on every device, forever. So this changes the default and adds drag:
env(safe-area-inset-*)(read as pixels off a zero-size probe element, so notches and the iOS home indicator are handled without device guesses), and persists position inlocalStorageas a fraction of the range so rotation and resize keep the relative spot.click, notpointerup, so Enter and Space work; a drag sets a flag the trailingclickconsumes, and that flag is cleared on everypointerdownso a browser that skipsclickcan't swallow the next tap. Arrow keys reposition it, since dragging is pointer-only.components/user-nav.tsxis the discoverable path, at zero screen cost.transformonly, never a layout property. The transitioned property list dropstransformmid-drag so the puck tracks the finger exactly and eases (ease-out-quart, 200ms) only when snapping home.prefers-reduced-motionhonoured.Two adjacent bugs fixed in files already being touched:
<html lang>is server-rendered and readable at init.resolvedTheme.How to QA
Mobile viewport (390×844) is the point; the collision doesn't appear on desktop.
npm run db:reset && npm run dev, then opendefault.lvh.me:3000and log in asstudent@e2etest.com/password123./dashboard/student/courses/1001/lessons/1001) with the viewport at 390×844.Tabto the puck, pressEnter(form opens),Esc, then arrow keys to move it between edges and up/down./es/...and repeat step 5 — the form should read "Reportar un problema" / "Enviar reporte".Screenshots / GIF
390×844, lesson page. The before frame reproduces
masterexactly:autoInjectflipped back on and our own puck hidden, so only Sentry's actor is present.Checklist
npm run typecheckandnpm run test:unitpass (611 tests, 49 files)npm run buildpassestenant_id(or the table genuinely has no such column) — n/a, no queries added[locale]layout / user navcreateFormreturnsfalseinstead of appearing brokenmessages/en.jsonandmessages/es.jsonVerification notes
autoInjectflipped back on (overlaps: true, 72% by intersection math), gone after (overlaps: false, 259px clearance), Sentry's actor absent from the DOM.x=334(390−44−12),y=493(12 + 0.62×776), 44×44 touch target.x=12), persists{"side":"left","yFraction":0.723}, survives navigation.pointerdown → mousedown → pointerup → mouseup → click, dialog created and opened.ArrowRight/Leftswitch edges,ArrowUp/Downmove 62px per press, persisted.useSyncExternalStorewith afalseserver snapshot, so the puck can't appear in the first client render where the server rendered nothing.Not addressed here
The SDK also logs
ACTION REQUIRED: export an onRouterTransitionStart hook from instrumentation-client. Pre-existing and unrelated to this collision, so I left it rather than widen the diff. One line to add if you want it here.🤖 Generated with Claude Code
https://claude.ai/code/session_01FJ4dqwXuyKUPtg11ni5BSb