p = 'frontend/src/pages/QuizPage.jsx'
s = open(p).read()

s = s.replace('''  const captureHighlightSelection = useCallback(() => {
    const selected = getManualHighlightSelection()
    if (!selected || !current) return
    const [questionKey] = selected.id.split('::')
    if (questionKey !== String(current.id)) return''',
'''  const captureHighlightSelection = useCallback(() => {
    const selected = getManualHighlightSelection()
    if (!selected || !current) return
    const [questionKey, fieldKey] = selected.id.split('::')
    if (questionKey !== String(current.id)) return
    // Not on an option. Highlighting is for working through a stem; an option
    // is a few words you are choosing between, and a yellow smear across one
    // of them is a mark on the answer rather than a note on the case.
    if (fieldKey?.startsWith('option-')) return''')

s = s.replace('''                            value={opt}
                            className="rich-inline"
                            textId={`${current.id}::${optionFieldKey}`}
                            highlights={highlightsFor(optionFieldKey)}
                            speechRange={optionSpeechRange}
                            onRemoveHighlight={removeJoinedHighlight}
                            onTipOpen={noteHint}''',
'''                            value={opt}
                            className="rich-inline"
                            // `textId` stays: it is what the read-aloud cursor
                            // rides on. What goes is the highlight layer —
                            // nothing stored is drawn and nothing new can be
                            // made, so an option cannot be marked yellow.
                            textId={`${current.id}::${optionFieldKey}`}
                            speechRange={optionSpeechRange}
                            onTipOpen={noteHint}''')

# The empty case, said rather than left silent.
s = s.replace('''                      {openWhy && (
                        <div className="quiz-option-explanation">
                          <RichText value={current.option_explanations[opt]}
                            className="rich-inline" linkArticles previewLinks={false} />
                        </div>
                      )}''',
'''                      {marked && (
                        <div className="quiz-option-explanation">
                          {current.option_explanations?.[opt] ? (
                            <RichText value={current.option_explanations[opt]}
                              className="rich-inline" linkArticles previewLinks={false} />
                          ) : (
                            /* Every row opens, including the ones with nothing
                               to say. A red row that stayed shut read as a
                               click that had not worked; saying the source is
                               silent is honest and takes one line. Nothing is
                               invented to fill it — a made-up reason on a
                               wrong option is worse than no reason. */
                            <span className="quiz-option-quiet">
                              {isCorrectOpt
                                ? 'The source explanation does not add anything here.'
                                : 'Not the answer here — the source explanation does not say why.'}
                            </span>
                          )}
                        </div>
                      )}''')
s = s.replace('''                        openWhy ? ' has-why' : ''}`}>''', '''                        marked ? ' has-why' : ''}`}>''')
open(p, 'w').write(s)
print('written')
