-
FE Agent 작업 완료 리포트 (Claude Code: Opus 4.5, Cycle Mode)이코에코(Eco²)/Agent 2026. 1. 23. 10:00


Chat Agent SSE FE-BE 안정화 완료, 이코에코 캐릭터를 주입받은 나노 바나나 Pro가 생성한 이미지들 

Cycle Mode 예시(1.Location, 2.Web-agent): plan 생성 -> 스텝 진행(구현, 테스트, skills 코드 리뷰 루프) -> 완료까지 Cycle Mode로 운용 -> 개발자 검증 Claude Code Cycle Mode, shift + tap https://code.claude.com/docs/en/common-workflows
Common workflows - Claude Code Docs
Step-by-step guides for exploring codebases, fixing bugs, refactoring, testing, and other everyday tasks with Claude Code.
code.claude.com
Tap 1: FE Agent, Chat Agent SSE 안정화
Tap 2: BE 주요 Chat Agents 기능 안정화(완료) -> 도메인별 에러 핸들링 보강
Tap 3: Tool callling-Web Search Agent 보강
Tap 4: 분산 트레이싱(OTEL, Jaeger) 점검 및 보강
Tap 5: Location 도메인 고도화, BE-FE 병행
Tap 6: 포트폴리오 업데이트
FE 에이전트 작업 요약
항목 수량 총 PR 29개 Feature/Fix PR 19개 Deploy PR 10개 전체 머지 완료 O 수정 파일 수 20+
1. FE Agent 채팅 데이터 무결성
#92 refactor(agent): IndexedDB v3 스키마 계층화 및 데이터 무결성 개선
배경: IndexedDB 스키마가 백엔드 계층 구조와 불일치하여 세션 관리, 메시지 격리에 문제 발생
변경 내용:
chat_id→session_id명명 변경 (Backendchat_conversations.id와 매칭)- User isolation을 위한
by-user-session-created복합 인덱스 추가 - v1 → v2 → v3 자동 마이그레이션 로직 구현
- SSE keepalive 이벤트 타임아웃 리셋 처리
- Session cleanup: 채팅 전환 시 SSE 연결 정리
- Image upload 400 에러 수정 (
image_url빈 문자열 →undefined) - Race condition 방지:
isSendingRefflag
수정 파일:
src/db/messageDB.ts,src/db/schema.ts,src/hooks/agent/useAgentChat.ts,src/hooks/agent/useAgentSSE.ts,src/api/services/agent/agent.type.ts,src/components/agent/AgentContainer.tsx,AgentInputBar.tsx
#93 feat(agent): Skills v2 - 데이터 무결성 & Vercel Best Practices 통합
변경 내용:
docs/reports/→.claude/skills/재구성 (6개 카테고리)- 각 skill:
SKILL.md(개요/의사결정 트리) +references/(상세 문서) - Vercel React Best Practices 통합 (40+ 규칙, 8개 카테고리)
수정 파일:
.claude/skills/하위 13개 파일
#94 [Release] Agent Feature v2 (develop → main)
2. Agent UI/UX 개선
#95 fix: Agent 이미지 인식 및 중복 메시지 버그 수정
원인 1 (이미지 인식 실패):
AgentInputBar에서 이미지 업로드 후 stale closure로selectedImage가onSend에 전달되지 않음해결: 컴포넌트에서 업로드 후 CDN URL을 직접 전달
원인 2 (중복 메시지):
handleSSEComplete에서updateMessageStatus호출 시server_id전달 누락 → reconcile 중복 제거 실패해결: server_id 설정으로 정상 dedup
// Before updateMessageStatus(msg, 'committed') // After updateMessageStatus(msg, 'committed', userServerId)수정 파일:
src/components/agent/AgentInputBar.tsx,src/hooks/agent/useAgentChat.ts
#96 feat(toast): 채팅 삭제 시 로딩/완료 토스트 추가
변경 내용:
toast.loading(message)API + AnimatedDots (. → .. → ... 순환)수정 파일:
src/components/Toast/ToastContainer.tsx,toast.ts,AgentContainer.tsx
#97 feat(sidebar): 스와이프하여 삭제 기능 구현
변경 내용: 터치 이벤트 기반 좌측 스와이프 제스처 (임계값 60px, 수직 스크롤 충돌 방지)
수정 파일:
src/components/agent/sidebar/AgentSidebarItem.tsx
#102 style: 모델 선택 버튼 위치 수정
변경 내용: 갤러리 버튼
items-center, 모델 버튼self-end+ml-6수정 파일:
src/components/agent/AgentInputBar.tsx
#105 feat(agent): 이미지만 전송 시 기본 분류 프롬프트 추가
코드베이스 (
useAgentChat.ts:453-457):// 이미지만 전송 시 기본 프롬프트 추가 const effectiveMessage = !message.trim() && finalImageUrl ? '이 이미지 분류해줘' : message;수정 파일:
src/hooks/agent/useAgentChat.ts
3. SSE 스트리밍 안정화 (iOS Safari / PWA)
#98 fix: Safari SSE 안정성 및 iOS 이미지 선택 버그 수정
원인 (SSE): Safari 백그라운드 전환 시 EventSource 연결 끊어짐 + 복구 로직 부재
코드베이스 (
useAgentSSE.ts:401-428— visibility change 핸들러):useEffect(() => { const handleVisibilityChange = () => { if (document.visibilityState === 'visible') { if (currentJobIdRef.current && eventSourceRef.current && !isManualDisconnectRef.current) { const readyState = eventSourceRef.current.readyState; // EventSource가 CLOSED면 자동 재연결 실패한 것 → 새로 생성 if (readyState === EventSource.CLOSED) { createEventSourceFn(currentJobIdRef.current); } } } }; document.addEventListener('visibilitychange', handleVisibilityChange); return () => document.removeEventListener('visibilitychange', handleVisibilityChange); }, [createEventSourceFn]);원인 (iOS 이미지): 키보드 활성화 상태에서 파일 선택기 열릴 때 viewport 깨짐
해결: 키보드를 먼저 닫고 100ms 후 파일 선택 열기
수정 파일:
src/hooks/agent/useAgentSSE.ts,src/components/agent/AgentInputBar.tsx
#104 fix(pwa): SSE 무한 대기 및 사이드바 비율 수정
원인:
EventSourceAPI가 iOS PWA의 Service Worker를 통과할 때 이벤트 스트리밍 차단해결:
fetch()+ReadableStream으로 교체 (후에 #107에서 EventSource로 복원)수정 파일:
src/hooks/agent/useAgentSSE.ts,src/components/agent/AgentContainer.tsx
#107 fix(sse): EventSource 복원 + polling fallback 추가
원인: PR #104의 fetch+ReadableStream이 iOS Safari/크롬에서 cross-origin 스트리밍 미지원
해결: EventSource 복원 +
onStale콜백으로 polling fallback 트리거코드베이스 (
useAgentSSE.ts:64-70— onStale 인터페이스):interface UseAgentSSEOptions { onToken?: (token: string) => void; onProgress?: (stage: CurrentStage) => void; onComplete?: (result: DoneEvent['result']) => void; onError?: (error: Error) => void; /** SSE 연결은 되었지만 meaningful 이벤트를 받지 못할 때 콜백 */ onStale?: (jobId: string) => void; }수정 파일:
src/hooks/agent/useAgentSSE.ts,src/hooks/agent/useAgentChat.ts
#109 fix(reconcile): SSE 실패 후 메시지 중복 버그 수정
원인: SSE 실패 후 복귀 시 로컬
failed메시지와 서버 메시지의 ID 불일치로 중복 표시코드베이스 (
message.ts:130-168— content 기반 매칭):// 서버 메시지 content 기반 매칭 (role + content → timestamp) const CONTENT_MATCH_WINDOW_MS = 120000; // 2분 이내 같은 내용이면 동일 메시지로 판단 const serverContentIndex = new Map<string, number>(); serverMessages.forEach((m) => { const key = `${m.role}:${m.content}`; const ts = new Date(m.created_at).getTime(); const existing = serverContentIndex.get(key); if (!existing || ts > existing) { serverContentIndex.set(key, ts); } }); // 로컬 메시지: content 매칭으로 중복 확인 if (!local.server_id) { const contentKey = `${local.role}:${local.content}`; const serverTs = serverContentIndex.get(contentKey); if (serverTs !== undefined) { const localTs = new Date(local.created_at).getTime(); if (Math.abs(localTs - serverTs) < CONTENT_MATCH_WINDOW_MS) { return false; // 서버에 동일 메시지 존재 → 로컬 드롭 } } }수정 파일:
src/utils/message.ts
#110 feat(sse): 네이티브 Last-Event-ID 기반 복구
변경 내용: 수동
?last_seq=X→ SSE 표준Last-Event-ID헤더 (브라우저 자동 재연결 활용)코드베이스 (
useAgentSSE.ts:82-94— 상수 정의):// 네이티브 EventSource 자동 재연결 시 연속 에러 최대 허용 횟수 const MAX_CONSECUTIVE_ERRORS = 5; // 타임아웃 설정 const DEFAULT_EVENT_TIMEOUT = 60000; // 60초 const IMAGE_GENERATION_TIMEOUT = 180000; // 3분 // 연결 상태 확인 주기 const HEALTH_CHECK_INTERVAL = 10000; // 10초 // Stale 감지: meaningful 이벤트 없이 이 시간이 지나면 onStale 호출 const STALE_THRESHOLD = 3000; // 3초수정 파일:
src/hooks/agent/useAgentSSE.ts
#112 fix(streaming): PWA 토큰 스트리밍 화면 깜빡임 수정
원인: 3개의 스크롤 effect 충돌 + smooth/instant 경합 + overflow-anchor 충돌 + Streamdown components 매번 재생성
해결:
이전 현재 useEffect 3개 통합 1개 (RAF 기반) scrollToBottom('smooth'/'instant')scrollTop = scrollHeight직접 설정streamingText직접 렌더useDeferredValue(streamingText)배칭components = {...}매번 생성useMemo(() => ({...}), [])수정 파일:
src/components/agent/AgentMarkdownRenderer.tsx,AgentMessageList.tsx
#116 fix(sse,layout): SSE 스트리밍 복구 전략 개선 및 채팅 스크롤 고정
원인 (SSE 1/3 확률 실패):
- Gateway Pub/Sub 구독 완료 전 Event Router가 publish → 이벤트 유실
- State KV 없으면 catch-up 불가
- 기존 25s stale + keepalive reset → stale 미발동
코드베이스 (
useAgentChat.ts:279-358— 3단계 복구 전략):const handleSSEStale = useCallback( (jobId: string) => { const chatId = pendingChatIdRef.current; if (!chatId) return; // 최대 3회 SSE 재연결 시도 (gateway State catch-up 기대) const MAX_SSE_RECONNECTS = 3; if (sseReconnectAttemptRef.current < MAX_SSE_RECONNECTS) { sseReconnectAttemptRef.current += 1; console.log(`[SSE] Stale - reconnecting (attempt ${sseReconnectAttemptRef.current}/${MAX_SSE_RECONNECTS})`); connectSSERef.current?.(jobId); return; } // 재연결 모두 실패: polling fallback (3초 간격, getChatDetail) pollingIntervalRef.current = setInterval(async () => { const response = await AgentService.getChatDetail(chatId, { limit: 5 }); const lastMsg = response.messages[response.messages.length - 1]; if (lastMsg && lastMsg.role === 'assistant') { stopPolling(); stopGenerationRef.current?.(); // handleSSEComplete과 동일한 처리... } }, 3000); // 최대 120초 후 폴링 중단 setTimeout(() => { if (pollingIntervalRef.current) stopPolling(); }, 120000); }, [messages.length, userId, stopPolling], );Stale 감지 (
useAgentSSE.ts:207-214):// Stale 감지 타이머 (meaningful 이벤트 없으면 polling fallback 트리거) if (!receivedMeaningfulEventRef.current) { staleTimeoutRef.current = setTimeout(() => { if (!receivedMeaningfulEventRef.current && !isManualDisconnectRef.current) { onStaleRef.current?.(jobId); } }, STALE_THRESHOLD); // 3초 }SSE ref 패턴 (circular dependency 방지,
useAgentChat.ts:361-382):// handleSSEStale가 connectSSE를 호출해야 하지만, connectSSE는 useAgentSSE에서 반환 // → ref로 우회 const stopGenerationRef = useRef<(() => void) | null>(null); const connectSSERef = useRef<((jobId: string) => void) | null>(null); const { connect: connectSSE, disconnect: stopGeneration } = useAgentSSE({ onComplete: handleSSEComplete, onError: handleSSEError, onStale: handleSSEStale, }); useEffect(() => { stopGenerationRef.current = stopGeneration; connectSSERef.current = connectSSE; }, [stopGeneration, connectSSE]);스크롤 고정 (
AppLayout.tsx:12-16, 44-45):// 자체 스크롤을 관리하는 페이지 (외부 스크롤 비활성화) const selfScrollPaths = ['/agent', '/chat']; const isSelfScroll = selfScrollPaths.some((path) => pathname.startsWith(path)); // 콘텐츠 영역 <div className={`absolute right-0 left-0 ${isSelfScroll ? 'overflow-hidden' : 'overflow-y-auto'}`}>수정 파일:
src/hooks/agent/useAgentSSE.ts,useAgentChat.ts,src/pages/App/AppLayout.tsx
4. 메시지 정합성 (Reconcile / 세션 관리)
#103 fix: 세션 전환 시 메시지 이동 및 이미지 소실 버그 수정
원인: SSE 스트리밍 중 채팅 전환 시
handleSSEComplete가 현재 세션에 저장코드베이스 (
useAgentChat.ts:104-106, 167-171):// 메시지 전송 시점의 chatId 추적 (세션 전환 시 올바른 채팅에 저장) const pendingChatIdRef = useRef<string | null>(null); // SSE 완료 시 세션 비교 const originalChatId = pendingChatIdRef.current; const currentChatId = currentChatRef.current?.id; const isSameSession = originalChatId === currentChatId; // isSameSession이 false면 UI 업데이트 스킵 (IndexedDB에는 원래 chatId로 저장)수정 파일:
src/hooks/agent/useAgentChat.ts,src/utils/message.ts
#114 fix(chat): 세션 간 메시지 오염, 순서 역전, 이미지 소실 수정
수정 사항 (5건):
- Cross-session 오염:
setMessages([])로 채팅 전환 시 즉시 클리어 - User/Assistant 순서 역전: assistant
created_at을 user +1ms로 보정 - 페이지네이션 드롭: append + dedup 전략
- 이미지 소실:
localImageMap기반 복원
코드베이스 (
message.ts:108-125— localImageMap):// 로컬 image_url 보존 맵 (서버가 image_url을 반환하지 않을 때 대비) const localImageMap = new Map<string, string>(); localMessages.forEach((m) => { if (m.image_url) { if (m.server_id) localImageMap.set(m.server_id, m.image_url); localImageMap.set(m.client_id, m.image_url); } }); // 서버 메시지 변환 (image_url 없으면 로컬에서 복원) const serverConverted = serverMessages.map((sm) => { const converted = serverToClientMessage(sm); if (!converted.image_url) { const localImage = localImageMap.get(sm.id); if (localImage) converted.image_url = localImage; } return converted; });- Reconcile
committed+server_id무조건 유지 로직 제거: 오염 메시지 영구 보존 원인
수정 파일:
src/hooks/agent/useAgentChat.ts,src/utils/message.ts
5. iOS PWA 스크롤 / 키보드
#99 fix: 스트리밍 중 스크롤 튕김 현상 수정
해결: RAF 기반 스크롤 +
instantbehavior +force파라미터 +isAtBottom체크수정 파일:
src/components/agent/AgentMessageList.tsx,src/hooks/agent/useScrollToBottom.ts
#100 fix: iOS 키보드 닫힘 타이밍 및 스크롤 튕김 수정
해결:
visualViewportAPI로 키보드 감지, 열림 상태면 350ms 대기 후 파일 선택수정 파일:
src/components/agent/AgentInputBar.tsx
#101 fix: Agent UI/UX 개선 및 Safari 안정성 강화
변경 내용: PR #99 + #100 통합 + 모델 선택 버튼 위치 조정
6. 이미지 관련
#118 fix(image): iOS PWA 이미지 다운로드 후 레이아웃 깨짐 수정
원인:
<a download>가 iOS PWA에서 네비게이션 → 풀사이즈 이미지 + 히스토리 오염코드베이스 (
AgentImage.tsx:10-13— iOS 판별):const isIOS = () => /iPad|iPhone|iPod/.test(navigator.userAgent) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);코드베이스 (
AgentImage.tsx:114-149— blob 다운로드 핸들러):const handleDownload = useCallback( async (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); if (!src) return; try { if (isIOS() && navigator.share) { // iOS: Web Share API 사용 (사진 앱 저장 가능) const res = await fetch(src); const blob = await res.blob(); const file = new File([blob], alt || 'image.png', { type: blob.type }); await navigator.share({ files: [file] }); } else { // 기타: Blob 다운로드 const res = await fetch(src); const blob = await res.blob(); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = alt || 'image'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } } catch { window.open(src, '_blank'); } closeModal(); }, [src, alt, closeModal], );코드베이스 (
AgentImage.tsx:34-54— 히스토리 정리):const closeModal = useCallback(() => { if (isClosingRef.current) return; isClosingRef.current = true; setIsExpanded(false); setShowDownload(false); // pushState로 추가한 히스토리 엔트리 제거 if (window.history.state?.imageModal) { window.history.back(); } setTimeout(() => { isClosingRef.current = false; }, 0); }, []);수정 파일:
src/components/agent/AgentImage.tsx
7. Scan (카메라 분류)
#119 fix(scan): step 전진을 completed 이벤트 기준으로 변경
원인:
started/completed구분 없이 step 전진 → 실제 완료 전에 체크마크 표시백엔드 Scan Worker 이벤트 스키마:
seq = STAGE_ORDER[stage] * 10 + (1 if status == "completed" else 0) vision:started (seq=10) → UI: spinner vision:completed (seq=11) → UI: ✓ rule:started (seq=20) → UI: spinner rule:completed (seq=21) → UI: ✓ answer:started (seq=30) → UI: spinner answer:completed (seq=31) → UI: ✓ done:completed (seq=51) → UI: Answer 페이지 이동코드베이스 (
useScanSSE.ts:140-158— completed 기준 핸들러):const handleEvent = (data: ScanSSEEvent) => { console.log(`[Scan SSE] ${data.stage}:${data.status} seq=${data.seq}`); // completed 이벤트만 step 전진 if (data.status === 'completed') { const step = STAGE_TO_STEP[data.stage] ?? 0; setCurrentStep((prev) => Math.max(prev, step)); } // done 완료 → 결과 조회 if (data.stage === 'done' && data.status === 'completed') { disconnect(); ScanService.getScanResult(jobId).then((scanResult) => { setIsComplete(true); setResult(scanResult); options?.onComplete?.(scanResult); }); } };코드베이스 (
scan.type.ts:29-40— 업데이트된 타입):export type ScanSSEEvent = { job_id: string; stage: ScanSSEStage; status: 'started' | 'completed' | 'failed'; seq: number; ts: string; progress?: number; result?: Record<string, unknown>; trace_id?: string; span_id?: string; traceparent?: string; };수정 파일:
src/hooks/useScanSSE.ts,src/api/services/scan/scan.type.ts
8. Deploy (develop → main)
PR 제목 포함 내용 #94 [Release] Agent Feature v2 #92, #93 #106 release: develop → main 배포 #104, #105 #108 deploy: SSE EventSource 복원 + polling fallback #107 #111 deploy: Last-Event-ID + 메시지 중복 수정 #109, #110 #113 deploy: PWA 스트리밍 안정화 #112 #115 Release: 메시지 정합성 및 이미지 소실 수정 #114 #117 deploy: SSE 스트리밍 안정화 및 채팅 스크롤 고정 #116 #120 deploy: 스캔 step 완료 기준 변경 + iOS 이미지 #118, #119
아키텍처 다이어그램
SSE 스트리밍 흐름 (Agent Chat)
┌─────────┐ ┌──────────────┐ ┌──────────────┐ ┌───────────┐ ┌────────┐ │ Worker │───▶│ Redis Streams │───▶│ Event Router │───▶│ State KV │ │ Client │ │ │ │ (XADD) │ │ (XREADGROUP) │ │ + Pub/Sub │ │ │ └─────────┘ └──────────────┘ └──────────────┘ └─────┬─────┘ └───┬────┘ │ │ ▼ │ ┌───────────┐ │ │SSE Gateway│───────┘ │(subscribe)│ EventSource └───────────┘복구 전략:
T+0s SSE 연결 + Pub/Sub 구독 T+3s Stale 감지 → 재연결 1 (State 존재 시 catch-up 성공) T+6s Stale 감지 → 재연결 2 T+9s Stale 감지 → 재연결 3 T+12s 포기 → Polling fallback (getChatDetail 3초 간격, 최대 120초)메시지 Reconcile 흐름
┌──────────────┐ ┌──────────────┐ │ Local State │ │ Server State │ │ (messages[]) │ │ (GET /chat) │ └──────┬───────┘ └──────┬───────┘ │ │ ▼ ▼ ┌────────────────────────────────────┐ │ reconcileMessages() │ │ │ │ 1. localImageMap 생성 (image 보존) │ │ 2. serverConverted 변환 + 이미지복원│ │ 3. serverIdMap 중복 체크 │ │ 4. content 매칭 (2분 윈도우) │ │ 5. pending/streaming 유지 │ │ 6. committed retention (30초) │ │ 7. 병합 + 시간순 정렬 │ └────────────────────────────────────┘Scan Worker 이벤트 흐름
┌─────────────┐ ┌──────────────┐ ┌──────────────┐ ┌────────┐ │ Scan Worker │───▶│ Redis Streams│───▶│ Event Router │───▶│ Client │ │ │ │ (Lua script) │ │ │ │ │ └─────────────┘ └──────────────┘ └──────────────┘ └────────┘ 이벤트: queued → vision → rule → answer → reward → done Status: started ────────────────────────────────▶ completed UI Step: spinner ────────────────────────────────▶ ✓ 체크마크
정합성 체크 결과
발견된 불일치 (수정 완료)
위치 문제 수정 useAgentChat.ts:150주석 "1회 재연결 후 polling" 코드는 MAX_SSE_RECONNECTS=3→ 주석을 "3회"로 수정검증 완료 항목
항목 PR 설명 코드베이스 일치 STALE_THRESHOLD 3초 3000(L94)✅ MAX_SSE_RECONNECTS 3회 3(L285)✅ MAX_CONSECUTIVE_ERRORS 5회 5(L83)✅ DEFAULT_EVENT_TIMEOUT 60초 60000(L86)✅ IMAGE_GENERATION_TIMEOUT 3분 180000(L87)✅ HEALTH_CHECK_INTERVAL 10초 10000(L90)✅ CONTENT_MATCH_WINDOW_MS 2분 120000(L132)✅ committedRetentionMs 30초 30000(L106)✅ Polling interval (agent) 3초 3000(L348)✅ Polling max duration 120초 120000(L352)✅ Polling interval (scan) 2초 2000(L20)✅ MAX_POLLING_ATTEMPTS (scan) 60회 60(L21)✅ LONG_PRESS_DURATION 500ms 500(L20)✅ image_url 빈 문자열 방지 || undefinedL468 finalImageUrl || undefined✅ 이미지 기본 프롬프트 "이 이미지 분류해줘" L456 ✅ selfScrollPaths /agent,/chatAppLayout L13 ✅ ScanSSEEvent.seq number 타입 scan.type.ts L33 ✅ Scan step completed만 전진 data.status === 'completed'useScanSSE L145 ✅ iOS 감지 userAgent + maxTouchPoints AgentImage L11-13 ✅ 히스토리 정리 imageModalstate checkAgentImage L47 ✅ popstate 재진입 방지 isClosingRefAgentImage L31,35-36 ✅ PR 파일 vs 코드베이스 교차 검증
PR 설명 파일 실제 변경 일치 #116 useAgentSSE, useAgentChat, AppLayout 3개 파일 모두 변경 확인 ✅ #118 AgentImage.tsx <button>+ handleDownload + isClosingRef 확인✅ #119 useScanSSE, scan.type.ts handleEvent + ScanSSEEvent 타입 확인 ✅ #114 useAgentChat, message.ts pendingChatIdRef + localImageMap 확인 ✅ #109 message.ts serverContentIndex + CONTENT_MATCH_WINDOW_MS 확인 ✅
SSE 스트리밍 변천사 (시간순)
PR 방식 문제점 결과 #92 EventSource + last_seq Safari 백그라운드 끊김 초기 구현 #98 + visibility change + health check iOS 키보드 viewport 안정성 개선 #104 fetch + ReadableStream iOS PWA에서 SW 차단 cross-origin 미지원 #107 EventSource 복원 + onStale 25s stale → 폴링 CORS 정상 #110 Last-Event-ID 네이티브 수동 재연결 제거 브라우저 자동 복구 #112 useDeferredValue + RAF 스크롤 깜빡임 렌더링 최적화 #116 3s stale × 3회 재연결 Pub/Sub 타이밍 race 최종 안정화
수정 파일 전체 목록
파일 관련 PR 변경 내용 src/hooks/agent/useAgentSSE.ts#92,#98,#104,#107,#110,#116 SSE 스트리밍 아키텍처 (EventSource, stale, health check) src/hooks/agent/useAgentChat.ts#92,#95,#103,#105,#107,#114,#116 세션 관리, 재연결, polling fallback, 이미지 프롬프트 src/utils/message.ts#103,#109,#112,#114 reconcile, content 매칭, localImageMap, sort 최적화 src/components/agent/AgentInputBar.tsx#95,#98,#100,#101,#102 이미지 업로드, 키보드 처리, 버튼 위치 src/components/agent/AgentImage.tsx#92,#118 blob 다운로드, 히스토리 정리, iOS Share API src/components/agent/AgentMessageList.tsx#99,#112 RAF 스크롤, useDeferredValue src/components/agent/AgentMarkdownRenderer.tsx#112 useMemo components src/components/agent/AgentContainer.tsx#92,#96,#104 사이드바 absolute, 토스트 연동 src/components/agent/sidebar/AgentSidebarItem.tsx#97 스와이프 삭제 src/hooks/agent/useScrollToBottom.ts#99 RAF + instant + force src/hooks/useScanSSE.ts#119 completed 기준 step, handleEvent 통합 src/api/services/scan/scan.type.ts#119 ScanSSEEvent (seq, ts, traceparent) src/pages/App/AppLayout.tsx#92,#116 selfScrollPaths overflow-hidden src/db/messageDB.ts#92 IndexedDB v3, by-user-session-created src/db/schema.ts#92 v3 스키마 정의 src/components/Toast/ToastContainer.tsx#96 AnimatedDots src/components/Toast/toast.ts#96 toast.loading API .claude/skills/**#93 Skills v2.0 (6개 카테고리) '이코에코(Eco²) > Agent' 카테고리의 다른 글