Skip to content

feat: 시뮬레이션 결과 화면 분리 — 새 라우트 /experience/simulation (#36) - #37

Merged
wook10 merged 2 commits into
feature/34-block-catalogfrom
feature/36-simulation-result
Sep 16, 2026
Merged

wook10 merged 2 commits into
feature/34-block-catalogfrom
feature/36-simulation-result

Conversation

@wook10

@wook10 wook10 commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

개요

Closes #36

스택 PR 안내: #33(블록 삭제)·#35(블록 카탈로그)가 아직 머지되지 않아, 이 PR도 base 를 main 대신 feature/34-block-catalog로 잡았습니다. 앞의 두 PR이 순서대로 머지되면 이 PR의 base 를 main으로 옮기겠습니다.

Simulation 기능명세서의 "시뮬레이션 실행"이 페이지 필요: YES로 바뀌어, 워크스페이스 안 텍스트 힌트 대신 Figma Slide 6(실패)·Slide 7(성공)의 전용 결과 화면을 새 라우트로 분리했다.

기명서·Figma 대조표

항목 근거 구현
시뮬레이션 실행 결과를 별도 화면으로 표시 Simulation DB "페이지 필요: YES" 신규 라우트 /experience/simulation + SimulationResultView
결과 카드(성공/실패 배지·안내 문구·버튼 2개) Figma Slide 6·7 결과 카드 성공: 시뮬레이션 통과!(초록) / 실패: 조금만 고치면 돼요!(빨강) + 안전 위반 메시지
실행순서(단계별 완료/문제 발생/대기) Figma Slide 6·7 실행순서 목록 unrollExecutionSteps로 repeat 를 실제 실행 횟수만큼 펼치고, 서버가 계산한 failedAtIndex 기준으로 단계별 상태 표시
실행 블록 Figma Slide 6·7 실행 블록 목록 기존 BlockStack 그대로 재사용(읽기 전용)
로봇 미리보기(배지 + 진행 바 + 스탯) Figma Slide 6·7 좌측 패널 기존 격자 박스 패턴을 이 화면 크기(900×500)에 맞춰 재구성 + 진행상태 행 추가
'블록 고치기'/'로봇 실행하기' Figma 버튼 2개, 실행 중 화면은 Figma에 없음 둘 다 /experience로 복귀 — 돌아간 화면에서 ExperienceView 가 남겨진 결과를 이어받아 BlockWorkspace/RunReadyWorkspace 중 맞는 쪽을 그대로 보여줌(기존 RunReadyWorkspace 흐름 유지)

구현

  • features/simulation: SimulationResult.failedAtIndex 추가. evaluateProgram이 repeat 를 실제 실행 횟수만큼 펼쳐(unroll) 안전 구역을 처음 벗어나는 위치를 계산
  • views/experience/model/blockProgram.ts: unrollExecutionSteps — 서버와 같은 펼치기 규칙을 클라이언트에서 재사용해 실행순서 목록을 만든다(서버 응답은 인덱스 숫자만 주고, 목록 자체는 클라이언트가 이미 가진 program 으로 만든다 — 페이로드 최소화)
  • views/experience/lib/simulationResultStorage.ts: ExperienceView ↔ 결과 화면 간 전달용 sessionStorage 스냅샷(program+result). ExperienceView 는 라우트를 넘어가면 새로 마운트돼 mutation state 를 잃기 때문에 필요
  • views/experience/ui/SimulationResultView.tsx: 신규 컴포넌트
  • views/experience/ui/ExperienceView.tsx: 시뮬레이션 성공/실패 모두 결과 화면으로 라우팅, 복귀 시 저장된 결과가 지금 program 과 일치하면(그 사이 편집 안 했으면) 통과 상태를 이어받음

테스트

  • simulateProgram.test.ts: failedAtIndex 계산 3건(안전 구역 위반 위치, 반복+개별 이동 합산 시 위치, 값 무효 시 undefined) + 통과 시 undefined
  • blockProgram.test.ts: unrollExecutionSteps 3건(non-repeat 통과, repeat count 만큼 펼침, 펼친 단계가 원래 파라미터 보존)
  • SimulationResultView.test.tsx: 신규 3건(저장된 결과 없으면 리다이렉트, 실패 화면 배지·상태·버튼, 성공 화면 배지·상태·버튼)
  • ExperienceView.test.tsx: 신규 6건(결과 화면으로 이동 — 통과/실패 모두, 스냅샷 기록, 복귀 시 통과 이어받기, program 불일치 시 무시, 처음으로 시 기록도 함께 삭제)
  • 전체 106/106 통과, typecheck/lint/build/format:check 클린
  • CDP로 성공·실패 화면 모두 시각 확인(배지 색상·버튼 활성/잠금·진행 바 비율·실행순서 상태 전부 기대대로 렌더)

참고

  • next/navigation useRouter를 테스트에서 쓸 수 있게 src/shared/testing/routerMock.ts + vitest.setup.ts 전역 mock 추가(기존엔 App Router 컴포넌트를 렌더하는 테스트가 없어 필요 없었음)
  • 두 패널(900px+586px)의 합이 Figma 정확한 값 그대로 1440px 뷰포트보다 넓어 좁은 화면에선 가로 스크롤이 생김 — 임의로 줄이지 않고 Figma 값을 그대로 유지(이 화면은 1920 기준 키오스크 목업)

🤖 Generated with Claude Code

@inryeok-bot inryeok-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

리뷰 결과

변경된 17개 파일을 검토했으며, 수정이 필요한 문제 2개를 발견했습니다.

심각도 개수
Critical 0
High 0
Medium 2
Low 0

주요 내용

  • MEDIUM · ERROR_HANDLING — 결과 저장 실패에도 결과 라우트로 이동해 시뮬레이션 결과가 사라집니다
  • MEDIUM · DATA_INTEGRITY — JSON 구문만 유효하면 잘못된 저장 데이터도 결과 객체로 신뢰합니다

검토한 head: 6fbb605c3970

// 결과 화면(신규 라우트)이 이어받을 수 있게 program·result 스냅샷을 남긴다 —
// ExperienceView 는 라우트를 넘어가면 새로 마운트돼 이 mutation 상태를 잃는다.
writeSimulationResult({ program, result });
router.push('/experience/simulation');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 MEDIUM · ERROR_HANDLING

결과 저장 실패에도 결과 라우트로 이동해 시뮬레이션 결과가 사라집니다

writeSimulationResult()는 sessionStorage 접근 거부나 용량 초과를 내부에서 삼키지만, 호출자는 성공 여부를 알 수 없고 항상 router.push()를 실행합니다. 이동한 결과 화면은 저장된 값이 없어 즉시 /experience로 되돌아가므로, 완료된 시뮬레이션 결과와 실패 이유가 사용자에게 전혀 표시되지 않습니다.

저장 함수가 성공 여부를 반환하게 하고 성공한 경우에만 이동하거나, 저장에 실패하면 현재 화면에서 결과를 표시하고 오류 안내를 제공해야 합니다.

if (!store) return null;
try {
const raw = store.getItem(KEY);
return raw ? (JSON.parse(raw) as StoredSimulationResult) : null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 MEDIUM · DATA_INTEGRITY

JSON 구문만 유효하면 잘못된 저장 데이터도 결과 객체로 신뢰합니다

JSON.parse 결과를 구조 검증 없이 StoredSimulationResult로 단언합니다. JSON 자체는 유효하지만 program, stack, result 또는 숫자 필드가 빠진 값이면 읽기는 성공하고, 이후 program.stack, result.passed, totalDistanceM.toFixed() 접근 중 결과 페이지가 예외로 중단됩니다. 저장 데이터의 형태와 필수 필드를 검증하고 불일치 시 null로 처리해야 합니다.

Simulation "시뮬레이션 실행"이 페이지 필요:YES 로 바뀌어, 워크스페이스 안
텍스트 힌트 대신 전용 결과 화면(Figma Slide 6 실패·Slide 7 성공)을 새로
분리했다.

- SimulationResult.failedAtIndex 추가: repeat 를 실제 실행 횟수만큼 펼친
  순서 기준으로 안전 구역을 벗어난 지점(1-based) — evaluateProgram 이 계산
- views/experience/model: unrollExecutionSteps — 같은 펼치기 규칙을 클라이
  언트에서 재사용해 "실행순서" 단계 목록을 만든다
- SimulationResultView: 결과 카드(성공/실패) + 실행순서(완료/문제 발생/
  대기) + 실행 블록(BlockStack 재사용) + 로봇 미리보기(배지·진행 바)
- simulationResultStorage: ExperienceView ↔ 결과 화면 간 program·result
  스냅샷을 sessionStorage 로 전달 (라우트 이동 시 리액트 state 소실 보완)
- ExperienceView: 시뮬레이션 성공/실패 모두 결과 화면으로 이동, 결과
  화면에서 돌아왔을 때(같은 program 이면) 통과 상태를 이어받아
  RunReadyWorkspace 를 그대로 보여준다
- '블록 고치기'·'로봇 실행하기' 버튼은 모두 /experience 로 돌아간다 —
  실제 실행 요청은 기존 RunReadyWorkspace 흐름 그대로 유지

테스트 인프라: next/navigation useRouter 를 테스트 전역에서 mock (기존
"invariant expected app router to be mounted" 회피) — shared/testing/routerMock.ts

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@wook10
wook10 force-pushed the feature/36-simulation-result branch from 6fbb605 to 9169605 Compare September 16, 2026 14:11
inryeok-bot MEDIUM 2건:

1. ERROR_HANDLING — writeSimulationResult() 가 sessionStorage 저장 실패
   (용량 초과·접근 거부 등)를 내부에서 삼키는데도 호출자는 항상 결과
   화면으로 이동했다. 그 화면은 읽을 게 없어 곧장 /experience 로 돌아가
   방금 끝난 시뮬레이션 결과가 안내 없이 사라졌다.
   → writeSimulationResult 가 성공 여부를 반환하고, 실패하면 이동하지
     않는다(이 화면의 기존 인라인 안내로 계속 보여준다).

2. DATA_INTEGRITY — readSimulationResult() 가 JSON.parse 결과를 구조
   검증 없이 StoredSimulationResult 로 단언했다. 필드가 빠지거나 타입이
   어긋난 저장값도 그대로 신뢰해, 결과 화면이 program.stack 등에 접근
   하다 예외로 죽을 수 있었다.
   → program 은 기존 isBlockProgramSnapshot 재사용, result 는 새
     isSimulationResultShape 로 필드·타입을 확인하고, 하나라도 어긋나면
     null 로 처리한다(localDraft 복원과 같은 방어 패턴).

테스트: simulationResultStorage.test.ts 신규(왕복·저장 실패·손상값 4종),
ExperienceView.test.tsx 에 저장 실패 시 이동하지 않고 인라인으로 계속
보여주는 통합 테스트 추가.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@wook10
wook10 merged commit b3b00bc into feature/34-block-catalog Sep 16, 2026
@wook10
wook10 deleted the feature/36-simulation-result branch September 16, 2026 14:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant