From fc4df94a6aab7899616e9eb71a6646bfeabff12a Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 11 Sep 2026 21:13:38 +0200 Subject: [PATCH] feat: the tutor is an administrator's to allow, and a handbook explains the rest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The tutor is handed the correct answer and the explanation and told it may reveal them, which is why it has never been offered during a running exam — require_question_access already refuses that, whatever anyone sets. What was missing is the other half: an administrator can now withhold it from study sessions too. Enforced on the server rather than by hiding a button, because hiding a button does not stop a request. Reviewing a finished attempt is not "during" and is unaffected; the answers are shown by then anyway. If Redis is unreachable the tutor stays on — nothing is revealed that study mode does not already show, so the permissive direction is the safe one here. GET /teach/prompt renders the instructions against a stand-in question, so an educator answering "why did the tutor say that?" can read them rather than infer them. And a handbook at /handbook, for anyone who maintains questions or articles whatever access they hold. It answers the things that were only in the code: that a question links to an article three different ways — a further-reading row, a key point carrying an article and section, and a [[id|label]] marker in prose keyed by id so renaming does not break it — what the tutor is told, why a blueprint shapes a paper, why deleting a question hides it, and why changing the embedding model invalidates every vector. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01TqXevQJhxFrM7jJg82cgZN --- backend/app/routers/teach.py | 83 ++++++++- backend/app/services/site_settings.py | 5 + backend/tests/test_invites_and_flags.py | 26 +++ frontend/src/App.jsx | 5 + frontend/src/components/SitePolicy.jsx | 17 +- frontend/src/pages/HandbookPage.css | 51 ++++++ frontend/src/pages/HandbookPage.jsx | 209 +++++++++++++++++++++++ frontend/src/pages/HandbookPage.test.jsx | 57 +++++++ frontend/src/pages/QuizPage.jsx | 17 +- frontend/src/pages/SettingsPage.jsx | 1 + 10 files changed, 467 insertions(+), 4 deletions(-) create mode 100644 frontend/src/pages/HandbookPage.css create mode 100644 frontend/src/pages/HandbookPage.jsx create mode 100644 frontend/src/pages/HandbookPage.test.jsx diff --git a/backend/app/routers/teach.py b/backend/app/routers/teach.py index d089b4c..d5265f3 100644 --- a/backend/app/routers/teach.py +++ b/backend/app/routers/teach.py @@ -8,10 +8,12 @@ from sqlalchemy.orm import Session from app.database import get_db from app.models.question import Question from app.models.ai_model_config import AIModelConfig +from app.models.attempt import QuizAttempt from app.models.user import User from app.utils.quiz_access import require_question_access +from app.services import site_settings from app.services.quiz_builder import bank_question_predicate -from app.utils.auth import get_current_user, check_rate_limit +from app.utils.auth import check_rate_limit, get_current_user, require_moderator router = APIRouter() @@ -131,6 +133,84 @@ def list_teach_models( return [{"id": m.id, "name": m.name, "model_id": m.model_id, "is_default": m.is_default} for m in models] +@router.get("/policy") +def tutor_policy(current_user: User = Depends(get_current_user)): + """Whether the tutor may be opened during a session. + + Asked before the quiz offers it, so a switch an administrator has thrown + reads as "not there" rather than as a button that fails when pressed. + Study mode is a separate rule the interface already applies and the server + enforces regardless of this answer. + """ + return {"in_quiz": site_settings.get_flag("tutor_in_quiz")} + + +@router.get("/prompt") +def show_prompt( + db: Session = Depends(get_db), + current_user: User = Depends(require_moderator), +): + """What the tutor is actually told, for anyone who maintains questions. + + An educator answering "why did the tutor say that?" should be able to read + the instructions rather than infer them, and the answer matters: the tutor + is handed the correct answer and the explanation, and is told it may + reveal them. That is the reason it is never offered during a running exam. + + Rendered against a stand-in question so the shape is visible without + naming any real one. + """ + example = Question( + id=0, + question_text="", + options=["