Contributing to arah²
arah² (the arah² repository) is a privacy-respecting, Telegram-first GPS navigator
for the Kuala Lumpur metro — a grammY bot + Cloudflare Worker, a Vite/MapLibre Mini App,
a design-token package, a curated KL gazetteer, and the routing engine behind the
turn-by-turn guidance. Code is GPL-3.0; the map-derived data files are ODbL-1.0.
This file tells you what belongs in the project, how to set up, and — most importantly — which gates a change must pass before a maintainer will look at it. The repo has no CI, so you are the CI.
The charter — what belongs here, and what never will
arah²'s core value: a user finds a local destination via Telegram and gets there with
reliable turn-by-turn guidance — tracked by nothing beyond what they explicitly opted in
to. The project charter (.planning/PROJECT.md, tracked in this repo) records the
scope boundaries and, crucially, why each exclusion was made, so they don't get
re-litigated every milestone.
In scope (v1): the Telegram bot command surface, destination search over the curated KL gazetteer, the Mini App webview, route display, live turn-by-turn guidance with hybrid chat turn-cards, KL-metro tuning, and the Telemetry Rams design system.
Out of scope — the deferred-ideas law (from .planning/PROJECT.md "Out of Scope"):
- Incident reports (
/accident,/traffic,/hazard+ broadcasts) — navigation first; reports ship after the core works. v1 reports nothing. - Gamification, points, leaderboards — depends on reports; deferred with them.
- Regional clusters via Telegram groups — unvalidated social hypothesis.
- Voice-to-text reporting, offline mode, anonymized public API — future ideas.
- Push notifications beyond Telegram chat (Resend email etc.) — Telegram itself is the channel.
- Independent mobile apps — the Mini App webview flow is the product.
- Any paid-tier infrastructure. The $0 law is a product constraint, not an aspiration — see docs/BUDGETS.md. IDEAS.md's RabbitMQ, Prometheus/Grafana, Neon, and Resend entries are aspirational; do not wire them in.
If your proposal touches any of these, the bar is high: it needs a new charter decision,
not just a PR. Architecture seams for the deferred work exist deliberately (e.g. the
SearchSource interface), but the seams stay unused.
Development setup
Read docs/GETTING-STARTED.md for prerequisites and first-run instructions, and docs/DEVELOPMENT.md for the full local development setup. The short version:
-
Clone and install — npm workspaces install all five packages at once:
git clone http://gitmirah:3000/c-fu/arah2.git cd arah2 npm install -
Copy
bot/.dev.vars.exampletobot/.dev.varsand fill inBOT_TOKEN,WEBHOOK_SECRET, andWEBHOOK_URL(gitignored; never commit it). -
npm run build— theme tokens first, then the web bundle (the theme build is an input to the web build). -
npm run dev— Worker on http://localhost:8787.
There is no linter, formatter, or commit-hook setup to install — see Code standards below.
Code standards
- No ESLint, Prettier, Biome, or
.editorconfigexists. There is nolintorformatscript in any workspace and no CI to enforce one. Style is a review-time convention; the mechanically enforced gates are listed in the next section. Match the existing source: explicit.tsextensions on relative imports,node:-prefixed builtins,.mjsfor scripts/tests, heavy header comments stating what a file is and which decision IDs it satisfies. - TypeScript strictness is real: both
bot/tsconfig.jsonandweb/tsconfig.jsonusestrict+noUncheckedIndexedAccess+verbatimModuleSyntax;search/androuting/follow the same shape.tsc --noEmit(npm run *:typecheck) is the gate. - Design tokens: never hardcode a color, spacing, font, or radius that belongs to a
token.
theme/src/tokens.cssis the single source of truth;npm run auditflags off-token values in rendered CSS. Never editstitch_minimalist_route_navigator/— the mockups are read-only reference. - All user-facing copy lives in the i18n strings files (
bot/src/i18n/strings.ts,web/src/i18n/strings.ts) withen/mslocale parity (gateCOPY1). No inline copy in shell modules (gateCOPY5).
The gates — what must be green before review
There is no CI pipeline (no .github/, no .gitea/ workflows). Run these by hand
before opening a change, and paste the output into your PR:
npm run build # FIRST — web:test's BA cases read web/dist
npm run bot:test # bot/test/runner.mjs
npm run web:test # web/test/runner.mjs
npm run search:test # search/test/runner.mjs
npm run routing:test # routing/test/runner.mjs
npm test -w theme # theme/scripts/audit.test.mjs (no root passthrough)
npm run bot:typecheck
npm run web:typecheck
npm run search:typecheck
npm run routing:typecheck
npm run audit # off-token CSS audit — only if you touched theme/ or rendered CSS
Order matters in one place: npm run build before npm run web:test, or the
build-artifact gates (BA1–BA6) fail with a "build first" message.
The repo's law enforcement: the two audit CLIs
Two maintainer-time tools (nothing in any src tree imports them) enforce the project's
non-negotiable laws. A PR that makes either one fail is a regression, full stop.
npm run privacy-audit(docs/PRIVACY-AUDIT.md,scripts/privacy-audit.mjs+scripts/lib/) — the privacy law:- TTL-only storage: every KV
.put(must carryexpirationTtlon the same line, referencing a namedSCREAMING_SNAKEconstant (thekv-ttlgate; PG1 is the suite-level twin). The four KV families are the complete storage surface. - Category-only logs: no log line may carry a coordinate, chat id, user id,
token, initData payload, geometry, or serialized payload (the
log-vocabularygate). - Zero telemetry: no analytics, beacons, tracking pixels, third-party CDN, or
error-reporting host — in source and in the shipped
web/distbundle (storage-api,runtime-hosts,bundle-telemetrygates). v1 has nothing to opt into.
- TTL-only storage: every KV
npm run budget-audit(docs/BUDGETS.md,scripts/budget-audit.mjs+scripts/lib/budget-gates.mjs) — the $0 law: bundle sizes, KV write classes (the cron tick writes zero — source-proven), subrequest inventories, and the golden corpora, all re-derived against the Cloudflare free-plan ceilings. If you add a route class or a KV write path, its inventory row must follow the code.
Both audits are GPL-3.0 and reusable by forks; rerun them over your own tree before your
first driver. The --live legs (KV expiry, cache headers) need Cloudflare credentials
and are maintainer-time.
The capture harness
For any Mini App UI change, npm run web:capture (Playwright, against the harness build
in web/dist-harness/) is the verification surface: screenshot legs over every
boot/route/guidance state, a DSGN-02 side-by-side against the committed mockup, and the
GEO-03 runtime storage probe that fails if any app-controlled storage surface is
non-empty after boot-to-live. Include the relevant captures in your PR. Outputs are
gitignored; regenerate, never commit.
Branch and commit conventions
-
The active branch is
main. Work lands there via atomic commits; if you prefer a branch,feat/,fix/, anddocs/prefixes match the commit vocabulary. No branch-protection or required-reviewer config exists. -
Commit messages are conventional-commit style with a phase-scoped type:
type(phase): subject— typesfeat,fix,docs,chore,test; the scope is a phase identifier (e.g.07-06). Examples fromgit log:fix(07-06): launch-gate decisions — KV cron retune to 720/day (the $0 law holds) test(07-06): RED — SA6 doc-pointer static-text class vector (URL literals only)Subject is lowercase, imperative, and carries an em-dash clause explaining the why or the observable failure. Reference decision IDs (
D-09,PRIV-02,GEO-03) in parentheses when relevant. One concern per commit.
Pull requests
There is no PR template and no configured review process (this is a private Gitea
repo — git remote -v points at http://gitmirah:3000/c-fu/arah2.git). If you open a
PR there, reviewers look for:
- All gates green, pasted. The full list from the gates
above, plus
npm run privacy-auditandnpm run budget-auditif you touched any storage, logging, network, bundle, or KV surface — i.e. if in doubt, run both audits. - The observable before/after, stated like a commit subject: what was broken, what it does now.
- Gate-relevant surface called out explicitly. Touched a KV write? Say which
expirationTtland which gate re-verified it. Touchedweb/src?GEO3/COPY1/COPY5re-run. Touched the command surface?CS4/CS6re-run. - Screenshots for Mini App changes (from
npm run web:build:harness && npm run web:capture). - No generated artifacts committed.
theme/dist,web/dist,web/dist-harness, and allscreenshots/directories are gitignored by design — always rebuild. In particular, committedtheme/distwould go stale because the web build consumes it at build time without vendoring.
Licensing your contribution
- Code is GPL-3.0. The full text is in LICENSE, and GPL-3.0 is a
deliberate project law (decision D-04, per LICENSES.md): a fork must
be able to read, change, and redistribute the whole working system. There is no CLA
and no DCO configured — no
Signed-off-bylines exist in the commit history, and no sign-off mechanism is set up. By submitting a PR you agree your code contribution is licensed under GPL-3.0 alongside the rest of the project. If you add a new source file, a copyright header is welcome but not required. - Map-derived data is ODbL-1.0, not GPL. The curated gazetteer
(
search/src/data/kl-gazetteer.json), the route fixtures (routing/src/data/route-fixtures.json), and the derived road graph are all curated or derived from OpenStreetMap data — © OpenStreetMap contributors, ODbL 1.0 (https://opendatacommons.org/licenses/odbl/1-0/). The attribution clause travels with derived databases, so:- Never remove the
_license/_attribution/_noticeenvelope fields from a data file. - Any new data file derived from OSM must carry the same envelope.
- Any restructured/derived index of the gazetteer carries the same notice (ODbL §4.4 share-alike for Derivative Databases). Code and data are separately licensed — ODbL §2.3(a) excludes computer programs.
- See docs/ROAD-GRAPH.md for the road-graph licensing surface.
- Never remove the
Reporting issues and proposing ideas
There are no issue templates (no .github/ISSUE_TEMPLATE/). The repository lives on a
private Gitea instance at http://gitmirah:3000/c-fu/arah2 — use its issue tracker if
you have access.
For a bug report, include:
- Steps to reproduce, expected vs actual behavior, and environment (which Telegram
client surface —
?mock=value if you used the harness — and OS). - The failing gate output, verbatim: the runners and audits print evidence strings that are meant to be quoted, never paraphrased.
For a feature proposal, first check the charter above: if it's in the deferred list,
argue why the deferral no longer holds, and expect that discussion to happen at the
charter level (.planning/PROJECT.md), not in the PR.
Where decisions live. Project-level decisions are recorded in .planning/PROJECT.md
(charter, key decisions), .planning/ROADMAP.md, and per-phase documents under
.planning/phases/ — all tracked in the repo. The checked-in evidence records are the
docs/ files: PRIVACY-AUDIT.md (the signed privacy audit),
BUDGETS.md (the measured budget record),
SEARCH-QUALITY.md and ROAD-GRAPH.md
(validation records), and KNOWN-LIMITATIONS.md. If a
reviewer asks "where was this decided?", the answer is one of those files.
arah²
Packages
Repo
- Source tree
- API — API
- Deployment — Deployment
- Contributing — Contributing
- Core concepts — Core-Concepts