e087dd46e2
Reviewed-on: #1
8.6 KiB
8.6 KiB
AGENTS.md
Commands
npm run dev— Vite dev server with HMRnpm run build—vue-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 bothvite.config.tsandtsconfig.app.json) - Supabase client singleton at
src/lib/supabase.ts— throws at import if env vars missing - AMap loaded lazily via
src/lib/amap.tswith type declarations insrc/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:requiresAuthredirects to/login,requiresAdminredirects to/403 - SEO meta tags applied per-route via
lib/seo.tsinrouter.afterEach
Auth Flow
main.tsinitializes auth before mounting:authStore.initialize()is called, which callsgetSession()and subscribes toonAuthStateChange. The app only mounts after the initial session check completes.- Login sets
user.valueexplicitly:login()extractsdata.userfromsignInWithPasswordand assigns it to the store directly, rather than relying ononAuthStateChange. - Profile is auto-created by DB trigger (
handle_new_useronauth.users), not by the frontend. - Auth error messages are translated to Chinese in the store.
register()checks username uniqueness before callingsignUp()— username is passed viaoptions.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_typestable. Fetched once, shared across views. - encyclopedia — Cloud types + user's collection (unlock state). Tracks
unlockPercentfor progress display. Depends onauthStore. - profile — User profile pages. Fetches profile + cloud list per-user. Supports update/delete/visibility-toggle with optimistic cache patching.
deleteCloudsalso removes from Supabase Storage.
Supabase
- Env var is
VITE_SUPABASE_PUBLISHABLE_KEY(notVITE_SUPABASE_ANON_KEY), using Supabase'ssb_publishable_key format. - All tables have RLS enabled. Check
plan.mdsection 10 for the full schema and RLS policies. - Storage bucket
cloudsis public read, authenticated upload. - Profile
rolefield (user/admin) controls admin access — checked in route guard, not in JWT metadata. user_collectionstracks encyclopedia unlocks withfirst_cloud_idFK toclouds.
Gallery Pagination
- 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 acountquery and adataquery that run in parallel viaPromise.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
useUploadcomposable 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 withstatus: 'pending', badge unlock detection viauser_collectionsupsert.UploadViewis the full-page batch uploader.QuickUploadModalis 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: generatesrobots.txtandsitemap.xmlat build time. Disallows/admin,/auth/,/login,/register,/upload,/profile/settings. lib/canvas.ts: patchesHTMLCanvasElement.getContext('2d')to always passwillReadFrequently: true— needed for the badge card renderer inlib/cloudBadges.ts.
Environment Variables
Required (app won't start without):
VITE_SUPABASE_URLVITE_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.cssto keep buttons visually consistent with the sky-atlas theme. - Auth buttons use
oc-primary-buttonwith semantic variants:oc-primary-button--teal— login / account-access actionsoc-primary-button--sky— register / create / forward actions
- Panel and utility buttons use
oc-panel-buttonwith semantic variants:oc-panel-button--neutral— white card-style utility actionsoc-panel-button--sky— primary panel actionsoc-panel-button--teal— active toggles / confirm actionsoc-panel-button--danger— destructive actionsoc-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-cardoc-panel-card-softoc-empty-card
- When styling
NButton, prefertype="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
ContributionHeatmapintentionally use separate buttons with gap spacing instead ofNButtonGroup, 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