feat: NCWF-5/6/7/8/9 chess analysis page and engine integration

- NCWF-5: scaffold /analysis route with ChessBoard viewer and navigation
- NCWF-6: FEN / PGN / Game-ID input form with depth selector
- NCWF-7: extend GameApiService with analyzePosition(); add AnalysisService
  with game-wide annotation pipeline; proxy /api/analysis -> :8087
- NCWF-8: EvalTimelineComponent — SVG win-chance chart per ply
- NCWF-9: AnnotatedMoveListComponent — quality labels (!! ! ?! ? ??)
  derived from win-chance delta

Also fix pre-existing app.spec.ts failure (missing provideHttpClient).
Apply project-wide prettier formatting pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Janis Eccarius
2026-06-10 15:55:14 +02:00
parent ae952d70b0
commit 8e2afb93f3
115 changed files with 6134 additions and 2378 deletions
+26 -37
View File
@@ -1,50 +1,39 @@
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Challenge, DeclineChallengeRequest, ListChallengesResponse, SendChallengeRequest } from '../models/challenge.models';
import {
Challenge,
DeclineChallengeRequest,
ListChallengesResponse,
SendChallengeRequest,
} from '../models/challenge.models';
@Injectable({ providedIn: 'root' })
export class ChallengeService {
private readonly http = inject(HttpClient);
private readonly challengeBaseUrl = '/api/challenge';
private readonly http = inject(HttpClient);
private readonly challengeBaseUrl = '/api/challenge';
sendChallenge(username: string, request: SendChallengeRequest): Observable<Challenge> {
return this.http.post<Challenge>(
`${this.challengeBaseUrl}/${username}`,
request
);
}
sendChallenge(username: string, request: SendChallengeRequest): Observable<Challenge> {
return this.http.post<Challenge>(`${this.challengeBaseUrl}/${username}`, request);
}
listChallenges(): Observable<ListChallengesResponse> {
return this.http.get<ListChallengesResponse>(
`${this.challengeBaseUrl}`
);
}
listChallenges(): Observable<ListChallengesResponse> {
return this.http.get<ListChallengesResponse>(`${this.challengeBaseUrl}`);
}
getChallenge(challengeId: string): Observable<Challenge> {
return this.http.get<Challenge>(
`${this.challengeBaseUrl}/${challengeId}`
);
}
getChallenge(challengeId: string): Observable<Challenge> {
return this.http.get<Challenge>(`${this.challengeBaseUrl}/${challengeId}`);
}
acceptChallenge(challengeId: string): Observable<Challenge> {
return this.http.post<Challenge>(
`${this.challengeBaseUrl}/${challengeId}/accept`,
{}
);
}
acceptChallenge(challengeId: string): Observable<Challenge> {
return this.http.post<Challenge>(`${this.challengeBaseUrl}/${challengeId}/accept`, {});
}
declineChallenge(challengeId: string, request?: DeclineChallengeRequest): Observable<void> {
return this.http.post<void>(
`${this.challengeBaseUrl}/${challengeId}/decline`,
request || {}
);
}
declineChallenge(challengeId: string, request?: DeclineChallengeRequest): Observable<void> {
return this.http.post<void>(`${this.challengeBaseUrl}/${challengeId}/decline`, request || {});
}
cancelChallenge(challengeId: string): Observable<void> {
return this.http.post<void>(
`${this.challengeBaseUrl}/${challengeId}/cancel`,
{}
);
}
cancelChallenge(challengeId: string): Observable<void> {
return this.http.post<void>(`${this.challengeBaseUrl}/${challengeId}/cancel`, {});
}
}