8.6 KiB
8.6 KiB
Project Status: Marco
Last Updated: Mon Jan 26 2026
Project Context
We are building Marco, a decentralized maps application using Ember.js (Octane/Polaris edition with GJS/GLIMMER), Vite, and OpenLayers. The core feature is storing place bookmarks in RemoteStorage.js, using a custom module structure.
What We Have Done
1. Map Integration
- Set up OpenLayers in
app/components/map.gjs(class-based component). - Switched tiles to OpenFreeMap Liberty style (supports vector POIs).
- Implemented a hybrid click handler:
- Detects clicks on visual vector tiles.
- Falls back to fetching authoritative data from an Overpass API service.
- Uses a heuristic (distance + type matching) to link visual clicks to API results (handling data desynchronization).
- Logic Upgrade: Map intelligently detects if any sidebar/pane is open and handles outside clicks to close them instead of initiating new searches.
- Optimization: Added 10px hit tolerance for easier tapping on mobile devices.
- Visuals: Increased bookmark marker size (Radius 9px) and added a subtle drop shadow.
- Feedback: Implemented a "pulse" animation (via OpenLayers Overlay) at the click location to visualize the search radius (30m/50m).
- Mobile UX:
- Touch: Disabled browser tap highlights (
-webkit-tap-highlight-color: transparent) to prevent blue flashing on Android. - Scroll: Disabled "pull-to-refresh" (
overscroll-behavior: none) on the body to prevent accidental reloads while keeping the sidebar scrollable (contain). - Auto-Pan: On mobile screens, if a selected pin is obscured by the bottom sheet, the map automatically pans to center the pin in the visible top half of the screen.
- Controls: Fixed positioning of "Locate" and "Rotate" buttons on mobile by correcting CSS
insetsyntax. - iOS Polish:
- Prevented input auto-zoom by ensuring
.form-controlfont size is1rem(16px). - Added
-webkit-text-size-adjust: 100%to prevent text inflation on rotation. - Set base
bodyfont size to16px.
- Prevented input auto-zoom by ensuring
- Touch: Disabled browser tap highlights (
- Geolocation ("Locate Me"):
- Implemented a "Locate Me" button with robust tracking logic.
- Dynamic Zoom: Automatically zooms to a level where the accuracy circle covers ~10% of the map (fallback logic handles missing accuracy data).
- Smart Pulse: Displays a pulsing blue circle during the search phase.
- Auto-Stop: Pulse and tracking automatically stop when high accuracy (≤20m) is achieved or after a 10s timeout.
- Persistence: Saves and restores map center and zoom level using
localStorage(key:marco:map-view). - Controls: Enabled standard OpenLayers Rotate control (re-north) and custom Locate control.
- Pin Animation: Selected pins are highlighted with a custom Red Pin overlay that drops in with an animation. The center dot is styled as a solid dark red circle (
#b31412).
2. RemoteStorage Module (@remotestorage/module-places)
- Created a custom TypeScript module in
vendor/remotestorage-module-places/. - Schema:
placeobject containingid(ULID),title,lat,lon,geohash,osmId,url, etc. - Storage Path: Nested
<2-char>/<2-char>/<id>(based on geohash) for scalability. - API:
getPlaces(prefixes?): efficient partial loading of specific sectors (or full recursive scan if no prefixes provided).- Uses
getListingfor directory traversal andgetAllfor object retrieval. - configured with
maxAge: falseto ensure data freshness.
- Dependencies: Uses
ulidandlatlon-geohashinternally.
3. App Infrastructure & Build
- Services:
storage.js: Initializes RemoteStorage, claims access, enables caching, and sets up the widget. Consumes the newgetPlacesAPI.- Optimization: Implemented Debounced Reload (200ms) for bookmark updates to handle rapid change events efficiently.
- Optimization: Correctly handles deletion/updates by clearing stale data for reloaded geohash sectors.
osm.js: Fetches nearby POIs from Overpass API.- Configurable: Now supports dynamic API endpoints via
SettingsService. - Reliability: Implemented
fetchWithRetryto handle HTTP 504/502/503 timeouts and 429 rate limits, in addition to network errors.
- Configurable: Now supports dynamic API endpoints via
settings.js: Manages user preferences (currently Overpass API provider) persisted tolocalStorage.
- UI Components:
places-sidebar.gjs: Displays a list of nearby POIs.- Layout: Responsive design that transforms into a Bottom Sheet (50% height) on mobile screens (
<=768px) with rounded corners and upward shadow.
- Layout: Responsive design that transforms into a Bottom Sheet (50% height) on mobile screens (
place-details.gjs: Dedicated component for displaying rich place information.- Features: Icons (via
feather-icons), Address, Phone, Website, Opening Hours, Cuisine, Wikipedia. - Layout: Polished UI with distinct sections for Actions and Meta info.
- Features: Icons (via
app-header.gjs: Transparent header with "Menu" button (Settings) and User Avatar (Login).settings-pane.gjs: Sidebar component for app info ("About" section) and settings.- Features: Dropdown to select Overpass API provider (bke.ro, overpass-api.de, private.coffee).
- Mobile: Renders as a 2/3 height bottom sheet on mobile.
- Z-Index: Configured to overlay the Places sidebar correctly (
z-index: 3200).
- Geo Utils:
app/utils/geo.js: Haversine distance calculations.app/utils/geohash-coverage.js: Logic to calculate required 4-char geohash prefixes for a given bounding box.
- Build & DevOps:
- Icon Generation: Added
build:iconsscript usingmagickandrsvg-convertto automate PNG generation from SVG. - Dependencies: Documented system requirements (ImageMagick, librsvg) in
README.md. - Ember CLI: Added as dev dependency to support generator commands.
- License: Added AGPLv3 license.
- Icon Generation: Added
4. Routing & Data Optimization
- Explicit URLs: Implemented routing support for specific OSM entities via
/place/osm:node:<id>and/place/osm:way:<id>, distinguishing them from local bookmarks (ULIDs). - Smart Linking: The
showPlacesaction intercepts search results and automatically resolves them to existing Bookmarks if a match is found (viastorage.findPlaceById). This ensures the app navigates to the persistent Bookmark URL (ULID) and correctly reflects the "Saved" status in the UI instead of treating it as a new generic OSM place. - Data Normalization: Refactored
OsmServiceto return normalized objects (osmTags,osmType) for all queries. This ensures consistent data structures between fresh Overpass results and saved bookmarks throughout the app. - Performance: Optimized navigation to prevent redundant network requests. Clicking a map pin passes the existing data object to the route, skipping the
modelhook (no re-fetch) while maintaining correct deep-linkable URLs via a customserializehook inPlaceRoute.
Current State
- Repo: The app runs via
pnpm start. - Workflow:
- User pans map ->
moveendtriggersstorage.loadPlacesInBounds. - User clicks map -> "Pulse" animation -> hybrid hit detection (Visual Tile vs Overpass).
- Navigation: Selected place is checked against bookmarks; if found, it uses the Bookmark object. Otherwise, it uses the OSM object.
- Sidebar displays details via
<PlaceDetails>component (Bottom sheet on mobile). - User clicks "Save Bookmark" -> Stores JSON in RemoteStorage.
- RemoteStorage change event -> Debounced reload updates the map reactive-ly.
- Editing: User can edit the Title and Description of saved bookmarks via an "Edit" button in the details view.
- Settings: User can change the Overpass API provider via the new Settings menu.
- User pans map ->
Files Currently in Focus
app/styles/app.css: Mobile CSS fixes (font sizes, control positioning).package.json: New scripts and dependencies.README.md: Updated documentation.
Next Steps & Pending Tasks
- Mobile Polish: Verify "Locate Me" animation on iOS Safari.
- Collections/Lists: Implement ability to organize bookmarks into lists/collections.
- Linting & Code Quality: Fix remaining CSS errors, remove inline styles in
map.gjs, and address unused variables/runloop usage. - Performance: Monitor performance with large datasets (thousands of bookmarks).
- Testing: Add automated tests for the geohash coverage, retry logic, and new editing features.
Technical Constraints
- Template Style: Strict Mode GJS (
<template>). - Package Manager:
pnpmfor the main app,npmfor the vendor module. - Visuals: No Tailwind/Bootstrap; using custom CSS in
app/styles/app.css.