Skip to content

feat: avatar frame frontend and integration contracts - #214

Draft
16th-admin wants to merge 1 commit into
mainfrom
codex/avatar-frames
Draft

feat: avatar frame frontend and integration contracts#214
16th-admin wants to merge 1 commit into
mainfrom
codex/avatar-frames

Conversation

@16th-admin

Copy link
Copy Markdown
Collaborator

Summary

  • 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.

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

🤖 Codex-Style Code Review by OpenCode (Vue Best Practice Enabled)

💡 Autonomous AI Reviewer inspecting git commit history and Vue code quality.

PR Review: feat: add avatar frame frontend and integration contracts (#214)

Reviewed origin/main...HEAD (commit 445816d + merge ed44a3a): 21 files, +607/−27.

Verdict

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 onMountedwatch(..., { 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:12frameInventory 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's ExpectedRevision 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:20noMessagesPath 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.vue frameUser and user can diverge after login (low)

src/views/Home.vue:94/:178frameUser 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 no AvatarFrameID 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:11getPath(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.tsavatarFrames.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.

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