7.6 KiB
Web Spec
Scope
This spec applies to web/**.
Read this file before changing the Astro site, React app islands, authenticated app routes, API clients, i18n, or responsive layout.
Current Stack
- Astro 6 for static public pages and route files.
- React 19 for interactive client UI.
- React Router DOM for the authenticated business app shell.
- Tailwind CSS 4 through
@tailwindcss/vite. - TypeScript strict mode.
- Local i18n from
web/src/i18n/utils.ts. - Backend API base for production:
https://api.meeyao.com. - Local development API access uses the Vite
/apiproxy inweb/astro.config.mjs.
Do not introduce a second frontend framework, a second router, or scattered API URL construction for web code.
Route Architecture
Public pages are Astro pages under web/src/pages/{locale}/ and use Marketing.astro.
Authenticated pages are Astro route shells that all render DashboardAppPage.astro. The actual logged-in application is a single React Router app:
DashboardApp.tsxowns React Router routes for dashboard, store, history, notifications, profile, settings, and divination pages.AppShell.tsxowns the persistent sidebar, mobile drawer, route guard, and authenticated layout.- Business page components render only their page body. They must not wrap themselves in
AppShell. - Sidebar navigation must use React Router navigation so the shell remains mounted and only the right-side content changes.
- Direct browser refresh on each existing business route must still render the app shell through Astro.
Login and public marketing/legal pages are not part of the authenticated app shell.
Auth Rules
- Login and registration are the same email-code flow. The backend auto-registers new email accounts.
- Test credentials for local verification:
test@example.comwith code123456. - Auth state is stored by
web/src/lib/auth.tsunder one local storage key. - Every authenticated route must recover or refresh the session before showing business content.
- Missing, expired, invalid, or refresh-failed tokens must clear local auth and redirect to
/{locale}/login. - Do not add silent success paths for auth failures.
API Rules
- All API paths live in
web/src/lib/api-routes.ts. - Shared request behavior lives in
web/src/lib/api-client.ts. - Auth/session behavior lives in
web/src/lib/auth.ts. - Business API functions live in
web/src/lib/api.ts. - Shared authenticated read caching lives in
web/src/lib/data-client.tsandweb/src/lib/resources.ts. - Components must call typed API helper functions, not inline
fetch('/api/...'). - Components that need profile, points, packages, history, notifications, or unread-count data should use the resource hooks/functions from
web/src/lib/resources.tsinstead of starting their own duplicate GET lifecycle. - Dashboard-visible user, points, notification, and history data must come from the backend. Do not hardcode those values.
- Production API host is
https://api.meeyao.com; local dev should use same-origin/apiand the Vite proxy.
Authenticated Data Resource Pattern
Use this pattern for backend reads that are reused across authenticated pages:
// lib/api.ts: transport-only business API
export function getPointsBalance(): Promise<PointsBalance> {
return authFetch<PointsBalance>(API_ROUTES.points.balance);
}
// lib/resources.ts: cache policy + hook/function surface
export function usePoints() {
return useResource({
key: pointsBalanceKey,
ttlMs: 60_000,
fetcher: getPointsBalance,
staleWhileRevalidate: true,
});
}
Resource contracts:
lib/api.tsremains transport-only: no per-endpoint ad hoc memory cache there.lib/resources.tsowns resource keys, TTLs, in-flight dedupe, stale-while-revalidate behavior, prefetch, and mutation invalidation.clearAuth()must clear the shared data cache so authenticated data cannot leak across users.- Resource hooks must support disabled/optional keys for pages where an id may be absent; do not create a fetcher that intentionally rejects during normal render.
- Active hooks must refetch after invalidation when they still need the resource.
Invalidation matrix:
- Profile, avatar, or settings write -> set the profile resource with the returned backend profile.
- Divination run or follow-up completion -> invalidate points and the relevant history list/thread resources.
- Notification mark-read -> patch the notification list and decrement unread count when the item changes from unread to read.
- Mark-all-notifications-read -> patch the notification list and set unread count to zero.
- Logout, expired refresh, or invalid auth -> clear auth and clear all resource data.
Wrong vs correct:
// Wrong: every page starts an independent duplicate GET.
useEffect(() => {
getUserProfile().then(setProfile);
}, []);
// Correct: subscribe to the shared profile resource.
const profileState = useProfile();
Layout Rules
- Build mobile-first, then add
sm:,md:,lg:, andxl:refinements. - Business pages must not require horizontal scrolling at common phone widths such as
390x844. - Use responsive stacks for fixed-width desktop columns:
flex-col lg:flex-row,w-full lg:w-[...]. - Keep the authenticated shell as
h-screenwith the main content scrollable. - Mobile sidebar must be reachable through the menu button and must not hide the page content permanently.
- Public header mobile navigation must expose feature, pricing, about, login, and language switching.
Mobile Guided Overlays
- Keep one dimming strategy per viewport. Do not combine a full-screen dark overlay with a spotlight element that also uses an oversized outer shadow on the same mobile viewport.
- Mobile spotlight targets should fit inside the phone viewport. If a desktop tutorial highlights a tall panel, use a smaller mobile-only target such as the rows or controls that the step actually explains.
- Tooltip placement and arrow direction must match: a tooltip above the target uses a bottom arrow pointing down; a tooltip below the target uses a top arrow pointing up.
- When the app shell owns scrolling, compute mobile overlay coordinates relative to the page component host and visible scroll container, not the document body.
i18n Rules
- Supported locales:
zh,zh_Hant,en. - Routes are prefixed by locale, including the default locale.
- User-visible text should come from
web/src/i18n/utils.tsor locale-specific content assets. - Do not add user-facing strings in only one locale.
Design Source
- Pencil design files under
web/design/are the visual source for login and public page design. - If UI implementation diverges from Pencil, inspect the design first and keep the code aligned unless the user explicitly asks to change the design.
- Assets in
web/public/imagesandweb/public/legalare symlinks toweb/design/assets; do not duplicate them.
Verification
Before finishing meaningful web changes:
- Run
npm run buildinweb/. - Use Chrome DevTools on
http://localhost:4322/for at least one desktop and one phone viewport when layout or routing changed. - For authenticated changes, verify the test account can log in and lands on
/{locale}/dashboard. - Verify direct unauthenticated access to a business route redirects to login.
- Verify sidebar navigation changes content without a full document reload.
Forbidden
- Do not hardcode API URLs or endpoint paths in components.
- Do not add
.envas a required file for normal local development. - Do not reintroduce page-refresh sidebar navigation inside the authenticated app.
- Do not wrap business page components in nested cards or duplicate
AppShell. - Do not add fallback/mock success behavior for failed API calls.