Files
2026-10-23 22:37:55 +08:00

263 lines
27 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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/<bot>?startapp=<handoff>")` 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*