@@ -7,6 +7,11 @@ import GraphView from "../components/GraphView";
77import { useAppState } from "../hooks/useAppState" ;
88import { useCodeHealthController } from "../hooks/useCodeHealthController" ;
99
10+ type AppView = "dashboard" | "settings" ;
11+ type Theme = "light" | "dark" ;
12+
13+ const THEME_STORAGE_KEY = "structa-theme" ;
14+
1015export default function App ( ) {
1116 const { state, actions, canAnalyze } = useAppState ( ) ;
1217 const {
@@ -27,12 +32,25 @@ export default function App() {
2732 service : tauriCodeHealthService
2833 } ) ;
2934 const [ copyStatus , setCopyStatus ] = useState ( "" ) ;
35+ const [ view , setView ] = useState < AppView > ( "dashboard" ) ;
36+ const [ theme , setTheme ] = useState < Theme > ( ( ) => {
37+ const storedTheme = window . localStorage . getItem ( THEME_STORAGE_KEY ) ;
38+ if ( storedTheme === "dark" || storedTheme === "light" ) {
39+ return storedTheme ;
40+ }
41+ return "light" ;
42+ } ) ;
3043 const showSuggestionPanel = Boolean ( activeSuggestion ) ;
3144
3245 useEffect ( ( ) => {
3346 setCopyStatus ( "" ) ;
3447 } , [ activeSuggestion ?. runId ] ) ;
3548
49+ useEffect ( ( ) => {
50+ document . documentElement . setAttribute ( "data-theme" , theme ) ;
51+ window . localStorage . setItem ( THEME_STORAGE_KEY , theme ) ;
52+ } , [ theme ] ) ;
53+
3654 async function handleCopySuggestion ( ) {
3755 if ( ! activeSuggestion ) {
3856 return ;
@@ -66,84 +84,115 @@ export default function App() {
6684
6785 return (
6886 < main className = "app-shell" >
69- < h1 > Structa</ h1 >
70- < Controls
71- projectPath = { state . projectPath }
72- status = { state . status }
73- onSelectFolder = { handleSelectFolder }
74- onAnalyze = { handleAnalyze }
75- canAnalyze = { canAnalyze }
76- />
77-
78- { state . error ? < div className = "card error" > { state . error } </ div > : null }
79- { agentRunProgress ? < AgentProgressPanel progress = { agentRunProgress } /> : null }
80-
81- < section
82- className = { `layout-grid ${
83- showSuggestionPanel ? "layout-grid-with-suggestion" : "layout-grid-without-suggestion"
84- } `}
85- >
86- < div className = "column-left" >
87- < ErrorListPanel
88- graph = { state . graph }
89- selectedNodeId = { state . selectedNodeId }
90- onSelectError = { handleErrorClick }
91- onResolveError = { handleResolveError }
92- onOpenSuggestion = { handleOpenSuggestionForError }
93- hasSuggestionForError = { hasSuggestionForError }
94- resolvingErrorId = { resolvingErrorId }
95- resolvingProgress = {
96- agentRunProgress && resolvingErrorId === agentRunProgress . errorId
97- ? agentRunProgress . progress
98- : null
99- }
100- resolvingMessage = {
101- agentRunProgress && resolvingErrorId === agentRunProgress . errorId
102- ? agentRunProgress . message
103- : null
104- }
105- />
87+ < header className = "app-header" >
88+ < h1 > Structa</ h1 >
89+ < div className = "header-actions" >
90+ < button type = "button" onClick = { ( ) => setView ( "dashboard" ) } disabled = { view === "dashboard" } >
91+ Dashboard
92+ </ button >
93+ < button type = "button" onClick = { ( ) => setView ( "settings" ) } disabled = { view === "settings" } >
94+ Settings
95+ </ button >
10696 </ div >
107- < div className = "column-right" >
108- < GraphView
109- graph = { state . graph }
110- focusRequest = { state . graphFocusRequest }
111- selectedNodeId = { state . selectedNodeId }
112- onNodeClick = { handleNodeClick }
97+ </ header >
98+
99+ { view === "dashboard" ? (
100+ < >
101+ < Controls
102+ projectPath = { state . projectPath }
103+ status = { state . status }
104+ onSelectFolder = { handleSelectFolder }
105+ onAnalyze = { handleAnalyze }
106+ canAnalyze = { canAnalyze }
113107 />
114- </ div >
115- { showSuggestionPanel ? (
116- < div className = "column-suggestion" >
117- < section className = "card panel suggestion-panel" >
118- < h2 > Agent Suggestion</ h2 >
119- { activeSuggestion ? (
120- < >
121- < p className = "suggestion-meta" >
122- Error: < code > { activeSuggestion . errorId } </ code > ({ activeSuggestion . ruleCode } ) | Model:{ " " }
123- { activeSuggestion . model } | Run: < code > { activeSuggestion . runId } </ code >
124- </ p >
125- < div className = "suggestion-actions" >
126- < button type = "button" onClick = { ( ) => void handleCopySuggestion ( ) } >
127- Copy Suggestion
128- </ button >
129- < button type = "button" onClick = { ( ) => handleCloseSuggestion ( activeSuggestion . runId ) } >
130- Close Suggestion
131- </ button >
132- < span className = "suggestion-copy-status" aria-live = "polite" >
133- { copyStatus }
134- </ span >
135- </ div >
136- < pre > { activeSuggestion . suggestion } </ pre >
137- </ >
138- ) : (
139- < p className = "suggestion-empty-state" >
140- No active suggestion.
141- </ p >
142- ) }
143- </ section >
144- </ div >
145- ) : null }
146- </ section >
108+
109+ { state . error ? < div className = "card error" > { state . error } </ div > : null }
110+ { agentRunProgress ? < AgentProgressPanel progress = { agentRunProgress } /> : null }
111+
112+ < section
113+ className = { `layout-grid ${
114+ showSuggestionPanel ? "layout-grid-with-suggestion" : "layout-grid-without-suggestion"
115+ } `}
116+ >
117+ < div className = "column-left" >
118+ < ErrorListPanel
119+ graph = { state . graph }
120+ selectedNodeId = { state . selectedNodeId }
121+ onSelectError = { handleErrorClick }
122+ onResolveError = { handleResolveError }
123+ onOpenSuggestion = { handleOpenSuggestionForError }
124+ hasSuggestionForError = { hasSuggestionForError }
125+ resolvingErrorId = { resolvingErrorId }
126+ resolvingProgress = {
127+ agentRunProgress && resolvingErrorId === agentRunProgress . errorId
128+ ? agentRunProgress . progress
129+ : null
130+ }
131+ resolvingMessage = {
132+ agentRunProgress && resolvingErrorId === agentRunProgress . errorId
133+ ? agentRunProgress . message
134+ : null
135+ }
136+ />
137+ </ div >
138+ < div className = "column-right" >
139+ < GraphView
140+ graph = { state . graph }
141+ focusRequest = { state . graphFocusRequest }
142+ selectedNodeId = { state . selectedNodeId }
143+ onNodeClick = { handleNodeClick }
144+ />
145+ </ div >
146+ { showSuggestionPanel ? (
147+ < div className = "column-suggestion" >
148+ < section className = "card panel suggestion-panel" >
149+ < h2 > Agent Suggestion</ h2 >
150+ { activeSuggestion ? (
151+ < >
152+ < p className = "suggestion-meta" >
153+ Error: < code > { activeSuggestion . errorId } </ code > ({ activeSuggestion . ruleCode } ) | Model:{ " " }
154+ { activeSuggestion . model } | Run: < code > { activeSuggestion . runId } </ code >
155+ </ p >
156+ < div className = "suggestion-actions" >
157+ < button type = "button" onClick = { ( ) => void handleCopySuggestion ( ) } >
158+ Copy Suggestion
159+ </ button >
160+ < button type = "button" onClick = { ( ) => handleCloseSuggestion ( activeSuggestion . runId ) } >
161+ Close Suggestion
162+ </ button >
163+ < span className = "suggestion-copy-status" aria-live = "polite" >
164+ { copyStatus }
165+ </ span >
166+ </ div >
167+ < pre > { activeSuggestion . suggestion } </ pre >
168+ </ >
169+ ) : (
170+ < p className = "suggestion-empty-state" >
171+ No active suggestion.
172+ </ p >
173+ ) }
174+ </ section >
175+ </ div >
176+ ) : null }
177+ </ section >
178+ </ >
179+ ) : (
180+ < section className = "settings-layout" >
181+ < section className = "card settings-panel" >
182+ < h2 > Appearance</ h2 >
183+ < label className = "toggle-row" htmlFor = "dark-mode-toggle" >
184+ < span > Dark mode</ span >
185+ < input
186+ id = "dark-mode-toggle"
187+ type = "checkbox"
188+ checked = { theme === "dark" }
189+ onChange = { ( event ) => setTheme ( event . currentTarget . checked ? "dark" : "light" ) }
190+ />
191+ </ label >
192+ < p className = "settings-note" > Theme preference is saved on this device.</ p >
193+ </ section >
194+ </ section >
195+ ) }
147196 </ main >
148197 ) ;
149198}
0 commit comments