Running loop route planner for mobile
Mobile app that generates a loop running route of a chosen distance from the phone's current GPS position, with map, distance, and duration.
Built 1 time, downloaded 0 times.
Install from your terminal
Run this in your project folder. It signs you in, shows what changes and asks before it overwrites a file.
npx claude-lab add running-loop-route-planner-for-mobile-a2a54bCLAUDE.md# Runner route planner
## Purpose
The app lets a runner pick a distance, such as 5 km or 10 km, and generates a loop route that starts at the phone's current GPS position. The route appears on a map with its distance and estimated duration. The user can save the route and start a run in the device's maps app.
This is a solo project built by a beginner. Keep changes small and explain them plainly.
## Stack
Stack not yet chosen. Default recommendation: React Native with Expo and TypeScript.
Record the final choice here once made (framework, map library, routing provider, target platforms).
## Commands
Fill these in once the stack is set. Until then, the verify skill is the source of truth.
- Install: `<install command>`
- Start dev server: `<start command>`
- Unit tests: `<unit test command>`
- Type check: `<type check command>`
- Lint: `<lint command>`
- Build for iOS simulator: `<iOS build command>`
- Build for Android simulator: `<Android build command>`
## Read first
- SPEC.md: requirements and acceptance criteria.
- PLAN.md: the current phase and its /goal prompt.
- REVIEW.md: checklist to run before calling a change done.
@REVIEW.md
## Verify rule
- After every change, run the verify skill.
- Do not report done until it passes.
- Show the exit code of each step.
## Location and privacy
- Never hardcode coordinates in source.
- Use fixtures under test/fixtures for sample GPS data.
- Location data stays on the device unless a feature explicitly says otherwise.
- Never log raw coordinates, even in debug output.
- Request location only while planning a route, with a clear reason string on iOS and Android.
## API keys
- Read keys from environment variables, such as MAPS_API_KEY.
- Never commit keys.
- Real keys live in .claude/settings.local.json or the shell environment.
- In examples and docs, use the placeholder YOUR_MAPS_API_KEY.
## Routing
- All route generation goes through the single module in src/routing.
- The routing provider sits behind an interface so it can be swapped.
- UI components never call a routing API directly.
- Route tests use the fake routing provider and need no network.
## Conventions
- TypeScript in strict mode.
- Functional components only.
- Small files. Split a component once it grows past about 200 lines.
- Tests live next to the code they test.
- Ask before adding any new dependency.
## Gotchas
- iOS and Android need a clear permission reason string before the app asks for location.
- Simulators do not produce real GPS movement. Use mock location to test a run.
- The map provider stays open until the questions in SPEC.md are answered.
- Keep this file under 120 lines. Move long detail into SPEC.md, PLAN.md or skills.
.claude/agents/test-runner.md---
name: test-runner
description: Runs the project's verify steps (type check, lint, unit tests, Android export smoke check) and returns only failures with file and line. Use to keep test output out of the main context.
model: haiku
effort: low
maxTurns: 10
tools: Bash, Read
---
You run the project's checks and report results briefly. You do not fix code.
Run these steps in order, using Bash:
1. `npx tsc --noEmit`
2. `npx eslint .`
3. `npx jest`
4. `npx expo export --platform android`
Rules:
- Run every step even if an earlier one fails, so the report is complete.
- Use Read only to open a file named in an error, and only to confirm the line number.
- Do not edit, create or delete any file.
- Do not paste full logs. Do not print passing test names.
Return this report and nothing else:
- One line per step: step name and exit code.
- For each failure: test or rule name, file path, line number, and a one-line description of the error.
- If everything passed, say "All checks passed" and list the exit codes.
.claude/settings.json{
"permissions": {
"defaultMode": "acceptEdits",
"allow": [
"Bash(npx expo start:*)",
"Bash(npx expo export:*)",
"Bash(npx jest:*)",
"Bash(npx tsc --noEmit)",
"Bash(npx eslint:*)",
"Bash(git status:*)",
"Bash(git diff:*)",
"Bash(git log:*)"
],
"ask": [
"Bash(git push:*)",
"Bash(npm publish:*)",
"Bash(rm -rf:*)",
"Bash(npm install:*)",
"Bash(npm i:*)",
"Bash(npx expo install:*)",
"Bash(yarn add:*)"
],
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./.claude/settings.local.json)",
"Read(**/*.pem)",
"Read(**/*.key)",
"Bash(rm -rf /)",
"Bash(rm -rf /*)"
],
"blockWritesOutsideWorkingDirectories": true
},
"env": {
"MAPS_API_KEY": "YOUR_SECRET"
}
}
.claude/skills/route-check/SKILL.md---
name: route-check
description: Use when changing src/routing or any route-related UI, to confirm generated loops match the SPEC distance tolerance and start and end points.
---
# Route check
Use this skill to check route generation quality against the acceptance criteria in SPEC.md. It runs only the routing tests and the fixture checks, not the full verify list.
## Steps
1. Run the routing tests: `npx jest src/routing`
2. Confirm loop length is within 10 percent of target for the three reference fixtures: 5 km, 10 km and 15 km. The fixtures live under `test/fixtures`.
3. For each fixture, confirm the route starts and ends at the start point given as input. The two points must match exactly, or within the rounding used by the fake provider.
4. If the tests use the fake routing provider, state that in your report. No network is needed.
## Reporting
- If any distance is outside tolerance, report the fixture name, the target distance, and the actual measured distance. Example: `10 km fixture: target 10.0 km, actual 11.6 km (+16%), outside 10% tolerance`.
- If the start or end point does not match the input, report the fixture name and both coordinates. Do not print raw coordinates into logs or commit them; quote them only in the chat report.
- If every check passes, say so and list the three fixtures checked.
## Scope
- Do not change tolerance values to make a check pass. Tolerance comes from SPEC.md.
- If a fixture is missing, report it and stop. Do not create real coordinate data in source files.
- Run the full verify skill before calling the change done.
.claude/skills/verify/SKILL.md---
name: verify
description: Use after any code change in this project, before reporting the task as done, to prove type check, lint, unit tests and the Android build smoke check still pass.
---
# Verify
Run these steps in order from the project root. Do not skip a step, and do not reorder them.
## Steps
1. Type check: `npx tsc --noEmit`
2. Lint: `npx eslint .`
3. Unit tests: `npx jest`
4. Build smoke check: `npx expo export --platform android`
## Rules
- Show the exit code of every step, for example `tsc: exit 0`. Never claim success without showing the exit codes.
- If a step fails, read the failing output and fix only the part that failed. Then rerun the whole list from step 1, not just the failed step.
- Stop and report the first failure if you cannot fix it after two attempts. Include the exact error text.
- Route-length tests in `src/routing` use the fake routing provider. They need no network and no API key.
- Simulator checks are manual. They are not run by this skill. They are listed in the phase goal in PLAN.md, so check the phase goal for them before calling a phase done.
- Do not install packages to make a step pass. Ask the user first.
## Report format
```
tsc: exit N
eslint: exit N
jest: exit N
expo: exit N
```
Then one sentence: either "All four steps exit 0." or the step that failed and what is left to fix.
INSTALL.md# Setup guide
## What this is
Claude Code is an AI coding assistant that runs in your terminal and edits files in a project folder when you ask it to. The files in this project tell Claude what the app should do (`SPEC.md`), the build steps in order (`PLAN.md`), how to work in this codebase (`CLAUDE.md`), how to check its own work (`REVIEW.md` and the skills in `.claude/skills`), and which actions it may take on its own (`.claude/settings.json`).
## Prerequisites
1. Node.js LTS from nodejs.org.
2. Git from git-scm.com.
3. For iOS: Xcode on a Mac. For Android: Android Studio.
4. A free Expo account, if you want one.
5. Claude Code installed and logged in. Follow the official install instructions, then run `claude` once and log in.
## Step 1: Create the project folder
1. Create a folder, for example `runner-route-planner`.
2. Copy the generated files into it, keeping the same paths. You should have `CLAUDE.md`, `SPEC.md`, `PLAN.md`, `REVIEW.md`, `INSTALL.md`, and the `.claude` folder.
3. Open a terminal in that folder and run:
```
git init
```
4. Create a file named `.gitignore` in the folder with these three lines:
```
node_modules/
.env
.claude/settings.local.json
```
The last line keeps your personal settings, including your map key, out of Git.
## Step 2: Start Claude Code
1. In the same terminal, run:
```
claude
```
2. Check that Claude has loaded `CLAUDE.md`. Ask it "what does this project do?" and confirm the answer matches SPEC.md.
3. Run `/skills` or look at the skill list to confirm the `verify` skill appears.
You do not need `/init`. `CLAUDE.md` is already provided. Only run `/init` if that file is missing.
## Step 3: Add your map key
1. Create the file `.claude/settings.local.json` with this content:
```json
{
"env": {
"MAPS_API_KEY": "YOUR_SECRET"
}
}
```
2. Replace `YOUR_MAPS_API_KEY` with your real key. Only do this on your own machine.
3. Never paste the real key into `settings.json`, `CLAUDE.md`, or any other shared file. The file is in `.gitignore`, so Git will not commit it.
If you prefer, you can set `MAPS_API_KEY` in your shell environment instead.
## Step 4: Answer the open questions
Open `SPEC.md` and answer the questions at the bottom: which map provider, whether a paid key is acceptable, which platforms (iOS, Android or both), and whether you need accounts. Edit the file or tell Claude in chat. The first phase asks you once as well.
## Step 5: Install the plugins
In Claude Code, type `/plugin` and install these from the `claude-plugins-official` marketplace:
- `code-review`: reviews changes before you call a phase done.
- `commit-commands`: makes clean git commits at the end of each phase.
- `feature-dev`: a guided workflow for the routing module and planner screen.
## Step 6: Start Phase 0
1. Open `PLAN.md` and copy the Phase 0 `/goal` prompt.
2. Paste it into Claude Code and press Enter.
3. Answer Claude's question about the map provider and platforms.
4. Let it run. It stops after 40 turns and reports what remains.
Phase 0 ends when the placeholder app runs and the checks pass.
## Step 7: Move through the phases
For each later phase:
1. Run `/clear` to start a fresh session.
2. Open `PLAN.md`, copy that phase's `/goal` prompt, and paste it.
3. Let it run, then read the report before moving on.
4. Commit with the `commit-commands` plugin if the phase passed.
Use `/compact` at natural breaks inside a long phase. Use `/clear` between unrelated tasks.
## Changing the model and effort
The project uses Opus 5.5 at medium effort by default. Opus is the most capable model and costs the most per token.
To switch models for the current session, type:
```
/model
```
Pick Sonnet 5.5 to reduce cost. To make Sonnet the project default, change the `model` key in `.claude/settings.json` to the Sonnet 5.5 model name shown by `/model`.
To change effort for the current session, type `/effort` followed by `low`, `medium`, `high`, `xhigh` or `max`. Changing effort in the middle of a task is cheaper than switching models. Use `low` for simple edits and `high` for routing logic.
## Checking token spend
Type `/usage` to see where tokens went in the current session. If a session feels expensive, type `/clear` and start the next task fresh.
## Troubleshooting
**Permission prompts keep appearing.** The project's `.claude/settings.json` asks before risky actions such as `git push`, `npm publish`, `rm -rf` and installing packages. Read each prompt. Approve it only if it matches what you asked for. If an ordinary test or lint command keeps asking, check that it is listed under `permissions.allow`.
**Claude will not write files outside the project.** This is intentional. The `blockWritesOutsideWorkingDirectories` setting keeps writes inside the project folder.
**The simulator does not show your location.** Simulators do not produce real GPS movement. In Xcode, use the simulator's Features menu to set a custom location. In Android Studio, use the emulator's extended controls to send a location. Then reload the app.
**The app asks for location but the map stays empty.** Check that you allowed location access when prompted. On iOS, open Settings, find the app, and set Location to "While Using the App". On Android, check the app's permissions in system settings.
**Setup feels broken.** Type `/doctor` inside Claude Code. It checks the installation and common configuration problems and explains how to fix them.
**Claude says a check failed but you are not sure why.** Ask it to show the exit code and the first failing line of output. Do not accept "done" until the verify skill passes.
PLAN.md# Build plan
Each phase has a `/goal` prompt at the end. Paste one prompt at a time.
**Before each phase:** start a fresh session with `/clear`. Use `/compact` at natural breaks inside a phase. Check token spend with `/usage`.
Every phase ends with the verify skill passing. Simulator checks are manual and listed in each phase's checklist.
## Phase 0: Setup
Choose the stack (Expo with TypeScript recommended), scaffold the project, run the app on one simulator, and add the test runner and lint.
```text
/goal The Expo TypeScript app in this folder shows a placeholder home screen, and these four commands each exit 0: npx tsc --noEmit, npx eslint ., npx jest, npx expo export --platform android. Steps: (1) Ask me once for the map provider and target platforms (iOS, Android, or both). If I say nothing, use Expo with react-native-maps and both platforms. (2) Scaffold an Expo TypeScript project in this folder with a placeholder home screen and one passing smoke test. (3) Add ESLint and Jest config. (4) Record the chosen stack under Stack in CLAUDE.md. Constraints: do not install packages beyond those the Expo scaffold creates without asking me first; keep each file under 200 lines; never write API keys into tracked files; do not run git push. Check: run the four commands and show each exit code. Turn bound: stop after 40 turns and report what remains.
```
Manual checklist:
- [ ] App opens on one simulator and shows the placeholder screen.
## Phase 1: Location
Request foreground location permission, read the current position, and show it on a map.
```text
/goal The planner screen requests foreground location permission, reads the current position, and renders it as a marker on the map. A jest test feeds a mocked position from test/fixtures and asserts the marker renders with those coordinates. These commands each exit 0: npx tsc --noEmit, npx eslint ., npx jest. Constraints: request permission only when the planner screen opens; set a clear permission reason string in the app config; never hardcode coordinates in source (use test/fixtures); never log raw coordinates; do not install new packages without asking me first; keep each file under 200 lines. Turn bound: stop after 40 turns and report what remains.
```
Manual checklist:
- [ ] On a simulator with mock location set, the marker appears at the mocked position.
- [ ] The permission prompt shows the reason text.
## Phase 2: Routing core
Implement the `src/routing` module. It generates a loop route of a target distance from a start point, using a provider behind an interface, with a fake provider for tests.
```text
/goal src/routing exposes a RouteProvider interface and a loop generator that takes a start point and target distance. A fake provider in src/routing/fake is used by tests and makes no network calls. Jest tests prove that for 5 km, 10 km and 15 km targets the loop length is within 10 percent of the target and the route starts and ends at the input point. These commands each exit 0: npx tsc --noEmit, npx eslint ., npx jest. Constraints: UI components must not call any routing API directly; the provider is chosen by config, not hardcoded; no API keys in source; do not install new packages without asking me first; keep each file under 200 lines. Turn bound: stop after 40 turns and report what remains.
```
Manual checklist:
- [ ] Reading the fake provider code shows the interface is the only entry point.
## Phase 3: Planner UI
Build the distance input, generate button, polyline display, distance and duration labels, and the error states from SPEC.md.
```text
/goal The planner screen has a distance input, a Generate button, a route polyline on the map, and labels for total distance and estimated duration. It shows the three error states from SPEC.md (no GPS fix, permission denied, no route found with a suggestion to try another distance). Component tests cover the happy path and each error state, and all pass. These commands each exit 0: npx tsc --noEmit, npx eslint ., npx jest. Record the simulator screenshot path under Phase 3 notes in PLAN.md. Constraints: UI calls only the src/routing interface; split any component over about 200 lines; do not install new packages without asking me first; no real coordinates in source. Turn bound: stop after 40 turns and report what remains.
```
Manual checklist:
- [ ] Generate a 5 km route on a simulator with mock location and check the labels.
- [ ] Trigger each error state by hand and read the message.
Phase 3 notes: (Claude fills in the screenshot path.)
## Phase 4: Polish and save
Save the last route locally, hand off to the device maps app for navigation, and handle the permission-denied and no-fix states fully.
```text
/goal The last generated route is saved on the device and reloaded when the planner opens. A Start navigation button opens the device maps app with the route's start point. Permission-denied and no-GPS-fix states are handled end to end. All checks are green: npx tsc --noEmit, npx eslint ., npx jest, npx expo export --platform android each exit 0. Write docs/manual-test.md with a checklist covering every feature in SPEC.md. Constraints: ask me before adding any storage or navigation package; never log raw coordinates; keep each file under 200 lines; do not run git push. Turn bound: stop after 40 turns and report what remains.
```
Manual checklist (also in docs/manual-test.md):
- [ ] Saved route reappears after the app restarts.
- [ ] Start navigation opens the maps app.
- [ ] Denying permission shows the explanation and settings link.
- [ ] With location off, the no-fix message appears and retry works.
REVIEW.md# Review checklist
Check every item before saying a task is done. CLAUDE.md imports this file, so this checklist is always in context.
## Checks
- [ ] Tests, type check and lint all pass. Run the verify skill and show each exit code.
- [ ] `npx expo export --platform android` exits 0 (build smoke check from the verify skill).
- [ ] Route length test still passes for the three reference distances (5, 10 and 15 km).
## Secrets and location data
- [ ] No API keys, tokens or passwords in tracked files. Keys come from environment variables such as `MAPS_API_KEY`.
- [ ] No real coordinates committed. Sample GPS data lives in `test/fixtures`.
- [ ] No raw coordinates appear in logs, error messages or test output.
- [ ] Location data stays on the device unless the feature says otherwise.
## Permissions and errors
- [ ] Location permission is requested only when the user starts planning, not at app launch.
- [ ] The iOS and Android permission reason strings are present and readable.
- [ ] No GPS fix, permission denied and no walkable route are each handled and shown to the user.
- [ ] Each error message suggests a next step, such as trying a different distance.
## Architecture
- [ ] Route generation calls go only through the `src/routing` interface.
- [ ] No UI component imports a routing provider or calls a routing API directly.
- [ ] No new dependency was added without the user approving it in the chat.
## Size and style
- [ ] TypeScript strict mode is on. No `any` added without a comment explaining why.
- [ ] Components are functional. Files stay small; split any component over about 200 lines.
- [ ] Tests sit next to the code they test.
- [ ] The change matches the current phase in PLAN.md and the requirements in SPEC.md.
## Reporting
- [ ] Say what changed, which checks ran, and their exit codes.
- [ ] List anything not verified, such as manual simulator checks.
SPEC.md# Runner route planner: v1 spec
## Goal
A runner opens the app, enters a distance (for example 5 km or 10 km), and gets a loop route that starts from where they are standing.
## Inputs
- Current GPS position, read once when the planner opens and again when the runner taps Generate.
- Target distance in kilometres.
- Optional preference: loop (default) or out-and-back.
## Outputs
- Route drawn as a polyline on a map.
- Total route distance, shown in kilometres.
- Estimated duration at an easy pace.
- A "Start navigation" button that hands the route to the device's maps app.
## Acceptance criteria
- Route length is within 10 percent of the target distance.
- For a loop, the route starts and ends at the chosen start point.
- For out-and-back, the route returns to the start point.
- Offline use of cached maps is out of scope for v1.
## Error states
Each error shows a short message on screen. None of them crash the app.
- **No GPS fix:** explain that the phone has no location yet and offer a retry.
- **Permission denied:** explain why location is needed and link to system settings.
- **No walkable route found:** say so and suggest a different distance.
## Out of scope for v1
- Social features and live tracking with friends.
- Watch apps.
- Elevation profiles.
- Turn-by-turn voice guidance.
- User accounts (unless the open questions below say otherwise).
## Non-functional requirements
- Route generation responds within 5 seconds on a typical phone.
- Location is requested only while the planner is open, to keep battery use low.
- Location data stays on the device. Raw coordinates are never written to logs.
## Open questions for the user
1. Which map provider should be used, and is a paid API key acceptable?
2. Which platforms are targeted: iOS, Android, or both?
3. Are user accounts needed, or is everything stored on the device?
Answers go under this section, and the chosen stack is recorded in CLAUDE.md.