# Stack Research: araharah (Telegram-first GPS navigation) **Domain:** Telegram bot + Mini App turn-by-turn navigation on OpenStreetMap data (KL metro first) **Researched:** 2026-10-22 (all versions verified against registries/official docs on this date) **Confidence:** HIGH (core stack) / MEDIUM (geolocation workarounds, snapshot path) ## Executive Verdict The viable 2026 stack for this project is already fully assembled from cloud-native + OSM components: **grammY (TypeScript) on a single Cloudflare Worker** for the bot + Mini App API, **Cloudflare Workers static assets** (not legacy Pages) hosting the Mini App, **MapLibre GL JS v6** with **OpenFreeMap** vector tiles for the webview map (custom style JSON = the Telemetry Rams look), **self-hosted Photon + OSRM on one cheap VPS** for geocoding and routing (Malaysia/country extract only — no planet imports), and **Cloudflare KV** for ephemeral session state. Total infra cost: ~€5–10/month VPS + $0 Cloudflare. Critically: every public/community OSM service (OSRM demo, photon.komoot.io, nominatim.openstreetmap.org) is explicitly **not production-grade** and every one of them forbids or throttles exactly the traffic a navigation app generates. Self-hosting a Malaysia-area extract is small (tens of GB, not terabytes), cheap, and is the only option consistent with the privacy stance (user origin/destination coordinates go to infrastructure that stores nothing — not to ad-funded or quota-metered third parties). Also note: a KL-country-scale deployment does not need a database for routing or geocoding state at all. The aspirational stack in IDEAS.md must be cut hard for v1. Neon/PostGIS, RabbitMQ, Prometheus/Grafana, Resend, and Maptiler are all **deferred or dropped** — none is needed for search → route → guidance. The privacy stance actually *reinforces* this: if nothing is stored, there is almost nothing to database. --- ## Recommended Stack — Core Technologies ### 1. Telegram bot layer | Technology | Version | Purpose | Why Recommended | |------------|---------|---------|-----------------| | **Telegram Bot API** | 10.3 (Aug 2026) | Bot transport, commands, Mini App launch, location messages | Current stable API. Bot API 8.0 (2024) added the Mini App `LocationManager` — the geolocation API this product needs — and Bot API 10.2 (Jul 2026) added Rich Messages including `InputRichBlockMap` (native map blocks in chat, a candidate for backgrounded guidance cards). | | **grammY** | 1.46.0 (npm verified) | Bot framework | De-facto standard TypeScript bot framework. First-class webhook support (works natively on Cloudflare Workers — the official docs document exactly this deployment), `BotCommand`-scoped menus, update validation via `X-Telegram-Bot-Api-Secret-Token`, and the `conversations` plugin for multi-step search→confirm flows. Actively maintained at Bot API 10.x parity. | | **@grammyjs/conversations** | latest 2.x | Multi-turn flows | `Query "KLCC" → result list → open Mini App` is a conversation; conversations plugin models it cleanly without hand-rolled state machines. | **Webhook vs polling (decided): webhook.** Reasons: (a) the bot backend lives on Cloudflare Workers, which is request-driven — polling via long-running processes is a poor fit for Workers (no persistent connections); (b) webhook gives instant routing of the secret-token header for update validation; (c) scale-to-zero cost. Polling (`grammy runner`) only if you later move the bot to a long-lived VPS — unnecessary duplication. **Bot commands / menus:** configure via `setMyCommands` (a `/start`, `/navigate`, `/search` minimum) and set the **default chat menu button to open the Mini App** (BotFather → Menu Button, or `setChatMenuButton` API). No custom keyboard grids for v1 — inline keyboards on result messages suffice (Rams: less, but better). ### 2. Mini App front end (the webview map) | Technology | Version | Purpose | Why Recommended | |------------|---------|---------|-----------------| | **telegram-web-app.js** (Telegram official script) | per core.telegram.org/bots/webapps | Mini App bindings, viewport, LocationManager, initData | The canonical WebApp JS API, loaded from `https://telegram.org/js/telegram-web-app.js`. Gives you: `initData` for identity, `LocationManager` (Bot API 8.0+) for persistent geolocation permission on mobile, `BackButton`, `Expand`, `MainButton`, viewportChanged events, and theme wiring. | | **Vite + TypeScript** | Vite ^8 (npm verified 2026-10-22) | Build/dev for the Mini App | Cloudflare has an official Vite plugin; Mockups exist as HTML fragments — Vite multi-page or single-entry builds them cleanly. No framework needed for 4 screens. | | **MapLibre GL JS** | ^6.15.0 (npm verified; v6 line started 2026-07-22) | Map rendering in the webview | Vector rendering means one tile schema + **custom style JSON** → the entire Telemetry Rams cartography (Matte Black land, Signal Orange route casing, Olive water) is editable CSS-like style JSON. Supports `bearing` (rotate map to course — essential for driving UX), smooth tile LOD, and is Mapbox GL fork lineage with permissive BSD. Note v6 requires explicit `setWorkerUrl` wiring in bundlers (Vite snippet exists in OpenFreeMap quick start). | | **OpenFreeMap** | tiles.openfreemap.org | Free vector tile provider | Free, **unlimited map views, no API key, no registration, commercial use allowed**, privacy-positive (no cookies, no tracking, OpenMapTiles schema), weekly OSM updates — verified on openfreemap.org. Use the hosted `liberty`/`positron` styles as baseline, then fork the style JSON into the Rams look. Maximum data zoom z14 is fine inside KL at driving zooms. | **Cloudflare hosting (decided): Workers with static assets — not Pages.** Cloudflare's own docs (updated Oct 2026) direct **all new projects to Workers**: "all of our investment, optimizations, and feature work will be dedicated to improving Workers" (Pages still works but is legacy — its own docs carry a "migrate from Pages to Workers" guide). A single `wrangler.jsonc` project gives you both the Mini App static files (`assets.directory`, `not_found_handling: "single-page-application"`) **and** the API/bot webhook routes (`/api/geocode`, `/api/route`, `/telegram/webhook`) in one deployable unit. HTTPS is automatic — Telegram Mini Apps require HTTPS and Worker URLs are TLS-terminated by Cloudflare out of the box. ### 3. Geocoding / POI search (must find "KLCC") | Technology | Version | Purpose | Why Recommended | |------------|---------|---------|-----------------| | **Photon (self-hosted)** | latest (Java 21+; pin release at setup) | Forward/reverse geocode + **typeahead/autocomplete** | The only free OSM geocoder that natively does search-as-you-type, typo tolerance, and location bias. Runs prebuilt country dumps: GraphHopper hosts weekly **Malaysia extract** (`download1.graphhopper.com/public/extracts/by-country-code/my/`) — download two files, unzip, `java -jar photon-*.jar serve`. ~<5 GB disk for MY, no Nominatim/Postgres needed. Excellent POI recall for abbreviated landmark names ("KLCC", "KLIA", "Pavilion") because it indexes all OSM names incl. `name:en`. | | **Cloudflare Worker proxy + cache** | — | Geocode API edge in front of Photon | Route all Mini App/bot geocode queries through your Worker → your VPS Photon. Adds HTTPS (Photon is HTTP), hides the origin, and lets you cache hot queries (KL top-POIs) in KV. Also the integration seam if you later swap Photon for a paid API. | **Layer strategy for v1 quality:** prefer Photon results **filtered to MY bounding box + `layer=venue/poi` boost**; fall back to Nominatim semantics only for address-mode queries. Rate-limit-aware caching in KV (TTL 24 h on hot POI ids) keeps VPS load trivial at early-user scale. ### 4. Routing engine | Technology | Version | Purpose | Why Recommended | |------------|---------|---------|-----------------| | **OSRM (self-hosted)** | v26.10.0 (CalVer, release verified 2026-10) | Car routing with turn-by-turn steps | Purpose-built for car navigation, `steps=true` returns per-maneuver instructions + geometry, `alternatives=2` returns a short route list. With a **Malaysia extract** (~100–200 MB pbf via Geofabrik extracts, or a KL-only custom extract via osmium), the preprocess (`osrm-extract` → `osrm-partition` → `osrm-contract`) runs in <4 GB RAM on a shared VPS. Query latency single-digit ms; no per-request cost. Supports custom profiles but the standard car profile suits Malaysian roads (incl. tolled highways flagged). | **Why not the other engines first (Opinionated):** - The **official OSRM demo server (`router.project-osrm.org` / `routing.openstreetmap.de`)** is a volunteer demo — its own wiki and maintainers say *"NOT SUITABLE FOR PRODUCTION"*, throttle >1 req/s, no SLA, and have had multi-day outages (verified: OSM community threads 2024–2025, GitHub issues #6647/#5997). Testing-only. - **Valhalla (3.9.1)** — richer feature set (elevation, isochrones) and better instructions, but noticeably more complex to build/update and its C++ toolchain is less standardized in Docker images. Switch to it later if you add cycling/walking modes, elevation cues, or turn-by-turn instruction localization grammars. - **GraphHopper cloud** — solid but paid for anything beyond hobby volume. - **OpenRouteService free cloud** — 2 000 directions/day, now at `api.heigit.org` (api.openrouteservice.org endpoints were deprecated in 2026). Fine as dev fallback but a hard ceiling for a location-refreshing navigation app, and each route request carries your users' GPS points to a third party. ### 5. Backend state & storage | Technology | Version | Purpose | When to Use | |------------|---------|---------|-------------| | **Cloudflare KV** | free tier | Ephemeral session state: in-flight search results, pending route selection, route-exhaust windows | Drop-in KV adapter in grammY; TTL key expiry naturally enforces "no long-term location storage". | | **Cloudflare D1 (SQLite)** | free tier | Later: opt-in anonymized telemetry, saved places | **Not v1.** Only when users consent to opt-in features. | | **Cloudflare Queues** | free tier | Future: async report ingestion (v2) | Not v1 — direct synchronous API calls suffice for search/route. | ## Supporting Libraries | Library | Version | Purpose | When to Use | |---------|---------|---------|-------------| | **@turf/turf** | 7.4.0 (npm verified) | Geo math client-side: `nearestPointOnLine` (off-route detection), `bearing` (map rotation), `distance` | Map/geometry utilities inside the Mini App for route-following without server round-trips. | | **hono** | 4.13.13 (npm verified) | Typed HTTP routing inside the Worker (`/api/*`, `/telegram/*`) | With one Worker serving both static assets and API routes, Hono keeps the API routes readable and typed. Alternatives (raw `Request` handler) fine if you prefer zero deps. | | **zod** | 4.6.7 (npm verified) | Schema validation for all request payloads | Validate `initData`-derived user IDs, geocode query strings, coordinates. | | **@grammyjs/storage-free** | latest | grammY storage adapter for Cloudflare KV | Backs the conversations plugin with key/value storage — required once the bot runs serverless (no in-memory session). | | **@telegram-apps/sdk** | 3.x | Optional richer TS SDK over telegram-web-app.js | Only if you want typed `initData` parsing helper + LocationManager wrappers; the raw script is enough if you keep front-end deps minimal. | ## Development Tools | Tool | Purpose | Notes | |------|---------|-------| | wrangler ^4.151 (npm verified) | Workers deploy/local dev | `npm create cloudflare@latest` scaffolds static-assets worker in one step; use `assets.directory` + `not_found_handling: "single-page-application"`. | | Docker Compose (dev VPS) | OSRM + Photon local orchestration | Official `osrm/osrm-backend` image preprocessed on a Malaysia extract; photon runs its country dump; both lightweight in Compose. Note: current OSRM images live on GHCR (`ghcr.io/project-osrm/osrm-backend`) — the Docker Hub mirror is stale at v5.25. | | Geofabrik malaysia-singapore-brunei extract | Source OSM data for OSRM/Photon | Choose `malaysia-singapore-brunei` (whole-country) rather than planet — trivially small. | | Bot webhook/API testing | wrangler local dev + HTTP client | `wrangler dev` exercises the Worker (assets + `/telegram/webhook`) locally; Bruno/Postman replays saved Telegram update payloads for fast iteration on update shape. | ## Installation ```bash # Cloudflare side (bot API + Mini App statics) npm create cloudflare@latest araharah # choose Worker + static assets template, TypeScript npm install grammy @grammyjs/conversations hono zod npm install -D wrangler @cloudflare/workers-types # Mini App side (within same repo, e.g. apps/miniapp) npm install maplibre-gl @turf/turf npm install -D vite # Style: fork https://tiles.openfreemap.org/styles/liberty into rams.style.json # Routing/geocoding side (self-hosted VPS, Docker Compose) # NOTE: the legacy Docker Hub image `osrm/osrm-backend` is stale (top tag v5.25, 2021). # Current releases ship via GitHub Container Registry: docker run -t -v "${PWD}/malaysia-latest.osm.pbf:/data/malaysia-latest.osm.pbf" \ ghcr.io/project-osrm/osrm-backend:v26.10.0 osrm-extract -p /opt/car.lua /data/malaysia-latest.osm.pbf # ... osrm-partition / osrm-contract / osrm-routed # Photon: download https://download1.graphhopper.com/public/extracts/by-country-code/my/... and # java -jar photon-*.jar serve ``` ## Alternatives Considered | Slot | Recommended | Alternative | When to Use Alternative | |------|-------------|-------------|-------------------------| | Bot framework | grammY 1.46 | Telegraf 4.16.3 | Only if team already has large Telegraf codebase; Telegraf lags Bot API 10.x features and has weaker Workers ergonomics. | | Tile provider | OpenFreeMap (free, no key, unlimited) | MapTiler (free: 100k req / 5k sessions per month, **non-commercial only**, forced MapTiler logo — verified pricing page) | If you need a *hosted stylable map* — MapTiler's `Session` model + required logo + non-commercial clause fights both the budget and the design system. Also skip MapTiler's *Rendered raster* tile flow (512px raster counts as 4 requests and prevents custom vector styling). | | Tile provider (2) | OpenFreeMap | Protomaps self-hosted on Cloudflare R2 + PMTiles | Full control/per-region: a Malaysia-only PMTiles bucket costs ~pennies on R2 and no external calls. Only worth it if you later need a custom tile schema or worry about OpenFreeMap's lone-maintainer risk. | | Map renderer | MapLibre GL JS v6 | Leaflet | Leaflet if you need tiny bundle + wide browser support and are happy with raster tiles + limited styling. In a navigation app you want the vector pipeline (style JSON, bearing, GPU cam) that Leaflet effectively lacks. | | Geocoder | Photon self-host (MY dump) | Nominatim (`nominatim.openstreetmap.org`) | Nominatim public is capped at **1 req/s** and **forbids autocomplete** on the public instance (verified policy) — a navigation app with typeahead breaks both rules in one hour of usage. | | Geocoder (2) | Photon self-host | Paid APIs (MapTiler geocode / HERE / Geocode Maps) | If you want zero-ops and the privacy stance tolerates a commercial geocoder's logs — MapTiler's new 2026 *session* model makes costs predictable. Only justified if/when Photon's MY quality falls short. | | Routing | OSRM self-host v26.10 | Valhalla self-host 3.9.1 | More feature-parity for guided navigation with elevation, better instruction localization, and isochrones — but larger ops effort and image sizes. Use if/when you extend beyond driving. | | Routing (alt) | OSRM self-host | OpenRouteService cloud (api.heigit.org, 2 000 directions/day standard) | Great docs + bundled geocoder/pelias; use only if you want zero VPS and accept quota ceilings. | | Backend state | Cloudflare KV | Neon Postgres+PostGIS | **Defer to v2 for opt-in telemetry/reports.** PostGIS is genuinely useful for incident geo-queries and traffic heatmaps — but only once the privacy-consent feature actually exists. | | Static hosting | Workers with static assets | Cloudflare Pages (classic) | Pages still fully works but Cloudflare's own docs route every new project to Workers; no reason to start a new project on the legacy path. | ## What NOT to Use | Avoid | Why | Use Instead | |-------|-----|-------------| | `router.project-osrm.org` in production | Officially "NOT SUITABLE FOR PRODUCTION", >1 req/s forbidden, no SLA, has multi-day outages, CF/Worker-origin traffic will be flagged | Self-hosted OSRM on a small VPS (Malaysia extract) | | Public `nominatim.openstreetmap.org` for typeahead | Usage policy caps 1 req/s and **explicitly forbids autocomplete**; withdrawing access without notice | Self-hosted Photon (typeahead-native) or a paid API | | `photon.komoot.io` demo server in production | Komoot's own guidance: demo only, "if you have to ask about limits, run your own instance" | The same Photon software self-hosted with the GraphHopper MY dump | | **Google / Mapbox / HERE / Apple Maps SDKs everywhere** | Forces proprietary data licensing, ad-tracking concerns, breaks the OSM/privacy brand, and adds token dance to a webview app you want strip-simple | OSM stack (Photon/OSRM/OpenFreeMap) | | **Neon PostGIS in v1** | Nothing persistent survives the session; PostGIS adds an ops dependency before there is any incident/report data worth geo-querying | Cloudflare KV (ephemeral); add PostGIS only for v2 `reports/telemetry` phase | | **RabbitMQ** | A route request + a search query are synchronous and millisecond-fast; nothing needs decoupled queues at v1 scale | Direct Hono route → OSRM/Photon HTTP calls | | **Prometheus + Grafana** | You have single-digit services; Cloudflare Workers Observability (logs/tail) covers it; a full metrics stack here is toy-scope inflation | Cloudflare built-in Workers logs + a simple `/healthz` in the VPS Compose | | **Resend (email)** | Telegram *is* the notification channel — IDEAS.md itself says so | Telegram bot messages | | **MapTiler as primary paid dependency** | Free tier is non-commercial + forced logo, and their Rendered tiles count 512px raster as 4 requests (pricing-page verified) — bad economics for a driving app that repaints constantly | OpenFreeMap with a custom style JSON | | **Heavy SPA framework (Next.js/Nuxt) for the Mini App** | Massive payload for a roads-and-buttons webview; Mini Apps live inside Telegram and boot on tap — bundle size is the boot-time tax | Vite + TypeScript, <200 KB gzip JS budget; add Preact only if state management demands it | | **MapLibre GL JS ≤ v4 pinned** | v6 (2026) fixed the bundler community's worker-URL pitfalls and improves webview performance; pinning to v4 locks you out of current style-JSON tuning | `^6.15.0` with explicit `setWorkerUrl` | --- ## Geolocation in the Cloudflare-hosted Mini App (worked-through) This was a specific concern; here is the concrete, verified picture: 1. **HTTPS:** Worker URLs (`*.workers.dev` or custom domain) are HTTPS by default → satisfies Telegram's hard requirement, including the "Mini App methods from origins different from the original Mini App domain" hardening that auto-enables for **all** Mini Apps on 2026-07-20 (Bot API 10.2 changelog). Deploy the Mini App from one origin — do not route it through redirects to foreign domains. 2. **Secure-iframe constraint:** Telegram Desktop / Telegram Web run Mini Apps inside a browser context where your page cannot pop cross-origin permission prompts the way a standalone tab would. Practically: **`navigator.geolocation` works on mobile Telegram (iOS + Android webview), and on Desktop it either** **does nothing or needs extra setup** (verified: Telegram-Mini-Apps issues #77, #964, tma.js discussion #675 — Desktop is a known gap). 3. **`LocationManager` (Bot API 8.0+) is the primary path:** it goes through Telegram's native permission bridge, so the grant **persists across sessions** on mobile — a big UX win vs. web geolocation, where permission resets each session (verified: Telegram-Mini-Apps issue #56 comment by stefda, Jul 2025). Call `WebApp.LocationManager.init()` → `getLocation(cb)`; the payload includes `speed` and `course` (useful for driving UI). 4. **Android bug to code around:** `LocationManager.getLocation` on Android returns static/cached coords when called on a timer (verified: Telegram-Mini-Apps #56, reported Jun 2025, still open). Workaround shipped in the wild: also use `navigator.geolocation.watchPosition` inside the webview (permissions reset per session, but works continuously once granted) — combine both sources, prefer freshest `speed/course` flagged fix — and route cross-platform behavior through one small `getLiveData()` wrapper. 5. **Desktop fallback via the chat:** Telegram Desktop can't geolocate inside the Mini App → offer `KeyboardButton(request_location=true)` (a chat-side native Telegram location) as the universal fallback; the bot receives a `location` message and hands coordinates to the Mini App a deep link (`WebApp.openTelegramLink("https://t.me/?startapp=")` or a fresh `startapp` payload). This also serves the "bot-first" UX (user shares location from the chat without ever opening the Mini App). 6. **What this means architecturally:** the origin-bias request always flows through `initData`-verified account + at most one ephemeral KV entry (view-state only), so the flow stays inside the "no stored location history" promise. ## Stack Patterns by Variant **If the Mini App's map misses the Rams look at first release:** - Fork the OpenFreeMap `liberty` style JSON and restyle: bg = Matte Black `#1A1A1A`, land = Soft Beige `#D9D2C6`, water = deep slate-olive, route casing = Functional Red `#ED3F1C` (outer) over Signal Orange `#EB8008` (inner). This is exactly the property vector tiles give you — raster tiles cannot deliver it. Iterate on style JSON only, no code changes. **If Photon MY quality ever disappoints on real KL queries:** - Add a hand-seeded KL landmark alias file (top-50: KLCC, KLIA, LCCT, Mid Valley, Pavilion, KL Sentral, …) checked before Photon. 5 minutes of data work that fixes 80% of short-name POI misses. Keep Photon as the fallback — don't over-engineer synonyms early. **If you ever outgrow the single VPS:** - First leverage: move OSRM/Photon into Hetzner Cloud separate VMs (cheap) before any managed service. Skip "managed OSM infrastructure" entirely — it is the most over-priced niche in the cloud market. **If you lean on Rich Messages for the chat map later:** - Bot API 10.2 (Jul 2026) added `InputRichBlockMap` (rich messages with map blocks). This may become a native way to send backgrounded chat "turn cards" with a real map, replacing custom static tiles. **Do not bank on it for v1** — filed as a phase-research flag, not a stack dependency. ## Version Compatibility | Package A | Compatible With | Notes | |-----------|-----------------|-------| | `grammy@^1.46` | Bot API 10.3 | `@grammyjs/types@^5` ships Bot API 10.x types; check type parity on any API version bump (`@BotNews` for deprecations). | | `maplibre-gl@^6.15` | Vite ^8 | v6 requires explicit worker wiring: `import { setWorkerUrl } from 'maplibre-gl'; import workerUrl from 'maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url'; setWorkerUrl(workerUrl);` (documented in OpenFreeMap quick start). Without this the webview render may hang in production builds. | | `wrangler@^4.151` | `compatibility_date >= 2026-10-01` | Use jsonc config; toml is being edged out. Static assets config: `assets.directory` + `not_found_handling`. | | Bot API `LocationManager` | requires Telegram app **8.0+ (2024)** | Still guard `isSupported` for older clients, and always keep the chat-location fallback. | | Bot API `RichMessage`/`InputRichBlockMap` (10.2) | Telegram app late-2026+ | Post-v1 feature; do not make backgrounded guidance depend on it until usage share confirms client support. | | OSRM `v26.10` profile files | long-lived car.lua profile | Pin the Docker image tag; an OSRM profile regression silently changes recommended route quality. | ## Sources - Telegram Bot API changelog (official, core.telegram.org/bots/api-changelog) — verified: current Bot API 10.3, Aug 2026; 10.2 RichMessage/InputRichBlockMap + origin hardening enabled 2026-07-20. **HIGH** - Telegram Mini Apps docs (official, core.telegram.org/bots/webapps) — verified: `LocationManager` is Bot API 8.0+, `LocationData` has `speed`/`course`; bot-side chat geolocation is standard `KeyboardButton(request_location)`/message. **HIGH** - npm registry (checked 2026-10-22) — `grammy@1.46.0`, `@grammyjs/types@5.0.0`, `maplibre-gl@6.15.0`, `@turf/turf@7.4.0`, `wrangler@4.151.3`, `hono@4.13.13`, `vite@8.3.5`, `zod@4.6.7`. **HIGH** (versions) / MEDIUM-HIGH (fitness for this domain, community consensus) - OpenFreeMap official site + GitHub (openfreemap.org, hyperknot/openfreemap) — verified: free unlimited, no API key, commercial allowed, OpenMapTiles schema, privacy model, `setWorkerUrl` Vite guidance; also their own note that they can't offer PMTiles-on-Cloudflare due to latency (supports the R2→PMTiles "later, don't do it first" call). **HIGH** - OpenFreeMap vs Maptoolkit vs Protomaps comparison (maptoolkit.org/compare, 2026) — cross-informs tile-provider landscape table; Maptoolkit itself is a 2025-launched alternative (10 req/s throttled unregistered, <€1M commercial cap). **MEDIUM** - MapTiler Cloud pricing / terms (maptiler.com/cloud/pricing, /terms/cloud, doc "sessions vs requests") — verified: free tier is non-commercial, forced MapTiler logo, 100k req / 5k sessions per month, rendered 512px raster count as 4 requests. **HIGH** - OSRM demo-server usage policy + `Project-OSRM/osrm-backend` wiki — verified: demo is best-effort, forbidden >1 req/s, "not suitable for production" warning carried in LRM and OSM forum threads. **HIGH** (policy) / MEDIUM (updated world-extract size claims) - OSRM release feed — `Project-OSRM/osrm-backend` latest release `v26.10.0` (2026-10-01), CalVer. **HIGH** (version). - Valhalla release feed — `valhalla/valhalla` latest `3.9.1` (2026-10-06). **HIGH** (version). - Docker Hub `osrm/osrm-backend` tag listing (checked 2026-10-22) — image mirror stale (top tag v5.25, 2021); current OSRM releases must be pulled from GHCR. **HIGH** (staleness fact verified directly). - grammY official docs + npm — Workers deployment + webhook secret-token validation + conversations plugin; concrete Cloudflare adapters. **HIGH** (fit) / MEDIUM-HIGH (exact 1.46 code-verified API shape — verify at phase start) - Telegram-Mini-Apps issue tracker (#77, #964, #56, tma.js disc #675) — verified: Mini App geolocation is mobile-first, Desktop unsupported, Android stale-location bug open as of mid/late 2025. **MEDIUM** (community-confirmed bugs, official stance not yet changed) - Cloudflare Workers docs: "Migrate from Pages to Workers" (developers.cloudflare.com, updated 2026-10-12) + Developer-Week guidance — verified: all-new-projects → Workers with static assets; feature matrix favors Workers (gradual deployments, observability, cron). **HIGH** - Cloudflare Workers static-assets pricing note — assets requests are **free**; makes the whole cloud stack's marginal cost effectively zero at v1 scale. **HIGH** --- *Stack research for: araharah — Telegram bot + Mini App OSM navigation (KL metro)* *Researched: 2026-10-22*