Writing and editing a question¶
/questions/manage is the list. /questions/:id and /questions/new are the
editor.
Both are gated on being able to manage questions — a moderator, or the holder of any editorial grant — not on the moderator role. The server then refuses anything outside the grant.
The list¶
/questions/manage asks for only what you may edit. It puts an Edit and a
Delete on every row, so listing the whole bank to somebody whose grant covers
nine categories was a page that lied about what it was offering.
The four tiles¶
One-click filters, and a health check:
| Tile | Counts |
|---|---|
| All questions | Everything in scope |
| Uncategorized | question_category_id is null |
| No explanation | Empty or whitespace |
| No difficulty | Not set |
A grantee's counts are their branches' counts, not the bank's.
Filters¶
Search over stem, options and #id; category (your branches, or the whole tree
for a moderator); folder;
difficulty. 25 to a page.
Bulk actions¶
Select rows and a bar appears. POST /questions/bulk, at most 500 ids per
action.
| Action | Notes |
|---|---|
| Set category | The destination must be a category you hold. A moderator holds all of them; a grantee can move a question between their own branches but never out of them. |
| Set difficulty | |
| Add to folder | Through the folder picker |
| Delete | Moderator only, and see the warning below |
Bulk delete is not the bin
A single row's Delete is a soft delete: deleted_at is set and the
question goes to /trash, where a moderator can restore it.
Bulk delete is a hard delete. POST /questions/bulk with
action: "delete" removes the rows outright, along with the additional
category links and every answer anybody ever gave them. The dialog says so
— "They go permanently… This cannot be undone" — and it is accurate. There
is no bin behind it and no restore.
The editor¶
The question itself¶
| Field | Notes |
|---|---|
| Stem | Markdown, with a formatting toolbar and a live preview. May carry tables and $maths$. |
| Type | mcq, true_false, fill_blank |
| Options | Up to eight (A–H). One is marked correct with a radio. |
| Per-option explanation | Why this option is right or wrong. Markdown, same toolbar. Shown in the option's own colour once the answer is revealed — and only where there is something to say. |
| Explanation | The one for the whole question. |
| Attending tip | One nudge, read before answering. Leave it empty and learners are offered no button at all — an empty panel is worse than no panel. Opening it before answering counts as a hint. |
Validation, in the browser and again at the server: text required, a correct answer required, at least two options for an MCQ, and the correct answer must be one of them.
Filing¶
A primary category decides where it is filed; also appears in adds any
number of additional links. additional_category_ids is a full replacement
set — send the list you want to end up with, not a delta.
Both pickers offer only what you may file into. A grantee shown the whole tree could put a question somewhere they then cannot edit.
The rest of the panel¶
| Panel | Does |
|---|---|
| Difficulty | easy / medium / hard, or not set |
| Topic reading | The articles offered after answering this question. Point at a section where the answer lives in one part of the topic. Unlinking is moderator-only. |
| History | Every earlier version, with Restore |
| Feedback | Reports learners have left about this question, beside the fields they are about |
| Figures | Any number, each with a role (stem or explanation) and a label the explanation can refer to |
Duplicate copies the question into a new one with "(copy)" appended. It is moderator-only, because duplicating is creating.
Opening one you may not edit¶
You get the stem and a sentence saying why, not a form full of blanks. The answer side is redacted, not missing, and typing into empty boxes would only earn a 403.
What an edit does to answers already given¶
PATCH /questions/{id} snapshots the question first, then settles the answers
that already exist. Three different outcomes:
| You changed | What happens |
|---|---|
| The wording only | Nothing. The answer is to the same question. |
| The correct answer | Every stored answer is regraded, and the scores of the attempts they sit in are recounted. A fixed key is a fact about the question, not about the day it was fixed — somebody who chose the right option before the fix was right. Void rows stay void. |
| The options | Answers held in sessions still open are withdrawn and marked void. Those learners have the old options on screen, so nothing they send can be compared to the new ones. Finished attempts are left alone: they were marked against the question as it stood, and that is history. |
A void row is kept and shown in the review with a note. It counts nowhere. This is why a score can stay stable while the bank underneath it changes.
History¶
Every edit is snapshotted into question_versions before any field changes —
the stem, type, options, key, explanation, per-option explanations, key points,
attending tip, difficulty, category and the two legacy image paths.
The whole history is kept. It used to be trimmed to the last five, which is enough to undo a recent mistake and not enough to answer "what did this question say in March"; a question body is a few kilobytes and edits are rare.
Restoring snapshots the current state first, so a mistaken restore has a way back.
The editor's own copy is stale
With no versions yet the panel reads "The last 5 edits are kept." That is no longer true. The list itself shows every version.
Deleting¶
DELETE /questions/{id} |
Soft. deleted_at is set; the row and the id survive, so anything pointing at it can be put back. Moderator only. |
/trash |
The bin. Restore with PATCH /questions/{id}/restore. |
DELETE /questions/{id}/permanent |
For good. Moderator only. |
There is no scheduled purge: the bin never empties itself.
Trashing a question also withdraws the answers to it held in sessions still
open — the same withdraw above.