Fixed
- Directions now has a Get directions button, including for endpoints selected on the map. Transit dispatches to OpenTripPlanner and decodes its polyline5 geometry; empty or failed routes clear the old line instead of displaying a misleading "Route ready" message (#45, #47).
- Directions preserves typed endpoints when changing travel mode and fits the map to the resulting route. Trips outside the loaded region show a coverage hint. On mobile screens the controls and map share the viewport, keeping the map and point picker accessible.
- Placeholder installs its official prebuilt database automatically on first start. Its data directory honours
PUID/PGID; downloads stream to a temporary file, are checked against the pinned service's schema, and only then replace the database. Invalid previous databases are backed up; valid databases are reused offline. This removes the manual WhosOnFirst import from normal Search setup (#26). - The catalog generator runs without starting the database or control plane, fixing the monthly refresh on a clean CI checkout.
- The docker socket group was read from the wrong place.
compose.ymlexportsDOCKER_GIDinto the app container, defaulting to 999, while the socket on macOS is gid 0 — so preferring that variable granted a group the daemon does not answer on and left the control plane degraded anyway. The socket's own gid now decides;DOCKER_GIDis honoured as an additional group, which is the only lever available when the socket is not visible from inside the container. - Dokploy deployments never gave Valhalla its region.
/work/data/valhallahad no volume there, so the staged PBF landed in the container's ephemeral layer and Valhalla crash-looped exactly as it did elsewhere. - Service status ignored every parser. The status guards matched atom phases while all parsers emit strings, so
downloading,buildinganderrorwere unreachable and any enabled service that was not ready collapsed to "starting" — a crashed geocoder reported itself as still starting up, and a multi-GB index download showed no phase for its whole run. - An apply announced itself finished while its sidecars were still ingesting. A sidecar that had not logged in the seconds before the applier returned was written off as absent. Services the apply actually restarts now hold the timeline open until they report; only ones it deliberately skipped settle early.
- Failures during the restart step went unrecorded: a
docker compose restartthat failed was discarded and reported as a successful apply, and when it followed a failed conversion it vanished entirely — leaving the sidecar rows to turn green on log lines from the containers that never received the new data. Every service is now attempted, and every failure is named. - A finished apply stayed on screen forever — the Region tab and
/admin/applykept rendering the last run until another one started. It can now be dismissed. - A failed restart no longer reports the Overpass conversion as broken, a completed stage is no longer reddened by a later crash, and a progress bar can no longer exceed 100% when an upstream under-reports its
Content-Length. - The control plane came up permanently degraded. The entrypoint re-added
DOCKER_GIDafter dropping to uid 65534, but compose'sgroup_add:never exports that value into the container, so the dropped process ran with an empty supplementary group list and could not reach the docker socket; the socket's own gid is now read instead. Separately,HOME=/rootis unreadable by 65534, which made the docker CLI abort plugin discovery and reportunknown flag: --shortrather than a permission error —DOCKER_CONFIGnow points somewhere readable. - Valhalla crash-looped after every apply, and lost its tiles on restart. The gis-ops image scans its own
/custom_filesfor*.osm.pbfand exits with "No local PBF files... Nothing to do" without one, but the applier only ever wrote toosm/. The region PBF is now staged where Valhalla actually looks, and its tile directory has a bind mount, so a rebuilt graph survivesdocker compose restart. - Applying more than one region always failed, in milliseconds.
osmiuminfers its output format from the file extension, and the applier merges tocurrent.osm.pbf.partialso an interrupted run leaves a file the partial sweep can recognise; osmium rejects that name outright without an explicit-f pbf. - OpenTripPlanner never reported itself ready. The log parser watched for four phrases OTP does not emit — it logs
Started listener bound to, notStarted listening, andGrizzly server running, notGrizzly server started— so transit sat atsaving-graph 90%until something happened to call the API and a fallback pattern rescued it. The fixture that covered this was written to match the regex rather than the program; it is now a verbatim capture, alongside an unedited full container start that the phase-order test runs against. - OTP silently discarded every time-based restriction. Without
osmDefaults.timeZoneit logs "time-restricted entities will not be created" and drops opening hours and conditional access from the street graph. Atlas wrote no OTP config at all; it now pins the zone when the selected regions agree on one, and removes a stale config when they do not, since a zone outliving its extract shifts every restriction by whole hours without saying so. The country table is generated from the IANA database: a country qualifies only when all of its zones hold the same UTC offset all year, so Berlin + Vienna resolve while Berlin + London stay ambiguous, and the US, Canada, Australia, Brazil and Russia are excluded by construction. - Region subdivisions had no country. Geofabrik publishes
country_codeonly at country level, sogf:berlinand two thirds of the shipped catalog arrived asnil. The catalog now inherits it up the parent chain (523 of 796 regions resolve a zone, up from 265). - The apply progress bar sat at exactly 50% for the whole Valhalla build.
building-tileswas treated as a measured phase, but the parser assigns it a flat0.5on sight of "Running valhalla_build_tiles" and its only real-progress pattern matches OpenTripPlanner's wording. Sidecar stages for services that never started now settle as skipped with a reason instead of spinning indefinitely. - Search answered every failure with an empty panel, so a geocoder that was never installed, one that had crashed, and a misspelt place name were indistinguishable from a dead button. The panel now names the service and the reason — "Photon is not installed" is actionable where "unavailable" is not, and it is the ordinary state of a fresh instance — and separates not installed from still downloading its dataset from installed but not responding.
- Hovering and keyboard-highlighting a search result had no visible effect: both painted the row
bg-base-200, the colour the panel already sits on. - A malformed value in any upstream tuning knob (
PHOTON_TIMEOUT,VALHALLA_TIMEOUT,*_OPEN_TIMEOUT, …) no longer takes down every request that reads it.env_int/2usedString.to_integer/1, soVALHALLA_TIMEOUT=10sraised on each call; it now falls back to the default and logs a warning naming the variable. - The Protomaps daily-planet basemap targets the previous day’s build, which is already published, instead of a build that may not exist yet (#2)
- Transit planning now uses OpenTripPlanner's GraphQL API instead of the removed legacy REST endpoint (#25).
- Region apply no longer stalls or silently serves stale POI data: the PBF→bz2 convert is no longer bounded by a 30-minute wall-clock timeout (a genuinely hung osmium is now caught by a stall watchdog instead), orphaned
.partialfiles are swept fromosm/,osm/sources/andgtfs/, and a failed conversion fails the apply loudly instead of leaving overpass on a weeks-old snapshot (#34, #28). The convert now runs after OTP staging, so a broken overpass source no longer withholds fresh data from valhalla and OTP. - Reverse-proxy deployments no longer loop on HTTPS redirects when TLS terminates before Caddy (#20)
- The app no longer crash-loops when the data dir is owned by another uid:
PUID/PGIDare honoured, the entrypoint takes ownership before dropping privileges, and an unwritable dir reports a clear error instead of a barePermission denied(#23) - Headless LAN deployments work over plain HTTP on a non-standard port:
PHX_SCHEME,PHX_PORT,FORCE_SSLandPHX_CHECK_ORIGINare now configurable, andPHX_SCHEME=httpturns the HTTPS redirect off by default (#19) - The in-app control plane now reads the project
.env, so region, UID/GID and heap settings reach control-plane-launched services instead of silently falling back to compose defaults (#22). Note: if your.envsets a differentCOUNTRY_CODEthan the compose default, the next service start will now honour it and re-download that region's data. - Valhalla now starts with a safe default worker cap and file-descriptor limit on multi-core hosts (#24)
- Overpass diff updates are now opt-in so POIs can serve after an initial import without waiting on Geofabrik catch-up (#27)
- Dokploy's minimal compose file no longer starts or exposes Overpass by default; the Overpass service and
/overpassproxy are now commented out together as optional poster-sidecar configuration.
Added
- Search markers, dismissal and shareable queries behave as one thing. Picking a result, pressing Escape and clearing the box all clear the pins with the list; flying to a result no longer re-runs the query that put it there; and a shared
?q=link reproduces the sender's results instead of being re-scoped to wherever the viewer's map happened to open. - Type-as-you-go search. Results now arrive while typing (200 ms debounce, two-character minimum) instead of only on Enter, with arrow-key navigation, Enter to pick the highlighted result and Escape to dismiss. Search is scoped to the visible map again and asks for up to 40 results, re-querying as you pan — so searching a chain name answers "which of these can I see" rather than returning a handful from across the country.
- Every search result is now a marker, not just the one you click, so you can see where the matches are before choosing. Clicking a pin opens a popup with the place's category, a one-line address, its coordinates and a link to the OpenStreetMap object. Results carry a category icon and label in the list as well.
- Searches are shareable. The query lives in the URL as
?q=…and is applied when such a link is opened, so a search reproduces what the sender saw. The same path serves typing, a pasted link and the back button. - A copy button on both log viewers, so reading a crash-looping sidecar no longer means selecting hundreds of lines by hand. It reports failure inline rather than silently succeeding, because clipboard writes are blocked on insecure origins — which a LAN deployment over plain HTTP is.
- The region-apply timeline reports what it is actually doing: which file is downloading, from which URL, and how many bytes of how many; which stage is running and which are still pending; and it continues past the applier's return through Valhalla, Overpass and OTP ingest, since an apply that finished but left Valhalla hours from serving a route has not finished in any sense that matters. Stages whose progress nothing measures show their phase rather than an invented percentage.
- Map matching —
POST /api/v1/map-matchsnaps a recorded GPS trace onto the road network via Valhalla's Meili matcher, the inverse of/api/v1/route: routing invents a path between two points, matching takes a path you already walked and decides which edges you were on. Post ashapeof{lat, lon}points (optionally withtimeandaccuracy);mode,shape_matchand Valhalla'ssearch_radius/gps_accuracy/breakage_distanceare accepted.format=polyline6(default) returns Valhalla's legs verbatim, matching what/api/v1/routealready returns;format=geojsonstitches them into one decodedLineString, so callers need no polyline decoder of their own. A trace Valhalla rejects is reported as invalid input rather than as an upstream failure, relaying Valhalla's own reason anderror_code— whether the trace fell outside the loaded region, was too sparse or noisy, or exceeded a service limit such as the 200 kmmax_distance. Trace length is capped byMAP_MATCH_MAX_POINTS(default 10000), and matching gets its ownVALHALLA_MATCH_TIMEOUT(default 60000 ms) separate from the routing timeout, since it is superlinear in point count and holds a Valhalla worker for the whole request. Both knobs are read from.env.
Changed
- Release publication and image builds now run Phoenix and deployment tests first, then build and boot the actual image on native amd64 and arm64 runners. Stable image tags are published only after both architectures pass. Release notes must match the application version;
Unreleasedcannot accidentally select an older release, and an interrupted publication can be resumed on the same commit. ghcr.io/dawarich-app/atlas/app:latestnow means "newest stable release", not "tip ofmain". Images are cut from published GitHub releases; pushing tomainno longer publishes one. Pin a version tag if you were relying onlatesttracking every merge — and note that a freshdocker compose pullwill now hold at the last release rather than moving with development.- Releases are tagged and published automatically from this file: when the top heading is a version with a date (rather than
Unreleased), CI tags it, creates the GitHub release, and builds the image. Per-commit builds continue on the OneDev registry for testing. mix credo --strictis clean and enforced in CI, with the project's ruleset checked in atapp-phoenix/.credo.exs. It previously failed onmainand ran ahead of the test step, so the test suite had never actually executed for a pull request.- The deployment-config assertions in
test/deployment_config.test.mjsnow run in CI, on a workflow that triggers whenCaddyfileor acompose*.ymlchanges. They had no runner, so the reverse-proxy and sidecar defaults they guard could regress silently.