From aec524da464d1c041cba87c2fd3ccc14142626c5 Mon Sep 17 00:00:00 2001 From: aisshwaryaa8-collab Date: Thu, 11 Jun 2026 17:41:42 +0530 Subject: [PATCH 1/2] fix(api): clamp and sanitize grace param instead of rejecting invalid values --- lib/validations.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/validations.ts b/lib/validations.ts index b1f6205ff..9e2b3abaa 100644 --- a/lib/validations.ts +++ b/lib/validations.ts @@ -299,14 +299,12 @@ const baseStreakParamsSchema = z.object({ grace: z .string() .optional() - .refine( - (val) => { - if (val === undefined || val === '') return true; - return /^\d+$/.test(val) && Number(val) >= 0 && Number(val) <= 7; - }, - { message: 'grace must be an integer between 0 and 7' } - ) - .transform((val) => (val === undefined || val === '' ? 1 : Number(val))) + .transform((val) => { + if (val === undefined || val === '') return 1; + const n = Number(val); + if (isNaN(n) || !Number.isInteger(n)) return 1; + return Math.min(7, Math.max(0, n)); + }) .default(1), mode: z.enum(['commits', 'loc']).catch('commits').default('commits'), From bfa7f55aa00d98c1845adb81265c62a10bbe9789 Mon Sep 17 00:00:00 2001 From: aisshwaryaa8-collab Date: Sun, 21 Jun 2026 11:58:28 +0530 Subject: [PATCH 2/2] feat(CherryBlossom): respect prefers-reduced-motion accessibility setting --- .../CherryBlossom.accessibility.test.tsx | 1 + .../CherryBlossom.empty-fallback.test.tsx | 1 + .../CherryBlossom.error-resilience.test.tsx | 1 + .../CherryBlossom.mock-integrations.test.tsx | 1 + ...CherryBlossom.mouse-interactivity.test.tsx | 1 + ...rryBlossom.responsive-breakpoints.test.tsx | 1 + components/CherryBlossom.test.tsx | 31 +++++++++++++++++++ .../CherryBlossom.theme-contrast.test.tsx | 1 + components/CherryBlossom.tsx | 6 +++- 9 files changed, 43 insertions(+), 1 deletion(-) diff --git a/components/CherryBlossom.accessibility.test.tsx b/components/CherryBlossom.accessibility.test.tsx index 79164cb71..79247521e 100644 --- a/components/CherryBlossom.accessibility.test.tsx +++ b/components/CherryBlossom.accessibility.test.tsx @@ -5,6 +5,7 @@ import CherryBlossom from './CherryBlossom'; import type React from 'react'; vi.mock('framer-motion', () => ({ + useReducedMotion: vi.fn(() => false), motion: { div: ({ children, diff --git a/components/CherryBlossom.empty-fallback.test.tsx b/components/CherryBlossom.empty-fallback.test.tsx index 964ba0647..a96e7eb1e 100644 --- a/components/CherryBlossom.empty-fallback.test.tsx +++ b/components/CherryBlossom.empty-fallback.test.tsx @@ -5,6 +5,7 @@ import CherryBlossom from './CherryBlossom'; // Mock framer-motion to avoid animation DOM complexity vi.mock('framer-motion', () => { return { + useReducedMotion: vi.fn(() => false), motion: { div: ({ children }: { children?: React.ReactNode }) =>
{children}
, }, diff --git a/components/CherryBlossom.error-resilience.test.tsx b/components/CherryBlossom.error-resilience.test.tsx index f9978be98..029d4663e 100644 --- a/components/CherryBlossom.error-resilience.test.tsx +++ b/components/CherryBlossom.error-resilience.test.tsx @@ -11,6 +11,7 @@ const motionRuntime = vi.hoisted(() => ({ })); vi.mock('framer-motion', () => ({ + useReducedMotion: vi.fn(() => false), motion: { div: ({ children, ...props }: React.HTMLAttributes) => { if (motionRuntime.shouldThrow) { diff --git a/components/CherryBlossom.mock-integrations.test.tsx b/components/CherryBlossom.mock-integrations.test.tsx index 691f53d85..a3b19eb80 100644 --- a/components/CherryBlossom.mock-integrations.test.tsx +++ b/components/CherryBlossom.mock-integrations.test.tsx @@ -6,6 +6,7 @@ import CherryBlossom from './CherryBlossom'; // 1. Mock standard asynchronous imports and databases using stubs // We mock framer-motion to execute synchronously to avoid async animation timeouts vi.mock('framer-motion', () => ({ + useReducedMotion: vi.fn(() => false), motion: { // eslint-disable-next-line @typescript-eslint/no-explicit-any div: ({ children, className, ...props }: any) => ( diff --git a/components/CherryBlossom.mouse-interactivity.test.tsx b/components/CherryBlossom.mouse-interactivity.test.tsx index 1e76cfad5..27d03d088 100644 --- a/components/CherryBlossom.mouse-interactivity.test.tsx +++ b/components/CherryBlossom.mouse-interactivity.test.tsx @@ -6,6 +6,7 @@ import '@testing-library/jest-dom'; // Mock framer-motion to keep the tests simple and stable vi.mock('framer-motion', () => ({ + useReducedMotion: vi.fn(() => false), motion: { div: ({ children, diff --git a/components/CherryBlossom.responsive-breakpoints.test.tsx b/components/CherryBlossom.responsive-breakpoints.test.tsx index dd3bbb455..ce858e4a8 100644 --- a/components/CherryBlossom.responsive-breakpoints.test.tsx +++ b/components/CherryBlossom.responsive-breakpoints.test.tsx @@ -6,6 +6,7 @@ import '@testing-library/jest-dom'; // Mock framer-motion to inspect the props passed to the animated elements vi.mock('framer-motion', () => ({ + useReducedMotion: vi.fn(() => false), motion: { div: ({ children, diff --git a/components/CherryBlossom.test.tsx b/components/CherryBlossom.test.tsx index 320bdf387..67c1b9354 100644 --- a/components/CherryBlossom.test.tsx +++ b/components/CherryBlossom.test.tsx @@ -1,8 +1,10 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, waitFor } from '@testing-library/react'; import CherryBlossom from './CherryBlossom'; +import { useReducedMotion } from 'framer-motion'; import type React from 'react'; import '@testing-library/jest-dom'; + // Mock framer-motion vi.mock('framer-motion', () => ({ motion: { @@ -15,11 +17,13 @@ vi.mock('framer-motion', () => ({ ), }, + useReducedMotion: vi.fn(() => false), // default: motion is allowed })); describe('CherryBlossom', () => { beforeEach(() => { vi.restoreAllMocks(); + vi.mocked(useReducedMotion).mockReturnValue(false); }); it('renders without crashing after mount', async () => { @@ -68,3 +72,30 @@ describe('CherryBlossom', () => { expect(() => unmount()).not.toThrow(); }); }); + +describe('CherryBlossom - Reduced Motion Accessibility', () => { + beforeEach(() => { + vi.restoreAllMocks(); + vi.mocked(useReducedMotion).mockReturnValue(false); + }); + + it('renders nothing when the user prefers reduced motion', async () => { + vi.mocked(useReducedMotion).mockReturnValue(true); + + const { container } = render(); + + await waitFor(() => { + expect(container.firstChild).toBeNull(); + }); + }); + + it('still renders the falling petals when reduced motion is not requested', async () => { + vi.mocked(useReducedMotion).mockReturnValue(false); + + const { container } = render(); + + await waitFor(() => { + expect(container.querySelector('.fixed.inset-0')).toBeInTheDocument(); + }); + }); +}); diff --git a/components/CherryBlossom.theme-contrast.test.tsx b/components/CherryBlossom.theme-contrast.test.tsx index 861261618..492a5281f 100644 --- a/components/CherryBlossom.theme-contrast.test.tsx +++ b/components/CherryBlossom.theme-contrast.test.tsx @@ -6,6 +6,7 @@ import '@testing-library/jest-dom'; // Mock framer-motion to inspect the props passed to the animated elements vi.mock('framer-motion', () => ({ + useReducedMotion: vi.fn(() => false), motion: { div: ({ children, diff --git a/components/CherryBlossom.tsx b/components/CherryBlossom.tsx index 2626b67f9..ac17f5b19 100644 --- a/components/CherryBlossom.tsx +++ b/components/CherryBlossom.tsx @@ -1,6 +1,6 @@ 'use client'; -import { motion } from 'framer-motion'; +import { motion, useReducedMotion } from 'framer-motion'; import { useEffect, useState } from 'react'; const generatePetals = (count: number) => { @@ -32,6 +32,7 @@ export interface Petal { export default function CherryBlossom() { const [petals] = useState(() => generatePetals(25)); const [mounted, setMounted] = useState(false); + const prefersReducedMotion = useReducedMotion(); // SSR hydration guard: the petal animations use framer-motion values derived // from Math.random() at component initialisation time (via useState initialiser). @@ -43,6 +44,9 @@ export default function CherryBlossom() { }, []); if (!mounted) return null; + if (prefersReducedMotion) { + return null; + } return (