Polishing a WebGPU Chat UI: Progress Bars, Byte Formatting, and Controlled Inputs
Building a polished UI for on-device AI means sweating details like byte formatting, defensive prop handling, and controlled form inputs. These patterns are the difference between a brittle demo and an interface that stays predictable as state complexity grows.
The third installment of a series building a browser-based AI chat interface replaces a crude text-only progress display with a real, width-animated progress bar driven by percentage props. A `formatBytes` function converts raw byte counts into human-readable strings like "63.74 MB" using logarithmic math, while the nullish coalescing assignment operator (`??=`) guards against undefined props.
The chat input is implemented as a React controlled component, where a state variable is the single source of truth for the textarea's value. TypeScript type assertions (`as HTMLTextAreaElement`) resolve the generic event target, and keyboard event handling maps Enter to message submission while preserving Ctrl+Enter for newlines. The submit handler resets the UI state machine to a loading condition, ready for the model inference that will be wired up in the next part of the series.
The `??=` operator is underused in many React codebases; its narrow focus on null/undefined makes it a safer default than `||` for numeric props where zero is a valid value.
Teaching `formatBytes` through logarithms rather than a loop or chain of conditionals is a good example of using math to keep utility functions compact and branch-free.
The insistence on controlled components over two-way binding is framed as a debugging advantage: data flow is always explicit and traceable, which matters more as forms grow complex.
Type assertions in event handlers are a recurring friction point for React+TypeScript newcomers; the explanation that `EventTarget` is deliberately generic and must be narrowed is a practical bridge concept.