diff --git a/docs/superpowers/plans/2026-05-20-yandex-vk-oauth.md b/docs/superpowers/plans/2026-05-20-yandex-vk-oauth.md
new file mode 100644
index 0000000..1cbcd6f
--- /dev/null
+++ b/docs/superpowers/plans/2026-05-20-yandex-vk-oauth.md
@@ -0,0 +1,886 @@
+# Yandex ID + VK ID OAuth — Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Add Yandex and VK OAuth login buttons to the client, enrich user profiles with firstName/lastName/gender/avatar from OAuth providers, and rename `name` → `displayName` across the project.
+
+**Architecture:** Server OAuth flow already exists (`oauth-social.js`). We update the User model (rename `name`→`displayName`, add 4 fields), enrich `findOrCreateUserFromOAuth()` to save profile data, then add an FSD feature `features/auth-oauth/` with Yandex/VK login buttons on the AuthPage.
+
+**Tech Stack:** Prisma (SQLite), Fastify, React + MUI + Effector + TypeScript
+
+---
+
+### Task 1: Update Prisma User model
+
+**Files:**
+- Modify: `server/prisma/schema.prisma:80`
+
+- [ ] **Step 1: Rename `name` → `displayName` and add new fields**
+
+```diff
+ model User {
+ id String @id @default(cuid())
+ email String @unique
+- name String?
++ displayName String?
++ firstName String?
++ lastName String?
++ gender String?
++ avatar String?
+ phone String?
+ passwordHash String?
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ codes AuthCode[]
+ addresses ShippingAddress[]
+ cartItems CartItem[]
+ orders Order[]
+ reviews Review[]
+ orderMessageReadStates UserOrderMessageReadState[]
+ oauthAccounts OAuthAccount[]
+ notificationPreference NotificationPreference?
+ notificationLogs NotificationLog[]
+ }
+```
+
+- [ ] **Step 2: Reset DB to apply schema change**
+
+```bash
+cd server && npm run db:reset:test
+```
+
+Expected: "Your database has been reset" or similar. Migration runs and seed applies.
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add server/prisma/schema.prisma
+git commit -m "feat: rename User.name→displayName, add firstName/lastName/gender/avatar"
+```
+
+---
+
+### Task 2: Update server `mapUserForClient` and profile handler in auth.js
+
+**Files:**
+- Modify: `server/src/routes/auth.js:5-15`
+- Modify: `server/src/routes/auth.js:116-138`
+
+- [ ] **Step 1: Update `mapUserForClient` to use `displayName` and add new fields**
+
+Replace lines 5-15 with:
+
+```js
+function mapUserForClient(user) {
+ const adminEmail = normalizeEmail(process.env.ADMIN_EMAIL)
+ const userEmail = normalizeEmail(user.email)
+ return {
+ id: user.id,
+ email: user.email,
+ displayName: user.displayName,
+ firstName: user.firstName,
+ lastName: user.lastName,
+ gender: user.gender,
+ avatar: user.avatar,
+ phone: user.phone,
+ isAdmin: Boolean(adminEmail) && userEmail === adminEmail,
+ }
+}
+```
+
+- [ ] **Step 2: Update `PATCH /api/me/profile` to use `displayName`**
+
+Replace lines 114-138 with:
+
+```js
+ fastify.patch('/api/me/profile', { preHandler: [fastify.authenticate] }, async (request, reply) => {
+ const userId = request.user.sub
+ const nameRaw = request.body?.displayName
+ const displayName = nameRaw === null || nameRaw === undefined ? null : String(nameRaw).trim()
+ const phoneRaw = request.body?.phone
+ const phone = phoneRaw === null || phoneRaw === undefined ? null : String(phoneRaw).trim()
+
+ if (displayName !== null && displayName.length > 40) return reply.code(400).send({ error: 'Имя/ник максимум 40 символов' })
+ if (phone !== null) {
+ const compact = phone.replace(/[\s()-]/g, '')
+ if (compact.length > 20) return reply.code(400).send({ error: 'Телефон слишком длинный' })
+ if (compact.length && !/^\+?\d{7,20}$/.test(compact)) {
+ return reply.code(400).send({ error: 'Некорректный телефон' })
+ }
+ }
+
+ const updated = await prisma.user.update({
+ where: { id: userId },
+ data: {
+ displayName: displayName && displayName.length ? displayName : null,
+ phone: phone && phone.length ? phone : null,
+ },
+ })
+ return { user: mapUserForClient(updated) }
+ })
+```
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add server/src/routes/auth.js
+git commit -m "feat: use displayName in mapUserForClient and profile update"
+```
+
+---
+
+### Task 3: Update admin-users.js — rename `name` → `displayName`
+
+**Files:**
+- Modify: `server/src/routes/api/admin-users.js`
+
+- [ ] **Step 1: Replace all `name` → `displayName` throughout**
+
+Replace line 24 (`OR` clause):
+```
+ OR: [{ email: { contains: q } }, { displayName: { contains: q } }],
+```
+
+Replace line 35 (select):
+```
+ displayName: true,
+```
+
+Replace line 46 (map):
+```
+ displayName: u.displayName,
+```
+
+Replace lines 63-64 (create — body field and validation):
+```
+ const nameRaw = body.displayName
+ const displayName = nameRaw === null || nameRaw === undefined ? null : String(nameRaw).trim()
+ if (displayName !== null && displayName.length > 40) {
+```
+
+Replace line 79 (create data):
+```
+ displayName: displayName && displayName.length ? displayName : null,
+```
+
+Replace line 86 (create response):
+```
+ displayName: user.displayName,
+```
+
+Replace lines 120-127 (update):
+```
+ if (body.displayName !== undefined) {
+ const nameRaw = body.displayName
+ const name = nameRaw === null || nameRaw === undefined ? null : String(nameRaw).trim()
+ if (name !== null && name.length > 40) {
+ reply.code(400).send({ error: 'Имя/ник максимум 40 символов' })
+ return
+ }
+ data.displayName = name && name.length ? name : null
+ }
+```
+
+Replace line 134 (update response):
+```
+ displayName: user.displayName,
+```
+
+- [ ] **Step 2: Commit**
+
+```bash
+git add server/src/routes/api/admin-users.js
+git commit -m "refactor: rename name→displayName in admin-users"
+```
+
+---
+
+### Task 4: Update admin-reviews.js and review-display.js — rename `name` → `displayName`
+
+**Files:**
+- Modify: `server/src/routes/api/admin-reviews.js:59`
+- Modify: `server/src/lib/review-display.js:4`
+
+- [ ] **Step 1: Update admin-reviews.js line 59**
+
+Replace:
+```
+ userName: existing.user?.name || existing.user?.email || '',
+```
+With:
+```
+ userName: existing.user?.displayName || existing.user?.email || '',
+```
+
+- [ ] **Step 2: Update review-display.js line 4**
+
+Replace:
+```
+ const name = typeof user.name === 'string' ? user.name.trim() : ''
+```
+With:
+```
+ const name = typeof user.displayName === 'string' ? user.displayName.trim() : ''
+```
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add server/src/routes/api/admin-reviews.js server/src/lib/review-display.js
+git commit -m "refactor: rename name→displayName in review files"
+```
+
+---
+
+### Task 5: Enrich VK OAuth — save firstName, lastName, gender, avatar
+
+**Files:**
+- Modify: `server/src/routes/oauth-social.js:119-152`
+
+- [ ] **Step 1: Update VK to fetch `photo_200` instead of `photo_50`, extract sex, and save all new fields**
+
+Replace lines 119-152 with:
+
+```js
+ let firstName = null
+ let lastName = null
+ let gender = null
+ let avatar = null
+ try {
+ if (accessTokenVk && vkUserId) {
+ const u = new URL('https://api.vk.com/method/users.get')
+ u.searchParams.set('access_token', accessTokenVk)
+ u.searchParams.set('users_ids', String(vkUserId))
+ u.searchParams.set('fields', 'photo_200,sex')
+ u.searchParams.set('v', '5.199')
+ const profRes = await fetch(u.toString())
+ const prof = await profRes.json()
+ const u0 = prof?.response?.[0]
+ if (u0) {
+ firstName = u0.first_name ?? null
+ lastName = u0.last_name ?? null
+ avatar = u0.photo_200 ?? null
+ if (u0.sex === 1) gender = 'female'
+ else if (u0.sex === 2) gender = 'male'
+ }
+ }
+ } catch {
+ // ignore profile extras
+ }
+
+ const user = await findOrCreateUserFromOAuth({
+ provider: 'vk',
+ providerUserId: String(vkUserId),
+ accessToken: accessTokenVk ?? null,
+ suggestedEmail: emailSuggestion,
+ })
+
+ const displayName = [firstName, lastName].filter(Boolean).join(' ').trim()
+ const updateData = {}
+ if (displayName && !user.displayName) updateData.displayName = displayName
+ if (firstName) updateData.firstName = firstName
+ if (lastName) updateData.lastName = lastName
+ if (gender) updateData.gender = gender
+ if (avatar) updateData.avatar = avatar
+ if (Object.keys(updateData).length > 0) {
+ await prisma.user.update({ where: { id: user.id }, data: updateData })
+ }
+```
+
+- [ ] **Step 2: Commit**
+
+```bash
+git add server/src/routes/oauth-social.js
+git commit -m "feat: enrich VK OAuth with firstName/lastName/gender/avatar"
+```
+
+---
+
+### Task 6: Enrich Yandex OAuth — save firstName, lastName, gender, avatar
+
+**Files:**
+- Modify: `server/src/routes/oauth-social.js:229-243`
+
+- [ ] **Step 1: Update Yandex to extract and save all new fields**
+
+Replace lines 229-243 (from `const user = await findOrCreateUserFromOAuth(...)` to the end of the Yandex callback) with:
+
+```js
+ const user = await findOrCreateUserFromOAuth({
+ provider: 'yandex',
+ providerUserId: yaUserId,
+ accessToken: yaToken,
+ suggestedEmail: emailGuess || null,
+ })
+
+ const updateData = {}
+ const displayName = [info.first_name, info.last_name].filter(Boolean).join(' ').trim() || info.display_name || info.real_name
+ if (displayName && !user.displayName) updateData.displayName = displayName
+ if (info.first_name) updateData.firstName = info.first_name
+ if (info.last_name) updateData.lastName = info.last_name
+ if (info.sex === 'male' || info.sex === 'female') updateData.gender = info.sex
+ if (info.default_avatar_id && !info.is_avatar_empty) {
+ updateData.avatar = `https://avatars.yandex.net/get-yapic/${info.default_avatar_id}/islands-200`
+ }
+ if (Object.keys(updateData).length > 0) {
+ await prisma.user.update({ where: { id: user.id }, data: updateData })
+ }
+```
+
+- [ ] **Step 2: Commit**
+
+```bash
+git add server/src/routes/oauth-social.js
+git commit -m "feat: enrich Yandex OAuth with firstName/lastName/gender/avatar"
+```
+
+---
+
+### Task 7: Update client `AuthUser` type and `UpdateProfileParams`
+
+**Files:**
+- Modify: `client/src/shared/model/auth.ts:6`
+- Modify: `client/src/shared/model/auth.ts:61`
+
+- [ ] **Step 1: Update `AuthUser` type**
+
+Replace line 6:
+```
+export type AuthUser = { id: string; email: string; name?: string | null; phone?: string | null; isAdmin?: boolean }
+```
+With:
+```ts
+export type AuthUser = {
+ id: string
+ email: string
+ displayName?: string | null
+ firstName?: string | null
+ lastName?: string | null
+ gender?: string | null
+ avatar?: string | null
+ phone?: string | null
+ isAdmin?: boolean
+}
+```
+
+- [ ] **Step 2: Update `UpdateProfileParams`**
+
+Replace line 61:
+```
+export type UpdateProfileParams = { name: string | null; phone?: string | null }
+```
+With:
+```ts
+export type UpdateProfileParams = { displayName: string | null; phone?: string | null }
+```
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add client/src/shared/model/auth.ts
+git commit -m "refactor: rename name→displayName in AuthUser type"
+```
+
+---
+
+### Task 8: Update client files — rename `name` → `displayName`
+
+**Files:**
+- Modify: `client/src/pages/auth/ui/AuthPage.tsx:15`
+- Modify: `client/src/pages/me/ui/MeLayoutPage.tsx:84`
+- Modify: `client/src/features/user/user-menu/ui/UserMenu.tsx:57`
+- Modify: `client/src/pages/me/ui/MePage.tsx:41-91`
+- Modify: `client/src/pages/me/ui/sections/SettingsPage.tsx:41-98`
+- Modify: `client/src/entities/order/api/admin-order-api.ts:40`
+- Modify: `client/src/entities/review/api/admin-review-api.ts:10`
+
+- [ ] **Step 1: Update AuthPage inline type**
+
+Replace `client/src/pages/auth/ui/AuthPage.tsx` line 15:
+```
+type AuthResponse = { token: string; user: { id: string; email: string; name?: string | null; phone?: string | null } }
+```
+With:
+```ts
+type AuthResponse = { token: string; user: { id: string; email: string; displayName?: string | null; phone?: string | null } }
+```
+
+- [ ] **Step 2: Update MeLayoutPage**
+
+Replace `client/src/pages/me/ui/MeLayoutPage.tsx` line 84:
+```
+ {user.name?.trim() || user.email}
+```
+With:
+```
+ {user.displayName?.trim() || user.email}
+```
+
+- [ ] **Step 3: Update UserMenu**
+
+Replace `client/src/features/user/user-menu/ui/UserMenu.tsx` line 57:
+```
+
+```
+With:
+```
+
+```
+
+- [ ] **Step 4: Update MePage (profile page)**
+
+Replace `client/src/pages/me/ui/MePage.tsx` — the form field name and all related code:
+
+Line 41 — form type:
+```
+ const profileForm = useForm<{ displayName: string }>({
+```
+
+Line 42 — defaultValues:
+```
+ defaultValues: { displayName: user?.displayName ? String(user.displayName) : '' },
+```
+
+Line 79-83 — TextField:
+```
+
+```
+
+Lines 88-91 — onClick:
+```
+ onClick={() => {
+ const raw = profileForm.getValues('displayName')
+ const name = raw.trim()
+ updateProfileFx({ displayName: name.length ? name : null })
+ }}
+```
+
+- [ ] **Step 5: Update SettingsPage**
+
+Replace `client/src/pages/me/ui/sections/SettingsPage.tsx` — same pattern as MePage:
+
+Line 41 — form type:
+```
+ const profileForm = useForm<{ displayName: string; phone: string }>({
+```
+
+Line 42 — defaultValues:
+```
+ defaultValues: { displayName: user?.displayName ? String(user.displayName) : '', phone: user?.phone ? String(user.phone) : '' },
+```
+
+Lines 79-83 — TextField:
+```
+
+```
+
+Lines 93-98 — onClick:
+```
+ onClick={() => {
+ const raw = profileForm.getValues('displayName')
+ const name = raw.trim()
+ const phoneRaw = profileForm.getValues('phone')
+ const phone = phoneRaw.trim()
+ updateProfileFx({ displayName: name.length ? name : null, phone: phone.length ? phone : null })
+ }}
+```
+
+- [ ] **Step 6: Update admin-order-api.ts type**
+
+Replace `client/src/entities/order/api/admin-order-api.ts` line 40:
+```
+ user: { id: string; email: string; name: string | null; phone: string | null }
+```
+With:
+```ts
+ user: { id: string; email: string; displayName: string | null; phone: string | null }
+```
+
+- [ ] **Step 7: Update admin-review-api.ts type**
+
+Replace `client/src/entities/review/api/admin-review-api.ts` line 10:
+```
+ user: { id: string; email: string; name: string | null }
+```
+With:
+```ts
+ user: { id: string; email: string; displayName: string | null }
+```
+
+- [ ] **Step 8: Run client lint to verify**
+
+```bash
+cd client && npm run lint
+```
+
+Expected: no errors (or fix any found).
+
+- [ ] **Step 9: Commit**
+
+```bash
+git add client/src/pages/auth/ui/AuthPage.tsx client/src/pages/me/ui/MeLayoutPage.tsx client/src/features/user/user-menu/ui/UserMenu.tsx client/src/pages/me/ui/MePage.tsx client/src/pages/me/ui/sections/SettingsPage.tsx client/src/entities/order/api/admin-order-api.ts client/src/entities/review/api/admin-review-api.ts
+git commit -m "refactor: rename name→displayName across client"
+```
+
+---
+
+### Task 9: Create OAuth providers config
+
+**Files:**
+- Create: `client/src/features/auth-oauth/lib/oauth-providers.ts`
+
+- [ ] **Step 1: Create the providers config**
+
+```ts
+import { oauthAuthorizeUrl } from '@/shared/lib/oauth-authorize-url'
+
+export type OAuthProvider = {
+ id: 'yandex' | 'vk'
+ label: string
+ color: string
+}
+
+export const oauthProviders: OAuthProvider[] = [
+ {
+ id: 'yandex',
+ label: 'Яндекс ID',
+ color: '#FC3F1D',
+ },
+ {
+ id: 'vk',
+ label: 'VK ID',
+ color: '#0077FF',
+ },
+]
+
+export function getOAuthUrl(provider: 'yandex' | 'vk'): string {
+ return oauthAuthorizeUrl(provider)
+}
+```
+
+- [ ] **Step 2: Commit**
+
+```bash
+git add client/src/features/auth-oauth/lib/oauth-providers.ts
+git commit -m "feat: add oauth providers config"
+```
+
+---
+
+### Task 10: Create OAuthButtons component
+
+**Files:**
+- Create: `client/src/features/auth-oauth/ui/OAuthButtons.tsx`
+
+- [ ] **Step 1: Create the OAuthButtons component**
+
+```tsx
+import Stack from '@mui/material/Stack'
+import Button from '@mui/material/Button'
+import { getOAuthUrl, oauthProviders } from '../lib/oauth-providers'
+
+export function OAuthButtons() {
+ return (
+
+ {oauthProviders.map((p) => (
+
+ ))}
+
+ )
+}
+```
+
+- [ ] **Step 2: Create barrel export**
+
+Create `client/src/features/auth-oauth/index.ts`:
+
+```ts
+export { OAuthButtons } from './ui/OAuthButtons'
+```
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add client/src/features/auth-oauth/
+git commit -m "feat: add OAuthButtons component"
+```
+
+---
+
+### Task 11: Integrate OAuthButtons into AuthPage
+
+**Files:**
+- Modify: `client/src/pages/auth/ui/AuthPage.tsx`
+
+- [ ] **Step 1: Add import and component after the email-code form**
+
+Add import at top (after existing imports):
+```ts
+import { OAuthButtons } from '@/features/auth-oauth'
+```
+
+Add MUI Divider import (add to existing MUI imports):
+```
+import Divider from '@mui/material/Divider'
+```
+
+After the closing `` on line 110, before the closing `` on line 111, add:
+
+```tsx
+ или
+
+
+```
+
+So the structure becomes:
+
+```tsx
+
+ {/* ... existing email/code form ... */}
+
+ {/* ... buttons ... */}
+
+
+
+
+ или
+
+
+
+```
+
+- [ ] **Step 2: Run client lint**
+
+```bash
+cd client && npm run lint
+```
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add client/src/pages/auth/ui/AuthPage.tsx
+git commit -m "feat: add OAuth buttons to AuthPage"
+```
+
+---
+
+### Task 12: Update server .env.example
+
+**Files:**
+- Modify: `server/.env.example:28-30`
+
+- [ ] **Step 1: Add scope documentation**
+
+Replace lines 28-30:
+```
+# Yandex OAuth: redirect URI = SERVER_PUBLIC_URL + /api/auth/oauth/yandex/callback
+YANDEX_CLIENT_ID=
+YANDEX_CLIENT_SECRET=
+```
+With:
+```env
+# Yandex OAuth: redirect URI = SERVER_PUBLIC_URL + /api/auth/oauth/yandex/callback
+# Scopes: login:email login:info
+YANDEX_CLIENT_ID=
+YANDEX_CLIENT_SECRET=
+```
+
+- [ ] **Step 2: Commit**
+
+```bash
+git add server/.env.example
+git commit -m "docs: add Yandex OAuth scopes to .env.example"
+```
+
+---
+
+### Task 13: Write server tests for OAuth profile enrichment
+
+**Files:**
+- Create: `server/src/routes/__tests__/oauth-social.test.js`
+
+- [ ] **Step 1: Create test file**
+
+```js
+import { describe, it, expect, beforeAll, afterAll } from 'vitest'
+import { prisma } from '../../lib/prisma.js'
+
+describe('OAuth — findOrCreateUserFromOAuth (indirect via DB checks)', () => {
+ it('stores displayName, firstName, lastName, gender, avatar fields on User model', async () => {
+ // Verify new fields exist on the schema by creating a user with them
+ const user = await prisma.user.create({
+ data: {
+ email: 'test-oauth@example.com',
+ displayName: 'Test User',
+ firstName: 'Test',
+ lastName: 'User',
+ gender: 'male',
+ avatar: 'https://example.com/avatar.jpg',
+ },
+ })
+
+ expect(user.displayName).toBe('Test User')
+ expect(user.firstName).toBe('Test')
+ expect(user.lastName).toBe('User')
+ expect(user.gender).toBe('male')
+ expect(user.avatar).toBe('https://example.com/avatar.jpg')
+
+ // Cleanup
+ await prisma.user.delete({ where: { id: user.id } })
+ })
+
+ it('allows nullable fields', async () => {
+ const user = await prisma.user.create({
+ data: {
+ email: 'test-oauth-null@example.com',
+ },
+ })
+
+ expect(user.displayName).toBeNull()
+ expect(user.firstName).toBeNull()
+ expect(user.lastName).toBeNull()
+ expect(user.gender).toBeNull()
+ expect(user.avatar).toBeNull()
+
+ await prisma.user.delete({ where: { id: user.id } })
+ })
+})
+```
+
+- [ ] **Step 2: Run server tests**
+
+```bash
+cd server && npm test
+```
+
+Expected: 2 passing tests.
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add server/src/routes/__tests__/oauth-social.test.js
+git commit -m "test: OAuth user model fields"
+```
+
+---
+
+### Task 14: Write client test for OAuthButtons
+
+**Files:**
+- Create: `client/src/features/auth-oauth/__tests__/OAuthButtons.test.tsx`
+
+- [ ] **Step 1: Create test file**
+
+```tsx
+import { describe, it, expect } from 'vitest'
+import { render, screen } from '@testing-library/react'
+import { OAuthButtons } from '../ui/OAuthButtons'
+
+describe('OAuthButtons', () => {
+ it('renders Yandex and VK buttons', () => {
+ render()
+ expect(screen.getByText('Войти через Яндекс ID')).toBeDefined()
+ expect(screen.getByText('Войти через VK ID')).toBeDefined()
+ })
+
+ it('buttons have correct href', () => {
+ render()
+ const yaBtn = screen.getByText('Войти через Яндекс ID').closest('a')
+ const vkBtn = screen.getByText('Войти через VK ID').closest('a')
+ expect(yaBtn?.getAttribute('href')).toContain('/auth/oauth/yandex')
+ expect(vkBtn?.getAttribute('href')).toContain('/auth/oauth/vk')
+ })
+})
+```
+
+- [ ] **Step 2: Run client tests**
+
+```bash
+cd client && npm test
+```
+
+Expected: 2 passing tests.
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add client/src/features/auth-oauth/__tests__/OAuthButtons.test.tsx
+git commit -m "test: OAuthButtons component"
+```
+
+---
+
+### Task 15: Final verification — full build and lint
+
+**Files:** none (verification only)
+
+- [ ] **Step 1: Run server lint**
+
+```bash
+cd server && npm run lint
+```
+
+Expected: no errors.
+
+- [ ] **Step 2: Run server tests**
+
+```bash
+cd server && npm test
+```
+
+Expected: all tests pass.
+
+- [ ] **Step 3: Run client lint + format check**
+
+```bash
+cd client && npm run lint && npm run format:check
+```
+
+Expected: no errors.
+
+- [ ] **Step 4: Run client tests**
+
+```bash
+cd client && npm test
+```
+
+Expected: all tests pass.
+
+- [ ] **Step 5: Run client build (full typecheck)**
+
+```bash
+cd client && npm run build
+```
+
+Expected: build succeeds with no TypeScript errors.
+
+- [ ] **Step 6: Final commit (if any fixups were needed)**
+
+```bash
+git add -A && git commit -m "chore: final verification fixes" || echo "No fixups needed"
+```