wiki: Home — HTML-entity the confusable literals (²→·×) so Gitea's ambiguous-unicode banner clears; rendered text identical
+21
-21
@@ -1,10 +1,10 @@
|
|||||||
<!-- generated-by: gsd-doc-writer -->
|
<!-- generated-by: gsd-doc-writer -->
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="http://gitmirah:3000/c-fu/arah2/media/branch/main/assets/MASTER_FILE-arah2_logo.svg" alt="arah² logo" width="280">
|
<img src="http://gitmirah:3000/c-fu/arah2/media/branch/main/assets/MASTER_FILE-arah2_logo.svg" alt="arah² logo" width="280">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
# arah²
|
# arah²
|
||||||
|
|
||||||
A privacy-respecting, Telegram-first GPS navigator — a waze-like route navigator delivered through a Telegram bot and Mini App instead of an app-store install, built on OpenStreetMap data, and designed to Dieter Rams' "less, but better." First users are in the Kuala Lumpur metro.
|
A privacy-respecting, Telegram-first GPS navigator — a waze-like route navigator delivered through a Telegram bot and Mini App instead of an app-store install, built on OpenStreetMap data, and designed to Dieter Rams' "less, but better." First users are in the Kuala Lumpur metro.
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ The Telegram bot is **arah2bot**. The product is live — a private beta with re
|
|||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
arah² is an npm-workspaces monorepo with five packages:
|
arah² is an npm-workspaces monorepo with five packages:
|
||||||
|
|
||||||
| Package | Name | What it does |
|
| Package | Name | What it does |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
@@ -46,7 +46,7 @@ graph TD
|
|||||||
APP -->|"GET /api/location — Bearer token"| W
|
APP -->|"GET /api/location — Bearer token"| W
|
||||||
APP -->|"POST /api/route — Bearer token"| R2
|
APP -->|"POST /api/route — Bearer token"| R2
|
||||||
W --> KV
|
W --> KV
|
||||||
W -->|"cron */2 → turn cards"| TG
|
W -->|"cron */2 → turn cards"| TG
|
||||||
APP --> MAP
|
APP --> MAP
|
||||||
THEME -.->|"inlined at build time"| MAP
|
THEME -.->|"inlined at build time"| MAP
|
||||||
THEME -.->|"dist/rams.css"| APP
|
THEME -.->|"dist/rams.css"| APP
|
||||||
@@ -109,14 +109,14 @@ cp bot/.dev.vars.example bot/.dev.vars
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Quickstart: clone → production (your own bot, your own Cloudflare)
|
## Quickstart: clone → production (your own bot, your own Cloudflare)
|
||||||
|
|
||||||
The concise path. Full detail lives in [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md); local development in [docs/GETTING-STARTED.md](docs/GETTING-STARTED.md).
|
The concise path. Full detail lives in [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md); local development in [docs/GETTING-STARTED.md](docs/GETTING-STARTED.md).
|
||||||
|
|
||||||
**1. Create the bot** — in Telegram, open [@BotFather](https://t.me/BotFather):
|
**1. Create the bot** — in Telegram, open [@BotFather](https://t.me/BotFather):
|
||||||
- `/newbot` → pick a name + username → copy the **bot token**
|
- `/newbot` → pick a name + username → copy the **bot token**
|
||||||
- `/setuserpic` → upload `assets/logo_arah2.png` (the bot's avatar)
|
- `/setuserpic` → upload `assets/logo_arah2.png` (the bot's avatar)
|
||||||
- Bot Settings → **Main Mini App** → set your future production URL (you'll fill the real one in step 4; BotFather's app *name* is what Telegram shows in the mini-app header)
|
- Bot Settings → **Main Mini App** → set your future production URL (you'll fill the real one in step 4; BotFather's app *name* is what Telegram shows in the mini-app header)
|
||||||
|
|
||||||
**2. Clone + install**
|
**2. Clone + install**
|
||||||
|
|
||||||
@@ -139,8 +139,8 @@ MINI_APP_URL=https://<your-worker>.workers.dev
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx wrangler login # browser auth, once
|
npx wrangler login # browser auth, once
|
||||||
npm run build # theme tokens → web/dist
|
npm run build # theme tokens → web/dist
|
||||||
npm run deploy # theme → web → worker + assets + webhook + commands
|
npm run deploy # theme → web → worker + assets + webhook + commands
|
||||||
```
|
```
|
||||||
|
|
||||||
**5. Provision the road graph** (engine A's routing data — R2, also free):
|
**5. Provision the road graph** (engine A's routing data — R2, also free):
|
||||||
@@ -155,7 +155,7 @@ node routing/scripts/upload-road-graph.mjs # builds + uploads the KL graph, ro
|
|||||||
```bash
|
```bash
|
||||||
node bot/scripts/set-webhook.mjs # arms the webhook (+ /telegram/webhook path)
|
node bot/scripts/set-webhook.mjs # arms the webhook (+ /telegram/webhook path)
|
||||||
node bot/scripts/register-commands.mjs # /help /privacy /cari in every command scope
|
node bot/scripts/register-commands.mjs # /help /privacy /cari in every command scope
|
||||||
node bot/scripts/set-menu-button.mjs # the menu button opens the app ("arah²")
|
node bot/scripts/set-menu-button.mjs # the menu button opens the app ("arah²")
|
||||||
```
|
```
|
||||||
|
|
||||||
**7. Verify** — the canaries (read-backs, nothing trusted blindly):
|
**7. Verify** — the canaries (read-backs, nothing trusted blindly):
|
||||||
@@ -163,9 +163,9 @@ node bot/scripts/set-menu-button.mjs # the menu button opens the app ("arah
|
|||||||
```bash
|
```bash
|
||||||
npm run privacy-audit -- --live && npm run budget-audit -- --live
|
npm run privacy-audit -- --live && npm run budget-audit -- --live
|
||||||
```
|
```
|
||||||
Then in Telegram: restart the client → open the bot → `/cari KLCC` → venue card → **OPEN** → the map routes. Done: your own private navigator, $0/month.
|
Then in Telegram: restart the client → open the bot → `/cari KLCC` → venue card → **OPEN** → the map routes. Done: your own private navigator, $0/month.
|
||||||
|
|
||||||
> **Permission notes:** the Cloudflare token (optional, only for the audit `--live` probes) needs Account → Workers KV Storage → **Read** only. The bot itself needs no special Telegram permissions; `/setprivacy` default (off) is correct — the bot never reads non-command messages.
|
> **Permission notes:** the Cloudflare token (optional, only for the audit `--live` probes) needs Account → Workers KV Storage → **Read** only. The bot itself needs no special Telegram permissions; `/setprivacy` default (off) is correct — the bot never reads non-command messages.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -176,7 +176,7 @@ Then in Telegram: restart the client → open the bot → `/cari KLCC` → venue
|
|||||||
- **WASM-router track** — fully client-side routing in the webview (origin+destination never touch a server; static-hosted; the fork-perfect endgame)
|
- **WASM-router track** — fully client-side routing in the webview (origin+destination never touch a server; static-hosted; the fork-perfect endgame)
|
||||||
- **Battery measurement** — the <10%/hr guidance-drain record (deferred from launch)
|
- **Battery measurement** — the <10%/hr guidance-drain record (deferred from launch)
|
||||||
- **Bitmap map snapshots** in minimized chat turn-cards (needs a rasterizer in the $0 stack)
|
- **Bitmap map snapshots** in minimized chat turn-cards (needs a rasterizer in the $0 stack)
|
||||||
- **Voice guidance** · **live trip share** (`sendLocation(live_period)`) · **toll toggle** · **speed-limit display** — the REQUIREMENTS deferred table
|
- **Voice guidance** · **live trip share** (`sendLocation(live_period)`) · **toll toggle** · **speed-limit display** — the REQUIREMENTS deferred table
|
||||||
|
|
||||||
**v2 (post user-validation):**
|
**v2 (post user-validation):**
|
||||||
- Incident reports + community layer, gamification, traffic-weighted ETAs — per the original vision doc ([IDEAS.md](IDEAS.md))
|
- Incident reports + community layer, gamification, traffic-weighted ETAs — per the original vision doc ([IDEAS.md](IDEAS.md))
|
||||||
@@ -195,7 +195,7 @@ The bot answers in English and Bahasa Melayu. Commands are registered into Teleg
|
|||||||
|
|
||||||
| Command | Alias | Behaviour |
|
| Command | Alias | Behaviour |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `/start` | `/mula` | One-sentence introduction to arah² and its privacy posture. |
|
| `/start` | `/mula` | One-sentence introduction to arah² and its privacy posture. |
|
||||||
| `/help` | — | Lists the commands that exist and points at the known-limits record. |
|
| `/help` | — | Lists the commands that exist and points at the known-limits record. |
|
||||||
| `/privacy` | `/privasi` | Names exactly what is stored (user id, chat id, language, ~15 min) and exactly what is never stored (location, routes, trip history, profile). |
|
| `/privacy` | `/privasi` | Names exactly what is stored (user id, chat id, language, ~15 min) and exactly what is never stored (location, routes, trip history, profile). |
|
||||||
| `/cari` | `/search` | Searches the curated KL gazetteer (`/cari KLCC`) and replies with **one** native venue card (`sendVenue`) carrying an OPEN IN ARAHARAH Mini App button plus 2–4 alternates as callback buttons. Out-of-area queries get an honest refusal that names the v1 area, never the query text. |
|
| `/cari` | `/search` | Searches the curated KL gazetteer (`/cari KLCC`) and replies with **one** native venue card (`sendVenue`) carrying an OPEN IN ARAHARAH Mini App button plus 2–4 alternates as callback buttons. Out-of-area queries get an honest refusal that names the v1 area, never the query text. |
|
||||||
@@ -204,7 +204,7 @@ The bot answers in English and Bahasa Melayu. Commands are registered into Teleg
|
|||||||
|
|
||||||
1. **Search.** `/cari <place>` normalizes and scores the query against the KL gazetteer (`@araharah/search`); the best hit ships as a venue card with deep link `MINI_APP_URL/?dest=<gazetteer-id>`.
|
1. **Search.** `/cari <place>` normalizes and scores the query against the KL gazetteer (`@araharah/search`); the best hit ships as a venue card with deep link `MINI_APP_URL/?dest=<gazetteer-id>`.
|
||||||
2. **Open the Mini App.** The webview reads `?dest=` from `location.search`, preloads the destination, and mints a session by posting the verbatim `Telegram.WebApp.initData` string to `/api/session`.
|
2. **Open the Mini App.** The webview reads `?dest=` from `location.search`, preloads the destination, and mints a session by posting the verbatim `Telegram.WebApp.initData` string to `/api/session`.
|
||||||
3. **Acquire a starting point.** The three-link location chain (`web/src/location/chain.ts`) tries **Telegram → WebView → chat pin**, in that locked order.
|
3. **Acquire a starting point.** The three-link location chain (`web/src/location/chain.ts`) tries **Telegram → WebView → chat pin**, in that locked order.
|
||||||
4. **Route.** The app posts the origin and destination to `/api/route`; the Worker's router (`bot/src/routing/worker-router.ts`) loads the R2-hosted CSR graph once per cold isolate, snaps, and runs A* (`@araharah/routing`). Every miss class answers `200 null` — router-pending, never a wrong line.
|
4. **Route.** The app posts the origin and destination to `/api/route`; the Worker's router (`bot/src/routing/worker-router.ts`) loads the R2-hosted CSR graph once per cold isolate, snaps, and runs A* (`@araharah/routing`). Every miss class answers `200 null` — router-pending, never a wrong line.
|
||||||
5. **Guidance.** Accepting a route mints a guidance envelope (`POST /api/guidance`, 7200 s TTL); a scheduled cron (`*/2 * * * *`) projects progress along the frozen route snapshot and pushes due turn cards — a maneuver glyph photo with a live caption — into the chat, with a button back into the Mini App at `?g=<envelope-id>`.
|
5. **Guidance.** Accepting a route mints a guidance envelope (`POST /api/guidance`, 7200 s TTL); a scheduled cron (`*/2 * * * *`) projects progress along the frozen route snapshot and pushes due turn cards — a maneuver glyph photo with a live caption — into the chat, with a button back into the Mini App at `?g=<envelope-id>`.
|
||||||
|
|
||||||
@@ -224,16 +224,16 @@ The Worker's `fetch` handler routes exactly six paths and returns `404` for anyt
|
|||||||
```bash
|
```bash
|
||||||
# Mint a session from a Mini App initData string
|
# Mint a session from a Mini App initData string
|
||||||
curl -X POST http://localhost:8787/api/session --data-binary @initdata.txt
|
curl -X POST http://localhost:8787/api/session --data-binary @initdata.txt
|
||||||
# → {"token":"<opaque>"}
|
# → {"token":"<opaque>"}
|
||||||
|
|
||||||
# Poll the location relay with the token in memory
|
# Poll the location relay with the token in memory
|
||||||
curl -H "Authorization: Bearer <opaque>" http://localhost:8787/api/location
|
curl -H "Authorization: Bearer <opaque>" http://localhost:8787/api/location
|
||||||
# → {"pin":null} (honest null, not a fabricated coordinate)
|
# → {"pin":null} (honest null, not a fabricated coordinate)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Location acquisition in the Mini App
|
### Location acquisition in the Mini App
|
||||||
|
|
||||||
`web/src/location/chain.ts` is a three-link state machine with a locked order — **Telegram → WebView → chat pin** — and freshness is a separate axis from availability. A Telegram fix byte-identical to the last accepted one routes to the nudge, never to success; denial is a state, not an exception. The chain exposes a `Fix`-shaped abstraction with a pluggable cadence and hard-codes no interval, so continuous-mode polling is a later decision, not a rewrite. Past 45 seconds the strip flips to `SIGNAL LOST` and the last marker dims.
|
`web/src/location/chain.ts` is a three-link state machine with a locked order — **Telegram → WebView → chat pin** — and freshness is a separate axis from availability. A Telegram fix byte-identical to the last accepted one routes to the nudge, never to success; denial is a state, not an exception. The chain exposes a `Fix`-shaped abstraction with a pluggable cadence and hard-codes no interval, so continuous-mode polling is a later decision, not a rewrite. Past 45 seconds the strip flips to `SIGNAL LOST` and the last marker dims.
|
||||||
|
|
||||||
### Consuming the design tokens
|
### Consuming the design tokens
|
||||||
|
|
||||||
@@ -262,9 +262,9 @@ import ramsStyle from "@araharah/theme/basemap/liberty-rams.json";
|
|||||||
The Telemetry Rams design system lives in `theme/` as a Tailwind v4 CSS-first token package — the single source of truth for every color, spacing, typography, and radius value in the project. Consume the tokens by importing `theme/src/tokens.css` (plain CSS custom properties, usable by any stylesheet or the MapLibre basemap) or the compiled stylesheet produced by `npm run build`. The token vocabulary mirrors the mockup screens verbatim, so markup derived from them maps one to one. Rams philosophy lives on in `DESIGN.md`, which is superseded for specifics — values are never restated outside the token package.
|
The Telemetry Rams design system lives in `theme/` as a Tailwind v4 CSS-first token package — the single source of truth for every color, spacing, typography, and radius value in the project. Consume the tokens by importing `theme/src/tokens.css` (plain CSS custom properties, usable by any stylesheet or the MapLibre basemap) or the compiled stylesheet produced by `npm run build`. The token vocabulary mirrors the mockup screens verbatim, so markup derived from them maps one to one. Rams philosophy lives on in `DESIGN.md`, which is superseded for specifics — values are never restated outside the token package.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run build -w theme # src/theme.css → dist/rams.css (minified)
|
npm run build -w theme # src/theme.css → dist/rams.css (minified)
|
||||||
npm run sheet # build + print the sheet.html path
|
npm run sheet # build + print the sheet.html path
|
||||||
npm run gallery # build + screenshot → serve gallery.html and basemap on :8123
|
npm run gallery # build + screenshot → serve gallery.html and basemap on :8123
|
||||||
npm run audit # off-token audit of the rendered CSS — exits non-zero on violations
|
npm run audit # off-token audit of the rendered CSS — exits non-zero on violations
|
||||||
npm run capture # Playwright screenshots of the token sheet
|
npm run capture # Playwright screenshots of the token sheet
|
||||||
```
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user