renderer 는 SSE 에서 받은 조각을 UI 에 보여주는 경계야. token 마다 바로 append 해 UI 를 계속 다시 그리거나 취소를 무시하면 버벅여. 좋은 renderer 는 frame 시간 안에서 모아 반영하고, cancel() 을 제공하며, 완료될 때 남은 text 를 모두 flush 해.
16ms frame 간격으로 모아 그려
초당 50 token 이 올 때마다 React 를 다시 render 하면 reconciliation 부담이 커져. 새 token 을 accumulator 에 모으고 16ms requestAnimationFrame tick 마다 한 번씩 반영해. 사용자는 차이를 알아채지 못하지만 UI 는 훨씬 부드러워져. cwkPippa frontend 도 이 방식을 써.
취소는 stream 까지 전달돼야 해
사용자가 Stop 을 누르면 renderer 는 SSE stream 을 닫아 server 생성도 멈추고, 지금까지 모은 text 를 UI 에 반영한 뒤 partial output 을 보존해야 해. 취소 신호가 consumer 까지 이어지지 않으면 화면만 숨겨지고 server 는 계속 token 을 만들 수 있어.
cwkPippa 의 renderer
메시지마다 accumulator 를 두고 16ms requestAnimationFrame loop 에서 flush 해. abort signal 은 SSE consumer 까지 전달되고, 중간에 멈춰도 partial text 는 JSONL 에 남겨. React 나 Streamlit 앱에도 적용할 수 있는 패턴이야.