The app had no error boundary, so any throw inside the dashboard unmounted the
whole tree and left a blank white page with no way to read the cause on a phone.
Add a route-scoped boundary that shows the failure details, and fix the chat
shell to fit small viewports: dynamic viewport height, shrinkable input row,
safe-area padding, and wrapping message bubbles.
Also pause the particle canvas while the tab is hidden and cap its particle
count so phones are not driving a needless animation loop.