Skip to content

[1.94] 대화 화면 통합 — 첫 전송 Note 무선생성·이력·진행 Activity·draft·정지 버튼 - #313

Merged
chowon442 merged 4 commits into
devfrom
worktree-task-1.94
Oct 20, 2026
Merged

chowon442 merged 4 commits into
devfrom
worktree-task-1.94

Conversation

@chowon442

@chowon442 chowon442 commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

📌 관련 이슈

🏷️ PR 타입

  • ✨ 기능 추가 (Feature)
  • 🐛 버그 수정 (Bug Fix)
  • ♻️ 리팩토링 (Refactoring)
  • 🗑️ 코드 제거 (Chore)
  • 📝 문서 수정 (Documentation)
  • 🎨 스타일 변경 (Style)
  • ✅ 테스트 추가 (Test)

📝 작업 내용

홈 첫 질문부터 Note 화면의 이력·진행 Activity·완료 응답·draft·정지 버튼까지 대화 화면을 1.93 run 세션·1.91 renderer·1.92 Activity 박스에 연결했다. 구형 v2 코드는 지우지 않았다(1.95). 화면 경로는 새 run 세션만 쓴다.

첫 질문 (useHomeSend)

  • Note를 먼저 만들지 않는다. noteId 없이 POST /api/conversations 한 번으로 보낸다.
  • run.created의 noteId로 /app/chat/{noteId}로 이동한다.
  • 세션은 RunSessionRegistryProvider(AppLayout, 라우트 위)에 맡긴다. Note 화면이 같은 세션을 이어 받아 stream이 끊기지 않는다.
  • POST /api/notes·제목 생성·/api/credits/use를 호출하지 않는다.
  • 접수 전 거부(요청 한도·잔액 부족 등)면 이동하지 않고 안내를 보이며 입력을 남긴다.

Note 화면 (useNoteConversation + ConversationPanel)

  • 진입 시 대화 조회(limit=30, before로 "이전 대화 더 보기")로 이력을 그리고, 그 페이지로 session.restore(page)를 부른다.
    • activeRun이 있으면 seq 1부터 이어 받는다(1.93 replay 결정 그대로).
  • 이력 메시지와 이 화면의 live run을 runId로 합쳐 같은 목록에 그린다(buildConversationTurns).
    • user: text·latex
    • assistant: 완료 응답·실패·중단·진행 placeholder
    • 접수 전(run.created 전) 전송은 pending 자리로 보인다.
  • live run 표시:
    • ActivityBox는 run 시작부터(Activity 0개여도) mount한다(인계 a).
    • terminal 뒤에도 남아 진행 기록을 보인다.
    • 본문은 COMPLETED canonical 응답(AssistantResponseView)만 그린다. 검증 전 본문은 없다.
  • 이력 COMPLETED 메시지는 toCompletedAssistantResponse로 변환한다(인계 e).
  • 화면을 떠나면 dispose()만 부른다. 연결만 끊고 run은 취소하지 않는다.

입력창 (ChatInput·InputToolbar)

  • Front 직접 차감(useUseCredit)과 크레딧 모달 호출을 제거했다. 차감은 Server 접수가 맡는다.
  • Active Run(접수 대기 포함) 동안:
    • 입력은 draft만 쓴다. Enter·전송이 없다.
    • 전송 버튼 대신 정지 버튼(답변 중단)을 보이고, 안내 줄을 붙인다.
  • 정지 요청 중·CANCELING이면 비활성 "중단 중" 버튼을 보이고 전송을 막는다.
  • terminal 뒤에는 사용자가 직접 누를 때만 보낸다. 자동 재전송은 어디에도 없다.
  • Server가 받지 않은 질문(접수 거부·409·Note 없음·계약 밖 오류)은 clearDraft() 전에 입력창으로 되돌린다.
    • 입력창에 써 둔 draft가 있으면 덮어쓰지 않고 되돌린 질문을 앞에 붙인다("질문 다시 쓰기"도 같다).
  • run 접수(run.created)·terminal 뒤 잔액(creditKeys.all)·프로필·노트 상세·노트 목록을 invalidate한다.

안내 구분 (conversation_notice.ts)

  • 요청 한도 RUN4291: Retry-After 남은 초 카운트다운. 그동안 전송 버튼이 비활성이다(안내를 닫아도 유지, 초는 aria-hidden·시작/끝 문구만 live region).
  • 잔액 부족 RUN4021: "요금제 보기" 링크
  • 미지원 요청:
    • RUN4002: 접수 거부 안내
    • unsupported_only: 1.91의 응답 안내
  • 그 밖의 거부: RUN4221·RUN4092·RUN4001
  • recoverable 실패: Server 문구 + "질문 다시 쓰기". 입력창에 넣기만 하고 보내지 않는다.
  • 409: 진행 중 run에 붙고 draft를 돌려준다. 최신 페이지를 다시 읽어 다른 탭의 질문을 함께 보인다.
  • not_found(RUN4041): Note 삭제 안내
  • reconnect_exhausted·stream_rejected: "다시 연결" 버튼(session.reconnect())
    • run.created 전(홈·Note 모두)에 멈추면 접수됐을 수 있으므로 세션을 남기고 새 전송을 막는다. "다시 연결"이 같은 requestId로 다시 보내 중복 Note·run·차감을 막는다.
  • backoff 중: "다시 연결하는 중"
  • unauthorized: 세션이 /error/401로 보내므로 안내하지 않는다.
  • 용어:
    • run을 멈춘 결과는 "중단됨", 진행 중은 "중단 중"이다.
    • Activity 하나의 취소는 1.92 그대로 "취소됨"이다(인계 c).
    • 화면 문구는 conversation_notice.ts에 모았다.

그 밖

  • DEV MSW(src/mocks/handlers.ts): 1.93 createBrowserRunFixtureServer() handler를 구형 editor mock보다 앞에 뒀다. 새 화면이 dev에서 v1 fixture를 쓴다. VITE_API_MODE 명시 전환은 1.95.
  • tsconfig.test.json에 src/**/*.d.ts를 포함했다.
    • 화면 통합 테스트가 처음으로 notes_api.ts(axios meta 모듈 확장 src/shared/api/axios.d.ts)를 import한다.
    • 이것이 없으면 typecheck:test가 실패한다.
  • 1.89~1.93 산출물(src/features/run/**)과 1.88 공용 타입은 바꾸지 않았다.

화면 문서 메모 — Activity header 요약 (인계 b)

  • header 요약과 live region 문구는 "가장 최근 사건"의 Activity다.
    • 열린 Activity는 startedSeq, 끝난 Activity는 updatedSeq 기준이다.
  • run이 CANCELING·CANCELED·FAILED면 run 상태("중단 중"·"중단됨"·"실패")가 요약이다.
  • 그래서 앞서 열린 Activity가 뒤 Activity 시작 이후에 끝나면 그 outcome이 요약에 나온다.

📸 스크린샷 (UI 변경 시 필수)

fixture 모드(DEV MSW + browser-run-v1 fixture handler, vite --port 5194)에서 실제 흐름으로 캡처했다.

  • 흐름: 홈 첫 전송 → 진행 → 완료 → 두 번째 질문 중 draft 작성 → 정지
  • dev 전용 TanStack Query Devtools 버튼은 숨겼다.
  • 이미지는 코드가 아닌 별도 ref pr-assets/task-1.94에 올렸다(merge 대상 아님, 필요 없으면 지워도 된다).

Before(구형 v2 경로: 노트 선생성 → 토큰 스트림·순환 진행 문구)는 이 PR에서 화면 경로가 바뀌어 같은 흐름으로 캡처할 수 없다.

데스크탑 1440×900

홈 입력 진행 중(Activity·정지 버튼) 완료(canonical 응답)
Active Run 중 draft 작성 정지 뒤(중단됨·draft 유지·자동 전송 없음)

태블릿 가로 1024×768

진행 중 완료 정지 뒤

태블릿 세로 768×1024

완료 Active Run 중 draft·정지 버튼
  • 요청 한도·잔액 부족·409·CANCELING 화면은 브라우저 fixture Server에 장애 주입 API가 없어 캡처하지 않았다. 아래 통합 테스트가 확인한다.
  • fixture Server는 페이지 메모리에 있어 새로고침하면 run이 사라진다. 그래서 새로고침 복원도 통합 테스트로 확인했다.

✅ 체크리스트

공통

  • 셀프 리뷰를 완료했습니다
  • 코드 리뷰를 받을 준비가 완료되었습니다
  • pnpm tsc --noEmit 타입 체크를 통과했습니다 (tsc -p tsconfig.app.json, pnpm typecheck:test, pnpm run build의 tsc -b)
  • 브랜치명 컨벤션을 준수했습니다 — 오케스트레이터가 지정한 worktree-task-1.94 사용

UI/레이아웃 변경 시

  • 태블릿(768px~) / 데스크탑(1280px~) / 와이드(1360px~) 반응형을 확인했습니다 — 768·1024·1440 캡처
  • PageContainer / ContentGrid 공통 컴포넌트를 활용했습니다 — 기존 ChatPage 레이아웃을 그대로 썼다

인증 관련 변경 시

  • 카카오 / 네이버 로그인 흐름에 영향이 없음을 확인했습니다 — 인증 코드 변경 없음. 401은 1.93 세션의 /error/401 흐름

🧪 검증

src/features/chat/run/conversation_screen.test.tsx (10건) 구성:

  • 실제 HomePage·ChatPage·ChatInput을 createMemoryRouter로 렌더한다.
  • run API는 1.93 createBrowserRunMockServer(msw/node)가 받는다.
  • 그 밖의 구형 API(노트·사용자·도구·크레딧)는 호출을 기록하는 handler가 받는다.
시나리오 확인
홈 첫 전송 → 진행 → 완료 POST 1회·GET events 0회(같은 stream), noteId 9001로 이동, 진행 중 본문 없음, 완료 뒤 응답. 구형 기록에 POST /api/notes·generate-title·/api/credits/use·구형 /api/conversations 없음. 두 번째 질문 run도 noteId 9001
StrictMode 재마운트 첫 마운트 정리로 끊긴 POST stream을 같은 run의 GET /events(Last-Event-ID {runId}:{seq})로 이어 받음, POST 1회, 질문 bubble 1개
홈 접수 거부(RUN4021) /app/home 유지, 안내·요금제 링크, 입력 유지, Note 생성 없음
새로고침 복원 이력(unsupported_only 안내) + 진행 중 run에 cursor 없이(seq 1부터) 붙어 Activity 복원, 정지 버튼, commit 뒤 완료, POST 0회
409 복원 다른 탭 run에 붙음(cursor 없음), 안내, 내 질문은 입력창에 남음, 다른 탭 질문 표시, 완료 뒤에도 POST 1회·run 1개
취소 Active Run 중 draft 작성·Enter 무시, 정지 → "중단 중" 비활성·Enter 무시, 확정 → "중단됨 · 답변 생성을 중단했습니다.", Activity 완료/취소됨 유지, draft 유지, POST 1회·cancel 1회, 직접 전송 시에만 POST 2회
요청 한도(RUN4291 Retry-After: 30) 안내 + 30/29초 카운트다운, 질문 입력창 복원, 전송 비활성(클릭해도 POST 1회)
미지원(RUN4002) vs 잔액 부족(RUN4021) 문구 구분
recoverable 실패(failed-verification) Server 문구 + "질문 다시 쓰기" → 입력창에만 넣고 POST 1회
unsupported_only 응답 미지원 안내

mutation probe

  • draft 반환(restoreDraft)을 지우면 409·요청 한도·미지원 3건이 실패했다(확인 뒤 되돌림).
  • ChatInput의 Active Run 전송 가드를 지운 probe는 통과했다. 세션이 busy로 한 번 더 막기 때문이고, 입력창 가드는 이중 방어다.

로컬 실행 결과

  • pnpm test(flock): 19 files, 828 passed / 2 skipped
  • pnpm typecheck:test, tsc -p tsconfig.app.json 통과
  • 변경 파일 eslint·prettier --check 통과
  • pnpm run build(flock) 통과(기존 chunk 크기 경고만)

CI 결과는 코멘트로 남긴다.

리뷰 1회차 반영 (27ce60f)

# 처리
1 홈 접수 전 재접속 소진 반영 — 세션 유지·"다시 연결"(같은 requestId)·새 전송 차단. 테스트 추가
2 입력창 active 고착 반영 — 접수 대기는 연결 중일 때만, 그 밖의 정지는 draft 반환
3 Retry-After 차단 반영 — 차단 시각을 안내와 분리. 테스트에 "닫아도 차단" 추가
4 draft 덮어쓰기 반영 — 앞에 붙임. 테스트 추가
5 invalidate 누락 반영 — run.created·terminal. 테스트(노트 상세 재조회)
6 멘션·도구 말없이 버림 반영 — 첨부와 같은 안내·전송 차단
7 countdown 매초 낭독 반영
8 여러 수식 latex 반영 — 단위 테스트
9 pending→run remount 반영 — key를 requestId로 통일. 단위 테스트
10 이력 재조회가 stream 끊음 반영 — 세션 수명 effect 분리. 테스트 추가
11 접수 전 홈 이탈 반영 — 연결 유지 후 접수되면 registry에 맡기고 목록 invalidate

mutation probe: #10(이력 effect에 dispose 복귀)·#3(차단을 안내에서 읽기)을 되돌리면 각 테스트가 실패했다(확인 뒤 복원).
로컬: pnpm test 20 files 834 passed / 2 skipped, typecheck:test, 변경 파일 eslint·prettier, pnpm run build 통과.

리뷰 2회차 반영 (6fe3446)

항목 처리
차단 1 홈 이탈 뒤 접수 시 강제 이동·이중 handOff 반영 — unmount 시 홈의 접수 대기 구독을 끊고, 이탈 뒤 접수는 background에서 handOff(detached) 1회·노트 목록 invalidate만 한다(이동 없음). 통합 테스트와 probe
권고 A detached 세션 미해제 반영 — terminal·stopped에 dispose·삭제, 같은 noteId 재handOff 시 이전 세션 dispose
권고 B 복원 run turn remount 반영 — 보낸 run만 requestId key, 복원·409 run은 runId key 유지(이력 메시지에 requestId가 없다)
권고 C 줄바꿈 접힘 반영 — <br>로 구분
1회차 #2·#6·#7·#11 테스트 추가 — Note stall·unexpected draft 반환·#파일 멘션 차단·countdown live region·홈 이탈

CI run 39383532779: test 21 files 840 passed / 2 skipped, typecheck:test·build·lint 통과.

📎 기타 참고사항

1.95 인계 — 화면 경로는 새 run 세션만 쓴다

홈·Note 화면이 import하는 대화 경로는 다음뿐이다.

  • features/chat/run/*
  • features/run/{api,model,session,components}
  • features/chat/components/rightpanel/Conversation*.tsx

아래 구형 코드는 화면에서 참조하지 않지만 남겨 두었다.

남은 구형 코드 비고
src/features/chat/hooks/useChatMessages.ts (+ hooks/index.ts export) v2 토큰 누적·노드명 분기·createConversation·parseSSEStream·generateNoteTitle·업로드
src/features/chat/components/rightpanel/{RightPanel,ChatMessages,ThinkingBar}.tsx (+ components/index.ts·rightpanel/index.ts export) 순환 진행 문구 FINAL_LOADING_MESSAGES·ThinkingBar
src/features/editor/api/editor_api.ts createConversation·createConversationJson·getConversationDetail 구형 conversation 호출. searchConversations는 검색(useConversationSearch)이 쓰므로 남긴다. uploadCanvasImage는 useEditorQueries가 쓴다
src/features/editor/utils/parse_sse_stream.ts, src/features/editor/types/sse_types.ts v2 SSE parser·타입
src/features/settings/hooks/useCredit.ts useUseCredit 직접 차감 hook. 화면 호출 0건(잔액 조회는 유지)
src/features/notes/hooks/useNotes.ts useCreateNote, notes_api.ts createNote·generateNoteTitle Note 선생성·제목 생성. 화면 호출 0건
src/pages/hooks/useHomeSend.ts FirstMessageState 타입 구형 useChatMessages만 쓴다
src/mocks/handlers/editor.ts 구형 POST /api/conversations SSE mock, notes.ts POST /api/notes fixture handler가 앞에 있어 가려진다
main.tsx DEV 자동 MSW VITE_API_MODE 명시 전환

알려진 한계

  • 업로드·뷰어(S09 범위):
    • 첨부·#파일 멘션·@도구 선택이 있는 질문은 보내지 않고 "아직 질문과 함께 보낼 수 없어요" 안내를 보인다(말없이 버려 질문 의미가 바뀌지 않게).
    • 홈 뷰어 파일은 노트에 올라가지 않으며, 보내면 안내 toast를 보인다.
  • @ 도구 선택은 browser-run-v1 requestedTasks와의 매핑이 정의되지 않아 위처럼 전송을 막는다. 매핑·#파일 멘션 연동은 S09·S11.
  • 수식 입력은 기존 추출 그대로다.
    • text에 $…$로 들어가고 latex 필드에도 실린다.
    • draft 복원 시 수식은 $…$ 텍스트로 돌아온다.
  • 노트 제목은 기존 노트 상세 조회로 읽는다. fixture 모드에서는 새 Note가 구형 노트 mock에 없어 "노트 9001"로 보인다.

🤖 Generated with Claude Code

chowon442 and others added 2 commits October 20, 2026 11:35
…aft·정지 버튼 (1.94)

- 홈 첫 질문: noteId 없이 POST /api/conversations 한 번, run.created noteId로 이동하며 세션을 registry로 넘겨 같은 stream을 이어 받음 (Note 선생성·제목 생성·직접 차감 없음)
- Note 화면: 대화 조회(before/limit) 이력 + live run을 runId로 합쳐 표시, activeRun 복원, 409 시 진행 중 run에 붙고 draft 반환
- ChatInput: 직접 차감 제거, Active Run 동안 draft·정지 버튼, CANCELING 중단 중, 거부 시 draft 복원·안내(요청 한도·잔액 부족·미지원·recoverable 실패)
- DEV MSW에 browser-run-v1 fixture handler를 구형 mock 앞에 둠
- fixture MSW 통합 테스트 10건

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chowon442

Copy link
Copy Markdown
Member Author

CI (Frontend CI run 39360934231, head 772165b): pass

  • changed-file eslint --max-warnings=0 통과
  • pnpm test: 19 files, 828 passed / 2 skipped. conversation_screen.test.tsx 10 tests 포함
  • pnpm typecheck:test 통과
  • pnpm run build 통과

…리, draft 덮어쓰기 방지, run 접수·종료 뒤 잔액·노트 invalidate, 이력 재조회와 세션 수명 분리

- #1 홈: run.created 전 reconnect_exhausted·stream_rejected면 세션을 남기고 '다시 연결'(같은 requestId)만 허용, 새 전송 차단
- #2 접수 대기 판정을 연결 중으로 좁히고 그 밖의 정지(unexpected·unauthorized)는 draft를 입력창에 돌려줌
- #3 요청 한도 차단 시각을 닫을 수 있는 안내와 분리(홈·Note)
- #4 draft 복원·질문 다시 쓰기가 입력창 내용을 덮어쓰지 않고 앞에 붙임
- #5 run.created·terminal 뒤 credit·profile·note detail·note list invalidate
- #6 #파일 멘션·@도구도 첨부처럼 안내 후 전송 막음
- #7 countdown 초는 aria-hidden, 시작·끝 문구만 live region
- #8 여러 수식 latex를 '; '로 나눠 text에 없는 것만 덧붙임
- #9 접수 전·후 turn key를 requestId로 통일(remount·스크롤 끌림 방지)
- #10 이력 다시 불러오기가 세션 dispose와 묶이지 않게 effect 분리
- #11 run.created 전 홈 이탈 시 연결 유지 후 접수되면 registry에 맡기고 노트 목록 invalidate

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chowon442

Copy link
Copy Markdown
Member Author

리뷰 1회차 반영 27ce60f — CI run 39362598524 success: changed-file eslint, pnpm test 20 files 834 passed / 2 skipped(conversation_screen 12·conversation_turns 4 포함), typecheck:test, build 통과. 권고 11건 모두 반영(본문 표 참고).

…, 되돌린 질문 줄바꿈 <br>, 복원 run turn key 유지, 무테스트 변경 보강

- 차단 1: 홈 unmount 시 접수 대기 구독을 먼저 끊어 이탈 뒤 접수는 background finish만 처리(이동 없음·handOff 1회·노트 목록 invalidate)
- 권고 A: detached handoff는 terminal·stopped에 dispose·삭제, 같은 noteId에 다른 세션을 맡기면 이전 세션 dispose
- 권고 B: 화면이 보낸 run만 requestId key, 복원·409 run은 runId key 유지
- 권고 C: 앞에 붙이는 질문은 <br>로 줄 구분
- 테스트: 홈 이탈 시나리오, Note 접수 전 stall, unexpected draft 반환, #파일 멘션 차단, countdown live region

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chowon442

Copy link
Copy Markdown
Member Author

리뷰 2회차 반영 6fe3446 — CI run 39383532779 success: changed-file eslint, pnpm test 21 files 840 passed / 2 skipped, typecheck:test, build 통과. 차단 1·권고 A·B·C 반영, 1회차 무테스트 변경(#2·#6·#7·#11) 테스트 추가.

@chowon442
chowon442 merged commit 0cf5529 into dev Oct 20, 2026
1 check passed
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.

[Feat] 대화 화면 통합 — 첫 전송 Note 생성·이력·draft·정지 버튼 [1.94]

1 participant