BTC Map September Recap

October 2, 2026  |  BTC Map  ·  Projects

Note: this only reflects my personal work this month. Visit the BTC Map Blog for consolidated monthly reports.

My previous monthly recap

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/events now accepts updated_since, limit and include_deleted, returning a change log ordered by updated_at, id. Soft-deleted rows come back only with include_deleted=true and carry deleted_at, so clients can evict them locally.
  • GET /v4/areas gained the same incremental mode with an AreaDelta projection that exposes the bbox but no raw tags or geometry unless geo_json is explicitly requested.
  • The area delta carries localized_name and localized_description, built from the name:<lang> and description:<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.

/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-reports and POST /v4/place-submissions mirror the report_place and submit_place RPCs for signed-in users, always recording origin='user' and the caller in a new submitted_by column.
  • POST /v4/places/{id}/images lets signed-in users attach a photo to a place directly, and GET /v4/places/{id}/images serves 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_elements with a process-wide atomic flag, so a manual or retried call can’t start a second sync
  • Stopped returning soft-deleted area_element links from the v4 places and areas endpoint, with a regression test
  • Added more element icons and refreshed the v4 and RPC docs

CLI

  • event create now requires --starts-at, and event update dropped --clear-starts-at
  • event create and event update gained --timezone (auto or an IANA name) and dropped the old cron-schedule flags
  • Fixed event update clobbering 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: auto and 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.md log, 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}/events endpoint
  • 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.