From 779d2c3b90422db17ea3d5aa9ec0349817eacc2b Mon Sep 17 00:00:00 2001 From: Rafael Bittencourt Date: Mon, 16 Feb 2026 19:34:41 -0300 Subject: [PATCH 1/6] fix: resolve 10 UX/a11y/security issues from expert review - Observability dashboard: fix double URL prefix, add auth headers, add AbortController to prevent polling overlap on unmount - Landing cards: replace useInView+animate with whileInView for reliable scroll-triggered animations - Landing sections: reduce excessive vertical padding - Badge contrast: increase font size to 11px + add drop-shadow for WCAG compliance on gradient backgrounds - PWA install banner: reposition to bottom-left to avoid overlap - Manifest: set start_url and lang to English (default locale) - i18n: add nav.accessibility key, fix captioning_description - Dashboard page test: mock PageTransition to fix class assertions - Stats padding: responsive py-8/md:py-12 Reviewed by GPT-5.2 (backend/arch) and Gemini-3-Pro (frontend/UX). All 1115 frontend tests + 13 observability tests passing. Co-Authored-By: Claude Opus 4.6 --- frontend/public/manifest.json | 4 +-- .../[locale]/(app)/dashboard/page.test.tsx | 22 ++++++++----- .../landing/landing-demo-login.test.tsx | 5 ++- .../components/landing/landing-demo-login.tsx | 16 +++++----- .../landing/landing-features.test.tsx | 5 ++- .../components/landing/landing-features.tsx | 15 +++++---- .../landing/landing-how-it-works.test.tsx | 5 ++- .../landing/landing-how-it-works.tsx | 17 +++++----- .../src/components/landing/landing-stats.tsx | 2 +- .../observability/observability-dashboard.tsx | 22 ++++++++++--- .../src/components/pwa/install-prompt.tsx | 4 +-- frontend/src/messages/en.json | 3 +- frontend/src/messages/es.json | 3 +- frontend/src/messages/pt-BR.json | 1 + .../api/routers/observability.py | 31 ++++++++++++++++--- runtime/tests/test_observability_api.py | 10 ++++++ 16 files changed, 106 insertions(+), 59 deletions(-) diff --git a/frontend/public/manifest.json b/frontend/public/manifest.json index 53453c4..3a272ac 100644 --- a/frontend/public/manifest.json +++ b/frontend/public/manifest.json @@ -2,7 +2,7 @@ "name": "AiLine - Adaptive Inclusive Learning", "short_name": "AiLine", "description": "AI-powered inclusive education platform for adaptive learning", - "start_url": "/pt-BR", + "start_url": "/en", "display": "standalone", "background_color": "#FFFFFF", "theme_color": "#2563EB", @@ -22,6 +22,6 @@ } ], "categories": ["education", "productivity"], - "lang": "pt-BR", + "lang": "en", "dir": "ltr" } diff --git a/frontend/src/app/[locale]/(app)/dashboard/page.test.tsx b/frontend/src/app/[locale]/(app)/dashboard/page.test.tsx index 017d666..d811972 100644 --- a/frontend/src/app/[locale]/(app)/dashboard/page.test.tsx +++ b/frontend/src/app/[locale]/(app)/dashboard/page.test.tsx @@ -8,6 +8,12 @@ vi.mock('@/components/dashboard/dashboard-content', () => ({ ), })) +vi.mock('@/components/ui/page-transition', () => ({ + PageTransition: ({ children }: { children: React.ReactNode }) => ( +
{children}
+ ), +})) + describe('DashboardPage', () => { it('renders the DashboardContent component', () => { render() @@ -16,14 +22,14 @@ describe('DashboardPage', () => { it('wraps content in a max-width container', () => { const { container } = render() - const wrapper = container.firstChild as HTMLElement - expect(wrapper.className).toContain('max-w-5xl') + const inner = container.querySelector('.max-w-5xl') + expect(inner).toBeInTheDocument() }) it('applies mx-auto for centering', () => { const { container } = render() - const wrapper = container.firstChild as HTMLElement - expect(wrapper.className).toContain('mx-auto') + const inner = container.querySelector('.mx-auto') + expect(inner).toBeInTheDocument() }) it('renders a single root element', () => { @@ -31,10 +37,10 @@ describe('DashboardPage', () => { expect(container.children).toHaveLength(1) }) - it('renders DashboardContent as the only child', () => { + it('renders DashboardContent inside the container', () => { const { container } = render() - const wrapper = container.firstChild as HTMLElement - expect(wrapper.children).toHaveLength(1) - expect(wrapper.querySelector('[data-testid="dashboard-content"]')).toBeInTheDocument() + const inner = container.querySelector('.max-w-5xl') + expect(inner).toBeInTheDocument() + expect(inner?.querySelector('[data-testid="dashboard-content"]')).toBeInTheDocument() }) }) diff --git a/frontend/src/components/landing/landing-demo-login.test.tsx b/frontend/src/components/landing/landing-demo-login.test.tsx index 02e43f5..c0c043b 100644 --- a/frontend/src/components/landing/landing-demo-login.test.tsx +++ b/frontend/src/components/landing/landing-demo-login.test.tsx @@ -12,15 +12,14 @@ vi.mock('next/navigation', () => ({ vi.mock('motion/react', () => ({ motion: { article: ({ children, ...rest }: Record) => { - const { initial: _i, animate: _a, transition: _t, ...safe } = rest + const { initial: _i, animate: _a, transition: _t, whileInView: _w, viewport: _v, ...safe } = rest return
{children as React.ReactNode}
}, div: ({ children, ...rest }: Record) => { - const { initial: _i, animate: _a, transition: _t, ...safe } = rest + const { initial: _i, animate: _a, transition: _t, whileInView: _w, viewport: _v, ...safe } = rest return
{children as React.ReactNode}
}, }, - useInView: () => true, useReducedMotion: () => false, })) diff --git a/frontend/src/components/landing/landing-demo-login.tsx b/frontend/src/components/landing/landing-demo-login.tsx index 71dba01..29c2052 100644 --- a/frontend/src/components/landing/landing-demo-login.tsx +++ b/frontend/src/components/landing/landing-demo-login.tsx @@ -1,7 +1,7 @@ 'use client' -import { useRef, useCallback } from 'react' -import { motion, useInView, useReducedMotion } from 'motion/react' +import { useCallback } from 'react' +import { motion, useReducedMotion } from 'motion/react' import { useRouter } from 'next/navigation' import { cn } from '@/lib/cn' import { useAccessibilityStore } from '@/stores/accessibility-store' @@ -118,8 +118,6 @@ function ProfileCard({ enterAs: string roleLabel: string }) { - const ref = useRef(null) - const isInView = useInView(ref, { once: true, margin: '-30px' }) const prefersReducedMotion = useReducedMotion() const noMotion = prefersReducedMotion ?? false const router = useRouter() @@ -149,11 +147,11 @@ function ProfileCard({ return ( @@ -321,7 +319,7 @@ export function LandingDemoLogin(props: LandingDemoLoginProps) { return (
diff --git a/frontend/src/components/landing/landing-features.test.tsx b/frontend/src/components/landing/landing-features.test.tsx index 6187b7b..2f5e7a5 100644 --- a/frontend/src/components/landing/landing-features.test.tsx +++ b/frontend/src/components/landing/landing-features.test.tsx @@ -5,15 +5,14 @@ import { LandingFeatures } from './landing-features' vi.mock('motion/react', () => ({ motion: { article: ({ children, ...rest }: Record) => { - const { initial: _i, animate: _a, transition: _t, ...safe } = rest + const { initial: _i, animate: _a, transition: _t, whileInView: _w, viewport: _v, ...safe } = rest return
{children as React.ReactNode}
}, div: ({ children, ...rest }: Record) => { - const { initial: _i, animate: _a, transition: _t, ...safe } = rest + const { initial: _i, animate: _a, transition: _t, whileInView: _w, viewport: _v, ...safe } = rest return
{children as React.ReactNode}
}, }, - useInView: () => true, useReducedMotion: () => false, })) diff --git a/frontend/src/components/landing/landing-features.tsx b/frontend/src/components/landing/landing-features.tsx index 01e4baa..6c1bd61 100644 --- a/frontend/src/components/landing/landing-features.tsx +++ b/frontend/src/components/landing/landing-features.tsx @@ -1,7 +1,6 @@ 'use client' -import { motion, useInView, useReducedMotion } from 'motion/react' -import { useRef } from 'react' +import { motion, useReducedMotion } from 'motion/react' import { cn } from '@/lib/cn' interface Feature { @@ -64,16 +63,14 @@ function FeatureIcon({ icon }: { icon: Feature['icon'] }) { } function FeatureCard({ feature, index }: { feature: Feature; index: number }) { - const ref = useRef(null) - const isInView = useInView(ref, { once: true, margin: '-40px' }) const prefersReducedMotion = useReducedMotion() const noMotion = prefersReducedMotion ?? false return (
{/* Navigation items */} - + {/* Subtle divider */}