feat: add material asset workspace
This commit is contained in:
@@ -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.*
|
||||
|
||||
@@ -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.*
|
||||
|
||||
28
.planning/phases/009-material-asset-workspace/009-CONTEXT.md
Normal file
28
.planning/phases/009-material-asset-workspace/009-CONTEXT.md
Normal file
@@ -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.
|
||||
34
.planning/phases/009-material-asset-workspace/009-PLAN.md
Normal file
34
.planning/phases/009-material-asset-workspace/009-PLAN.md
Normal file
@@ -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.
|
||||
@@ -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.
|
||||
35
.planning/phases/009-material-asset-workspace/009-SUMMARY.md
Normal file
35
.planning/phases/009-material-asset-workspace/009-SUMMARY.md
Normal file
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
</section>
|
||||
<section>
|
||||
<h2>Concept memory</h2>
|
||||
<div>${mastery.map((item) => `<span class="concept-pill">${escapeHTML(item.concept.label)} · ${escapeHTML(item.state)}</span>`).join("")}</div>
|
||||
<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 class="status-line">${escapeHTML(challenge.concept.label)} - ${escapeHTML(challenge.ladder_level)}</p>
|
||||
<p>${escapeHTML(challenge.question)}</p>
|
||||
</section>
|
||||
`;
|
||||
}
|
||||
|
||||
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 = `
|
||||
<div class="summary-strip">
|
||||
<span class="summary-chip">${concepts.length} concepts</span>
|
||||
<span class="summary-chip">${(state.ontology.edges || []).length} edges</span>
|
||||
<span class="summary-chip">${(state.ontology.gaps || []).length} gaps</span>
|
||||
</div>
|
||||
<section>
|
||||
<h2>Candidate concepts</h2>
|
||||
<div>${concepts.map((item) => `<span class="concept-pill">${escapeHTML(item.concept.label)} - ${escapeHTML(item.review_state)}</span>`).join("") || "No candidates yet."}</div>
|
||||
</section>
|
||||
`;
|
||||
|
||||
els.assetConcept.innerHTML = concepts
|
||||
.map((item) => `<option value="${escapeHTML(item.concept.id)}">${escapeHTML(item.concept.label)}</option>`)
|
||||
.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 = `
|
||||
<div class="summary-strip">
|
||||
<span class="summary-chip">${escapeHTML(prompt.model_key)}</span>
|
||||
<span class="summary-chip">${escapeHTML(prompt.review_state)}</span>
|
||||
<span class="summary-chip">verify model id: ${prompt.requires_model_id_verification ? "yes" : "no"}</span>
|
||||
</div>
|
||||
<pre class="prompt-text">${escapeHTML(prompt.prompt)}</pre>
|
||||
${evidenceBlock(prompt.source_evidence)}
|
||||
`;
|
||||
}
|
||||
|
||||
function renderFeedback() {
|
||||
if (!state.lastAnswer) {
|
||||
els.feedback.className = "feedback empty-state";
|
||||
|
||||
@@ -51,6 +51,56 @@
|
||||
<textarea id="answer-text" rows="7" placeholder="Select a question, then answer with concrete production reasoning."></textarea>
|
||||
<button id="answer-button" type="submit" disabled>Submit answer</button>
|
||||
</form>
|
||||
|
||||
<section class="content-workspace" aria-label="Material and asset workspace">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<p class="eyebrow">Content operations</p>
|
||||
<h2>Source to asset prompt</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="material-form" class="material-form">
|
||||
<label>
|
||||
Material title
|
||||
<input id="material-title" value="Backend interview notes" />
|
||||
</label>
|
||||
<label>
|
||||
Source type
|
||||
<input id="material-source" value="markdown" />
|
||||
</label>
|
||||
<label class="wide-field">
|
||||
Source material
|
||||
<textarea id="material-body" rows="5">Idempotent API retries need transactions. Cache invalidation uses TTL tradeoffs and database indexes support query plans.</textarea>
|
||||
</label>
|
||||
<button id="material-button" type="submit">Ingest material</button>
|
||||
</form>
|
||||
|
||||
<div id="ontology" class="ontology-view empty-state">
|
||||
Ingest material to inspect ontology candidates.
|
||||
</div>
|
||||
|
||||
<form id="asset-form" class="asset-form">
|
||||
<label>
|
||||
Concept
|
||||
<select id="asset-concept" disabled></select>
|
||||
</label>
|
||||
<label>
|
||||
Asset type
|
||||
<select id="asset-type">
|
||||
<option value="diagram">Diagram</option>
|
||||
<option value="lesson_slice">Lesson slice</option>
|
||||
<option value="worksheet">Worksheet</option>
|
||||
<option value="interview_card">Interview card</option>
|
||||
</select>
|
||||
</label>
|
||||
<button id="asset-button" type="submit" disabled>Generate prompt</button>
|
||||
</form>
|
||||
|
||||
<div id="asset-output" class="ontology-view empty-state">
|
||||
Generate a prompt to inspect model key, review state, and evidence.
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<aside class="feedback-pane" aria-label="Feedback">
|
||||
|
||||
@@ -26,7 +26,8 @@ body {
|
||||
|
||||
button,
|
||||
input,
|
||||
textarea {
|
||||
textarea,
|
||||
select {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
@@ -87,6 +88,18 @@ h2 {
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.material-form,
|
||||
.asset-form {
|
||||
align-items: end;
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
grid-template-columns: repeat(2, minmax(180px, 1fr)) auto;
|
||||
}
|
||||
|
||||
.wide-field {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
label {
|
||||
display: grid;
|
||||
gap: 7px;
|
||||
@@ -96,7 +109,8 @@ label {
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
textarea,
|
||||
select {
|
||||
width: 100%;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
@@ -113,7 +127,8 @@ textarea {
|
||||
}
|
||||
|
||||
input:focus,
|
||||
textarea:focus {
|
||||
textarea:focus,
|
||||
select:focus {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px rgba(25, 118, 75, 0.12);
|
||||
}
|
||||
@@ -248,6 +263,42 @@ button:disabled {
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.content-workspace {
|
||||
border-top: 1px solid var(--line);
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
padding-top: 24px;
|
||||
}
|
||||
|
||||
.ontology-view {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.summary-strip {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.summary-chip {
|
||||
background: var(--surface-muted);
|
||||
border-radius: 6px;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
font-weight: 750;
|
||||
padding: 9px 11px;
|
||||
}
|
||||
|
||||
.prompt-text {
|
||||
background: #fbfcfa;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
margin: 0;
|
||||
padding: 14px;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.workspace {
|
||||
grid-template-columns: 1fr;
|
||||
@@ -257,4 +308,9 @@ button:disabled {
|
||||
max-width: 100%;
|
||||
font-size: 42px;
|
||||
}
|
||||
|
||||
.material-form,
|
||||
.asset-form {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
- [x] 1. Implement web app shell served by the Go backend.
|
||||
- [x] 2. Implement diagnostic session start and answer submission UI.
|
||||
- [x] 3. Implement learner memory, readiness, and next challenge UI.
|
||||
- [ ] 4. Implement material ingestion and ontology inspection UI.
|
||||
- [ ] 5. Implement teaching asset prompt candidate UI.
|
||||
- [ ] 6. Validate frontend MVP with tests, smoke checks, and OpenSpec.
|
||||
- [x] 4. Implement material ingestion and ontology inspection UI.
|
||||
- [x] 5. Implement teaching asset prompt candidate UI.
|
||||
- [x] 6. Validate frontend MVP with tests, smoke checks, and OpenSpec.
|
||||
|
||||
Reference in New Issue
Block a user