diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index a26be2e..fcfae7a 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -76,12 +76,12 @@ interview-ready after each short practice loop. - [x] **WEB-03**: User can answer a diagnostic question and see rubric feedback. - [x] **WEB-04**: User can see learner memory, readiness, and next challenge after answering. -- [ ] **WEB-05**: Operator can ingest source material from the web app. -- [ ] **WEB-06**: Operator can inspect ontology candidate concepts, edges, and +- [x] **WEB-05**: Operator can ingest source material from the web app. +- [x] **WEB-06**: Operator can inspect ontology candidate concepts, edges, and gaps. -- [ ] **WEB-07**: Operator can generate and inspect teaching asset prompt +- [x] **WEB-07**: Operator can generate and inspect teaching asset prompt candidates. -- [ ] **WEB-08**: Web UI includes loading, empty, and error states for the MVP +- [x] **WEB-08**: Web UI includes loading, empty, and error states for the MVP flows. ### General Student Expansion @@ -118,7 +118,7 @@ interview-ready after each short practice loop. | ASSET-01..ASSET-03 | Phase 6 | Complete | | WEB-01..WEB-03 | Phase 7 | Complete | | WEB-04 | Phase 8 | Complete | -| WEB-05..WEB-08 | Phase 9 | Pending | +| WEB-05..WEB-08 | Phase 9 | Complete | **Coverage:** - v1 requirements: 28 total @@ -128,4 +128,4 @@ interview-ready after each short practice loop. --- *Requirements defined: 2026-04-26* -*Last updated: 2026-04-26 after Phase 8 execution.* +*Last updated: 2026-04-26 after Phase 9 execution.* diff --git a/.planning/STATE.md b/.planning/STATE.md index b6acb3b..0f1f57f 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -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 interview-ready after each short practice loop. -**Current focus:** Phase 9 planning: Material and Asset Workspace. +**Current focus:** v2 Frontend MVP implemented; ready for milestone audit. ## Current Decisions @@ -39,10 +39,11 @@ interview-ready after each short practice loop. usable web service. - Phase 7 web app shell and diagnostic start UI is implemented and verified. - Phase 8 learning progress view is implemented and verified. +- Phase 9 material and asset workspace is implemented and verified. ## Next Actions -1. Plan and execute Phase 9: Material and Asset Workspace. +1. Run a v2 Frontend MVP milestone audit. 2. Verify the production OpenAI image model identifier before real image generation calls. 3. Add standardized SUMMARY frontmatter or Nyquist validation files if future @@ -84,6 +85,10 @@ interview-ready after each short practice loop. - 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. +- 2026-04-26: Phase 9 implementation verified with `go test ./...`, OpenSpec + validation, app script smoke, and material/ontology/teaching-asset API smoke. + Chrome DevTools MCP browser screenshot attempt timed out and remains a + verification follow-up. --- *State initialized: 2026-04-26.* diff --git a/.planning/phases/009-material-asset-workspace/009-CONTEXT.md b/.planning/phases/009-material-asset-workspace/009-CONTEXT.md new file mode 100644 index 0000000..ea24455 --- /dev/null +++ b/.planning/phases/009-material-asset-workspace/009-CONTEXT.md @@ -0,0 +1,28 @@ +# Phase 9 Context: Material and Asset Workspace + +**Status:** Ready for execution +**Started:** 2026-04-26 + +## Goal + +Expose material ingestion, ontology inspection, and teaching asset prompt +candidate generation in the web app. + +## Requirements + +- WEB-05: Operator can ingest source material from the web app. +- WEB-06: Operator can inspect ontology candidate concepts, edges, and gaps. +- WEB-07: Operator can generate and inspect teaching asset prompt candidates. +- WEB-08: Web UI includes loading, empty, and error states for the MVP flows. + +## UX Direction + +Keep content operations as a secondary workspace below the diagnostic answer +surface. The operator flow should show provenance and candidate status without +turning the page into an admin dashboard. + +## Out of Scope + +- Full ontology editor. +- Human review promotion controls. +- Actual image generation. diff --git a/.planning/phases/009-material-asset-workspace/009-PLAN.md b/.planning/phases/009-material-asset-workspace/009-PLAN.md new file mode 100644 index 0000000..118886d --- /dev/null +++ b/.planning/phases/009-material-asset-workspace/009-PLAN.md @@ -0,0 +1,34 @@ +# Phase 9 Plan: Material and Asset Workspace + +**Status:** Ready for execution +**Phase Goal:** Let operators use ontology and teaching asset prompt workflows +from the web app. + +## Tasks + +### 1. Add material ingestion UI + +- Add title/source/body fields. +- Call material ingestion API. +- Show loading and error states. + +### 2. Add ontology inspection UI + +- Render concept, edge, and gap counts. +- Show candidate concept labels and review states. +- Populate asset concept selector. + +### 3. Add teaching asset prompt UI + +- Generate prompt candidate for selected concept and asset type. +- Show prompt text, model key, review state, and verification guard. + +### 4. Verify + +- Update tests for content-operation frontend wiring. +- Run Go tests, OpenSpec validation, line-count check, and smoke. + +## Out of Scope + +- Real image generation. +- Ontology graph editor. diff --git a/.planning/phases/009-material-asset-workspace/009-RESEARCH.md b/.planning/phases/009-material-asset-workspace/009-RESEARCH.md new file mode 100644 index 0000000..d86e0e1 --- /dev/null +++ b/.planning/phases/009-material-asset-workspace/009-RESEARCH.md @@ -0,0 +1,20 @@ +# Phase 9 Research: Material and Asset Workspace + +## Findings + +The existing APIs are sufficient for a browser proof: + +- `POST /api/v1/materials` +- `GET /api/v1/ontology` +- `POST /api/v1/teaching-assets/prompts` +- `GET /api/v1/teaching-assets` + +The frontend should make candidate state obvious and preserve evidence in +compact text. A full graph canvas would be premature. + +## Recommendation + +- Use a text ingestion form. +- Render candidate concepts as selectable options for asset prompt generation. +- Show counts for concepts, edges, gaps, and prompts. +- Show model verification guard in the generated prompt output. diff --git a/.planning/phases/009-material-asset-workspace/009-SUMMARY.md b/.planning/phases/009-material-asset-workspace/009-SUMMARY.md new file mode 100644 index 0000000..5fa883b --- /dev/null +++ b/.planning/phases/009-material-asset-workspace/009-SUMMARY.md @@ -0,0 +1,35 @@ +# Phase 9 Summary + +**Status:** Complete +**Completed:** 2026-04-26 + +## Delivered + +- Added material ingestion workspace to the web app. +- Added ontology candidate summary with concept, edge, and gap counts. +- Added candidate concept selector for teaching asset prompt generation. +- Added asset type selector and prompt generation UI. +- Rendered prompt text, model key, review state, evidence, and model-id + verification guard. +- Added frontend asset test coverage for teaching asset API wiring. + +## Verification + +```powershell +gofmt -w cmd internal +go test ./... +openspec validate frontend-mvp --strict +``` + +Additional smoke check: + +- Static app script includes material and teaching asset API wiring. +- Material ingestion returned 4 concepts and 3 edges. +- Teaching asset prompt generation returned `asset-prompt-1` with verification + guard enabled. + +## Deferred + +- Browser screenshot audit because Chrome DevTools MCP timed out. +- Full ontology graph editor. +- Real image generation. diff --git a/.planning/phases/009-material-asset-workspace/009-VERIFICATION.md b/.planning/phases/009-material-asset-workspace/009-VERIFICATION.md new file mode 100644 index 0000000..12a3ab6 --- /dev/null +++ b/.planning/phases/009-material-asset-workspace/009-VERIFICATION.md @@ -0,0 +1,31 @@ +# Phase 9 Verification + +## Verdict + +PASS + +## Requirement Coverage + +- WEB-05: PASS. The web app includes material ingestion UI wired to the real + backend API. +- WEB-06: PASS. The web app renders ontology candidate concept, edge, and gap + counts and candidate concept labels. +- WEB-07: PASS. The web app can generate and inspect teaching asset prompt + candidates. +- WEB-08: PASS. The MVP frontend includes loading, empty, and error states for + diagnostic, progress, material, and asset prompt flows. + +## Evidence + +- `go test ./...` passed. +- `openspec validate frontend-mvp --strict` passed. +- Live material ingestion and teaching asset prompt smoke passed. +- Static app script contains material and teaching asset API integration. +- Source files remain under 600 lines. + +## Residual Risk + +Chrome DevTools MCP timed out while opening the local page, so browser +screenshot verification is still pending. HTTP/API smoke confirms the served +assets and backend flows, but a visual pass should be repeated when the browser +tool is responsive. diff --git a/internal/webapp/assets_test.go b/internal/webapp/assets_test.go index c860041..d4f1457 100644 --- a/internal/webapp/assets_test.go +++ b/internal/webapp/assets_test.go @@ -36,4 +36,7 @@ func TestHandlerServesAsset(t *testing.T) { if !strings.Contains(rec.Body.String(), "readiness-map") { t.Fatal("expected progress API content") } + if !strings.Contains(rec.Body.String(), "teaching-assets") { + t.Fatal("expected teaching asset API content") + } } diff --git a/internal/webapp/static/app.js b/internal/webapp/static/app.js index 39ca69b..c7090bb 100644 --- a/internal/webapp/static/app.js +++ b/internal/webapp/static/app.js @@ -3,6 +3,8 @@ const state = { selectedQuestion: null, lastAnswer: null, progress: null, + ontology: null, + assetPrompt: null, }; const els = { @@ -14,6 +16,12 @@ const els = { 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"), @@ -41,6 +49,7 @@ els.sessionForm.addEventListener("submit", async (event) => { state.lastAnswer = null; renderSession(); renderFeedback(); + renderProgress(); setStatus(`Session ${session.id} ready`); } catch (error) { showError(error.message); @@ -81,9 +90,54 @@ els.answerForm.addEventListener("submit", async (event) => { } }); +els.materialForm.addEventListener("submit", async (event) => { + event.preventDefault(); + clearError(); + setStatus("Ingesting material..."); + + 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"); + } +}); + +els.assetForm.addEventListener("submit", async (event) => { + event.preventDefault(); + clearError(); + setStatus("Generating prompt candidate..."); + + 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"); + } +}); + function renderSession() { if (!state.session) return; - els.title.textContent = `${state.session.target_role} · ${state.session.questions.length} questions`; + els.title.textContent = `${state.session.target_role} - ${state.session.questions.length} questions`; els.questions.className = "question-list"; els.questions.innerHTML = ""; @@ -143,16 +197,64 @@ function renderProgress() {

Concept memory

-
${mastery.map((item) => `${escapeHTML(item.concept.label)} · ${escapeHTML(item.state)}`).join("")}
+
${mastery.map((item) => `${escapeHTML(item.concept.label)} - ${escapeHTML(item.state)}`).join("")}

Next challenge

-

${escapeHTML(challenge.concept.label)} · ${escapeHTML(challenge.ladder_level)}

+

${escapeHTML(challenge.concept.label)} - ${escapeHTML(challenge.ladder_level)}

${escapeHTML(challenge.question)}

`; } +function renderOntology() { + if (!state.ontology) { + els.ontology.className = "ontology-view empty-state"; + els.ontology.textContent = "Ingest material to inspect ontology candidates."; + return; + } + + const concepts = state.ontology.concepts || []; + els.ontology.className = "ontology-view"; + els.ontology.innerHTML = ` +
+ ${concepts.length} concepts + ${(state.ontology.edges || []).length} edges + ${(state.ontology.gaps || []).length} gaps +
+
+

Candidate concepts

+
${concepts.map((item) => `${escapeHTML(item.concept.label)} - ${escapeHTML(item.review_state)}`).join("") || "No candidates yet."}
+
+ `; + + els.assetConcept.innerHTML = concepts + .map((item) => ``) + .join(""); + els.assetConcept.disabled = concepts.length === 0; + els.assetButton.disabled = concepts.length === 0; +} + +function renderAssetPrompt() { + if (!state.assetPrompt) { + els.assetOutput.className = "ontology-view empty-state"; + els.assetOutput.textContent = "Generate a prompt to inspect model key, review state, and evidence."; + return; + } + + const prompt = state.assetPrompt; + els.assetOutput.className = "ontology-view"; + els.assetOutput.innerHTML = ` +
+ ${escapeHTML(prompt.model_key)} + ${escapeHTML(prompt.review_state)} + verify model id: ${prompt.requires_model_id_verification ? "yes" : "no"} +
+
${escapeHTML(prompt.prompt)}
+ ${evidenceBlock(prompt.source_evidence)} + `; +} + function renderFeedback() { if (!state.lastAnswer) { els.feedback.className = "feedback empty-state"; diff --git a/internal/webapp/static/index.html b/internal/webapp/static/index.html index 63485d9..e07d4dc 100644 --- a/internal/webapp/static/index.html +++ b/internal/webapp/static/index.html @@ -51,6 +51,56 @@ + +
+
+
+

Content operations

+

Source to asset prompt

+
+
+ +
+ + + + +
+ +
+ Ingest material to inspect ontology candidates. +
+ +
+ + + +
+ +
+ Generate a prompt to inspect model key, review state, and evidence. +
+