diff --git a/src/components/Componentes/dev-tap-instrumentation.tsx b/src/components/Componentes/dev-tap-instrumentation.tsx index 9423a5a..6bcbca2 100644 --- a/src/components/Componentes/dev-tap-instrumentation.tsx +++ b/src/components/Componentes/dev-tap-instrumentation.tsx @@ -4,38 +4,79 @@ import { useEffect } from "react"; /** * Development-only capture-phase instrumentation for debugging section-card - * tap responsiveness. Records pointerdown → pointerup → click timing and - * whether the event reaches the DOM at all (vs being swallowed by a native - * overlay such as Flutter's loading cover). + * and question flow tap/focus responsiveness. Records pointerdown → pointerup → click, + * touchstart → touchmove → touchend, and focusin/focusout lifecycle events. * - * Mount this inside the questions-list page during development. Remove or - * gate behind process.env.NODE_ENV check for production. + * Mount this inside the questions-list and question detail flows during development. + * Automatically inactive outside of development. */ export default function DevTapInstrumentation() { useEffect(() => { if (process.env.NODE_ENV !== "development") return; - const events = ["pointerdown", "pointerup", "pointercancel", "click"] as const; + const events = [ + "pointerdown", + "pointerup", + "pointercancel", + "click", + "touchstart", + "touchmove", + "touchend", + "touchcancel", + "focusin", + "focusout", + ] as const; + const startTime = performance.now(); let sequenceId = 0; + let touchStartY: number | null = null; const handler = (event: Event) => { - const e = event as PointerEvent; - const target = e.target as HTMLElement | null; + const target = event.target as HTMLElement | null; const anchor = target?.closest?.("a"); const elapsed = (performance.now() - startTime).toFixed(1); sequenceId += 1; + let deltaY: number | null = null; + if (event.type === "touchstart") { + touchStartY = (event as TouchEvent).touches?.[0]?.clientY ?? null; + } else if (event.type === "touchmove" || event.type === "touchend") { + const currentY = + (event as TouchEvent).touches?.[0]?.clientY ?? + (event as TouchEvent).changedTouches?.[0]?.clientY ?? + null; + if (touchStartY !== null && currentY !== null) { + deltaY = Math.round(touchStartY - currentY); + } + } + + const isInteractive = Boolean( + target?.closest?.( + 'input, textarea, select, button, label, a, [role="button"], [role="option"], [role="checkbox"], [role="radio"], [role="switch"], [role="combobox"], [role="listbox"], [contenteditable="true"]', + ), + ); + + const color = event.type.startsWith("focus") + ? "#0EB13C" + : event.type.startsWith("touch") + ? "#3B82F6" + : "#E03950"; + console.debug( - `[tap-debug #${sequenceId}] %c${e.type}%c @ ${elapsed}ms`, - "color: #E03950; font-weight: bold", + `[tap-debug #${sequenceId}] %c${event.type}%c @ ${elapsed}ms`, + `color: ${color}; font-weight: bold`, "color: inherit", { - pointerType: (e as PointerEvent).pointerType || "n/a", + type: event.type, target: target?.tagName, targetId: target?.id, + targetClass: target?.className, + activeElement: document.activeElement?.tagName, + activeElementId: document.activeElement?.id, + isInteractive, + deltaY, closestAnchorHref: anchor?.getAttribute("href") || null, - defaultPrevented: e.defaultPrevented, + defaultPrevented: event.defaultPrevented, pathname: window.location.pathname, timestamp: performance.now(), }, diff --git a/src/components/Componentes/question-section-flow.tsx b/src/components/Componentes/question-section-flow.tsx index 9570803..054814a 100644 --- a/src/components/Componentes/question-section-flow.tsx +++ b/src/components/Componentes/question-section-flow.tsx @@ -17,6 +17,7 @@ import AnswerPaceSheet from "@/app/questions-list/[slug]/answer-pace-sheet"; import { FixToTheEnd } from "./fix-to-the-end"; import Button from "./button"; import { markFirstEntryCompleted } from "@/lib/first-entry-helper"; +import DevTapInstrumentation from "./dev-tap-instrumentation"; type QuestionSectionFlowProps = { children: ReactNode; @@ -133,6 +134,7 @@ function SectionFlowContent({ {children} + {process.env.NODE_ENV === "development" ? : null}