34 KiB
Active Context — Strava Frontend
Task State
- task_id: TASK-INTEG-SECTION
- status: success
- summary: Playwright-интеграционный тест для TASK-SECTION-DURATION-ZOOM: 3 passed. Фиксы 2 багов (setChartRef timing + resetAll programmatic zoom).
- next task: —
✅ CODER SUCCESS REPORT — TASK-INTEG-SECTION (+TASK-SECTION-BUGS)
../integration/tests/section-duration.spec.ts— 3 теста (duration row, zoom persist across tabs, reset on all tabs), self-contained (stable owneritest-section-owner, API-seed, Chart.js viacanvas.__vueParentComponent.exposed.chart).- Bug 1 фикс (
WorkoutItem.vuesetChartRef~1155): ref пушится сразу (без guardr.chart), финализация (collectCharts+applySelectedRangeZoom) через retrynextTick(до 20 попыток) — дождётся асинхронной инициализации Chart.js.nextTickдобавлен в импорт из "vue" (~строка 440). - Bug 2 фикс (
ChartGroup.tsresetAll~125): послеresetZoom(chart)явноx.min=undefined; x.max=undefined; chart.update("none")(broadcastZoom писал min/max напрямую, минуя плагин). Дополнительно: на время циклаresetAllустановленаthis.broadcasting=true(try/finally) —resetZoomтриггеритonZoomComplete, который иначе повторно разносит ещё-зумированный диапазон на остальные чарты; guardisBroadcasting()отбрасывает эти колбэки. npx playwright test tests/section-duration.spec.ts→ 3 passed (56s) на docker-стече.yarn build(vue-tsc --noEmit + vite) → exit 0.- Known: диагностический
tests/chartprobe.spec.tsне удалён (rm отклонён) — удалить вручную, если мешает.
TASK-INTEG-SECTION: план делегирования
Scope: 1 новый файл — ../integration/tests/section-duration.spec.ts + запуск на стеке. Не трогать src/.
Что тестируется (3 UX-требования, компонент src/pages/workouts/components/WorkoutItem.vue, страница workout_item):
- Клик по графику (выделение участка) → в блоке
.area-dataпоявляется строка «Продолжительность». - Клик по вкладке «Дистанция» → тот же участок остаётся в zoom (не весь путь).
- Клик по reset → участок сброшен; после смена вкладки → весь путь (reset на всех вкладках).
Контракты (проверено Архитектором):
- Детальная страница (приватная, с графиками + вкладками): маршрут
workout_item→ URL{FRONTEND}/workouts/workouts/{id}(родительworkouts+ вложенныйworkouts/:id). НавигацияwaitUntil: "domcontentloaded"(Yandex map держит соединения). - Seed через API (чисто, без UI-dedup):
POST {BACKEND}/api/v0/signup(200) ИЛИPOST /api/v0/signin(fallback на повторном прогоне) → token;POST /api/v0/attachment/upload(FormData полеfile, Bearer) →{id};POST /api/v0/workouts{attachment_id, name}→{id}(400ObjectExists→ взять id изGET /api/v0/workouts[0]).- Файл:
../../backend/tests/fitness_tracker_data_parsing-main/20240507053735.fit(тот же, что вworkout-upload-flow.spec.ts).
- Файл:
- Детальный ответ
GET /api/v0/workouts/{id}→{ results: [{ timestamp, longitude, latitude, speed, heart_rate, power, elevation, ... }], workout: {...} }— из него взять полный диапазон (первая/последняяtimestamp, длина) для порогов zoom. - Chart.js: canvas графиков —
#workout-charts .workout-chart-canvas canvas(несколько; брать[0]). Чертит только если есть метрика (у fixture есть speed). Zoom-состояние:window.Chart.getChart(canvas).isZoomedOrPanned()+chart.options.scales.x.min/max(broadcastZoom пишет сюда) +chart.scales.x.min/max. - Клик по графику (выбор участка) — через Chart.js
onClickопцию:mouse.down→mouse.move→mouse.upв точке внутри chartArea (не drag, чтобы не было drag-zoom). Координаты взять черезchart.chartArea(left/right/top/bottom) вpage.evaluate. - Кнопки вкладок: по тексту «Время» и «Дистанция» (VaButton). Reset:
#zoom-botton. Блок данных участка:.area-data(заголовок «Данные участка»), строка «Продолжительность».
Структура спе (self-contained, repeatable):
beforeAll: проверить стек (frontend<500, backend/api/v0/html_test/login==200), проверить наличие fixture-файла; seed stable owner (itest-section-owner@example.com, idempotent signup/signin) + его workout (upload attachment + create; на 400 — id из списка).- 3 независимых
test'а (каждый:authedPage(token)→openWorkoutDetail(id)→expectcanvas visible):- «duration row on section select»: canvas-click →
expect .area-datavisible →expectтекст «Продолжительность» в.area-data. - «section zoom persists across tab switch»: canvas-click (zoom) → assert zoomed (x.min<full_min) → click «Дистанция» (кнопка active/primary) → assert всё ещё zoomed (x.min≈, не полный диапазон) → click «Время» → assert zoomed (симметрия).
- «reset clears section on all tabs»: canvas-click (zoom) → assert zoomed → click
#zoom-botton(reset) → assert NOT zoomed → click «Дистанция» → assert NOT zoomed (весь путь).
- «duration row on section select»: canvas-click →
Замечания:
- Не
expectвнутриpage.evaluate— толькоreturnданных наружу. authedPage: goto → setlocalStorage.token→ goto (паттерн изworkout-upload-flow.spec.ts).- Node ≥20: запуск через nvm
export PATH="$HOME/.nvm/versions/node/v22.14.0/bin:$PATH". yarn up(docker compose up -d --build) → ждать backend:until curl -sf http://localhost:8000/api/v0/html_test/login >/dev/null; do sleep 2; done→npx playwright test tests/section-duration.spec.ts.
Acceptance criteria:
../integration/tests/section-duration.spec.tsсоздан.npx playwright test tests/section-duration.spec.ts→ 3 passed на запущенном стеке.- Не сломать существующие спе (опционально:
npx playwright test --list).
TASK-SECTION-DURATION-ZOOM: план делегирования
Scope: 1 файл — src/pages/workouts/components/WorkoutItem.vue (компонент, НЕ страница src/pages/workouts/WorkoutItem.vue).
Ключевые якоря (текущее состояние кода):
times(~строка 1061) — сырые ISO-метки времени всех точек (общие для всех метрик).distances(prop) — накопительные метры на точку.activeSection = ref<number | null>(null)(~строка 661) — номер секции, зависит отxAxisMode(потому чтоsectionBoundsпересчитывается под режим).computeAreaData(start, end)(~строка 590) — считает средние по метрикам в блок «Данные участка» (areaAvgData).switchXAxis(mode)(~строка 1135) — сбрасываетactiveSection = null(баг п.2).onZoomComplete(~строка 873) —activeSection = null+computeAreaData(start,end).onClick(~строка 930) — выбор/тоггл секции.resetChartZoom()(~строка 1449) — reset.setChartRef(~строка 1126) — после сбора всех refs делаетgroup.charts = collectCharts()(сигнал для re-zoom после remount).secondsToDuration— экспортируется из../Definitions.vue(не импортирован сейчас — добавить в импорт).
Изменения:
- Заменить
activeSection: number|nullнаselectedRange: { startIdx: number; endIdx: number } | null(единственный source of truth, не зависит от режима — индексы точек). - В
computeAreaData(start,end): добавитьavgData["Продолжительность"] = secondsToDuration(durSec), гдеdurSec = max(0, round((Number(new Date(times[end-1])) - Number(new Date(times[start])))/1000)). ИмпортироватьsecondsToDurationиз../Definitions.vue. onClick: выбратьselectedRange = {startIdx, endIdx}поsectionBounds; тоггл-off когдаselectedRangeсовпадает (startIdx== и endIdx==); выбрать иначе →selectedRange,computeAreaData,broadcastZoom.onZoomComplete:selectedRange = null(ручной zoom снимает выбор),computeAreaData(start,end)сохранить.switchXAxis: НЕ сбрасыватьselectedRange; сохранитьareaAvgData/markedCoordinats(они режим-независимы). ДобавитьapplySelectedRangeZoom():labels = xAxisMode==='time' ? times : distances;toValue = time? Number(new Date(l)) : Number(l);min=toValue(labels[startIdx]),max=toValue(labels[endIdx-1]);group.broadcastZoom(min,max). ВызватьapplySelectedRangeZoom()вsetChartRefпослеgroup.charts = collectCharts()(re-apply zoom в новом режиме после remount).resetChartZoom():selectedRange = null(чтобы после reset на другой вкладке тоже был весь путь), остальное как есть (resetAll,areaAvgData={},markedCoordinats=[]).
**Почему работает «на всех вкладках»: ** reset обнуляет selectedRange → при смене вкладки zoom re-apply вызывается только если selectedRange set → после reset всегда весь путь.
Acceptance criteria:
yarn build(vue-tsc --noEmit) → exit 0.yarn lint→ 0 ошибок (или зафиксировать пре-экзистинг системныйtsutils.iterateComments).- Ручной сценарий (MCP browser): выделить участок → в «Данные участка» есть «Продолжительность» → перейти на вкладку «Дистанция» → та же секция остаётся в zoom (не весь путь) → нажать reset → участок сброшен на всех вкладках.
✅ CODER SUCCESS REPORT — TASK-SECTION-DURATION-ZOOM
src/pages/workouts/components/WorkoutItem.vue:- импорт
secondsToDurationдобавлен в импорт из../Definitions.vue(рядом сformatTime). activeSection: number|null→selectedRange: { startIdx: number; endIdx: number } | null(единственный source of truth; индексы точек, не зависят отxAxisMode).computeAreaData(start,end): в начале цикла добавлена строкаavgData["Продолжительность"] = secondsToDuration(durSec), гдеdurSec = max(0, round((Number(new Date(times[end-1])) - Number(new Date(times[start])))/1000)).onClick: тоггл-off по совпадениюselectedRange.startIdx===startIdx && selectedRange.endIdx===endIdx; иначеselectedRange={startIdx,endIdx}+computeAreaData+broadcastZoom.onZoomComplete:selectedRange=null(ручной zoom снимает выбор).switchXAxis: УБРАЛ сбросactiveSection(выбор сохраняется);areaAvgData/markedCoordinatsне трогаю (режим-независимы). ДобавленаapplySelectedRangeZoom()(labels = time? times : distances; broadcastZoom(min,max)).setChartRef: внутриif (chartRefs.length === chartConfigs.value.length)послеgroup.charts = collectCharts()вызываетсяapplySelectedRangeZoom()(re-apply zoom в новом режиме после remount).resetChartZoom:selectedRange=null(после reset на любой вкладке — весь путь).
- импорт
- Verified:
yarn build(vue-tsc --noEmit) → ✓ built in 7.14s, Done (0 type errors). - Verified:
grep activeSection→ 0 совпадений;grep selectedRange→ 9 совпадений;grep secondsToDuration→ импорт + использование (2 совпадения). - Note:
yarn lintсломан системно —tsutils.iterateComments is not a functionна всех 73 файлах (пре-экзистинг, дефект env eslint/typescript-estree, не от этих изменений). - Browser verification (MCP ad-hoc): НЕ выполнена — dev server
:5173и интеграционный стек:8000не запущены (не поднимал docker-стек ради ad-hoc проверки). Результат по критериям 1–4. - ✅ Architect review: прочитаны все изменённые блоки (импорт 481,
computeAreaData599-606,selectedRange670,onClick997-1008,onZoomComplete921,switchXAxis1164-1173,applySelectedRangeZoom1142-1153 + вызов вsetChartRef1161,resetChartZoom1479). Корректность подтверждена:times/distances/lineCoordinatesодной длины; re-apply zoom после remount черезsetChartRef; после resetselectedRange=null→ на любой вкладке весь путь.
TASK-PROFILE-ULID-BUG: план делегирования
Scope: ../backend/ (3 файла фикса + 1 новый тест).
Изменения:
../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, ~106-108) — послеdel new_dict["created_at"]добавитьnew_dict.pop("id", None)(PK не должен участвовать в UPDATE).- Новый тест
../backend/tests/test_api_profile.py(паттернtest_api_general.py, фикстурыclient+signin_dataизconftest.py):- signup → получить
profile_id(изdata["profile"]["id"]) + token; PATCH /api/v0/profiles/{profile_id}с{"profile": {"first_name": "New", "surname": "Family"}}→ 200;GET /api/v0/profiles/{profile_id}→profile.first_name == "New"иsurname == "Family";- второй PATCH (ещё раз) → 200 + персистится (ловит регрессию «один ULID на процесс»).
- signup → получить
Acceptance criteria:
./venv/bin/python -m pytest tests/test_api_profile.py -v→ passed../venv/bin/python -m pytest tests/ -v→ green (зафиксировать diff, если есть пре-экзистинг failing)../venv/bin/python -m mypy app/modules/users/ --explicit-package-bases --no-error-summary→ 0 errors.ruff check(поruff.toml) → 0 errors.
Не в scope (follow-up): обновление ../integration/tests/workout-upload-flow.spec.ts (добавить assert персистентного имени, убрать comment KNOWN BACKEND BUG) и ../integration/README.md — отдельная мини-задача после success.
✅ CODER SUCCESS REPORT — TASK-PROFILE-ULID-BUG
../backend/app/modules/users/domains/profiles.py:32—default=str(ULID())→default_factory=lambda: str(ULID())(ULID теперь вычисляется на каждыйProfile(), а не один раз при импорте).../backend/app/modules/users/domains/users.py:20— та же замена (оборона в глубине).../backend/app/modules/users/repositories/profile.py(DBProfileRepository.update) — добавленоnew_dict.pop("id", None)послеdel new_dict["created_at"](PK не участвует в UPDATE — defensive).../backend/tests/test_api_profile.py— 2 API-теста: (1) PATCH с{"profile": {"first_name": "New", "surname": "Family"}}→ 200 + GET → персистентность имени/фамилии; (2) повторные PATCH → 200 + GETfirst_name == "Second"(ловит регрессию «один ULID на процесс»).- Verified:
./venv/bin/python -m pytest tests/test_api_profile.py -v→ 2 passed. - Verified:
./venv/bin/python -m pytest tests/ -v→ 56 passed, 10 errors — все 10 errors пре-экзистинг (проверено черезgit stash:NotImplementedError: No support for ALTER of constraints in SQLite dialectв fixture-setuptest_db_profile.py/test_db_workout.py/test_db_workout_photos.py— миграции на SQLite, не связаны с фиксом; до/после идентично). - Verified:
./venv/bin/python -m mypy app/modules/users/ --explicit-package-bases --no-error-summary→ 0 errors. - Verified:
./venv/bin/python -m ruff check app/modules/users/ tests/test_api_profile.py→ All checks passed (полныйruff checkпо репо: единственная находка F401 вmigration/versions/2025-09-25-0000-...-hide-start-end.py:8— пре-экзистинг, коммитff5f468, вне scope). - Follow-up (отдельная задача): update
../integration/tests/workout-upload-flow.spec.ts(assert персистентного имени вместо KNOWN BACKEND BUG comment) +../integration/README.md.
✅ CODER SUCCESS REPORT — TASK-TEST-WORKOUT-UPLOAD
../integration/tests/workout-upload-flow.spec.tsсоздан: 5 последовательных тестов (signup UI+профиль, upload .fit + resolve id, make public→GET public=200, hide_start_end→authed GET body true, add photo→img в.workout-photos-grid).- Ключевое решение: стабильный owner (
itest-workout-owner@example.com, idempotent signup/signin) для Части 2 — dedup по content-hash ГЛОБАЛЕН, аGET /api/v0/workoutsscoped поcreated_by, поэтому уникальный юзер на повторном прогоне всегда падал бы в dedup-ветку с пустым своим списком. Part 1 (профиль) — уникальный юзер через UI (genuine signup). - Детальный URL
/workouts/workouts/:id(сверено сrouter/index.ts: вложенныйworkouts→workouts/:id); навигацияdomcontentloaded(Yandex map держит соединения,networkidleне стабилизируется). ../integration/README.mdобновлён (секция + notes про dedup и про backend-баг имени).- Verified:
npx playwright test tests/workout-upload-flow.spec.ts→ 5 passed, стабильно ×3 (dedup-путь) + прогон на свежей DB (redirect-путь).yarn build(vue-tsc --noEmit) exit 0. - Cleanup: диагностический
../integration/tests/_diag.spec.tsудалён;npx playwright test --list→ 16 tests в 5 файлах,_diagотсутствует.
⚠️ Discovered (NOT fixed, out-of-scope test-only)
- Backend-баг сменy имени:
PATCH /api/v0/profiles/{id}(веткаprofile) возвращает 500. Причина:../backend/app/modules/users/domains/profiles.py:32—id: str = field(default=str(ULID()), ...)вычисляет ULID ОДИН раз при импорте (неdefault_factory), ORM-UPDATE переписывает PK →IntegrityError: duplicate key (profiles_pkey). SPA показывает toast синхронно (до чтения ответа), поэтому имя НЕ персистится. Spec асертит SPA-toast (реальное UI-поведение), персистентное имя задокументировано, но не asserted. - Стек должен быть свежим: колонка
hide_start_endпоявляется только послеalembic upgrade head. Перед прогоном спе — свежий стек /docker compose up -d --force-recreate backend. yarn lintсломан системно —tsutils.iterateComments is not a function(пре-экзистинг, падает и на нетронутые.ts, дефект env eslint/typescript-estree).
✅ CODER SUCCESS REPORT — TASK-HIDE-TRACK-3-FIX
src/pages/workouts/components/WorkoutItem.vue:- импорт
import { useI18n } from "vue-i18n"+const { t } = useI18n();(рядом сuseToast). - template: чекбокс
hide_start_end(v-model +v-on:change="changeHideStartEnd(...)") после блокаis_public. - script:
changeHideStartEnd(value: boolean)→axiosAuth.patch('/api/v0/workouts/${workoutItem.id}', { hide_start_end: value })(с guard!workoutItemи catch-тостом, как уchangePublic).
- импорт
- i18n: ключ
workout.hide_start_endдобавлен во ВСЕ 6 локали (br, cn, es, gb, ir, ru) — ранее отсутствовал (секцияworkoutбыла создана заново). - Verified:
yarn buildexit 0 (vue-tsc --noEmit 0 ошибок, vite ✓). - Verified:
grep hide_start_end→ 4 совпадения;grep useI18n→ 2 совпадения. - Note:
yarn lintсломан системно —tsutils.iterateComments is not a functionна всех .vue файлах (существующий дефект окружения, не от этих изменений).
✅ CODER SUCCESS REPORT — TASK-HIDE-TRACK-T1
../backend/tests/test_api_workout.py: добавленtest_workout_hide_start_end— проверяет PATCHhide_start_end(False→True→False) с GET-верификацией на каждом шаге.- Verified:
./venv/bin/python -m pytest tests/test_api_workout.py::test_workout_hide_start_end -v→ PASSED (1.92s) - Verified:
./venv/bin/python -m pytest tests/test_api_workout.py -v→ 4 passed (5.13s)
✅ CODER SUCCESS REPORT — TASK-HIDE-TRACK-B3
../backend/app/web/v0/workout.py:WorkoutEditReq+ полеhide_start_end: bool | None = Field(None); endpoint передаёт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.- 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.
✅ CODER SUCCESS REPORT — TASK-HIDE-TRACK-B2
../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(...). БезIS_TEST-guard (колонка нужна и в тестах).- Verified:
python3 -c "import ast; ast.parse(...)"→ OK.
✅ CODER SUCCESS REPORT — TASK-HIDE-TRACK-4
src/pages/workouts/WorkoutPublicItem.vue: импортtrimTrack; в.then()приd.workoutItem.hide_start_end === true— обрезкаlineCoordinates/distances/dataчерезtrimTrack(..., totalDist)(totalDist = последний элементdistances).mapCenterостаётся неизменным.src/pages/workouts/Definitions.vue: добавлено полеhide_start_end: booleanв типWorkoutItem(восстановлено — было потеряно при git stash).- Приватный вид (
WorkoutItem.vueстраница) НЕ затронут. - Verified:
yarn buildexit 0 (vue-tsc --noEmit 0 ошибок, vite ✓ 6.86s). - Note:
yarn lint→ системная ошибкаtsutils.iterateComments is not a function(касается ВСЕХ .vue файлов, не связано с изменениями — существующий дефект окружения lint-депенденций).
✅ CODER SUCCESS REPORT — TASK-TEST-AUTH
../integration/tests/auth-flows.spec.tsсоздан: 4 теста (login success, login failure, signup+auto-login, logout)- Self-seed via
POST /api/v0/signup, Vuestic selectors (input[type=email],input[type=password]) - Verified:
npx playwright test tests/auth-flows.spec.ts→ 4 passed (19.2s)
TASK-TEST-PUBLIC: public-pages.spec.ts
Цель: Playwright spec для auth-сценариев (signup, login success/error, logout, validation).
Файл для создания: ../integration/tests/auth-flows.spec.ts
Конвенции (см. SKILL.md):
- Self-seed user via
POST /api/v0/signupinbeforeAll - Selectors:
input[type=email],input[type=password],getByRole('button', { name: 'Вход' }) - Vuestic floating labels → select by input type, NOT placeholder
- Auth mock via
page.evaluate(() => localStorage.setItem(...)) - Do NOT call
expect()insidepage.evaluate() - Base URLs:
FRONTEND(5173),BACKEND(8000) freshPage()helper: goto → clear localStorage → goto again
Тесты в spec:
- Signup:
POST /api/v0/signup→ returns token, user, profile; navigate to/auth/signup→ fill form → submit → redirect to/explore - Login success: pre-seed user via API → UI login → token in localStorage → redirect to
/explore - Login failure: wrong password → error toast appears, stays on
/auth/login - Logout: logged in → open profile dropdown → «Выход» → redirect to login, token cleared
Acceptance Criteria:
- Spec file created at
../integration/tests/auth-flows.spec.ts - All tests self-contained (self-seed data)
- Follows existing
sidebar-auth-switch.spec.tspatterns npx playwright test tests/auth-flows.spec.tspasses against integration stack
TASK-MCP-SETUP: Playwright MCP configuration
Что сделано:
- MCP-конфиг Zoo Code (
mcp_settings.json): serverweb-browser→@playwright/mcp@latest, Node 22 via nvm absolute path,--headless - Chromium
1234уже установлен в~/.cache/ms-playwright/ .mcp.jsonсоздан в проекте (project-level, для совместимости)techContext.mdобновлён: секция "UI Verification (MCP Browser)"
Как использовать (workflow):
yarn dev— запустить dev server (порт 5173)browser_navigate→http://localhost:5173/explore— проверить публичную страницуbrowser_evaluate→() => { localStorage.setItem('token', 'test'); localStorage.setItem('user', JSON.stringify({id:1,name:'Test'})); location.reload(); }— сымитировать authbrowser_navigate→ проверить auth-страницы (/workouts,/workouts/:id)browser_snapshot— проверить DOM-структуруbrowser_take_screenshot— визуальная проверкаbrowser_network_requests— проверить API-запросы
Баг (контекст)
Сайдбар показывает publicRoutes (Лента + Маршрут) после логина до F5. Корень: NavigationRoutes.ts:70 — routes вычисляется один раз при module evaluation и кэшируется. TASK-AUTH-NAV-1 уже добавил реактивный isAuthenticated в useGlobalStore (инициализация из localStorage + setAuthenticated в Login/Logout). Эта задача переключает UI на computed.
TASK-AUTH-NAV-2: реактивные маршруты (3 файла)
1. src/components/sidebar/NavigationRoutes.ts
- Сделать named exports:
export const authRoutes: INavigationRoute[]иexport const publicRoutes: INavigationRoute[](сейчас это локальныеconst, не экспортированные). - Убрать статическое поле
routesиз default export (или оставить default безroutes, т.к. оба потребителя перейдут на computed).INavigationRouteинтерфейс — не трогать. - Важно: массивы
authRoutes/publicRoutesостаются теми же по структуре/содержимому — только экспортируются.
2. src/components/sidebar/AppSidebar.vue (Options API, setup())
import { useGlobalStore } from "../../stores/global-store";+import { authRoutes, publicRoutes } from "./NavigationRoutes";(импортnavigationRoutesпо default можно оставить/убрать — но все обращенияnavigationRoutes.routesзаменить).- В
setup():const globalStore = useGlobalStore(); - computed:
const routes = computed(() => globalStore.isAuthenticated ? authRoutes : publicRoutes); - В
returnзаменитьnavigationRoutesнаroutes(тепловой шаблон уже используетnavigationRoutes.routes→ поправить наroutes.value? НЕТ — в Options API computed из setup доступно в шаблоне какroutesбез.value, т.е. в шаблонеnavigationRoutes.routes→routes). setActiveExpand:navigationRoutes.routes.map(...)→routes.value.map(...)(в setup.valueнужен).- Шаблоны:
v-for="(route, index) in navigationRoutes.routes"→in routes.
3. src/components/app-layout-navigation/AppLayoutNavigation.vue (script setup)
import { authRoutes, publicRoutes } from "../sidebar/NavigationRoutes";+import { useGlobalStore } from "../../stores/global-store";const globalStore = useGlobalStore();findRouteName(строка ~64):traverse(NavigationRoutes.routes)→traverse(globalStore.isAuthenticated ? authRoutes : publicRoutes).- Убедиться, что
itemscomputed (зависит отroute.matchedиfindRouteName) остаётся реактивным — т.к.findRouteNameчитаетglobalStore.isAuthenticatedвнутриitemscomputed, реactivity сохранится.
Acceptance Criteria (TASK-AUTH-NAV-2)
yarn lint→ 0 ошибок.yarn build→ 0 ошибок (vue-tsc --noEmit).NavigationRoutes.ts:authRoutesиpublicRoutesэкспортируются; статическогоroutesс localStorage-тернарником больше нет.AppSidebar.vue: routes = computed отglobalStore.isAuthenticated;setActiveExpandиспользуетroutes.value.AppLayoutNavigation.vue:traverseиспользует выбор поglobalStore.isAuthenticated.- Ручной сценарий (ключевой): без F5 — выход из профиля →
/explore→ логин → сайдбар сразу показывает «Мои тренировки» (authRoutes). Аут → сайдбар сразуpublicRoutes.
✅ CODER SUCCESS REPORT — TASK-AUTH-NAV-2
src/components/sidebar/NavigationRoutes.ts:export const authRoutes/export const publicRoutes; удалён статическийroutes: localStorage.getItem("token") ? ...из default export (оставлен толькоroot).src/components/sidebar/AppSidebar.vue: импортauthRoutes, publicRoutes+useGlobalStore;computed(() => globalStore.isAuthenticated ? authRoutes : publicRoutes);setActiveExpandиспользуетroutes.value.map; шаблонv-for="(route, index) in routes".src/components/app-layout-navigation/AppLayoutNavigation.vue:const globalStore = useGlobalStore();traverse(globalStore.isAuthenticated ? authRoutes : publicRoutes)внутриfindRouteName(вызывается вitemscomputed → реactivity сохранена).- Verified:
yarn lintexit 0,yarn buildexit 0 (vue-tsc --noEmit 0 ошибок, vite ✓ 7.07s).
История (закрытые задачи)
-
TASK-HIDE-TRACK-4: success —
trimTrackвWorkoutPublicItem.vueприhide_start_end;hide_start_endв типеWorkoutItem(Definitions.vue). Verified:yarn build0. (Пред. active: TASK-TEST-PUBLIC → в очереди TASK-TEST-PROTECTED.) -
TASK-AUTH-NAV-1: success —
isAuthenticated+setAuthenticatedвuseGlobalStore; LoginsetAuthenticated(true), LogoutsetAuthenticated(false). Verified lint/build 0. -
TASK-SSR-DETAIL: success — SSR /public/workouts/:id под DOM WorkoutItem.vue.
-
TASK-404: success — HTTP 404 для неизвестных страниц.
-
TASK-PUBLIC-ROUTE-FIX: success — workout_public_item в top-level router.
-
TASK-F11/F12/F13/F14: success — SEO + фиксы карты тренировки.