chore(canary-phase14): Frontend API client complete

Phase 14 deliverables (audited PASS):
- Zod 4 schemas mirror backend handler/DTO byte-shapes (verified
  against backend/internal/token/{handler,dto,types}.go,
  backend/internal/event/{dto,entity}.go, and
  backend/internal/middleware/turnstile.go — not just the spec).
- api/types/token.ts: tokenResponseSchema, manageTokenViewSchema,
  artifactSchema (z.discriminatedUnion on kind), typeDescriptorSchema,
  createTokenRequestSchema with superRefine for telegram/webhook
  required_if semantics, per-type metadata helpers.
- api/types/event.ts: eventResponseSchema (with GeoView, NotifyStatus
  enum, nullable user_agent/referer/notified_at) and
  manageResponseSchema covering cursor pagination.
- api/types/error.ts: apiErrorEnvelopeSchema + successEnvelope<T>
  factory; single source of truth for envelope shape.
- core/api/errors.ts refactored: ApiError carries canary error codes
  (VALIDATION_ERROR, TURNSTILE_FAILED, BAD_CURSOR, BAD_PARAM, ...)
  and a Record<string,string> fields map. No more FastAPI-style
  parsing. Module augmentation flows ApiError as defaultError to all
  TanStack hooks. Obsolete template api.config.ts removed.
- api/client.ts: axios instance over VITE_API_URL (fallback /api),
  request interceptor adds CF-Turnstile-Response header from
  pluggable provider when present, response interceptor routes axios
  errors through Zod-backed transformAxiosError -> ApiError.
  apiGet/apiPost/apiDelete typed via ZodType<T>; PARSE_ERROR carries
  the failing field path + message for debuggability.
- 4 hooks: useTokenTypes (static, no polling), useCreateToken
  (invalidates manage on success), useManageToken (URI-encoded id,
  splayed cursor/limit query key, frequent polling), useDeleteToken
  (removeQueries on success).

Implicit contract documented here for the next AI: the
cf_turnstile_response body field is intentionally optional in
createTokenRequestSchema because backend middleware/turnstile.go
reads the CF-Turnstile-Response HEADER first (set via the request
interceptor). Operators wiring the Turnstile widget should call
setTurnstileTokenProvider(() => widgetToken) on app init; the form
need not carry the token in the body.

Audited: superpowers:code-reviewer + general-purpose spec adherence
— PASS on first dispatch. Audit findings S-1/S-3/S-4/S-7 fixed
pre-rollup in 7c0a504d. I-1 (config_schema gap in
backend TypeDescriptor — spec stale vs code) and I-2 (turnstile
body-field optional) flagged for future phases; not Phase 14 issues.

OPERATOR PAUSE BOUNDARY (spec §16.3, plan line 2314). No UI/
component/page/styling code in this phase. Phase 15 resumes with
infrastructure (nginx + telemetry).
This commit is contained in:
CarterPerez-dev 2026-05-17 13:04:03 -04:00
parent 7c0a504d0b
commit 37cdf97133

Diff Content Not Available