95 lines
8.2 KiB
Markdown
95 lines
8.2 KiB
Markdown
# Active Context — Strava Frontend
|
||
|
||
## Task State
|
||
|
||
- task_id: TASK-F14
|
||
- status: success
|
||
- parent_task: TASK-F12, TASK-F13
|
||
- summary: **Карта тренировки: одиночные метки фото не пропадают при сбросе участка (v-memo + стабильные :settings), метки центрированы на GPS-точках, клик по треку — поиск ближайшей точки (радиус 40 м).**
|
||
- next task: — (no active tasks)
|
||
|
||
### TASK-F12/F13/F14 — фиксы карты тренировки (WorkoutItem.vue)
|
||
|
||
- Ф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»).
|
||
|
||
## TASK-404: HTTP 404 для несуществующих страниц (SEO)
|
||
|
||
### Проблема
|
||
|
||
Любой неизвестный URL (например `/random-page`) отдаёт `200` + redirect на `/explore`. Поисковик путается.
|
||
|
||
### Решение
|
||
|
||
1. `server/index.ts` — catch-all 404 handler: `app.use((req, res) => { if (req.method === "GET") res.status(404).send(renderTemplate(...)) })` с SEO-мета (title: "404 — страница не найдена — Cycle Rider", noindex meta).
|
||
2. `nginx.conf` — whitelist SPA-роутов (`/workouts`, `/auth`, `/preferences`, `/404`) → `try_files $uri /index.html`; всё остальное (кроме SSR-локаций) → `proxy_pass http://127.0.0.1:3001`.
|
||
3. `src/router/index.ts` — catch-all `/:pathMatch(.*)*` → redirect на `{ name: "404" }` (вместо `explore`).
|
||
4. `src/router/seo.ts` — запись `404: { title: "404 — страница не найдена — Cycle Rider", description: "..." }` в SEO_MAP.
|
||
|
||
### Acceptance Criteria
|
||
|
||
- `curl -s -o /dev/null -w "%{http_code}" http://localhost:80/random-path` → `404`
|
||
- `curl -s http://localhost:80/random-path | grep -c '404'` → > 0
|
||
- `curl -s -o /dev/null -w "%{http_code}" http://localhost:80/workouts` → `200`
|
||
- `curl -s -o /dev/null -w "%{http_code}" http://localhost:80/auth/login` → `200`
|
||
- `curl -s -o /dev/null -w "%{http_code}" http://localhost:80/explore` → `200`
|
||
- `yarn lint` → 0, `yarn build` → 0
|
||
|
||
## ✅ CODER SUCCESS REPORT — TASK-404
|
||
|
||
- `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 для неизвестных путей).
|
||
- `src/router/index.ts` — catch-all `/:pathMatch(.*)*` redirect: `{ name: "explore" }` → `{ name: "404" }`.
|
||
- `src/router/seo.ts` — добавлена запись `"404"` в `SEO_MAP` (title + description).
|
||
- Verified: `yarn lint` exit 0, `yarn build` exit 0 (vue-tsc 0 errors, vite build ✓ 7.02s).
|
||
|
||
## ✅ CODER SUCCESS REPORT — TASK-F11
|
||
|
||
- `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.
|
||
|
||
## ✅ CODER SUCCESS REPORT — TASK-PUBLIC-ROUTE-FIX
|
||
|
||
- Изменён файл (единственный): `src/router/index.ts`:
|
||
- Удалён вложенный `workout_public_item` из `children` маршрута `workouts` (бывшие строки 50–54: `path: "public/workouts/:id"`).
|
||
- Добавлен top-level роут `{ name: "workout_public_item", path: "/public/workouts/:id", component: () => import("../pages/workouts/WorkoutPublicItem.vue") }` — зарегистрирован **до** catch-all `/:pathMatch(.*)*`, вне `AppLayout` (без auth-guard'а).
|
||
- Поведение: 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'ов от вложенности не найдено.
|
||
- `yarn lint`: exit 0 (prettier: все файлы unchanged, eslint 0 ошибок).
|
||
- `yarn build`: exit 0 (vue-tsc --noEmit 0 ошибок, vite build ✓ 6.95s; warning о chunk >500kB — предсуществующий).
|
||
|
||
## ✅ CODER SUCCESS REPORT — TASK-SSR-3
|
||
|
||
- Изменённые файлы:
|
||
- `server/index.ts` — добавлены `GET /sitemap.xml` и `GET /robots.txt`.
|
||
- `nginx.conf` — полная замена: SSR proxy, cookie-based landing, assets cache, SPA fallback.
|
||
- `Dockerfile` — multi-stage: node:20-alpine build → runtime (nginx + tsx server).
|
||
- `run.sh` — `npx tsx server/index.ts & nginx -g 'daemon off;'`.
|
||
- `package.json` — `tsx` → dependencies.
|
||
- `src/stores/user-store.ts` — `typeof window` guard для SSR.
|
||
- `src/main.ts` — `typeof window` guard перед `localStorage`.
|
||
- Verified: lint 0, build 0, curl /robots.txt 200, /sitemap.xml 200, /health ok.
|
||
|
||
## ✅ CODER SUCCESS REPORT — TASK-SSR-2
|
||
|
||
- `server/index.ts` — 4 SSR route handlers + `app.disable("x-powered-by")`: `/`, `/explore`, `/public/workouts/:id`, `/routes`.
|
||
- Helpers: `getAssetTagsSafe()`, `escapeHtml`, `formatDate`, `formatDuration`, `formatDistance`, `formatSpeed`, `getToken`.
|
||
- Canonical base: `SSR_BASE_URL` || `https://cycle-rider.ru`.
|
||
- Verified: lint 0, build 0, curl all routes OK.
|
||
|
||
## ✅ CODER SUCCESS REPORT — TASK-SSR-1
|
||
|
||
- Created `server/index.ts`, `server/template.ts`, `server/api.ts`.
|
||
- `package.json`: +express, +@types/express, +tsx, +3 server scripts.
|
||
- Verified: lint 0, build 0, curl /health OK.
|
||
|
||
## ✅ CODER SUCCESS REPORT — TASK-SSR-0
|
||
|
||
- `src/router/index.ts`: `dashboard` → `explore` (name + path), legacy redirect `/dashboard` → `explore`.
|
||
- `NavigationRoutes.ts`, `AppLayoutNavigation.vue`, `AppLayout.vue`, `Login.vue`, `Signup.vue`, `CheckTheEmail.vue` — все ссылки `dashboard` → `explore`.
|
||
- Verified: lint 0, build 0.
|