WS-2 R2: render answer sub-structure on response boxes (components/MC boxes/units)
app-ci-deploy / test-build-deploy (push) Has been cancelled

From KC's review — the canvas now draws the detail the spike detected:
- numbered sub-answer bands ('suggest two' → dashed dividers + 1/2 labels),
- per-option MC selection boxes (drawn from meta.boxes rel coords; filled tint
  when detected marked),
- the final-answer line's expected quantity + unit pill.
Region meta (components/boxes/final_answer_line + figure name/desc) is carried
through load→shape props (metaJson)→render, and round-trips on save via
exam_response_areas.meta. tsc clean; 7/7 model tests pass.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GruxHXxfdp4kZCgAMVFgvV
This commit is contained in:
2026-07-04 12:57:01 +00:00
co-authored by Claude Opus 4.8
parent e0e36225fe
commit 81e1b5ed25
3 changed files with 49 additions and 8 deletions
+10 -5
View File
@@ -36,6 +36,9 @@ export interface ExamCanvasShapeModel {
description?: string
/** Label of the owning question, for a context region's visible tether pill. */
linkLabel?: string
/** Rich recognition detail (exam_response_areas.meta): answer components, MC option boxes, final-answer
* line unit/quantity, figure name/description. Carried through so the canvas can draw sub-structure. */
meta?: Record<string, unknown> | null
source?: ExamTemplateSource
confirmed?: boolean
confidence?: number | null
@@ -216,11 +219,13 @@ export function serializeCanvasShapes(template: ExamTemplateDetail, shapes: Exam
|| questions[0]?.id
if (!questionId) continue
const kind = region.kind as ExamCanvasRegionKind
// Carry the figure/table name + caption through the save so a named context region survives a round-trip
// (the replace endpoint persists exam_response_areas.meta).
// Carry rich recognition detail through the save so it survives a round-trip (the replace endpoint
// persists exam_response_areas.meta): answer components / MC boxes / final-answer for responses, and the
// figure name + caption for context regions.
const carried = (region.meta && typeof region.meta === 'object') ? region.meta as Record<string, unknown> : undefined
const meta = kind === 'context' && (region.label || region.description)
? { name: region.label || undefined, description: region.description || undefined }
: undefined
? { ...(carried ?? {}), name: region.label || carried?.name, description: region.description || carried?.description }
: carried
response_areas.push({ id: isUuid(region.id) ? region.id : newDomainId(), question_id: questionId, page: pageForShape(region, pages), bounds: bounds(region), kind, response_form: kind === 'response' ? (region.responseForm ?? 'lines') : null, context_type: kind === 'context' ? (region.contextType ?? 'generic') : null, meta, source: persistedSource(region), confirmed: persistedConfirmed(region), confidence: persistedConfidence(region), mark_subtype: null, derivation: persistedDerivation(region) })
}
@@ -258,7 +263,7 @@ export function shapesFromTemplate(detail: ExamTemplateDetail, pages?: CanvasPag
const label = r.kind === 'context'
? (name || (r.context_type && r.context_type !== 'generic' ? r.context_type : 'Context'))
: (q ? `${q.label}` : r.kind)
shapes.push({ id: r.id, kind: r.kind, x: Number(bb.x ?? 100), y: Number(bb.y ?? pageTop(r.page, pages) + 360), w: Number(bb.w ?? 360), h: Number(bb.h ?? 120), label, description, linkLabel: q?.label, responseForm: (r.response_form as ExamCanvasShapeModel['responseForm']) ?? undefined, contextType: r.context_type ?? undefined, questionId: r.question_id, source: r.source, confirmed: r.confirmed, confidence: r.confidence, derivation: r.derivation })
shapes.push({ id: r.id, kind: r.kind, x: Number(bb.x ?? 100), y: Number(bb.y ?? pageTop(r.page, pages) + 360), w: Number(bb.w ?? 360), h: Number(bb.h ?? 120), label, description, linkLabel: q?.label, meta: (r as { meta?: Record<string, unknown> }).meta ?? undefined, responseForm: (r.response_form as ExamCanvasShapeModel['responseForm']) ?? undefined, contextType: r.context_type ?? undefined, questionId: r.question_id, source: r.source, confirmed: r.confirmed, confidence: r.confidence, derivation: r.derivation })
}
return addCheapReviewFlags(shapes, pages)
}