docs: Add comprehensive implementation plans for all todo items
Created detailed markdown plans for all items in todo.md: 1. 01-playwright-scaffolding.md - Base Playwright infrastructure 2. 02-magnitude-tests-comprehensive.md - Complete test coverage 3. 03-stream-ai-to-deepgram-tts.md - TTS latency optimization 4. 04-fix-galaxy-node-clicking.md - Galaxy navigation bugs 5. 05-dark-light-mode-theme.md - Dark/light mode with dynamic favicons 6. 06-fix-double-border-desktop.md - UI polish 7. 07-delete-backup-files.md - Code cleanup 8. 08-ai-transition-to-edit.md - Intelligent node creation flow 9. 09-umap-minimum-nodes-analysis.md - Technical analysis Each plan includes: - Detailed problem analysis - Proposed solutions with code examples - Manual Playwright MCP testing strategy - Magnitude test specifications - Implementation steps - Success criteria Ready to implement in sequence. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
463
plans/05-dark-light-mode-theme.md
Normal file
463
plans/05-dark-light-mode-theme.md
Normal file
@@ -0,0 +1,463 @@
|
||||
# Plan: Add Dark/Light Mode with Dynamic Favicons
|
||||
|
||||
**Priority:** MEDIUM
|
||||
**Dependencies:** None
|
||||
**Affects:** User experience, accessibility, branding
|
||||
|
||||
## Overview
|
||||
|
||||
Add full dark mode / light mode support throughout the app, including:
|
||||
- Dynamic theme switching
|
||||
- Persistent user preference
|
||||
- System preference detection
|
||||
- Mode-specific favicons
|
||||
- Smooth transitions
|
||||
|
||||
## Current State
|
||||
|
||||
- App uses a minimal grayscale theme
|
||||
- No dark mode support
|
||||
- Single favicon for all modes
|
||||
- No theme toggle UI
|
||||
|
||||
## Proposed Implementation
|
||||
|
||||
### 1. Mantine Color Scheme Support
|
||||
|
||||
Mantine has built-in dark mode support. We'll leverage `@mantine/core` ColorSchemeScript and hooks.
|
||||
|
||||
#### Update `app/layout.tsx`
|
||||
```typescript
|
||||
import {
|
||||
ColorSchemeScript,
|
||||
MantineProvider,
|
||||
} from '@mantine/core';
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<ColorSchemeScript defaultColorScheme="auto" />
|
||||
{/* Dynamic favicon based on theme */}
|
||||
<link
|
||||
rel="icon"
|
||||
href="/favicon-light.svg"
|
||||
media="(prefers-color-scheme: light)"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
href="/favicon-dark.svg"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<MantineProvider defaultColorScheme="auto">
|
||||
{children}
|
||||
</MantineProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Update Theme Configuration
|
||||
|
||||
#### `app/theme.ts`
|
||||
```typescript
|
||||
import { createTheme, MantineColorsTuple } from '@mantine/core';
|
||||
|
||||
const gray: MantineColorsTuple = [
|
||||
'#f8f9fa',
|
||||
'#f1f3f5',
|
||||
'#e9ecef',
|
||||
'#dee2e6',
|
||||
'#ced4da',
|
||||
'#adb5bd',
|
||||
'#868e96',
|
||||
'#495057',
|
||||
'#343a40',
|
||||
'#212529',
|
||||
];
|
||||
|
||||
export const theme = createTheme({
|
||||
fontFamily: 'Inter, system-ui, sans-serif',
|
||||
primaryColor: 'gray',
|
||||
colors: {
|
||||
gray,
|
||||
},
|
||||
|
||||
// Dark mode specific colors
|
||||
black: '#0a0a0a',
|
||||
white: '#ffffff',
|
||||
|
||||
// Component-specific dark mode overrides
|
||||
components: {
|
||||
AppShell: {
|
||||
styles: (theme) => ({
|
||||
main: {
|
||||
backgroundColor: theme.colorScheme === 'dark'
|
||||
? theme.colors.dark[8]
|
||||
: theme.white,
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
Paper: {
|
||||
styles: (theme) => ({
|
||||
root: {
|
||||
backgroundColor: theme.colorScheme === 'dark'
|
||||
? theme.colors.dark[7]
|
||||
: theme.white,
|
||||
borderColor: theme.colorScheme === 'dark'
|
||||
? theme.colors.dark[5]
|
||||
: theme.colors.gray[3],
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
Modal: {
|
||||
styles: (theme) => ({
|
||||
content: {
|
||||
backgroundColor: theme.colorScheme === 'dark'
|
||||
? theme.colors.dark[7]
|
||||
: theme.white,
|
||||
},
|
||||
header: {
|
||||
backgroundColor: theme.colorScheme === 'dark'
|
||||
? theme.colors.dark[6]
|
||||
: theme.colors.gray[0],
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### 3. Theme Toggle Component
|
||||
|
||||
#### `components/ThemeToggle.tsx`
|
||||
```typescript
|
||||
'use client';
|
||||
|
||||
import { ActionIcon, useMantineColorScheme, useComputedColorScheme } from '@mantine/core';
|
||||
import { IconSun, IconMoon } from '@tabler/icons-react';
|
||||
|
||||
export function ThemeToggle() {
|
||||
const { setColorScheme } = useMantineColorScheme();
|
||||
const computedColorScheme = useComputedColorScheme('light');
|
||||
|
||||
return (
|
||||
<ActionIcon
|
||||
onClick={() => setColorScheme(computedColorScheme === 'dark' ? 'light' : 'dark')}
|
||||
variant="subtle"
|
||||
size="lg"
|
||||
aria-label="Toggle color scheme"
|
||||
>
|
||||
{computedColorScheme === 'dark' ? (
|
||||
<IconSun size={20} />
|
||||
) : (
|
||||
<IconMoon size={20} />
|
||||
)}
|
||||
</ActionIcon>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Add Toggle to Desktop Navigation
|
||||
|
||||
#### `components/DesktopNav.tsx`
|
||||
```typescript
|
||||
import { ThemeToggle } from './ThemeToggle';
|
||||
|
||||
export function DesktopNav() {
|
||||
return (
|
||||
<AppShell.Navbar p="md">
|
||||
<Stack justify="space-between" h="100%">
|
||||
<Stack>
|
||||
{/* Navigation items */}
|
||||
</Stack>
|
||||
|
||||
<Stack>
|
||||
<ThemeToggle />
|
||||
{/* Profile menu */}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</AppShell.Navbar>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Create Mode-Specific Favicons
|
||||
|
||||
#### Design Requirements
|
||||
- **Light mode favicon:** Dark icon on transparent background
|
||||
- **Dark mode favicon:** Light icon on transparent background
|
||||
- Both should be SVG for scalability
|
||||
- Simple, recognizable icon representing "thoughts" or "ponderants"
|
||||
|
||||
#### `public/favicon-light.svg`
|
||||
```svg
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||
<!-- Dark icon for light mode -->
|
||||
<circle cx="50" cy="50" r="40" fill="#212529" />
|
||||
<circle cx="35" cy="45" r="5" fill="#ffffff" />
|
||||
<circle cx="65" cy="45" r="5" fill="#ffffff" />
|
||||
<path d="M 30 65 Q 50 75 70 65" stroke="#ffffff" stroke-width="3" fill="none" />
|
||||
</svg>
|
||||
```
|
||||
|
||||
#### `public/favicon-dark.svg`
|
||||
```svg
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||
<!-- Light icon for dark mode -->
|
||||
<circle cx="50" cy="50" r="40" fill="#f8f9fa" />
|
||||
<circle cx="35" cy="45" r="5" fill="#212529" />
|
||||
<circle cx="65" cy="45" r="5" fill="#212529" />
|
||||
<path d="M 30 65 Q 50 75 70 65" stroke="#212529" stroke-width="3" fill="none" />
|
||||
</svg>
|
||||
```
|
||||
|
||||
### 6. Update Galaxy Visualization for Dark Mode
|
||||
|
||||
The 3D galaxy needs to look good in both modes:
|
||||
|
||||
#### `components/galaxy/ThoughtGalaxy.tsx`
|
||||
```typescript
|
||||
'use client';
|
||||
|
||||
import { useComputedColorScheme } from '@mantine/core';
|
||||
|
||||
export function ThoughtGalaxy() {
|
||||
const colorScheme = useComputedColorScheme('light');
|
||||
const isDark = colorScheme === 'dark';
|
||||
|
||||
return (
|
||||
<Canvas
|
||||
style={{
|
||||
background: isDark
|
||||
? 'linear-gradient(to bottom, #0a0a0a, #1a1a1a)'
|
||||
: 'linear-gradient(to bottom, #f8f9fa, #e9ecef)',
|
||||
}}
|
||||
>
|
||||
<ambientLight intensity={isDark ? 0.3 : 0.5} />
|
||||
<pointLight position={[10, 10, 10]} intensity={isDark ? 0.8 : 1} />
|
||||
|
||||
{/* Node spheres - adjust color based on theme */}
|
||||
<NodeSphere color={isDark ? '#f8f9fa' : '#212529'} />
|
||||
|
||||
{/* Link lines */}
|
||||
<LinkLine color={isDark ? 'rgba(248, 249, 250, 0.3)' : 'rgba(33, 37, 41, 0.3)'} />
|
||||
</Canvas>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Persist User Preference
|
||||
|
||||
Mantine automatically stores the preference in localStorage with the key `mantine-color-scheme-value`.
|
||||
|
||||
For server-side rendering, we can add a cookie fallback:
|
||||
|
||||
#### `middleware.ts`
|
||||
```typescript
|
||||
import { NextResponse } from 'next/server';
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
export function middleware(request: NextRequest) {
|
||||
const response = NextResponse.next();
|
||||
|
||||
// Set color scheme cookie if not present
|
||||
if (!request.cookies.has('mantine-color-scheme')) {
|
||||
response.cookies.set('mantine-color-scheme', 'auto');
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Manual Playwright MCP Testing
|
||||
|
||||
#### Test 1: Theme Toggle
|
||||
```typescript
|
||||
test('User can toggle between light and dark mode', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Check initial theme
|
||||
const initialTheme = await page.evaluate(() => {
|
||||
return document.documentElement.getAttribute('data-mantine-color-scheme');
|
||||
});
|
||||
|
||||
// Click theme toggle
|
||||
await page.click('[aria-label="Toggle color scheme"]');
|
||||
|
||||
// Check theme changed
|
||||
const newTheme = await page.evaluate(() => {
|
||||
return document.documentElement.getAttribute('data-mantine-color-scheme');
|
||||
});
|
||||
|
||||
expect(newTheme).not.toBe(initialTheme);
|
||||
});
|
||||
```
|
||||
|
||||
#### Test 2: Theme Persistence
|
||||
```typescript
|
||||
test('Theme preference persists across page refreshes', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Set to dark mode
|
||||
await page.click('[aria-label="Toggle color scheme"]');
|
||||
await page.waitForTimeout(100);
|
||||
|
||||
const darkTheme = await page.evaluate(() => {
|
||||
return document.documentElement.getAttribute('data-mantine-color-scheme');
|
||||
});
|
||||
|
||||
// Refresh page
|
||||
await page.reload();
|
||||
|
||||
// Check theme persisted
|
||||
const persistedTheme = await page.evaluate(() => {
|
||||
return document.documentElement.getAttribute('data-mantine-color-scheme');
|
||||
});
|
||||
|
||||
expect(persistedTheme).toBe(darkTheme);
|
||||
});
|
||||
```
|
||||
|
||||
#### Test 3: Favicon Changes
|
||||
```typescript
|
||||
test('Favicon changes with theme', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Get favicon in light mode
|
||||
const lightFavicon = await page.evaluate(() => {
|
||||
const link = document.querySelector('link[rel="icon"]');
|
||||
return link?.getAttribute('href');
|
||||
});
|
||||
|
||||
// Switch to dark mode
|
||||
await page.click('[aria-label="Toggle color scheme"]');
|
||||
await page.waitForTimeout(100);
|
||||
|
||||
// Get favicon in dark mode
|
||||
const darkFavicon = await page.evaluate(() => {
|
||||
const link = document.querySelector('link[rel="icon"]');
|
||||
return link?.getAttribute('href');
|
||||
});
|
||||
|
||||
expect(lightFavicon).toContain('light');
|
||||
expect(darkFavicon).toContain('dark');
|
||||
});
|
||||
```
|
||||
|
||||
### Magnitude Tests
|
||||
|
||||
```typescript
|
||||
import { test } from 'magnitude-test';
|
||||
|
||||
test('User can switch between light and dark mode', async (agent) => {
|
||||
await agent.open('http://localhost:3000');
|
||||
await agent.check('Page loads in default theme');
|
||||
|
||||
await agent.act('Click theme toggle button');
|
||||
await agent.check('Theme switches to opposite mode');
|
||||
await agent.check('All UI elements are visible in new theme');
|
||||
});
|
||||
|
||||
test('Dark mode looks good throughout app', async (agent) => {
|
||||
await agent.open('http://localhost:3000');
|
||||
await agent.act('Switch to dark mode');
|
||||
|
||||
await agent.act('Navigate to /chat');
|
||||
await agent.check('Chat interface is readable in dark mode');
|
||||
|
||||
await agent.act('Navigate to /galaxy');
|
||||
await agent.check('Galaxy visualization looks good in dark mode');
|
||||
|
||||
await agent.act('Navigate to /edit');
|
||||
await agent.check('Editor is readable in dark mode');
|
||||
});
|
||||
|
||||
test('Theme preference persists', async (agent) => {
|
||||
await agent.open('http://localhost:3000');
|
||||
await agent.act('Switch to dark mode');
|
||||
await agent.check('App is in dark mode');
|
||||
|
||||
await agent.act('Refresh page');
|
||||
await agent.check('App remains in dark mode');
|
||||
});
|
||||
```
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
1. **Update Mantine provider and layout**
|
||||
- Add ColorSchemeScript to head
|
||||
- Add dynamic favicon links
|
||||
- Update MantineProvider with defaultColorScheme
|
||||
|
||||
2. **Update theme configuration**
|
||||
- Add dark mode color overrides
|
||||
- Update component styles for both modes
|
||||
|
||||
3. **Create ThemeToggle component**
|
||||
- Add sun/moon icon toggle
|
||||
- Wire up to Mantine color scheme hooks
|
||||
|
||||
4. **Add toggle to navigation**
|
||||
- Add to DesktopNav
|
||||
- Add to mobile nav if applicable
|
||||
|
||||
5. **Create favicons**
|
||||
- Design light mode favicon (dark icon)
|
||||
- Design dark mode favicon (light icon)
|
||||
- Export as SVG
|
||||
- Add to public folder
|
||||
|
||||
6. **Update galaxy visualization**
|
||||
- Add theme-aware colors
|
||||
- Test in both modes
|
||||
|
||||
7. **Test with Playwright MCP**
|
||||
- Test toggle functionality
|
||||
- Test persistence
|
||||
- Test favicon changes
|
||||
- Test all pages in both modes
|
||||
|
||||
8. **Add Magnitude tests**
|
||||
- Write comprehensive theme tests
|
||||
- Ensure all pass
|
||||
|
||||
9. **Commit and push (NO DEPLOY)**
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- ✅ User can toggle between light and dark mode
|
||||
- ✅ Theme preference persists across sessions
|
||||
- ✅ Favicon changes based on active theme
|
||||
- ✅ All UI elements are readable in both modes
|
||||
- ✅ Galaxy visualization looks good in both modes
|
||||
- ✅ System preference is detected and respected
|
||||
- ✅ Smooth transitions between modes
|
||||
- ✅ All Playwright MCP tests pass
|
||||
- ✅ All Magnitude tests pass
|
||||
|
||||
## Files to Create
|
||||
|
||||
1. `components/ThemeToggle.tsx` - Toggle component
|
||||
2. `public/favicon-light.svg` - Light mode favicon
|
||||
3. `public/favicon-dark.svg` - Dark mode favicon
|
||||
4. `tests/playwright/theme.spec.ts` - Manual tests
|
||||
5. `tests/magnitude/theme.mag.ts` - Magnitude tests
|
||||
|
||||
## Files to Update
|
||||
|
||||
1. `app/layout.tsx` - Add ColorSchemeScript and dynamic favicons
|
||||
2. `app/theme.ts` - Add dark mode color overrides
|
||||
3. `components/DesktopNav.tsx` - Add ThemeToggle
|
||||
4. `components/galaxy/ThoughtGalaxy.tsx` - Add theme-aware colors
|
||||
Reference in New Issue
Block a user