# Project export: Buckit

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 12.0
- Tagline: Buckit is a social app that turns forgotten bucket list items into shared experiences, using AI to find the next personalized challenge you’ll actually follow through on.
- Devpost: https://devpost.com/software/buckit-l28hvx
- GitHub: https://github.com/dvdawg/buckit
- Video: https://www.youtube.com/embed/dqLuAI0mO38?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — David Kim (64 commits), Brandon Yu (55 commits)

## Devpost submission (written by the team)

### Inspiration

We all have those things we’ve been wanting to do, the weekend trip we’ve postponed, the restaurant we’ve been saving “for when we have time.” But the truth is: we rarely do them. In a world where the economy is uncertain, workloads are heavier than ever, and burnout has become the norm, most of us feel too consumed by survival in work and school to pursue what actually makes us feel alive. Since the wifi was down during the morning, we weren’t able to demo our app on the video so a screen recording demo is uploaded here https://youtube.com/shorts/gwBSbZ5WAfQ?si=BBn0g7Ib8itHSqai

### What it does

Buckit is a social bucket list app that helps people discover, organize, and complete new experiences with friends. Users create buckets, collections of goals or activities they want to try, think "Trip to San Francisco," "Cafes," "Family." Each bucket contains individual items with details like description, location, and timing. Friends can join buckets, complete challenges together, and track shared progress. Buckit's AI model drives exploration features of the app.

### How we built it

The scoring system for recommendations is as follows: where s represents a score given u (the user), i (the item/activity), and c (the context). Buckit's recommendation system is based off of brain signals - inspired by vmPFC (ventromedial PreFrontal Cortex) signals, modeling social reward and peer influence, and DMM (Default Mode Network) activity associated with intrinsic valuation and aesthetics. These two are modeled by the SocialBonus and Appeal terms respectively. For the remaining terms: The z trait represents long term interests constituted by aggregate historical activities and interactions inner producted with the embedding of the input item in the same vector space. The z state represents shorter term interests derived from DIN-style attention describing latent interests similarly inner producted with the item embeddin. The EffortCost represents the "cost" of an item derived from geographical distance and price modeled after vmPFS models on effort vs reward The Novelty/Diversity term creates variety, created from MMR and LinUCB Bandit exploration rewards roughly inspired by dopaminagenic novelty networks. Combining the effect of all of these factors creates a trained comprehensive score, weighted by coefficients alpha, beta, gamma, delta, lambda, and rho. This was inspired by the following research efforts: Human vmPFC is necessary for (pro-)social valuation (Nature Human Behaviour, 2024 — Lockwood et al.) A lesion study with 25 vmPFC patients with controls using effort-reward tasks in selfish and "prosocial" conditions. These lesions disrupted reward/effort tradeoffs and effort for prosocial behaviors, showing necessity for computing subjective social values. For Buckit: We implement scalar value function that integrates social parameters and subtracts cost terms. Prosocial features should be weighted in the value computation. Value estimation vs effort mobilization dissociate (J. Neurosci, 2024 — Clairis et al.) An fMRI study breaking down expected utility from effort with RL/utility models. vmPFC signals track subjective value estimation and dorsomedial PFC track effort. These systems estimate value before energization. For Buckit: We decouple "appeal" and "executability" in the scoring function. Compute raw value from content features and social bonuses, then apply cost discounting (distance, price, time, coordination complexity). Post-process with an execution feasibility gate that downweights high-friction items. Emphasizes both appeal and feasibility signals in the UI. Default-Mode Network represents aesthetic appeal across domains (PNAS, 2019 — Vessel et al.) A study on fMRI during visual stimulus viewing across categories. DMN activity patterns encode aesthetic appeal in general domains, independent of category-specific visual processing. For Buckit: We train a multimodal appeal predictor over CLIP embeddings of activity images, text, and descriptions. Supervising on completion/skip decisions and sentiment annotations creates a computational analog of DMN's aesthetic signal. State-dependent connectivity predicts peak pleasure (PLoS Biology, 2024 — Mori et al.) A study on resting-state fMRI before music listening. Pre-task connectivity between auditory and reward networks predicted pleasure intensity better than trait-level baselines. For Buckit: Represent users as trait embeddings (long-run interaction history) plus state embeddings created from context (timestamp, weather, recent activity sequence, location, social configuration). reweight category scores by session state, increasing the weight of indoor/cultural activities during rain/evening, outdoor activities during sun/morning. Multi-objective recommender systems for long-term outcomes (Frontiers in Big Data, 2023 — Jannach et al.) A survey of recommendation goals besides accuracy: diversity, novelty, fairness, long-term engagement, and more. Covers evaluation frameworks and optimization strategies including constrained reranking, Pareto methods, and exploration-aware bandits. For Buckit: We define KPIs including verified completions per 1000 impressions and catalog coverage with two-stage ranking: (1) recommended candidate list creation using embedding similarity + geo-filtering; (2) reranking with diversity, novelty, and budget constraints (MMR + fairness bounds).

### Challenges we ran into

One of the greatest challenges we encountered was connectivity. Our product relies heavily on user interaction and sharing, thus it was critical that all corners of our app allowed for that, which brought array of difficulties.

### Accomplishments we're proud of

We are especially proud of the idea of our project. There was a great tendency throughout this project to overcomplicate or over automate features, however we believe we struck a good balance of tech that works for people.

### What we learned

We learned that data persistence is really hard.

### What's next

Our AI model is unique in its ability to understand the user's social behaviors and long-term/short-term identity. But, it still has a long way to go.

## README (from the GitHub repository)

# Buckit
Buckit is a social app that turns forgotten bucket list items into shared experiences, using AI to find the next challenge you’ll actually follow through on.

We all have those things we’ve been wanting to do, the weekend trip we’ve postponed, the restaurant we’ve been saving “for when we have time.”

But the truth is: we rarely do them.

In a world where the economy is uncertain, workloads are heavier than ever, and burnout has become the norm, most of us feel too consumed by survival in work and school to pursue what actually makes us feel alive.

Buckit is a social bucket list app that helps people discover, organize, and complete new experiences with friends.

Users create buckets, collections of goals or activities they want to try, think "Trip to San Francisco," "Cafes," "Family." Each bucket contains individual items with details like description, location, and timing.

Friends can join buckets, complete challenges together, and track shared progress.

Buckit's AI model drives exploration features of the app.

Try it. Track it. Buckit.

## Initial Design Mockups
![initial_design](./readme_assets/initial_designs.png)
## PRD v1.0
[Read the full document here (formatted with Chat-GPT)](https://docs.google.com/document/d/1zJ0PVIeczIu6qECpJUD3cGE9qiwpAGbLtxmicYlJFzo/edit?usp=sharing)

## Technical Design
### Scoring
The scoring system for the recommendation system is as follows:

![equation](https://latex.codecogs.com/svg.image?%20s(u,i,c)=%5Cunderbrace%7B%5Calpha%5C,%5Ctextbf%7BAppeal%7D_%7B%5Ctext%7BMM%7D%7D(i)%7D_%7B%5Ctext%7BDMN/appeal%7D%7D&plus;%5Cunderbrace%7B%5Cbeta%5C,%5Clangle%5Cmathbf%7Bz%7D%5E%7B%5Ctext%7Btrait%7D%7D_u,%5Cmathbf%7Be%7D_i%5Crangle%7D_%7B%5Ctext%7Bwho%20you%20are%7D%7D&plus;%5Cunderbrace%7B%5Cgamma%5C,%5Clangle%5Cmathbf%7Bz%7D%5E%7B%5Ctext%7Bstate%7D%7D_u(c),%5Cmathbf%7Be%7D_i%5Crangle%7D_%7B%5Ctext%7Bhow%20you%20feel%20now%7D%7D&plus;%5Cunderbrace%7B%5Cdelta%5C,%5Ctext%7BSocialBonus%7D(u,i)%7D_%7B%5Ctext%7BvmPFC%20social%7D%7D-%5Cunderbrace%7B%5Clambda%5C,%5Ctext%7BEffortCost%7D(i,c)%7D_%7B%5Ctext%7Bvalue%20minus%20cost%7D%7D&plus;%5Cunderbrace%7B%5Crho%5C,%5Ctext%7BNovelty/Diversity%7D(i%5Cmid%5Cmathcal%7BL%7D)%7D_%7B%5Ctext%7Bmulti-objective%7D%7D)

where s represents a score given u (the user), i (the item/activity), and c (the context).

Buckit's recommendation system is based off of brain signals - inspired by vmPFC (ventromedial PreFrontal Cortex) signals, modeling social reward and peer influence, and DMM (Default Mode Network) activity associated with intrinsic valuation and aesthetics. These two are modeled by the SocialBonus and Appeal terms respectively. 

For the remaining terms:
- The z trait represents long term interests constituted by aggregate historical activities and interactions inner producted with the embedding of the input item in the same vector space.
- The z state represents shorter term interests derived from DIN-style attention describing latent interests similarly inner producted with the item embeddin.
- The EffortCost represents the "cost" of an item derived from geographical distance and price modeled after vmPFS models on effort vs reward
- The Novelty/Diversity term creates variety, created from MMR and LinUCB Bandit exploration rewards roughly inspired by dopaminagenic novelty networks.

Combining the effect of all of these factors creates a trained comprehensive score, weighted by coefficients alpha, beta, gamma, delta, lambda, and rho.

### Reasoning, Inspiration, and Scientific Backing
The following papers fed into our scoring system design:

#### Human vmPFC is necessary for (pro-)social valuation (Nature Human Behaviour, 2024 — Lockwood et al.)
A lesion study with 25 vmPFC patients with controls using effort-reward tasks in selfish and "prosocial" conditions. These lesions disrupted reward/effort tradeoffs and effort for prosocial behaviors, showing necessity for computing subjective social values.

For Buckit: We implement scalar value function that integrates social parameters and subtracts cost terms. Prosocial features should be weighted in the value computation.

#### Value estimation vs effort mobilization dissociate (J. Neurosci, 2024 — Clairis et al.)
An fMRI study breaking down expected utility from effort with RL/utility models. vmPFC signals track subjective value estimation and dorsomedial PFC track effort. These systems estimate value before energization.

For Buckit: We decouple "appeal" and "executability" in the scoring function. Compute raw value from content features and social bonuses, then apply cost discounting (distance, price, time, coordination complexity). Post-process with an execution feasibility gate that downweights high-friction items. Emphasizes both appeal and feasibility signals in the UI.

#### Default-Mode Network represents aesthetic appeal across domains (PNAS, 2019 — Vessel et al.)
A study on fMRI during visual stimulus viewing across categories. DMN activity patterns encode aesthetic appeal in general domains, independent of category-specific visual processing.

For Buckit: We train a multimodal appeal predictor over CLIP embeddings of activity images, text, and descriptions. Supervising on completion/skip decisions and sentiment annotations creates a computational analog of DMN's aesthetic signal.


#### State-dependent connectivity predicts peak pleasure (PLoS Biology, 2024 — Mori et al.)
A study on resting-state fMRI before music listening. Pre-task connectivity between auditory and reward networks predicted pleasure intensity better than trait-level baselines.

For Buckit: Represent users as trait embeddings (long-run interaction history) plus state embeddings created from context (timestamp, weather, recent activity sequence, location, social configuration). reweight category scores by session state, increasing the weight of indoor/cultural activities during rain/evening, outdoor activities during sun/morning.

#### Multi-objective recommender systems for long-term outcomes (Frontiers in Big Data, 2023 — Jannach et al.)
A survey of recommendation goals besides accuracy: diversity, novelty, fairness, long-term engagement, and more. Covers evaluation frameworks and optimization strategies including constrained reranking, Pareto methods, and exploration-aware bandits.

For Buckit: We define KPIs including verified completions per 1000 impressions and catalog coverage with two-stage ranking: (1) recommended candidate list creation using embedding similarity + geo-filtering; (2) reranking with diversity, novelty, and budget constraints (MMR + fairness bounds).


## Detected evidence (automated analysis)

Indexed codebase: 234 recognized source files, 1102 KB.
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- SQL (language) — detected in the code
- Supabase (technology) — detected in the code
- TypeScript (language) — detected in the code
- PostgreSQL (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (120 of 244)

```
.gitignore
apps/mobile/buckit/.gitignore
apps/mobile/buckit/app.config.ts
apps/mobile/buckit/app.json
apps/mobile/buckit/app/_layout.tsx
apps/mobile/buckit/app/(tabs)/_layout.tsx
apps/mobile/buckit/app/(tabs)/add.tsx
apps/mobile/buckit/app/(tabs)/explore.tsx
apps/mobile/buckit/app/(tabs)/home.tsx
apps/mobile/buckit/app/(tabs)/my-buckets.tsx
apps/mobile/buckit/app/(tabs)/profile.tsx
apps/mobile/buckit/app/add-friends.tsx
apps/mobile/buckit/app/buckets/_layout.tsx
apps/mobile/buckit/app/buckets/[id].tsx
apps/mobile/buckit/app/buckets/[id]/challenge.tsx
apps/mobile/buckit/app/buckets/index.tsx
apps/mobile/buckit/app/challenges/[id].tsx
apps/mobile/buckit/app/challenges/index.tsx
apps/mobile/buckit/app/challenges/theme/[theme].tsx
apps/mobile/buckit/app/create-bucket.tsx
apps/mobile/buckit/app/create-challenge.tsx
apps/mobile/buckit/app/definition.tsx
apps/mobile/buckit/app/dev/metrics.tsx
apps/mobile/buckit/app/friend-requests.tsx
apps/mobile/buckit/app/friends-list.tsx
apps/mobile/buckit/app/index.tsx
apps/mobile/buckit/app/invite-friends.tsx
apps/mobile/buckit/app/login.tsx
apps/mobile/buckit/app/modal.tsx
apps/mobile/buckit/app/modals/_layout.tsx
apps/mobile/buckit/app/modals/new-bucket.tsx
apps/mobile/buckit/app/modals/new-item.tsx
apps/mobile/buckit/app/performance.tsx
apps/mobile/buckit/app/profile/[id].tsx
apps/mobile/buckit/app/recommendations.tsx
apps/mobile/buckit/app/register.tsx
apps/mobile/buckit/app/search-users.tsx
apps/mobile/buckit/app/settings.tsx
apps/mobile/buckit/app/splash.tsx
apps/mobile/buckit/app/user-profile/[id].tsx
apps/mobile/buckit/components/Avatar.tsx
apps/mobile/buckit/components/BucketCard.tsx
apps/mobile/buckit/components/BucketLogo.tsx
apps/mobile/buckit/components/BucketVisibilitySelector.tsx
apps/mobile/buckit/components/ChallengeDetailModal.tsx
apps/mobile/buckit/components/ChallengeModal.tsx
apps/mobile/buckit/components/ChallengeRatingModal.tsx
apps/mobile/buckit/components/ColdStartModal.tsx
apps/mobile/buckit/components/CollaboratorDebug.tsx
apps/mobile/buckit/components/CompletionRatingModal.tsx
apps/mobile/buckit/components/DatabaseTest.tsx
apps/mobile/buckit/components/external-link.tsx
apps/mobile/buckit/components/FeedEvent.tsx
apps/mobile/buckit/components/FloatingAddButton.tsx
apps/mobile/buckit/components/FriendsCompletionCard.tsx
apps/mobile/buckit/components/FriendsSelectionModal.tsx
apps/mobile/buckit/components/GlassCard.tsx
apps/mobile/buckit/components/GlassStyleDemo.tsx
apps/mobile/buckit/components/haptic-tab.tsx
apps/mobile/buckit/components/hello-wave.tsx
apps/mobile/buckit/components/ItemRow.tsx
apps/mobile/buckit/components/LocationDisplay.tsx
apps/mobile/buckit/components/LocationPicker.tsx
apps/mobile/buckit/components/parallax-scroll-view.tsx
apps/mobile/buckit/components/PerformanceDashboard.tsx
apps/mobile/buckit/components/PerformancePreview.tsx
apps/mobile/buckit/components/RecommendationCard.tsx
apps/mobile/buckit/components/SharedPhotoAlbum.tsx
apps/mobile/buckit/components/SupabaseTest.tsx
apps/mobile/buckit/components/TabBarStyles.tsx
apps/mobile/buckit/components/themed-text.tsx
apps/mobile/buckit/components/themed-view.tsx
apps/mobile/buckit/components/ui/collapsible.tsx
apps/mobile/buckit/components/ui/icon-symbol.ios.tsx
apps/mobile/buckit/components/ui/icon-symbol.tsx
apps/mobile/buckit/components/ViewOnlyChallengeCard.tsx
apps/mobile/buckit/components/ViewOnlyChallengeModal.tsx
apps/mobile/buckit/constants/theme.ts
apps/mobile/buckit/eslint.config.js
apps/mobile/buckit/hooks/use-color-scheme.ts
apps/mobile/buckit/hooks/use-color-scheme.web.ts
apps/mobile/buckit/hooks/use-recommendations.ts
apps/mobile/buckit/hooks/use-theme-color.ts
apps/mobile/buckit/hooks/useBucket.ts
apps/mobile/buckit/hooks/useBucketCollaborators.ts
apps/mobile/buckit/hooks/useBuckets.ts
apps/mobile/buckit/hooks/useChallengesByTheme.ts
apps/mobile/buckit/hooks/useCompletions.ts
apps/mobile/buckit/hooks/useFriends.ts
apps/mobile/buckit/hooks/useFriendsFeed.ts
apps/mobile/buckit/hooks/useItems.ts
apps/mobile/buckit/hooks/useLocation.ts
apps/mobile/buckit/hooks/useMe.ts
apps/mobile/buckit/hooks/usePerformance.ts
apps/mobile/buckit/hooks/usePerformanceSimple.ts
apps/mobile/buckit/hooks/usePopularThemes.ts
apps/mobile/buckit/hooks/usePullToRefresh.ts
apps/mobile/buckit/hooks/useSession.tsx
apps/mobile/buckit/hooks/useSessionMonitor.ts
apps/mobile/buckit/hooks/useSharedCompletions.ts
apps/mobile/buckit/hooks/useUserPreferences.ts
apps/mobile/buckit/lib/recommendations.ts
apps/mobile/buckit/lib/supabase.ts
apps/mobile/buckit/LOCATION_SETUP.md
apps/mobile/buckit/metro.config.js
apps/mobile/buckit/package.json
apps/mobile/buckit/README.md
apps/mobile/buckit/scripts/reset-project.js
apps/mobile/buckit/tsconfig.json
package.json
README.md
scripts/deploy-models.sh
scripts/train-models.py
services/recs/training/appeal_head_train.py
supabase/.gitignore
supabase/config.toml
supabase/cron.json
supabase/functions/appeal-precompute/index.ts
supabase/functions/bandit-update/index.ts
supabase/functions/challenges-by-theme/index.ts
[124 more files omitted for size]
```

### Dependencies

- apps/mobile/buckit/package.json: @expo-google-fonts/poppins@^0.4.1, @expo/ngrok@^4.1.3, @expo/vector-icons@^15.0.3, @react-native-community/datetimepicker@8.4.4, @react-native/virtualized-lists@^0.82.1, @react-navigation/bottom-tabs@^7.4.0, @react-navigation/elements@^2.6.3, @react-navigation/native@^7.1.8, @supabase/supabase-js@^2.76.1, @types/react@~19.1.0, bin@^0.0.0, dotenv@^17.2.3, eslint@^9.25.0, eslint-config-expo@~10.0.0, expo@~54.0.20, expo-blur@^15.0.7, expo-constants@~18.0.10, expo-font@~14.0.9, expo-haptics@~15.0.7, expo-image@~3.0.10, expo-image-picker@^17.0.8, expo-linear-gradient@~15.0.7, expo-linking@~8.0.8, expo-location@^19.0.7, expo-router@~6.0.13, expo-secure-store@^15.0.7, expo-splash-screen@~31.0.10, expo-status-bar@~3.0.8, expo-symbols@~1.0.7, expo-system-ui@~6.0.8, expo-web-browser@~15.0.8, react@19.1.0, react-dom@19.1.0, react-native@0.81.5, react-native-gesture-handler@~2.28.0, react-native-get-random-values@~1.11.0, react-native-reanimated@~4.1.1, react-native-safe-area-context@~5.6.0, react-native-screens@~4.16.0, react-native-svg@15.12.1, react-native-url-polyfill@^3.0.0, react-native-web@~0.21.0, react-native-worklets@0.5.1, typescript@~5.9.2
- package.json: @supabase/supabase-js@^2.76.1
- tests/package.json: @supabase/supabase-js@^2.38.0, @types/node@^20.0.0, ts-node@^10.9.0, typescript@^5.0.0

### Recent commits (newest first)

- Update README.md
- readme update
- wewaefs
- bgddxd
- more readme
- more fixes
- readme updates
- more and more fixes
- friends feed
- more fixes
- Merge branch 'main' of https://github.com/dvdawg/buckit
- lots of fixes again
- Update README.md
- Merge branch 'main' of https://github.com/dvdawg/buckit
- lots of fixes
- Update README.md
- Merge branch 'main' of https://github.com/dvdawg/buckit
- friends, ui fixes, model
- fix
- fix

## Key source files (fetched from GitHub, selected and truncated for size)

### apps/mobile/buckit/LOCATION_SETUP.md

```markdown
# Location Services Setup

This document explains how to set up location services for the Buckit app.

## Required API Keys

### Google Places API Key

1. Go to the [Google Cloud Console](https://console.cloud.google.com/)
2. Create a new project or select an existing one
3. Enable the following APIs:
   - Places API
   - Maps JavaScript API
   - Geocoding API
4. Create credentials (API Key)
5. Restrict the API key to your app's bundle identifier
6. Add the API key to your environment variables

### Environment Variables

Create a `.env` file in the root of your mobile app directory with:

```env
EXPO_PUBLIC_GOOGLE_PLACES_API_KEY=your_google_places_api_key_here
```

## Features Implemented

### LocationPicker Component
- Google Places autocomplete search
- Current location detection
- Interactive map with marker placement
- Location validation and error handling

### Database Integration
- Location name storage (`location_name` field)
- Geographic coordinates storage (`location_point` field using PostGIS)
- Updated RPC functions to handle location data

### Challenge Creation & Editing
- Location picker in challenge creation form
- Location picker in challenge editing interface
- Location display with map preview in challenge details

## Database Schema

The `items` table includes:
- `location_name` (TEXT): Human-readable location name
- `location_point` (GEOGRAPHY(POINT, 4326)): Geographic coordinates

## RPC Functions

### create_item_secure
```sql
create_item_secure(
    p_bucket_id UUID,
    p_title TEXT,
    p_description TEXT,
    p_category TEXT DEFAULT NULL,
    p_location_name TEXT DEFAULT NULL,
    p_location_point TEXT DEFAULT NULL
)
```

### update_item_secure
```sql
update_item_secure(
    p_item_id UUID,
    p_title TEXT DEFAULT NULL,
    p_description TEXT DEFAULT NULL,
    p_category TEXT DEFAULT NULL,
    p_location_name TEXT DEFAULT NULL,
    p_location_point TEXT DEFAULT NULL
)
```

### get_items_with_location_secure
Returns items with location data including latitude/longitude coordinates.

## Usage

### Creating a Challenge with Location
1. Open the challenge creation form
2. Tap the location field
3. Search for a location or use current location
4. Confirm the selection
5. The location will be saved with both name and coordinates

### Editing Challenge Location
1. Open a challenge in bucket detail
2. Tap the edit button
3. Tap the location field to open the location picker
4. Select a new location or clear the existing one
5. Save the changes

### Viewing Challenge Location
1. Open challenge details
2. Tap on the location to view it on a map
3. See location name, address, and coordinates

## ML Model Integration

The location data is stored in a format suitable for ML model integration:

- **Location Name**: Text for natural language processing
- **Coordinates**: Numeric latitude/longitude for geographic analysis
- **Address**: Structured address information for location-based recommendations

This data can be used for:
- Ge
[truncated — 142 more characters]
```

### package.json

```
{
  "dependencies": {
    "@supabase/supabase-js": "^2.76.1"
  }
}

```

### tests/package.json

```
{
  "name": "buckit-recommender-tests",
  "version": "1.0.0",
  "description": "Integration tests for Buckit recommendation system",
  "main": "recommend.test.ts",
  "scripts": {
    "test": "ts-node recommend.test.ts",
    "test:watch": "ts-node --watch recommend.test.ts"
  },
  "dependencies": {
    "@supabase/supabase-js": "^2.38.0",
    "typescript": "^5.0.0",
    "ts-node": "^10.9.0"
  },
  "devDependencies": {
    "@types/node": "^20.0.0"
  }
}

```

### apps/mobile/buckit/package.json

```
{
  "name": "buckit",
  "main": "expo-router/entry",
  "version": "1.0.0",
  "scripts": {
    "start": "expo start",
    "reset-project": "node ./scripts/reset-project.js",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "web": "expo start --web",
    "lint": "expo lint"
  },
  "dependencies": {
    "@expo-google-fonts/poppins": "^0.4.1",
    "@expo/ngrok": "^4.1.3",
    "@expo/vector-icons": "^15.0.3",
    "@react-native-community/datetimepicker": "8.4.4",
    "@react-native/virtualized-lists": "^0.82.1",
    "@react-navigation/bottom-tabs": "^7.4.0",
    "@react-navigation/elements": "^2.6.3",
    "@react-navigation/native": "^7.1.8",
    "@supabase/supabase-js": "^2.76.1",
    "bin": "^0.0.0",
    "dotenv": "^17.2.3",
    "expo": "~54.0.20",
    "expo-blur": "^15.0.7",
    "expo-constants": "~18.0.10",
    "expo-font": "~14.0.9",
    "expo-haptics": "~15.0.7",
    "expo-image": "~3.0.10",
    "expo-image-picker": "^17.0.8",
    "expo-linear-gradient": "~15.0.7",
    "expo-linking": "~8.0.8",
    "expo-location": "^19.0.7",
    "expo-router": "~6.0.13",
    "expo-secure-store": "^15.0.7",
    "expo-splash-screen": "~31.0.10",
    "expo-status-bar": "~3.0.8",
    "expo-symbols": "~1.0.7",
    "expo-system-ui": "~6.0.8",
    "expo-web-browser": "~15.0.8",
    "react": "19.1.0",
    "react-dom": "19.1.0",
    "react-native": "0.81.5",
    "react-native-gesture-handler": "~2.28.0",
    "react-native-get-random-values": "~1.11.0",
    "react-native-reanimated": "~4.1.1",
    "react-native-safe-area-context": "~5.6.0",
    "react-native-screens": "~4.16.0",
    "react-native-svg": "15.12.1",
    "react-native-url-polyfill": "^3.0.0",
    "react-native-web": "~0.21.0",
    "react-native-worklets": "0.5.1"
  },
  "devDependencies": {
    "@types/react": "~19.1.0",
    "eslint": "^9.25.0",
    "eslint-config-expo": "~10.0.0",
    "typescript": "~5.9.2"
  },
  "private": true
}

```

### supabase/functions/bandit-update/index.ts

```typescript
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
import { createClient } from "https://esm.sh/@supabase/supabase-js@2"

export const handler = serve(async (req) => {
  try {
    const supabaseUrl = Deno.env.get("SUPABASE_URL")!;
    const supabaseServiceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
    
    const supabase = createClient(supabaseUrl, supabaseServiceKey);
    const { userId, itemId, eventType, features } = await req.json();

    if (!userId || !itemId || !eventType) {
      return new Response(JSON.stringify({ error: "Missing required fields" }), {
        status: 400,
        headers: { "Content-Type": "application/json" },
      });
    }

    const rewardMap: Record<string, number> = {
      'impression': 0.0,
      'view': 0.1,
      'like': 0.5,
      'save': 0.7,
      'start': 0.8,
      'complete': 1.0,
      'hide': -0.3,
      'skip': -0.1
    };

    const reward = rewardMap[eventType] || 0.0;

    const { error } = await supabase.rpc('update_bandit_arm', {
      p_user_id: userId,
      p_item_id: itemId,
      p_features: features || [0, 0, 0, 0, 0, 0],
      p_reward: reward,
      p_alpha: 1.0
    });

    if (error) {
      console.error("Error updating bandit arm:", error);
      throw error;
    }

    return new Response(JSON.stringify({ 
      success: true, 
      reward,
      message: `Updated bandit arm for user ${userId}, item ${itemId} with reward ${reward}`
    }), {
      status: 200,
      headers: { "Content-Type": "application/json" },
    });
  } catch (error) {
    console.error("Error in bandit-update function:", error);
    return new Response(JSON.stringify({ error: error.message }), {
      status: 500,
      headers: { "Content-Type": "application/json" },
    });
  }
});

```

### supabase/functions/metrics/index.ts

```typescript
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
import { createClient } from "https://esm.sh/@supabase/supabase-js@2"

export const handler = serve(async (req) => {
  try {
    const supabaseUrl = Deno.env.get("SUPABASE_URL")!;
    const supabaseServiceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
    
    const supabase = createClient(supabaseUrl, supabaseServiceKey);
    const url = new URL(req.url);
    const metricType = url.searchParams.get('type') || 'summary';

    let result;

    switch (metricType) {
      case 'ctr':
        const { data: ctrData } = await supabase
          .from('recs_ctr_7d')
          .select('*')
          .order('date', { ascending: false })
          .limit(7);
        result = { metric: 'ctr_7d', data: ctrData };
        break;

      case 'cpr':
        const { data: cprData } = await supabase
          .from('recs_cpr_7d')
          .select('*')
          .order('date', { ascending: false })
          .limit(7);
        result = { metric: 'cpr_7d', data: cprData };
        break;

      case 'coverage':
        const { data: coverageData } = await supabase
          .from('recs_coverage_k')
          .select('*')
          .order('date', { ascending: false })
          .limit(7);
        result = { metric: 'coverage_k', data: coverageData };
        break;

      case 'diversity':
        const { data: diversityData } = await supabase
          .from('recs_diversity_k')
          .select('*')
          .order('date', { ascending: false })
          .limit(7);
        result = { metric: 'diversity_k', data: diversityData };
        break;

      case 'latency':
        const { data: latencyData } = await supabase
          .from('recs_latency_p95')
          .select('*')
          .order('hour', { ascending: false })
          .limit(24);
        result = { metric: 'latency_p95', data: latencyData };
        break;

      case 'summary':
      default:
        const { data: summaryData } = await supabase
          .from('recs_metrics_summary')
          .select('*');
        result = { metric: 'summary', data: summaryData };
        break;
    }

    return new Response(JSON.stringify(result), {
      status: 200,
      headers: { "Content-Type": "application/json" },
    });
  } catch (error) {
    console.error("Error in metrics function:", error);
    return new Response(JSON.stringify({ error: error.message }), {
      status: 500,
      headers: { "Content-Type": "application/json" },
    });
  }
});

```

### supabase/functions/debug-trait/index.ts

```typescript
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
import { createClient } from "https://esm.sh/@supabase/supabase-js@2"

const EMBED_DIM = Number(Deno.env.get("EMBED_DIM") ?? 1536);

export const handler = serve(async (req) => {
  try {
    const supabaseUrl = Deno.env.get("SUPABASE_URL")!;
    const supabaseServiceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
    
    const supabase = createClient(supabaseUrl, supabaseServiceKey);
    const { userId } = await req.json();

    if (!userId) {
      return new Response(JSON.stringify({ error: "userId required" }), {
        status: 400,
        headers: { "Content-Type": "application/json" },
      });
    }

    const { data: uv } = await supabase
      .from("user_vectors")
      .select("emb")
      .eq("user_id", userId)
      .maybeSingle();

    if (!uv?.emb) {
      return new Response(JSON.stringify({ 
        error: "No user vector found",
        userId,
        hasUserVector: false 
      }), {
        status: 200,
        headers: { "Content-Type": "application/json" },
      });
    }

    const { data: items, error } = await supabase
      .from("items")
      .select("id, title, description, embedding, embedding_vec")
      .not("embedding", "is", null)
      .limit(10);

    if (error) {
      throw error;
    }

    const results = (items ?? [])
      .map((item: any) => {
        const emb = item.embedding_vec || item.embedding;
        if (!emb) return null;
        
        const trait = dot(uv.emb, emb);
        return {
          id: item.id,
          title: item.title,
          trait_similarity: trait,
          has_embedding_vec: !!item.embedding_vec,
          has_embedding: !!item.embedding,
        };
      })
      .filter(Boolean)
      .sort((a: any, b: any) => b.trait_similarity - a.trait_similarity)
      .slice(0, 5);

    return new Response(JSON.stringify({
      userId,
      hasUserVector: true,
      userVectorDim: uv.emb?.length,
      topItems: results,
    }), {
      status: 200,
      headers: { "Content-Type": "application/json" },
    });
  } catch (error) {
    console.error("Error in debug-trait function:", error);
    return new Response(JSON.stringify({ error: error.message }), {
      status: 500,
      headers: { "Content-Type": "application/json" },
    });
  }
});

function dot(a: number[] | null | undefined, b: number[] | null | undefined) {
  if (!a || !b) return 0;
  let s = 0;
  for (let i=0; i<Math.min(a.length,b.length); i++) s += a[i]*b[i];
  return s / (a.length || 1);
}

```

### supabase/functions/appeal-precompute/index.ts

```typescript
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
import { createClient } from "https://esm.sh/@supabase/supabase-js@2"

const EMBED_DIM = Number(Deno.env.get("EMBED_DIM") ?? 1536);

export const handler = serve(async (req) => {
  try {
    const supabaseUrl = Deno.env.get("SUPABASE_URL")!;
    const supabaseServiceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
    
    const supabase = createClient(supabaseUrl, supabaseServiceKey);

    const { data: items, error: qerr } = await supabase
      .from("items")
      .select("id, title, description")
      .or("appeal_score.is.null,appeal_score.eq.0")
      .not("title", "is", null)
      .limit(100);
    
    if (qerr) {
      console.error("Error fetching items:", qerr);
      throw qerr;
    }

    if (!items || items.length === 0) {
      return new Response(JSON.stringify({ 
        updated: 0, 
        message: "No items need appeal scores" 
      }), {
        status: 200,
        headers: { "Content-Type": "application/json" },
      });
    }

    const updates = [];
    for (const item of items) {
      const title = item.title || "";
      const description = item.description || "";
      const text = [title, description].filter(Boolean).join(" ");
      
      if (!text.trim()) {
        console.log(`Skipping item ${item.id} - no text content`);
        continue;
      }
      
      const { data: appealScore, error: scoreErr } = await supabase
        .rpc("compute_appeal_score", { p_item_id: item.id });
      
      if (scoreErr) {
        console.error(`Error computing appeal score for item ${item.id}:`, scoreErr);
        continue;
      }
      
      let finalScore = appealScore;
      if (finalScore === null) {
        const textLength = text.length;
        const hasKeywords = /amazing|great|awesome|fantastic|wonderful|excellent/i.test(text);
        const hasNegativeKeywords = /boring|terrible|awful|bad|horrible/i.test(text);
        
        let heuristicScore = 0.5;
        
        if (textLength > 100) heuristicScore += 0.1;
        if (textLength > 200) heuristicScore += 0.1;
        
        if (hasKeywords) heuristicScore += 0.2;
        if (hasNegativeKeywords) heuristicScore -= 0.3;
        
        finalScore = Math.max(0, Math.min(1, heuristicScore));
      }
      
      updates.push({ id: item.id, appeal_score: finalScore });
    }

    if (updates.length > 0) {
      for (const update of updates) {
        const { error: uerr } = await supabase
          .from("items")
          .update({ appeal_score: update.appeal_score })
          .eq("id", update.id);
        
        if (uerr) {
          console.error(`Error updating appeal score for item ${update.id}:`, uerr);
        }
      }
    }

    return new Response(JSON.stringify({ 
      updated: updates.length,
      message: `Updated appeal scores for ${updates.length} items`
    }), {
      status: 200,
      headers: { "Content-Type": "application/json" },
    });
  } catch (error) {
    console.error("Error in appeal-precompute function:", error);
    return new Response(JSON.stringify({ error: error.message }), {
      status: 500,
      headers: { "Content-Type": "application/json" },
    });
  }
});

```

### supabase/functions/embeddings/index.ts

```typescript
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
import { createClient } from "https://esm.sh/@supabase/supabase-js@2"

const EMBED_DIM = Number(Deno.env.get("EMBED_DIM") ?? 1536);

interface RequestBody {
  limit?: number;
}

export const handler = serve(async (req) => {
  try {
    const supabaseUrl = Deno.env.get("SUPABASE_URL")!;
    const supabaseServiceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
    
    const supabase = createClient(supabaseUrl, supabaseServiceKey);

    const body: RequestBody = await req.json().catch(() => ({}));
    const limit = body.limit ?? 200;

    const { data: items, error: qerr } = await supabase
      .from("items")
      .select("id, title, description")
      .is("embedding", null)
      .not("title", "is", null)
      .limit(limit);
    
    if (qerr) {
      console.error("Error fetching items:", qerr);
      throw qerr;
    }

    if (!items || items.length === 0) {
      return new Response(JSON.stringify({ updated: 0, message: "No items need embeddings" }), {
        status: 200,
        headers: { "Content-Type": "application/json" },
      });
    }

    const updates = [];
    for (const item of items) {
      const title = item.title || "";
      const description = item.description || "";
      const text = [title, description].filter(Boolean).join(" ");
      
      if (!text.trim()) {
        console.log(`Skipping item ${item.id} - no text content`);
        continue;
      }
      
      const vec = await getTextEmbedding(text);
      updates.push({ id: item.id, embedding: vec });
    }

    if (updates.length > 0) {
      for (const update of updates) {
        const { error: uerr } = await supabase
          .from("items")
          .update({ embedding: update.embedding })
          .eq("id", update.id);
        
        if (uerr) {
          console.error(`Error updating embedding for item ${update.id}:`, uerr);
        }
      }
    }

    const { error: ferr } = await supabase.rpc("refresh_recs_materialized");
    if (ferr) {
      console.error("Error refreshing materialized views:", ferr);
    }

    return new Response(JSON.stringify({ updated: updates.length }), {
      status: 200,
      headers: { "Content-Type": "application/json" },
    });
  } catch (error) {
    console.error("Error in embeddings function:", error);
    return new Response(JSON.stringify({ error: error.message }), {
      status: 500,
      headers: { "Content-Type": "application/json" },
    });
  }
});

async function getTextEmbedding(text: string): Promise<number[]> {
  const claudeApiKey = Deno.env.get("ANTHROPIC_API_KEY");
  const openaiApiKey = Deno.env.get("OPENAI_API_KEY");
  
  if (claudeApiKey) {
    try {
      const response = await fetch("https://api.anthropic.com/v1/messages",
        method: "POST",
        headers: {
          "x-api-key": claudeApiKey,
          "Content-Type": "application/json",
          "anthropic-version": "2023-06-01",
        },
        body: JSON.stringify({
          model: "claude-3-sonnet-20240229",
          max_tokens: 1024,
          messages: [{
            role: "user",
            content: `Create a dense vector embedding for this text. Return only a JSON array of 1536 floating-point numbers between -1 and 1: "${text}"`
          }]
        }),
      });

      if (!response.ok) {
        throw new Error(`Claude API error: ${response.status}`);
      }

      const data = await response.json();
      const content = data.content[0].text;
      
      const embedding = JSON.parse(content);
      if (Array.isArray(embedding) && embedding.length === EMBED_DIM) {
        return embedding;
      }
    } catch (error) {
      console.error("Claude embedding error:", error);
    }
  }
  
  if (openaiApiKey) {
    try {
      const response = await fetch("https://api.anthropic.com/v1/messages",
        method: "POST",
        headers: {
          "Authorization": `Bearer ${openaiApiKey}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          input: text,
          model: "text-embedding-3-small",
        }),
      });

      if (!response.ok) {
        throw new Error(`OpenAI API error: ${response.status}`);
      }

      const data = await response.json();
      return data.data[0].embedding;
    } catch (error) {
      console.error("OpenAI embedding error:", error);
    }
  }
  
  const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
  const hashArray = new Uint8Array(hash);
  
  const embedding = new Array(EMBED_DIM).fill(0);
  for (let i = 0; i < EMBED_DIM; i++) {
    const hashIndex = i % hashArray.length;
    const seed = hashArray[hashIndex] + i * 0.01;
    embedding[i] = Math.sin(seed) * 0.1;
  }
  
  return embedding;
}

```

### supabase/functions/scheduled-jobs/index.ts

```typescript
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
import { createClient } from "https://esm.sh/@supabase/supabase-js@2"

export const handler = serve(async (req) => {
  try {
    const supabaseUrl = Deno.env.get("SUPABASE_URL")!;
    const supabaseServiceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
    
    const supabase = createClient(supabaseUrl, supabaseServiceKey);
    const { jobType } = await req.json();

    let result;

    switch (jobType) {
      case 'embeddings':
        result = await runEmbeddingsJob(supabase);
        break;
      case 'refresh_materialized':
        result = await runRefreshMaterializedJob(supabase);
        break;
      case 'appeal_precompute':
        result = await runAppealPrecomputeJob(supabase);
        break;
      case 'bandit_decay':
        result = await runBanditDecayJob(supabase);
        break;
      case 'cleanup_cache':
        result = await runCleanupCacheJob(supabase);
        break;
      case 'all':
        result = await runAllJobs(supabase);
        break;
      default:
        throw new Error(`Unknown job type: ${jobType}`);
    }

    return new Response(JSON.stringify(result), {
      status: 200,
      headers: { "Content-Type": "application/json" },
    });
  } catch (error) {
    console.error("Error in scheduled-jobs function:", error);
    return new Response(JSON.stringify({ error: error.message }), {
      status: 500,
      headers: { "Content-Type": "application/json" },
    });
  }
});

async function runEmbeddingsJob(supabase: any) {
  console.log("Running embeddings job...");
  
  const response = await fetch(`${Deno.env.get("SUPABASE_URL")}/functions/v1/embeddings`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ limit: 200 })
  });

  const result = await response.json();
  console.log("Embeddings job completed:", result);

  return {
    job: 'embeddings',
    success: response.ok,
    result
  };
}

async function runRefreshMaterializedJob(supabase: any) {
  console.log("Running refresh materialized views job...");
  
  const { error } = await supabase.rpc('refresh_recs_materialized');
  
  if (error) {
    console.error("Error refreshing materialized views:", error);
    throw error;
  }

  console.log("Materialized views refreshed successfully");

  return {
    job: 'refresh_materialized',
    success: true,
    message: 'Materialized views refreshed'
  };
}

async function runAppealPrecomputeJob(supabase: any) {
  console.log("Running appeal precompute job...");
  
  const response = await fetch(`${Deno.env.get("SUPABASE_URL")}/functions/v1/appeal-precompute`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({})
  });

  const result = await response.json();
  console.log("Appeal precompute job completed:", result);

  return {
    job: 'appeal_precompute',
    success: response.ok,
    result
  };
}

async function runBanditDecayJob(supabase: any) {
  console.log("Running bandit decay job...");
  
  const { data, error } = await supabase
    .from('recs_bandit_arms')
    .update({ 
      alpha: supabase.sql`alpha * 0.99`
    })
    .lt('last_updated', new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString())
    .select();

  if (error) {
    console.error("Error decaying bandit arms:", error);
    throw error;
  }

  console.log(`Decayed ${data?.length || 0} bandit arms`);

  return {
    job: 'bandit_decay',
    success: true,
    decayed_count: data?.length || 0
  };
}

async function runCleanupCacheJob(supabase: any) {
  console.log("Running cache cleanup job...");
  
  const { data, error } = await supabase.rpc('cleanup_expired_cache');
  
  if (error) {
    console.error("Error cleaning up cache:", error);
    throw error;
  }

  console.log(`Cleaned up ${data || 0} expired cache entries`);

  return {
    job: 'cleanup_cache',
    success: true,
    cleaned_count: data || 0
  };
}

async function runAllJobs(supabase: any) {
  console.log("Running all scheduled jobs...");
  
  const results = [];
  
  try {
    results.push(await runRefreshMaterializedJob(supabase));
    results.push(await runEmbeddingsJob(supabase));
    results.push(await runAppealPrecomputeJob(supabase));
    results.push(await runBanditDecayJob(supabase));
    results.push(await runCleanupCacheJob(supabase));
  } catch (error) {
    console.error("Error in all jobs:", error);
    results.push({ job: 'error', success: false, error: error.message });
  }

  return {
    job: 'all',
    success: results.every(r => r.success),
    results
  };
}

```

[225 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]