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) {