feat(client): probe /api/health on server-setup save (#236) #382

Merged
james merged 1 commit from 236-health-probe into main 2026-06-29 18:11:33 +00:00
Owner

Closes #236

The server-setup screen only checked that the URL was syntactically http(s). A wrong address surfaced later as a confusing login failure. Now, after the syntactic check and before saving, the screen probes the server and confirms it actually looks like Carol.

What changed

  • lib/serverUrlProbe.ts (new): pure helper with injectable fetch. GETs <url>/api/health with a 5s AbortSignal.timeout. Success = HTTP 200 and a JSON body carrying string status + version (HealthDto's signature). A throw/abort → unreachable; a non-200 or wrong-shaped body → notCarol.
  • app/server-setup.tsx: probe runs between the syntactic check and the save; inline ActivityIndicator spinner + disabled submit while probing; distinct error copy per failure reason.
  • i18n: serverSetup.errors.unreachable / serverSetup.errors.notCarol in Carol's voice (verified they resolve relative to the screen's namespace).
  • Unit tests (9): happy path, network throw, AbortError, timeout, 404, 503, wrong body, unparseable JSON.

Verification

  • typecheck / lint clean, 181 tests pass, semgrep 0, en.json valid.
  • Not verifiable headlessly: the submit spinner + navigation-on-success need a device/browser visual check (client vitest is node-env, no RN render).

Merge note

Overlaps #239 on apps/client/app/server-setup.tsx (this PR changes its logic; #239 scrubs its style arrays). Merge this first; #239 rebases cleanly on top.

🤖 Generated with Claude Code

Closes #236 The server-setup screen only checked that the URL was syntactically `http(s)`. A wrong address surfaced later as a confusing login failure. Now, after the syntactic check and before saving, the screen probes the server and confirms it actually looks like Carol. ## What changed - **`lib/serverUrlProbe.ts`** (new): pure helper with injectable `fetch`. GETs `<url>/api/health` with a 5s `AbortSignal.timeout`. Success = HTTP 200 **and** a JSON body carrying string `status` + `version` (HealthDto's signature). A throw/abort → `unreachable`; a non-200 or wrong-shaped body → `notCarol`. - **`app/server-setup.tsx`**: probe runs between the syntactic check and the save; inline `ActivityIndicator` spinner + disabled submit while probing; distinct error copy per failure reason. - **i18n**: `serverSetup.errors.unreachable` / `serverSetup.errors.notCarol` in Carol's voice (verified they resolve relative to the screen's namespace). - **Unit tests** (9): happy path, network throw, AbortError, timeout, 404, 503, wrong body, unparseable JSON. ## Verification - typecheck / lint clean, 181 tests pass, semgrep 0, en.json valid. - **Not verifiable headlessly**: the submit spinner + navigation-on-success need a device/browser visual check (client vitest is node-env, no RN render). ## Merge note Overlaps **#239** on `apps/client/app/server-setup.tsx` (this PR changes its logic; #239 scrubs its style arrays). Merge this first; #239 rebases cleanly on top. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(client): probe /api/health on server-setup save (#236)
All checks were successful
PR / Lint (pull_request) Successful in 3m29s
PR / Package age policy (soft) (pull_request) Successful in 48s
PR / Typecheck (pull_request) Successful in 4m2s
Secrets / gitleaks (pull_request) Successful in 50s
PR / Test (sqlite) (pull_request) Successful in 4m35s
PR / Test (postgres) (pull_request) Successful in 5m26s
PR / Build (pull_request) Successful in 5m30s
PR / Coverage (soft) (pull_request) Successful in 2m37s
Commits / Conventional Commits (pull_request) Successful in 8s
PR / E2E (Playwright) (pull_request) Successful in 6m9s
PR / Trivy (image) (pull_request) Successful in 3m20s
PR / Static analysis (pull_request) Successful in 2m23s
PR / OpenAPI (pull_request) Successful in 2m53s
PR / Client (web export smoke) (pull_request) Successful in 3m3s
PR / pnpm audit (pull_request) Successful in 3m4s
PR / OSV-Scanner (pull_request) Successful in 53s
df4fbc9625
The server-setup screen only validated the URL was syntactically
http(s); a wrong address surfaced later as a confusing login error.
After the syntactic check and before saving, probe the server.

- lib/serverUrlProbe.ts: pure, injectable-fetch helper. GET <url>/api/health
  with a 5s AbortSignal.timeout; success = 200 + a body carrying string
  `status` + `version` (HealthDto's signature). A throw/abort → unreachable;
  non-200 or wrong body → notCarol.
- server-setup.tsx: probe between the syntactic check and the save;
  inline spinner + disabled submit while probing; distinct error copy.
- i18n: serverSetup.errors.{unreachable,notCarol} (Carol's voice).
- Unit tests: happy path + network-throw + abort/timeout + non-200 +
  wrong/unparseable body.

Closes #236

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

📊 Test coverage

Patch coverage: no testable lines changed.

Overall (app/, lib/, db/, excluding UI per ADR-0019):

Metric Value Soft target
Lines 79.6% ≥ 50%
Branches 71.5% ⚠️ ≥ 75%
Functions 80.8% informational

Soft thresholds per ADR-0019. Coverage is informational and does not block merge.

<!-- coverage-comment --> ## 📊 Test coverage **Patch coverage:** no testable lines changed. **Overall** (`app/`, `lib/`, `db/`, excluding UI per ADR-0019): | Metric | Value | Soft target | |---|---|---| | Lines | 79.6% ✅ | ≥ 50% | | Branches | 71.5% ⚠️ | ≥ 75% | | Functions | 80.8% | informational | Soft thresholds per [ADR-0019](docs/adr/0019-coverage-soft-targets.md). Coverage is informational and does not block merge.
james merged commit 0e11d37177 into main 2026-06-29 18:11:33 +00:00
james deleted branch 236-health-probe 2026-06-29 18:11:33 +00:00
Sign in to join this conversation.
No description provided.