# Project export: Red String

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: UC Berkeley AI Hackathon 2025
- Tagline: Where fate and science intertwine, and socialization turns into a game. Meet the people you were always meant to meet, overcome social anxiety, and watch as your network evolves with you.
- Devpost: https://devpost.com/software/red-string
- GitHub: https://github.com/michaelkongg/redstring
- Demo: https://github.com/Sanjayc0204/redstring-server
- Video: https://www.youtube.com/embed/VQC9N3b1TZM?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 4 GitHub contributor(s) — AaryanVirani (14 commits), michaelkongg (8 commits), Sanjay Chunduru (7 commits), Cate (3 commits)

## Devpost submission (written by the team)

### Inspiration

缘分—destined connections in Chinese—and the red string of fate, a myth that says we are invisibly tied together, destined to meet no matter the time or place, even in the chaos of a crowded party. We asked ourselves: What if technology could help us find those connections we were always meant to make?

### What it does

AI Matching Engine: Our backend takes in basic inputs — MBTI, birthday, school — but it doesn’t stop there. Every time you connect with someone, you're prompted to share why you connected. Those short reflections train the AI to learn about you and your social patterns on a deeper level. Red String Matches: At every event, AI curates a personalized “who to meet” list from the guest list, predicting your most meaningful matches — not just statistically, but emotionally. Social Graph Visualization: Every connection forms a node in your dynamic visual graph. First-degree, mutuals, and distant links light up your map as your network grows in real time, turning mingling into a playful, gamified experience.

### How we built it

Frontend RedString’s user interface is built with React Native and Expo, enabling us to target both iOS and Android from a single codebase. We used Expo for rapid prototyping, hot reloading, and easy device testing. The UI leverages React Native’s component system, with additional libraries like @react-native-picker/picker for dropdowns and react-native-modal-datetime-picker for smooth date selection. For image uploads, we integrated expo-image-picker, and for QR code scanning, we used expo-camera. The app’s navigation is handled by React Navigation, supporting both stack and tab navigators for a seamless user experience. Backend For our backend, we chose Supabase, an open-source alternative to Firebase. Supabase provides a Postgres database, authentication, and real-time APIs out of the box. We used the @supabase/supabase-js client to interact with our backend, storing user profiles, events, and social connections. This allowed us to focus on building features rather than infrastructure, while still having the flexibility of SQL and real-time updates. AI Engine To power our intelligent matching system, we built a separate backend server using Express that leverages Google’s Gemini AI to generate semantic embeddings from user interests and profiles. When a user completes onboarding or updates their interests, our app sends this data to the AI server, which uses Gemini to compute high-dimensional embeddings. These embeddings are then used to calculate similarity scores between users, enabling us to suggest meaningful “Red String” matches—people with shared interests or compatible personalities. This AI-driven approach makes our social graph more dynamic and compelling. Authentication User authentication is handled via Supabase’s OTP-based email login. When a user enters their email, they receive a one-time code to verify their identity. This approach is both secure and user-friendly, eliminating the need for passwords. The authentication flow is tightly integrated with our onboarding process, which collects additional user information like MBTI personality type and interests. Social Graph & Features A standout feature of RedString is the visual social graph. We built a dynamic, interactive graph using React Native’s SVG and animation libraries, managed with React Context for state. This graph visually represents first, second, and third-degree connections, updating in real time as users make new friends or scan QR codes at events. The QR code scanner, built with expo-camera, allows users to instantly connect by scanning each other’s codes, which updates their connection graph in the backend. Design & UX Our design philosophy centered on a clean, modern interface with a signature “red string” motif, symbolizing connections. We used Figma, custom styles, and SVG elements to create a visually distinctive experience. Accessibility and responsiveness were priorities, ensuring the app looks and works great on all devices and screen sizes.

### Challenges we ran into

Data Matching at Scale: Matching in real-time from large RSVP guest lists while considering both static traits and dynamic user reflections pushed us to optimize our models and caching layers carefully.

### Accomplishments we're proud of

Proud of building a project that can creatively challenge us, but also incorporate topics we care about, such as loneliness and social anxiety, while integrating a bit of culture.

### What we learned

We learned the importance of visualizing data in ways that feel alive. Watching your social graph bloom from a single point into a web of possibilities made abstract connections feel tangible, even beautiful.

### What's next

Incorporating NFC to allow instant exchange of contact / connecting people.

## README (from the GitHub repository)

# redstring

## Detected evidence (automated analysis)

Indexed codebase: 26 recognized source files, 80 KB.
- C (language) — detected in the code
- JavaScript (language) — detected in the code
- React (technology) — detected in the code
- Supabase (technology) — detected in the code
- Swift (language) — detected in the code
- TypeScript (language) — detected in the code
- Express (technology) — claimed on Devpost, not found in the code
- Google Gemini (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (47 of 47)

```
.DS_Store
.gitignore
package.json
README.md
RedStringApp/.gitignore
RedStringApp/app.json
RedStringApp/App.tsx
RedStringApp/BottomTabNavigator.js
RedStringApp/components/TopicPill.js
RedStringApp/FriendConnections.js
RedStringApp/GraphContext.js
RedStringApp/GraphStackNavigator.js
RedStringApp/index.ts
RedStringApp/InputScreen.js
RedStringApp/ios/.gitignore
RedStringApp/ios/.xcode.env
RedStringApp/ios/Podfile
RedStringApp/ios/Podfile.properties.json
RedStringApp/ios/RedStringApp.xcodeproj/project.pbxproj
RedStringApp/ios/RedStringApp.xcodeproj/xcshareddata/xcschemes/RedStringApp.xcscheme
RedStringApp/ios/RedStringApp/AppDelegate.swift
RedStringApp/ios/RedStringApp/Images.xcassets/AppIcon.appiconset/Contents.json
RedStringApp/ios/RedStringApp/Images.xcassets/Contents.json
RedStringApp/ios/RedStringApp/Images.xcassets/SplashScreenBackground.colorset/Contents.json
RedStringApp/ios/RedStringApp/Images.xcassets/SplashScreenLogo.imageset/Contents.json
RedStringApp/ios/RedStringApp/Info.plist
RedStringApp/ios/RedStringApp/RedStringApp-Bridging-Header.h
RedStringApp/ios/RedStringApp/RedStringApp.entitlements
RedStringApp/ios/RedStringApp/SplashScreen.storyboard
RedStringApp/ios/RedStringApp/Supporting/Expo.plist
RedStringApp/lettaClient.js
RedStringApp/MapStackNavigator.js
RedStringApp/OnBoarding.js
RedStringApp/OnBoardingTwo.js
RedStringApp/package.json
RedStringApp/screens/CameraScreen.js
RedStringApp/screens/ConnectPromptScreen.js
RedStringApp/screens/CreateEventScreen.js
RedStringApp/screens/EventDetailScreen.js
RedStringApp/screens/GraphScreen.js
RedStringApp/screens/MainPage.js
RedStringApp/screens/PartyScreen.js
RedStringApp/screens/ProfileScreen.js
RedStringApp/screens/RedStringScreen.js
RedStringApp/supabaseClient.js
RedStringApp/tsconfig.json
RedStringApp/VerifyOTPScreen.js
```

### Dependencies

- package.json: @react-navigation/native@^7.1.14, @react-navigation/stack@^7.4.0, react-native-safe-area-context@^5.5.0, react-native-screens@^4.11.1, react-native-shared-element@^0.8.9, react-native-svg@^15.12.0, react-navigation-shared-element@^3.1.3
- RedStringApp/package.json: @babel/core@^7.25.2, @expo/metro-runtime@~5.0.4, @letta-ai/letta-client@^0.1.132, @react-native-community/datetimepicker@8.3.0, @react-native-picker/picker@2.11.0, @react-navigation/bottom-tabs@^7.4.0, @react-navigation/native@^7.1.14, @react-navigation/native-stack@^7.3.19, @react-navigation/stack@^7.4.0, @supabase/supabase-js@^2.50.0, @types/react@~19.0.10, @types/react-native@^0.72.8, dotenv@^16.5.0, expo@~53.0.12, expo-camera@~16.1.8, expo-image-picker@^16.1.4, expo-router@~5.1.0, expo-status-bar@~2.2.3, letta@^0.5.2, react@19.0.0, react-dom@19.0.0, react-native@0.79.4, react-native-gesture-handler@~2.24.0, react-native-modal-datetime-picker@^18.0.0, react-native-qrcode-svg@^6.3.15, react-native-reanimated@~3.17.4, react-native-safe-area-context@5.4.0, react-native-screens@~4.11.1, react-native-shared-element@^0.8.9, react-native-svg@15.11.2, react-native-url-polyfill@^2.0.0, react-native-web@^0.20.0, react-navigation-shared-element@^3.1.3, typescript@~5.8.3

### Recent commits (newest first)

- Added letta
- Fixed merge conflict
- QR Code Generated
- Merge remote changes and resolve conflicts
- complete demo
- Currently connected app. Fixed errors
- Merge pull request #3 from catehrsn/main
- Merge branch 'main' into main
- Merge branch 'main' of https://github.com/michaelkongg/redstring
- profile and maps
- feat: update event page UI and lowercase event names
- Fixed screen flow
- Added Camera
- Merge pull request #2 from catehrsn/frontend-mainpage
- Merge branch 'main' into frontend-mainpage
- frontend-mainpage-draft
- Merge branch 'main' of https://github.com/michaelkongg/redstring
- slight changes
- Connected Supabase to Onboarding 2
- Merge branch 'main' of https://github.com/michaelkongg/redstring

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

### package.json

```
{
  "dependencies": {
    "@react-navigation/native": "^7.1.14",
    "@react-navigation/stack": "^7.4.0",
    "react-native-safe-area-context": "^5.5.0",
    "react-native-screens": "^4.11.1",
    "react-native-shared-element": "^0.8.9",
    "react-native-svg": "^15.12.0",
    "react-navigation-shared-element": "^3.1.3"
  }
}

```

### RedStringApp/package.json

```
{
  "name": "redstringapp",
  "license": "0BSD",
  "version": "1.0.0",
  "main": "index.ts",
  "scripts": {
    "start": "expo start",
    "android": "expo run:android",
    "ios": "expo run:ios",
    "web": "expo start --web"
  },
  "dependencies": {
    "@expo/metro-runtime": "~5.0.4",
    "@letta-ai/letta-client": "^0.1.132",
    "@react-native-community/datetimepicker": "8.3.0",
    "@react-native-picker/picker": "2.11.0",
    "@react-navigation/bottom-tabs": "^7.4.0",
    "@react-navigation/native": "^7.1.14",
    "@react-navigation/native-stack": "^7.3.19",
    "@react-navigation/stack": "^7.4.0",
    "@supabase/supabase-js": "^2.50.0",
    "dotenv": "^16.5.0",
    "expo": "~53.0.12",
    "expo-camera": "~16.1.8",
    "expo-image-picker": "^16.1.4",
    "expo-router": "~5.1.0",
    "expo-status-bar": "~2.2.3",
    "letta": "^0.5.2",
    "react": "19.0.0",
    "react-dom": "19.0.0",
    "react-native": "0.79.4",
    "react-native-gesture-handler": "~2.24.0",
    "react-native-modal-datetime-picker": "^18.0.0",
    "react-native-qrcode-svg": "^6.3.15",
    "react-native-reanimated": "~3.17.4",
    "react-native-safe-area-context": "5.4.0",
    "react-native-screens": "~4.11.1",
    "react-native-shared-element": "^0.8.9",
    "react-native-svg": "15.11.2",
    "react-native-url-polyfill": "^2.0.0",
    "react-native-web": "^0.20.0",
    "react-navigation-shared-element": "^3.1.3"
  },
  "devDependencies": {
    "@babel/core": "^7.25.2",
    "@types/react": "~19.0.10",
    "@types/react-native": "^0.72.8",
    "typescript": "~5.8.3"
  },
  "private": true
}

```

### RedStringApp/index.ts

```typescript
import { registerRootComponent } from 'expo';

import App from './App';

// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
// It also ensures that whether you load the app in Expo Go or in a native build,
// the environment is set up appropriately
registerRootComponent(App);

```

### RedStringApp/App.tsx

```typescript
import React from 'react';
import 'react-native-url-polyfill/auto';
import { NavigationContainer } from '@react-navigation/native';
import { createSharedElementStackNavigator } from 'react-navigation-shared-element';

import { GraphProvider } from './GraphContext';
import InputScreen from './InputScreen';
import VerifyOTPScreen from './VerifyOTPScreen';
import OnBoarding from './OnBoarding';
import OnBoardingScreenTwo from './OnBoardingTwo';
import BottomTabNavigator from './BottomTabNavigator';
import CreateEventScreen from './screens/CreateEventScreen';
import ConnectPromptScreen from './screens/ConnectPromptScreen';
import RedStringScreen from './screens/RedStringScreen';
import EventDetailScreen from './screens/EventDetailScreen';

const Stack = createSharedElementStackNavigator();

export default function App() {
  return (
    <GraphProvider>
      <NavigationContainer>
        <Stack.Navigator
          screenOptions={{
            headerShown: false,
            gestureEnabled: false,
            cardStyleInterpolator: ({ current: { progress } }) => ({
              cardStyle: {
                opacity: progress,
              },
            }),
          }}>
          <Stack.Screen name="PhoneInput" component={InputScreen} />
          <Stack.Screen name="VerifyOTP" component={VerifyOTPScreen} />
          <Stack.Screen name="OnBoarding" component={OnBoarding} />
          <Stack.Screen name="OnBoardingTwo" component={OnBoardingScreenTwo} />
          <Stack.Screen name="MainApp" component={BottomTabNavigator} />
          <Stack.Screen name="CreateEvent" component={CreateEventScreen} />
          <Stack.Screen name="ConnectPrompt" component={ConnectPromptScreen} />
          <Stack.Screen name="RedString" component={RedStringScreen} />
          <Stack.Screen name="EventDetail" component={EventDetailScreen} />
        </Stack.Navigator>
      </NavigationContainer>
    </GraphProvider>
  );
}

```

### RedStringApp/supabaseClient.js

```javascript
// supabaseClient.js
import 'react-native-url-polyfill/auto';
import Constants from 'expo-constants';
import { createClient } from '@supabase/supabase-js';

const { extra } = Constants.expoConfig;
if (!extra?.supabaseUrl || !extra?.supabaseKey) {
  throw new Error('Missing Supabase credentials in app.json!');
}

export const supabase = createClient(
  extra.supabaseUrl,
  extra.supabaseKey
);
```

### RedStringApp/lettaClient.js

```javascript
// lettaClient.js
import Constants from 'expo-constants'
import { LettaClient } from '@letta-ai/letta-client'

// // Pull your key out of app.config.js → extra
// const { LETTA_API_KEY } = Constants.expoConfig.extra || {}

// if (!LETTA_API_KEY) {
//   throw new Error(
//     'Missing LETTA_API_KEY in Constants.expoConfig.extra — ' +
//     'make sure you added it to your .env and app.config.js'
//   )
// }

// Instantiate and export your Letta client
// export const letta = new LettaClient({
//   token: LETTA_API_KEY,
// })

```

### RedStringApp/GraphStackNavigator.js

```javascript
import React from 'react';
import { createSharedElementStackNavigator } from 'react-navigation-shared-element';

import GraphScreen from './screens/GraphScreen';
import FriendConnections from './FriendConnections';

const Stack = createSharedElementStackNavigator();

export default function GraphStackNavigator() {
  return (
    <Stack.Navigator
      screenOptions={{
        headerShown: false,
        gestureEnabled: false,
      }}>
      <Stack.Screen name="Graph" component={GraphScreen} />
      <Stack.Screen
        name="FriendConnections"
        component={FriendConnections}
        sharedElements={(route) => {
          const { friend } = route.params;
          return [`item.${friend.id}.photo`];
        }}
      />
    </Stack.Navigator>
  );
} 
```

### RedStringApp/MapStackNavigator.js

```javascript
// MapStackNavigator.js
import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import PartyScreen       from './screens/PartyScreen';
import FriendConnections from './FriendConnections'; // Adjust the import path as necessary

const MapStack = createNativeStackNavigator();

export default function MapStackNavigator() {
  return (
    <MapStack.Navigator
      screenOptions={{
        headerShown: false,
        gestureEnabled: false,
        // Native stack has its own animation APIs; 
        // if you need a fade, you can use `animation: 'fade'`
        animation: 'fade',
      }}
    >
      <MapStack.Screen name="Party"             component={PartyScreen} />
      <MapStack.Screen name="FriendConnections" component={FriendConnections} />
    </MapStack.Navigator>
  );
}

```

### RedStringApp/InputScreen.js

```javascript
// InputScreen.js
import React, { useState } from 'react';
import {
  View,
  TextInput,
  Button,
  Alert,
  StyleSheet,
  ActivityIndicator,
} from 'react-native';
import { supabase } from './supabaseClient';

export default function InputScreen({ navigation }) {
  const [email, setEmail]     = useState('');
  const [loading, setLoading] = useState(false);

  const sendOtp = async () => {
    // Basic email validation
    if (!email.includes('@')) {
      return Alert.alert('Invalid email', 'Please enter a valid address');
    }

    setLoading(true);
    try {
      // 1️⃣ Check if this email is already verified
      const { data: vu, error: vuErr } = await supabase
        .from('Verified_Users')      // make sure your table is lowercase
        .select('verified, user_id')
        .eq('email', email)
        .single();

      const userId = vu?.user_id
      // If no error and verified is true, skip OTP
      if (!vuErr && vu?.verified) {
        setLoading(false);
        return navigation.reset({
          index: 0,
          routes: [{ name: 'MainApp', params: { userId } }],
        });
      }
      // If vuErr.code === 'PGRST116', that's “no rows found”—we fall through

      // 2️⃣ Not verified yet: send the OTP
      const { error: otpErr } = await supabase.auth.signInWithOtp({ email });
      if (otpErr) throw otpErr;

      navigation.navigate('VerifyOTP', { email });
    } catch (err) {
      Alert.alert('Error', err.message);
    } finally {
      setLoading(false);
    }
  };

  return (
    <View style={styles.container}>
      <TextInput
        placeholder="you@example.com"
        value={email}
        onChangeText={setEmail}
        keyboardType="email-address"
        autoCapitalize="none"
        style={styles.input}
        editable={!loading}
      />

      {loading
        ? <ActivityIndicator />
        : <Button title="Send Code" onPress={sendOtp} />
      }
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex:1, justifyContent:'center', padding:24 },
  input:     {
    borderBottomWidth:1,
    marginBottom:20,
    fontSize:16,
    paddingVertical:8,
  },
});

```

### RedStringApp/VerifyOTPScreen.js

```javascript
// VerifyOTPScreen.js
import React, { useState } from 'react';
import { View, TextInput, Button, Alert, StyleSheet } from 'react-native';
import { supabase } from './supabaseClient';

export default function VerifyOTPScreen({ route, navigation }) {
  const { email } = route.params;
  const [token, setToken] = useState('');

  const verify = async () => {
    // 1) Exchange the OTP for a session
    const { data: verifyData, error: verifyErr } = await supabase.auth.verifyOtp({
      email,
      token,
      type: 'email',
    });
    console.log('OTP verification result:', verifyData, verifyErr);
    if (verifyErr) {
      return Alert.alert('Invalid code', verifyErr.message);
    }

    const userId = verifyData.session.user.id;

    // 2) Upsert into `users` with email and user_id
   const { data: profData, error: profErr } = await supabase
        .from('User_Profile')
        .upsert({
          user_id:      userId,
          contact_info: email,
          // you can add other profile fields here if you already have them
        });
      console.log("userId", userId);
      console.log("profData", profData);
      console.log('Profile upsert →', profData, profErr);
      if (profErr) {
        return Alert.alert('Couldn’t save profile', profErr.message);
      }

    // 3) Upsert into `verified_users` with user_id, email, and verified flag
    const { data: verRes, error: verErr } = await supabase
      .from('Verified_Users')
      .upsert({
        user_id:  userId,
        email:    email,
        verified: true,             
        onboarded: false,
      });
    console.log('Verified upsert →', { verRes, verErr });
    if (verErr) {
      return Alert.alert('Couldn’t mark verified', verErr.message);
    }

    // 4) Navigate into onboarding (or MainApp)
    navigation.reset({
      index: 0,
      routes: [{ name: 'OnBoarding', params: { userId } }],
    });
  };

  return (
    <View style={styles.container}>
      <TextInput
        placeholder="Enter 6-digit code"
        value={token}
        onChangeText={setToken}
        keyboardType="number-pad"
        style={styles.input}
      />
      <Button title="Verify Code" onPress={verify} />
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex:1, justifyContent:'center', padding:24 },
  input:     { borderBottomWidth:1, marginBottom:20, fontSize:16, paddingVertical:8 }
});

```

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