diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000000..44abcba0d2 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,243 @@ +# PackRat - Outdoor Adventure Planning Platform + +PackRat is a modern full-stack application for outdoor enthusiasts to plan and organize their adventures. Built with React Native/Expo for mobile, Next.js for web, and Cloudflare Workers for API, all managed in a monorepo using Bun. + +**Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.** + +## Working Effectively + +### Prerequisites and Installation + +Install required tools: +- Install Bun: `curl -fsSL https://bun.sh/install | bash && source ~/.bashrc` +- Install Node.js (required for some tooling): Use Node.js 20+ +- Install GitHub CLI: `sudo apt install gh` (Ubuntu/Debian) or follow [GitHub CLI installation](https://cli.github.com) +- Install Wrangler CLI: `bun install -g wrangler` + +### Initial Setup + +**CRITICAL:** GitHub authentication is required for private packages: +1. Authenticate with GitHub CLI: `gh auth login` +2. Add packages scope: `gh auth refresh -h github.com -s read:packages` +3. Install dependencies: `bun install` (takes ~1 minute) + +**Alternative:** Set environment variable `PACKRAT_NATIVEWIND_UI_GITHUB_TOKEN` with a Personal Access Token that has `read:packages` scope. + +### Build and Development Commands + +**Install Dependencies:** +```bash +bun install # Takes ~1 minute. NEVER CANCEL. +``` + +**Code Quality:** +```bash +bun format # Format code with Biome (takes ~1 second) +bun lint # Lint code with Biome (takes ~1 second) +bun check-types # TypeScript checking - WILL FAIL without GitHub auth (takes ~17 seconds) +``` + +**Application Development:** + +**API Server (Cloudflare Workers):** +```bash +bun api # Start API development server on http://localhost:8787 +# Takes ~10 seconds to start. NEVER CANCEL. +# Shows Cloudflare Workers dev environment with local R2, queues, and AI bindings +# Will show network warnings - these are normal in development +``` + +**Mobile App (Expo/React Native):** +```bash +bun expo # Start Expo development server (Metro bundler) +bun android # Run on Android device/emulator +bun ios # Run on iOS device/simulator +# Expo starts in ~10 seconds. NEVER CANCEL. +# Runs on http://localhost:8081 +``` + +**Landing Page (Next.js):** +```bash +cd apps/landing +bun dev # Start Next.js dev server on http://localhost:3000 +# Takes ~5 seconds to start +``` + +**Guides Site (Next.js):** +```bash +cd apps/guides +bun dev # Start Next.js dev server on http://localhost:3000 +# Takes ~5 seconds to start +# Note: Content is pre-built during installation +``` + +### Testing + +**API Tests:** +```bash +cd packages/api +bun test # Run Vitest tests with Cloudflare Workers environment +# Currently requires GitHub authentication to pass +# Takes ~5 seconds when configured correctly +``` + +**Note:** API tests use Cloudflare Workers vitest pool and require proper authentication setup to pass. + +### Building for Production + +**API Deployment:** +```bash +cd packages/api +wrangler deploy # Deploy to Cloudflare Workers +``` + +**Next.js Applications:** +```bash +cd apps/landing && bun build # Build landing page +cd apps/guides && bun build # Build guides site (includes content generation) +# Note: Builds may fail in environments without internet access due to Google Fonts +``` + +**Mobile App Builds:** +```bash +cd apps/expo +# EAS Build (requires Expo account) +bun build:preview # Preview build locally +bun build:production # Production build locally +bun build:preview:eas # Preview build on EAS +bun build:production:eas # Production build on EAS +# Local builds take 10-15 minutes. NEVER CANCEL. Set timeout to 30+ minutes. +``` + +## Validation Scenarios + +**Always test these scenarios after making changes:** + +1. **API Validation:** + - Start API server: `bun api` + - Test health endpoint: `curl http://localhost:8787/api/health` + - Expected: `{"error":"Unauthorized"}` (auth required) + +2. **Mobile App Validation:** + - Start Expo: `bun expo` + - Check Metro bundler is running on http://localhost:8081 + - Can connect with Expo Go app or simulator + +3. **Web Applications:** + - Start dev servers for landing/guides: `cd apps/landing && bun dev` + - Access http://localhost:3000 + - Check for no build errors in console + +4. **Code Quality Validation:** + - Run `bun format && bun lint` - should complete without errors + - Pre-push hooks automatically run formatting checks + +## Repository Structure + +### Key Directories + +**Applications (`apps/`):** +- `expo/` - React Native mobile app with Expo +- `landing/` - Marketing/landing website (Next.js) +- `guides/` - Content site with generated outdoor guides (Next.js) + +**Packages (`packages/`):** +- `api/` - Cloudflare Workers API with Hono framework +- `ui/` - Shared UI components (requires GitHub auth) + +**Configuration Files:** +- `biome.json` - Code formatting and linting config +- `lefthook.yml` - Git hooks configuration +- `bunfig.toml` - Bun package manager configuration +- `package.json` - Monorepo scripts and dependencies + +### Important Files to Check + +**When modifying API:** +- Always check `packages/api/wrangler.jsonc` for Cloudflare configuration +- Update `packages/api/src/routes/` for new endpoints +- Check `packages/api/drizzle/` for database schema changes + +**When modifying mobile app:** +- Check `apps/expo/app.config.js` for Expo configuration +- Update `apps/expo/app/` for screen changes (uses Expo Router) +- Check `apps/expo/components/` for reusable components + +**When adding dependencies:** +- Run `bun fix:deps` to check for version mismatches +- Update relevant package.json files +- Private packages require GitHub authentication + +## Common Issues and Solutions + +**GitHub Authentication Failures:** +- Ensure `gh auth login` and `gh auth refresh -h github.com -s read:packages` are completed +- Alternative: Set `PACKRAT_NATIVEWIND_UI_GITHUB_TOKEN` environment variable +- Error: `401` from npm.pkg.github.com means authentication failed + +**Build Failures:** +- Next.js builds may fail without internet access (Google Fonts dependency) +- Type checking fails without private package authentication +- Use `bun clean && bun install` to reset dependencies + +**Development Server Issues:** +- API server shows network warnings - these are normal +- Expo runs in CI mode in some environments - reloads may be disabled +- Multiple apps running simultaneously may cause port conflicts + +**Testing Issues:** +- API tests require GitHub authentication and proper Cloudflare configuration +- Tests use `@cloudflare/vitest-pool-workers` for Workers environment simulation +- Mock external services for unit tests + +## Time Expectations + +**NEVER CANCEL these operations - they are expected to take time:** + +- Initial `bun install`: ~1 minute +- API server startup: ~10 seconds +- Expo startup: ~10 seconds +- Next.js dev server startup: ~5 seconds +- Type checking: ~17 seconds +- Code formatting: ~1 second +- Code linting: ~1 second +- API tests: ~5 seconds (when properly configured) +- Mobile app builds (local): 10-15 minutes - Set timeout to 30+ minutes +- Mobile app builds (EAS): 15-30 minutes - Set timeout to 60+ minutes + +## CI/CD Integration + +**Required Environment Variables for CI:** +- `PACKRAT_NATIVEWIND_UI_GITHUB_TOKEN` - GitHub Personal Access Token with `read:packages` scope +- Cloudflare API tokens for API deployment +- Expo credentials for mobile builds + +**GitHub Actions:** +- `.github/workflows/biome.yml` - Code quality checks +- See workflow files for complete CI setup + +## Development Workflow + +**Typical development session:** +```bash +# 1. Setup (one-time) +gh auth login +gh auth refresh -h github.com -s read:packages +bun install + +# 2. Start development servers (separate terminals) +bun api # Terminal 1: API server +bun expo # Terminal 2: Mobile app +cd apps/guides && bun dev # Terminal 3: Web app (optional) + +# 3. Make changes and validate +bun format # Format code +bun lint # Check linting +# Test functionality in Expo Go app or web browser + +# 4. Before committing +bun format && bun lint # Final quality check +# Git hooks will automatically run on push +``` + +**Always validate changes work end-to-end before committing.** \ No newline at end of file diff --git a/apps/expo/app.config.ts b/apps/expo/app.config.ts index 95218719f7..835a03ac62 100644 --- a/apps/expo/app.config.ts +++ b/apps/expo/app.config.ts @@ -6,7 +6,7 @@ export default (): ExpoConfig => { name: 'PackRat', slug: 'packrat', - version: '2.0.2', + version: '2.0.3', scheme: 'packrat', web: { bundler: 'metro', diff --git a/apps/expo/app/(app)/ai-chat.tsx b/apps/expo/app/(app)/ai-chat.tsx index a4301aada2..f6675153c5 100644 --- a/apps/expo/app/(app)/ai-chat.tsx +++ b/apps/expo/app/(app)/ai-chat.tsx @@ -2,6 +2,7 @@ import { type UIMessage, useChat } from '@ai-sdk/react'; import { Button, Text } from '@packrat/ui/nativewindui'; import { Icon } from '@roninoss/icons'; import { FlashList } from '@shopify/flash-list'; +import { DefaultChatTransport, type TextUIPart } from 'ai'; import { fetch as expoFetch } from 'expo/fetch'; import { clientEnvs } from 'expo-app/env/clientEnvs'; import { ChatBubble } from 'expo-app/features/ai/components/ChatBubble'; @@ -39,7 +40,6 @@ import Animated, { useSharedValue, } from 'react-native-reanimated'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { type TextUIPart, DefaultChatTransport } from 'ai'; const HEADER_HEIGHT = Platform.select({ ios: 88, default: 64 }); const _dimensions = Dimensions.get('window'); diff --git a/apps/expo/features/ai/components/ChatBubble.tsx b/apps/expo/features/ai/components/ChatBubble.tsx index 97e2f39d84..3705dda938 100644 --- a/apps/expo/features/ai/components/ChatBubble.tsx +++ b/apps/expo/features/ai/components/ChatBubble.tsx @@ -1,11 +1,11 @@ import { Text } from '@packrat/ui/nativewindui'; +import type { ToolUIPart, UIMessage } from 'ai'; import { ReportButton } from 'expo-app/features/ai/components/ReportButton'; import { cn } from 'expo-app/lib/cn'; import { formatAIResponse } from 'expo-app/utils/format-ai-response'; import { Platform, Pressable, View, type ViewStyle } from 'react-native'; import Animated, { interpolate, type SharedValue, useAnimatedStyle } from 'react-native-reanimated'; import { ToolInvocationRenderer } from './ToolInvocationRenderer'; -import type { UIMessage, ToolUIPart } from 'ai'; const BORDER_CURVE: ViewStyle = { borderCurve: 'continuous', @@ -21,7 +21,7 @@ export function ChatBubble({ item, translateX, userQuery }: ChatBubbleProps) { const rootStyle = useAnimatedStyle(() => ({ transform: [{ translateX: translateX.value }], })); - const dateStyle = useAnimatedStyle(() => ({ + const _dateStyle = useAnimatedStyle(() => ({ width: 75, position: 'absolute', right: 0, diff --git a/apps/expo/features/catalog/components/ItemLinks.tsx b/apps/expo/features/catalog/components/ItemLinks.tsx index f481a6ab0f..4c0c81577e 100644 --- a/apps/expo/features/catalog/components/ItemLinks.tsx +++ b/apps/expo/features/catalog/components/ItemLinks.tsx @@ -1,7 +1,7 @@ import { Text } from '@packrat/ui/nativewindui'; import { Icon } from '@roninoss/icons'; -import { useColorScheme } from 'expo-app/lib/hooks/useColorScheme'; import type { CatalogItem } from 'expo-app/features/catalog/types'; +import { useColorScheme } from 'expo-app/lib/hooks/useColorScheme'; import { Linking, TouchableOpacity, View } from 'react-native'; type ItemLinksProps = { diff --git a/apps/expo/features/catalog/screens/CatalogItemDetailScreen.tsx b/apps/expo/features/catalog/screens/CatalogItemDetailScreen.tsx index 47ad9c70e8..7e03205a6e 100644 --- a/apps/expo/features/catalog/screens/CatalogItemDetailScreen.tsx +++ b/apps/expo/features/catalog/screens/CatalogItemDetailScreen.tsx @@ -1,3 +1,4 @@ +import { Ionicons } from '@expo/vector-icons'; import { Button, Text } from '@packrat/ui/nativewindui'; import { Icon } from '@roninoss/icons'; import { Chip } from 'expo-app/components/initial/Chip'; @@ -10,7 +11,6 @@ import { ErrorScreen } from '../../../screens/ErrorScreen'; import { LoadingSpinnerScreen } from '../../../screens/LoadingSpinnerScreen'; import { NotFoundScreen } from '../../../screens/NotFoundScreen'; import { useCatalogItemDetails } from '../hooks'; -import { Ionicons } from '@expo/vector-icons'; export function CatalogItemDetailScreen() { const router = useRouter(); diff --git a/apps/expo/features/catalog/types.ts b/apps/expo/features/catalog/types.ts index 7e297f5613..7227bbd0b8 100644 --- a/apps/expo/features/catalog/types.ts +++ b/apps/expo/features/catalog/types.ts @@ -1,5 +1,3 @@ -import type { WeightUnit } from 'expo-app/types'; - export interface CatalogItemLink { id: string; title: string; diff --git a/apps/expo/package.json b/apps/expo/package.json index f9770056f4..4a84e75a63 100644 --- a/apps/expo/package.json +++ b/apps/expo/package.json @@ -1,6 +1,6 @@ { "name": "packrat-expo-v2-poc", - "version": "2.0.2", + "version": "2.0.3", "main": "expo-router/entry", "scripts": { "android": "expo run:android", diff --git a/apps/guides/package.json b/apps/guides/package.json index 7804d06989..128caf5258 100644 --- a/apps/guides/package.json +++ b/apps/guides/package.json @@ -1,6 +1,6 @@ { "name": "packrat-guides", - "version": "2.0.2", + "version": "2.0.3", "private": true, "scripts": { "dev": "next dev", diff --git a/apps/landing/package.json b/apps/landing/package.json index b557451144..765ef8f896 100644 --- a/apps/landing/package.json +++ b/apps/landing/package.json @@ -1,6 +1,6 @@ { "name": "packrat-landing", - "version": "2.0.2", + "version": "2.0.3", "private": true, "scripts": { "dev": "next dev", diff --git a/package.json b/package.json index c911ec7e73..544e409280 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "packrat-monorepo", - "version": "2.0.2", + "version": "2.0.3", "workspaces": [ "apps/*", "packages/*" diff --git a/packages/api/src/services/catalogService.ts b/packages/api/src/services/catalogService.ts index 478ddc4224..3db4d970a3 100644 --- a/packages/api/src/services/catalogService.ts +++ b/packages/api/src/services/catalogService.ts @@ -209,7 +209,7 @@ export class CatalogService { values: queries, openAiApiKey: this.env.OPENAI_API_KEY, cloudflareAccountId: this.env.CLOUDFLARE_ACCOUNT_ID, - cloudflareGatewayId: this.env.CLOUDFLARE_AI_GATEWAY_ID_ORG, + cloudflareGatewayId: this.env.CLOUDFLARE_AI_GATEWAY_ID, provider: this.env.AI_PROVIDER, }); @@ -296,7 +296,7 @@ export class CatalogService { openAiApiKey: this.env.OPENAI_API_KEY, values: embeddingTexts, cloudflareAccountId: this.env.CLOUDFLARE_ACCOUNT_ID, - cloudflareGatewayId: this.env.CLOUDFLARE_AI_GATEWAY_ID_ORG, + cloudflareGatewayId: this.env.CLOUDFLARE_AI_GATEWAY_ID, provider: this.env.AI_PROVIDER, }); diff --git a/packages/api/src/services/embeddingService.ts b/packages/api/src/services/embeddingService.ts index 7c85d9fa91..4be50644b6 100644 --- a/packages/api/src/services/embeddingService.ts +++ b/packages/api/src/services/embeddingService.ts @@ -54,7 +54,7 @@ export const generateManyEmbeddings = async ( const aiProvider = createAIProvider(providerConfig); const { embeddings } = await embedMany({ - model: aiProvider.embedding(DEFAULT_MODELS.EMBEDDING), + model: aiProvider.embedding(DEFAULT_MODELS.OPENAI_EMBEDDING), values: cleanValues, }); diff --git a/packages/api/src/services/etl/queue.ts b/packages/api/src/services/etl/queue.ts index 25ed40aace..96a5b610a2 100644 --- a/packages/api/src/services/etl/queue.ts +++ b/packages/api/src/services/etl/queue.ts @@ -265,7 +265,7 @@ async function processCatalogETLWriteBatch({ openAiApiKey: env.OPENAI_API_KEY, values: embeddingTexts, cloudflareAccountId: env.CLOUDFLARE_ACCOUNT_ID, - cloudflareGatewayId: env.CLOUDFLARE_AI_GATEWAY_ID_ORG, + cloudflareGatewayId: env.CLOUDFLARE_AI_GATEWAY_ID, provider: env.AI_PROVIDER, }); diff --git a/packages/api/src/services/packService.ts b/packages/api/src/services/packService.ts index be092aee09..815b45e043 100644 --- a/packages/api/src/services/packService.ts +++ b/packages/api/src/services/packService.ts @@ -167,7 +167,7 @@ export class PackService { private async generatePackConcepts(count: number): Promise { const openai = createOpenAI({ - apiKey: env(this.c).OPENAI_API_KEY, + apiKey: getEnv(this.c, 'OPENAI_API_KEY'), }); const { object } = await generateObject({ diff --git a/packages/ui/package.json b/packages/ui/package.json index a6d67afb0a..ddfd8b710e 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@packrat/ui", - "version": "2.0.2", + "version": "2.0.3", "private": true, "dependencies": { "@packrat-ai/nativewindui": "1.0.8"