feat: show learning progress in web app
This commit is contained in:
@@ -74,7 +74,7 @@ interview-ready after each short practice loop.
|
|||||||
- [x] **WEB-02**: User can create a diagnostic interview session from the web
|
- [x] **WEB-02**: User can create a diagnostic interview session from the web
|
||||||
app.
|
app.
|
||||||
- [x] **WEB-03**: User can answer a diagnostic question and see rubric feedback.
|
- [x] **WEB-03**: User can answer a diagnostic question and see rubric feedback.
|
||||||
- [ ] **WEB-04**: User can see learner memory, readiness, and next challenge
|
- [x] **WEB-04**: User can see learner memory, readiness, and next challenge
|
||||||
after answering.
|
after answering.
|
||||||
- [ ] **WEB-05**: Operator can ingest source material from the web app.
|
- [ ] **WEB-05**: Operator can ingest source material from the web app.
|
||||||
- [ ] **WEB-06**: Operator can inspect ontology candidate concepts, edges, and
|
- [ ] **WEB-06**: Operator can inspect ontology candidate concepts, edges, and
|
||||||
@@ -117,7 +117,7 @@ interview-ready after each short practice loop.
|
|||||||
| ONTO-01..ONTO-04 | Phase 5 | Complete |
|
| ONTO-01..ONTO-04 | Phase 5 | Complete |
|
||||||
| ASSET-01..ASSET-03 | Phase 6 | Complete |
|
| ASSET-01..ASSET-03 | Phase 6 | Complete |
|
||||||
| WEB-01..WEB-03 | Phase 7 | Complete |
|
| WEB-01..WEB-03 | Phase 7 | Complete |
|
||||||
| WEB-04 | Phase 8 | Pending |
|
| WEB-04 | Phase 8 | Complete |
|
||||||
| WEB-05..WEB-08 | Phase 9 | Pending |
|
| WEB-05..WEB-08 | Phase 9 | Pending |
|
||||||
|
|
||||||
**Coverage:**
|
**Coverage:**
|
||||||
@@ -128,4 +128,4 @@ interview-ready after each short practice loop.
|
|||||||
|
|
||||||
---
|
---
|
||||||
*Requirements defined: 2026-04-26*
|
*Requirements defined: 2026-04-26*
|
||||||
*Last updated: 2026-04-26 after Phase 7 execution.*
|
*Last updated: 2026-04-26 after Phase 8 execution.*
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ See: `.planning/PROJECT.md` (updated 2026-04-26)
|
|||||||
**Core value:** The user should feel and prove that they are becoming more
|
**Core value:** The user should feel and prove that they are becoming more
|
||||||
interview-ready after each short practice loop.
|
interview-ready after each short practice loop.
|
||||||
|
|
||||||
**Current focus:** Phase 8 planning: Learning Progress View.
|
**Current focus:** Phase 9 planning: Material and Asset Workspace.
|
||||||
|
|
||||||
## Current Decisions
|
## Current Decisions
|
||||||
|
|
||||||
@@ -38,10 +38,11 @@ interview-ready after each short practice loop.
|
|||||||
- v2 Frontend MVP milestone selected to turn the backend learning loop into a
|
- v2 Frontend MVP milestone selected to turn the backend learning loop into a
|
||||||
usable web service.
|
usable web service.
|
||||||
- Phase 7 web app shell and diagnostic start UI is implemented and verified.
|
- Phase 7 web app shell and diagnostic start UI is implemented and verified.
|
||||||
|
- Phase 8 learning progress view is implemented and verified.
|
||||||
|
|
||||||
## Next Actions
|
## Next Actions
|
||||||
|
|
||||||
1. Plan and execute Phase 8: Learning Progress View.
|
1. Plan and execute Phase 9: Material and Asset Workspace.
|
||||||
2. Verify the production OpenAI image model identifier before real image
|
2. Verify the production OpenAI image model identifier before real image
|
||||||
generation calls.
|
generation calls.
|
||||||
3. Add standardized SUMMARY frontmatter or Nyquist validation files if future
|
3. Add standardized SUMMARY frontmatter or Nyquist validation files if future
|
||||||
@@ -80,6 +81,9 @@ interview-ready after each short practice loop.
|
|||||||
- 2026-04-26: Phase 7 implementation verified with `go test ./...`, OpenSpec
|
- 2026-04-26: Phase 7 implementation verified with `go test ./...`, OpenSpec
|
||||||
validation, root/asset HTTP smoke, and diagnostic API smoke through the
|
validation, root/asset HTTP smoke, and diagnostic API smoke through the
|
||||||
server used by the web app.
|
server used by the web app.
|
||||||
|
- 2026-04-26: Phase 8 implementation verified with `go test ./...`, OpenSpec
|
||||||
|
validation, app script smoke, and learner memory/readiness/next-challenge API
|
||||||
|
smoke after an answer.
|
||||||
|
|
||||||
---
|
---
|
||||||
*State initialized: 2026-04-26.*
|
*State initialized: 2026-04-26.*
|
||||||
|
|||||||
34
.planning/phases/008-learning-progress-view/008-CONTEXT.md
Normal file
34
.planning/phases/008-learning-progress-view/008-CONTEXT.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Phase 8 Context: Learning Progress View
|
||||||
|
|
||||||
|
**Status:** Ready for execution
|
||||||
|
**Started:** 2026-04-26
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Show evidence-backed learning progress in the web app after diagnostic
|
||||||
|
practice.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- WEB-04: User can see learner memory, readiness, and next challenge after
|
||||||
|
answering.
|
||||||
|
|
||||||
|
## Inputs
|
||||||
|
|
||||||
|
- Phase 7 web app shell.
|
||||||
|
- Existing backend endpoints:
|
||||||
|
- `GET /api/v1/learners/{userID}/memory`
|
||||||
|
- `GET /api/v1/learners/{userID}/readiness-map`
|
||||||
|
- `GET /api/v1/learners/{userID}/next-challenge`
|
||||||
|
|
||||||
|
## UX Direction
|
||||||
|
|
||||||
|
Keep progress in the right-side context column so the answer workspace remains
|
||||||
|
centered. The user should see the loop close immediately: answer, feedback,
|
||||||
|
memory, readiness, next challenge.
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
- Full graph visualization.
|
||||||
|
- Historical readiness timeline.
|
||||||
|
- Editing learner memory.
|
||||||
26
.planning/phases/008-learning-progress-view/008-PLAN.md
Normal file
26
.planning/phases/008-learning-progress-view/008-PLAN.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Phase 8 Plan: Learning Progress View
|
||||||
|
|
||||||
|
**Status:** Ready for execution
|
||||||
|
**Phase Goal:** Close the practice loop with visible learning progress.
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
### 1. Add progress UI region
|
||||||
|
|
||||||
|
- Add learner progress section to the right context pane.
|
||||||
|
- Include manual refresh affordance.
|
||||||
|
|
||||||
|
### 2. Fetch progress after answer
|
||||||
|
|
||||||
|
- Fetch learner memory, readiness map, and next challenge after grading.
|
||||||
|
- Render empty/error states when progress is unavailable.
|
||||||
|
|
||||||
|
### 3. Verify
|
||||||
|
|
||||||
|
- Add/update web app tests for progress asset content.
|
||||||
|
- Run Go tests, OpenSpec validation, line-count check, and live smoke.
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
- Charts.
|
||||||
|
- Historical progress storage.
|
||||||
20
.planning/phases/008-learning-progress-view/008-RESEARCH.md
Normal file
20
.planning/phases/008-learning-progress-view/008-RESEARCH.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Phase 8 Research: Learning Progress View
|
||||||
|
|
||||||
|
## Findings
|
||||||
|
|
||||||
|
The backend already provides progress projection after answer submission. The
|
||||||
|
frontend only needs to fetch and summarize the three endpoints after grading.
|
||||||
|
|
||||||
|
Useful MVP display:
|
||||||
|
|
||||||
|
- profile target role and stack
|
||||||
|
- top concept mastery states
|
||||||
|
- readiness percentage
|
||||||
|
- next challenge concept, ladder level, and question
|
||||||
|
|
||||||
|
## Recommendation
|
||||||
|
|
||||||
|
- Refresh progress automatically after successful answer submission.
|
||||||
|
- Add a manual refresh button for recovery.
|
||||||
|
- Use empty state before the first answer.
|
||||||
|
- Keep evidence labels compact so they do not overwhelm the practice surface.
|
||||||
33
.planning/phases/008-learning-progress-view/008-SUMMARY.md
Normal file
33
.planning/phases/008-learning-progress-view/008-SUMMARY.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Phase 8 Summary
|
||||||
|
|
||||||
|
**Status:** Complete
|
||||||
|
**Completed:** 2026-04-26
|
||||||
|
|
||||||
|
## Delivered
|
||||||
|
|
||||||
|
- Added learning progress region to the web app right context pane.
|
||||||
|
- Added manual progress refresh action.
|
||||||
|
- After answer submission, the app fetches learner memory, readiness map, and
|
||||||
|
next challenge.
|
||||||
|
- Rendered readiness percentage, concept mastery states, and recommended next
|
||||||
|
challenge.
|
||||||
|
- Added frontend asset test coverage for progress API wiring.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
gofmt -w cmd internal
|
||||||
|
go test ./...
|
||||||
|
openspec validate frontend-mvp --strict
|
||||||
|
```
|
||||||
|
|
||||||
|
Additional smoke check:
|
||||||
|
|
||||||
|
- Submitted a diagnostic answer, then verified learner memory, readiness, and
|
||||||
|
next challenge APIs returned progress consumed by the app script.
|
||||||
|
|
||||||
|
## Deferred
|
||||||
|
|
||||||
|
- Browser screenshot audit.
|
||||||
|
- Charts and historical progress.
|
||||||
|
- Editable learner memory.
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Phase 8 Verification
|
||||||
|
|
||||||
|
## Verdict
|
||||||
|
|
||||||
|
PASS
|
||||||
|
|
||||||
|
## Requirement Coverage
|
||||||
|
|
||||||
|
- WEB-04: PASS. The web app can fetch and render learner memory, readiness, and
|
||||||
|
next challenge after an answer.
|
||||||
|
|
||||||
|
## Evidence
|
||||||
|
|
||||||
|
- `go test ./...` passed.
|
||||||
|
- `openspec validate frontend-mvp --strict` passed.
|
||||||
|
- Static app script includes the readiness API integration.
|
||||||
|
- Live smoke confirmed memory mastery, readiness percentage, and next challenge
|
||||||
|
after diagnostic answer submission.
|
||||||
|
|
||||||
|
## Residual Risk
|
||||||
|
|
||||||
|
The UI is still verified through code and HTTP/API smoke rather than browser
|
||||||
|
screenshots. Phase 9 should add visual/browser validation after the workspace
|
||||||
|
surface is complete.
|
||||||
@@ -33,4 +33,7 @@ func TestHandlerServesAsset(t *testing.T) {
|
|||||||
if !strings.Contains(rec.Body.String(), "diagnostic-sessions") {
|
if !strings.Contains(rec.Body.String(), "diagnostic-sessions") {
|
||||||
t.Fatal("expected app script content")
|
t.Fatal("expected app script content")
|
||||||
}
|
}
|
||||||
|
if !strings.Contains(rec.Body.String(), "readiness-map") {
|
||||||
|
t.Fatal("expected progress API content")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ const state = {
|
|||||||
session: null,
|
session: null,
|
||||||
selectedQuestion: null,
|
selectedQuestion: null,
|
||||||
lastAnswer: null,
|
lastAnswer: null,
|
||||||
|
progress: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const els = {
|
const els = {
|
||||||
@@ -11,6 +12,8 @@ const els = {
|
|||||||
answerButton: document.querySelector("#answer-button"),
|
answerButton: document.querySelector("#answer-button"),
|
||||||
questions: document.querySelector("#questions"),
|
questions: document.querySelector("#questions"),
|
||||||
feedback: document.querySelector("#feedback"),
|
feedback: document.querySelector("#feedback"),
|
||||||
|
progress: document.querySelector("#progress"),
|
||||||
|
refreshProgress: document.querySelector("#refresh-progress"),
|
||||||
status: document.querySelector("#status-line"),
|
status: document.querySelector("#status-line"),
|
||||||
error: document.querySelector("#error-line"),
|
error: document.querySelector("#error-line"),
|
||||||
title: document.querySelector("#session-title"),
|
title: document.querySelector("#session-title"),
|
||||||
@@ -45,6 +48,11 @@ els.sessionForm.addEventListener("submit", async (event) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
els.refreshProgress.addEventListener("click", async () => {
|
||||||
|
clearError();
|
||||||
|
await refreshProgress();
|
||||||
|
});
|
||||||
|
|
||||||
els.answerForm.addEventListener("submit", async (event) => {
|
els.answerForm.addEventListener("submit", async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
clearError();
|
clearError();
|
||||||
@@ -63,6 +71,7 @@ els.answerForm.addEventListener("submit", async (event) => {
|
|||||||
});
|
});
|
||||||
state.lastAnswer = answer;
|
state.lastAnswer = answer;
|
||||||
renderFeedback();
|
renderFeedback();
|
||||||
|
await refreshProgress();
|
||||||
setStatus(`Answer graded as ${answer.grade.overall}`);
|
setStatus(`Answer graded as ${answer.grade.overall}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showError(error.message);
|
showError(error.message);
|
||||||
@@ -96,6 +105,54 @@ function renderSession() {
|
|||||||
els.answerButton.disabled = !state.selectedQuestion;
|
els.answerButton.disabled = !state.selectedQuestion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function refreshProgress() {
|
||||||
|
if (!state.session) return;
|
||||||
|
setStatus("Refreshing learning progress...");
|
||||||
|
|
||||||
|
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.textContent = "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 = `
|
||||||
|
<section>
|
||||||
|
<div class="readiness-value">${readiness.readiness_percentage}%</div>
|
||||||
|
<p class="status-line">${escapeHTML(memory.profile.target_role)} readiness</p>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<h2>Concept memory</h2>
|
||||||
|
<div>${mastery.map((item) => `<span class="concept-pill">${escapeHTML(item.concept.label)} · ${escapeHTML(item.state)}</span>`).join("")}</div>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<h2>Next challenge</h2>
|
||||||
|
<p class="status-line">${escapeHTML(challenge.concept.label)} · ${escapeHTML(challenge.ladder_level)}</p>
|
||||||
|
<p>${escapeHTML(challenge.question)}</p>
|
||||||
|
</section>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
function renderFeedback() {
|
function renderFeedback() {
|
||||||
if (!state.lastAnswer) {
|
if (!state.lastAnswer) {
|
||||||
els.feedback.className = "feedback empty-state";
|
els.feedback.className = "feedback empty-state";
|
||||||
|
|||||||
@@ -61,6 +61,16 @@
|
|||||||
<div id="feedback" class="feedback empty-state">
|
<div id="feedback" class="feedback empty-state">
|
||||||
Submit an answer to see grade, evidence, and follow-up.
|
Submit an answer to see grade, evidence, and follow-up.
|
||||||
</div>
|
</div>
|
||||||
|
<div class="section-heading progress-heading">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">Progress</p>
|
||||||
|
<h2>Learning state</h2>
|
||||||
|
</div>
|
||||||
|
<button id="refresh-progress" class="small-button" type="button" disabled>Refresh</button>
|
||||||
|
</div>
|
||||||
|
<div id="progress" class="feedback empty-state">
|
||||||
|
Answer once to update learner memory and readiness.
|
||||||
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
</main>
|
</main>
|
||||||
<script src="/assets/app.js" type="module"></script>
|
<script src="/assets/app.js" type="module"></script>
|
||||||
|
|||||||
@@ -199,6 +199,16 @@ button:disabled {
|
|||||||
margin-top: 22px;
|
margin-top: 22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.progress-heading {
|
||||||
|
margin-top: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-button {
|
||||||
|
min-height: 34px;
|
||||||
|
padding: 0 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.metric-row {
|
.metric-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr auto;
|
grid-template-columns: 1fr auto;
|
||||||
@@ -213,6 +223,24 @@ button:disabled {
|
|||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.readiness-value {
|
||||||
|
color: var(--accent);
|
||||||
|
font-size: 48px;
|
||||||
|
font-weight: 850;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.concept-pill {
|
||||||
|
display: inline-flex;
|
||||||
|
margin: 4px 6px 4px 0;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 6px 9px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
|
||||||
.small-list {
|
.small-list {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding-left: 18px;
|
padding-left: 18px;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
- [x] 1. Implement web app shell served by the Go backend.
|
- [x] 1. Implement web app shell served by the Go backend.
|
||||||
- [x] 2. Implement diagnostic session start and answer submission UI.
|
- [x] 2. Implement diagnostic session start and answer submission UI.
|
||||||
- [ ] 3. Implement learner memory, readiness, and next challenge UI.
|
- [x] 3. Implement learner memory, readiness, and next challenge UI.
|
||||||
- [ ] 4. Implement material ingestion and ontology inspection UI.
|
- [ ] 4. Implement material ingestion and ontology inspection UI.
|
||||||
- [ ] 5. Implement teaching asset prompt candidate UI.
|
- [ ] 5. Implement teaching asset prompt candidate UI.
|
||||||
- [ ] 6. Validate frontend MVP with tests, smoke checks, and OpenSpec.
|
- [ ] 6. Validate frontend MVP with tests, smoke checks, and OpenSpec.
|
||||||
|
|||||||
Reference in New Issue
Block a user