Skip to content

Feat/remove client side engines#23

Merged
chefmatteo merged 13 commits into
mainfrom
feat/remove-client-side-engines
Jun 23, 2026
Merged

Feat/remove client side engines#23
chefmatteo merged 13 commits into
mainfrom
feat/remove-client-side-engines

Conversation

@chefmatteo

@chefmatteo chefmatteo commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Description

remove client side engines, wired map-backend, setup maptile for map frontend recording app

Type of change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Docs or CI/infra update

Testing

Describe how you tested this:

  • Local testing (e.g., ran pytest, flutter test)
  • Manual testing on device/simulator
  • Ran full CI locally or via PR

Checklist

  • I have self-reviewed my code
  • I have added tests (if applicable)
  • I have updated docs (if applicable)
  • My commit messages follow the Conventional Commits format (feat:, fix:, docs:, etc.)
  • No uncommitted secrets or large files (>100MB) in this PR
  • CI passes (tests, lints, build)

Screenshots / Logs (if applicable)

Summary by CodeRabbit

  • New Features
    • Activity thumbnails now generate and display on maps and in listings
    • Filter your activities by type, date range, and search terms
    • Resort trail overlays appear on maps, color-coded by difficulty level
    • Processing indicator displays while activity data finalizes after upload
    • Enhanced map style loading with error recovery and retry functionality

chefmatteo and others added 4 commits June 23, 2026 21:50
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>
@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@chefmatteo, we couldn't start this review because you've reached your PR review rate limit.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: af4de213-ddf9-420f-bcd9-a53adc8691c2

📥 Commits

Reviewing files that changed from the base of the PR and between 34cd0a3 and 4da4d18.

📒 Files selected for processing (5)
  • backend/map-backend/domains/activities_service/api.py
  • frontend/lib/features/activities/data/activities_repository.dart
  • frontend/lib/models/activity.dart
  • frontend/lib/screens/activities/activity_detail_screen.dart
  • frontend/lib/screens/record/record_screen.dart
📝 Walkthrough

Walkthrough

The 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 MapConfig, a map style-load timeout/retry UI in RecordMapView, a post-save processing overlay with pipeline polling in RecordScreen, and migrates the profile activities tab from a mock service to ActivitiesRepository.

Changes

Activity Thumbnail Pipeline

Layer / File(s) Summary
DB migration and shared response models
backend/db/migrations/004_activities_thumbnail_url.sql, backend/shared/track_pipeline_schemas.py, backend/activity-backend/models.py
Adds the thumbnail_url text column to activities, adds optional thumbnail_url to MapActivityDetailResponse and FrontendActivityResponse.
Pillow renderer, Supabase upload, and config
backend/map-backend/config.py, backend/map-backend/engine/thumbnail.py, backend/map-backend/services/supabase_client.py
Adds THUMBNAIL_BUCKET config, implements render_route_png (gradient background, coordinate projection, glow+route lines, PNG encoding), and adds upload_thumbnail returning a Supabase public URL.
Map-backend POST /activities/thumbnail endpoint
backend/map-backend/domains/activities_service/api.py
Adds _ThumbnailRequest, _render_and_upload/_generate_thumbnail helpers, registers POST /activities/thumbnail, and updates create_activity to call the thumbnail helper after the DB transaction.
Activity-backend client, pipeline, and create_activity wiring
backend/activity-backend/routes/activities_management_routes.py, backend/activity-backend/services/map_backend_client.py, backend/activity-backend/services/pipeline_processor.py, backend/activity-backend/services/supabase_client.py, backend/activity-backend/routes/activity_transformers.py
Adds MapBackendClient.generate_thumbnail, extends update_activity_pipeline_fields to accept and persist thumbnail_url, wires thumbnail_url through the pipeline processor, adds _background_thumbnail helper and BackgroundTasks injection to create_activity, and maps thumbnail_url into the frontend payload.

Map Features: Config, RecordMapView, and Trails Overlay

Layer / File(s) Summary
MapConfig runtime key loading and style selection
frontend/lib/services/map_config.dart, frontend/pubspec.yaml, frontend/lib/core/di/service_locator.dart
Adds MapConfig.init() to load the MapTiler key from .env.local.json at runtime with --dart-define precedence, updates style selection and public getter logic, registers the init call in the service locator bootstrap, and adds .env.local.json to Flutter assets.
RecordMapView StatefulWidget with timeout and error overlay
frontend/lib/screens/record/record_map_view.dart
Converts RecordMapView to a StatefulWidget with a 10-second Timer, _onStyleLoaded handler, _retry method, and a _MapErrorOverlay widget shown on timeout.
MapsScreen ski trails GeoJSON overlay
frontend/lib/services/apis/map_activities_api.dart, frontend/lib/screens/maps/maps_screen.dart
Adds MapActivitiesApi.getResortTrails(bbox) with bbox formatting and empty GeoJSON fallback; adds _onStyleLoaded (GeoJSON source + difficulty-colored line layer), _refreshTrails (zoom-gated fetch and source update), _trailsLoaded state, and wires onStyleLoadedCallback/onCameraIdle on MapLibreMap.

Activities Data Layer and Record Processing Flow

Layer / File(s) Summary
ActivitiesApi getMyActivities, ActivitiesRepository, and profile tab migration
frontend/lib/services/apis/activities_api.dart, frontend/lib/features/activities/data/activities_repository.dart, frontend/lib/screens/profile/activities_tab.dart, frontend/lib/services/profile_activities_service.dart
Adds GET /activities/me with filter/pagination and response normalization to ActivitiesApi, adds getMyActivities to ActivitiesRepository, migrates ActivitiesTab from the mock ProfileActivitiesService to sl<ActivitiesRepository>(), stubs kudos/share/comment, and deletes ProfileActivitiesService.
RecordScreen post-save processing overlay and pipeline polling
frontend/lib/screens/record/record_screen.dart
Adds _isProcessing flag and raw stat fields; branches post-save flow into null/non-pending/pipeline-pending paths, where the pending path starts _pollPipelineStatus; adds _ProcessingOverlay and _StatItem widgets shown as a full-screen absorbing overlay during pipeline processing.

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇 Hop hop, a thumbnail is born from the snow,
Pillow paints the trail with a gradient glow.
The map loads its trails when the camera goes still,
A spinner appears while the pipeline fulfills.
The mock service is gone — the repo now leads,
A bunny approves of these well-structured deeds! 🗺️✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title mentions removing client-side engines, which aligns with removing ProfileActivitiesService and refactoring map configuration, but this is not the primary change. The main feature is backend-driven thumbnail generation for GPS routes. Consider a more descriptive title like 'Add backend-driven thumbnail generation for activities' or 'Implement route thumbnails with runtime MapTiler key loading' to better reflect the primary changes.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/remove-client-side-engines

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

- 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>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1fc8496 and 34cd0a3.

📒 Files selected for processing (23)
  • backend/activity-backend/models.py
  • backend/activity-backend/routes/activities_management_routes.py
  • backend/activity-backend/routes/activity_transformers.py
  • backend/activity-backend/services/map_backend_client.py
  • backend/activity-backend/services/pipeline_processor.py
  • backend/activity-backend/services/supabase_client.py
  • backend/db/migrations/004_activities_thumbnail_url.sql
  • backend/map-backend/config.py
  • backend/map-backend/domains/activities_service/api.py
  • backend/map-backend/engine/thumbnail.py
  • backend/map-backend/services/supabase_client.py
  • backend/shared/track_pipeline_schemas.py
  • frontend/lib/core/di/service_locator.dart
  • frontend/lib/features/activities/data/activities_repository.dart
  • frontend/lib/screens/maps/maps_screen.dart
  • frontend/lib/screens/profile/activities_tab.dart
  • frontend/lib/screens/record/record_map_view.dart
  • frontend/lib/screens/record/record_screen.dart
  • frontend/lib/services/apis/activities_api.dart
  • frontend/lib/services/apis/map_activities_api.dart
  • frontend/lib/services/map_config.dart
  • frontend/lib/services/profile_activities_service.dart
  • frontend/pubspec.yaml
💤 Files with no reviewable changes (1)
  • frontend/lib/services/profile_activities_service.dart

Comment thread backend/map-backend/domains/activities_service/api.py Outdated
Comment on lines +27 to +33
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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 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 -20

Repository: Syntraksoftware/Snowtrak

Length of output: 1162


🏁 Script executed:

# Find the supabase_client.py file and examine its content
find . -name "supabase_client.py" -type f

Repository: Syntraksoftware/Snowtrak

Length of output: 323


🏁 Script executed:

# Search for configuration and bucket setup
rg "THUMBNAIL_BUCKET" -A 2 -B 2

Repository: 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 -60

Repository: 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 -20

Repository: 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.py

Repository: 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 -10

Repository: 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 py

Repository: 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 -80

Repository: Syntraksoftware/Snowtrak

Length of output: 162


🏁 Script executed:

# Search for calls to upload_thumbnail
rg "upload_thumbnail" --type py -B 3 -A 3

Repository: Syntraksoftware/Snowtrak

Length of output: 162


🏁 Script executed:

# Look for endpoints that serve/access thumbnails
rg "thumbnail" --type py -B 2 -A 2

Repository: 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 -100

Repository: 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 -200

Repository: 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 -20

Repository: Syntraksoftware/Snowtrak

Length of output: 1160


🏁 Script executed:

# Look at activity-backend main routes
cat backend/activity-backend/main.py 2>/dev/null | head -150

Repository: 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 -100

Repository: 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 -80

Repository: 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 -60

Repository: 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 -200

Repository: 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 -150

Repository: 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 py

Repository: 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 -200

Repository: 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 -150

Repository: 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 2

Repository: 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 -200

Repository: Syntraksoftware/Snowtrak

Length of output: 8765


🏁 Script executed:

# Look for any signed URL implementations in the codebase
rg "signed.*url\|sign.*url" --type py -i

Repository: 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 -i

Repository: 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 2

Repository: 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.py

Repository: 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 -200

Repository: 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.py

Repository: 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.

Comment thread frontend/lib/features/activities/data/activities_repository.dart
Comment on lines +108 to +135
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();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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:


🏁 Script executed:

find . -name "maps_screen.dart" -type f

Repository: Syntraksoftware/Snowtrak

Length of output: 207


🏁 Script executed:

wc -l ./frontend/lib/screens/maps/maps_screen.dart

Repository: Syntraksoftware/Snowtrak

Length of output: 211


🏁 Script executed:

ast-grep outline ./frontend/lib/screens/maps/maps_screen.dart --view expanded

Repository: Syntraksoftware/Snowtrak

Length of output: 221


🏁 Script executed:

ast-grep outline ./frontend/lib/screens/maps/maps_screen.dart

Repository: Syntraksoftware/Snowtrak

Length of output: 221


🏁 Script executed:

cat -n ./frontend/lib/screens/maps/maps_screen.dart | head -150

Repository: 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.

Comment thread frontend/lib/screens/profile/activities_tab.dart
Comment on lines +53 to +61
void _retry() {
setState(() {
_styleLoaded = false;
_timedOut = false;
});
_loadTimer = Timer(const Duration(seconds: 10), () {
if (mounted && !_styleLoaded) setState(() => _timedOut = true);
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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:


🏁 Script executed:

# First, locate the file and check its existence
find . -name "record_map_view.dart" -type f

Repository: 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.

Comment thread frontend/lib/screens/record/record_screen.dart
Comment thread frontend/lib/screens/record/record_screen.dart
Comment on lines +24 to +31
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.
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 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/null

Repository: 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 gitignore

Repository: 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.dart

Repository: 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.

Comment thread frontend/pubspec.yaml
assets:
- assets/images/
- assets/logos/
- .env.local.json

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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' frontend

Repository: 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.

chefmatteo and others added 8 commits June 23, 2026 22:08
- 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>
@chefmatteo chefmatteo merged commit c027b7f into main Jun 23, 2026
7 of 13 checks passed
@chefmatteo chefmatteo deleted the feat/remove-client-side-engines branch June 23, 2026 15:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant