Feedback Dialog
Feedback Dialog
Component File: src/components/shared/FeedbackDialog.tsx Dialog host: src/components/shared/FeedbackDialogHost.tsx (mounts FeedbackDialog + open-feedback listener) Console Capture: src/lib/consoleCapture.ts Edge Function: supabase/functions/submit-feedback/index.ts Route: Global (available via sidebar footer and keyboard shortcut) Access Level: All authenticated users Last Updated: April 6, 2026 (iOS WebKit automatic screenshot restore; dedupe Send Feedback UI) Status: ✅ Implemented
Recent Fixes
Duplicate Send Feedback control removed (Apr 6, 2026)
- Problem: Users saw two "Send Feedback" actions — the sidebar footer button plus a fixed bottom-right pill (previously portaled by
FloatingFeedbackButton.tsx). The pill’s shadow also read as a blue glow overlapping the nav on dark themes. - Cause: Intentional overlap from an older pattern (FAB for modal stacking). The sidebar button already calls
openFeedbackDialog(), so the second button was redundant. - Fix: the dialog host now lives in
FeedbackDialogHost.tsx, which only mountsFeedbackDialogand listens foropen-feedback. Screenshot capture stays inFeedbackDialog.tsx(including iOS WebKit auto-capture — see iPhone/iPad section below). - Entry points: Sidebar footer,
Ctrl+Shift+F/⌘+Shift+F(seeAppSidebar.tsx).
iPhone/iPad: automatic screenshot capture restored (Apr 6, 2026)
- Goal: Match desktop behavior as closely as possible: automatic
domToPngcapture when the feedback panel opens, with non-blocking submit if capture fails (text-only feedback is still accepted). - Previous interim (Apr 5): DOM capture was skipped on iOS; users attached from Photos. That path is removed as the primary product behavior.
- Implementation (`FeedbackDialog.tsx`):
- iOS WebKit profile (separate from desktop Safari):
timeoutMs: 50000, basescale: 0.24,font: false,drawImageInterval: 250. - Canvas safety:
clampFeedbackScreenshotScaleForIOS()uses exported limits fromimageUtils.ts(SAFARI_WEBKIT_MAX_CANVAS_AREA,SAFARI_WEBKIT_MAX_CANVAS_DIM— ~16.7MP / 4096px) so output dimensions stay under WebKit canvas caps beforedomToPngruns. - Scoped capture: On iOS, auto-capture and Recapture use `<main>` only — if
<main>is missing, capture fails gracefully (nodocument.bodyon iOS). After a failed<main>capture, there is no automaticdocument.bodyretry on iOS. Desktop/Android auto-capture uses<main>first, then may retry once withdocument.body; Recapture on desktop/Android usesdocument.bodyto include portaled drawers. - UI: Same states as desktop — capturing, preview + Recapture, or failure with retry. Copy includes
feedbackScreenshotOptionalNote(submit without screenshot remains enabled). - Desktop Safari remains on the existing profile (
60s,scale: 0.32, etc.) via `isDesktopSafari` (Safari UA and not iOS/iPadOS WebKit). - Edge function (`submit-feedback`): Screenshot remains optional; MIME inference from data URLs unchanged.
Chrome / Chromium: Fund Accounting + default capture budgets (Mar 23, 2026)
- Report: Christy Nelson (InFocus Ministries),
/fund-accounting, Chrome 145 on macOS. Console:Screenshot capture timeout— prior code used 8s for all non-Safari browsers; heavy Fund Accounting DOMs often exceeded that. - Fix (`getFeedbackCaptureProfile()` in `FeedbackDialog.tsx`):
- `/fund-accounting`: timeout 35s, scale 0.58, `font: false` (skip webfont embedding for speed).
- Other routes (Chromium/Firefox): timeout 20s, scale 0.75, default font embedding.
- Desktop macOS Safari (`isDesktopSafari`): 60s, scale 0.32,
font: false,drawImageInterval: 200(iPhone/iPad use the separate iOS profile — see iPhone/iPad section above). - Fallback: Recapture on desktop/Android still uses
document.body; auto-capture prefers<main>. On iOS WebKit, Recapture stays on<main>only; nobodyfallback after failed auto-capture.
macOS Safari screenshot reliability (Mar 21, 2026)
- Problem: Feedback screenshots still showed "Screenshot unavailable" for some macOS Safari users on heavy pages (e.g. DonorsCRM, ContactsCRM, large viewports). Prior mitigations (iOS false-positive fix, longer Safari timeout, lower scale) were not always enough — WebKit
foreignObjectserialization plus font embedding remained slow. - Fix (desktop Safari only, via `isDesktopSafari` — Safari UA and not iOS/iPadOS WebKit):
- External
Promise.race+domToPngtimeout: 60s (non-Safari profiles — see Chrome / Chromium Mar 23, 2026 above). scale: 0.32 (non-Safari uses route-based scale — see same section).font: false— skip webfont embedding; feedback shots only need layout/context.drawImageInterval: 200— wider interval than the library default (100ms) for WebKit image decode stability.- iPhone/iPad: Use the iOS WebKit profile above (not this desktop Safari block). Recapture on iOS stays on
<main>only.
Viewport-Only Screenshot Capture & i18n (Feb 28, 2026)
- Problem: Screenshot always captured the top of the page content, not what the user was currently viewing. The
<main>element usesoverflow-y: autowithin ah-screenflex layout, sodomToPngrendered the DOM clone at scroll position 0 — users never saw their actual viewport in the screenshot. - Fix: Added
features: { restoreScrollPosition: true }todomToPng. The library'sgetBoundingClientRect()gives<main>'sclientHeight(viewport-sized canvas), andrestoreScrollPositiontranslates cloned children by-scrollTopso the visible portion renders at canvas Y=0. No manual cropping needed — the resulting PNG is exactly what the user sees. - Preview fix: Changed screenshot preview from
object-cover object-toptoobject-containso the full captured area is visible in the thumbnail instead of cropping to the top. - Timeout:
timeoutondomToPngaligns with externalPromise.race— see Chromium/Firefox and `/fund-accounting` profiles in Chrome / Chromium (Mar 23, 2026); Safari uses 60s and Safari-only options (see Mar 21, 2026 above). - i18n: All ~20 hardcoded English strings wrapped in
t()calls. 21 new keys added to thecommonnamespace, with English as the fallback language for untranslated locales. - Accessibility: Added
aria-describedbylinking to the description paragraph. - Code quality:
handleCloseconverted touseCallbackand moved before the Escape keyuseEffectto fix used-before-declaration lint error.
Non-Modal Panel (Feb 14, 2026)
- Problem: Modal dialog with overlay blocked users from seeing page content while writing feedback.
- Solution: Converted from Radix
<Dialog>modal to a non-modal panel portaled todocument.body. No overlay — page remains fully visible and interactive. - Position: Fixed and viewport-centered (
left: 50%,top: 50%,transform: translate(-50%, -50%)) so the panel sits over the layout without a dedicated “sidebar offset” rule. - Panel specs:
w-[420px]desktop (sm:),w-[calc(100vw-2rem)]mobile,z-[250000](above drawer content/popover layers so an open popup can be recaptured without blocking the textarea, but still below alert dialogs). - Close: X button, Escape key. Clicking outside does NOT close (intentional — non-modal).
- Focus & pointer isolation: Two-layer defense against Radix/Vaul focus stealing:
- Layer 1: Capture-phase
focusin/pointerdowninterceptors on bothdocumentanddocument.body. CallsstopImmediatePropagation()for events inside[data-feedback-panel], blocking Radix DismissableLayer's native listeners. - Layer 2: Native
<textarea>with direct DOM event listeners via ref callback. Bypasses React's synthetic event system entirely — even if Radix processesfocusin, the textarea's input/keydown/focus handling is attached directly to the DOM element.
Screenshot Capture Reliability (Feb 14, 2026)
- Problem: 71% screenshot failure rate on
fund-accountingpage (ReconciliationManager). 3-second timeout too short for complex DOMs. - Fixes:
- Non-Safari timeouts/scales evolved — see Chrome / Chromium (Mar 23, 2026); Safari — see macOS Safari screenshot reliability above.
- Capture scoped to
<main>element (falls back todocument.body). Portaled drawers at z-index 100000 cause timeouts when included. - Route-based scale for Chromium (e.g. lower on
/fund-accounting) — see architecture Screenshot Capture - Pre-capture delay reduced to single
requestAnimationFrame(was 100ms + rAF) - Background color reads
--backgroundCSS variable instead of hardcoded#0a0a0a(theme-aware) - Filter only excludes
[data-feedback-panel],[data-feedback-button],[data-sonner-toaster], and<iframe>— drawers/sheets ARE captured (they're what the user sees) - Visible "Screenshot unavailable" indicator with
CameraOfficon when capture fails - Recapture button — desktop/Android: open a drawer, then "Recapture" targets
document.bodyto include the drawer. iOS: Recapture stays on<main>only.
Single Dialog Instance (Feb 14, 2026)
- Problem: Two independent
FeedbackDialoginstances existed (sidebar + FAB), causing potential conflicts. - Solution: the dialog host owns the single instance. Sidebar button and keyboard shortcut dispatch a custom
open-feedbackevent.openFeedbackDialog()is exported for use anywhere.
Feedback Breadcrumbs (Feb 14, 2026)
- Feature:
feedbackBreadcrumb(action)utility inconsoleCapture.tsrecords structured user actions (max 30) separate from console logs. - Included in:
metadata.breadcrumbsfield of feedback submissions. - Purpose: Gives richer debugging context than console errors alone during beta.
Edge Function Hardening (Feb 14, 2026)
- Removed
@ts-nocheck - Added per-user rate limiting: 1 feedback per 15 seconds (HTTP 429)
iOS Safari Hardening (Feb 10, 2026; capture policy updated Apr 6, 2026)
- Screenshot capture: iPhone/iPad browsers again attempt automatic DOM capture with a dedicated iOS profile, scale clamping against WebKit canvas limits, and
<main>-only capture (no body fallback). Detection: iPhone/iPod/iPad UA, or iPadOS-on-Macintosh vianavigator.maxTouchPoints > 1. Desktop macOS Safari uses the desktop Safari profile (Mar 21, 2026). If iOS capture fails, users see the failure state and can retry or submit without a screenshot. - Fetch timeout: 15s
AbortControllertimeout on thesubmit-feedbackfetch call. - Retry button: After 10s of
sendingstate, a "Taking too long? Retry" button appears.
Global Keyboard Shortcut (Jan 19, 2026)
Ctrl+Shift+F(Windows) /⌘+Shift+F(Mac) opens feedback panel- Implemented via global
keydownlistener inAppSidebar.tsxdispatchingopenFeedbackDialog()
Floating FAB removed (was Jan 12, 2026; retired Apr 6, 2026)
- Previously a portaled fixed bottom-right button carried
data-feedback-buttonfor DOM screenshot filtering. - Retired to avoid duplicating the sidebar control; the filter still checks
data-feedback-buttonif reintroduced later.
Overview
The Feedback Panel allows authenticated users to submit feedback, bug reports, and feature requests directly from within the application. It renders as a non-modal panel (viewport-centered, no blocking overlay) so users can reference page content and drawers while writing. Submissions are stored in the database and emailed to administrators.
UI Features
Panel Interface
- Non-modal panel, viewport-centered (no overlay, page stays interactive)
- Category selection pills (Bug, Feature Request, General Feedback)
- Text area for detailed description
- Auto-captured screenshot on open (scoped to
<main>first; desktop Recapture can usedocument.bodyfor drawers) - Recapture on iPhone/iPad re-captures `<main>` only (avoids large body canvases on WebKit)
- "Screenshot unavailable" with retry when capture fails; submit stays enabled (optional screenshot)
- Submit button with loading state and retry option
Feedback Categories
- Bug Report - Issues and errors
- Feature Request - New functionality suggestions
- General Feedback - Comments and suggestions
Data Requirements
Feedback is stored in the feedback table and submitted via the authenticated submit-feedback edge function:
id(uuid) - Unique identifieruser_id(uuid) - Submitting user (from JWT)category(string) - 'bug', 'feature', 'general'message(text) - Feedback content (max 5000 chars)screenshot_url(string, nullable) - Attached screenshot (max 3MB)page_url(string) - Page where feedback was submittedcurrent_page(string, nullable) - App page slugselected_entity(string, nullable) - Current entity filterconsole_logs(jsonb, nullable) - Recent console logs (last 30)metadata(jsonb, nullable) - Browser/performance context, breadcrumbs, nav historycreated_at(timestamp)
Architecture
Single Instance Pattern
FeedbackDialogHostowns the singleFeedbackDialoginstance (rendered inApp.tsx)openFeedbackDialog()exported fromFeedbackDialogHost.tsx— dispatches custom event- Sidebar footer button and keyboard shortcut call
openFeedbackDialog(); no separate FAB
Screenshot Capture
- Library:
modern-screenshotv4.6.7 (domToPng) - Viewport-only:
restoreScrollPosition: truetranslates cloned children by-scrollTop, so the viewport-visible content renders at canvas Y=0.getBoundingClientRect()sizes the canvas toclientHeight(viewport). No manual cropping needed. - Auto-capture target: `document.querySelector('main') || document.body` on desktop/Android. On iOS WebKit, `<main>` only — missing
<main>fails the capture (nobodysubstitute). - Recapture target:
document.bodyon desktop/Android (portaled drawers). On iPhone/iPad WebKit, `<main>` only. - Chromium / Firefox (not Safari):
getFeedbackCaptureProfile()— default timeout 20s, scale 0.75, fonts embedded; path `/fund-accounting`: timeout 35s, scale 0.58,font: false. - Desktop macOS Safari (`isDesktopSafari`): timeout 60s, scale 0.32,
font: false,drawImageInterval: 200 - iPhone/iPad WebKit (`isIOSWebKit`): timeout 50s, base scale 0.24,
font: false,drawImageInterval: 250, plus `clampFeedbackScreenshotScaleForIOS` vsSAFARI_WEBKIT_MAX_*inimageUtils.ts - Filter: excludes
[data-feedback-panel],[data-feedback-button],[data-sonner-toaster],<iframe> - Background: reads
--backgroundCSS variable (theme-aware) - Upload format: edge function infers the screenshot content type from the data URL instead of forcing PNG metadata
Implementation Status
| Feature | Status |
|---------|--------|
| Non-modal panel UI | ✅ Complete |
| Category selection | ✅ Complete |
| Message submission | ✅ Complete |
| Automatic DOM screenshot (optional) | ✅ Complete |
| Screenshot failure indicator | ✅ Complete |
| Feedback breadcrumbs | ✅ Complete |
| Rate limiting (15s) | ✅ Complete |
| Single instance consolidation | ✅ Complete |
Related Documentation
- 02-APP-SIDEBAR.md - Sidebar trigger location
Synced from IFMmvp-Frontend documentation: pages/components/12-FEEDBACK-DIALOG.md
Ready to Get Started?
See how Alignmint can simplify your nonprofit's operations. Schedule a free demo with our team and we'll walk you through everything.
Questions? Email us at steven@getalignmint.org