You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add an avatar-frame inventory page, equip/unequip controls and three CSS/SVG frames.
Share avatar rendering across home, comments, user cards, friends and notifications.
Add development-only local persistence and simulated bans: clear equipped frames while retaining ownership.
Isolate demo identity from cached real-account ban status.
Document typed inventory/equip contracts and add five-language UI strings.
Release status
Draft for maintainer integration. Production inventory entry and API operations remain opt-in via VITE_AVATAR_FRAMES=on; keep disabled pending backend implementation.
Server inventory, grants, authenticated ownership checks, atomic revision/ban handling, fresh public metadata, cross-device synchronization and moderation notifications remain required. Real-account switching/cache invalidation and complete avatar coverage also need release validation. No backend integration is claimed.
Validation
Chromium + WebKit: 8 local checks passed (4 scenarios per browser).
Type checking passed.
ESLint passed for the inventory page and regression test; pre-existing complexity warnings remain elsewhere.
Production build passed; existing bundle-size, WASM externalization and deprecated-option warnings remain.
git diff --check passed.
GitHub CI results are pending.
Local preview
Start npm run dev -- --host 127.0.0.1 --port 5175 --strictPort and open /#/avatar-frames?frameDemo=1.
Run npx playwright test --config scripts/tests/avatar-frames.local.config.ts --workers=2.
See AVATAR_FRAMES.md for integration details and release gates.
Approve with minor comments. This is a well-scoped, well-documented feature-gated PR. The demo-mode isolation, the mismatch/ban suppression logic, and the client-side Playwright coverage are notably thorough. The failure-closed visibleFrame and the "never trust client ban state" contract in AVATAR_FRAMES.md are good security posture.
What works well
UserAvatar.vue is a clean, small presentational component (Composition API + <script setup>), with proper a11y (aria-hidden, focusable="false"), a fallback guard, and no prop destructuring that would lose reactivity.
friends/item.vue correctly upgrades onMounted → watch(..., { immediate }) with onCleanup to drop stale icon lookups and a .catch that falls back to the neutral icon.
NotificationItem.vue suppresses the frame for system messages, missing status, and AvatarUser.ID !== Users[0] mismatches — matching the documented fail-closed contract.
Race protection via busy on equip/load, and optimistic reset of frameInventory before each loadFrames().
The Playwright spec is genuinely valuable (cached-banned-account isolation, live reactivity, ban/unban persistence, unowned/banned rejection) and integrates with the existing dev-server suite.
Issues
1. Module-level inventory state is never reset on logout / account switch (medium)
src/services/avatarFrames.ts:12 — frameInventory is a module-scope ref that is cleared only inside loadFrames(). The repo actively supports account switching and logout (userAuthInfo cleared in handle403, Emitter 'userLogin', the quick-account-switch branch). If a user switches accounts or logs out between page visits, the next equipFrame (avatarFrames.ts:70) sends the previous account'sExpectedRevision and loadFrames() never re-fetches because the inventory is already populated. Suggest resetting frameInventory.value = null on logout/login events (userAuthInfo removal, Emitter.on('userLogin')) — this is also listed as a release gate ("verify real-account switching") in AVATAR_FRAMES.md but is not handled client-side.
2. New API paths trigger the global loading overlay (low)
Interceptor.ts:20 — noMessagesPath doesn't include /AvatarFrames/GetInventory or /AvatarFrames/Equip, so beforeRequest shows a full showMessage('loading') overlay on every equip click. Consider adding both paths to noMessagesPath for a silent, fast equip interaction.
3. Home.vueframeUser and user can diverge after login (low)
src/views/Home.vue:94/:178 — frameUser is only updated in loadPageData (the null-null homepage login). The token-login path (processAuthInfo) and the userLogin emitter update user but not frameUser, so after an equip the header can briefly show identity/avatar from one source and the frame from a stale cached userInfo. Update both together (single source of truth).
4. Comment frame contract is invisible to the type system (low)
MessageItem.vue:4 passes the entire CommentResult as :user, but CommentResult has noAvatarFrameID field — it compiles only because all FramedUser fields are optional, and will silently never render a frame until the server enriches it (documented as "integration work", but nothing type-guards it). Add an optional AvatarFrameID?/FramedUser field to the payload interface so a missing server field is a compile-time signal rather than silent.
5. Nits
src/views/Profile.vue:46 — router-link is a single ~120-char line; prettier would wrap it (CI runs npm run format? It doesn't fail the build, but it diverges from repo formatting).
NotificationItem.vue:11 — getPath(avatarUrl) double-resolves since getUserUrl already calls getPath; harmless today but easy to misread.
Route /avatar-frames has no auth guard (router/index.ts:6). Consistent with the rest of the app (no guards exist), so acceptable — but the server must 403 and the page must fail closed (it does).
storage/index.ts ↔ avatarFrames.ts are mutually type-importing; erased at runtime, fine, just noting the dependency direction.
Not blocking
isFrameDemo() reads location.hash non-reactively; acceptable for a dev-only flag.
Demo equipFrame re-reads storage instead of mutating frameInventory.value — intentional for isolation; correct.
No blocking defects found. The changes respect Vue 3 conventions, the new API contracts are clearly documented, and the feature is safely gated behind VITE_AVATAR_FRAMES=on.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Release status
Draft for maintainer integration. Production inventory entry and API operations remain opt-in via VITE_AVATAR_FRAMES=on; keep disabled pending backend implementation.
Server inventory, grants, authenticated ownership checks, atomic revision/ban handling, fresh public metadata, cross-device synchronization and moderation notifications remain required. Real-account switching/cache invalidation and complete avatar coverage also need release validation. No backend integration is claimed.
Validation
Local preview
Start npm run dev -- --host 127.0.0.1 --port 5175 --strictPort and open /#/avatar-frames?frameDemo=1.
Run npx playwright test --config scripts/tests/avatar-frames.local.config.ts --workers=2.
See AVATAR_FRAMES.md for integration details and release gates.