Files
HealthLog/.planning/phase-B2-report.md
T
Marc-André BombeckandClaude Opus 4.7 395129a18c docs(planning): file v1.4.18 reconcile + v1.4.19 backlog + v1.5 update
Wave-D reconcile report at .planning/phase-D-reconcile-report.md;
deferred items seeded to .planning/v1419-backlog.md; strategic
v1.5 items appended to .planning/v15-backlog.md from the v1.4.18
Product-Lead review. STATE.md marks Wave D done.

Prettier sweep also touched the planning markdown table widths;
no content change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 11:09:41 +02:00

7.8 KiB

Phase B2 — AI provider settings UX cleanup (Pulldown-driven, v1.4.16)

Completed 2026-05-10 ~01:15 CEST on agent/b2-ai-provider-ux worktree.

Previous v1.4.15 phase-B2 report (Withings + moodLog sync robustness) superseded — that work shipped under tag v1.4.15 already and lives in docs/audit/v1415-summary.md.

What landed

Four atomic TDD-first commits on agent/b2-ai-provider-ux, ready to fast-forward into origin/main:

# Commit What it ships
1 feat(api): PUT /api/insights/provider-chain persists user fallback chain New PUT writes the chain to User.aiProviderChain. Server-side priority is recomputed from insertion order so the UI's visual contract ("first row = priority 1") cannot drift from what gets persisted. Unknown provider types and duplicates 422; an empty chain is rejected outright. parseProviderChain() still falls back to the default chain when the persisted JSON is malformed, so a stale-tab resubmit cannot poison generation. 5 unit tests pin the contract.
2 feat(settings): AI provider section with single dropdown driving form below The big one. Single Pulldown (ai-active-provider-select) drives a switch-rendered config card below: Codex (connect/disconnect/status + last-insight + CODEX_MODEL note), OpenAI (API key + model select + collapsed Base URL override), Anthropic (API key + model), Local (Base URL + key + model), Admin OpenAI (read-only). Fallback chain card with up/down arrow reorder + per-row enable Switch + remove + add + reset-to-defaults. URL-synced via ?provider=… so deep links work and the SSR test drives each branch deterministically.
3 test(insights): integration coverage for PUT /api/insights/provider-chain Two scenarios pinned end-to-end against the postgres testcontainer: saving a valid reordered chain persists the JSON column with priority normalised; saving an empty chain rejects with 422 + leaves the column untouched.
4 test(e2e): AI provider dropdown switches the rendered config form Playwright spec confirms the dropdown switches every config form (Codex / OpenAI / Anthropic / Local) and the fallback chain card surfaces every row with the right data-chain-row markers. Mocks every backend endpoint the section reads so no real keys are needed in CI.

Acceptance criteria mapping

  • #1 New section component layout — <AiSection> is the single card; <ActiveProviderSelect> is the Pulldown; <ProviderConfigCard> switch-renders one of five forms; <FallbackChainCard> is the chain manager with arrow controls (no new dependency — dnd-kit is not in package.json).
  • #2 Codex form — <CodexProviderForm> shows connect / disconnect / status badge / last-insight / CODEX_MODEL slug note, reusing the unchanged device-code flow + OAuth callback handler.
  • #3 OpenAI form — <OpenAIProviderForm> has masked API-key input, model dropdown (gpt-4o, gpt-4o-mini, gpt-4-turbo, Custom slug…), and a collapsed Base-URL override (advanced).
  • #4 Fallback chain UI — arrow up/down reorders, per-row Switch enables/disables, X removes, "+ Add Provider" picks from configured-but-not-in-chain providers, "Reset to defaults" with confirm dialog, "Save chain order" calls the new PUT.
  • #5 i18n keys — EN + DE keys land under settings.ai.activeProvider*, settings.ai.providerSelect.*, settings.ai.providerConfigTitle, settings.ai.providerChain.{title, description, moveUp, moveDown, removeFromChain, addProvider, saveOrder, saved, saveFailed, resetDefaults, resetConfirm*}, settings.ai.openai.{modelSelect, modelOptionCustom, modelCustomLabel, baseUrl*, showAdvanced, hideAdvanced, apiKey, apiKeyPlaceholder}, settings.ai.codex.{statusConnected, statusDisconnected, statusExpired, connectButton, disconnectButton, modelSlugLabel, modelSlugBody, lastInsight}, settings.ai.adminOpenai.{title, body, notConfigured}, settings.ai.{testProvider, testSuccess, testFailedShort}. The i18n parity guard test (src/lib/__tests__/i18n*.test.ts) is green.
  • #6 Tests — 5 new PUT route tests + 8 new component SSR tests
    • 2 integration tests + 1 Playwright spec (covers form-switch contract + chain row markers). The existing <AiSection> smoke test in sections.test.tsx still passes (mock now exposes useSearchParams).

Verification

  • pnpm test — 1316 / 1316 unit tests pass (was 1299 → +17 net).
  • pnpm test:integration — 55 / 55 integration tests pass (was 53 → +2 for the new PUT integration tests).
  • pnpm typecheck — 0 errors.
  • pnpm lint — 12 pre-existing warnings / 0 errors. The react-hooks/set-state-in-effect lint required a defensive pattern: the dropdown re-seeds on chain-data arrival via a render-time seededFor marker rather than a useEffect setter.

Notes for B5c / future phases

  • The new ai-section.tsx no longer imports <ProviderChainSummary> (the read-only summary panel B5b shipped). Its functionality is fully absorbed by <FallbackChainCard>. No external references remain — verified via grep -r "ProviderChainSummary".
  • The chain PUT endpoint deduplicates and 422s on unknown provider types, so when B5c adds a new provider tag it must extend PROVIDER_CHAIN_TYPES in both provider-chain.ts (server) and the client-side PROVIDER_TYPES array in ai-section.tsx.
  • The aiProvider legacy column is updated in lock-step with the chain so the v1.4.x single-result resolveProvider() (still used by weight-status.ts, mood-status.ts, etc.) sees the same selection. When B5c migrates those last consumers off the legacy resolver, the OPENAI / ANTHROPIC / LOCAL save-mutation can drop the aiProvider write and rely on the chain alone.
  • E2E spec uses data-chain-row + data-testid markers so the assertions are locale-independent. Reuse the same convention when B5c adds the per-recommendation explainability card.

Worktree

Branch agent/b2-ai-provider-ux (4 commits ahead of origin/main at gate 8352c6d). Push + fast-forward to main is the next step.