feat: the topic behind the right answer, as a chip where the eye already is
Reading links existed and sat at the foot of the whole explanation block, under everything else, as a headed list. A learner who has just been shown the right answer is looking at the right answer — so the link now sits directly under it, as a chip, the way a reference site does it. Outside the option's button rather than inside it: a link nested in a button is neither, and the click would have toggled the explanation instead of opening the article. Also a real bug beside it. Key points were gated on `option_explanations` being non-empty — the wrong field entirely — so a question with key points and no per-option reasoning showed none of them. It happens not to bite today because exactly one question in the bank has either, which is its own finding. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TqXevQJhxFrM7jJg82cgZN
This commit is contained in:
parent
2d80df8d73
commit
0d41179b30
3 changed files with 52 additions and 3 deletions
|
|
@ -9,7 +9,7 @@ import api from '../api/client'
|
||||||
* the article's own sections for it — this used to ask for every linked
|
* the article's own sections for it — this used to ask for every linked
|
||||||
* article in full, prose and all, to print a title and a heading.
|
* article in full, prose and all, to print a title and a heading.
|
||||||
*/
|
*/
|
||||||
export default function QuestionReadingLinks({ questionId }) {
|
export default function QuestionReadingLinks({ questionId, variant = 'list' }) {
|
||||||
const [links, setLinks] = useState(null)
|
const [links, setLinks] = useState(null)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!questionId) { setLinks([]); return }
|
if (!questionId) { setLinks([]); return }
|
||||||
|
|
@ -17,6 +17,25 @@ export default function QuestionReadingLinks({ questionId }) {
|
||||||
.then(res => setLinks(res.data)).catch(() => setLinks([]))
|
.then(res => setLinks(res.data)).catch(() => setLinks([]))
|
||||||
}, [questionId])
|
}, [questionId])
|
||||||
if (!links || links.length === 0) return null
|
if (!links || links.length === 0) return null
|
||||||
|
|
||||||
|
// Under the right answer in the player, the same links are a row of chips —
|
||||||
|
// one control, plainly a door to an article — rather than a headed list,
|
||||||
|
// which at that point in the page reads as another section of explanation.
|
||||||
|
if (variant === 'chips') {
|
||||||
|
return (
|
||||||
|
<div className="question-reading-chips" data-testid="question-reading">
|
||||||
|
{links.map(link => (
|
||||||
|
<Link key={`${link.article_id}-${link.section_id || 'all'}`}
|
||||||
|
className="question-reading-chip"
|
||||||
|
to={link.section_id ? `/articles/${link.article_id}?section=${link.section_id}` : `/articles/${link.article_id}`}>
|
||||||
|
<span aria-hidden="true">▤</span>
|
||||||
|
{link.section_title || link.title}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="question-reading" data-testid="question-reading">
|
<div className="question-reading" data-testid="question-reading">
|
||||||
<strong>Topic reading</strong>
|
<strong>Topic reading</strong>
|
||||||
|
|
|
||||||
|
|
@ -2104,6 +2104,14 @@ const timerStarted = timeLeft !== null
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
{/* The topic behind the right answer, offered where the
|
||||||
|
learner is already looking. Outside the option's
|
||||||
|
button rather than inside it, because a link nested
|
||||||
|
in a button is neither: the click would toggle the
|
||||||
|
explanation instead of opening the article. */}
|
||||||
|
{showCorrect && (
|
||||||
|
<QuestionReadingLinks questionId={current.id} variant="chips" />
|
||||||
|
)}
|
||||||
{/* Outside the option, so ruling one out is never
|
{/* Outside the option, so ruling one out is never
|
||||||
mistaken for choosing it. Gone once the question is
|
mistaken for choosing it. Gone once the question is
|
||||||
marked — there is nothing left to narrow down. */}
|
marked — there is nothing left to narrow down. */}
|
||||||
|
|
@ -2183,7 +2191,12 @@ const timerStarted = timeLeft !== null
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{current.option_explanations && Object.keys(current.option_explanations).length > 0 && (
|
{/* Gated on key points, not on option explanations. It asked
|
||||||
|
whether the *other* field was populated, so a question
|
||||||
|
with key points and no per-option reasoning showed none of
|
||||||
|
them — which is most of the bank, once anybody writes
|
||||||
|
them. */}
|
||||||
|
{(current.key_points || []).length > 0 && (
|
||||||
<div className="explanation" style={{ marginTop: 16 }}>
|
<div className="explanation" style={{ marginTop: 16 }}>
|
||||||
<strong>Key points</strong>
|
<strong>Key points</strong>
|
||||||
<ul className="quiz-key-points">
|
<ul className="quiz-key-points">
|
||||||
|
|
@ -2198,7 +2211,6 @@ const timerStarted = timeLeft !== null
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<QuestionReadingLinks questionId={current.id} />
|
|
||||||
{current.question_type === 'fill_blank' && (
|
{current.question_type === 'fill_blank' && (
|
||||||
<div className="explanation" style={{ marginTop: 12, borderLeftColor: '#22c55e' }}>
|
<div className="explanation" style={{ marginTop: 12, borderLeftColor: '#22c55e' }}>
|
||||||
<strong>Correct Answer:</strong> {current.correct_answer}
|
<strong>Correct Answer:</strong> {current.correct_answer}
|
||||||
|
|
|
||||||
|
|
@ -932,3 +932,21 @@ body:has(.quiz-player.is-exam-chrome) .site-footer { display: none; }
|
||||||
/* The clocks, at the foot of the rail with everything else about the session
|
/* The clocks, at the foot of the rail with everything else about the session
|
||||||
rather than about the question in front of you. */
|
rather than about the question in front of you. */
|
||||||
.quiz-rail-foot { display: flex; align-items: center; }
|
.quiz-rail-foot { display: flex; align-items: center; }
|
||||||
|
|
||||||
|
/* The topic behind the right answer, in the player. A chip rather than a
|
||||||
|
headed list: at that point on the page a heading reads as another section of
|
||||||
|
explanation, and what this is is a door. */
|
||||||
|
.question-reading-chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0 4px 42px; }
|
||||||
|
.question-reading-chip {
|
||||||
|
display: inline-flex; align-items: center; gap: 8px;
|
||||||
|
padding: 7px 13px; border: 1px solid var(--border); border-radius: 8px;
|
||||||
|
background: var(--card-bg); color: var(--text); text-decoration: none;
|
||||||
|
font-size: 0.85rem; font-weight: 600;
|
||||||
|
}
|
||||||
|
.question-reading-chip:hover { border-color: var(--primary); color: var(--primary); }
|
||||||
|
.question-reading-chip span { color: var(--text-muted); font-size: 0.9rem; }
|
||||||
|
.question-reading-chip:hover span { color: var(--primary); }
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.question-reading-chips { margin-left: 0; }
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue