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

Closed
opened 2026-06-23 12:06:47 +00:00 by james · 0 comments
Owner

Context

The /server-setup screen on native (Android, Tauri shell) validates that the entered server URL is a well-formed http(s)://<host> string before saving (lib/serverUrlValidate.ts), but it doesn't actually probe whether the URL points at a running Carol instance. The reachability test happens implicitly on the next login attempt — by which point the user has filed away a wrong URL and the error surfaces as a confusing "Network request failed" or "Invalid credentials" rather than "this isn't Carol."

The original RN scaffolding ticket (#29) called for hitting /api/health before saving the URL, with a clear error on failure. That criterion didn't land in the #207 implementation and was acknowledged by the implementing agent in the PR body. Closing #29 leaves this gap orphaned, so it lives here.

Source

#29 acceptance criterion 2: "An invalid or unreachable URL produces a clear error and isn't saved."

Scope

In apps/client/app/server-setup.tsx (or a sibling validator):

  • After the syntactic http(s):// check passes, before saving to SecureStore, issue a GET <url>/api/health with a short timeout (5s default; configurable in code if needed for slow LANs).
  • Treat a 200 JSON response with { status: "ok" } (or whatever /api/health currently returns — read it, don't guess) as success.
  • Treat any of these as a save-blocking error with a distinct user-facing message:
    • Network error (DNS, connection refused, timeout) → "Can't reach that URL. Check the address and that the server is running."
    • Non-200 status → "That URL replied, but doesn't look like Carol. Check you've copied the address from the right place."
    • 200 but wrong / unparseable body → same "doesn't look like Carol" message.
  • The submit button should show an inline spinner while probing; disabled during the probe.
  • Translated strings via react-i18next. Add new keys under serverSetup.* (or whichever namespace the screen already uses).
  • DS tokens via useTheme().

/api/health is in the public-routes allowlist (apps/api/lib/auth/public-routes.ts), so the probe doesn't need auth — perfect for first-launch validation.

Acceptance criteria

  • Submitting a syntactically valid but unreachable URL (typo, server down, wrong port) shows a "can't reach" error and doesn't store the URL.
  • Submitting a syntactically valid URL that responds with non-Carol content shows a "doesn't look like Carol" error and doesn't store the URL.
  • Submitting a valid Carol URL passes the probe, stores, and navigates to /login as today.
  • The probe times out at 5 seconds (or configurable) so a hung server doesn't lock the form indefinitely.
  • The submit button is disabled + spinning during the probe.
  • Unit tests cover all three reject paths and the happy path against a mocked fetch.

Out of scope

  • TLS certificate validation (handled by the platform's fetch, not Carol).
  • Auto-correcting common URL mistakes (trailing slashes, missing protocols).
  • Probing on URL change in the Account screen's "Change server" flow — that flow already exists; if a user wants the same protection there, it's a small follow-up (or fold into this ticket if you want).

Composes with

  • #187 — original Android pipeline + runtime URL plumbing.
  • #214/api/health surface decisions (this ticket relies on the endpoint's response shape).

Part of

Standalone — no parent epic.

## Context The `/server-setup` screen on native (Android, Tauri shell) validates that the entered server URL is a well-formed `http(s)://<host>` string before saving (`lib/serverUrlValidate.ts`), but it doesn't actually probe whether the URL points at a running Carol instance. The reachability test happens implicitly on the next login attempt — by which point the user has filed away a wrong URL and the error surfaces as a confusing "Network request failed" or "Invalid credentials" rather than "this isn't Carol." The original RN scaffolding ticket ([#29](https://forge.wynning.tech/james/carol/issues/29)) called for hitting `/api/health` before saving the URL, with a clear error on failure. That criterion didn't land in the [#207](https://forge.wynning.tech/james/carol/pulls/207) implementation and was acknowledged by the implementing agent in the PR body. Closing #29 leaves this gap orphaned, so it lives here. ## Source [#29](https://forge.wynning.tech/james/carol/issues/29) acceptance criterion 2: "An invalid or unreachable URL produces a clear error and isn't saved." ## Scope In `apps/client/app/server-setup.tsx` (or a sibling validator): - After the syntactic `http(s)://` check passes, before saving to SecureStore, issue a `GET <url>/api/health` with a short timeout (5s default; configurable in code if needed for slow LANs). - Treat a 200 JSON response with `{ status: "ok" }` (or whatever `/api/health` currently returns — read it, don't guess) as success. - Treat any of these as a save-blocking error with a distinct user-facing message: - Network error (DNS, connection refused, timeout) → `"Can't reach that URL. Check the address and that the server is running."` - Non-200 status → `"That URL replied, but doesn't look like Carol. Check you've copied the address from the right place."` - 200 but wrong / unparseable body → same "doesn't look like Carol" message. - The submit button should show an inline spinner while probing; disabled during the probe. - Translated strings via `react-i18next`. Add new keys under `serverSetup.*` (or whichever namespace the screen already uses). - DS tokens via `useTheme()`. `/api/health` is in the public-routes allowlist (`apps/api/lib/auth/public-routes.ts`), so the probe doesn't need auth — perfect for first-launch validation. ## Acceptance criteria - [ ] Submitting a syntactically valid but unreachable URL (typo, server down, wrong port) shows a "can't reach" error and doesn't store the URL. - [ ] Submitting a syntactically valid URL that responds with non-Carol content shows a "doesn't look like Carol" error and doesn't store the URL. - [ ] Submitting a valid Carol URL passes the probe, stores, and navigates to `/login` as today. - [ ] The probe times out at 5 seconds (or configurable) so a hung server doesn't lock the form indefinitely. - [ ] The submit button is disabled + spinning during the probe. - [ ] Unit tests cover all three reject paths and the happy path against a mocked fetch. ## Out of scope - TLS certificate validation (handled by the platform's fetch, not Carol). - Auto-correcting common URL mistakes (trailing slashes, missing protocols). - Probing on URL change in the Account screen's "Change server" flow — that flow already exists; if a user wants the same protection there, it's a small follow-up (or fold into this ticket if you want). ## Composes with - [#187](https://forge.wynning.tech/james/carol/issues/187) — original Android pipeline + runtime URL plumbing. - [#214](https://forge.wynning.tech/james/carol/issues/214) — `/api/health` surface decisions (this ticket relies on the endpoint's response shape). ## Part of Standalone — no parent epic.
james closed this issue 2026-06-29 18:11:33 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
james/carol#236
No description provided.