# Progress — Strava Frontend ## 2026-09-26 — TASK-INTEG-SECTION + TASK-SECTION-BUGS: интеграционный тест section-duration + фикс 2 багов - `../integration/tests/section-duration.spec.ts` (new): 3 Playwright-теста (duration row on section select; zoom persist across Время↔Дистанция; reset clears on all tabs). Self-contained: stable owner `itest-section-owner@example.com` (idempotent signup/signin), API-seed workout (upload + create, dedup-fallback → id из `GET /workouts`[0]), Chart.js access через `canvas.__vueParentComponent.exposed.chart` (глобального `window.Chart` в SPA нет); zoom-детект по `options.scales.x.min/max` vs полный диапазон (`isZoomedOrPanned()` всегда false — zoom применяется прямой записью, минуя плагин). - **Bug 1** (`src/pages/workouts/components/WorkoutItem.vue`, `setChartRef` ~строка 1155): ref пушится сразу (guard `r.chart` убран — Chart.js создаёт инстанс асинхронно); финализация (`group.charts = collectCharts()` + `applySelectedRangeZoom()`) через `tryFinalize(retries)` — retry через `nextTick` (до 20 попыток) до тех пор, пока все рефы не получат `chart`. `nextTick` добавлен в импорт из "vue". Корень бага: на remount после `switchXAxis` `setChartRef` срабатывал до создания chart → `chartRefs` не пополнялся → `applySelectedRangeZoom()` не вызывался → zoom терялся. - **Bug 2** (`src/pages/workouts/components/ChartGroup.ts`, `resetAll` ~строка 125): после `resetZoom(chart)` явный сброс `options.scales.x.min/max = undefined` + `chart.update("none")` (broadcastZoom писал min/max напрямую, минуя внутреннее состояние плагина, поэтому `resetZoom` один ничего не сбрасывал). Дополнительная корневая причина: `resetZoom` триггерит `onZoomComplete`, который повторно разносит ещё-зумированный диапазон на остальные чарты (перетирая сброшенный) — в `resetAll` на время цикла `this.broadcasting = true` (try/finally), guard `isBroadcasting()` в `onZoomComplete` отбрасывает эти колбэки. - Verified: `npx playwright test tests/section-duration.spec.ts` → **3 passed** (56s, docker-стек); `yarn build` (vue-tsc --noEmit + vite) → exit 0. - Known: диагностический `../integration/tests/chartprobe.spec.ts` не удалён (rm-команда отклонена окружением) — удалить вручную. ## 2026-09-26 — TASK-SECTION-DURATION-ZOOM: «Продолжительность» + сохранение выбора участка при смене вкладки - `src/pages/workouts/components/WorkoutItem.vue`: - импорт `secondsToDuration` из `../Definitions.vue`. - `activeSection: number|null` → `selectedRange: { startIdx, endIdx } | null` (режим-независимый source of truth, индексы точек). - `computeAreaData`: строка `avgData["Продолжительность"] = secondsToDuration(durSec)` по `times[start]`/`times[end-1]`. - `onClick`: тоггл-off по совпадению индексов (`startIdx` и `endIdx`); иначе `selectedRange={startIdx,endIdx}` + `computeAreaData` + `broadcastZoom`. - `onZoomComplete`: `selectedRange = null` (ручной zoom снимает выбор). - `switchXAxis`: убран сброс выбора; добавлена `applySelectedRangeZoom()`; `areaAvgData`/`markedCoordinats` не сбрасываются. - `setChartRef`: `applySelectedRangeZoom()` после `group.charts = collectCharts()`. - `resetChartZoom`: `selectedRange = null` (после reset — весь путь на всех вкладках). - Verified: `yarn build` → ✓ built in 7.14s (vue-tsc --noEmit 0 ошибок). - Verified: `grep activeSection` → 0 совпадений; `grep selectedRange` → 9; `grep secondsToDuration` → 2 (импорт + использование). - Known issue: `yarn lint` сломан системно — `tsutils.iterateComments is not a function` на всех 73 файлах (дефект окружения eslint/typescript-estree). - Browser (MCP ad-hoc): не выполнена — dev server `:5173` и интеграционный стек `:8000` не запущены (не поднимал docker-стек ради ad-hoc проверки). Результат по критериям 1–4. ## 2026-09-25 — TASK-PROFILE-ULID-BUG: фикс PATCH /profiles 500 (смена имени не персистится) - `../backend/app/modules/users/domains/profiles.py:32` — `default=str(ULID())` → `default_factory=lambda: str(ULID())`. - `../backend/app/modules/users/domains/users.py:20` — та же замена. - `../backend/app/modules/users/repositories/profile.py` — `DBProfileRepository.update`: `new_dict.pop("id", None)` (PK вне UPDATE). - `../backend/tests/test_api_profile.py` — 2 теста (персистентность имени + повторные PATCH). - Verified: `pytest tests/test_api_profile.py -v` → 2 passed; `pytest tests/ -v` → 56 passed, 10 errors (все 10 пре-экзистинг: SQLite alembic ALTER-constraints в db-fixture setup, подтверждено git stash); `mypy app/modules/users/ --explicit-package-bases --no-error-summary` → 0; `ruff check app/modules/users/ tests/test_api_profile.py` → 0. - Фикс закрывает known issue из TASK-TEST-WORKOUT-UPLOAD (backend-баг имени). ## 2026-09-25 — TASK-TEST-WORKOUT-UPLOAD: интеграционный тест загрузки тренировки (Playwright) - `../integration/tests/workout-upload-flow.spec.ts` — 5 последовательных тестов полного user-journey: (1) signup через UI + профиль (аватар-загрузка toast «Фото успешно загружено!», смена имени toast «Вы успешно изменили имя!»), (2) загрузка `.fit` на `/workouts/upload` + resolve id (redirect ИЛИ dedup-фолбэк через owner's `GET /api/v0/workouts`), (3) «Сделать публичной» → `GET /api/v0/public/workouts/{id}`=200, (4) «Скрыть начало и конец трека» → authed detail `workout.hide_start_end===true`, (5) «Добавить фото» → `` в `.workout-photos-grid`. - Ключевое: **стабильный owner**-юзер для Части 2 (глобальный content-hash dedup vs scoped `GET /workouts`); unique-юзер через UI для Части 1. Detail URL `/workouts/workouts/:id`, nav `domcontentloaded`. - `../integration/README.md` — секция про спек + notes (workout-dedup, backend-баг имени). - Cleanup: `../integration/tests/_diag.spec.ts` (диагностический) удалён. - Verified: `npx playwright test tests/workout-upload-flow.spec.ts` → **5 passed** (×3 dedup-путь + 1 свежая DB redirect-путь); `yarn build` exit 0; `npx playwright test --list` → 16 tests / 5 files (без `_diag`). - Known issues (не фикс, out-of-scope): backend `PATCH /profiles` (имя) → 500 из-за `Profile.id = field(default=str(ULID()))` (не `default_factory`) → имя не персистится, SPA toast ложный; `yarn lint` системно сломан (`tsutils.iterateComments`); перед прогоном спе нужен свежий стек / force-recreate backend (миграция `hide_start_end`). ## 2026-09-25 — TASK-HIDE-TRACK-3-FIX: восстановление чекбокса hide_start_end в WorkoutItem.vue - `src/pages/workouts/components/WorkoutItem.vue` — восстановлены (потеряны в git stash при TASK-HIDE-TRACK-4): - `import { useI18n } from "vue-i18n"` + `const { t } = useI18n();` - template: чекбокс `hide_start_end` после блока `is_public` - `changeHideStartEnd(value: boolean)` → PATCH `hide_start_end` (guard + catch-тост, паттерн `changePublic`) - i18n: секция `workout` + ключ `hide_start_end` добавлены во все 6 локалей (`br, cn, es, gb, ir, ru`) — секция отсутствовала. - Verified: `yarn build` exit 0 (vue-tsc 0 errors, vite ✓ 7.09s). - Known issue: `yarn lint` сломан системно — `tsutils.iterateComments is not a function` (дефект окружения). ## 2026-09-25 — TASK-HIDE-TRACK-B3: hide_start_end в PATCH endpoint + service (backend) - `../backend/app/web/v0/workout.py` — `WorkoutEditReq` + `hide_start_end: bool | None = Field(None)`; PATCH `/workouts/{workout_id}` передаёт `hide_start_end=req.hide_start_end` в `workout_service.workout_edit(...)`. - `../backend/app/modules/charts/services/workout.py` — `workout_edit` + параметр `hide_start_end: bool | None = None`; `if hide_start_end is not None: params["hide_start_end"] = hide_start_end` перед `repository.update`. - Verified: `./venv/bin/python -c "from app.web.v0.workout import workout_update; print('OK')"` → OK. - Verified: `./venv/bin/python -m mypy app/web/v0/workout.py app/modules/charts/services/workout.py --explicit-package-bases --no-error-summary` → 0 errors. ## 2026-09-25 — TASK-HIDE-TRACK-B2: Alembic migration hide_start_end (backend) - `../backend/migration/versions/2025-09-25-0000-a1b2c3d4e5f6-hide-start-end.py` создан: `revision='a1b2c3d4e5f6'`, `down_revision='b1c2d3e4f5a6'`; `upgrade()` — `op.add_column('workouts', sa.Column('hide_start_end', sa.Boolean(), server_default=sa.text('FALSE'), nullable=False))`; `downgrade()` — `op.drop_column('workouts', 'hide_start_end')`. Без `IS_TEST`-guard (это колонка, не index/FK — нужна в тестовой БД тоже). - Verified: `python3 -c "import ast; ast.parse(open(...).read())"` → OK (exit 0). ## 2026-09-25 — TASK-HIDE-TRACK-4: trimTrack в публичном виде тренировки - `src/pages/workouts/WorkoutPublicItem.vue` — импорт `trimTrack` (`./components/TrimTrack`); в `.then()` `initWorkout`: при `d.workoutItem.hide_start_end` → `trimTrack(d.lineCoordinates, d.distances, d.data, totalDist)`, где `totalDist = d.distances[d.distances.length - 1] ?? 0`; иначе значения присваиваются как есть. `mapCenter` не меняется. Приватный вид не затронут. - `src/pages/workouts/Definitions.vue` — `WorkoutItem` + поле `hide_start_end: boolean` (восстановлено после потери в git stash). - Verified: `yarn build` exit 0 (vue-tsc 0 errors, vite ✓ 6.86s). - Known issue: `yarn lint` сломан системно — `tsutils.iterateComments is not a function` на всех .vue файлах (дефект окружения, не от этих изменений). ## 2026-09-24 — UI-testing skill + mandatory rule - `.roo/skills/ui-testing/SKILL.md` — how to test UI via (A) MCP browser ad-hoc and (B) the Playwright integration stand: run commands, spec conventions, selectors, gotchas. - `.roo/rules/ui-testing.md` — MANDATORY rule: after any `src/` change affecting a visible route/interaction the task is not done until `yarn lint` + `yarn build` + browser verification (MCP ad-hoc for quick/visual, Playwright spec for must-not-regress) + green `npx playwright test`. Includes run cheat-sheet and required reporting format. ## 2026-09-24 — Integration UI-test stand (Playwright + docker-compose) - Created `../integration/` (sibling of `frontend/`, `backend/`): - `docker-compose.yml` — 3 services: `db` (postgres:15, host:5433, schema `strava` via `db-init/01-schema.sql`), `backend` (python:3.12, `pip install -r requirements.txt` + `alembic upgrade head` + `python -m app.web.__main__`, port 8000, `DB_URI=postgresql+asyncpg://svcuser:svcpass@db:5432/svc`), `frontend` (node:20-alpine, `yarn install --frozen-lockfile --ignore-scripts` + `yarn dev --host 0.0.0.0 --port 5173`, env `VITE_HOST=http://localhost:8000`, `HUSKY=0`). - `package.json` + `playwright.config.ts` (`@playwright/test` ^1.54, baseURL `FRONTEND_URL||localhost:5173`, `API_URL`). - `tests/sidebar-auth-switch.spec.ts` — TASK-AUTH-NAV regression: publicRoutes (no «Тренировки») → UI login (`/auth/login`, `input[type=email]` + `input[type=password]` + button «Вход») → authRoutes («Тренировки» appears, no reload) → UI logout (profile dropdown → «Выход») → publicRoutes. Self-seeds a unique user via `POST /api/v0/signup` in `beforeAll`. - `README.md` — run instructions (`yarn up`, wait for backend, `yarn test`). - Frontend change: `src/main.ts` `HOST = import.meta.env.VITE_HOST || "https://cycle-rider.ru"` (production unaffected — env unset → falls back to prod host). - Verified: full stack up (frontend 200, backend 200 on `/api/v0/html_test/login`, db healthy); `npx playwright test tests/sidebar-auth-switch.spec.ts` → **1 passed (9.9s)** on Node 22 (nvm `~/.nvm/versions/node/v22.14.0/bin`). - Gotchas: (1) npm install in frontend container fails on `@unhead/vue` peer-dep (wants vite>=6, project vite 5) → must use **yarn** (`yarn.lock`), and `node:20-alpine` already ships yarn (do NOT `npm i -g yarn` → EEXIST); (2) backend health path is `/api/v0/html_test/login` (router_public prefix `/api/v0`, no `/auth`); (3) Vuestic `VaInput` uses floating labels → no usable placeholder, select login fields by `input[type=email]` / `input[type=password]`; (4) Playwright/MCP need Node ≥20 (system node is 18) → run via nvm Node 22. ## 2026-09-24 — TASK-AUTH-NAV-2: реактивные маршруты в сайдбаре и хлебных крошках - `NavigationRoutes.ts`: named exports `authRoutes`/`publicRoutes`, удалён module-level `localStorage.getItem("token")` тернарник. - `AppSidebar.vue`: computed `routes` от `globalStore.isAuthenticated`; `AppLayoutNavigation.vue`: `traverse` по `globalStore.isAuthenticated ? authRoutes : publicRoutes`. - Verified: `yarn lint` exit 0, `yarn build` exit 0 (vue-tsc 0 ошибок). ## 2026-09-24 — TASK-AUTH-NAV-1: auth-состояние в Pinia (реактивный isAuthenticated) - `src/stores/global-store.ts` — state: `isAuthenticated` (SSR-guard `typeof window !== "undefined"`), action `setAuthenticated(v: boolean)`. - `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)`. - Verified: `yarn lint` exit 0, `yarn build` exit 0 (vue-tsc 0 errors, vite ✓ 7.17s). ## 2026-09-19 — TASK-404: HTTP 404 для несуществующих страниц (SEO) - `server/index.ts` — catch-all 404 handler (`app.use` без path, перед `app.listen`): GET → `renderTemplate` (SEO-мета, canonical `/404`), non-GET → `res.status(404).send("Not Found")`. - `nginx.conf` — SPA whitelist location `~ ^/(workouts|auth|preferences|404)(/|$)` → `try_files $uri /index.html`; catch-all `location /` → `proxy_pass http://127.0.0.1:3001` (SSR 404 для неизвестных URL). - `src/router/index.ts` — catch-all redirect: `explore` → `404`. - `src/router/seo.ts` — `SEO_MAP["404"]` добавлен. - Verified: `yarn lint` exit 0, `yarn build` exit 0 (vue-tsc 0, vite ✓ 7.02s). ## 2026-09-19 — TASK-F11: SEO @unhead/vue dynamic title + meta description per route - `package.json` — `@unhead/vue` ^3.4.1 added to dependencies (`npm install @unhead/vue --legacy-peer-deps`; vite transitively bumped 4.5.5→5.4.21). - Created `src/router/seo.ts` — `setupSeo(router, head: Unhead)`: `SEO_MAP` keyed by route `name` (+ `""` for `/`), `DEFAULT_SEO` fallback, `head.push({ title, meta: [{ name: "description", content }] })` + `router.afterEach(() => update())`. Covers: `""`, `explore`, `routes`, `list_workouts`, `upload_workouts`, `workout_item`, `workout_public_item`, `preferences`, `login`, `signup`, `recover-password`. - `src/main.ts` — `import { createHead } from "unhead/client"`, `import { headSymbol } from "@unhead/vue"`; `const head = createHead(); app.provide(headSymbol, head); setupSeo(router, head);` right after `app.use(router)`. (NOT `app.use(head)` — `ClientUnhead` is not a Vue plugin; `@unhead/vue` v3 installs via `app.provide(headSymbol, head)`.) - `index.html` — `` set to «Cycle Rider — платформа для анализа велотренировок: мощность, пульс, скорость, каденс»; added `<meta name="description">` in `<head>` as fallback; moved Yandex.Metrika counter block from `<head>` to top of `<body>` (Vite 5 parse5 rejects `<div>/<img>` inside `<noscript>` within `<head>` → `disallowed-content-in-noscript-in-head`). - Verified: `npm run build` exit 0 (vue-tsc 0 errors, vite build ✓ 6.65s); `npm run lint` exit 0. - Tech note: `@unhead/vue` v3 API — `createUnhead` is in `unhead` core (`createHead` from `unhead/client` adds DOM renderer); the `Unhead` type is exported from `unhead/types`. `@unhead/vue` itself only re-exports `useHead`/`useSeoMeta`/`injectHead`/`headSymbol` — no `createHead` from it. ## 2026-09-19 — TASK-PUBLIC-ROUTE-FIX: workout_public_item moved to top-level (no auth) - `src/router/index.ts`: removed nested `workout_public_item` from `workouts` children; added top-level route `/public/workouts/:id` before catch-all, outside AppLayout. - Result: `/public/workouts/:id` accessible without auth, aligns with SSR endpoint + nginx proxy. - Verified: `yarn lint` exit 0, `yarn build` exit 0. ## 2026-09-18 — TASK-SSR-3: Sitemap, robots.txt, nginx, Dockerfile, guards - Modified: `server/index.ts` (+49 lines: `GET /sitemap.xml` — XML with 3 static URLs + workout URLs from API, `Content-Type: application/xml`; `GET /robots.txt` — Disallow `/auth/ /workouts/ /preferences/ /admin/`, Sitemap line, `Content-Type: text/plain`). - Modified: `nginx.conf` — full rewrite: SSR proxy locations for `/explore|/routes|/sitemap.xml|/robots.txt` and `/public/workouts/`; `location = /` with `hascookie token` (no cookie → proxy, cookie → static); `/assets/` with 30d immutable cache; SPA fallback `try_files $uri /index.html`. - Modified: `Dockerfile` — multi-stage (node:20-alpine): build stage (yarn install + yarn build), runtime stage (nginx + node, copies dist/, server/, node_modules/, package.json; CMD `npx tsx server/index.ts & nginx -g 'daemon off;'`). - Modified: `run.sh` — `cd /app && npx tsx server/index.ts & nginx -g 'daemon off;'`. - Modified: `package.json` — `tsx` moved from devDependencies to dependencies. - Modified: `src/stores/user-store.ts` — `typeof window === "undefined"` guard in `state()` (returns empty state on SSR). - Modified: `src/main.ts` — `typeof window !== "undefined" &&` guard before `localStorage.getItem("token")` (line 110). - Verified: `yarn lint` exit 0, `yarn build` exit 0 (vue-tsc 0 errors, vite 6.68s). Curl: `/robots.txt` → 200 + correct text; `/sitemap.xml` → 200 + valid XML (3 static + 2 workout URLs); `/health` → ok. ## 2026-09-18 — TASK-SSR-2: SSR route handlers - Modified: `server/index.ts` — 4 route handlers: `GET /` (landing, 302→/explore if `token` cookie), `GET /explore` (public feed via `getPublicWorkouts()`), `GET /public/workouts/:id` (detail via `getPublicWorkout()`, 404 on API 404, 500 on other errors), `GET /routes` (static SEO). `app.disable("x-powered-by")`. Helpers: `getAssetTagsSafe()` (falls back to dev script tag if no `dist/index.html`), `escapeHtml`, `formatDate` (DD.MM.YYYY), `formatDuration` (X ч. Y мин.), `formatDistance` (m→km 1 decimal), `formatSpeed` (m/s→km/h rounded). Canonical base: `SSR_BASE_URL` || `https://cycle-rider.ru`. - Verified: `yarn lint` exit 0, `yarn build` exit 0 (vue-tsc 0 errors). Curl: `/` → `<h1>Cycle Rider — анализ велотренировок</h1>`; `/routes` → `<h1>Конструктор маршрутов</h1>`; `/public/workouts/test` → 500 (API unreachable/404→500 acceptable locally); `/explore` → 200; `/` with `token` cookie → 302 → `/explore`; `/health` → ok. ## 2026-09-18 — TASK-SSR-1: SSR server skeleton - Created: `server/index.ts`, `server/template.ts`, `server/api.ts`. - Modified: `package.json` (+express, +@types/express, +tsx, +3 scripts). - `tsconfig.json` unchanged (server/ already excluded via `include` glob). - Verified: `yarn lint` exit 0, `yarn build` exit 0, `npx tsx server/index.ts` + curl /health OK. ## What works (verified at Memory Bank init) - `yarn dev` / `yarn build` / `yarn lint` scripts defined; build runs `vue-tsc --noEmit` type-check. - App boots: axios providers, Pinia, router, i18n, Vuestic, Yandex Maps, optional GTM (`src/main.ts`). - Auth flow: JWT Bearer interceptor, 401 -> logout redirect, startup `/api/v0/auth/check`. - Pages: workout feed (dashboard), workout list/detail/public detail, upload, routes, preferences, auth pages, 404. - Charts (Chart.js) and Yandex Maps route rendering for workout data. ## Known issues / tech debt (observed, not yet fixed) - `HOST`, Yandex Maps API key and GTM keys hardcoded in `src/main.ts` (no env-driven base URL). - `GetWorkout.ts` uses mutable module-level `let` variables shared across calls (state leakage risk) — refactor to pure functions returning state if touched. - `user-store.ts` embeds a huge base64 avatar blob in source. - No test suite; no global router auth guard (relies on 401 interceptor). - Mixed callback (`.then/.catch`) and imperative styles in pages. - ~~**BLOCKER (2026-09-04, TASK-DEPS-UPDATE-W1)**~~ — **RESOLVED by W0a (build) + W0b (lint)**: both `yarn build` and `yarn lint` are green; W1/W2/W3 unblocked. - `vue/no-mutating-props` in `workouts/components/WorkoutItem.vue` (checkbox/name edits mutate the prop object in place) — suppressed file-targeted in `eslint.config.mjs`; proper fix = local state extraction, needs its own task. - `src/main.ts` axios interceptors loosely typed (`any`) — suppressed file-targeted; pre-existing documented debt. - ~~**`typescript` capped at 5.4.5** (TASK-DEPS-UPDATE-W1)~~ — **RESOLVED by W2b**: `typescript` now 5.8.3 (`"5.8"` pinned no-caret in package.json), `vue-tsc` 2.2.12 (`^2`). vue-tsc 2.x officially supports TS up to 5.8 — do NOT go to TS 5.9. - **`sass` left at 1.69.5** (TASK-DEPS-UPDATE-W1): latest 1.x (1.104.0) requires node ≥20.19.0. Docker is now node:22, so a sass bump is viable — verify the local dev node version first; small follow-up task. - **Stale `package-lock.json`** in repo (npm workflow abandoned for yarn in W2c-FIX) — remove as a small cleanup task. - **yarn v1 nested-stale dirs**: after major bumps, `node_modules` can keep leftover nested packages (observed: `@typescript-eslint/*@7` under `typescript-eslint/`) causing transient CJS resolution glitches — fix with a clean `rm -rf node_modules && yarn install`; not reproducible from the lockfile. - Installed yarn 1.22.22 binary lacks the `up` alias — use `yarn upgrade` (verified equivalent). ## Milestones ### 2026-09-19 — SSR PHASE COMPLETE (TASK-SSR-0 → SSR-3 + PUBLIC-ROUTE-FIX) - **SSR-0**: Renamed `/dashboard` → `/explore` across router, nav, auth pages, layout guard. Legacy redirect `/dashboard` → `explore`. - **SSR-1**: Created `server/` (Express on :3001): `index.ts` (app + /health), `template.ts` (SEO HTML shell with JSON-LD, OG, canonical), `api.ts` (getPublicWorkouts/getPublicWorkout). - **SSR-2**: 4 SSR route handlers: `GET /` (landing/302), `GET /explore`, `GET /public/workouts/:id`, `GET /routes`. Helpers for formatting. - **SSR-3**: `GET /sitemap.xml`, `GET /robots.txt`. Nginx rewrite (SSR proxy + SPA fallback). Dockerfile multi-stage (node:20-alpine + nginx). tsx → dependencies. SSR guards in user-store/main.ts. - **PUBLIC-ROUTE-FIX**: `workout_public_item` moved to top-level router (outside AppLayout) — accessible without auth, aligns with SSR + nginx. - **Final state**: SSR renders `/`, `/explore`, `/routes`, `/public/workouts/:id`, `/sitemap.xml`, `/robots.txt` with full SEO meta + JSON-LD. Nginx proxies these to :3001; all else → SPA. Docker: multi-stage, `npx tsx server/index.ts & nginx`. - Verified: lint 0, build 0, docker build 0, curl all SSR endpoints 200. ### 2026-09-05 — TASK-DEPS-UPDATE-W0a: baseline `yarn build` green (uncommitted) - Replaced `// @ts-ignore` with non-null assertions (`data!.labels!`) in `LineWithLineChart.ts` `draw()` — eliminates the latent TS2532 and the `ban-ts-comment` lint error for that file. - Verified: `vue-tsc --noEmit` 0 errors, `vite build` OK (1309 modules). - The two SFC "parsing errors" were confirmed eslint-only (vite/vue-tsc parse both files fine) — deferred to W0b along with remaining lint errors. - Note: on the clean tree the build was already green (TS2532 was suppressed by the `@ts-ignore`); the fix is still required because stricter TS/vue-tsc in W2 would re-surface it without the suppression. ### 2026-09-05 — TASK-DEPS-UPDATE-W0b: `yarn lint` green (uncommitted) - Rewrote `eslint.config.mjs` (flat config): - TS parser wired into `.vue` files (`languageOptions.parserOptions.parser = tsParser` from `typescript-eslint` CJS default import) — this was the root cause of all ~20 SFC parse errors (vue flat preset leaves espree as inner parser). - `@typescript-eslint/no-unused-vars` global `argsIgnorePattern: "^_"`. - File-targeted overrides only (no inline eslint-disable anywhere): `src/main.ts` (any + unused, documented exception), `src/pages/**/*.vue` (multi-word names), `Logout.vue` (valid-template-root), `components/WorkoutItem.vue` (no-mutating-props). - Code fixes (21 files, type-only / dead-code / unused-cleanup): unused import in `router/index.ts`; `any`→`unknown`/`Event`/structural types across `services/utils.ts`, auth pages, workouts pages, `AppLayoutNavigation.vue`; `@ts-ignore`→`@ts-expect-error` in `LineWithLineChart.ts`; removed dead code (`VuesticLogo` computed, `PreferencesHeader.readFile`, `Login.HOST`, `AppNavbarActions.t`, `Logout.push`); removed redundant `v-if` on `v-for` in `Feed.vue`/`WorkoutList.vue`; null-safe error handling in `CheckTheEmail.vue` (fixes TS18048/TS18046 surfaced by the new strict typing); `AppSidebar` name → `AppSidebar`; added `:key` to `v-for` marker in `components/WorkoutItem.vue`. - Prettier auto-reformatted ~30 `src/` files via `prelint` — formatting changes are part of the intended clean diff. - Verified: `yarn lint` 0 errors (exit 0), `yarn build` green (`vue-tsc --noEmit` 0 errors, vite build OK). ### 2026-09-05 — TASK-DEPS-UPDATE-W1: patch/minor dependency refresh via `yarn upgrade` (uncommitted) - Ran `yarn upgrade <all direct deps except sass>` (yarn 1.22.22 binary has no `up` alias). Baseline was green before the run (lint 0 errors, build green). - Result: `package.json` unchanged; `yarn.lock` refreshed — 61 direct deps verified, none moved to a new major. Notable bumps: typescript 5.2.2→5.4.5 (capped), postcss 8.4.31→8.5.28, axios 1.7.7→1.20.0, chart.js 4.4.4→4.5.1, eslint 8.57.0→8.57.1, typescript-eslint 7.6.0→7.18.0, @typescript-eslint/\* 6.11.0→6.21.0, prettier 3.1.0→3.9.6, tailwindcss 3.4.1→3.4.19, vite 4.5.5→4.5.14, vue-i18n 9.6.5→9.14.5, vue-router 4.2.5→4.6.4, vue-yandex-maps 2.1.4→2.3.3, vuestic-ui 1.9.0→1.10.3, storybook suite →7.6.24, pinia 2.1.7→2.3.1. - Two caps applied (rule 5): `typescript` pinned to 5.4.5 in the lock (vue-tsc 1.8.27 incompatible with TS ≥5.5; `yarn add -D typescript@5.4.5` then `git checkout -- package.json` + 1-line lock key rename `typescript@5.4.5`→`typescript@^5.2.2`); `sass` excluded from the upgrade (engine node ≥20.19.0 vs baseline 18.19.1). - Verified after upgrade: `yarn install --frozen-lockfile` clean; `yarn lint` 0 errors; `yarn build` green (vue-tsc 0 errors, vite build OK ~7.3s). - No `src/` changes in W1; no HOST/Yandex/GTM changes. ### 2026-09-05 — TASK-DEPS-UPDATE-W2a: vue 3.3.9 → 3.5.42 + pinia 2 → 3.0.4 (uncommitted) - `yarn add vue@^3.5 pinia@^3`: `package.json` changed exactly two lines (`vue: 3.3.9 → ^3.5`, `pinia: ^2.1.7 → ^3`); lockfile resolved `vue@3.5.42`, `pinia@3.0.4`. No other direct dep moved to a new major (new lock entries are only the vue 3.5 / pinia 3 subtrees). - Vue 3.5 template type-check surfaced 6× TS2322 (`number[]` → ymaps `LngLat` tuple) in the `vue-yandex-maps` `:settings` props of `src/pages/routes/Route.vue` and `src/pages/workouts/components/WorkoutItem.vue`. Fixed type-only: `ref<LngLat>` in Route.vue; `import type { LngLat }` + 4 template `as LngLat`/`as LngLat[]` casts + `clickCoordinates` typed `ref<LngLat[]>` in components/WorkoutItem.vue. No runtime/prop-signature changes. - Verified: `yarn lint` 0 errors; `yarn build` green (vue-tsc 0 errors, vite build ~7.5s); `yarn dev` smoke-check OK (boot + key modules serve 200, no console/compile errors). ### 2026-09-05 — TASK-DEPS-UPDATE-W2b: typescript 5.4.5 → 5.8.3 + vue-tsc 1.8.27 → 2.2.12 (uncommitted) - `yarn add -D typescript@5.8 vue-tsc@^2`: `package.json` changed exactly the two lines (`typescript: ^5.2.2 → "5.8"`, `vue-tsc: ^1.8.22 → ^2`); lock resolved `typescript@5.8.3`, `vue-tsc@2.2.12`. No other direct dep moved to a new major (new lock entries are only the vue-tsc 2.x subtree: `@vue/language-core@2.2.12`, `@volar/*@2.4.15`, `muggle-string`, `alien-signals`, `vscode-uri`, `@vue/compiler-vue2`). - vue-tsc 2 + TS 5.8 surfaced 4 type errors in 2 file-upload pages (TS 5.8 `Blob` gained required `bytes` property + definite-assignment TS2454 on the uninitialized `let file`). Fixed type-only: legacy 14-line inline structural `file` type → `let file: File | undefined = undefined` in both files; `formData.append("file", file!)` non-null assertion in `PreferencesHeader.vue` (`WorkoutUpload.vue` already guards `if (file == undefined) return`). No runtime/prop-signature changes. - Verified: baseline green before update; after update `yarn lint` 0 errors, `yarn build` green (vue-tsc 2.2.12 --noEmit 0 errors, vite build ~7.3s). ### 2026-09-05 — TASK-DEPS-UPDATE-W2c: vite 4.5.14 → 5.4.21 + @vitejs/plugin-vue 4.6.2 → 5.2.4 (uncommitted) - `yarn add -D vite@^5 @vitejs/plugin-vue@^5`: `package.json` changed exactly the two lines (`vite: ^4.4.6 → ^5`, `@vitejs/plugin-vue: ^4.2.3 → ^5`); lock resolved `vite@5.4.21`, `@vitejs/plugin-vue@5.2.4`. No other direct dep moved to a new major — new lock entries are only the vite 5 subtree (`rollup@4.63.1` + `@rollup/*` platform binaries, `esbuild@0.21.5` + `@esbuild/*` platform binaries, `@napi-rs/lzma-linux-x64-gnu`); storybook pin `@vitejs/plugin-vue@^4.0.0` still resolves 4.6.2 (separate lock entry); `esbuild@0.18.20`/`rollup@3.30.0` retained for storybook. - No `src/` or `vite.config.ts` changes — config API-compatible between vite 4 and 5. - Warnings: CJS Node API deprecation (vite 5, expected, documented, not fixed); NO sass legacy-API warnings appeared (sass 1.69.5 untouched, per task rule). - Verified: baseline green before update; after update `yarn lint` 0 errors, `yarn build` green (vue-tsc 0 errors, vite 5.4.21 build ~6.5s, 1396 modules); `yarn dev` smoke-check OK (VITE v5.4.21 ready 372 ms; `GET /`, `/src/main.ts`, `/src/App.vue`, `/src/pages/workouts/Feed.vue` all 200; server stopped after check). ### 2026-09-05 — TASK-DEPS-UPDATE-W2c-FIX: Dockerfile switched to yarn (uncommitted) - `Dockerfile` only (2 lines): `COPY package.json package-lock.json ./` → `COPY package.json yarn.lock ./`; `RUN npm install` → `RUN yarn install --frozen-lockfile`. Root cause fixed: the Docker build ran `npm install` against a stale `package-lock.json` while dev uses yarn; npm's strict peer validation failed on `@storybook/vue3-vite@7.6.20` → `@vitejs/plugin-vue@^4` vs root `^5`. yarn 1 resolves this via a nested plugin-vue 4.6.2 for storybook (existing separate lock entry). - `node:18` base image ships yarn 1.22.22 out of the box (verified via `docker run --rm node:18 yarn --version`) — no `npm i -g yarn@1` line added. - `package-lock.json` intentionally left in the repo (removal = separate decision, out of scope). - Verified: `yarn install --frozen-lockfile` clean locally (lockfiles untouched per `git status`); `yarn lint` 0 errors; `yarn build` green (vue-tsc 0 errors, vite 5.4.21 ~6.4s); **full `docker build` green** (image `strava-frontend-w2cfix` built: yarn install + npm run build + nginx steps all OK). ### 2026-09-05 — TASK-DEPS-UPDATE-W2d: dead Storybook deps removed (uncommitted) - `yarn remove` of 9 packages: `storybook`, `@storybook/addon-essentials`, `@storybook/addon-interactions`, `@storybook/addon-links`, `@storybook/blocks`, `@storybook/testing-library`, `@storybook/vue3`, `@storybook/vue3-vite`, `eslint-plugin-storybook` + manual removal of the 2 scripts (`storybook`, `build-storybook`) from `package.json`. - `eslint.config.mjs` verified: zero storybook references — untouched. - `yarn.lock` pruned: `grep -c storybook` → 0; nested `@vitejs/plugin-vue@4.6.2` (storybook peer-conflict workaround) gone — only `@vitejs/plugin-vue@^5` → 5.2.4 remains. - Baseline green before removal; after: `yarn install` clean, `yarn lint` 0 errors, `yarn build` green (vue-tsc 0 errors, vite 5.4.21 ~6.3s, 1396 modules). - No `src/` changes; `package-lock.json` left stale (separate decision). ### 2026-09-05 — TASK-DEPS-UPDATE-W3 (FINAL): eslint 8→9 + typescript-eslint 7→8 (uncommitted) - `package.json` devDeps: `eslint` ^8.57.0→**^9** (resolved 9.39.5), `typescript-eslint` ^7.6.0→**^8** (resolved 8.69.0); REMOVED `@typescript-eslint/eslint-plugin@^6.11.0` + `@typescript-eslint/parser@^6.11.0` (not imported by `eslint.config.mjs` — verified). No other package moved to a new major (lock checked: vue 3.5.42, pinia 3, TS 5.8.3, vue-tsc 2.2.12, vite 5.4.21, sass 1.69.5). - `eslint.config.mjs`: **unchanged** — flat config (CJS default-import of the `typescript-eslint` meta-package, `configs.recommended` spread, `tseslintPkg.parser`) works as-is on eslint 9 / typescript-eslint 8. - `Dockerfile`: base image `node:18` → `node:22` (transitive `brace-expansion@5.0.9` / `eslint-visitor-keys@5` engines require node ≥20; node 18 EOL). Local dev verified on node 22.14.0. - Verified: baseline green at wave start; after — `yarn lint` 0 errors, `yarn build` green (vue-tsc 0 errors, vite 5.4.21 ~5.3s), `docker build --pull --no-cache` green (image `strava-frontend-w3`, `yarn install --frozen-lockfile` clean inside image). - No `src/` changes; `vite.config.ts`, i18n, HOST/keys untouched. W3 = FINAL wave → TASK-DEPS-UPDATE ready to close. ### 2026-09-05 — TASK-DEPS-UPDATE CLOSED (parent) - All waves W0a → W3 completed and Architect-verified. Final stack: vue 3.5.42, pinia 3.0.4, TS 5.8.3, vue-tsc 2.2.12, vite 5.4.21, @vitejs/plugin-vue 5.2.4, eslint 9.39.5, typescript-eslint 8.69.0, axios 1.20.0, tailwind 3.4.19, vuestic 1.10.3, node:22 (Docker). Storybook fully removed. - End state: `yarn lint` 0 errors, `yarn build` green, `docker build` green. Runtime behavior unchanged (type-level + config + lockfile only). - Lessons captured in `systemPatterns.md`: TS parser for `.vue` in flat eslint config; `LngLat` typing contract for vue-yandex-maps; vue-tsc 2 ↔ TS ≤5.8 ceiling; yarn-only workflow; `ban-ts-comment` enforcement. ## Left to build (small follow-ups, no urgency) - Remove stale `package-lock.json` from the repo (npm workflow abandoned in W2c-FIX). - Bump `sass` to latest 1.x — Docker (node:22) is ready; check local dev node ≥20.19 first. - Consider vite 6/7 after the ecosystem settles (own breaking changes). - `GetWorkout.ts` module-level `let` refactor to pure functions returning state. - `components/WorkoutItem.vue` prop mutation → local state extraction (unblock the `no-mutating-props` suppression). ### 2026-09-12 — TASK-F1: workout photos gallery + upload/delete + map markers (uncommitted) - `src/pages/workouts/Definitions.vue`: `WorkoutPhoto` type (`{id, url, size, latitude: number|null, longitude: number|null}`) + `photos?: Array<WorkoutPhoto>` in `WorkoutItem`. No fetch changes (backend already sends `workout.photos` via `GET /workouts/{id}` / public / list). - `src/pages/workouts/components/WorkoutItem.vue`: local `photos` ref initialized from `workoutItem?.photos`; `photosWithCoords` computed (non-null lat/lon); photo markers as `<yandex-map-default-marker>` with `coordinates: [lon, lat] as LngLat` (same order as `clickCoordinates`), `color: 'blue'`, `onClick` → `window.open(photo.url, "_blank")`. - Same file: `#workout-photos` section under the map/short-data container — grid of `<img :src="photo.url">` (auto-fill 160px, 120px cover), «Добавить фото» (`v-if="isPrivate"`, hidden `input[type=file] accept="image/*" multiple`, `axiosAuth.post` multipart field `file`, `Promise.allSettled`, success → push to `photos` + toast, partial failure → error toast, VaButton `:loading`), per-photo «Удалить» (`v-if="isPrivate"`, `axiosAuth.delete` → filter from `photos`, error toast). `isPrivate` added to `defineProps` destructure. Styles: `.workout-photos-*` block. - `WorkoutListItem.vue` untouched (per scope). - Verified: `npm run build` exit 0 (vue-tsc --noEmit 0 errors, vite `✓ built in 11.71s`; pre-existing >500kB chunk warning), `npm run lint` exit 0 (prettier reformatted the component, eslint 0 errors). ### 2026-09-17 — TASK-LINK1: «Ссылки на описание» скрыта на публичной странице без ссылки (uncommitted) - `src/pages/workouts/components/WorkoutItem.vue` (единственный файл, 1 строка): на внешний `div.workout-item-params` секции «Ссылки на описание» добавлен `v-if="dzenLink || isPrivate"`. Поведение: публичная без ссылки → секция скрыта; публичная со ссылкой → только «Дзен»; приватная без ссылки → «Добавить» (модалка доступна); приватная со ссылкой → «Дзен» + иконка edit. Внутренняя логика секции не тронута. - Verified: `yarn lint` exit 0, `yarn build` exit 0 (vue-tsc --noEmit 0 ошибок, vite `✓ built in 5.41s`; pre-existing >500kB chunk warning). ### 2026-09-18 — TASK-SSR-0: rename /dashboard → /explore + legacy redirect (uncommitted) - `src/router/index.ts`: маршрут `name: "explore"`, `path: "explore"` (Feed.vue); catch-all и admin redirect → `{ name: "explore" }`; новый legacy redirect `{ path: "/dashboard", redirect: { name: "explore" } }` (до catch-all). - `src/components/sidebar/NavigationRoutes.ts`: `name: "dashboard"` → `"explore"` в `authRoutes` и `publicRoutes` (i18n-ключ `menu.dashboard` сохранён). - `src/components/app-layout-navigation/AppLayoutNavigation.vue`: breadcrumb `:to="{ name: 'explore' }"`. - `src/layouts/AppLayout.vue`: auth-guard `useRoute().path != "/explore"`. - `src/pages/auth/Login.vue` (2×), `src/pages/auth/Signup.vue` (2×), `src/pages/auth/CheckTheEmail.vue` (1×): `push({ name: "explore" })`. - `src/pages/workouts/Feed.vue`: изменений не потребовалось (ссылок на dashboard нет). - Verified: `yarn lint` exit 0, `yarn build` exit 0 (vue-tsc --noEmit 0 ошибок, vite `✓ built in 6.99s`; pre-existing >500kB chunk warning). ## TASK-HIDE-TRACK-T1 (2026-09-25) - `../backend/tests/test_api_workout.py`: добавлен `test_workout_hide_start_end` (PATCH hide_start_end False→True→False + GET-верификация) - Результат: `python -m pytest tests/test_api_workout.py -v` → **4 passed (5.13s)**