43 KiB
43 KiB
Progress — Strava Frontend
2026-09-26 — TASK-SECTION-REZOOM-LOOP: фикс бесконечного цикла re-render (CPU-шум)
src/pages/workouts/components/WorkoutItem.vue:- Новый флаг
let pendingRezoom = false;(рядом сchartRefs, ~строка 526). switchXAxis: послеchartRefs.length = 0добавленоpendingRezoom = true.setChartRef/tryFinalize:applySelectedRangeZoom()вызывается только еслиpendingRezoom === true(затем флаг сбрасывается в false). Устранён цикл: клик → areaAvgData → re-render → setChartRef → applySelectedRangeZoom → broadcastZoom → update("none") → reactivity → re-render → setChartRef → ... (бесконечно).
- Новый флаг
- Verified:
yarn buildexit 0;npx playwright test tests/section-duration.spec.ts→ 3 passed (13.7s).
2026-09-26 — TASK-SECTION-DIST-FIX: строка «Дистанция» + drag-zoom персистится при смене вкладки
src/pages/workouts/components/WorkoutItem.vue:computeAreaData: добавлена строкаavgData["Дистанция"]=distances[end-1] - distances[start](формат: >=1000 → "X.X км", иначе "X м"). Расположена после «Продолжительность», перед циклом поchartConfigs.onZoomComplete:selectedRange.value = null→selectedRange.value = { startIdx: start, endIdx: end }— drag-zoom теперь сохраняет диапазон индексов, иapplySelectedRangeZoom()(изsetChartRefна remount) повторно применяет zoom после смены вкладки.
- Verified:
yarn buildexit 0;npx playwright test tests/section-duration.spec.ts→ 3 passed.
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 owneritest-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/maxvs полный диапазон (isZoomedOrPanned()всегда false — zoom применяется прямой записью, минуя плагин).- Bug 1 (
src/pages/workouts/components/WorkoutItem.vue,setChartRef~строка 1155): ref пушится сразу (guardr.chartубран — Chart.js создаёт инстанс асинхронно); финализация (group.charts = collectCharts()+applySelectedRangeZoom()) черезtryFinalize(retries)— retry черезnextTick(до 20 попыток) до тех пор, пока все рефы не получатchart.nextTickдобавлен в импорт из "vue". Корень бага: на remount послеswitchXAxissetChartRefсрабатывал до создания 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), guardisBroadcasting()в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'sGET /api/v0/workouts), (3) «Сделать публичной» →GET /api/v0/public/workouts/{id}=200, (4) «Скрыть начало и конец трека» → authed detailworkout.hide_start_end===true, (5) «Добавить фото» →<img>в.workout-photos-grid.- Ключевое: стабильный owner-юзер для Части 2 (глобальный content-hash dedup vs scoped
GET /workouts); unique-юзер через UI для Части 1. Detail URL/workouts/workouts/:id, navdomcontentloaded. ../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 buildexit 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)→ PATCHhide_start_end(guard + catch-тост, паттернchangePublic)
- i18n: секция
workout+ ключhide_start_endдобавлены во все 6 локалей (br, cn, es, gb, ir, ru) — секция отсутствовала. - Verified:
yarn buildexit 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 buildexit 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 anysrc/change affecting a visible route/interaction the task is not done untilyarn lint+yarn build+ browser verification (MCP ad-hoc for quick/visual, Playwright spec for must-not-regress) + greennpx playwright test. Includes run cheat-sheet and required reporting format.
2026-09-24 — Integration UI-test stand (Playwright + docker-compose)
- Created
../integration/(sibling offrontend/,backend/):docker-compose.yml— 3 services:db(postgres:15, host:5433, schemastravaviadb-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, envVITE_HOST=http://localhost:8000,HUSKY=0).package.json+playwright.config.ts(@playwright/test^1.54, baseURLFRONTEND_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 viaPOST /api/v0/signupinbeforeAll.README.md— run instructions (yarn up, wait for backend,yarn test).
- Frontend change:
src/main.tsHOST = 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/vuepeer-dep (wants vite>=6, project vite 5) → must use yarn (yarn.lock), andnode:20-alpinealready ships yarn (do NOTnpm i -g yarn→ EEXIST); (2) backend health path is/api/v0/html_test/login(router_public prefix/api/v0, no/auth); (3) VuesticVaInputuses floating labels → no usable placeholder, select login fields byinput[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 exportsauthRoutes/publicRoutes, удалён module-levellocalStorage.getItem("token")тернарник.AppSidebar.vue: computedroutesотglobalStore.isAuthenticated;AppLayoutNavigation.vue:traverseпоglobalStore.isAuthenticated ? authRoutes : publicRoutes.- Verified:
yarn lintexit 0,yarn buildexit 0 (vue-tsc 0 ошибок).
2026-09-24 — TASK-AUTH-NAV-1: auth-состояние в Pinia (реактивный isAuthenticated)
src/stores/global-store.ts— state:isAuthenticated(SSR-guardtypeof window !== "undefined"), actionsetAuthenticated(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 lintexit 0,yarn buildexit 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-alllocation /→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 lintexit 0,yarn buildexit 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_MAPkeyed by routename(+""for/),DEFAULT_SEOfallback,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 afterapp.use(router). (NOTapp.use(head)—ClientUnheadis not a Vue plugin;@unhead/vuev3 installs viaapp.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 buildexit 0 (vue-tsc 0 errors, vite build ✓ 6.65s);npm run lintexit 0. - Tech note:
@unhead/vuev3 API —createUnheadis inunheadcore (createHeadfromunhead/clientadds DOM renderer); theUnheadtype is exported fromunhead/types.@unhead/vueitself only re-exportsuseHead/useSeoMeta/injectHead/headSymbol— nocreateHeadfrom it.
2026-09-19 — TASK-PUBLIC-ROUTE-FIX: workout_public_item moved to top-level (no auth)
src/router/index.ts: removed nestedworkout_public_itemfromworkoutschildren; added top-level route/public/workouts/:idbefore catch-all, outside AppLayout.- Result:
/public/workouts/:idaccessible without auth, aligns with SSR endpoint + nginx proxy. - Verified:
yarn lintexit 0,yarn buildexit 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.txtand/public/workouts/;location = /withhascookie token(no cookie → proxy, cookie → static);/assets/with 30d immutable cache; SPA fallbacktry_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; CMDnpx 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—tsxmoved from devDependencies to dependencies. - Modified:
src/stores/user-store.ts—typeof window === "undefined"guard instate()(returns empty state on SSR). - Modified:
src/main.ts—typeof window !== "undefined" &&guard beforelocalStorage.getItem("token")(line 110). - Verified:
yarn lintexit 0,yarn buildexit 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 iftokencookie),GET /explore(public feed viagetPublicWorkouts()),GET /public/workouts/:id(detail viagetPublicWorkout(), 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 nodist/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 lintexit 0,yarn buildexit 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;/withtokencookie → 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.jsonunchanged (server/ already excluded viaincludeglob).- Verified:
yarn lintexit 0,yarn buildexit 0,npx tsx server/index.ts+ curl /health OK.
What works (verified at Memory Bank init)
yarn dev/yarn build/yarn lintscripts defined; build runsvue-tsc --noEmittype-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 insrc/main.ts(no env-driven base URL).GetWorkout.tsuses mutable module-levelletvariables shared across calls (state leakage risk) — refactor to pure functions returning state if touched.user-store.tsembeds 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): bothyarn buildandyarn lintare green; W1/W2/W3 unblocked.vue/no-mutating-propsinworkouts/components/WorkoutItem.vue(checkbox/name edits mutate the prop object in place) — suppressed file-targeted ineslint.config.mjs; proper fix = local state extraction, needs its own task.src/main.tsaxios interceptors loosely typed (any) — suppressed file-targeted; pre-existing documented debt.— RESOLVED by W2b:typescriptcapped at 5.4.5 (TASK-DEPS-UPDATE-W1)typescriptnow 5.8.3 ("5.8"pinned no-caret in package.json),vue-tsc2.2.12 (^2). vue-tsc 2.x officially supports TS up to 5.8 — do NOT go to TS 5.9.sassleft 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.jsonin repo (npm workflow abandoned for yarn in W2c-FIX) — remove as a small cleanup task. - yarn v1 nested-stale dirs: after major bumps,
node_modulescan keep leftover nested packages (observed:@typescript-eslint/*@7undertypescript-eslint/) causing transient CJS resolution glitches — fix with a cleanrm -rf node_modules && yarn install; not reproducible from the lockfile. - Installed yarn 1.22.22 binary lacks the
upalias — useyarn upgrade(verified equivalent).
Milestones
2026-09-19 — SSR PHASE COMPLETE (TASK-SSR-0 → SSR-3 + PUBLIC-ROUTE-FIX)
- SSR-0: Renamed
/dashboard→/exploreacross 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_itemmoved 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.txtwith 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-ignorewith non-null assertions (data!.labels!) inLineWithLineChart.tsdraw()— eliminates the latent TS2532 and theban-ts-commentlint error for that file. - Verified:
vue-tsc --noEmit0 errors,vite buildOK (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
.vuefiles (languageOptions.parserOptions.parser = tsParserfromtypescript-eslintCJS 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-varsglobalargsIgnorePattern: "^_".- 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).
- TS parser wired into
- Code fixes (21 files, type-only / dead-code / unused-cleanup): unused import in
router/index.ts;any→unknown/Event/structural types acrossservices/utils.ts, auth pages, workouts pages,AppLayoutNavigation.vue;@ts-ignore→@ts-expect-errorinLineWithLineChart.ts; removed dead code (VuesticLogocomputed,PreferencesHeader.readFile,Login.HOST,AppNavbarActions.t,Logout.push); removed redundantv-ifonv-forinFeed.vue/WorkoutList.vue; null-safe error handling inCheckTheEmail.vue(fixes TS18048/TS18046 surfaced by the new strict typing);AppSidebarname →AppSidebar; added:keytov-formarker incomponents/WorkoutItem.vue. - Prettier auto-reformatted ~30
src/files viaprelint— formatting changes are part of the intended clean diff. - Verified:
yarn lint0 errors (exit 0),yarn buildgreen (vue-tsc --noEmit0 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 noupalias). Baseline was green before the run (lint 0 errors, build green). - Result:
package.jsonunchanged;yarn.lockrefreshed — 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):
typescriptpinned to 5.4.5 in the lock (vue-tsc 1.8.27 incompatible with TS ≥5.5;yarn add -D typescript@5.4.5thengit checkout -- package.json+ 1-line lock key renametypescript@5.4.5→typescript@^5.2.2);sassexcluded from the upgrade (engine node ≥20.19.0 vs baseline 18.19.1). - Verified after upgrade:
yarn install --frozen-lockfileclean;yarn lint0 errors;yarn buildgreen (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.jsonchanged exactly two lines (vue: 3.3.9 → ^3.5,pinia: ^2.1.7 → ^3); lockfile resolvedvue@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[]→ ymapsLngLattuple) in thevue-yandex-maps:settingsprops ofsrc/pages/routes/Route.vueandsrc/pages/workouts/components/WorkoutItem.vue. Fixed type-only:ref<LngLat>in Route.vue;import type { LngLat }+ 4 templateas LngLat/as LngLat[]casts +clickCoordinatestypedref<LngLat[]>in components/WorkoutItem.vue. No runtime/prop-signature changes. - Verified:
yarn lint0 errors;yarn buildgreen (vue-tsc 0 errors, vite build ~7.5s);yarn devsmoke-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.jsonchanged exactly the two lines (typescript: ^5.2.2 → "5.8",vue-tsc: ^1.8.22 → ^2); lock resolvedtypescript@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
Blobgained requiredbytesproperty + definite-assignment TS2454 on the uninitializedlet file). Fixed type-only: legacy 14-line inline structuralfiletype →let file: File | undefined = undefinedin both files;formData.append("file", file!)non-null assertion inPreferencesHeader.vue(WorkoutUpload.vuealready guardsif (file == undefined) return). No runtime/prop-signature changes. - Verified: baseline green before update; after update
yarn lint0 errors,yarn buildgreen (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.jsonchanged exactly the two lines (vite: ^4.4.6 → ^5,@vitejs/plugin-vue: ^4.2.3 → ^5); lock resolvedvite@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.0still resolves 4.6.2 (separate lock entry);esbuild@0.18.20/rollup@3.30.0retained for storybook.- No
src/orvite.config.tschanges — 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 lint0 errors,yarn buildgreen (vue-tsc 0 errors, vite 5.4.21 build ~6.5s, 1396 modules);yarn devsmoke-check OK (VITE v5.4.21 ready 372 ms;GET /,/src/main.ts,/src/App.vue,/src/pages/workouts/Feed.vueall 200; server stopped after check).
2026-09-05 — TASK-DEPS-UPDATE-W2c-FIX: Dockerfile switched to yarn (uncommitted)
Dockerfileonly (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 rannpm installagainst a stalepackage-lock.jsonwhile dev uses yarn; npm's strict peer validation failed on@storybook/vue3-vite@7.6.20→@vitejs/plugin-vue@^4vs root^5. yarn 1 resolves this via a nested plugin-vue 4.6.2 for storybook (existing separate lock entry).node:18base image ships yarn 1.22.22 out of the box (verified viadocker run --rm node:18 yarn --version) — nonpm i -g yarn@1line added.package-lock.jsonintentionally left in the repo (removal = separate decision, out of scope).- Verified:
yarn install --frozen-lockfileclean locally (lockfiles untouched pergit status);yarn lint0 errors;yarn buildgreen (vue-tsc 0 errors, vite 5.4.21 ~6.4s); fulldocker buildgreen (imagestrava-frontend-w2cfixbuilt: yarn install + npm run build + nginx steps all OK).
2026-09-05 — TASK-DEPS-UPDATE-W2d: dead Storybook deps removed (uncommitted)
yarn removeof 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) frompackage.json.eslint.config.mjsverified: zero storybook references — untouched.yarn.lockpruned: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 installclean,yarn lint0 errors,yarn buildgreen (vue-tsc 0 errors, vite 5.4.21 ~6.3s, 1396 modules). - No
src/changes;package-lock.jsonleft stale (separate decision).
2026-09-05 — TASK-DEPS-UPDATE-W3 (FINAL): eslint 8→9 + typescript-eslint 7→8 (uncommitted)
package.jsondevDeps: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 byeslint.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 thetypescript-eslintmeta-package,configs.recommendedspread,tseslintPkg.parser) works as-is on eslint 9 / typescript-eslint 8.Dockerfile: base imagenode:18→node:22(transitivebrace-expansion@5.0.9/eslint-visitor-keys@5engines require node ≥20; node 18 EOL). Local dev verified on node 22.14.0.- Verified: baseline green at wave start; after —
yarn lint0 errors,yarn buildgreen (vue-tsc 0 errors, vite 5.4.21 ~5.3s),docker build --pull --no-cachegreen (imagestrava-frontend-w3,yarn install --frozen-lockfileclean 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 lint0 errors,yarn buildgreen,docker buildgreen. Runtime behavior unchanged (type-level + config + lockfile only). - Lessons captured in
systemPatterns.md: TS parser for.vuein flat eslint config;LngLattyping contract for vue-yandex-maps; vue-tsc 2 ↔ TS ≤5.8 ceiling; yarn-only workflow;ban-ts-commentenforcement.
Left to build (small follow-ups, no urgency)
- Remove stale
package-lock.jsonfrom the repo (npm workflow abandoned in W2c-FIX). - Bump
sassto 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.tsmodule-levelletrefactor to pure functions returning state.components/WorkoutItem.vueprop mutation → local state extraction (unblock theno-mutating-propssuppression).
2026-09-12 — TASK-F1: workout photos gallery + upload/delete + map markers (uncommitted)
src/pages/workouts/Definitions.vue:WorkoutPhototype ({id, url, size, latitude: number|null, longitude: number|null}) +photos?: Array<WorkoutPhoto>inWorkoutItem. No fetch changes (backend already sendsworkout.photosviaGET /workouts/{id}/ public / list).src/pages/workouts/components/WorkoutItem.vue: localphotosref initialized fromworkoutItem?.photos;photosWithCoordscomputed (non-null lat/lon); photo markers as<yandex-map-default-marker>withcoordinates: [lon, lat] as LngLat(same order asclickCoordinates),color: 'blue',onClick→window.open(photo.url, "_blank").- Same file:
#workout-photossection under the map/short-data container — grid of<img :src="photo.url">(auto-fill 160px, 120px cover), «Добавить фото» (v-if="isPrivate", hiddeninput[type=file] accept="image/*" multiple,axiosAuth.postmultipart fieldfile,Promise.allSettled, success → push tophotos+ toast, partial failure → error toast, VaButton:loading), per-photo «Удалить» (v-if="isPrivate",axiosAuth.delete→ filter fromphotos, error toast).isPrivateadded todefinePropsdestructure. Styles:.workout-photos-*block. WorkoutListItem.vueuntouched (per scope).- Verified:
npm run buildexit 0 (vue-tsc --noEmit 0 errors, vite✓ built in 11.71s; pre-existing >500kB chunk warning),npm run lintexit 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 lintexit 0,yarn buildexit 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-guarduseRoute().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 lintexit 0,yarn buildexit 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)
TASK-SSR-EXPLORE-CARDS (2026-09-27)
server/index.ts: хэндлерGET /explore—content=<section class="news-feed">c<article class="news-item">(.news-item-header→<h3><a href="/public/workouts/{id}">,ul.metadata9 условных<li>в порядке клиента,.image-container-<a>cbackground-image); SEO-мета и catch→500 без изменений; импортWorkoutItemиз./api.server/index.ts: новый форматтерdistInt(m)=Math.round(m/1000)(послеformatSpeed, ~строка 52);formatDistanceне тронут.tsconfig.server.json(новый файл):module commonjs,outDir dist-server,strict,rootDir server,include server/**/*.ts— скриптserver:buildссылался на него, файла не было..gitignore: добавлена строкаdist-server.- Verified:
yarn server:build→ Done (0 ошибок);yarn build→ ✓ built in 6.58s (0 ошибок, pre-existing >500kB chunk warning). - Verified HTML:
curl http://localhost:3001/explore(SSR dev, production backend) —news-feed×1,news-item×3,metadata×3,image-container×3,<a href="/public/workouts/{id}", метки Дата/Фото/Скорость/Пульс/Расстояние/Продолжительность/Каденс/Мощность; «Ссылки» нет (у всех 3 workout'овexternal_links.valuesпустые — корректно, v-if). - Verified browser (MCP): 3 карточки (
articlec<h3>-ссылкой +list«Дата: …»), title SSR-заголовка; console — только 3 pre-existing 404 на статические ассеты (dev SSR не сервитdist/; в prod — nginx), app-ошибок нет.