A standalone Android app (Expo SDK 52) that acts as a conversational agent for a 6-month MAANG interview preparation system. Logs daily adherence to a Notion database (with day-aware scoring), answers questions about the prep roadmap, tracks weekly progress and streak — all via the Gemini API. No backend server. Runs entirely on the phone.
| Layer | Technology |
|---|---|
| App framework | Expo SDK 52 + expo-router v4 (file-based routing) |
| Language | TypeScript (strict) |
| LLM | Google Gemini (@google/generative-ai) via free tier |
| Notion integration | Notion REST API v1 via fetch (no SDK — RN incompatible) |
| Web CORS proxy | express + cors dev server (proxy.js) — required for Expo web |
| Date picker | @react-native-community/datetimepicker |
| Secure storage | expo-secure-store (Android Keystore backed) |
| Build | EAS Build (cloud APK, free tier) |
| Environment | conda env ranger with Node.js 22 |
ranger/
├── app/
│ ├── _layout.tsx # Root Stack navigator, status bar
│ ├── index.tsx # Entry point — redirects to /setup or /(tabs)
│ ├── setup.tsx # First-launch screen: API keys + prep start date (date picker)
│ └── (tabs)/
│ ├── _layout.tsx # Tab bar (Chat / Log Day / Settings)
│ ├── index.tsx # Chat screen — conversational agent UI with header stats
│ ├── log.tsx # Daily log screen — day-aware toggles + per-activity notes
│ └── settings.tsx # Settings — edit start date, rotate API keys, clear data
├── lib/
│ ├── agent.ts # Gemini agent + tool-call loop (context-aware system prompt)
│ ├── notion.ts # Notion REST API helpers (upsert, query, update)
│ ├── storage.ts # expo-secure-store read/write helpers
│ └── weekTracker.ts # Week calculator + day-type detector
├── constants/
│ └── tools.ts # Gemini function declarations (log_daily, update_daily_log, get_streak, etc.)
├── .claude/
│ └── commands/ # Claude Code slash commands for this project
│ ├── add-tool.md # /add-tool — scaffold a new agent tool
│ ├── update-notion.md # /update-notion — edit Notion via MCP
│ └── build-apk.md # /build-apk — EAS build walkthrough
├── proxy.js # Express CORS proxy for Expo web (forwards /notion/* → Notion API)
├── .mcp.json # Notion MCP server config for Claude Code (gitignored — contains API key)
├── .env # Gemini API key + model (EXPO_PUBLIC_ prefix required)
├── app.json # Expo config (package name, plugins)
├── eas.json # EAS build profiles (preview = APK, production = AAB)
├── babel.config.js
├── tsconfig.json
└── package.json
- Expo Go installed on your Android phone
- conda installed on your PC
- A Google AI Studio account (free Gemini API key)
- A Notion account with the MAANG Prep System page created
- Go to notion.so/my-integrations → New integration
- Name it (e.g. "Ranger"), select your workspace → Submit
- Copy the
secret_...token - Open the 6 Month MAANG Prep System page in Notion
- Click ••• (top-right) → Connections → add your integration
- This grants access to the page and the Daily Log database (child page)
# Clone / navigate to project
cd "e:/Personal Projects/ranger"
# Activate the conda env
conda activate ranger
# Install dependencies (already done, but run if node_modules is missing)
npm installThe .env at the project root must have:
EXPO_PUBLIC_GOOGLE_API_KEY=your_gemini_key_here
EXPO_PUBLIC_GEMINI_MODEL=gemini-3.1-flash-lite-previewThe
EXPO_PUBLIC_prefix is required — Expo only inlines variables with this prefix into the client bundle.
conda activate ranger
cd "e:/Personal Projects/ranger"
npx expo startScan the QR code with Expo Go. On first launch the Setup screen appears — enter your Notion token, prep start date, and optionally a Gemini key.
Notion's API blocks direct browser requests (CORS). Run the proxy first in a separate terminal:
# Terminal 1 — CORS proxy (required for web)
node proxy.js
# Terminal 2 — Expo
npx expo start --webThe proxy runs on http://localhost:3001 and forwards all /notion/* requests server-side. The app detects Platform.OS === 'web' and routes Notion calls through it automatically.
No server or PC needed after installation.
conda activate ranger
cd "e:/Personal Projects/ranger"
# First time only
npm install -g eas-cli
eas login
eas build:configure # adds projectId to app.json
# Build (~5–10 min, uses EAS cloud)
eas build --platform android --profile previewDownload the .apk from the EAS dashboard URL it prints, then side-load it on your phone (Settings → Security → allow unknown apps).
User message
│
▼
runAgent() in lib/agent.ts
│
├── Builds system prompt with:
│ ├── today's date + day-of-week
│ ├── current week (1–24) + weeks remaining
│ ├── prep start date (gates backfill)
│ ├── 24-week roadmap by month/topic
│ ├── daily schedule (weekday / Saturday / Sunday)
│ ├── scoring rules (day-aware, SD unweighted)
│ └── streak rule (≥1 DSA problem, Sunday exempt)
│
├── Resolves API key: process.env.EXPO_PUBLIC_GOOGLE_API_KEY ?? SecureStore
├── Resolves model: process.env.EXPO_PUBLIC_GEMINI_MODEL ?? 'gemini-2.0-flash'
│
▼
Gemini model.startChat({ history, tools: AGENT_TOOLS })
│
▼
chat.sendMessage(userMessage)
│
▼
┌─── response.functionCalls() present? ──── No ──→ return response.text()
│ │
│ Yes ▼
│ │ displayed in chat UI
│ ▼
│ executeToolCall() for each call
│ ├── log_daily → upsertDailyLogEntry() → Notion POST /pages (idempotent)
│ ├── update_daily_log → updateDailyLogEntryByDate() → Notion PATCH /pages
│ ├── get_today_log → getEntryByDate() → Notion POST /databases/{id}/query
│ ├── get_week_number → getCurrentWeek(startDate)
│ ├── get_weekly_stats → queryWeekEntries(week) → Notion POST /databases/{id}/query (+ daily_notes)
│ ├── get_streak → queryRecentEntries(60) → Notion query + streak calc
│ ├── get_roadmap → static 24-week map (sourced from Notion page, compiled in agent.ts)
│ └── list_tools → returns tool registry inline
│ │
│ ▼
└── chat.sendMessage(functionResponses) ──→ loop
- No LangGraph — the tool-call loop is ~30 lines. LangGraph.js requires Node.js internals that don't run in React Native.
- No Notion SDK —
@notionhq/clientuses Node.js streams; all calls usefetchdirectly instead. - Env vars take priority over SecureStore — allows bundling the key at build time for personal use while keeping the setup screen functional for fresh installs without a
.env. - Notion API key never in
.env— Notion token stays in SecureStore only (entered via setup screen), since it's more sensitive than an AI API key. - Day-aware scoring — weekday = (Gym + DSA) / 2, Saturday = (DSA + AI + Assignments) / 3, Sunday = 0 (rest). System Design tracked but never weighted. Formula checks
formatDate(prop("Date"), "dddd")in Notion. - Idempotent logging —
log_dailyqueries by date first; if a row exists, it updates in place rather than duplicating. - Streak rule from plan — ≥1 DSA problem per day maintains streak. Sundays skip (rest day exempt, don't break streak).
- Page ID:
34888ab3-488e-8106-8f22-e37ee58b0d9d - URL: https://www.notion.so/34888ab3488e81068f22e37ee58b0d9d
- Contains: Daily Schedule, Fallback System, Weekly Tracker, Saturday Structure, Sunday Rule, 6-Month Roadmap, Progress Dashboard
- Database ID:
28842f0050534b4a8925edad850dd4b5 - Data Source ID:
collection://72fbd185-6e3d-455b-9667-b886025c882c
| Property | Type | Notes |
|---|---|---|
| Day | title | Date string e.g. 2026-04-20 |
| Date | date | ISO date (key for queries) |
| Weekday fields | ||
| Gym | checkbox | |
| Gym Notes | rich_text | Optional notes on the gym session |
| DSA Completed | checkbox | |
| DSA Count | number | Problems solved today (feeds weekly total) |
| DSA Notes | rich_text | Optional notes on the DSA session |
| Saturday-only fields | ||
| AI Course | checkbox | Saturday only |
| AI Course Notes | rich_text | |
| Assignments | checkbox | Saturday only |
| Assignments Notes | rich_text | |
| Always tracked | ||
| System Design | checkbox | Optional, unweighted in score |
| SD Notes | rich_text | |
| Mock Interview | checkbox | Tracked for Dashboard |
| Auto-calculated | ||
| Week | number | 1–24, auto-calculated by agent from start date |
| Score % | formula | Weekday: (Gym + DSA) / 2 × 100; Sat: (DSA + AI + Assignments) / 3 × 100; Sun: 0 |
| Notes | rich_text | Overall daily reflection |
Defined in constants/tools.ts, executed in lib/agent.ts.
| Tool | Description | Parameters |
|---|---|---|
log_daily |
Create or update a daily log entry (idempotent by date). On weekdays: Gym + DSA + SD. On Saturday: DSA + AI Course + Assignments + SD. On Sunday: rest day (warns user). | date?: YYYY-MM-DD (defaults to today), gym?: bool, gym_notes?: str, dsa?: bool, dsa_count?: num, dsa_notes?: str, system_design?: bool, sd_notes?: str, mock_interview?: bool, ai_course?: bool, ai_course_notes?: str, assignments?: bool, assignments_notes?: str, notes?: str |
update_daily_log |
Partial update to an existing entry for a given date. Only provided fields are modified. | date: YYYY-MM-DD (required), any of the above |
get_today_log |
Fetch the existing log entry for a date (defaults to today). Returns null if none exists. | date?: YYYY-MM-DD |
get_week_number |
Get the current week (1–24) based on stored start date. | none |
get_weekly_stats |
Get adherence stats + per-day notes for a week: days logged, DSA count, mocks, average score, daily_notes array. | week?: number (defaults to current) |
get_streak |
Calculate current streak. Rule: ≥1 DSA problem per day (Sundays skipped, rest exempt). | none |
get_roadmap |
Fetch topic, problem types, and weekly goal from the 24-week plan. | week?: number (omit for full roadmap) |
list_tools |
List all available agent tools with descriptions. | none |
Use the /add-tool Claude Code skill — it guides all three required file changes.
- Day-aware UI: Weekday layout (Gym + DSA + SD) vs. Saturday layout (DSA + AI + Assignments + SD) vs. Sunday rest warning
- Per-activity notes: Each activity (Gym, DSA, AI Course, etc.) has its own notes field
- DSA problem counter: Track the number of problems solved each day
- Mock Interview toggle: Log whether a mock interview was done
- System Design tracking: Optional, unweighted in score
- Date-locked logging: Can't log before prep start date; start date is locked after setup
- Auto-populate: If logging today again, previous entry loads into form
- Sunday rest reminder: Confirms with user if they try to log on Sunday
- Live score preview: Shows real-time (Gym + DSA) / 2 or appropriate Saturday/Sunday formula
- Context-aware: System prompt injects today's date, current week, start date, day type
- Tool-powered: Call agent tools to log, fetch stats, calculate streak
- Chat history: Maintains conversation history for multi-turn queries
- Quick actions: Preset chips for common queries ("What week am I on?", "Show this week's stats", etc.)
- Live stats header: Shows current week, this week's adherence %, and current streak
- Backfill support: "log yesterday" or "log April 15th" by passing
dateparameter tolog_daily
- Edit start date (with confirmation; locked once saved)
- Rotate Notion API key (without re-entering Gemini key)
- Override Gemini key (if
.envkey needs updating) - Clear all local data (wipes keys and start date; Notion logs preserved)
- Idempotent logging: Submit same day multiple times → updates in place, no duplicates
- Day-aware scoring: Formula branches on day-of-week (weekday vs. Sat vs. Sun)
- Weekly rollup:
get_weekly_statsqueries all entries for a week, calculates average score and total problems - Streak tracking:
get_streakscans last 60 days, counts consecutive days with ≥1 DSA, skips Sundays
| Variable | Required | Source | Description |
|---|---|---|---|
EXPO_PUBLIC_GOOGLE_API_KEY |
No | .env |
Gemini API key from aistudio.google.com. Can be left blank; reads from SecureStore if not in .env. |
EXPO_PUBLIC_GEMINI_MODEL |
No | .env |
Model name (default: gemini-2.0-flash). Used at build time if present, otherwise falls back to default. |
| Notion API key | Yes | SecureStore (setup screen) | secret_... from notion.so/my-integrations. Never in .env. |
| Start date | Yes | SecureStore (setup screen) | Prep start date (YYYY-MM-DD) for week calculation and date-gating. Locked after setup. |
Project-level slash commands in .claude/commands/:
| Command | Description |
|---|---|
/add-tool |
Scaffold a new Gemini agent tool across all three required files (tools.ts, agent.ts, notion.ts) |
/update-notion |
Update the Notion dashboard directly via MCP (includes all key page/DB IDs) |
/build-apk |
Step-by-step EAS APK build and installation guide |
/manage-daily-log |
Update Daily Log database schema or data via Notion MCP |
/test-logging |
Test the daily logging flow end-to-end |
.mcp.json configures the @notionhq/notion-mcp-server for Claude Code sessions, giving Claude direct read/write access to your Notion workspace. This file is gitignored (contains your API key). To recreate it:
{
"mcpServers": {
"notion": {
"command": "npx",
"args": ["-y", "@notionhq/notion-mcp-server"],
"env": {
"OPENAPI_MCP_HEADERS": "{\"Authorization\": \"Bearer YOUR_NOTION_KEY\", \"Notion-Version\": \"2022-06-28\"}"
}
}
}
}- Run
conda activate ranger && npx expo start - Scan QR code with Expo Go
- On first launch, setup screen appears
- Enter Notion API key (required), start date (via date picker), optional Gemini key
- Tap "START" → redirects to tabs
- Chat tab: ask "What week am I on?" to verify agent context
- Log Day tab: toggle activities and submit to test Notion write
- Settings tab: verify date picker and key rotation work
lib/agent.tschanges → no rebuild needed (hot reload)constants/tools.tschanges → no rebuild needed.envchanges → full rebuild (eas build) if building APK; hot reload otherwise- Package changes →
npm install, then rebuild - Type errors → run
npx tsc --noEmitto verify
- "Cannot log before start date" — expected behavior if today < startDate in setup. Use Settings tab to verify or change start date.
- "Notion API key not configured" — ensure Notion integration is connected to the Prep System page (step 5 of setup above)
- Score formula errors in Notion — open Daily Log DB and check the Score % property. If the formula doesn't render, manually update it to the day-branched version shown in the schema table above.
- MAANG Prep System page:
34888ab3-488e-8106-8f22-e37ee58b0d9d - Daily Log database:
28842f0050534b4a8925edad850dd4b5 - Daily Log data source (collection):
collection://72fbd185-6e3d-455b-9667-b886025c882c
Use these IDs with the /update-notion skill to modify the Notion dashboard directly.