Address audit findings S-1, S-3, S-4, S-7 pre-rollup (fix-in-phase).
S-1: Eliminate hand-rolled envelope error parser duplication.
Moved transformAxiosError out of core/api/errors.ts (where it
required a private parseEnvelopeError mirror of the Zod schema)
into api/client.ts where it now uses apiErrorEnvelopeSchema directly
via safeParse. core/api/errors.ts is now ApiError class + code
constants + module augmentation only, with no canary-envelope
knowledge — keeps layering core <- universal, api <- canary clean.
S-3: useManageToken query key now splays cursor/limit individually
instead of carrying the params object reference. Without this,
callers passing a freshly-allocated { cursor } each render would
miss the cache even with identical values.
S-4: unwrapEnvelope's PARSE_ERROR now includes the first Zod issue
(path + message) in the ApiError.message so operators see WHICH
field shape failed, not just that something did.
S-7: Add request interceptor rejection handler so request-phase
errors propagate via Promise.reject instead of throwing
synchronously.
- api/client.ts: axios instance with baseURL from VITE_API_URL env
(falls back to /api) and 15s timeout.
- Request interceptor adds CF-Turnstile-Response header from a
pluggable provider (setTurnstileTokenProvider). Backend's
middleware/turnstile.go reads header first then body field; using
the header keeps the request body shape clean and matches the plan.
- Response interceptor routes axios errors through transformAxiosError
(canary envelope-aware) so callers see typed ApiError everywhere.
- Envelope-aware helpers apiGet / apiPost / apiDelete:
- apiGet/apiPost unwrap {success:true,data:T} via successEnvelope
factory; on shape mismatch throw ApiError(PARSE_ERROR).
- apiDelete returns void for 204 responses; envelope error path
handled by the interceptor.
- Plumbed into api/index.ts barrel.