Skip to content

Allow Reordering Global and Position Questions by Drag and Drop #426

Description

@b-at-neu

Problem

Question order is write-once. There is no way to reorder questions — for global questions or for position questions — anywhere in the product.

Confirmed in code:

  • order is assigned at creation only, as max(order) + 1 (prisma/actions/global-questions.ts:40-49, prisma/actions/position-question-actions.ts:46-59)
  • Neither update action accepts or writes order. baseQuestionSchema carries label, type, required, options and allowOtherorder is not in it (prisma/actions/global-questions.ts:78-81, position-question-actions.ts:92-95)
  • No reorder action exists in prisma/actions/
  • The value is displayed and sortable in the admin table, which makes it look editable when it is not

The only current workaround is to delete and recreate questions in the desired sequence — which is destructive:

  • Global questions: GlobalAnswer is unique on (userId, globalQuestionId), so recreating a question orphans every user's stored answer to it and silently resets that field across the whole user base
  • Position questions: existing PositionApplicationAnswer rows keep pointing at the soft-deleted question, so submitted applications and in-progress drafts diverge

Soft deletion also leaves permanent gaps in the sequence, since nothing renumbers.

Server side

A single reorder action per question type, taking the full ordered list of ids and rewriting order inside one transaction — rather than per-item swap actions, which are racy and produce inconsistent intermediate states.

reorderGlobalQuestions(ids: string[])
reorderPositionQuestions(positionId: string, ids: string[])

Validate that the submitted id set exactly matches the current live set for that scope, so a stale client cannot drop or inject a question.

UI — drag and drop

Reordering is drag-and-drop, on both /global-questions and the position edit page.

Recommended library: @dnd-kit (@dnd-kit/core + @dnd-kit/sortable). It is the current standard for React 19, ships a keyboard sensor and screen-reader announcements out of the box, and does not depend on react-dom internals the way the deprecated react-beautiful-dnd does. Radix provides no drag primitive, so something has to be added.

Adding a dependency is a bundle decision (.claude/docs/ENGINEERING.md §6). Worth noting the net effect here is favourable: #390 removes @tanstack/react-table (zero references) and @tanstack/react-query (zero useQuery/useMutation consumers, yet wrapping the whole app in a client provider), so this can land without growing the bundle overall.

Keyboard operability is not optional. A pointer-only reorder is inaccessible and would fail .claude/docs/ENGINEERING.md §5. Every question must be reorderable via keyboard — pick up, move, drop, cancel — with the position change announced to assistive tech.

Keep the drag handle a distinct control rather than making the whole card draggable, so the existing Edit and Delete buttons stay clickable and the card is still selectable text.

Acceptance criteria

Behaviour

  • Admins can drag to reorder global questions at /global-questions
  • Admins and position managers can drag to reorder questions on a position's edit page
  • Order persists on drop via the reorder action, with an optimistic update
  • A failed save reverts the list to its previous order and shows an error toast — the UI must never show an order the server rejected
  • Order is renumbered contiguously, closing gaps left by soft-deleted questions
  • The applicant-facing form and the profile page reflect the new order immediately (both already order by order)

Server

  • Reordering is a single transactional write; no intermediate state where two questions share an order
  • The submitted id set is validated against the live set for that scope
  • Authorization matches the existing question actions — admin for global, checkPositionAccess for position

Accessibility and input

  • Full keyboard support: pick up, move, drop, and cancel
  • Position changes announced to screen readers ("Question moved to position 3 of 7")
  • Drag handle is a labelled control with a visible focus ring
  • Touch drag works, and the handle meets the ~44px target from .claude/docs/DESIGN.md §7
  • Verified at 375px as well as desktop

Decisions needed

  1. Does reordering affect how existing applications display? Answers are currently rendered in createdAt order on the review page, not question order, so reordering will not re-sequence answers on already-submitted applications. That is arguably correct — it preserves the order the applicant actually saw — but it should be a deliberate choice rather than an accident.
  2. Should managers be able to reorder on an archived position? Should follow the same read-only rule as Make Archived Positions Read-Only for Managers #360.

Related

  • There is no unique constraint on order ((positionId, order) or global). The comment at position-question-actions.ts:45 claims the transaction "prevents duplicate order values under concurrent inserts" — under READ COMMITTED it does not, and nothing catches the collision. Worth adding the constraint as part of this work, since reordering makes duplicates much easier to produce.
  • Remove Dead Code and Unused Dependencies #390 (removes the two unused @tanstack dependencies — sequencing this first keeps the bundle flat)
  • Make Archived Positions Read-Only for Managers #360 (archived positions read-only for managers)
  • Design the Position Edit Policy #398 (position edit policy) — reordering is part of what a manager may change, and when

From the 2026-08-10 full platform audit.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions