Files
app/plans/05-dark-light-mode-theme.md
Albert b96159ec02 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>
2025-11-09 21:07:42 +00:00

12 KiB

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

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

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

'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

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 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 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

'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

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

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

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

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

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