Test Connection endpoint validates provider API keys before saving
`3fb09bb3` adds a `POST /user/test-connection` backend route that fires a minimal live request against a user's API key before it's committed, and rewrites the account models page into a provider-card "Connect AI Account" flow. The test route is the reusable piece; the page rewrite is Gary-specific styling.
The backend route lives in backend/src/routes/user.ts (~60 new lines). It accepts a provider and optional apiKey from the request body. If no key is supplied, it falls back to the user's saved keys or environment-configured keys for that provider. It normalizes the provider name, picks the smallest available model per provider (claude-haiku-4-5 for Anthropic, gpt-5.4-nano for OpenAI, gemini-3.1-flash-lite-preview for Google), and sends a single completeText call with a 20-token budget and a prompt asking for exactly "Connection successful." If the response matches, it returns { ok: true }. On error, it logs the provider and error message with the user id (no key value in the log) and returns the error message to the client.
The frontend side rewrites frontend/src/app/(pages)/account/models/page.tsx from a settings form into a provider-card layout with a Test Connection button per provider. A new helper in frontend/src/app/lib/mikeApi.ts calls the endpoint. The setup guide was updated to document the four-step flow: choose provider, get key, paste and test, save only after a green result.
A few things worth checking if you're considering adopting the test endpoint pattern. The route sends the key to the provider for a live completion, which means it incurs a small cost against the user's account. The log entry confirms the key value is not logged - only the userId and error message. There's no rate limiting on the test endpoint in the diff; a user could call it repeatedly against their own key or an invalid key without restriction. That's worth adding if your deployment is multi-user. The model names hardcoded in the route will need updating as providers retire and rename models.
Spotted something wrong? Or know the PR text has fresher detail than the writeup above?