You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The app throws the code away. ErrorDto in :core:network models only { "error": ... }, and safeApiCall maps HTTP status → AppError, with the AppError subclasses in :core:common
carrying just a message.
safeApiCall currently string-sniffs. It decides SubscriptionRequired with a message.contains("subscription") heuristic. That is a user-visible behaviour hanging off
English prose in a server response — it breaks the day the copy is reworded.
What to do
Add code to ErrorDto.
Widen AppError (or add a typed code alongside the message) so call sites can branch on the
server's own vocabulary.
Replace the contains("subscription") heuristic with the real code.
Fold :feature:ai's module-local AiError back onto the shared type once it can express quota_exceeded / rate_limited (+ Retry-After) / no_provider_configured.
Why it is filed rather than done
It touches every AppError construction site across all feature modules, so it needs a quiet
moment with no parallel branches in flight — otherwise it conflicts with everything. Best done
after the current parity wave merges.
Observed codes so far:bad_request (rejected viewingPreference), unauthorized
(GET /api/auth/accounts to a bearer token), plus quota_exceeded, rate_limited and no_provider_configured documented at https://interlinedlist.com/help/api/ai-integration.
Discovered while implementing #33, and it affects every feature module — filing it rather than
smuggling a repo-wide refactor into an unrelated PR.
The problem
Every InterlinedList API error comes back as:
{ "error": "<human message>", "code": "<snake_case_code>" }The app throws the
codeaway.ErrorDtoin:core:networkmodels only{ "error": ... }, andsafeApiCallmaps HTTP status →AppError, with theAppErrorsubclasses in:core:commoncarrying just a
message.Two concrete costs:
:feature:aihad to define its own module-localAiError/AiResult(see AI: status, gating and quota plumbing (:feature:aiscaffold) #4 / PR feat(ai): :feature:ai scaffold — status, gating, quota and preview→confirm (#4) #92) purely because the sharedAppErrorcannot tellquota_exceededfromrate_limited— both are HTTP 429 — norno_provider_configuredfrom ageneric conflict. Every future module with meaningful error codes will face the same fork.
safeApiCallcurrently string-sniffs. It decidesSubscriptionRequiredwith amessage.contains("subscription")heuristic. That is a user-visible behaviour hanging offEnglish prose in a server response — it breaks the day the copy is reworded.
What to do
codetoErrorDto.AppError(or add a typedcodealongside the message) so call sites can branch on theserver's own vocabulary.
contains("subscription")heuristic with the real code.:feature:ai's module-localAiErrorback onto the shared type once it can expressquota_exceeded/rate_limited(+Retry-After) /no_provider_configured.Why it is filed rather than done
It touches every
AppErrorconstruction site across all feature modules, so it needs a quietmoment with no parallel branches in flight — otherwise it conflicts with everything. Best done
after the current parity wave merges.
Observed codes so far:
bad_request(rejectedviewingPreference),unauthorized(
GET /api/auth/accountsto a bearer token), plusquota_exceeded,rate_limitedandno_provider_configureddocumented athttps://interlinedlist.com/help/api/ai-integration.