JunkBox apps/chess-ui 가이드
1. 애플리케이션 역할
apps/chess-ui는 Lichess sleepzz-bot 체스 샌드박스용 React + Vite 프런트엔드 앱임.
- 사용자가 상대 유형, 상대 ID, 색상, Lichess 내장 AI 레벨을 선택하고 대국 시작 요청을 보낼 수 있는 화면을 제공함.
react-chessboard로 현재 대국 판세를 화면 안에 상시 렌더링함.
- 실시간 WebSocket 이벤트를 받아 대국 상태, 시스템 상태, sleepzz-bot 선택 수, sleepzz-bot thought, 상대 move, Lichess 이벤트를 타임라인 카드로 표시함.
- 저장된 게임 이력을 Tabulator로 표시하고, 과거 게임 선택 시 저장 로그를 조회하며, 완료된 과거 게임의 move 로그 선택 시 해당 로그 시점의 보드 상태를 표시함.
- 진행 중인 게임 선택 시 WebSocket live timeline으로 복귀함.
- 체스판은 서버가 전달한 turn log의 FEN을 기준으로 표시하며, 보드 하단 메타 영역에서 현재 판세 요약과 AI 판단 상태를 함께 표시함.
- Lichess 사이트의 원본 대국 화면으로 이동하는 링크를 함께 제공함.
- 스타일은
shared-ui 공통 자산을 사용하며 화면 전용 CSS 파일을 소유하지 않음.
2. 런타임 구성
- 번들러는 Vite 6 계열을 사용함.
- React + TypeScript strict 기준으로 구현함.
- 체스판 UI는
react-chessboard를 사용함.
- FEN 검증, 현재 턴, 체크 여부, legal move 개수, material balance 계산과 향후 사용자 move 입력 확장을 위한 체스 규칙 유틸은
chess.js를 사용함.
- 개발 서버 기본 포트는
15002임.
- 빌드 산출물은
apps/chess-ui/dist에 생성됨.
- 운영에서는
chess-api가 이 산출물을 /chess-ui 경로로 직접 서빙함.
- API와 WebSocket 호출은 같은 origin의
/api/chess/* 경로를 사용함.
3. 엔트리와 화면 구조
- 메인 엔트리는
src/entries/chess.tsx임.
- 루트 마운트 지점은 chess 화면 템플릿의 React root임.
- 메인 화면 컴포넌트는
src/pages/ChessSandbox.tsx임.
- 타입 계약은
src/types/models.ts에서 관리함.
4. 사용자 흐름
4.1 대국 시작
- 사용자는
상대 ID를 입력할 수 있음.
- 설정 영역은
상대 유형 / 색상, 상대 ID / AI 레벨 2개 행으로 구성됨.
- 상대 유형 기본값은
Lichess 내장 AI이며, select 옵션도 Lichess 내장 AI, 일반/봇 계정 순서로 표시함.
- 상대 유형이
일반/봇 계정이면 입력한 상대 ID 또는 서버 기본 상대 ID를 사용함.
- 상대 유형이
Lichess 내장 AI이면 상대 ID 입력은 비활성화되고 AI 레벨 선택값을 사용함.
- 대국 설정 필드는 항상 화면에 표시되며, 현재 상대 유형에 맞지 않는 필드만 disabled 상태가 됨.
- 색상은
random, white, black 중 하나를 선택함.
- 색상 select 표기값은
Random, White, Black 형식을 사용함.
- AI 레벨 기본값은
Level 1이며, select 표기값은 Level 1부터 Level 8까지의 형식을 사용함.
대국 시작 버튼은 POST /api/chess/challenges를 호출함.
4.2 상대 유형
일반/봇 계정은 Lichess username 기반 challenge를 의미함.
일반/봇 계정은 백엔드에서 /api/challenge/{username}으로 변환됨.
Lichess 내장 AI는 Lichess가 제공하는 AI challenge를 의미함.
Lichess 내장 AI는 백엔드에서 /api/challenge/ai와 level 파라미터로 변환됨.
Lichess 내장 AI의 level은 1부터 8까지 선택함.
4.3 실시간 로그 관전
- 화면 로드 시
/api/chess/ws/logs WebSocket에 연결함.
game 이벤트는 좌측 메타 영역의 game, side, status, started 값을 갱신함.
status 이벤트는 info/warning/error 카드로 표시함.
turn_log 이벤트는 턴 번호, 주체, 색상, thought, move, SAN/UCI, PGN, 시간, sleepzz-bot 생성 latency 정보를 카드로 표시함.
- 로그 카드의 시간은
yyyy-mm-dd hh24:mi:ss 형식을 사용함.
- 화면 중앙 체스판은 현재 선택된 active game의 최신
turn_log.fen을 기준으로 현재 판세를 갱신함.
- 최신
turn_log.uci 또는 turn_log.move가 UCI 좌표 형식이면 출발 칸과 도착 칸을 마지막 수로 하이라이트함.
- 진행 중인 게임이 없거나 유효한 FEN이 아직 없으면 기본 시작 포지션을 표시함.
- 보드 방향은 최신
own_bot turn log의 side를 우선 사용하고, 없으면 현재 게임의 side를 사용해 Lichess처럼 내 봇이 하단에 오도록 표시함.
- 보드 하단 메타 영역은 현재 FEN을
chess.js로 해석해 Turn, Check, Material, Legal 값을 계산함.
Material은 보드 방향 기준 내 봇 관점의 단순 기물 점수 득실임.
Legal은 현재 차례의 legal move 개수임.
Decision, Latency, Choice, Candidates, Fallback은 현재 보드 로그 또는 최신 own_bot 로그의 event_payload와 commentary_latency_ms를 기준으로 표시함.
- 대국 ID가 있으면 체스판 헤더에서 Lichess 원본 대국 링크를 제공함.
- 진행 중인 live 게임이면 체스판 헤더에
게임 포기 버튼을 표시하고, 사용자가 confirm을 승인하면 /api/chess/games/{game_id}/resign을 호출함.
turn_log 카드의 move 배지는 우리 white, 상대 black 같은 주체 라벨 우측에 표시함.
- move 배지는 색상별로 구분하며 white 착수는 흰 배경과 검은 글씨, black 착수는 검은 배경과 흰 글씨 및 흰 테두리를 사용함.
- move 배지 텍스트는 SAN 표기가 있으면 SAN을 우선 표시하고, 없으면 UCI 또는 move 값을 표시함.
- SAN 표기에서 폰 이동은 도착 칸만 표시하며, 기물 이동은
K, Q, R, B, N 기호를 사용함.
- SAN 표기에서
x는 capture, +는 check, #는 checkmate, O-O와 O-O-O는 castling을 의미함.
- live timeline은 같은
game_id, ply_number, actor_type, event_type을 가진 turn_log를 다시 받으면 기존 카드를 갱신함.
- live 로그가 여러 게임 이벤트를 포함하더라도 체스판 상태 계산은 active game 로그만 사용함.
- own bot move는 sleepzz-bot이 직접 선택한 수와
현재 상황 / 착수 근거 thought를 함께 표시하며, 두 말머리는 badge 형태로 강조함.
- 타임라인은 최신 생성 이벤트가 가장 위에 오도록 정렬함.
- 로그 카드 하단의 PGN/기보 영역은 기본적으로 접힘 상태이며,
기보 토글 버튼으로 표시 여부를 전환함.
- PGN 텍스트는 카드 좌우 폭 전체를 사용해 줄바꿈되도록 표시함.
4.4 게임 이력 조회
- 좌측 하단
Game History 영역은 Tabulator 기반 페이지네이션 표임.
- 게임 이력은 chess 앱을 통해 발생한 AI 대국을 대상으로 함.
- 정렬 기준은 시작 시각 역순임.
- 표시는 시작 시각, 내 색상, 결과를 중심으로 구성함.
- 시작 시각은
yyyy-mm-dd hh24:mi:ss 형식을 사용함.
- 결과의
승은 파란색 텍스트, 패는 빨간색 텍스트로 표시하고, started 같은 진행 상태는 일반 텍스트로 표시함.
- 과거 게임 행을 선택하면
GET /api/chess/games/{game_id}/logs로 저장 로그를 조회해 우측 로그 패널에 표시함.
- 과거 게임 행을 선택하면 저장 로그의 최신 FEN으로 중앙 체스판도 함께 갱신함.
- 완료된 과거 게임의 우측 로그 패널에서는 유효한
fen을 가진 event_type=move 로그 카드만 보드 시점 선택 대상으로 동작함.
- 완료된 과거 게임에서 move 로그 카드를 클릭하거나 키보드
Enter 또는 Space로 선택하면 중앙 체스판은 해당 로그의 fen을 우선 표시함.
- 선택된 move 로그의
uci 또는 move가 UCI 좌표 형식이면 해당 시점의 마지막 수 출발 칸과 도착 칸을 하이라이트함.
game_start, game_finish, status, warning, error 같은 비 move 이벤트 로그는 저장된 fen이 있더라도 로그 카드 선택으로 보드 시점을 바꾸지 않음.
- 진행 중인 게임이나 live timeline의 로그 카드는 보드 시점 선택 대상으로 동작하지 않음.
- 과거 게임 행을 새로 선택하거나 live mode로 복귀하면 기존 move 로그 선택 상태는 초기화됨.
- 현재 진행 중인 게임 행을 선택하면 WebSocket live timeline 표시로 복귀함.
5. API 계약
5.1 Challenge 요청
- 요청 경로는
POST /api/chess/challenges임.
- 요청 본문은
opponent_id, opponent_type, lichess_ai_level, side를 포함함.
opponent_type은 user 또는 lichess_ai를 사용함.
lichess_ai_level은 내장 AI challenge에서 사용함.
side는 random, white, black 중 하나임.
5.2 Challenge 응답
- 응답은
challenge_id, game_id, status, raw를 포함함.
- challenge 요청 실패는 status timeline error 카드로 표시함.
5.3 Game 목록과 이력
- 화면 초기화 시
GET /api/chess/games를 호출해 현재 런타임에서 알고 있는 game 목록을 읽음.
- 가장 최신 game을 좌측 메타 영역에 표시함.
GET /api/chess/game-history는 page, size query를 받아 페이지 단위 게임 이력을 반환함.
- 게임 이력 응답은
items, page, size, total, last_page를 포함함.
GET /api/chess/games/{game_id}/logs는 선택한 게임의 저장 로그 목록을 반환함.
5.4 WebSocket 이벤트
ChessSocketEvent는 game, turn_log, status union 타입으로 관리함.
- status 이벤트의 생성 시각은 클라이언트 수신 시각을 기준으로 생성함.
- turn log 이벤트의 생성 시각은 서버가 전달한 KST ISO 문자열을 사용함.
- turn log 이벤트는
commentary_latency_ms를 포함할 수 있음.
- own bot turn log의
event_payload는 commentary_status, choice, candidate_moves, fallback_reason 같은 보드 하단 판단 메타의 원본으로 사용될 수 있음.
- 게임 이력에서 과거 로그를 보고 있는 동안에도 WebSocket 연결은 유지되며, 진행 중인 게임 선택 시 live timeline 데이터를 다시 표시함.
- 저장 로그 응답의 각
turn_log.fen은 과거 게임의 특정 move 시점 보드 복원에 직접 사용됨.
6. 화면 레이아웃
6.1 PC/FHD 기준
- PC에서는 3컬럼 구조를 사용함.
- 좌측 컬럼은 360px 고정 폭을 사용함.
- 좌측 컬럼은 대국 설정 카드, 연결/게임 상태 메타, Game History 영역을 포함함.
- 중앙 컬럼은
react-chessboard 기반 현재 판세 보드와 보드 메타 영역을 포함함.
- 중앙 컬럼은 체스판 실제 표시 폭에 맞춰 과도한 좌우 여백을 줄이고, 우측 로그 컬럼에 더 넓은 가로 공간을 배정함.
- 중앙 컬럼의 보드 메타는 패널 하단에 붙이고, 남는 세로 공간은 체스판 표시 영역에 우선 배정함.
- 중앙 컬럼의 보드 메타는 체스판 표시 폭과 같은 폭으로 정렬함.
- PC/FHD 기준 보드 메타는 3열 grid를 사용함.
- 보드 메타 표시 순서는
Last move, Turn, Check, Material, Legal, Ply, Bot time, Opponent, Result, Mode, Decision, Latency, Choice, Candidates, Fallback 순서임.
Fallback 행은 값이 길 수 있으므로 PC/FHD 기준 3열 전체 폭을 사용함.
- 우측 컬럼은 실시간 에이전트 로그 패널임.
- FHD 기준 페이지 전체 브라우저 스크롤이 생기지 않도록 화면 높이를 viewport에 맞춤.
- 로그가 많아지면 우측 로그 패널 내부에서만 스크롤됨.
- Game History와 우측 로그 영역은 같은 화면 하단 기준에 맞게 배치됨.
- 좌측 상단 설정/상태 영역은 360px 폭 안에서 텍스트가 잘리지 않도록 2컬럼 grid와 요약 메타 grid를 사용함.
- 좌측 메타 영역의
Started 행은 2컬럼 전체 폭을 차지하고 라벨은 좌측, 값은 우측 정렬해 yyyy-mm-dd hh24:mi:ss 값을 온전히 표시함.
6.2 모바일 기준
- 모바일은 360px 가로 폭을 주요 기준으로 함.
- 모바일에서는 매칭 설정, Game History, 체스판, 로그가 위아래로 배치되는 단일 컬럼 구조를 사용함.
- 좁은 화면에서는 우측 로그 영역이 Game History 아래로 내려가며 360px 기준 폭에 맞춰 표시됨.
- 체스판은 모바일에서도 항상 표시하며 정사각형 비율을 유지함.
- 모바일 보드 메타는 2열 grid를 유지함.
- 모바일에서도 공통 다크 톤과 Pretendard 폰트 규칙을 유지함.
7. 공통 UI 사용 규칙
chess-ui는 화면 전용 CSS 파일을 만들지 않음.
- 체스 화면 전용에 가까운 레이아웃 클래스도
shared-ui의 jb-chess-* 공통 base CSS에서 관리함.
- 체스 HTML 템플릿은 CSS 링크를 직접 선언하지 않고
shared-ui stylesheet 매크로를 통해 hub-ui-base.css, hub-ui-desktop.css, Tabulator vendor CSS, junkbox-tabulator.css 조합을 로드함.
- 입력과 select는
jb-dark-field를 사용함.
jb-dark-field select option은 흰 배경이 노출되지 않도록 공통 다크 option 스타일을 사용함.
- 버튼은 공통
jb-button과 jb-button-full을 사용함.
- 패널은 공통
jb-panel을 사용함.
- 칩은 공통
jb-chip을 사용함.
- 게임 이력 표는 Tabulator와
junkbox-tabulator.css 공통 자산을 사용함.
- 현재 판세 보드 패널은
jb-chess-board-* 계열 클래스를 사용함.
- 보드 하단 메타는
jb-chess-board-meta와 jb-chess-meta-row 계열 클래스를 사용함.
jb-chess-board-meta는 모바일 2열, PC/FHD 3열 grid 기준으로 동작함.
- 로그 카드 상태 구분은
jb-chess-log-card--status, jb-chess-log-card--warning, jb-chess-log-card--error 클래스를 사용하되, 기본 카드는 미니멀하고 플랫한 시각 규칙을 따름.
- 완료된 과거 게임의 선택 가능한 move 로그 카드는
jb-chess-log-card--selectable을 함께 사용하고, 현재 보드에 반영된 로그 카드는 jb-chess-log-card--selected를 함께 사용함.
- 로그 헤더의 기보 표시 전환은
jb-chess-log-toggle 계열 클래스를 사용함.
- 진행 중인 게임 포기 버튼은
jb-chess-resign-button 클래스를 사용하며, 흰색 강조 버튼으로 표시함.
- 좌측 메타와 보드 메타의 넓은 행은
jb-chess-meta-row--wide를 사용하고, 로그 카드의 move 배지는 jb-chess-log-move를 inline 요소로 사용함.
- 로그 카드의 thought 말머리 badge는
jb-chess-thought-badge를 사용함.
- 폰트는
shared-ui가 제공하는 Pretendard 기준을 따르며 별도 monospace를 강제하지 않음.
8. 시간 표시
- 시간 표시 함수는
Asia/Seoul timezone을 명시함.
- 날짜 객체 파싱에 실패하면 원본 문자열을 그대로 표시함.
created_at, started_at 같은 서버 전달 시간은 KST offset 포함 ISO 문자열을 기준으로 함.
- 좌측 메타 영역의
Started, Game History의 시작 시각, 우측 로그 카드 시간은 yyyy-mm-dd hh24:mi:ss 형식을 사용함.
9. 운영 제약
- 체스판은 화면 내에서 렌더링하지만 실제 Lichess 대국의 원본 상태 확인을 위해 Lichess 링크를 유지함.
- 이번 단계에서 프론트엔드는 사용자 move 입력을 받지 않으며 보드는 관전 전용으로 동작함.
- 사용자 직접 플레이를 도입할 경우 프런트는
chess.js로 1차 합법수 검증을 수행하고, 서버는 기존 python-chess 기준으로 최종 검증해야 함.
- sleepzz-bot 수 선택과 서버 측 move legality 검증은 백엔드에서 수행함.
- 사용자 노출 문구는 화면 컴포넌트에 한국어 문구로 직접 작성하고, 통일이 필요한 표현은 공통 UI 가이드와 현재 화면 문구를 기준으로 맞춤.
10. 관련 문서
- 프로젝트 전체 구조는
docs/junkbox/common/guide-jb-core.md를 따름.
- 체스 백엔드 구조는
docs/junkbox/apps/guide-apps-chess-api.md를 따름.
- 공통 AI 런타임은
docs/junkbox/libs/guide-lib-shared-ai.md를 따름.
- 공통 UI 자산 구조는
docs/junkbox/libs/guide-lib-shared-ui.md를 따름.
- 공통 UI 규칙은
docs/junkbox/common/guide-jb-common-ui.md를 따름.