# Phase 8: Gitea CI - Context

**Gathered:** 2026-06-11
**Status:** Ready for planning

<domain>
## Phase Boundary

Phase 8 adds CI on the existing self-hosted **Gitea Actions** runner. Two outcomes:

1. **PR regression gate** — every PR targeting `main` runs lint, typecheck (both apps), unit tests, API-integration tests against a MariaDB service container, **and the Phase 7 mobile Playwright harness** (against a CI-brought-up dev stack with `DEV_AUTH_BYPASS=true`). Any failure blocks the merge.
2. **Publish on merge** — a push to `main` builds and publishes the API Docker image to the Gitea container registry.

This phase owns only the CI plumbing: workflow files, dev-stack bring-up + readiness waits, image build/push. It does **not** modify the Phase 7 harness specs (CI reuses them unchanged), the Dockerfile (already multi-stage, builds API + PWA), or application code. Requirements: **CI-01, CI-02**.

</domain>

<decisions>
## Implementation Decisions

### Dev-stack bring-up in CI (for the harness step)

- **D-01:** Bring up the stack with **bare background processes + a MariaDB service container** — NOT docker compose, NOT a production image.
  - MariaDB runs as a Gitea **service container** (the same one the API-integration job needs; `DB_HOST=127.0.0.1`, service creds).
  - The **API** runs as a background process via `pnpm dev:api` (or equivalent) with `DEV_AUTH_BYPASS=true` and `DB_HOST=127.0.0.1`, listening on `:3000`.
  - The **PWA** Vite dev server is started by **Playwright's own `webServer`** config (already present; `reuseExistingServer: !process.env.CI`), on `:5173`. Vite proxies `/api`, `/health`, `/callback` → `:3000`.
  - Rationale: no docker-in-docker on the self-hosted runner; matches the Phase 7 dev-server harness contract exactly; reuses the MariaDB service container already required for integration tests.
- **D-02:** The harness step MUST wait for **both** the API (`:3000`) and the PWA Vite server (`:5173`) to accept connections before Playwright launches. The harness already polls `baseURL/health` (proxied to the API) in `global-setup.ts`; CI must additionally ensure the API process is up first. This is on top of the MariaDB-11 readiness loop (Pitfall 11 — `healthcheck.sh --connect --innodb_initialized`, never `mysqladmin ping`).

### Workflow topology & jobs

- **D-03:** **One workflow file with parallel, event-gated jobs.**
  - `pull_request` → `main`: fast-checks job (lint + typecheck both apps + unit tests) runs **in parallel** with the heavier API-integration job and the harness job. Fast feedback — a lint failure does not wait behind the harness.
  - `push` → `main` (merge): build-and-publish job runs.
  - Single file so the whole regression + publish story lives in one place; accept the minor setup duplication (checkout, pnpm cache, Node-22 pin) across jobs.

### Docker image tag strategy (CI-02)

- **D-04:** On merge to `main`, publish the API image with **two tags**: `:latest` (moving pointer) **and** `:<milestone>-<shortsha>` (immutable, e.g. `v1.1-4303a1b`).
  - The milestone string (e.g. `v1.1`) is read from PROJECT.md / ROADMAP.md, not hardcoded inline if avoidable.
  - `<shortsha>` is the short commit SHA of the merge commit.
  - Rationale: `:latest` for easy pulls; the milestone-prefixed SHA tag groups builds by release line and stays immutable for rollback/traceability.

### Failure artifacts & browser matrix

- **D-05:** Run **both** device profiles in CI — iPhone 14/WebKit **and** Pixel 7/Chromium (the full Phase 7 matrix). Install whatever system deps WebKit needs on the runner (probe in the runner-probe step).
- **D-06:** On harness **failure**, upload Playwright **traces / screenshots / videos** as CI artifacts for debugging. The config already emits `trace`/`video` `on-first-retry` and `screenshot: only-on-failure`; CI must upload the `test-results/` output. Note the config's `reporter: 'github'` may not render natively in Gitea Actions — verify during the runner probe and fall back to `list`/`html` if annotations don't surface.

### Claude's Discretion

- Exact job names, step ordering within a job, pnpm store cache key strategy, and whether fast-checks is one job or split — planner/executor decide.
- Whether the API background process is launched with `pnpm dev:api` vs a built `node dist` — pick whatever gives reliable `:3000` readiness under `DEV_AUTH_BYPASS`; the harness only needs the authed PWA reachable (Dev User 1 has no CalDAV creds, so verify layout/flows, not live event-create).
- Registry hostname / image repository path under the Gitea registry.

</decisions>

<canonical_refs>

## Canonical References

**Downstream agents MUST read these before planning or implementing.**

### Phase scope & requirements

- `.planning/ROADMAP.md` §"Phase 8: Gitea CI" — goal, 6 success criteria, pitfalls this phase owns.
- `.planning/REQUIREMENTS.md` — CI-01 (PR regression incl. harness), CI-02 (publish image on merge).
- `.planning/PITFALLS.md` — Pitfalls 11 (MariaDB-11 readiness), 12 (runner-probe first), 13 (`--password-stdin`), 15 (SW block, harness side).

### Harness the CI step runs (reused unchanged from Phase 7)

- `apps/pwa/playwright.config.ts` — device matrix, `serviceWorkers: 'block'`, `webServer` (Vite-only, `reuseExistingServer: !CI`), `retries`/`workers`/`reporter` under `CI`, env-driven `PLAYWRIGHT_BASE_URL`.
- `apps/pwa/e2e/global-setup.ts` — `/health` readiness poll, `/api/me` DEV_AUTH_BYPASS reachability gate, fail-closed env guard (refuses `NODE_ENV=production` or missing `DEV_AUTH_BYPASS`), mysql2 truncate-and-seed (calendar id 10, lists/items for user 1).
- `.planning/phases/07-mobile-test-harness/07-CONTEXT.md` — Phase 7 decisions D-01..D-10 (auth strategy, SW block, env baseURL, compose-managed backend).

### Infra the CI builds/runs against

- `apps/api/Dockerfile` — multi-stage: `builder` (API), `pwa-builder` (PWA dist → `./public`), `production` target. CI publishes the `production` target.
- `docker-compose.yml` / `docker-compose.dev.yml` — service shape, MariaDB 11 healthcheck (`healthcheck.sh --connect --innodb_initialized`), dev override exposing 3306, API `dev` build target.
- `apps/pwa/vite.config.ts` — dev proxy (`/api`, `/health`, `/callback` → `:3000`) the harness depends on.
- `package.json` (root) — scripts: `dev:api`, `dev:pwa`, `test`, `test:e2e`, `lint`, `typecheck`.

</canonical_refs>

<code_context>

## Existing Code Insights

### Reusable Assets

- **Playwright config + global-setup (Phase 7):** ready to run headlessly in CI. `retries: 2`, `workers: 1`, `reporter: 'github'` already gated on `process.env.CI`. CI sets `CI=true` and `PLAYWRIGHT_BASE_URL` and the harness behaves correctly. No spec changes.
- **MariaDB service-container pattern:** API-integration tests already require a real MariaDB with `DB_HOST=127.0.0.1` + service creds + Drizzle `generate`+`migrate` for schema. The harness's `global-setup` seeds the same DB directly via mysql2. One MariaDB service container can back both the integration job and the harness job.
- **Multi-stage Dockerfile:** `production` target already builds API + PWA and serves both on `:3000`. CI build/push is a thin wrapper (`docker build --target production` + `docker login --password-stdin` + `docker push`).

### Established Patterns

- **Gitea, not GitHub:** origin is self-hosted Gitea; `main` is protected (PRs only). Gitea Actions is GitHub-Actions-syntax-compatible but **do not assume `actions/setup-node` behaves identically** — runner-probe first (Pitfall 12), pin Node 22 explicitly.
- **node-cron lesson (long-running process):** not directly relevant to CI, but the API in CI is short-lived/background — no scheduler concerns.

### Integration Points

- CI orchestrates, in order, for the harness job: MariaDB service container (readiness loop) → Drizzle generate+migrate → API background process (`DEV_AUTH_BYPASS=true`, `:3000`, readiness wait) → Playwright (`webServer` starts Vite `:5173`, `global-setup` polls `/health` + `/api/me`) → specs → upload artifacts on failure.
- Publish job depends on `apps/api/Dockerfile` `production` target + Gitea registry credentials (PAT with `write:package`, piped via `--password-stdin`).

</code_context>

<specifics>
## Specific Ideas

- Image tag format locked to `:latest` + `:v1.1-<shortsha>` (milestone prefix + short SHA). Example: `v1.1-4303a1b`.
- Start the very first CI iteration as a **runner-probe** only: `node --version` / `pnpm --version` / Docker access / WebKit dep availability on the `self-hosted` runner — before any real test/build steps are designed.

</specifics>

<deferred>
## Deferred Ideas

- **ROADMAP status fix:** ROADMAP.md line 29 marks Phase 8 "completed 2026-06-11" while line 204 says "Not started" and no Phase 8 artifacts exist. This is a bookkeeping error to correct (Phase 8 is being started now) — a docs/roadmap cleanup, not Phase 8 scope.
- **Desktop e2e coverage:** moved to backlog **Phase 999.15** — Phase 8 CI gates mobile only; adding a Desktop Playwright profile + desktop-safe specs is out of CI-plumbing scope.

</deferred>

---

_Phase: 8-Gitea CI_
_Context gathered: 2026-06-11_
