diff --git a/docs/plans/bug-summary.md b/docs/plans/bug-summary.md
new file mode 100644
index 0000000..8aa1c63
--- /dev/null
+++ b/docs/plans/bug-summary.md
@@ -0,0 +1,337 @@
+# Bug Summary
+
+## Purpose
+
+This document summarizes the bugs, improvement requests, and open product questions collected so far for CREATE.
+
+It is based on the issue list provided in the conversation and is meant to serve as a stable reference before the work is split into implementation-specific plans.
+
+## Status Legend
+
+- `Fixed`: already reported as fixed
+- `Bug`: broken or incorrect behavior
+- `Functional improve`: an improvement to an existing workflow
+- `Functional request`: a new or expanded capability
+- `AI system optimize`: AI behavior, prompt, or content-quality issue
+- `H5P bug`: export or H5P-specific content issue
+
+## Current Bug and Request Inventory
+
+### 1. Question variants repetitive
+
+Category:
+
+- `AI system optimize`
+
+Problem summary:
+
+- When the learning objective is precise and the user asks for multiple multiple-choice questions, the system may generate repeated question stems with only different distractors.
+- When a learning objective contains multiple sub-topics, the system does not reliably narrow the scope and generate questions for only one slice of the learning objective.
+
+Example noted:
+
+- For a learning objective like describing anthropogenic sources of `CO2`, `CH4`, and `N2O`, the system keeps generating the same broad question instead of targeting only one gas at a time.
+
+Likely area:
+
+- question-generation prompt
+- duplication control
+- learning-objective slicing logic
+
+### 2. Questions and learning objectives coverage
+
+Category:
+
+- `AI system optimize`
+
+Problem summary:
+
+- It is unclear how to ensure generated questions actually cover the intended content.
+- The current assumption is that CREATE links learning objectives and materials correctly, but that coverage behavior is not transparent or guaranteed.
+
+Likely area:
+
+- retrieval logic
+- prompt instructions
+- coverage validation
+
+### 3. AI prompt customization
+
+Category:
+
+- `AI system optimize`
+
+Problem summary:
+
+- The user wants more control over the question generator prompt.
+- Desired controls include:
+ - specific page or section of material
+ - specific misconception to use for distractors
+ - preferred option style
+ - more detailed generation guidance
+
+Likely area:
+
+- prompt architecture
+- UI for optional generation controls
+- generation request schema
+
+### 4. Preview content feature
+
+Category:
+
+- `Functional request`
+
+Problem summary:
+
+- It is unclear whether preview content means the full document or only part of it.
+- The user wants a way to visualize and select the content CREATE should focus on.
+
+Likely area:
+
+- materials preview UX
+- content selection workflow
+
+### 5. Export format: more text-based export options
+
+Category:
+
+- `Functional request`
+
+Problem summary:
+
+- The user wants markdown or another text export format in addition to current exports.
+
+Likely area:
+
+- export controller
+- export service layer
+- export UI
+
+### 6. Export format: include explanation panel in PDF solutions
+
+Category:
+
+- `Functional request`
+
+Problem summary:
+
+- The explanation panel that exists in the product should also appear in PDF export with solutions.
+
+Likely area:
+
+- PDF export formatting
+- answer key layout
+
+### 7. Feedback on multiple-choice question
+
+Category:
+
+- `Functional improve`
+
+Problem summary:
+
+- The user wants answer-level feedback, not only a general explanation.
+- H5P multiple-choice supports feedback per answer, and CREATE should support this too.
+
+Likely area:
+
+- question data model
+- review/edit UI
+- question generation output format
+- H5P export mapping
+
+### 8. Regenerate questions with pedagogical re-selection
+
+Category:
+
+- `Functional improve`
+
+Problem summary:
+
+- When users regenerate questions, they may want to choose a different pedagogical approach instead of only regenerating the same pattern.
+- The flow should redirect users back to the pedagogical approach options when appropriate.
+
+Likely area:
+
+- question generation flow
+- AI Plan Configuration navigation
+
+### 9. Materials limits and quality impact
+
+Category:
+
+- `AI system optimize`
+
+Problem summary:
+
+- The user wants to know how many files can be uploaded under Materials.
+- The user also wants to know how file count affects generated question quality.
+
+Likely area:
+
+- product limits
+- retrieval quality
+- documentation
+
+### 10. Unable to edit course name
+
+Category:
+
+- `Fixed`
+
+Problem summary:
+
+- Previously the course name could not be edited.
+
+Current status:
+
+- reported as fixed
+
+### 11. Export H5P quiz with flashcards produces empty dialog cards
+
+Category:
+
+- `H5P bug`
+
+Problem summary:
+
+- When exporting a quiz composed of flashcards, the import on H5P Hub works, but the dialog cards are empty.
+- PDF export works correctly.
+- Optional flashcard explanation/feedback always starts with the same verb, `"Understanding..."`.
+
+Likely area:
+
+- H5P flashcard export mapping
+- flashcard back/explanation content formatting
+
+### 12. Adding learning objectives: "Generate from materials" does not load
+
+Category:
+
+- `Bug`
+
+Problem summary:
+
+- In the learning-objective creation flow, clicking `Generate from materials` does nothing.
+- Other options such as `AI Classify` and `Add manually` still work.
+
+Likely area:
+
+- objective generation UI handler
+- materials processing dependency
+- loading state wiring
+
+### 13. AI Plan Configuration page is hard to access after generation
+
+Category:
+
+- `Functional improve`
+
+Problem summary:
+
+- Users who want to regenerate H5P elements should be able to return to the pedagogical stream page and choose between:
+ - Support Learning
+ - Assess Understanding
+ - Gamify Learning
+
+Current understanding:
+
+- This is the active issue now being planned under the `go back` workflow.
+
+Likely area:
+
+- `QuestionGeneration.tsx`
+- view-state management
+- generate-again UX
+
+### 14. AI Plan Configuration page should list H5P elements per stream
+
+Category:
+
+- `Fixed`
+
+Problem summary:
+
+- It would be useful to show some H5P elements available for each stream.
+
+Current status:
+
+- reported as fixed
+
+### 15. Add more quiz bug
+
+Category:
+
+- `Bug`
+
+Problem summary:
+
+- Reported in the source list, but no detailed description was included yet.
+
+Action needed:
+
+- collect exact reproduction steps
+- define expected vs actual behavior
+
+## Current Focus Areas We Have Already Started Planning
+
+### Focus A: Go back to AI Plan Configuration
+
+Status:
+
+- planning in progress
+
+Related document:
+
+- [go-back-plan.md](/Users/fanhaocheng/tlef-create/tlef-create/docs/plans/go-back-plan.md)
+
+### Focus B: Improve system prompt
+
+Status:
+
+- analyzed at a high level
+- not yet split into implementation plan
+
+### Focus C: Support more export formats
+
+Status:
+
+- identified as a future implementation track
+- not yet split into implementation plan
+
+### Focus D: Feedback on multiple-choice question
+
+Status:
+
+- identified as a future implementation track
+- not yet split into implementation plan
+
+## Suggested Next Planning Breakdown
+
+To keep the work manageable, these issues should eventually be split into separate implementation plans.
+
+### Recommended plan files to add later
+
+- `prompt-improvement-plan.md`
+- `multiple-choice-feedback-plan.md`
+- `export-format-plan.md`
+- `materials-preview-plan.md`
+- `learning-objective-generation-plan.md`
+- `h5p-flashcard-export-plan.md`
+
+## Open Gaps In The Bug Inventory
+
+The following items still need more detail before implementation planning:
+
+- `Add more quiz bug`
+- exact reproduction steps for `Generate from materials`
+- exact expected behavior for content preview and content selection
+- preferred first new export format after PDF and H5P
+- desired UX for prompt customization controls
+
+## Notes
+
+This file is intentionally broad and descriptive.
+
+Implementation details for individual issues should go into separate plan documents once one issue is selected for active work.
diff --git a/docs/plans/go-back-plan.md b/docs/plans/go-back-plan.md
new file mode 100644
index 0000000..21a2bd1
--- /dev/null
+++ b/docs/plans/go-back-plan.md
@@ -0,0 +1,279 @@
+# Go Back Plan
+
+## Context
+
+This document captures the current bug context and the implementation plan for restoring a safe "go back to AI Plan Configuration" flow in CREATE.
+
+The goal is to avoid losing the original problem framing while we implement the fix.
+
+## Related Issues Collected So Far
+
+### Original bug report themes
+
+- Users want to go back and access the AI Plan Configuration page after questions have already been generated.
+- Users may want to regenerate with a different pedagogical approach instead of only regenerating the same style of questions.
+- Current state management is fragile when moving backward in the generation flow.
+- The product needs a clear rule for what should happen to existing generated questions after the user goes back and changes the plan.
+
+### Broader issue list from the notes
+
+- Question variants can become repetitive.
+- The system may not break a large learning objective into smaller assessable slices.
+- Prompt quality and prompt controls need improvement.
+- More export formats are desired.
+- Multiple-choice questions need answer-level feedback, not only a general explanation.
+- Regeneration flow should better support changing pedagogical approach.
+
+## What We Know From the Current Code
+
+### Frontend behavior
+
+In `src/components/generation/QuestionGeneration.tsx`, once questions exist, the component returns the generated-results view early.
+
+That means:
+
+- users can no longer access `AIConfigPanel`
+- users can no longer access `PlanEditor`
+- the flow becomes one-way after generation
+
+### Current regeneration behavior
+
+There is already a "Regenerate All Questions" action.
+
+Current behavior:
+
+- ask for confirmation
+- delete all existing questions
+- generate again
+
+This is too destructive for the new "go back" use case because users may only want to revise the plan first and decide later whether to keep or replace existing questions.
+
+## Product Decision We Discussed
+
+### Recommended behavior
+
+Going back should be a navigation action, not a destructive data action.
+
+When a user goes back:
+
+- return them to AI Plan Configuration and Plan Editor
+- keep existing generated questions for now
+- do not delete questions automatically
+
+When the user tries to generate again after modifying the plan:
+
+- ask whether to add new questions or replace existing questions
+
+### Why this is safer
+
+- users do not lose work by accident
+- state transitions become easier to reason about
+- destructive behavior happens only after explicit confirmation
+- the product supports both "supplement existing quiz" and "start over" workflows
+
+## Proposed User Flows
+
+### Flow A: Add new questions
+
+User intent:
+
+- keep existing generated questions
+- add more questions using the updated plan
+
+System behavior:
+
+- preserve existing questions
+- generate new questions based on the current plan
+- append them to the quiz
+
+### Flow B: Replace existing questions
+
+User intent:
+
+- discard the old generated set
+- generate a fresh set from the new plan
+
+System behavior:
+
+- delete existing questions only after confirmation
+- generate a new set using the updated plan
+
+## Scope For This Plan
+
+This document only covers the "go back" feature.
+
+It does not yet implement:
+
+- prompt redesign
+- multiple-choice answer-level feedback
+- new export formats
+
+Those should be tracked in separate plan documents later.
+
+## Implementation Plan
+
+### Phase 1: Stabilize the product behavior
+
+#### Goal
+
+Define explicit UI states instead of relying on `hasQuestions` alone.
+
+#### Tasks
+
+- Introduce a view state for the question generation page.
+- Separate "plan/config view" from "results view".
+- Stop using question existence as the only condition that determines the entire screen.
+- Add a visible "Go back to plan" action in the generated-results view.
+
+#### Expected outcome
+
+Users can return to the plan/configuration screen even after question generation has completed.
+
+### Phase 2: Add a safe regeneration decision point
+
+#### Goal
+
+Prevent accidental deletion or accidental mixing of old and new generation runs.
+
+#### Tasks
+
+- Detect when a quiz already has generated questions and the user tries to generate again.
+- Show a confirmation dialog with two explicit choices:
+ - Add new questions
+ - Replace existing questions
+- Keep cancel behavior available.
+
+#### Expected outcome
+
+The user must intentionally choose how the new generation interacts with existing questions.
+
+### Phase 3: Refactor generation flow logic
+
+#### Goal
+
+Make the question generation path consistent for both add and replace flows.
+
+#### Tasks
+
+- Extract generation start logic into a reusable path.
+- Extract replacement logic so deletion is only triggered in replace mode.
+- Ensure save-plan behavior still runs before generation.
+- Ensure question counts and notifications remain correct in both modes.
+
+#### Expected outcome
+
+The flow is easier to maintain and less likely to break when more generation options are added later.
+
+### Phase 4: Validate state transitions
+
+#### Goal
+
+Make sure the UI and data stay consistent when moving between generated results and plan editing.
+
+#### Tasks
+
+- Verify plan state is preserved when leaving and returning to config view.
+- Verify AI config state is preserved.
+- Verify existing questions are still shown if the user returns to results without regenerating.
+- Verify add mode appends questions correctly.
+- Verify replace mode deletes and regenerates correctly.
+
+#### Expected outcome
+
+Backward navigation no longer causes confusing or destructive state behavior.
+
+## Key Engineering Risks
+
+### Risk 1: UI state and data state remain coupled
+
+If the page still derives its entire mode from `questions.length > 0`, the "go back" fix will be unstable.
+
+### Risk 2: Replace flow becomes too implicit
+
+If deletion still happens deep inside the old regenerate path without a clear mode variable, the new UX will be brittle.
+
+### Risk 3: Add mode creates ambiguous totals
+
+If existing question counts and newly planned counts are not communicated clearly, users may be confused about what "Generate 10 Questions" means when 12 already exist.
+
+## Open Decisions
+
+These should be confirmed before implementation details are finalized.
+
+### Decision 1
+
+Should the default action in the confirmation dialog be "Add new questions" or should there be no default emphasized choice?
+
+Current recommendation:
+
+- no destructive default
+- visually emphasize the safer option
+
+### Decision 2
+
+Should the generated-results screen continue to be the first screen shown when questions already exist, or should we remember the last sub-view the user selected?
+
+Current recommendation:
+
+- start with existing behavior plus a working "Go back to plan" button
+- do not add persistent sub-view memory until the flow is stable
+
+## Suggested Next Execution Order
+
+1. Add explicit view state to `QuestionGeneration.tsx`
+2. Add "Go back to plan" action
+3. Add generate-again decision modal
+4. Split add vs replace generation logic
+5. Test state transitions
+
+## Phase 1 Checklist
+
+This checklist is for the first implementation pass only.
+
+The goal of Phase 1 is to restore safe backward navigation without changing question-deletion behavior yet.
+
+### Checklist
+
+- [ ] Add a dedicated UI view state in `QuestionGeneration.tsx`
+- [ ] Limit that view state to navigation concerns only
+- [ ] Stop using `hasQuestions` as the only determinant of the whole page view
+- [ ] Keep the streaming early-return behavior unchanged
+- [ ] Replace the generated-results early return with a conditional branch based on the new view state
+- [ ] Preserve the existing default behavior where quizzes with generated questions initially open in the results view
+- [ ] Ensure the default view decision runs only during initialization, not on every render
+- [ ] Add a visible `Go back to plan` action in the generated-results view
+- [ ] Ensure `Go back to plan` only changes the UI view and does not trigger any data mutation
+- [ ] Ensure `Go back to plan` does not clear `planItems`
+- [ ] Ensure `Go back to plan` does not clear `aiConfig`
+- [ ] Ensure `Go back to plan` does not clear `planMode`
+- [ ] Ensure `Go back to plan` does not clear `deliveryTarget`
+- [ ] Ensure `Go back to plan` does not clear `targetFormat`
+- [ ] Ensure question generation completion explicitly returns the UI to the results view
+- [ ] Ensure `restoreSettings()` does not unintentionally overwrite a user-triggered view change
+- [ ] Ensure reloading questions does not force the page back to the results view unless explicitly intended
+
+### Phase 1 Validation Scenarios
+
+- [ ] A quiz with no questions opens in the plan/configuration view
+- [ ] A quiz with existing questions opens in the results view
+- [ ] After generation completes, the user sees the results view
+- [ ] The user can click `Go back to plan` and access AI Plan Configuration
+- [ ] The user can still see their existing plan configuration after going back
+- [ ] The user can return to the results view without losing previously generated questions
+- [ ] Refreshing the page still behaves correctly for a quiz that already has generated questions
+
+### Not Included In Phase 1
+
+- [ ] No question deletion confirmation flow yet
+- [ ] No `Add new questions` vs `Replace existing questions` decision modal yet
+- [ ] No refactor of regeneration semantics yet
+- [ ] No prompt or export changes yet
+
+## Out Of Scope For This Document
+
+- prompt improvement strategy
+- answer-level multiple-choice feedback
+- markdown or other new export formats
+- learning objective slicing logic
+
+These should be documented separately after the go-back flow is stable.
diff --git a/docs/plans/intelligent-question-generation-workflow-plan.md b/docs/plans/intelligent-question-generation-workflow-plan.md
new file mode 100644
index 0000000..599bf69
--- /dev/null
+++ b/docs/plans/intelligent-question-generation-workflow-plan.md
@@ -0,0 +1,471 @@
+# Intelligent Question Generation Workflow Plan
+
+## Purpose
+
+This document proposes a workflow-based solution for improving AI question generation in CREATE.
+
+The goal is to solve a core issue that prompt tuning alone does not fully solve:
+
+- when multiple questions are generated from the same learning objective, the system produces questions that are too similar
+- the system does not reliably split a broad learning objective into multiple assessable tasks
+- the system does not actively manage coverage across a generation batch
+
+## Problem This Workflow Is Designed To Solve
+
+### Current failure mode
+
+When the system is asked to generate multiple questions from one broad learning objective, it often behaves like this:
+
+1. receive one broad learning objective
+2. retrieve relevant materials
+3. generate one question
+4. repeat that same pattern several times
+
+This creates two common bad outcomes:
+
+- repeated broad questions
+- repeated narrow questions on the same selected slice
+
+### Example failure
+
+Learning objective:
+
+- `Describe the anthropogenic sources of carbon dioxide (CO2), methane (CH4), and nitrous oxide (N2O).`
+
+Expected outcome for 5 questions:
+
+- one question on `CO2`
+- one on `CH4`
+- one on `N2O`
+- one comparative question
+- one misconception-focused question
+
+Actual outcome seen during testing:
+
+- the system first repeated the whole broad LO
+- after prompt improvement, it then repeated only the `CH4` slice
+
+This shows that prompt improvements helped, but the system still lacks workflow-level coverage management.
+
+## Recommendation
+
+Do not jump directly to a heavy multi-agent architecture.
+
+Instead, build a structured workflow with state.
+
+This should act like an internal planning loop for generation:
+
+1. analyze the learning objective
+2. propose candidate slices
+3. track used slices
+4. generate one question at a time
+5. validate for similarity
+6. retry if needed
+
+## Why Workflow Is Better Than More Prompt Alone
+
+Prompt-only improvements can encourage better behavior, but they still depend on one generation call correctly reasoning about:
+
+- slicing
+- coverage
+- novelty
+- distractor quality
+
+A workflow can enforce these steps explicitly.
+
+This makes the system:
+
+- more reliable
+- easier to debug
+- easier to extend
+- easier to measure
+
+## High-Level Workflow
+
+### Stage 1: Learning Objective Analysis
+
+Input:
+
+- learning objective
+- target question type
+- relevant content
+- requested number of questions
+
+Output:
+
+- LO type: `narrow` or `broad`
+- candidate concept slices
+- candidate misconception targets
+- candidate comparison or application targets
+
+Example output for the greenhouse gas LO:
+
+- `CO2 anthropogenic sources`
+- `CH4 anthropogenic sources`
+- `N2O anthropogenic sources`
+- `compare major source categories across gases`
+- `distinguish anthropogenic vs natural sources`
+
+### Stage 2: Question Task Planning
+
+Input:
+
+- candidate slices
+- requested question count
+- question type
+
+Output:
+
+- a list of question tasks for the batch
+
+Example:
+
+- Task 1: assess `CO2 sources` with multiple-choice
+- Task 2: assess `CH4 sources` with multiple-choice
+- Task 3: assess `N2O sources` with multiple-choice
+- Task 4: compare `CO2 vs CH4`
+- Task 5: misconception check on `fertilizers vs methane`
+
+### Stage 3: Coverage Ledger
+
+Purpose:
+
+- maintain state during batch generation
+
+Track items such as:
+
+- used concept slices
+- used scenarios
+- used misconception categories
+- used comparisons
+- generated question texts
+
+This becomes the memory for the batch.
+
+### Stage 4: Generate One Question At A Time
+
+Instead of asking for 5 questions at once, the system should:
+
+1. choose the next planned task
+2. inject the task into the generation prompt
+3. generate exactly one question
+4. attach generation metadata to it
+
+The prompt for each question should no longer say only:
+
+- `generate a question for this LO`
+
+It should say something closer to:
+
+- `generate a question for this specific planned slice`
+
+### Stage 5: Similarity And Coverage Validation
+
+After one question is generated, run a validator.
+
+Checks can include:
+
+- is the stem too similar to an existing question?
+- is the assessed slice already covered?
+- is the scenario too similar?
+- for MCQ, are distractors distinct enough?
+
+If validation fails:
+
+- reject the question
+- pick another slice or scenario
+- regenerate
+
+### Stage 6: Save Final Batch
+
+Only after the full batch is assembled and validated should the questions be finalized and stored.
+
+## Recommended Architecture
+
+This does not need to be a full multi-agent system.
+
+### Recommended first version
+
+A single workflow service with multiple internal steps:
+
+- `analyzer`
+- `task planner`
+- `coverage ledger`
+- `question generator`
+- `validator`
+
+These can all be implemented in one backend pipeline first.
+
+### Possible future version
+
+If needed later, some stages can become specialized agents:
+
+- `LO-slicing agent`
+- `question-writing agent`
+- `question-critique agent`
+
+But this should come later, not first.
+
+## How This Solves The Current Problem
+
+### Problem
+
+Five questions from one LO become too similar.
+
+### Workflow fix
+
+Because the workflow explicitly plans five different tasks first, each generation call starts from a different intended target.
+
+So the system is no longer doing:
+
+- `same LO -> same call pattern -> repeated question`
+
+It becomes:
+
+- `same LO -> different planned task -> different question target`
+
+### Result
+
+This should dramatically reduce:
+
+- repeated stems
+- repeated concept focus
+- repeated scenario framing
+
+## Interaction With Question Type
+
+The workflow should consider not only the LO, but also the question type.
+
+### Example
+
+Same LO:
+
+- `Describe the anthropogenic sources of CO2, CH4, and N2O.`
+
+For `multiple-choice`, good tasks include:
+
+- single-gas source identification
+- compare two gases
+- identify a misconception
+
+For `matching`, good tasks include:
+
+- match gas to source category
+
+For `summary`, good tasks include:
+
+- synthesize all major sources and contrasts
+
+For `ordering`, that LO may be a weak fit, so fewer tasks should be assigned.
+
+This means the workflow can help with:
+
+- LO slicing
+- question type suitability
+- coverage planning
+
+## Proposed Backend Components
+
+### 1. LO Analysis Function
+
+Responsibility:
+
+- inspect LO
+- extract candidate slices
+- classify LO breadth
+
+Possible location:
+
+- new service under `routes/create/services/`
+
+### 2. Batch Task Planner
+
+Responsibility:
+
+- create a structured task list for `N` questions
+
+Possible location:
+
+- new service or helper used by question generation
+
+### 3. Coverage Ledger
+
+Responsibility:
+
+- store in-memory generation state for one batch
+
+Track:
+
+- used slices
+- used misconceptions
+- used scenarios
+- rejected generations
+
+### 4. Question Validator
+
+Responsibility:
+
+- detect near-duplicates
+- detect repeated concept focus
+- detect poor distractor diversity
+
+### 5. Retry Controller
+
+Responsibility:
+
+- decide when to regenerate
+- choose another slice or another framing
+
+## Suggested Integration Points In Current Code
+
+### Existing relevant files
+
+- [llmService.js](/Users/fanhaocheng/tlef-create/tlef-create/routes/create/services/llmService.js)
+- [questionStreamingService.js](/Users/fanhaocheng/tlef-create/tlef-create/routes/create/services/questionStreamingService.js)
+- [intelligentQuestionGenerationService.js](/Users/fanhaocheng/tlef-create/tlef-create/routes/create/services/intelligentQuestionGenerationService.js)
+
+### Likely integration path
+
+#### Step 1
+
+Introduce a planner stage before the per-question generation call.
+
+#### Step 2
+
+Change generation from:
+
+- `repeat the same generation call N times`
+
+to:
+
+- `generate from a planned task list`
+
+#### Step 3
+
+Add a lightweight validator before saving each question.
+
+## Implementation Phases
+
+## Phase 1: Explicit LO Slice Planning
+
+Goal:
+
+- identify candidate slices for one LO
+
+Deliverables:
+
+- helper that turns broad LO into multiple question tasks
+
+This phase alone would already help with the repeated-CH4 problem.
+
+## Phase 2: Batch Coverage Ledger
+
+Goal:
+
+- maintain per-batch memory of used slices
+
+Deliverables:
+
+- in-memory ledger structure
+- selection rule for next task
+
+## Phase 3: One-Question-At-A-Time Generation
+
+Goal:
+
+- generate from task list, not directly from raw LO each time
+
+Deliverables:
+
+- updated orchestration path
+
+## Phase 4: Validation Loop
+
+Goal:
+
+- reject near-duplicate or low-diversity questions
+
+Deliverables:
+
+- similarity checks
+- retry mechanism
+
+## Phase 5: Observability
+
+Goal:
+
+- understand whether workflow improves output
+
+Deliverables:
+
+- logs for chosen slices
+- logs for rejected duplicates
+- logs for coverage distribution
+
+## Validation Criteria
+
+The workflow should be considered successful if it improves these scenarios:
+
+### Scenario 1: One broad LO, five MCQs
+
+Expected:
+
+- questions cover different slices
+- repeated stems drop significantly
+
+### Scenario 2: One broad LO, mixed question types
+
+Expected:
+
+- different types are matched to suitable sub-tasks
+
+### Scenario 3: Narrow LO
+
+Expected:
+
+- workflow should not over-split
+- it should still vary framing and misconceptions when possible
+
+## Risks
+
+### Risk 1: Too much complexity too early
+
+Mitigation:
+
+- start with one service and explicit stages
+- do not build a multi-agent system first
+
+### Risk 2: Latency increases
+
+Mitigation:
+
+- keep analysis and planning lightweight
+- only retry when validation fails
+
+### Risk 3: Over-splitting harms pedagogical quality
+
+Mitigation:
+
+- allow synthesis tasks when appropriate
+- not every broad LO must be decomposed into only atomic facts
+
+## Recommendation Summary
+
+Recommended solution:
+
+- build a workflow, not a heavy multi-agent system
+- introduce slice planning, coverage memory, and validation
+
+This is the most practical way to solve:
+
+- repeated questions from the same LO
+- weak LO decomposition
+- lack of intelligent task splitting by question type
+
+## Next Suggested Plan
+
+After this document, the next implementation-oriented plan should likely be:
+
+- `lo-slice-planning-plan.md`
+
+That plan can define the first concrete coding step for this workflow.
diff --git a/docs/plans/lo-slice-planning-plan.md b/docs/plans/lo-slice-planning-plan.md
new file mode 100644
index 0000000..8161edb
--- /dev/null
+++ b/docs/plans/lo-slice-planning-plan.md
@@ -0,0 +1,451 @@
+# LO Slice Planning Plan
+
+## Purpose
+
+This document defines the first concrete implementation step for intelligent question generation:
+
+- turn one learning objective into a set of candidate assessment slices
+- use those slices to plan multiple non-redundant question tasks
+
+This plan is a child plan of:
+
+- [intelligent-question-generation-workflow-plan.md](/Users/fanhaocheng/tlef-create/tlef-create/docs/plans/intelligent-question-generation-workflow-plan.md)
+
+## Why Start Here
+
+The current repeated-question problem comes from a missing intermediate step.
+
+Right now the system often does this:
+
+1. receive one learning objective
+2. ask the model to generate a question for it
+3. repeat several times
+
+What is missing:
+
+- a planning step that says what each question should assess before generation begins
+
+Without that step, even a good prompt can still produce:
+
+- repeated broad questions
+- repeated narrow questions on the same slice
+
+## Scope
+
+This plan only covers:
+
+- detecting whether an LO is broad or narrow
+- producing candidate slices for that LO
+- converting slices into structured question tasks
+
+This plan does not yet implement:
+
+- batch coverage ledger
+- similarity validator
+- retry loop
+- multi-agent behavior
+
+## Core Idea
+
+For one LO, the system should build a small internal representation like this:
+
+- `originalLO`
+- `breadth`
+- `candidateSlices`
+- `recommendedTaskSequence`
+
+Example:
+
+Original LO:
+
+- `Describe the anthropogenic sources of carbon dioxide (CO2), methane (CH4), and nitrous oxide (N2O).`
+
+Candidate slices:
+
+- `CO2 anthropogenic sources`
+- `CH4 anthropogenic sources`
+- `N2O anthropogenic sources`
+- `compare source patterns across gases`
+- `distinguish anthropogenic vs natural sources`
+
+Recommended task sequence for 5 MCQs:
+
+- Task 1: `CO2 anthropogenic sources`
+- Task 2: `CH4 anthropogenic sources`
+- Task 3: `N2O anthropogenic sources`
+- Task 4: `compare source patterns across gases`
+- Task 5: `misconception check on source attribution`
+
+## Desired Output Structure
+
+The slice-planning layer should eventually return a structured object like:
+
+```json
+{
+ "originalLO": "Describe the anthropogenic sources of carbon dioxide (CO2), methane (CH4), and nitrous oxide (N2O).",
+ "breadth": "broad",
+ "candidateSlices": [
+ {
+ "id": "co2_sources",
+ "label": "CO2 anthropogenic sources",
+ "kind": "component"
+ },
+ {
+ "id": "ch4_sources",
+ "label": "CH4 anthropogenic sources",
+ "kind": "component"
+ }
+ ],
+ "recommendedTasks": [
+ {
+ "sliceId": "co2_sources",
+ "questionIntent": "source-identification"
+ }
+ ]
+}
+```
+
+This is a conceptual target, not a final locked schema yet.
+
+## Breadth Detection
+
+The first question the planner should answer is:
+
+- is this LO `narrow` or `broad`?
+
+### Signals of a broad LO
+
+- multiple enumerated components
+- conjunction-heavy structure such as `A, B, and C`
+- multiple verbs or multiple outcomes in one statement
+- category + comparison + explanation all bundled together
+
+Examples:
+
+- `Describe the anthropogenic sources of CO2, CH4, and N2O.`
+- `Explain the structure and function of carbohydrates, lipids, proteins, and nucleic acids.`
+
+### Signals of a narrow LO
+
+- one core concept
+- one clear skill
+- one well-bounded process or relationship
+
+Example:
+
+- `Explain why methane emissions are strongly associated with livestock agriculture.`
+
+## Slice Types
+
+Slices should not all be treated the same.
+
+The planner should eventually support several slice categories.
+
+### Component slices
+
+Use when the LO lists multiple concepts or entities.
+
+Examples:
+
+- `CO2 sources`
+- `CH4 sources`
+- `N2O sources`
+
+### Comparison slices
+
+Use when multiple components can be meaningfully contrasted.
+
+Examples:
+
+- `compare CO2 and CH4 source patterns`
+- `compare agricultural vs fossil-fuel-related emissions`
+
+### Misconception slices
+
+Use when the LO is suitable for common confusions.
+
+Examples:
+
+- `anthropogenic vs natural source confusion`
+- `fertilizer-related emissions confused with methane`
+
+### Application slices
+
+Use when the LO can be grounded in case-based reasoning.
+
+Examples:
+
+- `identify which human activity mainly increases methane`
+
+## Question-Type-Aware Task Planning
+
+The planner should not produce the same task sequence for every question type.
+
+### For multiple-choice
+
+Good slice/task patterns:
+
+- source identification
+- comparison
+- misconception discrimination
+
+### For true/false
+
+Good slice/task patterns:
+
+- narrow claim validation
+- misconception correction
+
+### For matching
+
+Good slice/task patterns:
+
+- gas to source category mapping
+
+### For summary
+
+Good slice/task patterns:
+
+- broader synthesis
+- organized multi-slice explanation
+
+This means the task planner needs both:
+
+- LO slices
+- question-type fit
+
+## Recommended First Implementation Strategy
+
+Do not start with a fully autonomous LLM planner.
+
+Start with a hybrid approach:
+
+### Step 1: Lightweight heuristic pre-processing
+
+Use deterministic logic to detect obvious broad structures:
+
+- comma-separated enumerations
+- `and` / `or` joined components
+- parenthetical abbreviations
+
+This should catch many important educational cases cheaply.
+
+### Step 2: Optional LLM-assisted slice extraction
+
+If heuristics detect a broad LO, use the LLM to propose candidate slices in a structured format.
+
+This is safer than asking the LLM to generate questions directly because:
+
+- the task is narrower
+- the output can be validated more easily
+
+### Step 3: Deterministic task selection
+
+Once slices are produced, use deterministic rules to choose a task sequence for the requested number of questions.
+
+This helps avoid:
+
+- random repetition
+- overuse of one slice
+
+## Proposed Backend Components
+
+### 1. `loSlicePlanner` service
+
+Responsibility:
+
+- accept one LO and generation context
+- return slice-planning output
+
+Likely location:
+
+- `routes/create/services/loSlicePlanner.js`
+
+### 2. `questionTaskPlanner` helper
+
+Responsibility:
+
+- convert slices into question tasks for the requested count and question type
+
+Likely location:
+
+- same service at first, split later if needed
+
+## Proposed Inputs
+
+The first version should likely accept:
+
+- `learningObjective`
+- `questionType`
+- `requestedCount`
+- `courseContext`
+- `relevantContent`
+
+Optional future inputs:
+
+- `additionalInstructions`
+- `alreadyUsedSlices`
+- `targetDifficulty`
+
+## Proposed Outputs
+
+First version should likely return:
+
+- `breadth`
+- `candidateSlices`
+- `recommendedTasks`
+
+Optional later:
+
+- `reasoning`
+- `confidence`
+- `rejectedSlices`
+
+## Integration Plan
+
+### Current generation model
+
+Today, the system effectively repeats question generation for the same LO.
+
+### Target near-term integration
+
+Before generating a batch of questions for a single LO:
+
+1. call the slice planner
+2. get candidate slices
+3. build recommended tasks
+4. generate each question from a selected task instead of the raw LO
+
+This can happen before the validator and coverage ledger are introduced.
+
+## Implementation Phases
+
+## Phase 1: Define schema and add planning service
+
+Goal:
+
+- create the planning service and return stable structured output
+
+Deliverables:
+
+- service file
+- output schema
+- simple tests or fixtures
+
+## Phase 2: Heuristic breadth detection
+
+Goal:
+
+- identify obvious broad LOs without needing an LLM call
+
+Deliverables:
+
+- heuristic rules
+- broad/narrow classification
+
+## Phase 3: LLM-assisted slice extraction
+
+Goal:
+
+- use the LLM to propose candidate slices when needed
+
+Deliverables:
+
+- prompt for slice extraction
+- parser and validation logic
+
+## Phase 4: Task sequencing by question type
+
+Goal:
+
+- turn slices into an ordered question task list
+
+Deliverables:
+
+- task-planning rules
+- question-type-specific sequencing
+
+## Phase 5: Connect to generation pipeline
+
+Goal:
+
+- use planned tasks in the actual generation path
+
+Deliverables:
+
+- integration with existing generation service
+
+## Validation Scenarios
+
+### Scenario 1: Broad enumerated LO
+
+Input:
+
+- `Describe the anthropogenic sources of CO2, CH4, and N2O.`
+
+Expected:
+
+- classified as `broad`
+- produces at least three component slices
+- recommended tasks rotate across slices
+
+### Scenario 2: Narrow LO
+
+Input:
+
+- `Explain why methane emissions are associated with livestock agriculture.`
+
+Expected:
+
+- classified as `narrow`
+- no unnecessary over-splitting
+
+### Scenario 3: Broad LO with conceptual families
+
+Input:
+
+- `Explain the structure and function of carbohydrates, lipids, proteins, and nucleic acids.`
+
+Expected:
+
+- component slices for each biomolecule family
+- possible comparison slices
+
+## Risks
+
+### Risk 1: Over-splitting
+
+If every LO is broken too aggressively, question quality can become fragmented.
+
+Mitigation:
+
+- broad/narrow classification first
+- allow narrow LO passthrough behavior
+
+### Risk 2: Slice extraction becomes noisy
+
+LLM-based slice proposals may be redundant or low quality.
+
+Mitigation:
+
+- validate and deduplicate slices after extraction
+- start with constrained prompt format
+
+### Risk 3: Integration grows too big
+
+Trying to connect slice planning, ledger, validator, and retries all at once could slow delivery.
+
+Mitigation:
+
+- ship slice planning first
+- add later workflow stages incrementally
+
+## Recommendation Summary
+
+The first real implementation step toward intelligent generation should be:
+
+- add LO slice planning
+- classify LO breadth
+- create structured question tasks before generation
+
+This is the smallest change that directly addresses the repeated-question problem seen in testing.
diff --git a/docs/plans/prompt-improvement-plan.md b/docs/plans/prompt-improvement-plan.md
new file mode 100644
index 0000000..38cc3fe
--- /dev/null
+++ b/docs/plans/prompt-improvement-plan.md
@@ -0,0 +1,413 @@
+# Prompt Improvement Plan
+
+## Purpose
+
+This document captures the current AI prompt problems in CREATE and proposes a staged plan for improving prompt quality and prompt controls.
+
+The main motivation comes from the issue list already recorded in:
+
+- [bug-summary.md](/Users/fanhaocheng/tlef-create/tlef-create/docs/plans/bug-summary.md)
+
+## Main Problems To Solve
+
+### 1. Repetitive question variants
+
+Observed behavior:
+
+- When a user asks for multiple multiple-choice questions for one learning objective, the system may generate nearly the same question stem multiple times.
+- Sometimes only the wrong answers change while the assessed concept stays identical.
+
+Why this matters:
+
+- poor pedagogical variety
+- inflated quiz length without increasing coverage
+- lower trust in AI-generated questions
+
+### 2. Large learning objectives are not sliced well
+
+Observed behavior:
+
+- If one learning objective contains multiple assessable sub-topics, the system often keeps generating broad questions instead of focusing on one sub-skill at a time.
+
+Example from issue notes:
+
+- A learning objective covering anthropogenic sources of `CO2`, `CH4`, and `N2O` should be sliceable into smaller assessable targets, but the system keeps asking the same broad question.
+
+Why this matters:
+
+- broad questions repeat easily
+- question difficulty becomes inconsistent
+- content coverage becomes shallow
+
+### 3. Weak coverage guarantees
+
+Observed behavior:
+
+- It is not clear how CREATE ensures that generated questions cover the intended materials and learning objectives well.
+- Current behavior appears to rely on the model inferring coverage from the learning objective and retrieved content, but there is no explicit coverage strategy.
+
+Why this matters:
+
+- some content may be over-assessed
+- some content may never be assessed
+- instructor expectations are harder to satisfy
+
+### 4. Prompt is not configurable enough
+
+Observed behavior:
+
+- Instructors want more control over generation, such as:
+ - focus on a page or section
+ - use a specific misconception for distractors
+ - use a specific option style
+ - emphasize a specific content area
+
+Why this matters:
+
+- current prompt only supports a general `additionalInstructions` field
+- important pedagogical constraints are not structured
+
+## Current Prompt Architecture
+
+There are currently two different prompt layers that should be treated separately.
+
+### Layer A: AI plan generation prompt
+
+Purpose:
+
+- decide question distribution across learning objectives and question types
+
+Main code entry points:
+
+- [planController.js](/Users/fanhaocheng/tlef-create/tlef-create/routes/create/controllers/planController.js)
+- [promptTemplateInitializer.js](/Users/fanhaocheng/tlef-create/tlef-create/routes/create/services/promptTemplateInitializer.js)
+
+Current behavior:
+
+- a system template is selected by pedagogical approach
+- learning objectives and materials are injected into a distribution prompt
+- the LLM returns a `planItems` JSON object
+
+Current limitation:
+
+- the plan prompt thinks mainly in terms of counts and types
+- it does not reason deeply about sub-skill slicing or coverage intent
+
+### Layer B: question generation prompt
+
+Purpose:
+
+- generate the actual question content for one question
+
+Main code entry point:
+
+- [llmService.js](/Users/fanhaocheng/tlef-create/tlef-create/routes/create/services/llmService.js)
+
+Current behavior:
+
+- builds one prompt for a single question
+- includes:
+ - learning objective or custom prompt
+ - relevant content chunks
+ - difficulty
+ - previous question texts to avoid duplication
+
+Current limitation:
+
+- duplication prevention is too weak
+- prompt does not require explicit sub-skill selection
+- prompt does not require explicit coverage reasoning
+- prompt does not support structured instructor controls
+
+## Diagnosis Of Why Repetition Happens
+
+### Cause 1: duplication guard is text-only
+
+The current question-generation prompt mostly avoids duplication by listing previous `questionText` strings.
+
+Why this is weak:
+
+- same idea can be paraphrased
+- same LO slice can be reused with different wording
+- same stem can be regenerated with different distractors
+
+### Cause 2: no explicit "choose one slice" step
+
+The model is told to generate a question for a learning objective, but not forced to pick a specific sub-skill when the objective is broad.
+
+Effect:
+
+- the model defaults to broad, summary-style question stems
+- those broad stems are easy to repeat
+
+### Cause 3: no explicit coverage ledger
+
+The prompt does not ask the model to track what has already been assessed across previous questions.
+
+Effect:
+
+- the model has no internal checklist of covered sub-topics
+- repeated focus areas are common
+
+### Cause 4: distractor strategy is under-specified
+
+The prompt says to create plausible distractors, but does not require:
+
+- distinct misconception categories
+- contrast between distractors
+- evidence that distractors target different misunderstandings
+
+Effect:
+
+- distractors can feel shallow or repetitive
+
+## Prompt Improvement Strategy
+
+The plan is to strengthen prompt behavior in layers instead of trying to solve everything with one giant prompt edit.
+
+## Phase 1: Improve question-generation prompt quality
+
+### Goal
+
+Reduce repetition and improve coverage without changing the UI yet.
+
+### Prompt strategy changes
+
+- Require the model to identify the specific sub-skill or sub-topic it is assessing before generating the question.
+- Require the model to avoid previously assessed sub-skills, not only previously used wording.
+- Require the model to generate one question that targets a narrow slice of the learning objective when the objective is broad.
+- For multiple-choice questions, require distractors to represent different misconception types.
+
+### Example conceptual additions
+
+- `Select one specific assessable sub-skill from the learning objective.`
+- `Do not reuse a sub-skill, scenario, or conceptual contrast already covered by previous questions.`
+- `If the learning objective contains multiple components, assess only one component unless the prompt explicitly requires synthesis.`
+- `For multiple-choice questions, each incorrect option must reflect a different misconception or reasoning error.`
+
+### Expected impact
+
+- fewer repeated stems
+- narrower and more varied questions
+- better multiple-choice quality
+
+## Phase 2: Improve plan-generation prompt quality
+
+### Goal
+
+Generate better distributions before question generation starts.
+
+### Prompt strategy changes
+
+- Ask the plan model to identify whether each learning objective is broad or narrow.
+- Ask it to allocate multiple questions across different conceptual slices when an LO is broad.
+- Encourage balanced coverage instead of only proportional counts.
+
+### Example conceptual additions
+
+- `If a learning objective contains multiple assessable components, distribute questions so different components can be covered across the set.`
+- `Prefer conceptual diversity within each learning objective when assigning repeated question counts.`
+
+### Expected impact
+
+- better diversity at the plan level
+- less repetition before the question-generation step even begins
+
+## Phase 3: Add structured instructor controls
+
+### Goal
+
+Support more precise prompt customization than free-form text alone.
+
+### Candidate controls
+
+- focus section or page
+- target misconception
+- distractor style
+- scenario style
+- coverage emphasis
+- question tone or formality
+
+### Likely implementation direction
+
+- extend generation request schema
+- keep `additionalInstructions` as fallback
+- inject structured controls into prompt in a stable format
+
+## Phase 4: Add observability and evaluation
+
+### Goal
+
+Make prompt improvements measurable.
+
+### Candidate checks
+
+- repeated concept rate across a generation batch
+- repeated stem similarity
+- LO coverage diversity
+- distractor distinctness review
+
+### Why this matters
+
+- prompt changes are otherwise hard to evaluate reliably
+- without measurement, regressions are easy to miss
+
+## Suggested Implementation Order
+
+1. Update the question-generation prompt in `llmService.js`
+2. Improve duplication instructions beyond `previousQuestions`
+3. Add explicit sub-skill selection behavior
+4. Improve multiple-choice distractor instructions
+5. Update plan-generation templates in `promptTemplateInitializer.js`
+6. Add support for structured prompt controls in API and frontend later
+
+## Detailed Execution Plan
+
+### Step 1: Rewrite the question prompt instructions
+
+Files:
+
+- [llmService.js](/Users/fanhaocheng/tlef-create/tlef-create/routes/create/services/llmService.js)
+
+Tasks:
+
+- strengthen prompt instructions for novelty
+- require narrow assessment scope for broad LOs
+- add explicit coverage and sub-skill guidance
+- improve multiple-choice distractor instructions
+
+### Step 2: Expand previous-question context
+
+Files:
+
+- [llmService.js](/Users/fanhaocheng/tlef-create/tlef-create/routes/create/services/llmService.js)
+
+Tasks:
+
+- stop relying on raw question text alone
+- include stronger anti-duplication framing
+- optionally include summarized concept-focus metadata later
+
+### Step 3: Improve plan prompt templates
+
+Files:
+
+- [promptTemplateInitializer.js](/Users/fanhaocheng/tlef-create/tlef-create/routes/create/services/promptTemplateInitializer.js)
+- [planController.js](/Users/fanhaocheng/tlef-create/tlef-create/routes/create/controllers/planController.js)
+
+Tasks:
+
+- revise pedagogical approach templates
+- encourage conceptual diversity within the same LO
+- improve distribution guidance for broad learning objectives
+
+### Step 4: Prepare for prompt controls
+
+Files likely affected later:
+
+- frontend AI config components
+- generation request payloads
+- backend prompt builders
+
+Tasks:
+
+- define a stable schema for instructor controls
+- decide which controls belong in plan generation vs question generation
+
+## Proposed Prompt Principles
+
+These principles should guide future prompt edits.
+
+### Principle 1: Assess one thing clearly
+
+A question should usually assess one narrow idea unless synthesis is intentional.
+
+### Principle 2: Track conceptual novelty, not just wording novelty
+
+Different wording is not enough if the assessed concept is still the same.
+
+### Principle 3: Distinguish coverage from quantity
+
+More questions do not automatically mean better assessment coverage.
+
+### Principle 4: Make distractors pedagogically meaningful
+
+Wrong answers should reveal different misunderstandings, not just random incorrectness.
+
+### Principle 5: Prefer stable structure over ad hoc free-text instructions
+
+As more controls are added, they should become structured inputs rather than ever-growing free-text notes.
+
+## Risks
+
+### Risk 1: Prompt becomes too long
+
+If too many constraints are added at once, the model may become less reliable or more verbose.
+
+Mitigation:
+
+- prioritize the highest-impact constraints first
+- keep JSON output instructions simple and clear
+
+### Risk 2: Over-constraining harms creativity
+
+If novelty rules are too rigid, the model may generate awkward or overly narrow questions.
+
+Mitigation:
+
+- test against both narrow and broad LOs
+- prefer "target one slice when appropriate" instead of "always target one tiny fact"
+
+### Risk 3: Plan prompt and question prompt drift apart
+
+If the plan layer and question layer follow different assumptions, the resulting behavior may be inconsistent.
+
+Mitigation:
+
+- document both layers together
+- update both prompt systems intentionally
+
+## Validation Scenarios
+
+The improved prompt system should be tested against cases like these.
+
+### Scenario 1: Broad LO with multiple assessable components
+
+Expected behavior:
+
+- questions spread across different components instead of repeating a broad synthesis question
+
+### Scenario 2: Requesting five multiple-choice questions on one LO
+
+Expected behavior:
+
+- question stems are not near-duplicates
+- distractors vary by misconception type
+
+### Scenario 3: Limited material context
+
+Expected behavior:
+
+- prompt still generates usable questions without hallucinating too much unsupported specificity
+
+### Scenario 4: Instructor guidance present
+
+Expected behavior:
+
+- custom instructions influence output in a stable and visible way
+
+## Not In Scope For This Document
+
+- full UI design for prompt customization controls
+- implementation of answer-level multiple-choice feedback
+- markdown export
+- content preview and section picker UX
+
+Those should be tracked in separate plans.
+
+## Suggested Next Plan Documents
+
+- `multiple-choice-feedback-plan.md`
+- `export-format-plan.md`
+- `materials-preview-plan.md`
+
diff --git a/routes/create/__tests__/integration/export.test.js b/routes/create/__tests__/integration/export.test.js
index 0b0e8a7..2371275 100644
--- a/routes/create/__tests__/integration/export.test.js
+++ b/routes/create/__tests__/integration/export.test.js
@@ -296,6 +296,45 @@ describe('Export API Integration Tests', () => {
});
});
+ describe('POST /api/export/markdown/:quizId', () => {
+ test('should generate Markdown with questions type', async () => {
+ const response = await request(app)
+ .post(`/api/export/markdown/${quizId}`)
+ .send({ type: 'questions' })
+ .expect(201);
+
+ trackExportFile(response);
+
+ expect(response.body.success).toBe(true);
+ expect(response.body.data.filename).toContain('.md');
+ expect(response.body.data.metadata.exportFormat).toBe('markdown');
+ expect(response.body.data.metadata.exportType).toBe('questions');
+ expect(response.body.data.metadata.fileSize).toBeGreaterThan(0);
+ });
+
+ test('should generate Markdown with combined type', async () => {
+ const response = await request(app)
+ .post(`/api/export/markdown/${quizId}`)
+ .send({ type: 'combined' })
+ .expect(201);
+
+ trackExportFile(response);
+
+ expect(response.body.success).toBe(true);
+ expect(response.body.data.metadata.exportType).toBe('combined');
+ });
+
+ test('should return 400 for invalid markdown export type', async () => {
+ const response = await request(app)
+ .post(`/api/export/markdown/${quizId}`)
+ .send({ type: 'invalid' })
+ .expect(400);
+
+ expect(response.body.error).toBeDefined();
+ expect(response.body.error.message).toContain('Invalid export type');
+ });
+ });
+
describe('GET /api/export/:quizId/preview', () => {
test('should return quiz preview successfully', async () => {
const response = await request(app)
@@ -398,6 +437,11 @@ describe('Export API Integration Tests', () => {
expect(jsonFormat).toBeDefined();
expect(jsonFormat.supported).toBe(true);
+ const markdownFormat = response.body.data.formats.find(f => f.id === 'markdown');
+ expect(markdownFormat).toBeDefined();
+ expect(markdownFormat.supported).toBe(true);
+ expect(markdownFormat.fileExtension).toBe('.md');
+
// Check QTI format exists but not supported
const qtiFormat = response.body.data.formats.find(f => f.id === 'qti');
expect(qtiFormat).toBeDefined();
diff --git a/routes/create/__tests__/unit/markdownExportService.test.js b/routes/create/__tests__/unit/markdownExportService.test.js
new file mode 100644
index 0000000..5777022
--- /dev/null
+++ b/routes/create/__tests__/unit/markdownExportService.test.js
@@ -0,0 +1,60 @@
+import { describe, test, expect } from '@jest/globals';
+import {
+ renderMarkdownQuestion,
+ renderMarkdownQuestionContent,
+ renderMarkdownAnswerContent
+} from '../../services/markdownExportService.js';
+
+describe('markdownExportService', () => {
+ test('renders multiple-choice question content with options', () => {
+ const output = renderMarkdownQuestionContent({
+ type: 'multiple-choice',
+ content: {
+ options: [
+ { text: 'Alpha', isCorrect: false },
+ { text: 'Beta', isCorrect: true }
+ ]
+ }
+ });
+
+ expect(output).toContain('### Options');
+ expect(output).toContain('- A. Alpha');
+ expect(output).toContain('- B. Beta');
+ });
+
+ test('renders answer content with explanation', () => {
+ const output = renderMarkdownAnswerContent({
+ type: 'multiple-choice',
+ content: {
+ options: [
+ { text: 'Wrong', isCorrect: false },
+ { text: 'Right', isCorrect: true }
+ ]
+ },
+ explanation: 'Because Right is supported by the material.'
+ });
+
+ expect(output).toContain('- Correct Answer: B\\. Right');
+ expect(output).toContain('- Explanation:');
+ expect(output).toContain('Because Right is supported by the material.');
+ });
+
+ test('renders combined markdown question with metadata and answer section', () => {
+ const output = renderMarkdownQuestion({
+ type: 'true-false',
+ questionText: 'The Earth orbits the Sun.',
+ difficulty: 'easy',
+ correctAnswer: 'true',
+ explanation: 'This is a basic astronomy fact.',
+ learningObjective: { text: 'Identify basic astronomy facts' }
+ }, 0, 'combined');
+
+ expect(output).toContain('## Question 1');
+ expect(output).toContain('The Earth orbits the Sun.');
+ expect(output).toContain('- Type: True False');
+ expect(output).toContain('- Learning Objective: Identify basic astronomy facts');
+ expect(output).toContain('### Answer');
+ expect(output).toContain('- Correct Answer: True');
+ });
+});
+
diff --git a/routes/create/__tests__/unit/pdfExportService.test.js b/routes/create/__tests__/unit/pdfExportService.test.js
index aec4345..7763509 100644
--- a/routes/create/__tests__/unit/pdfExportService.test.js
+++ b/routes/create/__tests__/unit/pdfExportService.test.js
@@ -160,6 +160,48 @@ describe('pdfExportService', () => {
expect(texts.some(t => t.includes('B. Right'))).toBe(true);
});
+ test('multiple-choice: includes option feedback and explanation', () => {
+ const question = {
+ type: 'multiple-choice',
+ explanation: 'Because this aligns with the learning objective.',
+ content: {
+ options: [
+ { text: 'Wrong', isCorrect: false, chosenFeedback: 'This reflects a common misconception.' },
+ { text: 'Right', isCorrect: true, notChosenFeedback: 'You should have selected this option.' }
+ ]
+ }
+ };
+
+ addAnswerContent(doc, question);
+ const texts = getTextCalls(doc);
+
+ expect(texts).toContain('Option Feedback:');
+ expect(texts.some(t => t.includes('A. If selected: This reflects a common misconception.'))).toBe(true);
+ expect(texts.some(t => t.includes('B. If not selected: You should have selected this option.'))).toBe(true);
+ expect(texts).toContain('Explanation:');
+ expect(texts).toContain('Because this aligns with the learning objective.');
+ });
+
+ test('multiple-choice: supports multiple correct answers', () => {
+ const question = {
+ type: 'multiple-choice',
+ content: {
+ selectionMode: 'multiple',
+ options: [
+ { text: 'Alpha', isCorrect: true },
+ { text: 'Beta', isCorrect: false },
+ { text: 'Gamma', isCorrect: true }
+ ]
+ }
+ };
+
+ addAnswerContent(doc, question);
+ const texts = getTextCalls(doc);
+
+ expect(texts.some(t => t.includes('A. Alpha'))).toBe(true);
+ expect(texts.some(t => t.includes('C. Gamma'))).toBe(true);
+ });
+
test('multiple-choice: falls back to correctAnswer when no isCorrect', () => {
const question = {
type: 'multiple-choice',
diff --git a/routes/create/controllers/exportController.js b/routes/create/controllers/exportController.js
index e0b40d9..0ded9f3 100644
--- a/routes/create/controllers/exportController.js
+++ b/routes/create/controllers/exportController.js
@@ -11,6 +11,7 @@ import crypto from 'crypto';
import { createH5PPackage } from '../services/h5pExportService.js';
import { createPDFExport } from '../services/pdfExportService.js';
+import { createMarkdownExport } from '../services/markdownExportService.js';
import { getQuestionTypeBreakdown, getDifficultyDistribution, estimateExportSize } from '../services/exportUtils.js';
const router = express.Router();
@@ -124,6 +125,58 @@ router.post('/pdf/:quizId', authenticateToken, validateQuizId, asyncHandler(asyn
}
}));
+/**
+ * POST /api/export/markdown/:quizId
+ * Generate Markdown export
+ */
+router.post('/markdown/:quizId', authenticateToken, validateQuizId, asyncHandler(async (req, res) => {
+ const { type } = req.body;
+
+ if (!['questions', 'answers', 'combined'].includes(type)) {
+ return errorResponse(res, 'Invalid export type. Must be "questions", "answers", or "combined"', 'INVALID_TYPE', HTTP_STATUS.BAD_REQUEST);
+ }
+
+ const quiz = await loadQuizForExport(req.params.quizId, req.user.id);
+ if (!quiz) return notFoundResponse(res, 'Quiz');
+ if (!quiz.questions || quiz.questions.length === 0) {
+ return errorResponse(res, 'Quiz must have questions before exporting', 'NO_QUESTIONS', HTTP_STATUS.BAD_REQUEST);
+ }
+
+ try {
+ const exportId = crypto.randomBytes(16).toString('hex');
+ const typeLabel = type.charAt(0).toUpperCase() + type.slice(1);
+ const filename = `${quiz.name.replace(/[^a-zA-Z0-9]/g, '_')}_${typeLabel}_${exportId}.md`;
+ const uploadsDir = path.join('./routes/create/uploads/');
+ const filePath = path.join(uploadsDir, filename);
+
+ await fs.mkdir(uploadsDir, { recursive: true });
+ await createMarkdownExport(quiz, filePath, type);
+
+ if (quiz.addExport) {
+ await quiz.addExport(filePath);
+ } else {
+ console.log(`Markdown export created for quiz ${quiz._id}: ${filePath}`);
+ }
+
+ const stats = await fs.stat(filePath);
+
+ return successResponse(res, {
+ exportId,
+ filename,
+ downloadUrl: `/api/export/${exportId}/download`,
+ metadata: {
+ questionCount: quiz.questions.length,
+ exportFormat: 'markdown',
+ exportType: type,
+ fileSize: stats.size
+ }
+ }, 'Markdown export generated successfully', HTTP_STATUS.CREATED);
+ } catch (error) {
+ console.error('Markdown export error:', error);
+ return errorResponse(res, 'Failed to generate Markdown export', 'EXPORT_ERROR', HTTP_STATUS.SERVICE_UNAVAILABLE);
+ }
+}));
+
/**
* GET /api/export/:exportId/download
* Download exported file
@@ -150,7 +203,11 @@ router.get('/:exportId/download', authenticateToken, asyncHandler(async (req, re
const filename = path.basename(exportRecord.filePath);
const fileExtension = path.extname(filename).toLowerCase();
- const contentType = fileExtension === '.pdf' ? 'application/pdf' : 'application/octet-stream';
+ const contentType = fileExtension === '.pdf'
+ ? 'application/pdf'
+ : fileExtension === '.md'
+ ? 'text/markdown; charset=utf-8'
+ : 'application/octet-stream';
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
res.setHeader('Content-Type', contentType);
@@ -255,6 +312,14 @@ router.get('/:quizId/formats', authenticateToken, validateQuizId, asyncHandler(a
supported: true,
fileExtension: '.json',
features: ['Developer friendly', 'Easy parsing', 'Custom integration']
+ },
+ {
+ name: 'Markdown',
+ id: 'markdown',
+ description: 'Readable text export for editing, sharing, or version control',
+ supported: true,
+ fileExtension: '.md',
+ features: ['Human readable', 'Easy to edit', 'Works in docs and Git workflows']
}
];
diff --git a/routes/create/controllers/streamingController.js b/routes/create/controllers/streamingController.js
index 7476270..7621ab9 100644
--- a/routes/create/controllers/streamingController.js
+++ b/routes/create/controllers/streamingController.js
@@ -7,6 +7,7 @@ import express from 'express';
import { authenticateToken } from '../middleware/auth.js';
import { asyncHandler } from '../utils/asyncHandler.js';
import sseService from '../services/sseService.js';
+import { planLOSlices } from '../services/loSlicePlanner.js';
import { v4 as uuidv4 } from 'uuid';
const router = express.Router();
@@ -41,6 +42,74 @@ function enrichQuestionConfigsWithObjectives(questionConfigs, quiz) {
});
}
+function getLearningObjectiveText(config) {
+ if (typeof config.learningObjective === 'string') {
+ return config.learningObjective;
+ }
+
+ return config.learningObjective?.text || '';
+}
+
+function buildPlanningGroupKey(config) {
+ const loText = getLearningObjectiveText(config);
+ return [
+ config.learningObjective?._id?.toString?.() || config.learningObjectiveId || loText,
+ config.questionType,
+ config.selectionMode || 'single',
+ config.customPrompt || ''
+ ].join('::');
+}
+
+/**
+ * Assign a distinct LO slice to each repeated config before parallel generation.
+ * This prevents parallel LLM calls from independently choosing the same narrow focus.
+ */
+function attachPlannedTasksToQuestionConfigs(questionConfigs) {
+ const groups = new Map();
+
+ questionConfigs.forEach((config, index) => {
+ const loText = getLearningObjectiveText(config);
+ if (!loText || !config.questionType) {
+ return;
+ }
+
+ const groupKey = buildPlanningGroupKey(config);
+ if (!groups.has(groupKey)) {
+ groups.set(groupKey, []);
+ }
+ groups.get(groupKey).push({ config, index, loText });
+ });
+
+ const plannedConfigs = questionConfigs.map(config => ({ ...config }));
+
+ for (const group of groups.values()) {
+ if (group.length < 2) {
+ continue;
+ }
+
+ const slicePlan = planLOSlices({
+ learningObjective: group[0].loText,
+ questionType: group[0].config.questionType,
+ requestedCount: group.length
+ });
+
+ console.log(
+ `🧩 Streaming slice plan for ${group[0].config.questionType}:`,
+ slicePlan.recommendedTasks.map(task => task.sliceLabel).join(' | ')
+ );
+
+ group.forEach(({ index }, taskIndex) => {
+ plannedConfigs[index] = {
+ ...plannedConfigs[index],
+ plannedTask: slicePlan.recommendedTasks[taskIndex] || null,
+ slicePlanBreadth: slicePlan.breadth
+ };
+ });
+ }
+
+ return plannedConfigs;
+}
+
// ============================================================
// Production endpoints
// ============================================================
@@ -90,7 +159,9 @@ router.post('/generate-questions', authenticateToken, asyncHandler(async (req, r
return res.status(404).json({ error: 'Quiz not found' });
}
- const enrichedConfigs = enrichQuestionConfigsWithObjectives(questionConfigs, quiz);
+ const enrichedConfigs = attachPlannedTasksToQuestionConfigs(
+ enrichQuestionConfigsWithObjectives(questionConfigs, quiz)
+ );
// Get processed material IDs for RAG retrieval
const processedMaterialIds = (quiz.materials || [])
diff --git a/routes/create/h5p-libs/H5P.SingleChoiceSet-1.11/scripts/solution-view.js b/routes/create/h5p-libs/H5P.SingleChoiceSet-1.11/scripts/solution-view.js
index 744105f..8c1bc8d 100644
--- a/routes/create/h5p-libs/H5P.SingleChoiceSet-1.11/scripts/solution-view.js
+++ b/routes/create/h5p-libs/H5P.SingleChoiceSet-1.11/scripts/solution-view.js
@@ -42,6 +42,9 @@ H5P.SingleChoiceSet.SolutionView = (function ($, EventDispatcher) {
self.populate();
}
+ SolutionView.prototype = Object.create(EventDispatcher.prototype);
+ SolutionView.prototype.constructor = SolutionView;
+
/**
* Will append the solution view to a container DOM
* @param {jQuery} $container The DOM object to append to
diff --git a/routes/create/models/Question.js b/routes/create/models/Question.js
index 3ae48c8..149d5f5 100644
--- a/routes/create/models/Question.js
+++ b/routes/create/models/Question.js
@@ -53,8 +53,16 @@ const questionSchema = new mongoose.Schema({
options: [{
text: { type: String, required: true },
isCorrect: { type: Boolean, default: false },
- order: { type: Number }
+ order: { type: Number },
+ tip: { type: String, default: '' },
+ chosenFeedback: { type: String, default: '' },
+ notChosenFeedback: { type: String, default: '' }
}],
+ selectionMode: {
+ type: String,
+ enum: ['single', 'multiple'],
+ default: 'single'
+ },
// For Flashcard
front: { type: String }, // Question side
@@ -319,4 +327,4 @@ questionSchema.set('toJSON', {
}
});
-export default mongoose.model('Question', questionSchema);
\ No newline at end of file
+export default mongoose.model('Question', questionSchema);
diff --git a/routes/create/services/h5pExportService.js b/routes/create/services/h5pExportService.js
index 861283c..0e8d8c7 100644
--- a/routes/create/services/h5pExportService.js
+++ b/routes/create/services/h5pExportService.js
@@ -11,6 +11,27 @@ import { createWriteStream } from 'fs';
import LIBRARY_REGISTRY, { getNeededLibraries } from '../config/h5pLibraryRegistry.js';
import { escapeHtml, generateAvailableOptionsText } from './exportUtils.js';
+function getMultipleChoiceSelectionMode(question) {
+ if (question.content?.selectionMode === 'multiple') {
+ return 'multiple';
+ }
+
+ const correctCount = (question.content?.options || []).filter(option => option.isCorrect).length;
+ return correctCount > 1 ? 'multiple' : 'single';
+}
+
+function mapMultipleChoiceAnswers(options = []) {
+ return options.map(option => ({
+ correct: option.isCorrect || false,
+ text: `