# Progress — Strava Frontend
## 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: `/` → `
Cycle Rider — анализ велотренировок
`; `/routes` → `Конструктор маршрутов
`; `/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-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 ` (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` in Route.vue; `import type { LngLat }` + 4 template `as LngLat`/`as LngLat[]` casts + `clickCoordinates` typed `ref` 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` 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 `` 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 `
` (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).