Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
4bbc0a5
Remove duplicate plan
cafca Apr 17, 2026
ff85cce
chore(mobile): scaffold beebeebike flutter app
cafca Apr 17, 2026
39516be
chore(mobile): remove stale generated widget test
cafca Apr 17, 2026
2af5b1d
feat(mobile): add freezed models and dio api clients
cafca Apr 17, 2026
bdea74b
fix(mobile): fix geocode mapping, null params, user model, 404 handling
cafca Apr 17, 2026
11b93ca
feat(mobile): bootstrap auth, home, and search persistence
cafca Apr 17, 2026
44b83d7
fix(mobile): wire SharedPreferences, guard clear(), skip smoke test u…
cafca Apr 17, 2026
2f2c959
feat(mobile): add map screen shell and overlay adapter
cafca Apr 17, 2026
a417d63
fix(mobile): add ratingOverlayControllerProvider, note ConsumerWidget…
cafca Apr 17, 2026
c5c7de2
feat(mobile): add search and route preview flow
cafca Apr 17, 2026
fee7bef
fix(mobile): guard preview race, surface loading/error states, fix co…
cafca Apr 17, 2026
aaece8b
feat(mobile): add ferrostar-backed navigation service
cafca Apr 17, 2026
62de89c
fix(mobile): guard navigation service start, handle reroute errors
cafca Apr 17, 2026
684e220
feat(mobile): add navigation screen and camera follow state
cafca Apr 17, 2026
fd9aae9
fix(mobile): use StatelessWidget for NavigationScreen, add SafeArea t…
cafca Apr 17, 2026
3978727
fix(mobile): suppress invalid_annotation_target for freezed JsonKey p…
cafca Apr 17, 2026
49c180e
chore: rename frontend/ to web/
cafca Apr 17, 2026
b6aed9a
ci: add path-gated Flutter test workflow for mobile and plugin
cafca Apr 17, 2026
0fc42ee
docs: add mobile app build instructions and iOS platform note
cafca Apr 17, 2026
1d0d190
chore: ignore generated SwiftPM workspace dir
cafca Apr 17, 2026
0f38801
feat(mobile): wire real geolocator stream into NavigationService
cafca Apr 17, 2026
2f771f9
feat(mobile): wire flutter_tts for spoken navigation instructions
cafca Apr 17, 2026
0b30fdb
feat(mobile): expose NavigationService.stateStream for live nav state
cafca Apr 17, 2026
b422fe3
fix(mobile): catch TTS speak errors to prevent unhandled exceptions
cafca Apr 17, 2026
f49df80
feat(mobile): live turn instructions and ETA in NavigationScreen
cafca Apr 17, 2026
5869888
fix(mobile): remove duplicate dispose call in close button handler
cafca Apr 17, 2026
df2c46d
feat(mobile): iOS simulator smoke test + ci-mobile integration job
cafca Apr 17, 2026
55c46b1
fix(mobile): handle geolocator -1 sentinel for unknown heading/speed/…
cafca Apr 17, 2026
95033f9
fix(mobile): harden GPS permission, reroute race, location accuracy, …
cafca Apr 17, 2026
76626cb
fix(ci): use latest stable Flutter instead of pinning 3.19.x
cafca Apr 17, 2026
80aca83
fix(plugin): replace stale boilerplate widget test referencing MyApp
cafca Apr 17, 2026
ce209c2
fix(ios): bump deployment target to iOS 16.0
cafca Apr 17, 2026
67a4d64
fix(plugin): silence analyze warnings blocking CI
cafca Apr 17, 2026
66f98d2
ci: only trigger ci-mobile on push to main, not all branches
cafca Apr 17, 2026
e59de3e
feat(mobile): enable pinch-to-zoom and map-tap destination on MapLibr…
cafca Apr 18, 2026
f72848e
feat(mobile): eagerly bootstrap anonymous auth session on app startup
cafca Apr 18, 2026
b567749
fix(mobile): replace onMapClick with GestureDetector+toLatLng for iOS…
cafca Apr 18, 2026
68c6385
fix(mobile): scope map tap GestureDetector to map widget, not full sc…
cafca Apr 18, 2026
512e6a0
feat(mobile): add login screen and wire settings to it
cafca Apr 18, 2026
a8c6845
fix(mobile): handle AsyncValue.guard absorbing login errors in LoginS…
cafca Apr 18, 2026
4b1725d
test(mobile): add widget test helpers with mock Dio and provider over…
cafca Apr 18, 2026
da0d44a
test(mobile): add missing mock endpoints and fix tile URL in test hel…
cafca Apr 18, 2026
3787119
test(mobile): add SearchScreen widget tests
cafca Apr 18, 2026
3958d4b
test(mobile): add MapScreen widget tests for route card states
cafca Apr 18, 2026
1275705
test(mobile): add SettingsScreen and LoginScreen widget tests
cafca Apr 18, 2026
9f564cc
fix(mobile): default API and tile URLs to docker dev stack
cafca Apr 18, 2026
2bbb406
feat(mobile): bundled style asset, route overlay, tap region, lifecyc…
cafca Apr 18, 2026
7848ca3
docs: navigation camera lifecycle design
cafca Apr 18, 2026
22e9613
docs: navigation camera lifecycle implementation plan
cafca Apr 18, 2026
f724bd5
feat(mobile): navigation camera state machine
cafca Apr 18, 2026
cb9ed4c
test(mobile): cover all no-op paths in camera controller
cafca Apr 18, 2026
67dae96
feat(mobile): RouteOverlay.draw fitCamera opt-out
cafca Apr 18, 2026
e5f64bb
feat(mobile): RecenterFab widget
cafca Apr 18, 2026
4b79289
feat(mobile): ReroutingToast widget
cafca Apr 18, 2026
3bd003a
feat(mobile): ArrivedSheet widget
cafca Apr 18, 2026
69050e5
feat(mobile): navigationCameraControllerProvider
cafca Apr 18, 2026
aa8f760
feat(mobile): wire NavigationScreen to camera lifecycle
cafca Apr 18, 2026
8807536
fix(mobile): mounted guards and route overlay cleanup in NavigationSc…
cafca Apr 18, 2026
d745924
test(mobile): NavigationScreen lifecycle — FAB, toast, arrived sheet
cafca Apr 18, 2026
14acf0e
fix(mobile): fire camera arrived state before map-controller check
cafca Apr 18, 2026
1bc4daa
fix(mobile): recreate location stream per start() call
cafca Apr 18, 2026
7b19632
refactor(mobile): unify browse/navigation into single MapScreen
cafca Apr 18, 2026
999cc42
fix(mobile): restore pinch/rotate, auto-GPS tap origin, de-DE TTS
cafca Apr 18, 2026
feb90d9
chore(mobile): add debug logs for navigation lifecycle
cafca Apr 18, 2026
e6fe7ac
fix(mobile): drive rerouting toast via stream, redraw polyline after …
cafca Apr 18, 2026
e5a7593
feat(mobile): add close button to route summary sheet
cafca Apr 18, 2026
6715438
refactor(mobile): extract EtaSheet, nav constants, maneuver helpers
cafca Apr 20, 2026
b1a8358
Merge remote-tracking branch 'origin/main' into claude/silly-roentgen…
cafca Apr 20, 2026
1fe696f
fix(mobile): use named harness class in nav test, drop lint-flagged _…
cafca Apr 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions .github/workflows/ci-mobile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
name: ci-mobile

on:
push:
branches:
- main
paths:
- 'mobile/**'
- 'packages/ferrostar_flutter/**'
pull_request:
paths:
- 'mobile/**'
- 'packages/ferrostar_flutter/**'

jobs:
test-mobile:
name: Flutter tests (iOS)
runs-on: macos-latest

steps:
- uses: actions/checkout@v4

- uses: subosito/flutter-action@v2
with:
channel: 'stable'
cache: true

- name: Install dependencies (plugin)
working-directory: packages/ferrostar_flutter
run: flutter pub get

- name: Install dependencies (app)
working-directory: mobile
run: flutter pub get

- name: Analyze (plugin)
working-directory: packages/ferrostar_flutter
run: flutter analyze

- name: Analyze (app)
working-directory: mobile
run: flutter analyze

- name: Test (plugin)
working-directory: packages/ferrostar_flutter
run: flutter test

- name: Test (app)
working-directory: mobile
run: flutter test

test-mobile-ios:
name: Flutter integration tests (iOS simulator)
runs-on: macos-latest

steps:
- uses: actions/checkout@v4

- uses: flutter-actions/setup-flutter@v3
with:
channel: stable

- name: Install dependencies (plugin)
working-directory: packages/ferrostar_flutter
run: flutter pub get

- name: Install dependencies (app)
working-directory: mobile
run: flutter pub get

- name: Boot iPhone 17 simulator
run: |
UDID=$(xcrun simctl list devices available --json \
| python3 -c "
import json,sys
devs = json.load(sys.stdin)['devices']
for runtime, devices in devs.items():
for d in devices:
if 'iPhone 17' in d['name'] and d['isAvailable']:
print(d['udid']); exit()
")
xcrun simctl boot "$UDID"
echo "SIM_UDID=$UDID" >> "$GITHUB_ENV"

- name: Enable Flutter SPM integration
working-directory: mobile
run: flutter config --enable-swift-package-manager

- name: Build (simulator, no codesign)
working-directory: mobile
run: flutter build ios --no-codesign --simulator

- name: Run integration smoke test
working-directory: mobile
run: flutter test integration_test/navigation_smoke_test.dart -d "$SIM_UDID"
11 changes: 8 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,18 @@ jobs:
with:
node-version: 22
cache: npm
cache-dependency-path: frontend/package-lock.json
cache-dependency-path: web/package-lock.json

- run: npm ci
working-directory: frontend
working-directory: web

- run: npm run build
working-directory: frontend
working-directory: web

- name: regenerate mobile style and verify it matches committed artifact
run: |
npm --prefix web run build:mobile-style
git diff --exit-code mobile/assets/styles/beebeebike-style.json

publish:
needs: [lint, test-backend, test-frontend]
Expand Down
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,13 @@ data/tiles/

# ── GSD baseline (auto-generated) ──
vendor/

# Flutter
**/.dart_tool/
**/.flutter-plugins
**/.flutter-plugins-dependencies
**/.packages
**/flutter_export_environment.sh
**/Pods/
**/.symlinks/
mobile/ios/Runner.xcworkspace/xcshareddata/swiftpm/
37 changes: 33 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,44 @@ cargo clippy -- -D warnings

Backend uses `SQLX_OFFLINE=true` for Docker builds (no live DB needed at compile time). Locally, sqlx connects to Postgres at build time for query checking.

### Frontend only
### Web app (Svelte)

```bash
cd frontend
cd web
npm ci
npm run dev # Vite dev server on :5173, proxies /api → localhost:3000
npm run build # production build → frontend/dist/
npm run build # production build → web/dist/
```

### Mobile app (iOS only)

```bash
cd mobile
flutter pub get
flutter test

# Run on iOS simulator (defaults point to docker dev stack:
# api 127.0.0.1:3000, tile server 127.0.0.1:8080)
flutter run -d ios

# Override for non-default environments:
flutter run -d ios \
--dart-define=BEEBEEBIKE_API_BASE_URL=http://other-host:3000 \
--dart-define=BEEBEEBIKE_TILE_SERVER_BASE_URL=http://other-host:8080
```

Platform scope: iOS only in v0.1. `ferrostar_flutter` (at `packages/ferrostar_flutter/`) is a path dependency and must be present.

### Map style (web + mobile)

The bicycle-planning visual style lives in [web/src/lib/bicycle-style.js](web/src/lib/bicycle-style.js) as `buildBicycleStyle`. It wraps [`@versatiles/style`](https://github.com/versatiles-org/versatiles-style)'s `colorful` style with a custom palette and adds an extra set of bike-priority layers. Both web (at runtime) and mobile (at build time) call the same function. Mobile bundles a pre-baked artifact at `mobile/assets/styles/beebeebike-style.json` whose URLs use the `{{TILE_BASE}}` placeholder, swapped at runtime for `AppConfig.tileServerBaseUrl`. After editing the shared builder, regenerate the mobile artifact:

```bash
npm --prefix web run build:mobile-style
```

CI fails if the committed mobile artifact diverges from what the script produces.

### Static data (not in repo)

OSM extract and vector tiles must be downloaded before first run:
Expand Down Expand Up @@ -97,7 +126,7 @@ Rating values are discrete: -7, -3, -1, 0 (eraser), 1, 3, 7. The paint endpoint
| db | 5432 | PostGIS (spatial queries for rated areas) |
| graphhopper | 8989 | Bicycle routing with custom model support |
| tiles | 8080 | VersaTiles vector tile server |
| frontend (dev only) | 5173 (default) | Vite dev server with API proxy |
| web (dev only) | 5173 (default) | Vite dev server with API proxy |

### Migrations

Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,20 @@ Then open the app at `http://localhost:5173`.

Docker Compose files live at the project root and are meant to be run from there.

## Mobile app (iOS)

Flutter client in `mobile/`. Requires the `ferrostar_flutter` plugin at `packages/ferrostar_flutter/`.

```bash
cd mobile
flutter pub get
flutter run -d ios \
--dart-define=BEEBEEBIKE_API_BASE_URL=http://127.0.0.1:3000 \
--dart-define=BEEBEEBIKE_TILE_STYLE_URL=http://127.0.0.1:8080/tiles/assets/styles/colorful/style.json
```

> Android support is planned for a future release.

## Contributing

Contributions are welcome. Please open an issue first so we can talk through the idea, the shape of the change, and any bike-brain edge cases before you start building.
12 changes: 6 additions & 6 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# syntax=docker/dockerfile:1.7

FROM node:22-bookworm AS frontend-builder
WORKDIR /app/frontend
COPY frontend/package.json frontend/package-lock.json ./
FROM node:22-bookworm AS web-builder
WORKDIR /app/web
COPY web/package.json web/package-lock.json ./
RUN --mount=type=cache,target=/root/.npm npm ci
COPY frontend ./
COPY web ./
ARG VITE_FATHOM_URL
RUN npm run build

Expand All @@ -24,6 +24,6 @@ FROM debian:bookworm-slim
WORKDIR /app
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=backend-builder /usr/local/bin/beebeebike-backend /usr/local/bin/
COPY --from=frontend-builder /app/frontend/dist /app/frontend/dist
ENV STATIC_DIR=/app/frontend/dist
COPY --from=web-builder /app/web/dist /app/web/dist
ENV STATIC_DIR=/app/web/dist
CMD ["beebeebike-backend"]
2 changes: 1 addition & 1 deletion backend/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ impl Config {
photon_url: env::var("PHOTON_URL")
.unwrap_or_else(|_| "https://photon.komoot.io".into()),
listen_addr: env::var("LISTEN_ADDR").unwrap_or_else(|_| "0.0.0.0:3000".into()),
static_dir: env::var("STATIC_DIR").unwrap_or_else(|_| "../frontend/dist".into()),
static_dir: env::var("STATIC_DIR").unwrap_or_else(|_| "../web/dist".into()),
rating_weight: env::var("RATING_WEIGHT")
.ok()
.and_then(|v| v.parse().ok())
Expand Down
8 changes: 4 additions & 4 deletions compose.dev.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
services:
frontend:
web:
image: node:22-bookworm
working_dir: /workspace/frontend
working_dir: /workspace/web
command: ["sh", "-c", "npm ci && npm run dev -- --host 0.0.0.0"]
environment:
VITE_API_PROXY_TARGET: http://backend:3000
Expand All @@ -11,9 +11,9 @@ services:
- "${VITE_DEV_PORT:-5173}:${VITE_DEV_PORT:-5173}"
volumes:
- .:/workspace
- frontend_node_modules:/workspace/frontend/node_modules
- web_node_modules:/workspace/web/node_modules
depends_on:
- backend

volumes:
frontend_node_modules:
web_node_modules:
2 changes: 1 addition & 1 deletion compose.prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ services:
GRAPHHOPPER_URL: http://graphhopper:8989
PHOTON_URL: https://photon.komoot.io
LISTEN_ADDR: 0.0.0.0:3000
STATIC_DIR: /app/frontend/dist
STATIC_DIR: /app/web/dist
depends_on:
- db
- graphhopper
Expand Down
2 changes: 1 addition & 1 deletion compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ services:
GRAPHHOPPER_URL: http://graphhopper:8989
PHOTON_URL: https://photon.komoot.io
LISTEN_ADDR: 0.0.0.0:3000
STATIC_DIR: /app/frontend/dist
STATIC_DIR: /app/web/dist
depends_on:
- db
- graphhopper
Expand Down
Loading
Loading