Two follow-ups on the dropdown-actions pass:
- All four action menus (exams, results, questions, candidates panel) now
trigger off a plain three-dot icon button instead of a text 'Actions'
button with a chevron.
- Exam status change no longer lists all six statuses flat in the dropdown
(bad UX, looked like six always-visible options). 'Status' is now one
menu item that opens a small picker modal (current status + a Select +
Update) rather than the full edit form.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same treatment as the questions table, applied consistently: exams list,
exam results list, and the exam-detail candidates panel all now show a
single Actions button opening a dropdown instead of a row of inline
buttons/icons. Also fixes the exams list's action column header, which was
rendering the literal untranslated key ('exam.columns.actions') — missing
a fallback string, same pattern already used elsewhere in this file.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Matches the exams table's action pattern — a single Actions button opening
a menu, instead of a row of inline buttons/icons that got crowded once a
question had submit/approve/reject/retire/edit/delete all applicable at
once.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Created error context markdown files for failed tests in seafarer registration, detailing validation errors related to profile details.
- Added binary files (PNG, ZIP, WEBM) for test results, including snapshots and traces for debugging purposes.
- Updated test cases to include new error context and media files for better analysis of failures.
Matches the new backend rule (online_exam_requires_choice_form) — picking
Online in the exam form now auto-sets Form to Choice and disables that
field with an explanatory hint, instead of letting the user pick Essay
and only finding out at save time.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
/seafarer-registration had a page of its own that wrote the profile directly
and created no application at all. A "submitted" registration was therefore
never reviewed, never approved and never numbered — there was nothing for an
officer to open. The shared wizard, the review queue and the approval side
effects all already existed; only the route pointed away from them.
The page is deleted rather than repaired. It was a second wizard maintained
alongside the config-driven one, and the seed already describes every step it
was hand-rolling — including the physical characteristics and medical details
added this release, which it would not have known about. /seafarer-registration
now redirects, so navigation, deep links and the profile gate keep working.
Also adds Configuration -> Number Formats, for the shape of generated seafarer,
seaman book and BTC numbers. The sample is rendered by the server rather than
formatted here, so the preview cannot drift from what an approval will actually
generate, and asking for one never consumes a number.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two UX gaps found in testing:
- Exams list: changing status (e.g. PENDING -> ACTIVE) required opening the
full Edit modal for a one-field change. Added a status menu (teal toggle
icon) directly in the row actions, calling PUT /exams/:id with just
{status} — the backend DTO was already PartialType, nothing to change
there.
- Exam Results: publishing required first picking the exam in a page-level
filter to un-grey the header Publish button — not discoverable, and an
extra step disconnected from the row you actually care about. Added a
Publish action directly on any APPROVED result's row, confirm-gated since
it publishes every approved result for that exam (not just the one row)
and notifies every candidate. The header filter+button still works too,
for publishing a batch at once.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two fixes surfaced by actual candidate-flow testing:
- Portal 'My registrations' always showed 'Take exam' regardless of whether
the candidate had already finished — clicking it just hit
attempt_already_submitted server-side. The registrations/mine response
now carries the attempt's status; the row shows a Completed/Time expired
badge once finished, or 'Resume exam' while still in progress, and only
invites a fresh start when there's genuinely nothing yet.
- Backoffice candidates panel: a Regrade button next to a SUBMITTED/EXPIRED
sitting, calling the new POST /exam-attempts/:id/regrade — the officer's
path to the same staff-triggered regrade, no SQL needed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Print showed blank A/B/C/D fill-in lines for every CHOICE question instead
of the real options — the detail page never fetched option data at all
(GET /exams/:id?i=questions, no nested options), and the print template
hardcoded the blanks regardless. Now requests
?i=questions,questions.options and prints the real option text, falling
back to blank lines only when a question genuinely has none on record.
Also fixed the print-time cutting-point guard: it compared raw point sum
to cutting point regardless of evaluationMethod, wrongly blocking valid
low-point-sum PERCENTAGE papers from printing. Now branches the same way
RecordResultModal's grading preview already does, and surfaces the
matching backend validation error (paper_cannot_reach_cutting_point) added
server-side for the same reason.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Three bugs compounded into a button that looked unwired:
- cuttingPoint carried a native HTML required attribute while sitting on
the hidden Settings tab (Mantine keeps inactive tab panels mounted with
display:none). The browser blocked form submission entirely, silently
- no toast, no error, nothing. Replaced required with withAsterisk and
moved validation into JS.
- Manual required-field validation never checked type/form/adminMethod/
evalMethod/cuttingPoint at all, so even without the native block above
a half-filled Settings tab would silently no-op. Validation now covers
every required field on both tabs and switches to whichever tab has
the problem.
- Filling only the English Direction field (leaving Amharic blank) passed
frontend validation but the backend hard-rejects it, producing a 400
with a confusing error. Frontend now requires both-or-neither, matching
the same rule already applied to MCQ options.
Also translates the previously hardcoded validation message in both
locales.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Options editor used a single toggle-based bilingual field (BilingualInput)
that swapped English/Amharic in place via a tiny, easy-to-miss button.
Authors were filling English only and never noticing Amharic was empty.
- QuestionOptionsEditor: show separate always-visible English/Amharic
TextInputs per option instead of the toggle field.
- BilingualInput (shared): for other callers still using the toggle,
add a tooltip and a red dot indicator when the hidden language is
empty, so the gap is visible without switching.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>