strava-frontend/.roo/memory-bank/progress.md

32 KiB
Raw Blame History

Progress — Strava Frontend

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 — <title> 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).
  • 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)