diff --git a/.gitignore b/.gitignore index 1cb17af..2742205 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,3 @@ public/styles/ public/fonts/ public/sprites/ public/map-assets-manifest.json - -# Local-only successor-agent manual (references host setup/credentials pointers) — never commit -ORCHESTRATOR.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 08c4ec3..a262b65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## v0.22.2 — 2026-09-20 07:32 +- Docs: ORCHESTRATOR.md successor-agent manual committed to the repo; its token reference now points to the gitignored local .env (GITEA_TOKEN, rotated after the Sept 2026 incident); §9 rewritten as durable 'how to find current state' guidance instead of a stale snapshot; no code changes + ## v0.22.1 — 2026-09-20 06:57 - Hygiene: gitignore the local-only ORCHESTRATOR.md successor-agent manual (it references host-specific setup and must never be version-controlled); no code changes diff --git a/ORCHESTRATOR.md b/ORCHESTRATOR.md new file mode 100644 index 0000000..dd96418 --- /dev/null +++ b/ORCHESTRATOR.md @@ -0,0 +1,69 @@ +# ORCHESTRATOR.md — Operating Manual for Successor Agents + +You are taking over the autonomous development loop on **Flight-Ops** (browser game: run an airplane transport company in Europe; server-authoritative sim, MapLibre GL JS map). This document captures operational knowledge that is NOT written down in AGENTS.md. Read AGENTS.md first — it defines the mandatory change process and this file complements it. + +Credentials are deliberately NOT stored in this file — see §1 for where they live. Never paste token values into any tracked file. + +## 1. Project identity +- Repo: Gitea at https://git.kkhost.pl → org **OpenCode**, repo **Flight-Ops** (full name `OpenCode/Flight-Ops`). ⚠️ NOT "krzysztof/flight" — that path 404s and caused a long dead-end in Sept 2026. Every Gitea API/MCP call uses owner=`OpenCode`, repo=`Flight-Ops`. +- Local workdir + prod deploy dir: `/home/krzyw/projects/sandbox/flight` (git checkout; `docker compose up -d --build` deploys from here). +- Prod URL: https://flight.dev.kkhost.pl (Traefik → docker-compose stack on this host). `GET /api/version` reports the running version. +- Gitea API access for git.kkhost.pl: prefer the gitea MCP tools — they are already configured with a token. For raw REST/curl, read the current token from the local .env file at repo root (variable `GITEA_TOKEN`; .env is gitignored and must never be committed): `source .env && curl -s -H "Authorization: token $GITEA_TOKEN" https://git.kkhost.pl/api/v1/user`. (Incident note, Sept 2026: an early draft of this doc committed a live token to the repo; that commit was purged from all refs and the token has since been rotated — never repeat this.) +- Current user: kamilkrzywda (id 1). + +## 2. The loop (per issue) +1. Small incremental change on a feature branch off `master`; connected issues may share one branch/PR with a single version bump. +2. Bump `package.json` version + add CHANGELOG.md entry at top: `## v — YYYY-MM-DD HH:MM` then bullet points of what changed (docs-only changes get patch bumps too — precedent v0.19.1/v0.19.2). +3. Local verification ONLY (there are NO local e2e runs): `npx tsc --noEmit`, `npx vitest run` (unit baseline: 288 tests in 31 files), `npx playwright test --list` (baseline: 41 tests in 12 files). +4. Push → CI (~7 min) → **auto-merge on green** (PRs have auto-merge configured; no manual merge needed — verify via `GET /pulls/{n}` that state=closed and merged_at is set). Note: if a PR was created via API rather than create-on-push, double-check auto-merge actually fires. +5. Tag the version on master: `git tag v origin/master && git push origin v` (after `git fetch origin master`). +6. Deploy prod in a PTY (long task): `cd /home/krzyw/projects/sandbox/flight && git checkout master && git reset --hard origin/master && docker compose up -d --build`; then verify `curl https://flight.dev.kkhost.pl/api/version` returns the new version and the root page is HTTP 200. +7. Close each issue with a comment: what changed, version, PR number, merge commit SHA, deployed URL. +8. Repeat for the next open issue: `GET /issues?state=open&type=issues` — note Gitea lists pull requests in this endpoint too; filter out entries with a `pull_request` key. + +## 3. Gitea API + MCP quirks (learned the hard way) +- The gitea MCP tools work for issues/PRs/files/tags, and Actions endpoints work IF you pass owner=`OpenCode`, repo=`Flight-Ops`. A wrong owner/repo makes EVERY call return `{"message":"not found","url":"https://git.kkhost.pl/api/swagger"}` — that swagger URL inside a 404 is the signature of "wrong path", not "Gitea is down". +- MCP merge endpoint 405s → don't bother; auto-merge handles merging. Manual fallback if ever needed: `POST /repos/OpenCode/Flight-Ops/pulls/{n}/merge` with body `{"do":"merge","merge_style":"merge"}`. +- Gitea REST shapes (NOT GitHub's): list runs → `{workflow_runs:[...]}`; run jobs → `/actions/runs/{id}/jobs` returns `{"jobs":[...]}`; single job log via MCP `get_job_log_preview` (param tail_lines) or `download_job_log` to a file, then grep. +- Failure anatomy in CI logs: the Playwright summary at the end lists "N failed" + test titles; each failure block has a code frame with line numbers plus an "Error Context:" path. The e2e step is the only one that can fail flakily; unit failures are real regressions. +- CI watcher pattern that works: `run_background_process` (notify=true) running a loop that polls `GET /actions/runs/{id}` every 60 s and exits when status=completed, printing status+conclusion each tick. Don't poll in the foreground; don't sleep-loop on pty_read. + +## 4. CI facts (.gitea/workflows/ci.yml + docker-compose.ci.yml) +- Single job "build + unit + e2e" on runner gitea-runner (docker-in-docker). Fresh DB per run (volume removed); compose project `flight-ci`; app container on a static IP → `E2E_BASE_URL=http://172.39.0.10:3000`; `PW_REMOTE_BROWSER=0` (CI runs local chromium — the shared remote browser server at 192.168.1.4 cannot reach a stack inside dind); `E2E_COMPOSE_ARGS="-f docker-compose.ci.yml -p flight-ci"` is read by e2e/state.spec.ts for its mid-run `docker compose restart flight`. +- Flaky uplink → retry loops in ci.yml; an occasional "flaky" test (failed once, passed on retry) does NOT fail the run. +- Branch protection requires status check `CI / build + unit + e2e (pull request)` — PRs cannot merge until green; CI never deploys to production. + +## 5. E2E conventions (e2e/*.spec.ts) — read before touching tests +- Sim scale in tests: SCALE=1000; hub `HUB='EDDF'` (Frankfurt); typical destination LEMD. +- `flyHomeViaApi(page)` self-heal at test start — interrupted runs can leave planes mid-flight; landing waits up to 240 s. +- Pause-sim pattern for deterministic marker clicks: pause, act, assert, resume. +- Dev hook: `window.__flightMap` exposes the live MapLibre map instance in dev builds (also `__flightLastSnapshot`). +- `queryRenderedFeatures` is viewport-scoped → fitBounds/fitToAirports before asserting on far features; give the renderer ~300 ms after a camera jump. +- **expect.poll callbacks must be total** — a throw inside fails the test instantly instead of retrying. Guard/filter everything you dereference. + +## 6. ⚠️ The big one: Playwright evaluate serialization drops prototype getters (root cause of CI runs #69–#71) +MapLibre's `queryRenderedFeatures` returns live `GeoJSONFeature` class instances whose `geometry` is a **lazy PROTOTYPE getter** (own properties are only type/properties/id/source/state plus private fields). Playwright serializes evaluate results by copying **own enumerable properties only**, so every feature arriving in test context has NO geometry — silently. +- Verified empirically: `page.evaluate(() => { class A{constructor(){this.own=1} get geo(){return 42}} return [new A()] })` → `[{"own":1}]`. +- Symptom matrix (how it masqueraded as "the line isn't drawn" while the trace rendered fine on screen): strict length guard → timeout; direct `.geometry.coordinates` access → `TypeError: Cannot read properties of undefined (reading 'coordinates')`; defensive filter → clean 15 s timeout. +- **Rule: never return maplibre feature instances across page.evaluate — always map to plain objects in-page** (`{type, id, properties, geometry: f.geometry}` forces the getter to evaluate). Canonical pattern: `renderedFeatures()` in e2e/hover.spec.ts. Other specs that touch `.geometry` do so inside evaluate (safe) or only read array length (safe). + +## 7. MapLibre rendering facts (for map assertions) +- GeoJSON geometry is stored per-tile in INTEGER grids; long lines are clipped → `queryRenderedFeatures` returns ONE FEATURE PER TILE the line crosses (the EDDF→LEMD leg ≈ 4–5 segments at z=4/5). +- Coordinates are quantized to the grid: error up to ~half a grid step (~0.001° observed at z≥4, worst ~0.02° at z=0 with extent 8192). Endpoint assertions must use an EPS (hover tests use `HOVER_ROUTE_EPS_DEG = 0.05` ≈ 5 km — above worst-case quantization at any zoom, below the distance between any two EU airports) and check endpoints ACROSS ALL SEGMENTS: some segment must START within EPS of origin AND some must END within EPS of destination. Never strict equality on a single feature. +- The true line endpoints survive clipping verbatim in whichever tile contains them. + +## 8. App architecture quick map +- `components/Map.tsx` — the MapLibre layer: sources/layers created in ensureLayers (dedicated `hover-routes` GeoJSON source + top-most dashed `hover-route` LineLayer, paint #facc15 width 4 dasharray [2,2]; `airports-target-highlight` green #4ade80 ring added last so it sits above the line at its endpoint); applyHoverRef re-pushes data on every hover; `window.__flightMap` dev hook. +- `lib/mapData.ts` — pure GeoJSON builders (`hoverRouteGeoJson`, `routesGeoJson`, `greatCircleArc` slerp) + unit tests in lib/mapData.test.ts. +- Hover protocol: panes report a HoverTarget via mouseenter/leave (`components/hoverTypes.ts`): `{kind:'plane';id} | {kind:'package';originIcao;destIcao;planeId?} | {kind:'route';fromIcao;toIcao}`; the `''` sentinel means no hover. +- Server: sim ticks server-side, client polls /api/state; OpenAPI spec generated from lib/openapi.ts and served at /api/openapi — update it whenever an endpoint changes. + +## 9. Finding current state (this file keeps no snapshots) +This manual is meant to stay valid long-term, so it deliberately contains NO version or issue snapshots — they rot. To orient yourself when you take over: `git log --oneline -10` + CHANGELOG.md for what has shipped; `GET /issues?state=open&type=issues` (filter out entries with a `pull_request` key) for what is left to do; PLAN.md's "Later" section for the roadmap backlog. + +## 10. Standing user instruction & etiquette +- The user's standing order for this project: work through the issue list autonomously — implement → PR → CI green → merge → tag → deploy prod → close with comment → next — until told to stop. +- Small incremental changes; each part fully tested (unit + e2e) before moving on. +- Long terminal tasks → pty_* tools (deploys, builds). Background watchers → run_background_process with notify=true. +- Never push directly to master; never deploy from a feature branch; CI never touches prod — production deploys are manual only (`docker compose up -d --build` after tagging). +- **Never commit credentials.** Tokens/secrets live in the MCP server config or with the user, never in tracked files (see incident note in §1). diff --git a/package.json b/package.json index 478040f..87a9af6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flight-ops", - "version": "0.22.1", + "version": "0.22.2", "private": true, "scripts": { "dev": "next dev -p 3000",