Skip to main content

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 mounts FeedbackDialog and listens for open-feedback. Screenshot capture stays in FeedbackDialog.tsx (including iOS WebKit auto-capture — see iPhone/iPad section below).
  • Entry points: Sidebar footer, Ctrl+Shift+F / ⌘+Shift+F (see AppSidebar.tsx).

iPhone/iPad: automatic screenshot capture restored (Apr 6, 2026)

  • Goal: Match desktop behavior as closely as possible: automatic domToPng capture 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, base scale: 0.24, font: false, drawImageInterval: 250.
  • Canvas safety: clampFeedbackScreenshotScaleForIOS() uses exported limits from imageUtils.ts (SAFARI_WEBKIT_MAX_CANVAS_AREA, SAFARI_WEBKIT_MAX_CANVAS_DIM — ~16.7MP / 4096px) so output dimensions stay under WebKit canvas caps before domToPng runs.
  • Scoped capture: On iOS, auto-capture and Recapture use `<main>` only — if <main> is missing, capture fails gracefully (no document.body on iOS). After a failed <main> capture, there is no automatic document.body retry on iOS. Desktop/Android auto-capture uses <main> first, then may retry once with document.body; Recapture on desktop/Android uses document.body to 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; no body fallback 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 foreignObject serialization plus font embedding remained slow.
  • Fix (desktop Safari only, via `isDesktopSafari` — Safari UA and not iOS/iPadOS WebKit):
  • External Promise.race + domToPng timeout: 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 uses overflow-y: auto within a h-screen flex layout, so domToPng rendered the DOM clone at scroll position 0 — users never saw their actual viewport in the screenshot.
  • Fix: Added features: { restoreScrollPosition: true } to domToPng. The library's getBoundingClientRect() gives <main>'s clientHeight (viewport-sized canvas), and restoreScrollPosition translates cloned children by -scrollTop so 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-top to object-contain so the full captured area is visible in the thumbnail instead of cropping to the top.
  • Timeout: timeout on domToPng aligns with external Promise.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 the common namespace, with English as the fallback language for untranslated locales.
  • Accessibility: Added aria-describedby linking to the description paragraph.
  • Code quality: handleClose converted to useCallback and moved before the Escape key useEffect to 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 to document.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/pointerdown interceptors on both document and document.body. Calls stopImmediatePropagation() 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 processes focusin, 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-accounting page (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 to document.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 --background CSS 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 CameraOff icon when capture fails
  • Recapture button — desktop/Android: open a drawer, then "Recapture" targets document.body to include the drawer. iOS: Recapture stays on <main> only.

Single Dialog Instance (Feb 14, 2026)

  • Problem: Two independent FeedbackDialog instances existed (sidebar + FAB), causing potential conflicts.
  • Solution: the dialog host owns the single instance. Sidebar button and keyboard shortcut dispatch a custom open-feedback event. openFeedbackDialog() is exported for use anywhere.

Feedback Breadcrumbs (Feb 14, 2026)

  • Feature: feedbackBreadcrumb(action) utility in consoleCapture.ts records structured user actions (max 30) separate from console logs.
  • Included in: metadata.breadcrumbs field 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 via navigator.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 AbortController timeout on the submit-feedback fetch call.
  • Retry button: After 10s of sending state, 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 keydown listener in AppSidebar.tsx dispatching openFeedbackDialog()

Floating FAB removed (was Jan 12, 2026; retired Apr 6, 2026)

  • Previously a portaled fixed bottom-right button carried data-feedback-button for DOM screenshot filtering.
  • Retired to avoid duplicating the sidebar control; the filter still checks data-feedback-button if 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 use document.body for 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 identifier
  • user_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 submitted
  • current_page (string, nullable) - App page slug
  • selected_entity (string, nullable) - Current entity filter
  • console_logs (jsonb, nullable) - Recent console logs (last 30)
  • metadata (jsonb, nullable) - Browser/performance context, breadcrumbs, nav history
  • created_at (timestamp)

Architecture

Single Instance Pattern

  • FeedbackDialogHost owns the single FeedbackDialog instance (rendered in App.tsx)
  • openFeedbackDialog() exported from FeedbackDialogHost.tsx — dispatches custom event
  • Sidebar footer button and keyboard shortcut call openFeedbackDialog(); no separate FAB

Screenshot Capture

  • Library: modern-screenshot v4.6.7 (domToPng)
  • Viewport-only: restoreScrollPosition: true translates cloned children by -scrollTop, so the viewport-visible content renders at canvas Y=0. getBoundingClientRect() sizes the canvas to clientHeight (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 (no body substitute).
  • Recapture target: document.body on 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` vs SAFARI_WEBKIT_MAX_* in imageUtils.ts
  • Filter: excludes [data-feedback-panel], [data-feedback-button], [data-sonner-toaster], <iframe>
  • Background: reads --background CSS 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


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

Ready to get started?Start Plus Trial