- summary: **Настроить Playwright MCP для UI-верификации через браузер. Конфиг Zoo Code обновлён (Node 22), Chromium установлен.**
- next task: —
### TASK-F12/F13/F14 — фиксы карты тренировки (WorkoutItem.vue)
## TASK-MCP-SETUP: Playwright MCP configuration
- Ф12: `position="top-center left-center"` (центрирование маркера на координате); `findNearestTrackIndex` (радиус 40 м) вместо точного lookup `getKey`/`coordWithIndex`.
- Ф13: стабильные `:settings` фото-маркеров через Map-кэш (`getPhotoMarkerSettings`) — одного этого оказалось НЕДОСТАТОЧНО (Vue патчит VNode-ы и при неизменённых пропсах).
- Ф14: `v-memo="[photo.id]"` на фото-маркерах — жёсткий пропуск патча, `onUpdated`/`clearElement()` больше не срабатывают. Методология и причины записаны в `systemPatterns.md` (секция «Charts & maps»).
**Что сделано:**
- MCP-конфиг Zoo Code (`mcp_settings.json`): server `web-browser` → `@playwright/mcp@latest`, Node 22 via nvm absolute path, `--headless`
- Chromium `1234` уже установлен в `~/.cache/ms-playwright/`
- `.mcp.json` создан в проекте (project-level, для совместимости)
Любой неизвестный URL (например `/random-page`) отдаёт `200` + redirect на `/explore`. Поисковик путается.
Сайдбар показывает `publicRoutes` (Лента + Маршрут) после логина до F5. Корень: [`NavigationRoutes.ts:70`](src/components/sidebar/NavigationRoutes.ts:70) — `routes` вычисляется один раз при module evaluation и кэшируется. TASK-AUTH-NAV-1 уже добавил реактивный `isAuthenticated` в `useGlobalStore` (инициализация из localStorage + `setAuthenticated` в Login/Logout). Эта задача переключает UI на computed.
- Сделать named exports: `export const authRoutes: INavigationRoute[]` и `export const publicRoutes: INavigationRoute[]` (сейчас это локальные `const`, не экспортированные).
- Убрать статическое поле `routes` из default export (или оставить default без `routes`, т.к. оба потребителя перейдут на computed). `INavigationRoute` интерфейс — не трогать.
- Важно: массивы `authRoutes`/`publicRoutes` остаются теми же по структуре/содержимому — только экспортируются.
- `import { useGlobalStore } from "../../stores/global-store";` + `import { authRoutes, publicRoutes } from "./NavigationRoutes";` (импорт `navigationRoutes` по default можно оставить/убрать — но все обращения `navigationRoutes.routes` заменить).
- В`return` заменить `navigationRoutes` на `routes` (тепловой шаблон уже использует `navigationRoutes.routes` → поправить на `routes.value`? НЕТ — в Options API computed из setup доступно в шаблоне как `routes` без `.value`, т.е. в шаблоне `navigationRoutes.routes` → `routes`).
- `setActiveExpand`: `navigationRoutes.routes.map(...)` → `routes.value.map(...)` (в setup `.value` нужен).
- Шаблоны: `v-for="(route, index) in navigationRoutes.routes"` → `in routes`.
- `server/index.ts` — добавлен catch-all 404 handler (`app.use(...)` без path) перед `app.listen`: для GET — `renderTemplate`с SEO-мета (title: "404 — Страница не найдена — Cycle Rider", canonical `/404`), для остальных методов — `res.status(404).send("Not Found")`.
- `nginx.conf` — реорганизация: добавлен SPA whitelist `~ ^/(workouts|auth|preferences|404)(/|$)` → `try_files $uri /index.html`; старое `location /` (SPA fallback) заменено на catch-all → `proxy_pass http://127.0.0.1:3001` (SSR вернёт 404 для неизвестных путей).
- Убедиться, что `items` computed (зависит от `route.matched` и `findRouteName`) остаётся реактивным — т.к. `findRouteName` читает `globalStore.isAuthenticated` внутри `items` computed, реactivity сохранится.
- `package.json` — `@unhead/vue` ^3.4.1 в dependencies (установлено через `npm install @unhead/vue --legacy-peer-deps`; peer-конфликт vite 4 vs vite 5 разрешён legacy-flag'ом; vite в project поднят до 5.4.21 транзитивно).
- Создан `src/router/seo.ts` — `setupSeo(router, head: Unhead)`: SEO_MAP по name роута + DEFAULT_SEO, `head.push({title, meta:[description]})` + `router.afterEach(() => update())`.
- `src/main.ts` — `import { createHead } from "unhead/client"` + `import { headSymbol } from "@unhead/vue"`; `const head = createHead(); app.provide(headSymbol, head); setupSeo(router, head);` (после `app.use(router)`).
- `index.html` — `<title>` заменён на «Cycle Rider — платформа для анализа велотренировок: ...»; добавлен `<meta name="description">` в `<head>` как fallback; блок Yandex.Metrika перенесён из `<head>` в начало `<body>` (Vite 5 parse5 запрещает `<div>/<img>` внутри `<noscript>` в `<head>` — `disallowed-content-in-noscript-in-head`).
- Verified: `npm run build` exit 0 (vue-tsc 0 ошибок, vite build ✓ 6.65s; warning chunk >500kB — предсуществующий); `npm run lint` exit 0.
### Acceptance Criteria (TASK-AUTH-NAV-2)
## ✅ CODER SUCCESS REPORT — TASK-PUBLIC-ROUTE-FIX
1. `yarn lint` → 0 ошибок.
2. `yarn build` → 0 ошибок (vue-tsc --noEmit).
3. `NavigationRoutes.ts`: `authRoutes` и `publicRoutes` экспортируются; статического `routes`с localStorage-тернарником больше нет.
4. `AppSidebar.vue`: routes = computed от `globalStore.isAuthenticated`; `setActiveExpand` использует `routes.value`.
5. `AppLayoutNavigation.vue`: `traverse` использует выбор по `globalStore.isAuthenticated`.
6. **Ручной сценарий (ключевой)**: без F5 — выход из профиля → `/explore` → логин → сайдбар **сразу** показывает «Мои тренировки» (authRoutes). Аут → сайдбар сразу `publicRoutes`.
- Поведение: URL `/public/workouts/:id` теперь доступен без авторизации (не обёрнут в `AppLayout`), совпадает с SSR-эндпоинтом, nginx-прокси `location ~ ^/public/workouts/` и sitemap. Внутренняя навигация по `name: "workout_public_item"` (см. `Feed.vue`) не тронута — имя не менялось.
- `WorkoutPublicItem.vue` / `WorkoutItem.vue`: проверено — `inject("axiosAuth")` работает глобально (app.provide в `main.ts`), от AppLayout не зависит; auth-check'ов от вложенности не найдено.
- `src/pages/auth/Login.vue` — `import { useGlobalStore }`; в `.then` после `localStorage.setItem(...)` и до `push({ name: "explore" })`: `globalStore.setAuthenticated(true)`.
- `src/pages/auth/Logout.vue` — `import { useGlobalStore }`; после `localStorage.clear()` и до `push({ name: "login" })`: `globalStore.setAuthenticated(false)`.