mirror of
https://github.com/block/buzz.git
synced 2026-10-29 13:31:44 +00:00
Widgets rendered initials for every person who actually had an avatar.
Canvas frames run with `connect-src 'none'` and an `img-src` that
excludes remote origins, so the host inlines avatars as `data:` URLs in
the RPC payload — but it forwarded them at full resolution and then
rejected anything over 48 KiB. A 512px gravatar, the size the app itself
requests, is ~230 KB and ~307,000 characters once base64-encoded: 6.3x
over the cap. `inlineAvatarDataUrl` fetched it, encoded it, discarded it,
and cached the miss for the rest of the session, so `people.lookup`
always answered `avatarDataUrl: null`.
Re-encode instead of rejecting. `avatarDownscale` center-crops the
decoded image to a square, redraws it at no more than 96px (canvas
avatars render at 20-42px, so this stays crisp at 2x), and encodes WebP
with a JPEG fallback for webviews that silently return a PNG from
`toDataURL("image/webp")`. The same gravatar lands at ~4 KB. Decode and
encode sit behind an injectable seam because neither exists under Node.
Also bound the combined size, a second and independent failure: avatars
were capped individually at 48 KiB, but a whole response shares the
64 KiB port-message ceiling, so two large avatars in one `people.lookup`
trip `postSized` and the frame receives a `too-large` error instead of
the result — losing every display name too, not just the pictures.
`selectAvatarsWithinBudget` keeps avatars in order until 40 KiB is spent
and degrades the rest to initials; the lookup path and the
channel-snapshot path in `ProjectChannelHome` both go through it.
`fetchAvatarBlob` splits out of `fetchAvatarDataUrl` so the canvas path
can re-encode raw bytes while self-profile storage keeps caching the
verbatim data URL it puts in localStorage. Both still share one fetch
policy and the same 256 KB ceiling.
Tests pin the downscaler on target size (drawing at full size fails it),
center-crop geometry, the no-upscale rule, the WebP-to-JPEG fallback,
degradation to null rather than a broken image, and bitmap release on
both the success and failure paths; the budget is pinned on ordering,
index alignment, and staying under PROJECT_CANVAS_MAX_PORT_MESSAGE_BYTES
for a full 32-person lookup.
Verified: desktop unit suite passes 5914, and biome, tsc, and the
pubkey-truncation guard are clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
Buzz
Desktop chat shell with:
- Tauri + React + TypeScript + Vite
- Tailwind CSS
- shadcn/ui-ready shared components
- Biome (lint/format/check)
- Feature-driven frontend structure
Scripts
pnpm dev- run the web frontendpnpm tauri dev- run the desktop apppnpm build- typecheck and build frontendpnpm typecheck- TypeScript checkspnpm lint- Biome lintpnpm format- Biome format (write)pnpm check- Biome check
Structure
src/shared- reusable app-wide code (ui,lib,styles)src/features- feature modules (vertical slices)src/app- top-level app composition