# Progress — Strava Frontend ## 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).