Note: this only reflects my personal work this month. Visit the BTC Map Blog for consolidated monthly reports.
Table of Contents
Thanks
Thanks to Nathanael for another round of bug reports and testing.
Android
It has been six months since our last Android release. Version 1.2.0 shipped on September 30, and most of the work below feeds into it.
1.2.0 Release
The release finally collects the offline, search and account work into a stable build. The headline items:
- Global search covering places, communities and conferences
- Full offline mode with a bundled world map and downloadable high-res region packs
- Community pulse and community/country screens
- Optional accounts with saved places and areas, and a personalized activity feed
- Per-app language selection on Android 13+
- Simplified add-location and verify/report flows
- Human-readable opening hours and a database/image diagnostics screen
Offline First
This was the through-line of the month. The app now ships curated data snapshots so a fresh install is usable without a network:
- Full place records, so the first sync only fetches the delta since the snapshot
- The full areas snapshot, including borders
- Place-comments and events snapshots, so comments and event search work offline
- A low-zoom world basemap
The map, area screen, search and activity feed read from the local database and stay usable offline, and the sync state is centralized and observable through a new database stats screen.
I also added downloadable high-res offline map packs per region, using MapLibre’s offline manager, with a size estimate before download and progress and delete controls afterwards.
Places & Reports
- A native place submission form with a draggable-pin mini map, replacing the external btcmap.org link
- A native verify/report screen that posts to the new place-report endpoint, with the verify and report actions split
- Place issues on the area screen, and OpenStreetMap deep links
- Opening hours summarized per weekday with today highlighted
- btcmap.org place links now open in the app instead of the browser
- Boosted places now appear at the top of search results
Events & Activity Feed
- A dedicated event screen with deep link support
- The activity feed split into Local and Following tabs, with the filter moved into a dialog
- Upcoming-event badges on area icons and clickable event cards on the area screen
Documentation
I published a new documentation website: android.btcmap.org. It is fully static, so it loads instantly, and its minimalistic Material-inspired design with plenty of screenshots makes the user guides a real pleasure to read. I am genuinely happy with how it turned out.
Bug Fixes & Polish
- Fixed a pile of auth session races: token encryption, 401 handling, revoking tokens on sign-out and recovering undecryptable tokens
- Sped up search by reading only the fields results need
- Rendered outdated merchants with reduced opacity and gray icons, and filtered the oldest ones out by default
- Disabled map rotation by default, with an opt-in setting
- Hardened the map against rotation and edge-case input, and bounded the area geometry cache
- Restored the Carto Dark Matter style as a bundled option and fixed several marker and layout issues
API
Most of my API work this month went into areas and events. I promoted both to first-class citizens by enabling incremental sync and proper localization support, so caching clients can finally treat them the same way they already treat places and comments.
Delta Sync
I extended the v4 delta sync from places and comments to areas and events, so caching clients can pull only what changed:
GET /v4/eventsnow acceptsupdated_since,limitandinclude_deleted, returning a change log ordered byupdated_at, id. Soft-deleted rows come back only withinclude_deleted=trueand carrydeleted_at, so clients can evict them locally.GET /v4/areasgained the same incremental mode with anAreaDeltaprojection that exposes the bbox but no raw tags or geometry unlessgeo_jsonis explicitly requested.- The area delta carries
localized_nameandlocalized_description, built from thename:<lang>anddescription:<lang>tags, so a client can localize area names without a request per language.
I also fixed a subtle bug where comment timestamps in delta sync were compared as raw text instead of instants, which could make a client skip rows.
Events
Events used to be allowed without a start date. I made starts_at mandatory (migration 119 rebuilds the table with a NOT NULL constraint) and removed the now-dead “undated events are always returned” branches from get_events, /v4/events, the area upcoming events and search.
The bigger change is timezones. create_event and update_event now accept either an RFC 3339 timestamp with an explicit offset or a floating local time plus a timezone, where auto infers the zone from the event’s coordinates via tzf-rs (DST aware). The resolved offset is echoed back so callers can verify what was inferred. The unused cron_schedule column was dropped entirely.
Search
/v4/search now matches events by name alongside places and areas, keeping the same future-or-undated filter as /v4/events. Area results carry their icon, so clients no longer need a follow-up request per row.
I also added a lang query parameter to GET /v4/areas/{id} for localized names and descriptions, and attached upcoming events to the area lat/lon search results by testing each point against the area polygon. A new GET /v4/areas/{id}/events endpoint serves the same data with an optional date window.
Place Reports, Submissions & Images
I opened the user-facing write paths on REST, since mobile apps are less privileged clients that never use RPC:
POST /v4/place-reportsandPOST /v4/place-submissionsmirror thereport_placeandsubmit_placeRPCs for signed-in users, always recordingorigin='user'and the caller in a newsubmitted_bycolumn.POST /v4/places/{id}/imageslets signed-in users attach a photo to a place directly, andGET /v4/places/{id}/imagesserves them.- Place reports accept optional photo evidence (at most five photos, raster-only, size and dimension limited)
I also taught the issue sync to prefix titles with the country and community tags, and to pass raw osm:<tag> extra fields (address, payment methods) through to the paste-ready OSM tag block.
Auth
POST /v4/auth/signout now revokes the bearer token that authenticates the request, while leaving the user’s other tokens valid. It mirrors the existing RPC method, so the mobile clients can stay on REST for the whole session lifecycle.
Maintenance
- Guarded
sync_elementswith a process-wide atomic flag, so a manual or retried call can’t start a second sync - Stopped returning soft-deleted
area_elementlinks from the v4 places and areas endpoint, with a regression test - Added more element icons and refreshed the v4 and RPC docs
CLI
event createnow requires--starts-at, andevent updatedropped--clear-starts-atevent createandevent updategained--timezone(auto or an IANA name) and dropped the old cron-schedule flags- Fixed
event updateclobbering fields on partial updates and expanded its options
Event Admin
- Added “Clone previous event” to the add form, listing past events by distance so a new event can start from a template
- Removed the cron schedule and area fields from the event form
- Added an editable UTC offset (defaulting to the browser timezone for the entered date), then switched to sending
timezone: autoand let the server infer the zone from the location - Auto-bump the end date when the start date passes it
Infra
Overpass
For a long time we relied on a public Overpass instance for our OpenStreetMap sync. This month it became progressively less reliable: syncs would stall, and at times we lagged OpenStreetMap by 12 hours or more. New merchants and edits took far too long to reach the map, and the instance’s reliability was entirely out of our hands.
So I set up our own Overpass server at overpass.btcmap.org and switched the sync over to it. It is not ready for public use yet, but if it holds up we might offer it to the broader community in the future.
Fleet & Maintenance
- Added a README with a server fleet table (roles, IP addresses, maintenance dates) and a
MAINTENANCE.mdlog, replacing the informal notes - Recorded maintenance sessions for gitea, lnd and umami: full system upgrades, reboot autonomy tests and SSH hardening
- Added the lnd, lnbits/nostr and umami hosts to the inventory
Website
- Self-service signup with a username and password, so users no longer have to accept an auto-generated account
- An events section on community and country pages, fed by the new
/v4/areas/{id}/eventsendpoint - Android app metadata, and faster beta APK downloads via the CDN
- Dropped a vestigial areas and reports sync from the leaderboard pages
Future Plans
A few things on my mind:
- I’ve just started a Kotlin Multiplatform shared module and a Compose Multiplatform rewrite of the UI, with a desktop app as the eventual payoff. That will dominate the next report.
- Cut the next Android release once the Compose work settles.
- Continue the API docs cleanup.
- Finish eradicating the legacy v2 endpoints from the website.