docs: full docs-update pass — 9 GSD docs regenerated + verified (434→454 claims; verifier corrections applied)
This commit is contained in:
+98
-37
@@ -8,7 +8,8 @@ bot (**arah2bot**), its Cloudflare Worker, and the Mini App webview — in five
|
||||
For what each piece does, see [ARCHITECTURE.md](ARCHITECTURE.md). For the full list of
|
||||
environment variables, config files, and per-package scripts, see
|
||||
[CONFIGURATION.md](CONFIGURATION.md). For a package overview, see the root
|
||||
[README.md](../README.md).
|
||||
[README.md](../README.md). For what arah² honestly does **not** do yet, see
|
||||
[KNOWN-LIMITATIONS.md](KNOWN-LIMITATIONS.md).
|
||||
|
||||
---
|
||||
|
||||
@@ -16,21 +17,23 @@ environment variables, config files, and per-package scripts, see
|
||||
|
||||
| Requirement | Why | Notes |
|
||||
| --- | --- | --- |
|
||||
| **Node.js + npm** | Every build, dev, test, and deploy script in the repo is an npm script; all three workspaces are ESM (`"type": "module"`) | No `engines` field, `.nvmrc`, or `.node-version` exists anywhere in the repo. Use a current Node LTS. |
|
||||
| **Node.js + npm** | Every build, dev, test, and deploy script in the repo is an npm script; all five workspaces are ESM (`"type": "module"`) | No `engines` field, `.nvmrc`, or `.node-version` exists anywhere in the repo. Use a current Node LTS — the repo's development and all four test runners run on Node v24, and the search/routing corpus tooling relies on Node 24's native TypeScript type-stripping. |
|
||||
| A **Telegram account** | You chat with arah2bot and receive the Mini App webview | — |
|
||||
| A bot token from **[@BotFather](https://t.me/BotFather)** | `BOT_TOKEN` is required for the Worker and every registration script | Also used as the HMAC key that validates Mini App `initData` |
|
||||
| **Internet access** | `wrangler dev`, the Telegram Bot API, and OpenFreeMap tile fetches all need the network | — |
|
||||
| **A Cloudflare account** *(deploy and `npm run bot:commands` only)* | `wrangler deploy` and the Workers KV namespace live there | Wrangler prompts for browser-based auth on first use |
|
||||
| **A Cloudflare account** *(deploy and `npm run bot:commands` only)* | `wrangler deploy`, the Workers KV namespace, and the R2 road-graph bucket live there | Wrangler prompts for browser-based auth on first use |
|
||||
|
||||
<!-- VERIFY: minimum Node.js version — no `engines` field, `.nvmrc`, or `.node-version` file exists in the repository, so no supported floor is recorded. Confirm the intended version with the maintainers. -->
|
||||
<!-- VERIFY: minimum Node.js version — no `engines` field, `.nvmrc`, or `.node-version` file exists in the repository, so no supported floor is recorded. The repo is developed on Node v24.18.0; confirm the intended minimum with the maintainers. -->
|
||||
|
||||
Only required for specific commands, not for a normal first run:
|
||||
|
||||
- **Python 3** — `npm run gallery` serves the design gallery via `python3 -m http.server 8123`
|
||||
(`theme/package.json`, script `basemap:preview`).
|
||||
- **A Playwright-managed Chromium** — `npm run capture` and `npm run web:capture` both call
|
||||
bare `chromium.launch()` (`theme/scripts/screenshot.mjs:103`, `web/scripts/capture.mjs:163`), so
|
||||
bare `chromium.launch()` (`theme/scripts/screenshot.mjs:103`, `web/scripts/capture.mjs:201`), so
|
||||
they need `npx playwright install chromium` first. No system Chrome is used.
|
||||
- **`sharp`** (in `routing/`'s devDependencies) — only the `npm run glyphs -w routing` glyph-builder
|
||||
needs it; nothing else in a first run imports it. Install is still one `npm install` from the root.
|
||||
|
||||
There is no database to install, no Docker step, and no `.env` file to author — local secrets
|
||||
live only in `bot/.dev.vars` (created in step 3). See
|
||||
@@ -53,9 +56,10 @@ live only in `bot/.dev.vars` (created in step 3). See
|
||||
cd arah2
|
||||
```
|
||||
|
||||
3. **Install all three workspaces from the root.** npm workspaces hoist dependencies into a
|
||||
single root `node_modules`, so one install covers `theme`, `bot`, and `web` — including
|
||||
`@araharah/theme`, which `web` consumes by relative path and never needs to be published.
|
||||
3. **Install all five workspaces from the root.** npm workspaces hoist dependencies into a
|
||||
single root `node_modules`, so one install covers `theme`, `bot`, `web`, `search`, and
|
||||
`routing` — including `@araharah/theme`, which `web` consumes by relative path and never
|
||||
needs to be published.
|
||||
|
||||
```bash
|
||||
npm install
|
||||
@@ -144,7 +148,7 @@ and serves three things on **one origin, port `8787`**:
|
||||
Because it is the single origin, you can smoke-test the API without Telegram at all:
|
||||
|
||||
```bash
|
||||
# Unknown paths are 404 — the Worker's fetch handler routes exactly four paths.
|
||||
# Unknown paths are 404 — the Worker's fetch handler routes exactly six paths.
|
||||
curl -i http://localhost:8787/api/location
|
||||
# → 401 (no bearer token)
|
||||
|
||||
@@ -153,6 +157,13 @@ curl -i http://localhost:8787/api/location
|
||||
curl -i -X POST http://localhost:8787/telegram/webhook
|
||||
```
|
||||
|
||||
The turn-card cron can be fired by hand against the local server — wrangler's scheduled-event
|
||||
endpoint, no tunnel needed:
|
||||
|
||||
```bash
|
||||
curl "http://localhost:8787/cdn-cgi/local/scheduled"
|
||||
```
|
||||
|
||||
### 4. Expose the webhook
|
||||
|
||||
`wrangler dev` binds to localhost only, so Telegram cannot reach it. Expose port `8787` with a
|
||||
@@ -195,16 +206,18 @@ WEBHOOK_URL had no path — normalized to .../telegram/webhook
|
||||
|
||||
<!-- VERIFY: the recommended tunnel tooling is not pinned by the repository — no `cloudflared`, `ngrok`, or `localtunnel` dependency or config exists. Replace with whatever tunnel your team standardizes on, and record the choice here. -->
|
||||
|
||||
Register the persistent command menu (English + Bahasa Melayu) once, so `/help` and `/privacy`
|
||||
appear in the Telegram UI:
|
||||
Register the persistent command menu (English + Bahasa Melayu) once, so `/help`, `/privacy`,
|
||||
and `/cari` appear in the Telegram UI:
|
||||
|
||||
```bash
|
||||
npm run bot:commands
|
||||
```
|
||||
|
||||
`register-commands.mjs` registers exactly `["help", "privacy"]` in two scopes — the default scope
|
||||
plus `language_code: "ms"` — and deliberately reserves no stub entries for future commands, so
|
||||
`/start` is a working handler that does **not** appear in the menu (type it manually). The
|
||||
`register-commands.mjs` registers exactly `["help", "privacy", "cari"]` in four private-chat
|
||||
scopes — default (en), default+ms, all_private_chats (en), all_private_chats+ms — and clears the
|
||||
group scopes, because Telegram resolves a chat's menu from the *narrowest* scope that has any
|
||||
commands and a stale set shadows the default one. `/start` is a working handler that does **not**
|
||||
appear in the menu (type it manually); `/search` is `/cari`'s registered twin handler. The
|
||||
descriptions are derived from the `helpMenu` lines in `bot/src/i18n/strings.ts`, the same text
|
||||
`/help` replies with, so the menu and the reply cannot drift.
|
||||
|
||||
@@ -216,11 +229,13 @@ Open Telegram, find **arah2bot**, and send `/start`. The Worker receives the upd
|
||||
| Command | Alias | Behaviour |
|
||||
| --- | --- | --- |
|
||||
| `/start` | `/mula` | One-sentence introduction to arah² and its privacy posture |
|
||||
| `/help` | — | Lists the commands available in this phase |
|
||||
| `/help` | — | Lists the commands available and points at the known-limits record |
|
||||
| `/privacy` | `/privasi` | States exactly what is stored (~15 min) and what is never stored |
|
||||
| `/cari` | `/search` | Searches the KL gazetteer (`/cari KLCC`) and replies with one venue card carrying the OPEN IN ARAHARAH Mini App button plus 2–4 alternates |
|
||||
|
||||
The Mini App opens from the bot's menu button. The script that repoints it at the deployed origin
|
||||
(`type: web_app`) has **no npm alias** — run it directly:
|
||||
The Mini App opens from the venue card's OPEN button, or from the bot's menu button. The script
|
||||
that repoints the menu button at the deployed origin (`type: web_app`) has **no npm alias** —
|
||||
run it directly:
|
||||
|
||||
```bash
|
||||
node bot/scripts/set-menu-button.mjs
|
||||
@@ -243,28 +258,48 @@ There is no Worker behind it, so `web/src/main.ts` falls back to the mock Telegr
|
||||
|
||||
## Verify the setup
|
||||
|
||||
Run both suites and both typechecks — the runners are hand-rolled and zero-dependency
|
||||
(`node bot/test/runner.mjs` and `node web/test/runner.mjs`), so no test framework needs installing:
|
||||
Run the four test suites and the four typechecks — the runners are hand-rolled and
|
||||
zero-dependency (`node <workspace>/test/runner.mjs` each), so no test framework needs installing:
|
||||
|
||||
```bash
|
||||
npm run bot:test # 6 suites in bot/test/*.test.mjs
|
||||
npm run web:test # 6 suites in web/test/*.test.mjs
|
||||
npm run bot:typecheck # tsc --noEmit
|
||||
npm run web:typecheck # tsc --noEmit
|
||||
npm run bot:test # 10 suites in bot/test/*.test.mjs
|
||||
npm run web:test # 14 suites in web/test/*.test.mjs
|
||||
npm run search:test # 8 suites in search/test/*.test.mjs
|
||||
npm run routing:test # 9 suites in routing/test/*.test.mjs
|
||||
npm run bot:typecheck # tsc --noEmit
|
||||
npm run web:typecheck # tsc --noEmit
|
||||
npm run search:typecheck # tsc --noEmit
|
||||
npm run routing:typecheck # tsc --noEmit
|
||||
```
|
||||
|
||||
Each runner prints `PASS <name>` / `FAIL <name>` per case and exits `0` only when every case
|
||||
passed. If a suite fails to load, it is recorded as one failing case rather than crashing the run.
|
||||
(The search and routing counts include their golden-corpus suites, which measure the *committed*
|
||||
datasets offline — no network.)
|
||||
|
||||
The bot suites are `commands`, `initdata`, `location-pin`, `privacy`, `sessions`, and `webhook`;
|
||||
the web suites are `build-artifact`, `copy-gate`, `location-chain`, `no-storage`, `session`, and
|
||||
`theme`. Two are worth knowing about as you get started:
|
||||
The bot suites are `cari`, `commands`, `guidance-cron`, `guidance-envelope`, `initdata`,
|
||||
`location-pin`, `privacy`, `route-privacy`, `sessions`, and `webhook`; the web suites include
|
||||
`build-artifact`, `copy-gate`, `destination-preload`, `failure-state-matrix`, `fullscreen`,
|
||||
`guidance-eta`, `guidance-replay`, `guidance-surface`, `homescreen`, `location-chain`,
|
||||
`no-storage`, `route-surface`, `session`, and `theme`. Four are worth knowing about as you get
|
||||
started:
|
||||
|
||||
- **`no-storage`** pins that nothing in `web/src` uses `localStorage`, `sessionStorage`, cookies,
|
||||
IndexedDB, or the Cache API.
|
||||
- **`privacy`** greps that every `chat_sessions` write carries an explicit `expirationTtl`.
|
||||
- **`route-privacy`** pins the same category-only, claim-derived access control on `/api/route`.
|
||||
- **`guidance-*`** (bot and web) pin the envelope lifecycle, the cron card projection, and the
|
||||
Mini App's replay/ETA surfaces.
|
||||
|
||||
Optionally, prove the design tokens carry no off-token values:
|
||||
Optionally, prove the repo-wide privacy and budget invariants — these are the maintainer-time
|
||||
audits a fork must re-run before its first driver:
|
||||
|
||||
```bash
|
||||
npm run privacy-audit # storage, KV-TTL, category-only logs, host allowlist, bundle telemetry
|
||||
npm run budget-audit # Worker CPU, bundle sizes, KV write classes, golden corpora
|
||||
```
|
||||
|
||||
And prove the design tokens carry no off-token values:
|
||||
|
||||
```bash
|
||||
npm run audit
|
||||
@@ -284,10 +319,10 @@ typography, radius, shadow, or blur value not derived from `theme/src/tokens.css
|
||||
`npm run build` first, or just use `npm run dev` — it already builds.
|
||||
|
||||
**`[webhook] rejected: misconfigured` → `500 Server misconfigured`.**
|
||||
`WEBHOOK_SECRET` is missing or empty in `bot/.dev.vars`. The guard is at `bot/src/webhook.ts:65`
|
||||
and it fails closed deliberately: grammY's `compareSecretToken` silently accepts *every* request
|
||||
when the secret is `undefined`, so an unarmed secret must never reach it. Generate a new random
|
||||
string and put it in `WEBHOOK_SECRET`.
|
||||
`WEBHOOK_SECRET` is missing or empty in `bot/.dev.vars`. The fail-closed guard sits at the top of
|
||||
the `/telegram/webhook` branch in `bot/src/webhook.ts` and it fails closed deliberately: grammY's
|
||||
`compareSecretToken` silently accepts *every* request when the secret is `undefined`, so an
|
||||
unarmed secret must never reach it. Generate a new random string and put it in `WEBHOOK_SECRET`.
|
||||
|
||||
**`setWebhook ok:true` but arah2bot never responds.**
|
||||
Telegram accepted the URL, so it answered `ok` while delivering updates to a path that `404`s.
|
||||
@@ -295,6 +330,14 @@ This happens when `WEBHOOK_URL` is a bare workers.dev origin. `set-webhook.mjs`
|
||||
`/telegram/webhook` for you — if you registered the webhook by hand or via a different tool,
|
||||
re-run `node bot/scripts/set-webhook.mjs` and confirm the registered URL includes the path.
|
||||
|
||||
**`/cari` is in the code but missing from the Telegram command menu.**
|
||||
The bot-side bug that caused this was found and fixed on 2026-10-25 (`register-commands.mjs` sent
|
||||
the scope under the wrong parameter name); re-running `npm run bot:commands` now arms all
|
||||
private-chat scopes correctly. What can *still* bite: the Telegram client **caches** the menu, so
|
||||
a device that opened the bot before a fix may keep showing the stale menu until the app restarts.
|
||||
The workaround is always available — type `/cari` in the chat; the command itself has always
|
||||
worked.
|
||||
|
||||
**`Missing BOT_TOKEN, WEBHOOK_URL, WEBHOOK_SECRET` and exit code `1`.**
|
||||
`set-webhook.mjs` checks all three before any network call and never echoes values — only names.
|
||||
Confirm you ran it from a path where `bot/.dev.vars` resolves (`bot/.dev.vars` is read relative to
|
||||
@@ -306,6 +349,15 @@ Re-run the tunnel, update `WEBHOOK_URL`, re-run `node bot/scripts/set-webhook.mj
|
||||
`tunnel origin` in `.dev.vars` still matches the one currently printing in Terminal 2 — tunnels
|
||||
rotate their hostname on every restart.
|
||||
|
||||
**`/api/route` answers bare `null` for long trips.**
|
||||
That is the honest degradation, not a bug: route computation runs inside the Cloudflare Workers
|
||||
free plan's small per-request CPU allowance. Short KL trips (2–8 km) measure 2–8 ms and resolve;
|
||||
cross-metro trips (20–30 km) measured p95 38–93 ms against the same graph, beyond the free plan,
|
||||
so the app shows the router-pending card instead of freezing or drawing a wrong line. The committed
|
||||
upgrade lever (ALT landmark preprocessing) is a next-milestone item — see
|
||||
[KNOWN-LIMITATIONS.md](KNOWN-LIMITATIONS.md). Workers Paid was deliberately rejected: it breaks
|
||||
the project's $0 mandate.
|
||||
|
||||
**`chromium.launch()` fails — `npm run capture` or `npm run web:capture` errors on a missing browser.**
|
||||
Both scripts use bare `chromium.launch()` with no `executablePath` and no `channel`, so they need
|
||||
Playwright's own browser build:
|
||||
@@ -333,10 +385,15 @@ chains theme then web; the web build imports the compiled stylesheet, not the to
|
||||
|
||||
## Next steps
|
||||
|
||||
- **[ARCHITECTURE.md](ARCHITECTURE.md)** — how the Worker, KV namespace, Mini App, and design
|
||||
system fit together; the Worker routing table; the three-link location acquisition chain.
|
||||
- **[ARCHITECTURE.md](ARCHITECTURE.md)** — how the Worker, KV namespace, R2 graph, Mini App, and
|
||||
design system fit together; the Worker routing table; the three-link location acquisition chain.
|
||||
- **[CONFIGURATION.md](CONFIGURATION.md)** — every environment variable, `bot/wrangler.jsonc`
|
||||
and `web/vite.config.ts`, and the full command reference per workspace.
|
||||
- **[TESTING.md](TESTING.md)** — each suite's contract and how to write a new one.
|
||||
- **[KNOWN-LIMITATIONS.md](KNOWN-LIMITATIONS.md)** — stated plainly: no traffic awareness, no lane
|
||||
guidance, the cross-metro CPU degradation, the stale Telegram menu cache, and what is on the
|
||||
roadmap. Beta-quality caveats included (e.g. battery drain is a *planned* measurement before the
|
||||
next driver onboarding, not an estimate).
|
||||
- **[../README.md](../README.md)** — package overview, HTTP surface, design-system usage.
|
||||
- **[../DESIGN.md](../DESIGN.md)** — the Dieter Rams philosophy behind the token vocabulary.
|
||||
|
||||
@@ -347,8 +404,10 @@ chains theme then web; the web build imports the compiled stylesheet, not the to
|
||||
| `npm run dev` | Build, then `wrangler dev --port 8787 --local` |
|
||||
| `npm run build` | Build theme → Mini App (`theme/dist/rams.css` must exist first) |
|
||||
| `npm run web:dev` | Vite dev server on `5173` with a mock WebApp and no Worker |
|
||||
| `npm run bot:test` / `npm run web:test` | Zero-dependency test runners |
|
||||
| `npm run bot:typecheck` / `npm run web:typecheck` | `tsc --noEmit` |
|
||||
| `npm run bot:test` / `npm run web:test` / `npm run search:test` / `npm run routing:test` | Zero-dependency test runners |
|
||||
| `npm run bot:typecheck` / `npm run web:typecheck` / `npm run search:typecheck` / `npm run routing:typecheck` | `tsc --noEmit` |
|
||||
| `npm run privacy-audit` | Repo-wide privacy audit (add `--` and `--live` for the live KV/cache probes) |
|
||||
| `npm run budget-audit` | Free-tier budget audit, measured and printed |
|
||||
| `npm run audit` | Off-token audit of the rendered CSS; non-zero on violations |
|
||||
| `npm run bot:commands` | Register the persistent Telegram command menu (EN + MS) |
|
||||
| `npm run deploy` | Build, then `wrangler deploy` + `set-webhook.mjs` |
|
||||
@@ -361,6 +420,8 @@ carry. `vite.config.ts` calls this out as the reason the split exists.
|
||||
|
||||
`npm run deploy` publishes the Worker to Cloudflare and registers the webhook. It reads no local
|
||||
file for secrets — production values live as Cloudflare Workers secrets. See
|
||||
[CONFIGURATION.md](CONFIGURATION.md#per-environment-overrides).
|
||||
[CONFIGURATION.md](CONFIGURATION.md#per-environment-overrides). A fork must also provision its own
|
||||
R2 road-graph bucket (`npx wrangler r2 bucket create <name>` +
|
||||
`routing/scripts/upload-road-graph.mjs`) — the fork contract is [ROAD-GRAPH.md](ROAD-GRAPH.md).
|
||||
|
||||
<!-- VERIFY: which environment variables are set as Cloudflare Workers secrets via `wrangler secret put`, and under which Cloudflare account, cannot be determined from the repository. -->
|
||||
<!-- VERIFY: which environment variables are set as Cloudflare Workers secrets via `wrangler secret put`, and under which Cloudflare account, cannot be determined from the repository. -->
|
||||
|
||||
Reference in New Issue
Block a user