138 lines
9.7 KiB
Markdown
138 lines
9.7 KiB
Markdown
# 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 `inset` syntax.
|
|
- **iOS Polish:**
|
|
- Prevented input auto-zoom by ensuring `.form-control` font size is `1rem` (16px).
|
|
- Added `-webkit-text-size-adjust: 100%` to prevent text inflation on rotation.
|
|
- Set base `body` font size to `16px`.
|
|
- **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:** `place` object containing `id` (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 `getListing` for directory traversal and `getAll` for object retrieval.
|
|
- configured with `maxAge: false` to ensure data freshness.
|
|
- **Dependencies:** Uses `ulid` and `latlon-geohash` internally.
|
|
|
|
### 3. App Infrastructure & Build
|
|
|
|
- **Services:**
|
|
- `storage.js`: Initializes RemoteStorage, claims access, enables caching, and sets up the widget. Consumes the new `getPlaces` API.
|
|
- **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 `fetchWithRetry` to handle HTTP 504/502/503 timeouts and 429 rate limits, in addition to network errors.
|
|
- **Caching:** Implemented in-memory cache for repeated `getNearbyPois` requests (same lat/lon/radius) to enable instant "Back" navigation.
|
|
- `settings.js`: Manages user preferences (currently Overpass API provider) persisted to `localStorage`.
|
|
- **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.
|
|
- `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.
|
|
- `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-tag` helper: Standardized logic (Title Case, space replacement) for displaying OSM tags like `guest_house` -> "Guest House".
|
|
- **Build & DevOps:**
|
|
- **Icon Generation:** Added `build:icons` script using `magick` and `rsvg-convert` to 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.
|
|
|
|
### 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 `search` route 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 `showPlaces` action intercepts search results and automatically resolves them to existing **Bookmarks** if a match is found (via `storage.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 `OsmService` to 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/new` route 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.
|
|
- `PlaceEditForm` conditionally disables autofocus on mobile screens (`<= 768px`) to prevent the onscreen keyboard from obscuring the map view immediately.
|
|
- Responsive crosshair sizing (48px desktop / 24px mobile).
|
|
- **Persistence:** Form data (Title, Description) and Map coordinates are securely saved to RemoteStorage via `storage.storePlace`.
|
|
|
|
## Current State
|
|
|
|
- **Repo:** The app runs via `pnpm start`.
|
|
- **Workflow:**
|
|
1. User pans map -> `moveend` triggers `storage.loadPlacesInBounds`.
|
|
2. User clicks map -> Route transition to `/search` -> "Pulse" animation -> hybrid hit detection (Visual Tile vs Overpass).
|
|
3. **Navigation:**
|
|
- If direct match: Redirect to `/place/:id`.
|
|
- If multiple results: Show `/search` list view.
|
|
4. Sidebar displays details via `<PlaceDetails>` component (Bottom sheet on mobile).
|
|
5. **Creation:** User clicks "Create Place" -> Enters creation mode (crosshair) -> Positions map -> Enters details -> Save.
|
|
6. **Persistence:** RemoteStorage change event -> Debounced reload updates the map reactive-ly.
|
|
7. **Editing:** User can edit the Title and Description of saved bookmarks via an "Edit" button in the details view.
|
|
8. **Settings:** User can change the Overpass API provider via the new Settings menu.
|
|
|
|
## Files Currently in Focus
|
|
|
|
- `app/components/map.gjs`
|
|
- `app/components/place-edit-form.gjs`
|
|
- `app/templates/place/new.gjs`
|
|
|
|
## Next Steps & Pending Tasks
|
|
|
|
1. **Linting & Code Quality:** Fix remaining CSS errors and address unused variables/runloop usage.
|
|
2. **Testing:** Add automated tests for the geohash coverage, retry logic, and new editing features.
|
|
3. **Performance:** Monitor performance with large datasets (thousands of bookmarks).
|
|
|
|
## Technical Constraints
|
|
|
|
- **Template Style:** Strict Mode GJS (`<template>`).
|
|
- **Package Manager:** `pnpm` for the main app, `npm` for the vendor module.
|
|
- **Visuals:** No Tailwind/Bootstrap; using custom CSS in `app/styles/app.css`.
|