Reverts the following commits that broke composite question handling:
- 85661a5 auto-detect composite sub-questions from text patterns
- 064b3cf use slice to preserve full content when wrapping selections
- 2562de7 remove isolating to allow nested question blocks
Restores the working state from ccf6c03 where:
- wrapInQuestion fails gracefully in isolating nodes
- selected text is preserved when creating sub-questions
- composite question structure is intact
Root cause: When users paste reading comprehension content into a
composite question block without manually marking each sub-question
as a questionBlock, the sub-questions were treated as plain text and
merged into the question stem. This caused:
1. Sub-questions not showing in the preview's sub-question area
2. Text from different paragraphs being concatenated without newlines
3. "A." appearing because parseOptions misidentified list items
Fix:
1. extractText now inserts newlines between paragraphs/listItems,
preserving text structure so pattern detection can work
2. Added detectSubQuestionsFromText: for composite questions without
explicit questionBlock children, auto-detect sub-questions from
text patterns:
- Numbered lines: "1.xxx", "2.xxx", "(1)xxx", "①xxx"
- Lines with score markers: "xxx(3分)"
- If numbered sub-questions are found, check preceding lines for
un-numbered sub-questions (e.g., the first sub-question that
lacks a number but has a score marker)
3. extractMaterialText removes detected sub-question text from the
question stem, keeping only the reading material/passage
This allows users to paste reading comprehension content directly
into a composite question block and have sub-questions automatically
detected, without needing to manually mark each one.
Root cause: wrapIn (Tiptap's built-in command) can fail or lose content
when the selection spans multiple paragraphs or partial paragraphs. When
users selected a long reading passage and clicked "复合" (composite),
the content was destroyed — only fragments remained.
Fix: replace wrapIn with a manual slice-based approach:
1. Use doc.slice(from, to) to get the complete node structure of the
selection (preserves paragraphs, lists, images, etc.)
2. deleteRange to remove the original selection
3. insertContentAt to insert a new questionBlock/groupBlock/sectionBlock
containing the sliced content
This is more reliable than wrapIn because it doesn't depend on
ProseMirror's wrapping logic, which has edge cases with multi-paragraph
selections. The slice API captures the exact node structure, so no
content is lost.
Applied to all three wrapping operations:
- insertQuestion (questionBlock)
- insertGroup (groupBlock)
- insertSection (sectionBlock)
Root cause: questionBlock/groupBlock/sectionBlock had `isolating: true`,
which prevents ProseMirror operations (wrapIn, insertContentAt) from
inserting nodes inside these blocks. When users selected text inside a
composite question and clicked "填空/简答" to create a sub-question,
the new questionBlock was inserted OUTSIDE the composite block instead
of inside it, so buildQuestion couldn't detect it as a subQuestion.
Fix: remove `isolating: true` from all three block extensions. The
`defining: true` property is sufficient to maintain node boundaries
during normal editing operations.
This allows wrapInQuestion to work correctly inside composite questions,
creating properly nested sub-question blocks that are detected by
buildQuestion's recursive parsing.
When wrapIn fails inside isolating nodes (e.g. composite question block),
the previous fallback used insertContent which replaced the entire
selection with an empty questionBlock, causing other sub-questions to
disappear and content to be cleared.
New approach: when wrapIn fails, extract the selected text, delete the
selection, then insert a new node (questionBlock/groupBlock/sectionBlock)
containing the selected text as paragraphs. This preserves the content
and converts it into the desired structure.
Applied to all three wrap operations:
- insertQuestion (questionBlock)
- insertGroup (groupBlock)
- insertSection (sectionBlock)
- Fix parseOptions: deduplicate option ids within a single question to
avoid multiple "A" keys when a question contains multiple lists
- Fix ExamPreview: use composite keys (questionId-optId, questionId-sub-id)
to ensure global uniqueness across questions
- Fix selection-toolbar: when wrapInQuestion fails inside isolating nodes
(e.g. composite question block), fall back to insertQuestion instead of
silently doing nothing
- Fix buildQuestion in editor-to-structure.ts: recursively detect nested
questionBlock nodes as subQuestions (composite questions now properly
extract child questions instead of treating them as plain text)
- Fix buildQuestionBlock in actions.ts: AI auto-mark now generates nested
questionBlock nodes for sub-questions instead of plain paragraphs, so
they are properly structured and detectable by the parser
- Rewrite ExamPreview component with proper layout:
- Question header: number + type label badge + score
- Indented question text and options
- Composite sub-questions shown in nested block with left border
- Image thumbnails
- Empty state message
- Title centered at top
- Group titles with bottom border
- Fix groupBlock/questionBlock insertContent error: empty content violates
schema "block+", now provide default empty paragraph
- Fix buildTiptapDocFromAiResponse: ensure groupBlock/questionBlock always
have content (fallback to empty paragraph)
- Add wrapInGroup/wrapInQuestion commands to wrap selected text into
question/group blocks (vs insert which creates empty blocks)
- Update SelectionToolbar: use wrap when text selected, insert when not
- Redesign exam-rich-form layout:
- Merge basic info into single-row toolbar (title/subject/grade/difficulty/score/duration)
- Remove separate "source text" textarea (user pastes directly in editor)
- AI auto-mark now reads from editor content via getText()
- Editor + preview takes full height (calc(100vh-180px))
- Enhance AI prompt for Chinese exam papers:
- Support reading material (阅读理解选段)
- Support dotted chars (加点字注音)
- Support sub-questions (阅读理解小题)
- Better type detection (single/multiple choice, judgment, fill, essay, composite)
- Add splitByDottedTexts helper to mark dotted chars in Tiptap doc
- Add i18n keys for titlePlaceholder, editorArea description
- Replace cramped 3-column grid with vertical layout
- Add 3 large selectable cards at top: Manual / AI / Rich Text Editor
- Rich Text Editor mode redirects to /teacher/exams/new
- Basic info form is now always visible (not hidden in AI mode)
- Exam mode config always visible at bottom
- Add "rich" to mode enum with validation bypass
- Replace all hardcoded English/Chinese strings with i18n keys
- Add 20+ new i18n keys to zh-CN and en (mode labels, descriptions, actions)
- Clean up mixed-language UI text
- Add Tiptap-based rich text editor with custom extensions (dotted-mark,
blank-node, image-node, group-block, question-block) for exam creation
- Add AI auto-marking action to convert pasted exam text to structured editor doc
- Add resizable split-panel layout for editor + live preview
- Add student scan upload (photo of paper answers) with drag-drop and reorder
- Add scan image viewer with zoom/rotate/fullscreen for teachers
- Add scan grading view with side-by-side questions and scan images
- Add /teacher/exams/new and /teacher/homework/submissions/[id]/scan-grading routes
- Fix getScansAction to support both teacher (HOMEWORK_GRADE) and student
(HOMEWORK_SUBMIT) permission scopes
- Add i18n keys for rich editor, scan upload, and scan grading (zh-CN/en)
- Sync architecture diagrams (004/005) with new modules, routes, and deps
- Add admin/lesson-plans, parent/lesson-plans, student/lesson-plans routes
- Add student/practice and teacher/practice routes for adaptive practice
- Add management/grade/dashboard and management/grade/practice routes
- Add teacher/lesson-plans error and loading boundaries
- Update existing admin, parent, student, teacher pages with new features
- Update globals.css and proxy middleware
- Add ai-chart-renderer for rendering charts in AI responses
- Add use-floating-ball hook for draggable AI assistant widget
- Update ai-assistant-widget, ai-chat-panel, ai-markdown-renderer, ai-provider-selector
- Update use-ai-chat-stream hook and prompt-templates service
- Add adaptive practice module with data-access, schema, types, and components
- Provides personalized practice based on student performance and error patterns
- Update architecture impact map (004) and data (005) with new modules
- Add lesson-preparation usage fixes design spec
- Add teacher web test post-audit report
- Add TOTP implementation and two-factor data-access for 2FA enrollment
- Add security center card with password policy and session management
- Add avatar upload action and component
- Add system settings actions and data-access (actions-system-settings, data-access-system-settings)
- Add notification preferences and service actions
- Add security-utils and student-overview-data with tests
- Update existing settings views, data-access, and types for new features
- Add server actions for onboarding flow orchestration
- Add data-access layer for onboarding state persistence
- Add type definitions for onboarding module