add sume
This commit is contained in:
@@ -1,7 +1,221 @@
|
||||
# araharah
|
||||
<!-- generated-by: gsd-doc-writer -->
|
||||
|
||||
A privacy-respecting, Telegram-first GPS navigator — Rams-inspired instrument-panel design, OpenStreetMap-based routing, KL metro first.
|
||||
# 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.
|
||||
|
||||
The Telegram bot is **arah2bot**.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
arah² is an npm-workspaces monorepo with three packages:
|
||||
|
||||
| Package | Name | What it does |
|
||||
|---|---|---|
|
||||
| `theme/` | `@araharah/theme` | The Telemetry Rams design system as a Tailwind v4 CSS-first token package. Every color, spacing, type, and radius value in the project is defined once in `theme/src/tokens.css` and compiled to `theme/dist/rams.css`. Also ships the MapLibre basemap style `theme/basemap/liberty-rams.json` plus gallery/audit/screenshot tooling. |
|
||||
| `bot/` | `@araharah/bot` | A [grammY](https://grammy.dev) Telegram bot running on a Cloudflare Worker. It serves the `/telegram/webhook` route, four JSON API routes, keeps TTL-only chat sessions in Workers KV, and serves the Mini App's static assets through a wrangler assets binding. |
|
||||
| `web/` | `@araharah/web` | The Telegram Mini App: a MapLibre GL navigation shell built with Vite. Owns the three-source location acquisition chain, an in-memory session store, and the Telegram WebApp integrations (theme slots, BackButton, safe-area insets). |
|
||||
|
||||
The Worker is the single origin. It answers `/api/*` and `/telegram/*` itself (`run_worker_first`) and serves everything else out of `web/dist`.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
U["Telegram client — arah2bot chat + Mini App webview"]
|
||||
W["Cloudflare Worker — araharah-bot"]
|
||||
KV[("Workers KV — chat_sessions, TTL 900s")]
|
||||
TG["Telegram Bot API"]
|
||||
APP["Mini App assets — web/dist via ASSETS binding"]
|
||||
MAP["MapLibre GL — Rams basemap, OpenFreeMap tiles"]
|
||||
THEME["@araharah/theme — tokens.css + liberty-rams.json"]
|
||||
|
||||
U -->|"POST /telegram/webhook (secret-token header)"| TG
|
||||
TG --> W
|
||||
U -->|"GET Mini App"| APP
|
||||
APP --> W
|
||||
W -->|"POST /api/session, /api/location-fallback"| TG
|
||||
APP -->|"POST /api/session — initData HMAC"| W
|
||||
APP -->|"GET /api/location — Bearer token"| W
|
||||
W --> KV
|
||||
APP --> MAP
|
||||
THEME -.->|"inlined at build time"| MAP
|
||||
THEME -.->|"dist/rams.css"| APP
|
||||
|
||||
classDef telegram fill:#EB8008,stroke:#1A1A1A,stroke-width:2px,color:#1A1A1A
|
||||
classDef worker fill:#1A1A1A,stroke:#EB8008,stroke-width:2px,color:#F7F0E3
|
||||
classDef storage fill:#D9D2C6,stroke:#1A1A1A,stroke-width:2px,color:#1A1A1A
|
||||
classDef web fill:#F5EDE0,stroke:#736B1E,stroke-width:2px,color:#1E1B14
|
||||
class U,TG telegram
|
||||
class W worker
|
||||
class KV storage
|
||||
class APP,MAP,THEME web
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
Clone the repository and install workspace dependencies:
|
||||
|
||||
```bash
|
||||
git clone http://gitmirah:3000/c-fu/arah2.git
|
||||
cd arah2
|
||||
npm install
|
||||
```
|
||||
|
||||
Requires Node.js and npm (no version is pinned in the repo — use a current LTS). Wrangler handles Cloudflare authentication on first use via `wrangler dev` / `wrangler deploy`.
|
||||
|
||||
<!-- VERIFY: Node.js minimum version — no `engines` field or `.nvmrc` exists in the repository; confirm the supported floor with the maintainers. -->
|
||||
|
||||
For the bot's local secrets, copy the example file and fill it in:
|
||||
|
||||
```bash
|
||||
cp bot/.dev.vars.example bot/.dev.vars
|
||||
```
|
||||
|
||||
`bot/.dev.vars` is gitignored and must never be committed. It holds `BOT_TOKEN` (from [@BotFather](https://t.me/BotFather)), `WEBHOOK_SECRET`, and `WEBHOOK_URL` (full origin; `set-webhook.mjs` appends `/telegram/webhook` if only an origin is given).
|
||||
|
||||
---
|
||||
|
||||
## Quick start
|
||||
|
||||
1. **Build the design tokens and the Mini App.** `theme/dist/rams.css` is an input to the web build, so it must exist first.
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
2. **Run everything locally.** Builds the theme, then starts the Worker on port `8787` with `--local`, serving `/telegram/webhook`, the `/api/*` routes, and `web/dist`.
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
3. **Iterate on the Mini App alone** with Vite on port `5173` (`strictPort`), for map/UI work that doesn't need the bot:
|
||||
|
||||
```bash
|
||||
npm run web:dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### Talking to the bot
|
||||
|
||||
The bot answers in English and Bahasa Melayu. Commands are registered into Telegram's persistent menu by `npm run bot:commands`, which derives its descriptions from `bot/src/i18n/strings.ts` so the menu and the replies cannot drift.
|
||||
|
||||
| Command | Alias | Behaviour |
|
||||
|---|---|---|
|
||||
| `/start` | `/mula` | One-sentence introduction to arah² and its privacy posture. |
|
||||
| `/help` | — | Lists the commands that exist in this phase. |
|
||||
| `/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). |
|
||||
|
||||
### HTTP surface
|
||||
|
||||
The Worker's `fetch` handler routes exactly four paths and returns `404` for anything else. All rejections carry a category only — never a payload, a user id, or a timestamp.
|
||||
|
||||
| Method | Path | Purpose |
|
||||
|---|---|---|
|
||||
| `POST` | `/telegram/webhook` | grammY webhook adapter. Fails closed with `500` if `WEBHOOK_SECRET` is missing, then constant-time-compares `X-Telegram-Bot-Api-Secret-Token` before any bot processing or KV touch. |
|
||||
| `POST` | `/api/session` | The security gate. The client posts its verbatim `Telegram.WebApp.initData` string (capped at 4 KB, over-cap bodies are rejected `413` before parsing). An HMAC-validated, fresh identity receives a one-time opaque session token. |
|
||||
| `GET` | `/api/location` | Polled by the Mini App with its in-memory `Bearer` token. The KV key derives from the resolved claim's `chat_id`, never from a client-supplied id, so one user's token can never read another user's pin. Returns honest nulls; a missing or expired token is `401`. |
|
||||
| `POST` | `/api/location-fallback` | The "ask via chat" path when geolocation is denied. The bot sends a `request_location` reply keyboard into the user's own chat. Target chat id and locale come from the resolved claim — the request body is never read. A rejected Telegram send is a category-only `502`. |
|
||||
|
||||
```bash
|
||||
# Mint a session from a Mini App initData string
|
||||
curl -X POST http://localhost:8787/api/session --data-binary @initdata.txt
|
||||
# → {"token":"<opaque>"}
|
||||
|
||||
# Poll the location relay with the token in memory
|
||||
curl -H "Authorization: Bearer <opaque>" http://localhost:8787/api/location
|
||||
# → {"pin":null} (honest null, not a fabricated coordinate)
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
### Consuming the design tokens
|
||||
|
||||
`theme/src/tokens.css` is the single source of truth — 108 custom properties in the `@theme static` block, declared `static` so Tailwind v4's tree-shaking cannot drop tokens the MapLibre basemap derivation and plain-CSS consumers need.
|
||||
|
||||
```css
|
||||
/* raw token access — any stylesheet, no Tailwind required */
|
||||
@import "@araharah/theme/dist/rams.css";
|
||||
|
||||
.instrument {
|
||||
background: var(--color-signal-orange);
|
||||
color: var(--color-matte-black);
|
||||
padding: var(--spacing-space-md);
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
// build-time inlining of the Rams basemap style (never re-set at runtime)
|
||||
import ramsStyle from "@araharah/theme/basemap/liberty-rams.json";
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Design system
|
||||
|
||||
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
|
||||
npm run build -w theme # src/theme.css → dist/rams.css (minified)
|
||||
npm run sheet # build + print the sheet.html path
|
||||
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 capture # Playwright screenshots of the token sheet
|
||||
```
|
||||
|
||||
The audit script treats "zero off-token values" as evidence rather than an eyeball claim: it scans rendered CSS and flags any color, spacing, typography, radius, shadow, or blur value that does not derive from `tokens.css`. Fonts (Geist, Space Grotesk) are self-hosted from `theme/fonts/` — both SIL Open Font License 1.1, no Google Fonts CDN.
|
||||
|
||||
---
|
||||
|
||||
## Commands
|
||||
|
||||
Run from the repo root, or with `-w <workspace>`.
|
||||
|
||||
| Command | Description |
|
||||
|---|---|
|
||||
| `npm run build` | Build the theme, then the Mini App |
|
||||
| `npm run dev` | Build, then run the Worker locally on port `8787` |
|
||||
| `npm run deploy` | Build, then `wrangler deploy` + `set-webhook.mjs` |
|
||||
| `npm run bot:test` / `npm run web:test` | Run each package's zero-dependency test suite |
|
||||
| `npm run bot:typecheck` / `npm run web:typecheck` | `tsc --noEmit` |
|
||||
| `npm run bot:commands` | Register the persistent Telegram command menu (EN + MS) |
|
||||
| `npm run web:capture` | Capture Mini App screenshots |
|
||||
| `npm run web:build:harness` | Dual build into `dist-harness/` with the mock installer and fetch stubs |
|
||||
| `npm run sheet` / `gallery` / `audit` / `capture` | Design-token tooling (see above) |
|
||||
|
||||
The two build outputs must never share an `outDir` — the harness build emits mock fetch stubs that production must not carry.
|
||||
|
||||
---
|
||||
|
||||
## Privacy
|
||||
|
||||
Privacy is structural, not a policy page:
|
||||
|
||||
- Every write to the `chat_sessions` KV namespace carries an explicit `expirationTtl` on its call line; the storage test suite greps for this.
|
||||
- The persisted claim is exactly four fields — user id, chat id, issued-at, language code — under a 900-second TTL. Minting rotates any prior token for the chat.
|
||||
- The Mini App holds its session token in a module-scope closure. There is no `localStorage`, `sessionStorage`, cookie, IndexedDB, or Cache API anywhere in `web/src` — the `no-storage` test suite pins this.
|
||||
- Nothing durable, nothing reconstructable. A token cannot outlive the 15-minute initData freshness proof that minted it.
|
||||
|
||||
## Repository layout
|
||||
|
||||
```
|
||||
theme/ @araharah/theme — tokens.css, components.css, theme.css, fonts/, basemap/, scripts/
|
||||
bot/ @araharah/bot — src/webhook.ts entry, bot/, kv/, security/, i18n/, scripts/, wrangler.jsonc
|
||||
web/ @araharah/web — src/main.ts entry, location/, map/, session/, telegram/, ui/, mock/
|
||||
stitch_minimalist_route_navigator/ read-only mockup screens (class-vocabulary source for the theme build)
|
||||
DESIGN.md Rams philosophy — superseded for any specific value
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Private repository. No license file is present; all rights reserved by the project owner.
|
||||
|
||||
<!-- VERIFY: repository licensing — no `LICENSE` file exists in the working tree, and the project is marked `private: true` in `package.json`. -->
|
||||
Reference in New Issue
Block a user