Replace pnpm-lock.yaml with package-lock.json. There was no pnpm-workspace.yaml and no packageManager field, so the lockfile was the only pnpm-specific config. Add package.json#allowScripts for sharp and unrs-resolver. pnpm 11 refuses to install this branch at all without that approval (ERR_PNPM_IGNORED_BUILDS), so recording it under npm keeps the branch installable; sharp backs next/image and unrs-resolver backs eslint module resolution. Update CLAUDE.md to describe npm commands and drop the stale pnpm permission from the tracked local settings. next-env.d.ts and tsconfig.tsbuildinfo are regenerated by the build. Type-check, lint, test, and build fail on this branch exactly as they did on the pnpm lockfile: six TS errors, an unresolvable @typescript-eslint/recommended config, and a jest.config.ts that imports next/jest instead of next/jest.js. Verified against the pre-change tree; none of it is caused by this commit.
16 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Important Instructions
ALWAYS update this CLAUDE.md file after completing tasks that the user marks as done. Add relevant information about new features, architectural changes, or important implementation details that future Claude instances should know about.
Project Overview
VIGEOGUESSR is a Vietnamese geography guessing game built with Next.js 15 and React 19. Players view 360° panoramic street images from Vietnamese cities and guess the location on an interactive map. The game uses Mapillary Graph API for panoramic images and calculates distance-based scoring.
🛠️ Development Environment
- Language: TypeScript (
^5.0.0) - Framework: Next.js 15 (App Router)
- React: React 19
- Styling: Tailwind CSS
- Component Library: shadcn/ui (for generic UI components)
- Specialized Libraries: MapLibre GL JS + MapillaryJS (for mapping/street view)
- Data Fetching: React Query (TanStack) for API calls
- Testing: Jest + React Testing Library
- Linting: ESLint with
@typescript-eslint - Formatting: Prettier
- Package Manager: npm
📂 Project Structure
src/
├── app/ # Next.js App Router
│ ├── layout.tsx
│ ├── page.tsx
│ ├── game/
│ └── api/ # Server-side API routes
├── components/ # UI components (shadcn/ui + game-specific)
│ ├── features/ # Game-specific components
│ │ ├── GameMap.tsx # MapLibre GL JS integration
│ │ ├── PanoViewer.tsx # MapillaryJS integration
│ │ └── ResultModal.tsx # Result display with map + street view
│ └── ui/ # shadcn/ui components
├── services/ # Service layer for external APIs and business logic
│ ├── geography.service.ts # Coordinate validation and distance calculations
│ ├── scoring.service.ts # Game scoring and point calculations
│ ├── mapillary.service.ts # Mapillary API integration
│ └── game-image.service.ts # Game image fetching logic
├── hooks/ # Custom React hooks
│ ├── useMapillary.ts # Image fetching with React Query
│ └── useScoring.ts # Scoring API with React Query
├── lib/ # Client helpers, API wrappers, utilities
├── constants/ # Game constants (cities, locations)
├── styles/ # Tailwind customizations
├── tests/ # Unit and integration tests
└── public/
📁 Configuration Files
Key configuration files in the project root:
tsconfig.json- TypeScript configuration with strict settings and path mappingtailwind.config.ts- Tailwind CSS customization with TypeScript.eslintrc.json- ESLint with TypeScript rulespostcss.config.ts- PostCSS configuration with TypeScriptnext.config.ts- Next.js configuration with TypeScriptjest.config.ts- Jest testing configuration with TypeScriptjest.setup.ts- Jest setup and mocks with TypeScriptpackage.json- Dependencies and scriptsnext-env.d.ts- Next.js TypeScript environment types
📦 Installation & Setup
- Tailwind CSS with PostCSS configuration
- shadcn/ui:
npx shadcn-ui@latest init - React Query:
<QueryClientProvider>in app/layout.tsx - MapLibre GL JS:
npm install maplibre-gl - MapillaryJS:
npm install mapillary-js - TypeScript configuration with
tsconfig.json - ESLint with TypeScript support
⚙️ Development Commands
- Dev server:
npm run dev- Start development server on localhost:3000 - Build:
npm run build- Build for production - Start:
npm start- Start production server - Lint:
npm run lint- Run ESLint to check code quality - Test:
npm test- Run Jest + RTL tests - Format:
npm run format- Format code with Prettier
🧠 React Query Architecture
API Data Fetching (Option A - Recommended)
// hooks/useMapillary.ts
const { data: imageData, isLoading } = useQuery({
queryKey: ["mapillary", city],
queryFn: () =>
fetch(`/api/mapillary?location=${city}`).then((res) => res.json()),
staleTime: 5 * 60 * 1000, // 5 minutes
});
// hooks/useScoring.ts
const scoreMutation = useMutation({
mutationFn: (scoreData: ScoreData) =>
fetch("/api/scoring", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(scoreData),
}).then((res) => res.json()),
});
Benefits:
- Automatic caching of API responses
- Built-in loading/error states
- Retry logic for failed requests
- Server APIs unchanged
🎨 Styling Architecture
Tailwind + Specialized Libraries
// GameMap component with MapLibre GL JS
import "maplibre-gl/dist/maplibre-gl.css";
interface GameMapProps {
choiceLocation: string;
onLocationSelect: (coords: [number, number]) => void;
}
export default function GameMap({ choiceLocation, onLocationSelect }: GameMapProps) {
return (
<div className="w-full h-96 rounded-lg border border-gray-200 overflow-hidden">
<div ref={mapRef} className="w-full h-full" />
</div>
);
}
Integration Strategy:
- Keep required CSS imports for MapLibre GL JS and MapillaryJS
- Wrap components with Tailwind utility classes
- Create custom Tailwind components for consistent styling
- Remove custom CSS in favor of Tailwind utilities
🧩 Component Guidelines
shadcn/ui Integration
- Replace: Header, DonateModal, UsernameModal → Button, Dialog, Input components
- Keep: GameMap, PanoViewer, ResultModal (specialized functionality)
- Mixing: Game components use shadcn/ui internally for buttons, forms, etc.
Game-Specific Components
// PanoViewer.tsx - Keep (MapillaryJS integration)
// GameMap.tsx - Keep (MapLibre GL JS integration)
// ResultModal.tsx - Keep but use shadcn/ui Button, Dialog internally
🧪 Testing Strategy
Jest + React Testing Library
// tests/GameMap.test.tsx
import { render, screen } from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import GameMap from "@/components/features/GameMap";
// Mock MapLibre GL JS
jest.mock("maplibre-gl", () => ({
Map: jest.fn(() => ({
on: jest.fn(),
remove: jest.fn(),
})),
}));
test("renders game map container", () => {
const queryClient = new QueryClient();
render(
<QueryClientProvider client={queryClient}>
<GameMap choiceLocation="HN" onLocationSelect={jest.fn()} />
</QueryClientProvider>
);
expect(screen.getByRole("application")).toBeInTheDocument();
});
Testing Considerations
- Mock specialized libraries: MapLibre GL JS, MapillaryJS
- Test React Query hooks: Use
QueryClientwrapper - Focus on user interactions: Button clicks, form submissions
- Skip complex map/street view rendering: Test props and callbacks
📝 Code Style Standards
- TypeScript: Use strict typing, prefer arrow functions, annotate return types
- Imports: Group React → Next.js → libraries → local components
- Components: Always destructure props, use meaningful names, define interfaces
- Types: Avoid
anytype, useunknownor strict generics - Styling: Prefer Tailwind utilities over custom CSS
- API: Keep server logic in
/apiroutes, client logic in hooks
🔍 Migration Guidelines
From Custom CSS to Tailwind
- Replace custom CSS classes with Tailwind utilities
- Keep required CSS imports for MapLibre/MapillaryJS
- Use Tailwind components for consistent styling
- Remove
globals.csscustom styles gradually
Adding shadcn/ui Components
- Install:
npx shadcn-ui@latest add button dialog input - Replace existing UI components with shadcn/ui equivalents
- Maintain game-specific component functionality
- Use shadcn/ui components within specialized components
Architecture
Multi-Library Approach: Each Tool for Its Optimal Purpose
This project uses a carefully chosen combination of specialized libraries rather than trying to force a single library to handle all use cases. This approach provides superior user experience, maintainability, and performance.
Core Game Flow
Multi-User Gameplay Experience:
- User Entry: Home page checks localStorage for cached username, displays popup if not found
- City Selection: User selects from available Vietnamese cities on home page
- Game Initialization: Navigate to game scene with selected city parameter
- Image Loading: Server provides random panoramic image from city bounds via
src/app/api/mapillary/route.ts - Gameplay:
- Display raw panoramic image without location metadata
- Show city overview map for location guessing
- User submits guess location with username
- Scoring System: Server calculates distance-based points (0-5 scale) per user session:
- 0-50m = 5 points
- 50m-100m = 4 points
- 100m-200m = 3 points
- 200m-500m = 2 points
- 500m-1km = 1 point
-
1km = 0 points
- Results:
- Show guess location, actual location, distance, and points
- Enable MapillaryJS navigation around actual location
- "Next" button continues to next round
Technical Implementation:
- Image Fetching:
src/services/game-image.service.tshandles client-side image fetching with retry logic - API Integration:
src/services/mapillary.service.tsprovides server-side Mapillary API integration - Display:
src/components/features/PanoViewer.tsxrenders raw 360° images using MapillaryJS npm module - Interaction:
src/components/features/GameMap.tsxprovides MapLibre GL JS-based interactive map for location guessing - Scoring:
src/services/scoring.service.tshandles point calculation with configurable thresholds - Geography:
src/services/geography.service.tsprovides distance calculation with OpenStreetMap validation - Session Management: Per-user session tracking for concurrent multi-player support
Key Components
- GameMap: MapLibre GL JS integration with click-based location selection, automatic centering based on city choice, and native coordinate synchronization
- PanoViewer: MapillaryJS npm module integration with precise coordinate extraction, error handling and fallback display
- ResultModal: MapLibre GL JS result map showing true location (green), guess location (red), and connecting line with distance calculation
- Game Page: Main game orchestration with state management for loading, guessing, and results
Data Structure
- City boundaries defined in
src/constants/locations.ts:LOCATION_BOUNDSobject with coordinates and delta values for API queries - Location codes: HN (Hanoi), TPHCM (Ho Chi Minh City), HP (Hai Phong), ND (Nam Dinh), DN (Da Nang), DL (Dalat), DHLA (Duc Hoa)
- Constants organized with enums:
LOCATION_CODES,LOCATION_BOUNDS,LOCATION_NAMES
External Dependencies
- MapLibre GL JS: Interactive map rendering (npm module with CSS import) - provides native coordinate synchronization with MapillaryJS
- MapillaryJS: 360° street imagery display (npm module with CSS import)
- Mapillary Graph API: Street view image source with server-side access token security
State Management
Game state managed at page level with React hooks:
- Image loading states and error handling
- Location selection and guess submission
- Distance calculation and result display
Recent Implementation Updates
Modern Tech Stack Migration (Latest)
- Language: TypeScript (
^5.7.3) with strict typing, interfaces, and complete migration - Package Manager: npm, with scripts dev, build, start, lint, format, test, type-check
- Tailwind CSS: Complete migration from custom CSS to Tailwind utilities with custom design tokens matching game theme
- shadcn/ui: Installed and configured with utility functions, ready for UI component replacements
- React Query: Added TanStack Query with QueryClientProvider in layout and custom hooks (
useMapillary,useScoring) - Testing Setup: Configured Jest + React Testing Library with specialized mocks for MapLibre/MapillaryJS libraries
- Project Structure: Added modern hooks/, lib/, types/ directories following TypeScript architectural guidelines
- Development Environment: Updated to modern toolchain with TypeScript, ESLint, Prettier formatting and enhanced DX
- File Migration: All 25+ JavaScript files converted to TypeScript with proper typing
- Type Safety: Custom type definitions for external libraries (MapLibre GL JS, MapillaryJS)
- Zero JavaScript: Complete elimination of ALL
.jsfiles from entire project (src/ + root config files)
Current Architecture Status
- ✅ Framework: Next.js 15 + React 19
- ✅ Language: TypeScript with strict configuration - COMPLETE MIGRATION
- ✅ Styling: Tailwind CSS with custom game design tokens
- ✅ Data Fetching: React Query hooks for API management
- ✅ Testing: Jest + RTL with TypeScript and library mocks
- ✅ Package Management: npm with modern scripts
- ✅ App Router: All app router files migrated to TypeScript (.tsx)
- ✅ API Routes: All API routes migrated to TypeScript (.ts)
- ✅ Service Layer: All service files migrated with proper TypeScript interfaces
- ✅ Components: All components migrated to TypeScript (GameMap, PanoViewer, ResultModal, Header, DonateModal, UsernameModal)
- ✅ Hooks: All hooks migrated with proper TypeScript return types
- ✅ Lib Files: React Query provider and utilities migrated to TypeScript
- ✅ Constants: Location constants migrated with TypeScript interfaces and enums
- ✅ Type Definitions: Custom type definitions created for MapLibre GL JS and MapillaryJS
- ✅ Index Files: All index files migrated and JavaScript files removed
- ✅ Config Files: All root configuration files migrated (next.config.ts, jest.config.ts, postcss.config.ts, tailwind.config.ts)
- 🚧 UI Components: Ready for shadcn/ui migration (existing components are TypeScript-ready)
🎉 COMPLETE TYPESCRIPT MIGRATION ACHIEVED - Zero JavaScript files remaining in entire project. All source code AND configuration files are now TypeScript-enabled with modern tooling and industry-standard development practices.
🏗️ Service Layer Architecture
Service Organization
The project now uses a clean service layer architecture with specialized services:
// Geography service for coordinate operations
import { calculateDistance, isValidCoordinate } from '@/services/geography.service';
// Scoring service for game mechanics
import { calculatePoints, getScoreDescription, formatDistance } from '@/services/scoring.service';
// Mapillary service for street view integration
import { getRandomMapillaryImage, validateMapillaryImage } from '@/services/mapillary.service';
// Game image service for client-side image fetching
import { fetchRandomGameImage } from '@/services/game-image.service';
Service Benefits
- Separation of Concerns: Each service handles a specific domain
- Testability: Services can be tested in isolation
- Reusability: Services can be imported across components
- Type Safety: JSDoc documentation provides IDE autocomplete
- Error Handling: Consistent error patterns across services
- Configuration: Centralized configuration objects
Migration from Legacy Utils
The legacy src/utils/ directory has been completely removed. All functionality has been migrated to the service layer with improved error handling, validation, and documentation.