Files
opencloud/AGENTS.md
T
2026-05-30 00:39:20 +08:00

8.6 KiB

AGENTS.md

Commands

  • npm run dev — Vite dev server with HMR
  • npm run buildvue-tsc -b && vite build (typecheck must pass or build aborts)
  • npx vue-tsc -b — standalone typecheck (no script in package.json)
  • No linter, formatter, or test runner exists

Architecture

  • Vue 3 + Vite + Tailwind CSS + Vue Router + Pinia + Supabase + AMap (高德地图) + Naive UI
  • Path alias @/src/ (configured in both vite.config.ts and tsconfig.app.json)
  • Supabase client singleton at src/lib/supabase.ts — throws at import if env vars missing
  • AMap loaded lazily via src/lib/amap.ts with type declarations in src/types/amap.d.ts
  • UI language is Chinese (zh-CN)
  • All DB operations are direct supabase.from(...) calls from the browser — security depends on Supabase RLS policies

Directory Map

Path Purpose
src/lib/ Singletons and utilities: supabase, 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 upload with thumbnail generation, EXIF extraction, badge unlocking)
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 visual system hooks: shared button/card utility classes, base typography, body background
src/views/ Route-level page components (see Routes below)
src/types/ TypeScript types: database models (database.ts), AMap declarations (amap.d.ts), router meta (router.d.ts)

Routes

Path View Auth Required
/ MapView No
/login, /register, /auth/confirm, /auth/reset-password Auth views No
/upload UploadView Yes
/encyclopedia EncyclopediaView No
/encyclopedia/:id CloudTypeView No
/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
/:pathMatch(.*)* NotFoundView No
  • Route guards in router/index.ts: requiresAuth redirects to /login, 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() is called, which calls getSession() and subscribes to onAuthStateChange. The app only mounts after the initial session check completes.
  • Login sets user.value explicitly: login() extracts data.user from signInWithPassword and assigns it to the store directly, rather than relying on onAuthStateChange.
  • Profile is auto-created by DB trigger (handle_new_user on auth.users), not by the frontend.
  • Auth error messages are translated to Chinese in the store.
  • register() checks username uniqueness before calling signUp() — username is passed via options.data.username.

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 table. Fetched once, 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. deleteClouds also removes from Supabase Storage.

Supabase

  • Env var is VITE_SUPABASE_PUBLISHABLE_KEY (not VITE_SUPABASE_ANON_KEY), using Supabase's sb_publishable_ key format.
  • All tables have RLS enabled. Check plan.md section 10 for the full schema and RLS policies.
  • Storage bucket clouds is public read, authenticated upload.
  • Profile role field (user/admin) controls admin access — checked in route guard, not in JWT metadata.
  • user_collections tracks encyclopedia unlocks with first_cloud_id FK to clouds.
  • Page-based navigation (50 items/page), not infinite scroll.
  • resolveSearchFilters() pre-fetches user IDs or cloud type IDs before the main query.
  • buildFilteredQuery() returns a chainable query builder; loadPage() forks it into a count query and a data query that run in parallel via Promise.all.
  • 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 composable handles: file selection (drag/drop/click), client-side thumbnail generation (JPEG, max 640px edge, 0.72 quality), EXIF date extraction, coordinate blurring (2 decimal places), Supabase Storage upload (original + thumbnail), DB insert with status: 'pending', badge unlock detection via user_collections upsert.
  • 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.
  • SEO plugin in vite.config.ts: generates robots.txt and sitemap.xml at build time. Disallows /admin, /auth/, /login, /register, /upload, /profile/settings.
  • 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 (app won't start without):

  • VITE_SUPABASE_URL
  • VITE_SUPABASE_PUBLISHABLE_KEY

Required for map features:

  • VITE_AMAP_KEY

Optional:

  • VITE_SITE_URL — canonical URL for SEO meta and sitemap (falls back to Vercel env or hardcoded default)

Future (not in .env):

  • OPENAI_API_KEY, OPENWEATHERMAP_API_KEY

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 <style> blocks for slider styling, transitions, and component tweaks.
  • Do not rely on default Naive UI primary/secondary colors for page CTAs or panel actions. The project now uses shared global classes in src/style.css to keep buttons visually consistent with the sky-atlas theme.
  • Auth buttons use oc-primary-button with semantic variants:
    • oc-primary-button--teal — login / account-access actions
    • oc-primary-button--sky — register / create / forward actions
  • Panel and utility buttons use oc-panel-button with semantic variants:
    • oc-panel-button--neutral — white card-style utility actions
    • oc-panel-button--sky — primary panel actions
    • oc-panel-button--teal — active toggles / confirm actions
    • oc-panel-button--danger — destructive actions
    • oc-panel-button--amber — admin/state-toggle actions where warning emphasis fits better than danger
  • Card-like containers should prefer shared shadow classes instead of ad-hoc shadows:
    • oc-panel-card
    • oc-panel-card-soft
    • oc-empty-card
  • When styling NButton, prefer type="default" plus the shared class when a custom panel button variant is intended. Otherwise Naive UI theme variables may override white backgrounds or semantic colors.

Visual Notes

  • The site icon and header brand mark are a matching pixel-style cloud motif; avoid reintroducing emoji or unrelated icon styles for primary brand surfaces.
  • Header action buttons use slight hover lift (translate(-1px, -1px)) with hard-offset shadow growth; new header-like actions should follow that interaction pattern.
  • Heatmap view toggles in ContributionHeatmap intentionally use separate buttons with gap spacing instead of NButtonGroup, because grouped buttons visually collide once hard shadows and hover transforms are applied.

MVP Constraints (from plan.md)

  • No Supabase Realtime — refresh-based loading
  • No OAuth — email/password only, email confirmation required
  • No AI cloud identification — manual type selection
  • AMap only (China-focused), no Mapbox fallback yet