Developer handoff
What already exists, what this concept adds, and what has to be settled before building. Nothing in the ATS application or the shared Figma file was changed to prepare it.
How this was checked
The ATS repository folder was not readable from the design workspace, so no file was opened directly and no import path was verified against current source. Three kinds of evidence were available instead.
- Snapshot read A saved copy of the source file was read in full:
button.tsx,checkbox.tsx,ModalShell.tsx,StatusBadge.tsx,statusConfig.tsandPageHeader.tsx. Snapshot date 18 Aug 2026. - Indexed The file and its export appear in the ATS code index at the stated path. Contents, props and variants were not read.
- Gap No reusable component was found. The concept follows the established pattern and flags it for a decision.
Tokens come from the Evalzo token snapshot, not from src/app/theme.css itself. The brief's inspected commit is 7edfd98 from 1 Sep 2026. Reconcile everything below against current main before implementation.
Component mapping
| Concept element | Repository source | Evidence | Notes |
|---|---|---|---|
| Primary, secondary, tertiary, link and destructive buttons | src/components/base/buttons/button.tsx | Snapshot read | Colors primary, secondary, tertiary, link-color, primary-destructive. Sizes sm and md. isLoading for Replacing and Saving. |
| Checkboxes: sources, possible skills, library multi-select | src/components/base/checkbox/checkbox.tsx | Snapshot read | Size md. |
| Modal shell | src/components/ModalShell.tsx | Snapshot read | Use maxWidth="max-w-[960px]". The shell always closes on Escape and backdrop click. It needs a way to block dismissal while selecting and saving. See open dependencies. |
| Host modal and method cards | src/components/assessments/CreateWrittenAssessmentModal.tsx | Indexed | Remove the blurred Coming soon treatment. Rename the card to Build with AI. |
| Requisition selector and menu | src/components/job-descriptions/JDSelector.tsx, AnchoredDropdown.tsx, CustomSelect.tsx | Indexed | Reuse reqLabel() for REQ ID plus title. Loading, empty, no results and error states must exist or be added. |
| Create requisition | src/components/CreateJDDrawer.tsx | Indexed | The prototype drawer is a stand-in with fewer fields. Keep the real drawer's fields and validation. It already calls extractJdFields(). |
| Edit requisition route | src/components/job-descriptions/ViewJDDrawer.tsx | Indexed | Opened from the job description preview. Must return to the modal with inputs intact. |
| Requisition entry point | src/components/job-descriptions/LinkAssessmentModal.tsx | Indexed | Should open the same modal with the requisition preselected. |
| Editor page and question list | src/app/create/page.tsx, components/QuestionsSection.tsx | Indexed | The prototype editor is an approximation. Only the additions listed below are proposed. |
| Question rows, filters, library drawer | src/components/question-library/QuestionCard.tsx, QuestionFilters.tsx, QuestionLibraryDrawer.tsx | Indexed | Replace question reuses the drawer pattern. Library questions convert through convertLibraryQuestions(). |
| Page header, breadcrumbs, status | PageHeader.tsx, Breadcrumbs.tsx, StatusBadge.tsx | Snapshot read | status="draft", variant with-dot. Breadcrumbs is indexed only. |
| Linked requisition card, change flow | src/components/assessments/LinkedJdCard.tsx, ChangeJdModal.tsx | Indexed | Unchanged. |
| Create and upload question | src/app/create/components/AddMCQModal.tsx, AddCodingModal.tsx | Indexed | Unchanged. Shown as entry points only. |
| Unsaved changes confirmation | src/app/create/components/UnsavedChangesModal.tsx | Indexed | Prototype copy is a stand-in. Keep the existing copy and buttons. |
| Toasts | src/components/Snackbar.tsx | Indexed | Success for replaced, removed, added and saved. |
| Loading | src/components/LoadingSkeleton.tsx, Loader.tsx | Indexed | Skeletons for requisition list and recommendation. Loader for selecting. |
| Inline errors | src/components/ErrorAlert.tsx | Indexed | Warning, info and success variants are unverified. |
| Duration limits | src/constants/assessmentDuration.ts | Indexed | MIN_DURATION_MINS, MAX_DURATION_MINS, clampDurationMins(). The brief reports 5 to 300. |
| Draft persistence | src/app/create/hooks/useAssessmentFormSave.ts, useAssessmentPersistence.ts, services/assessment/assessmentService.ts | Indexed | createAssessment() and updateAssessment() exist. How AI selection plugs in is open. |
| Header, theme toggle, tooltip | Header.tsx, ThemeToggle.tsx, Tooltip.tsx | Indexed | Shell stand-in only. |
| Icons | @untitledui/icons | Indexed | Prototype glyphs are stand-ins. Suggested names: Stars02, BookOpen01, SearchLg, XClose, Plus, Minus, SwitchHorizontal01, Trash01, Eye, Edit02, Lock01, AlertTriangle, AlertCircle, CheckCircle, RefreshCw01. Confirm each exists in the installed version. |
Component gaps
These have no confirmed reusable component. None should be solved by adding a UI library. Each needs a decision within the repository-only component rule.
| Control | Used for | Pattern followed in the concept |
|---|---|---|
| Gap Text input | Assessment title, requisition search, library search | The 40px field in the Figma modal, 8px radius, gray-300 border. |
| Gap Textarea | Job description in the requisition drawer | Same field treatment. The real drawer's control takes precedence. |
| Gap Number stepper | Question counts and duration | The minus, value, plus control in the Figma modal, with labelled buttons. |
| Gap Radio card group | Method, replacement choice | The Hovered/Selected card in Figma, plus a radio indicator. Built on native radios. |
| Gap Step indicator | Job details, Question setup | Two numbered marks. step_icon_selected.svg suggests a stepper exists somewhere. Locate it first. |
| Gap Filter chip | Replace filters, library filters | QuestionFilters.tsx likely covers this. Confirm before building. |
| New Selection summary | Above the question list | Counts and tags only. |
| New Matches label | Question cards and alternatives | A tag holding the mapped skill or topic. No generated prose. |
| New Stage list and result panels | Selecting, partial, failure states | Composed from existing buttons, alerts and tables. |
Existing, proposed, open
Existing behavior kept
- Centered modal as the entry, about 960px wide.
- Manual path: empty draft, same validations.
- Requisition is required for written assessments, per ticket 14ymvzwkx3k. This is product evidence, not verified deployed behavior.
- Requisition creation and editing through the existing drawers.
- Editor structure, Save, Preview and Publish as separate actions.
- Create question, upload questions, candidate instructions.
- Unsaved changes confirmation.
Proposed additions
- Build with AI card enabled, with the label and helper text from the brief.
- Two steps for the AI path, with Back preserving every input.
- Job description preview with its source named.
- Step two as a confirmation: one recommended mix and the duration.
- The recommendation is shown in editable fields: two counts, a difficulty dropdown and a live total. Reset to recommendation appears after a change.
- Non-dismissible selecting state and result panels.
- Draft confirmation, selection summary and Matches label.
- Remove, Replace and the replace drawer.
Open dependencies
- Backend contract in 86d2cmctr: analysis, recommendation and selection as separate calls or one.
- Which progress stages the service can report. Hide any it cannot.
- Draft status lookup after a timeout, keyed so a retry cannot duplicate.
- Whether replacement persists on its own or through the editor's Save.
- Blocking dismissal in
ModalShellduring final persistence. - Whether question timing data exists. Without it, no estimate is shown.
- Duplicate-content detection for alternatives.
- Whether method cards open with nothing selected, as prototyped, or keep manual preselected.
- Skills are never shown or edited in setup. Confirm that the backend needs no recruiter input on skills.
Open-ended questions. They appear in older designs and in a catalog enum, but the inspected editor and converter handle multiple choice and coding only. They are left out of every control here. Ticket 14ymvzwnf2r covers the authoring, persistence, attempt and scoring support they need before they can be offered.
Older ticket comments. References to the model authoring questions, to Vite and JSX, and to Express do not apply. The inspected app uses the Next.js App Router with React and TypeScript, and this concept selects from the repository.
Rules to revalidate
| Rule | In the prototype | Status |
|---|---|---|
| Assessment title length | 3 to 70 characters after trimming | From inspected source. Revalidate. PageHeader allows 80 when editing in place, which conflicts. |
| Job description | Mandatory on every requisition. The drawer blocks saving without one, and no missing-description state exists in the flow. | Decided 28 Sep 2026. Confirm the existing drawer enforces it. |
| Title suggestion | Role title plus Assessment. Follows the requisition until the recruiter types, then never changes. | Proposed. |
| Duration | 5 to 300 minutes, default 60, steps of 5 | Range from inspected source. Default is a prototype value. |
| Question counts | Whole numbers from 0. Zero of one type is valid. A total of zero is blocked. | Proposed. |
| Maximum count | The stepper stops at 50 per type | Prototype placeholder. The real limit is implementation configuration. Historical 5 to 20 and 1 to 30 limits are not carried forward. |
| Difficulty | Dropdown with Easy, Balanced, Hard. Opens on the recommended level. Maps to CustomSelect.tsx. | Balanced is a mix. It is never stored on a question. |
| Sources | No control in this version. Selection uses both permitted libraries. | Decided 28 Sep 2026. Each question still shows its source in the editor. |
| Requisition change | Clears role analysis and recommendation. Custom counts typed by the recruiter are kept. | Proposed. |
| Publish | Disabled with no questions | The exact minimum is unverified. |
| Credits | Not shown | No verified billing contract exists for repository selection. |
State inventory
| Condition | Behavior | Recovery |
|---|---|---|
| Requisitions fail to load | Error inside the menu. Other fields untouched. | Try again, Create new requisition |
| Role mapping unclear | No recommendation is shown and no skills are guessed. | Edit requisition, Add questions manually |
| Recommendation unavailable | Requirements stay. Mix is missing. | Try again, Set up manually |
| Partial match | Exact counts by type and coverage by skill. Nothing saved. | Use these questions, Edit setup, Browse library |
| No matching questions | No draft is created. | Edit setup, Browse library |
| Selection unavailable | Setup preserved. | Try again, Back to setup, Browse library |
| Request timed out | Checking draft status, then saved or not saved. | Open draft, or Try again when nothing was saved |
| Draft save failed | Selection kept in memory. | Save draft again, Back to setup |
| Question became unavailable | Affected question named before saving. | Choose a replacement, or continue without it |
| Requisition archived mid-flow | Returns to Job details. Other fields kept. | Choose another requisition |
| No comparable alternative | Empty state in the drawer. | Change filters, Browse library |
| Replacement failed | Original question stays in place. | Try again, Cancel |
| Removals leave a gap | Actual composition and missing skill shown. | Add from library |
| Leave with unsaved changes | Existing confirmation. | Keep editing, Discard changes |
Accessibility
- Method and difficulty are native radio groups inside fieldsets, so arrow keys and Space work without custom handling.
- Requisition field is a combobox with a listbox. Arrow keys move, Enter selects, Escape closes the menu before the modal.
- Focus moves to the heading when a modal, step or result opens, stays inside the top layer, and returns to the control that opened it.
- Errors are text with an icon, linked through
aria-describedby. Focus moves to the first invalid field. - Progress and results are announced through a polite live region. The live total is a status.
- Steppers have labelled decrease and increase buttons and a labelled value.
- Difficulty, selection and gaps always carry text and a shape, never color alone.
- Reduced motion stops the spinner and removes transitions.
- Touch targets are 44px at 390px. Desktop controls stay at the product's 40px.
Prototype deviations
- Primary button fill. The prototype uses brand-700. The repository button uses brand-600, which gives 3.74:1 with white at 14px. This is a token question for the whole product, not part of this feature.
- Editor and shell. Layout is approximated from component names and the July production audit. Treat only the additions as design intent.
- Requisition drawer. Fewer fields than the real drawer. Upload attaches a sample document.
- Selection. Simulated in the browser from 37 invented questions. It calls no production API.
- Timings. Loading and progress delays are fixed for the demo. They say nothing about real latency.
Acceptance check
- Existing library questions are being selected.Said in the card helper text, the step two description and the source on every question.
- Both mixes are demonstrated.The recommendation fills the fields. The recruiter confirms it as is, or changes the counts and difficulty in place.
- Job description is visible where it is chosen.Preview on step one and the linked requisition in the editor. Step two leaves it out by decision, to keep the confirmation quiet.
- Creation yields a draft. Publication is separate.The footer note, the Draft status and a distinct Publish action.
- Scope is truthful.Multiple choice and coding only. Open-ended is absent.
- Partial results need a decision.Use these questions is explicit, and skill gaps persist into the editor.
- Replace changes one question.Order is kept, and a failure leaves the original in place.
- Evalzo layout and tokens are reused.Tokens are verbatim. Component reuse is mapped above and is provisional.
- No invented claims.No credits, speed promises, confidence percentages or model name appear in any screen.
- Every state connects.Each failure state has at least one recovery action that returns to the flow.