feat(jobs): render structured job-error bodies on submit and /my-jobs#218
Merged
Conversation
Implements both rendering paths described in the modelseed-api repo's
docs/JOB_ERROR_UI_INTEGRATION.md so users see actionable failures instead
of a bare "Failed" badge or a "modelseed-api ... failed (404)" stub.
Path 1 (sync 4xx on submit): modelseedFetch now throws ModelseedApiError
carrying { status, detail } parsed from FastAPI's `{ detail: {...} }`
body. The reconstruct, FBA, gapfill, and merge submit handlers route
errors through presentJobSubmitError() and a new JobSubmitErrorAlert
that renders message + hint + affected-input. TOKEN_EXPIRED triggers
useTokenExpiredRedirect() to clear auth and bounce to the home page
with `?reason=token_expired` so a one-time "Session expired" notice
greets the user on the sign-in form.
Path 2 (async job.error on /my-jobs): the Failed chip is now clickable
with a tooltip preview, and a new FailedJobDetailsDialog shows the full
error string, the last progress note, parameters, and a deep link to
the targeted model. The info-icon action button opens the same dialog.
Tests: 11 new unit tests cover ModelseedApiError parsing and the
presentJobSubmitError helper; 6 new Playwright e2e tests cover the
sync-4xx alert, the failed-job dialog (chip + icon entry points), and
the session-expired notice. playwright.config.ts now reads
PLAYWRIGHT_PORT / PLAYWRIGHT_BASE_URL so tests can target a dev server
on a non-default port.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements both rendering paths from modelseed-api/docs/JOB_ERROR_UI_INTEGRATION.md so users see actionable failures instead of a bare "Failed" badge or a
modelseed-api ... failed (404)stub.Path 1 — sync 4xx on submit (new backend behavior, live 2026-06-10)
lib/api/modelseed.tsintroducesModelseedApiErrorcarrying{ status, detail }andextractApiErrorDetail().modelseedFetchnow throws the typed error instead of a flatError, preserving the structured{ code, message, hint, field, retryable }body.lib/utils/jobErrors.tsaddspresentJobSubmitError()which lifts structured fields onto a render-ready shape and flagsTOKEN_EXPIRED(also defensively flags any bare 401).components/ui/JobSubmitErrorAlert.tsx(new) renders the message as the Alert title,hintin lighter weight beneath, and anAffected input: <field>caption.lib/hooks/useTokenExpiredRedirect.ts(new) clears auth and redirects to/?returnTo=…&reason=token_expired. The home page shows a one-time "Session expired" notice above the sign-in form when the reason is present.app/(build-model)/plant/page.tsx), FBA/Gapfill (app/model/[...path]/page.tsxvia a newactionErrorprop onModelDetailHeader), and merge (app/(user-data)/my-models/page.tsx).codevalues still rendermessage+hint(open enum, no hard failure).Path 2 — async
job.erroron/my-jobs(pre-existing backend behavior)components/ui/FailedJobDetailsDialog.tsxrenders the fulljob.error(scrollable, monospace), the lastjob.progressnote, theparameters.argumentsblock as pretty JSON, and a deep link to theoutput_pathmodel when one is recorded.mergeApiAndTrackedJobsnow plumbsprogressandparametersonto the row so the dialog has the data without a second fetch.Test coverage
ModelseedApiErrorparsing from 404/422/401/empty bodies;presentJobSubmitErrorfor structured, unstructured, legacy_ERROR_Object not found_ERROR_, andTOKEN_EXPIREDcases.GENOME_NOT_FOUNDwith message + hint + field, Failed chip tooltip, Failed chip click → dialog, info-icon click → dialog, session-expired notice shown/hidden based on?reason=token_expired. Backend calls are stubbed viapage.route, so no live PATRIC token is required.playwright.config.tsnow respectsPLAYWRIGHT_PORT/PLAYWRIGHT_BASE_URLso tests can run against a dev server bound to a non-default port.All 108 unit tests pass; the 6 new e2e tests pass on a local dev server. The other pre-existing e2e failures are unrelated (they need a real
PATRIC_TOKENto populate the model grid) and reproduce onstagingwithout these changes.Test plan
npm run test:run— 108/108 passnpx tsc --noEmit— cleannpx eslint --quiet .— cleanPLAYWRIGHT_PORT=3005 PLAYWRIGHT_BASE_URL=http://localhost:3005 npx playwright test tests/e2e/jobs/job-error-integration.spec.ts— 6/6 passcurlrecipes (bad genome → seeGENOME_NOT_FOUNDalert; expired token → see session-expired notice on bounce-back)🤖 Generated with Claude Code