From 38c8caecb942d4e64b767394c3d8d888655b4cfa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Jun 2026 17:24:53 +0000 Subject: [PATCH 1/3] Initial plan From 778dd53fe9368b00a4cc2c8e22b7d1794a5ca0fb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Jun 2026 17:32:16 +0000 Subject: [PATCH 2/3] Fix exam question formatting --- api/exam.go | 13 +++++ api/exam_test.go | 11 ++++ static/exam.html | 128 +++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 130 insertions(+), 22 deletions(-) diff --git a/api/exam.go b/api/exam.go index 09f45a0..00e7cd1 100644 --- a/api/exam.go +++ b/api/exam.go @@ -8,6 +8,7 @@ import ( mathrand "math/rand" "net/http" "strconv" + "strings" "time" "github.com/ZipCodeCore/zip-athena/database" @@ -320,6 +321,7 @@ func (h *Handler) StartExamHandler(w http.ResponseWriter, r *http.Request) { type examQuestion struct { ID int `json:"id"` QuestionText string `json:"question_text"` + Category string `json:"category"` Difficulty int `json:"difficulty"` Answers []examAnswer `json:"answers"` } @@ -341,9 +343,20 @@ func (h *Handler) StartExamHandler(w http.ResponseWriter, r *http.Request) { for i, a := range answers { eas[i] = examAnswer{ID: a.ID, AnswerText: a.AnswerText} } + categoryLabel := "" + if topic, err := h.db.GetTopic(q.TopicID); err == nil { + categoryLabel = strings.TrimSpace(topic.Name) + if categoryLabel == "" { + categoryLabel = strings.TrimSpace(topic.Category) + } + } + if categoryLabel == "" && len(q.Tags) > 0 { + categoryLabel = strings.TrimSpace(q.Tags[0]) + } questions = append(questions, examQuestion{ ID: q.ID, QuestionText: q.QuestionText, + Category: categoryLabel, Difficulty: q.Difficulty, Answers: eas, }) diff --git a/api/exam_test.go b/api/exam_test.go index e5fb45b..7577de0 100644 --- a/api/exam_test.go +++ b/api/exam_test.go @@ -276,6 +276,17 @@ func TestStartExamHandler(t *testing.T) { if result["questions"] == nil { t.Error("Expected questions in response") } + questionsResult, ok := result["questions"].([]interface{}) + if !ok || len(questionsResult) == 0 { + t.Fatalf("Expected at least one question in response, got %v", result["questions"]) + } + firstQuestion, ok := questionsResult[0].(map[string]interface{}) + if !ok { + t.Fatalf("Expected first question to be an object, got %T", questionsResult[0]) + } + if firstQuestion["category"] != "Java Basics" { + t.Errorf("Expected question category %q, got %v", "Java Basics", firstQuestion["category"]) + } } func TestStartExamHandler_NotAssigned(t *testing.T) { diff --git a/static/exam.html b/static/exam.html index 03e6625..a0e58ac 100644 --- a/static/exam.html +++ b/static/exam.html @@ -212,6 +212,87 @@

Exam Complete!

.replace(/'/g, '''); } + function renderQuestionText(container, text) { + container.innerHTML = ''; + + const codeBlockRegex = /```(\w+)?\r?\n?([\s\S]*?)```/g; + let lastIndex = 0; + let match; + + while ((match = codeBlockRegex.exec(text)) !== null) { + if (match.index > lastIndex) { + const preamble = text.substring(lastIndex, match.index).trim(); + if (preamble) { + const p = document.createElement('p'); + p.className = 'question-preamble mb-2'; + p.textContent = preamble; + container.appendChild(p); + } + } + + const language = match[1] || ''; + const code = match[2]; + const pre = document.createElement('pre'); + pre.className = 'code-block'; + const codeEl = document.createElement('code'); + codeEl.className = language ? `language-${language}` : ''; + codeEl.textContent = code; + pre.appendChild(codeEl); + container.appendChild(pre); + + lastIndex = match.index + match[0].length; + } + + if (lastIndex < text.length) { + const remaining = text.substring(lastIndex).trim(); + if (remaining) { + const p = document.createElement('p'); + p.className = 'question-preamble mb-2'; + p.textContent = remaining; + container.appendChild(p); + } + } + + if (lastIndex === 0) { + container.textContent = text; + } + } + + function getQuestionCategory(question) { + return question && question.category ? question.category : ''; + } + + function appendQuestionContent(container, question, index, icon) { + const header = document.createElement('div'); + header.className = 'd-flex flex-wrap align-items-center gap-2 mb-2'; + + const numberBadge = document.createElement('span'); + numberBadge.className = 'badge bg-secondary'; + numberBadge.textContent = index + 1; + header.appendChild(numberBadge); + + const category = getQuestionCategory(question); + if (category) { + const categoryBadge = document.createElement('span'); + categoryBadge.className = 'badge rounded-pill bg-info text-dark'; + categoryBadge.textContent = category; + header.appendChild(categoryBadge); + } + + if (icon) { + const iconSpan = document.createElement('span'); + iconSpan.textContent = icon; + header.appendChild(iconSpan); + } + + container.appendChild(header); + + const questionText = document.createElement('div'); + questionText.className = 'mb-3'; + renderQuestionText(questionText, question.question_text || ''); + container.appendChild(questionText); + } + // ============ Render Exam ============ // Questions from API: { id, question_text, difficulty, answers: [{id, answer_text}] } function renderExam() { @@ -247,13 +328,11 @@

Exam Complete!

div.innerHTML = `
-

- ${idx + 1} - ${escapeHtml(q.question_text)} -

+
${answersHtml}
`; + appendQuestionContent(div.querySelector('.question-content'), q, idx); container.appendChild(div); }); @@ -353,7 +432,16 @@

Exam Complete!

// Question-by-question breakdown using the original exam question data const qResults = result.question_results || {}; if (examData && examData.questions && examData.questions.length > 0) { - let html = '
Question Breakdown
'; + const questionResultsEl = document.getElementById('questionResults'); + questionResultsEl.innerHTML = ''; + + const title = document.createElement('h5'); + title.className = 'text-start mb-3'; + title.textContent = 'Question Breakdown'; + questionResultsEl.appendChild(title); + + const resultsList = document.createElement('div'); + resultsList.className = 'text-start'; examData.questions.forEach((q, idx) => { const isCorrect = qResults[String(q.id)] === true; const icon = isCorrect ? '✅' : '❌'; @@ -363,25 +451,21 @@

Exam Complete!

const selectedEl = document.querySelector(`input[name="q_${q.id}"]:checked`); const yourAnswerText = selectedEl ? selectedEl.dataset.answerText : '(not answered)'; - // Find the correct answer text from the answers list - let correctAnswerText = ''; - if (!isCorrect && q.answers) { - // We don't know which answer is correct from the client side without extra API call - // Just show what they answered - } + const card = document.createElement('div'); + card.className = `card mb-2 ${cardClass}`; + const cardBody = document.createElement('div'); + cardBody.className = 'card-body py-2 px-3'; + appendQuestionContent(cardBody, q, idx, icon); - html += ` -
-
-

${idx + 1} - ${icon} ${escapeHtml(q.question_text)}

-

Your answer: ${escapeHtml(yourAnswerText)}

-
-
- `; + const answer = document.createElement('p'); + answer.className = 'mb-0 small'; + answer.innerHTML = `Your answer: ${escapeHtml(yourAnswerText)}`; + cardBody.appendChild(answer); + + card.appendChild(cardBody); + resultsList.appendChild(card); }); - html += '
'; - document.getElementById('questionResults').innerHTML = html; + questionResultsEl.appendChild(resultsList); } showPanel('resultsPanel'); From 611ea17f0d5412e4873483899988013b25752b3c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Jun 2026 17:34:50 +0000 Subject: [PATCH 3/3] Refine exam question rendering --- api/exam.go | 2 ++ static/exam.html | 13 ++++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/api/exam.go b/api/exam.go index 00e7cd1..e231545 100644 --- a/api/exam.go +++ b/api/exam.go @@ -349,6 +349,8 @@ func (h *Handler) StartExamHandler(w http.ResponseWriter, r *http.Request) { if categoryLabel == "" { categoryLabel = strings.TrimSpace(topic.Category) } + } else { + h.logger.WithContext(r.Context()).WithError(err).WithField("question_id", q.ID).WithField("topic_id", q.TopicID).Warn("Failed to load topic for exam question") } if categoryLabel == "" && len(q.Tags) > 0 { categoryLabel = strings.TrimSpace(q.Tags[0]) diff --git a/static/exam.html b/static/exam.html index a0e58ac..0d3b9d4 100644 --- a/static/exam.html +++ b/static/exam.html @@ -218,8 +218,10 @@

Exam Complete!

const codeBlockRegex = /```(\w+)?\r?\n?([\s\S]*?)```/g; let lastIndex = 0; let match; + let foundCodeBlock = false; while ((match = codeBlockRegex.exec(text)) !== null) { + foundCodeBlock = true; if (match.index > lastIndex) { const preamble = text.substring(lastIndex, match.index).trim(); if (preamble) { @@ -231,7 +233,7 @@

Exam Complete!

} const language = match[1] || ''; - const code = match[2]; + const code = match[2].replace(/^(?:\r?\n)+/, '').trimEnd(); const pre = document.createElement('pre'); pre.className = 'code-block'; const codeEl = document.createElement('code'); @@ -243,6 +245,11 @@

Exam Complete!

lastIndex = match.index + match[0].length; } + if (!foundCodeBlock) { + container.textContent = text; + return; + } + if (lastIndex < text.length) { const remaining = text.substring(lastIndex).trim(); if (remaining) { @@ -252,10 +259,6 @@

Exam Complete!

container.appendChild(p); } } - - if (lastIndex === 0) { - container.textContent = text; - } } function getQuestionCategory(question) {