Phase 8: Gitea CI — runner probe + PR gating jobs (fast-checks + api) #3

Merged
luckberg merged 66 commits from gsd/phase-08-gitea-ci into main 2026-06-11 16:11:42 -04:00
Owner

CI-01 (non-harness half): PR-gated fast-checks (lint/typecheck/PWA unit) + api job (MariaDB 11 service container, drizzle migrate, DB-backed API tests). Built on probe-confirmed Docker-executor runner. Harness (08-03) and publish (08-04) jobs follow.

CI-01 (non-harness half): PR-gated fast-checks (lint/typecheck/PWA unit) + api job (MariaDB 11 service container, drizzle migrate, DB-backed API tests). Built on probe-confirmed Docker-executor runner. Harness (08-03) and publish (08-04) jobs follow.
luckberg added 49 commits 2026-06-11 13:58:14 -04:00
Adds 07-UI-SPEC.md for Phase 7 (Mobile Test Harness). Documents the
assertable mobile-UI quality bar the harness enforces: touch-target
thresholds (≥44px), no-horizontal-overflow rule, two-profile viewport
matrix (iPhone 14/WebKit + Pixel 7/Chromium), role-based accessible name
requirements, empty/error-state copy anchors, screenshot omission rationale,
and CI portability rules. Locks D-01 through D-10 from CONTEXT.md as the
assertion contract.
- Add @playwright/test@1.60.0 to apps/pwa devDependencies (pinned, not floated)
- Install WebKit and Chromium browser engines via playwright install
- Add test:e2e, test:e2e:ui, test:e2e:headed scripts to apps/pwa/package.json
- Add root workspace test:e2e delegate script to package.json
- Two projects: iphone/WebKit (iPhone 14) + pixel/Chromium (Pixel 7)
- serviceWorkers: 'block' on both profiles per D-02/Pitfall 15
- env-driven baseURL via PLAYWRIGHT_BASE_URL (D-08/Rule 8)
- globalSetup ref to e2e/global-setup.ts (Plan 02 implements)
- webServer manages vite only with reuseExistingServer (D-10)
- no storageState, no toHaveScreenshot per D-01/UI-SPEC Rule 6
- add e2e/global-setup.ts placeholder (stub) so config path resolves
- Add exclude: ['e2e/**', 'node_modules/**'] to vitest.config.ts test block
  to prevent Playwright specs from being picked up by Vitest jsdom runner
- Add tsconfig.e2e.json extending main tsconfig with node types for
  playwright.config.ts and e2e/**/* typecheck coverage
- Add @types/node to pwa devDependencies (required by playwright.config.ts)
- Update typecheck script to run both src and e2e tsc passes
- 191 unit tests still pass; no e2e import errors in vitest run
- Poll baseURL/health with 60s timeout; throw on expiry (D-08 readiness gate)
- TRUNCATE list_items, list_shares, lists, calendar_events (FK checks toggled)
- INSERT IGNORE calendars guard for id=10 (Pitfall 4 CI resilience)
- Seed one timed calendar_event on calendar_id=10 (uid: e2e-seed-event-001)
- Seed E2E Grocery List (owner_id=1, is_shared=true) + list_shares + Milk/Eggs items
- Add mysql2@3.22.4 devDependency to apps/pwa (needed for global-setup types)
- No @playwright/test imports — plain Node.js only (Pitfall 2)
- Documents pnpm test:e2e run commands and single-profile / headed variants
- Documents DEV_AUTH_BYPASS=true must be set before API starts (Pitfall 5)
- States production compose MUST NOT set DEV_AUTH_BYPASS (Elevation of Privilege)
- Lists PLAYWRIGHT_BASE_URL and DB_* env vars (all credentials env-only, never hardcoded)
- States no storageState file is used (D-01 — no expiring session cookie)
- Describes globalSetup readiness gate + seed anchors (Milk/Eggs/Seeded Test Event)
- Notes Phase 8 CI scope and --with-deps WebKit requirement
- Replace ISO 8601 'T' separator with space in dtstart_utc value
- MariaDB TIMESTAMP requires 'YYYY-MM-DD HH:MM:SS', not 'YYYY-MM-DDTHH:MM:SSZ'
- Was causing 'Incorrect datetime value' error blocking all e2e harness runs
- Rule 1: boundingBox assertions for BottomTabBar Calendar/Lists tabs (≥44px),
  PhoneNav settings button (≥44px), New Event FAB (≥56px) on both profiles
- Rule 2: scrollWidth ≤ clientWidth on /calendar and /lists
- Rule 3: BottomTabBar in-viewport (bottom edge ≤ viewport height), PhoneNav visible
- Rule 4: navigation landmark locatable by role+name (getByRole with accessible name)
- Self-validation: addStyleTag injection proves Rule 1 tracks geometry (20px height <44),
  proves Rule 2 detects overflow (2000px body width); both recover after removal
- Runs on iphone (WebKit/390px) and pixel (Chromium/412px) profiles; 30 tests pass
Playwright transpiles specs without typechecking (esbuild), so layout.spec.ts ran
green while `tsc -p tsconfig.e2e.json` failed: page.evaluate(() => document...)
callbacks need the DOM lib, and styleHandle.evaluate((el) => el.remove()) typed el as
Node (no .remove()). Phase 8 CI runs the typecheck gate, so this would have broken CI.
Adds DOM/DOM.Iterable to the e2e tsconfig (also covers 07-04 specs) and casts el to Element.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Populated state: asserts .sx-react-calendar-wrapper visible + 'Nothing here' absent
- Error state: mocks /api/events* to 500, asserts 'Couldn't load events' heading + Retry ≥44px + no overflow; unroutes after
- TEST-02 preconditions: asserts nav landmark visible (authed reach) + URL on localhost (no Authelia redirect) + navigator.serviceWorker.controller null (SW block)
- No absolute URLs; route mocks paired with unroute (T-07-11)
- Populated state: asserts 'Open list: E2E Grocery List' button visible + listitem count ≥1 + 'No lists yet' absent + no overflow
- Empty state: routes /api/lists to 200 [] before goto, asserts 'No lists yet' + 'Tap + to create' visible + no overflow; unroutes after
- Seeded DB not mutated — empty state is network-simulated (T-07-11 / D-06)
- No absolute URLs; both states pass Rule 2 overflow check
global-setup.ts TRUNCATEs four tables against whatever DB_* points at, with no
production guard — an operator with prod DB_* still exported could wipe lists/
list_items/list_shares/calendar_events. The README promised a DEV_AUTH_BYPASS
guardrail the code never enforced. Adds a fail-closed guard mirroring
apps/api/src/auth/devBypass.ts: hard NODE_ENV==='production' check first, then
require DEV_AUTH_BYPASS==='true' before opening any DB connection. README updated
with the test-process env requirement (run command + CI runner env).

Verified: guard throws without DEV_AUTH_BYPASS; full 58-test suite passes with it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Deep review found the calendar 'populated state' assertions were vacuous:
- getByText('Nothing here').toHaveCount(0) targeted CalendarShell's EmptyState,
  which CalendarShell NEVER renders (success branch always mounts ScheduleXCalendar;
  EmptyState.tsx is dead code, imported by nothing). The check was permanently green
  regardless of the seed — a regression dropping all events would have shipped green.
- .sx-react-calendar-wrapper renders on any successful auth, with or without events,
  so it never proved the seed reached the UI.

Replaced the dead-EmptyState check with a real DB→UI proof: assert the seeded event
title 'Seeded Test Event' is rendered in the grid. Verified non-vacuous — passes with
the seed on both profiles; with /api/events mocked to [] the title is absent (would fail).

BL-02: the seed anchored the event at now+24h. Both phone profiles render the
month-agenda view of the CURRENT month, so on a month's last day 'tomorrow' falls into
the next month and vanishes from the grid, making the new visibility assertion date-fragile.
Re-anchored to noon-today (UTC) — always today's local date, always in the current-month view.

Verified: full 58-test suite passes both profiles; typecheck clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Probe-only workflow triggering on gsd/phase-08-gitea-ci branch only
- Answers P-01..P-11 + P-13: Node version, pnpm, runner mode (critical
  fork Docker vs host), Docker socket, MariaDB service container spawn
  and reachability on both hostnames, actions/cache, Playwright WebKit
  deps, gitea-upload-artifact fork, and GITHUB_SHA short-SHA expression
- Uses healthcheck.sh --connect --innodb_initialized for MariaDB (never
  the binary removed from mariadb:11 — Pitfall 11)
- Uses ChristopherHX/gitea-upload-artifact@v4 (not the official action
  which aborts on Gitea with GHES detection — Pitfall 6 / T-08-SC)
- P-12 (docker login) deferred to Plan 04 — probe never references any
  secret (T-08-01 compliant)
- All steps that may fail use continue-on-error: true so probe reports
  findings instead of red-failing on expected unknowns
fix(08-01): probe runs-on ubuntu-latest — runner has no self-hosted label
runner-probe / runner-probe (push) Successful in 5m33s
134d4db08a
The act_runner advertises ubuntu-latest/ubuntu-24.04/ubuntu-22.04; runs-on:
self-hosted matched no runner and the probe job stayed queued. Switch the
probe (and the plan key-link) to ubuntu-latest. ci.yml (Plans 02-04) must
use the same label.
- Docker-executor mode confirmed; runs-on: ubuntu-latest (not self-hosted)
- MariaDB service container works; DB_HOST=mariadb; no mysql CLI in image
- actions/cache@v4 unreliable (timeout) — skip in Plans 02/03
- Playwright WebKit deps install cleanly; Phase-7 harness CI-feasible
- ChristopherHX/gitea-upload-artifact@v4 confirmed; actions/upload-artifact@v4 broken
- ${GITHUB_SHA:0:7} valid for image tags
- STATE.md: plan 08-01 complete, position advanced to 08-02 (Wave 2)
- ROADMAP.md: 08 phase progress updated (1/4 plans complete)
- on: pull_request + push branches:[main]; workflow env MILESTONE: v1.1
- fast-checks job: runs-on ubuntu-latest, if pull_request
- Node 22 via actions/setup-node@v4 + corepack enable pnpm
- No actions/cache (D-PROBE-04: times out on this runner)
- pnpm install --frozen-lockfile, lint (no-op), typecheck, PWA unit tests
- DB-backed pnpm test intentionally absent from this job
feat(08-02): ci.yml api job with mariadb service
runner-probe / runner-probe (push) Successful in 1m47s
CI / fast-checks (pull_request) Failing after 22s
CI / api (pull_request) Failing after 26s
3343f36e97
- api job: runs-on ubuntu-latest, if pull_request, parallel with fast-checks (no needs:)
- services: mariadb:11 with healthcheck.sh --connect --innodb_initialized options
  (--health-start-period=30s for MariaDB 11 InnoDB cold-start, --health-retries=10)
- DB_HOST: mariadb (Docker-executor confirmed by D-PROBE-02)
- Throwaway creds: familysync/testpass scoped to ephemeral service container (T-08-03)
- No actions/cache (D-PROBE-04)
- Node mysql2 readiness poll via --input-type=commonjs inline script, 90s deadline
  (no mysql CLI in runner image per D-PROBE-03; Pitfall 11 belt-and-suspenders)
- db:migrate (drizzle-kit migrate); drizzle push never used (T-08-04, MariaDB unsafe)
- pnpm --filter @familysync/api test: full DB-backed API test suite
luckberg added 3 commits 2026-06-11 14:29:43 -04:00
0000_easy_slipstream already created lists/list_shares/list_items and the
calendars unique constraint, but 0001_lists_schema re-created those tables and
0001_calendars_user_url_unique was an orphan (not in _journal) — so a cold
`drizzle-kit migrate` against an empty DB failed with ERROR 1050 'Table lists
already exists'. Dev only survived because its DB was built incrementally; CI
is the first cold migrate and exposed it.

Regenerated a single 0000_baseline.sql from schema.ts. Verified on a fresh
mariadb:11: migrate succeeds, schema is structurally identical to the running
dev DB, `drizzle-kit generate` reports no drift, and all 238 API tests pass.
Local dev DBs must be rebuilt (drop + db:migrate); no prod exists.
pnpm -r lint exited 1 (ERR_PNPM_RECURSIVE_RUN_NO_SCRIPT) because no package
defines a lint script yet, failing the CI fast-checks job. Switch to
pnpm -r --if-present lint so it exits 0 today and gates automatically once a
lint script is added. (Lint tooling wiring remains out of scope.)
chore(08-01): probe to manual-only (workflow_dispatch)
CI / api (pull_request) Successful in 57s
CI / fast-checks (pull_request) Successful in 48s
0b148b96f8
Stops the throwaway probe re-running on every push and contending with
ci.yml on the single runner. Fork answers already captured in 08-01-SUMMARY.
luckberg added 4 commits 2026-06-11 14:43:14 -04:00
docs(08): correct premature completion — phase 8 + CI-01 still in progress
CI / api (pull_request) Successful in 57s
CI / fast-checks (pull_request) Successful in 48s
2a34a94cc6
Plans 08-03 (harness UI-regression job) and 08-04 (publish job) are not yet
built; CI-01's definition includes the harness step (08-03). Revert phase 8
and CI-01 from complete back to in-progress.
luckberg added 2 commits 2026-06-11 14:46:33 -04:00
- Add harness job to ci.yml (ubuntu-latest, pull_request, parallel with fast-checks + api)
- MariaDB 11 service container with healthcheck.sh readiness (same pattern as api job)
- mysql2 readiness poll (no mysql CLI in runner image, D-PROBE-03)
- db:migrate via drizzle-kit (never db:push, T-08-07)
- pnpm --filter @familysync/api build before starting (Pitfall 4)
- API background: DEV_AUTH_BYPASS=true inline on node line (Pitfall 8), NODE_ENV=development
- curl retry loop on localhost:3000/health, 60s deadline, kill+exit on timeout (D-02/T-08-08)
feat(08-03): add Playwright install + harness run (both profiles) + artifact upload
CI / fast-checks (pull_request) Successful in 48s
CI / api (pull_request) Successful in 56s
CI / harness (pull_request) Failing after 2m53s
71c89093b1
- npx playwright install --with-deps webkit chromium from apps/pwa/ (D-PROBE-05)
- pnpm test:e2e -- --reporter=list,html overrides github reporter (Pitfall 5; Gitea ignores annotations)
- Both iphone (WebKit) + pixel (Chromium) profiles run (D-05; no --project filter)
- Step env: CI=true, PLAYWRIGHT_BASE_URL=http://localhost:5173, DEV_AUTH_BYPASS=true, NODE_ENV=development, DB_*
- On failure: upload apps/pwa/test-results/ via ChristopherHX/gitea-upload-artifact@v4 (D-06/D-PROBE-06)
- Always: kill API background process via /tmp/api.pid
- No edits to playwright.config.ts / global-setup.ts / vite.config.ts / specs (phase boundary)
luckberg added 1 commit 2026-06-11 15:05:07 -04:00
fix(08-03): keep API alive during harness — start API + run e2e in one step
CI / fast-checks (pull_request) Successful in 49s
CI / api (pull_request) Successful in 56s
CI / harness (pull_request) Failing after 2m50s
53a989c3fb
Run #7 harness failed: global-setup polled :5173/health (Vite proxy → :3000)
and never got 200. The API connected to the DB and :3000/health was green
during the separate 'Wait for API' step, but the bare-backgrounded node
process was reaped at the step boundary and was dead by the time the e2e step
ran (after the multi-minute browser install). Confirmed locally the API does
not self-crash (alive + healthy for 75s in-shell).

Install browsers first, then start the API and run Playwright in a SINGLE step
so the API stays a child of the test shell for the whole run; capture the test
exit code and kill the API after. No harness files touched.
luckberg added 1 commit 2026-06-11 15:20:28 -04:00
fix(08-03): harness uses 127.0.0.1 + ipv4first — Vite is IPv4-only
CI / fast-checks (pull_request) Successful in 50s
CI / api (pull_request) Successful in 56s
CI / harness (pull_request) Failing after 1m49s
73897407c7
global-setup polls baseURL/health via Node fetch; the runner resolves localhost
to ::1 first, but Vite binds IPv4-only (127.0.0.1:5173), so localhost→::1:5173
→ ECONNREFUSED → health poll never returns 200. (curl passed for :3000 because
curl falls back IPv4↔IPv6 and the API is dual-stack; Node fetch does not.)
Proven: [::1]:5173 ECONNREFUSED vs 127.0.0.1:5173 200. Point PLAYWRIGHT_BASE_URL
at 127.0.0.1 and add --dns-result-order=ipv4first. No harness files changed.
luckberg added 1 commit 2026-06-11 15:36:56 -04:00
fix(08-03): seed dev user id=1 — global-setup assumes it exists
CI / fast-checks (pull_request) Successful in 48s
CI / api (pull_request) Successful in 56s
CI / harness (pull_request) Failing after 2m0s
e486c6be9f
global-setup.ts seeds calendars/lists/events for user_id=1 but never creates
the user (DEV_AUTH_BYPASS injects DEV_USER in-memory only). On a fresh CI DB the
calendars INSERT IGNORE is silently skipped on the users FK → calendar 10 missing
→ calendar_events insert fails FK. Add an idempotent users(id=1) seed after
migrate (validated locally: full insert chain passes). No harness files changed.
luckberg added 1 commit 2026-06-11 15:43:11 -04:00
fix(08-03): call pwa test:e2e directly so --reporter forwards cleanly
CI / fast-checks (pull_request) Successful in 49s
CI / api (pull_request) Successful in 1m0s
CI / harness (pull_request) Successful in 3m26s
03e8088238
Run #10 cleared global-setup but playwright errored 'No tests found':
`pnpm test:e2e -- --reporter=list,html` routes through two pnpm script layers
(root→pwa) and the `--` survives into `playwright test -- --reporter=list,html`,
where playwright treats --reporter as a test-file filter. Call the pwa script
directly and append the flag without `--` (validated: 58 specs list vs 0).
luckberg added 3 commits 2026-06-11 15:57:53 -04:00
- Run #11 (PR #3): 58 passed in 1.6 min (iphone/WebKit + pixel/Chromium)
- 4 infrastructure fixes: API-reap at step boundary, IPv4-first for Vite, dev-user FK seed, reporter double-forward via pnpm
- No Phase 7 harness file modified (phase boundary D-01/D-02 held)
- Advance position to 08-04 (publish job)
- Add publish job gated on push to refs/heads/main (never pull_request)
- docker login via --password-stdin with secrets.GITEA_REGISTRY_PAT (Pitfall 13)
- docker build --target production -f apps/api/Dockerfile . (repo-root context, T-08-10)
- Push :latest and :${MILESTONE}-${SHORT_SHA} tags per D-04
- docker logout in always() step to drop credential after push
- No dev-bypass flag in publish job (T-08-09 boundary)
chore(08): remove throwaway runner-probe workflow before merge to main
CI / fast-checks (pull_request) Successful in 50s
CI / api (pull_request) Successful in 56s
CI / harness (pull_request) Successful in 3m27s
CI / publish (pull_request) Has been skipped
dcf42423a5
Probe answers are recorded in 08-01-SUMMARY; the probe is no longer needed and
should not live on main (it was workflow_dispatch-only/inert anyway).
luckberg added 1 commit 2026-06-11 16:05:56 -04:00
fix(08-04): reference secrets.REGISTRY_PAT — Gitea reserves the GITEA_ prefix
CI / fast-checks (pull_request) Successful in 49s
CI / api (pull_request) Successful in 57s
CI / harness (pull_request) Successful in 3m30s
CI / publish (pull_request) Has been skipped
73eecf7559
The registry PAT secret had to be named REGISTRY_PAT (Gitea forbids the GITEA_
prefix for secret names). Point the publish docker-login at secrets.REGISTRY_PAT.
luckberg merged commit 98acff8108 into main 2026-06-11 16:11:42 -04:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: luckberg/familysync#3