Feat/remove client side engines#23
Conversation
Renders a PNG of the GPS route via Pillow, uploads to Supabase storage, and returns the public URL. Adds DB migration for thumbnail_url column and wires the /activities/thumbnail endpoint. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Calls map-backend /thumbnail as a non-blocking background task after save, and propagates thumbnail_url through pipeline processor and Supabase client. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces compile-time String.fromEnvironment with async init() that reads from a bundled asset file, so plain flutter run works without --dart-define flags. CI/CD can still override via --dart-define. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… remove mock service Adds trailing slash to POST/GET /activities/ to match FastAPI routes. Adds 10s timeout error overlay to RecordMapView. Removes mock ProfileActivitiesService in favour of the real backend repository. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Warning Review limit reached
More reviews will be available in 54 minutes and 1 second. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate. For paid Pro and Pro+ PR reviews, CodeRabbit uses rolling per-developer review limits. Reviews become available again as older review attempts age out of the rolling limit window. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughThe PR adds end-to-end activity thumbnail generation (Pillow PNG renderer, Supabase storage, new map-backend endpoint, background task wiring in activity-backend, DB migration), a ski trails GeoJSON overlay on the maps screen, a runtime MapTiler key loader in ChangesActivity Thumbnail Pipeline
Map Features: Config, RecordMapView, and Trails Overlay
Activities Data Layer and Record Processing Flow
Sequence Diagram(s)sequenceDiagram
rect rgba(30, 30, 80, 0.5)
note over RecordScreen,ActivitiesApi: Post-save pipeline polling flow
RecordScreen->>ActivitiesApi: createActivity(...)
ActivitiesApi-->>RecordScreen: saved Activity (pipeline_pending=true)
RecordScreen->>RecordScreen: _isProcessing=true, show _ProcessingOverlay
end
rect rgba(30, 80, 30, 0.5)
note over ActivityBackend,MapBackendApi: Background thumbnail generation
ActivityBackend->>MapBackendClient: generate_thumbnail(id, gps_path)
MapBackendClient->>MapBackendApi: POST /activities/thumbnail
MapBackendApi->>render_route_png: points → PNG bytes
render_route_png-->>MapBackendApi: png_bytes
MapBackendApi->>upload_thumbnail: activity_id, png_bytes
upload_thumbnail-->>MapBackendApi: public URL
MapBackendApi-->>MapBackendClient: thumbnail_url
MapBackendClient-->>ActivityBackend: thumbnail_url
ActivityBackend->>ActivitySupabaseClient: update_activity_pipeline_fields(thumbnail_url=...)
end
rect rgba(80, 30, 30, 0.5)
note over RecordScreen,ActivityDetailScreen: Polling resolves
loop _pollPipelineStatus
RecordScreen->>ActivitiesApi: getActivity(id)
ActivitiesApi-->>RecordScreen: status
end
RecordScreen->>RecordScreen: _isProcessing=false
RecordScreen->>ActivityDetailScreen: navigate
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
- Replace simple "Save?" confirmation sheet with a rich summary bottom sheet: checkmark badge, editable activity name (auto-seeded from time-of-day + type), stats row (distance / time / elevation), full-width Save Activity button - Refine processing overlay: off-white background, bordered stats card, activity-type-aware subtitle - Thread activity name through to Activity.name on save - Compute raw stats once in _stopRecording, share between dialog and save - Fix post-delete navigation: pushReplacement → push so deleting an activity from the detail screen returns to the idle RecordScreen instead of the home tab Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 12
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/map-backend/domains/activities_service/api.py`:
- Around line 39-47: The upload_thumbnail function in _render_and_upload
performs blocking network I/O operations (Supabase upload and URL retrieval) but
is called directly without thread offloading, which blocks the event loop. Wrap
the upload_thumbnail call with asyncio.to_thread() following the same pattern
used for render_route_png, ensuring the function call and its return value are
properly awaited to maintain asynchronicity.
In `@backend/map-backend/services/supabase_client.py`:
- Around line 27-33: The upload_thumbnail function always returns a permanent
public URL via get_public_url() regardless of the activity's visibility status,
exposing private activity thumbnails as permanently accessible assets. Modify
the upload_thumbnail function to either accept a visibility parameter and only
call get_public_url() when visibility is public, or replace get_public_url()
with bucket.create_signed_url() to generate time-limited signed URLs that
expire. This ensures private activity thumbnails are not permanently accessible
outside of authorization checks.
In `@frontend/lib/features/activities/data/activities_repository.dart`:
- Around line 39-50: The getMyActivities method in ActivitiesRepository does not
expose the pagination parameters (limit and offset) that are supported by the
underlying _api.getMyActivities call, forcing consumers to use default
pagination and potentially losing data for users with large activity histories.
Add optional limit and offset parameters to the getMyActivities method signature
and pass them through to the _api.getMyActivities call along with the existing
search, activityType, startDate, and endDate parameters.
In `@frontend/lib/screens/maps/maps_screen.dart`:
- Around line 108-135: The `_onStyleLoaded` method lacks error handling around
the `addGeoJsonSource` and `addLineLayer` calls, which can throw exceptions
during style transitions when sources already exist (leaving `_trailsLoaded`
false and silently disabling the overlay). Wrap both the `addGeoJsonSource` call
and the `addLineLayer` call in a try/catch block similar to the error handling
pattern already used in the `_refreshTrails` method to properly handle any
exceptions and ensure consistent error management throughout the code.
In `@frontend/lib/screens/profile/activities_tab.dart`:
- Around line 250-252: The methods `_shareActivity` and `_commentActivity` are
empty no-op handlers that are wired to UI buttons, causing taps to fail silently
with no user feedback. Either implement the actual share and comment
functionality for these methods, or add temporary user feedback (such as showing
a SnackBar message) to indicate that these features are not yet available. This
will prevent confusion when users attempt to use these features.
- Line 55: The _loadActivities method is awaiting a backend call to
_repo.getMyActivities() without proper error handling or widget lifecycle
checks. Wrap the await call in a try/catch block to handle potential request
failures, and before any state updates after the await, add a mounted check to
prevent updating state after the widget has been disposed or the user has
navigated away. This ensures the app handles both network failures gracefully
and respects the widget lifecycle.
In `@frontend/lib/screens/record/record_map_view.dart`:
- Around line 47-51: The _onStyleLoaded method needs to reset the _timedOut flag
to false in addition to setting _styleLoaded to true. Currently, when the style
loads after the 10-second timer has already fired, _styleLoaded becomes true but
_timedOut remains true, causing the error overlay to persist. Update the
setState call in the _onStyleLoaded method to set both _styleLoaded and
_timedOut to their appropriate values (true and false respectively) to ensure
the overlay is cleared when the map loads even after a timeout has occurred.
- Around line 53-61: The _retry() method only resets the state flags and
restarts the timer but does not actually trigger a reload of the map style in
MapLibreMap. To fix this, after resetting the _styleLoaded and _timedOut flags
in setState(), you need to add a call to setStyleString() on the map controller
(if available) to force the MapLibreMap widget to retry loading the style, or
alternatively add a ValueKey to the MapLibreMap widget that changes when
_retry() is called so the widget rebuilds with a new key, forcing it to recreate
and attempt reloading the style.
In `@frontend/lib/screens/record/record_screen.dart`:
- Around line 362-373: The issue is that `_resetState()` is called
unconditionally after checking the mounted state, but `_resetState()` calls
`setState(...)` internally, which fails when the widget is unmounted. Move the
`_resetState()` call inside the `if (mounted)` block (after the SnackBar
display) so that the state reset only executes when the widget is still mounted
and can safely call `setState`.
- Around line 393-407: In the _pollPipelineStatus method, wrap the
activityProvider.getActivity(activityId) call in a try/catch block to handle
network errors, and ensure _isProcessing is reset to false in the catch handler
so the overlay does not remain stuck. Additionally, add a maximum polling
timeout or attempt limit (e.g., track elapsed time or iteration count) to the
while loop, and when this deadline is exceeded, reset _isProcessing to false and
trigger an appropriate error handling path (such as showing an error message or
retry option) instead of allowing the poll to continue indefinitely.
In `@frontend/lib/services/map_config.dart`:
- Around line 24-31: The MapTiler API key loaded in the _loadMapTilerKey method
from the bundled .env.local.json asset file will be extractable from the
application package, making it visible to any user who installs the app. While
the file is properly git-ignored and only included locally, ensure that the
MAPTILER_API_KEY value you place in .env.local.json is configured with
URL/referrer restrictions in your MapTiler dashboard settings. This restricts
the key to work only from your legitimate application domains, preventing misuse
by attackers who extract the key from the APK or IPA package.
In `@frontend/pubspec.yaml`:
- Line 74: The asset declaration for `.env.local.json` in pubspec.yaml does not
match an actual file in the frontend directory, causing MapConfig.init() to
always fail when attempting to load this file via rootBundle.loadString. Either
remove the `.env.local.json` asset entry from pubspec.yaml if this configuration
is optional, or create the `.env.local.json` file in the frontend directory with
the appropriate configuration values to ensure MapConfig.init() can successfully
load the environment settings instead of silently falling back to open-source
tiles.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b29b6c27-a756-41f1-81a6-55ddae5d56ee
📒 Files selected for processing (23)
backend/activity-backend/models.pybackend/activity-backend/routes/activities_management_routes.pybackend/activity-backend/routes/activity_transformers.pybackend/activity-backend/services/map_backend_client.pybackend/activity-backend/services/pipeline_processor.pybackend/activity-backend/services/supabase_client.pybackend/db/migrations/004_activities_thumbnail_url.sqlbackend/map-backend/config.pybackend/map-backend/domains/activities_service/api.pybackend/map-backend/engine/thumbnail.pybackend/map-backend/services/supabase_client.pybackend/shared/track_pipeline_schemas.pyfrontend/lib/core/di/service_locator.dartfrontend/lib/features/activities/data/activities_repository.dartfrontend/lib/screens/maps/maps_screen.dartfrontend/lib/screens/profile/activities_tab.dartfrontend/lib/screens/record/record_map_view.dartfrontend/lib/screens/record/record_screen.dartfrontend/lib/services/apis/activities_api.dartfrontend/lib/services/apis/map_activities_api.dartfrontend/lib/services/map_config.dartfrontend/lib/services/profile_activities_service.dartfrontend/pubspec.yaml
💤 Files with no reviewable changes (1)
- frontend/lib/services/profile_activities_service.dart
| def upload_thumbnail(activity_id: str, png_bytes: bytes) -> str: | ||
| """Upload a route thumbnail PNG and return its public URL.""" | ||
| cfg = get_config() | ||
| path = f"thumbnails/{activity_id}.png" | ||
| bucket = get_map_client().storage.from_(cfg.THUMBNAIL_BUCKET) | ||
| bucket.upload(path, png_bytes, {"content-type": "image/png", "upsert": "true"}) | ||
| return bucket.get_public_url(path) |
There was a problem hiding this comment.
🔒 Security & Privacy | 🔴 Critical
🧩 Analysis chain
🏁 Script executed:
# First, let's explore the repository structure and find relevant files
git ls-files | grep -E "(supabase|storage|thumbnail|activity)" | head -20Repository: Syntraksoftware/Snowtrak
Length of output: 1162
🏁 Script executed:
# Find the supabase_client.py file and examine its content
find . -name "supabase_client.py" -type fRepository: Syntraksoftware/Snowtrak
Length of output: 323
🏁 Script executed:
# Search for configuration and bucket setup
rg "THUMBNAIL_BUCKET" -A 2 -B 2Repository: Syntraksoftware/Snowtrak
Length of output: 162
🏁 Script executed:
# Look for how thumbnails are accessed/served in the application
rg "upload_thumbnail|get_public_url|thumbnail" --type py -B 2 -A 2 | head -60Repository: Syntraksoftware/Snowtrak
Length of output: 162
🏁 Script executed:
# Search for any authorization/visibility checks related to thumbnails or activities
rg "private.*activity|visibility.*check|auth.*check" --type py | head -20Repository: Syntraksoftware/Snowtrak
Length of output: 162
🏁 Script executed:
# Read the supabase_client.py file from map-backend
cat -n backend/map-backend/services/supabase_client.pyRepository: Syntraksoftware/Snowtrak
Length of output: 1503
🏁 Script executed:
# Check the config file in map-backend
cat -n backend/map-backend/config.py 2>/dev/null || echo "Not found in map-backend"Repository: Syntraksoftware/Snowtrak
Length of output: 4184
🏁 Script executed:
# Look for config files with bucket settings
find backend -name "config.py" -o -name ".env*" | head -10Repository: Syntraksoftware/Snowtrak
Length of output: 447
🏁 Script executed:
# Search for get_public_url usage across the codebase
rg "get_public_url" -B 3 -A 3 --type pyRepository: Syntraksoftware/Snowtrak
Length of output: 162
🏁 Script executed:
# Search for bucket configuration patterns
rg "storage\.from_\|BUCKET\|bucket" --type py -B 2 -A 2 | head -80Repository: Syntraksoftware/Snowtrak
Length of output: 162
🏁 Script executed:
# Search for calls to upload_thumbnail
rg "upload_thumbnail" --type py -B 3 -A 3Repository: Syntraksoftware/Snowtrak
Length of output: 162
🏁 Script executed:
# Look for endpoints that serve/access thumbnails
rg "thumbnail" --type py -B 2 -A 2Repository: Syntraksoftware/Snowtrak
Length of output: 162
🏁 Script executed:
# Check the Supabase setup SQL to understand bucket configuration
cat backend/map-backend/SUPABASE_SETUP.sql 2>/dev/null || find backend -name "SUPABASE*.sql" -exec cat {} \;Repository: Syntraksoftware/Snowtrak
Length of output: 4161
🏁 Script executed:
# Search for activity visibility/privacy checks
rg "is_private|private.*activity|visibility" --type py -B 2 -A 2 | head -100Repository: Syntraksoftware/Snowtrak
Length of output: 162
🏁 Script executed:
# Look for main.py or API routes to see how thumbnails are exposed
find backend/map-backend -name "main.py" -o -name "*routes*" -o -name "*api*" | xargs cat 2>/dev/null | head -200Repository: Syntraksoftware/Snowtrak
Length of output: 15953
🏁 Script executed:
# Search for activity retrieval and thumbnail URL handling in activity-backend
find backend/activity-backend -name "*.py" -type f | head -20Repository: Syntraksoftware/Snowtrak
Length of output: 1160
🏁 Script executed:
# Look at activity-backend main routes
cat backend/activity-backend/main.py 2>/dev/null | head -150Repository: Syntraksoftware/Snowtrak
Length of output: 3104
🏁 Script executed:
# Search for activity API endpoints that might return thumbnail info
rg "def.*activity\|@.*route\|@.*get\|@.*post" backend/activity-backend --type py -B 1 -A 3 | head -100Repository: Syntraksoftware/Snowtrak
Length of output: 162
🏁 Script executed:
# Check how map_activity_id is used and where thumbnails are exposed
rg "map_activity_id\|thumbnail" --type py -B 2 -A 2 | head -80Repository: Syntraksoftware/Snowtrak
Length of output: 162
🏁 Script executed:
# Look for any authorization checks on storage/thumbnail access
rg "visibility\|private\|public\|auth" backend/activity-backend --type py -B 1 -A 1 | head -60Repository: Syntraksoftware/Snowtrak
Length of output: 162
🏁 Script executed:
# Read the activity routes file
cat -n backend/activity-backend/routes/activities.py 2>/dev/null | head -200Repository: Syntraksoftware/Snowtrak
Length of output: 910
🏁 Script executed:
# Read the activity list routes
cat -n backend/activity-backend/routes/activities_list_routes.py 2>/dev/null | head -150Repository: Syntraksoftware/Snowtrak
Length of output: 5553
🏁 Script executed:
# Look for where thumbnail URLs or map_activity_id is exposed in responses
rg "map_activity_id" -B 3 -A 3 --type pyRepository: Syntraksoftware/Snowtrak
Length of output: 162
🏁 Script executed:
# Check the transformers file for how activities are converted to responses
cat -n backend/activity-backend/routes/activity_transformers.py 2>/dev/null | head -200Repository: Syntraksoftware/Snowtrak
Length of output: 9095
🏁 Script executed:
# Search for endpoint definitions that return activity data
rg "@.*get\|@.*post" backend/activity-backend/routes --type py -A 5 | head -150Repository: Syntraksoftware/Snowtrak
Length of output: 162
🏁 Script executed:
# Search for where activities are filtered by visibility
rg "visibility.*public\|is_public" --type py -B 2 -A 2Repository: Syntraksoftware/Snowtrak
Length of output: 162
🏁 Script executed:
# Check the supabase client list_activities method to see if it filters
cat -n backend/activity-backend/services/supabase_client.py | head -200Repository: Syntraksoftware/Snowtrak
Length of output: 8765
🏁 Script executed:
# Look for any signed URL implementations in the codebase
rg "signed.*url\|sign.*url" --type py -iRepository: Syntraksoftware/Snowtrak
Length of output: 162
🏁 Script executed:
# Check if bucket access control is configured anywhere
rg "RLS\|row.*level\|bucket.*public\|bucket.*private" --type py --type sql -iRepository: Syntraksoftware/Snowtrak
Length of output: 162
🏁 Script executed:
# Search for where list_activities is called and if there's any visibility filtering
rg "list_activities\|visibility.*public\|\.eq.*visibility" --type py -B 2 -A 2Repository: Syntraksoftware/Snowtrak
Length of output: 162
🏁 Script executed:
# Check if there's RLS (Row Level Security) configured in Supabase setup
find backend -name "*.sql" -exec grep -l "RLS\|POLICY" {} \;Repository: Syntraksoftware/Snowtrak
Length of output: 162
🏁 Script executed:
# Look at the full supabase_client list_activities to see if visibility filter is applied
sed -n '159,169p' backend/activity-backend/services/supabase_client.pyRepository: Syntraksoftware/Snowtrak
Length of output: 579
🏁 Script executed:
# Check the activity-backend upload routes to see if thumbnails are only created for certain visibility levels
cat -n backend/activity-backend/routes/activities_upload_routes.py 2>/dev/null | head -200Repository: Syntraksoftware/Snowtrak
Length of output: 5921
🏁 Script executed:
# Check the complete list_activities implementation in supabase_client
sed -n '159,198p' backend/activity-backend/services/supabase_client.pyRepository: Syntraksoftware/Snowtrak
Length of output: 1576
🏁 Script executed:
# Verify what the public endpoint actually returns
cat -n backend/activity-backend/routes/activities_list_routes.py | sed -n '28,54p'Repository: Syntraksoftware/Snowtrak
Length of output: 1441
Private activity thumbnails permanently accessible due to public URL generation without visibility checks.
The list_activities() endpoint docstring claims to return "public activities" but the underlying activity_client.list_activities() query contains no visibility filter—it retrieves all activities regardless of privacy status. Combined with upload_thumbnail() always returning public URLs via get_public_url(), this exposes private activity thumbnails as permanently accessible assets outside authorization checks. Users believe private activities are protected, but the thumbnail assets remain discoverable and accessible to anyone with the URL.
Consider filtering activities by visibility = 'public' in the database query, or issuing signed URLs that expire after visibility/authorization verification instead of permanent public URLs.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/map-backend/services/supabase_client.py` around lines 27 - 33, The
upload_thumbnail function always returns a permanent public URL via
get_public_url() regardless of the activity's visibility status, exposing
private activity thumbnails as permanently accessible assets. Modify the
upload_thumbnail function to either accept a visibility parameter and only call
get_public_url() when visibility is public, or replace get_public_url() with
bucket.create_signed_url() to generate time-limited signed URLs that expire.
This ensures private activity thumbnails are not permanently accessible outside
of authorization checks.
| Future<void> _onStyleLoaded() async { | ||
| final controller = _mapController; | ||
| if (controller == null) return; | ||
| await controller.addGeoJsonSource( | ||
| _trailSourceId, | ||
| const {'type': 'FeatureCollection', 'features': []}, | ||
| ); | ||
| await controller.addLineLayer( | ||
| _trailSourceId, | ||
| _trailLayerId, | ||
| const LineLayerProperties( | ||
| lineColor: [ | ||
| 'match', ['get', 'difficulty'], | ||
| 'easy', '#4CAF50', | ||
| 'novice', '#4CAF50', | ||
| 'intermediate', '#2196F3', | ||
| 'advanced', '#212121', | ||
| 'expert', '#212121', | ||
| 'freeride', '#FF5A1F', | ||
| '#9E9E9E', // fallback (null / unknown) | ||
| ], | ||
| lineWidth: 2.5, | ||
| lineOpacity: 0.85, | ||
| ), | ||
| ); | ||
| _trailsLoaded = true; | ||
| await _refreshTrails(); | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major
🧩 Analysis chain
🌐 Web query:
In maplibre_gl 0.20.0, does MapLibreMapController.addGeoJsonSource throw if a source with the same id already exists?
💡 Result:
In maplibre_gl version 0.20.0, calling addGeoJsonSource with a source ID that already exists will typically cause a platform-specific error or a native crash, rather than being handled gracefully by the library [1]. Historically, this issue has been a known source of instability in the flutter-maplibre-gl package. On iOS, for example, attempting to add a source with an existing ID has been known to trigger an unhandled MLNRedundantSourceIdentifierException, resulting in a complete app crash [1]. Behavior on other platforms can also lead to error logs or failures during the platform-side execution of the command [2]. Because the library does not inherently prevent or automatically handle these collisions, it is recommended practice to manage your source IDs externally (e.g., using a state management approach or a tracking list) to ensure you do not add a source that is already present [1]. If you need to update data for an existing source, you should use the setGeoJsonSource method instead of calling addGeoJsonSource again [3]. Note that subsequent versions of the package have worked toward improving error handling for such conflicts [4], but version 0.20.0 does not include these fixes [5].
Citations:
- 1: [BUG] Adding an existing source causes app to crash maplibre/flutter-maplibre-gl#512
- 2: [BUG] Removing source on web maplibre/flutter-maplibre-gl#525
- 3: https://github.com/maplibre/flutter-maplibre-gl/blob/cf183b15/maplibre_gl/lib/src/controller.dart
- 4: maplibre/flutter-maplibre-gl@c8c7dbf
- 5: https://github.com/maplibre/flutter-maplibre-gl/releases/tag/v0.20.0
🏁 Script executed:
find . -name "maps_screen.dart" -type fRepository: Syntraksoftware/Snowtrak
Length of output: 207
🏁 Script executed:
wc -l ./frontend/lib/screens/maps/maps_screen.dartRepository: Syntraksoftware/Snowtrak
Length of output: 211
🏁 Script executed:
ast-grep outline ./frontend/lib/screens/maps/maps_screen.dart --view expandedRepository: Syntraksoftware/Snowtrak
Length of output: 221
🏁 Script executed:
ast-grep outline ./frontend/lib/screens/maps/maps_screen.dartRepository: Syntraksoftware/Snowtrak
Length of output: 221
🏁 Script executed:
cat -n ./frontend/lib/screens/maps/maps_screen.dart | head -150Repository: Syntraksoftware/Snowtrak
Length of output: 5834
🏁 Script executed:
cat -n ./frontend/lib/screens/maps/maps_screen.dart | sed -n '150,300p'Repository: Syntraksoftware/Snowtrak
Length of output: 5512
🏁 Script executed:
cat -n ./frontend/lib/screens/maps/maps_screen.dart | sed -n '300,400p'Repository: Syntraksoftware/Snowtrak
Length of output: 4138
Wrap source/layer creation in try/catch for parity with _refreshTrails.
addGeoJsonSource/addLineLayer can throw in maplibre_gl 0.20.0 (e.g. if a source with the same ID already exists, which can occur during style transitions when the ValueKey rebuilds the map). Unlike _refreshTrails, this isn't guarded, so an unhandled async exception leaves _trailsLoaded false, silently disabling the overlay. The codebase already shows the correct pattern in _refreshTrails (lines 143–149).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/lib/screens/maps/maps_screen.dart` around lines 108 - 135, The
`_onStyleLoaded` method lacks error handling around the `addGeoJsonSource` and
`addLineLayer` calls, which can throw exceptions during style transitions when
sources already exist (leaving `_trailsLoaded` false and silently disabling the
overlay). Wrap both the `addGeoJsonSource` call and the `addLineLayer` call in a
try/catch block similar to the error handling pattern already used in the
`_refreshTrails` method to properly handle any exceptions and ensure consistent
error management throughout the code.
| void _retry() { | ||
| setState(() { | ||
| _styleLoaded = false; | ||
| _timedOut = false; | ||
| }); | ||
| _loadTimer = Timer(const Duration(seconds: 10), () { | ||
| if (mounted && !_styleLoaded) setState(() => _timedOut = true); | ||
| }); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor
🧩 Analysis chain
🌐 Web query:
In maplibre_gl 0.20.0 Flutter, does MapLibreMap re-attempt loading the style on its own after onStyleLoadedCallback fails, or is recreating the widget / calling setStyleString required?
💡 Result:
In maplibre_gl 0.20.0, the MapLibreMap widget does not automatically re-attempt to load a style if the initial attempt fails [1]. The onStyleLoadedCallback is triggered only upon successful loading of the map style [2]. If the style fails to load (e.g., due to network issues or invalid style specifications), there is no built-in mechanism to retry automatically [1]. To recover from a style loading failure, you are required to take manual action. You can either: 1. Use the setStyleString method via the MapLibreMapController to re-apply the style or provide a different, valid style URI [3][4]. 2. Recreate the MapLibreMap widget, although this is generally less efficient than using setStyleString [4]. Because the map controller and many of its operations rely on the map being fully initialized, developers should implement error handling around style requests and use setStyleString to trigger a recovery attempt when a failure is detected [3][5].
Citations:
- 1: [Android] Chain validation failed (example app) maplibre/flutter-maplibre-gl#369
- 2: https://pub.dev/documentation/maplibre_gl/latest/maplibre_gl/MapLibreMap-class.html
- 3: https://pub.dev/documentation/maplibre_gl/latest/maplibre_gl/MapLibreMapController-class.html
- 4: added setStyle method on map controller maplibre/flutter-maplibre-gl#431
- 5: https://flutter-maplibre.pages.dev/docs/getting-started/programmatic-control
🏁 Script executed:
# First, locate the file and check its existence
find . -name "record_map_view.dart" -type fRepository: Syntraksoftware/Snowtrak
Length of output: 213
🏁 Script executed:
# Read the file around lines 53-61 and surrounding context to understand full implementation
cat -n ./frontend/lib/screens/record/record_map_view.dart | sed -n '1,150p'Repository: Syntraksoftware/Snowtrak
Length of output: 5170
_retry() does not actually force a retry of the style load.
The method only resets flags and restarts the timer, but the MapLibreMap widget itself is unchanged—the styleString is fixed and no key is provided (line 69). According to MapLibreMap documentation, when a style fails to load, the widget will not automatically retry without manual intervention. After another 10 seconds, the error overlay will reappear without the style ever being reloaded.
To fix, either call setStyleString() on the map controller to trigger a reload, or recreate the MapLibreMap widget using a changed ValueKey.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/lib/screens/record/record_map_view.dart` around lines 53 - 61, The
_retry() method only resets the state flags and restarts the timer but does not
actually trigger a reload of the map style in MapLibreMap. To fix this, after
resetting the _styleLoaded and _timedOut flags in setState(), you need to add a
call to setStyleString() on the map controller (if available) to force the
MapLibreMap widget to retry loading the style, or alternatively add a ValueKey
to the MapLibreMap widget that changes when _retry() is called so the widget
rebuilds with a new key, forcing it to recreate and attempt reloading the style.
| try { | ||
| final raw = await rootBundle.loadString('.env.local.json'); | ||
| final map = jsonDecode(raw) as Map<String, dynamic>; | ||
| _mapTilerKey = (map['MAPTILER_API_KEY'] as String? ?? '').trim(); | ||
| } catch (_) { | ||
| // File missing or malformed — tiles fall back to open sources. | ||
| } | ||
| } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify the asset file is git-ignored and locate any committed instance.
fd -H -t f 'env.local.json'
echo '--- gitignore matches ---'
rg -n 'env.local.json' frontend/.gitignore .gitignore 2>/dev/nullRepository: Syntraksoftware/Snowtrak
Length of output: 188
🏁 Script executed:
#!/bin/bash
# Check gitignore files for any .env patterns or related exclusions
echo "=== Root .gitignore ==="
if [ -f .gitignore ]; then
cat .gitignore | head -50
else
echo "No root .gitignore found"
fi
echo ""
echo "=== Frontend .gitignore ==="
if [ -f frontend/.gitignore ]; then
cat frontend/.gitignore
else
echo "No frontend/.gitignore found"
fi
echo ""
echo "=== Search for .env files or patterns ==="
rg '\.env' --type gitignoreRepository: Syntraksoftware/Snowtrak
Length of output: 1337
🏁 Script executed:
#!/bin/bash
# Check if there are any .env* files or references in the codebase
echo "=== Searching for .env files ==="
fd -H '\.env'
echo ""
echo "=== Searching for env.local patterns ==="
rg -i 'env.local' --type-add 'dart:*.dart' --type dart
echo ""
echo "=== Check pubspec.yaml for asset configuration ==="
rg -A 5 'assets:' frontend/pubspec.yaml 2>/dev/null || echo "Not found or error"Repository: Syntraksoftware/Snowtrak
Length of output: 518
🏁 Script executed:
#!/bin/bash
# Check the pubspec.yaml to see if .env.local.json is listed as an asset to be bundled
echo "=== Frontend pubspec.yaml assets section ==="
grep -A 20 "flutter:" frontend/pubspec.yaml | grep -A 15 "assets:"
echo ""
echo "=== Check if .env.example.json exists and what it contains ==="
if [ -f frontend/.env.example.json ]; then
cat frontend/.env.example.json
fi
echo ""
echo "=== Look at the full map_config.dart to understand context ==="
head -50 frontend/lib/services/map_config.dartRepository: Syntraksoftware/Snowtrak
Length of output: 2186
The .env.local.json asset is properly git-ignored; ensure the MapTiler key is URL-restricted.
The asset file is correctly excluded via the .env.* pattern in frontend/.gitignore, preventing secrets from being committed. The pubspec.yaml explicitly declares .env.local.json as a bundled asset, which is the intended pattern for local development. Since the key ships in the bundle and is extractable from the APK/IPA, this approach is safe only if the MapTiler key in your dashboard is restricted to your referrer domains.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/lib/services/map_config.dart` around lines 24 - 31, The MapTiler API
key loaded in the _loadMapTilerKey method from the bundled .env.local.json asset
file will be extractable from the application package, making it visible to any
user who installs the app. While the file is properly git-ignored and only
included locally, ensure that the MAPTILER_API_KEY value you place in
.env.local.json is configured with URL/referrer restrictions in your MapTiler
dashboard settings. This restricts the key to work only from your legitimate
application domains, preventing misuse by attackers who extract the key from the
APK or IPA package.
| assets: | ||
| - assets/images/ | ||
| - assets/logos/ | ||
| - .env.local.json |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
sed -n '60,80p' frontend/pubspec.yaml
rg -n "loadString\('" frontend/lib/services/map_config.dart
fd -H -t f 'env.local.json' frontendRepository: Syntraksoftware/Snowtrak
Length of output: 489
Remove the .env.local.json asset declaration or create the missing file.
The asset key in pubspec.yaml line 74 matches the rootBundle.loadString path in MapConfig.init() (both reference .env.local.json), but the actual file does not exist in the frontend/ directory. This causes MapConfig.init() to always fall into the catch block and silently fall back to open-source tiles. Either remove the asset declaration from pubspec.yaml if this file is optional/intentionally omitted, or create a .env.local.json file in frontend/ with the expected configuration.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/pubspec.yaml` at line 74, The asset declaration for
`.env.local.json` in pubspec.yaml does not match an actual file in the frontend
directory, causing MapConfig.init() to always fail when attempting to load this
file via rootBundle.loadString. Either remove the `.env.local.json` asset entry
from pubspec.yaml if this configuration is optional, or create the
`.env.local.json` file in the frontend directory with the appropriate
configuration values to ensure MapConfig.init() can successfully load the
environment settings instead of silently falling back to open-source tiles.
- Full visual overhaul of ActivityDetailScreen to match app color system:
- AppBar: white surface, title = activity name (fallback to type),
subtitle = formatted date, back arrow, delete icon in textTertiary
- Map style toggle: primary blue selected state (was hardcoded orange)
- Color mode chips: animated pill buttons using SyntrakColors.primary
- Stats row: single bordered card with Distance / Time / Elevation /
Avg Speed — no icons, clean label-under-value layout
- Details section: icon+label+value tiles in a bordered card
- Delete confirmation: bottom sheet matching the save dialog style
- Fix formattedPace: averagePace % 60 on a double produced full float
precision string; now rounds to int before formatting
- Remove unused _descentSegments field
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Full layout overhaul following Strava design language with SyntrakColors: - Dark background throughout (darkBackground / darkSurface) - AppBar: sport type as title, more button opens delete confirmation - Activity name as large hero text in body, date subtitle below - Map full-width at 240px; controls styled dark - Stats in 2x2 dark card, label-above-value, left-aligned - Color mode chips as animated dark pill buttons - Details card with icon+label+value rows - Delete bottom sheet uses dark surface Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Same Strava-inspired layout, all dark color tokens replaced with their light SyntrakColors equivalents (background, surface, textPrimary, textSecondary, divider). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Delete _SaveStatCell (identical to _StatItem); update 3 call sites - Collapse _StyleBtn class into a local builder inside _MapStyleToggle Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Description
remove client side engines, wired map-backend, setup maptile for map frontend recording app
Type of change
Testing
Describe how you tested this:
pytest,flutter test)Checklist
feat:,fix:,docs:, etc.)Screenshots / Logs (if applicable)
Summary by CodeRabbit