diff --git a/src/pages/exam/setup/ExamTemplateSetupPage.tsx b/src/pages/exam/setup/ExamTemplateSetupPage.tsx index 7186213..2023027 100644 --- a/src/pages/exam/setup/ExamTemplateSetupPage.tsx +++ b/src/pages/exam/setup/ExamTemplateSetupPage.tsx @@ -154,11 +154,23 @@ function bringDomainShapesToFront(editor: Editor) { if (ids.length) try { editor.bringToFront(ids as any) } catch { /* */ } } +// Draw order = z-order (tldraw stacks by creation). Backdrops (boundaries, preamble bands, container frames) +// go first/behind so the figures, responses and parts on top stay hoverable and legible. +function zPriority(m: ExamCanvasShapeModel): number { + if (m.kind === 'boundary') return 0 + if (m.kind === 'context' && m.contextType === 'preamble') return 1 + if (m.kind === 'part' && m.isContainer) return 2 + if (m.kind === 'part') return 3 + if (m.kind === 'context') return 5 // figures/tables on top so their description tooltip is reachable + return 4 // responses + other regions +} + function loadShapes(editor: Editor, models: ExamCanvasShapeModel[]) { const existing = editor.getCurrentPageShapes().filter((s) => shapeTypeToKind(s.type)).map((s) => s.id) if (existing.length) editor.deleteShapes(existing) if (!models.length) return - editor.createShapes(models.map((m) => ({ + const ordered = [...models].sort((a, b) => zPriority(a) - zPriority(b)) + editor.createShapes(ordered.map((m) => ({ id: createShapeId(m.id), type: SHAPE_TYPES[m.kind], x: m.x, diff --git a/src/pages/exam/setup/examCanvasShapes.tsx b/src/pages/exam/setup/examCanvasShapes.tsx index f40bcb9..b2e8a81 100644 --- a/src/pages/exam/setup/examCanvasShapes.tsx +++ b/src/pages/exam/setup/examCanvasShapes.tsx @@ -184,6 +184,11 @@ function renderResponseSubstructure(shape: ExamCanvasTLShape): React.ReactNode { const filled = Number(b.fill ?? 0) > 0.35 els.push(
) }) + // "tick N" instruction for multiple-choice (select_n = how many boxes to tick) + const selectN = Number(m.select_n ?? 0) + if (selectN > 0 && boxes.length) { + els.push(tick {selectN}) + } const fal = m.final_answer_line if (fal && (fal.unit || fal.quantity)) { els.push({[fal.quantity, fal.unit && `(${fal.unit})`].filter(Boolean).join(' ')}) @@ -191,6 +196,29 @@ function renderResponseSubstructure(shape: ExamCanvasTLShape): React.ReactNode { return els.length ? <>{els}> : null } +// A container question is a BACKDROP, not a big box over the content: the main question (depth 0) relies on +// its start/end boundary rules for extent and shows only a small corner tab (label + total marks); an +// intermediate part-group (depth ≥ 1) also gets a faint dashed frame to convey nesting. Non-interactive so it +// never blocks a hover on the figures/responses on top. +function renderContainerTab(shape: ExamCanvasTLShape) { + const depth = shape.props.depth ?? 0 + const stroke = containerStroke(depth) + const isAiSuggestion = shape.props.source === 'ai' && shape.props.confirmed === false + const marks = shape.props.maxMarks + const title = provenanceTitle(shape, depth === 0 ? 'Question (container)' : 'Part group (container)') + return ( +