From 6f3c3695e6a7222755d1c8533eb321b449ad9a68 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sun, 1 Nov 2026 00:56:37 -0700 Subject: [PATCH 1/4] feat(v2): sun/moon theme switch on the rail, one theme setting (TASK-740) Co-Authored-By: Claude Sonnet 5.5 Co-authored-by: wisp Agent-Model: claude/claude-sonnet-5-5/default --- .../src/v2/__tests__/V2ThemeSwitch.test.tsx | 33 +++++++++++++++++++ frontend/src/v2/components/V2NavRail.tsx | 2 ++ frontend/src/v2/components/V2SettingsPage.tsx | 10 +++--- frontend/src/v2/components/V2ThemeSwitch.tsx | 33 +++++++++++++++++++ .../src/v2/components/v2-theme-switch.css | 12 +++++++ frontend/src/v2/hooks/useV2ThemePref.ts | 7 ++++ frontend/src/v2/utils/theme.ts | 17 ++++++++++ 7 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 frontend/src/v2/__tests__/V2ThemeSwitch.test.tsx create mode 100644 frontend/src/v2/components/V2ThemeSwitch.tsx create mode 100644 frontend/src/v2/components/v2-theme-switch.css create mode 100644 frontend/src/v2/hooks/useV2ThemePref.ts diff --git a/frontend/src/v2/__tests__/V2ThemeSwitch.test.tsx b/frontend/src/v2/__tests__/V2ThemeSwitch.test.tsx new file mode 100644 index 0000000000..edb7b6e360 --- /dev/null +++ b/frontend/src/v2/__tests__/V2ThemeSwitch.test.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { render, screen, fireEvent, act } from '@testing-library/react'; +import V2ThemeSwitch from '../components/V2ThemeSwitch'; +import useV2ThemePref from '../hooks/useV2ThemePref'; +import { setV2Theme, V2_THEME_KEY } from '../utils/theme'; + +// TASK-740 (D224): the rail switch writes the one stored theme preference that +// Settings > Theme reads, and a change made elsewhere moves the switch. + +const Probe: React.FC = () => {useV2ThemePref()}; + +beforeEach(() => { + window.localStorage.clear(); + document.documentElement.removeAttribute('data-v2-theme'); +}); + +describe('V2ThemeSwitch', () => { + test('a click stores the same key and html attribute as Settings > Theme', () => { + render(<>); + fireEvent.click(screen.getByRole('button', { name: /dark/i })); + expect(window.localStorage.getItem(V2_THEME_KEY)).toBe('dark'); + expect(document.documentElement.getAttribute('data-v2-theme')).toBe('dark'); + expect(screen.getByRole('button', { name: /dark/i })).toHaveAttribute('aria-pressed', 'true'); + expect(screen.getByRole('button', { name: /light/i })).toHaveAttribute('aria-pressed', 'false'); + expect(screen.getByTestId('pref')).toHaveTextContent('dark'); + }); + + test('a preference set elsewhere moves the switch', () => { + render(); + act(() => setV2Theme('light')); + expect(screen.getByRole('button', { name: /light/i })).toHaveAttribute('aria-pressed', 'true'); + }); +}); diff --git a/frontend/src/v2/components/V2NavRail.tsx b/frontend/src/v2/components/V2NavRail.tsx index 62ff70649f..c5f2988394 100644 --- a/frontend/src/v2/components/V2NavRail.tsx +++ b/frontend/src/v2/components/V2NavRail.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { useNavigate, useLocation } from 'react-router-dom'; import V2FeedbackMenu from './V2FeedbackMenu'; import V2AccountMenu from './V2AccountMenu'; +import V2ThemeSwitch from './V2ThemeSwitch'; import { useAuth } from '../../context/AuthContext'; import { useTranslation } from 'react-i18next'; import Badge from '@mui/material/Badge'; @@ -127,6 +128,7 @@ const V2NavRail: React.FC = ({ onPodsMobileNav, needsYouCount })
+