# AGENTS.md ## Commands - `npm run dev` — Vite dev server with HMR - `npm run check:styles` — validates shared style contracts and blocks deprecated palettes/classes - `npm run build` — `vue-tsc -b && vite build` (typecheck must pass or build aborts) - `npx vue-tsc -b` — standalone typecheck (no script in package.json) - `npm run preview` — previews the production build on strict port `5173` - No general linter, formatter, or test runner exists; `check:styles` is the project-specific visual-system check ## Architecture - **Vue 3 + Vite + Tailwind CSS + Vue Router + Pinia + FastAPI + AMap (高德地图) + Naive UI** - Path alias `@/` → `src/` (configured in both `vite.config.ts` and `tsconfig.app.json`) - FastAPI client at `src/lib/api.ts`; API base URL comes from `VITE_API_URL` - AMap loaded lazily via `src/lib/amap.ts` with type declarations in `src/types/amap.d.ts` - UI language is Chinese (zh-CN) - All persistence, storage, authorization, and admin operations go through the FastAPI backend ## Directory Map | Path | Purpose | | --- | --- | | `src/lib/` | Singletons and utilities: FastAPI client, amap, canvas patch, cloudTypes constants, cloudBadges canvas renderer, SEO meta builder | | `src/stores/` | Pinia stores: auth, clouds (cloud_types cache), encyclopedia (collection + unlock tracking), profile (user pages + cloud CRUD) | | `src/composables/` | Vue composables: `useUpload` (batch multipart upload, EXIF extraction, badge result handling) | | `src/components/cloud/` | Cloud-related modals and widgets: ImageDetailModal, CloudEditModal, MapPickerModal, QuickUploadModal, MiniLocationMap | | `src/components/layout/` | AppHeader (top nav bar with auth state) | | `src/components/profile/` | ContributionHeatmap | | `src/style.css` | Global stylesheet assembly point; imports Tailwind, tokens, base rules, and shared components | | `src/styles/` | Visual system source: semantic tokens, global square-corner baseline, shared `oc-*` component contracts | | `src/lib/theme.ts` | Naive UI theme overrides aligned with the semantic tokens | | `src/views/` | Route-level page components (see Routes below) | | `src/types/` | TypeScript types: domain models (`database.ts`), API DTOs (`api.ts`), AMap declarations (`amap.d.ts`), router meta (`router.d.ts`) | | `docs/style-system.md` | Concise implementation and naming guide for the visual system | | `scripts/check-style-system.mjs` | Automated guard against deprecated palettes and malformed shared classes | ## Routes | Path | View | Auth Required | | --- | --- | :---: | | `/` | MapView | No | | `/login`, `/register`, `/forgot-password`, `/auth/confirm`, `/auth/reset-password` | Auth views | No | | `/upload` | UploadView | Yes | | `/encyclopedia` | EncyclopediaView | Yes | | `/encyclopedia/:id` | CloudTypeView | Yes | | `/gallery` | GalleryView | No | | `/community` | CommunityView | No | | `/profile` | ProfileView (own) | Yes | | `/profile/settings` | ProfileSettingsView | Yes | | `/profile/:id` | ProfileView (public) | No | | `/admin` | AdminView | Yes (admin) | | `/403` | ForbiddenView | No | | `/401` | AuthRequiredView | No | | `/:pathMatch(.*)*` | NotFoundView | No | - Route guards in `router/index.ts`: `requiresAuth` redirects to `/401` with the intended URL in the `redirect` query; `requiresAdmin` redirects to `/403` - SEO meta tags applied per-route via `lib/seo.ts` in `router.afterEach` ## Auth Flow - **`main.ts` initializes auth before mounting**: `authStore.initialize()` exchanges the HttpOnly refresh cookie for an access token. The app only mounts after this initial check. - Access tokens live in memory only. Refresh cookies are managed by the backend and sent with `credentials: 'include'`. - `src/lib/api.ts` performs one automatic refresh-and-retry after an authenticated request receives `401`. - API auth state changes are synchronized back to the Pinia store through `opencloud:auth-updated` and `opencloud:auth-expired` window events. - Email confirmation and password reset pages consume the opaque `token` query parameter generated by the backend. - Passwords must contain at least 8 characters in registration, settings, and reset flows. - Registration, email confirmation, login, password reset, profile creation, and uniqueness checks are backend responsibilities. ## Stores - **auth** — User session, profile, login/register/logout, username/password update, password reset. `initialize()` must be called before app mounts. - **clouds** — Simple cache of cloud types returned by `GET /cloud-types`. Fetched once and shared across views. - **encyclopedia** — Cloud types + user's collection (unlock state). Tracks `unlockPercent` for progress display. Depends on `authStore`. - **profile** — User profile pages. Fetches profile + cloud list per-user. Supports update/delete/visibility-toggle with optimistic cache patching; backend deletion also removes stored files. ## FastAPI Backend - Backend project lives in the sibling directory `../opencloud-backend`. - Default development API URL is `http://localhost:8000/api/v1`; set `VITE_API_URL` for deployed environments. - Frontend route guards are for navigation UX only. The backend enforces ownership, authentication, admin roles, and disabled-account rules. - Images are uploaded as multipart form data. The backend stores originals, creates thumbnails, blurs coordinates, writes database records, and atomically unlocks badges. ## API Conventions - All requests must go through `src/lib/api.ts`; do not call `fetch` directly from views, stores, or composables. - API paths passed to `apiRequest()` are relative to `VITE_API_URL`, for example `/clouds` rather than `/api/v1/clouds`. - Public requests explicitly use `{ auth: false }`. Authenticated requests use the default and receive a Bearer access token. - JSON request bodies are plain objects. Image uploads use `FormData`; do not set the multipart `Content-Type` header manually. - FastAPI errors use `detail`; `ApiError` converts string and validation-array details into a user-facing message. - Backend DTO fields use `snake_case`. Page/store adapters may expose display-only camelCase fields such as `cloudTypeName`. - Paginated endpoints return `items`, `page`, `page_size`, `total`, and `total_pages`. - Cloud batch mutation endpoints accept at most 100 IDs. Profile and admin code split larger selections into chunks of 100. ## Gallery Pagination - Page-based navigation (50 items/page), not infinite scroll. - The backend resolves cloud-type/custom-type searches and `@username` searches. - Each response includes `items`, `total`, `page`, `page_size`, and `total_pages`. - Search debounced at 250ms. ## Map Timeline - Realtime mode: slider selects a minute of today, shows clouds captured within 2 hours before that time. Marker opacity decays with age. - Archive mode: browse clouds by day or month. Toggling timeline controls closed auto-returns to realtime. - Slider resets to current time each time the controls panel is opened. ## Upload Flow - `useUpload` handles file selection, previews, EXIF date extraction, validation, sequential multipart uploads, progress, and badge results. Thumbnail generation, coordinate blurring, storage, database insertion, and badge unlocking run on the backend. - Batch items upload sequentially. If a later item fails, earlier successful uploads remain saved; there is no transactional rollback or resumable upload in the frontend. - `UploadView` is the full-page batch uploader. `QuickUploadModal` is a single-image shortcut from the map page. ## Build & Deploy - **Vercel** with `vercel.json`: SPA rewrites, security headers (X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy). No CSP or HSTS configured. - Vite dev and preview servers both use strict port `5173`, matching the backend's default `CORS_ORIGINS` and `FRONTEND_URL`. - **SEO plugin** in `vite.config.ts`: generates `robots.txt` and `sitemap.xml` at build time. Auth-only routes, including `/encyclopedia`, are excluded from the sitemap and disallowed in `robots.txt`. - **`lib/canvas.ts`**: patches `HTMLCanvasElement.getContext('2d')` to always pass `willReadFrequently: true` — needed for the badge card renderer in `lib/cloudBadges.ts`. ## Environment Variables Required for map features: - `VITE_AMAP_KEY` Optional: - `VITE_API_URL` — FastAPI base URL including `/api/v1` (defaults to `http://localhost:8000/api/v1`) - `VITE_SITE_URL` — canonical frontend URL used by route SEO and generated sitemap; set it explicitly in deployed environments Backend-only variables such as database credentials, JWT secrets, SMTP settings, cookie policy, CORS origins, and upload paths belong in `../opencloud-backend/.env`, never in the Vite environment. ## Visual System - The design language is a clean sky atlas: airy sky-blue surfaces, fresh eucalyptus green, square editorial panels, pixel-cloud identity, and hard offset shadows. - Read `.agents/skills/opencloud-style/SKILL.md` before material visual changes. Treat `src/styles/tokens.css`, `src/styles/base.css`, `src/styles/components.css`, and `src/lib/theme.ts` as the implementation sources of truth. - Shared/stable decisions belong in layers: - tokens and palette remapping → `src/styles/tokens.css` - global baseline and square-corner policy → `src/styles/base.css` - cross-page semantic classes → `src/styles/components.css` - feature-specific map/slider/media details → the component's scoped style - one-off layout and responsive composition → Tailwind in the template - The global interface is square by default. Ordinary cards, inputs, dropdowns, buttons, modals, and map controls use `0px` radius. Circular marker dots, media overlay controls, AMap-generated bubbles/cards, and the media-focused image-detail shell are explicit exceptions. - Use hard zero-blur offset shadows (`--oc-shadow-xs` through `--oc-shadow-xl`) for ordinary UI depth. Interactive controls may lift by `translate(-1px, -1px)` on hover. - Brand/interaction green is fresh eucalyptus: main `#34745d`, dark `#274c40`, light surface `#f2faf6`. - Success green is a distinct leaf green: main `#4d7f3b`, dark `#35522c`, light surface `#f4faef`. - Existing `teal-*` and `emerald-*` Tailwind class names are compatibility aliases remapped in `tokens.css`; do not assume Tailwind's default values. - Do not introduce Tailwind `green-*`/`lime-*`, vivid default teal, purple, indigo, or generic gray palettes. Use slate, sky, remapped teal/emerald, amber, and rose according to semantic purpose. - The header brand mark is the pixel cloud/sun SVG inside a square `40px` `sky-100` surface with a `sky-200` border and neutral `4px` hard shadow. Do not replace it with emoji or give it a green background. - Map floating buttons use `oc-map-button`: square `40px × 40px`, white background, slate border, `0px` radius, and a hard shadow even when disabled. - For material visual changes, inspect representative desktop and real `375px` mobile screenshots and verify there is no horizontal overflow. ## Naive UI - Used for modals, buttons, inputs, tags, progress bars, alerts, dropdowns, empty states, skeletons, and message toasts. - No SSR — pure client-side rendering. - Custom CSS overrides via scoped `