In Living Stage, widgets appear on stage while the model is still talking. Waiting for a complete response would feel dead in front of an audience, so each widget starts rendering from a partial, still-invalid stream of structured output. The hard part isn't the streaming. It's that half a JSON object is not a JSON object, and the UI has to do something reasonable with it anyway.
Living Stage에서는 모델이 아직 말하는 중인데 위젯이 무대에 나타나요. 관객 앞에서 완전한 응답을 기다리면 화면이 죽은 것처럼 보이거든요. 그래서 위젯 하나하나가 아직 유효하지 않은, 반쯤 온 structured output 스트림으로 그리기 시작해요. 어려운 건 스트리밍 자체가 아니에요. 반쪽짜리 JSON 객체는 JSON 객체가 아닌데, UI는 그걸로도 그럴듯한 뭔가를 해내야 한다는 거예요.
A streamed object arrives a piece at a time: an opening brace, a key, half a string, a number that hasn't finished. At any instant what you're holding is a fragment. Render it the normal way and it breaks in small, specific ways: a field that isn't there yet reads as undefined, an array of points that hasn't arrived isn't iterable, a label that's still empty throws when you try to draw it. None of these are bugs in the model. They're just what a value looks like before it's done.
스트리밍되는 객체는 조각조각 와요. 여는 중괄호, 키 하나, 반쯤 온 문자열, 아직 안 끝난 숫자. 어느 순간이든 손에 든 건 조각이에요. 평소처럼 그리면 작고 구체적인 방식으로 깨져요. 아직 안 온 필드는 undefined로 읽히고, 아직 안 온 좌표 배열은 iterable이 아니고, 아직 비어 있는 라벨은 그리려고 하면 에러를 던져요. 이건 모델의 버그가 아니에요. 값이 완성되기 전의 모습일 뿐이에요.
So the renderer assumes nothing. A partial-JSON parser turns whatever has arrived into the best object it can at each step, and then every widget guards its own fields. A flow node with no label yet renders blank instead of throwing. A plot whose points haven't arrived draws an empty axis instead of crashing on a non-iterable. A compare row with no key falls back to its position in the list. Each guard is small; the discipline is applying it everywhere, because on a partial stream every field is sometimes missing.
그래서 렌더러는 아무것도 가정하지 않아요. partial-JSON 파서가 지금까지 온 걸 매 순간 최선의 객체로 만들어주고, 그다음 위젯마다 자기 필드를 지켜요. 라벨이 아직 안 온 flow 노드는 에러를 던지는 대신 빈 칸으로 그리고, 좌표가 아직 안 온 plot은 non-iterable에 깨지는 대신 빈 축을 그리고, 키가 없는 compare 행은 목록에서의 위치로 대신해요. 가드 하나하나는 작아요. 어려운 건 그걸 전부에 적용하는 거예요. 스트림이 반쯤일 땐 어떤 필드든 없을 때가 있으니까요.
The one that mattered most for how it feels: the widget doesn't remount on every token. If you rebuild a widget each time a new piece arrives, a still-growing chart tears down and pops back a few times a second, which on stage reads as flicker. So a formula that's still streaming compiles tolerantly and updates in place, and a widget paints a skeleton first and fills into it. The goal was never correctness on a fragment; it's that the audience sees something grow smoothly instead of stutter.
느낌을 좌우한 건 이거예요. 위젯이 토큰마다 다시 마운트되지 않아요. 새 조각이 올 때마다 위젯을 다시 만들면, 아직 자라는 중인 차트가 초당 몇 번씩 사라졌다 다시 튀어나와요. 무대에서는 그게 깜빡임으로 보여요. 그래서 아직 스트리밍 중인 수식은 관대하게 컴파일해서 제자리에서 갱신되고, 위젯은 먼저 스켈레톤을 그린 다음 그 안을 채워요. 목표는 조각 상태에서 정확한 게 아니었어요. 관객이 버벅임 대신 부드럽게 자라는 걸 보는 거였어요.
This is a five-hour hackathon build (Google I/O, hosted by Cerebral Valley and Google DeepMind, solo, no award), so it's a technique proven on a stage, not a hardened library. Tolerant rendering has a cost: the guards live in every widget, and "reasonable on a fragment" is a judgment call I made per widget, not a general rule. What it buys is the thing the whole demo depends on, a UI that starts responding the instant the model does, instead of waiting for it to finish.
이건 5시간짜리 해커톤 결과물이에요(구글 I/O, Cerebral Valley와 Google DeepMind 주최, 솔로, 수상 없음). 그래서 무대에서 통한 기법이지, 단단하게 다듬은 라이브러리는 아니에요. 관대한 렌더링에는 비용이 있어요. 가드가 위젯마다 들어가 있고, '조각 상태에서 그럴듯하게'는 제가 위젯마다 내린 판단이지 일반 규칙이 아니에요. 그 대신 얻는 건 데모 전체가 기대는 바로 그거예요. 모델이 끝나기를 기다리는 대신, 모델이 말하기 시작하는 순간 같이 반응하기 시작하는 UI요.
Living Stage was built solo at a Google I/O hackathon (React, TypeScript, Gemini, Web Speech API, mathjs, Cloudflare Workers). The repo is public: github.com/Poiurity/Predator. Screenshots and the stack are on the project card.
Living Stage는 구글 I/O 해커톤에서 혼자 만들었어요(React, TypeScript, Gemini, Web Speech API, mathjs, Cloudflare Workers). 저장소는 공개예요: github.com/Poiurity/Predator. 스크린샷과 스택은 프로젝트 카드에 있어요.