Use a concise sentence-case title that describes the work item.
Summary
Lift messageForApiError out of FeedbackSubmitForm.tsx into a shared helper and converge the four other feedback call sites on it, so a server-supplied error message reaches the user instead of being replaced by generic copy.
Problem / Context
apiClient rejects with a plain { success: false, error: "..." } object, not an Error (app/src/services/apiClient.ts:109-111,126,130). Every err instanceof Error ? err.message : t(...) against it therefore takes the fallback branch unconditionally — the server's message is discarded 100% of the time, and the user is told "Something went wrong. Please try again." when the backend explained exactly what was wrong.
#5431 fixed this for the submit form with a file-local messageForApiError, which matters there because the backend quality gate returns the reason a draft was refused. The same pattern is still live at four more sites on the same surface, all going through the same apiClient:
app/src/components/feedback/FeedbackComments.tsx:56 — comment load
app/src/components/feedback/FeedbackComments.tsx:78 — comment post
app/src/components/feedback/FeedbackAdminMenu.tsx:42 — admin status update
app/src/pages/Feedback.tsx:82 — board load
Raised in review on #5431 and deliberately left out of that PR as scope. Left alone, each site grows its own copy of the fix.
Scope (optional)
In scope: a single exported helper (services-level, next to apiClient, since the shape it decodes is apiClient's), the four call sites above, and unit coverage for both rejection shapes.
Not in scope: changing what apiClient throws. Making it reject with a real Error would be the deeper fix, but it is a cross-cutting change to every consumer in the app, not a feedback-surface cleanup — worth its own issue if wanted.
Note the helper must keep the fallback for a blank or missing error field, and must not surface a raw payload dump — only the error string.
Acceptance criteria
Related
Use a concise sentence-case title that describes the work item.
Summary
Lift
messageForApiErrorout ofFeedbackSubmitForm.tsxinto a shared helper and converge the four other feedback call sites on it, so a server-supplied error message reaches the user instead of being replaced by generic copy.Problem / Context
apiClientrejects with a plain{ success: false, error: "..." }object, not anError(app/src/services/apiClient.ts:109-111,126,130). Everyerr instanceof Error ? err.message : t(...)against it therefore takes the fallback branch unconditionally — the server's message is discarded 100% of the time, and the user is told "Something went wrong. Please try again." when the backend explained exactly what was wrong.#5431 fixed this for the submit form with a file-local
messageForApiError, which matters there because the backend quality gate returns the reason a draft was refused. The same pattern is still live at four more sites on the same surface, all going through the sameapiClient:app/src/components/feedback/FeedbackComments.tsx:56— comment loadapp/src/components/feedback/FeedbackComments.tsx:78— comment postapp/src/components/feedback/FeedbackAdminMenu.tsx:42— admin status updateapp/src/pages/Feedback.tsx:82— board loadRaised in review on #5431 and deliberately left out of that PR as scope. Left alone, each site grows its own copy of the fix.
Scope (optional)
In scope: a single exported helper (services-level, next to
apiClient, since the shape it decodes isapiClient's), the four call sites above, and unit coverage for both rejection shapes.Not in scope: changing what
apiClientthrows. Making it reject with a realErrorwould be the deeper fix, but it is a cross-cutting change to every consumer in the app, not a feedback-surface cleanup — worth its own issue if wanted.Note the helper must keep the fallback for a blank or missing
errorfield, and must not surface a raw payload dump — only theerrorstring.Acceptance criteria
messageForApiError(err, fallback)lives in one place, handles theErrorshape, the{ success, error }shape, and falls back on a blank or absent message.FeedbackSubmitForm.tsxuse it; noinstanceof Errorcheck against anapiClientrejection remains on the feedback surface..github/workflows/ci-lite.yml) when code changes are involved.Related