ambek .planning

This commit is contained in:
araharah-executor
2026-10-23 22:37:55 +08:00
parent e512104d5e
commit 275879a18b
54 changed files with 9921 additions and 0 deletions
+262
View File
@@ -0,0 +1,262 @@
# 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*