Skip to content

refactor(client): split Avatar.ts and drop write-only chat-bubble fields#7

Merged
danjdewhurst merged 3 commits into
mainfrom
avatar-refactor
Jun 13, 2026
Merged

refactor(client): split Avatar.ts and drop write-only chat-bubble fields#7
danjdewhurst merged 3 commits into
mainfrom
avatar-refactor

Conversation

@danjdewhurst

@danjdewhurst danjdewhurst commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Two behavior-preserving cleanups of the 1557-line Avatar.ts (review findings 8.1, 8.7, 5.3):

  1. Extract body rendering (8.1): the ~700 lines of stateless pixel-art body rendering (drawAvatarBody + draw helpers, AVATAR_* palette, color helpers, body types) move to a focused avatarBody.ts. Avatar.ts keeps movement, the chat-bubble lifecycle, and composition; it imports the shared palette/color helpers and re-exports drawAvatarBody + types so importers (AvatarPreview, preview harness) are unchanged.
  2. Drop write-only chat-bubble aliases (8.7 / 5.3): the public chatBubble* fields were write-only mirrors of chatBubbles.at(-1), kept in sync only for tests — and kept non-null via a throwaway ChatBubbleView allocation in the constructor and syncLatestChatBubbleReferences. Removed the fields, the method, and both allocations; tests read chatBubbles.at(-1) instead.

Avatar.ts: 1557 → 812 lines · new avatarBody.ts: 750 lines.

Verified behavior-preserving

  • Pixel-for-pixel identical avatar rendering after each change: ImageMagick compare -metric AE = 0 differing pixels across all 28 hair-style variants (body + chat bubble).
  • bun run typecheck ✅ · bun run lint ✅ · bun test (client) ✅ 80 pass.

Scoped out

The review flagged the hair-style switch as "duplicated" between drawHair (body) and drawChatBubbleAvatar (bubble portrait). They share the style enum but are two distinct renderers (full body vs. a tiny face portrait) with entirely different draw calls — no mechanical de-duplication, so left as-is.

Stacked on #6 (needs the single-context preview to verify rendering). Re-targets to main once #6 merges.

🤖 Generated with Claude Code

danjdewhurst and others added 3 commits June 13, 2026 22:25
The avatar preview created one Pixi Application — and therefore one WebGL
context — per grid cell (28 cells: 14 body + 14 bubble). Browsers cap live
WebGL contexts at ~16, so the excess were context-lost: their shaders failed to
compile (with an empty info log) and Pixi's error logger then threw on the null
program log, blanking the page behind a runtime-error overlay.

Use a single shared offscreen renderer and snapshot each variant into its own
static 2D canvas via renderer.extract.canvas. One context total, so the harness
now scales to any number of cells.

Not a product bug: the real app uses a single Pixi context per room and renders
full-body avatars correctly. This only affected the preview tool.

Verified in Chromium (Playwright): all 28 avatar variants render with zero
shader errors and no error overlay.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Split the 1557-line Avatar.ts (review finding 8.1). The ~700 lines of stateless
pixel-art body rendering (drawAvatarBody + its draw helpers, the AVATAR_* palette
constants, the color helpers, and the body draw types) move to a focused
avatarBody.ts. Avatar.ts keeps movement, the chat-bubble lifecycle, and
composition (1557 -> 835 lines; avatarBody.ts is 750).

Pure code move — no logic changes. Avatar.ts imports the shared palette/color
helpers it needs and re-exports drawAvatarBody and the body types so existing
importers (AvatarPreview, the preview harness) are unchanged.

Verified behavior-preserving: the avatar preview renders pixel-for-pixel
identically before and after (ImageMagick AE diff = 0 across all 28 hair-style
variants), plus typecheck, lint, and the client test suite pass.

Note: the review also flagged the hair-style switch as "duplicated" between the
body (drawHair) and the chat-bubble portrait (drawChatBubbleAvatar). They share
the style enum but are two distinct renderers (full body vs. tiny face portrait)
with entirely different draw calls, so there is no mechanical de-duplication;
left as-is.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the public chatBubble/chatBubbleBackground/chatBubbleAvatar/chatBubbleText
fields (review finding 8.7). They were write-only aliases to the latest entry of
the chatBubbles array, maintained only so tests could read "the current bubble" —
never read by the avatar itself. Keeping them in sync also required a throwaway
ChatBubbleView allocation in the constructor and in syncLatestChatBubbleReferences
whenever the queue drained (finding 5.3).

Tests now read the latest bubble via chatBubbles.at(-1), so the fields, the
syncLatestChatBubbleReferences method, and both throwaway allocations are gone.

Verified behavior-preserving: avatar preview renders pixel-for-pixel identically
(ImageMagick AE = 0 across all 28 variants); typecheck, lint, and the client
tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@danjdewhurst danjdewhurst changed the title refactor(client): extract avatar body rendering into avatarBody.ts refactor(client): split Avatar.ts and drop write-only chat-bubble fields Jun 13, 2026
Base automatically changed from fix-avatar-preview to main June 13, 2026 22:03
@danjdewhurst danjdewhurst merged commit 13adee4 into main Jun 13, 2026
1 check passed
@danjdewhurst danjdewhurst deleted the avatar-refactor branch June 13, 2026 22:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant