add sume
This commit is contained in:
@@ -0,0 +1,462 @@
|
||||
<!-- generated-by: gsd-doc-writer -->
|
||||
# arah² — Development Guide
|
||||
|
||||
Contributor-facing reference for working on arah² day to day: local setup, the
|
||||
per-workspace dev loop, the build commands, the code style the project actually
|
||||
enforces, branch and commit conventions, and the project-specific gates you must
|
||||
not break.
|
||||
|
||||
For *using* the app, see the [README](../README.md). For *how the system is put
|
||||
together*, see [ARCHITECTURE.md](./ARCHITECTURE.md). For *what to configure*
|
||||
(secrets, `wrangler.jsonc`, env vars), see [CONFIGURATION.md](./CONFIGURATION.md).
|
||||
|
||||
---
|
||||
|
||||
## Local setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
| Requirement | Notes |
|
||||
| --- | --- |
|
||||
| Node.js + npm | No `engines` field and no `.nvmrc`/`.node-version` exist in the repo. The README says to use a current Node.js LTS. <!-- VERIFY: the actual minimum Node.js version — nothing in the repository pins it; confirm with maintainers. --> |
|
||||
| npm with workspace support | npm 7+ required for `workspaces`. The repo uses npm exclusively — `package-lock.json` is committed and there is no `pnpm-lock.yaml` or `yarn.lock`. |
|
||||
| A Cloudflare account | `wrangler` authenticates on first use via an interactive browser login. Needed for `bot:dev`, `bot:deploy`, and KV access. |
|
||||
| A Telegram bot token | From @BotFather. Required by `bot:dev` and by every script in `bot/scripts/`. |
|
||||
| Python 3 | Only for `theme`'s `gallery` / `basemap:preview`, which serve on port `8123` via `python3 -m http.server 8123`. |
|
||||
| Network access | Fonts, tiles, and the Play CDN in the mockup reference pages are all fetched over the network; the capture harnesses need it. |
|
||||
|
||||
### Clone and install
|
||||
|
||||
```bash
|
||||
git clone http://gitmirah:3000/c-fu/arah2.git
|
||||
cd arah2
|
||||
npm install
|
||||
```
|
||||
|
||||
`npm install` at the root installs all three workspaces (`theme`, `bot`, `web`) at
|
||||
once — npm hoists shared deps into the root `node_modules/`. There is no
|
||||
per-workspace `npm install` step.
|
||||
|
||||
### Configure local secrets
|
||||
|
||||
There is no committed `.env`. The Worker reads secrets from `bot/.dev.vars`
|
||||
(gitignored via `bot/.gitignore`):
|
||||
|
||||
```bash
|
||||
cp bot/.dev.vars.example bot/.dev.vars
|
||||
# then edit bot/.dev.vars and fill in BOT_TOKEN, WEBHOOK_SECRET, WEBHOOK_URL
|
||||
```
|
||||
|
||||
Full details, including the `process.env` vs `.dev.vars` precedence rule, are in
|
||||
[CONFIGURATION.md](./CONFIGURATION.md#environment-variables).
|
||||
|
||||
### Pre-build before first run
|
||||
|
||||
Both `npm run dev` and `npm run deploy` run the build first, but building by hand
|
||||
first gives you faster feedback:
|
||||
|
||||
```bash
|
||||
npm run build # theme -> web (this is the deployable artifact)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## The dev loop
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
classDef edit fill:#EB8008,stroke:#1A1A1A,stroke-width:2px,color:#1A1A1A
|
||||
classDef check fill:#D9D2C6,stroke:#1A1A1A,stroke-width:2px,color:#1A1A1A
|
||||
classDef gate fill:#ED3F1C,stroke:#1A1A1A,stroke-width:2px,color:#FFFFFF
|
||||
|
||||
EDIT["Edit source<br/>bot/src, web/src, theme/src"]:::edit
|
||||
TC["npm run bot:typecheck<br/>npm run web:typecheck<br/>tsc --noEmit"]:::check
|
||||
T1["npm run bot:test<br/>node test/runner.mjs"]:::check
|
||||
T2["npm run web:test<br/>node test/runner.mjs"]:::check
|
||||
AU["npm run audit<br/>off-token CSS audit"]:::check
|
||||
BUILD["npm run build<br/>theme then web"]:::check
|
||||
DEV1["npm run bot:dev<br/>wrangler dev :8787"]:::edit
|
||||
DEV2["npm run web:dev<br/>vite :5173"]:::edit
|
||||
HARN["npm run web:build:harness<br/>+ npm run web:capture"]:::check
|
||||
COMMIT["git commit<br/>type(phase): subject"]:::gate
|
||||
|
||||
EDIT --> TC
|
||||
EDIT --> T1
|
||||
EDIT --> T2
|
||||
EDIT --> AU
|
||||
TC --> BUILD
|
||||
T1 --> BUILD
|
||||
T2 --> BUILD
|
||||
AU --> BUILD
|
||||
BUILD --> DEV1
|
||||
BUILD --> DEV2
|
||||
BUILD --> HARN
|
||||
DEV1 --> COMMIT
|
||||
DEV2 --> COMMIT
|
||||
HARN --> COMMIT
|
||||
COMMIT --> EDIT
|
||||
```
|
||||
|
||||
### Bot workspace (`@araharah/bot`)
|
||||
|
||||
```bash
|
||||
npm run bot:dev # wrangler dev --port 8787 --local
|
||||
```
|
||||
|
||||
Runs the Worker on <http://localhost:8787>. `--local` keeps everything on
|
||||
Miniflare: KV is emulated under `bot/.wrangler/` (gitignored) and nothing touches
|
||||
the production namespace. Secrets come from `bot/.dev.vars`.
|
||||
|
||||
The Worker serves both the API and the Mini App static assets, so after changing
|
||||
anything under `web/` you must rebuild for `wrangler dev` to serve the new
|
||||
bundle:
|
||||
|
||||
```bash
|
||||
npm run build && npm run bot:dev
|
||||
```
|
||||
|
||||
### Web workspace (`@araharah/web`)
|
||||
|
||||
```bash
|
||||
npm run web:dev # vite dev server on http://localhost:5173
|
||||
```
|
||||
|
||||
Vite is configured with `port: 5173` and `strictPort: true`, so it will fail
|
||||
rather than silently pick 5174.
|
||||
|
||||
Important: **there is no Worker behind the Vite dev server.** `main.ts` calls
|
||||
relative paths (`/api/session`, `/api/location`), which 404 against plain Vite.
|
||||
For interactive work against a real API, either run `bot:dev` and open
|
||||
<http://localhost:8787> (the Worker serves the built bundle), or use the harness
|
||||
below. In `import.meta.env.DEV`, `main.ts` falls back to the mock Telegram
|
||||
WebApp so the shell still renders.
|
||||
|
||||
### Harness (Mock-Telegram) workflow
|
||||
|
||||
The Mini App's UI can be driven without Telegram at all. The harness build swaps
|
||||
in `web/src/mock/telegram-mock.ts` plus `/api/*` fetch stubs:
|
||||
|
||||
```bash
|
||||
npm run web:build:harness # vite build --mode harness -> web/dist-harness/
|
||||
npm run web:capture # Playwright screenshots of every boot/live/denied state
|
||||
```
|
||||
|
||||
`npm run web:capture` serves `web/dist-harness` (never `web/dist` — the harness
|
||||
block is dead-code-eliminated from the production build) and writes
|
||||
`web/screenshots/*.png` (gitignored). It waits on the `RAMS-BASEMAP-READY` and
|
||||
`ARAHARAH-LIVE` console signals and runs the GEO-03 runtime storage probe in-page
|
||||
before declaring success.
|
||||
|
||||
Harness URL parameters, documented in `web/index.html`:
|
||||
|
||||
| Parameter | Values | Effect |
|
||||
| --- | --- | --- |
|
||||
| `?mock=` | `android`, `ios`, `tdesktop`, `macos`, `web` | Which Telegram client surface to simulate |
|
||||
| `?scheme=` | `light`, `dark` | Telegram color scheme |
|
||||
| `?pin=` | `1` | Drive the chat-pin relay link (`?pin=1` stubs `/api/location`) |
|
||||
| `?locale=` | `en`, `ms` | Force a locale |
|
||||
| `?sessiondelay=` | milliseconds | Delay the session handshake |
|
||||
| `?fixdelay=` | milliseconds | Delay the location fix |
|
||||
|
||||
### Theme workspace (`@araharah/theme`)
|
||||
|
||||
```bash
|
||||
npm run build -w theme # src/theme.css -> dist/rams.css (minified)
|
||||
npm run sheet # build, then open theme/src/sheet.html
|
||||
npm run gallery # build + capture, then serve gallery.html + basemap on :8123
|
||||
npm run audit # off-token audit of the rendered CSS
|
||||
```
|
||||
|
||||
`npm run audit` and `npm run capture` are the two pieces of design-system
|
||||
evidence tooling. `capture` renders `sheet.html` and the four read-only mockups
|
||||
via Playwright and writes `theme/screenshots/` (gitignored). `gallery` blocks on
|
||||
`python3 -m http.server 8123` serving the theme workspace, so stop it with
|
||||
`Ctrl-C` when done.
|
||||
|
||||
Note that `npm run gallery` ends with a **blocking** `basemap:preview` server —
|
||||
it will appear to hang. That is expected.
|
||||
|
||||
### Registering bot commands
|
||||
|
||||
The Telegram `/` menu is persistent server state, not something the Worker pushes.
|
||||
Register it explicitly after adding or renaming a command:
|
||||
|
||||
```bash
|
||||
npm run bot:commands # node scripts/register-commands.mjs
|
||||
```
|
||||
|
||||
`bot/scripts/register-commands.mjs` reads `BOT_TOKEN` (from `process.env`, else
|
||||
from `bot/.dev.vars`), derives each description from the `helpMenu` lines in
|
||||
`bot/src/i18n/strings.ts`, and makes exactly two `setMyCommands` calls — one for
|
||||
the default scope (English) and one for `language_code: "ms"` (Malay). Because
|
||||
the descriptions are *derived* from the same strings `/help` replies with, the
|
||||
menu and the reply cannot drift.
|
||||
|
||||
If you add a command, you must add it to the `["help", "privacy"]` array in
|
||||
`menuFor()` **and** to the corresponding `helpMenu` lines — the script throws
|
||||
`helpMenu for "<locale>" is missing its /<command> line (D-03 copy drift)` if the
|
||||
two disagree. The gate `CS5 D-03: register-commands derives both scopes from
|
||||
helpMenu` in `bot/test/commands.test.mjs` enforces this.
|
||||
|
||||
Only re-run this when the command *list or its descriptions* change. Repointing
|
||||
the webhook is a separate script (`set-webhook.mjs`), run automatically by
|
||||
`npm run deploy`.
|
||||
|
||||
---
|
||||
|
||||
## Build and check commands
|
||||
|
||||
### Root scripts (`package.json`)
|
||||
|
||||
| Command | Description |
|
||||
| --- | --- |
|
||||
| `npm run build` | Build `theme`, then `web`. Produces the deployable artifact. |
|
||||
| `npm run dev` | `build`, then `bot:dev` — the one-command local app loop. |
|
||||
| `npm run deploy` | `build`, then `bot:deploy` (`wrangler deploy` + `set-webhook.mjs`). |
|
||||
| `npm run bot:test` | `npm test -w bot` — the bot's hand-rolled suite. |
|
||||
| `npm run bot:typecheck` | `tsc --noEmit` in `bot`. |
|
||||
| `npm run bot:dev` | `npm run dev -w bot` — `wrangler dev --port 8787 --local`. |
|
||||
| `npm run bot:deploy` | `wrangler deploy && node scripts/set-webhook.mjs`. |
|
||||
| `npm run bot:commands` | Register the Telegram command menu (EN + MS). |
|
||||
| `npm run web:test` | `npm test -w web` — the Mini App suite. |
|
||||
| `npm run web:typecheck` | `tsc --noEmit` in `web`. |
|
||||
| `npm run web:dev` | `npm run dev -w web` — Vite dev server on 5173. |
|
||||
| `npm run web:build` | Alias of `build` (theme then web). |
|
||||
| `npm run web:build:harness` | Harness-mode build into `web/dist-harness/`. |
|
||||
| `npm run web:capture` | Playwright screenshots of the Mini App states. |
|
||||
| `npm run sheet` | Passthrough to the theme workspace. |
|
||||
| `npm run gallery` | Passthrough to the theme workspace. |
|
||||
| `npm run audit` | Passthrough to the theme workspace — off-token CSS audit. |
|
||||
| `npm run capture` | Passthrough to the theme workspace — token-sheet screenshots. |
|
||||
|
||||
### Workspace-local scripts
|
||||
|
||||
| Command | Where | Description |
|
||||
| --- | --- | --- |
|
||||
| `npm test` | `bot`, `web` | `node test/runner.mjs` — zero-dependency, auto-discovers `*.test.mjs`. |
|
||||
| `npm run typecheck` | `bot`, `web` | `tsc --noEmit`, `strict` + `noUncheckedIndexedAccess`. |
|
||||
| `npm run dev` | `bot` | `wrangler dev --port 8787 --local`. |
|
||||
| `npm run deploy` | `bot` | `wrangler deploy && node scripts/set-webhook.mjs`. |
|
||||
| `npm run commands` | `bot` | `node scripts/register-commands.mjs`. |
|
||||
| `npm run build` | `web` | `vite build` → `web/dist`. |
|
||||
| `npm run build:harness` | `web` | `vite build --mode harness` → `web/dist-harness`. |
|
||||
| `npm run capture` | `web` | `node scripts/capture.mjs`. |
|
||||
| `npm run build` | `theme` | `tailwindcss -i ./src/theme.css -o ./dist/rams.css --minify`. |
|
||||
| `npm run send` | `theme` | Copy self-hosted `woff2` fonts into `dist/fonts/`. |
|
||||
| `npm run basemap` | `theme` | `node basemap/build.mjs` — derive `basemap/liberty-rams.json` from tokens. |
|
||||
| `npm run basemap:preview` | `theme` | `python3 -m http.server 8123` (blocking). |
|
||||
| `npm run audit` | `theme` | `node ./scripts/audit.mjs ./dist/rams.css`. |
|
||||
| `npm run capture` | `theme` | `node ./scripts/screenshot.mjs`. |
|
||||
| `npm run test` | `theme` | `node ./scripts/audit.test.mjs`. |
|
||||
|
||||
### Before you open a change
|
||||
|
||||
Run all four. They are fast and they are the project's actual quality gates:
|
||||
|
||||
```bash
|
||||
npm run bot:typecheck
|
||||
npm run web:typecheck
|
||||
npm run bot:test
|
||||
npm run web:test
|
||||
npm run audit # only if you touched theme/ or any rendered CSS
|
||||
```
|
||||
|
||||
`theme` has its own suite (`npm test -w theme` → `node ./scripts/audit.test.mjs`).
|
||||
Note it has **no root passthrough script** — you must use the `-w theme` form to
|
||||
run it, unlike `bot` and `web`.
|
||||
|
||||
---
|
||||
|
||||
## Code style
|
||||
|
||||
**There is no linter and no formatter configured in this repository.** No
|
||||
ESLint, no Prettier, no Biome, no `.editorconfig`, no `lint` or `format` script
|
||||
in any of the four `package.json` files, and no commit hooks (no Husky, no
|
||||
`.git/hooks` wiring). If you add one, it becomes the project's standard
|
||||
retroactively — but today style is a review-time convention, not an automated
|
||||
check. What *is* mechanically enforced is TypeScript strictness and the grep
|
||||
gates described below.
|
||||
|
||||
### TypeScript
|
||||
|
||||
Both `bot/tsconfig.json` and `web/tsconfig.json` are strict beyond the default.
|
||||
The settings that actually change how you write code:
|
||||
|
||||
| Setting | `bot` | `web` | Why it matters |
|
||||
| --- | --- | --- | --- |
|
||||
| `strict` | yes | yes | No implicit `any`, strict null checks. |
|
||||
| `noUncheckedIndexedAccess` | yes | yes | Indexing an array/record yields `T \| undefined` — you must narrow. This is the single most common source of compile friction in this repo. |
|
||||
| `verbatimModuleSyntax` | yes | yes | Type-only imports must use `import type`. |
|
||||
| `allowImportingTsExtensions` | yes | yes | Import with explicit `.ts` extensions is allowed and is the house style. |
|
||||
| `noEmit` | yes | yes | `typecheck` is `tsc --noEmit`; bundling is wrangler's or Vite's job. |
|
||||
| `target` / `lib` | ES2023 / ES2023 | ES2023 / ES2023 + DOM | No downleveling. |
|
||||
| `types` | `@cloudflare/workers-types` | `vite/client` | Determines which globals exist. `bot` has no DOM; `web` does. |
|
||||
| `resolveJsonModule` | no | yes | `web` imports the basemap JSON style. |
|
||||
|
||||
`moduleResolution` is `"bundler"` in both — deliberately not `node16`, because
|
||||
both runtimes are bundler-hosted (wrangler / Vite), not raw Node.
|
||||
|
||||
### House conventions observable in the source
|
||||
|
||||
- **Explicit `.ts` extensions on relative imports.** e.g. `import { strings } from
|
||||
"../src/i18n/strings.ts"`, `from "./harness.mjs"`.
|
||||
- **`.mjs` for scripts and tests, `.ts` for source.** Everything in
|
||||
`bot/scripts/`, `bot/test/`, `web/test/`, `web/scripts/`, and `theme/scripts/`
|
||||
is `.mjs` and runs under plain Node.
|
||||
- **`node:` prefixed builtins** — `import { readFileSync } from "node:fs"`, never
|
||||
bare `"fs"`.
|
||||
- **Heavy header comments.** Every script and several source modules open with a
|
||||
block comment stating what it is, which decision IDs it satisfies, and its
|
||||
known constraints. Match that when adding a file.
|
||||
- **Named constants in SCREAMING_SNAKE for tunables** — `SESSION_TTL_SECONDS = 900`,
|
||||
`PIN_TTL_SECONDS = 300`. The privacy gate greps for this exact shape (see below).
|
||||
- **Zero inline user-facing copy.** All strings live in `bot/src/i18n/strings.ts`
|
||||
and `web/src/i18n/strings.ts`, keyed by locale, with `en` and `ms` carrying
|
||||
identical key-sets. Gate: `COPY5 no shell module inlines copy`.
|
||||
- **Structural interfaces for injection.** `KvLike`, `TelegramWebAppLike`,
|
||||
`acquireFix(deps, …)` — dependencies are passed in so tests can supply fakes
|
||||
without touching Workers types or `window`.
|
||||
|
||||
---
|
||||
|
||||
## Project conventions (the grep gates)
|
||||
|
||||
These are the rules the codebase enforces on itself. Each is a real test that
|
||||
reads source or build artifacts — treat them as non-negotiable, because a change
|
||||
that breaks one is a regression even if the behavior looks fine.
|
||||
|
||||
| Gate | Suite | Rule |
|
||||
| --- | --- | --- |
|
||||
| `PG1` | `bot/test/privacy.test.mjs` | Every `.put(` line in `kv/sessions.ts`, `kv/location-pin.ts`, and `webhook.ts` must carry `expirationTtl` **on the same line**, and the value must be a named `SCREAMING_SNAKE` constant — never an inline literal. No TTL-free write path can ship. |
|
||||
| `PG2` | `bot/test/privacy.test.mjs` | The KV modules export nothing persistence-shaped: no `saveProfile`, `storeProfile`, `saveLocation`, `storeLocation`, `saveHistory`, `storeHistory`, `putHistory`, `setLocation`. |
|
||||
| `GEO3` | `web/test/no-storage.test.mjs` | After stripping comments, no `web/src` file may reference `localStorage`, `sessionStorage`, `indexedDB`, `caches`, `CacheStorage`, `document.cookie`, `WebApp.DeviceStorage`, `WebApp.SecureStorage`, or `WebApp.CloudStorage`. Also no `serviceWorker.register`, `navigator.serviceWorker`, or `workbox`. |
|
||||
| `WK5` | `bot/test/webhook.test.mjs` | An empty `WEBHOOK_SECRET` must fail the webhook **closed** with a constant 500, without running the bot or touching KV. |
|
||||
| `CS4` | `bot/test/commands.test.mjs` | All replies go out with link previews disabled and nothing else attached; the command tree has no reply-menu or inline-button surface. |
|
||||
| `CS6` | `bot/test/commands.test.mjs` | The command stack registers exactly `/start`, `/mula`, `/help`, `/privacy`, `/privasi`. |
|
||||
| `COPY1` | `web/test/copy-gate.test.mjs` | `en` and `ms` carry identical key-sets (locale parity). |
|
||||
| `COPY5` | `web/test/copy-gate.test.mjs` | No module under `web/src/ui/*.ts` or `web/src/main.ts` inlines copy strings. |
|
||||
| `BA1`–`BA6` | `web/test/build-artifact.test.mjs` | The production bundle references no third-party CDN, self-hosts its `woff2` fonts, contains a hashed MapLibre worker chunk, carries the inlined Rams style JSON, and contains **no** Mock-Telegram installer or runner-page fetch stub in any emitted file. |
|
||||
| audit exit codes | `theme/scripts/audit.mjs` | `0` clean, `1` off-token violations, `2` token-sheet drift. No silent passes. |
|
||||
|
||||
The same privacy promise is asserted from both sides: the **bot** must never
|
||||
write without a TTL, and the **web** client must have no storage surface at all.
|
||||
If you add a persistence mechanism, these suites fail — which is the point.
|
||||
|
||||
### The two design invariants worth internalizing
|
||||
|
||||
**Access control.** The `chat_id` for `/api/location` and `/api/location-fallback`
|
||||
is *always* resolved from the session claim, never from client input; locale comes
|
||||
from `claim.language_code`; the fallback request body is never read. If you add a
|
||||
route, derive identity the same way.
|
||||
|
||||
**Honest degradation.** Every failure is a labeled state, never a silent one and
|
||||
never a fabricated value. A `null` heading hides the marker cone instead of
|
||||
guessing; `getPin` range-validates so a corrupt record cannot fly the camera to
|
||||
null island; the chat-pin poll is bounded at 60 s so `AWAITING PIN` cannot persist
|
||||
forever; every source timeout resolves `null` so the chain always advances;
|
||||
`bootMap()` returning `null` renders the shell with `data-map="absent"`. Copy
|
||||
along this pattern: if you cannot do the thing, say so in a state, don't fake it.
|
||||
|
||||
### Design-token workflow
|
||||
|
||||
`theme/src/tokens.css` is the **single source of truth** for every color, spacing,
|
||||
typography, and radius value in the project. It declares them in a Tailwind v4
|
||||
`@theme static` block (55 `--color-*` tokens plus spacing and typography);
|
||||
`static` forces every token into the compiled CSS so the package is consumable as
|
||||
plain custom properties by anything, including the MapLibre basemap derivation.
|
||||
|
||||
Rules:
|
||||
|
||||
1. **Never hardcode a value that belongs to a token.** Not a hex, not a px
|
||||
padding, not a font stack. `npm run audit` will flag it in rendered CSS.
|
||||
2. **Never restate token values in documentation.** `DESIGN.md` and other docs
|
||||
describe principles and point at the token package.
|
||||
3. **Never edit `stitch_minimalist_route_navigator/`.** Those four mockups are
|
||||
read-only reference; `theme/src/theme.css` `@source`-scans them to compile
|
||||
their utility vocabulary, and `theme/scripts/screenshot.mjs` falls back to
|
||||
their committed `screen.png` on capture failure.
|
||||
4. **Adding a token** means editing `tokens.css`, rebuilding (`npm run build -w
|
||||
theme`), and re-running `npm run audit`. If it is a cartographic color, also
|
||||
consider whether `theme/basemap/build.mjs` should derive it.
|
||||
5. **`theme/src/theme.css`** is the entry stylesheet: it `@import`s tailwindcss,
|
||||
`tokens.css`, and `components.css`, declares seven self-hosted `@font-face`
|
||||
rules from `theme/fonts/` (no Google Fonts CDN), and scopes class generation
|
||||
with explicit `@source` lines (the four mockups are the allowed utility
|
||||
sources; `@source not "../scripts"` and `@source not "../basemap"` exclude
|
||||
prose).
|
||||
6. **`theme/basemap/build.mjs`** forks OpenFreeMap `liberty` and derives every
|
||||
cartographic color from `tokens.css` via `desaturate(...)`, with an accent
|
||||
guard that fails the build if `#EB8008`, `#ED3F1C`, or `#736B1E` leak into any
|
||||
layer. `building-3d` is stripped (no faux 3D).
|
||||
|
||||
---
|
||||
|
||||
## Branch and commit conventions
|
||||
|
||||
### Branches
|
||||
|
||||
The repo's only branch is `main` (there is a stray `master` but it is not the
|
||||
active branch), and there is **no remote branch convention documented** — no
|
||||
`CONTRIBUTING.md`, no PR template, no CI config (no `.github/`, no `.gitea/`).
|
||||
In practice, work lands on `main` via atomic commits rather than long-lived
|
||||
feature branches. If you prefer branches, `feat/`, `fix/`, and `docs/` prefixes
|
||||
match the commit vocabulary below.
|
||||
|
||||
### Commit messages
|
||||
|
||||
The convention is **conventional-commit style with a phase-scoped type**:
|
||||
`type(phase): subject`. The type is one of `feat`, `fix`, `docs`, `chore`, `test`.
|
||||
The scope in parentheses is a phase identifier, sometimes with a sub-task
|
||||
(`03-04`). Examples straight from `git log`:
|
||||
|
||||
```
|
||||
feat(03-03): status strip, state cards, recenter control, boot choreography — map-first, D-09 staged states
|
||||
fix(03-04): load telegram-web-app.js in head — production WebApp was never defined
|
||||
docs(03-04): log debug round 5 — diagnostic probe armed
|
||||
test(02-01): hand-rolled harness + RED initData vector suite (D-12, TDD RED)
|
||||
chore(03-01): lockfile resolution for the web workspace
|
||||
fix(02-02): ... (when the scope names the phase area rather than a number)
|
||||
```
|
||||
|
||||
The subject is lowercase after the colon, imperative, and — the house habit —
|
||||
carries an em-dash clause explaining the *why* or the observable failure it fixes
|
||||
("production WebApp was never defined (no initData…)"). Reference decision IDs
|
||||
(`D-09`, `WR-03`, `PRIV-02`) are appended in parentheses when relevant.
|
||||
|
||||
Keep commits atomic: the log shows one concern per commit, frequently a
|
||||
`fix(...)` immediately after the `docs(...)` log entry that motivated it.
|
||||
|
||||
---
|
||||
|
||||
## Pull requests
|
||||
|
||||
**There is no PR process configured** — no `.github/PULL_REQUEST_TEMPLATE.md`, no
|
||||
CONTRIBUTING file, no CI, no required reviewers. Given the private single-branch
|
||||
setup, changes are typically committed straight to `main`. If you do open a PR,
|
||||
match what the project actually relies on:
|
||||
|
||||
- **Run the gates** and paste the output: both typechecks, both test suites, and
|
||||
`npm run audit` if any rendered CSS or theme changed.
|
||||
- **Explain the observable before/after**, following the commit-subject habit
|
||||
(what was broken, what it does now).
|
||||
- **Call out gate-relevant surface**: if you touched a KV write, `web/src`
|
||||
persistence, the command surface, or the i18n strings, say which gates you
|
||||
re-verified (`PG1`, `GEO3`, `CS6`, `COPY1`…).
|
||||
- **Include screenshots** for any Mini App change — `npm run web:build:harness
|
||||
&& npm run web:capture` produces the current states.
|
||||
- **Do not commit build output.** `theme/dist`, `web/dist`, `web/dist-harness`,
|
||||
and the `screenshots/` directories are gitignored by design; they are
|
||||
regenerated at build time.
|
||||
|
||||
---
|
||||
|
||||
## Committing generated artifacts
|
||||
|
||||
`dist/` and the screenshot directories are **never committed**. This is a
|
||||
deliberate rule, called out in `.gitignore`: the web build consumes
|
||||
`theme/dist/rams.css` at build time without vendoring, so a committed
|
||||
`theme/dist` would go stale. Always rebuild (`npm run build`) rather than
|
||||
expecting a checked-in artifact.
|
||||
|
||||
`.planning/` is also gitignored (local-only planning docs), as is `bot/.dev.vars`
|
||||
and `bot/.wrangler/`.
|
||||
Reference in New Issue
Block a user