feat(web): ping API ponta-a-ponta via TanStack Query + Zod contract

Fecha loop B+C — Web consome @sar/api-interface em runtime, não só build.

- Vite proxy /api → localhost:3000 (zero CORS em dev, mesma URL em prod via Nginx)
- api-client.ts: fetch wrapper parseando RFC 9457 problem+json em ApiError
- useApiPing: TanStack Query + PingResponseSchema.parse — drift servidor falha alto
- FoundationStatus pill na Topbar (verde/vermelho/cinza + Tooltip com requestId)

Validado via curl proxy:4200 → 200 ok contratual; /nope → 404 problem+json.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-27 19:14:40 +00:00
parent 4649289213
commit 29321f54c0
6 changed files with 273 additions and 1 deletions

View File

@@ -319,6 +319,30 @@
4. **Master-login stub + WorkspacePrismaPool** (frente arquitetural pesada — depende de #3).
5. **OpenTelemetry SDK** plugar quando entrar em catálogo (stub atual mantém posição).
### 2026-05-27 — Web→API ponta-a-ponta (loop B+C fechado) CONCLUÍDO ✅
- **Escopo da sessão:** Pendência #1 do roadmap — provar que `@sar/api-interface` é honrado pelos dois lados em runtime, não só em build time. Loop B (Web foundation) + C (Zod contracts) fechado.
- **Arquivos novos:**
- `apps/web/src/lib/api-client.ts` — fetch wrapper que parseia `application/problem+json` (RFC 9457) em `ApiError` estruturado carregando status+type+title+detail+requestId+errors[]. Sem validação Zod aqui — responsabilidade do caller (CODING-RULES §05).
- `apps/web/src/lib/queries/ping.ts``useApiPing()` TanStack Query chamando `/api/v1/ping` + `PingResponseSchema.parse(...)`. Drift servidor falha alto **antes** de chegar nos componentes. `refetchInterval: 30s` (Visual DNA "sereno").
- `apps/web/src/components/layout/FoundationStatus.tsx` — pill discreto na Topbar (verde/vermelho/cinza) com Tooltip detalhando service+version+workspaceId+requestId+uptime. Pulse `processing` no refetch silencioso. Conscientemente temporário — quando produto entrar em normal, vira indicador só em `/health`.
- **Modificados:**
- `apps/web/vite.config.mts``server.proxy['/api']: http://localhost:3000`. Evita CORS em dev e mantém URL relativa no código da Web; em produção, Nginx roteia mesmo origin. `changeOrigin: false` (mesma host).
- `apps/web/src/components/layout/Topbar.tsx``<FoundationStatus />` antes do sino.
- **Validação ponta-a-ponta:**
- `nx run web:lint` ✅ · `nx run web:build` ✅ (821ms, 309KB gzip)
- `curl :4200/api/v1/ping` via proxy Vite → `200 application/json` com payload contratual completo (`status=ok`, `workspaceId=dev-workspace`, `requestId`, `uptimeSeconds=144`, `now`)
- `curl :4200/api/v1/nope``404 application/problem+json` com `type/title/detail/instance/requestId` — prova que `ApiError` captura erro estruturado quando servidor falhar
- Headers helmet, x-request-id, CORS expose-headers passam pelo proxy intactos
- **Decisão arquitetural confirmada:** Web consome lib `@sar/api-interface` sem arrastar nada do Nest. `PingResponseSchema` viaja como Zod puro; `ApiError` na Web não conhece `HttpException` do Nest — só o contrato HTTP+JSON. Alinhamento com regra "lib stays framework-free" da sessão anterior.
- **Pegadinhas notadas (não bloquearam):**
- `_sidebarOpen` no AppShell ainda tem `eslint-disable` — fica pra Frente D ou primeiro responsivo mobile.
- Bundle Web 976KB (309KB gzip) — code-splitting esperado quando rotas de cockpit virarem separadas (TanStack Router suporta nativo). Não age agora.
- **Pendente próxima sessão (ordem atualizada):**
1. **Frente D — ESLint boundaries** (tags Nx `scope:* · type:* · domain:*`) + Husky + gitleaks. Higiene de PR antes de feature pesada.
2. **PRD WDS** via `/bmad-prd create` antes de modelar domínio.
3. **Master-login stub + WorkspacePrismaPool** (frente arquitetural — depende do PRD).
4. **OpenTelemetry SDK** plugar quando entrar no catálogo.
---
## About This Folder