feat: 응답 대기 화면 단계별 안내와 취소 (S2 1단계) - #7
Draft
jk030430-coder wants to merge 8 commits into
Draft
jk030430-coder wants to merge 8 commits into
jk030430-coder wants to merge 8 commits into
Conversation
- 기록에서 해설을 다시 요청하면 현재 설정의 프리셋이 들어가 힌트가 어긋났다
- 기다린 시간에 따라 문구·아이콘이 바뀐다 (읽는 중 → 떠올리는 중 → 거의 다 됨 → 오래 걸림) - 정답 대기 중 캡처 미리보기, 해설 대기 중 스켈레톤을 보여준다 - 15초가 넘으면 안내와 취소 버튼을 연다 (패널·토스트) - 요청 소유권(SolveRequestGate)으로 취소·기록 전환 뒤 도착한 늦은 응답을 버린다 - 정답 확정 전 취소는 무료 횟수를 차감하지 않는다. 확정 후 취소는 해설에만 적용된다 - URLError.cancelled를 네트워크 오류가 아닌 취소로 분류한다 - 해설 요청 연타를 무시하고, 오래된 토스트 타이머가 새 화면을 지우지 않게 한다
- 정답 → 해설 2단계 요청을 앱과 같은 프롬프트로 재현해 구간별 시간을 CSV로 남긴다 - 같은 연결에서 GET으로 먼저 핸드셰이크하는 예열 실험을 함께 잰다 - 토큰은 헤더 파일로 넘겨 프로세스 목록과 결과물에 남지 않게 한다 - 측정용 문제 이미지는 직접 만든 것이라 저작권 문제가 없다
- 요청 소유권을 캡처 전부터 잡아, 영역 선택 중 기록으로 바꾸면 이전 풀이가 끼어들지 않게 한다 - 실행 전에 취소된 해설 작업이 새 화면의 상태를 바꾸지 않게 진입 시 소유권을 확인한다 - 대기 도중 동작 줄이기를 켜면 스켈레톤 반복 애니메이션을 즉시 멈춘다 - 측정 스크립트에서 405는 예열 GET만 정상으로 보고 POST에서는 실패로 집계한다
- 정답 · 해설 대기 모두 요청 시작 3초 후부터 취소할 수 있다 (영역 선택 중에는 기존 Esc) - 취소 자리를 처음부터 잡아 두어 버튼이 생겨도 화면이 튀지 않고, 3초 전에는 클릭 · 키보드 · VoiceOver로 실행되지 않는다 - 토스트 스피너를 기다린 시간으로 채우는 원형 진행 표시로 바꾼다 (실제 진행률을 몰라 90%에서 멈추는 추정치) - 설정에서 토스트 대기 방식을 고른다: 단계 안내(기본) / 간단히. 간단히도 15초부터 오래 걸린다고 알리고 3초 후 × 취소를 쓴다 - 대기 방식은 토스트를 띄울 때 고정하고, 새 캡처를 시작하면 이전 정답 토스트를 닫아 넓은 대기 화면이 잘리지 않게 한다 - 곧 끝난다고 약속하는 "거의 다 됐어요"를 "답을 정리하고 있어요"로 바꾼다
- 취소 · 오류 · 새 캡처 · 기록 열기로 토스트를 닫을 때 페이드하면, 그 0.3초 사이 내용이 빈 자리("—")로 바뀌고 창도 그 크기로 줄어 작은 말풍선이 번쩍였다
- 정답을 보여준 뒤 자동으로 사라질 때만 페이드를 유지한다
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
작업 내용
S2 "응답 대기 UX 개선 + 해설 속도 최적화"의 1단계입니다. (담당 김재겸 · 9/21 비대면 회의 #2, 9/29 피드백 반영)
답을 기다리는 화면이 스피너 한 줄뿐이라 멈춘 건지 알 수 없었고, 오래 걸려도 멈출 방법이 없었습니다. 이 PR은 대기 화면을 단계별 안내로 바꾸고 취소를 추가합니다. 해설 속도 개선의 본 작업(해설 스트리밍)은 앱·프록시 계약을 함께 바꿔야 해서 2단계로 나누고, 이번에는 측정 도구만 넣었습니다.
scripts/measure-latency.sh추가한 것
1. 기다린 시간에 따라 바뀌는 대기 화면
기존 vs 개선
정답 대기 — 0–3초 / 3초~(취소) / 8–15초 / 15초~(오래 걸림 안내)
해설 대기 — 정답은 먼저, 해설 자리는 스켈레톤, 3초 후 취소
WaitingStage.answerStage(elapsed:)/explanationStage(elapsed:)/allowsCancel(elapsed:)순수 함수로 두고 테스트했습니다.2. 토스트: 원형 진행 표시와 대기 방식 선택
0.9 × (1 − e^(−t/5))로 처음엔 빠르게, 점점 느리게 채우고 90%에서 멈춥니다(3초 ≈ 41%, 8초 ≈ 72%, 15초 ≈ 86%). 답이 오면 바로 정답으로 바뀌고, 100%를 채우는 연출로 정답을 늦추지 않습니다.WaitingProgress.estimatedFraction(elapsed:)3. 취소와 무료 횟수 경계⚠️ 사용량 정책
usage.recordUse()의 위치와 조건은 바꾸지 않았습니다. 확정 직전에 요청 소유권 검사만 추가했습니다.SolveRequestGate가 요청마다 ID를 발급하고, 모든await뒤에서 "아직 유효한 요청인지" 확인합니다. 취소 · 기록 전환 · 새 요청 뒤에 도착한 응답은 화면 · 기록 · 사용량을 바꾸지 못합니다.URLError.cancelled를GeminiError.network로 감싸지 않고CancellationError로 분류해, 취소가 "네트워크 오류" 화면으로 보이지 않게 했습니다.4. API 지연 시간 측정 스크립트
scripts/measure-latency.sh: 앱과 같은 프롬프트로 정답 → 해설을 호출해 DNS · TCP · TLS · 첫 바이트 · 총 시간을 CSV로 남기고 p50/p90을 냅니다. 앱에 로깅을 넣지 않기로 한 규칙 때문에 프록시를 직접 호출합니다.PAXO_PROXY_URL,PAXO_TOKEN환경 변수를 쓰고, 토큰은 헤더 파일로 넘겨 프로세스 목록과 결과물에 남지 않습니다. 결과 CSV(latency-*.csv)는.gitignore에 추가했습니다.scripts/fixtures/sample-problem.jpg)는 직접 만든 것입니다.5. 문서
docs/decisions.md: 4건을 "제안" 상태로 추가 (대기 화면 방식과 3초 취소, 속도 개선 1·2단계 분할, 취소 시 과금 경계, 토스트 원형 진행 · 대기 방식 선택). 확정되면 상태를 바꿉니다.docs/architecture.md: 풀이 흐름에 취소 경로(3초부터) 한 줄.README.md: 토스트 대기 방식을 고를 수 있다는 한 줄.고친 것 (기존 버그)
버그 1. 풀이 중 기록을 열면 그 기록이 새 답으로 덮어써짐
재현: ⌥⌘S로 캡처 → 답을 기다리는 동안 메뉴바에서 이전 기록 항목 클릭 → 새 답이 도착하면 방금 연 기록의 정답이 새 문제의 답으로 바뀌고, 그대로 저장됨.
원인:
showFromHistory가current를 기록 항목으로 바꾸는데, 진행 중이던runSolve는 응답이 오면 그때의current에 답을 쓰고upsertHistory()로 저장했습니다.sequenceDiagram participant U as 사용자 participant A as AppState participant P as 프록시 U->>A: ⌥⌘S 캡처 (새 문제 N) A->>P: 정답 요청 (N) U->>A: 메뉴에서 기록 H 클릭 A->>A: current = H P-->>A: N의 정답 도착 A->>A: current(H).answer = N의 정답 ❌ A->>A: upsertHistory() → H가 틀린 답으로 저장 ❌수정: 기록을 열 때 진행 중인 요청을 먼저 취소하고(정답 확정 전이면 미차감), 응답은 자기 요청 ID가 유효할 때만 반영합니다.
sequenceDiagram participant U as 사용자 participant A as AppState participant P as 프록시 U->>A: ⌥⌘S 캡처 (새 문제 N, 요청 #1) A->>P: 정답 요청 (N) U->>A: 메뉴에서 기록 H 클릭 A->>A: 요청 #1 취소 → current = H P-->>A: N의 정답 도착 A->>A: 요청 #1은 무효 → 버림 ✅ (H 그대로, 차감 없음)버그 2. 기록에서 해설을 다시 요청하면 과목 힌트가 어긋남
재현: 수학 프리셋으로 문제를 풀고 → 설정에서 프리셋을 "자격시험"으로 변경 → 메뉴에서 그 수학 기록을 열어 "해설 보기" → 해설 프롬프트에 "자격시험 문제입니다…" 힌트가 들어감.
원인과 수정: 해설 요청이 기록의 프리셋이 아니라 현재 설정값을 썼습니다. (별도 커밋
c383281)문제 3. "해설 보기" 연속 클릭 시 중복 요청 가능
첫 클릭에 버튼이 스켈레톤으로 바뀌어 실제로는 드물지만, 막는 장치가 없어 같은 프레임 안의 연속 클릭은 해설 API를 두 번 호출할 수 있었습니다. 이미 진행 중인 요청이 있으면 무시하도록 했습니다.
변경 및 영향 범위
AGENTS.md,docs/) —docs/decisions.md제안 3건,docs/architecture.md사전 식별 리스크
결과 및 검증
기대 효과
실제 반영 결과
546ac35): 취소 · 오류로 토스트를 닫을 때 0.3초 페이드 사이에 내용이 빈 자리("—")로 바뀌고 창이 58×48로 줄어드는 현상. 정답 없이 닫힐 때는 즉시 닫습니다.api.paxo.co.kr) 토큰이 필요합니다.검증
WaitingStageTests(단계 경계 · 3초 취소 경계 · 완료를 약속하는 문구 없음),WaitingProgressTests(0 ≤ 값 ≤ 0.9 · 줄지 않음 · 주요 시점 값),CodableRoundTripTests에 토스트 대기 방식 rawValue와 기본값 복구,SolveRequestGateTests(새 요청이 이전 요청 무효화 · 취소 후 응답 거부 · 늦은 종료가 새 요청을 지우지 않음),GeminiServiceTests에 취소 분류 2건AppState가 싱글턴이라 단위 테스트 대신 실제 앱으로 확인했습니다. 응답 없는 로컬 서버로 대기 상태를 재현하고, 토스트 창 크기를 0.05초 간격으로 기록하면서 화면을 찍었습니다.리스크·결정·리뷰
트러블슈팅 및 회고
tccutil reset ScreenCapture com.hyeseong.Paxo후 재허용이 필요합니다.메모
57528cf,2967ba3): 취소 3초부터 · 토스트 원형 진행 표시 · 대기 방식 선택. 계획은 Codex 리뷰(조건부 승인)를 거쳐 캡처 중 취소 금지, 숨긴 버튼 입력 차단, 토스트 크기 정책을 보강했습니다.ToastController가 최소 폭 120으로 위치를 잡은 뒤 SwiftUI가 창을 내용 크기로 줄이기 때문입니다. 크기 결정을 컨트롤러로 모으면 해결되며, 별도 PR로 제안합니다.POST /generate-stream신설(기존/generate유지), SSE 중계, 앱 연결 종료 시 upstream abort, 스트리밍 중 텍스트와 완료된 해설 분리./proxy-change절차로 별도 PR 예정.