From 3ee8760496a2b788d47de713fa2dc2f7b0c44422 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 1 Jul 2025 20:45:35 +0000 Subject: [PATCH 1/3] Initial plan From acd70faf9e28b524b73a5b32d2d1b9f4742bcc67 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 1 Jul 2025 20:53:06 +0000 Subject: [PATCH 2/3] Initial analysis and build fixes for dark mode switcher implementation Co-authored-by: skylar-anderson <884151+skylar-anderson@users.noreply.github.com> --- app/actions.ts | 4 ++-- app/columns/IssuePRColumnType.tsx | 2 +- app/functions/updateIssue.ts | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/actions.ts b/app/actions.ts index 878e836..7f5e612 100644 --- a/app/actions.ts +++ b/app/actions.ts @@ -15,7 +15,7 @@ export type Option = { export type PrimaryDataType = 'issue' | 'commit' | 'pull-request' | 'snippet' | 'item'; type GridCellState = 'empty' | 'generating' | 'done' | 'error'; -export type ColumnType = 'text' | 'select' | 'select-user' | 'file' | 'issue-pr' | 'commit'; +export type ColumnType = 'text' | 'select' | 'select-user' | 'file' | 'issue-pr' | 'commit' | 'boolean'; export type ColumnResponse = { text: string; @@ -50,7 +50,7 @@ export type ColumnResponse = { message?: string; }>; }; - boolean: boolean; + boolean: { value: boolean }; }; export type GridCell = { diff --git a/app/columns/IssuePRColumnType.tsx b/app/columns/IssuePRColumnType.tsx index bf612ce..efeb0ce 100644 --- a/app/columns/IssuePRColumnType.tsx +++ b/app/columns/IssuePRColumnType.tsx @@ -110,7 +110,7 @@ export const IssuePRColumnType: BaseColumnType<'issue-pr'> = { parsed = JSON.parse(responseContent); } catch (error) { console.error('Failed to parse response content:', error); - return multiple ? { references: [] } : { reference: null }; + return multiple ? { references: [] } : { reference: undefined }; } return multiple ? { references: parsed.references } : { reference: parsed.reference }; }, diff --git a/app/functions/updateIssue.ts b/app/functions/updateIssue.ts index 881609c..3fc5715 100644 --- a/app/functions/updateIssue.ts +++ b/app/functions/updateIssue.ts @@ -65,7 +65,6 @@ async function run( stateReason?: 'completed' | 'not_planned' | 'reopened' ) { return await updateIssue({ - repository, title, body, labels, From 9ad58dc4efefa50320687c4f1824ff4924e6f095 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 1 Jul 2025 21:03:45 +0000 Subject: [PATCH 3/3] Implement light/dark mode theme switcher with persistence Co-authored-by: skylar-anderson <884151+skylar-anderson@users.noreply.github.com> --- app/components/GridHeader.tsx | 2 ++ app/components/ThemeContext.tsx | 59 ++++++++++++++++++++++++++++++++ app/components/ThemeSwitcher.tsx | 19 ++++++++++ app/grid/[id]/page.tsx | 37 +++++++++++++++++--- app/page.tsx | 31 +++++++++++++++-- 5 files changed, 141 insertions(+), 7 deletions(-) create mode 100644 app/components/ThemeContext.tsx create mode 100644 app/components/ThemeSwitcher.tsx diff --git a/app/components/GridHeader.tsx b/app/components/GridHeader.tsx index 22387fa..554cfce 100644 --- a/app/components/GridHeader.tsx +++ b/app/components/GridHeader.tsx @@ -2,6 +2,7 @@ import { TextInput, Text, ActionMenu, ActionList, Box, Button, CounterLabel } fr import { ArrowLeftIcon } from '@primer/octicons-react'; import { SearchIcon } from '@primer/octicons-react'; import { useGridContext } from './GridContext'; +import { ThemeSwitcher } from './ThemeSwitcher'; import NextLink from 'next/link'; export function Search() { @@ -167,6 +168,7 @@ export function GridHeader({ title, setShowNewColumnForm, count }: GridHeaderPro + diff --git a/app/components/ThemeContext.tsx b/app/components/ThemeContext.tsx new file mode 100644 index 0000000..1fcf0be --- /dev/null +++ b/app/components/ThemeContext.tsx @@ -0,0 +1,59 @@ +'use client'; + +import React, { createContext, useContext, useEffect, useState } from 'react'; + +type ColorMode = 'light' | 'dark'; + +interface ThemeContextType { + colorMode: ColorMode; + setColorMode: (mode: ColorMode) => void; + toggleColorMode: () => void; + isLoaded: boolean; +} + +const ThemeContext = createContext(undefined); + +export const useTheme = () => { + const context = useContext(ThemeContext); + if (!context) { + throw new Error('useTheme must be used within a ThemeProvider'); + } + return context; +}; + +interface ThemeProviderProps { + children: React.ReactNode; +} + +export const ThemeProvider: React.FC = ({ children }) => { + const [colorMode, setColorModeState] = useState('light'); + const [isLoaded, setIsLoaded] = useState(false); + + // Load saved theme preference on mount + useEffect(() => { + const savedTheme = localStorage.getItem('colorMode') as ColorMode; + if (savedTheme && (savedTheme === 'light' || savedTheme === 'dark')) { + setColorModeState(savedTheme); + } + setIsLoaded(true); + }, []); + + const setColorMode = (mode: ColorMode) => { + setColorModeState(mode); + localStorage.setItem('colorMode', mode); + }; + + const toggleColorMode = () => { + const newMode = colorMode === 'light' ? 'dark' : 'light'; + setColorMode(newMode); + }; + + const value = { + colorMode, + setColorMode, + toggleColorMode, + isLoaded, + }; + + return {children}; +}; \ No newline at end of file diff --git a/app/components/ThemeSwitcher.tsx b/app/components/ThemeSwitcher.tsx new file mode 100644 index 0000000..d95ee1d --- /dev/null +++ b/app/components/ThemeSwitcher.tsx @@ -0,0 +1,19 @@ +'use client'; + +import React from 'react'; +import { IconButton } from '@primer/react'; +import { SunIcon, MoonIcon } from '@primer/octicons-react'; +import { useTheme } from './ThemeContext'; + +export const ThemeSwitcher: React.FC = () => { + const { colorMode, toggleColorMode } = useTheme(); + + return ( + + ); +}; \ No newline at end of file diff --git a/app/grid/[id]/page.tsx b/app/grid/[id]/page.tsx index 9e53d03..89390a4 100644 --- a/app/grid/[id]/page.tsx +++ b/app/grid/[id]/page.tsx @@ -1,17 +1,46 @@ +'use client'; + import { createPrimaryColumn, hydrateCell } from '../../actions'; -import { BaseStyles, ThemeProvider } from '@primer/react'; +import { BaseStyles, ThemeProvider as PrimerThemeProvider } from '@primer/react'; +import { ThemeProvider } from '../../components/ThemeContext'; +import { useTheme } from '../../components/ThemeContext'; import Grid from '../../components/Grid'; -export default function GridPage({ params }: { params: { id: string } }) { +function ThemedApp({ initialGridId }: { initialGridId: string }) { + const { colorMode, isLoaded } = useTheme(); + + // Don't render until theme is loaded to prevent hydration mismatch + if (!isLoaded) { + return ( + + + + + + ); + } + return ( - + + + ); +} + +export default function GridPage({ params }: { params: { id: string } }) { + return ( + + ); } diff --git a/app/page.tsx b/app/page.tsx index f011b20..d184ab6 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,14 +1,39 @@ +'use client'; + import { createPrimaryColumn, hydrateCell } from './actions'; -import { BaseStyles, ThemeProvider } from '@primer/react'; +import { BaseStyles, ThemeProvider as PrimerThemeProvider } from '@primer/react'; +import { ThemeProvider } from './components/ThemeContext'; +import { useTheme } from './components/ThemeContext'; import Grid from './components/Grid'; -export default function Page() { +function ThemedApp() { + const { colorMode, isLoaded } = useTheme(); + + // Don't render until theme is loaded to prevent hydration mismatch + if (!isLoaded) { + return ( + + + + + + ); + } + return ( - + + + ); +} + +export default function Page() { + return ( + + ); }