9.7 KiB
9.7 KiB
Strava Frontend Developer Instructions
You must strictly follow these architectural, coding, and formatting rules for this project (Vue 3 SPA "cycle-rider"). All generated code and modifications must fully comply with the guidelines below.
🧠 Agent Behavior
- Reasoning Language: All thoughts, analysis, and reasoning MUST be written in English to minimize token usage.
- Communication: Communicate with the user in Russian unless asked otherwise.
- Task Progress: Always track implementation progress with a todo list.
🏗 Project Structure (src/)
The SPA is organized as follows — keep the dependency direction pages -> components / stores / services:
src/main.ts— app bootstrap: axios instances, Pinia, router, i18n, Vuestic UI, Yandex Maps, optional GTM. Do NOT relocate providers; components rely onapp.providekeys.src/router/index.ts— single router config. All pages MUST be lazy-loaded via() => import(...). Routes are nested underAppLayout(authenticated shell) orAuthLayout(auth pages); catch-all redirects todashboard.src/pages/— route-level components and page-private logic. One folder per domain area (workouts/,routes/,auth/,preferences/,admin/). Page-private helper modules live in acomponents/subfolder of the page (e.g.,pages/workouts/components/GetWorkout.ts).src/components/— reusable presentational components (navbar/,sidebar/,icons/,app-layout-navigation/). No direct API calls here; receive data via props.src/stores/— Pinia stores (options style viadefineStore).useUserStore(user/profile data hydrated fromlocalStorage),useGlobalStore(sidebar/UI state).src/i18n/— vue-i18n setup; locale files auto-discovered fromsrc/i18n/locales/*.json(locale name = file name).src/services/— cross-cutting helpers:utils.ts(validators, sleep),vuestic-ui/(global config, themes, icon registry).src/scss/— global styles; do not duplicate Vuestic theme tokens — use--va-*CSS variables.
🌐 API Access Pattern
- Two Axios instances are created in
src/main.tsand injected app-wide:axiosAuth— request interceptor attachesAuthorization: Bearer <localStorage.token>;axiosPublic— no auth header.
- Consume them with
inject('axiosAuth') as AxiosInstance/inject('axiosPublic') as AxiosInstance. Do NOT create new axios instances in components. - All backend endpoints are under
/api/v0(base URL =HOSTconstant inmain.ts). - A shared response interceptor handles 401 (clears
localStorage, redirects tologin). Do not add per-component 401 handling. - Auth data lives in
localStoragekeys:token,user,profile,attachments. Keep key names stable —useUserStoreandmain.tsdepend on them.
📊 Charts & Maps
- Charts: Chart.js 4 via
vue-chartjs; adapters/plugins already installed (chartjs-adapter-moment,chartjs-plugin-zoom,chartjs-chart-geofor elevation). Chart-data building logic belongs in page-level.tshelper modules (e.g.,pages/workouts/components/*), not in.vuetemplates. - Maps: Yandex Maps via
vue-yandex-maps(registered inmain.ts). Route polylines are built from workoutresultscoordinates[longitude, latitude]. - Workout detail API response shape:
{ workout, results: [{ timestamp, longitude, latitude, elevation, power, heart_rate, speed }, ...] }. Null-valued metrics (power/heart_rate/elevation) must be detected and their charts hidden, matching the existing pattern inGetWorkout.ts.
💾 State Management (Pinia)
- Use options-style stores (
defineStore("name", { state, actions })) consistent with existing stores. - Auth/profile state is hydrated from
localStorageinsidestate()— there is no pinia-persist plugin. Do not add persistence of sensitive data beyond the existing keys. - UI/visual state (sidebar, theme) belongs in
useGlobalStore.
🎨 Styling
- Vuestic UI is the component library. Customize through
src/services/vuestic-ui/global-config.tsandthemes.ts, never by forking component internals. - Tailwind CSS utilities are available; color tokens MUST reference Vuestic CSS variables (
var(--va-primary)etc.) as mapped intailwind.config.js(primary,textPrimary,backgroundCardPrimary, ...). Do not hardcode hex colors that bypass the theme. - Font-size tokens:
text-tag,text-regularSmall,text-regularMedium,text-regularLarge(defined intailwind.config.js). - Global styles live in
src/scss/main.scss; page-specific overrides go in the page's<style scoped>. - Custom Vuestic icons are registered in
src/services/vuestic-ui/icons-config/.
🌍 i18n
- vue-i18n composition mode (
legacy: false); default and fallback locale isru. - New translations: add the key to ALL locale files under
src/i18n/locales/(br, cn, es, gb, ir, ru) — missing keys in any locale break the UI for that locale. - In components use the
useI18n()composable (t()). - User-facing strings must go through i18n; exception:
validatorsinsrc/services/utils.tscurrently hardcode Russian messages — keep the existing style when extending that file.
📘 Coding Conventions (TypeScript / Vue)
- TypeScript strict:
tsconfig.jsonhasstrict: trueand the build runsvue-tsc --noEmit— type errors break the build. Mandatory type hints for all functions and props. - Modern types: use built-in types (
Array,Record,| null/| undefinedunions). Avoidany; when unavoidable (e.g., axios error handling), keep it local and typed via type guards (axios.isAxiosError). - Composition API: prefer
<script setup lang="ts">for new components; do not force-convert existing Options API components unless the task requires it. - Naming: PascalCase for components (
WorkoutItem.vue), kebab-case for component usage in templates, camelCase for variables/functions, UPPER_SNAKE_CASE for constants. - Async style: existing pages use
.then/.catchchains — match the surrounding style in a file rather than mixing in async/await. - No forbidden debugging:
console.log/debuggerstatements are prohibited in committed code (the shared axios interceptor inmain.tsis a known exception — do not add more). - No mutable module-level state in helper
.tsmodules (the currentGetWorkout.tspattern of module-levelletis tech debt — do NOT copy it; return state from functions instead). - Absolute imports: use
../-relative or@/paths consistently; the project uses relative imports fromsrc/— keep it relative withinsrc.
🧩 Component Guidelines
- Pages own data fetching and orchestration; presentational components receive props and emit events.
- Reusable logic (fetch + transform) belongs in a page-level
.tsmodule exporting pure functions (seeGetWorkout.ts,LineWithLineChart.tsas the pattern to follow — but without the module-levelletmutation). - Route meta and navigation: use named routes (
router.push({ name: "login" })), never string paths for internal navigation.
📋 Page/Route Catalog (current)
| Route name | Path | Page | Notes |
|---|---|---|---|
dashboard |
/dashboard |
pages/workouts/Feed.vue |
public workout feed, default landing |
routes |
/routes |
pages/routes/Route.vue |
routes listing |
list_workouts |
/workouts |
pages/workouts/WorkoutList.vue |
user's workouts |
upload_workouts |
/workouts/upload |
pages/workouts/WorkoutUpload.vue |
FIT/GPX upload |
workout_item |
/workouts/:id |
pages/workouts/WorkoutItem.vue |
private detail, charts + map |
workout_public_item |
/public/workouts/:id |
pages/workouts/WorkoutPublicItem.vue |
public detail |
preferences |
/preferences |
pages/preferences/Preferences.vue |
profile settings, modals |
login / signup / logout / recover-password / recover-password-email |
/auth/* |
pages/auth/* |
AuthLayout pages |
404 |
/404 |
pages/404.vue |
error page |
⚠️ Environment & Secrets
- The API
HOST, Yandex Maps API key, and GTM config live insrc/main.ts/import.meta.env(VITE_APP_GTM_ENABLED,VITE_APP_GTM_KEY). Do not hardcode new secrets; useimport.meta.env.VITE_*for anything environment-specific. - Do not swap the production
HOSTunless the task explicitly requires local development.
🚀 Final Verification
When working in agent mode, always run yarn lint and yarn build as the final step before completing the task (build includes vue-tsc --noEmit type-check). There is NO test runner in this repo — do not invent test commands.