refactor(services): unify API error handling behind one mapper and request helper master
The API service had grown to 1,619 lines of per-endpoint boilerplate, and the error taxonomy had forked three ways: coves_api_service's _handleDioException, hand-rolled DioException switches in vote_service and comment_service, and a dead ApiException.fromDioError that only tests used and that disagreed with all of them. Worst, the 401-refresh interceptor existed twice: the shared copy in auth_interceptor.dart logged error response bodies unredacted while the inline copy in coves_api_service redacted them because bodies can echo credentials. - ApiException.fromDioError is now the single canonical DioException mapper (production _handleDioException semantics: JSON/plain-text message extraction, 401/404/5xx typing, timeout/DNS/connection typing), preferring the human-readable XRPC `message` over the `error` code. mapDioException wraps it with redacted debug logging. All three services delegate to it; _handleDioException is deleted. - createAuthInterceptor is now token-based and the only 401-refresh implementation: CovesApiService's ~145-line inline copy is deleted, the refresh-endpoint sign-out guard is preserved for all callers, and the shared copy now redacts logged response bodies (closing the credential leak). - New _request<T>() helper runs every CovesApiService endpoint through one try/catch, so endpoints are just query/body construction plus a parse callback (file drops from 1,619 to 970 lines). This also fixes endpoints that swallowed their own ApiExceptions into generic messages (e.g. subscribeToCommunity). Parse failures uniformly become ApiException('Failed to parse server response') — services no longer leak FormatException. - redactBearerTokens moves to log_redaction.dart so the interceptor and exception mapper can share it. - The mapper tests move from vote_service_test.dart to a dedicated api_exceptions_test.dart covering the canonical semantics. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>