const state = {
session: null,
selectedQuestion: null,
lastAnswer: null,
progress: null,
ontology: null,
assetPrompt: null,
};
const els = {
sessionForm: document.querySelector("#session-form"),
answerForm: document.querySelector("#answer-form"),
answerText: document.querySelector("#answer-text"),
answerButton: document.querySelector("#answer-button"),
questions: document.querySelector("#questions"),
feedback: document.querySelector("#feedback"),
progress: document.querySelector("#progress"),
refreshProgress: document.querySelector("#refresh-progress"),
materialForm: document.querySelector("#material-form"),
assetForm: document.querySelector("#asset-form"),
ontology: document.querySelector("#ontology"),
assetOutput: document.querySelector("#asset-output"),
assetConcept: document.querySelector("#asset-concept"),
assetButton: document.querySelector("#asset-button"),
status: document.querySelector("#status-line"),
error: document.querySelector("#error-line"),
title: document.querySelector("#session-title"),
gSignIn: document.querySelector(".g_id_signin"),
userInfo: document.querySelector("#user-info"),
logoutButton: document.querySelector("#logout-button"),
};
const readinessClassMap = {
unknown: "pill-neutral",
fragile: "pill-weak",
improving: "pill-warn",
interview_ready: "pill-good",
strong_signal: "pill-strong",
};
const reviewClassMap = {
candidate: "pill-neutral",
reviewed: "pill-good",
};
const gradeClassMap = {
miss: "grade-miss",
partial: "grade-partial",
solid: "grade-solid",
strong: "grade-strong",
};
function setButtonLoading(button, loadingText) {
button.disabled = true;
button.classList.add("is-loading");
const textEl = button.querySelector(".btn-text");
if (textEl && loadingText) {
textEl.dataset.originalText = textEl.textContent;
textEl.textContent = loadingText;
}
}
function clearButtonLoading(button) {
button.disabled = false;
button.classList.remove("is-loading");
const textEl = button.querySelector(".btn-text");
if (textEl && textEl.dataset.originalText) {
textEl.textContent = textEl.dataset.originalText;
delete textEl.dataset.originalText;
}
}
els.sessionForm.addEventListener("submit", async (event) => {
event.preventDefault();
clearError();
setStatus("Creating diagnostic session...", true);
setButtonLoading(event.submitter || document.querySelector("#start-button"), "Starting...");
const payload = {
user_id: value("#user-id"),
target_role: value("#target-role"),
stack: value("#stack").split(",").map((item) => item.trim()).filter(Boolean),
interview_timeline: value("#timeline"),
};
try {
const session = await request("/api/v1/diagnostic-sessions", {
method: "POST",
body: JSON.stringify(payload),
});
state.session = session;
state.selectedQuestion = session.questions[0] || null;
state.lastAnswer = null;
renderSession();
renderFeedback();
renderProgress();
setStatus(`Session ${session.id} ready`);
} catch (error) {
showError(error.message);
setStatus("Ready");
} finally {
clearButtonLoading(document.querySelector("#start-button"));
}
});
els.refreshProgress.addEventListener("click", async () => {
clearError();
await refreshProgress();
});
els.answerForm.addEventListener("submit", async (event) => {
event.preventDefault();
clearError();
if (!state.session || !state.selectedQuestion) return;
setStatus("Submitting answer...", true);
setButtonLoading(event.submitter || els.answerButton, "Grading...");
try {
const answer = await request(`/api/v1/diagnostic-sessions/${state.session.id}/answers`, {
method: "POST",
body: JSON.stringify({
question_id: state.selectedQuestion.id,
answer_text: els.answerText.value,
}),
});
state.lastAnswer = answer;
renderFeedback();
await refreshProgress();
setStatus(`Answer graded as ${answer.grade.overall}`);
} catch (error) {
showError(error.message);
setStatus("Session ready");
} finally {
clearButtonLoading(els.answerButton);
els.answerButton.disabled = !state.selectedQuestion;
}
});
els.materialForm.addEventListener("submit", async (event) => {
event.preventDefault();
clearError();
setStatus("Ingesting material...", true);
setButtonLoading(event.submitter || document.querySelector("#material-button"), "Ingesting...");
try {
const result = await request("/api/v1/materials", {
method: "POST",
body: JSON.stringify({
title: value("#material-title"),
source_type: value("#material-source"),
body: value("#material-body"),
}),
});
state.ontology = result.snapshot;
renderOntology();
setStatus(`Material ${result.material.id} ingested`);
} catch (error) {
showError(error.message);
setStatus("Content workspace ready");
} finally {
clearButtonLoading(document.querySelector("#material-button"));
}
});
els.assetForm.addEventListener("submit", async (event) => {
event.preventDefault();
clearError();
setStatus("Generating prompt candidate...", true);
setButtonLoading(event.submitter || els.assetButton, "Generating...");
try {
const prompt = await request("/api/v1/teaching-assets/prompts", {
method: "POST",
body: JSON.stringify({
concept_id: els.assetConcept.value,
asset_type: value("#asset-type"),
}),
});
state.assetPrompt = prompt;
renderAssetPrompt();
setStatus(`Prompt ${prompt.id} generated`);
} catch (error) {
showError(error.message);
setStatus("Content workspace ready");
} finally {
clearButtonLoading(els.assetButton);
}
});
function renderSession() {
if (!state.session) return;
els.title.textContent = `${state.session.target_role} — ${state.session.questions.length} questions`;
els.questions.className = "question-list";
els.questions.innerHTML = "";
state.session.questions.forEach((question) => {
const button = document.createElement("button");
button.type = "button";
button.className = "question-button";
button.setAttribute("aria-pressed", String(state.selectedQuestion?.id === question.id));
button.innerHTML = `${escapeHTML(question.id)}${escapeHTML(question.prompt)}`;
button.addEventListener("click", () => {
state.selectedQuestion = question;
els.answerText.value = "";
renderSession();
setStatus(`Selected ${question.id}`);
});
els.questions.append(button);
});
els.answerButton.disabled = !state.selectedQuestion;
}
async function refreshProgress() {
if (!state.session) return;
setStatus("Refreshing learning progress...", true);
try {
const userID = encodeURIComponent(state.session.user_id);
const [memory, readiness, challenge] = await Promise.all([
request(`/api/v1/learners/${userID}/memory`),
request(`/api/v1/learners/${userID}/readiness-map`),
request(`/api/v1/learners/${userID}/next-challenge`),
]);
state.progress = { memory, readiness, challenge };
renderProgress();
setStatus("Learning progress updated");
} catch (error) {
showError(error.message);
renderProgress();
}
}
function renderProgress() {
els.refreshProgress.disabled = !state.session;
if (!state.progress) {
els.progress.className = "feedback empty-state";
els.progress.innerHTML = `Answer once to update learner memory and readiness.`;
return;
}
const { memory, readiness, challenge } = state.progress;
const mastery = memory.mastery || [];
els.progress.className = "feedback";
els.progress.innerHTML = `
${escapeHTML(memory.profile.target_role)} readiness ${escapeHTML(challenge.concept.label)} — ${escapeHTML(challenge.ladder_level)} ${escapeHTML(challenge.question)}Concept memory
Next challenge
${escapeHTML(prompt.prompt)}
${evidenceBlock(prompt.source_evidence)}
`;
}
function renderFeedback() {
if (!state.lastAnswer) {
els.feedback.className = "feedback empty-state";
els.feedback.innerHTML = `Submit an answer to see grade, evidence, and follow-up.`;
return;
}
const grade = state.lastAnswer.grade;
const gradeClass = gradeClassMap[grade.overall] || "";
els.feedback.className = "feedback";
els.feedback.innerHTML = `
${escapeHTML(grade.strengths?.[0] || "Answer was graded.")}
${escapeHTML(followUp.question)}