9.7 KiB
9.7 KiB
Project Status: Marco
Last Updated: Tue Jan 27 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.
- 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. - Caching: Implemented in-memory cache for repeated
getNearbyPoisrequests (same lat/lon/radius) to enable instant "Back" navigation.
- 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.
- Format Utils:
app/utils/format-text.js&humanize-osm-taghelper: Standardized logic (Title Case, space replacement) for displaying OSM tags likeguest_house-> "Guest House".
- 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 & Architecture (Refactored)
- URL-Driven Architecture: Moved from service-based state to proper route-based state management.
/search?lat=...&lon=...&q=...: Displays search results list./place/:place_id: Displays details for a specific place (OSM POI or Bookmark).
- Heuristic Navigation: The
searchroute implements "visual click matching" logic. If a search yields a direct match (exact name or very close proximity), it automatically redirects to the/place/route, skipping the list view. - Back Button Support: Browser history works correctly. Navigating "Back" from a place returns to the cached search results instantly without network requests.
- Explicit URLs: Routes support 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.
5. Creation & Editing Workflow
- Create Place:
- Implemented
/place/newroute for creating new private places. - UX: Map displays a central crosshair for precise location selection.
- Mobile Optimization:
- Disabled map inertia (
kinetic: false) to ensure the map stops exactly where the finger releases. PlaceEditFormconditionally disables autofocus on mobile screens (<= 768px) to prevent the onscreen keyboard from obscuring the map view immediately.- Responsive crosshair sizing (48px desktop / 24px mobile).
- Disabled map inertia (
- Persistence: Form data (Title, Description) and Map coordinates are securely saved to RemoteStorage via
storage.storePlace.
- Implemented
Current State
- Repo: The app runs via
pnpm start. - Workflow:
- User pans map ->
moveendtriggersstorage.loadPlacesInBounds. - User clicks map -> Route transition to
/search-> "Pulse" animation -> hybrid hit detection (Visual Tile vs Overpass). - Navigation:
- If direct match: Redirect to
/place/:id. - If multiple results: Show
/searchlist view.
- If direct match: Redirect to
- Sidebar displays details via
<PlaceDetails>component (Bottom sheet on mobile). - Creation: User clicks "Create Place" -> Enters creation mode (crosshair) -> Positions map -> Enters details -> Save.
- Persistence: 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/components/map.gjsapp/components/place-edit-form.gjsapp/templates/place/new.gjs
Next Steps & Pending Tasks
- Linting & Code Quality: Fix remaining CSS errors and address unused variables/runloop usage.
- Testing: Add automated tests for the geohash coverage, retry logic, and new editing features.
- Performance: Monitor performance with large datasets (thousands of bookmarks).
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.